Skip to main content

max / makenotwork

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