Skip to main content

max / makenotwork

9.9 KB · 243 lines History Blame Raw
1 (function() {
2 var input = document.getElementById('project-image-input');
3 if (!input) return;
4 var __cfg = document.getElementById('tab-project-settings-cfg');
5 var projectId = __cfg ? __cfg.dataset.projectId : '';
6 input.addEventListener('change', function() {
7 var file = this.files[0];
8 if (!file) return;
9 var status = document.getElementById('project-image-status');
10 status.textContent = 'Uploading...';
11
12 fetch('/api/projects/image/presign', {
13 method: 'POST',
14 headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
15 body: JSON.stringify({ project_id: projectId, file_name: file.name, content_type: file.type || 'image/jpeg' })
16 })
17 .then(function(res) { if (!res.ok) throw new Error('Presign failed'); return res.json(); })
18 .then(function(data) {
19 var xhr = new XMLHttpRequest();
20 xhr.open('PUT', data.upload_url);
21 xhr.setRequestHeader('Content-Type', file.type || 'image/jpeg');
22 if (data.cache_control) xhr.setRequestHeader('Cache-Control', data.cache_control);
23 return new Promise(function(resolve, reject) {
24 xhr.onload = function() { xhr.status < 300 ? resolve(data.s3_key) : reject(new Error('Upload failed')); };
25 xhr.onerror = function() { reject(new Error('Network error')); };
26 xhr.send(file);
27 });
28 })
29 .then(function(s3Key) {
30 return fetch('/api/projects/image/confirm', {
31 method: 'POST',
32 headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
33 body: JSON.stringify({ project_id: projectId, s3_key: s3Key })
34 });
35 })
36 .then(function(res) { if (!res.ok) throw new Error('Confirm failed'); return res.json(); })
37 .then(function(data) {
38 status.textContent = 'Saved.';
39 var preview = document.getElementById('project-image-preview');
40 preview.innerHTML = '<img src="' + data.image_url + '" alt="Project image">';
41 setTimeout(function() { status.textContent = ''; }, 2000);
42 })
43 .catch(function(err) { status.textContent = err.message; });
44 });
45 })();
46
47 // ---------------------------------------------------------------------------
48 // Gallery manager (was tab-project-settings-2.js)
49 // ---------------------------------------------------------------------------
50 // This tab can arrive via an HTMX swap (DOMContentLoaded won't fire), so init
51 // immediately, lazy-loading gallery.js if it isn't present yet.
52 (function() {
53 var __cfg = document.getElementById('tab-project-settings-cfg');
54 var projectId = __cfg ? __cfg.dataset.projectId : '';
55 function go() {
56 initGalleryManager({
57 targetType: 'project',
58 targetId: projectId,
59 listId: 'project-gallery-list',
60 inputId: 'project-gallery-input',
61 statusId: 'project-gallery-status'
62 });
63 }
64 if (window.initGalleryManager) { go(); return; }
65 var s = document.createElement('script');
66 s.src = '/static/gallery.js';
67 s.onload = go;
68 document.head.appendChild(s);
69 })();
70
71 // ---------------------------------------------------------------------------
72 // Category suggestion dropdown (was tab-project-settings-3.js)
73 // ---------------------------------------------------------------------------
74 (function() {
75 // Category suggestion dropdown
76 var input = document.getElementById('settings-category');
77 var dropdown = document.getElementById('settings-category-dropdown');
78 if (!input || !dropdown) return;
79 var debounce;
80
81 function showDropdown(items, query) {
82 dropdown.innerHTML = '';
83 items.forEach(function(c) {
84 var div = document.createElement('div');
85 div.className = 'suggestion-item';
86 div.textContent = c.name;
87 div.addEventListener('mousedown', function(e) {
88 e.preventDefault();
89 input.value = c.name;
90 dropdown.classList.remove('open');
91 });
92 dropdown.appendChild(div);
93 });
94 var q = query.trim();
95 if (q.length > 0 && !items.some(function(c) { return c.name.toLowerCase() === q.toLowerCase(); })) {
96 var create = document.createElement('div');
97 create.className = 'suggestion-item suggestion-create';
98 create.textContent = 'Create: ' + q;
99 create.addEventListener('mousedown', function(e) {
100 e.preventDefault();
101 input.value = q;
102 dropdown.classList.remove('open');
103 });
104 dropdown.appendChild(create);
105 }
106 if (dropdown.children.length > 0) {
107 dropdown.classList.add('open');
108 } else {
109 dropdown.classList.remove('open');
110 }
111 }
112
113 input.addEventListener('input', function() {
114 clearTimeout(debounce);
115 var q = input.value.trim();
116 if (q.length < 1) { dropdown.classList.remove('open'); return; }
117 debounce = setTimeout(function() {
118 fetch('/api/categories/search?q=' + encodeURIComponent(q))
119 .then(function(r) { return r.json(); })
120 .then(function(cats) { showDropdown(cats, q); })
121 .catch(function() {});
122 }, 200);
123 });
124
125 input.addEventListener('focus', function() {
126 if (dropdown.children.length > 0) dropdown.classList.add('open');
127 });
128
129 input.addEventListener('blur', function() {
130 setTimeout(function() { dropdown.classList.remove('open'); }, 150);
131 });
132 })();
133
134 // Project info save
135 function saveProjectInfo(e, projectId) {
136 e.preventDefault();
137 var status = document.getElementById('project-save-status');
138 var data = {
139 title: document.getElementById('project-name').value,
140 description: document.getElementById('project-description').value,
141 category: document.getElementById('settings-category').value
142 };
143 fetch('/api/projects/' + projectId, {
144 method: 'PUT',
145 headers: {'Content-Type': 'application/json', ...csrfHeaders()},
146 body: JSON.stringify(data)
147 }).then(function(r) {
148 if (r.ok) {
149 if (status) { status.textContent = 'Saved'; status.style.color = 'var(--success)'; }
150 setTimeout(function() { if (status) status.textContent = ''; }, 2000);
151 } else {
152 r.text().then(function(body) {
153 var msg = 'Save failed';
154 try { msg = JSON.parse(body).error || msg; } catch(_) {}
155 if (status) { status.textContent = msg; status.style.color = 'var(--danger)'; }
156 });
157 }
158 }).catch(function() {
159 if (status) { status.textContent = 'Network error'; status.style.color = 'var(--danger)'; }
160 });
161 return false;
162 }
163
164 // Monetization save
165 function updateSettingsPricingUI() {
166 var model = document.getElementById('settings-pricing-model').value;
167 var sections = [
168 { id: 'settings-buy-once-fields', active: model === 'buy_once' },
169 { id: 'settings-pwyw-fields', active: model === 'pwyw' },
170 { id: 'settings-subscription-note', active: model === 'subscription' }
171 ];
172 sections.forEach(function(s) {
173 var el = document.getElementById(s.id);
174 if (el) el.classList.toggle('hidden', !s.active);
175 });
176 }
177 updateSettingsPricingUI();
178
179 function saveProjectPricing(e, projectId) {
180 e.preventDefault();
181 var status = document.getElementById('project-pricing-status');
182 var model = document.getElementById('settings-pricing-model').value;
183 var data = { pricing_model: model };
184
185 if (model === 'buy_once') {
186 var p = parseFloat(document.getElementById('settings-price-dollars').value);
187 if (!(p >= 0.50)) {
188 if (status) { status.textContent = 'Price must be at least $0.50'; status.style.color = 'var(--danger)'; }
189 return false;
190 }
191 data.price_dollars = p;
192 } else if (model === 'pwyw') {
193 var v = document.getElementById('settings-pwyw-min-dollars').value;
194 data.pwyw_min_dollars = v === '' ? 0 : parseFloat(v);
195 }
196
197 fetch('/api/projects/' + projectId, {
198 method: 'PUT',
199 headers: {'Content-Type': 'application/json', ...csrfHeaders()},
200 body: JSON.stringify(data)
201 }).then(function(r) {
202 if (r.ok) {
203 if (status) { status.textContent = 'Saved'; status.style.color = 'var(--success)'; }
204 setTimeout(function() { if (status) status.textContent = ''; }, 2000);
205 } else {
206 r.text().then(function(body) {
207 var msg = 'Save failed';
208 try { msg = JSON.parse(body).error || msg; } catch(_) {}
209 if (status) { status.textContent = msg; status.style.color = 'var(--danger)'; }
210 });
211 }
212 }).catch(function() {
213 if (status) { status.textContent = 'Network error'; status.style.color = 'var(--danger)'; }
214 });
215 return false;
216 }
217
218 // Features update
219 function updateFeatures(projectId) {
220 var checkboxes = document.querySelectorAll('#features-grid input[type="checkbox"]');
221 var selected = [];
222 checkboxes.forEach(function(cb) {
223 if (cb.checked) selected.push(cb.value);
224 });
225 var status = document.getElementById('features-save-status');
226
227 fetch('/api/projects/' + projectId, {
228 method: 'PUT',
229 headers: {'Content-Type': 'application/json', ...csrfHeaders()},
230 body: JSON.stringify({features: selected})
231 }).then(function(r) {
232 if (r.ok) {
233 if (status) { status.textContent = 'Saved'; status.style.color = 'var(--success)'; }
234 // Reload the page so the tab bar reflects new features
235 setTimeout(function() { window.location.reload(); }, 300);
236 } else {
237 if (status) { status.textContent = 'Save failed'; status.style.color = 'var(--danger)'; }
238 }
239 }).catch(function() {
240 if (status) { status.textContent = 'Network error'; status.style.color = 'var(--danger)'; }
241 });
242 }
243