@jsonpages/cli 3.0.63 → 3.0.64

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.
@@ -36,14 +36,16 @@ cat << 'END_OF_FILE_CONTENT' > "package.json"
36
36
  "dev:clean": "vite --force",
37
37
  "build": "tsc && vite build",
38
38
  "dist": "bash ./src2Code.sh src index.html package.json",
39
- "preview": "vite preview"
39
+ "preview": "vite preview",
40
+ "bake:email": "tsx scripts/bake-email.tsx",
41
+ "bakemail": "npm run bake:email --"
40
42
  },
41
43
  "dependencies": {
42
44
  "@tiptap/extension-image": "^2.11.5",
43
45
  "@tiptap/extension-link": "^2.11.5",
44
46
  "@tiptap/react": "^2.11.5",
45
47
  "@tiptap/starter-kit": "^2.11.5",
46
- "@jsonpages/core": "^1.0.50",
48
+ "@jsonpages/core": "^1.0.51",
47
49
  "clsx": "^2.1.1",
48
50
  "lucide-react": "^0.474.0",
49
51
  "react": "^19.0.0",
@@ -62,7 +64,10 @@ cat << 'END_OF_FILE_CONTENT' > "package.json"
62
64
  "@types/react-dom": "^19.0.3",
63
65
  "@vitejs/plugin-react": "^4.3.4",
64
66
  "typescript": "^5.7.3",
65
- "vite": "^6.0.11"
67
+ "vite": "^6.0.11",
68
+ "@react-email/components": "^0.0.41",
69
+ "@react-email/render": "^1.0.5",
70
+ "tsx": "^4.20.5"
66
71
  }
67
72
  }
68
73
 
