Skip to main content

max / makenotwork

12.0 KB · 240 lines History Blame Raw
1 <div class="tab-docs"><a href="/docs/projects">Docs: Projects &rarr;</a></div>
2
3 <div class="form-section">
4 <h2 class="subsection-title">Project Image</h2>
5 <div class="proj-image-row">
6 <div id="project-image-preview" class="proj-image-preview">
7 {% match project.cover_image_url %}
8 {% when Some with (url) %}
9 <img src="{{ url }}" alt="Project image">
10 {% when None %}
11 <span class="proj-image-empty">No image</span>
12 {% endmatch %}
13 </div>
14 <div>
15 <p class="proj-image-hint">Square, at least 400x400px. JPG, PNG, or WebP.</p>
16 <input type="file" id="project-image-input" class="sr-only" accept="image/jpeg,image/png,image/webp">
17 <button type="button" class="btn-secondary btn-compact" data-action="click" data-target="project-image-input">Change Image</button>
18 <span id="project-image-status" class="field-status"></span>
19 </div>
20 </div>
21 </div>
22 <div id="tab-project-settings-cfg" hidden data-project-id="{{ project_id }}"></div>
23
24 <div class="form-section">
25 <h2 class="subsection-title">Gallery Images</h2>
26 <p class="proj-image-hint">Up to 8 extra images shown in a carousel on your public project page. The Project Image above stays the card/preview image.</p>
27 <div id="project-gallery-list" class="gallery-manager-list"></div>
28 <input type="file" id="project-gallery-input" class="sr-only" accept="image/jpeg,image/png,image/webp" multiple>
29 <button type="button" class="btn-secondary btn-compact" data-action="click" data-target="project-gallery-input">Add Images</button>
30 <span id="project-gallery-status" class="field-status"></span>
31 </div>
32
33 <div class="form-section">
34 <h2 class="subsection-title">Project Information</h2>
35
36 <form id="project-info-form" data-submit="submitProjectInfo" data-project-id="{{ project.id }}">
37 <div class="form-group">
38 <label for="project-name">Project Name</label>
39 <input type="text" id="project-name" name="title" value="{{ project.title }}" placeholder="Project name" title="The public name of your project" required>
40 </div>
41
42 <div class="form-group">
43 <label for="project-description">Description</label>
44 <textarea id="project-description" name="description" placeholder="Describe your project..." title="A brief description shown to visitors">{{ project.description }}</textarea>
45 </div>
46
47 <div class="form-group">
48 <label for="settings-category">Category</label>
49 <div class="suggestion-input" id="settings-category-suggestion">
50 <input type="text" id="settings-category" name="category" value="{{ category_name }}" placeholder="What kind of project is this?" autocomplete="off">
51 <div class="suggestion-dropdown" id="settings-category-dropdown"></div>
52 </div>
53 <div class="hint">Choose an existing category or type a new one. Leave blank to clear.</div>
54 </div>
55
56 <button class="btn-primary" type="submit">
57 Save Changes
58 <span id="project-spinner" class="htmx-indicator"> ...</span>
59 </button>
60 <span id="project-save-status"></span>
61 </form>
62 </div>
63
64 <div class="form-section">
65 <h2 class="subsection-title">Monetization</h2>
66 <p class="section-lead">
67 How visitors access this project. 0% platform fee: only ~3% payment processing.
68 Changing models is safe but does not refund or cancel existing purchases or subscriptions.
69 </p>
70
71 <form id="project-pricing-form" data-submit="submitProjectPricing" data-project-id="{{ project.id }}">
72 <div class="form-group">
73 <label for="settings-pricing-model">Pricing model</label>
74 <select id="settings-pricing-model" name="pricing_model" data-change="updateSettingsPricingUI">
75 <option value="free"{% if pricing_model == "free" %} selected{% endif %}>Free: anyone can access</option>
76 <option value="buy_once"{% if pricing_model == "buy_once" %} selected{% endif %}>One-time purchase</option>
77 <option value="pwyw"{% if pricing_model == "pwyw" %} selected{% endif %}>Pay what you want</option>
78 <option value="subscription"{% if pricing_model == "subscription" %} selected{% endif %}>Membership: recurring monthly</option>
79 </select>
80 </div>
81
82 <div id="settings-buy-once-fields" class="hidden">
83 <div class="form-group">
84 <label for="settings-price-dollars">Price</label>
85 <input type="number" id="settings-price-dollars" name="price_dollars" min="0.50" step="0.01"
86 placeholder="9.99" value="{{ price_dollars }}"
87 aria-describedby="settings-price-dollars-unit">
88 <span class="form-unit" id="settings-price-dollars-unit">USD</span>
89 </div>
90 </div>
91
92 <div id="settings-pwyw-fields" class="hidden">
93 <div class="form-group">
94 <label for="settings-pwyw-min-dollars">Minimum price</label>
95 <input type="number" id="settings-pwyw-min-dollars" name="pwyw_min_dollars" min="0" step="0.01"
96 placeholder="0.00" value="{{ pwyw_min_dollars }}"
97 aria-describedby="settings-pwyw-min-dollars-unit settings-pwyw-min-dollars-hint">
98 <span class="form-unit" id="settings-pwyw-min-dollars-unit">USD</span>
99 <p class="form-hint" id="settings-pwyw-min-dollars-hint">Set to 0 for no minimum.</p>
100 </div>
101 </div>
102
103 <div id="settings-subscription-note" class="hidden subscription-note">
104 <p>
105 Manage tiers in the <strong>Subscriptions</strong> tab.
106 </p>
107 </div>
108
109 <button class="btn-primary" type="submit">Save Monetization</button>
110 <span id="project-pricing-status" class="field-status"></span>
111 </form>
112 </div>
113
114 <div class="form-section">
115 <h2 class="subsection-title">Features</h2>
116 <p class="section-lead">Platform tools enabled for this project. Changes take effect immediately.</p>
117
118 <div class="type-grid" id="features-grid">
119 {% for (value, label, desc) in project_features %}
120 <label class="card--selectable">
121 <input type="checkbox" name="feature" value="{{ value }}"
122 {% if features.contains(&value.to_string()) %}checked{% endif %}
123 data-change="updateFeatures" data-arg="{{ project.id }}">
124 <span class="card--selectable-inner">
125 <span class="card--selectable-label">{{ label }}</span>
126 <span class="card--selectable-desc">{{ desc }}</span>
127 </span>
128 </label>
129 {% endfor %}
130 </div>
131 <span id="features-save-status" class="features-grid-status"></span>
132 </div>
133
134 <div class="form-section">
135 <h2 class="subsection-title">Theme</h2>
136 <p class="section-lead">Color palette for this project's public page and its items.</p>
137
138 <form hx-put="/api/projects/{{ project.id }}/theme"
139 hx-target="#project-theme-save-status"
140 hx-swap="innerHTML"
141 hx-indicator="#project-theme-spinner">
142 <div class="form-group">
143 <label for="project-theme">Project theme</label>
144 <select id="project-theme" name="theme_id">
145 {% for t in theme_options %}
146 <option value="{{ t.id }}"{% if t.selected %} selected{% endif %}>{{ t.name }}</option>
147 {% endfor %}
148 </select>
149 <div class="hint">Items in this project inherit its theme.</div>
150 </div>
151
152 <button class="btn-primary" type="submit">
153 Save Theme
154 <span id="project-theme-spinner" class="htmx-indicator"> ...</span>
155 </button>
156 <span id="project-theme-save-status"></span>
157 </form>
158 </div>
159
160 <div class="form-section">
161 <h2 class="subsection-title">Custom page</h2>
162 <p class="section-lead">
163 Write your own HTML and CSS for this project's public page. Its items
164 inherit the page's styling. No JavaScript, on-platform links only.
165 </p>
166 <a class="btn-secondary" href="/dashboard/project/{{ project.slug }}/custom-page">Edit custom page</a>
167 </div>
168
169 <div class="section-group-label">Project Management</div>
170
171 <div class="form-section">
172 <h2 class="subsection-title">Delete Project</h2>
173 <p class="section-lead">
174 Deleting this project is permanent and cannot be undone.
175 </p>
176 <button class="btn-danger"
177 hx-delete="/api/projects/{{ project.id }}"
178 hx-confirm="Delete project '{{ project.title }}'? This cannot be undone."
179 data-after="nav" data-href="/dashboard">
180 Delete Project
181 </button>
182 </div>
183
184 <!-- Pages (project-level markdown sections) -->
185 <details class="content-section pages-toggle raised" id="psections-management"{% if !sections.is_empty() %} open{% endif %}>
186 <summary>
187 <h2 class="subsection-title">Pages (<span id="psection-count">{{ sections.len() }}</span>)</h2>
188 </summary>
189 <p class="pages-intro">Markdown pages that apply to your whole project: Privacy Policy, Terms, FAQ, etc. They appear as tabs on your public project page and are linkable via {{ crate::quasi::literal::html("#section-<slug>")|safe }}. Max 10.</p>
190
191 <div id="psections-list">
192 {% if sections.is_empty() %}
193 <p id="psections-empty" class="empty-state">No pages yet. Common pages: Privacy Policy, Terms of Service, FAQ, Support.</p>
194 {% else %}
195 {% for section in sections %}
196 <div class="psection-row" data-id="{{ section.id }}">
197 <span class="psection-row-title">{{ section.title }}</span>
198 <span class="psection-row-anchor">{{ crate::quasi::literal::of(["#section-", section.slug.as_str()])|safe }}</span>
199 <span class="psection-row-length">{{ section.body.chars().count() }} chars</span>
200 <button type="button" class="btn-secondary psection-edit-btn" data-id="{{ section.id }}"
201 data-title="{{ section.title }}">Edit</button>
202 <button type="button" class="btn-secondary psection-del-btn" data-id="{{ section.id }}">Delete</button>
203 </div>
204 <textarea data-body-for="{{ section.id }}" class="hidden">{{ section.body }}</textarea>
205 {% endfor %}
206 {% endif %}
207 </div>
208
209 <details class="psection-add-details" id="psection-add-details">
210 <summary>Add Page</summary>
211 <div class="psection-add-fields">
212 <div class="form-group">
213 <label for="new-psec-title">Title</label>
214 <input type="text" id="new-psec-title" placeholder="e.g. Privacy Policy, Terms..." autocomplete="off">
215 </div>
216 {{ crate::quasi::rich_field::html("new-psec", "Body (Markdown)", "Page content...", "", crate::quasi::rich_field::Height::Standard)|safe }}
217 <button type="button" class="btn-secondary" id="add-psec-btn" data-project-id="{{ project.id }}">Add Page</button>
218 <span id="psec-add-status" class="field-status"></span>
219 </div>
220 </details>
221
222 <div id="psection-edit-modal" class="psection-edit-modal hidden">
223 <input type="hidden" id="edit-psec-id">
224 <div class="form-group">
225 <label for="edit-psec-title">Title</label>
226 <input type="text" id="edit-psec-title" autocomplete="off">
227 </div>
228 {{ crate::quasi::rich_field::html("edit-psec", "Body (Markdown)", "Page content...", "", crate::quasi::rich_field::Height::Standard)|safe }}
229 <div class="psection-edit-actions">
230 <button type="button" class="btn-primary btn-compact" id="save-psec-btn">Save</button>
231 <button type="button" class="btn-secondary btn-compact" id="cancel-psec-btn">Cancel</button>
232 </div>
233 <span id="psec-edit-status" class="field-status"></span>
234 </div>
235 </details>
236 <script src="/static/project-sections.js" defer></script>
237 <!-- Single consolidated settings-tab script: image upload, gallery manager, and
238 category dropdown, loaded at the end so all referenced DOM is present. -->
239 <script src="/static/tab-project-settings.js?v=0702"></script>
240