astro-tractstack 2.1.3 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -266
- package/bin/create-tractstack.js +9 -6
- package/dist/index.js +109 -71
- package/package.json +4 -2
- package/templates/css/custom.css +5 -0
- package/templates/icons/code.svg +18 -0
- package/templates/icons/li.svg +4 -0
- package/templates/icons/link.svg +22 -0
- package/templates/icons/p.svg +3 -0
- package/templates/src/client/app.js +80 -1
- package/templates/src/components/Footer.astro +1 -1
- package/templates/src/components/codehooks/BunnyVideoSetup.tsx +6 -6
- package/templates/src/components/codehooks/EpinetDurationSelector.tsx +3 -3
- package/templates/src/components/codehooks/FeaturedArticleSetup.tsx +1 -1
- package/templates/src/components/codehooks/ListContentSetup.tsx +2 -2
- package/templates/src/components/codehooks/ProductCardSetup.tsx +1 -1
- package/templates/src/components/codehooks/ProductGridSetup.tsx +2 -2
- package/templates/src/components/codehooks/SandboxRegisterForm.tsx +3 -3
- package/templates/src/components/compositor/Compositor.tsx +25 -9
- package/templates/src/components/compositor/Node.tsx +168 -496
- package/templates/src/components/compositor/PanelVisibilityWrapper.tsx +1 -0
- package/templates/src/components/compositor/elements/SignUp.tsx +1 -1
- package/templates/src/components/compositor/elements/YouTubeWrapper.tsx +2 -0
- package/templates/src/components/compositor/nodes/CreativePane.tsx +262 -0
- package/templates/src/components/compositor/nodes/GhostInsertBlock.tsx +4 -6
- package/templates/src/components/compositor/nodes/GridLayout.tsx +4 -2
- package/templates/src/components/compositor/nodes/Markdown.tsx +18 -3
- package/templates/src/components/compositor/nodes/Pane.tsx +11 -5
- package/templates/src/components/compositor/nodes/RenderChildren.tsx +1 -1
- package/templates/src/components/compositor/nodes/tagElements/NodeAnchorComponent.tsx +5 -5
- package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag.tsx +90 -42
- package/templates/src/components/compositor/nodes/tagElements/NodeImg.tsx +2 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeText.tsx +27 -1
- package/templates/src/components/compositor/preview/PaneSnapshotGenerator.tsx +10 -8
- package/templates/src/components/compositor/tools/NodeOverlay.tsx +224 -0
- package/templates/src/components/compositor/tools/PaneOverlay.tsx +122 -0
- package/templates/src/components/edit/Header.tsx +68 -9
- package/templates/src/components/edit/PanelSwitch.tsx +42 -4
- package/templates/src/components/edit/SettingsPanel.tsx +2 -3
- package/templates/src/components/edit/ToolMode.tsx +1 -31
- package/templates/src/components/edit/pane/AddPanePanel_break.tsx +2 -2
- package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +1 -1
- package/templates/src/components/edit/pane/AddPanePanel_new.tsx +193 -659
- package/templates/src/components/edit/pane/AddPanePanel_reuse.tsx +15 -82
- package/templates/src/components/edit/pane/AiRestylePaneModal.tsx +95 -45
- package/templates/src/components/edit/pane/ConfigPanePanel.tsx +137 -49
- package/templates/src/components/edit/pane/RestylePaneModal.tsx +1 -1
- package/templates/src/components/edit/pane/steps/AiCreativeDesignStep.tsx +375 -0
- package/templates/src/components/edit/pane/steps/AiDesignStep.tsx +1 -23
- package/templates/src/components/edit/pane/steps/AiLibraryCopyStep.tsx +327 -0
- package/templates/src/components/edit/pane/steps/AiRefineDesignStep.tsx +267 -0
- package/templates/src/components/edit/pane/steps/AiStandardDesignStep.tsx +371 -0
- package/templates/src/components/edit/pane/steps/CopyInputStep.tsx +201 -76
- package/templates/src/components/edit/pane/steps/CreativeInjectStep.tsx +141 -0
- package/templates/src/components/edit/panels/CreativeImagePanel.tsx +435 -0
- package/templates/src/components/edit/panels/CreativeLinkPanel.tsx +110 -0
- package/templates/src/components/edit/panels/StyleCodeHookPanel.tsx +1 -1
- package/templates/src/components/edit/panels/StyleParentPanel.tsx +118 -126
- package/templates/src/components/edit/panels/StyleParentPanel_add.tsx +3 -2
- package/templates/src/components/edit/panels/StyleParentPanel_deleteLayer.tsx +1 -0
- package/templates/src/components/edit/panels/StyleParentPanel_remove.tsx +3 -1
- package/templates/src/components/edit/panels/StyleParentPanel_update.tsx +3 -1
- package/templates/src/components/edit/panels/StyleWidgetPanel.tsx +1 -1
- package/templates/src/components/edit/state/SaveModal.tsx +19 -787
- package/templates/src/components/edit/state/SaveToLibraryModal.tsx +2 -2
- package/templates/src/components/edit/storyfragment/StoryFragmentPanel_menu.tsx +1 -1
- package/templates/src/components/edit/widgets/BunnyWidget.tsx +5 -5
- package/templates/src/components/edit/widgets/InteractiveDisclosureWidget.tsx +1 -1
- package/templates/src/components/edit/widgets/SignupWidget.tsx +1 -1
- package/templates/src/components/fields/ActionBuilderTimeSelector.tsx +1 -1
- package/templates/src/components/fields/ArtpackImage.tsx +11 -3
- package/templates/src/components/fields/BackgroundImage.tsx +8 -0
- package/templates/src/components/fields/BackgroundImageWrapper.tsx +15 -9
- package/templates/src/components/fields/ImageUpload.tsx +6 -0
- package/templates/src/components/form/ActionBuilderField.tsx +15 -5
- package/templates/src/components/form/ActionBuilderSlugSelector.tsx +1 -1
- package/templates/src/components/form/ColorPicker.tsx +1 -1
- package/templates/src/components/form/EnumSelect.tsx +1 -1
- package/templates/src/components/form/NumberInput.tsx +1 -1
- package/templates/src/components/form/StringArrayInput.tsx +1 -1
- package/templates/src/components/form/StringInput.tsx +1 -1
- package/templates/src/components/form/UnsavedChangesBar.tsx +1 -1
- package/templates/src/components/form/advanced/APIConfigSection.tsx +2 -2
- package/templates/src/components/form/advanced/AuthConfigSection.tsx +2 -2
- package/templates/src/components/profile/ProfileCreate.tsx +1 -1
- package/templates/src/components/profile/ProfileEdit.tsx +1 -1
- package/templates/src/components/storykeep/Dashboard_Advanced.tsx +2 -2
- package/templates/src/components/storykeep/controls/content/BeliefForm.tsx +1 -1
- package/templates/src/components/storykeep/controls/content/ContentSummary.tsx +2 -2
- package/templates/src/components/storykeep/controls/content/KnownResourceTable.tsx +1 -1
- package/templates/src/components/storykeep/controls/content/ManageContent.tsx +6 -6
- package/templates/src/components/storykeep/controls/content/MenuForm.tsx +1 -1
- package/templates/src/components/storykeep/controls/content/PaneTable.tsx +358 -0
- package/templates/src/components/storykeep/controls/content/ResourceTable.tsx +1 -1
- package/templates/src/constants/prompts.json +18 -10
- package/templates/src/constants.ts +3 -0
- package/templates/src/hooks/usePaneFragments.ts +60 -0
- package/templates/src/lib/session.ts +71 -16
- package/templates/src/pages/[...slug].astro +4 -46
- package/templates/src/pages/api/css.ts +149 -0
- package/templates/src/pages/maint.astro +1 -1
- package/templates/src/pages/storykeep/login.astro +2 -2
- package/templates/src/stores/nodes.ts +162 -49
- package/templates/src/stores/orphanAnalysis.ts +6 -30
- package/templates/src/stores/previews.ts +7 -0
- package/templates/src/stores/storykeep.ts +0 -8
- package/templates/src/types/compositorTypes.ts +53 -10
- package/templates/src/utils/compositor/aiGeneration.ts +93 -0
- package/templates/src/utils/compositor/allowInsert.ts +2 -0
- package/templates/src/utils/compositor/htmlAst.ts +704 -0
- package/templates/src/utils/compositor/nodesHelper.ts +281 -102
- package/templates/src/utils/compositor/savePipeline.ts +893 -0
- package/templates/src/utils/etl/index.ts +3 -0
- package/templates/src/utils/etl/transformer.ts +10 -0
- package/templates/src/utils/helpers.ts +101 -0
- package/utils/inject-files.ts +100 -62
- package/templates/icons/text.svg +0 -6
- package/templates/src/components/compositor/NodeWithGuid.tsx +0 -69
- package/templates/src/components/compositor/nodes/GridLayout_eraser.tsx +0 -33
- package/templates/src/components/compositor/nodes/Markdown_eraser.tsx +0 -56
- package/templates/src/components/compositor/nodes/Pane_DesignLibrary.tsx +0 -269
- package/templates/src/components/compositor/nodes/Pane_eraser.tsx +0 -186
- package/templates/src/components/compositor/nodes/Pane_layout.tsx +0 -79
- package/templates/src/components/compositor/nodes/tagElements/NodeA_eraser.tsx +0 -26
- package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_eraser.tsx +0 -61
- package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_insert.tsx +0 -120
- package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_settings.tsx +0 -62
- package/templates/src/components/compositor/nodes/tagElements/NodeButton_eraser.tsx +0 -26
|
@@ -3,25 +3,10 @@ import { Dialog } from '@ark-ui/react/dialog';
|
|
|
3
3
|
import { Portal } from '@ark-ui/react/portal';
|
|
4
4
|
import { navigate } from 'astro:transitions/client';
|
|
5
5
|
import { getCtx } from '@/stores/nodes';
|
|
6
|
-
import {
|
|
7
|
-
transformLivePaneForSave,
|
|
8
|
-
transformStoryFragmentForSave,
|
|
9
|
-
} from '@/utils/etl/index';
|
|
10
|
-
import {
|
|
11
|
-
getPendingImageOperation,
|
|
12
|
-
clearPendingImageOperation,
|
|
13
|
-
pendingHomePageSlugStore,
|
|
14
|
-
} from '@/stores/storykeep';
|
|
6
|
+
import { pendingHomePageSlugStore } from '@/stores/storykeep';
|
|
15
7
|
import { startLoadingAnimation } from '@/utils/helpers';
|
|
16
|
-
import {
|
|
17
|
-
import type {
|
|
18
|
-
FlatNode,
|
|
19
|
-
BaseNode,
|
|
20
|
-
PaneNode,
|
|
21
|
-
StoryFragmentNode,
|
|
22
|
-
MarkdownPaneFragmentNode,
|
|
23
|
-
GridLayoutNode,
|
|
24
|
-
} from '@/types/compositorTypes';
|
|
8
|
+
import { executeSavePipeline } from '@/utils/compositor/savePipeline';
|
|
9
|
+
import type { PaneNode, StoryFragmentNode } from '@/types/compositorTypes';
|
|
25
10
|
|
|
26
11
|
type SaveStage =
|
|
27
12
|
| 'PREPARING'
|
|
@@ -52,13 +37,6 @@ interface SaveModalProps {
|
|
|
52
37
|
hydrate?: boolean;
|
|
53
38
|
}
|
|
54
39
|
|
|
55
|
-
const PROGRESS_PHASES = {
|
|
56
|
-
PREPARATION: 5,
|
|
57
|
-
UPLOADS: 60,
|
|
58
|
-
PROCESSING: 25,
|
|
59
|
-
FINALIZATION: 10,
|
|
60
|
-
};
|
|
61
|
-
|
|
62
40
|
const INDETERMINATE_STAGES: SaveStage[] = [
|
|
63
41
|
'COOKING_NODES',
|
|
64
42
|
'SAVING_PANES',
|
|
@@ -168,773 +146,27 @@ export default function SaveModal({
|
|
|
168
146
|
const runSaveProcess = async () => {
|
|
169
147
|
isSaving.current = true;
|
|
170
148
|
const ctx = getCtx();
|
|
171
|
-
const allDirtyNodes = ctx.getDirtyNodes();
|
|
172
149
|
|
|
173
150
|
try {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
isContext
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
);
|
|
185
|
-
let dirtyStoryFragments = allDirtyNodes.filter(
|
|
186
|
-
(node) => node.nodeType === 'StoryFragment'
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
if (isContext) {
|
|
190
|
-
dirtyStoryFragments = [];
|
|
191
|
-
addDebugMessage('Context mode: Ignoring StoryFragment nodes');
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const nodesWithPendingFiles = allDirtyNodes.filter(
|
|
195
|
-
(node): node is BaseNode & { base64Data?: string } =>
|
|
196
|
-
'base64Data' in node && !!node.base64Data
|
|
197
|
-
);
|
|
198
|
-
const storyFragmentsWithPendingImages = dirtyStoryFragments.filter(
|
|
199
|
-
(fragment) => {
|
|
200
|
-
const pendingOp = getPendingImageOperation(fragment.id);
|
|
201
|
-
return pendingOp && pendingOp.type === 'upload';
|
|
202
|
-
}
|
|
203
|
-
);
|
|
204
|
-
|
|
205
|
-
const totalFileBytes = nodesWithPendingFiles.reduce(
|
|
206
|
-
(sum, node) => sum + (node.base64Data?.length || 0),
|
|
207
|
-
0
|
|
208
|
-
);
|
|
209
|
-
const totalOgBytes = storyFragmentsWithPendingImages.reduce(
|
|
210
|
-
(sum, fragment) => {
|
|
211
|
-
const pendingOp = getPendingImageOperation(fragment.id);
|
|
212
|
-
return sum + (pendingOp?.data?.length || 0);
|
|
151
|
+
await executeSavePipeline(
|
|
152
|
+
ctx,
|
|
153
|
+
{
|
|
154
|
+
slug,
|
|
155
|
+
isContext,
|
|
156
|
+
isCreateMode,
|
|
157
|
+
hydrate,
|
|
158
|
+
tenantId,
|
|
159
|
+
backendUrl: goBackend,
|
|
160
|
+
pendingHomePageSlug,
|
|
213
161
|
},
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
dirtyPanes.length + dirtyStoryFragments.length;
|
|
221
|
-
addDebugMessage(
|
|
222
|
-
`Found ${relevantNodeCount} relevant dirty nodes to save (${dirtyPanes.length} Panes, ${dirtyStoryFragments.length} StoryFragments)`
|
|
223
|
-
);
|
|
224
|
-
addDebugMessage(
|
|
225
|
-
`Found ${storyFragmentsWithPendingImages.length} story fragments with pending OG image operations`
|
|
226
|
-
);
|
|
227
|
-
addDebugMessage(
|
|
228
|
-
`Found ${nodesWithPendingFiles.length} nodes with pending file uploads`
|
|
229
|
-
);
|
|
230
|
-
|
|
231
|
-
if (
|
|
232
|
-
relevantNodeCount === 0 &&
|
|
233
|
-
nodesWithPendingFiles.length === 0 &&
|
|
234
|
-
storyFragmentsWithPendingImages.length === 0 &&
|
|
235
|
-
!pendingHomePageSlug
|
|
236
|
-
) {
|
|
237
|
-
addDebugMessage('No changes to save');
|
|
238
|
-
setStage('COMPLETED');
|
|
239
|
-
setProgress(100);
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
const uploadedOGPaths: Record<string, string> = {};
|
|
244
|
-
|
|
245
|
-
if (nodesWithPendingFiles.length > 0) {
|
|
246
|
-
setStage('SAVING_PENDING_FILES');
|
|
247
|
-
for (let i = 0; i < nodesWithPendingFiles.length; i++) {
|
|
248
|
-
const fileNode = nodesWithPendingFiles[i];
|
|
249
|
-
const fileBytes = fileNode.base64Data?.length || 0;
|
|
250
|
-
const endpoint = `${goBackend}/api/v1/nodes/files/create`;
|
|
251
|
-
|
|
252
|
-
setStageProgress({
|
|
253
|
-
currentStep: i + 1,
|
|
254
|
-
totalSteps: nodesWithPendingFiles.length,
|
|
255
|
-
currentFileName: `${fileNode.id}.jpg`,
|
|
256
|
-
isUploading: true,
|
|
257
|
-
});
|
|
258
|
-
addDebugMessage(
|
|
259
|
-
`Processing file ${i + 1}/${nodesWithPendingFiles.length}: ${
|
|
260
|
-
fileNode.id
|
|
261
|
-
} -> POST ${endpoint}`
|
|
262
|
-
);
|
|
263
|
-
|
|
264
|
-
try {
|
|
265
|
-
const response = await fetch(endpoint, {
|
|
266
|
-
method: 'POST',
|
|
267
|
-
headers: {
|
|
268
|
-
'Content-Type': 'application/json',
|
|
269
|
-
'X-Tenant-ID': tenantId,
|
|
270
|
-
},
|
|
271
|
-
credentials: 'include',
|
|
272
|
-
body: JSON.stringify({ base64Data: fileNode.base64Data }),
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
if (!response.ok) {
|
|
276
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
const result = await response.json();
|
|
280
|
-
const updatedNode = { ...fileNode, isChanged: true };
|
|
281
|
-
|
|
282
|
-
if ('base64Data' in updatedNode) {
|
|
283
|
-
delete updatedNode.base64Data;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
if ('fileId' in updatedNode) {
|
|
287
|
-
updatedNode.fileId = result.fileId;
|
|
288
|
-
}
|
|
289
|
-
if ('src' in updatedNode) {
|
|
290
|
-
updatedNode.src = result.src;
|
|
291
|
-
}
|
|
292
|
-
if ('srcSet' in updatedNode && result.srcSet) {
|
|
293
|
-
updatedNode.srcSet = result.srcSet;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
ctx.modifyNodes([updatedNode]);
|
|
297
|
-
|
|
298
|
-
addDebugMessage(
|
|
299
|
-
`File ${fileNode.id} uploaded successfully - got fileId: ${result.fileId}`
|
|
300
|
-
);
|
|
301
|
-
} catch (error) {
|
|
302
|
-
const errorMsg =
|
|
303
|
-
error instanceof Error ? error.message : 'Unknown error';
|
|
304
|
-
addDebugMessage(`File ${fileNode.id} upload failed: ${errorMsg}`);
|
|
305
|
-
throw new Error(
|
|
306
|
-
`Failed to upload file ${fileNode.id}: ${errorMsg}`
|
|
307
|
-
);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
completedUploadBytes += fileBytes;
|
|
311
|
-
const uploadProgress =
|
|
312
|
-
totalUploadBytes > 0
|
|
313
|
-
? (completedUploadBytes / totalUploadBytes) *
|
|
314
|
-
PROGRESS_PHASES.UPLOADS
|
|
315
|
-
: 0;
|
|
316
|
-
setProgress(PROGRESS_PHASES.PREPARATION + uploadProgress);
|
|
317
|
-
setStageProgress((prev) => ({ ...prev, isUploading: false }));
|
|
162
|
+
{
|
|
163
|
+
setStage,
|
|
164
|
+
setProgress,
|
|
165
|
+
setStageProgress,
|
|
166
|
+
logDebug: addDebugMessage,
|
|
167
|
+
setIsIndeterminateStage,
|
|
318
168
|
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
if (storyFragmentsWithPendingImages.length > 0) {
|
|
322
|
-
setStage('PROCESSING_OG_IMAGES');
|
|
323
|
-
for (let i = 0; i < storyFragmentsWithPendingImages.length; i++) {
|
|
324
|
-
const fragment = storyFragmentsWithPendingImages[i];
|
|
325
|
-
const pendingOp = getPendingImageOperation(fragment.id);
|
|
326
|
-
const imageBytes = pendingOp?.data?.length || 0;
|
|
327
|
-
|
|
328
|
-
if (pendingOp && pendingOp.type === 'upload' && pendingOp.data) {
|
|
329
|
-
const ogUploadEndpoint = `${goBackend}/api/v1/nodes/images/og`;
|
|
330
|
-
addDebugMessage(
|
|
331
|
-
`Processing OG image ${i + 1}/${
|
|
332
|
-
storyFragmentsWithPendingImages.length
|
|
333
|
-
}: ${fragment.id} -> POST ${ogUploadEndpoint}`
|
|
334
|
-
);
|
|
335
|
-
|
|
336
|
-
setStageProgress({
|
|
337
|
-
currentStep: i + 1,
|
|
338
|
-
totalSteps: storyFragmentsWithPendingImages.length,
|
|
339
|
-
currentFileName: pendingOp?.filename || `${fragment.id}-og.png`,
|
|
340
|
-
isUploading: true,
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
const uploadPayload = {
|
|
344
|
-
data: pendingOp.data,
|
|
345
|
-
filename:
|
|
346
|
-
pendingOp.filename || `${fragment.id}-${Date.now()}.png`,
|
|
347
|
-
};
|
|
348
|
-
|
|
349
|
-
try {
|
|
350
|
-
const response = await fetch(ogUploadEndpoint, {
|
|
351
|
-
method: 'POST',
|
|
352
|
-
headers: {
|
|
353
|
-
'Content-Type': 'application/json',
|
|
354
|
-
'X-Tenant-ID': tenantId,
|
|
355
|
-
},
|
|
356
|
-
credentials: 'include',
|
|
357
|
-
body: JSON.stringify(uploadPayload),
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
if (!response.ok) {
|
|
361
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
const result = await response.json();
|
|
365
|
-
|
|
366
|
-
uploadedOGPaths[fragment.id] = result.path;
|
|
367
|
-
addDebugMessage(
|
|
368
|
-
`OG image uploaded successfully: ${result.path}`
|
|
369
|
-
);
|
|
370
|
-
} catch (error) {
|
|
371
|
-
const errorMsg =
|
|
372
|
-
error instanceof Error ? error.message : 'Unknown error';
|
|
373
|
-
addDebugMessage(
|
|
374
|
-
`OG image upload failed for ${fragment.id}: ${errorMsg}`
|
|
375
|
-
);
|
|
376
|
-
throw new Error(
|
|
377
|
-
`Failed to upload OG image for ${fragment.id}: ${errorMsg}`
|
|
378
|
-
);
|
|
379
|
-
}
|
|
380
|
-
completedUploadBytes += imageBytes;
|
|
381
|
-
const uploadProgress =
|
|
382
|
-
totalUploadBytes > 0
|
|
383
|
-
? (completedUploadBytes / totalUploadBytes) *
|
|
384
|
-
PROGRESS_PHASES.UPLOADS
|
|
385
|
-
: 0;
|
|
386
|
-
setProgress(PROGRESS_PHASES.PREPARATION + uploadProgress);
|
|
387
|
-
setStageProgress((prev) => ({ ...prev, isUploading: false }));
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
if (totalUploadBytes > 0) {
|
|
393
|
-
setProgress(PROGRESS_PHASES.PREPARATION + PROGRESS_PHASES.UPLOADS);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
const totalProcessingSteps =
|
|
397
|
-
dirtyPanes.length + dirtyStoryFragments.length;
|
|
398
|
-
let completedProcessingSteps = 0;
|
|
399
|
-
|
|
400
|
-
// --- NEW COOKING STAGE ---
|
|
401
|
-
if (allDirtyNodes.length > 0) {
|
|
402
|
-
setStage('COOKING_NODES');
|
|
403
|
-
setIsIndeterminateStage(true);
|
|
404
|
-
addDebugMessage('Cooking nodes for whitelist extraction...');
|
|
405
|
-
|
|
406
|
-
const cookingUpdates: BaseNode[] = [];
|
|
407
|
-
|
|
408
|
-
allDirtyNodes.forEach((liveNode) => {
|
|
409
|
-
try {
|
|
410
|
-
let updatedNode: BaseNode | null = null;
|
|
411
|
-
|
|
412
|
-
// Pattern 1: TagElements -> elementCss
|
|
413
|
-
if (liveNode.nodeType === 'TagElement') {
|
|
414
|
-
const flatNode = liveNode as FlatNode;
|
|
415
|
-
const computedCSS = ctx.getNodeClasses(flatNode.id, 'auto', 0);
|
|
416
|
-
if (flatNode.elementCss !== computedCSS) {
|
|
417
|
-
updatedNode = {
|
|
418
|
-
...liveNode,
|
|
419
|
-
elementCss: computedCSS,
|
|
420
|
-
} as FlatNode;
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
// Pattern 2: Markdown Nodes -> parentCss & gridCss
|
|
424
|
-
else if (liveNode.nodeType === 'Markdown') {
|
|
425
|
-
const markdownNode = liveNode as MarkdownPaneFragmentNode;
|
|
426
|
-
let needsUpdate = false;
|
|
427
|
-
const nextNode = { ...markdownNode };
|
|
428
|
-
|
|
429
|
-
// parentCss
|
|
430
|
-
if (markdownNode.parentClasses) {
|
|
431
|
-
const computedParentCss = markdownNode.parentClasses.map(
|
|
432
|
-
(_: any, index: number) =>
|
|
433
|
-
ctx.getNodeClasses(liveNode.id, 'auto', index)
|
|
434
|
-
);
|
|
435
|
-
if (
|
|
436
|
-
JSON.stringify(markdownNode.parentCss) !==
|
|
437
|
-
JSON.stringify(computedParentCss)
|
|
438
|
-
) {
|
|
439
|
-
nextNode.parentCss = computedParentCss;
|
|
440
|
-
needsUpdate = true;
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
// gridCss
|
|
445
|
-
if (markdownNode.gridClasses) {
|
|
446
|
-
const [allClasses] = processClassesForViewports(
|
|
447
|
-
markdownNode.gridClasses,
|
|
448
|
-
{},
|
|
449
|
-
1
|
|
450
|
-
);
|
|
451
|
-
if (allClasses && allClasses.length > 0) {
|
|
452
|
-
const computedGridCss = allClasses[0];
|
|
453
|
-
if (markdownNode.gridCss !== computedGridCss) {
|
|
454
|
-
nextNode.gridCss = computedGridCss;
|
|
455
|
-
needsUpdate = true;
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
if (needsUpdate) updatedNode = nextNode;
|
|
461
|
-
}
|
|
462
|
-
// Pattern 3: GridLayout Nodes -> parentCss & gridCss
|
|
463
|
-
else if (liveNode.nodeType === 'GridLayoutNode') {
|
|
464
|
-
const gridNode = liveNode as GridLayoutNode;
|
|
465
|
-
let needsUpdate = false;
|
|
466
|
-
const nextNode = { ...gridNode };
|
|
467
|
-
|
|
468
|
-
// parentCss
|
|
469
|
-
if (gridNode.parentClasses) {
|
|
470
|
-
const computedParentCss = gridNode.parentClasses.map(
|
|
471
|
-
(_: any, index: number) =>
|
|
472
|
-
ctx.getNodeClasses(liveNode.id, 'auto', index)
|
|
473
|
-
);
|
|
474
|
-
if (
|
|
475
|
-
JSON.stringify(gridNode.parentCss) !==
|
|
476
|
-
JSON.stringify(computedParentCss)
|
|
477
|
-
) {
|
|
478
|
-
nextNode.parentCss = computedParentCss.join(` `);
|
|
479
|
-
needsUpdate = true;
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
// gridCss
|
|
484
|
-
if (gridNode.gridColumns) {
|
|
485
|
-
const { mobile, tablet, desktop } = gridNode.gridColumns;
|
|
486
|
-
let computedGridCss = `grid grid-cols-${mobile}`;
|
|
487
|
-
if (tablet !== mobile) {
|
|
488
|
-
computedGridCss += ` md:grid-cols-${tablet}`;
|
|
489
|
-
}
|
|
490
|
-
if (desktop !== tablet) {
|
|
491
|
-
computedGridCss += ` xl:grid-cols-${desktop}`;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
if (gridNode.gridCss !== computedGridCss) {
|
|
495
|
-
nextNode.gridCss = computedGridCss;
|
|
496
|
-
needsUpdate = true;
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
if (needsUpdate) updatedNode = nextNode;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
if (updatedNode) {
|
|
504
|
-
cookingUpdates.push(updatedNode);
|
|
505
|
-
}
|
|
506
|
-
} catch (e) {
|
|
507
|
-
console.warn(`Failed to cook node ${liveNode.id}`, e);
|
|
508
|
-
}
|
|
509
|
-
});
|
|
510
|
-
|
|
511
|
-
if (cookingUpdates.length > 0) {
|
|
512
|
-
ctx.modifyNodes(cookingUpdates, {
|
|
513
|
-
notify: false,
|
|
514
|
-
recordHistory: false,
|
|
515
|
-
});
|
|
516
|
-
addDebugMessage(`Cooked ${cookingUpdates.length} nodes.`);
|
|
517
|
-
}
|
|
518
|
-
setIsIndeterminateStage(false);
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
// --- PROCESSING STYLES ---
|
|
522
|
-
// Moved before SAVING_PANES to ensure the whitelist is generated from the cooked, exhaustive inventory
|
|
523
|
-
setStage('PROCESSING_STYLES');
|
|
524
|
-
setIsIndeterminateStage(true);
|
|
525
|
-
const baseFinalizationProgress =
|
|
526
|
-
PROGRESS_PHASES.PREPARATION +
|
|
527
|
-
PROGRESS_PHASES.UPLOADS +
|
|
528
|
-
PROGRESS_PHASES.PROCESSING;
|
|
529
|
-
setProgress(
|
|
530
|
-
baseFinalizationProgress + PROGRESS_PHASES.FINALIZATION / 2
|
|
531
169
|
);
|
|
532
|
-
addDebugMessage(`Processing styles...`);
|
|
533
|
-
|
|
534
|
-
try {
|
|
535
|
-
const { dirtyPaneIds, classes: dirtyClasses } =
|
|
536
|
-
ctx.getDirtyNodesClassData();
|
|
537
|
-
|
|
538
|
-
const astroEndpoint = `/api/tailwind`;
|
|
539
|
-
const astroPayload = { dirtyPaneIds, dirtyClasses };
|
|
540
|
-
const astroResponse = await fetch(astroEndpoint, {
|
|
541
|
-
method: 'POST',
|
|
542
|
-
headers: {
|
|
543
|
-
'Content-Type': 'application/json',
|
|
544
|
-
'X-Tenant-ID': tenantId,
|
|
545
|
-
},
|
|
546
|
-
credentials: 'include',
|
|
547
|
-
body: JSON.stringify(astroPayload),
|
|
548
|
-
});
|
|
549
|
-
|
|
550
|
-
if (!astroResponse.ok) {
|
|
551
|
-
throw new Error(
|
|
552
|
-
`CSS generation failed! status: ${astroResponse.status}`
|
|
553
|
-
);
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
const astroResult = await astroResponse.json();
|
|
557
|
-
|
|
558
|
-
if (!astroResult.success || !astroResult.generatedCss) {
|
|
559
|
-
throw new Error('CSS generation failed: no CSS returned');
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
addDebugMessage(
|
|
563
|
-
`CSS generated: ${astroResult.generatedCss.length} bytes for ${dirtyClasses.length} classes`
|
|
564
|
-
);
|
|
565
|
-
|
|
566
|
-
const goEndpoint = `${goBackend}/api/v1/tailwind/update`;
|
|
567
|
-
const goPayload = { frontendCss: astroResult.generatedCss };
|
|
568
|
-
const goResponse = await fetch(goEndpoint, {
|
|
569
|
-
method: 'POST',
|
|
570
|
-
headers: {
|
|
571
|
-
'Content-Type': 'application/json',
|
|
572
|
-
'X-Tenant-ID': tenantId,
|
|
573
|
-
},
|
|
574
|
-
credentials: 'include',
|
|
575
|
-
body: JSON.stringify(goPayload),
|
|
576
|
-
});
|
|
577
|
-
|
|
578
|
-
if (!goResponse.ok) {
|
|
579
|
-
throw new Error(`CSS save failed! status: ${goResponse.status}`);
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
const goResult = await goResponse.json();
|
|
583
|
-
addDebugMessage(
|
|
584
|
-
`CSS saved successfully: stylesVer ${goResult.stylesVer}`
|
|
585
|
-
);
|
|
586
|
-
} catch (error) {
|
|
587
|
-
const errorMsg =
|
|
588
|
-
error instanceof Error ? error.message : 'Unknown error';
|
|
589
|
-
addDebugMessage(`Styles processing failed: ${errorMsg}`);
|
|
590
|
-
throw new Error(`Failed to process styles: ${errorMsg}`);
|
|
591
|
-
} finally {
|
|
592
|
-
setIsIndeterminateStage(false);
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
// --- SAVING PANES ---
|
|
596
|
-
// Runs after styles to ensure DB gets minimal, correct payload
|
|
597
|
-
if (dirtyPanes.length > 0) {
|
|
598
|
-
setStage('SAVING_PANES');
|
|
599
|
-
setIsIndeterminateStage(true);
|
|
600
|
-
setStageProgress({
|
|
601
|
-
currentStep: 0,
|
|
602
|
-
totalSteps: dirtyPanes.length,
|
|
603
|
-
});
|
|
604
|
-
|
|
605
|
-
const bulkPayload = dirtyPanes.map((paneNode) =>
|
|
606
|
-
transformLivePaneForSave(ctx, paneNode.id, isContext)
|
|
607
|
-
);
|
|
608
|
-
|
|
609
|
-
// Update context with minimal strings (idempotent, restoring runtime state)
|
|
610
|
-
bulkPayload.forEach((payload) => {
|
|
611
|
-
payload.optionsPayload.nodes.forEach((transformedNode) => {
|
|
612
|
-
const liveNode = ctx.allNodes.get().get(transformedNode.id);
|
|
613
|
-
if (!liveNode) return;
|
|
614
|
-
|
|
615
|
-
let needsUpdate = false;
|
|
616
|
-
let updatedNode: BaseNode = { ...liveNode };
|
|
617
|
-
|
|
618
|
-
if (
|
|
619
|
-
transformedNode.nodeType === 'TagElement' &&
|
|
620
|
-
transformedNode.elementCss
|
|
621
|
-
) {
|
|
622
|
-
const flatNode = liveNode as FlatNode;
|
|
623
|
-
if (flatNode.elementCss !== transformedNode.elementCss) {
|
|
624
|
-
(updatedNode as FlatNode).elementCss =
|
|
625
|
-
transformedNode.elementCss;
|
|
626
|
-
needsUpdate = true;
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
if (
|
|
631
|
-
transformedNode.nodeType === 'Markdown' &&
|
|
632
|
-
transformedNode.parentCss
|
|
633
|
-
) {
|
|
634
|
-
const markdownNode = liveNode as MarkdownPaneFragmentNode;
|
|
635
|
-
const currentParentCss = markdownNode.parentCss;
|
|
636
|
-
const newParentCss = transformedNode.parentCss as string[];
|
|
637
|
-
|
|
638
|
-
const isDifferent =
|
|
639
|
-
!currentParentCss ||
|
|
640
|
-
currentParentCss.length !== newParentCss.length ||
|
|
641
|
-
currentParentCss.some(
|
|
642
|
-
(css, index) => css !== newParentCss[index]
|
|
643
|
-
);
|
|
644
|
-
|
|
645
|
-
if (isDifferent) {
|
|
646
|
-
(updatedNode as MarkdownPaneFragmentNode).parentCss =
|
|
647
|
-
newParentCss;
|
|
648
|
-
needsUpdate = true;
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
if (needsUpdate) {
|
|
653
|
-
ctx.allNodes.get().set(transformedNode.id, updatedNode);
|
|
654
|
-
}
|
|
655
|
-
});
|
|
656
|
-
});
|
|
657
|
-
|
|
658
|
-
const endpoint = `${goBackend}/api/v1/nodes/panes/bulk`;
|
|
659
|
-
addDebugMessage(
|
|
660
|
-
`Processing ${dirtyPanes.length} panes via -> POST ${endpoint}`
|
|
661
|
-
);
|
|
662
|
-
|
|
663
|
-
try {
|
|
664
|
-
const response = await fetch(endpoint, {
|
|
665
|
-
method: 'POST',
|
|
666
|
-
headers: {
|
|
667
|
-
'Content-Type': 'application/json',
|
|
668
|
-
'X-Tenant-ID': tenantId,
|
|
669
|
-
},
|
|
670
|
-
credentials: 'include',
|
|
671
|
-
body: JSON.stringify({ panes: bulkPayload }),
|
|
672
|
-
});
|
|
673
|
-
|
|
674
|
-
if (!response.ok) {
|
|
675
|
-
const errorText = await response.text();
|
|
676
|
-
throw new Error(
|
|
677
|
-
`HTTP error! status: ${response.status} - ${errorText}`
|
|
678
|
-
);
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
await response.json();
|
|
682
|
-
addDebugMessage(
|
|
683
|
-
`${dirtyPanes.length} panes saved successfully via bulk endpoint.`
|
|
684
|
-
);
|
|
685
|
-
} catch (bulkError) {
|
|
686
|
-
const errorMsg =
|
|
687
|
-
bulkError instanceof Error
|
|
688
|
-
? bulkError.message
|
|
689
|
-
: 'Unknown bulk save error';
|
|
690
|
-
addDebugMessage(`Bulk pane save failed: ${errorMsg}`);
|
|
691
|
-
throw new Error(`Failed to save panes in bulk: ${errorMsg}`);
|
|
692
|
-
} finally {
|
|
693
|
-
setIsIndeterminateStage(false);
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
setStageProgress({
|
|
697
|
-
currentStep: dirtyPanes.length,
|
|
698
|
-
totalSteps: dirtyPanes.length,
|
|
699
|
-
});
|
|
700
|
-
completedProcessingSteps += dirtyPanes.length;
|
|
701
|
-
const processingProgress =
|
|
702
|
-
(completedProcessingSteps / totalProcessingSteps) *
|
|
703
|
-
PROGRESS_PHASES.PROCESSING;
|
|
704
|
-
setProgress(
|
|
705
|
-
PROGRESS_PHASES.PREPARATION +
|
|
706
|
-
PROGRESS_PHASES.UPLOADS +
|
|
707
|
-
processingProgress
|
|
708
|
-
);
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
if (!isContext && dirtyStoryFragments.length > 0) {
|
|
712
|
-
setStage('SAVING_STORY_FRAGMENTS');
|
|
713
|
-
setStageProgress({
|
|
714
|
-
currentStep: 0,
|
|
715
|
-
totalSteps: dirtyStoryFragments.length,
|
|
716
|
-
});
|
|
717
|
-
for (let i = 0; i < dirtyStoryFragments.length; i++) {
|
|
718
|
-
const fragment = dirtyStoryFragments[i];
|
|
719
|
-
|
|
720
|
-
try {
|
|
721
|
-
const payload = await transformStoryFragmentForSave(
|
|
722
|
-
ctx,
|
|
723
|
-
fragment.id,
|
|
724
|
-
tenantId
|
|
725
|
-
);
|
|
726
|
-
|
|
727
|
-
if (uploadedOGPaths[fragment.id]) {
|
|
728
|
-
payload.socialImagePath = uploadedOGPaths[fragment.id];
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
const endpoint = isCreateMode
|
|
732
|
-
? `${goBackend}/api/v1/nodes/storyfragments/create`
|
|
733
|
-
: `${goBackend}/api/v1/nodes/storyfragments/${payload.id}/complete`;
|
|
734
|
-
const method = isCreateMode ? 'POST' : 'PUT';
|
|
735
|
-
|
|
736
|
-
addDebugMessage(
|
|
737
|
-
`Processing story fragment ${i + 1}/${
|
|
738
|
-
dirtyStoryFragments.length
|
|
739
|
-
}: ${fragment.id} -> ${method} ${endpoint}`
|
|
740
|
-
);
|
|
741
|
-
|
|
742
|
-
const response = await fetch(endpoint, {
|
|
743
|
-
method,
|
|
744
|
-
headers: {
|
|
745
|
-
'Content-Type': 'application/json',
|
|
746
|
-
'X-Tenant-ID': tenantId,
|
|
747
|
-
},
|
|
748
|
-
credentials: 'include',
|
|
749
|
-
body: JSON.stringify(payload),
|
|
750
|
-
});
|
|
751
|
-
|
|
752
|
-
if (!response.ok) {
|
|
753
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
await response.json();
|
|
757
|
-
addDebugMessage(
|
|
758
|
-
`StoryFragment ${fragment.id} saved successfully`
|
|
759
|
-
);
|
|
760
|
-
|
|
761
|
-
if (uploadedOGPaths[fragment.id]) {
|
|
762
|
-
clearPendingImageOperation(fragment.id);
|
|
763
|
-
addDebugMessage(
|
|
764
|
-
`Cleared pending image operation for ${fragment.id}`
|
|
765
|
-
);
|
|
766
|
-
}
|
|
767
|
-
} catch (etlError) {
|
|
768
|
-
const errorMsg =
|
|
769
|
-
etlError instanceof Error ? etlError.message : 'Unknown error';
|
|
770
|
-
addDebugMessage(
|
|
771
|
-
`StoryFragment ${fragment.id} ETL failed: ${errorMsg}`
|
|
772
|
-
);
|
|
773
|
-
throw new Error(
|
|
774
|
-
`Failed to save story fragment ${fragment.id}: ${errorMsg}`
|
|
775
|
-
);
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
setStageProgress((prev) => ({ ...prev, currentStep: i + 1 }));
|
|
779
|
-
completedProcessingSteps++;
|
|
780
|
-
const processingProgress =
|
|
781
|
-
(completedProcessingSteps / totalProcessingSteps) *
|
|
782
|
-
PROGRESS_PHASES.PROCESSING;
|
|
783
|
-
setProgress(
|
|
784
|
-
PROGRESS_PHASES.PREPARATION +
|
|
785
|
-
PROGRESS_PHASES.UPLOADS +
|
|
786
|
-
processingProgress
|
|
787
|
-
);
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
if (dirtyPanes.length > 0) {
|
|
792
|
-
setStage('LINKING_FILES');
|
|
793
|
-
setIsIndeterminateStage(true);
|
|
794
|
-
// ... Linking files logic continues ...
|
|
795
|
-
// Note: Linking files remains after saving panes because it relies on panes existing in DB
|
|
796
|
-
setProgress(baseFinalizationProgress);
|
|
797
|
-
addDebugMessage('Starting file-pane relationship linking...');
|
|
798
|
-
|
|
799
|
-
const relationships = [];
|
|
800
|
-
for (const paneNode of dirtyPanes) {
|
|
801
|
-
const fileIds = ctx.getPaneImageFileIds(paneNode.id);
|
|
802
|
-
relationships.push({
|
|
803
|
-
paneId: paneNode.id,
|
|
804
|
-
fileIds: fileIds,
|
|
805
|
-
});
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
if (relationships.some((rel) => rel.fileIds.length > 0)) {
|
|
809
|
-
try {
|
|
810
|
-
const bulkEndpoint = `${goBackend}/api/v1/nodes/panes/files/bulk`;
|
|
811
|
-
addDebugMessage(
|
|
812
|
-
`Linking relationships: ${JSON.stringify(relationships)}`
|
|
813
|
-
);
|
|
814
|
-
|
|
815
|
-
const response = await fetch(bulkEndpoint, {
|
|
816
|
-
method: 'POST',
|
|
817
|
-
headers: {
|
|
818
|
-
'Content-Type': 'application/json',
|
|
819
|
-
'X-Tenant-ID': tenantId,
|
|
820
|
-
},
|
|
821
|
-
credentials: 'include',
|
|
822
|
-
body: JSON.stringify({ relationships }),
|
|
823
|
-
});
|
|
824
|
-
|
|
825
|
-
if (!response.ok) {
|
|
826
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
const result = await response.json();
|
|
830
|
-
addDebugMessage(
|
|
831
|
-
`File-pane relationships linked successfully: ${result.message}`
|
|
832
|
-
);
|
|
833
|
-
} catch (error) {
|
|
834
|
-
const errorMsg =
|
|
835
|
-
error instanceof Error ? error.message : 'Unknown error';
|
|
836
|
-
addDebugMessage(
|
|
837
|
-
`Failed to link file-pane relationships: ${errorMsg}`
|
|
838
|
-
);
|
|
839
|
-
throw new Error(
|
|
840
|
-
`Failed to link file-pane relationships: ${errorMsg}`
|
|
841
|
-
);
|
|
842
|
-
}
|
|
843
|
-
} else {
|
|
844
|
-
addDebugMessage('No file relationships to link');
|
|
845
|
-
}
|
|
846
|
-
setIsIndeterminateStage(false);
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
if (pendingHomePageSlug) {
|
|
850
|
-
setStage('UPDATING_HOME_PAGE');
|
|
851
|
-
setIsIndeterminateStage(true);
|
|
852
|
-
setProgress(
|
|
853
|
-
baseFinalizationProgress + (PROGRESS_PHASES.FINALIZATION - 2)
|
|
854
|
-
);
|
|
855
|
-
addDebugMessage(`Updating home page to: ${pendingHomePageSlug}`);
|
|
856
|
-
|
|
857
|
-
try {
|
|
858
|
-
const response = await fetch(`${goBackend}/api/v1/config/brand`, {
|
|
859
|
-
method: 'GET',
|
|
860
|
-
headers: {
|
|
861
|
-
'Content-Type': 'application/json',
|
|
862
|
-
'X-Tenant-ID': tenantId,
|
|
863
|
-
},
|
|
864
|
-
credentials: 'include',
|
|
865
|
-
});
|
|
866
|
-
|
|
867
|
-
if (!response.ok) {
|
|
868
|
-
throw new Error(
|
|
869
|
-
`Failed to get current brand config: ${response.status}`
|
|
870
|
-
);
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
const currentBrandConfig = await response.json();
|
|
874
|
-
|
|
875
|
-
const updatedBrandConfig = {
|
|
876
|
-
...currentBrandConfig,
|
|
877
|
-
HOME_SLUG: pendingHomePageSlug,
|
|
878
|
-
};
|
|
879
|
-
|
|
880
|
-
const updateResponse = await fetch(
|
|
881
|
-
`${goBackend}/api/v1/config/brand`,
|
|
882
|
-
{
|
|
883
|
-
method: 'PUT',
|
|
884
|
-
headers: {
|
|
885
|
-
'Content-Type': 'application/json',
|
|
886
|
-
'X-Tenant-ID': tenantId,
|
|
887
|
-
},
|
|
888
|
-
credentials: 'include',
|
|
889
|
-
body: JSON.stringify(updatedBrandConfig),
|
|
890
|
-
}
|
|
891
|
-
);
|
|
892
|
-
|
|
893
|
-
if (!updateResponse.ok) {
|
|
894
|
-
throw new Error(
|
|
895
|
-
`Failed to update home page: ${updateResponse.status}`
|
|
896
|
-
);
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
pendingHomePageSlugStore.set(null);
|
|
900
|
-
addDebugMessage('Home page updated successfully');
|
|
901
|
-
} catch (error) {
|
|
902
|
-
const errorMsg =
|
|
903
|
-
error instanceof Error ? error.message : 'Unknown error';
|
|
904
|
-
addDebugMessage(`Home page update failed: ${errorMsg}`);
|
|
905
|
-
throw new Error(`Failed to update home page: ${errorMsg}`);
|
|
906
|
-
} finally {
|
|
907
|
-
setIsIndeterminateStage(false);
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
if (hydrate) {
|
|
912
|
-
addDebugMessage('Finalizing setup (Kill Switch)...');
|
|
913
|
-
try {
|
|
914
|
-
const response = await fetch(`${goBackend}/api/v1/setup/complete`, {
|
|
915
|
-
method: 'POST',
|
|
916
|
-
headers: {
|
|
917
|
-
'Content-Type': 'application/json',
|
|
918
|
-
'X-Tenant-ID': tenantId,
|
|
919
|
-
},
|
|
920
|
-
credentials: 'include',
|
|
921
|
-
body: JSON.stringify({}),
|
|
922
|
-
});
|
|
923
|
-
|
|
924
|
-
if (!response.ok) {
|
|
925
|
-
throw new Error(`Kill Switch failed: ${response.status}`);
|
|
926
|
-
}
|
|
927
|
-
addDebugMessage('Hydration token cleared.');
|
|
928
|
-
} catch (e) {
|
|
929
|
-
console.error('Kill switch error:', e);
|
|
930
|
-
// We don't throw here to ensure the user still gets to the dashboard
|
|
931
|
-
addDebugMessage('Warning: Failed to clear hydration token.');
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
setStage('COMPLETED');
|
|
936
|
-
setProgress(100);
|
|
937
|
-
addDebugMessage('Save process completed successfully!');
|
|
938
170
|
} catch (err) {
|
|
939
171
|
setStage('ERROR');
|
|
940
172
|
const errorMessage =
|