@@ -213,7 +218,6 @@ console.log("🔍 DEBUG ENV:", {
213
218
  export default App;
214
219
 
215
220
  END_OF_FILE_CONTENT
216
- # SKIP: src/App.tsx:Zone.Identifier è un file binario e non può essere convertito in testo.
217
221
  echo "Creating src/App_.tsx..."
218
222
  cat << 'END_OF_FILE_CONTENT' > "src/App_.tsx"
219
223
  import { useState, useEffect } from 'react';
@@ -3085,17 +3089,248 @@ export type ProductTriadSettings = z.infer<typeof BaseSectionSettingsSchema>;
3085
3089
 
3086
3090
  END_OF_FILE_CONTENT
3087
3091
  mkdir -p "src/components/save-drawer"
3088
- # SKIP: src/components/save-drawer/DeployConnector.tsx:Zone.Identifier è un file binario e non può essere convertito in testo.
3089
- # SKIP: src/components/save-drawer/DeployConnector.tsx:Zone.Identifier:Zone.Identifier è un file binario e non può essere convertito in testo.
3090
- # SKIP: src/components/save-drawer/DeployNode.tsx:Zone.Identifier è un file binario e non può essere convertito in testo.
3091
- # SKIP: src/components/save-drawer/DeployNode.tsx:Zone.Identifier:Zone.Identifier è un file binario e non può essere convertito in testo.
3092
- # SKIP: src/components/save-drawer/DopaDrawer.tsx:Zone.Identifier è un file binario e non può essere convertito in testo.
3093
- # SKIP: src/components/save-drawer/DopaDrawer.tsx:Zone.Identifier:Zone.Identifier è un file binario e non può essere convertito in testo.
3094
- # SKIP: src/components/save-drawer/Visuals.tsx:Zone.Identifier è un file binario e non può essere convertito in testo.
3095
- # SKIP: src/components/save-drawer/Visuals.tsx:Zone.Identifier:Zone.Identifier è un file binario e non può essere convertito in testo.
3096
- # SKIP: src/components/save-drawer/saverStyle.css:Zone.Identifier è un file binario e non può essere convertito in testo.
3097
- # SKIP: src/components/save-drawer/saverStyle.css:Zone.Identifier:Zone.Identifier è un file binario e non può essere convertito in testo.
3098
3092
  mkdir -p "src/components/tiptap"
3093
+ echo "Creating src/components/tiptap/INTEGRATION.md..."
3094
+ cat << 'END_OF_FILE_CONTENT' > "src/components/tiptap/INTEGRATION.md"
3095
+ # Tiptap Editorial — Integration Guide
3096
+
3097
+ How to add the `tiptap` section to a new tenant.
3098
+
3099
+ ---
3100
+
3101
+ ## 1. Copy the component
3102
+
3103
+ Copy the entire folder into the new tenant:
3104
+
3105
+ ```
3106
+ src/components/tiptap/
3107
+ index.ts
3108
+ types.ts
3109
+ View.tsx
3110
+ ```
3111
+
3112
+ ---
3113
+
3114
+ ## 2. Install npm dependencies
3115
+
3116
+ Add to the tenant's `package.json` and run `npm install`:
3117
+
3118
+ ```json
3119
+ "@tiptap/extension-image": "^2.11.5",
3120
+ "@tiptap/extension-link": "^2.11.5",
3121
+ "@tiptap/react": "^2.11.5",
3122
+ "@tiptap/starter-kit": "^2.11.5",
3123
+ "react-markdown": "^9.0.1",
3124
+ "rehype-sanitize": "^6.0.0",
3125
+ "remark-gfm": "^4.0.1",
3126
+ "tiptap-markdown": "^0.8.10"
3127
+ ```
3128
+
3129
+ ---
3130
+
3131
+ ## 3. Add CSS to `src/index.css`
3132
+
3133
+ Two blocks are required — one for the public (visitor) view, one for the editor (studio) view.
3134
+
3135
+ ```css
3136
+ /* ==========================================================================
3137
+ TIPTAP — Public content typography (visitor view)
3138
+ ========================================================================== */
3139
+ .jp-tiptap-content > * + * { margin-top: 0.75em; }
3140
+
3141
+ .jp-tiptap-content h1 { font-size: 2em; font-weight: 700; line-height: 1.2; margin-top: 1.25em; margin-bottom: 0.25em; }
3142
+ .jp-tiptap-content h2 { font-size: 1.5em; font-weight: 700; line-height: 1.3; margin-top: 1.25em; margin-bottom: 0.25em; }
3143
+ .jp-tiptap-content h3 { font-size: 1.25em; font-weight: 600; line-height: 1.4; margin-top: 1.25em; margin-bottom: 0.25em; }
3144
+ .jp-tiptap-content h4 { font-size: 1em; font-weight: 600; line-height: 1.5; margin-top: 1em; margin-bottom: 0.25em; }
3145
+
3146
+ .jp-tiptap-content p { line-height: 1.7; }
3147
+ .jp-tiptap-content strong { font-weight: 700; }
3148
+ .jp-tiptap-content em { font-style: italic; }
3149
+ .jp-tiptap-content s { text-decoration: line-through; }
3150
+
3151
+ .jp-tiptap-content a { color: var(--primary); text-decoration: underline; text-underline-offset: 2px; }
3152
+ .jp-tiptap-content a:hover { opacity: 0.8; }
3153
+
3154
+ .jp-tiptap-content code {
3155
+ font-family: var(--font-mono, ui-monospace, monospace);
3156
+ font-size: 0.875em;
3157
+ background: color-mix(in oklch, var(--foreground) 8%, transparent);
3158
+ border-radius: 0.25em;
3159
+ padding: 0.1em 0.35em;
3160
+ }
3161
+ .jp-tiptap-content pre {
3162
+ background: color-mix(in oklch, var(--background) 60%, black);
3163
+ border-radius: 0.5em;
3164
+ padding: 1em 1.25em;
3165
+ overflow-x: auto;
3166
+ }
3167
+ .jp-tiptap-content pre code { background: none; padding: 0; }
3168
+
3169
+ .jp-tiptap-content ul { list-style-type: disc; padding-left: 1.625em; }
3170
+ .jp-tiptap-content ol { list-style-type: decimal; padding-left: 1.625em; }
3171
+ .jp-tiptap-content li { line-height: 1.7; margin-top: 0.25em; }
3172
+ .jp-tiptap-content li + li { margin-top: 0.25em; }
3173
+
3174
+ .jp-tiptap-content blockquote {
3175
+ border-left: 3px solid var(--border);
3176
+ padding-left: 1em;
3177
+ color: var(--muted-foreground);
3178
+ font-style: italic;
3179
+ }
3180
+ .jp-tiptap-content hr { border: none; border-top: 1px solid var(--border); margin: 1.5em 0; }
3181
+ .jp-tiptap-content img { max-width: 100%; height: auto; border-radius: 0.5rem; }
3182
+
3183
+ /* ==========================================================================
3184
+ TIPTAP / PROSEMIRROR — Editor typography (studio view)
3185
+ ========================================================================== */
3186
+ .jp-simple-editor .ProseMirror { outline: none; word-break: break-word; }
3187
+ .jp-simple-editor .ProseMirror > * + * { margin-top: 0.75em; }
3188
+
3189
+ .jp-simple-editor .ProseMirror h1 { font-size: 2em; font-weight: 700; line-height: 1.2; margin-top: 1.25em; margin-bottom: 0.25em; }
3190
+ .jp-simple-editor .ProseMirror h2 { font-size: 1.5em; font-weight: 700; line-height: 1.3; margin-top: 1.25em; margin-bottom: 0.25em; }
3191
+ .jp-simple-editor .ProseMirror h3 { font-size: 1.25em; font-weight: 600; line-height: 1.4; margin-top: 1.25em; margin-bottom: 0.25em; }
3192
+ .jp-simple-editor .ProseMirror h4 { font-size: 1em; font-weight: 600; line-height: 1.5; margin-top: 1em; margin-bottom: 0.25em; }
3193
+
3194
+ .jp-simple-editor .ProseMirror p { line-height: 1.7; }
3195
+ .jp-simple-editor .ProseMirror strong { font-weight: 700; }
3196
+ .jp-simple-editor .ProseMirror em { font-style: italic; }
3197
+ .jp-simple-editor .ProseMirror s { text-decoration: line-through; }
3198
+
3199
+ .jp-simple-editor .ProseMirror a { color: var(--primary); text-decoration: underline; text-underline-offset: 2px; }
3200
+ .jp-simple-editor .ProseMirror a:hover { opacity: 0.8; }
3201
+
3202
+ .jp-simple-editor .ProseMirror code {
3203
+ font-family: var(--font-mono, ui-monospace, monospace);
3204
+ font-size: 0.875em;
3205
+ background: color-mix(in oklch, var(--foreground) 8%, transparent);
3206
+ border-radius: 0.25em;
3207
+ padding: 0.1em 0.35em;
3208
+ }
3209
+ .jp-simple-editor .ProseMirror pre {
3210
+ background: color-mix(in oklch, var(--background) 60%, black);
3211
+ border-radius: 0.5em;
3212
+ padding: 1em 1.25em;
3213
+ overflow-x: auto;
3214
+ }
3215
+ .jp-simple-editor .ProseMirror pre code { background: none; padding: 0; }
3216
+
3217
+ .jp-simple-editor .ProseMirror ul { list-style-type: disc; padding-left: 1.625em; }
3218
+ .jp-simple-editor .ProseMirror ol { list-style-type: decimal; padding-left: 1.625em; }
3219
+ .jp-simple-editor .ProseMirror li { line-height: 1.7; margin-top: 0.25em; }
3220
+ .jp-simple-editor .ProseMirror li + li { margin-top: 0.25em; }
3221
+
3222
+ .jp-simple-editor .ProseMirror blockquote {
3223
+ border-left: 3px solid var(--border);
3224
+ padding-left: 1em;
3225
+ color: var(--muted-foreground);
3226
+ font-style: italic;
3227
+ }
3228
+ .jp-simple-editor .ProseMirror hr { border: none; border-top: 1px solid var(--border); margin: 1.5em 0; }
3229
+
3230
+ .jp-simple-editor .ProseMirror img { max-width: 100%; height: auto; border-radius: 0.5rem; }
3231
+ .jp-simple-editor .ProseMirror img[data-uploading="true"] {
3232
+ opacity: 0.6;
3233
+ filter: grayscale(0.25);
3234
+ outline: 2px dashed rgb(59 130 246 / 0.7);
3235
+ outline-offset: 2px;
3236
+ }
3237
+ .jp-simple-editor .ProseMirror img[data-upload-error="true"] {
3238
+ outline: 2px solid rgb(239 68 68 / 0.8);
3239
+ outline-offset: 2px;
3240
+ }
3241
+ .jp-simple-editor .ProseMirror p.is-editor-empty:first-child::before {
3242
+ content: attr(data-placeholder);
3243
+ color: var(--muted-foreground);
3244
+ opacity: 0.5;
3245
+ pointer-events: none;
3246
+ float: left;
3247
+ height: 0;
3248
+ }
3249
+ ```
3250
+
3251
+ ---
3252
+
3253
+ ## 4. Register in `src/lib/schemas.ts`
3254
+
3255
+ ```ts
3256
+ import { TiptapSchema } from '@/components/tiptap';
3257
+
3258
+ export const SECTION_SCHEMAS = {
3259
+ // ... existing schemas
3260
+ 'tiptap': TiptapSchema,
3261
+ } as const;
3262
+ ```
3263
+
3264
+ ---
3265
+
3266
+ ## 5. Register in `src/lib/addSectionConfig.ts`
3267
+
3268
+ ```ts
3269
+ const addableSectionTypes = [
3270
+ // ... existing types
3271
+ 'tiptap',
3272
+ ] as const;
3273
+
3274
+ const sectionTypeLabels = {
3275
+ // ... existing labels
3276
+ 'tiptap': 'Tiptap Editorial',
3277
+ };
3278
+
3279
+ function getDefaultSectionData(type: string) {
3280
+ switch (type) {
3281
+ // ... existing cases
3282
+ case 'tiptap': return { content: '# Post title\n\nStart writing in Markdown...' };
3283
+ }
3284
+ }
3285
+ ```
3286
+
3287
+ ---
3288
+
3289
+ ## 6. Register in `src/lib/ComponentRegistry.tsx`
3290
+
3291
+ ```tsx
3292
+ import { Tiptap } from '@/components/tiptap';
3293
+
3294
+ export const ComponentRegistry = {
3295
+ // ... existing components
3296
+ 'tiptap': Tiptap,
3297
+ };
3298
+ ```
3299
+
3300
+ ---
3301
+
3302
+ ## 7. Register in `src/types.ts`
3303
+
3304
+ ```ts
3305
+ import type { TiptapData, TiptapSettings } from '@/components/tiptap';
3306
+
3307
+ export type SectionComponentPropsMap = {
3308
+ // ... existing entries
3309
+ 'tiptap': { data: TiptapData; settings?: TiptapSettings };
3310
+ };
3311
+
3312
+ declare module '@jsonpages/core' {
3313
+ export interface SectionDataRegistry {
3314
+ // ... existing entries
3315
+ 'tiptap': TiptapData;
3316
+ }
3317
+ export interface SectionSettingsRegistry {
3318
+ // ... existing entries
3319
+ 'tiptap': TiptapSettings;
3320
+ }
3321
+ }
3322
+ ```
3323
+
3324
+ ---
3325
+
3326
+ ## Notes
3327
+
3328
+ - Typography uses tenant CSS variables (`--primary`, `--border`, `--muted-foreground`, `--font-mono`) — no hardcoded colors.
3329
+ - `@tailwindcss/typography` is **not** required; the CSS blocks above replace it.
3330
+ - The toolbar is admin-only (studio mode). In visitor mode, content is rendered via `ReactMarkdown`.
3331
+ - Underline is intentionally excluded: `tiptap-markdown` with `html: false` cannot round-trip `<u>` tags.
3332
+
3333
+ END_OF_FILE_CONTENT
3099
3334
  echo "Creating src/components/tiptap/View.tsx..."
3100
3335
  cat << 'END_OF_FILE_CONTENT' > "src/components/tiptap/View.tsx"
3101
3336
  import React from 'react';
@@ -3770,7 +4005,7 @@ const StudioTiptapEditor: React.FC<{ data: TiptapData }> = ({ data }) => {
3770
4005
  // ── Public view ───────────────────────────────────────────────────────────────
3771
4006
 
3772
4007
  const PublicTiptapContent: React.FC<{ content: string }> = ({ content }) => (
3773
- <article className="prose max-w-none prose-zinc" data-jp-field="content">
4008
+ <article className="jp-tiptap-content" data-jp-field="content">
3774
4009
  <ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeSanitize]}>
3775
4010
  {content}
3776
4011
  </ReactMarkdown>
@@ -3782,12 +4017,14 @@ const PublicTiptapContent: React.FC<{ content: string }> = ({ content }) => (
3782
4017
  export const Tiptap: React.FC<{ data: TiptapData; settings?: TiptapSettings }> = ({ data }) => {
3783
4018
  const { mode } = useStudio();
3784
4019
  return (
3785
- <section className="w-full">
3786
- {mode === 'studio' ? (
3787
- <StudioTiptapEditor data={data} />
3788
- ) : (
3789
- <PublicTiptapContent content={data.content ?? ''} />
3790
- )}
4020
+ <section className="w-full py-12">
4021
+ <div className="container mx-auto px-6 max-w-3xl">
4022
+ {mode === 'studio' ? (
4023
+ <StudioTiptapEditor data={data} />
4024
+ ) : (
4025
+ <PublicTiptapContent content={data.content ?? ''} />
4026
+ )}
4027
+ </div>
3791
4028
  </section>
3792
4029
  );
3793
4030
  };
@@ -4393,7 +4630,6 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/config/menu.json"
4393
4630
  ]
4394
4631
  }
4395
4632
  END_OF_FILE_CONTENT
4396
- # SKIP: src/data/config/menu.json:Zone.Identifier è un file binario e non può essere convertito in testo.
4397
4633
  echo "Creating src/data/config/site.json..."
4398
4634
  cat << 'END_OF_FILE_CONTENT' > "src/data/config/site.json"
4399
4635
  {
@@ -4481,7 +4717,6 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/config/site.json"
4481
4717
  }
4482
4718
  }
