@explorer02/cfm-survey-sdk 0.2.4 → 0.2.6
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/dist/cli/index.js +45 -44
- package/dist/cli/index.mjs +43 -42
- package/package.json +1 -6
- package/postinstall.js +2 -1
- package/templates/AGENT.md +3 -3
- package/templates/docs/00-integration/agent-execution-flow.md +46 -27
- package/templates/docs/00-integration/agent-operating-contract.md +13 -5
- package/templates/docs/00-integration/apply-ui-config.md +35 -10
- package/templates/docs/00-integration/aws-deploy.md +117 -3
- package/templates/docs/00-integration/component-checklist.md +8 -4
- package/templates/docs/00-integration/constraints.md +3 -2
- package/templates/docs/00-integration/custom-field-logic-and-navigation.md +1 -1
- package/templates/docs/00-integration/mockup-ui-extraction.md +113 -0
- package/templates/docs/00-integration/placeholders-and-custom-fields-wiring.md +192 -0
- package/templates/docs/00-integration/placeholders-and-tokens.md +2 -1
- package/templates/docs/00-integration/ui-customization-wizard.md +83 -104
- package/templates/docs/00-integration/wizard-api.md +74 -82
- package/templates/docs/00-integration/wizard-chrome-contract.md +210 -0
- package/templates/docs/00-integration/wizard-config-handoff.md +57 -9
- package/templates/docs/00-integration/wizard-preview-build-guide.md +30 -11
- package/templates/docs/00-integration/wizard-question-type-styling.md +4 -2
- package/templates/docs/00-integration/wizard-troubleshooting.md +80 -0
- package/templates/docs/01-components/10-header-footer.md +3 -3
- package/templates/docs/01-components/19-survey-sticky-chrome.md +16 -55
- package/templates/docs/MANIFEST.json +34 -13
- package/templates/docs/index.md +9 -11
- package/templates/docs/templates/Footer.tsx +72 -0
- package/templates/docs/templates/Header.tsx +84 -0
- package/templates/docs/templates/LanguageSelector.tsx +40 -0
- package/templates/docs/templates/PreviewBridgeInit.tsx +13 -0
- package/templates/docs/templates/ProgressBar.tsx +33 -0
- package/templates/docs/templates/Question.tsx +50 -7
- package/templates/docs/templates/SurveyStickyChrome.tsx +19 -5
- package/templates/docs/templates/deploy-to-aws.sh +74 -0
- package/templates/docs/templates/implementation_plan.md +19 -12
- package/templates/docs/templates/verify-agent-build.sh +25 -1
- package/templates/preview-harness/preview-bridge.inline.js +1 -1
- package/templates/previewBridge.ts +131 -38
- package/templates/wizard-dist/assets/{PreviewMock-DgHfrVeb.js → PreviewMock-7peNZK-z.js} +1 -1
- package/templates/wizard-dist/assets/{TypePanel-CFVC3Ptn.js → TypePanel-BxHnE3cm.js} +1 -1
- package/templates/wizard-dist/assets/index-DtZhNwPS.js +34 -0
- package/templates/wizard-dist/index.html +1 -1
- package/templates/wizard-dist/assets/index-DYK3X1e5.js +0 -34
|
@@ -19,10 +19,10 @@
|
|
|
19
19
|
"surveyUiConfig": {
|
|
20
20
|
"artifact": "survey-ui-config.json",
|
|
21
21
|
"schema": "templates/survey-ui-config.schema.json",
|
|
22
|
-
"cliCommand": "npx cfm-sdk customize
|
|
22
|
+
"cliCommand": "npx cfm-sdk customize",
|
|
23
23
|
"cliCommands": {
|
|
24
|
-
"
|
|
25
|
-
"
|
|
24
|
+
"default": "npx cfm-sdk customize",
|
|
25
|
+
"remoteClient": "npx cfm-sdk customize-ui",
|
|
26
26
|
"exportPreviews": "npx cfm-sdk export-previews"
|
|
27
27
|
},
|
|
28
28
|
"wizardApiDoc": "00-integration/wizard-api.md",
|
|
@@ -156,6 +156,10 @@
|
|
|
156
156
|
"path": "00-integration/client-lib-folder.md",
|
|
157
157
|
"purpose": "src/lib layout — customFieldValues + surveyUi portable templates"
|
|
158
158
|
},
|
|
159
|
+
{
|
|
160
|
+
"path": "00-integration/wizard-chrome-contract.md",
|
|
161
|
+
"purpose": "Wizard DOM/CSS hook contract — copy-first Header/Footer/ProgressBar templates (Phase 5)"
|
|
162
|
+
},
|
|
159
163
|
{
|
|
160
164
|
"path": "00-integration/wizard-preview-build-guide.md",
|
|
161
165
|
"purpose": "Build client components for wizard live preview — --cfm-* vars and data-cfm-* hooks (Phase 5)"
|
|
@@ -178,7 +182,19 @@
|
|
|
178
182
|
},
|
|
179
183
|
{
|
|
180
184
|
"path": "00-integration/wizard-api.md",
|
|
181
|
-
"purpose": "EC2 /wizard/* API
|
|
185
|
+
"purpose": "EC2 /wizard/* API — customize-ui remote-client fallback only"
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"path": "00-integration/wizard-troubleshooting.md",
|
|
189
|
+
"purpose": "Fix mock preview, local vs EC2, layout toggles, all-11-types wizard export (Phase 6b)"
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"path": "00-integration/mockup-ui-extraction.md",
|
|
193
|
+
"purpose": "UI-only extraction from client mockup — no logic/language/types from images (Phase 0–4)"
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
"path": "00-integration/placeholders-and-custom-fields-wiring.md",
|
|
197
|
+
"purpose": "Discover placeholder + _c_ keys from fetch; precise useSurveySDK wiring (Phase 4–5)"
|
|
182
198
|
}
|
|
183
199
|
],
|
|
184
200
|
"templateScaleFiles": [
|
|
@@ -203,7 +219,7 @@
|
|
|
203
219
|
"apiBaseEnv": "CFM_DEPLOY_API_BASE",
|
|
204
220
|
"apiBaseDefault": "http://43.204.26.213:3000",
|
|
205
221
|
"wizardClientUrlPattern": "{CFM_DEPLOY_API_BASE}/wizard-app/index.html?session={sessionId}&secret={sessionSecret}",
|
|
206
|
-
"wizardClientUrlNote": "
|
|
222
|
+
"wizardClientUrlNote": "customize-ui only (remote client): EC2 link after preview upload — default is npx cfm-sdk customize on localhost",
|
|
207
223
|
"instanceIdEnv": "CFM_INSTANCE_ID",
|
|
208
224
|
"outDirEnv": "OUT_DIR",
|
|
209
225
|
"healthPath": "/health",
|
|
@@ -263,11 +279,12 @@
|
|
|
263
279
|
"../AGENT.md",
|
|
264
280
|
"00-integration/agent-operating-contract.md",
|
|
265
281
|
"00-integration/agent-execution-flow.md",
|
|
282
|
+
"00-integration/mockup-ui-extraction.md",
|
|
266
283
|
"MANIFEST.json"
|
|
267
284
|
],
|
|
268
285
|
"actions": [
|
|
269
286
|
"detectFramework",
|
|
270
|
-
"
|
|
287
|
+
"extractUiOnlyFromMockup"
|
|
271
288
|
]
|
|
272
289
|
},
|
|
273
290
|
{
|
|
@@ -320,6 +337,8 @@
|
|
|
320
337
|
"readAlways": [
|
|
321
338
|
"00-integration/agent-operating-contract.md",
|
|
322
339
|
"00-integration/agent-execution-flow.md",
|
|
340
|
+
"00-integration/mockup-ui-extraction.md",
|
|
341
|
+
"00-integration/placeholders-and-custom-fields-wiring.md",
|
|
323
342
|
"00-integration/component-checklist.md",
|
|
324
343
|
"00-integration/canonical-survey-fixtures.md",
|
|
325
344
|
"00-integration/client-lib-folder.md",
|
|
@@ -409,21 +428,23 @@
|
|
|
409
428
|
"00-integration/ui-customization-wizard.md",
|
|
410
429
|
"00-integration/wizard-preview-build-guide.md",
|
|
411
430
|
"00-integration/wizard-question-type-styling.md",
|
|
412
|
-
"00-integration/wizard-api.md"
|
|
431
|
+
"00-integration/wizard-api.md",
|
|
432
|
+
"00-integration/wizard-troubleshooting.md"
|
|
413
433
|
],
|
|
414
|
-
"cliCommand": "npx cfm-sdk customize
|
|
415
|
-
"
|
|
434
|
+
"cliCommand": "npx cfm-sdk customize",
|
|
435
|
+
"cliCommandRemote": "npx cfm-sdk customize-ui",
|
|
416
436
|
"cliConfirmCommand": "npx cfm-sdk confirm-ui-config",
|
|
417
|
-
"output": "survey-ui-config.
|
|
418
|
-
"
|
|
437
|
+
"output": "survey-ui-config.json",
|
|
438
|
+
"outputArtifactsRemote": [
|
|
419
439
|
"survey-ui-config.seed.json",
|
|
420
440
|
"survey-ui-config.final.json",
|
|
421
441
|
"survey-ui-config.diff.json",
|
|
422
442
|
"survey-ui-config.diff.md"
|
|
423
443
|
],
|
|
424
444
|
"agentMustWait": true,
|
|
425
|
-
"waitUntil": "
|
|
426
|
-
"clientHandoff": "
|
|
445
|
+
"waitUntil": "cliExit0UiConfigurationSaved",
|
|
446
|
+
"clientHandoff": "openLocalhostWizardSameMachine",
|
|
447
|
+
"clientHandoffRemote": "shareDeployedWizardUrlAfterPreviewUpload",
|
|
427
448
|
"prerequisites": [
|
|
428
449
|
"phase6VerifyPassed",
|
|
429
450
|
"devServerRunning"
|
package/templates/docs/index.md
CHANGED
|
@@ -77,9 +77,9 @@ Full step-by-step: [`agent-execution-flow.md`](00-integration/agent-execution-fl
|
|
|
77
77
|
| **2** Instance ID | Get JWT | `useSurveySDK.md` — **ask client** |
|
|
78
78
|
| **3** SDK contract | Hook + logic | `client-integration-guide.md`, `question-type-sdk-matrix.md`, logic docs |
|
|
79
79
|
| **4** Plan | Inventory + copy dispatcher | `component-checklist.md`, per-type MANIFEST docs, `implementation_plan.md` |
|
|
80
|
-
| **5** Build | Wizard-ready UI | `
|
|
80
|
+
| **5** Build | Wizard-ready UI | `wizard-chrome-contract.md`, `wizard-preview-build-guide.md`, `wizard-question-type-styling.md`, `03-ui-specs/*` |
|
|
81
81
|
| **6** Verify | Build + dev server | `verify-agent-build.sh` — **ask wizard** |
|
|
82
|
-
| **6b** Wizard | Optional customize | `customize-ui`
|
|
82
|
+
| **6b** Wizard | Optional customize | **`npx cfm-sdk customize`** (default) — EC2 `customize-ui` only if client on another device |
|
|
83
83
|
| **6c** Apply | Sync final config | `apply-ui-config.md` |
|
|
84
84
|
| **7** Deploy | Production | **ask deploy** — `aws-deploy.md` or Vercel |
|
|
85
85
|
|
|
@@ -101,7 +101,7 @@ Every agent build includes these even if the client later skips the wizard:
|
|
|
101
101
|
| Scale components | `templates/*Scale*.tsx` etc. | `src/components/` per inventory |
|
|
102
102
|
| Draft config | agent-written | `./survey-ui-config.json` |
|
|
103
103
|
|
|
104
|
-
**Chrome:** `
|
|
104
|
+
**Chrome:** copy `templates/docs/templates/Header.tsx` etc. — see `wizard-chrome-contract.md` for full hook list.
|
|
105
105
|
|
|
106
106
|
**Selection pattern:** focus ring = outer border + inner dot; cell selected = annulus fill — see `wizard-question-type-styling.md`.
|
|
107
107
|
|
|
@@ -130,12 +130,10 @@ Run: `bash templates/verify-agent-build.sh`
|
|
|
130
130
|
Only after Phase 6 passes and client says **yes**:
|
|
131
131
|
|
|
132
132
|
```bash
|
|
133
|
-
|
|
134
|
-
export CFM_DEPLOY_API_BASE="http://43.204.26.213:3000"
|
|
135
|
-
npx cfm-sdk customize-ui # uploads previews → prints client link → WAITS until submit
|
|
133
|
+
npx cfm-sdk customize # DEFAULT — opens localhost:4200, WAITS until client saves
|
|
136
134
|
```
|
|
137
135
|
|
|
138
|
-
**Phase 6b:**
|
|
136
|
+
**Phase 6b:** Client on same machine → `customize` writes `survey-ui-config.json` directly. Remote client → `customize-ui` + EC2 link (see `wizard-troubleshooting.md`). Apply per `apply-ui-config.md`.
|
|
139
137
|
|
|
140
138
|
**Phase 6c:** Apply from `survey-ui-config.final.json` per [`wizard-config-handoff.md`](00-integration/wizard-config-handoff.md) + [`apply-ui-config.md`](00-integration/apply-ui-config.md) — **every** diff path. Then:
|
|
141
139
|
|
|
@@ -151,13 +149,13 @@ Re-run verify script + build.
|
|
|
151
149
|
|
|
152
150
|
| When | Read |
|
|
153
151
|
|------|------|
|
|
154
|
-
| **Always first** | `agent-operating-contract.md`, `agent-execution-flow.md`, `index.md`, `client-integration-guide.md`, `constraints.md` |
|
|
152
|
+
| **Always first** | `agent-operating-contract.md`, `agent-execution-flow.md`, `mockup-ui-extraction.md`, `index.md`, `client-integration-guide.md`, `constraints.md` |
|
|
155
153
|
| Phase 3 contract | `question-type-sdk-matrix.md`, `useSurveySDK.md`, logic/navigation docs |
|
|
156
|
-
|
|
|
154
|
+
| Phase 4 plan | `mockup-ui-extraction.md`, `placeholders-and-custom-fields-wiring.md`, `component-checklist.md`, per-type `03-ui-specs/*` |
|
|
157
155
|
| Wizard build | `wizard-preview-build-guide.md`, `wizard-question-type-styling.md` |
|
|
158
|
-
| Wizard run | `wizard-config-handoff.md`, `ui-customization-wizard.md`, `wizard-api.md` |
|
|
156
|
+
| Wizard run | `wizard-config-handoff.md`, `ui-customization-wizard.md`, `wizard-api.md`, `wizard-troubleshooting.md` |
|
|
159
157
|
| After wizard (apply all paths) | `apply-ui-config.md` |
|
|
160
|
-
| Deploy | `aws-deploy.md
|
|
158
|
+
| Deploy | `aws-deploy.md` (§ Logos & local assets), `setup.md#nextjs-aws-static-export` |
|
|
161
159
|
| Config audit | `02-reference/config-field-index.md` |
|
|
162
160
|
|
|
163
161
|
---
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wizard-ready Footer — copy to src/components/Footer.tsx
|
|
3
|
+
* Required: footer.cfm-footer, data-cfm-copyright, data-cfm-footer-links, data-cfm-footer-logo
|
|
4
|
+
*/
|
|
5
|
+
'use client';
|
|
6
|
+
|
|
7
|
+
export default function Footer() {
|
|
8
|
+
return (
|
|
9
|
+
<footer
|
|
10
|
+
className="cfm-footer w-full"
|
|
11
|
+
style={{
|
|
12
|
+
background: 'var(--cfm-footer-bg, #262626)',
|
|
13
|
+
color: 'var(--cfm-footer-text, #9ca3af)',
|
|
14
|
+
padding: 'var(--cfm-footer-padding-y, 24px) var(--cfm-footer-padding-x, 40px)',
|
|
15
|
+
}}
|
|
16
|
+
>
|
|
17
|
+
<div
|
|
18
|
+
className="cfm-footer-inner mx-auto grid w-full max-w-4xl items-end gap-6"
|
|
19
|
+
style={{
|
|
20
|
+
display: 'var(--cfm-footer-inner-display, grid)',
|
|
21
|
+
flexDirection: 'var(--cfm-footer-inner-direction, row)' as React.CSSProperties['flexDirection'],
|
|
22
|
+
gridTemplateColumns: '1fr 1fr 1fr',
|
|
23
|
+
}}
|
|
24
|
+
>
|
|
25
|
+
<div
|
|
26
|
+
className="space-y-3"
|
|
27
|
+
style={{
|
|
28
|
+
gridColumn: 'var(--cfm-footer-logo-col, 1)',
|
|
29
|
+
justifySelf: 'var(--cfm-footer-logo-justify, start)',
|
|
30
|
+
}}
|
|
31
|
+
>
|
|
32
|
+
<img
|
|
33
|
+
data-cfm-footer-logo
|
|
34
|
+
data-cfm-logo
|
|
35
|
+
src="./logo.svg"
|
|
36
|
+
alt=""
|
|
37
|
+
style={{
|
|
38
|
+
display: 'none',
|
|
39
|
+
width: 'var(--cfm-footer-logo-width, 120px)',
|
|
40
|
+
height: 'var(--cfm-footer-logo-height, 32px)',
|
|
41
|
+
objectFit: 'contain',
|
|
42
|
+
}}
|
|
43
|
+
/>
|
|
44
|
+
<p
|
|
45
|
+
data-cfm-copyright
|
|
46
|
+
className="text-xs"
|
|
47
|
+
style={{ color: 'var(--cfm-footer-text, #9ca3af)' }}
|
|
48
|
+
>
|
|
49
|
+
© Company
|
|
50
|
+
</p>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<nav
|
|
54
|
+
data-cfm-footer-links
|
|
55
|
+
className="flex flex-wrap gap-x-6 gap-y-2 text-xs"
|
|
56
|
+
style={{
|
|
57
|
+
gridColumn: 'var(--cfm-footer-links-col, 3)',
|
|
58
|
+
justifySelf: 'var(--cfm-footer-links-justify, end)',
|
|
59
|
+
}}
|
|
60
|
+
>
|
|
61
|
+
<a
|
|
62
|
+
href="#"
|
|
63
|
+
className="transition-colors hover:opacity-80"
|
|
64
|
+
style={{ color: 'var(--cfm-footer-link, #9ca3af)' }}
|
|
65
|
+
>
|
|
66
|
+
Privacy
|
|
67
|
+
</a>
|
|
68
|
+
</nav>
|
|
69
|
+
</div>
|
|
70
|
+
</footer>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wizard-ready Header — copy to src/components/Header.tsx
|
|
3
|
+
* Required hooks: data-cfm-logo, data-cfm-logo-text, data-cfm-company
|
|
4
|
+
* Required class: cfm-header (layout toggles target header only — not progress bar)
|
|
5
|
+
*/
|
|
6
|
+
'use client';
|
|
7
|
+
|
|
8
|
+
type HeaderProps = {
|
|
9
|
+
/** When true, omits outer shadow — use inside SurveyStickyChrome */
|
|
10
|
+
embedded?: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default function Header({ embedded = false }: HeaderProps) {
|
|
14
|
+
return (
|
|
15
|
+
<header
|
|
16
|
+
className={`cfm-header relative w-full ${embedded ? '' : 'z-10 shadow-[0_2px_8px_rgba(0,0,0,0.06)]'}`}
|
|
17
|
+
style={{
|
|
18
|
+
height: 'var(--cfm-header-height, 120px)',
|
|
19
|
+
background: 'var(--cfm-header-bg, #fff)',
|
|
20
|
+
borderBottom: '1px solid var(--cfm-header-border, #e5e7eb)',
|
|
21
|
+
boxShadow: embedded ? undefined : 'var(--cfm-header-shadow, 0 2px 8px rgba(0,0,0,0.06))',
|
|
22
|
+
}}
|
|
23
|
+
>
|
|
24
|
+
<div
|
|
25
|
+
className="cfm-header-inner grid h-full w-full items-center"
|
|
26
|
+
style={{
|
|
27
|
+
gridTemplateColumns: '1fr 1fr 1fr',
|
|
28
|
+
padding: `0 var(--cfm-header-padding-x, 40px)`,
|
|
29
|
+
gap: 'var(--cfm-header-brand-gap, 16px)',
|
|
30
|
+
}}
|
|
31
|
+
>
|
|
32
|
+
<div
|
|
33
|
+
className="flex items-center"
|
|
34
|
+
style={{
|
|
35
|
+
gridColumn: 'var(--cfm-header-logo-col, 1)',
|
|
36
|
+
justifySelf: 'var(--cfm-header-logo-justify, start)',
|
|
37
|
+
}}
|
|
38
|
+
>
|
|
39
|
+
<img
|
|
40
|
+
data-cfm-logo
|
|
41
|
+
src="./logo.svg"
|
|
42
|
+
alt="Logo"
|
|
43
|
+
className="object-contain"
|
|
44
|
+
style={{
|
|
45
|
+
display: 'none',
|
|
46
|
+
width: 'var(--cfm-header-logo-width, 120px)',
|
|
47
|
+
height: 'var(--cfm-header-logo-height, 120px)',
|
|
48
|
+
padding: 'var(--cfm-header-logo-padding, 10px)',
|
|
49
|
+
background: 'var(--cfm-primary)',
|
|
50
|
+
}}
|
|
51
|
+
/>
|
|
52
|
+
<span
|
|
53
|
+
data-cfm-logo-text
|
|
54
|
+
className="font-semibold"
|
|
55
|
+
style={{
|
|
56
|
+
color: 'var(--cfm-header-company-color, var(--cfm-text))',
|
|
57
|
+
fontSize: 'var(--cfm-header-company-size, 18px)',
|
|
58
|
+
fontWeight: 'var(--cfm-header-company-weight, 600)',
|
|
59
|
+
}}
|
|
60
|
+
>
|
|
61
|
+
Company
|
|
62
|
+
</span>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<span
|
|
66
|
+
data-cfm-company
|
|
67
|
+
className="font-semibold"
|
|
68
|
+
style={{
|
|
69
|
+
gridColumn: 'var(--cfm-header-company-col, 2)',
|
|
70
|
+
justifySelf: 'var(--cfm-header-company-justify, center)',
|
|
71
|
+
marginTop: 'var(--cfm-header-company-margin-top, 0)',
|
|
72
|
+
marginLeft: 'var(--cfm-header-company-margin-left, 0)',
|
|
73
|
+
marginRight: 'var(--cfm-header-company-margin-right, 0)',
|
|
74
|
+
color: 'var(--cfm-header-company-color, var(--cfm-text))',
|
|
75
|
+
fontSize: 'var(--cfm-header-company-size, 18px)',
|
|
76
|
+
fontWeight: 'var(--cfm-header-company-weight, 600)',
|
|
77
|
+
}}
|
|
78
|
+
>
|
|
79
|
+
Company Name
|
|
80
|
+
</span>
|
|
81
|
+
</div>
|
|
82
|
+
</header>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { SurveyLanguage } from '@explorer02/cfm-survey-sdk';
|
|
4
|
+
|
|
5
|
+
type LanguageSelectorProps = {
|
|
6
|
+
languages: SurveyLanguage[];
|
|
7
|
+
selectedLanguage: string;
|
|
8
|
+
onChange: (language: string) => void;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/** Wizard toggle showLanguageSwitcher targets [data-cfm-language] */
|
|
12
|
+
export default function LanguageSelector({
|
|
13
|
+
languages,
|
|
14
|
+
selectedLanguage,
|
|
15
|
+
onChange,
|
|
16
|
+
}: LanguageSelectorProps) {
|
|
17
|
+
if (languages.length <= 1) return null;
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div data-cfm-language className="mb-6 flex justify-end">
|
|
21
|
+
<select
|
|
22
|
+
value={selectedLanguage}
|
|
23
|
+
onChange={(e) => onChange(e.target.value)}
|
|
24
|
+
className="rounded border px-3 py-1.5 text-sm outline-none"
|
|
25
|
+
style={{
|
|
26
|
+
background: 'var(--cfm-language-bg, #fff)',
|
|
27
|
+
color: 'var(--cfm-language-text, #2563eb)',
|
|
28
|
+
borderColor: 'var(--cfm-language-border, #2563eb)',
|
|
29
|
+
borderRadius: 'var(--cfm-border-radius, 8px)',
|
|
30
|
+
}}
|
|
31
|
+
>
|
|
32
|
+
{languages.map((lang) => (
|
|
33
|
+
<option key={lang.code} value={lang.code}>
|
|
34
|
+
{lang.label}
|
|
35
|
+
</option>
|
|
36
|
+
))}
|
|
37
|
+
</select>
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect } from 'react';
|
|
4
|
+
import { initPreviewBridge } from '@/lib/previewBridge';
|
|
5
|
+
|
|
6
|
+
/** Mount once in root layout — enables localhost dev preview with npx cfm-sdk customize */
|
|
7
|
+
export function PreviewBridgeInit() {
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
const cleanup = initPreviewBridge();
|
|
10
|
+
return cleanup;
|
|
11
|
+
}, []);
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wizard-ready ProgressBar — copy to src/components/ProgressBar.tsx
|
|
3
|
+
* Required: data-cfm-progress + class cfm-progress (separate from header toggle)
|
|
4
|
+
*/
|
|
5
|
+
'use client';
|
|
6
|
+
|
|
7
|
+
type ProgressBarProps = {
|
|
8
|
+
progressPercentage: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default function ProgressBar({ progressPercentage }: ProgressBarProps) {
|
|
12
|
+
const width = Math.min(100, Math.max(0, progressPercentage));
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div data-cfm-progress className="cfm-progress w-full">
|
|
16
|
+
<div
|
|
17
|
+
className="w-full overflow-hidden rounded-full"
|
|
18
|
+
style={{
|
|
19
|
+
height: 'var(--cfm-progress-height, 12px)',
|
|
20
|
+
background: 'var(--cfm-progress-track, #e5e7eb)',
|
|
21
|
+
}}
|
|
22
|
+
>
|
|
23
|
+
<div
|
|
24
|
+
className="h-full rounded-full transition-all duration-300 ease-out"
|
|
25
|
+
style={{
|
|
26
|
+
width: `${width}%`,
|
|
27
|
+
background: 'var(--cfm-progress-fill, var(--cfm-primary))',
|
|
28
|
+
}}
|
|
29
|
+
/>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -101,13 +101,35 @@ export default function Question({
|
|
|
101
101
|
}, [question, allAnswers, allQuestions, customFieldValues]);
|
|
102
102
|
|
|
103
103
|
return (
|
|
104
|
-
<section
|
|
105
|
-
|
|
104
|
+
<section
|
|
105
|
+
id={question.id}
|
|
106
|
+
className="cfm-question-card space-y-4"
|
|
107
|
+
style={{
|
|
108
|
+
background: 'var(--cfm-card-bg, #fff)',
|
|
109
|
+
borderColor: 'var(--cfm-card-border-color, #e5e7eb)',
|
|
110
|
+
borderWidth: 1,
|
|
111
|
+
borderStyle: 'solid',
|
|
112
|
+
padding: 'var(--cfm-card-padding, 24px)',
|
|
113
|
+
boxShadow: 'var(--cfm-card-shadow, 0 1px 3px rgba(0,0,0,0.1))',
|
|
114
|
+
borderRadius: 'var(--cfm-border-radius, 12px)',
|
|
115
|
+
fontFamily: 'var(--cfm-font-family)',
|
|
116
|
+
}}
|
|
117
|
+
>
|
|
118
|
+
<h2
|
|
119
|
+
className="text-lg font-medium leading-relaxed"
|
|
120
|
+
style={{ color: 'var(--cfm-text, #111827)' }}
|
|
121
|
+
>
|
|
106
122
|
{question.questionNumber != null && (
|
|
107
|
-
<span className="mr-1 font-semibold">
|
|
123
|
+
<span data-cfm-question-number className="mr-1 font-semibold">
|
|
124
|
+
{question.questionNumber}.
|
|
125
|
+
</span>
|
|
108
126
|
)}
|
|
109
127
|
<span dangerouslySetInnerHTML={{ __html: displayQuestionText }} />
|
|
110
|
-
{question.required &&
|
|
128
|
+
{question.required && (
|
|
129
|
+
<span data-cfm-required className="ml-1 text-red-500">
|
|
130
|
+
*
|
|
131
|
+
</span>
|
|
132
|
+
)}
|
|
111
133
|
</h2>
|
|
112
134
|
{displayQuestionDescription && (
|
|
113
135
|
<div
|
|
@@ -182,7 +204,14 @@ export default function Question({
|
|
|
182
204
|
return (
|
|
183
205
|
<label
|
|
184
206
|
key={option.id}
|
|
185
|
-
className=
|
|
207
|
+
className="flex cursor-pointer items-center gap-4 transition-all"
|
|
208
|
+
style={{
|
|
209
|
+
borderRadius: 'var(--cfm-mcq-border-radius, 8px)',
|
|
210
|
+
border: `2px solid ${isSelected ? 'var(--cfm-mcq-selected-accent, var(--cfm-primary))' : 'var(--cfm-card-border-color, #e5e7eb)'}`,
|
|
211
|
+
background: isSelected ? 'var(--cfm-mcq-selected-bg, var(--cfm-secondary))' : 'var(--cfm-card-bg, #fff)',
|
|
212
|
+
padding: 'var(--cfm-mcq-card-padding, 12px)',
|
|
213
|
+
marginBottom: 'var(--cfm-mcq-option-gap, 8px)',
|
|
214
|
+
}}
|
|
186
215
|
>
|
|
187
216
|
<input
|
|
188
217
|
type="checkbox"
|
|
@@ -211,7 +240,14 @@ export default function Question({
|
|
|
211
240
|
return (
|
|
212
241
|
<label
|
|
213
242
|
key={option.id}
|
|
214
|
-
className=
|
|
243
|
+
className="flex cursor-pointer items-center gap-4 transition-all"
|
|
244
|
+
style={{
|
|
245
|
+
borderRadius: 'var(--cfm-mcq-border-radius, 8px)',
|
|
246
|
+
border: `2px solid ${isSelected ? 'var(--cfm-mcq-selected-accent, var(--cfm-primary))' : 'var(--cfm-card-border-color, #e5e7eb)'}`,
|
|
247
|
+
background: isSelected ? 'var(--cfm-mcq-selected-bg, var(--cfm-secondary))' : 'var(--cfm-card-bg, #fff)',
|
|
248
|
+
padding: 'var(--cfm-mcq-card-padding, 12px)',
|
|
249
|
+
marginBottom: 'var(--cfm-mcq-option-gap, 8px)',
|
|
250
|
+
}}
|
|
215
251
|
>
|
|
216
252
|
<input
|
|
217
253
|
type="radio"
|
|
@@ -245,11 +281,18 @@ export default function Question({
|
|
|
245
281
|
|
|
246
282
|
{question.type === QUESTION_TYPE.TEXTFIELD && (
|
|
247
283
|
<textarea
|
|
284
|
+
data-cfm-textfield
|
|
248
285
|
rows={question.inputVariant === 'long' ? 4 : 1}
|
|
249
286
|
value={(selectedValue as string) ?? ''}
|
|
250
287
|
onChange={e => onSelect(e.target.value)}
|
|
251
288
|
placeholder={question.placeholder || 'Type your response here...'}
|
|
252
|
-
className="w-full
|
|
289
|
+
className="w-full outline-none"
|
|
290
|
+
style={{
|
|
291
|
+
height: question.inputVariant === 'long' ? undefined : 'var(--cfm-input-height, 44px)',
|
|
292
|
+
borderRadius: 'var(--cfm-input-radius, 8px)',
|
|
293
|
+
border: '1px solid var(--cfm-input-border, #d1d5db)',
|
|
294
|
+
padding: 'var(--cfm-input-padding, 12px)',
|
|
295
|
+
}}
|
|
253
296
|
/>
|
|
254
297
|
)}
|
|
255
298
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
1
3
|
import Header from '@/components/Header';
|
|
2
4
|
import ProgressBar from '@/components/ProgressBar';
|
|
3
5
|
|
|
@@ -6,17 +8,29 @@ type SurveyStickyChromeProps = {
|
|
|
6
8
|
};
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
|
-
* Sticky
|
|
10
|
-
*
|
|
11
|
+
* Sticky chrome — header and progress are SEPARATE targets for wizard layout toggles.
|
|
12
|
+
* - showHeader → .cfm-header only
|
|
13
|
+
* - showProgressBar → [data-cfm-progress] / .cfm-progress only
|
|
14
|
+
* Do NOT put cfm-header on this wrapper.
|
|
11
15
|
*/
|
|
12
16
|
export default function SurveyStickyChrome({ progressPercentage }: SurveyStickyChromeProps) {
|
|
13
17
|
return (
|
|
14
18
|
<div
|
|
15
|
-
className="sticky top-0 z-30
|
|
16
|
-
style={{
|
|
19
|
+
className="cfm-sticky-chrome sticky top-0 z-30"
|
|
20
|
+
style={{
|
|
21
|
+
background: 'var(--cfm-header-bg, #fff)',
|
|
22
|
+
boxShadow: 'var(--cfm-header-shadow, 0 2px 8px rgba(0,0,0,0.06))',
|
|
23
|
+
}}
|
|
17
24
|
>
|
|
18
25
|
<Header embedded />
|
|
19
|
-
<div
|
|
26
|
+
<div
|
|
27
|
+
className="mx-auto w-full px-4 sm:px-6 lg:px-8"
|
|
28
|
+
style={{
|
|
29
|
+
maxWidth: 'var(--cfm-max-width, 48rem)',
|
|
30
|
+
paddingTop: 'var(--cfm-sticky-pt, 16px)',
|
|
31
|
+
paddingBottom: 'var(--cfm-sticky-pb, 20px)',
|
|
32
|
+
}}
|
|
33
|
+
>
|
|
20
34
|
<ProgressBar progressPercentage={progressPercentage} />
|
|
21
35
|
</div>
|
|
22
36
|
</div>
|
|
@@ -24,6 +24,55 @@ echo "API: $CFM_DEPLOY_API_BASE"
|
|
|
24
24
|
echo "OUT: $OUT_DIR ($FILE_COUNT files)"
|
|
25
25
|
echo
|
|
26
26
|
|
|
27
|
+
# Verify wizard logos and other public assets made it into the static build (not just browser memory).
|
|
28
|
+
python3 <<'PY' || exit 1
|
|
29
|
+
import json, os, re, sys
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
|
|
32
|
+
out = Path(os.environ["OUT_DIR"])
|
|
33
|
+
root = Path(os.environ.get("PROJECT_ROOT", os.getcwd()))
|
|
34
|
+
config_path = root / "survey-ui-config.json"
|
|
35
|
+
missing = []
|
|
36
|
+
|
|
37
|
+
if config_path.is_file():
|
|
38
|
+
cfg = json.load(open(config_path))
|
|
39
|
+
logo = (cfg.get("global") or {}).get("logo") or {}
|
|
40
|
+
fn = logo.get("fileName")
|
|
41
|
+
url = logo.get("url") or ""
|
|
42
|
+
if fn:
|
|
43
|
+
if url.startswith("data:"):
|
|
44
|
+
print(f"FAIL: global.logo.url is a data: URL — apply Phase 6c: Header src=./{fn}, file in public/")
|
|
45
|
+
sys.exit(1)
|
|
46
|
+
for base in (root / "public", out):
|
|
47
|
+
if not (base / fn).is_file():
|
|
48
|
+
missing.append(str(base / fn))
|
|
49
|
+
if missing:
|
|
50
|
+
print("FAIL: logo file missing from build output:")
|
|
51
|
+
for p in missing:
|
|
52
|
+
print(f" - {p}")
|
|
53
|
+
print("Fix: run confirm-ui-config (EC2 wizard) or re-upload in customize; update Header data-cfm-logo src")
|
|
54
|
+
sys.exit(1)
|
|
55
|
+
print(f"Logo OK: {fn} in out/")
|
|
56
|
+
else:
|
|
57
|
+
print("No global.logo.fileName in survey-ui-config.json — skipping logo check")
|
|
58
|
+
|
|
59
|
+
for header in sorted((root / "src").rglob("Header.tsx")):
|
|
60
|
+
text = header.read_text()
|
|
61
|
+
for m in re.finditer(r'data-cfm-logo[^>]*\ssrc=["\']([^"\']+)["\']', text):
|
|
62
|
+
src = m.group(1)
|
|
63
|
+
if src.startswith(("http://", "https://", "data:")):
|
|
64
|
+
print(f"FAIL: {header} uses external/data logo src: {src[:60]}")
|
|
65
|
+
sys.exit(1)
|
|
66
|
+
rel = src.lstrip("./").lstrip("/")
|
|
67
|
+
if rel and not (out / rel).is_file():
|
|
68
|
+
missing.append(str(out / rel))
|
|
69
|
+
print(f"FAIL: Header references {src} but {out / rel} not in build")
|
|
70
|
+
sys.exit(1)
|
|
71
|
+
|
|
72
|
+
if not missing:
|
|
73
|
+
print("Static asset preflight OK")
|
|
74
|
+
PY
|
|
75
|
+
|
|
27
76
|
HEALTH=$(curl -s "$CFM_DEPLOY_API_BASE/health")
|
|
28
77
|
echo "Health: $HEALTH"
|
|
29
78
|
echo
|
|
@@ -111,4 +160,29 @@ if [ "$HTTP" != "200" ]; then
|
|
|
111
160
|
exit 1
|
|
112
161
|
fi
|
|
113
162
|
|
|
163
|
+
# Optional: verify logo asset under same deploy prefix (broken img = missing from out/ or wrong Header src)
|
|
164
|
+
python3 <<'PY' || true
|
|
165
|
+
import json, os, re, subprocess
|
|
166
|
+
from pathlib import Path
|
|
167
|
+
from urllib.parse import urljoin
|
|
168
|
+
|
|
169
|
+
root = Path(os.environ.get("PROJECT_ROOT", os.getcwd()))
|
|
170
|
+
cfg_path = root / "survey-ui-config.json"
|
|
171
|
+
live = json.load(open("/tmp/deploy-complete.json"))["url"]
|
|
172
|
+
if not cfg_path.is_file():
|
|
173
|
+
raise SystemExit(0)
|
|
174
|
+
fn = ((json.load(open(cfg_path)).get("global") or {}).get("logo") or {}).get("fileName")
|
|
175
|
+
if not fn:
|
|
176
|
+
raise SystemExit(0)
|
|
177
|
+
base = live.rsplit("/", 1)[0] + "/"
|
|
178
|
+
logo_url = urljoin(base, fn)
|
|
179
|
+
code = subprocess.check_output(
|
|
180
|
+
["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}", logo_url],
|
|
181
|
+
text=True,
|
|
182
|
+
).strip()
|
|
183
|
+
print(f"Logo asset: {logo_url} → HTTP {code}")
|
|
184
|
+
if code != "200":
|
|
185
|
+
print("WARN: logo missing on CloudFront — rebuild after Phase 6c logo apply (see aws-deploy.md § Static assets)")
|
|
186
|
+
PY
|
|
187
|
+
|
|
114
188
|
echo "=== Deploy complete ==="
|