Skip to main content

max / makenotwork

20.5 KB · 390 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 || mail.complaints.elevated %} 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 class="storage-row mt-section">
133 <span>
134 Marked as spam:
135 {% if mail.complaints.measurable %}
136 {{ mail.complaints.rate_display }}
137 {% else %}
138 too little sending to measure
139 {% endif %}
140 </span>
141 <span class="muted">{{ mail.complaints.complaints }} of {{ mail.complaints.sent }} in the last {{ mail.complaints.window_days }} days</span>
142 </div>
143 <p class="muted text-sm mt-peer">
144 A healthy list stays under {{ mail.complaints.healthy_display }}. {{ mail.complaints.limit_display }} is the line mail providers act on, and it is the one we watch. Under {{ mail.complaints.min_sent }} emails in the window the number is too small a sample to say anything, so we do not read it as good or bad.
145 </p>
146 {% if let Some(complaint_warning) = mail.complaints.warning %}
147 <div class="alert alert-warning">{{ complaint_warning }}</div>
148 {% endif %}
149 </div>
150
151 {% match mail.request %}
152 {% when Some with (request) %}
153 <p class="text-sm muted">
154 {% if request.status == "pending" %}
155 You asked for {{ request.requested_cap }} a month on {{ request.created_at }}. We will come back to you.
156 {% else if request.status == "granted" %}
157 Your request from {{ request.created_at }} was granted{% if let Some(granted) = request.granted_cap %} at {{ granted }} a month{% endif %}.
158 {% else %}
159 Your request from {{ request.created_at }} was not granted. Ask again if the shape of your sending has changed.
160 {% endif %}
161 </p>
162 {% if request.status != "pending" %}
163 <form hx-post="/api/mail-cap/request" hx-target="#mail-cap-status" hx-swap="innerHTML" class="stack">
164 {% if let Some(token) = csrf_token %}<input type="hidden" name="_csrf" value="{{ token }}">{% endif %}
165 <label class="text-sm" for="mail-cap-requested">Emails per month</label>
166 <input type="number" id="mail-cap-requested" name="requested_cap" min="1" max="1000000" required>
167 <label class="text-sm" for="mail-cap-reason">Why you need it</label>
168 <textarea id="mail-cap-reason" name="reason" rows="3" maxlength="1000" required></textarea>
169 <button type="submit" class="btn">Request an increase</button>
170 </form>
171 {% endif %}
172 {% when None %}
173 <form hx-post="/api/mail-cap/request" hx-target="#mail-cap-status" hx-swap="innerHTML" class="stack">
174 {% if let Some(token) = csrf_token %}<input type="hidden" name="_csrf" value="{{ token }}">{% endif %}
175 <label class="text-sm" for="mail-cap-requested">Emails per month</label>
176 <input type="number" id="mail-cap-requested" name="requested_cap" min="1" max="1000000" required>
177 <label class="text-sm" for="mail-cap-reason">Why you need it</label>
178 <textarea id="mail-cap-reason" name="reason" rows="3" maxlength="1000" required></textarea>
179 <button type="submit" class="btn">Request an increase</button>
180 </form>
181 {% endmatch %}
182 <div id="mail-cap-status"></div>
183 </details>
184 {% endif %}
185
186 <div class="section-group-label creator-section-label">Outreach</div>
187
188 <details class="form-section creator-form-section">
189 <summary><h2 class="creator-h2 creator-h2--sm">Broadcast to Followers</h2></summary>
190 <div class="broadcast-head mb-section">
191 <p class="text-sm muted m-0 broadcast-desc">
192 Send a plain-text announcement to everyone who follows you or your projects.
193 Limited to one broadcast per 24 hours.
194 {% if follower_count > 0 %}
195 <strong>{{ follower_count }}</strong> follower{% if follower_count != 1 %}s{% endif %} will receive this email.
196 {% else %}
197 You don't have any followers yet.
198 {% endif %}
199 </p>
200 {% if follower_count > 0 %}
201 {# broadcast-export and nowrap are this page's layout, not the control's,
202 so they stay on a wrapper the description does not own. #}
203 <span class="broadcast-export nowrap">{{ crate::quasi::export_act::html("/api/export/followers", "followers.csv")|safe }}</span>
204 {% endif %}
205 </div>
206 <form hx-post="/api/broadcast" hx-target="#broadcast-result" hx-swap="innerHTML"
207 hx-confirm="Send this broadcast to {{ follower_count }} follower{% if follower_count != 1 %}s{% endif %}?">
208 <div class="form-group">
209 <label for="broadcast-subject">Subject</label>
210 <input type="text" id="broadcast-subject" name="subject" required maxlength="200"
211 placeholder="Subject line for your announcement">
212 </div>
213 <div class="form-group">
214 <label for="broadcast-body">Message</label>
215 <textarea id="broadcast-body" name="body" rows="6" required maxlength="5000"
216 placeholder="Your announcement..."></textarea>
217 </div>
218 <div id="broadcast-result"></div>
219 <button type="submit" class="btn-primary">Send Broadcast</button>
220 </form>
221 </details>
222
223 {% if invites_enabled %}
224 <details class="form-section creator-form-section">
225 <summary><h2 class="creator-h2 creator-h2--sm">Invite Codes</h2></summary>
226 <p class="muted text-sm mb-section">
227 Invite people to Makenotwork. They skip the pitch but still need admin approval.
228 {{ active_invite_count }}/{{ invite_limit }} active.
229 </p>
230
231 {% if active_invite_count < invite_limit %}
232 <div id="invite-result" class="mb-section"></div>
233 <button class="btn-primary mb-pane"
234 hx-post="/api/invites/create"
235 hx-target="#invite-result"
236 hx-swap="innerHTML">Generate Invite Code</button>
237 {% else %}
238 <p class="dimmed text-sm mb-pane">
239 You have reached the limit of {{ invite_limit }} active codes. Codes free up when redeemed.
240 </p>
241 {% endif %}
242
243 {% if !invite_codes.is_empty() %}
244 <div class="scroll-x">
245 <table class="compact-table" aria-label="Invite codes">
246 <thead>
247 <tr>
248 <th>Code</th>
249 <th>Status</th>
250 <th>Created</th>
251 </tr>
252 </thead>
253 <tbody>
254 {% for code in invite_codes %}
255 <tr>
256 <td class="mono-sm">{{ code.formatted_code }}</td>
257 <td>
258 {% if code.redeemed %}
259 <span class="badge dimmed">Redeemed</span>
260 {% else %}
261 <span class="badge" data-tone="success">Available</span>
262 {% endif %}
263 </td>
264 <td class="text-sm">{{ code.created_at }}</td>
265 </tr>
266 {% endfor %}
267 </tbody>
268 </table>
269 </div>
270 {% endif %}
271 </details>
272 {% endif %}
273
274 {% else %}
275
276 <div class="creator-tab-section">
277 <h2 class="creator-h2">Become a Creator</h2>
278 <p class="creator-intro mb-pane">
279 Apply for creator access to start publishing and selling your work.
280 Tell us what you make and which tier fits. Most applications are approved within a few days.
281 </p>
282 </div>
283
284 <hr class="creator-divider">
285
286 {% if let Some(entry) = waitlist_entry %}
287 <div class="creator-tab-section">
288 <h2 class="creator-h2 mb-section">Application Submitted</h2>
289 {% if let Some(pitch_text) = entry.pitch %}
290 <div class="creator-pitch-box mb-section">
291 <div class="meta mb-peer">Your pitch</div>
292 <p>{{ pitch_text }}</p>
293 </div>
294 {% else %}
295 <div class="creator-pitch-box mb-section">
296 <p class="muted">You were invited: no pitch required.</p>
297 </div>
298 {% endif %}
299 <div class="muted text-sm">
300 Applied {{ entry.created_at }}
301 {% if entry.status == "pending" %}
302 &middot; Status: <strong>Pending review</strong>
303 {% else if entry.status == "approved" %}
304 &middot; Status: <strong>Approved</strong>
305 {% else %}
306 &middot; Status: {{ entry.status }}
307 {% endif %}
308 </div>
309 <p class="text-sm mt-section">
310 We review applications individually, usually within a few days. You'll get an email when you're approved.
311 </p>
312 </div>
313 {% else %}
314 {% if email_verified %}
315 <div class="creator-tab-section">
316 <h2 class="creator-h2 mb-section">Apply for Creator Access</h2>
317 <p class="mb-pane">
318 Tell us what you make and which tier you need. If you have existing work online,
319 link to it: a portfolio, channel, or profile elsewhere helps us review faster.
320 </p>
321 <form hx-post="/api/waitlist/apply"
322 hx-target="closest div"
323 hx-swap="innerHTML"
324 id="waitlist-form">
325 <div class="form-group">
326 <label for="pitch">What do you make?</label>
327 <textarea id="pitch" name="pitch" rows="4"
328 minlength="20" maxlength="500" required
329 placeholder="Briefly describe your work. A link to your portfolio, channel, or existing storefront is helpful."></textarea>
330 <div class="hint">20-500 characters.</div>
331 </div>
332 <div class="form-group">
333 <label for="preferred-tier">Which tier fits?</label>
334 <select id="preferred-tier" name="preferred_tier">
335 <option value="">Not sure yet</option>
336 {# Guard the positional indexing: Askama `[N]` panics (raw 500) if the
337 card list ever shrinks. Safe today (four fixed tiers); the length
338 check keeps it from becoming a 500 if tiers go data-driven. #}
339 {% if tier_cards.len() >= 4 %}
340 <option value="basic">Basic (${{ tier_cards[0].standard_monthly }}/mo): text, blogs, newsletters</option>
341 <option value="small_files">Small Files (${{ tier_cards[1].standard_monthly }}/mo): audio, software, plugins</option>
342 <option value="big_files">Big Files (${{ tier_cards[2].standard_monthly }}/mo): video, games, large software</option>
343 <option value="everything">Everything (${{ tier_cards[3].standard_monthly }}/mo): all features, current and future</option>
344 {% endif %}
345 </select>
346 </div>
347 <div class="form-group">
348 <label>
349 <input type="checkbox" id="free-trial" name="free_trial" value="yes"
350 data-change="toggleTrialDetails">
351 I'd like to request a free trial
352 </label>
353 </div>
354 <div id="trial-details" class="hidden">
355 <div class="form-group">
356 <label for="trial-length">How long do you need?</label>
357 <select id="trial-length" name="trial_length">
358 <option value="2 weeks">2 weeks</option>
359 <option value="1 month" selected>1 month</option>
360 <option value="2 months">2 months</option>
361 <option value="3 months">3 months</option>
362 </select>
363 </div>
364 <div class="form-group">
365 <label for="trial-reason">What do you want to test?</label>
366 <textarea id="trial-reason" name="trial_reason" rows="3" maxlength="500"
367 placeholder="What feature or workflow are you unsure about? What do you need to try before committing?"></textarea>
368 <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>
369 </div>
370 </div>
371 <div id="apply-result"></div>
372 <button type="submit" class="btn-primary">Submit Application</button>
373 </form>
374 </div>
375 {% else %}
376 <div class="creator-tab-section creator-tab-section--centered">
377 <h2 class="creator-h2 mb-section">Verify Your Email First</h2>
378 <p class="muted mb-pane">
379 You need a verified email address to apply for creator access.
380 </p>
381 <button class="btn-primary"
382 hx-post="/api/resend-verification"
383 hx-target="#verify-result"
384 hx-swap="innerHTML">Resend Verification Email</button>
385 <div id="verify-result" class="mt-section"></div>
386 </div>
387 {% endif %}
388 {% endif %}
389 {% endif %}
390