4483
4719
  END_OF_FILE_CONTENT
4484
- # SKIP: src/data/config/site.json:Zone.Identifier è un file binario e non può essere convertito in testo.
4485
4720
  echo "Creating src/data/config/theme.json..."
4486
4721
  cat << 'END_OF_FILE_CONTENT' > "src/data/config/theme.json"
4487
4722
  {
@@ -4513,7 +4748,6 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/config/theme.json"
4513
4748
  }
4514
4749
  }
4515
4750
  END_OF_FILE_CONTENT
4516
- # SKIP: src/data/config/theme.json:Zone.Identifier è un file binario e non può essere convertito in testo.
4517
4751
  mkdir -p "src/data/pages"
4518
4752
  echo "Creating src/data/pages/docs.json..."
4519
4753
  cat << 'END_OF_FILE_CONTENT' > "src/data/pages/docs.json"
@@ -5106,7 +5340,6 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/pages/home.json"
5106
5340
  ]
5107
5341
  }
5108
5342
  END_OF_FILE_CONTENT
5109
- # SKIP: src/data/pages/home.json:Zone.Identifier è un file binario e non può essere convertito in testo.
5110
5343
  echo "Creating src/data/pages/post.json..."
5111
5344
  cat << 'END_OF_FILE_CONTENT' > "src/data/pages/post.json"
