Skip to main content

max / makenotwork

11.5 KB · 241 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 </div>
88 </div>
89
90 <div id="settings-pwyw-fields" class="hidden">
91 <div class="form-group">
92 <label for="settings-pwyw-min-dollars">Minimum price ($, 0 for no minimum)</label>
93 <input type="number" id="settings-pwyw-min-dollars" name="pwyw_min_dollars" min="0" step="0.01"
94 placeholder="0.00" value="{{ pwyw_min_dollars }}">
95 </div>
96 </div>
97
98 <div id="settings-subscription-note" class="hidden subscription-note">
99 <p>
100 Manage tiers in the <strong>Subscriptions</strong> tab.
101 </p>
102 </div>
103
104 <button class="btn-primary" type="submit">Save Monetization</button>
105 <span id="project-pricing-status" class="field-status"></span>
106 </form>
107 </div>
108
109 <div class="form-section">
110 <h2 class="subsection-title">Features</h2>
111 <p class="section-lead">Platform tools enabled for this project. Changes take effect immediately.</p>
112
113 <div class="type-grid" id="features-grid">
114 {% for (value, label, desc) in project_features %}
115 <label class="type-card">
116 <input type="checkbox" name="feature" value="{{ value }}"
117 {% if features.contains(&value.to_string()) %}checked{% endif %}
118 data-change="updateFeatures" data-arg="{{ project.id }}">
119 <span class="type-card-inner">
120 <span class="type-card-label">{{ label }}</span>
121 <span class="type-card-desc">{{ desc }}</span>
122 </span>
123 </label>
124 {% endfor %}
125 </div>
126 <span id="features-save-status" class="features-grid-status"></span>
127 </div>
128
129 <div class="form-section">
130 <h2 class="subsection-title">Theme</h2>
131 <p class="section-lead">Color palette for this project's public page and its items.</p>
132
133 <form hx-put="/api/projects/{{ project.id }}/theme"
134 hx-target="#project-theme-save-status"
135 hx-swap="innerHTML"
136 hx-indicator="#project-theme-spinner">
137 <div class="form-group">
138 <label for="project-theme">Project theme</label>
139 <select id="project-theme" name="theme_id">
140 {% for t in theme_options %}
141 <option value="{{ t.id }}"{% if t.selected %} selected{% endif %}>{{ t.name }}</option>
142 {% endfor %}
143 </select>
144 <div class="hint">Items in this project inherit its theme.</div>
145 </div>
146
147 <button class="btn-primary" type="submit">
148 Save Theme
149 <span id="project-theme-spinner" class="htmx-indicator"> ...</span>
150 </button>
151 <span id="project-theme-save-status"></span>
152 </form>
153 </div>
154
155 <div class="form-section">
156 <h2 class="subsection-title">Custom page</h2>
157 <p class="section-lead">
158 Write your own HTML and CSS for this project's public page. Its items
159 inherit the page's styling. No JavaScript, on-platform links only.
160 </p>
161 <a class="btn-secondary" href="/dashboard/project/{{ project.slug }}/custom-page">Edit custom page</a>
162 </div>
163
164 <div class="section-group-label">Project Management</div>
165
166 <div class="form-section">
167 <h2 class="subsection-title">Delete Project</h2>
168 <p class="section-lead">
169 Deleting this project is permanent and cannot be undone.
170 </p>
171 <button class="btn-danger"
172 hx-delete="/api/projects/{{ project.id }}"
173 hx-confirm="Delete project '{{ project.title }}'? This cannot be undone."
174 data-hx-nav="/dashboard">
175 Delete Project
176 </button>
177 </div>
178
179 <!-- Pages (project-level markdown sections) -->
180 <details class="content-section pages-toggle" id="psections-management"{% if !sections.is_empty() %} open{% endif %}>
181 <summary>
182 <h2 class="subsection-title">Pages (<span id="psection-count">{{ sections.len() }}</span>)</h2>
183 </summary>
184 <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 <code>#section-&lt;slug&gt;</code>. Max 10.</p>
185
186 <div id="psections-list">
187 {% if sections.is_empty() %}
188 <p id="psections-empty" class="empty-state">No pages yet. Common pages: Privacy Policy, Terms of Service, FAQ, Support.</p>
189 {% else %}
190 {% for section in sections %}
191 <div class="psection-row" data-id="{{ section.id }}">
192 <span class="psection-row-title">{{ section.title }}</span>
193 <code class="psection-row-anchor">#section-{{ section.slug }}</code>
194 <span class="psection-row-length">{{ section.body.chars().count() }} chars</span>
195 <button type="button" class="btn-secondary psection-edit-btn" data-id="{{ section.id }}"
196 data-title="{{ section.title }}">Edit</button>
197 <button type="button" class="btn-secondary psection-del-btn" data-id="{{ section.id }}">Delete</button>
198 </div>
199 <textarea data-body-for="{{ section.id }}" class="hidden">{{ section.body }}</textarea>
200 {% endfor %}
201 {% endif %}
202 </div>
203
204 <details class="psection-add-details" id="psection-add-details">
205 <summary>Add Page</summary>
206 <div class="psection-add-fields">
207 <div class="form-group">
208 <label for="new-psec-title">Title</label>
209 <input type="text" id="new-psec-title" placeholder="e.g. Privacy Policy, Terms..." autocomplete="off">
210 </div>
211 <div class="form-group">
212 <label for="new-psec-body">Body (Markdown)</label>
213 <textarea id="new-psec-body" rows="10" placeholder="Page content..."></textarea>
214 </div>
215 <button type="button" class="btn-secondary" id="add-psec-btn" data-project-id="{{ project.id }}">Add Page</button>
216 <span id="psec-add-status" class="field-status"></span>
217 </div>
218 </details>
219
220 <div id="psection-edit-modal" class="psection-edit-modal hidden">
221 <input type="hidden" id="edit-psec-id">
222 <div class="form-group">
223 <label for="edit-psec-title">Title</label>
224 <input type="text" id="edit-psec-title" autocomplete="off">
225 </div>
226 <div class="form-group">
227 <label for="edit-psec-body">Body (Markdown)</label>
228 <textarea id="edit-psec-body" rows="10"></textarea>
229 </div>
230 <div class="psection-edit-actions">
231 <button type="button" class="btn-primary btn-compact" id="save-psec-btn">Save</button>
232 <button type="button" class="btn-secondary btn-compact" id="cancel-psec-btn">Cancel</button>
233 </div>
234 <span id="psec-edit-status" class="field-status"></span>
235 </div>
236 </details>
237 <script src="/static/project-sections.js" defer></script>
238 <!-- Single consolidated settings-tab script: image upload, gallery manager, and
239 category dropdown, loaded at the end so all referenced DOM is present. -->
240 <script src="/static/tab-project-settings.js?v=0702"></script>
241