@wildo-ai/saas-website 1.1.7 → 1.1.9
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/esm/astro/assert-website-pages-have-sections.d.ts +28 -0
- package/dist/esm/astro/assert-website-pages-have-sections.d.ts.map +1 -0
- package/dist/esm/astro/assert-website-pages-have-sections.js +38 -0
- package/dist/esm/astro/assert-website-pages-have-sections.js.map +1 -0
- package/dist/esm/astro/blog-post-bridge.d.ts.map +1 -1
- package/dist/esm/astro/blog-post-bridge.js.map +1 -1
- package/dist/esm/astro/blog-post-head.renderer.d.ts.map +1 -1
- package/dist/esm/astro/bridge-runtime.d.ts.map +1 -1
- package/dist/esm/astro/bridge-runtime.js.map +1 -1
- package/dist/esm/astro/collect-expected-label-keys.d.ts.map +1 -1
- package/dist/esm/astro/headers-renderer.d.ts.map +1 -1
- package/dist/esm/astro/i18n-routing.helper.d.ts.map +1 -1
- package/dist/esm/astro/label-pack-loader.d.ts +6 -0
- package/dist/esm/astro/label-pack-loader.d.ts.map +1 -1
- package/dist/esm/astro/label-pack-loader.js +10 -0
- package/dist/esm/astro/label-pack-loader.js.map +1 -1
- package/dist/esm/astro/llms-txt-server.d.ts.map +1 -1
- package/dist/esm/astro/pick-labels-for-page.d.ts.map +1 -1
- package/dist/esm/astro/robots-renderer.d.ts.map +1 -1
- package/dist/esm/astro/robots-renderer.js.map +1 -1
- package/dist/esm/astro/sitemap-coverage.d.ts.map +1 -1
- package/dist/esm/astro/structured-data.renderer.d.ts.map +1 -1
- package/dist/esm/astro/website-page-head.renderer.d.ts.map +1 -1
- package/dist/esm/astro/website-page-head.renderer.js.map +1 -1
- package/dist/esm/astro/website-page-runtime.helper.d.ts.map +1 -1
- package/dist/esm/astro/website-site-context.d.ts.map +1 -1
- package/dist/esm/astro/website-site-context.js.map +1 -1
- package/dist/esm/astro-island.d.ts.map +1 -1
- package/dist/esm/astro.d.ts +1 -0
- package/dist/esm/astro.d.ts.map +1 -1
- package/dist/esm/astro.js +1 -0
- package/dist/esm/astro.js.map +1 -1
- package/dist/esm/companion-exports.d.ts.map +1 -1
- package/dist/esm/companion-exports.js.map +1 -1
- package/dist/esm/components/low-level/WebsiteInternalButton.d.ts.map +1 -1
- package/dist/esm/components/low-level/WebsiteInternalLink.d.ts.map +1 -1
- package/dist/esm/config/define-website-config.d.ts.map +1 -1
- package/dist/esm/config/define-website-config.js.map +1 -1
- package/dist/esm/config/index.d.ts.map +1 -1
- package/dist/esm/config/load-website-config.d.ts.map +1 -1
- package/dist/esm/config/load-website-config.js.map +1 -1
- package/dist/esm/config/load-website-pricing-catalog.d.ts.map +1 -1
- package/dist/esm/config/load-website-pricing-catalog.js.map +1 -1
- package/dist/esm/config/load-website-site-context.d.ts.map +1 -1
- package/dist/esm/config/load-website-site-context.js.map +1 -1
- package/dist/esm/config/wildo-website-config.schemas.d.ts.map +1 -1
- package/dist/esm/config-loader.d.ts.map +1 -1
- package/dist/esm/config-loader.js.map +1 -1
- package/dist/esm/core/anonymous-session/InboundContactForm.d.ts.map +1 -1
- package/dist/esm/core/anonymous-session/InboundContactForm.js.map +1 -1
- package/dist/esm/core/anonymous-session/inbound-contact-form.schema.d.ts.map +1 -1
- package/dist/esm/core/anonymous-session/inbound-contact-form.schema.js.map +1 -1
- package/dist/esm/core/anonymous-session/website-anonymous-session-client.d.ts.map +1 -1
- package/dist/esm/core/anonymous-session/website-anonymous-session-client.js.map +1 -1
- package/dist/esm/core/anonymous-session/website-submission-challenge.d.ts.map +1 -1
- package/dist/esm/core/anonymous-session/website-submission-challenge.js.map +1 -1
- package/dist/esm/core/consent/WebsiteConsentBanner.d.ts.map +1 -1
- package/dist/esm/core/consent/WebsiteConsentBanner.js.map +1 -1
- package/dist/esm/core/consent/WebsiteConsentContext.d.ts.map +1 -1
- package/dist/esm/core/consent/WebsiteConsentGate.d.ts.map +1 -1
- package/dist/esm/core/consent/useWebsiteConsent.d.ts.map +1 -1
- package/dist/esm/core/consent/website-consent-storage.d.ts.map +1 -1
- package/dist/esm/core/contexts/WebsitePageContext.d.ts.map +1 -1
- package/dist/esm/core/contexts/WebsiteRuntimeContext.d.ts.map +1 -1
- package/dist/esm/core/contexts/WebsiteRuntimeContext.js.map +1 -1
- package/dist/esm/core/contexts/WebsiteSectionContext.d.ts.map +1 -1
- package/dist/esm/core/contexts/useWebsitePage.d.ts.map +1 -1
- package/dist/esm/core/contexts/useWebsiteRuntime.d.ts.map +1 -1
- package/dist/esm/core/contexts/useWebsiteRuntime.js.map +1 -1
- package/dist/esm/core/contexts/useWebsiteSection.d.ts.map +1 -1
- package/dist/esm/core/external-providers/WebsiteProviderComponent.d.ts.map +1 -1
- package/dist/esm/core/external-providers/WebsiteProviderComponent.js.map +1 -1
- package/dist/esm/core/external-providers/frontend-provider-registry.website.d.ts.map +1 -1
- package/dist/esm/core/external-providers/provider-component-registry.website.d.ts.map +1 -1
- package/dist/esm/core/external-providers/provider-component-registry.website.js.map +1 -1
- package/dist/esm/core/external-providers/useWebsiteProviderComponent.d.ts.map +1 -1
- package/dist/esm/core/external-providers/useWebsiteProviderComponent.js.map +1 -1
- package/dist/esm/core/external-providers/useWebsiteProviderScripts.d.ts.map +1 -1
- package/dist/esm/core/external-providers/useWebsiteProviderScripts.js.map +1 -1
- package/dist/esm/core/external-providers/website-provider-sdk-activations.d.ts.map +1 -1
- package/dist/esm/core/external-providers/website-provider-sdk-activations.js.map +1 -1
- package/dist/esm/core/factories/define-website-page-manifest.d.ts.map +1 -1
- package/dist/esm/core/factories/define-website-section.d.ts +2 -0
- package/dist/esm/core/factories/define-website-section.d.ts.map +1 -1
- package/dist/esm/core/factories/define-website-section.js +2 -0
- package/dist/esm/core/factories/define-website-section.js.map +1 -1
- package/dist/esm/core/hooks/useWebsiteDesignTokens.d.ts.map +1 -1
- package/dist/esm/core/hooks/useWebsiteLabel.d.ts.map +1 -1
- package/dist/esm/core/layouts/WebsitePageLayout.d.ts.map +1 -1
- package/dist/esm/core/layouts/WebsitePageLayout.js.map +1 -1
- package/dist/esm/core/layouts/WebsitePageSections.d.ts +43 -0
- package/dist/esm/core/layouts/WebsitePageSections.d.ts.map +1 -0
- package/dist/esm/core/layouts/WebsitePageSections.js +13 -0
- package/dist/esm/core/layouts/WebsitePageSections.js.map +1 -0
- package/dist/esm/core/layouts/WebsiteSection.d.ts.map +1 -1
- package/dist/esm/core/routing/internal-routing.utils.d.ts.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/mdx/BlogPost.d.ts.map +1 -1
- package/dist/esm/mdx/blog-post-collection.config.d.ts.map +1 -1
- package/dist/esm/mdx/blog-post-frontmatter-source.d.ts.map +1 -1
- package/dist/esm/mdx/index.d.ts.map +1 -1
- package/dist/esm/mdx.d.ts.map +1 -1
- package/dist/esm/schemas/blog/blog-post-frontmatter.shared.schemas.d.ts.map +1 -1
- package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.d.ts.map +1 -1
- package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.js.map +1 -1
- package/dist/esm/schemas/label-keys/website-consent-label-keys.schemas.d.ts.map +1 -1
- package/dist/esm/schemas/label-keys/website-label-key.schemas.d.ts.map +1 -1
- package/dist/esm/schemas/label-keys/website-provider-component-label-keys.schemas.d.ts.map +1 -1
- package/dist/esm/schemas/label-keys/website-provider-component-label-keys.schemas.js.map +1 -1
- package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.d.ts +2 -0
- package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.d.ts.map +1 -1
- package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.js +12 -3
- package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.js.map +1 -1
- package/dist/esm/schemas/manifests/website-root-config.shared.schemas.d.ts +3 -3
- package/dist/esm/schemas/manifests/website-root-config.shared.schemas.d.ts.map +1 -1
- package/dist/esm/schemas/manifests/website-root-config.shared.schemas.js.map +1 -1
- package/dist/esm/schemas/refs/page-ref.schemas.d.ts.map +1 -1
- package/dist/esm/schemas/refs/section-ref.schemas.d.ts.map +1 -1
- package/dist/esm/schemas/sections/website-section-category.shared.d.ts.map +1 -1
- package/dist/esm/schemas/sections/website-section-definition.shared.schemas.d.ts +9 -0
- package/dist/esm/schemas/sections/website-section-definition.shared.schemas.d.ts.map +1 -1
- package/dist/esm/schemas/sections/website-section-definition.shared.schemas.js +11 -0
- package/dist/esm/schemas/sections/website-section-definition.shared.schemas.js.map +1 -1
- package/dist/esm/schemas/structured-data/pricing-offer-structured-data.shared.d.ts.map +1 -1
- package/dist/esm/schemas/structured-data/pricing-offer-structured-data.shared.js.map +1 -1
- package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.d.ts.map +1 -1
- package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.js.map +1 -1
- package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.d.ts.map +1 -1
- package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.js.map +1 -1
- package/dist/esm/schemas/validators/label-pack.validator.d.ts.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/src/astro/__tests__/assert-website-pages-have-sections.test.ts +75 -0
- package/src/astro/assert-website-pages-have-sections.ts +41 -0
- package/src/astro/label-pack-loader.ts +10 -0
- package/src/astro.ts +1 -0
- package/src/core/__tests__/WebsitePageSections.test.tsx +57 -0
- package/src/core/factories/define-website-section.ts +4 -0
- package/src/core/layouts/WebsitePageSections.tsx +68 -0
- package/src/index.ts +1 -0
- package/src/schemas/__tests__/page-manifest.test.ts +4 -7
- package/src/schemas/manifests/website-page-manifest.shared.schemas.ts +12 -3
- package/src/schemas/sections/website-section-definition.shared.schemas.ts +11 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InboundContactForm.js","sourceRoot":"","sources":["../../../../src/core/anonymous-session/InboundContactForm.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * InboundContactForm — the marketing site's real lead-capture form (React island).\n *\n * Self-contained + bundle-safe: uses only `react`, the website-local input schema, and\n * a `WebsiteAnonymousSessionClient` instance (constructed by the section/bridge wiring\n * from the site's API-origin config). On submit it validates client-side, then POSTs\n * `/inbound-contacts` through the anonymous-session client — the FIRST submission mints\n * the anonymous session (init header) and the backend stamps the lead's `anonymousUserId`\n * so it can later transpose to the visitor's account on signup/login.\n *\n * Labels are injected rather than read from the website runtime context, so the component stays framework-light and\n * unit-testable; the section wrapper passes the strings it resolved through `useWebsiteLabel`. EVERY label is required\n * and the form carries no words of its own (#1986): a fallback would be English in every locale and would hide the\n * missing label, while a required prop makes an omission a type error in the section, and a key the pack does not\n * carry reaches the page as the key itself — which is what `useWebsiteLabel` returns, so somebody authors it.\n *\n * Submission challenge (#1735): when the application protects the form with a captcha, each submission carries an\n * answer — the consented vendor's token when its browser part runs on the page, otherwise the engine's proof of work,\n * which needs no consent (see `website-submission-challenge.ts`). Nothing for the visitor to click or read: both are\n * invisible, so the form stays operable by keyboard and screen reader exactly as before. A vendor token the backend\n * refuses is retried once with the proof of work; a refusal that survives that shows `challengeRefusedMessage`.\n */\n\nimport React, { useCallback, useState } from \"react\";\nimport { SubmissionChallengeAction, WildoHeaderKeys } from \"@wildo-ai/saas-models/public-runtime\";\nimport {\n INBOUND_CONTACT_NAME_MAX_LENGTH,\n INBOUND_CONTACT_PHONE_MAX_LENGTH,\n InboundContactFormSchema,\n buildInboundContactCreateBody,\n type InboundContactFormValues,\n} from \"./inbound-contact-form.schema\";\nimport type { WebsiteAnonymousSessionClient } from \"./website-anonymous-session-client\";\nimport {\n isSubmissionChallengeRefusal,\n obtainSubmissionChallengeAnswer,\n useWebsiteSubmissionChallengeSource,\n type WebsiteSubmissionChallengeSource,\n} from \"./website-submission-challenge\";\n\n/**\n * Every word the form shows. All required (#1986): the section resolves each through `useWebsiteLabel` and lists its\n * key in `expectedLabelKeys`, so a missing label is a type error in the section or a visible key on the page, never a\n * silent English substitute.\n */\nexport interface InboundContactFormLabels {\n emailLabel: string;\n firstNameLabel: string;\n lastNameLabel: string;\n phoneLabel: string;\n /** OPTIONAL marketing opt-in checkbox. */\n marketingConsentLabel: string;\n submitLabel: string;\n submittingLabel: string;\n successMessage: string;\n errorMessage: string;\n /** The anti-spam check did not pass, even with the proof of work (#1735). */\n challengeRefusedMessage: string;\n /** Shown beside the email field when what was typed is not an email address (#1986). */\n invalidEmailMessage: string;\n}\n\ntype SubmitState = { status: \"idle\" } | { status: \"submitting\" } | { status: \"success\" } | { status: \"error\"; message: string };\n\nexport interface InboundContactFormProps {\n /** The anonymous-session client (constructed by the section wiring from site config). */\n client: WebsiteAnonymousSessionClient;\n /** The section's i18n-resolved labels — every one of them (#1986). */\n labels: InboundContactFormLabels;\n /** Optional callback fired after a successful submission (e.g. analytics). */\n onSubmitted?: () => void;\n /** The page's captcha, for a form rendered outside a page layout (tests). Defaults to the page's own. */\n challengeSource?: WebsiteSubmissionChallengeSource;\n}\n\nconst EMPTY_VALUES: InboundContactFormValues = {\n email: \"\",\n firstName: \"\",\n lastName: \"\",\n phone: \"\",\n marketingConsent: false,\n};\n\nexport function InboundContactForm({ client, labels, onSubmitted, challengeSource }: InboundContactFormProps): React.ReactElement {\n const pageChallengeSource = useWebsiteSubmissionChallengeSource();\n const source = challengeSource ?? pageChallengeSource;\n const [values, setValues] = useState<InboundContactFormValues>(EMPTY_VALUES);\n const [emailInvalid, setEmailInvalid] = useState(false);\n const [submit, setSubmit] = useState<SubmitState>({ status: \"idle\" });\n\n const setField = useCallback(<K extends keyof InboundContactFormValues>(key: K, value: InboundContactFormValues[K]) => {\n setValues((prev) => ({ ...prev, [key]: value }));\n }, []);\n\n const handleSubmit = useCallback(\n async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n setEmailInvalid(false);\n\n const parsed = InboundContactFormSchema.safeParse(values);\n if (!parsed.success) {\n // The schema's own issue messages are never shown: they are English and nobody authors them (#1986). The email\n // is the one field a visitor can get wrong, and it has its label. The other fields' only rule is a length the\n // inputs already enforce with `maxLength`, so reaching a failure there means the values did not come from\n // typing, and the form answers with its generic error rather than inventing a sentence.\n const emailFailed = parsed.error.issues.some((issue) => issue.path[0] === \"email\");\n setEmailInvalid(emailFailed);\n if (!emailFailed) setSubmit({ status: \"error\", message: labels.errorMessage });\n return;\n }\n\n setSubmit({ status: \"submitting\" });\n const body = buildInboundContactCreateBody(parsed.data);\n const send = async (proofOfWorkOnly: boolean): Promise<void> => {\n const answer = await obtainSubmissionChallengeAnswer({ source, action: SubmissionChallengeAction.INBOUND_CONTACT, client, proofOfWorkOnly });\n await client.postAnonymous(\"/inbound-contacts\", body, answer === undefined ? undefined : { [WildoHeaderKeys.SUBMISSION_CHALLENGE_RESPONSE]: answer });\n };\n try {\n try {\n await send(false);\n } catch (error) {\n // A refused challenge is answered ONCE more, with the proof of work: a vendor that scored a real person low,\n // could not be reached, or a site that did not know the backend protects the form must not lock anyone out.\n if (!isSubmissionChallengeRefusal(error)) throw error;\n await send(true);\n }\n setSubmit({ status: \"success\" });\n setValues(EMPTY_VALUES);\n onSubmitted?.();\n } catch (error) {\n // The backend-only `emailUserMatch` triage signal is never returned here, so a\n // failure exposes no account-existence information — just a generic retry.\n setSubmit({\n status: \"error\",\n message: isSubmissionChallengeRefusal(error) ? labels.challengeRefusedMessage : labels.errorMessage,\n });\n }\n },\n [client, values, onSubmitted, source, labels.errorMessage, labels.challengeRefusedMessage],\n );\n\n if (submit.status === \"success\") {\n return (\n <div role=\"status\" aria-live=\"polite\">\n {labels.successMessage}\n </div>\n );\n }\n\n const isSubmitting = submit.status === \"submitting\";\n\n return (\n <form onSubmit={handleSubmit} noValidate>\n <label>\n {labels.emailLabel}\n <input\n type=\"email\"\n name=\"email\"\n required\n value={values.email}\n onChange={(e) => setField(\"email\", e.target.value)}\n aria-invalid={emailInvalid ? true : undefined}\n />\n {emailInvalid ? <span role=\"alert\">{labels.invalidEmailMessage}</span> : null}\n </label>\n\n <label>\n {labels.firstNameLabel}\n <input\n type=\"text\"\n name=\"firstName\"\n maxLength={INBOUND_CONTACT_NAME_MAX_LENGTH}\n value={values.firstName ?? \"\"}\n onChange={(e) => setField(\"firstName\", e.target.value)}\n />\n </label>\n\n <label>\n {labels.lastNameLabel}\n <input\n type=\"text\"\n name=\"lastName\"\n maxLength={INBOUND_CONTACT_NAME_MAX_LENGTH}\n value={values.lastName ?? \"\"}\n onChange={(e) => setField(\"lastName\", e.target.value)}\n />\n </label>\n\n <label>\n {labels.phoneLabel}\n <input\n type=\"tel\"\n name=\"phone\"\n maxLength={INBOUND_CONTACT_PHONE_MAX_LENGTH}\n value={values.phone ?? \"\"}\n onChange={(e) => setField(\"phone\", e.target.value)}\n />\n </label>\n\n <label>\n <input type=\"checkbox\" name=\"marketingConsent\" checked={values.marketingConsent} onChange={(e) => setField(\"marketingConsent\", e.target.checked)} />\n {labels.marketingConsentLabel}\n </label>\n\n {submit.status === \"error\" ? <div role=\"alert\">{submit.message}</div> : null}\n\n <button type=\"submit\" disabled={isSubmitting}>\n {isSubmitting ? labels.submittingLabel : labels.submitLabel}\n </button>\n </form>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"InboundContactForm.js","sourceRoot":"","sources":["../../../../../src/core/anonymous-session/InboundContactForm.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * InboundContactForm — the marketing site's real lead-capture form (React island).\n *\n * Self-contained + bundle-safe: uses only `react`, the website-local input schema, and\n * a `WebsiteAnonymousSessionClient` instance (constructed by the section/bridge wiring\n * from the site's API-origin config). On submit it validates client-side, then POSTs\n * `/inbound-contacts` through the anonymous-session client — the FIRST submission mints\n * the anonymous session (init header) and the backend stamps the lead's `anonymousUserId`\n * so it can later transpose to the visitor's account on signup/login.\n *\n * Labels are injected rather than read from the website runtime context, so the component stays framework-light and\n * unit-testable; the section wrapper passes the strings it resolved through `useWebsiteLabel`. EVERY label is required\n * and the form carries no words of its own (#1986): a fallback would be English in every locale and would hide the\n * missing label, while a required prop makes an omission a type error in the section, and a key the pack does not\n * carry reaches the page as the key itself — which is what `useWebsiteLabel` returns, so somebody authors it.\n *\n * Submission challenge (#1735): when the application protects the form with a captcha, each submission carries an\n * answer — the consented vendor's token when its browser part runs on the page, otherwise the engine's proof of work,\n * which needs no consent (see `website-submission-challenge.ts`). Nothing for the visitor to click or read: both are\n * invisible, so the form stays operable by keyboard and screen reader exactly as before. A vendor token the backend\n * refuses is retried once with the proof of work; a refusal that survives that shows `challengeRefusedMessage`.\n */\n\nimport React, { useCallback, useState } from \"react\";\nimport { SubmissionChallengeAction, WildoHeaderKeys } from \"@wildo-ai/saas-models/public-runtime\";\nimport {\n INBOUND_CONTACT_NAME_MAX_LENGTH,\n INBOUND_CONTACT_PHONE_MAX_LENGTH,\n InboundContactFormSchema,\n buildInboundContactCreateBody,\n type InboundContactFormValues,\n} from \"./inbound-contact-form.schema\";\nimport type { WebsiteAnonymousSessionClient } from \"./website-anonymous-session-client\";\nimport {\n isSubmissionChallengeRefusal,\n obtainSubmissionChallengeAnswer,\n useWebsiteSubmissionChallengeSource,\n type WebsiteSubmissionChallengeSource,\n} from \"./website-submission-challenge\";\n\n/**\n * Every word the form shows. All required (#1986): the section resolves each through `useWebsiteLabel` and lists its\n * key in `expectedLabelKeys`, so a missing label is a type error in the section or a visible key on the page, never a\n * silent English substitute.\n */\nexport interface InboundContactFormLabels {\n emailLabel: string;\n firstNameLabel: string;\n lastNameLabel: string;\n phoneLabel: string;\n /** OPTIONAL marketing opt-in checkbox. */\n marketingConsentLabel: string;\n submitLabel: string;\n submittingLabel: string;\n successMessage: string;\n errorMessage: string;\n /** The anti-spam check did not pass, even with the proof of work (#1735). */\n challengeRefusedMessage: string;\n /** Shown beside the email field when what was typed is not an email address (#1986). */\n invalidEmailMessage: string;\n}\n\ntype SubmitState = { status: \"idle\" } | { status: \"submitting\" } | { status: \"success\" } | { status: \"error\"; message: string };\n\nexport interface InboundContactFormProps {\n /** The anonymous-session client (constructed by the section wiring from site config). */\n client: WebsiteAnonymousSessionClient;\n /** The section's i18n-resolved labels — every one of them (#1986). */\n labels: InboundContactFormLabels;\n /** Optional callback fired after a successful submission (e.g. analytics). */\n onSubmitted?: () => void;\n /** The page's captcha, for a form rendered outside a page layout (tests). Defaults to the page's own. */\n challengeSource?: WebsiteSubmissionChallengeSource;\n}\n\nconst EMPTY_VALUES: InboundContactFormValues = {\n email: \"\",\n firstName: \"\",\n lastName: \"\",\n phone: \"\",\n marketingConsent: false,\n};\n\nexport function InboundContactForm({ client, labels, onSubmitted, challengeSource }: InboundContactFormProps): React.ReactElement {\n const pageChallengeSource = useWebsiteSubmissionChallengeSource();\n const source = challengeSource ?? pageChallengeSource;\n const [values, setValues] = useState<InboundContactFormValues>(EMPTY_VALUES);\n const [emailInvalid, setEmailInvalid] = useState(false);\n const [submit, setSubmit] = useState<SubmitState>({ status: \"idle\" });\n\n const setField = useCallback(<K extends keyof InboundContactFormValues>(key: K, value: InboundContactFormValues[K]) => {\n setValues((prev) => ({ ...prev, [key]: value }));\n }, []);\n\n const handleSubmit = useCallback(\n async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n setEmailInvalid(false);\n\n const parsed = InboundContactFormSchema.safeParse(values);\n if (!parsed.success) {\n // The schema's own issue messages are never shown: they are English and nobody authors them (#1986). The email\n // is the one field a visitor can get wrong, and it has its label. The other fields' only rule is a length the\n // inputs already enforce with `maxLength`, so reaching a failure there means the values did not come from\n // typing, and the form answers with its generic error rather than inventing a sentence.\n const emailFailed = parsed.error.issues.some((issue) => issue.path[0] === \"email\");\n setEmailInvalid(emailFailed);\n if (!emailFailed) setSubmit({ status: \"error\", message: labels.errorMessage });\n return;\n }\n\n setSubmit({ status: \"submitting\" });\n const body = buildInboundContactCreateBody(parsed.data);\n const send = async (proofOfWorkOnly: boolean): Promise<void> => {\n const answer = await obtainSubmissionChallengeAnswer({ source, action: SubmissionChallengeAction.INBOUND_CONTACT, client, proofOfWorkOnly });\n await client.postAnonymous(\"/inbound-contacts\", body, answer === undefined ? undefined : { [WildoHeaderKeys.SUBMISSION_CHALLENGE_RESPONSE]: answer });\n };\n try {\n try {\n await send(false);\n } catch (error) {\n // A refused challenge is answered ONCE more, with the proof of work: a vendor that scored a real person low,\n // could not be reached, or a site that did not know the backend protects the form must not lock anyone out.\n if (!isSubmissionChallengeRefusal(error)) throw error;\n await send(true);\n }\n setSubmit({ status: \"success\" });\n setValues(EMPTY_VALUES);\n onSubmitted?.();\n } catch (error) {\n // The backend-only `emailUserMatch` triage signal is never returned here, so a\n // failure exposes no account-existence information — just a generic retry.\n setSubmit({\n status: \"error\",\n message: isSubmissionChallengeRefusal(error) ? labels.challengeRefusedMessage : labels.errorMessage,\n });\n }\n },\n [client, values, onSubmitted, source, labels.errorMessage, labels.challengeRefusedMessage],\n );\n\n if (submit.status === \"success\") {\n return (\n <div role=\"status\" aria-live=\"polite\">\n {labels.successMessage}\n </div>\n );\n }\n\n const isSubmitting = submit.status === \"submitting\";\n\n return (\n <form onSubmit={handleSubmit} noValidate>\n <label>\n {labels.emailLabel}\n <input\n type=\"email\"\n name=\"email\"\n required\n value={values.email}\n onChange={(e) => setField(\"email\", e.target.value)}\n aria-invalid={emailInvalid ? true : undefined}\n />\n {emailInvalid ? <span role=\"alert\">{labels.invalidEmailMessage}</span> : null}\n </label>\n\n <label>\n {labels.firstNameLabel}\n <input\n type=\"text\"\n name=\"firstName\"\n maxLength={INBOUND_CONTACT_NAME_MAX_LENGTH}\n value={values.firstName ?? \"\"}\n onChange={(e) => setField(\"firstName\", e.target.value)}\n />\n </label>\n\n <label>\n {labels.lastNameLabel}\n <input\n type=\"text\"\n name=\"lastName\"\n maxLength={INBOUND_CONTACT_NAME_MAX_LENGTH}\n value={values.lastName ?? \"\"}\n onChange={(e) => setField(\"lastName\", e.target.value)}\n />\n </label>\n\n <label>\n {labels.phoneLabel}\n <input\n type=\"tel\"\n name=\"phone\"\n maxLength={INBOUND_CONTACT_PHONE_MAX_LENGTH}\n value={values.phone ?? \"\"}\n onChange={(e) => setField(\"phone\", e.target.value)}\n />\n </label>\n\n <label>\n <input type=\"checkbox\" name=\"marketingConsent\" checked={values.marketingConsent} onChange={(e) => setField(\"marketingConsent\", e.target.checked)} />\n {labels.marketingConsentLabel}\n </label>\n\n {submit.status === \"error\" ? <div role=\"alert\">{submit.message}</div> : null}\n\n <button type=\"submit\" disabled={isSubmitting}>\n {isSubmitting ? labels.submittingLabel : labels.submitLabel}\n </button>\n </form>\n );\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inbound-contact-form.schema.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"inbound-contact-form.schema.d.ts","sourceRoot":"","sources":["../../../../../src/core/anonymous-session/inbound-contact-form.schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB;;;;GAIG;AACH,eAAO,MAAM,+BAA+B,MAAM,CAAC;AACnD,eAAO,MAAM,gCAAgC,KAAK,CAAC;AAEnD;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB;;;;;;iBAcnC,CAAC;AAEH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAEhF;;;;GAIG;AACH,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,wBAAwB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAcvG"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inbound-contact-form.schema.js","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"inbound-contact-form.schema.js","sourceRoot":"","sources":["../../../../../src/core/anonymous-session/inbound-contact-form.schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAEtE;;;;GAIG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG,GAAG,CAAC;AACnD,MAAM,CAAC,MAAM,gCAAgC,GAAG,EAAE,CAAC;AAEnD;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/C,mFAAmF;IACnF,+EAA+E;IAC/E,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE;IAChB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC,QAAQ,EAAE;IAC5E,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC,QAAQ,EAAE;IAC3E,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC,QAAQ,EAAE;IACzE;;;;;OAKG;IACH,gBAAgB,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;CAC7C,CAAC,CAAC;AAIH;;;;GAIG;AACH,MAAM,UAAU,6BAA6B,CAAC,MAAgC;IAC5E,OAAO;QACL,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE;QACxC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5D,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzD,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAChD,OAAO,EAAE;YACP,6FAA6F;YAC7F,0FAA0F;YAC1F,4FAA4F;YAC5F,iBAAiB,EAAE,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE;YAC5E,gBAAgB,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SAC3C;KACF,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Inbound-contact form input schema (website-local, bundle-safe).\n *\n * Deliberately a SEPARATE plain-Zod schema, NOT `InboundContactSchema` from\n * `@wildo-ai/saas-models`: that schema is decorator-bound (`.isPrimaryKey()`,\n * `.excludeFromCreate()`, `.isBackendOnly()`, …), so importing it would boot the\n * decorator framework into the browser bundle — forbidden by bundle discipline. The\n * website only needs to validate the handful of client-authored CREATE fields; the\n * backend is the authority on the full contract (it re-validates + injects the\n * server-set fields: `_id`, `anonymousUserId`, `userId`, `emailUserMatch`, timestamps).\n *\n * Mirrors the non-excluded, non-backend-only CREATE inputs of `InboundContact`\n * (`email` required; `firstName` / `lastName` / `phone` optional), plus ONE privacy\n * signal: an OPTIONAL marketing opt-in (mapped to the `privacy.consentedPurposes` set).\n * There is no second, required agreement field: a governed notice, contract or\n * consent interaction must use the separate exact-release receipt contract, because this\n * DTO cannot manufacture legal evidence from a checkbox and a client timestamp.\n * `relatedUserId` is intentionally absent — it is `.excludeFromCreate()` on the\n * resource (sales/admin-only, never client-authored).\n */\n\nimport { z } from \"zod\";\n\nimport { ConsentPurpose } from \"@wildo-ai/saas-models/public-runtime\";\n\n/**\n * Length ceilings of the optional text fields. Exported so the form puts the SAME numbers on its inputs as\n * `maxLength`: the browser then stops a visitor at the ceiling, and a length failure is not something the form ever\n * has to word (#1986).\n */\nexport const INBOUND_CONTACT_NAME_MAX_LENGTH = 200;\nexport const INBOUND_CONTACT_PHONE_MAX_LENGTH = 64;\n\n/**\n * The wire payload POSTed to `/api/v1/inbound-contacts`. `privacy` matches the\n * resource's `InboundContactPrivacySchema` shape (a nested consent object) so the\n * backend accepts it verbatim.\n *\n * No issue carries a message (#1986). A visitor reads the application's own words, which the form takes from its\n * section's label pack (`InboundContactFormLabels.invalidEmailMessage`); a message written here would be English in\n * every locale, and nobody authoring the pack could find it.\n */\nexport const InboundContactFormSchema = z.object({\n // Zod 4: top-level `z.email()` (not the deprecated `z.string().email()`), matching\n // the resource schema. An empty string fails it, so it also covers \"required\".\n email: z.email(),\n firstName: z.string().trim().max(INBOUND_CONTACT_NAME_MAX_LENGTH).optional(),\n lastName: z.string().trim().max(INBOUND_CONTACT_NAME_MAX_LENGTH).optional(),\n phone: z.string().trim().max(INBOUND_CONTACT_PHONE_MAX_LENGTH).optional(),\n /**\n * OPTIONAL marketing opt-in — maps into the resource's `privacy.consentedPurposes` set.\n * It is the only privacy field on this form, and it gates nothing: sending the form is the\n * essential act and never depends on optional consent, so an unchecked box still submits,\n * granting nothing.\n */\n marketingConsent: z.boolean().default(false),\n});\n\nexport type InboundContactFormValues = z.infer<typeof InboundContactFormSchema>;\n\n/**\n * Build the exact JSON body the backend CREATE expects from validated form values.\n * Keeps the `privacy` nesting + the consent purpose mapping in ONE place so the form\n * component stays declarative.\n */\nexport function buildInboundContactCreateBody(values: InboundContactFormValues): Record<string, unknown> {\n return {\n email: values.email.trim().toLowerCase(),\n ...(values.firstName ? { firstName: values.firstName } : {}),\n ...(values.lastName ? { lastName: values.lastName } : {}),\n ...(values.phone ? { phone: values.phone } : {}),\n privacy: {\n // The visitor SAW the opt-in and submitted, so this is a decision either way (#534): the set\n // they granted — MARKETING or nothing — plus a CLAIM that they decided now. The backend's\n // CREATE prefix replaces the instant with its own clock; a browser clock is never recorded.\n consentedPurposes: values.marketingConsent ? [ConsentPurpose.MARKETING] : [],\n consentDecidedAt: new Date().toISOString(),\n },\n };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"website-anonymous-session-client.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"website-anonymous-session-client.d.ts","sourceRoot":"","sources":["../../../../../src/core/anonymous-session/website-anonymous-session-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAeH,MAAM,WAAW,oCAAoC;IACnD,mFAAmF;IACnF,SAAS,EAAE,MAAM,CAAC;IAClB,sFAAsF;IACtF,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED,iEAAiE;AACjE,qBAAa,4BAA6B,SAAQ,KAAK;aAEnC,MAAM,EAAE,MAAM;aACd,QAAQ,EAAE,MAAM;gBADhB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM;IAMlC;;;OAGG;IACH,IAAI,SAAS,IAAI,MAAM,GAAG,SAAS,CAOlC;CACF;AAED,qBAAa,6BAA6B;IACxC,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAS;gBAEjC,OAAO,EAAE,oCAAoC;IAMzD,8FAA8F;IAC9F,kBAAkB,IAAI,MAAM,GAAG,IAAI;IAInC;;;;;;;;;;;;OAYG;IACH,yBAAyB,IAAI,MAAM;IAMnC,6EAA6E;IAC7E,eAAe,IAAI,OAAO;IAI1B;;;;;;OAMG;IACG,aAAa,CAAC,SAAS,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC;IAI1I;;;OAGG;IACG,SAAS,CAAC,SAAS,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;IAYtE;;;;;;;;;OASG;IACG,gBAAgB,CAAC,SAAS,GAAG,OAAO,EACxC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,OAAO;IACb,uHAAuH;IACvH,YAAY,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAC9C,OAAO,CAAC,SAAS,CAAC;IAoBrB,OAAO,CAAC,oBAAoB;IAQ5B,OAAO,CAAC,YAAY;IAcpB,OAAO,CAAC,mBAAmB;IAS3B,OAAO,CAAC,kBAAkB;IAY1B,OAAO,CAAC,OAAO;CAYhB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"website-anonymous-session-client.js","sourceRoot":"","sources":["../../../../src/core/anonymous-session/website-anonymous-session-client.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * WebsiteAnonymousSessionClient\n *\n * A self-contained, browser-only anonymous-session HTTP client for the marketing\n * website. It MIRRORS the SaaS app's `AnonymousSessionContext` protocol WITHOUT\n * importing `@wildo-ai/saas-frontend-lib` — the website ships into a browser bundle\n * that must not pull the decorator framework / heavy frontend graph (bundle\n * discipline). It uses ONLY browser globals (`fetch` / `crypto` / `localStorage`) and\n * the bundle-safe `@wildo-ai/saas-models/public-runtime` atoms. No axios (axios is the\n * SSR tier), no React — a React island context can wrap this class.\n *\n * Protocol (W7.3a):\n * 1. The first anonymous request sends `x-anonymous-session-init: <clientUuid>` — the\n * backend mints an `AnonymousUser` (uuidRef = clientUuid) and returns a signed JWT\n * in the `x-anonymous-session-token` RESPONSE header.\n * 2. Subsequent requests send `x-anonymous-session: <jwt>`.\n * Every request also sends `X-Frontend-Service-Name` (required by the anon backend\n * to accept the request and resolve the frontend identity).\n *\n * CORS (critical): the website is a DIFFERENT origin from the backend API, so the\n * `x-anonymous-session-token` response header is readable by `fetch` ONLY if the\n * backend lists it in `Access-Control-Expose-Headers` (non-safelisted response headers\n * are hidden from cross-origin `fetch`). Without that, the captured token is `null`,\n * the session never advances past init, and every request re-mints a session.\n *\n * Cross-origin continuity: the anonymous session created here (uuidRef) is what the\n * SaaS app transposes on signup/login. It is carried across the apex↔app boundary by\n * the backend-set parent-domain `__wildo_anon_session` cookie — hence `credentials:\n * 'include'`, so that cookie rides same-site requests to the API.\n */\n\nimport { WildoHeaderKeys, MAIN_API_BASE_PATH } from '@wildo-ai/saas-models/public-runtime';\n\n/**\n * localStorage keys. The website is self-contained (mirror-don't-import), so it pins\n * the stable literal values rather than importing the decorator-adjacent shared\n * contract. Cross-origin continuity flows through the backend cookie, NOT this\n * localStorage, so these keys need not match the app's byte-for-byte.\n */\nconst STORAGE_KEYS = {\n anonymousUserId: 'anonymousUserId',\n anonymousSessionToken: 'anonymousSessionToken',\n} as const;\n\nexport interface WebsiteAnonymousSessionClientOptions {\n /** Backend API origin WITHOUT a trailing slash, e.g. `https://api.example.com`. */\n apiOrigin: string;\n /** `X-Frontend-Service-Name` value identifying this marketing site to the backend. */\n frontendServiceName: string;\n}\n\n/** Thrown when an anonymous request returns a non-2xx status. */\nexport class WebsiteAnonymousRequestError extends Error {\n constructor(\n public readonly status: number,\n public readonly bodyText: string,\n ) {\n super(`Anonymous request failed with status ${status}`);\n this.name = 'WebsiteAnonymousRequestError';\n }\n\n /**\n * The refusal's stable `error.code` — what a client branches on (the engine hoists an error's `context.code` there) —\n * or `undefined` when the body is not the engine's error envelope or carries no code.\n */\n get errorCode(): string | undefined {\n try {\n const code = (JSON.parse(this.bodyText) as { error?: { code?: unknown } }).error?.code;\n return typeof code === 'string' ? code : undefined;\n } catch {\n return undefined;\n }\n }\n}\n\nexport class WebsiteAnonymousSessionClient {\n private anonymousUserId: string | null = null;\n private sessionToken: string | null = null;\n private readonly apiBaseUrl: string;\n private readonly frontendServiceName: string;\n\n constructor(options: WebsiteAnonymousSessionClientOptions) {\n this.apiBaseUrl = `${options.apiOrigin.replace(/\\/+$/, '')}${MAIN_API_BASE_PATH}`;\n this.frontendServiceName = options.frontendServiceName;\n this.restoreFromStorage();\n }\n\n /** The anonymous session's relational identity (client uuidRef), or `null` if not started. */\n getAnonymousUserId(): string | null {\n return this.anonymousUserId;\n }\n\n /**\n * Return the anonymous session's relational identity (client uuidRef),\n * STARTING the session first if it has not begun yet. Unlike\n * `getAnonymousUserId()` (which may return `null`), this always yields\n * a stable uuidRef.\n *\n * Used by NESTED anonymous routes that must embed the id in the URL\n * BEFORE the first request — e.g. draft-notes POSTs to\n * `/user-anonymous-users/<uuidRef>/draft-notes`. The id generated here\n * is the SAME one the next `postAnonymous(...)` sends as\n * `x-anonymous-session-init`, so the URL scope and the minted session\n * refer to one anonymous user (no split identity).\n */\n getOrStartAnonymousUserId(): string {\n this.ensureSessionStarted();\n // ensureSessionStarted() guarantees anonymousUserId is non-null.\n return this.anonymousUserId as string;\n }\n\n /** `true` once an init request has succeeded and a session token is held. */\n isSessionActive(): boolean {\n return this.anonymousUserId !== null && this.sessionToken !== null;\n }\n\n /**\n * POST `body` to an anonymous endpoint. `path` is relative to `/api/v1`\n * (e.g. `/inbound-contacts`, or a nested `/anonymous-users/<uuidRef>/draft-notes`).\n * Lazily starts the session on the first call, attaches the anon-session +\n * service-name headers, captures any returned session token, and returns the parsed\n * JSON response. Throws `WebsiteAnonymousRequestError` on a non-2xx status.\n */\n async postAnonymous<TResponse = unknown>(path: string, body: unknown, extraHeaders?: Readonly<Record<string, string>>): Promise<TResponse> {\n return this.requestAnonymous<TResponse>('POST', path, body, extraHeaders);\n }\n\n /**\n * GET a PUBLIC endpoint — one that needs no session (a proof-of-work challenge, #1735). Starts no anonymous session\n * and sends no session header or cookie: asking for a challenge must not mint an identity for the visitor.\n */\n async getPublic<TResponse = unknown>(path: string): Promise<TResponse> {\n const response = await fetch(`${this.apiBaseUrl}${path}`, {\n method: 'GET',\n headers: { [WildoHeaderKeys.FRONTEND_SERVICE_NAME]: this.frontendServiceName },\n credentials: 'omit',\n });\n if (!response.ok) {\n throw new WebsiteAnonymousRequestError(response.status, await safeReadText(response));\n }\n return (await response.json()) as TResponse;\n }\n\n /**\n * Send `body` to an anonymous endpoint with an explicit METHOD.\n *\n * `postAnonymous` above is the POST case and delegates here. This exists because not every anonymous\n * door is a POST and the method is not guessable from the path: the visitor consent door declares\n * `resourceOperationLike: UPDATE_MANY`, which the engine maps to PUT, so a POST to it answers 404 —\n * and this client's one silent caller swallowed exactly that. A caller reads the door's method from\n * the same place it reads the path (`ANONYMOUS_CONSENT_RECORD_METHOD` beside\n * `ANONYMOUS_CONSENT_RECORD_PATH`) rather than inferring it from a method name.\n */\n async requestAnonymous<TResponse = unknown>(\n method: string,\n path: string,\n body: unknown,\n /** Request-specific headers, such as a public form's challenge answer (#1735). Never overrides the session headers. */\n extraHeaders?: Readonly<Record<string, string>>,\n ): Promise<TResponse> {\n this.ensureSessionStarted();\n\n const response = await fetch(`${this.apiBaseUrl}${path}`, {\n method,\n headers: { ...extraHeaders, ...this.buildHeaders() },\n body: JSON.stringify(body),\n // Carry the parent-domain `__wildo_anon_session` cookie to the API so the\n // session is continuous across the apex/app/api origins.\n credentials: 'include',\n });\n\n this.captureSessionToken(response);\n\n if (!response.ok) {\n throw new WebsiteAnonymousRequestError(response.status, await safeReadText(response));\n }\n return (await response.json()) as TResponse;\n }\n\n private ensureSessionStarted(): void {\n if (!this.anonymousUserId) {\n this.anonymousUserId = crypto.randomUUID();\n this.sessionToken = null;\n this.persist();\n }\n }\n\n private buildHeaders(): Record<string, string> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n [WildoHeaderKeys.FRONTEND_SERVICE_NAME]: this.frontendServiceName,\n };\n // Once we hold the JWT, restore the session; before that, the raw UUID inits it.\n if (this.sessionToken) {\n headers[WildoHeaderKeys.ANONYMOUS_SESSION] = this.sessionToken;\n } else if (this.anonymousUserId) {\n headers[WildoHeaderKeys.ANONYMOUS_SESSION_INIT] = this.anonymousUserId;\n }\n return headers;\n }\n\n private captureSessionToken(response: Response): void {\n // Requires the backend to CORS-expose this header (see the class-level CORS note).\n const token = response.headers.get(WildoHeaderKeys.ANONYMOUS_SESSION_TOKEN);\n if (token && token !== this.sessionToken) {\n this.sessionToken = token;\n this.persist();\n }\n }\n\n private restoreFromStorage(): void {\n try {\n const userId = localStorage.getItem(STORAGE_KEYS.anonymousUserId);\n const token = localStorage.getItem(STORAGE_KEYS.anonymousSessionToken);\n if (userId) this.anonymousUserId = userId;\n if (token) this.sessionToken = token;\n } catch {\n // localStorage unavailable (SSR build / privacy mode) — the session stays\n // in-memory for this page load only.\n }\n }\n\n private persist(): void {\n try {\n if (this.anonymousUserId) {\n localStorage.setItem(STORAGE_KEYS.anonymousUserId, this.anonymousUserId);\n }\n if (this.sessionToken) {\n localStorage.setItem(STORAGE_KEYS.anonymousSessionToken, this.sessionToken);\n }\n } catch {\n // Best-effort persistence; a failure only means the session won't survive reload.\n }\n }\n}\n\nasync function safeReadText(response: Response): Promise<string> {\n try {\n return await response.text();\n } catch {\n return '';\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"website-anonymous-session-client.js","sourceRoot":"","sources":["../../../../../src/core/anonymous-session/website-anonymous-session-client.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * WebsiteAnonymousSessionClient\n *\n * A self-contained, browser-only anonymous-session HTTP client for the marketing\n * website. It MIRRORS the SaaS app's `AnonymousSessionContext` protocol WITHOUT\n * importing `@wildo-ai/saas-frontend-lib` — the website ships into a browser bundle\n * that must not pull the decorator framework / heavy frontend graph (bundle\n * discipline). It uses ONLY browser globals (`fetch` / `crypto` / `localStorage`) and\n * the bundle-safe `@wildo-ai/saas-models/public-runtime` atoms. No axios (axios is the\n * SSR tier), no React — a React island context can wrap this class.\n *\n * Protocol (W7.3a):\n * 1. The first anonymous request sends `x-anonymous-session-init: <clientUuid>` — the\n * backend mints an `AnonymousUser` (uuidRef = clientUuid) and returns a signed JWT\n * in the `x-anonymous-session-token` RESPONSE header.\n * 2. Subsequent requests send `x-anonymous-session: <jwt>`.\n * Every request also sends `X-Frontend-Service-Name` (required by the anon backend\n * to accept the request and resolve the frontend identity).\n *\n * CORS (critical): the website is a DIFFERENT origin from the backend API, so the\n * `x-anonymous-session-token` response header is readable by `fetch` ONLY if the\n * backend lists it in `Access-Control-Expose-Headers` (non-safelisted response headers\n * are hidden from cross-origin `fetch`). Without that, the captured token is `null`,\n * the session never advances past init, and every request re-mints a session.\n *\n * Cross-origin continuity: the anonymous session created here (uuidRef) is what the\n * SaaS app transposes on signup/login. It is carried across the apex↔app boundary by\n * the backend-set parent-domain `__wildo_anon_session` cookie — hence `credentials:\n * 'include'`, so that cookie rides same-site requests to the API.\n */\n\nimport { WildoHeaderKeys, MAIN_API_BASE_PATH } from '@wildo-ai/saas-models/public-runtime';\n\n/**\n * localStorage keys. The website is self-contained (mirror-don't-import), so it pins\n * the stable literal values rather than importing the decorator-adjacent shared\n * contract. Cross-origin continuity flows through the backend cookie, NOT this\n * localStorage, so these keys need not match the app's byte-for-byte.\n */\nconst STORAGE_KEYS = {\n anonymousUserId: 'anonymousUserId',\n anonymousSessionToken: 'anonymousSessionToken',\n} as const;\n\nexport interface WebsiteAnonymousSessionClientOptions {\n /** Backend API origin WITHOUT a trailing slash, e.g. `https://api.example.com`. */\n apiOrigin: string;\n /** `X-Frontend-Service-Name` value identifying this marketing site to the backend. */\n frontendServiceName: string;\n}\n\n/** Thrown when an anonymous request returns a non-2xx status. */\nexport class WebsiteAnonymousRequestError extends Error {\n constructor(\n public readonly status: number,\n public readonly bodyText: string,\n ) {\n super(`Anonymous request failed with status ${status}`);\n this.name = 'WebsiteAnonymousRequestError';\n }\n\n /**\n * The refusal's stable `error.code` — what a client branches on (the engine hoists an error's `context.code` there) —\n * or `undefined` when the body is not the engine's error envelope or carries no code.\n */\n get errorCode(): string | undefined {\n try {\n const code = (JSON.parse(this.bodyText) as { error?: { code?: unknown } }).error?.code;\n return typeof code === 'string' ? code : undefined;\n } catch {\n return undefined;\n }\n }\n}\n\nexport class WebsiteAnonymousSessionClient {\n private anonymousUserId: string | null = null;\n private sessionToken: string | null = null;\n private readonly apiBaseUrl: string;\n private readonly frontendServiceName: string;\n\n constructor(options: WebsiteAnonymousSessionClientOptions) {\n this.apiBaseUrl = `${options.apiOrigin.replace(/\\/+$/, '')}${MAIN_API_BASE_PATH}`;\n this.frontendServiceName = options.frontendServiceName;\n this.restoreFromStorage();\n }\n\n /** The anonymous session's relational identity (client uuidRef), or `null` if not started. */\n getAnonymousUserId(): string | null {\n return this.anonymousUserId;\n }\n\n /**\n * Return the anonymous session's relational identity (client uuidRef),\n * STARTING the session first if it has not begun yet. Unlike\n * `getAnonymousUserId()` (which may return `null`), this always yields\n * a stable uuidRef.\n *\n * Used by NESTED anonymous routes that must embed the id in the URL\n * BEFORE the first request — e.g. draft-notes POSTs to\n * `/user-anonymous-users/<uuidRef>/draft-notes`. The id generated here\n * is the SAME one the next `postAnonymous(...)` sends as\n * `x-anonymous-session-init`, so the URL scope and the minted session\n * refer to one anonymous user (no split identity).\n */\n getOrStartAnonymousUserId(): string {\n this.ensureSessionStarted();\n // ensureSessionStarted() guarantees anonymousUserId is non-null.\n return this.anonymousUserId as string;\n }\n\n /** `true` once an init request has succeeded and a session token is held. */\n isSessionActive(): boolean {\n return this.anonymousUserId !== null && this.sessionToken !== null;\n }\n\n /**\n * POST `body` to an anonymous endpoint. `path` is relative to `/api/v1`\n * (e.g. `/inbound-contacts`, or a nested `/anonymous-users/<uuidRef>/draft-notes`).\n * Lazily starts the session on the first call, attaches the anon-session +\n * service-name headers, captures any returned session token, and returns the parsed\n * JSON response. Throws `WebsiteAnonymousRequestError` on a non-2xx status.\n */\n async postAnonymous<TResponse = unknown>(path: string, body: unknown, extraHeaders?: Readonly<Record<string, string>>): Promise<TResponse> {\n return this.requestAnonymous<TResponse>('POST', path, body, extraHeaders);\n }\n\n /**\n * GET a PUBLIC endpoint — one that needs no session (a proof-of-work challenge, #1735). Starts no anonymous session\n * and sends no session header or cookie: asking for a challenge must not mint an identity for the visitor.\n */\n async getPublic<TResponse = unknown>(path: string): Promise<TResponse> {\n const response = await fetch(`${this.apiBaseUrl}${path}`, {\n method: 'GET',\n headers: { [WildoHeaderKeys.FRONTEND_SERVICE_NAME]: this.frontendServiceName },\n credentials: 'omit',\n });\n if (!response.ok) {\n throw new WebsiteAnonymousRequestError(response.status, await safeReadText(response));\n }\n return (await response.json()) as TResponse;\n }\n\n /**\n * Send `body` to an anonymous endpoint with an explicit METHOD.\n *\n * `postAnonymous` above is the POST case and delegates here. This exists because not every anonymous\n * door is a POST and the method is not guessable from the path: the visitor consent door declares\n * `resourceOperationLike: UPDATE_MANY`, which the engine maps to PUT, so a POST to it answers 404 —\n * and this client's one silent caller swallowed exactly that. A caller reads the door's method from\n * the same place it reads the path (`ANONYMOUS_CONSENT_RECORD_METHOD` beside\n * `ANONYMOUS_CONSENT_RECORD_PATH`) rather than inferring it from a method name.\n */\n async requestAnonymous<TResponse = unknown>(\n method: string,\n path: string,\n body: unknown,\n /** Request-specific headers, such as a public form's challenge answer (#1735). Never overrides the session headers. */\n extraHeaders?: Readonly<Record<string, string>>,\n ): Promise<TResponse> {\n this.ensureSessionStarted();\n\n const response = await fetch(`${this.apiBaseUrl}${path}`, {\n method,\n headers: { ...extraHeaders, ...this.buildHeaders() },\n body: JSON.stringify(body),\n // Carry the parent-domain `__wildo_anon_session` cookie to the API so the\n // session is continuous across the apex/app/api origins.\n credentials: 'include',\n });\n\n this.captureSessionToken(response);\n\n if (!response.ok) {\n throw new WebsiteAnonymousRequestError(response.status, await safeReadText(response));\n }\n return (await response.json()) as TResponse;\n }\n\n private ensureSessionStarted(): void {\n if (!this.anonymousUserId) {\n this.anonymousUserId = crypto.randomUUID();\n this.sessionToken = null;\n this.persist();\n }\n }\n\n private buildHeaders(): Record<string, string> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n [WildoHeaderKeys.FRONTEND_SERVICE_NAME]: this.frontendServiceName,\n };\n // Once we hold the JWT, restore the session; before that, the raw UUID inits it.\n if (this.sessionToken) {\n headers[WildoHeaderKeys.ANONYMOUS_SESSION] = this.sessionToken;\n } else if (this.anonymousUserId) {\n headers[WildoHeaderKeys.ANONYMOUS_SESSION_INIT] = this.anonymousUserId;\n }\n return headers;\n }\n\n private captureSessionToken(response: Response): void {\n // Requires the backend to CORS-expose this header (see the class-level CORS note).\n const token = response.headers.get(WildoHeaderKeys.ANONYMOUS_SESSION_TOKEN);\n if (token && token !== this.sessionToken) {\n this.sessionToken = token;\n this.persist();\n }\n }\n\n private restoreFromStorage(): void {\n try {\n const userId = localStorage.getItem(STORAGE_KEYS.anonymousUserId);\n const token = localStorage.getItem(STORAGE_KEYS.anonymousSessionToken);\n if (userId) this.anonymousUserId = userId;\n if (token) this.sessionToken = token;\n } catch {\n // localStorage unavailable (SSR build / privacy mode) — the session stays\n // in-memory for this page load only.\n }\n }\n\n private persist(): void {\n try {\n if (this.anonymousUserId) {\n localStorage.setItem(STORAGE_KEYS.anonymousUserId, this.anonymousUserId);\n }\n if (this.sessionToken) {\n localStorage.setItem(STORAGE_KEYS.anonymousSessionToken, this.sessionToken);\n }\n } catch {\n // Best-effort persistence; a failure only means the session won't survive reload.\n }\n }\n}\n\nasync function safeReadText(response: Response): Promise<string> {\n try {\n return await response.text();\n } catch {\n return '';\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"website-submission-challenge.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"website-submission-challenge.d.ts","sourceRoot":"","sources":["../../../../../src/core/anonymous-session/website-submission-challenge.ts"],"names":[],"mappings":"AAgBA,OAAO,EAQL,KAAK,mBAAmB,EAEzB,MAAM,sCAAsC,CAAC;AAI9C,OAAO,EAAgC,KAAK,6BAA6B,EAAE,MAAM,oCAAoC,CAAC;AAEtH,mHAAmH;AACnH,kEAAkE;AAClE,MAAM,WAAW,gCAAgC;IAC/C,wFAAwF;IACxF,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC,uGAAuG;IACvG,QAAQ,CAAC,cAAc,CAAC,EAAE;QAAE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAA;KAAE,CAAC;CAChG;AAED,mHAAmH;AACnH,wBAAgB,mCAAmC,IAAI,gCAAgC,CAatF;AAED;;;;GAIG;AACH,wBAAsB,+BAA+B,CAAC,MAAM,EAAE;IAC5D,QAAQ,CAAC,MAAM,EAAE,gCAAgC,CAAC;IAClD;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,6BAA6B,EAAE,WAAW,CAAC,CAAC;IAClE,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IACnC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAiB9B;AAED,+HAA+H;AAC/H,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAIpE;AACD,+DAA+D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"website-submission-challenge.js","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"website-submission-challenge.js","sourceRoot":"","sources":["../../../../../src/core/anonymous-session/website-submission-challenge.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The answer a public website form sends with its submission when the application protects it with a captcha (#1735), to\n * any operation that declares a submission challenge (#1985).\n *\n * Two answers, one header (`x-submission-challenge-response`), chosen in the visitor's browser:\n *\n * - the consented captcha vendor's TOKEN, when its browser part is running on the page (it starts only once the\n * visitor grants the purpose the vendor rests on — Google reCAPTCHA rests on ANALYTICS);\n * - otherwise the engine's PROOF OF WORK: a challenge fetched from the backend and solved here. Consent-exempt —\n * nothing is stored in the browser and nothing reaches a third party — so a visitor who declined still submits.\n *\n * The backend requires one of the two whenever it declares a captcha provider, and refuses a submission with neither.\n * A site that declares none sends nothing. A refused vendor token is retried ONCE with the proof of work (a low score or\n * an unreachable vendor must not lock a real person out); see {@link isSubmissionChallengeRefusal}.\n */\nimport { useContext, useMemo } from 'react';\nimport {\n BUILTIN_PROVIDER_CAPABILITY,\n SUBMISSION_CHALLENGE_PROOF_OF_WORK_PATH,\n SubmissionChallengeRefusalCode,\n SubmissionChallengeResponseKind,\n encodeSubmissionChallengeResponse,\n isFrontendCaptchaSink,\n solveProofOfWorkChallenge,\n type FrontendCaptchaSink,\n type ProofOfWorkChallenge,\n} from '@wildo-ai/saas-models/public-runtime';\n\nimport { WebsiteRuntimeContext } from '../contexts/WebsiteRuntimeContext';\nimport { useWebsiteProviderSdkActivations } from '../external-providers/website-provider-sdk-activations';\nimport { WebsiteAnonymousRequestError, type WebsiteAnonymousSessionClient } from './website-anonymous-session-client';\n\n/** @wildo_source:part:start saas.website.submission-challenge facet:layer:frontend facet:audience:app-developer */\n/** What a form knows about the page's captcha when it submits. */\nexport interface WebsiteSubmissionChallengeSource {\n /** This website declares a captcha provider, so its public forms answer a challenge. */\n readonly captchaDeclared: boolean;\n /** The captcha vendor RUNNING on the page — present only once the visitor consented and it started. */\n readonly runningCaptcha?: { readonly providerRef: string; readonly sink: FrontendCaptchaSink };\n}\n\n/** The challenge source of the current page: its declared captcha provider and, if started, its running client. */\nexport function useWebsiteSubmissionChallengeSource(): WebsiteSubmissionChallengeSource {\n // Read without `useWebsiteRuntime`'s throw: a form rendered outside a page (a test, a storybook) simply has no captcha.\n const runtime = useContext(WebsiteRuntimeContext);\n const running = useWebsiteProviderSdkActivations();\n const captchaDeclared = runtime?.frontendProviderRegistry?.findProviderByProviderCapability(BUILTIN_PROVIDER_CAPABILITY.FRONTEND_CAPTCHA) !== undefined;\n return useMemo(() => {\n const captcha = running.find((entry) => entry.providerCapabilities.includes(BUILTIN_PROVIDER_CAPABILITY.FRONTEND_CAPTCHA)\n && isFrontendCaptchaSink(entry.activation.handle));\n return {\n captchaDeclared,\n ...(captcha === undefined ? {} : { runningCaptcha: { providerRef: captcha.providerRef, sink: captcha.activation.handle as FrontendCaptchaSink } }),\n };\n }, [captchaDeclared, running]);\n}\n\n/**\n * The encoded answer to send, or `undefined` when this site asks for none. `proofOfWorkOnly` skips the vendor (the\n * retry after a refused token). Throws only when the proof-of-work challenge cannot be fetched or solved; the caller\n * then shows its ordinary failure and does not submit unanswered (`InboundContactForm` shows its `errorMessage`).\n */\nexport async function obtainSubmissionChallengeAnswer(params: {\n readonly source: WebsiteSubmissionChallengeSource;\n /**\n * The action the operation the form posts to DECLARES in its `submissionChallenge` (#1985): the engine's\n * `SubmissionChallengeAction.INBOUND_CONTACT` for `/inbound-contacts`, your own action for your own operation. A token\n * minted for any other action is refused.\n */\n readonly action: string;\n readonly client: Pick<WebsiteAnonymousSessionClient, 'getPublic'>;\n readonly proofOfWorkOnly?: boolean;\n readonly signal?: AbortSignal;\n}): Promise<string | undefined> {\n const { source, action, client, proofOfWorkOnly = false, signal } = params;\n if (!proofOfWorkOnly && source.runningCaptcha !== undefined) {\n const token = await source.runningCaptcha.sink.execute(action);\n if (token !== undefined) {\n return encodeSubmissionChallengeResponse({\n kind: SubmissionChallengeResponseKind.PROVIDER_TOKEN,\n providerRef: source.runningCaptcha.providerRef,\n token,\n });\n }\n }\n if (!source.captchaDeclared && !proofOfWorkOnly) return undefined;\n const challenge = await client.getPublic<ProofOfWorkChallenge>(SUBMISSION_CHALLENGE_PROOF_OF_WORK_PATH);\n const answer = await solveProofOfWorkChallenge(challenge, { signal });\n if (answer === undefined) throw new Error('The proof-of-work challenge could not be solved.');\n return encodeSubmissionChallengeResponse(answer);\n}\n\n/** Whether a failed submission was refused at its challenge — the one refusal a form answers again, with the proof of work. */\nexport function isSubmissionChallengeRefusal(error: unknown): boolean {\n if (!(error instanceof WebsiteAnonymousRequestError)) return false;\n const code = error.errorCode;\n return code === SubmissionChallengeRefusalCode.REQUIRED || code === SubmissionChallengeRefusalCode.REFUSED;\n}\n/** @wildo_source:part:end saas.website.submission-challenge */\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebsiteConsentBanner.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"WebsiteConsentBanner.d.ts","sourceRoot":"","sources":["../../../../../src/core/consent/WebsiteConsentBanner.tsx"],"names":[],"mappings":"AAAA,OAAc,EAA8B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAyF1E,wBAAgB,oBAAoB,IAAI,SAAS,CAyHhD;AAED,sGAAsG;AACtG,wBAAgB,4BAA4B,IAAI,SAAS,CAQxD;AACD,yDAAyD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebsiteConsentBanner.js","sourceRoot":"","sources":["../../../../src/core/consent/WebsiteConsentBanner.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, { useEffect, useId, useState, type ReactNode } from 'react';\nimport type { ConsentPurpose } from '@wildo-ai/saas-models/public-runtime';\n\nimport { websiteDesignTokenCssVar } from '../../schemas/design-tokens/website-design-tokens.shared.schemas';\nimport { useWebsiteLabelByKey } from '../hooks/useWebsiteLabel';\nimport { WebsiteConsentStatus } from './WebsiteConsentContext';\nimport { useWebsiteConsent } from './useWebsiteConsent';\nimport {\n WebsiteConsentLabelSlot,\n websiteConsentLabelKey,\n websiteConsentPurposeDescriptionLabelKey,\n websiteConsentPurposeLabelKey,\n} from '../../schemas/label-keys/website-consent-label-keys.schemas';\n\n/**\n * @wildo_source:part:start saas.website.consent.banner facet:layer:core facet:family:website\n *\n * `WebsiteConsentBanner` — the framework's default visitor consent panel (#534).\n *\n * Rendered by `WebsitePageLayout` when the site runs a consent-based provider and the visitor has not\n * decided, or re-opened the panel. A site replaces it by passing `consentBannerComponent` to its bridge\n * island; a replacement reads `useWebsiteConsent()` exactly as this one does and must keep its three\n * properties:\n *\n * 1. **No box is pre-ticked.** A pre-ticked box is not a choice.\n * 2. **Declining is as easy as accepting**, and is RECORDED — so the panel does not return on every page.\n * 3. **Only purposes in use are offered** (`purposesInUse`), each named by its label.\n *\n * Styled only through the site's `--website-*` design tokens, like the skip link, so it needs no\n * stylesheet and cannot be shadowed by a utility class.\n */\nfunction useWebsiteConsentSlotLabel(slot: WebsiteConsentLabelSlot): string {\n return useWebsiteLabelByKey(websiteConsentLabelKey(slot));\n}\n\n/**\n * Every row has the SAME shape: the control, then the name on its line, then the explanation beneath\n * the NAME rather than beneath the control — a two-column grid, so the second line starts where the\n * first line's words do and the row stays scannable.\n *\n * That includes the row that cannot be changed, which carries a real checkbox, CHECKED and DISABLED,\n * rather than a text status. A person scanning the panel reads DOWN the column of controls, and a row\n * whose control is missing does not read as \"cannot be changed\" — it reads as a row that was\n * forgotten. A checked, disabled box says it in the vocabulary the rest of the list already uses, and\n * a screen reader announces \"checked, unavailable\" without any extra words.\n */\nconst ROW_STYLE: React.CSSProperties = {\n display: 'grid',\n gridTemplateColumns: 'auto 1fr',\n columnGap: '0.625rem',\n alignItems: 'center',\n padding: '0.5rem 0.75rem',\n borderBottom: `1px solid ${websiteDesignTokenCssVar('colors', 'border', '#ccc')}`,\n};\n\nconst ROW_DESCRIPTION_STYLE: React.CSSProperties = { margin: 0, gridColumn: 2, fontSize: '0.75rem', opacity: 0.8 };\n\n/** Slightly larger than a default box: this is the one control the whole panel exists to offer. */\nconst ROW_CONTROL_STYLE: React.CSSProperties = { width: '1.15rem', height: '1.15rem' };\n\n/**\n * One purpose: its NAME, what agreeing to it MEANS for this visitor, and the control.\n *\n * The explanation is the half that was missing. A tick box labelled \"Analytics\" is a category, not\n * something a visitor can weigh, and consent is only valid if it is informed — which is why every\n * specialized consent platform pairs the two and why the label key is derived from the vocabulary,\n * so a purpose can never arrive with a name and no explanation.\n */\nfunction PurposeChoice({ purpose, checked, onChange }: { purpose: ConsentPurpose; checked: boolean; onChange: (checked: boolean) => void }): ReactNode {\n const label = useWebsiteLabelByKey(websiteConsentPurposeLabelKey(purpose));\n const description = useWebsiteLabelByKey(websiteConsentPurposeDescriptionLabelKey(purpose));\n return (\n <label data-website-consent-purpose={purpose} style={{ ...ROW_STYLE, cursor: 'pointer' }}>\n <input type=\"checkbox\" checked={checked} style={ROW_CONTROL_STYLE} onChange={(event) => onChange(event.target.checked)} />\n <span style={{ fontWeight: 600 }}>{label}</span>\n <span style={ROW_DESCRIPTION_STYLE}>{description}</span>\n </label>\n );\n}\n\nconst BUTTON_STYLE: React.CSSProperties = {\n padding: '0.5rem 1rem',\n borderRadius: websiteDesignTokenCssVar('radii', 'md', '0.375rem'),\n border: `1px solid ${websiteDesignTokenCssVar('colors', 'border', '#ccc')}`,\n background: websiteDesignTokenCssVar('colors', 'background', '#fff'),\n color: websiteDesignTokenCssVar('colors', 'foreground', '#000'),\n cursor: 'pointer',\n};\n\nexport function WebsiteConsentBanner(): ReactNode {\n const consent = useWebsiteConsent();\n const headingId = useId();\n const [selected, setSelected] = useState<readonly ConsentPurpose[]>(consent.grantedPurposes);\n // Re-seed from the stored decision each time the panel is re-opened, so it shows what is in force.\n useEffect(() => setSelected(consent.grantedPurposes), [consent.grantedPurposes, consent.isSettingsOpen]);\n\n const title = useWebsiteConsentSlotLabel(WebsiteConsentLabelSlot.TITLE);\n const description = useWebsiteConsentSlotLabel(WebsiteConsentLabelSlot.DESCRIPTION);\n const acceptAll = useWebsiteConsentSlotLabel(WebsiteConsentLabelSlot.ACCEPT_ALL);\n const declineAll = useWebsiteConsentSlotLabel(WebsiteConsentLabelSlot.DECLINE_ALL);\n const save = useWebsiteConsentSlotLabel(WebsiteConsentLabelSlot.SAVE);\n const essentialTitle = useWebsiteConsentSlotLabel(WebsiteConsentLabelSlot.ESSENTIAL_TITLE);\n const essentialDescription = useWebsiteConsentSlotLabel(WebsiteConsentLabelSlot.ESSENTIAL_DESCRIPTION);\n\n const offered = consent.purposesInUse;\n // Purposes the panel does not offer keep their stored grant; offered ones are granted exactly when ticked.\n const choose = (chosen: readonly ConsentPurpose[]): void =>\n consent.decide([...consent.grantedPurposes.filter((purpose) => !offered.includes(purpose)), ...chosen]);\n\n return (\n <section\n role=\"region\"\n aria-labelledby={headingId}\n data-website-consent-banner={consent.status === WebsiteConsentStatus.UNDECIDED ? 'undecided' : 'settings'}\n style={{\n /*\n * BOTTOM-RIGHT CORNER, and the three properties together are what make that work on a phone\n * too. `left` and `right` both bound the box to the viewport with a gutter; `maxWidth` caps it\n * on a wide screen; `marginLeft: auto` pushes the remaining slack to the left so the box hugs\n * the right edge. On a narrow screen the gutters bind before the cap does and it simply spans\n * the width — a corner popup that overflowed a phone would be worse than a bar.\n *\n * Inline styles, not a media query: this ships into a static marketing page with no stylesheet\n * of its own, which is the same constraint that keeps the rest of this file inline.\n */\n position: 'fixed',\n left: '1rem',\n right: '1rem',\n bottom: '1rem',\n zIndex: 9000,\n maxWidth: '30rem',\n marginLeft: 'auto',\n /*\n * The panel never exceeds the viewport, and its ACTIONS are never what gets cut off: the rows\n * scroll inside `maxHeight`, the buttons sit outside that box. A panel that grows with its\n * content pushes accept and reject off the bottom of a short screen — and a refusal that\n * cannot be reached is a refusal that was not offered, whatever the markup says. `dvh` rather\n * than `vh` because a mobile browser's toolbars make `vh` taller than the visible page.\n */\n maxHeight: '85dvh',\n padding: '0.875rem 1rem',\n display: 'flex',\n flexDirection: 'column',\n gap: '0.625rem',\n background: websiteDesignTokenCssVar('colors', 'surface', '#fff'),\n color: websiteDesignTokenCssVar('colors', 'surfaceForeground', '#000'),\n border: `1px solid ${websiteDesignTokenCssVar('colors', 'border', '#ccc')}`,\n borderRadius: websiteDesignTokenCssVar('radii', 'lg', '0.5rem'),\n }}\n >\n <h2 id={headingId} style={{ margin: 0, fontSize: '1.125rem' }}>{title}</h2>\n <p style={{ margin: 0 }}>{description}</p>\n {/* `data-website-consent-rows` is the scroll box's own hook. A test anchoring on a style\n substring instead matched the ROW (whose grid template also contains \"auto\") and asserted\n nothing — the same vacuous pin the app tier already had to repair. */}\n <div\n data-website-consent-rows=\"\"\n style={{ minHeight: 0, flex: 1, overflowY: 'auto', border: `1px solid ${websiteDesignTokenCssVar('colors', 'border', '#ccc')}`, borderRadius: websiteDesignTokenCssVar('radii', 'md', '0.375rem') }}\n >\n {/*\n * The always-active row, first, carrying a CHECKED and DISABLED box. It is not a purpose the\n * visitor decides — which is exactly why it is stated: a panel listing only the optional\n * purposes leaves them unable to see what is happening on the site at all, which is the\n * question they opened it to answer.\n *\n * It carried a text status instead until 2026-09-20, on the argument that a disabled control\n * invites a click that cannot work. The reversal: a visitor scans DOWN the column of boxes,\n * and a gap there does not read as \"cannot be changed\", it reads as a row that was\n * forgotten. A disabled box says it in the vocabulary the rest of the list already uses.\n */}\n <div data-website-consent-essential=\"\" style={ROW_STYLE}>\n {/*\n * `aria-label`, because this row is a `div` rather than a `<label>` — nothing else names\n * the control, and a checkbox with no accessible name is announced as an unlabelled\n * checkbox. Its own note claimed AT would announce \"checked, unavailable\"; it announced\n * no words at all.\n *\n * `readOnly` is gone: it is inert on a checkbox per HTML, and leaving it invites a later\n * reader to delete `disabled` believing the lock survives.\n */}\n <input type=\"checkbox\" checked disabled aria-label={essentialTitle} style={ROW_CONTROL_STYLE} />\n <span style={{ fontWeight: 600 }}>{essentialTitle}</span>\n <span style={ROW_DESCRIPTION_STYLE}>{essentialDescription}</span>\n </div>\n {offered.map((purpose) => (\n <PurposeChoice\n key={purpose}\n purpose={purpose}\n checked={selected.includes(purpose)}\n onChange={(checked) => setSelected((current) => (checked ? [...current.filter((entry) => entry !== purpose), purpose] : current.filter((entry) => entry !== purpose)))}\n />\n ))}\n </div>\n <div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.5rem', justifyContent: 'flex-end' }}>\n <button type=\"button\" style={BUTTON_STYLE} onClick={() => choose([])}>{declineAll}</button>\n {/*\n * SAVE appears only when a MIXED choice exists to save — two or more purposes in use.\n *\n * With one purpose the button is unanswerable: \"decline all\" and \"accept all\" already cover\n * the whole space, so a third action beside them describes nothing a visitor can picture and\n * reads as a question rather than an answer. Derived from the offered set, so a site that\n * adds a second purpose gets the control with no edit here.\n */}\n {offered.length > 1 && (\n <button type=\"button\" style={BUTTON_STYLE} onClick={() => choose(selected.filter((purpose) => offered.includes(purpose)))}>{save}</button>\n )}\n <button type=\"button\" style={BUTTON_STYLE} onClick={() => choose(offered)}>{acceptAll}</button>\n </div>\n </section>\n );\n}\n\n/** The reopen affordance: always reachable once a visitor decided, so every choice can be changed. */\nexport function WebsiteConsentSettingsButton(): ReactNode {\n const consent = useWebsiteConsent();\n const manage = useWebsiteConsentSlotLabel(WebsiteConsentLabelSlot.MANAGE);\n return (\n <button type=\"button\" data-website-consent-manage=\"\" style={BUTTON_STYLE} onClick={consent.openSettings}>\n {manage}\n </button>\n );\n}\n/** @wildo_source:part:end saas.website.consent.banner */\n"]}
|
|
1
|
+
{"version":3,"file":"WebsiteConsentBanner.js","sourceRoot":"","sources":["../../../../../src/core/consent/WebsiteConsentBanner.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, { useEffect, useId, useState, type ReactNode } from 'react';\nimport type { ConsentPurpose } from '@wildo-ai/saas-models/public-runtime';\n\nimport { websiteDesignTokenCssVar } from '../../schemas/design-tokens/website-design-tokens.shared.schemas';\nimport { useWebsiteLabelByKey } from '../hooks/useWebsiteLabel';\nimport { WebsiteConsentStatus } from './WebsiteConsentContext';\nimport { useWebsiteConsent } from './useWebsiteConsent';\nimport {\n WebsiteConsentLabelSlot,\n websiteConsentLabelKey,\n websiteConsentPurposeDescriptionLabelKey,\n websiteConsentPurposeLabelKey,\n} from '../../schemas/label-keys/website-consent-label-keys.schemas';\n\n/**\n * @wildo_source:part:start saas.website.consent.banner facet:layer:core facet:family:website\n *\n * `WebsiteConsentBanner` — the framework's default visitor consent panel (#534).\n *\n * Rendered by `WebsitePageLayout` when the site runs a consent-based provider and the visitor has not\n * decided, or re-opened the panel. A site replaces it by passing `consentBannerComponent` to its bridge\n * island; a replacement reads `useWebsiteConsent()` exactly as this one does and must keep its three\n * properties:\n *\n * 1. **No box is pre-ticked.** A pre-ticked box is not a choice.\n * 2. **Declining is as easy as accepting**, and is RECORDED — so the panel does not return on every page.\n * 3. **Only purposes in use are offered** (`purposesInUse`), each named by its label.\n *\n * Styled only through the site's `--website-*` design tokens, like the skip link, so it needs no\n * stylesheet and cannot be shadowed by a utility class.\n */\nfunction useWebsiteConsentSlotLabel(slot: WebsiteConsentLabelSlot): string {\n return useWebsiteLabelByKey(websiteConsentLabelKey(slot));\n}\n\n/**\n * Every row has the SAME shape: the control, then the name on its line, then the explanation beneath\n * the NAME rather than beneath the control — a two-column grid, so the second line starts where the\n * first line's words do and the row stays scannable.\n *\n * That includes the row that cannot be changed, which carries a real checkbox, CHECKED and DISABLED,\n * rather than a text status. A person scanning the panel reads DOWN the column of controls, and a row\n * whose control is missing does not read as \"cannot be changed\" — it reads as a row that was\n * forgotten. A checked, disabled box says it in the vocabulary the rest of the list already uses, and\n * a screen reader announces \"checked, unavailable\" without any extra words.\n */\nconst ROW_STYLE: React.CSSProperties = {\n display: 'grid',\n gridTemplateColumns: 'auto 1fr',\n columnGap: '0.625rem',\n alignItems: 'center',\n padding: '0.5rem 0.75rem',\n borderBottom: `1px solid ${websiteDesignTokenCssVar('colors', 'border', '#ccc')}`,\n};\n\nconst ROW_DESCRIPTION_STYLE: React.CSSProperties = { margin: 0, gridColumn: 2, fontSize: '0.75rem', opacity: 0.8 };\n\n/** Slightly larger than a default box: this is the one control the whole panel exists to offer. */\nconst ROW_CONTROL_STYLE: React.CSSProperties = { width: '1.15rem', height: '1.15rem' };\n\n/**\n * One purpose: its NAME, what agreeing to it MEANS for this visitor, and the control.\n *\n * The explanation is the half that was missing. A tick box labelled \"Analytics\" is a category, not\n * something a visitor can weigh, and consent is only valid if it is informed — which is why every\n * specialized consent platform pairs the two and why the label key is derived from the vocabulary,\n * so a purpose can never arrive with a name and no explanation.\n */\nfunction PurposeChoice({ purpose, checked, onChange }: { purpose: ConsentPurpose; checked: boolean; onChange: (checked: boolean) => void }): ReactNode {\n const label = useWebsiteLabelByKey(websiteConsentPurposeLabelKey(purpose));\n const description = useWebsiteLabelByKey(websiteConsentPurposeDescriptionLabelKey(purpose));\n return (\n <label data-website-consent-purpose={purpose} style={{ ...ROW_STYLE, cursor: 'pointer' }}>\n <input type=\"checkbox\" checked={checked} style={ROW_CONTROL_STYLE} onChange={(event) => onChange(event.target.checked)} />\n <span style={{ fontWeight: 600 }}>{label}</span>\n <span style={ROW_DESCRIPTION_STYLE}>{description}</span>\n </label>\n );\n}\n\nconst BUTTON_STYLE: React.CSSProperties = {\n padding: '0.5rem 1rem',\n borderRadius: websiteDesignTokenCssVar('radii', 'md', '0.375rem'),\n border: `1px solid ${websiteDesignTokenCssVar('colors', 'border', '#ccc')}`,\n background: websiteDesignTokenCssVar('colors', 'background', '#fff'),\n color: websiteDesignTokenCssVar('colors', 'foreground', '#000'),\n cursor: 'pointer',\n};\n\nexport function WebsiteConsentBanner(): ReactNode {\n const consent = useWebsiteConsent();\n const headingId = useId();\n const [selected, setSelected] = useState<readonly ConsentPurpose[]>(consent.grantedPurposes);\n // Re-seed from the stored decision each time the panel is re-opened, so it shows what is in force.\n useEffect(() => setSelected(consent.grantedPurposes), [consent.grantedPurposes, consent.isSettingsOpen]);\n\n const title = useWebsiteConsentSlotLabel(WebsiteConsentLabelSlot.TITLE);\n const description = useWebsiteConsentSlotLabel(WebsiteConsentLabelSlot.DESCRIPTION);\n const acceptAll = useWebsiteConsentSlotLabel(WebsiteConsentLabelSlot.ACCEPT_ALL);\n const declineAll = useWebsiteConsentSlotLabel(WebsiteConsentLabelSlot.DECLINE_ALL);\n const save = useWebsiteConsentSlotLabel(WebsiteConsentLabelSlot.SAVE);\n const essentialTitle = useWebsiteConsentSlotLabel(WebsiteConsentLabelSlot.ESSENTIAL_TITLE);\n const essentialDescription = useWebsiteConsentSlotLabel(WebsiteConsentLabelSlot.ESSENTIAL_DESCRIPTION);\n\n const offered = consent.purposesInUse;\n // Purposes the panel does not offer keep their stored grant; offered ones are granted exactly when ticked.\n const choose = (chosen: readonly ConsentPurpose[]): void =>\n consent.decide([...consent.grantedPurposes.filter((purpose) => !offered.includes(purpose)), ...chosen]);\n\n return (\n <section\n role=\"region\"\n aria-labelledby={headingId}\n data-website-consent-banner={consent.status === WebsiteConsentStatus.UNDECIDED ? 'undecided' : 'settings'}\n style={{\n /*\n * BOTTOM-RIGHT CORNER, and the three properties together are what make that work on a phone\n * too. `left` and `right` both bound the box to the viewport with a gutter; `maxWidth` caps it\n * on a wide screen; `marginLeft: auto` pushes the remaining slack to the left so the box hugs\n * the right edge. On a narrow screen the gutters bind before the cap does and it simply spans\n * the width — a corner popup that overflowed a phone would be worse than a bar.\n *\n * Inline styles, not a media query: this ships into a static marketing page with no stylesheet\n * of its own, which is the same constraint that keeps the rest of this file inline.\n */\n position: 'fixed',\n left: '1rem',\n right: '1rem',\n bottom: '1rem',\n zIndex: 9000,\n maxWidth: '30rem',\n marginLeft: 'auto',\n /*\n * The panel never exceeds the viewport, and its ACTIONS are never what gets cut off: the rows\n * scroll inside `maxHeight`, the buttons sit outside that box. A panel that grows with its\n * content pushes accept and reject off the bottom of a short screen — and a refusal that\n * cannot be reached is a refusal that was not offered, whatever the markup says. `dvh` rather\n * than `vh` because a mobile browser's toolbars make `vh` taller than the visible page.\n */\n maxHeight: '85dvh',\n padding: '0.875rem 1rem',\n display: 'flex',\n flexDirection: 'column',\n gap: '0.625rem',\n background: websiteDesignTokenCssVar('colors', 'surface', '#fff'),\n color: websiteDesignTokenCssVar('colors', 'surfaceForeground', '#000'),\n border: `1px solid ${websiteDesignTokenCssVar('colors', 'border', '#ccc')}`,\n borderRadius: websiteDesignTokenCssVar('radii', 'lg', '0.5rem'),\n }}\n >\n <h2 id={headingId} style={{ margin: 0, fontSize: '1.125rem' }}>{title}</h2>\n <p style={{ margin: 0 }}>{description}</p>\n {/* `data-website-consent-rows` is the scroll box's own hook. A test anchoring on a style\n substring instead matched the ROW (whose grid template also contains \"auto\") and asserted\n nothing — the same vacuous pin the app tier already had to repair. */}\n <div\n data-website-consent-rows=\"\"\n style={{ minHeight: 0, flex: 1, overflowY: 'auto', border: `1px solid ${websiteDesignTokenCssVar('colors', 'border', '#ccc')}`, borderRadius: websiteDesignTokenCssVar('radii', 'md', '0.375rem') }}\n >\n {/*\n * The always-active row, first, carrying a CHECKED and DISABLED box. It is not a purpose the\n * visitor decides — which is exactly why it is stated: a panel listing only the optional\n * purposes leaves them unable to see what is happening on the site at all, which is the\n * question they opened it to answer.\n *\n * It carried a text status instead until 2026-09-20, on the argument that a disabled control\n * invites a click that cannot work. The reversal: a visitor scans DOWN the column of boxes,\n * and a gap there does not read as \"cannot be changed\", it reads as a row that was\n * forgotten. A disabled box says it in the vocabulary the rest of the list already uses.\n */}\n <div data-website-consent-essential=\"\" style={ROW_STYLE}>\n {/*\n * `aria-label`, because this row is a `div` rather than a `<label>` — nothing else names\n * the control, and a checkbox with no accessible name is announced as an unlabelled\n * checkbox. Its own note claimed AT would announce \"checked, unavailable\"; it announced\n * no words at all.\n *\n * `readOnly` is gone: it is inert on a checkbox per HTML, and leaving it invites a later\n * reader to delete `disabled` believing the lock survives.\n */}\n <input type=\"checkbox\" checked disabled aria-label={essentialTitle} style={ROW_CONTROL_STYLE} />\n <span style={{ fontWeight: 600 }}>{essentialTitle}</span>\n <span style={ROW_DESCRIPTION_STYLE}>{essentialDescription}</span>\n </div>\n {offered.map((purpose) => (\n <PurposeChoice\n key={purpose}\n purpose={purpose}\n checked={selected.includes(purpose)}\n onChange={(checked) => setSelected((current) => (checked ? [...current.filter((entry) => entry !== purpose), purpose] : current.filter((entry) => entry !== purpose)))}\n />\n ))}\n </div>\n <div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.5rem', justifyContent: 'flex-end' }}>\n <button type=\"button\" style={BUTTON_STYLE} onClick={() => choose([])}>{declineAll}</button>\n {/*\n * SAVE appears only when a MIXED choice exists to save — two or more purposes in use.\n *\n * With one purpose the button is unanswerable: \"decline all\" and \"accept all\" already cover\n * the whole space, so a third action beside them describes nothing a visitor can picture and\n * reads as a question rather than an answer. Derived from the offered set, so a site that\n * adds a second purpose gets the control with no edit here.\n */}\n {offered.length > 1 && (\n <button type=\"button\" style={BUTTON_STYLE} onClick={() => choose(selected.filter((purpose) => offered.includes(purpose)))}>{save}</button>\n )}\n <button type=\"button\" style={BUTTON_STYLE} onClick={() => choose(offered)}>{acceptAll}</button>\n </div>\n </section>\n );\n}\n\n/** The reopen affordance: always reachable once a visitor decided, so every choice can be changed. */\nexport function WebsiteConsentSettingsButton(): ReactNode {\n const consent = useWebsiteConsent();\n const manage = useWebsiteConsentSlotLabel(WebsiteConsentLabelSlot.MANAGE);\n return (\n <button type=\"button\" data-website-consent-manage=\"\" style={BUTTON_STYLE} onClick={consent.openSettings}>\n {manage}\n </button>\n );\n}\n/** @wildo_source:part:end saas.website.consent.banner */\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebsiteConsentContext.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"WebsiteConsentContext.d.ts","sourceRoot":"","sources":["../../../../../src/core/consent/WebsiteConsentContext.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAA4D,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAExG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAS3E;;;;;;;;;;;;;GAaG;AACH,oBAAY,oBAAoB;IAC9B,6FAA6F;IAC7F,SAAS,cAAc;IACvB,8EAA8E;IAC9E,OAAO,YAAY;CACpB;AAED,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,oBAAoB,CAAC;IAC7B;;;;OAIG;IACH,UAAU,EAAE,OAAO,CAAC;IACpB,uDAAuD;IACvD,eAAe,EAAE,SAAS,cAAc,EAAE,CAAC;IAC3C,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qFAAqF;IACrF,aAAa,EAAE,SAAS,cAAc,EAAE,CAAC;IACzC,iFAAiF;IACjF,cAAc,EAAE,OAAO,CAAC;IACxB,oCAAoC;IACpC,UAAU,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,OAAO,CAAC;IACjD,qGAAqG;IACrG,MAAM,EAAE,CAAC,eAAe,EAAE,SAAS,cAAc,EAAE,KAAK,IAAI,CAAC;IAC7D,kEAAkE;IAClE,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,yDAAyD;IACzD,aAAa,EAAE,MAAM,IAAI,CAAC;CAC3B;AAED,eAAO,MAAM,qBAAqB,EAAE,KAAK,CAAC,OAAO,CAAC,0BAA0B,GAAG,IAAI,CAA0D,CAAC;AAE9I;;;;;;;GAOG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;;;;;;;OASG;IACH,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACjF;AAED,MAAM,WAAW,2BAA2B;IAC1C,aAAa,EAAE,SAAS,cAAc,EAAE,CAAC;IACzC;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,sBAAsB,CAAC;IAClC,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,wBAAgB,sBAAsB,CAAC,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,2BAA2B,GAAG,SAAS,CAkEpH;AACD,0DAA0D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebsiteConsentGate.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"WebsiteConsentGate.d.ts","sourceRoot":"","sources":["../../../../../src/core/consent/WebsiteConsentGate.tsx"],"names":[],"mappings":"AAAA,OAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAI3E;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,uBAAuB;IACtC,oEAAoE;IACpE,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,qGAAqG;IACrG,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,wBAAgB,kBAAkB,CAAC,EAAE,OAAO,EAAE,QAAe,EAAE,QAAQ,EAAE,EAAE,uBAAuB,GAAG,SAAS,CAI7G;AACD,uDAAuD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useWebsiteConsent.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"useWebsiteConsent.d.ts","sourceRoot":"","sources":["../../../../../src/core/consent/useWebsiteConsent.ts"],"names":[],"mappings":"AAEA,OAAO,EAAyB,KAAK,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AAEjG;;;;;;GAMG;AACH,wBAAgB,iBAAiB,IAAI,0BAA0B,CAM9D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"website-consent-storage.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"website-consent-storage.d.ts","sourceRoot":"","sources":["../../../../../src/core/consent/website-consent-storage.ts"],"names":[],"mappings":"AACA,OAAO,EACL,cAAc,EAGf,MAAM,sCAAsC,CAAC;AA+B9C,4DAA4D;AAC5D,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,eAAe,EAAE,SAAS,cAAc,EAAE,CAAC;IACpD,mHAAmH;IACnH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED;;;GAGG;AACH,eAAO,MAAM,6BAA6B,kCAAkC,CAAC;AAW7E,8EAA8E;AAC9E,wBAAgB,yBAAyB,CAAC,OAAO,GAAE,OAAO,GAAG,IAAuB,GAAG,2BAA2B,GAAG,IAAI,CAkBxH;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,eAAe,EAAE,SAAS,cAAc,EAAE,EAC1C,GAAG,GAAE,IAAiB,EACtB,OAAO,GAAE,OAAO,GAAG,IAAuB,GACzC,2BAA2B,CAc7B;AAED,gHAAgH;AAChH,wBAAgB,8BAA8B,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAW/E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebsitePageContext.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"WebsitePageContext.d.ts","sourceRoot":"","sources":["../../../../../src/core/contexts/WebsitePageContext.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAiB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAE7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAC;AAE1E;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,uBAAuB;IACtC;;;;OAIG;IACH,OAAO,EAAE,cAAc,CAAC;CACzB;AAED,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC,OAAO,CAAC,uBAAuB,GAAG,IAAI,CACxB,CAAC;AAEtD,MAAM,WAAW,+BAA+B;IAC9C,KAAK,EAAE,uBAAuB,CAAC;IAC/B,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,wBAAgB,0BAA0B,CAAC,EACzC,KAAK,EACL,QAAQ,GACT,EAAE,+BAA+B,GAAG,SAAS,CAM7C;yBATe,0BAA0B;;;AAW1C,uDAAuD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebsiteRuntimeContext.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"WebsiteRuntimeContext.d.ts","sourceRoot":"","sources":["../../../../../src/core/contexts/WebsiteRuntimeContext.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAiB,KAAK,aAAa,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAEjF,OAAO,KAAK,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAEpG,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kEAAkE,CAAC;AAC5G,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,0DAA0D,CAAC;AAChH,OAAO,KAAK,EAAE,gCAAgC,EAAE,MAAM,2DAA2D,CAAC;AAClH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oDAAoD,CAAC;AAC1F,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,4DAA4D,CAAC;AAC1G,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+CAA+C,CAAC;AAE/E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AACH,MAAM,WAAW,0BAA0B;IACzC;;;;OAIG;IACH,MAAM,EAAE,iBAAiB,CAAC;IAC1B;;;;;;;;;OASG;IACH,aAAa,EAAE,iBAAiB,CAAC;IACjC;;;;;;;;;;;;;;;;;;OAkBG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,SAAS,EAAE,SAAS,CAAC;IACrB;;;;;OAKG;IACH,YAAY,EAAE,mBAAmB,CAAC;IAClC;;;;;;OAMG;IACH,UAAU,EAAE,uBAAuB,CAAC;IACpC;;;;;;;;;;;;;;;OAeG;IACH,yBAAyB,EAAE,eAAe,CAAC;IAC3C;;;;;OAKG;IACH,wBAAwB,CAAC,EAAE,+BAA+B,CAAC;IAC3D;;;;;;OAMG;IACH,yBAAyB,CAAC,EAAE,gCAAgC,CAAC;IAC7D;;;;;OAKG;IACH,sBAAsB,CAAC,EAAE,aAAa,CAAC;IACvC;;;;;;;;;;;;OAYG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;;;OASG;IACH,cAAc,CAAC,EAAE,oBAAoB,CAAC;CACvC;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,EAAE,KAAK,CAAC,OAAO,CAAC,0BAA0B,GAAG,IAAI,CAC3B,CAAC;AAEzD,MAAM,WAAW,kCAAkC;IACjD,KAAK,EAAE,0BAA0B,CAAC;IAClC,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,EAC5C,KAAK,EACL,QAAQ,GACT,EAAE,kCAAkC,GAAG,SAAS,CAMhD;yBATe,6BAA6B;;;AAW7C,0DAA0D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebsiteRuntimeContext.js","sourceRoot":"","sources":["../../../../src/core/contexts/WebsiteRuntimeContext.tsx"],"names":[],"mappings":";AAAA,OAAc,EAAE,aAAa,EAAsC,MAAM,OAAO,CAAC;AAqMjF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAChC,aAAa,CAAoC,IAAI,CAAC,CAAC;AAOzD;;;;;GAKG;AACH,MAAM,UAAU,6BAA6B,CAAC,EAC5C,KAAK,EACL,QAAQ,GAC2B;IACnC,OAAO,CACL,KAAC,qBAAqB,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YACzC,QAAQ,GACsB,CAClC,CAAC;AACJ,CAAC;AACD,6BAA6B,CAAC,WAAW,GAAG,+BAA+B,CAAC","sourcesContent":["import React, { createContext, type ComponentType, type ReactNode } from 'react';\n\nimport type { AvailableLanguage, PublicPricingCatalog } from '@wildo-ai/saas-models/public-runtime';\n\nimport type { WebsiteDesignTokens } from '../../schemas/design-tokens/website-design-tokens.shared.schemas';\nimport type { FrontendWebsiteProviderRegistry } from '../external-providers/frontend-provider-registry.website';\nimport type { WebsiteProviderComponentRegistry } from '../external-providers/provider-component-registry.website';\nimport type { WebsiteLabelKey } from '../../schemas/label-keys/website-label-key.schemas';\nimport type { WebsiteNavigationConfig } from '../../schemas/manifests/website-root-config.shared.schemas';\nimport type { LabelPack } from '../../schemas/validators/label-pack.validator';\n\n/**\n * @wildo_source:part:start saas.website.runtime.context facet:layer:core facet:family:website\n *\n * `WebsiteRuntimeContext` — site-wide runtime state for the marketing\n * surface. Created **once per page render** by the host adapter: the\n * page's `.astro` frontmatter loads the site context and the resolved label\n * pack, and its bridge island (`createWebsitePageBridgeIsland`) calls\n * `<WebsiteRuntimeContextProvider value={…}>` around the page tree.\n *\n * **Why \"runtime\" and not \"config\"**: the values here are the\n * locale-resolved subset of `WebsiteRootConfig` — only the active\n * locale's label pack travels with the React tree (the other locales\n * live in their own per-locale static builds). A `*Config` name would\n * suggest the full multi-locale surface is in scope.\n *\n * **Three pieces of state**:\n *\n * - `locale` — the currently-rendering locale. Sections that need\n * locale-aware formatting (number/date in marketing copy is rare\n * but legitimate) read it via `useWebsiteRuntime()`.\n * - `labelPack` — the locale-resolved flat key→string map, validated\n * against the manifest by `validateLabelPackAgainstManifest` at\n * build start. Consumed by `useWebsiteLabel` and\n * `useWebsiteLabelByKey`.\n * - `designTokens` — the `WebsiteDesignTokens` from\n * `WebsiteRootConfig.designTokens`. Consumed by\n * `useWebsiteDesignTokens` and by the Phase 5 CSS emitter that\n * materializes them as `--website-*` custom properties on the\n * `<WebsitePageLayout>` root.\n * - `navigation` — the `WebsiteNavigationConfig` from\n * `WebsiteRootConfig.navigation`. Carried in the runtime context\n * (rather than as a separate `WebsiteNavigationContext`) because\n * `<WebsitePageLayout>` already reads runtime state and\n * header/footer slots are inseparable from \"the site is rendering\n * right now\". A second context for two component refs would split\n * the host-adapter's wiring surface without buying isolation.\n * - `frontendProviderRegistry` — optional website-facing public provider\n * registry hydrated from the materialized provider artifact for the\n * concrete `STATIC_WEBSITE` service. This keeps analytics / captcha /\n * browser-monitoring integrations on the same frontend-provider\n * contract the SaaS app now uses, instead of inventing a website-only\n * metadata seam.\n *\n * **Why the hook lives in a separate file**: React Fast Refresh only\n * preserves state for a module whose every export is a component, so\n * a module mixing components with plain functions loses its HMR\n * boundary. This file holds the two things that must be identical\n * objects at runtime — the context and the provider component that\n * writes it — while `useWebsiteRuntime` sits alone in\n * `useWebsiteRuntime.ts`. A section author editing a component that\n * merely READS the runtime therefore never remounts the provider.\n *\n * Until 2026-09-09 this paragraph described a different arrangement:\n * that only the bare context was kept here and the provider lived in\n * a file of its own. No such file has ever existed — the provider is\n * exported below — and splitting it out would be the wrong shape\n * anyway, since `useContext` matches on object identity.\n * prose-referent-waiver: the retired file name is quoted only to retire it.\n * The claim named `WebsiteRuntimeContextProvider.tsx`.\n */\nexport interface WebsiteRuntimeContextValue {\n /**\n * The currently-rendering locale. Marketing-site routing emits one\n * static build per locale (D8), so this value is constant per build\n * — never re-renders mid-session.\n */\n locale: AvailableLanguage;\n /**\n * The site's default locale (mirrors `WebsiteRootConfig.defaultLocale`).\n * Carried in the runtime context so client-tier components — namely\n * `WebsiteInternalLink` (Phase 6 fix H-β) — can compute the correct\n * locale-prefixed pathname (`/pricing` on the EN site,\n * `/fr/pricing` on the FR site) without re-importing the root\n * config. Plumbing it through the runtime keeps the routing rule\n * a framework-internal concern: section authors NEVER hand-prefix\n * paths.\n */\n defaultLocale: AvailableLanguage;\n /**\n * Origin of the application this marketing site sends visitors INTO — the\n * marketing → product handoff target, authored once as\n * `WildoMarketingWebsiteConfig.appOrigin` (#493).\n *\n * Carried here for exactly the reason `defaultLocale` above is: a\n * `wildo.website.config.ts` is Node-loaded and therefore invisible to a\n * client island, so before this a site resolved the app origin a SECOND\n * time from a public build-time environment variable it could read in the\n * browser — one host authored twice, in two files, with no mechanical link\n * between the copies. That is how a marketing site comes to point at the\n * wrong environment: a defect nobody notices until a customer follows the\n * link.\n *\n * Threading it through the runtime keeps the handoff a framework-internal\n * concern, the same way threading `defaultLocale` kept locale-prefixing one:\n * section authors never hand-assemble the product URL, and there is one\n * place to change it.\n */\n appOrigin?: string;\n /**\n * Flat `Record<string, string>` label pack for the active locale.\n * Lookup keys MUST be fully-composed `website.<page>.<section>.<slot>`\n * strings; consumers that hold leaf paths use `useWebsiteLabel(leaf)`\n * which composes the full key against the page+section context.\n */\n labelPack: LabelPack;\n /**\n * Design-token bundle for the active site. Same object referenced\n * by `WebsiteRootConfig.designTokens`; passed by reference (not\n * cloned) so token-aware components can rely on referential\n * stability across renders.\n */\n designTokens: WebsiteDesignTokens;\n /**\n * Site-wide navigation slot bindings (header / footer components).\n * Same object referenced by `WebsiteRootConfig.navigation`. Read\n * by `<WebsitePageLayout>` to render the chrome around each page's\n * `<main>` content; per-page `header` / `footer` props on\n * `<WebsitePageLayout>` override these defaults.\n */\n navigation: WebsiteNavigationConfig;\n /**\n * Fully-composed label key the framework reads to render the\n * \"Skip to main content\" affordance as the FIRST focusable element\n * on every page (Phase 6 third audit fix M-α). Mirrors\n * `WebsiteRootConfig.skipToMainContentLabelKey`. Carried in the\n * runtime context (NOT re-derived from the root config inside\n * `WebsitePageLayout`) for two reasons:\n * - The layout is a client-tier React component; it cannot import\n * the consumer's `WEBSITE_ROOT_CONFIG` symbol without dragging\n * server-only code (manifest registry, page components) into\n * the client bundle.\n * - Threading the key through the runtime keeps the layout's\n * contract symmetric with how every other localized text on the\n * page is sourced (label pack + key), so the skip link is\n * translated automatically without any per-locale branching.\n */\n skipToMainContentLabelKey: WebsiteLabelKey;\n /**\n * Website-facing public providers for the active static website\n * service. Optional so older tests and provider-free sites can omit\n * it, but when present it is the canonical frontend-safe provider\n * catalog for analytics / captcha / browser-monitoring slices.\n */\n frontendProviderRegistry?: FrontendWebsiteProviderRegistry;\n /**\n * Components the live providers contribute, keyed `capability:providerRef` (#1703) — the same\n * registry the application builds, from the same `components` declaration. Built by the page bridge\n * once per island from `frontendProviderRegistry`; read through `useWebsiteProviderComponent` and\n * rendered by `WebsiteProviderComponent`. Optional for the same reason as the registry above: a\n * provider-free site, or a test runtime, has none, and every slot then renders nothing.\n */\n providerComponentRegistry?: WebsiteProviderComponentRegistry;\n /**\n * Replaces the framework's visitor consent panel (#534). Reads `useWebsiteConsent()`; see\n * `WebsiteConsentBanner` for the three properties a replacement must keep. Absent uses the default.\n * Closed over by the bridge rather than passed as an island prop, like `navigation`, because a\n * component cannot cross Astro's serialization boundary.\n */\n consentBannerComponent?: ComponentType;\n /**\n * Backend API origin (WITHOUT a trailing slash) that anonymous-capture\n * sections POST to when constructing a `WebsiteAnonymousSessionClient`\n * (lead / draft-note forms). Sourced from\n * `WildoMarketingWebsiteConfig.apiOrigin` and passed as a\n * JSON-serializable island prop (the `.astro` page resolves the\n * per-environment value and forwards it).\n *\n * Optional because only pages that host an anonymous-capture section\n * need it — brochure pages omit it. A capture section that reads\n * `undefined` here surfaces a loud misconfiguration rather than\n * silently failing to submit.\n */\n apiOrigin?: string;\n /**\n * The application's public pricing catalogue (#1835), derived at build time from the billing\n * catalogue the backend sells from — so a pricing section states the prices the application\n * actually charges instead of a hand copy of them. Node-loaded\n * (`loadWebsitePricingCatalog` on `/config-loader`) and carried as an island prop for the\n * reason `appOrigin` above is. Read it with `useWebsitePricingCatalog()`.\n *\n * Optional because only pricing pages need it; a section that reads it on a page that did not\n * pass it fails loudly rather than rendering no plans.\n */\n pricingCatalog?: PublicPricingCatalog;\n}\n\n/**\n * Bare React context. Default `null` — `useWebsiteRuntime()` throws\n * with a clear error message when invoked outside of\n * `<WebsiteRuntimeContextProvider>`. The defensive throw is preferred\n * over a silent default value so a missing provider is detected at\n * the first hook call rather than at render time when a missing\n * label silently renders as the key string.\n */\nexport const WebsiteRuntimeContext: React.Context<WebsiteRuntimeContextValue | null> =\n createContext<WebsiteRuntimeContextValue | null>(null);\n\nexport interface WebsiteRuntimeContextProviderProps {\n value: WebsiteRuntimeContextValue;\n children: ReactNode;\n}\n\n/**\n * Provider component. Authored as a thin wrapper so consumers do NOT\n * have to import `WebsiteRuntimeContext.Provider` directly — the\n * provider component carries the documented authoring API and is the\n * Fast-Refresh-stable surface section authors compose against.\n */\nexport function WebsiteRuntimeContextProvider({\n value,\n children,\n}: WebsiteRuntimeContextProviderProps): ReactNode {\n return (\n <WebsiteRuntimeContext.Provider value={value}>\n {children}\n </WebsiteRuntimeContext.Provider>\n );\n}\nWebsiteRuntimeContextProvider.displayName = 'WebsiteRuntimeContextProvider';\n/** @wildo_source:part:end saas.website.runtime.context */\n"]}
|
|
1
|
+
{"version":3,"file":"WebsiteRuntimeContext.js","sourceRoot":"","sources":["../../../../../src/core/contexts/WebsiteRuntimeContext.tsx"],"names":[],"mappings":";AAAA,OAAc,EAAE,aAAa,EAAsC,MAAM,OAAO,CAAC;AAqMjF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAChC,aAAa,CAAoC,IAAI,CAAC,CAAC;AAOzD;;;;;GAKG;AACH,MAAM,UAAU,6BAA6B,CAAC,EAC5C,KAAK,EACL,QAAQ,GAC2B;IACnC,OAAO,CACL,KAAC,qBAAqB,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YACzC,QAAQ,GACsB,CAClC,CAAC;AACJ,CAAC;AACD,6BAA6B,CAAC,WAAW,GAAG,+BAA+B,CAAC","sourcesContent":["import React, { createContext, type ComponentType, type ReactNode } from 'react';\n\nimport type { AvailableLanguage, PublicPricingCatalog } from '@wildo-ai/saas-models/public-runtime';\n\nimport type { WebsiteDesignTokens } from '../../schemas/design-tokens/website-design-tokens.shared.schemas';\nimport type { FrontendWebsiteProviderRegistry } from '../external-providers/frontend-provider-registry.website';\nimport type { WebsiteProviderComponentRegistry } from '../external-providers/provider-component-registry.website';\nimport type { WebsiteLabelKey } from '../../schemas/label-keys/website-label-key.schemas';\nimport type { WebsiteNavigationConfig } from '../../schemas/manifests/website-root-config.shared.schemas';\nimport type { LabelPack } from '../../schemas/validators/label-pack.validator';\n\n/**\n * @wildo_source:part:start saas.website.runtime.context facet:layer:core facet:family:website\n *\n * `WebsiteRuntimeContext` — site-wide runtime state for the marketing\n * surface. Created **once per page render** by the host adapter: the\n * page's `.astro` frontmatter loads the site context and the resolved label\n * pack, and its bridge island (`createWebsitePageBridgeIsland`) calls\n * `<WebsiteRuntimeContextProvider value={…}>` around the page tree.\n *\n * **Why \"runtime\" and not \"config\"**: the values here are the\n * locale-resolved subset of `WebsiteRootConfig` — only the active\n * locale's label pack travels with the React tree (the other locales\n * live in their own per-locale static builds). A `*Config` name would\n * suggest the full multi-locale surface is in scope.\n *\n * **Three pieces of state**:\n *\n * - `locale` — the currently-rendering locale. Sections that need\n * locale-aware formatting (number/date in marketing copy is rare\n * but legitimate) read it via `useWebsiteRuntime()`.\n * - `labelPack` — the locale-resolved flat key→string map, validated\n * against the manifest by `validateLabelPackAgainstManifest` at\n * build start. Consumed by `useWebsiteLabel` and\n * `useWebsiteLabelByKey`.\n * - `designTokens` — the `WebsiteDesignTokens` from\n * `WebsiteRootConfig.designTokens`. Consumed by\n * `useWebsiteDesignTokens` and by the Phase 5 CSS emitter that\n * materializes them as `--website-*` custom properties on the\n * `<WebsitePageLayout>` root.\n * - `navigation` — the `WebsiteNavigationConfig` from\n * `WebsiteRootConfig.navigation`. Carried in the runtime context\n * (rather than as a separate `WebsiteNavigationContext`) because\n * `<WebsitePageLayout>` already reads runtime state and\n * header/footer slots are inseparable from \"the site is rendering\n * right now\". A second context for two component refs would split\n * the host-adapter's wiring surface without buying isolation.\n * - `frontendProviderRegistry` — optional website-facing public provider\n * registry hydrated from the materialized provider artifact for the\n * concrete `STATIC_WEBSITE` service. This keeps analytics / captcha /\n * browser-monitoring integrations on the same frontend-provider\n * contract the SaaS app now uses, instead of inventing a website-only\n * metadata seam.\n *\n * **Why the hook lives in a separate file**: React Fast Refresh only\n * preserves state for a module whose every export is a component, so\n * a module mixing components with plain functions loses its HMR\n * boundary. This file holds the two things that must be identical\n * objects at runtime — the context and the provider component that\n * writes it — while `useWebsiteRuntime` sits alone in\n * `useWebsiteRuntime.ts`. A section author editing a component that\n * merely READS the runtime therefore never remounts the provider.\n *\n * Until 2026-09-09 this paragraph described a different arrangement:\n * that only the bare context was kept here and the provider lived in\n * a file of its own. No such file has ever existed — the provider is\n * exported below — and splitting it out would be the wrong shape\n * anyway, since `useContext` matches on object identity.\n * prose-referent-waiver: the retired file name is quoted only to retire it.\n * The claim named `WebsiteRuntimeContextProvider.tsx`.\n */\nexport interface WebsiteRuntimeContextValue {\n /**\n * The currently-rendering locale. Marketing-site routing emits one\n * static build per locale (D8), so this value is constant per build\n * — never re-renders mid-session.\n */\n locale: AvailableLanguage;\n /**\n * The site's default locale (mirrors `WebsiteRootConfig.defaultLocale`).\n * Carried in the runtime context so client-tier components — namely\n * `WebsiteInternalLink` (Phase 6 fix H-β) — can compute the correct\n * locale-prefixed pathname (`/pricing` on the EN site,\n * `/fr/pricing` on the FR site) without re-importing the root\n * config. Plumbing it through the runtime keeps the routing rule\n * a framework-internal concern: section authors NEVER hand-prefix\n * paths.\n */\n defaultLocale: AvailableLanguage;\n /**\n * Origin of the application this marketing site sends visitors INTO — the\n * marketing → product handoff target, authored once as\n * `WildoMarketingWebsiteConfig.appOrigin` (#493).\n *\n * Carried here for exactly the reason `defaultLocale` above is: a\n * `wildo.website.config.ts` is Node-loaded and therefore invisible to a\n * client island, so before this a site resolved the app origin a SECOND\n * time from a public build-time environment variable it could read in the\n * browser — one host authored twice, in two files, with no mechanical link\n * between the copies. That is how a marketing site comes to point at the\n * wrong environment: a defect nobody notices until a customer follows the\n * link.\n *\n * Threading it through the runtime keeps the handoff a framework-internal\n * concern, the same way threading `defaultLocale` kept locale-prefixing one:\n * section authors never hand-assemble the product URL, and there is one\n * place to change it.\n */\n appOrigin?: string;\n /**\n * Flat `Record<string, string>` label pack for the active locale.\n * Lookup keys MUST be fully-composed `website.<page>.<section>.<slot>`\n * strings; consumers that hold leaf paths use `useWebsiteLabel(leaf)`\n * which composes the full key against the page+section context.\n */\n labelPack: LabelPack;\n /**\n * Design-token bundle for the active site. Same object referenced\n * by `WebsiteRootConfig.designTokens`; passed by reference (not\n * cloned) so token-aware components can rely on referential\n * stability across renders.\n */\n designTokens: WebsiteDesignTokens;\n /**\n * Site-wide navigation slot bindings (header / footer components).\n * Same object referenced by `WebsiteRootConfig.navigation`. Read\n * by `<WebsitePageLayout>` to render the chrome around each page's\n * `<main>` content; per-page `header` / `footer` props on\n * `<WebsitePageLayout>` override these defaults.\n */\n navigation: WebsiteNavigationConfig;\n /**\n * Fully-composed label key the framework reads to render the\n * \"Skip to main content\" affordance as the FIRST focusable element\n * on every page (Phase 6 third audit fix M-α). Mirrors\n * `WebsiteRootConfig.skipToMainContentLabelKey`. Carried in the\n * runtime context (NOT re-derived from the root config inside\n * `WebsitePageLayout`) for two reasons:\n * - The layout is a client-tier React component; it cannot import\n * the consumer's `WEBSITE_ROOT_CONFIG` symbol without dragging\n * server-only code (manifest registry, page components) into\n * the client bundle.\n * - Threading the key through the runtime keeps the layout's\n * contract symmetric with how every other localized text on the\n * page is sourced (label pack + key), so the skip link is\n * translated automatically without any per-locale branching.\n */\n skipToMainContentLabelKey: WebsiteLabelKey;\n /**\n * Website-facing public providers for the active static website\n * service. Optional so older tests and provider-free sites can omit\n * it, but when present it is the canonical frontend-safe provider\n * catalog for analytics / captcha / browser-monitoring slices.\n */\n frontendProviderRegistry?: FrontendWebsiteProviderRegistry;\n /**\n * Components the live providers contribute, keyed `capability:providerRef` (#1703) — the same\n * registry the application builds, from the same `components` declaration. Built by the page bridge\n * once per island from `frontendProviderRegistry`; read through `useWebsiteProviderComponent` and\n * rendered by `WebsiteProviderComponent`. Optional for the same reason as the registry above: a\n * provider-free site, or a test runtime, has none, and every slot then renders nothing.\n */\n providerComponentRegistry?: WebsiteProviderComponentRegistry;\n /**\n * Replaces the framework's visitor consent panel (#534). Reads `useWebsiteConsent()`; see\n * `WebsiteConsentBanner` for the three properties a replacement must keep. Absent uses the default.\n * Closed over by the bridge rather than passed as an island prop, like `navigation`, because a\n * component cannot cross Astro's serialization boundary.\n */\n consentBannerComponent?: ComponentType;\n /**\n * Backend API origin (WITHOUT a trailing slash) that anonymous-capture\n * sections POST to when constructing a `WebsiteAnonymousSessionClient`\n * (lead / draft-note forms). Sourced from\n * `WildoMarketingWebsiteConfig.apiOrigin` and passed as a\n * JSON-serializable island prop (the `.astro` page resolves the\n * per-environment value and forwards it).\n *\n * Optional because only pages that host an anonymous-capture section\n * need it — brochure pages omit it. A capture section that reads\n * `undefined` here surfaces a loud misconfiguration rather than\n * silently failing to submit.\n */\n apiOrigin?: string;\n /**\n * The application's public pricing catalogue (#1835), derived at build time from the billing\n * catalogue the backend sells from — so a pricing section states the prices the application\n * actually charges instead of a hand copy of them. Node-loaded\n * (`loadWebsitePricingCatalog` on `/config-loader`) and carried as an island prop for the\n * reason `appOrigin` above is. Read it with `useWebsitePricingCatalog()`.\n *\n * Optional because only pricing pages need it; a section that reads it on a page that did not\n * pass it fails loudly rather than rendering no plans.\n */\n pricingCatalog?: PublicPricingCatalog;\n}\n\n/**\n * Bare React context. Default `null` — `useWebsiteRuntime()` throws\n * with a clear error message when invoked outside of\n * `<WebsiteRuntimeContextProvider>`. The defensive throw is preferred\n * over a silent default value so a missing provider is detected at\n * the first hook call rather than at render time when a missing\n * label silently renders as the key string.\n */\nexport const WebsiteRuntimeContext: React.Context<WebsiteRuntimeContextValue | null> =\n createContext<WebsiteRuntimeContextValue | null>(null);\n\nexport interface WebsiteRuntimeContextProviderProps {\n value: WebsiteRuntimeContextValue;\n children: ReactNode;\n}\n\n/**\n * Provider component. Authored as a thin wrapper so consumers do NOT\n * have to import `WebsiteRuntimeContext.Provider` directly — the\n * provider component carries the documented authoring API and is the\n * Fast-Refresh-stable surface section authors compose against.\n */\nexport function WebsiteRuntimeContextProvider({\n value,\n children,\n}: WebsiteRuntimeContextProviderProps): ReactNode {\n return (\n <WebsiteRuntimeContext.Provider value={value}>\n {children}\n </WebsiteRuntimeContext.Provider>\n );\n}\nWebsiteRuntimeContextProvider.displayName = 'WebsiteRuntimeContextProvider';\n/** @wildo_source:part:end saas.website.runtime.context */\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebsiteSectionContext.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"WebsiteSectionContext.d.ts","sourceRoot":"","sources":["../../../../../src/core/contexts/WebsiteSectionContext.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAiB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAE7D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wDAAwD,CAAC;AACrG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAEhF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,0BAA0B;IACzC;;;OAGG;IACH,UAAU,EAAE,iBAAiB,CAAC;IAC9B;;;;;OAKG;IACH,QAAQ,EAAE,sBAAsB,CAAC;CAClC;AAED,eAAO,MAAM,qBAAqB,EAAE,KAAK,CAAC,OAAO,CAAC,0BAA0B,GAAG,IAAI,CAC3B,CAAC;AAEzD,MAAM,WAAW,kCAAkC;IACjD,KAAK,EAAE,0BAA0B,CAAC;IAClC,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,wBAAgB,6BAA6B,CAAC,EAC5C,KAAK,EACL,QAAQ,GACT,EAAE,kCAAkC,GAAG,SAAS,CAMhD;yBATe,6BAA6B;;;AAW7C,0DAA0D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useWebsitePage.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"useWebsitePage.d.ts","sourceRoot":"","sources":["../../../../../src/core/contexts/useWebsitePage.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,sBAAsB,CAAC;AAE9B;;;;;;;;GAQG;AACH,wBAAgB,cAAc,IAAI,uBAAuB,CAUxD;AACD,wDAAwD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useWebsiteRuntime.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"useWebsiteRuntime.d.ts","sourceRoot":"","sources":["../../../../../src/core/contexts/useWebsiteRuntime.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAEjF,OAAO,EAEL,KAAK,0BAA0B,EAChC,MAAM,yBAAyB,CAAC;AAEjC;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,IAAI,0BAA0B,CAW9D;AACD,2DAA2D;AAE3D;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,wBAAwB,IAAI,oBAAoB,CAU/D;AACD,mEAAmE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useWebsiteRuntime.js","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"useWebsiteRuntime.js","sourceRoot":"","sources":["../../../../../src/core/contexts/useWebsiteRuntime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAInC,OAAO,EACL,qBAAqB,GAEtB,MAAM,yBAAyB,CAAC;AAEjC;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,iBAAiB;IAC/B,MAAM,KAAK,GAAG,UAAU,CAAC,qBAAqB,CAAC,CAAC;IAChD,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CACb,6EAA6E;YAC3E,gGAAgG;YAChG,+FAA+F;YAC/F,sCAAsC,CACzC,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AACD,2DAA2D;AAE3D;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,wBAAwB;IACtC,MAAM,EAAE,cAAc,EAAE,GAAG,iBAAiB,EAAE,CAAC;IAC/C,IAAI,cAAc,KAAK,SAAS,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CACb,6GAA6G;cACzG,mHAAmH;cACnH,oEAAoE,CACzE,CAAC;IACJ,CAAC;IACD,OAAO,cAAc,CAAC;AACxB,CAAC;AACD,mEAAmE","sourcesContent":["import { useContext } from 'react';\n\nimport type { PublicPricingCatalog } from '@wildo-ai/saas-models/public-runtime';\n\nimport {\n WebsiteRuntimeContext,\n type WebsiteRuntimeContextValue,\n} from './WebsiteRuntimeContext';\n\n/**\n * @wildo_source:part:start saas.website.use-runtime.hook facet:layer:core facet:family:website\n *\n * `useWebsiteRuntime()` — read access to the\n * `WebsiteRuntimeContextValue`. Throws if invoked outside of a\n * `<WebsiteRuntimeContextProvider>` so a missing host adapter is\n * detected at the first hook call (fail-loud).\n *\n * Section authors usually do NOT call this directly — prefer the\n * higher-level hooks (`useWebsiteLabel`, `useWebsiteLabelByKey`,\n * `useWebsiteDesignTokens`) which expose only the slice they need.\n * Direct access is reserved for layout primitives and locale-aware\n * formatters that legitimately need the active locale or the raw\n * label pack.\n */\nexport function useWebsiteRuntime(): WebsiteRuntimeContextValue {\n const value = useContext(WebsiteRuntimeContext);\n if (value === null) {\n throw new Error(\n '[useWebsiteRuntime] must be called inside <WebsiteRuntimeContextProvider>. ' +\n 'In Astro pages this is provided by the page\\'s bridge island (`createWebsitePageBridgeIsland` ' +\n 'from @wildo-ai/saas-website/astro-island) — make sure the page renders its bridge instead of ' +\n 'rendering React components directly.',\n );\n }\n return value;\n}\n/** @wildo_source:part:end saas.website.use-runtime.hook */\n\n/**\n * @wildo_source:part:start saas.website.use-pricing-catalog.hook facet:layer:core facet:family:website facet:audience:app-developer\n *\n * `useWebsitePricingCatalog()` — the application's public pricing catalogue, for a pricing section.\n *\n * The plans, their prices, which one is recommended, how much of each price may be stated, and which\n * are announced but not yet on sale — all derived at build time from the billing catalogue the\n * backend sells from. A pricing section renders from THIS and never keeps its own copy of a price:\n * a copy is a price that will disagree with the one the application charges.\n *\n * Throws when the page did not pass the catalogue to its bridge island: a pricing section with no\n * plans would silently state that the application sells nothing. The page's `.astro` frontmatter\n * loads it with `loadWebsitePricingCatalog()` from `@wildo-ai/saas-website/config-loader` and passes\n * it as the island's `pricingCatalog` prop.\n */\nexport function useWebsitePricingCatalog(): PublicPricingCatalog {\n const { pricingCatalog } = useWebsiteRuntime();\n if (pricingCatalog === undefined) {\n throw new Error(\n '[useWebsitePricingCatalog] this page did not pass a pricing catalogue to its bridge island. In the page\\'s '\n + '`.astro` frontmatter, load it with `await loadWebsitePricingCatalog()` (the @wildo-ai/saas-website/config-loader '\n + 'subpath) and pass `pricingCatalog={pricingCatalog}` to the island.',\n );\n }\n return pricingCatalog;\n}\n/** @wildo_source:part:end saas.website.use-pricing-catalog.hook */\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useWebsiteSection.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"useWebsiteSection.d.ts","sourceRoot":"","sources":["../../../../../src/core/contexts/useWebsiteSection.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,0BAA0B,EAChC,MAAM,yBAAyB,CAAC;AAEjC;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,IAAI,0BAA0B,CAW9D;AACD,2DAA2D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebsiteProviderComponent.d.ts","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"WebsiteProviderComponent.d.ts","sourceRoot":"","sources":["../../../../../src/core/external-providers/WebsiteProviderComponent.tsx"],"names":[],"mappings":"AAAA,OAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAE9C,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AA6D/F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,MAAM,WAAW,6BAA6B,CAAC,MAAM,SAAS,MAAM;IAClE,0EAA0E;IAC1E,UAAU,EAAE,kBAAkB,CAAC;IAC/B,2EAA2E;IAC3E,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,gFAAgF;IAChF,cAAc,EAAE,MAAM,CAAC;IACvB,2GAA2G;IAC3G,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,mDAAmD;IACnD,qBAAqB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,eAAe,EAAE,SAAS,cAAc,EAAE,CAAC;QAAC,mBAAmB,EAAE,MAAM,IAAI,CAAA;KAAE,KAAK,SAAS,CAAC;CAC/H;AAED,wBAAgB,wBAAwB,CAAC,MAAM,SAAS,MAAM,EAAE,EAC9D,UAAU,EACV,WAAW,EACX,cAAc,EACd,iBAAiB,EACjB,qBAAqB,GACtB,EAAE,6BAA6B,CAAC,MAAM,CAAC,GAAG,SAAS,CAgCnD;AACD,kEAAkE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebsiteProviderComponent.js","sourceRoot":"","sources":["../../../../src/core/external-providers/WebsiteProviderComponent.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, { type ReactNode } from 'react';\nimport { ProviderComponentStatus } from '@wildo-ai/external-connectors-public/website';\nimport type { ConsentPurpose, ProviderCapability } from '@wildo-ai/saas-models/public-runtime';\n\nimport { websiteDesignTokenCssVar } from '../../schemas/design-tokens/website-design-tokens.shared.schemas';\nimport {\n WebsiteConsentLabelSlot,\n websiteConsentLabelKey,\n websiteConsentPurposeLabelKey,\n} from '../../schemas/label-keys/website-consent-label-keys.schemas';\nimport {\n WebsiteProviderComponentLabelSlot,\n websiteProviderComponentLabelKey,\n} from '../../schemas/label-keys/website-provider-component-label-keys.schemas';\nimport { useWebsiteConsent } from '../consent/useWebsiteConsent';\nimport { useWebsiteLabelByKey } from '../hooks/useWebsiteLabel';\nimport { useWebsiteProviderComponent } from './useWebsiteProviderComponent';\n\nconst STATE_STYLE: React.CSSProperties = {\n display: 'flex',\n flexDirection: 'column',\n gap: '0.5rem',\n padding: '0.75rem',\n border: `1px solid ${websiteDesignTokenCssVar('colors', 'border', '#ccc')}`,\n borderRadius: websiteDesignTokenCssVar('radii', 'md', '0.375rem'),\n};\n\nconst BUTTON_STYLE: React.CSSProperties = {\n alignSelf: 'flex-start',\n padding: '0.5rem 1rem',\n borderRadius: websiteDesignTokenCssVar('radii', 'md', '0.375rem'),\n border: `1px solid ${websiteDesignTokenCssVar('colors', 'border', '#ccc')}`,\n background: websiteDesignTokenCssVar('colors', 'background', '#fff'),\n color: websiteDesignTokenCssVar('colors', 'foreground', '#000'),\n cursor: 'pointer',\n};\n\nfunction PurposeName({ purpose }: { purpose: ConsentPurpose }): ReactNode {\n return <li data-website-provider-component-purpose={purpose}>{useWebsiteLabelByKey(websiteConsentPurposeLabelKey(purpose))}</li>;\n}\n\n/**\n * The words announced for a refusal. Rendered INTO the always-present live region, so a screen reader\n * announces the change when it happens; a region inserted already filled is often not announced at all.\n */\nfunction RefusalStatement({ status, missingPurposes }: { status: ProviderComponentStatus; missingPurposes: readonly ConsentPurpose[] }): ReactNode {\n const consentRequired = useWebsiteLabelByKey(websiteProviderComponentLabelKey(WebsiteProviderComponentLabelSlot.CONSENT_REQUIRED));\n const unavailable = useWebsiteLabelByKey(websiteProviderComponentLabelKey(WebsiteProviderComponentLabelSlot.UNAVAILABLE));\n if (status === ProviderComponentStatus.UNAVAILABLE) return <p style={{ margin: 0 }}>{unavailable}</p>;\n return (\n <>\n <p style={{ margin: 0 }}>{consentRequired}</p>\n <ul style={{ margin: 0 }}>\n {missingPurposes.map((purpose) => <PurposeName key={purpose} purpose={purpose} />)}\n </ul>\n </>\n );\n}\n\nfunction ManageConsentButton({ onClick }: { onClick: () => void }): ReactNode {\n return <button type=\"button\" style={BUTTON_STYLE} onClick={onClick}>{useWebsiteLabelByKey(websiteConsentLabelKey(WebsiteConsentLabelSlot.MANAGE))}</button>;\n}\n\n/**\n * @wildo_source:part:start saas.website.provider-component.slot facet:layer:core facet:family:website\n *\n * `<WebsiteProviderComponent>` — places ONE provider's component on a website page (#1703): an\n * address lookup on a contact form, a vendor widget on a marketing page. The component comes from the\n * provider's own `components[capability]` declaration, the same one the application reads, through\n * the website's provider-component registry.\n *\n * What it guarantees, so a page does not have to:\n *\n * - **Nothing of the vendor reaches the static HTML.** The component loads from an effect, which never\n * runs during the Astro server render, and the registry itself refuses to load where there is no\n * browser document. The server renders only the reserved box.\n * - **No layout shift.** `reservedMinHeight` applies to the box in EVERY state — the server\n * placeholder, loading, the refusals and the loaded component — so the page does not jump when the\n * component arrives. Set it to the component's rendered height.\n * - **The vendor's scripts load with the component**, not on every page: the layout injects at startup\n * only the scripts of providers that contribute no component.\n * - **Consent before download.** A provider resting on a purpose the visitor has not granted downloads\n * nothing; the box states that the feature is off, names the purposes it rests on, and offers the\n * consent panel (`openSettings`). Granting there loads the component in place.\n * - **Announced, and operable by keyboard.** The box holds a `role=\"status\"` live region from the first\n * render, so a refusal is announced when it appears; the consent button sits outside it, so it is not\n * read out as part of the message, and is a real button.\n * - **A failure says so.** A component that could not load renders the site's `unavailable` wording,\n * never a blank. A provider that offers no component here (left off in this environment) renders the\n * empty box, since there is nothing to retry.\n *\n * The words come from the site's label pack: `website.chrome.provider-component.consent-required` and\n * `.unavailable` are framework-owned keys every pack must carry, and the purpose names and the button\n * reuse the consent panel's keys. Pass `renderConsentRequired` to word the refusal in the page's own\n * chrome instead; it receives the missing purposes and the action that opens the panel, and then owns\n * its own announcement.\n *\n * It does not choose a provider. Pass the ref the page chose — `useWebsiteProviderComponentChoices`\n * lists the candidates — or `undefined` while nothing is chosen.\n */\nexport interface WebsiteProviderComponentProps<TProps extends object> {\n /** The capability the component fills, e.g. `FRONTEND_ADDRESS_LOOKUP`. */\n capability: ProviderCapability;\n /** The provider the page chose, or `undefined` while nothing is chosen. */\n providerRef: string | undefined;\n /** The props the loaded component receives — the capability's slot contract. */\n componentProps: TProps;\n /** CSS length reserved for the box in every state, so the page does not shift when the component loads. */\n reservedMinHeight?: string;\n /** Replaces the default consent-required state. */\n renderConsentRequired?: (input: { missingPurposes: readonly ConsentPurpose[]; openConsentSettings: () => void }) => ReactNode;\n}\n\nexport function WebsiteProviderComponent<TProps extends object>({\n capability,\n providerRef,\n componentProps,\n reservedMinHeight,\n renderConsentRequired,\n}: WebsiteProviderComponentProps<TProps>): ReactNode {\n const consent = useWebsiteConsent();\n const { status, Component, missingConsentPurposes } = useWebsiteProviderComponent(capability, providerRef);\n\n const isReady = status === ProviderComponentStatus.READY && Component !== null;\n const isConsentRequired = status === ProviderComponentStatus.CONSENT_REQUIRED;\n const isUnavailable = status === ProviderComponentStatus.UNAVAILABLE;\n const useDefaultRefusal = isUnavailable || (isConsentRequired && renderConsentRequired === undefined);\n // The registry cannot know a capability's props; the page states them through `componentProps`.\n const Loaded = isReady ? (Component as unknown as React.ComponentType<TProps>) : null;\n\n return (\n <div\n data-website-provider-component={capability}\n data-website-provider-component-state={status}\n aria-busy={status === ProviderComponentStatus.LOADING ? true : undefined}\n style={reservedMinHeight !== undefined ? { minHeight: reservedMinHeight } : undefined}\n >\n {Loaded !== null ? <Loaded {...componentProps} /> : null}\n {isConsentRequired && renderConsentRequired !== undefined\n ? renderConsentRequired({ missingPurposes: missingConsentPurposes, openConsentSettings: consent.openSettings })\n : null}\n {Loaded === null ? (\n <div style={useDefaultRefusal ? STATE_STYLE : undefined}>\n <div role=\"status\">\n {useDefaultRefusal ? <RefusalStatement status={status} missingPurposes={missingConsentPurposes} /> : null}\n </div>\n {isConsentRequired && renderConsentRequired === undefined ? <ManageConsentButton onClick={consent.openSettings} /> : null}\n </div>\n ) : null}\n </div>\n );\n}\n/** @wildo_source:part:end saas.website.provider-component.slot */\n"]}
|
|
1
|
+
{"version":3,"file":"WebsiteProviderComponent.js","sourceRoot":"","sources":["../../../../../src/core/external-providers/WebsiteProviderComponent.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, { type ReactNode } from 'react';\nimport { ProviderComponentStatus } from '@wildo-ai/external-connectors-public/website';\nimport type { ConsentPurpose, ProviderCapability } from '@wildo-ai/saas-models/public-runtime';\n\nimport { websiteDesignTokenCssVar } from '../../schemas/design-tokens/website-design-tokens.shared.schemas';\nimport {\n WebsiteConsentLabelSlot,\n websiteConsentLabelKey,\n websiteConsentPurposeLabelKey,\n} from '../../schemas/label-keys/website-consent-label-keys.schemas';\nimport {\n WebsiteProviderComponentLabelSlot,\n websiteProviderComponentLabelKey,\n} from '../../schemas/label-keys/website-provider-component-label-keys.schemas';\nimport { useWebsiteConsent } from '../consent/useWebsiteConsent';\nimport { useWebsiteLabelByKey } from '../hooks/useWebsiteLabel';\nimport { useWebsiteProviderComponent } from './useWebsiteProviderComponent';\n\nconst STATE_STYLE: React.CSSProperties = {\n display: 'flex',\n flexDirection: 'column',\n gap: '0.5rem',\n padding: '0.75rem',\n border: `1px solid ${websiteDesignTokenCssVar('colors', 'border', '#ccc')}`,\n borderRadius: websiteDesignTokenCssVar('radii', 'md', '0.375rem'),\n};\n\nconst BUTTON_STYLE: React.CSSProperties = {\n alignSelf: 'flex-start',\n padding: '0.5rem 1rem',\n borderRadius: websiteDesignTokenCssVar('radii', 'md', '0.375rem'),\n border: `1px solid ${websiteDesignTokenCssVar('colors', 'border', '#ccc')}`,\n background: websiteDesignTokenCssVar('colors', 'background', '#fff'),\n color: websiteDesignTokenCssVar('colors', 'foreground', '#000'),\n cursor: 'pointer',\n};\n\nfunction PurposeName({ purpose }: { purpose: ConsentPurpose }): ReactNode {\n return <li data-website-provider-component-purpose={purpose}>{useWebsiteLabelByKey(websiteConsentPurposeLabelKey(purpose))}</li>;\n}\n\n/**\n * The words announced for a refusal. Rendered INTO the always-present live region, so a screen reader\n * announces the change when it happens; a region inserted already filled is often not announced at all.\n */\nfunction RefusalStatement({ status, missingPurposes }: { status: ProviderComponentStatus; missingPurposes: readonly ConsentPurpose[] }): ReactNode {\n const consentRequired = useWebsiteLabelByKey(websiteProviderComponentLabelKey(WebsiteProviderComponentLabelSlot.CONSENT_REQUIRED));\n const unavailable = useWebsiteLabelByKey(websiteProviderComponentLabelKey(WebsiteProviderComponentLabelSlot.UNAVAILABLE));\n if (status === ProviderComponentStatus.UNAVAILABLE) return <p style={{ margin: 0 }}>{unavailable}</p>;\n return (\n <>\n <p style={{ margin: 0 }}>{consentRequired}</p>\n <ul style={{ margin: 0 }}>\n {missingPurposes.map((purpose) => <PurposeName key={purpose} purpose={purpose} />)}\n </ul>\n </>\n );\n}\n\nfunction ManageConsentButton({ onClick }: { onClick: () => void }): ReactNode {\n return <button type=\"button\" style={BUTTON_STYLE} onClick={onClick}>{useWebsiteLabelByKey(websiteConsentLabelKey(WebsiteConsentLabelSlot.MANAGE))}</button>;\n}\n\n/**\n * @wildo_source:part:start saas.website.provider-component.slot facet:layer:core facet:family:website\n *\n * `<WebsiteProviderComponent>` — places ONE provider's component on a website page (#1703): an\n * address lookup on a contact form, a vendor widget on a marketing page. The component comes from the\n * provider's own `components[capability]` declaration, the same one the application reads, through\n * the website's provider-component registry.\n *\n * What it guarantees, so a page does not have to:\n *\n * - **Nothing of the vendor reaches the static HTML.** The component loads from an effect, which never\n * runs during the Astro server render, and the registry itself refuses to load where there is no\n * browser document. The server renders only the reserved box.\n * - **No layout shift.** `reservedMinHeight` applies to the box in EVERY state — the server\n * placeholder, loading, the refusals and the loaded component — so the page does not jump when the\n * component arrives. Set it to the component's rendered height.\n * - **The vendor's scripts load with the component**, not on every page: the layout injects at startup\n * only the scripts of providers that contribute no component.\n * - **Consent before download.** A provider resting on a purpose the visitor has not granted downloads\n * nothing; the box states that the feature is off, names the purposes it rests on, and offers the\n * consent panel (`openSettings`). Granting there loads the component in place.\n * - **Announced, and operable by keyboard.** The box holds a `role=\"status\"` live region from the first\n * render, so a refusal is announced when it appears; the consent button sits outside it, so it is not\n * read out as part of the message, and is a real button.\n * - **A failure says so.** A component that could not load renders the site's `unavailable` wording,\n * never a blank. A provider that offers no component here (left off in this environment) renders the\n * empty box, since there is nothing to retry.\n *\n * The words come from the site's label pack: `website.chrome.provider-component.consent-required` and\n * `.unavailable` are framework-owned keys every pack must carry, and the purpose names and the button\n * reuse the consent panel's keys. Pass `renderConsentRequired` to word the refusal in the page's own\n * chrome instead; it receives the missing purposes and the action that opens the panel, and then owns\n * its own announcement.\n *\n * It does not choose a provider. Pass the ref the page chose — `useWebsiteProviderComponentChoices`\n * lists the candidates — or `undefined` while nothing is chosen.\n */\nexport interface WebsiteProviderComponentProps<TProps extends object> {\n /** The capability the component fills, e.g. `FRONTEND_ADDRESS_LOOKUP`. */\n capability: ProviderCapability;\n /** The provider the page chose, or `undefined` while nothing is chosen. */\n providerRef: string | undefined;\n /** The props the loaded component receives — the capability's slot contract. */\n componentProps: TProps;\n /** CSS length reserved for the box in every state, so the page does not shift when the component loads. */\n reservedMinHeight?: string;\n /** Replaces the default consent-required state. */\n renderConsentRequired?: (input: { missingPurposes: readonly ConsentPurpose[]; openConsentSettings: () => void }) => ReactNode;\n}\n\nexport function WebsiteProviderComponent<TProps extends object>({\n capability,\n providerRef,\n componentProps,\n reservedMinHeight,\n renderConsentRequired,\n}: WebsiteProviderComponentProps<TProps>): ReactNode {\n const consent = useWebsiteConsent();\n const { status, Component, missingConsentPurposes } = useWebsiteProviderComponent(capability, providerRef);\n\n const isReady = status === ProviderComponentStatus.READY && Component !== null;\n const isConsentRequired = status === ProviderComponentStatus.CONSENT_REQUIRED;\n const isUnavailable = status === ProviderComponentStatus.UNAVAILABLE;\n const useDefaultRefusal = isUnavailable || (isConsentRequired && renderConsentRequired === undefined);\n // The registry cannot know a capability's props; the page states them through `componentProps`.\n const Loaded = isReady ? (Component as unknown as React.ComponentType<TProps>) : null;\n\n return (\n <div\n data-website-provider-component={capability}\n data-website-provider-component-state={status}\n aria-busy={status === ProviderComponentStatus.LOADING ? true : undefined}\n style={reservedMinHeight !== undefined ? { minHeight: reservedMinHeight } : undefined}\n >\n {Loaded !== null ? <Loaded {...componentProps} /> : null}\n {isConsentRequired && renderConsentRequired !== undefined\n ? renderConsentRequired({ missingPurposes: missingConsentPurposes, openConsentSettings: consent.openSettings })\n : null}\n {Loaded === null ? (\n <div style={useDefaultRefusal ? STATE_STYLE : undefined}>\n <div role=\"status\">\n {useDefaultRefusal ? <RefusalStatement status={status} missingPurposes={missingConsentPurposes} /> : null}\n </div>\n {isConsentRequired && renderConsentRequired === undefined ? <ManageConsentButton onClick={consent.openSettings} /> : null}\n </div>\n ) : null}\n </div>\n );\n}\n/** @wildo_source:part:end saas.website.provider-component.slot */\n"]}
|