5112
5345
  {
@@ -5121,7 +5354,7 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/pages/post.json"
5121
5354
  "id": "post-editorial-main",
5122
5355
  "type": "tiptap",
5123
5356
  "data": {
5124
- "content": "# JsonPages Cloud – Terms of Service & EULA\n\n---\n\n### **Last Updated:** March 2026\n\n### 1. THE SERVICE\n\nJsonPages provides a hybrid content management infrastructure consisting of:\n\n- **The Core:** An open-source library (@jsonpages/core) governed by the **MIT License**.\n- **The Cloud:** A proprietary SaaS platform (`cloud.jsonpages.io`) that provides the \"Git Bridge,\" Asset Pipeline, and Managed Infrastructure.\n\nBy using the Cloud Service, you agree to these terms.\n\n### 2. DATA SOVEREIGNTY & OWNERSHIP\n\n- **Your Content:** All data (JSON files), code, and assets managed through JsonPages remain your exclusive property. JsonPages acts only as an **orchestrator**.\n- **The Bridge:** You grant JsonPages the necessary permissions to perform Git operations (commits/pushes) on your behalf to your designated repositories (GitHub/GitLab).\n- **Portability:** Since your content is stored as flat JSON files in your own repository, you retain the right to migrate away from the Cloud Service at any time without data lock-in.\n- \n\n### 3. SUBSCRIPTIONS & ENTITLEMENTS\n\n- **Billing:** The Cloud Service is billed on a subscription basis (**Monthly Recurring Revenue**).\n- **Entitlements:** Each \"Project\" or \"Tenant\" consumes one entitlement. Active entitlements grant access to the Visual Studio (ICE) and the Cloud Save API.\n- **Third-Party Costs:** You are solely responsible for any costs incurred on third-party platforms (e.g., **Vercel** hosting, **GitHub** storage, **Cloudflare** workers).\n- \n\n### 4. ACCEPTABLE USE\n\nYou may not use JsonPages Cloud to:\n\n- Host or manage illegal, harmful, or offensive content.\n- Attempt to reverse-engineer the proprietary Cloud Bridge or bypass entitlement checks.\n- Interfere with the stability of the API for other users.\n- \n\n### 5. LIMITATION OF LIABILITY\n\n- **\"As-Is\" Basis:** The service is provided \"as-is.\" While we strive for 99.9% uptime, JsonPages is not liable for data loss resulting from Git conflicts, third-party outages (Vercel/GitHub), or user error.\n- **No Warranty:** We do not warrant that the service will be error-free or uninterrupted.\n- \n\n### 6. TERMINATION\n\n- **By You:** You can cancel your subscription at any time. Your Studio access will remain active until the end of the current billing cycle.\n- \n- **By Us:** We reserve the right to suspend accounts that violate these terms or fail to settle outstanding invoices.\n\n### 7. GOVERNING LAW\n\nThese terms are governed by the laws of **Italy/European Union**, without regard to conflict of law principles."
5357
+ "content": "# JsonPages Cloud – Terms of Service & EULA\n\n---\n\n### **Last Updated:** March 2026\n\n### 1. THE SERVICE\n\nJsonPages provides a hybrid content management infrastructure consisting of:\n\n- **The Core:** An open-source library (@jsonpages/core) governed by the **MIT License**.\n- **The Cloud:** A proprietary SaaS platform (`cloud.jsonpages.io`) that provides the \"Git Bridge,\" Asset Pipeline, and Managed Infrastructure.\n\nBy using the Cloud Service, you agree to these terms.\n\n### 2. DATA SOVEREIGNTY & OWNERSHIP\n\n- **Your Content:** All data (JSON files), code, and assets managed through JsonPages remain your exclusive property. JsonPages acts only as an **orchestrator**.\n- **The Bridge:** You grant JsonPages the necessary permissions to perform Git operations (commits/pushes) on your behalf to your designated repositories (GitHub/GitLab).\n- **Portability:** Since your content is stored as flat JSON files in your own repository, you retain the right to migrate away from the Cloud Service at any time without data lock-in.\n- \n\n### 3. SUBSCRIPTIONS & ENTITLEMENTS\n\n- **Billing:** The Cloud Service is billed on a subscription basis (**Monthly Recurring Revenue**).\n- **Entitlements:** Each \"Project\" or \"Tenant\" consumes one entitlement. Active entitlements grant access to the Visual Studio (ICE) and the Cloud Save API.\n- **Third-Party Costs:** You are solely responsible for any costs incurred on third-party platforms (e.g., **Vercel** hosting, **GitHub** storage, **Cloudflare** workers).\n\n### 4. ACCEPTABLE USE\n\nYou may not use JsonPages Cloud to:\n\n- Host or manage illegal, harmful, or offensive content.\n- Attempt to reverse-engineer the proprietary Cloud Bridge or bypass entitlement checks.\n- Interfere with the stability of the API for other users.\n- \n\n### 5. LIMITATION OF LIABILITY\n\n- **\"As-Is\" Basis:** The service is provided \"as-is.\" While we strive for 99.9% uptime, JsonPages is not liable for data loss resulting from Git conflicts, third-party outages (Vercel/GitHub), or user error.\n- **No Warranty:** We do not warrant that the service will be error-free or uninterrupted.\n- \n\n### 6. TERMINATION\n\n- **By You:** You can cancel your subscription at any time. Your Studio access will remain active until the end of the current billing cycle.\n- \n- **By Us:** We reserve the right to suspend accounts that violate these terms or fail to settle outstanding invoices.\n\n### 7. GOVERNING LAW\n\nThese terms are governed by the laws of **Italy/European Union**, without regard to conflict of law principles."
5125
5358
  },
5126
5359
  "settings": {}
5127
5360
  }
@@ -5193,6 +5426,317 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/pages/servizi_trattamento.json"
5193
5426
  }
5194
5427
  ]
5195
5428
  }
