Skip to main content

max / makenotwork

19.5 KB · 373 lines History Blame Raw
1 <div class="tab-docs"><a href="/docs/getting-started">Docs: Getting Started &rarr;</a></div>
2
3 {% if can_create_projects %}
4 <div class="creator-tab-section creator-tab-section--centered">
5 <h2 class="creator-h2">You have creator access</h2>
6 <p class="muted mb-pane">You can create projects and publish content.</p>
7 <a href="/dashboard?tab=projects" class="btn-primary">Go to Projects</a>
8 </div>
9
10 <div class="creator-tab-section creator-tab-section--no-top">
11 <h2 class="creator-h2 creator-h2--sm mb-section">Creator Plan</h2>
12 {% if let Some(label) = creator_tier_label %}
13 <div class="creator-plan-box mb-section">
14 <div class="creator-plan-head">
15 <span class="creator-plan-name">{{ label }}</span>
16 {% if let Some(status) = creator_sub_status %}
17 {% if status == "active" %}
18 <span class="badge" data-tone="success">Active</span>
19 {% else if status == "past_due" %}
20 <span class="badge" data-tone="warning">Past Due</span>
21 {% else %}
22 <span class="badge">{{ status }}</span>
23 {% endif %}
24 {% endif %}
25 {% if is_founder_locked %}
26 <span class="badge badge--founder-locked" title="Founder pricing is locked in for the life of this account.">Founder · locked for life</span>
27 {% else if is_founder && founder_window_open %}
28 <span class="badge badge--founder-pending" title="Founder pricing applies while the window is open. Stay subscribed until the window closes to lock it in for life.">Founder · pending lock-in</span>
29 {% endif %}
30 </div>
31 {% if let Some(period_end) = creator_period_end %}
32 <p class="muted text-sm">Current period ends {{ period_end }}</p>
33 {% endif %}
34 <p class="muted text-sm mt-peer">{{ storage_total }} of {{ storage_max }} storage used ({{ storage_pct }}%). <a href="/docs/tiers" class="muted">Plan limits &rarr;</a></p>
35 </div>
36 {% else %}
37 <p class="muted text-sm mb-section">
38 No active plan. During alpha, all creator features are available without a membership.
39 </p>
40 {% if founder_window_open || is_founder_locked %}
41 <div class="founder-callout mb-section">
42 {% if is_founder_locked %}
43 <p class="m-0 text-sm"><strong>Your founder pricing is locked in.</strong> The rates below show the founder rate for each tier: you can subscribe (or upgrade later) at this rate for the life of your account.</p>
44 {% else %}
45 <p class="m-0 text-sm"><strong>Founder pricing window is open.</strong> Subscribe now to lock in half-price creator memberships for life. Window closes at 1,000 creators or exit-beta, whichever first. <a href="/docs/pricing">How it works &rarr;</a></p>
46 {% endif %}
47 </div>
48 {% endif %}
49 {% if creator_tiers_configured %}
50 {% let show_founder_rate = founder_window_open || is_founder_locked %}
51 <div class="creator-tier-grid mb-section">
52 {% for card in tier_cards %}
53 <div class="creator-tier-card">
54 <div class="creator-tier-name"><a href="/docs/tiers" class="creator-tier-link">{{ card.label }}</a></div>
55 {% if show_founder_rate %}
56 <div class="meta mb-peer"><strong>${{ card.founder_monthly }}/mo</strong> <span class="strike-dim">${{ card.standard_monthly }}</span></div>
57 <div class="meta creator-tier-sub mb-peer">or ${{ card.founder_annual }}/yr (10% off)</div>
58 {% else %}
59 <div class="meta mb-peer">${{ card.standard_monthly }}/mo</div>
60 <div class="meta creator-tier-sub mb-peer">or ${{ card.standard_annual }}/yr (10% off)</div>
61 {% endif %}
62 <div class="meta creator-tier-storage mb-section">{{ card.storage }}</div>
63 <div class="creator-tier-buttons">
64 <form method="post" action="/stripe/creator-tier" class="m-0">
65 {% if let Some(token) = csrf_token %}<input type="hidden" name="_csrf" value="{{ token }}">{% endif %}
66 <input type="hidden" name="tier" value="{{ card.key }}">
67 <input type="hidden" name="interval" value="monthly">
68 <button type="submit" class="btn-primary creator-tier-btn" data-loading-text="Redirecting to Stripe...">Monthly</button>
69 </form>
70 <form method="post" action="/stripe/creator-tier" class="m-0">
71 {% if let Some(token) = csrf_token %}<input type="hidden" name="_csrf" value="{{ token }}">{% endif %}
72 <input type="hidden" name="tier" value="{{ card.key }}">
73 <input type="hidden" name="interval" value="annual">
74 <button type="submit" class="btn-secondary creator-tier-btn" data-loading-text="Redirecting to Stripe...">Annual (save 10%)</button>
75 </form>
76 </div>
77 </div>
78 {% endfor %}
79 </div>
80 <p class="text-center mb-section"><a href="/docs/tiers" class="text-sm">Full tier details &rarr;</a></p>
81 <p class="text-center text-sm muted mb-section">
82 Upgrades take effect immediately and are prorated. Downgrades take effect at the next billing period; if you're over the new tier's storage cap, new uploads stay blocked until you're under it, but your existing content stays published either way.
83 </p>
84 {% endif %}
85 {% endif %}
86 </div>
87
88 <div class="section-group-label creator-section-label">Usage</div>
89
90 <details class="form-section creator-form-section">
91 <summary><h2 class="creator-h2 creator-h2--sm">Storage ({{ storage_total }} / {{ storage_max }})</h2></summary>
92 <div class="storage-box mb-section">
93 <div class="storage-row">
94 <span>{{ storage_total }} of {{ storage_max }} used</span>
95 <span class="muted">{{ storage_pct }}%</span>
96 </div>
97 <div class="progress-bar-container">
98 <div class="progress-bar progress-bar--highlight{% if !storage_tier.is_empty() %} progress-bar--{{ storage_tier }}{% endif %}" style="--fill: {{ storage_pct }}%"></div>
99 </div>
100 <div class="storage-breakdown">
101 <span class="muted">Audio: {{ storage_audio }}</span>
102 <span class="muted">Covers: {{ storage_covers }}</span>
103 <span class="muted">Downloads: {{ storage_downloads }}</span>
104 <span class="muted">Clips: {{ storage_insertions }}</span>
105 <span class="muted">Video: {{ storage_video }}</span>
106 <span class="muted">Media: {{ storage_media }}</span>
107 <span class="muted">Gallery: {{ storage_gallery }}</span>
108 </div>
109 </div>
110 </details>
111
112 {% if let Some(mail) = mail_allowance %}
113 <details class="form-section creator-form-section"{% if mail.warning %} open{% endif %}>
114 <summary><h2 class="creator-h2 creator-h2--sm">Email allowance ({{ mail.sent }} / {{ mail.cap }})</h2></summary>
115 <div class="storage-box mb-section">
116 <div class="storage-row">
117 <span>{{ mail.sent }} of {{ mail.cap }} emails sent this billing month</span>
118 <span class="muted">{{ mail.percent }}%</span>
119 </div>
120 <div class="progress-bar-container">
121 <div class="progress-bar progress-bar--highlight{% if !mail.tier.is_empty() %} progress-bar--{{ mail.tier }}{% endif %}" style="--fill: {{ mail.percent }}%"></div>
122 </div>
123 <p class="muted text-sm mt-peer">
124 {{ mail.remaining }} left. Resets {{ mail.resets_on }}.
125 {% if mail.warning %}
126 You are close to the allowance. A send that would cross it is refused rather than trimmed, so ask for more before you need it.
127 {% endif %}
128 </p>
129 <p class="muted text-sm">
130 The allowance covers announcements to your subscribers and broadcasts to your followers. It exists so one large send cannot degrade delivery for everyone on the shared sending pool, and it is set where ordinary use never meets it.
131 </p>
132 </div>
133
134 {% match mail.request %}
135 {% when Some with (request) %}
136 <p class="text-sm muted">
137 {% if request.status == "pending" %}
138 You asked for {{ request.requested_cap }} a month on {{ request.created_at }}. We will come back to you.
139 {% else if request.status == "granted" %}
140 Your request from {{ request.created_at }} was granted{% if let Some(granted) = request.granted_cap %} at {{ granted }} a month{% endif %}.
141 {% else %}
142 Your request from {{ request.created_at }} was not granted. Ask again if the shape of your sending has changed.
143 {% endif %}
144 </p>
145 {% if request.status != "pending" %}
146 <form hx-post="/api/mail-cap/request" hx-target="#mail-cap-status" hx-swap="innerHTML" class="stack">
147 {% if let Some(token) = csrf_token %}<input type="hidden" name="_csrf" value="{{ token }}">{% endif %}
148 <label class="text-sm" for="mail-cap-requested">Emails per month</label>
149 <input type="number" id="mail-cap-requested" name="requested_cap" min="1" max="1000000" required>
150 <label class="text-sm" for="mail-cap-reason">Why you need it</label>
151 <textarea id="mail-cap-reason" name="reason" rows="3" maxlength="1000" required></textarea>
152 <button type="submit" class="btn">Request an increase</button>
153 </form>
154 {% endif %}
155 {% when None %}
156 <form hx-post="/api/mail-cap/request" hx-target="#mail-cap-status" hx-swap="innerHTML" class="stack">
157 {% if let Some(token) = csrf_token %}<input type="hidden" name="_csrf" value="{{ token }}">{% endif %}
158 <label class="text-sm" for="mail-cap-requested">Emails per month</label>
159 <input type="number" id="mail-cap-requested" name="requested_cap" min="1" max="1000000" required>
160 <label class="text-sm" for="mail-cap-reason">Why you need it</label>
161 <textarea id="mail-cap-reason" name="reason" rows="3" maxlength="1000" required></textarea>
162 <button type="submit" class="btn">Request an increase</button>
163 </form>
164 {% endmatch %}
165 <div id="mail-cap-status"></div>
166 </details>
167 {% endif %}
168
169 <div class="section-group-label creator-section-label">Outreach</div>
170
171 <details class="form-section creator-form-section">
172 <summary><h2 class="creator-h2 creator-h2--sm">Broadcast to Followers</h2></summary>
173 <div class="broadcast-head mb-section">
174 <p class="text-sm muted m-0 broadcast-desc">
175 Send a plain-text announcement to everyone who follows you or your projects.
176 Limited to one broadcast per 24 hours.
177 {% if follower_count > 0 %}
178 <strong>{{ follower_count }}</strong> follower{% if follower_count != 1 %}s{% endif %} will receive this email.
179 {% else %}
180 You don't have any followers yet.
181 {% endif %}
182 </p>
183 {% if follower_count > 0 %}
184 {# broadcast-export and nowrap are this page's layout, not the control's,
185 so they stay on a wrapper the description does not own. #}
186 <span class="broadcast-export nowrap">{{ crate::quasi::export_act::html("/api/export/followers", "followers.csv")|safe }}</span>
187 {% endif %}
188 </div>
189 <form hx-post="/api/broadcast" hx-target="#broadcast-result" hx-swap="innerHTML"
190 hx-confirm="Send this broadcast to {{ follower_count }} follower{% if follower_count != 1 %}s{% endif %}?">
191 <div class="form-group">
192 <label for="broadcast-subject">Subject</label>
193 <input type="text" id="broadcast-subject" name="subject" required maxlength="200"
194 placeholder="Subject line for your announcement">
195 </div>
196 <div class="form-group">
197 <label for="broadcast-body">Message</label>
198 <textarea id="broadcast-body" name="body" rows="6" required maxlength="5000"
199 placeholder="Your announcement..."></textarea>
200 </div>
201 <div id="broadcast-result"></div>
202 <button type="submit" class="btn-primary">Send Broadcast</button>
203 </form>
204 </details>
205
206 {% if invites_enabled %}
207 <details class="form-section creator-form-section">
208 <summary><h2 class="creator-h2 creator-h2--sm">Invite Codes</h2></summary>
209 <p class="muted text-sm mb-section">
210 Invite people to Makenotwork. They skip the pitch but still need admin approval.
211 {{ active_invite_count }}/{{ invite_limit }} active.
212 </p>
213
214 {% if active_invite_count < invite_limit %}
215 <div id="invite-result" class="mb-section"></div>
216 <button class="btn-primary mb-pane"
217 hx-post="/api/invites/create"
218 hx-target="#invite-result"
219 hx-swap="innerHTML">Generate Invite Code</button>
220 {% else %}
221 <p class="dimmed text-sm mb-pane">
222 You have reached the limit of {{ invite_limit }} active codes. Codes free up when redeemed.
223 </p>
224 {% endif %}
225
226 {% if !invite_codes.is_empty() %}
227 <div class="scroll-x">
228 <table class="compact-table" aria-label="Invite codes">
229 <thead>
230 <tr>
231 <th>Code</th>
232 <th>Status</th>
233 <th>Created</th>
234 </tr>
235 </thead>
236 <tbody>
237 {% for code in invite_codes %}
238 <tr>
239 <td class="mono-sm">{{ code.formatted_code }}</td>
240 <td>
241 {% if code.redeemed %}
242 <span class="badge dimmed">Redeemed</span>
243 {% else %}
244 <span class="badge" data-tone="success">Available</span>
245 {% endif %}
246 </td>
247 <td class="text-sm">{{ code.created_at }}</td>
248 </tr>
249 {% endfor %}
250 </tbody>
251 </table>
252 </div>
253 {% endif %}
254 </details>
255 {% endif %}
256
257 {% else %}
258
259 <div class="creator-tab-section">
260 <h2 class="creator-h2">Become a Creator</h2>
261 <p class="creator-intro mb-pane">
262 Apply for creator access to start publishing and selling your work.
263 Tell us what you make and which tier fits. Most applications are approved within a few days.
264 </p>
265 </div>
266
267 <hr class="creator-divider">
268
269 {% if let Some(entry) = waitlist_entry %}
270 <div class="creator-tab-section">
271 <h2 class="creator-h2 mb-section">Application Submitted</h2>
272 {% if let Some(pitch_text) = entry.pitch %}
273 <div class="creator-pitch-box mb-section">
274 <div class="meta mb-peer">Your pitch</div>
275 <p>{{ pitch_text }}</p>
276 </div>
277 {% else %}
278 <div class="creator-pitch-box mb-section">
279 <p class="muted">You were invited: no pitch required.</p>
280 </div>
281 {% endif %}
282 <div class="muted text-sm">
283 Applied {{ entry.created_at }}
284 {% if entry.status == "pending" %}
285 &middot; Status: <strong>Pending review</strong>
286 {% else if entry.status == "approved" %}
287 &middot; Status: <strong>Approved</strong>
288 {% else %}
289 &middot; Status: {{ entry.status }}
290 {% endif %}
291 </div>
292 <p class="text-sm mt-section">
293 We review applications individually, usually within a few days. You'll get an email when you're approved.
294 </p>
295 </div>
296 {% else %}
297 {% if email_verified %}
298 <div class="creator-tab-section">
299 <h2 class="creator-h2 mb-section">Apply for Creator Access</h2>
300 <p class="mb-pane">
301 Tell us what you make and which tier you need. If you have existing work online,
302 link to it: a portfolio, channel, or profile elsewhere helps us review faster.
303 </p>
304 <form hx-post="/api/waitlist/apply"
305 hx-target="closest div"
306 hx-swap="innerHTML"
307 id="waitlist-form">
308 <div class="form-group">
309 <label for="pitch">What do you make?</label>
310 <textarea id="pitch" name="pitch" rows="4"
311 minlength="20" maxlength="500" required
312 placeholder="Briefly describe your work. A link to your portfolio, channel, or existing storefront is helpful."></textarea>
313 <div class="hint">20-500 characters.</div>
314 </div>
315 <div class="form-group">
316 <label for="preferred-tier">Which tier fits?</label>
317 <select id="preferred-tier" name="preferred_tier">
318 <option value="">Not sure yet</option>
319 {# Guard the positional indexing: Askama `[N]` panics (raw 500) if the
320 card list ever shrinks. Safe today (four fixed tiers); the length
321 check keeps it from becoming a 500 if tiers go data-driven. #}
322 {% if tier_cards.len() >= 4 %}
323 <option value="basic">Basic (${{ tier_cards[0].standard_monthly }}/mo): text, blogs, newsletters</option>
324 <option value="small_files">Small Files (${{ tier_cards[1].standard_monthly }}/mo): audio, software, plugins</option>
325 <option value="big_files">Big Files (${{ tier_cards[2].standard_monthly }}/mo): video, games, large software</option>
326 <option value="everything">Everything (${{ tier_cards[3].standard_monthly }}/mo): all features, current and future</option>
327 {% endif %}
328 </select>
329 </div>
330 <div class="form-group">
331 <label>
332 <input type="checkbox" id="free-trial" name="free_trial" value="yes"
333 data-change="toggleTrialDetails">
334 I'd like to request a free trial
335 </label>
336 </div>
337 <div id="trial-details" class="hidden">
338 <div class="form-group">
339 <label for="trial-length">How long do you need?</label>
340 <select id="trial-length" name="trial_length">
341 <option value="2 weeks">2 weeks</option>
342 <option value="1 month" selected>1 month</option>
343 <option value="2 months">2 months</option>
344 <option value="3 months">3 months</option>
345 </select>
346 </div>
347 <div class="form-group">
348 <label for="trial-reason">What do you want to test?</label>
349 <textarea id="trial-reason" name="trial_reason" rows="3" maxlength="500"
350 placeholder="What feature or workflow are you unsure about? What do you need to try before committing?"></textarea>
351 <div class="hint">Longer trials should have a specific reason: a feature you need to evaluate, a workflow you want to test with real content, etc.</div>
352 </div>
353 </div>
354 <div id="apply-result"></div>
355 <button type="submit" class="btn-primary">Submit Application</button>
356 </form>
357 </div>
358 {% else %}
359 <div class="creator-tab-section creator-tab-section--centered">
360 <h2 class="creator-h2 mb-section">Verify Your Email First</h2>
361 <p class="muted mb-pane">
362 You need a verified email address to apply for creator access.
363 </p>
364 <button class="btn-primary"
365 hx-post="/api/resend-verification"
366 hx-target="#verify-result"
367 hx-swap="innerHTML">Resend Verification Email</button>
368 <div id="verify-result" class="mt-section"></div>
369 </div>
370 {% endif %}
371 {% endif %}
372 {% endif %}
373