@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.
- package/assets/src_tenant_alpha.sh +714 -40
- package/package.json +1 -1
|
@@ -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.
|
|
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="
|
|
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
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
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
|
|
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
|
-
|
|
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" />
|