5429
+ END_OF_FILE_CONTENT
5430
+ mkdir -p "src/emails"
5431
+ echo "Creating src/emails/LeadNotificationEmail.tsx..."
5432
+ cat << 'END_OF_FILE_CONTENT' > "src/emails/LeadNotificationEmail.tsx"
5433
+ import {
5434
+ Body,
5435
+ Button,
5436
+ Container,
5437
+ Head,
5438
+ Heading,
5439
+ Hr,
5440
+ Html,
5441
+ Img,
5442
+ Preview,
5443
+ Section,
5444
+ Text,
5445
+ } from "@react-email/components";
5446
+
5447
+ type LeadData = Record<string, unknown>;
5448
+
5449
+ type EmailTheme = {
5450
+ colors?: {
5451
+ primary?: string;
5452
+ secondary?: string;
5453
+ accent?: string;
5454
+ background?: string;
5455
+ surface?: string;
5456
+ surfaceAlt?: string;
5457
+ text?: string;
5458
+ textMuted?: string;
5459
+ border?: string;
5460
+ };
5461
+ typography?: {
5462
+ fontFamily?: {
5463
+ primary?: string;
5464
+ display?: string;
5465
+ mono?: string;
5466
+ };
5467
+ };
5468
+ borderRadius?: {
5469
+ sm?: string;
5470
+ md?: string;
5471
+ lg?: string;
5472
+ xl?: string;
5473
+ };
5474
+ };
5475
+
5476
+ export type LeadNotificationEmailProps = {
5477
+ tenantName: string;
5478
+ correlationId: string;
5479
+ replyTo?: string | null;
5480
+ leadData: LeadData;
5481
+ brandName?: string;
5482
+ logoUrl?: string;
5483
+ logoAlt?: string;
5484
+ tagline?: string;
5485
+ theme?: EmailTheme;
5486
+ };
5487
+
5488
+ function safeString(value: unknown): string {
5489
+ if (value == null) return "-";
5490
+ if (typeof value === "string") {
5491
+ const trimmed = value.trim();
5492
+ return trimmed || "-";
5493
+ }
5494
+ return JSON.stringify(value);
5495
+ }
5496
+
5497
+ function flattenLeadData(data: LeadData) {
5498
+ return Object.entries(data)
5499
+ .filter(([key]) => !key.startsWith("_"))
5500
+ .slice(0, 20)
5501
+ .map(([key, value]) => ({ label: key, value: safeString(value) }));
5502
+ }
5503
+
5504
+ export function LeadNotificationEmail({
5505
+ tenantName,
5506
+ correlationId,
5507
+ replyTo,
5508
+ leadData,
5509
+ brandName,
5510
+ logoUrl,
5511
+ logoAlt,
5512
+ tagline,
5513
+ theme,
5514
+ }: LeadNotificationEmailProps) {
5515
+ const fields = flattenLeadData(leadData);
5516
+ const brandLabel = brandName || tenantName;
5517
+
5518
+ const colors = {
5519
+ primary: theme?.colors?.primary || "#2D5016",
5520
+ background: theme?.colors?.background || "#FAFAF5",
5521
+ surface: theme?.colors?.surface || "#FFFFFF",
5522
+ text: theme?.colors?.text || "#1C1C14",
5523
+ textMuted: theme?.colors?.textMuted || "#5A5A4A",
5524
+ border: theme?.colors?.border || "#D8D5C5",
5525
+ };
5526
+
5527
+ const fonts = {
5528
+ primary: theme?.typography?.fontFamily?.primary || "Inter, Arial, sans-serif",
5529
+ display: theme?.typography?.fontFamily?.display || "Georgia, serif",
5530
+ };
5531
+
5532
+ const radius = {
5533
+ md: theme?.borderRadius?.md || "10px",
5534
+ lg: theme?.borderRadius?.lg || "16px",
5535
+ };
5536
+
5537
+ return (
5538
+ <Html>
5539
+ <Head />
5540
+ <Preview>Nuovo lead ricevuto da {brandLabel}</Preview>
5541
+ <Body style={{ backgroundColor: colors.background, color: colors.text, fontFamily: fonts.primary, padding: "24px" }}>
5542
+ <Container style={{ backgroundColor: colors.surface, border: `1px solid ${colors.border}`, borderRadius: radius.lg, padding: "24px" }}>
5543
+ <Section>
5544
+ {logoUrl ? <Img src={logoUrl} alt={logoAlt || brandLabel} height="44" style={{ marginBottom: "8px" }} /> : null}
5545
+ <Text style={{ color: colors.text, fontSize: "18px", fontWeight: 700, margin: "0 0 6px 0" }}>{brandLabel}</Text>
5546
+ <Text style={{ color: colors.textMuted, marginTop: "0", marginBottom: "0" }}>{tagline || "Notifica automatica lead"}</Text>
5547
+ </Section>
5548
+
5549
+ <Hr style={{ borderColor: colors.border, margin: "20px 0" }} />
5550
+
5551
+ <Heading as="h2" style={{ color: colors.text, margin: "0 0 12px 0", fontSize: "22px", fontFamily: fonts.display }}>
5552
+ Nuovo lead da {tenantName}
5553
+ </Heading>
5554
+ <Text style={{ color: colors.textMuted, marginTop: "0", marginBottom: "16px" }}>Correlation ID: {correlationId}</Text>
5555
+
5556
+ <Section style={{ border: `1px solid ${colors.border}`, borderRadius: radius.md, padding: "12px" }}>
5557
+ {fields.length === 0 ? (
5558
+ <Text style={{ color: colors.textMuted, margin: 0 }}>Nessun campo lead disponibile.</Text>
5559
+ ) : (
5560
+ fields.map((field) => (
5561
+ <Text key={field.label} style={{ margin: "0 0 8px 0", color: colors.text, fontSize: "14px", wordBreak: "break-word" }}>
5562
+ <strong>{field.label}:</strong> {field.value}
5563
+ </Text>
5564
+ ))
5565
+ )}
5566
+ </Section>
5567
+
5568
+ <Section style={{ marginTop: "18px" }}>
5569
+ <Button
5570
+ href={replyTo ? `mailto:${replyTo}` : "mailto:"}
5571
+ style={{
5572
+ backgroundColor: colors.primary,
5573
+ color: "#ffffff",
5574
+ borderRadius: radius.md,
5575
+ textDecoration: "none",
5576
+ padding: "12px 18px",
5577
+ fontWeight: 600,
5578
+ }}
5579
+ >
5580
+ Rispondi ora
5581
+ </Button>
5582
+ </Section>
5583
+ </Container>
5584
+ </Body>
5585
+ </Html>
5586
+ );
5587
+ }
5588
+
5589
+ export default LeadNotificationEmail;
5590
+
5591
+ END_OF_FILE_CONTENT
5592
+ echo "Creating src/emails/LeadSenderConfirmationEmail.tsx..."
5593
+ cat << 'END_OF_FILE_CONTENT' > "src/emails/LeadSenderConfirmationEmail.tsx"
5594
+ import {
5595
+ Body,
5596
+ Container,
5597
+ Head,
5598
+ Heading,
5599
+ Hr,
5600
+ Html,
5601
+ Img,
5602
+ Preview,
5603
+ Section,
5604
+ Text,
5605
+ } from "@react-email/components";
5606
+
5607
+ type LeadData = Record<string, unknown>;
5608
+
5609
+ type EmailTheme = {
5610
+ colors?: {
5611
+ primary?: string;
5612
+ secondary?: string;
5613
+ accent?: string;
5614
+ background?: string;
5615
+ surface?: string;
5616
+ surfaceAlt?: string;
5617
+ text?: string;
5618
+ textMuted?: string;
5619
+ border?: string;
5620
+ };
5621
+ typography?: {
5622
+ fontFamily?: {
5623
+ primary?: string;
5624
+ display?: string;
5625
+ mono?: string;
5626
+ };
5627
+ };
5628
+ borderRadius?: {
5629
+ sm?: string;
5630
+ md?: string;
5631
+ lg?: string;
5632
+ xl?: string;
5633
+ };
5634
+ };
5635
+
5636
+ export type LeadSenderConfirmationEmailProps = {
5637
+ tenantName: string;
5638
+ correlationId: string;
5639
+ leadData: LeadData;
5640
+ brandName?: string;
5641
+ logoUrl?: string;
5642
+ logoAlt?: string;
5643
+ tagline?: string;
5644
+ theme?: EmailTheme;
5645
+ };
5646
+
5647
+ function safeString(value: unknown): string {
5648
+ if (value == null) return "-";
5649
+ if (typeof value === "string") {
5650
+ const trimmed = value.trim();
5651
+ return trimmed || "-";
5652
+ }
5653
+ return JSON.stringify(value);
5654
+ }
5655
+
5656
+ function flattenLeadData(data: LeadData) {
5657
+ const skipKeys = new Set(["recipientEmail", "tenant", "source", "submittedAt", "email_confirm"]);
5658
+ return Object.entries(data)
5659
+ .filter(([key]) => !key.startsWith("_") && !skipKeys.has(key))
5660
+ .slice(0, 12)
5661
+ .map(([key, value]) => ({ label: key, value: safeString(value) }));
5662
+ }
5663
+
5664
+ export function LeadSenderConfirmationEmail({
5665
+ tenantName,
5666
+ correlationId,
5667
+ leadData,
5668
+ brandName,
5669
+ logoUrl,
5670
+ logoAlt,
5671
+ tagline,
5672
+ theme,
5673
+ }: LeadSenderConfirmationEmailProps) {
5674
+ const fields = flattenLeadData(leadData);
5675
+ const brandLabel = brandName || tenantName;
5676
+
5677
+ const colors = {
5678
+ primary: theme?.colors?.primary || "#2D5016",
5679
+ background: theme?.colors?.background || "#FAFAF5",
5680
+ surface: theme?.colors?.surface || "#FFFFFF",
5681
+ text: theme?.colors?.text || "#1C1C14",
5682
+ textMuted: theme?.colors?.textMuted || "#5A5A4A",
5683
+ border: theme?.colors?.border || "#D8D5C5",
5684
+ };
5685
+
5686
+ const fonts = {
5687
+ primary: theme?.typography?.fontFamily?.primary || "Inter, Arial, sans-serif",
5688
+ display: theme?.typography?.fontFamily?.display || "Georgia, serif",
5689
+ };
5690
+
5691
+ const radius = {
5692
+ md: theme?.borderRadius?.md || "10px",
5693
+ lg: theme?.borderRadius?.lg || "16px",
5694
+ };
5695
+
5696
+ return (
5697
+ <Html>
5698
+ <Head />
5699
+ <Preview>Conferma invio richiesta - {brandLabel}</Preview>
5700
+ <Body style={{ backgroundColor: colors.background, color: colors.background, fontFamily: fonts.primary, padding: "24px" }}>
5701
+ <Container style={{ backgroundColor: colors.primary, color: colors.background, border: `1px solid ${colors.border}`, borderRadius: radius.lg, padding: "24px" }}>
5702
+ <Section>
5703
+ {logoUrl ? <Img src={logoUrl} alt={logoAlt || brandLabel} height="44" style={{ marginBottom: "8px" }} /> : null}
5704
+ <Text style={{ color: colors.background, fontSize: "18px", fontWeight: 700, margin: "0 0 6px 0" }}>{brandLabel}</Text>
5705
+ <Text style={{ color: colors.background, marginTop: "0", marginBottom: "0" }}>{tagline || "Conferma automatica di ricezione"}</Text>
5706
+ </Section>
5707
+
5708
+ <Hr style={{ borderColor: colors.border, margin: "20px 0" }} />
5709
+
5710
+ <Heading as="h2" style={{ color: colors.background, margin: "0 0 12px 0", fontSize: "22px", fontFamily: fonts.display }}>
5711
+ Richiesta ricevuta
5712
+ </Heading>
5713
+ <Text style={{ color: colors.background, marginTop: "0", marginBottom: "16px" }}>
5714
+ Grazie, abbiamo ricevuto la tua richiesta per {tenantName}. Ti risponderemo il prima possibile.
5715
+ </Text>
5716
+
5717
+ <Section style={{ border: `1px solid ${colors.border}`, borderRadius: radius.md, padding: "12px" }}>
5718
+ <Text style={{ margin: "0 0 8px 0", color: colors.background, fontWeight: 600 }}>Riepilogo inviato</Text>
5719
+ {fields.length === 0 ? (
5720
+ <Text style={{ color: colors.background, margin: 0 }}>Nessun dettaglio disponibile.</Text>
5721
+ ) : (
5722
+ fields.map((field) => (
5723
+ <Text key={field.label} style={{ margin: "0 0 8px 0", color: colors.background, fontSize: "14px", wordBreak: "break-word" }}>
5724
+ <strong>{field.label}:</strong> {field.value}
5725
+ </Text>
5726
+ ))
5727
+ )}
5728
+ </Section>
5729
+
5730
+ <Hr style={{ borderColor: colors.border, margin: "20px 0 12px 0" }} />
5731
+ <Text style={{ color: colors.background, fontSize: "12px", margin: 0 }}>Riferimento richiesta: {correlationId}</Text>
5732
+ </Container>
5733
+ </Body>
5734
+ </Html>
5735
+ );
5736
+ }
5737
+
5738
+ export default LeadSenderConfirmationEmail;
5739
+
5196
5740
  END_OF_FILE_CONTENT
