@jsonpages/cli 3.0.56 → 3.0.58
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/assets/src_tenant_alpha.sh +1617 -131
- package/package.json +1 -1
|
@@ -32,20 +32,22 @@ cat << 'END_OF_FILE_CONTENT' > "src/App.tsx"
|
|
|
32
32
|
* Data from getHydratedData (file-backed or draft); assets from public/assets/images.
|
|
33
33
|
* Supports Hybrid Persistence: Local Filesystem (Dev) or Cloud Bridge (Prod).
|
|
34
34
|
*/
|
|
35
|
-
import {
|
|
35
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
36
36
|
import { JsonPagesEngine } from '@jsonpages/core';
|
|
37
|
-
import type { LibraryImageEntry } from '@jsonpages/core';
|
|
37
|
+
import type { JsonPagesConfig, LibraryImageEntry, ProjectState } from '@jsonpages/core';
|
|
38
38
|
import { ComponentRegistry } from '@/lib/ComponentRegistry';
|
|
39
39
|
import { SECTION_SCHEMAS } from '@/lib/schemas';
|
|
40
40
|
import { addSectionConfig } from '@/lib/addSectionConfig';
|
|
41
41
|
import { getHydratedData } from '@/lib/draftStorage';
|
|
42
|
-
import type { JsonPagesConfig, ProjectState } from '@jsonpages/core';
|
|
43
42
|
import type { SiteConfig, ThemeConfig, MenuConfig } from '@/types';
|
|
44
|
-
|
|
43
|
+
import type { DeployPhase, StepId } from '@/types/deploy';
|
|
44
|
+
import { DEPLOY_STEPS } from '@/lib/deploySteps';
|
|
45
|
+
import { startCloudSaveStream } from '@/lib/cloudSaveStream';
|
|
45
46
|
import siteData from '@/data/config/site.json';
|
|
46
47
|
import themeData from '@/data/config/theme.json';
|
|
47
48
|
import menuData from '@/data/config/menu.json';
|
|
48
49
|
import { getFilePages } from '@/lib/getFilePages';
|
|
50
|
+
import { DopaDrawer } from '@/components/save-drawer/DopaDrawer';
|
|
49
51
|
|
|
50
52
|
import fontsCss from './fonts.css?inline';
|
|
51
53
|
import tenantCss from './index.css?inline';
|
|
@@ -62,13 +64,41 @@ const filePages = getFilePages();
|
|
|
62
64
|
const fileSiteConfig = siteData as unknown as SiteConfig;
|
|
63
65
|
const MAX_UPLOAD_SIZE_BYTES = 5 * 1024 * 1024;
|
|
64
66
|
|
|
67
|
+
interface CloudSaveUiState {
|
|
68
|
+
isOpen: boolean;
|
|
69
|
+
phase: DeployPhase;
|
|
70
|
+
currentStepId: StepId | null;
|
|
71
|
+
doneSteps: StepId[];
|
|
72
|
+
progress: number;
|
|
73
|
+
errorMessage?: string;
|
|
74
|
+
deployUrl?: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
65
77
|
function getInitialData() {
|
|
66
78
|
return getHydratedData(TENANT_ID, filePages, fileSiteConfig);
|
|
67
79
|
}
|
|
68
80
|
|
|
81
|
+
function getInitialCloudSaveUiState(): CloudSaveUiState {
|
|
82
|
+
return {
|
|
83
|
+
isOpen: false,
|
|
84
|
+
phase: 'idle',
|
|
85
|
+
currentStepId: null,
|
|
86
|
+
doneSteps: [],
|
|
87
|
+
progress: 0,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function stepProgress(doneSteps: StepId[]): number {
|
|
92
|
+
return Math.round((doneSteps.length / DEPLOY_STEPS.length) * 100);
|
|
93
|
+
}
|
|
94
|
+
|
|
69
95
|
function App() {
|
|
70
96
|
const [{ pages, siteConfig }] = useState(getInitialData);
|
|
71
97
|
const [assetsManifest, setAssetsManifest] = useState<LibraryImageEntry[]>([]);
|
|
98
|
+
const [cloudSaveUi, setCloudSaveUi] = useState<CloudSaveUiState>(getInitialCloudSaveUiState);
|
|
99
|
+
const activeCloudSaveController = useRef<AbortController | null>(null);
|
|
100
|
+
const pendingCloudSave = useRef<{ state: ProjectState; slug: string } | null>(null);
|
|
101
|
+
const isCloudMode = Boolean(CLOUD_API_URL && CLOUD_API_KEY);
|
|
72
102
|
|
|
73
103
|
useEffect(() => {
|
|
74
104
|
// In Cloud mode, listing assets might be different or disabled for MVP
|
|
@@ -78,11 +108,111 @@ function App() {
|
|
|
78
108
|
.then((list: LibraryImageEntry[]) => setAssetsManifest(Array.isArray(list) ? list : []))
|
|
79
109
|
.catch(() => setAssetsManifest([]));
|
|
80
110
|
}, []);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
111
|
+
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
return () => {
|
|
114
|
+
activeCloudSaveController.current?.abort();
|
|
115
|
+
};
|
|
116
|
+
}, []);
|
|
117
|
+
|
|
118
|
+
const runCloudSave = useCallback(
|
|
119
|
+
async (
|
|
120
|
+
payload: { state: ProjectState; slug: string },
|
|
121
|
+
rejectOnError: boolean
|
|
122
|
+
): Promise<void> => {
|
|
123
|
+
if (!CLOUD_API_URL || !CLOUD_API_KEY) {
|
|
124
|
+
const noCloudError = new Error('Cloud mode is not configured.');
|
|
125
|
+
if (rejectOnError) throw noCloudError;
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
pendingCloudSave.current = payload;
|
|
130
|
+
activeCloudSaveController.current?.abort();
|
|
131
|
+
const controller = new AbortController();
|
|
132
|
+
activeCloudSaveController.current = controller;
|
|
133
|
+
|
|
134
|
+
setCloudSaveUi({
|
|
135
|
+
isOpen: true,
|
|
136
|
+
phase: 'running',
|
|
137
|
+
currentStepId: null,
|
|
138
|
+
doneSteps: [],
|
|
139
|
+
progress: 0,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
await startCloudSaveStream({
|
|
144
|
+
apiBaseUrl: CLOUD_API_URL,
|
|
145
|
+
apiKey: CLOUD_API_KEY,
|
|
146
|
+
path: `src/data/pages/${payload.slug}.json`,
|
|
147
|
+
content: payload.state.page,
|
|
148
|
+
message: `Content update for ${payload.slug} via Visual Editor`,
|
|
149
|
+
signal: controller.signal,
|
|
150
|
+
onStep: (event) => {
|
|
151
|
+
setCloudSaveUi((prev) => {
|
|
152
|
+
if (event.status === 'running') {
|
|
153
|
+
return {
|
|
154
|
+
...prev,
|
|
155
|
+
isOpen: true,
|
|
156
|
+
phase: 'running',
|
|
157
|
+
currentStepId: event.id,
|
|
158
|
+
errorMessage: undefined,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (prev.doneSteps.includes(event.id)) {
|
|
163
|
+
return prev;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const nextDone = [...prev.doneSteps, event.id];
|
|
167
|
+
return {
|
|
168
|
+
...prev,
|
|
169
|
+
isOpen: true,
|
|
170
|
+
phase: 'running',
|
|
171
|
+
currentStepId: event.id,
|
|
172
|
+
doneSteps: nextDone,
|
|
173
|
+
progress: stepProgress(nextDone),
|
|
174
|
+
};
|
|
175
|
+
});
|
|
176
|
+
},
|
|
177
|
+
onDone: (event) => {
|
|
178
|
+
const completed = DEPLOY_STEPS.map((step) => step.id);
|
|
179
|
+
setCloudSaveUi({
|
|
180
|
+
isOpen: true,
|
|
181
|
+
phase: 'done',
|
|
182
|
+
currentStepId: 'live',
|
|
183
|
+
doneSteps: completed,
|
|
184
|
+
progress: 100,
|
|
185
|
+
deployUrl: event.deployUrl,
|
|
186
|
+
});
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
} catch (error: unknown) {
|
|
190
|
+
const message = error instanceof Error ? error.message : 'Cloud save failed.';
|
|
191
|
+
setCloudSaveUi((prev) => ({
|
|
192
|
+
...prev,
|
|
193
|
+
isOpen: true,
|
|
194
|
+
phase: 'error',
|
|
195
|
+
errorMessage: message,
|
|
196
|
+
}));
|
|
197
|
+
if (rejectOnError) throw new Error(message);
|
|
198
|
+
} finally {
|
|
199
|
+
if (activeCloudSaveController.current === controller) {
|
|
200
|
+
activeCloudSaveController.current = null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
[]
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
const closeCloudDrawer = useCallback(() => {
|
|
208
|
+
setCloudSaveUi(getInitialCloudSaveUiState());
|
|
209
|
+
}, []);
|
|
210
|
+
|
|
211
|
+
const retryCloudSave = useCallback(() => {
|
|
212
|
+
if (!pendingCloudSave.current) return;
|
|
213
|
+
void runCloudSave(pendingCloudSave.current, false);
|
|
214
|
+
}, [runCloudSave]);
|
|
215
|
+
|
|
86
216
|
const config: JsonPagesConfig = {
|
|
87
217
|
tenantId: TENANT_ID,
|
|
88
218
|
registry: ComponentRegistry as JsonPagesConfig['registry'],
|
|
@@ -95,30 +225,9 @@ console.log("🔍 DEBUG ENV:", {
|
|
|
95
225
|
addSection: addSectionConfig,
|
|
96
226
|
persistence: {
|
|
97
227
|
async saveToFile(state: ProjectState, slug: string): Promise<void> {
|
|
98
|
-
|
|
99
228
|
// ☁️ SCENARIO A: CLOUD BRIDGE (Production)
|
|
100
|
-
if (
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const res = await fetch(`${CLOUD_API_URL}/save`, {
|
|
104
|
-
method: 'POST',
|
|
105
|
-
headers: {
|
|
106
|
-
'Content-Type': 'application/json',
|
|
107
|
-
'Authorization': `Bearer ${CLOUD_API_KEY}`
|
|
108
|
-
},
|
|
109
|
-
body: JSON.stringify({
|
|
110
|
-
// Mapping logical slug to physical path in repo
|
|
111
|
-
path: `src/data/pages/${slug}.json`,
|
|
112
|
-
// We save the page config specifically
|
|
113
|
-
content: state.page,
|
|
114
|
-
message: `Content update for ${slug} via Visual Editor`
|
|
115
|
-
}),
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
if (!res.ok) {
|
|
119
|
-
const err = await res.json().catch(() => ({}));
|
|
120
|
-
throw new Error(err.error || `Cloud save failed: ${res.status}`);
|
|
121
|
-
}
|
|
229
|
+
if (isCloudMode) {
|
|
230
|
+
await runCloudSave({ state, slug }, true);
|
|
122
231
|
return;
|
|
123
232
|
}
|
|
124
233
|
|
|
@@ -164,15 +273,35 @@ console.log("🔍 DEBUG ENV:", {
|
|
|
164
273
|
},
|
|
165
274
|
};
|
|
166
275
|
|
|
167
|
-
return
|
|
276
|
+
return (
|
|
277
|
+
<>
|
|
278
|
+
<JsonPagesEngine config={config} />
|
|
279
|
+
<DopaDrawer
|
|
280
|
+
isOpen={cloudSaveUi.isOpen}
|
|
281
|
+
phase={cloudSaveUi.phase}
|
|
282
|
+
currentStepId={cloudSaveUi.currentStepId}
|
|
283
|
+
doneSteps={cloudSaveUi.doneSteps}
|
|
284
|
+
progress={cloudSaveUi.progress}
|
|
285
|
+
errorMessage={cloudSaveUi.errorMessage}
|
|
286
|
+
deployUrl={cloudSaveUi.deployUrl}
|
|
287
|
+
onClose={closeCloudDrawer}
|
|
288
|
+
onRetry={retryCloudSave}
|
|
289
|
+
/>
|
|
290
|
+
</>
|
|
291
|
+
);
|
|
168
292
|
}
|
|
169
293
|
|
|
170
294
|
export default App;
|
|
171
295
|
|
|
172
296
|
END_OF_FILE_CONTENT
|
|
173
297
|
# SKIP: src/App.tsx:Zone.Identifier è un file binario e non può essere convertito in testo.
|
|
174
|
-
echo "Creating src/
|
|
175
|
-
cat << 'END_OF_FILE_CONTENT' > "src/
|
|
298
|
+
echo "Creating src/_App.tsx..."
|
|
299
|
+
cat << 'END_OF_FILE_CONTENT' > "src/_App.tsx"
|
|
300
|
+
/**
|
|
301
|
+
* Thin Entry Point (Tenant).
|
|
302
|
+
* Data from getHydratedData (file-backed or draft); assets from public/assets/images.
|
|
303
|
+
* Supports Hybrid Persistence: Local Filesystem (Dev) or Cloud Bridge (Prod).
|
|
304
|
+
*/
|
|
176
305
|
import { useState, useEffect } from 'react';
|
|
177
306
|
import { JsonPagesEngine } from '@jsonpages/core';
|
|
178
307
|
import type { LibraryImageEntry } from '@jsonpages/core';
|
|
@@ -191,6 +320,10 @@ import { getFilePages } from '@/lib/getFilePages';
|
|
|
191
320
|
import fontsCss from './fonts.css?inline';
|
|
192
321
|
import tenantCss from './index.css?inline';
|
|
193
322
|
|
|
323
|
+
// Cloud Configuration (Injected by Vercel/Netlify Env Vars)
|
|
324
|
+
const CLOUD_API_URL = import.meta.env.VITE_JSONPAGES_CLOUD_URL;
|
|
325
|
+
const CLOUD_API_KEY = import.meta.env.VITE_JSONPAGES_API_KEY;
|
|
326
|
+
|
|
194
327
|
const themeConfig = themeData as unknown as ThemeConfig;
|
|
195
328
|
const menuConfig = menuData as unknown as MenuConfig;
|
|
196
329
|
const TENANT_ID = 'alpha';
|
|
@@ -208,16 +341,22 @@ function App() {
|
|
|
208
341
|
const [assetsManifest, setAssetsManifest] = useState<LibraryImageEntry[]>([]);
|
|
209
342
|
|
|
210
343
|
useEffect(() => {
|
|
344
|
+
// In Cloud mode, listing assets might be different or disabled for MVP
|
|
345
|
+
// For now, we keep the local fetch which will fail gracefully on Vercel (404)
|
|
211
346
|
fetch('/api/list-assets')
|
|
212
347
|
.then((r) => (r.ok ? r.json() : []))
|
|
213
348
|
.then((list: LibraryImageEntry[]) => setAssetsManifest(Array.isArray(list) ? list : []))
|
|
214
349
|
.catch(() => setAssetsManifest([]));
|
|
215
350
|
}, []);
|
|
216
|
-
|
|
351
|
+
|
|
352
|
+
console.log("🔍 DEBUG ENV:", {
|
|
353
|
+
URL: import.meta.env.VITE_JSONPAGES_CLOUD_URL,
|
|
354
|
+
KEY: import.meta.env.VITE_JSONPAGES_API_KEY ? "PRESENT" : "MISSING"
|
|
355
|
+
});
|
|
217
356
|
const config: JsonPagesConfig = {
|
|
218
357
|
tenantId: TENANT_ID,
|
|
219
358
|
registry: ComponentRegistry as JsonPagesConfig['registry'],
|
|
220
|
-
schemas:
|
|
359
|
+
schemas: SECTION_SCHEMAS as unknown as JsonPagesConfig['schemas'],
|
|
221
360
|
pages,
|
|
222
361
|
siteConfig,
|
|
223
362
|
themeConfig,
|
|
@@ -226,11 +365,41 @@ function App() {
|
|
|
226
365
|
addSection: addSectionConfig,
|
|
227
366
|
persistence: {
|
|
228
367
|
async saveToFile(state: ProjectState, slug: string): Promise<void> {
|
|
368
|
+
|
|
369
|
+
// ☁️ SCENARIO A: CLOUD BRIDGE (Production)
|
|
370
|
+
if (CLOUD_API_URL && CLOUD_API_KEY) {
|
|
371
|
+
console.log(`☁️ Saving ${slug} via Cloud Bridge...`);
|
|
372
|
+
|
|
373
|
+
const res = await fetch(`${CLOUD_API_URL}/save`, {
|
|
374
|
+
method: 'POST',
|
|
375
|
+
headers: {
|
|
376
|
+
'Content-Type': 'application/json',
|
|
377
|
+
'Authorization': `Bearer ${CLOUD_API_KEY}`
|
|
378
|
+
},
|
|
379
|
+
body: JSON.stringify({
|
|
380
|
+
// Mapping logical slug to physical path in repo
|
|
381
|
+
path: `src/data/pages/${slug}.json`,
|
|
382
|
+
// We save the page config specifically
|
|
383
|
+
content: state.page,
|
|
384
|
+
message: `Content update for ${slug} via Visual Editor`
|
|
385
|
+
}),
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
if (!res.ok) {
|
|
389
|
+
const err = await res.json().catch(() => ({}));
|
|
390
|
+
throw new Error(err.error || `Cloud save failed: ${res.status}`);
|
|
391
|
+
}
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// 💻 SCENARIO B: LOCAL FILESYSTEM (Development)
|
|
396
|
+
console.log(`💻 Saving ${slug} to Local Filesystem...`);
|
|
229
397
|
const res = await fetch('/api/save-to-file', {
|
|
230
398
|
method: 'POST',
|
|
231
399
|
headers: { 'Content-Type': 'application/json' },
|
|
232
400
|
body: JSON.stringify({ projectState: state, slug }),
|
|
233
401
|
});
|
|
402
|
+
|
|
234
403
|
const body = (await res.json().catch(() => ({}))) as { error?: string };
|
|
235
404
|
if (!res.ok) throw new Error(body.error ?? `Save to file failed: ${res.status}`);
|
|
236
405
|
},
|
|
@@ -239,19 +408,24 @@ function App() {
|
|
|
239
408
|
assetsBaseUrl: '/assets',
|
|
240
409
|
assetsManifest,
|
|
241
410
|
async onAssetUpload(file: File): Promise<string> {
|
|
411
|
+
// Note: Asset upload in Cloud Mode requires the R2 Bridge (Next Step in Roadmap)
|
|
412
|
+
// For now, this works in Local Mode.
|
|
242
413
|
if (!file.type.startsWith('image/')) throw new Error('Invalid file type.');
|
|
243
414
|
if (file.size > MAX_UPLOAD_SIZE_BYTES) throw new Error(`File too large. Max ${MAX_UPLOAD_SIZE_BYTES / 1024 / 1024}MB.`);
|
|
415
|
+
|
|
244
416
|
const base64 = await new Promise<string>((resolve, reject) => {
|
|
245
417
|
const reader = new FileReader();
|
|
246
418
|
reader.onload = () => resolve((reader.result as string).split(',')[1] ?? '');
|
|
247
419
|
reader.onerror = () => reject(reader.error);
|
|
248
420
|
reader.readAsDataURL(file);
|
|
249
421
|
});
|
|
422
|
+
|
|
250
423
|
const res = await fetch('/api/upload-asset', {
|
|
251
424
|
method: 'POST',
|
|
252
425
|
headers: { 'Content-Type': 'application/json' },
|
|
253
426
|
body: JSON.stringify({ filename: file.name, mimeType: file.type || undefined, data: base64 }),
|
|
254
427
|
});
|
|
428
|
+
|
|
255
429
|
const body = (await res.json().catch(() => ({}))) as { url?: string; error?: string };
|
|
256
430
|
if (!res.ok) throw new Error(body.error || `Upload failed: ${res.status}`);
|
|
257
431
|
if (typeof body.url !== 'string') throw new Error('Invalid server response: missing url');
|
|
@@ -265,6 +439,24 @@ function App() {
|
|
|
265
439
|
|
|
266
440
|
export default App;
|
|
267
441
|
|
|
442
|
+
END_OF_FILE_CONTENT
|
|
443
|
+
echo "Creating src/_main.tsx..."
|
|
444
|
+
cat << 'END_OF_FILE_CONTENT' > "src/_main.tsx"
|
|
445
|
+
import '@/types'; // TBP: load type augmentation from capsule-driven types
|
|
446
|
+
import React from 'react';
|
|
447
|
+
import ReactDOM from 'react-dom/client';
|
|
448
|
+
import App from './App';
|
|
449
|
+
// ... resto del file
|
|
450
|
+
|
|
451
|
+
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
452
|
+
<React.StrictMode>
|
|
453
|
+
<App />
|
|
454
|
+
</React.StrictMode>
|
|
455
|
+
);
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
|
|
268
460
|
END_OF_FILE_CONTENT
|
|
269
461
|
mkdir -p "src/components"
|
|
270
462
|
echo "Creating src/components/NotFound.tsx..."
|
|
@@ -3041,111 +3233,1200 @@ export type ProductTriadData = z.infer<typeof ProductTriadSchema>;
|
|
|
3041
3233
|
export type ProductTriadSettings = z.infer<typeof BaseSectionSettingsSchema>;
|
|
3042
3234
|
|
|
3043
3235
|
END_OF_FILE_CONTENT
|
|
3044
|
-
mkdir -p "src/components/
|
|
3045
|
-
echo "Creating src/components/
|
|
3046
|
-
cat << 'END_OF_FILE_CONTENT' > "src/components/
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3236
|
+
mkdir -p "src/components/save-drawer"
|
|
3237
|
+
echo "Creating src/components/save-drawer/DeployConnector.tsx..."
|
|
3238
|
+
cat << 'END_OF_FILE_CONTENT' > "src/components/save-drawer/DeployConnector.tsx"
|
|
3239
|
+
import type { StepState } from '@/types/deploy';
|
|
3240
|
+
|
|
3241
|
+
interface DeployConnectorProps {
|
|
3242
|
+
fromState: StepState;
|
|
3243
|
+
toState: StepState;
|
|
3244
|
+
color: string;
|
|
3245
|
+
}
|
|
3051
3246
|
|
|
3052
|
-
|
|
3053
|
-
|
|
3247
|
+
export function DeployConnector({ fromState, toState, color }: DeployConnectorProps) {
|
|
3248
|
+
const filled = fromState === 'done' && toState === 'done';
|
|
3249
|
+
const filling = fromState === 'done' && toState === 'active';
|
|
3250
|
+
const lit = filled || filling;
|
|
3054
3251
|
|
|
3055
|
-
function Checkbox({
|
|
3056
|
-
className,
|
|
3057
|
-
...props
|
|
3058
|
-
}: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
|
|
3059
3252
|
return (
|
|
3060
|
-
<
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
className
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3253
|
+
<div className="jp-drawer-connector">
|
|
3254
|
+
<div className="jp-drawer-connector-base" />
|
|
3255
|
+
|
|
3256
|
+
<div
|
|
3257
|
+
className="jp-drawer-connector-fill"
|
|
3258
|
+
style={{
|
|
3259
|
+
background: `linear-gradient(90deg, ${color}cc, ${color}66)`,
|
|
3260
|
+
width: filled ? '100%' : filling ? '100%' : '0%',
|
|
3261
|
+
transition: filling ? 'width 2s cubic-bezier(0.4,0,0.2,1)' : 'none',
|
|
3262
|
+
boxShadow: lit ? `0 0 8px ${color}77` : 'none',
|
|
3263
|
+
}}
|
|
3264
|
+
/>
|
|
3265
|
+
|
|
3266
|
+
{filling && (
|
|
3267
|
+
<div
|
|
3268
|
+
className="jp-drawer-connector-orb"
|
|
3269
|
+
style={{
|
|
3270
|
+
background: color,
|
|
3271
|
+
boxShadow: `0 0 14px ${color}, 0 0 28px ${color}88`,
|
|
3272
|
+
animation: 'orb-travel 2s cubic-bezier(0.4,0,0.6,1) forwards',
|
|
3273
|
+
}}
|
|
3073
3274
|
/>
|
|
3074
|
-
|
|
3075
|
-
</
|
|
3076
|
-
)
|
|
3275
|
+
)}
|
|
3276
|
+
</div>
|
|
3277
|
+
);
|
|
3077
3278
|
}
|
|
3078
3279
|
|
|
3079
|
-
|
|
3280
|
+
END_OF_FILE_CONTENT
|
|
3281
|
+
# SKIP: src/components/save-drawer/DeployConnector.tsx:Zone.Identifier è un file binario e non può essere convertito in testo.
|
|
3282
|
+
# SKIP: src/components/save-drawer/DeployConnector.tsx:Zone.Identifier:Zone.Identifier è un file binario e non può essere convertito in testo.
|
|
3283
|
+
echo "Creating src/components/save-drawer/DeployNode.tsx..."
|
|
3284
|
+
cat << 'END_OF_FILE_CONTENT' > "src/components/save-drawer/DeployNode.tsx"
|
|
3285
|
+
import type { CSSProperties } from 'react';
|
|
3286
|
+
import type { DeployStep, StepState } from '@/types/deploy';
|
|
3287
|
+
|
|
3288
|
+
interface DeployNodeProps {
|
|
3289
|
+
step: DeployStep;
|
|
3290
|
+
state: StepState;
|
|
3291
|
+
}
|
|
3080
3292
|
|
|
3293
|
+
export function DeployNode({ step, state }: DeployNodeProps) {
|
|
3294
|
+
const isActive = state === 'active';
|
|
3295
|
+
const isDone = state === 'done';
|
|
3296
|
+
const isPending = state === 'pending';
|
|
3081
3297
|
|
|
3298
|
+
return (
|
|
3299
|
+
<div className="jp-drawer-node-wrap">
|
|
3300
|
+
<div
|
|
3301
|
+
className={`jp-drawer-node ${isPending ? 'jp-drawer-node-pending' : ''}`}
|
|
3302
|
+
style={
|
|
3303
|
+
{
|
|
3304
|
+
background: isDone ? step.color : isActive ? 'rgba(0,0,0,0.5)' : undefined,
|
|
3305
|
+
borderWidth: isDone ? 0 : 1,
|
|
3306
|
+
borderColor: isActive ? `${step.color}80` : undefined,
|
|
3307
|
+
boxShadow: isDone
|
|
3308
|
+
? `0 0 20px ${step.color}55, 0 0 40px ${step.color}22`
|
|
3309
|
+
: isActive
|
|
3310
|
+
? `0 0 14px ${step.color}33`
|
|
3311
|
+
: undefined,
|
|
3312
|
+
animation: isActive ? 'node-glow 2s ease infinite' : undefined,
|
|
3313
|
+
['--glow-color' as string]: step.color,
|
|
3314
|
+
} as CSSProperties
|
|
3315
|
+
}
|
|
3316
|
+
>
|
|
3317
|
+
{isDone && (
|
|
3318
|
+
<svg className="h-5 w-5" viewBox="0 0 24 24" fill="none" aria-label="Done">
|
|
3319
|
+
<path
|
|
3320
|
+
className="stroke-dash-30 animate-check-draw"
|
|
3321
|
+
d="M5 13l4 4L19 7"
|
|
3322
|
+
stroke="#0a0f1a"
|
|
3323
|
+
strokeWidth="2.5"
|
|
3324
|
+
strokeLinecap="round"
|
|
3325
|
+
strokeLinejoin="round"
|
|
3326
|
+
/>
|
|
3327
|
+
</svg>
|
|
3328
|
+
)}
|
|
3082
3329
|
|
|
3330
|
+
{isActive && (
|
|
3331
|
+
<span
|
|
3332
|
+
className="jp-drawer-node-glyph jp-drawer-node-glyph-active"
|
|
3333
|
+
style={{ color: step.color, animation: 'glyph-rotate 9s linear infinite' }}
|
|
3334
|
+
aria-hidden
|
|
3335
|
+
>
|
|
3336
|
+
{step.glyph}
|
|
3337
|
+
</span>
|
|
3338
|
+
)}
|
|
3083
3339
|
|
|
3340
|
+
{isPending && (
|
|
3341
|
+
<span className="jp-drawer-node-glyph jp-drawer-node-glyph-pending" aria-hidden>
|
|
3342
|
+
{step.glyph}
|
|
3343
|
+
</span>
|
|
3344
|
+
)}
|
|
3084
3345
|
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3346
|
+
{isActive && (
|
|
3347
|
+
<span
|
|
3348
|
+
className="jp-drawer-node-ring"
|
|
3349
|
+
style={{
|
|
3350
|
+
inset: -7,
|
|
3351
|
+
borderColor: `${step.color}50`,
|
|
3352
|
+
animation: 'ring-expand 2s ease-out infinite',
|
|
3353
|
+
}}
|
|
3354
|
+
/>
|
|
3355
|
+
)}
|
|
3356
|
+
</div>
|
|
3089
3357
|
|
|
3090
|
-
|
|
3358
|
+
<span
|
|
3359
|
+
className="jp-drawer-node-label"
|
|
3360
|
+
style={{ color: isDone ? step.color : isActive ? 'rgba(255,255,255,0.85)' : 'rgba(255,255,255,0.18)' }}
|
|
3361
|
+
>
|
|
3362
|
+
{step.label}
|
|
3363
|
+
</span>
|
|
3364
|
+
</div>
|
|
3365
|
+
);
|
|
3366
|
+
}
|
|
3091
3367
|
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3368
|
+
END_OF_FILE_CONTENT
|
|
3369
|
+
# SKIP: src/components/save-drawer/DeployNode.tsx:Zone.Identifier è un file binario e non può essere convertito in testo.
|
|
3370
|
+
# SKIP: src/components/save-drawer/DeployNode.tsx:Zone.Identifier:Zone.Identifier è un file binario e non può essere convertito in testo.
|
|
3371
|
+
echo "Creating src/components/save-drawer/DopaDrawer.tsx..."
|
|
3372
|
+
cat << 'END_OF_FILE_CONTENT' > "src/components/save-drawer/DopaDrawer.tsx"
|
|
3373
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
3374
|
+
import { createPortal } from 'react-dom';
|
|
3375
|
+
import type { StepId, StepState } from '@/types/deploy';
|
|
3376
|
+
import { DEPLOY_STEPS } from '@/lib/deploySteps';
|
|
3377
|
+
import fontsCss from '@/fonts.css?inline';
|
|
3378
|
+
import saverStyleCss from './saverStyle.css?inline';
|
|
3379
|
+
import { DeployNode } from './DeployNode';
|
|
3380
|
+
import { DeployConnector } from './DeployConnector';
|
|
3381
|
+
import { BuildBars, ElapsedTimer, Particles, SuccessBurst } from './Visuals';
|
|
3382
|
+
|
|
3383
|
+
interface DopaDrawerProps {
|
|
3384
|
+
isOpen: boolean;
|
|
3385
|
+
phase: 'idle' | 'running' | 'done' | 'error';
|
|
3386
|
+
currentStepId: StepId | null;
|
|
3387
|
+
doneSteps: StepId[];
|
|
3388
|
+
progress: number;
|
|
3389
|
+
errorMessage?: string;
|
|
3390
|
+
deployUrl?: string;
|
|
3391
|
+
onClose: () => void;
|
|
3392
|
+
onRetry: () => void;
|
|
3104
3393
|
}
|
|
3105
3394
|
|
|
3106
|
-
export {
|
|
3395
|
+
export function DopaDrawer({
|
|
3396
|
+
isOpen,
|
|
3397
|
+
phase,
|
|
3398
|
+
currentStepId,
|
|
3399
|
+
doneSteps,
|
|
3400
|
+
progress,
|
|
3401
|
+
errorMessage,
|
|
3402
|
+
deployUrl,
|
|
3403
|
+
onClose,
|
|
3404
|
+
onRetry,
|
|
3405
|
+
}: DopaDrawerProps) {
|
|
3406
|
+
const [shadowMount, setShadowMount] = useState<HTMLElement | null>(null);
|
|
3407
|
+
const [burst, setBurst] = useState(false);
|
|
3408
|
+
const [countdown, setCountdown] = useState(3);
|
|
3409
|
+
|
|
3410
|
+
const isRunning = phase === 'running';
|
|
3411
|
+
const isDone = phase === 'done';
|
|
3412
|
+
const isError = phase === 'error';
|
|
3107
3413
|
|
|
3414
|
+
useEffect(() => {
|
|
3415
|
+
const host = document.createElement('div');
|
|
3416
|
+
host.setAttribute('data-jp-drawer-shadow-host', '');
|
|
3108
3417
|
|
|
3418
|
+
const shadowRoot = host.attachShadow({ mode: 'open' });
|
|
3419
|
+
const style = document.createElement('style');
|
|
3420
|
+
style.textContent = `${fontsCss}\n${saverStyleCss}`;
|
|
3109
3421
|
|
|
3422
|
+
const mount = document.createElement('div');
|
|
3423
|
+
shadowRoot.append(style, mount);
|
|
3110
3424
|
|
|
3425
|
+
document.body.appendChild(host);
|
|
3426
|
+
setShadowMount(mount);
|
|
3111
3427
|
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3428
|
+
return () => {
|
|
3429
|
+
setShadowMount(null);
|
|
3430
|
+
host.remove();
|
|
3431
|
+
};
|
|
3432
|
+
}, []);
|
|
3117
3433
|
|
|
3118
|
-
|
|
3434
|
+
useEffect(() => {
|
|
3435
|
+
if (!isOpen) {
|
|
3436
|
+
setBurst(false);
|
|
3437
|
+
setCountdown(3);
|
|
3438
|
+
return;
|
|
3439
|
+
}
|
|
3440
|
+
if (isDone) setBurst(true);
|
|
3441
|
+
}, [isDone, isOpen]);
|
|
3119
3442
|
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
}
|
|
3443
|
+
useEffect(() => {
|
|
3444
|
+
if (!isOpen || !isDone) return;
|
|
3445
|
+
setCountdown(3);
|
|
3446
|
+
const interval = window.setInterval(() => {
|
|
3447
|
+
setCountdown((prev) => {
|
|
3448
|
+
if (prev <= 1) {
|
|
3449
|
+
window.clearInterval(interval);
|
|
3450
|
+
onClose();
|
|
3451
|
+
return 0;
|
|
3452
|
+
}
|
|
3453
|
+
return prev - 1;
|
|
3454
|
+
});
|
|
3455
|
+
}, 1000);
|
|
3456
|
+
return () => window.clearInterval(interval);
|
|
3457
|
+
}, [isDone, isOpen, onClose]);
|
|
3458
|
+
|
|
3459
|
+
const currentStep = useMemo(
|
|
3460
|
+
() => DEPLOY_STEPS.find((step) => step.id === currentStepId) ?? null,
|
|
3461
|
+
[currentStepId]
|
|
3462
|
+
);
|
|
3135
3463
|
|
|
3136
|
-
|
|
3464
|
+
const activeColor = isDone ? '#34d399' : isError ? '#f87171' : (currentStep?.color ?? '#60a5fa');
|
|
3465
|
+
const particleCount = isDone ? 40 : doneSteps.length === 3 ? 28 : doneSteps.length === 2 ? 16 : doneSteps.length === 1 ? 8 : 4;
|
|
3137
3466
|
|
|
3467
|
+
const stepState = (index: number): StepState => {
|
|
3468
|
+
const step = DEPLOY_STEPS[index];
|
|
3469
|
+
if (doneSteps.includes(step.id)) return 'done';
|
|
3470
|
+
if (phase === 'running' && currentStepId === step.id) return 'active';
|
|
3471
|
+
return 'pending';
|
|
3472
|
+
};
|
|
3138
3473
|
|
|
3474
|
+
if (!shadowMount || !isOpen || phase === 'idle') return null;
|
|
3475
|
+
|
|
3476
|
+
return createPortal(
|
|
3477
|
+
<div className="jp-drawer-root">
|
|
3478
|
+
<div
|
|
3479
|
+
className="jp-drawer-overlay animate-fade-in"
|
|
3480
|
+
onClick={isDone || isError ? onClose : undefined}
|
|
3481
|
+
aria-hidden
|
|
3482
|
+
/>
|
|
3483
|
+
|
|
3484
|
+
<div
|
|
3485
|
+
role="status"
|
|
3486
|
+
aria-live="polite"
|
|
3487
|
+
aria-label={isDone ? 'Deploy completed' : isError ? 'Deploy failed' : 'Deploying'}
|
|
3488
|
+
className="jp-drawer-shell animate-drawer-up"
|
|
3489
|
+
style={{ bottom: 'max(2.25rem, env(safe-area-inset-bottom))' }}
|
|
3490
|
+
>
|
|
3491
|
+
<div
|
|
3492
|
+
className="jp-drawer-card"
|
|
3493
|
+
style={{
|
|
3494
|
+
backgroundColor: 'hsl(222 18% 7%)',
|
|
3495
|
+
boxShadow: `0 0 0 1px rgba(255,255,255,0.04), 0 -20px 60px rgba(0,0,0,0.6), 0 0 80px ${activeColor}0d`,
|
|
3496
|
+
transition: 'box-shadow 1.2s ease',
|
|
3497
|
+
}}
|
|
3498
|
+
>
|
|
3499
|
+
<div
|
|
3500
|
+
className="jp-drawer-ambient"
|
|
3501
|
+
style={{
|
|
3502
|
+
background: `radial-gradient(ellipse 70% 60% at 50% 110%, ${activeColor}12 0%, transparent 65%)`,
|
|
3503
|
+
transition: 'background 1.5s ease',
|
|
3504
|
+
animation: 'ambient-pulse 3.5s ease infinite',
|
|
3505
|
+
}}
|
|
3506
|
+
aria-hidden
|
|
3507
|
+
/>
|
|
3508
|
+
|
|
3509
|
+
{isDone && (
|
|
3510
|
+
<div className="jp-drawer-shimmer" aria-hidden>
|
|
3511
|
+
<div
|
|
3512
|
+
className="jp-drawer-shimmer-bar"
|
|
3513
|
+
style={{
|
|
3514
|
+
background: 'linear-gradient(90deg, transparent, rgba(255,255,255,0.04), transparent)',
|
|
3515
|
+
animation: 'shimmer-sweep 1.4s 0.1s ease forwards',
|
|
3516
|
+
}}
|
|
3517
|
+
/>
|
|
3518
|
+
</div>
|
|
3519
|
+
)}
|
|
3139
3520
|
|
|
3521
|
+
<Particles count={particleCount} color={activeColor} />
|
|
3522
|
+
{burst && <SuccessBurst />}
|
|
3140
3523
|
|
|
3524
|
+
<div className="jp-drawer-content">
|
|
3525
|
+
<div className="jp-drawer-header">
|
|
3526
|
+
<div className="jp-drawer-header-left">
|
|
3527
|
+
<div className="jp-drawer-status" style={{ color: activeColor }}>
|
|
3528
|
+
<span
|
|
3529
|
+
className="jp-drawer-status-dot"
|
|
3530
|
+
style={{
|
|
3531
|
+
background: activeColor,
|
|
3532
|
+
boxShadow: `0 0 6px ${activeColor}`,
|
|
3533
|
+
animation: isRunning ? 'ambient-pulse 1.5s ease infinite' : 'none',
|
|
3534
|
+
}}
|
|
3535
|
+
aria-hidden
|
|
3536
|
+
/>
|
|
3537
|
+
{isDone ? 'Live' : isError ? 'Build failed' : currentStep?.verb ?? 'Saving'}
|
|
3538
|
+
</div>
|
|
3141
3539
|
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3540
|
+
<div key={currentStep?.id ?? phase} className="jp-drawer-copy animate-text-in">
|
|
3541
|
+
{isDone ? (
|
|
3542
|
+
<div className="animate-success-pop">
|
|
3543
|
+
<p className="jp-drawer-copy-title jp-drawer-copy-title-lg">Your content is live.</p>
|
|
3544
|
+
<p className="jp-drawer-copy-sub">Deployed to production successfully</p>
|
|
3545
|
+
</div>
|
|
3546
|
+
) : isError ? (
|
|
3547
|
+
<>
|
|
3548
|
+
<p className="jp-drawer-copy-title jp-drawer-copy-title-md">Deploy failed at build.</p>
|
|
3549
|
+
<p className="jp-drawer-copy-sub jp-drawer-copy-sub-error">{errorMessage ?? 'Check your Vercel logs or retry below'}</p>
|
|
3550
|
+
</>
|
|
3551
|
+
) : currentStep ? (
|
|
3552
|
+
<>
|
|
3553
|
+
<p className="jp-drawer-poem-line jp-drawer-poem-line-1">{currentStep.poem[0]}</p>
|
|
3554
|
+
<p className="jp-drawer-poem-line jp-drawer-poem-line-2">{currentStep.poem[1]}</p>
|
|
3555
|
+
</>
|
|
3556
|
+
) : null}
|
|
3557
|
+
</div>
|
|
3558
|
+
</div>
|
|
3147
3559
|
|
|
3148
|
-
|
|
3560
|
+
<div className="jp-drawer-right">
|
|
3561
|
+
{isDone ? (
|
|
3562
|
+
<div className="jp-drawer-countdown-wrap animate-fade-up">
|
|
3563
|
+
<span className="jp-drawer-countdown-text" aria-live="polite">
|
|
3564
|
+
Chiusura in {countdown}s
|
|
3565
|
+
</span>
|
|
3566
|
+
<div className="jp-drawer-countdown-track">
|
|
3567
|
+
<div className="jp-drawer-countdown-bar countdown-bar" style={{ boxShadow: '0 0 6px #34d39988' }} />
|
|
3568
|
+
</div>
|
|
3569
|
+
</div>
|
|
3570
|
+
) : (
|
|
3571
|
+
<ElapsedTimer running={isRunning} />
|
|
3572
|
+
)}
|
|
3573
|
+
</div>
|
|
3574
|
+
</div>
|
|
3575
|
+
|
|
3576
|
+
<div className="jp-drawer-track-row">
|
|
3577
|
+
{DEPLOY_STEPS.map((step, i) => (
|
|
3578
|
+
<div key={step.id} style={{ display: 'flex', alignItems: 'center', flex: i < DEPLOY_STEPS.length - 1 ? 1 : 'none' }}>
|
|
3579
|
+
<DeployNode step={step} state={stepState(i)} />
|
|
3580
|
+
{i < DEPLOY_STEPS.length - 1 && (
|
|
3581
|
+
<DeployConnector fromState={stepState(i)} toState={stepState(i + 1)} color={DEPLOY_STEPS[i + 1].color} />
|
|
3582
|
+
)}
|
|
3583
|
+
</div>
|
|
3584
|
+
))}
|
|
3585
|
+
</div>
|
|
3586
|
+
|
|
3587
|
+
<div className="jp-drawer-bars-wrap">
|
|
3588
|
+
<BuildBars active={stepState(2) === 'active'} />
|
|
3589
|
+
</div>
|
|
3590
|
+
|
|
3591
|
+
<div className="jp-drawer-separator" />
|
|
3592
|
+
|
|
3593
|
+
<div className="jp-drawer-footer">
|
|
3594
|
+
<div className="jp-drawer-progress">
|
|
3595
|
+
<div
|
|
3596
|
+
className="jp-drawer-progress-indicator"
|
|
3597
|
+
style={{
|
|
3598
|
+
width: `${Math.max(0, Math.min(100, progress))}%`,
|
|
3599
|
+
background: `linear-gradient(90deg, ${DEPLOY_STEPS[0].color}, ${activeColor})`,
|
|
3600
|
+
}}
|
|
3601
|
+
/>
|
|
3602
|
+
</div>
|
|
3603
|
+
|
|
3604
|
+
<div className="jp-drawer-cta">
|
|
3605
|
+
{isDone && (
|
|
3606
|
+
<div className="jp-drawer-btn-row animate-fade-up">
|
|
3607
|
+
<button type="button" className="jp-drawer-btn jp-drawer-btn-secondary" onClick={onClose}>
|
|
3608
|
+
Chiudi
|
|
3609
|
+
</button>
|
|
3610
|
+
<button
|
|
3611
|
+
type="button"
|
|
3612
|
+
className="jp-drawer-btn jp-drawer-btn-emerald"
|
|
3613
|
+
onClick={() => {
|
|
3614
|
+
if (deployUrl) window.open(deployUrl, '_blank', 'noopener,noreferrer');
|
|
3615
|
+
}}
|
|
3616
|
+
disabled={!deployUrl}
|
|
3617
|
+
>
|
|
3618
|
+
<span aria-hidden>↗</span> Open site
|
|
3619
|
+
</button>
|
|
3620
|
+
</div>
|
|
3621
|
+
)}
|
|
3622
|
+
|
|
3623
|
+
{isError && (
|
|
3624
|
+
<div className="jp-drawer-btn-row animate-fade-up">
|
|
3625
|
+
<button type="button" className="jp-drawer-btn jp-drawer-btn-ghost" onClick={onClose}>
|
|
3626
|
+
Annulla
|
|
3627
|
+
</button>
|
|
3628
|
+
<button type="button" className="jp-drawer-btn jp-drawer-btn-destructive" onClick={onRetry}>
|
|
3629
|
+
Retry
|
|
3630
|
+
</button>
|
|
3631
|
+
</div>
|
|
3632
|
+
)}
|
|
3633
|
+
|
|
3634
|
+
{isRunning && (
|
|
3635
|
+
<span className="jp-drawer-running-step" aria-hidden>
|
|
3636
|
+
{doneSteps.length + 1} / {DEPLOY_STEPS.length}
|
|
3637
|
+
</span>
|
|
3638
|
+
)}
|
|
3639
|
+
</div>
|
|
3640
|
+
</div>
|
|
3641
|
+
</div>
|
|
3642
|
+
</div>
|
|
3643
|
+
</div>
|
|
3644
|
+
</div>,
|
|
3645
|
+
shadowMount
|
|
3646
|
+
);
|
|
3647
|
+
}
|
|
3648
|
+
|
|
3649
|
+
END_OF_FILE_CONTENT
|
|
3650
|
+
# SKIP: src/components/save-drawer/DopaDrawer.tsx:Zone.Identifier è un file binario e non può essere convertito in testo.
|
|
3651
|
+
# SKIP: src/components/save-drawer/DopaDrawer.tsx:Zone.Identifier:Zone.Identifier è un file binario e non può essere convertito in testo.
|
|
3652
|
+
echo "Creating src/components/save-drawer/Visuals.tsx..."
|
|
3653
|
+
cat << 'END_OF_FILE_CONTENT' > "src/components/save-drawer/Visuals.tsx"
|
|
3654
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3655
|
+
import type { CSSProperties } from 'react';
|
|
3656
|
+
|
|
3657
|
+
interface Particle {
|
|
3658
|
+
id: number;
|
|
3659
|
+
x: number;
|
|
3660
|
+
y: number;
|
|
3661
|
+
size: number;
|
|
3662
|
+
dur: number;
|
|
3663
|
+
delay: number;
|
|
3664
|
+
}
|
|
3665
|
+
|
|
3666
|
+
const PARTICLE_POOL: Particle[] = Array.from({ length: 44 }, (_, i) => ({
|
|
3667
|
+
id: i,
|
|
3668
|
+
x: 5 + Math.random() * 90,
|
|
3669
|
+
y: 15 + Math.random() * 70,
|
|
3670
|
+
size: 1.5 + Math.random() * 2.5,
|
|
3671
|
+
dur: 2.8 + Math.random() * 3.5,
|
|
3672
|
+
delay: Math.random() * 4,
|
|
3673
|
+
}));
|
|
3674
|
+
|
|
3675
|
+
interface ParticlesProps {
|
|
3676
|
+
count: number;
|
|
3677
|
+
color: string;
|
|
3678
|
+
}
|
|
3679
|
+
|
|
3680
|
+
export function Particles({ count, color }: ParticlesProps) {
|
|
3681
|
+
return (
|
|
3682
|
+
<div className="jp-drawer-particles" aria-hidden>
|
|
3683
|
+
{PARTICLE_POOL.slice(0, count).map((particle) => (
|
|
3684
|
+
<div
|
|
3685
|
+
key={particle.id}
|
|
3686
|
+
className="jp-drawer-particle"
|
|
3687
|
+
style={{
|
|
3688
|
+
left: `${particle.x}%`,
|
|
3689
|
+
bottom: `${particle.y}%`,
|
|
3690
|
+
width: particle.size,
|
|
3691
|
+
height: particle.size,
|
|
3692
|
+
background: color,
|
|
3693
|
+
boxShadow: `0 0 ${particle.size * 3}px ${color}`,
|
|
3694
|
+
opacity: 0,
|
|
3695
|
+
animation: `particle-float ${particle.dur}s ${particle.delay}s ease-out infinite`,
|
|
3696
|
+
}}
|
|
3697
|
+
/>
|
|
3698
|
+
))}
|
|
3699
|
+
</div>
|
|
3700
|
+
);
|
|
3701
|
+
}
|
|
3702
|
+
|
|
3703
|
+
const BAR_H = [0.45, 0.75, 0.55, 0.9, 0.65, 0.8, 0.5, 0.72, 0.6, 0.85, 0.42, 0.7];
|
|
3704
|
+
|
|
3705
|
+
interface BuildBarsProps {
|
|
3706
|
+
active: boolean;
|
|
3707
|
+
}
|
|
3708
|
+
|
|
3709
|
+
export function BuildBars({ active }: BuildBarsProps) {
|
|
3710
|
+
if (!active) return <div className="jp-drawer-bars-placeholder" />;
|
|
3711
|
+
|
|
3712
|
+
return (
|
|
3713
|
+
<div className="jp-drawer-bars" aria-hidden>
|
|
3714
|
+
{BAR_H.map((height, i) => (
|
|
3715
|
+
<div
|
|
3716
|
+
key={i}
|
|
3717
|
+
className="jp-drawer-bar"
|
|
3718
|
+
style={{
|
|
3719
|
+
height: `${height * 100}%`,
|
|
3720
|
+
animation: `bar-eq ${0.42 + i * 0.06}s ${i * 0.04}s ease-in-out infinite alternate`,
|
|
3721
|
+
}}
|
|
3722
|
+
/>
|
|
3723
|
+
))}
|
|
3724
|
+
</div>
|
|
3725
|
+
);
|
|
3726
|
+
}
|
|
3727
|
+
|
|
3728
|
+
const BURST_COLORS = ['#34d399', '#60a5fa', '#a78bfa', '#f59e0b', '#f472b6'];
|
|
3729
|
+
|
|
3730
|
+
export function SuccessBurst() {
|
|
3731
|
+
return (
|
|
3732
|
+
<div className="jp-drawer-burst" aria-hidden>
|
|
3733
|
+
{Array.from({ length: 16 }).map((_, i) => (
|
|
3734
|
+
<div
|
|
3735
|
+
key={i}
|
|
3736
|
+
className="jp-drawer-burst-dot"
|
|
3737
|
+
style={
|
|
3738
|
+
{
|
|
3739
|
+
background: BURST_COLORS[i % BURST_COLORS.length],
|
|
3740
|
+
['--r' as string]: `${i * 22.5}deg`,
|
|
3741
|
+
animation: `burst-ray 0.85s ${i * 0.03}s cubic-bezier(0,0.6,0.5,1) forwards`,
|
|
3742
|
+
transform: `rotate(${i * 22.5}deg)`,
|
|
3743
|
+
transformOrigin: '50% 50%',
|
|
3744
|
+
opacity: 0,
|
|
3745
|
+
} as CSSProperties
|
|
3746
|
+
}
|
|
3747
|
+
/>
|
|
3748
|
+
))}
|
|
3749
|
+
</div>
|
|
3750
|
+
);
|
|
3751
|
+
}
|
|
3752
|
+
|
|
3753
|
+
interface ElapsedTimerProps {
|
|
3754
|
+
running: boolean;
|
|
3755
|
+
}
|
|
3756
|
+
|
|
3757
|
+
export function ElapsedTimer({ running }: ElapsedTimerProps) {
|
|
3758
|
+
const [elapsed, setElapsed] = useState(0);
|
|
3759
|
+
const startRef = useRef<number | null>(null);
|
|
3760
|
+
const rafRef = useRef<number | null>(null);
|
|
3761
|
+
|
|
3762
|
+
useEffect(() => {
|
|
3763
|
+
if (!running) return;
|
|
3764
|
+
if (!startRef.current) startRef.current = performance.now();
|
|
3765
|
+
|
|
3766
|
+
const tick = () => {
|
|
3767
|
+
if (!startRef.current) return;
|
|
3768
|
+
setElapsed(Math.floor((performance.now() - startRef.current) / 1000));
|
|
3769
|
+
rafRef.current = requestAnimationFrame(tick);
|
|
3770
|
+
};
|
|
3771
|
+
|
|
3772
|
+
rafRef.current = requestAnimationFrame(tick);
|
|
3773
|
+
return () => {
|
|
3774
|
+
if (rafRef.current) cancelAnimationFrame(rafRef.current);
|
|
3775
|
+
};
|
|
3776
|
+
}, [running]);
|
|
3777
|
+
|
|
3778
|
+
const sec = String(elapsed % 60).padStart(2, '0');
|
|
3779
|
+
const min = String(Math.floor(elapsed / 60)).padStart(2, '0');
|
|
3780
|
+
return <span className="jp-drawer-elapsed" aria-live="off">{min}:{sec}</span>;
|
|
3781
|
+
}
|
|
3782
|
+
|
|
3783
|
+
END_OF_FILE_CONTENT
|
|
3784
|
+
# SKIP: src/components/save-drawer/Visuals.tsx:Zone.Identifier è un file binario e non può essere convertito in testo.
|
|
3785
|
+
# SKIP: src/components/save-drawer/Visuals.tsx:Zone.Identifier:Zone.Identifier è un file binario e non può essere convertito in testo.
|
|
3786
|
+
echo "Creating src/components/save-drawer/saverStyle.css..."
|
|
3787
|
+
cat << 'END_OF_FILE_CONTENT' > "src/components/save-drawer/saverStyle.css"
|
|
3788
|
+
/* Save Drawer strict_full isolated stylesheet */
|
|
3789
|
+
|
|
3790
|
+
.jp-drawer-root {
|
|
3791
|
+
--background: 222 18% 6%;
|
|
3792
|
+
--foreground: 210 20% 96%;
|
|
3793
|
+
--card: 222 16% 8%;
|
|
3794
|
+
--card-foreground: 210 20% 96%;
|
|
3795
|
+
--primary: 0 0% 95%;
|
|
3796
|
+
--primary-foreground: 222 18% 6%;
|
|
3797
|
+
--secondary: 220 14% 13%;
|
|
3798
|
+
--secondary-foreground: 210 20% 96%;
|
|
3799
|
+
--destructive: 0 72% 51%;
|
|
3800
|
+
--destructive-foreground: 0 0% 98%;
|
|
3801
|
+
--border: 220 14% 13%;
|
|
3802
|
+
--radius: 0.6rem;
|
|
3803
|
+
font-family: 'Geist', system-ui, sans-serif;
|
|
3804
|
+
}
|
|
3805
|
+
|
|
3806
|
+
.jp-drawer-overlay {
|
|
3807
|
+
position: fixed;
|
|
3808
|
+
inset: 0;
|
|
3809
|
+
z-index: 2147483600;
|
|
3810
|
+
background: rgb(0 0 0 / 0.4);
|
|
3811
|
+
backdrop-filter: blur(2px);
|
|
3812
|
+
}
|
|
3813
|
+
|
|
3814
|
+
.jp-drawer-shell {
|
|
3815
|
+
position: fixed;
|
|
3816
|
+
left: 0;
|
|
3817
|
+
right: 0;
|
|
3818
|
+
z-index: 2147483601;
|
|
3819
|
+
display: flex;
|
|
3820
|
+
justify-content: center;
|
|
3821
|
+
padding: 0 1rem;
|
|
3822
|
+
}
|
|
3823
|
+
|
|
3824
|
+
.jp-drawer-card {
|
|
3825
|
+
position: relative;
|
|
3826
|
+
width: 100%;
|
|
3827
|
+
max-width: 31rem;
|
|
3828
|
+
overflow: hidden;
|
|
3829
|
+
border-radius: 1rem;
|
|
3830
|
+
border: 1px solid rgb(255 255 255 / 0.07);
|
|
3831
|
+
}
|
|
3832
|
+
|
|
3833
|
+
.jp-drawer-ambient {
|
|
3834
|
+
position: absolute;
|
|
3835
|
+
inset: 0;
|
|
3836
|
+
pointer-events: none;
|
|
3837
|
+
}
|
|
3838
|
+
|
|
3839
|
+
.jp-drawer-shimmer {
|
|
3840
|
+
position: absolute;
|
|
3841
|
+
inset: 0;
|
|
3842
|
+
overflow: hidden;
|
|
3843
|
+
pointer-events: none;
|
|
3844
|
+
}
|
|
3845
|
+
|
|
3846
|
+
.jp-drawer-shimmer-bar {
|
|
3847
|
+
position: absolute;
|
|
3848
|
+
inset-block: 0;
|
|
3849
|
+
width: 35%;
|
|
3850
|
+
}
|
|
3851
|
+
|
|
3852
|
+
.jp-drawer-content {
|
|
3853
|
+
position: relative;
|
|
3854
|
+
z-index: 10;
|
|
3855
|
+
padding: 2rem 2rem 1.75rem;
|
|
3856
|
+
}
|
|
3857
|
+
|
|
3858
|
+
.jp-drawer-header {
|
|
3859
|
+
margin-bottom: 1.5rem;
|
|
3860
|
+
display: flex;
|
|
3861
|
+
align-items: flex-start;
|
|
3862
|
+
justify-content: space-between;
|
|
3863
|
+
}
|
|
3864
|
+
|
|
3865
|
+
.jp-drawer-header-left {
|
|
3866
|
+
display: flex;
|
|
3867
|
+
flex-direction: column;
|
|
3868
|
+
gap: 0.625rem;
|
|
3869
|
+
}
|
|
3870
|
+
|
|
3871
|
+
.jp-drawer-status {
|
|
3872
|
+
display: flex;
|
|
3873
|
+
align-items: center;
|
|
3874
|
+
gap: 0.5rem;
|
|
3875
|
+
font-size: 0.75rem;
|
|
3876
|
+
font-weight: 700;
|
|
3877
|
+
text-transform: uppercase;
|
|
3878
|
+
letter-spacing: 0.1em;
|
|
3879
|
+
transition: color 0.5s;
|
|
3880
|
+
}
|
|
3881
|
+
|
|
3882
|
+
.jp-drawer-status-dot {
|
|
3883
|
+
width: 0.375rem;
|
|
3884
|
+
height: 0.375rem;
|
|
3885
|
+
border-radius: 9999px;
|
|
3886
|
+
display: inline-block;
|
|
3887
|
+
}
|
|
3888
|
+
|
|
3889
|
+
.jp-drawer-copy {
|
|
3890
|
+
min-height: 52px;
|
|
3891
|
+
}
|
|
3892
|
+
|
|
3893
|
+
.jp-drawer-copy-title {
|
|
3894
|
+
margin: 0;
|
|
3895
|
+
color: white;
|
|
3896
|
+
line-height: 1.25;
|
|
3897
|
+
font-weight: 600;
|
|
3898
|
+
}
|
|
3899
|
+
|
|
3900
|
+
.jp-drawer-copy-title-lg {
|
|
3901
|
+
font-size: 1.125rem;
|
|
3902
|
+
}
|
|
3903
|
+
|
|
3904
|
+
.jp-drawer-copy-title-md {
|
|
3905
|
+
font-size: 1rem;
|
|
3906
|
+
}
|
|
3907
|
+
|
|
3908
|
+
.jp-drawer-copy-sub {
|
|
3909
|
+
margin: 0.125rem 0 0;
|
|
3910
|
+
color: rgb(255 255 255 / 0.4);
|
|
3911
|
+
font-size: 0.875rem;
|
|
3912
|
+
}
|
|
3913
|
+
|
|
3914
|
+
.jp-drawer-copy-sub-error {
|
|
3915
|
+
color: rgb(255 255 255 / 0.35);
|
|
3916
|
+
}
|
|
3917
|
+
|
|
3918
|
+
.jp-drawer-poem-line {
|
|
3919
|
+
margin: 0;
|
|
3920
|
+
font-size: 0.875rem;
|
|
3921
|
+
font-weight: 300;
|
|
3922
|
+
line-height: 1.5;
|
|
3923
|
+
}
|
|
3924
|
+
|
|
3925
|
+
.jp-drawer-poem-line-1 {
|
|
3926
|
+
color: rgb(255 255 255 / 0.55);
|
|
3927
|
+
}
|
|
3928
|
+
|
|
3929
|
+
.jp-drawer-poem-line-2 {
|
|
3930
|
+
color: rgb(255 255 255 / 0.3);
|
|
3931
|
+
}
|
|
3932
|
+
|
|
3933
|
+
.jp-drawer-right {
|
|
3934
|
+
margin-left: 1.5rem;
|
|
3935
|
+
display: flex;
|
|
3936
|
+
flex-direction: column;
|
|
3937
|
+
align-items: flex-end;
|
|
3938
|
+
gap: 0.5rem;
|
|
3939
|
+
flex-shrink: 0;
|
|
3940
|
+
}
|
|
3941
|
+
|
|
3942
|
+
.jp-drawer-countdown-wrap {
|
|
3943
|
+
display: flex;
|
|
3944
|
+
flex-direction: column;
|
|
3945
|
+
align-items: flex-end;
|
|
3946
|
+
gap: 0.5rem;
|
|
3947
|
+
}
|
|
3948
|
+
|
|
3949
|
+
.jp-drawer-countdown-text {
|
|
3950
|
+
font-family: 'Geist Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
3951
|
+
font-size: 0.75rem;
|
|
3952
|
+
font-weight: 600;
|
|
3953
|
+
color: #34d399;
|
|
3954
|
+
}
|
|
3955
|
+
|
|
3956
|
+
.jp-drawer-countdown-track {
|
|
3957
|
+
width: 6rem;
|
|
3958
|
+
height: 0.125rem;
|
|
3959
|
+
border-radius: 9999px;
|
|
3960
|
+
overflow: hidden;
|
|
3961
|
+
background: rgb(255 255 255 / 0.1);
|
|
3962
|
+
}
|
|
3963
|
+
|
|
3964
|
+
.jp-drawer-countdown-bar {
|
|
3965
|
+
width: 100%;
|
|
3966
|
+
height: 100%;
|
|
3967
|
+
border-radius: 9999px;
|
|
3968
|
+
background: #34d399;
|
|
3969
|
+
}
|
|
3970
|
+
|
|
3971
|
+
.jp-drawer-track-row {
|
|
3972
|
+
margin-bottom: 1rem;
|
|
3973
|
+
display: flex;
|
|
3974
|
+
align-items: center;
|
|
3975
|
+
}
|
|
3976
|
+
|
|
3977
|
+
.jp-drawer-bars-wrap {
|
|
3978
|
+
margin-bottom: 1rem;
|
|
3979
|
+
display: flex;
|
|
3980
|
+
justify-content: center;
|
|
3981
|
+
}
|
|
3982
|
+
|
|
3983
|
+
.jp-drawer-separator {
|
|
3984
|
+
margin-bottom: 1rem;
|
|
3985
|
+
height: 1px;
|
|
3986
|
+
width: 100%;
|
|
3987
|
+
border: 0;
|
|
3988
|
+
background: rgb(255 255 255 / 0.06);
|
|
3989
|
+
}
|
|
3990
|
+
|
|
3991
|
+
.jp-drawer-footer {
|
|
3992
|
+
display: flex;
|
|
3993
|
+
align-items: center;
|
|
3994
|
+
gap: 1rem;
|
|
3995
|
+
}
|
|
3996
|
+
|
|
3997
|
+
.jp-drawer-progress {
|
|
3998
|
+
flex: 1;
|
|
3999
|
+
height: 2px;
|
|
4000
|
+
border-radius: 9999px;
|
|
4001
|
+
overflow: hidden;
|
|
4002
|
+
background: rgb(255 255 255 / 0.06);
|
|
4003
|
+
}
|
|
4004
|
+
|
|
4005
|
+
.jp-drawer-progress-indicator {
|
|
4006
|
+
height: 100%;
|
|
4007
|
+
}
|
|
4008
|
+
|
|
4009
|
+
.jp-drawer-cta {
|
|
4010
|
+
display: flex;
|
|
4011
|
+
align-items: center;
|
|
4012
|
+
gap: 0.5rem;
|
|
4013
|
+
flex-shrink: 0;
|
|
4014
|
+
}
|
|
4015
|
+
|
|
4016
|
+
.jp-drawer-running-step {
|
|
4017
|
+
font-family: 'Geist Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
4018
|
+
font-size: 0.75rem;
|
|
4019
|
+
color: rgb(255 255 255 / 0.2);
|
|
4020
|
+
}
|
|
4021
|
+
|
|
4022
|
+
.jp-drawer-btn-row {
|
|
4023
|
+
display: flex;
|
|
4024
|
+
gap: 0.5rem;
|
|
4025
|
+
}
|
|
4026
|
+
|
|
4027
|
+
.jp-drawer-btn {
|
|
4028
|
+
border: 1px solid transparent;
|
|
4029
|
+
border-radius: 0.375rem;
|
|
4030
|
+
font-size: 0.8125rem;
|
|
4031
|
+
font-weight: 500;
|
|
4032
|
+
line-height: 1;
|
|
4033
|
+
height: 2.25rem;
|
|
4034
|
+
padding: 0 0.75rem;
|
|
4035
|
+
cursor: pointer;
|
|
4036
|
+
transition: all 0.2s ease;
|
|
4037
|
+
display: inline-flex;
|
|
4038
|
+
align-items: center;
|
|
4039
|
+
justify-content: center;
|
|
4040
|
+
gap: 0.375rem;
|
|
4041
|
+
}
|
|
4042
|
+
|
|
4043
|
+
.jp-drawer-btn:disabled {
|
|
4044
|
+
opacity: 0.5;
|
|
4045
|
+
cursor: not-allowed;
|
|
4046
|
+
}
|
|
4047
|
+
|
|
4048
|
+
.jp-drawer-btn-secondary {
|
|
4049
|
+
background: hsl(var(--secondary));
|
|
4050
|
+
color: hsl(var(--secondary-foreground));
|
|
4051
|
+
}
|
|
4052
|
+
|
|
4053
|
+
.jp-drawer-btn-secondary:hover {
|
|
4054
|
+
filter: brightness(1.08);
|
|
4055
|
+
}
|
|
4056
|
+
|
|
4057
|
+
.jp-drawer-btn-emerald {
|
|
4058
|
+
background: #34d399;
|
|
4059
|
+
color: #18181b;
|
|
4060
|
+
font-weight: 600;
|
|
4061
|
+
}
|
|
4062
|
+
|
|
4063
|
+
.jp-drawer-btn-emerald:hover {
|
|
4064
|
+
background: #6ee7b7;
|
|
4065
|
+
}
|
|
4066
|
+
|
|
4067
|
+
.jp-drawer-btn-ghost {
|
|
4068
|
+
background: transparent;
|
|
4069
|
+
color: rgb(255 255 255 / 0.9);
|
|
4070
|
+
}
|
|
4071
|
+
|
|
4072
|
+
.jp-drawer-btn-ghost:hover {
|
|
4073
|
+
background: rgb(255 255 255 / 0.08);
|
|
4074
|
+
}
|
|
4075
|
+
|
|
4076
|
+
.jp-drawer-btn-destructive {
|
|
4077
|
+
background: hsl(var(--destructive));
|
|
4078
|
+
color: hsl(var(--destructive-foreground));
|
|
4079
|
+
}
|
|
4080
|
+
|
|
4081
|
+
.jp-drawer-btn-destructive:hover {
|
|
4082
|
+
filter: brightness(1.06);
|
|
4083
|
+
}
|
|
4084
|
+
|
|
4085
|
+
.jp-drawer-node-wrap {
|
|
4086
|
+
position: relative;
|
|
4087
|
+
z-index: 10;
|
|
4088
|
+
display: flex;
|
|
4089
|
+
flex-direction: column;
|
|
4090
|
+
align-items: center;
|
|
4091
|
+
gap: 0.625rem;
|
|
4092
|
+
}
|
|
4093
|
+
|
|
4094
|
+
.jp-drawer-node {
|
|
4095
|
+
position: relative;
|
|
4096
|
+
width: 3rem;
|
|
4097
|
+
height: 3rem;
|
|
4098
|
+
border-radius: 9999px;
|
|
4099
|
+
border: 1px solid transparent;
|
|
4100
|
+
display: flex;
|
|
4101
|
+
align-items: center;
|
|
4102
|
+
justify-content: center;
|
|
4103
|
+
transition: all 0.5s;
|
|
4104
|
+
}
|
|
4105
|
+
|
|
4106
|
+
.jp-drawer-node-pending {
|
|
4107
|
+
border-color: rgb(255 255 255 / 0.08);
|
|
4108
|
+
background: rgb(255 255 255 / 0.02);
|
|
4109
|
+
}
|
|
4110
|
+
|
|
4111
|
+
.jp-drawer-node-glyph {
|
|
4112
|
+
font-size: 1.125rem;
|
|
4113
|
+
line-height: 1;
|
|
4114
|
+
}
|
|
4115
|
+
|
|
4116
|
+
.jp-drawer-node-glyph-active {
|
|
4117
|
+
display: inline-block;
|
|
4118
|
+
}
|
|
4119
|
+
|
|
4120
|
+
.jp-drawer-node-glyph-pending {
|
|
4121
|
+
color: rgb(255 255 255 / 0.15);
|
|
4122
|
+
}
|
|
4123
|
+
|
|
4124
|
+
.jp-drawer-node-ring {
|
|
4125
|
+
position: absolute;
|
|
4126
|
+
border-radius: 9999px;
|
|
4127
|
+
border: 1px solid transparent;
|
|
4128
|
+
}
|
|
4129
|
+
|
|
4130
|
+
.jp-drawer-node-label {
|
|
4131
|
+
font-size: 10px;
|
|
4132
|
+
font-weight: 600;
|
|
4133
|
+
text-transform: uppercase;
|
|
4134
|
+
letter-spacing: 0.1em;
|
|
4135
|
+
transition: color 0.5s;
|
|
4136
|
+
}
|
|
4137
|
+
|
|
4138
|
+
.jp-drawer-connector {
|
|
4139
|
+
position: relative;
|
|
4140
|
+
z-index: 0;
|
|
4141
|
+
flex: 1;
|
|
4142
|
+
height: 2px;
|
|
4143
|
+
margin-top: -24px;
|
|
4144
|
+
}
|
|
4145
|
+
|
|
4146
|
+
.jp-drawer-connector-base {
|
|
4147
|
+
position: absolute;
|
|
4148
|
+
inset: 0;
|
|
4149
|
+
border-radius: 9999px;
|
|
4150
|
+
background: rgb(255 255 255 / 0.08);
|
|
4151
|
+
}
|
|
4152
|
+
|
|
4153
|
+
.jp-drawer-connector-fill {
|
|
4154
|
+
position: absolute;
|
|
4155
|
+
left: 0;
|
|
4156
|
+
right: auto;
|
|
4157
|
+
top: 0;
|
|
4158
|
+
bottom: 0;
|
|
4159
|
+
border-radius: 9999px;
|
|
4160
|
+
}
|
|
4161
|
+
|
|
4162
|
+
.jp-drawer-connector-orb {
|
|
4163
|
+
position: absolute;
|
|
4164
|
+
top: 50%;
|
|
4165
|
+
transform: translateY(-50%);
|
|
4166
|
+
width: 10px;
|
|
4167
|
+
height: 10px;
|
|
4168
|
+
border-radius: 9999px;
|
|
4169
|
+
}
|
|
4170
|
+
|
|
4171
|
+
.jp-drawer-particles {
|
|
4172
|
+
position: absolute;
|
|
4173
|
+
inset: 0;
|
|
4174
|
+
overflow: hidden;
|
|
4175
|
+
pointer-events: none;
|
|
4176
|
+
}
|
|
4177
|
+
|
|
4178
|
+
.jp-drawer-particle {
|
|
4179
|
+
position: absolute;
|
|
4180
|
+
border-radius: 9999px;
|
|
4181
|
+
}
|
|
4182
|
+
|
|
4183
|
+
.jp-drawer-bars {
|
|
4184
|
+
height: 1.75rem;
|
|
4185
|
+
display: flex;
|
|
4186
|
+
align-items: flex-end;
|
|
4187
|
+
gap: 3px;
|
|
4188
|
+
}
|
|
4189
|
+
|
|
4190
|
+
.jp-drawer-bars-placeholder {
|
|
4191
|
+
height: 1.75rem;
|
|
4192
|
+
}
|
|
4193
|
+
|
|
4194
|
+
.jp-drawer-bar {
|
|
4195
|
+
width: 3px;
|
|
4196
|
+
border-radius: 2px;
|
|
4197
|
+
background: #f59e0b;
|
|
4198
|
+
transform-origin: bottom;
|
|
4199
|
+
}
|
|
4200
|
+
|
|
4201
|
+
.jp-drawer-burst {
|
|
4202
|
+
position: absolute;
|
|
4203
|
+
inset: 0;
|
|
4204
|
+
display: flex;
|
|
4205
|
+
align-items: center;
|
|
4206
|
+
justify-content: center;
|
|
4207
|
+
pointer-events: none;
|
|
4208
|
+
}
|
|
4209
|
+
|
|
4210
|
+
.jp-drawer-burst-dot {
|
|
4211
|
+
position: absolute;
|
|
4212
|
+
width: 5px;
|
|
4213
|
+
height: 5px;
|
|
4214
|
+
border-radius: 9999px;
|
|
4215
|
+
}
|
|
4216
|
+
|
|
4217
|
+
.jp-drawer-elapsed {
|
|
4218
|
+
font-family: 'Geist Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
4219
|
+
font-size: 0.75rem;
|
|
4220
|
+
letter-spacing: 0.1em;
|
|
4221
|
+
color: rgb(255 255 255 / 0.25);
|
|
4222
|
+
}
|
|
4223
|
+
|
|
4224
|
+
/* Animation helper classes */
|
|
4225
|
+
.animate-drawer-up { animation: drawer-up 0.45s cubic-bezier(0.22, 1, 0.36, 1) forwards; }
|
|
4226
|
+
.animate-fade-in { animation: fade-in 0.25s ease forwards; }
|
|
4227
|
+
.animate-fade-up { animation: fade-up 0.35s ease forwards; }
|
|
4228
|
+
.animate-text-in { animation: text-in 0.3s ease forwards; }
|
|
4229
|
+
.animate-success-pop { animation: success-pop 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) forwards; }
|
|
4230
|
+
.countdown-bar { animation: countdown-drain 3s linear forwards; }
|
|
4231
|
+
|
|
4232
|
+
.stroke-dash-30 {
|
|
4233
|
+
stroke-dasharray: 30;
|
|
4234
|
+
stroke-dashoffset: 30;
|
|
4235
|
+
}
|
|
4236
|
+
|
|
4237
|
+
.animate-check-draw {
|
|
4238
|
+
animation: check-draw 0.4s 0.05s ease forwards;
|
|
4239
|
+
}
|
|
4240
|
+
|
|
4241
|
+
@keyframes check-draw {
|
|
4242
|
+
to { stroke-dashoffset: 0; }
|
|
4243
|
+
}
|
|
4244
|
+
|
|
4245
|
+
@keyframes drawer-up {
|
|
4246
|
+
from { transform: translateY(100%); opacity: 0; }
|
|
4247
|
+
to { transform: translateY(0); opacity: 1; }
|
|
4248
|
+
}
|
|
4249
|
+
|
|
4250
|
+
@keyframes fade-in {
|
|
4251
|
+
from { opacity: 0; }
|
|
4252
|
+
to { opacity: 1; }
|
|
4253
|
+
}
|
|
4254
|
+
|
|
4255
|
+
@keyframes fade-up {
|
|
4256
|
+
from { opacity: 0; transform: translateY(8px); }
|
|
4257
|
+
to { transform: translateY(0); opacity: 1; }
|
|
4258
|
+
}
|
|
4259
|
+
|
|
4260
|
+
@keyframes text-in {
|
|
4261
|
+
from { opacity: 0; transform: translateX(-6px); }
|
|
4262
|
+
to { opacity: 1; transform: translateX(0); }
|
|
4263
|
+
}
|
|
4264
|
+
|
|
4265
|
+
@keyframes success-pop {
|
|
4266
|
+
0% { transform: scale(0.88); opacity: 0; }
|
|
4267
|
+
60% { transform: scale(1.04); }
|
|
4268
|
+
100% { transform: scale(1); opacity: 1; }
|
|
4269
|
+
}
|
|
4270
|
+
|
|
4271
|
+
@keyframes ambient-pulse {
|
|
4272
|
+
0%, 100% { opacity: 0.3; }
|
|
4273
|
+
50% { opacity: 0.65; }
|
|
4274
|
+
}
|
|
4275
|
+
|
|
4276
|
+
@keyframes shimmer-sweep {
|
|
4277
|
+
from { transform: translateX(-100%); }
|
|
4278
|
+
to { transform: translateX(250%); }
|
|
4279
|
+
}
|
|
4280
|
+
|
|
4281
|
+
@keyframes node-glow {
|
|
4282
|
+
0%, 100% { box-shadow: 0 0 12px var(--glow-color,#60a5fa55); }
|
|
4283
|
+
50% { box-shadow: 0 0 28px var(--glow-color,#60a5fa88), 0 0 48px var(--glow-color,#60a5fa22); }
|
|
4284
|
+
}
|
|
4285
|
+
|
|
4286
|
+
@keyframes glyph-rotate {
|
|
4287
|
+
from { transform: rotate(0deg); }
|
|
4288
|
+
to { transform: rotate(360deg); }
|
|
4289
|
+
}
|
|
4290
|
+
|
|
4291
|
+
@keyframes ring-expand {
|
|
4292
|
+
from { transform: scale(1); opacity: 0.7; }
|
|
4293
|
+
to { transform: scale(2.1); opacity: 0; }
|
|
4294
|
+
}
|
|
4295
|
+
|
|
4296
|
+
@keyframes orb-travel {
|
|
4297
|
+
from { left: 0%; }
|
|
4298
|
+
to { left: calc(100% - 10px); }
|
|
4299
|
+
}
|
|
4300
|
+
|
|
4301
|
+
@keyframes particle-float {
|
|
4302
|
+
0% { transform: translateY(0) scale(1); opacity: 0; }
|
|
4303
|
+
15% { opacity: 1; }
|
|
4304
|
+
100% { transform: translateY(-90px) scale(0.3); opacity: 0; }
|
|
4305
|
+
}
|
|
4306
|
+
|
|
4307
|
+
@keyframes bar-eq {
|
|
4308
|
+
from { transform: scaleY(0.4); }
|
|
4309
|
+
to { transform: scaleY(1); }
|
|
4310
|
+
}
|
|
4311
|
+
|
|
4312
|
+
@keyframes burst-ray {
|
|
4313
|
+
0% { transform: rotate(var(--r, 0deg)) translateX(0); opacity: 1; }
|
|
4314
|
+
100% { transform: rotate(var(--r, 0deg)) translateX(56px); opacity: 0; }
|
|
4315
|
+
}
|
|
4316
|
+
|
|
4317
|
+
@keyframes countdown-drain {
|
|
4318
|
+
from { width: 100%; }
|
|
4319
|
+
to { width: 0%; }
|
|
4320
|
+
}
|
|
4321
|
+
|
|
4322
|
+
END_OF_FILE_CONTENT
|
|
4323
|
+
# SKIP: src/components/save-drawer/saverStyle.css:Zone.Identifier è un file binario e non può essere convertito in testo.
|
|
4324
|
+
# SKIP: src/components/save-drawer/saverStyle.css:Zone.Identifier:Zone.Identifier è un file binario e non può essere convertito in testo.
|
|
4325
|
+
mkdir -p "src/components/ui"
|
|
4326
|
+
echo "Creating src/components/ui/checkbox.tsx..."
|
|
4327
|
+
cat << 'END_OF_FILE_CONTENT' > "src/components/ui/checkbox.tsx"
|
|
4328
|
+
"use client"
|
|
4329
|
+
|
|
4330
|
+
import * as React from "react"
|
|
4331
|
+
import { Checkbox as CheckboxPrimitive } from "radix-ui"
|
|
4332
|
+
|
|
4333
|
+
import { cn } from "@/lib/utils"
|
|
4334
|
+
import { CheckIcon } from "lucide-react"
|
|
4335
|
+
|
|
4336
|
+
function Checkbox({
|
|
4337
|
+
className,
|
|
4338
|
+
...props
|
|
4339
|
+
}: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
|
|
4340
|
+
return (
|
|
4341
|
+
<CheckboxPrimitive.Root
|
|
4342
|
+
data-slot="checkbox"
|
|
4343
|
+
className={cn(
|
|
4344
|
+
"border-input dark:bg-input/30 data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary data-checked:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 flex size-4 items-center justify-center rounded-[4px] border transition-colors group-has-disabled/field:opacity-50 focus-visible:ring-3 aria-invalid:ring-3 peer relative shrink-0 outline-none after:absolute after:-inset-x-3 after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50",
|
|
4345
|
+
className
|
|
4346
|
+
)}
|
|
4347
|
+
{...props}
|
|
4348
|
+
>
|
|
4349
|
+
<CheckboxPrimitive.Indicator
|
|
4350
|
+
data-slot="checkbox-indicator"
|
|
4351
|
+
className="[&>svg]:size-3.5 grid place-content-center text-current transition-none"
|
|
4352
|
+
>
|
|
4353
|
+
<CheckIcon
|
|
4354
|
+
/>
|
|
4355
|
+
</CheckboxPrimitive.Indicator>
|
|
4356
|
+
</CheckboxPrimitive.Root>
|
|
4357
|
+
)
|
|
4358
|
+
}
|
|
4359
|
+
|
|
4360
|
+
export { Checkbox }
|
|
4361
|
+
|
|
4362
|
+
|
|
4363
|
+
|
|
4364
|
+
|
|
4365
|
+
|
|
4366
|
+
END_OF_FILE_CONTENT
|
|
4367
|
+
echo "Creating src/components/ui/input.tsx..."
|
|
4368
|
+
cat << 'END_OF_FILE_CONTENT' > "src/components/ui/input.tsx"
|
|
4369
|
+
import * as React from "react"
|
|
4370
|
+
|
|
4371
|
+
import { cn } from "@/lib/utils"
|
|
4372
|
+
|
|
4373
|
+
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
|
4374
|
+
return (
|
|
4375
|
+
<input
|
|
4376
|
+
type={type}
|
|
4377
|
+
data-slot="input"
|
|
4378
|
+
className={cn(
|
|
4379
|
+
"dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 h-8 rounded-lg border bg-transparent px-2.5 py-1 text-base transition-colors file:h-6 file:text-sm file:font-medium focus-visible:ring-3 aria-invalid:ring-3 md:text-sm file:text-foreground placeholder:text-muted-foreground w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
|
|
4380
|
+
className
|
|
4381
|
+
)}
|
|
4382
|
+
{...props}
|
|
4383
|
+
/>
|
|
4384
|
+
)
|
|
4385
|
+
}
|
|
4386
|
+
|
|
4387
|
+
export { Input }
|
|
4388
|
+
|
|
4389
|
+
|
|
4390
|
+
|
|
4391
|
+
|
|
4392
|
+
|
|
4393
|
+
END_OF_FILE_CONTENT
|
|
4394
|
+
echo "Creating src/components/ui/label.tsx..."
|
|
4395
|
+
cat << 'END_OF_FILE_CONTENT' > "src/components/ui/label.tsx"
|
|
4396
|
+
import * as React from "react"
|
|
4397
|
+
import { Label as LabelPrimitive } from "radix-ui"
|
|
4398
|
+
|
|
4399
|
+
import { cn } from "@/lib/utils"
|
|
4400
|
+
|
|
4401
|
+
function Label({
|
|
4402
|
+
className,
|
|
4403
|
+
...props
|
|
4404
|
+
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
|
4405
|
+
return (
|
|
4406
|
+
<LabelPrimitive.Root
|
|
4407
|
+
data-slot="label"
|
|
4408
|
+
className={cn(
|
|
4409
|
+
"gap-2 text-sm leading-none font-medium group-data-[disabled=true]:opacity-50 peer-disabled:opacity-50 flex items-center select-none group-data-[disabled=true]:pointer-events-none peer-disabled:cursor-not-allowed",
|
|
4410
|
+
className
|
|
4411
|
+
)}
|
|
4412
|
+
{...props}
|
|
4413
|
+
/>
|
|
4414
|
+
)
|
|
4415
|
+
}
|
|
4416
|
+
|
|
4417
|
+
export { Label }
|
|
4418
|
+
|
|
4419
|
+
|
|
4420
|
+
|
|
4421
|
+
|
|
4422
|
+
|
|
4423
|
+
END_OF_FILE_CONTENT
|
|
4424
|
+
echo "Creating src/components/ui/select.tsx..."
|
|
4425
|
+
cat << 'END_OF_FILE_CONTENT' > "src/components/ui/select.tsx"
|
|
4426
|
+
import * as React from "react"
|
|
4427
|
+
import { Select as SelectPrimitive } from "radix-ui"
|
|
4428
|
+
|
|
4429
|
+
import { cn } from "@/lib/utils"
|
|
3149
4430
|
import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react"
|
|
3150
4431
|
|
|
3151
4432
|
function Select({
|
|
@@ -4587,6 +5868,7 @@ export const ComponentRegistry: {
|
|
|
4587
5868
|
};
|
|
4588
5869
|
|
|
4589
5870
|
END_OF_FILE_CONTENT
|
|
5871
|
+
# SKIP: src/lib/ComponentRegistry.tsx:Zone.Identifier è un file binario e non può essere convertito in testo.
|
|
4590
5872
|
echo "Creating src/lib/IconResolver.tsx..."
|
|
4591
5873
|
cat << 'END_OF_FILE_CONTENT' > "src/lib/IconResolver.tsx"
|
|
4592
5874
|
import React from 'react';
|
|
@@ -4645,6 +5927,7 @@ export const Icon: React.FC<IconProps> = ({ name, size = 20, className }) => {
|
|
|
4645
5927
|
|
|
4646
5928
|
|
|
4647
5929
|
END_OF_FILE_CONTENT
|
|
5930
|
+
# SKIP: src/lib/IconResolver.tsx:Zone.Identifier è un file binario e non può essere convertito in testo.
|
|
4648
5931
|
echo "Creating src/lib/addSectionConfig.ts..."
|
|
4649
5932
|
cat << 'END_OF_FILE_CONTENT' > "src/lib/addSectionConfig.ts"
|
|
4650
5933
|
import type { AddSectionConfig } from '@jsonpages/core';
|
|
@@ -4704,6 +5987,7 @@ export const addSectionConfig: AddSectionConfig = {
|
|
|
4704
5987
|
};
|
|
4705
5988
|
|
|
4706
5989
|
END_OF_FILE_CONTENT
|
|
5990
|
+
# SKIP: src/lib/addSectionConfig.ts:Zone.Identifier è un file binario e non può essere convertito in testo.
|
|
4707
5991
|
echo "Creating src/lib/base-schemas.ts..."
|
|
4708
5992
|
cat << 'END_OF_FILE_CONTENT' > "src/lib/base-schemas.ts"
|
|
4709
5993
|
import { z } from 'zod';
|
|
@@ -4746,6 +6030,179 @@ export const CtaSchema = z.object({
|
|
|
4746
6030
|
});
|
|
4747
6031
|
|
|
4748
6032
|
END_OF_FILE_CONTENT
|
|
6033
|
+
# SKIP: src/lib/base-schemas.ts:Zone.Identifier è un file binario e non può essere convertito in testo.
|
|
6034
|
+
echo "Creating src/lib/cloudSaveStream.ts..."
|
|
6035
|
+
cat << 'END_OF_FILE_CONTENT' > "src/lib/cloudSaveStream.ts"
|
|
6036
|
+
import type { StepId } from '@/types/deploy';
|
|
6037
|
+
|
|
6038
|
+
interface SaveStreamStepEvent {
|
|
6039
|
+
id: StepId;
|
|
6040
|
+
status: 'running' | 'done';
|
|
6041
|
+
label?: string;
|
|
6042
|
+
}
|
|
6043
|
+
|
|
6044
|
+
interface SaveStreamLogEvent {
|
|
6045
|
+
stepId: StepId;
|
|
6046
|
+
message: string;
|
|
6047
|
+
}
|
|
6048
|
+
|
|
6049
|
+
interface SaveStreamDoneEvent {
|
|
6050
|
+
deployUrl?: string;
|
|
6051
|
+
commitSha?: string;
|
|
6052
|
+
}
|
|
6053
|
+
|
|
6054
|
+
interface SaveStreamErrorEvent {
|
|
6055
|
+
message?: string;
|
|
6056
|
+
}
|
|
6057
|
+
|
|
6058
|
+
interface StartCloudSaveStreamInput {
|
|
6059
|
+
apiBaseUrl: string;
|
|
6060
|
+
apiKey: string;
|
|
6061
|
+
path: string;
|
|
6062
|
+
content: unknown;
|
|
6063
|
+
message?: string;
|
|
6064
|
+
signal?: AbortSignal;
|
|
6065
|
+
onStep: (event: SaveStreamStepEvent) => void;
|
|
6066
|
+
onLog?: (event: SaveStreamLogEvent) => void;
|
|
6067
|
+
onDone: (event: SaveStreamDoneEvent) => void;
|
|
6068
|
+
}
|
|
6069
|
+
|
|
6070
|
+
function parseSseEventBlock(rawBlock: string): { event: string; data: string } | null {
|
|
6071
|
+
const lines = rawBlock
|
|
6072
|
+
.split('\n')
|
|
6073
|
+
.map((line) => line.trimEnd())
|
|
6074
|
+
.filter((line) => line.length > 0);
|
|
6075
|
+
|
|
6076
|
+
if (lines.length === 0) return null;
|
|
6077
|
+
|
|
6078
|
+
let eventName = 'message';
|
|
6079
|
+
const dataLines: string[] = [];
|
|
6080
|
+
for (const line of lines) {
|
|
6081
|
+
if (line.startsWith('event:')) {
|
|
6082
|
+
eventName = line.slice(6).trim();
|
|
6083
|
+
continue;
|
|
6084
|
+
}
|
|
6085
|
+
if (line.startsWith('data:')) {
|
|
6086
|
+
dataLines.push(line.slice(5).trim());
|
|
6087
|
+
}
|
|
6088
|
+
}
|
|
6089
|
+
return { event: eventName, data: dataLines.join('\n') };
|
|
6090
|
+
}
|
|
6091
|
+
|
|
6092
|
+
export async function startCloudSaveStream(input: StartCloudSaveStreamInput): Promise<void> {
|
|
6093
|
+
const response = await fetch(`${input.apiBaseUrl}/save-stream`, {
|
|
6094
|
+
method: 'POST',
|
|
6095
|
+
headers: {
|
|
6096
|
+
'Content-Type': 'application/json',
|
|
6097
|
+
Authorization: `Bearer ${input.apiKey}`,
|
|
6098
|
+
},
|
|
6099
|
+
body: JSON.stringify({
|
|
6100
|
+
path: input.path,
|
|
6101
|
+
content: input.content,
|
|
6102
|
+
message: input.message,
|
|
6103
|
+
}),
|
|
6104
|
+
signal: input.signal,
|
|
6105
|
+
});
|
|
6106
|
+
|
|
6107
|
+
if (!response.ok || !response.body) {
|
|
6108
|
+
const body = (await response.json().catch(() => ({}))) as SaveStreamErrorEvent;
|
|
6109
|
+
throw new Error(body.message ?? `Cloud save stream failed: ${response.status}`);
|
|
6110
|
+
}
|
|
6111
|
+
|
|
6112
|
+
const reader = response.body.getReader();
|
|
6113
|
+
const decoder = new TextDecoder();
|
|
6114
|
+
let buffer = '';
|
|
6115
|
+
let receivedDone = false;
|
|
6116
|
+
|
|
6117
|
+
while (true) {
|
|
6118
|
+
const { value, done } = await reader.read();
|
|
6119
|
+
if (!done) {
|
|
6120
|
+
buffer += decoder.decode(value, { stream: true });
|
|
6121
|
+
} else {
|
|
6122
|
+
buffer += decoder.decode();
|
|
6123
|
+
}
|
|
6124
|
+
|
|
6125
|
+
const chunks = buffer.split('\n\n');
|
|
6126
|
+
buffer = done ? '' : (chunks.pop() ?? '');
|
|
6127
|
+
|
|
6128
|
+
for (const chunk of chunks) {
|
|
6129
|
+
const parsed = parseSseEventBlock(chunk);
|
|
6130
|
+
if (!parsed) continue;
|
|
6131
|
+
if (!parsed.data) continue;
|
|
6132
|
+
|
|
6133
|
+
if (parsed.event === 'step') {
|
|
6134
|
+
const payload = JSON.parse(parsed.data) as SaveStreamStepEvent;
|
|
6135
|
+
input.onStep(payload);
|
|
6136
|
+
} else if (parsed.event === 'log') {
|
|
6137
|
+
const payload = JSON.parse(parsed.data) as SaveStreamLogEvent;
|
|
6138
|
+
input.onLog?.(payload);
|
|
6139
|
+
} else if (parsed.event === 'error') {
|
|
6140
|
+
const payload = JSON.parse(parsed.data) as SaveStreamErrorEvent;
|
|
6141
|
+
throw new Error(payload.message ?? 'Cloud save failed.');
|
|
6142
|
+
} else if (parsed.event === 'done') {
|
|
6143
|
+
const payload = JSON.parse(parsed.data) as SaveStreamDoneEvent;
|
|
6144
|
+
input.onDone(payload);
|
|
6145
|
+
receivedDone = true;
|
|
6146
|
+
}
|
|
6147
|
+
}
|
|
6148
|
+
|
|
6149
|
+
if (done) break;
|
|
6150
|
+
}
|
|
6151
|
+
|
|
6152
|
+
if (!receivedDone) {
|
|
6153
|
+
throw new Error('Cloud save stream ended before completion.');
|
|
6154
|
+
}
|
|
6155
|
+
}
|
|
6156
|
+
|
|
6157
|
+
END_OF_FILE_CONTENT
|
|
6158
|
+
# SKIP: src/lib/cloudSaveStream.ts:Zone.Identifier è un file binario e non può essere convertito in testo.
|
|
6159
|
+
# SKIP: src/lib/cloudSaveStream.ts:Zone.Identifier:Zone.Identifier è un file binario e non può essere convertito in testo.
|
|
6160
|
+
echo "Creating src/lib/deploySteps.ts..."
|
|
6161
|
+
cat << 'END_OF_FILE_CONTENT' > "src/lib/deploySteps.ts"
|
|
6162
|
+
import type { DeployStep } from '@/types/deploy';
|
|
6163
|
+
|
|
6164
|
+
export const DEPLOY_STEPS: readonly DeployStep[] = [
|
|
6165
|
+
{
|
|
6166
|
+
id: 'commit',
|
|
6167
|
+
label: 'Commit',
|
|
6168
|
+
verb: 'Committing',
|
|
6169
|
+
poem: ['Crystallizing your edit', 'into permanent history.'],
|
|
6170
|
+
color: '#60a5fa',
|
|
6171
|
+
glyph: '◈',
|
|
6172
|
+
duration: 2200,
|
|
6173
|
+
},
|
|
6174
|
+
{
|
|
6175
|
+
id: 'push',
|
|
6176
|
+
label: 'Push',
|
|
6177
|
+
verb: 'Pushing',
|
|
6178
|
+
poem: ['Sending your vision', 'across the wire.'],
|
|
6179
|
+
color: '#a78bfa',
|
|
6180
|
+
glyph: '◎',
|
|
6181
|
+
duration: 2800,
|
|
6182
|
+
},
|
|
6183
|
+
{
|
|
6184
|
+
id: 'build',
|
|
6185
|
+
label: 'Build',
|
|
6186
|
+
verb: 'Building',
|
|
6187
|
+
poem: ['Assembling the pieces,', 'brick by digital brick.'],
|
|
6188
|
+
color: '#f59e0b',
|
|
6189
|
+
glyph: '⬡',
|
|
6190
|
+
duration: 7500,
|
|
6191
|
+
},
|
|
6192
|
+
{
|
|
6193
|
+
id: 'live',
|
|
6194
|
+
label: 'Live',
|
|
6195
|
+
verb: 'Going live',
|
|
6196
|
+
poem: ['Your content', 'is now breathing.'],
|
|
6197
|
+
color: '#34d399',
|
|
6198
|
+
glyph: '✦',
|
|
6199
|
+
duration: 1600,
|
|
6200
|
+
},
|
|
6201
|
+
] as const;
|
|
6202
|
+
|
|
6203
|
+
END_OF_FILE_CONTENT
|
|
6204
|
+
# SKIP: src/lib/deploySteps.ts:Zone.Identifier è un file binario e non può essere convertito in testo.
|
|
6205
|
+
# SKIP: src/lib/deploySteps.ts:Zone.Identifier:Zone.Identifier è un file binario e non può essere convertito in testo.
|
|
4749
6206
|
echo "Creating src/lib/draftStorage.ts..."
|
|
4750
6207
|
cat << 'END_OF_FILE_CONTENT' > "src/lib/draftStorage.ts"
|
|
4751
6208
|
/**
|
|
@@ -4774,6 +6231,7 @@ export function getHydratedData(
|
|
|
4774
6231
|
}
|
|
4775
6232
|
|
|
4776
6233
|
END_OF_FILE_CONTENT
|
|
6234
|
+
# SKIP: src/lib/draftStorage.ts:Zone.Identifier è un file binario e non può essere convertito in testo.
|
|
4777
6235
|
echo "Creating src/lib/getFilePages.ts..."
|
|
4778
6236
|
cat << 'END_OF_FILE_CONTENT' > "src/lib/getFilePages.ts"
|
|
4779
6237
|
/**
|
|
@@ -4809,6 +6267,7 @@ export function getFilePages(): Record<string, PageConfig> {
|
|
|
4809
6267
|
}
|
|
4810
6268
|
|
|
4811
6269
|
END_OF_FILE_CONTENT
|
|
6270
|
+
# SKIP: src/lib/getFilePages.ts:Zone.Identifier è un file binario e non può essere convertito in testo.
|
|
4812
6271
|
echo "Creating src/lib/schemas.ts..."
|
|
4813
6272
|
cat << 'END_OF_FILE_CONTENT' > "src/lib/schemas.ts"
|
|
4814
6273
|
export { BaseSectionData, BaseArrayItem, BaseSectionSettingsSchema, CtaSchema } from './base-schemas';
|
|
@@ -4856,6 +6315,7 @@ export const SECTION_SCHEMAS = {
|
|
|
4856
6315
|
export type SectionType = keyof typeof SECTION_SCHEMAS;
|
|
4857
6316
|
|
|
4858
6317
|
END_OF_FILE_CONTENT
|
|
6318
|
+
# SKIP: src/lib/schemas.ts:Zone.Identifier è un file binario e non può essere convertito in testo.
|
|
4859
6319
|
echo "Creating src/lib/utils.ts..."
|
|
4860
6320
|
cat << 'END_OF_FILE_CONTENT' > "src/lib/utils.ts"
|
|
4861
6321
|
import { clsx, type ClassValue } from 'clsx';
|
|
@@ -4866,6 +6326,7 @@ export function cn(...inputs: ClassValue[]) {
|
|
|
4866
6326
|
}
|
|
4867
6327
|
|
|
4868
6328
|
END_OF_FILE_CONTENT
|
|
6329
|
+
# SKIP: src/lib/utils.ts:Zone.Identifier è un file binario e non può essere convertito in testo.
|
|
4869
6330
|
echo "Creating src/main.tsx..."
|
|
4870
6331
|
cat << 'END_OF_FILE_CONTENT' > "src/main.tsx"
|
|
4871
6332
|
import '@/types'; // TBP: load type augmentation from capsule-driven types
|
|
@@ -4885,26 +6346,9 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
|
4885
6346
|
|
|
4886
6347
|
END_OF_FILE_CONTENT
|
|
4887
6348
|
# SKIP: src/main.tsx:Zone.Identifier è un file binario e non può essere convertito in testo.
|
|
4888
|
-
echo "Creating src/main_.tsx..."
|
|
4889
|
-
cat << 'END_OF_FILE_CONTENT' > "src/main_.tsx"
|
|
4890
|
-
import '@/types'; // TBP: load type augmentation from capsule-driven types
|
|
4891
|
-
import React from 'react';
|
|
4892
|
-
import ReactDOM from 'react-dom/client';
|
|
4893
|
-
import App from './App';
|
|
4894
|
-
// ... resto del file
|
|
4895
|
-
|
|
4896
|
-
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
4897
|
-
<React.StrictMode>
|
|
4898
|
-
<App />
|
|
4899
|
-
</React.StrictMode>
|
|
4900
|
-
);
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
END_OF_FILE_CONTENT
|
|
4906
6349
|
# SKIP: src/registry-types.ts è un file binario e non può essere convertito in testo.
|
|
4907
6350
|
mkdir -p "src/server"
|
|
6351
|
+
mkdir -p "src/types"
|
|
4908
6352
|
echo "Creating src/types.ts..."
|
|
4909
6353
|
cat << 'END_OF_FILE_CONTENT' > "src/types.ts"
|
|
4910
6354
|
import type { MenuItem } from '@jsonpages/core';
|
|
@@ -4994,6 +6438,26 @@ declare module '@jsonpages/core' {
|
|
|
4994
6438
|
export * from '@jsonpages/core';
|
|
4995
6439
|
|
|
4996
6440
|
END_OF_FILE_CONTENT
|
|
6441
|
+
echo "Creating src/types/deploy.ts..."
|
|
6442
|
+
cat << 'END_OF_FILE_CONTENT' > "src/types/deploy.ts"
|
|
6443
|
+
export type StepId = 'commit' | 'push' | 'build' | 'live';
|
|
6444
|
+
|
|
6445
|
+
export type StepState = 'pending' | 'active' | 'done';
|
|
6446
|
+
|
|
6447
|
+
export type DeployPhase = 'idle' | 'running' | 'done' | 'error';
|
|
6448
|
+
|
|
6449
|
+
export interface DeployStep {
|
|
6450
|
+
id: StepId;
|
|
6451
|
+
label: string;
|
|
6452
|
+
verb: string;
|
|
6453
|
+
poem: [string, string];
|
|
6454
|
+
color: string;
|
|
6455
|
+
glyph: string;
|
|
6456
|
+
duration: number;
|
|
6457
|
+
}
|
|
6458
|
+
|
|
6459
|
+
END_OF_FILE_CONTENT
|
|
6460
|
+
# SKIP: src/types/deploy.ts:Zone.Identifier è un file binario e non può essere convertito in testo.
|
|
4997
6461
|
echo "Creating src/vite-env.d.ts..."
|
|
4998
6462
|
cat << 'END_OF_FILE_CONTENT' > "src/vite-env.d.ts"
|
|
4999
6463
|
/// <reference types="vite/client" />
|
|
@@ -5005,4 +6469,26 @@ declare module '*?inline' {
|
|
|
5005
6469
|
|
|
5006
6470
|
|
|
5007
6471
|
|
|
6472
|
+
END_OF_FILE_CONTENT
|
|
6473
|
+
echo "Creating vercel.json..."
|
|
6474
|
+
cat << 'END_OF_FILE_CONTENT' > "vercel.json"
|
|
6475
|
+
{
|
|
6476
|
+
"rewrites": [
|
|
6477
|
+
{
|
|
6478
|
+
"source": "/(.*)",
|
|
6479
|
+
"destination": "/index.html"
|
|
6480
|
+
}
|
|
6481
|
+
],
|
|
6482
|
+
"headers": [
|
|
6483
|
+
{
|
|
6484
|
+
"source": "/assets/(.*)",
|
|
6485
|
+
"headers": [
|
|
6486
|
+
{
|
|
6487
|
+
"key": "Cache-Control",
|
|
6488
|
+
"value": "public, max-age=31536000, immutable"
|
|
6489
|
+
}
|
|
6490
|
+
]
|
|
6491
|
+
}
|
|
6492
|
+
]
|
|
6493
|
+
}
|
|
5008
6494
|
END_OF_FILE_CONTENT
|