5197
5741
  echo "Creating src/fonts.css..."
5198
5742
  cat << 'END_OF_FILE_CONTENT' > "src/fonts.css"
@@ -5412,6 +5956,61 @@ html {
5412
5956
  pointer-events: none;
5413
5957
  }
5414
5958
 
5959
+ /* ==========================================================================
5960
+ TIPTAP — Public content typography (visitor view)
5961
+ ReactMarkdown renders plain HTML; preflight resets it. Re-apply here.
5962
+ ========================================================================== */
5963
+ .jp-tiptap-content > * + * { margin-top: 0.75em; }
5964
+
5965
+ .jp-tiptap-content h1 { font-size: 2em; font-weight: 700; line-height: 1.2; margin-top: 1.25em; margin-bottom: 0.25em; }
5966
+ .jp-tiptap-content h2 { font-size: 1.5em; font-weight: 700; line-height: 1.3; margin-top: 1.25em; margin-bottom: 0.25em; }
5967
+ .jp-tiptap-content h3 { font-size: 1.25em; font-weight: 600; line-height: 1.4; margin-top: 1.25em; margin-bottom: 0.25em; }
5968
+ .jp-tiptap-content h4 { font-size: 1em; font-weight: 600; line-height: 1.5; margin-top: 1em; margin-bottom: 0.25em; }
5969
+
5970
+ .jp-tiptap-content p { line-height: 1.7; }
5971
+
5972
+ .jp-tiptap-content strong { font-weight: 700; }
5973
+ .jp-tiptap-content em { font-style: italic; }
5974
+ .jp-tiptap-content s { text-decoration: line-through; }
5975
+
5976
+ .jp-tiptap-content a { color: var(--primary); text-decoration: underline; text-underline-offset: 2px; }
5977
+ .jp-tiptap-content a:hover { opacity: 0.8; }
5978
+
5979
+ .jp-tiptap-content code {
5980
+ font-family: var(--font-mono, ui-monospace, monospace);
5981
+ font-size: 0.875em;
5982
+ background: color-mix(in oklch, var(--foreground) 8%, transparent);
5983
+ border-radius: 0.25em;
5984
+ padding: 0.1em 0.35em;
5985
+ }
5986
+ .jp-tiptap-content pre {
5987
+ background: color-mix(in oklch, var(--background) 60%, black);
5988
+ border-radius: 0.5em;
5989
+ padding: 1em 1.25em;
5990
+ overflow-x: auto;
5991
+ }
5992
+ .jp-tiptap-content pre code { background: none; padding: 0; }
5993
+
5994
+ .jp-tiptap-content ul { list-style-type: disc; padding-left: 1.625em; }
5995
+ .jp-tiptap-content ol { list-style-type: decimal; padding-left: 1.625em; }
5996
+ .jp-tiptap-content li { line-height: 1.7; margin-top: 0.25em; }
5997
+ .jp-tiptap-content li + li { margin-top: 0.25em; }
5998
+
5999
+ .jp-tiptap-content blockquote {
6000
+ border-left: 3px solid var(--border);
6001
+ padding-left: 1em;
6002
+ color: var(--muted-foreground);
6003
+ font-style: italic;
6004
+ }
6005
+
6006
+ .jp-tiptap-content hr {
6007
+ border: none;
6008
+ border-top: 1px solid var(--border);
6009
+ margin: 1.5em 0;
6010
+ }
6011
+
6012
+ .jp-tiptap-content img { max-width: 100%; height: auto; border-radius: 0.5rem; }
6013
+
5415
6014
  /* ==========================================================================
5416
6015
  TIPTAP / PROSEMIRROR — Editor typography
5417
6016
  Tailwind preflight resets all heading/list styles. Re-apply here using
@@ -5556,7 +6155,6 @@ export const ComponentRegistry: {
5556
6155
  };
5557
6156
 
5558
6157
  END_OF_FILE_CONTENT
5559
- # SKIP: src/lib/ComponentRegistry.tsx:Zone.Identifier è un file binario e non può essere convertito in testo.
5560
6158
  echo "Creating src/lib/IconResolver.tsx..."
5561
6159
  cat << 'END_OF_FILE_CONTENT' > "src/lib/IconResolver.tsx"
5562
6160
  import React from 'react';
@@ -5615,7 +6213,6 @@ export const Icon: React.FC<IconProps> = ({ name, size = 20, className }) => {
5615
6213
 
5616
6214
 
5617
6215
  END_OF_FILE_CONTENT
5618
- # SKIP: src/lib/IconResolver.tsx:Zone.Identifier è un file binario e non può essere convertito in testo.
5619
6216
  echo "Creating src/lib/addSectionConfig.ts..."
5620
6217
  cat << 'END_OF_FILE_CONTENT' > "src/lib/addSectionConfig.ts"
5621
6218
  import type { AddSectionConfig } from '@jsonpages/core';
@@ -5677,7 +6274,6 @@ export const addSectionConfig: AddSectionConfig = {
5677
6274
  };
5678
6275
 
5679
6276
  END_OF_FILE_CONTENT
5680
- # SKIP: src/lib/addSectionConfig.ts:Zone.Identifier è un file binario e non può essere convertito in testo.
5681
6277
  echo "Creating src/lib/base-schemas.ts..."
5682
6278
  cat << 'END_OF_FILE_CONTENT' > "src/lib/base-schemas.ts"
5683
6279
  import { z } from 'zod';
@@ -5720,11 +6316,6 @@ export const CtaSchema = z.object({
5720
6316
  });
5721
6317
 
5722
6318
  END_OF_FILE_CONTENT
5723
- # SKIP: src/lib/base-schemas.ts:Zone.Identifier è un file binario e non può essere convertito in testo.
5724
- # SKIP: src/lib/cloudSaveStream.ts:Zone.Identifier è un file binario e non può essere convertito in testo.
5725
- # SKIP: src/lib/cloudSaveStream.ts:Zone.Identifier:Zone.Identifier è un file binario e non può essere convertito in testo.
5726
- # SKIP: src/lib/deploySteps.ts:Zone.Identifier è un file binario e non può essere convertito in testo.
5727
- # SKIP: src/lib/deploySteps.ts:Zone.Identifier:Zone.Identifier è un file binario e non può essere convertito in testo.
5728
6319
  echo "Creating src/lib/draftStorage.ts..."
5729
6320
  cat << 'END_OF_FILE_CONTENT' > "src/lib/draftStorage.ts"
5730
6321
  /**
@@ -5753,7 +6344,6 @@ export function getHydratedData(
5753
6344
  }
5754
6345
 
5755
6346
  END_OF_FILE_CONTENT
5756
- # SKIP: src/lib/draftStorage.ts:Zone.Identifier è un file binario e non può essere convertito in testo.
5757
6347
  echo "Creating src/lib/getFilePages.ts..."
5758
6348
  cat << 'END_OF_FILE_CONTENT' > "src/lib/getFilePages.ts"
5759
6349
  /**
@@ -5802,7 +6392,6 @@ export function getFilePages(): Record<string, PageConfig> {
5802
6392
  }
5803
6393
 
5804
6394
  END_OF_FILE_CONTENT
5805
- # SKIP: src/lib/getFilePages.ts:Zone.Identifier è un file binario e non può essere convertito in testo.
5806
6395
  echo "Creating src/lib/schemas.ts..."
5807
6396
  cat << 'END_OF_FILE_CONTENT' > "src/lib/schemas.ts"
5808
6397
  export { BaseSectionData, BaseArrayItem, BaseSectionSettingsSchema, CtaSchema } from './base-schemas';
@@ -5852,7 +6441,95 @@ export const SECTION_SCHEMAS = {
5852
6441
  export type SectionType = keyof typeof SECTION_SCHEMAS;
5853
6442
 
5854
6443
  END_OF_FILE_CONTENT
5855
- # SKIP: src/lib/schemas.ts:Zone.Identifier è un file binario e non può essere convertito in testo.
6444
+ echo "Creating src/lib/useFormSubmit.ts..."
6445
+ cat << 'END_OF_FILE_CONTENT' > "src/lib/useFormSubmit.ts"
6446
+ import { useState, useCallback } from 'react';
6447
+
6448
+ export type SubmitStatus = 'idle' | 'submitting' | 'success' | 'error';
6449
+
6450
+ interface UseFormSubmitOptions {
6451
+ source: string;
6452
+ tenantId: string;
6453
+ }
6454
+
6455
+ export function useFormSubmit({ source, tenantId }: UseFormSubmitOptions) {
6456
+ const [status, setStatus] = useState<SubmitStatus>('idle');
6457
+ const [message, setMessage] = useState<string>('');
6458
+
6459
+ const submit = useCallback(async (
6460
+ formData: FormData,
6461
+ recipientEmail: string,
6462
+ pageSlug: string,
6463
+ sectionId: string
6464
+ ) => {
6465
+ const cloudApiUrl = import.meta.env.VITE_JSONPAGES_CLOUD_URL as string | undefined;
6466
+ const cloudApiKey = import.meta.env.VITE_JSONPAGES_API_KEY as string | undefined;
6467
+
6468
+ if (!cloudApiUrl || !cloudApiKey) {
6469
+ setStatus('error');
6470
+ setMessage('Configurazione API non disponibile. Riprova tra poco.');
6471
+ return false;
6472
+ }
6473
+
6474
+ // Trasformiamo FormData in un oggetto piatto per il payload JSON
6475
+ const data: Record<string, any> = {};
6476
+ formData.forEach((value, key) => {
6477
+ data[key] = String(value).trim();
6478
+ });
6479
+
6480
+ const payload = {
6481
+ ...data,
6482
+ recipientEmail,
6483
+ page: pageSlug,
6484
+ section: sectionId,
6485
+ tenant: tenantId,
6486
+ source: source,
6487
+ submittedAt: new Date().toISOString(),
6488
+ };
6489
+
6490
+ // Idempotency Key per evitare doppi invii accidentali
6491
+ const idempotencyKey = `form-${sectionId}-${Date.now()}`;
6492
+
6493
+ setStatus('submitting');
6494
+ setMessage('Invio in corso...');
6495
+
6496
+ try {
6497
+ const apiBase = cloudApiUrl.replace(/\/$/, '');
6498
+ const response = await fetch(`${apiBase}/forms/submit`, {
6499
+ method: 'POST',
6500
+ headers: {
6501
+ Authorization: `Bearer ${cloudApiKey}`,
6502
+ 'Content-Type': 'application/json',
6503
+ 'Idempotency-Key': idempotencyKey,
6504
+ },
6505
+ body: JSON.stringify(payload),
6506
+ });
6507
+
6508
+ const body = (await response.json().catch(() => ({}))) as { error?: string; code?: string };
6509
+
6510
+ if (!response.ok) {
6511
+ throw new Error(body.error || body.code || `Submit failed (${response.status})`);
6512
+ }
6513
+
6514
+ setStatus('success');
6515
+ setMessage('Richiesta inviata con successo. Ti risponderemo al più presto.');
6516
+ return true;
6517
+ } catch (error: unknown) {
6518
+ const errorMsg = error instanceof Error ? error.message : 'Invio non riuscito. Riprova tra poco.';
6519
+ setStatus('error');
6520
+ setMessage(errorMsg);
6521
+ return false;
6522
+ }
6523
+ }, [source, tenantId]);
6524
+
6525
+ const reset = useCallback(() => {
6526
+ setStatus('idle');
6527
+ setMessage('');
6528
+ }, []);
6529
+
6530
+ return { submit, status, message, reset };
6531
+ }
6532
+ END_OF_FILE_CONTENT
5856
6533
  echo "Creating src/lib/utils.ts..."
5857
6534
  cat << 'END_OF_FILE_CONTENT' > "src/lib/utils.ts"
5858
6535
  import { clsx, type ClassValue } from 'clsx';
@@ -5863,7 +6540,6 @@ export function cn(...inputs: ClassValue[]) {
5863
6540
  }
5864
6541
 
5865
6542
  END_OF_FILE_CONTENT
5866
- # SKIP: src/lib/utils.ts:Zone.Identifier è un file binario e non può essere convertito in testo.
5867
6543
  echo "Creating src/main.tsx..."
5868
6544
  cat << 'END_OF_FILE_CONTENT' > "src/main.tsx"
5869
6545
  import '@/types'; // TBP: load type augmentation from capsule-driven types
@@ -5882,7 +6558,6 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
5882
6558
 
5883
6559
 
5884
6560
  END_OF_FILE_CONTENT
5885
- # SKIP: src/main.tsx:Zone.Identifier è un file binario e non può essere convertito in testo.
5886
6561
  echo "Creating src/main_.tsx..."
5887
6562
  cat << 'END_OF_FILE_CONTENT' > "src/main_.tsx"
5888
6563
  import '@/types'; // TBP: load type augmentation from capsule-driven types
@@ -5997,7 +6672,6 @@ declare module '@jsonpages/core' {
5997
6672
  export * from '@jsonpages/core';
5998
6673
 
5999
6674
  END_OF_FILE_CONTENT
6000
- # SKIP: src/types/deploy.ts:Zone.Identifier è un file binario e non può essere convertito in testo.
6001
6675
  echo "Creating src/vite-env.d.ts..."
6002
6676
  cat << 'END_OF_FILE_CONTENT' > "src/vite-env.d.ts"
6003
6677
  /// <reference types="vite/client" />
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsonpages/cli",
3
- "version": "3.0.63",
3
+ "version": "3.0.64",
4
4
  "description": "The Sovereign CLI Engine for JsonPages.",
5
5
  "type": "module",
6
6
  "bin": {