@vtex/faststore 0.0.2 → 0.0.3
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/CHANGELOG.md +26 -0
- package/README.md +4 -2
- package/dist/catalog.json +286 -265
- package/dist/components.d.ts +2 -1
- package/dist/components.js +2 -1
- package/dist/content-types.json +16 -0
- package/dist/core/dist/actions/registry.d.ts +1 -1
- package/dist/core/dist/actions/registry.js +1 -1
- package/dist/core/dist/actions/registry.js.map +1 -1
- package/dist/docs/cart.md +2 -2
- package/dist/docs/session.md +12 -6
- package/dist/docs/sign-in-redirect.md +137 -0
- package/dist/matter/access-code-sign-in/access-code-sign-in.d.ts +2 -2
- package/dist/matter/access-code-sign-in/access-code-sign-in.js +5 -2
- package/dist/matter/access-code-sign-in/access-code-sign-in.js.map +1 -1
- package/dist/matter/buy-button/buy-button.d.ts +1 -1
- package/dist/matter/buy-button/buy-button.js +3 -1
- package/dist/matter/buy-button/buy-button.js.map +1 -1
- package/dist/matter/cart/cart.d.ts +6 -6
- package/dist/matter/cart/cart.js +13 -6
- package/dist/matter/cart/cart.js.map +1 -1
- package/dist/matter/cart-item/cart-item.d.ts +2 -2
- package/dist/matter/cart-item/cart-item.js +5 -2
- package/dist/matter/cart-item/cart-item.js.map +1 -1
- package/dist/matter/client-preferences/client-preferences.js +3 -1
- package/dist/matter/client-preferences/client-preferences.js.map +1 -1
- package/dist/matter/client-profile/client-profile.d.ts +1 -1
- package/dist/matter/client-profile/client-profile.js +3 -1
- package/dist/matter/client-profile/client-profile.js.map +1 -1
- package/dist/matter/client-profile-form/client-profile-form.d.ts +1 -1
- package/dist/matter/client-profile-form/client-profile-form.js +3 -1
- package/dist/matter/client-profile-form/client-profile-form.js.map +1 -1
- package/dist/matter/delivery-options/delivery-options-accordion.d.ts +1 -1
- package/dist/matter/delivery-options/delivery-options-accordion.js +3 -1
- package/dist/matter/delivery-options/delivery-options-accordion.js.map +1 -1
- package/dist/matter/delivery-options/delivery-options-controller.d.ts +1 -1
- package/dist/matter/delivery-options/delivery-options-controller.js +3 -1
- package/dist/matter/delivery-options/delivery-options-controller.js.map +1 -1
- package/dist/matter/delivery-options/delivery-options.d.ts +1 -1
- package/dist/matter/delivery-options/delivery-options.js +3 -1
- package/dist/matter/delivery-options/delivery-options.js.map +1 -1
- package/dist/matter/email-identification/email-identification.d.ts +1 -1
- package/dist/matter/email-identification/email-identification.js +3 -1
- package/dist/matter/email-identification/email-identification.js.map +1 -1
- package/dist/matter/identity-summary/identity-summary.d.ts +1 -1
- package/dist/matter/identity-summary/identity-summary.js +3 -1
- package/dist/matter/identity-summary/identity-summary.js.map +1 -1
- package/dist/matter/product-list/product-list.d.ts +1 -1
- package/dist/matter/product-list/product-list.js +3 -1
- package/dist/matter/product-list/product-list.js.map +1 -1
- package/dist/matter/region/region.d.ts +1 -1
- package/dist/matter/region/region.js +5 -2
- package/dist/matter/region/region.js.map +1 -1
- package/dist/matter/session/session.js +1 -1
- package/dist/matter/session/session.js.map +1 -1
- package/dist/matter/shipping-address/shipping-address.d.ts +1 -1
- package/dist/matter/shipping-address/shipping-address.js +3 -1
- package/dist/matter/shipping-address/shipping-address.js.map +1 -1
- package/dist/matter/sign-in-form/sign-in-form.d.ts +1 -1
- package/dist/matter/sign-in-form/sign-in-form.js +3 -1
- package/dist/matter/sign-in-form/sign-in-form.js.map +1 -1
- package/dist/matter/sign-in-redirect/sign-in-redirect.d.ts +45 -0
- package/dist/matter/sign-in-redirect/sign-in-redirect.js +127 -0
- package/dist/matter/sign-in-redirect/sign-in-redirect.js.map +1 -0
- package/dist/sdk/actions/actions-override-context.js +37 -0
- package/dist/sdk/actions/actions-override-context.js.map +1 -0
- package/dist/sdk/actions/cart-action-provider.d.ts +8 -6
- package/dist/sdk/actions/cart-action-provider.js +10 -8
- package/dist/sdk/actions/cart-action-provider.js.map +1 -1
- package/dist/sdk/actions/identity-action-provider.d.ts +2 -2
- package/dist/sdk/actions/identity-action-provider.js +4 -4
- package/dist/sdk/actions/identity-action-provider.js.map +1 -1
- package/dist/sdk/actions/inject-actions.d.ts +1 -5
- package/dist/sdk/actions/inject-actions.js +23 -21
- package/dist/sdk/actions/inject-actions.js.map +1 -1
- package/dist/sdk/actions/region-action-provider.d.ts +4 -3
- package/dist/sdk/actions/region-action-provider.js +6 -5
- package/dist/sdk/actions/region-action-provider.js.map +1 -1
- package/dist/sdk/actions/session-action-provider.d.ts +4 -3
- package/dist/sdk/actions/session-action-provider.js +6 -5
- package/dist/sdk/actions/session-action-provider.js.map +1 -1
- package/dist/sdk/actions/shipping-action-provider.d.ts +4 -3
- package/dist/sdk/actions/shipping-action-provider.js +6 -5
- package/dist/sdk/actions/shipping-action-provider.js.map +1 -1
- package/dist/sdk/cart/use-cart-actions.js.map +1 -1
- package/dist/sdk/client/create-client.js +24 -2
- package/dist/sdk/client/create-client.js.map +1 -1
- package/dist/sdk/content/create-content-runtime.js +1 -1
- package/dist/utils/safe-href.js +74 -4
- package/dist/utils/safe-href.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access-code-sign-in.js","names":[],"sources":["../../../src/matter/access-code-sign-in/access-code-sign-in.tsx"],"sourcesContent":["'use client';\n\nimport { defineComponent } from '@vtex/fs-core';\nimport type { ComponentPropsWithRef, FormEvent } from 'react';\nimport { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';\nimport { AccessCodeField } from '../../particles/access-code-field/access-code-field.js';\nimport { EmailField, isValidEmail } from '../../particles/email-field/email-field.js';\nimport type { ComponentActions } from '../../sdk/actions/types.js';\nimport { useDataOverride } from '../../sdk/data/data-override-context.js';\nimport type { SessionUserError } from '../../sdk/session/auth-operations.js';\nimport { useSession } from '../session/session.js';\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragment (colocated, ADR-0011) — clientProfile.email seeds the draft email as a\n// convenience pre-fill for an already-identified shopper; sessionAuthProviders.accessKeyAuthentication\n// is the account gate. Authored as a raw string, mirroring EmailIdentificationControl/ShippingSimulator.\n// ---------------------------------------------------------------------------\n\nconst fragment = /* GraphQL */ `\n fragment AccessCodeSignInData on Query {\n clientProfile {\n __typename\n id\n email\n }\n sessionAuthProviders {\n accessKeyAuthentication\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Colocated fragment slice forwarded by the reactive runtime — `null` until both fields resolve. */\nexport type AccessCodeSignInSlice = {\n clientProfile: { email: string | null } | null;\n sessionAuthProviders: { accessKeyAuthentication: boolean } | null;\n};\n\nexport type AccessCodeSignInStep = 'request' | 'validate';\n\nexport type AccessCodeSignInContextValue = Readonly<{\n /** The shopper's locally-typed draft email — never written to the cart; only ever an argument to\n * requestAccessCode/validateAccessCode. Pre-fills from `clientProfile.email` when known, but a\n * shopper edit is never persisted anywhere by this family. */\n email: string;\n /** Updates the draft email; marks it as shopper-touched so pre-fill no longer overwrites it. */\n setEmail: (email: string) => void;\n step: AccessCodeSignInStep;\n /** Opaque token round-tripped from requestAccessCode into validateAccessCode. */\n authenticationToken: string | null;\n /** Called by AccessCodeRequestControl on a successful requestAccessCode. */\n startValidation: (authenticationToken: string) => void;\n}>;\n\nexport type AccessCodeSignInRootProps = ComponentPropsWithRef<'div'> & {\n $data?: AccessCodeSignInSlice | null;\n};\n\nexport type AccessCodeEmailControlProps = ComponentPropsWithRef<'div'> & {\n label?: string;\n placeholder?: string;\n invalidEmailMessage?: string;\n};\n\nexport type AccessCodeRequestControlProps = ComponentPropsWithRef<'div'> & {\n label?: string;\n pendingLabel?: string;\n sendLimitMessage?: string;\n blockedMessage?: string;\n networkErrorMessage?: string;\n onRequested?: (authenticationToken: string) => void;\n $actions?: ComponentActions;\n};\n\nexport type AccessCodeValidateControlProps = ComponentPropsWithRef<'div'> & {\n label?: string;\n placeholder?: string;\n submitLabel?: string;\n codeLength?: number;\n invalidCodeMessage?: string;\n blockedMessage?: string;\n missingAuthContextMessage?: string;\n networkErrorMessage?: string;\n onValidated?: () => void;\n $actions?: ComponentActions;\n};\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\nconst ACCESS_CODE_SIGN_IN_CONTEXT_UNSET = Symbol('AccessCodeSignInContextUnset');\n\nconst AccessCodeSignInContext = createContext<AccessCodeSignInContextValue | typeof ACCESS_CODE_SIGN_IN_CONTEXT_UNSET>(\n ACCESS_CODE_SIGN_IN_CONTEXT_UNSET,\n);\n\n/**\n * Read the stepper state (email, step, authenticationToken) from the nearest `AccessCodeSignInRoot`.\n * Throws when called outside that subtree — a misplaced control is a composition bug that should\n * surface immediately, mirroring `useShippingSimulator()`/`useSession()`.\n */\nfunction useAccessCodeSignIn(): AccessCodeSignInContextValue {\n const ctx = useContext(AccessCodeSignInContext);\n if (ctx === ACCESS_CODE_SIGN_IN_CONTEXT_UNSET) {\n throw new Error('AccessCodeSignIn components must be used inside AccessCodeSignInRoot.');\n }\n return ctx;\n}\n\n// ---------------------------------------------------------------------------\n// Error-code → message mapping — every failure surfaces inline, at the control that produced it;\n// an unmapped code falls back to the server-supplied message rather than being silent.\n// ---------------------------------------------------------------------------\n\nfunction requestErrorMessage(\n error: SessionUserError,\n props: Pick<AccessCodeRequestControlProps, 'sendLimitMessage' | 'blockedMessage' | 'networkErrorMessage'>,\n): string {\n switch (error.code) {\n case 'ACCESS_CODE_SEND_LIMIT':\n return props.sendLimitMessage ?? error.message;\n case 'BLOCKED':\n return props.blockedMessage ?? error.message;\n case 'NETWORK_ERROR':\n return props.networkErrorMessage ?? error.message;\n default:\n return error.message;\n }\n}\n\nfunction validateErrorMessage(\n error: SessionUserError,\n props: Pick<\n AccessCodeValidateControlProps,\n 'invalidCodeMessage' | 'blockedMessage' | 'missingAuthContextMessage' | 'networkErrorMessage'\n >,\n): string {\n switch (error.code) {\n case 'INVALID_ACCESS_CODE':\n return props.invalidCodeMessage ?? error.message;\n case 'BLOCKED':\n return props.blockedMessage ?? error.message;\n case 'MISSING_AUTH_CONTEXT':\n return props.missingAuthContextMessage ?? error.message;\n case 'NETWORK_ERROR':\n return props.networkErrorMessage ?? error.message;\n default:\n return error.message;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const AccessCodeSignInRootDefinition = defineComponent<AccessCodeSignInRootProps>({\n name: 'AccessCodeSignInRoot',\n importPath: '@vtex/faststore/components',\n component: AccessCodeSignInRoot,\n fragment,\n fetchStrategy: 'reactive',\n requiresContext: 'session',\n editor: { label: 'Access Code Sign-In', category: 'identity' },\n schema: {},\n guideline:\n 'Self-gating root of the anonymous-or-identified, email + access-code sign-in: renders nothing once loggedIn or when the account disables accessKeyAuthentication, but is available from the anonymous state onward. Owns a locally-typed draft email (never written to the cart) and the request/validate stepper. Must be inside SessionRoot. See access-code-sign-in.md.',\n});\n\nexport const AccessCodeEmailControlDefinition = defineComponent<AccessCodeEmailControlProps>({\n name: 'AccessCodeEmailControl',\n importPath: '@vtex/faststore/components',\n component: AccessCodeEmailControl,\n editor: { label: 'Access Code Email', category: 'identity' },\n schema: {\n label: { type: 'string', label: 'Label' },\n placeholder: { type: 'string', label: 'Placeholder' },\n invalidEmailMessage: { type: 'string', label: 'Invalid email message' },\n },\n guideline:\n 'The draft email input for the access-code flow: visible only at the request step, pre-fills from clientProfile.email when known but never persists an edit anywhere — it is purely the argument requestAccessCode/validateAccessCode dispatch with. Must be inside AccessCodeSignInRoot. See access-code-sign-in.md.',\n});\n\nexport const AccessCodeRequestControlDefinition = defineComponent<AccessCodeRequestControlProps>({\n name: 'AccessCodeRequestControl',\n importPath: '@vtex/faststore/components',\n component: AccessCodeRequestControl,\n actions: { session: ['requestAccessCode'] },\n editor: { label: 'Access Code Request', category: 'identity' },\n schema: {\n label: { type: 'string', label: 'Label' },\n sendLimitMessage: { type: 'string', label: 'Send-limit message' },\n blockedMessage: { type: 'string', label: 'Blocked message' },\n networkErrorMessage: { type: 'string', label: 'Network error message' },\n },\n guideline:\n 'Send-code step: disabled until the Root-owned draft email is a valid, non-empty address; dispatches $actions.session.requestAccessCode with it and advances the stepper on success. Failures render inline, never as a blocking error. Must be inside AccessCodeSignInRoot. See access-code-sign-in.md.',\n});\n\nexport const AccessCodeValidateControlDefinition = defineComponent<AccessCodeValidateControlProps>({\n name: 'AccessCodeValidateControl',\n importPath: '@vtex/faststore/components',\n component: AccessCodeValidateControl,\n actions: { session: ['validateAccessCode'] },\n editor: { label: 'Access Code Validate', category: 'identity' },\n schema: {\n label: { type: 'string', label: 'Label' },\n placeholder: { type: 'string', label: 'Placeholder' },\n submitLabel: { type: 'string', label: 'Submit label' },\n codeLength: { type: 'number', default: 6, label: 'Code length' },\n invalidCodeMessage: { type: 'string', label: 'Invalid code message' },\n blockedMessage: { type: 'string', label: 'Blocked message' },\n networkErrorMessage: { type: 'string', label: 'Network error message' },\n },\n guideline:\n 'Code-entry step: composes AccessCodeField and dispatches $actions.session.validateAccessCode with the Root-owned email and authenticationToken; a successful validate flips the session reactively and hides the Root. Must be inside AccessCodeSignInRoot. See access-code-sign-in.md.',\n});\n\n// ---------------------------------------------------------------------------\n// Implementations\n// ---------------------------------------------------------------------------\n\n/**\n * Data-owning root of the access-code sign-in flow. Self-gates on two signals:\n * `useSession().state !== 'loggedIn'` (available from anonymous through identified; hides once\n * signed in) and `sessionAuthProviders.accessKeyAuthentication === true`. An unresolved fragment\n * read (`$data` still `null`) is treated as \"provider not enabled\" — the flow stays hidden rather\n * than flashing. Owns the `step`/`authenticationToken` stepper plus a locally-typed draft `email`\n * as local state, broadcasting all of it through context to its children. The draft email pre-fills\n * from `clientProfile.email` once (for an already-identified shopper) but a shopper edit is never\n * persisted anywhere by this family — it is purely the requestAccessCode/validateAccessCode\n * argument; the server sets clientProfileData.email as a side effect of a successful validate.\n * Reacts automatically once `useSession()` flips to `loggedIn` — no manual unmount call anywhere in\n * this family.\n */\nexport function AccessCodeSignInRoot({ $data: $dataProp, className, children, ...props }: AccessCodeSignInRootProps) {\n const $data = useDataOverride($dataProp);\n const { state } = useSession();\n\n const [step, setStep] = useState<AccessCodeSignInStep>('request');\n const [authenticationToken, setAuthenticationToken] = useState<string | null>(null);\n\n const startValidation = useCallback((token: string) => {\n setAuthenticationToken(token);\n setStep('validate');\n }, []);\n\n const savedEmail = $data?.clientProfile?.email ?? '';\n const [email, setEmailState] = useState('');\n const [touched, setTouched] = useState(false);\n\n // Pre-fill from the resolving/known profile email, but only while still at the request step and\n // until the shopper edits the draft — mirrors ClientProfileForm's untouched-field re-seed, never\n // clobbering an in-progress edit. Freezing the re-seed once the flow leaves 'request' keeps a\n // mid-flow profile-cache change from diverging the email between requestAccessCode and\n // validateAccessCode (the server rejects a validate whose email differs from the request).\n useEffect(() => {\n if (step === 'request' && !touched) setEmailState(savedEmail);\n }, [savedEmail, touched, step]);\n\n const setEmail = useCallback((next: string) => {\n setTouched(true);\n setEmailState(next);\n }, []);\n\n const providerEnabled = $data?.sessionAuthProviders?.accessKeyAuthentication ?? false;\n\n const contextValue = useMemo<AccessCodeSignInContextValue>(\n () => ({ email, setEmail, step, authenticationToken, startValidation }),\n [email, setEmail, step, authenticationToken, startValidation],\n );\n\n const gated = state !== 'loggedIn' && providerEnabled;\n if (!gated) return null;\n\n return (\n <AccessCodeSignInContext value={contextValue}>\n <div {...props} className={className} data-fs-access-code-sign-in-root=\"\">\n {children}\n </div>\n </AccessCodeSignInContext>\n );\n}\n\n/**\n * The draft email input for the access-code flow. Renders only at the request step. Bound to the\n * Root's local `email`/`setEmail` — never a fragment, never a mutation of its own; typing here\n * never touches the cart. Format validation is `EmailField`'s own blur-triggered check (never\n * per-keystroke); `invalidEmailMessage` is only its fallback text, mirroring how\n * `EmailIdentificationControl` defers to the same particle for the same reason.\n */\nexport function AccessCodeEmailControl({\n label,\n placeholder,\n invalidEmailMessage,\n className,\n ...props\n}: AccessCodeEmailControlProps) {\n const { step, email, setEmail } = useAccessCodeSignIn();\n\n if (step !== 'request') return null;\n\n return (\n <div {...props} className={className} data-fs-access-code-email-control=\"\">\n <EmailField\n label={label}\n placeholder={placeholder}\n value={email}\n onValueChange={setEmail}\n errorMessage={invalidEmailMessage}\n />\n </div>\n );\n}\n\n/**\n * Renders only while the Root's stepper is at `step === 'request'`. Reads the draft email from\n * context and dispatches `$actions.session.requestAccessCode({ email })` once it is a valid,\n * non-empty address (no account-existence pre-check beyond that format gate). On a result with a\n * non-null token and no `userErrors`, advances the Root's stepper. Every `userErrors` entry renders\n * inline via a host-supplied, per-code message; the step never changes on error, so the shopper can\n * retry or abandon the method and continue as a guest.\n */\nexport function AccessCodeRequestControl({\n label,\n pendingLabel,\n sendLimitMessage,\n blockedMessage,\n networkErrorMessage,\n onRequested,\n $actions,\n className,\n ...props\n}: AccessCodeRequestControlProps) {\n const { step, email, startValidation } = useAccessCodeSignIn();\n const [pending, setPending] = useState(false);\n const [error, setError] = useState<SessionUserError | null>(null);\n\n if (step !== 'request') return null;\n\n const canSubmit = !pending && isValidEmail(email);\n\n async function handleClick() {\n if (!canSubmit) return;\n setPending(true);\n setError(null);\n\n try {\n const result = (await $actions?.session?.requestAccessCode?.({ email })) ?? {\n authenticationToken: null,\n userErrors: [],\n };\n\n if (result.authenticationToken && result.userErrors.length === 0) {\n startValidation(result.authenticationToken);\n onRequested?.(result.authenticationToken);\n return;\n }\n\n const [firstError] = result.userErrors;\n if (firstError) setError(firstError);\n } finally {\n setPending(false);\n }\n }\n\n const message = error ? requestErrorMessage(error, { sendLimitMessage, blockedMessage, networkErrorMessage }) : null;\n\n return (\n <div\n {...props}\n className={className}\n data-fs-access-code-request-control=\"\"\n {...(pending ? { 'data-fs-access-code-request-pending': '' } : {})}\n >\n <button\n type=\"button\"\n onClick={handleClick}\n disabled={!canSubmit}\n aria-busy={pending || undefined}\n data-fs-access-code-request-submit=\"\"\n data-fs-button=\"\"\n >\n {pending ? (pendingLabel ?? label) : label}\n </button>\n {message ? (\n <div role=\"alert\" data-fs-access-code-request-error=\"\">\n {message}\n </div>\n ) : null}\n </div>\n );\n}\n\n/**\n * Renders only while the Root's stepper is at `step === 'validate'`. Composes `AccessCodeField` for\n * the code value and a submit control, disabled below `codeLength` (a UX courtesy, never a\n * validity claim). Dispatches `$actions.session.validateAccessCode` with the email and\n * `authenticationToken` read from context. On a non-empty `userErrors` result, sets the field's\n * `invalid`/`errorMessage` from a host-supplied per-code message and retains the code/token so the\n * shopper can retry without re-requesting. On success it calls the optional `onValidated` and renders\n * no local \"success\" UI — `useSession()` flipping to `loggedIn` is what makes the whole Root\n * disappear.\n */\nexport function AccessCodeValidateControl({\n label,\n placeholder,\n submitLabel,\n codeLength = 6,\n invalidCodeMessage,\n blockedMessage,\n missingAuthContextMessage,\n networkErrorMessage,\n onValidated,\n $actions,\n className,\n ...props\n}: AccessCodeValidateControlProps) {\n const { step, email, authenticationToken } = useAccessCodeSignIn();\n const [code, setCode] = useState('');\n const [pending, setPending] = useState(false);\n const [error, setError] = useState<SessionUserError | null>(null);\n\n if (step !== 'validate') return null;\n\n const canSubmit = !pending && code.length >= codeLength && authenticationToken != null;\n\n async function handleSubmit(event: FormEvent<HTMLFormElement>) {\n event.preventDefault();\n if (!canSubmit || authenticationToken == null) return;\n\n setPending(true);\n setError(null);\n\n try {\n const errors =\n (await $actions?.session?.validateAccessCode?.({ email, accessCode: code, authenticationToken })) ?? [];\n\n const [firstError] = errors;\n if (firstError) {\n setError(firstError);\n return;\n }\n } finally {\n setPending(false);\n }\n\n onValidated?.();\n }\n\n function handleCodeChange(next: string) {\n setCode(next);\n if (error) setError(null);\n }\n\n const errorMessage = error\n ? validateErrorMessage(error, {\n invalidCodeMessage,\n blockedMessage,\n missingAuthContextMessage,\n networkErrorMessage,\n })\n : undefined;\n\n return (\n <div\n {...props}\n className={className}\n data-fs-access-code-validate-control=\"\"\n {...(pending ? { 'data-fs-access-code-validate-pending': '' } : {})}\n >\n <form onSubmit={handleSubmit}>\n <AccessCodeField\n label={label}\n placeholder={placeholder}\n value={code}\n maxLength={codeLength}\n onValueChange={handleCodeChange}\n disabled={pending}\n invalid={error != null}\n errorMessage={errorMessage}\n />\n <button\n type=\"submit\"\n disabled={!canSubmit}\n aria-busy={pending || undefined}\n data-fs-access-code-validate-submit=\"\"\n data-fs-button=\"\"\n >\n {submitLabel}\n </button>\n </form>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Namespace export\n// ---------------------------------------------------------------------------\n\nexport const AccessCodeSignIn = {\n Root: AccessCodeSignInRoot,\n Email: AccessCodeEmailControl,\n Request: AccessCodeRequestControl,\n Validate: AccessCodeValidateControl,\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"access-code-sign-in.js","names":[],"sources":["../../../src/matter/access-code-sign-in/access-code-sign-in.tsx"],"sourcesContent":["'use client';\n\nimport { defineComponent } from '@vtex/fs-core';\nimport type { ComponentPropsWithRef, FormEvent } from 'react';\nimport { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';\nimport { AccessCodeField } from '../../particles/access-code-field/access-code-field.js';\nimport { EmailField, isValidEmail } from '../../particles/email-field/email-field.js';\nimport { useActionsOverride } from '../../sdk/actions/actions-override-context.js';\nimport type { ComponentActions } from '../../sdk/actions/types.js';\nimport { useDataOverride } from '../../sdk/data/data-override-context.js';\nimport type { SessionUserError } from '../../sdk/session/auth-operations.js';\nimport { useSession } from '../session/session.js';\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragment (colocated, ADR-0011) — clientProfile.email seeds the draft email as a\n// convenience pre-fill for an already-identified shopper; sessionAuthProviders.accessKeyAuthentication\n// is the account gate. Authored as a raw string, mirroring EmailIdentificationControl/ShippingSimulator.\n// ---------------------------------------------------------------------------\n\nconst fragment = /* GraphQL */ `\n fragment AccessCodeSignInData on Query {\n clientProfile {\n __typename\n id\n email\n }\n sessionAuthProviders {\n accessKeyAuthentication\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Colocated fragment slice forwarded by the reactive runtime — `null` until both fields resolve. */\nexport type AccessCodeSignInSlice = {\n clientProfile: { email: string | null } | null;\n sessionAuthProviders: { accessKeyAuthentication: boolean } | null;\n};\n\nexport type AccessCodeSignInStep = 'request' | 'validate';\n\nexport type AccessCodeSignInContextValue = Readonly<{\n /** The shopper's locally-typed draft email — never written to the cart; only ever an argument to\n * requestAccessCode/validateAccessCode. Pre-fills from `clientProfile.email` when known, but a\n * shopper edit is never persisted anywhere by this family. */\n email: string;\n /** Updates the draft email; marks it as shopper-touched so pre-fill no longer overwrites it. */\n setEmail: (email: string) => void;\n step: AccessCodeSignInStep;\n /** Opaque token round-tripped from requestAccessCode into validateAccessCode. */\n authenticationToken: string | null;\n /** Called by AccessCodeRequestControl on a successful requestAccessCode. */\n startValidation: (authenticationToken: string) => void;\n}>;\n\nexport type AccessCodeSignInRootProps = ComponentPropsWithRef<'div'> & {\n $data?: AccessCodeSignInSlice | null;\n};\n\nexport type AccessCodeEmailControlProps = ComponentPropsWithRef<'div'> & {\n label?: string;\n placeholder?: string;\n invalidEmailMessage?: string;\n};\n\nexport type AccessCodeRequestControlProps = ComponentPropsWithRef<'div'> & {\n label?: string;\n pendingLabel?: string;\n sendLimitMessage?: string;\n blockedMessage?: string;\n networkErrorMessage?: string;\n onRequested?: (authenticationToken: string) => void;\n $actions?: ComponentActions;\n};\n\nexport type AccessCodeValidateControlProps = ComponentPropsWithRef<'div'> & {\n label?: string;\n placeholder?: string;\n submitLabel?: string;\n codeLength?: number;\n invalidCodeMessage?: string;\n blockedMessage?: string;\n missingAuthContextMessage?: string;\n networkErrorMessage?: string;\n onValidated?: () => void;\n $actions?: ComponentActions;\n};\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\nconst ACCESS_CODE_SIGN_IN_CONTEXT_UNSET = Symbol('AccessCodeSignInContextUnset');\n\nconst AccessCodeSignInContext = createContext<AccessCodeSignInContextValue | typeof ACCESS_CODE_SIGN_IN_CONTEXT_UNSET>(\n ACCESS_CODE_SIGN_IN_CONTEXT_UNSET,\n);\n\n/**\n * Read the stepper state (email, step, authenticationToken) from the nearest `AccessCodeSignInRoot`.\n * Throws when called outside that subtree — a misplaced control is a composition bug that should\n * surface immediately, mirroring `useShippingSimulator()`/`useSession()`.\n */\nfunction useAccessCodeSignIn(): AccessCodeSignInContextValue {\n const ctx = useContext(AccessCodeSignInContext);\n if (ctx === ACCESS_CODE_SIGN_IN_CONTEXT_UNSET) {\n throw new Error('AccessCodeSignIn components must be used inside AccessCodeSignInRoot.');\n }\n return ctx;\n}\n\n// ---------------------------------------------------------------------------\n// Error-code → message mapping — every failure surfaces inline, at the control that produced it;\n// an unmapped code falls back to the server-supplied message rather than being silent.\n// ---------------------------------------------------------------------------\n\nfunction requestErrorMessage(\n error: SessionUserError,\n props: Pick<AccessCodeRequestControlProps, 'sendLimitMessage' | 'blockedMessage' | 'networkErrorMessage'>,\n): string {\n switch (error.code) {\n case 'ACCESS_CODE_SEND_LIMIT':\n return props.sendLimitMessage ?? error.message;\n case 'BLOCKED':\n return props.blockedMessage ?? error.message;\n case 'NETWORK_ERROR':\n return props.networkErrorMessage ?? error.message;\n default:\n return error.message;\n }\n}\n\nfunction validateErrorMessage(\n error: SessionUserError,\n props: Pick<\n AccessCodeValidateControlProps,\n 'invalidCodeMessage' | 'blockedMessage' | 'missingAuthContextMessage' | 'networkErrorMessage'\n >,\n): string {\n switch (error.code) {\n case 'INVALID_ACCESS_CODE':\n return props.invalidCodeMessage ?? error.message;\n case 'BLOCKED':\n return props.blockedMessage ?? error.message;\n case 'MISSING_AUTH_CONTEXT':\n return props.missingAuthContextMessage ?? error.message;\n case 'NETWORK_ERROR':\n return props.networkErrorMessage ?? error.message;\n default:\n return error.message;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const AccessCodeSignInRootDefinition = defineComponent<AccessCodeSignInRootProps>({\n name: 'AccessCodeSignInRoot',\n importPath: '@vtex/faststore/components',\n component: AccessCodeSignInRoot,\n fragment,\n fetchStrategy: 'reactive',\n requiresContext: 'session',\n editor: { label: 'Access Code Sign-In', category: 'identity' },\n schema: {},\n guideline:\n 'Self-gating root of the anonymous-or-identified, email + access-code sign-in: renders nothing once loggedIn or when the account disables accessKeyAuthentication, but is available from the anonymous state onward. Owns a locally-typed draft email (never written to the cart) and the request/validate stepper. Must be inside SessionRoot. See access-code-sign-in.md.',\n});\n\nexport const AccessCodeEmailControlDefinition = defineComponent<AccessCodeEmailControlProps>({\n name: 'AccessCodeEmailControl',\n importPath: '@vtex/faststore/components',\n component: AccessCodeEmailControl,\n editor: { label: 'Access Code Email', category: 'identity' },\n schema: {\n label: { type: 'string', label: 'Label' },\n placeholder: { type: 'string', label: 'Placeholder' },\n invalidEmailMessage: { type: 'string', label: 'Invalid email message' },\n },\n guideline:\n 'The draft email input for the access-code flow: visible only at the request step, pre-fills from clientProfile.email when known but never persists an edit anywhere — it is purely the argument requestAccessCode/validateAccessCode dispatch with. Must be inside AccessCodeSignInRoot. See access-code-sign-in.md.',\n});\n\nexport const AccessCodeRequestControlDefinition = defineComponent<AccessCodeRequestControlProps>({\n name: 'AccessCodeRequestControl',\n importPath: '@vtex/faststore/components',\n component: AccessCodeRequestControl,\n actions: { session: ['requestAccessCode'] },\n editor: { label: 'Access Code Request', category: 'identity' },\n schema: {\n label: { type: 'string', label: 'Label' },\n sendLimitMessage: { type: 'string', label: 'Send-limit message' },\n blockedMessage: { type: 'string', label: 'Blocked message' },\n networkErrorMessage: { type: 'string', label: 'Network error message' },\n },\n guideline:\n 'Send-code step: disabled until the Root-owned draft email is a valid, non-empty address; dispatches $actions.session.requestAccessCode with it and advances the stepper on success. Failures render inline, never as a blocking error. Must be inside AccessCodeSignInRoot. See access-code-sign-in.md.',\n});\n\nexport const AccessCodeValidateControlDefinition = defineComponent<AccessCodeValidateControlProps>({\n name: 'AccessCodeValidateControl',\n importPath: '@vtex/faststore/components',\n component: AccessCodeValidateControl,\n actions: { session: ['validateAccessCode'] },\n editor: { label: 'Access Code Validate', category: 'identity' },\n schema: {\n label: { type: 'string', label: 'Label' },\n placeholder: { type: 'string', label: 'Placeholder' },\n submitLabel: { type: 'string', label: 'Submit label' },\n codeLength: { type: 'number', default: 6, label: 'Code length' },\n invalidCodeMessage: { type: 'string', label: 'Invalid code message' },\n blockedMessage: { type: 'string', label: 'Blocked message' },\n networkErrorMessage: { type: 'string', label: 'Network error message' },\n },\n guideline:\n 'Code-entry step: composes AccessCodeField and dispatches $actions.session.validateAccessCode with the Root-owned email and authenticationToken; a successful validate flips the session reactively and hides the Root. Must be inside AccessCodeSignInRoot. See access-code-sign-in.md.',\n});\n\n// ---------------------------------------------------------------------------\n// Implementations\n// ---------------------------------------------------------------------------\n\n/**\n * Data-owning root of the access-code sign-in flow. Self-gates on two signals:\n * `useSession().state !== 'loggedIn'` (available from anonymous through identified; hides once\n * signed in) and `sessionAuthProviders.accessKeyAuthentication === true`. An unresolved fragment\n * read (`$data` still `null`) is treated as \"provider not enabled\" — the flow stays hidden rather\n * than flashing. Owns the `step`/`authenticationToken` stepper plus a locally-typed draft `email`\n * as local state, broadcasting all of it through context to its children. The draft email pre-fills\n * from `clientProfile.email` once (for an already-identified shopper) but a shopper edit is never\n * persisted anywhere by this family — it is purely the requestAccessCode/validateAccessCode\n * argument; the server sets clientProfileData.email as a side effect of a successful validate.\n * Reacts automatically once `useSession()` flips to `loggedIn` — no manual unmount call anywhere in\n * this family.\n */\nexport function AccessCodeSignInRoot({ $data: $dataProp, className, children, ...props }: AccessCodeSignInRootProps) {\n const $data = useDataOverride($dataProp);\n const { state } = useSession();\n\n const [step, setStep] = useState<AccessCodeSignInStep>('request');\n const [authenticationToken, setAuthenticationToken] = useState<string | null>(null);\n\n const startValidation = useCallback((token: string) => {\n setAuthenticationToken(token);\n setStep('validate');\n }, []);\n\n const savedEmail = $data?.clientProfile?.email ?? '';\n const [email, setEmailState] = useState('');\n const [touched, setTouched] = useState(false);\n\n // Pre-fill from the resolving/known profile email, but only while still at the request step and\n // until the shopper edits the draft — mirrors ClientProfileForm's untouched-field re-seed, never\n // clobbering an in-progress edit. Freezing the re-seed once the flow leaves 'request' keeps a\n // mid-flow profile-cache change from diverging the email between requestAccessCode and\n // validateAccessCode (the server rejects a validate whose email differs from the request).\n useEffect(() => {\n if (step === 'request' && !touched) setEmailState(savedEmail);\n }, [savedEmail, touched, step]);\n\n const setEmail = useCallback((next: string) => {\n setTouched(true);\n setEmailState(next);\n }, []);\n\n const providerEnabled = $data?.sessionAuthProviders?.accessKeyAuthentication ?? false;\n\n const contextValue = useMemo<AccessCodeSignInContextValue>(\n () => ({ email, setEmail, step, authenticationToken, startValidation }),\n [email, setEmail, step, authenticationToken, startValidation],\n );\n\n const gated = state !== 'loggedIn' && providerEnabled;\n if (!gated) return null;\n\n return (\n <AccessCodeSignInContext value={contextValue}>\n <div {...props} className={className} data-fs-access-code-sign-in-root=\"\">\n {children}\n </div>\n </AccessCodeSignInContext>\n );\n}\n\n/**\n * The draft email input for the access-code flow. Renders only at the request step. Bound to the\n * Root's local `email`/`setEmail` — never a fragment, never a mutation of its own; typing here\n * never touches the cart. Format validation is `EmailField`'s own blur-triggered check (never\n * per-keystroke); `invalidEmailMessage` is only its fallback text, mirroring how\n * `EmailIdentificationControl` defers to the same particle for the same reason.\n */\nexport function AccessCodeEmailControl({\n label,\n placeholder,\n invalidEmailMessage,\n className,\n ...props\n}: AccessCodeEmailControlProps) {\n const { step, email, setEmail } = useAccessCodeSignIn();\n\n if (step !== 'request') return null;\n\n return (\n <div {...props} className={className} data-fs-access-code-email-control=\"\">\n <EmailField\n label={label}\n placeholder={placeholder}\n value={email}\n onValueChange={setEmail}\n errorMessage={invalidEmailMessage}\n />\n </div>\n );\n}\n\n/**\n * Renders only while the Root's stepper is at `step === 'request'`. Reads the draft email from\n * context and dispatches `$actions.session.requestAccessCode({ email })` once it is a valid,\n * non-empty address (no account-existence pre-check beyond that format gate). On a result with a\n * non-null token and no `userErrors`, advances the Root's stepper. Every `userErrors` entry renders\n * inline via a host-supplied, per-code message; the step never changes on error, so the shopper can\n * retry or abandon the method and continue as a guest.\n */\nexport function AccessCodeRequestControl({\n label,\n pendingLabel,\n sendLimitMessage,\n blockedMessage,\n networkErrorMessage,\n onRequested,\n $actions: $actionsProp,\n className,\n ...props\n}: AccessCodeRequestControlProps) {\n const $actions = useActionsOverride($actionsProp);\n const { step, email, startValidation } = useAccessCodeSignIn();\n const [pending, setPending] = useState(false);\n const [error, setError] = useState<SessionUserError | null>(null);\n\n if (step !== 'request') return null;\n\n const canSubmit = !pending && isValidEmail(email);\n\n async function handleClick() {\n if (!canSubmit) return;\n setPending(true);\n setError(null);\n\n try {\n const result = (await $actions?.session?.requestAccessCode?.({ email })) ?? {\n authenticationToken: null,\n userErrors: [],\n };\n\n if (result.authenticationToken && result.userErrors.length === 0) {\n startValidation(result.authenticationToken);\n onRequested?.(result.authenticationToken);\n return;\n }\n\n const [firstError] = result.userErrors;\n if (firstError) setError(firstError);\n } finally {\n setPending(false);\n }\n }\n\n const message = error ? requestErrorMessage(error, { sendLimitMessage, blockedMessage, networkErrorMessage }) : null;\n\n return (\n <div\n {...props}\n className={className}\n data-fs-access-code-request-control=\"\"\n {...(pending ? { 'data-fs-access-code-request-pending': '' } : {})}\n >\n <button\n type=\"button\"\n onClick={handleClick}\n disabled={!canSubmit}\n aria-busy={pending || undefined}\n data-fs-access-code-request-submit=\"\"\n data-fs-button=\"\"\n >\n {pending ? (pendingLabel ?? label) : label}\n </button>\n {message ? (\n <div role=\"alert\" data-fs-access-code-request-error=\"\">\n {message}\n </div>\n ) : null}\n </div>\n );\n}\n\n/**\n * Renders only while the Root's stepper is at `step === 'validate'`. Composes `AccessCodeField` for\n * the code value and a submit control, disabled below `codeLength` (a UX courtesy, never a\n * validity claim). Dispatches `$actions.session.validateAccessCode` with the email and\n * `authenticationToken` read from context. On a non-empty `userErrors` result, sets the field's\n * `invalid`/`errorMessage` from a host-supplied per-code message and retains the code/token so the\n * shopper can retry without re-requesting. On success it calls the optional `onValidated` and renders\n * no local \"success\" UI — `useSession()` flipping to `loggedIn` is what makes the whole Root\n * disappear.\n */\nexport function AccessCodeValidateControl({\n label,\n placeholder,\n submitLabel,\n codeLength = 6,\n invalidCodeMessage,\n blockedMessage,\n missingAuthContextMessage,\n networkErrorMessage,\n onValidated,\n $actions: $actionsProp,\n className,\n ...props\n}: AccessCodeValidateControlProps) {\n const $actions = useActionsOverride($actionsProp);\n const { step, email, authenticationToken } = useAccessCodeSignIn();\n const [code, setCode] = useState('');\n const [pending, setPending] = useState(false);\n const [error, setError] = useState<SessionUserError | null>(null);\n\n if (step !== 'validate') return null;\n\n const canSubmit = !pending && code.length >= codeLength && authenticationToken != null;\n\n async function handleSubmit(event: FormEvent<HTMLFormElement>) {\n event.preventDefault();\n if (!canSubmit || authenticationToken == null) return;\n\n setPending(true);\n setError(null);\n\n try {\n const errors =\n (await $actions?.session?.validateAccessCode?.({ email, accessCode: code, authenticationToken })) ?? [];\n\n const [firstError] = errors;\n if (firstError) {\n setError(firstError);\n return;\n }\n } finally {\n setPending(false);\n }\n\n onValidated?.();\n }\n\n function handleCodeChange(next: string) {\n setCode(next);\n if (error) setError(null);\n }\n\n const errorMessage = error\n ? validateErrorMessage(error, {\n invalidCodeMessage,\n blockedMessage,\n missingAuthContextMessage,\n networkErrorMessage,\n })\n : undefined;\n\n return (\n <div\n {...props}\n className={className}\n data-fs-access-code-validate-control=\"\"\n {...(pending ? { 'data-fs-access-code-validate-pending': '' } : {})}\n >\n <form onSubmit={handleSubmit}>\n <AccessCodeField\n label={label}\n placeholder={placeholder}\n value={code}\n maxLength={codeLength}\n onValueChange={handleCodeChange}\n disabled={pending}\n invalid={error != null}\n errorMessage={errorMessage}\n />\n <button\n type=\"submit\"\n disabled={!canSubmit}\n aria-busy={pending || undefined}\n data-fs-access-code-validate-submit=\"\"\n data-fs-button=\"\"\n >\n {submitLabel}\n </button>\n </form>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Namespace export\n// ---------------------------------------------------------------------------\n\nexport const AccessCodeSignIn = {\n Root: AccessCodeSignInRoot,\n Email: AccessCodeEmailControl,\n Request: AccessCodeRequestControl,\n Validate: AccessCodeValidateControl,\n};\n"],"mappings":"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"}
|
|
@@ -84,7 +84,7 @@ export declare function BuyButtonRoot({ $data, selectedVariantId, children, ...p
|
|
|
84
84
|
export declare namespace BuyButtonRoot {
|
|
85
85
|
var Skeleton: typeof BuyButtonSkeleton;
|
|
86
86
|
}
|
|
87
|
-
export declare function BuyButtonButton({ availableLabel, unavailableLabel, className, children, onClick, $content, $actions, ...props }: BuyButtonButtonProps): import("react/jsx-runtime").JSX.Element | null;
|
|
87
|
+
export declare function BuyButtonButton({ availableLabel, unavailableLabel, className, children, onClick, $content, $actions: $actionsProp, ...props }: BuyButtonButtonProps): import("react/jsx-runtime").JSX.Element | null;
|
|
88
88
|
export declare function BuyButtonSkeleton({ className, ...props }: ComponentPropsWithRef<'div'>): import("react/jsx-runtime").JSX.Element;
|
|
89
89
|
export declare const BuyButton: {
|
|
90
90
|
Root: typeof BuyButtonRoot;
|
|
@@ -3,6 +3,7 @@ import { defineComponent } from "../../core/dist/define-component.js";
|
|
|
3
3
|
import { defineContentType } from "../../core/dist/define-content-type.js";
|
|
4
4
|
import "../../core/dist/index.js";
|
|
5
5
|
import { graphql } from "../../sdk/generated/gql.js";
|
|
6
|
+
import { useActionsOverride } from "../../sdk/actions/actions-override-context.js";
|
|
6
7
|
import { useDataOverride } from "../../sdk/data/data-override-context.js";
|
|
7
8
|
import { Button } from "../../particles/button/button.js";
|
|
8
9
|
import { HotDataProvider } from "../../sdk/data/hot-data-provider.js";
|
|
@@ -206,7 +207,8 @@ function BuyButtonView({ contextValue, className, children, ...props }) {
|
|
|
206
207
|
})
|
|
207
208
|
});
|
|
208
209
|
}
|
|
209
|
-
function BuyButtonButton({ availableLabel = "Comprar", unavailableLabel = "Indisponivel", className, children, onClick, $content, $actions, ...props }) {
|
|
210
|
+
function BuyButtonButton({ availableLabel = "Comprar", unavailableLabel = "Indisponivel", className, children, onClick, $content, $actions: $actionsProp, ...props }) {
|
|
211
|
+
const $actions = useActionsOverride($actionsProp);
|
|
210
212
|
const ctx = useBuyButton();
|
|
211
213
|
const [addItemPending, setAddItemPending] = useState(false);
|
|
212
214
|
const { quantity } = useProductQuantity();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buy-button.js","names":[],"sources":["../../../src/matter/buy-button/buy-button.tsx"],"sourcesContent":["'use client';\n\nimport { useReactiveVar } from '@apollo/client/react';\nimport { type ContentValue, defineComponent, defineContentType } from '@vtex/fs-core';\nimport type { ComponentPropsWithRef } from 'react';\nimport { createContext, Suspense, useContext, useState } from 'react';\nimport { Button } from '../../particles/button/button.js';\nimport type { ComponentActions } from '../../sdk/actions/types.js';\nimport { useDataOverride } from '../../sdk/data/data-override-context.js';\nimport { HotDataProvider } from '../../sdk/data/hot-data-provider.js';\nimport { graphql } from '../../sdk/generated/gql.js';\nimport type { VariantBuyButtonDataFragment } from '../../sdk/generated/graphql.js';\nimport { resolveDefaultSeller } from '../../sdk/seller.js';\nimport { publishedVariantIdFor, selectedVariantVar } from '../../sdk/state/variant-selector.js';\nimport { useProductQuantity } from '../product-quantity/product-quantity.js';\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragment\n// ---------------------------------------------------------------------------\n\nconst fragment = /* GraphQL */ `\n fragment BuyButtonData on Query {\n productBySlug(slug: $slug) {\n id\n title\n slug\n images(first: 1) {\n edges {\n node {\n url\n altText\n }\n }\n }\n variants(first: 1) {\n edges {\n node {\n id\n sku\n availableForSale\n sellers {\n id\n isDefault\n offer {\n price { amount currencyCode }\n listPrice { amount currencyCode }\n }\n }\n }\n }\n }\n }\n }\n`;\n\n// Variant slice carrying the selected variant's availability plus its default seller, authored via\n// graphql() so HotDataProvider gets a TypedDocumentNode. Declared so codegen scans the source and\n// emits VariantBuyButtonDataFragment; the fragment is spread into variantQuery at the GraphQL layer,\n// so the variable is unused at runtime. `offer` is selected only as the same purchasability gate the\n// product transform applies — the buy button never displays price.\nconst _variantFragment = graphql(`\n fragment VariantBuyButtonData on Variant {\n __typename\n id\n availableForSale\n sellers {\n id\n isDefault\n offer {\n price { amount currencyCode }\n }\n }\n }\n`);\n\nconst variantQuery = graphql(`\n query VariantBuyButtonQuery($id: ID!) {\n node(id: $id) {\n __typename\n ... on Variant {\n ...VariantBuyButtonData\n }\n }\n }\n`);\n\n// ---------------------------------------------------------------------------\n// Raw data types (shape of the fragment result)\n// ---------------------------------------------------------------------------\n\ntype MoneyValue = {\n amount: number;\n currencyCode: string;\n};\n\ntype RawBuyButtonData = {\n id: string;\n title: string;\n slug: string;\n images: {\n edges: Array<{\n node: {\n url: string;\n altText: string | null;\n };\n }>;\n };\n variants: {\n edges: Array<{\n node: {\n id: string;\n sku: string;\n availableForSale: boolean;\n sellers: Array<{\n id: string;\n isDefault: boolean;\n offer: {\n price: MoneyValue;\n listPrice: MoneyValue;\n } | null;\n }>;\n };\n }>;\n };\n};\n\n// ---------------------------------------------------------------------------\n// Content model\n// ---------------------------------------------------------------------------\n\nexport const BuyButtonLabels = defineContentType({\n id: 'buy-button-labels',\n title: 'Buy Button Labels',\n description: 'Editorial labels for the add-to-cart button.',\n schema: {\n type: 'object',\n additionalProperties: false,\n required: ['availableLabel', 'unavailableLabel'],\n properties: {\n availableLabel: { type: 'string', minLength: 1, title: 'Available label', default: 'Comprar' },\n unavailableLabel: { type: 'string', minLength: 1, title: 'Unavailable label', default: 'Indisponivel' },\n },\n },\n});\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype BuyButtonContextData = {\n available: boolean;\n /** Identifiers passed to useCart().addItem when the button is clicked. */\n variantId: string;\n seller: string;\n};\n\ntype BuyButtonContextValue = BuyButtonContextData | null;\n\nconst BUYBUTTON_CONTEXT_UNSET = Symbol('BuyButtonContextUnset');\n\nexport type BuyButtonRootProps = ComponentPropsWithRef<'div'> & {\n $data?: RawBuyButtonData | null;\n /** Page-supplied viewed SKU; adds this variant before the shopper interacts. A published swatch\n * selection wins over it once set. */\n selectedVariantId?: string;\n};\n\nexport type BuyButtonButtonProps = ComponentPropsWithRef<'button'> & {\n availableLabel?: string;\n unavailableLabel?: string;\n /** Merchant-authored labels, sliced from the page/layout content entry; win over the label props\n * when present. Absent in a non-CMS store — the label props (and their defaults) are the fallback. */\n $content?: ContentValue<typeof BuyButtonLabels>;\n /** Injected by the compiler from `actions: { cart: ['addItem'] }`; the cart write surface. */\n $actions?: ComponentActions;\n};\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\nconst BuyButtonContext = createContext<BuyButtonContextValue | typeof BUYBUTTON_CONTEXT_UNSET>(BUYBUTTON_CONTEXT_UNSET);\n\nfunction useBuyButton(): BuyButtonContextValue {\n const ctx = useContext(BuyButtonContext);\n if (ctx === BUYBUTTON_CONTEXT_UNSET) {\n throw new Error('BuyButton components must be used inside BuyButtonRoot.');\n }\n return ctx;\n}\n\n// ---------------------------------------------------------------------------\n// Transform\n// ---------------------------------------------------------------------------\n\n// Builds the add-to-cart context from a variant: the SKU (its Node id), its default seller, and its\n// availability. A variant whose resolved seller has no purchasable offer yields null (the button does\n// not render). Shared by the product-level and selected-variant transforms so the default-seller +\n// offer gate lives in one place.\nfunction buyButtonContextFromVariant(variant: {\n id: string;\n availableForSale: boolean;\n sellers: readonly { id: string; isDefault: boolean; offer: unknown }[];\n}): BuyButtonContextData | null {\n const seller = resolveDefaultSeller(variant.sellers);\n if (!seller?.offer) return null;\n\n return {\n available: variant.availableForSale,\n variantId: variant.id,\n seller: seller.id,\n };\n}\n\nfunction transformBuyButtonData(data: RawBuyButtonData): BuyButtonContextData | null {\n const edge = data.variants.edges[0];\n if (!edge) return null;\n\n return buyButtonContextFromVariant(edge.node);\n}\n\nexport function transformVariantBuyButtonData(variant: VariantBuyButtonDataFragment): BuyButtonContextData | null {\n return buyButtonContextFromVariant(variant);\n}\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const BuyButtonRootDefinition = defineComponent<BuyButtonRootProps>({\n name: 'BuyButtonRoot',\n importPath: '@vtex/faststore/components',\n component: BuyButtonRoot,\n fragment,\n skeleton: BuyButtonSkeleton,\n editor: { label: 'Buy Button', category: 'commerce' },\n schema: {\n selectedVariantId: { type: 'string', label: 'Selected Variant ID' },\n },\n guideline:\n 'Data provider for the buy button; place it on PDPs with BuyButtonButton as a child. Adds the variant the shopper picks (it reads the published VariantSelector selection), sending that SKU and its seller to the cart; the selectedVariantId prop is an optional pre-selection fallback, and it falls back to the product first variant on a variant fetch error. Cart writes flow through $actions.cart, so no CartRoot ancestor is required. See buy-button.md.',\n});\n\nexport const BuyButtonButtonDefinition = defineComponent({\n name: 'BuyButtonButton',\n importPath: '@vtex/faststore/components',\n component: BuyButtonButton,\n content: BuyButtonLabels,\n editor: { label: 'Buy Button Action', category: 'commerce' },\n schema: {\n availableLabel: { type: 'string', default: 'Comprar', label: 'Available label' },\n unavailableLabel: { type: 'string', default: 'Indisponivel', label: 'Unavailable label' },\n },\n actions: { cart: ['addItem'] },\n guideline:\n 'The add-to-cart button; must be a descendant of BuyButtonRoot. Cart writes flow through $actions.cart, so no CartRoot ancestor is required.',\n});\n\n// ---------------------------------------------------------------------------\n// Implementations\n// ---------------------------------------------------------------------------\n\nexport function BuyButtonRoot({ $data, selectedVariantId, children, ...props }: BuyButtonRootProps) {\n const productSlice = $data ?? null;\n\n // Read the shopper's published variant selection so the button adds the variant the shopper picked\n // — the selected SKU, its seller, and its availability — without a page navigation, scoped to this\n // product. The explicit selectedVariantId prop is the pre-selection fallback; the live published\n // selection wins once set.\n const publishedSelection = useReactiveVar(selectedVariantVar);\n const publishedVariantId = publishedVariantIdFor(publishedSelection, productSlice?.id ?? null);\n const activeVariantId = publishedVariantId ?? selectedVariantId;\n\n if (activeVariantId) {\n return (\n <Suspense fallback={null}>\n <HotDataProvider\n query={variantQuery}\n variables={{ id: activeVariantId }}\n field=\"node\"\n fallbackData={productSlice}\n >\n <BuyButtonProvider fallbackProductSlice={productSlice} {...props}>\n {children}\n </BuyButtonProvider>\n </HotDataProvider>\n </Suspense>\n );\n }\n\n const contextValue = productSlice ? transformBuyButtonData(productSlice) : null;\n\n return (\n <BuyButtonView contextValue={contextValue} {...props}>\n {children}\n </BuyButtonView>\n );\n}\n\ntype BuyButtonProviderProps = Omit<BuyButtonRootProps, '$data' | 'selectedVariantId'> & {\n fallbackProductSlice: RawBuyButtonData | null;\n};\n\n// Reads the override written by the nested HotDataProvider: a Variant node when the variant fetch\n// succeeds, or the product slice (fallbackData) on error/null. Dispatch on __typename so the whole\n// add-to-cart context — availability, the SKU (variantId), and the seller — resolves from the\n// selected variant; always source the product path from the explicit fallback prop so a mis-passed\n// Product node id can never crash the variant transform.\nfunction BuyButtonProvider({ fallbackProductSlice, ...viewProps }: BuyButtonProviderProps) {\n const override = useDataOverride<unknown>(null);\n const overrideTypename = (override as { __typename?: string } | null)?.__typename;\n\n let contextValue: BuyButtonContextValue = null;\n if (overrideTypename === 'Variant') {\n contextValue = transformVariantBuyButtonData(override as VariantBuyButtonDataFragment);\n } else if (fallbackProductSlice) {\n contextValue = transformBuyButtonData(fallbackProductSlice);\n }\n\n return <BuyButtonView contextValue={contextValue} {...viewProps} />;\n}\n\ntype BuyButtonViewProps = Omit<BuyButtonRootProps, '$data' | 'selectedVariantId'> & {\n contextValue: BuyButtonContextValue;\n};\n\nfunction BuyButtonView({ contextValue, className, children, ...props }: BuyButtonViewProps) {\n return (\n <BuyButtonContext value={contextValue}>\n <div {...props} className={className} data-fs-buy-button=\"\">\n {children}\n </div>\n </BuyButtonContext>\n );\n}\n\nexport function BuyButtonButton({\n availableLabel = 'Comprar',\n unavailableLabel = 'Indisponivel',\n className,\n children,\n onClick,\n $content,\n $actions,\n ...props\n}: BuyButtonButtonProps) {\n const ctx = useBuyButton();\n // `addItem` is async with no optimistic response, so the button owns its pending state locally\n // (the action surface no longer exposes a shared `addItemPending`).\n const [addItemPending, setAddItemPending] = useState(false);\n const { quantity } = useProductQuantity();\n\n if (!ctx) return null;\n\n const { available, variantId, seller } = ctx;\n\n function handleClick(e: React.MouseEvent<HTMLButtonElement>) {\n const addItem = $actions?.cart?.addItem;\n if (addItem) {\n setAddItemPending(true);\n void addItem({ variantId, seller, quantity }).finally(() => setAddItemPending(false));\n }\n onClick?.(e);\n }\n\n const isUnavailable = !available;\n const defaultText = isUnavailable\n ? ($content?.unavailableLabel ?? unavailableLabel)\n : ($content?.availableLabel ?? availableLabel);\n\n return (\n <Button\n {...props}\n className={className}\n onClick={handleClick}\n disabled={isUnavailable || addItemPending}\n aria-busy={addItemPending || undefined}\n data-fs-buy-button-button=\"\"\n {...(isUnavailable ? { 'data-fs-buy-button-unavailable': '' } : {})}\n {...(addItemPending ? { 'data-fs-buy-button-pending': '' } : {})}\n >\n {children ?? defaultText}\n </Button>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Skeleton\n// ---------------------------------------------------------------------------\n\nexport function BuyButtonSkeleton({ className, ...props }: ComponentPropsWithRef<'div'>) {\n return (\n <div {...props} className={className} data-fs-buy-button-skeleton=\"\" aria-hidden=\"true\">\n <span data-fs-buy-button-skeleton-line=\"\" />\n </div>\n );\n}\n\nBuyButtonRoot.Skeleton = BuyButtonSkeleton;\n\n// ---------------------------------------------------------------------------\n// Namespace export\n// ---------------------------------------------------------------------------\n\nexport const BuyButton = {\n Root: BuyButtonRoot,\n Button: BuyButtonButton,\n Skeleton: BuyButtonSkeleton,\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"buy-button.js","names":[],"sources":["../../../src/matter/buy-button/buy-button.tsx"],"sourcesContent":["'use client';\n\nimport { useReactiveVar } from '@apollo/client/react';\nimport { type ContentValue, defineComponent, defineContentType } from '@vtex/fs-core';\nimport type { ComponentPropsWithRef } from 'react';\nimport { createContext, Suspense, useContext, useState } from 'react';\nimport { Button } from '../../particles/button/button.js';\nimport { useActionsOverride } from '../../sdk/actions/actions-override-context.js';\nimport type { ComponentActions } from '../../sdk/actions/types.js';\nimport { useDataOverride } from '../../sdk/data/data-override-context.js';\nimport { HotDataProvider } from '../../sdk/data/hot-data-provider.js';\nimport { graphql } from '../../sdk/generated/gql.js';\nimport type { VariantBuyButtonDataFragment } from '../../sdk/generated/graphql.js';\nimport { resolveDefaultSeller } from '../../sdk/seller.js';\nimport { publishedVariantIdFor, selectedVariantVar } from '../../sdk/state/variant-selector.js';\nimport { useProductQuantity } from '../product-quantity/product-quantity.js';\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragment\n// ---------------------------------------------------------------------------\n\nconst fragment = /* GraphQL */ `\n fragment BuyButtonData on Query {\n productBySlug(slug: $slug) {\n id\n title\n slug\n images(first: 1) {\n edges {\n node {\n url\n altText\n }\n }\n }\n variants(first: 1) {\n edges {\n node {\n id\n sku\n availableForSale\n sellers {\n id\n isDefault\n offer {\n price { amount currencyCode }\n listPrice { amount currencyCode }\n }\n }\n }\n }\n }\n }\n }\n`;\n\n// Variant slice carrying the selected variant's availability plus its default seller, authored via\n// graphql() so HotDataProvider gets a TypedDocumentNode. Declared so codegen scans the source and\n// emits VariantBuyButtonDataFragment; the fragment is spread into variantQuery at the GraphQL layer,\n// so the variable is unused at runtime. `offer` is selected only as the same purchasability gate the\n// product transform applies — the buy button never displays price.\nconst _variantFragment = graphql(`\n fragment VariantBuyButtonData on Variant {\n __typename\n id\n availableForSale\n sellers {\n id\n isDefault\n offer {\n price { amount currencyCode }\n }\n }\n }\n`);\n\nconst variantQuery = graphql(`\n query VariantBuyButtonQuery($id: ID!) {\n node(id: $id) {\n __typename\n ... on Variant {\n ...VariantBuyButtonData\n }\n }\n }\n`);\n\n// ---------------------------------------------------------------------------\n// Raw data types (shape of the fragment result)\n// ---------------------------------------------------------------------------\n\ntype MoneyValue = {\n amount: number;\n currencyCode: string;\n};\n\ntype RawBuyButtonData = {\n id: string;\n title: string;\n slug: string;\n images: {\n edges: Array<{\n node: {\n url: string;\n altText: string | null;\n };\n }>;\n };\n variants: {\n edges: Array<{\n node: {\n id: string;\n sku: string;\n availableForSale: boolean;\n sellers: Array<{\n id: string;\n isDefault: boolean;\n offer: {\n price: MoneyValue;\n listPrice: MoneyValue;\n } | null;\n }>;\n };\n }>;\n };\n};\n\n// ---------------------------------------------------------------------------\n// Content model\n// ---------------------------------------------------------------------------\n\nexport const BuyButtonLabels = defineContentType({\n id: 'buy-button-labels',\n title: 'Buy Button Labels',\n description: 'Editorial labels for the add-to-cart button.',\n schema: {\n type: 'object',\n additionalProperties: false,\n required: ['availableLabel', 'unavailableLabel'],\n properties: {\n availableLabel: { type: 'string', minLength: 1, title: 'Available label', default: 'Comprar' },\n unavailableLabel: { type: 'string', minLength: 1, title: 'Unavailable label', default: 'Indisponivel' },\n },\n },\n});\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype BuyButtonContextData = {\n available: boolean;\n /** Identifiers passed to useCart().addItem when the button is clicked. */\n variantId: string;\n seller: string;\n};\n\ntype BuyButtonContextValue = BuyButtonContextData | null;\n\nconst BUYBUTTON_CONTEXT_UNSET = Symbol('BuyButtonContextUnset');\n\nexport type BuyButtonRootProps = ComponentPropsWithRef<'div'> & {\n $data?: RawBuyButtonData | null;\n /** Page-supplied viewed SKU; adds this variant before the shopper interacts. A published swatch\n * selection wins over it once set. */\n selectedVariantId?: string;\n};\n\nexport type BuyButtonButtonProps = ComponentPropsWithRef<'button'> & {\n availableLabel?: string;\n unavailableLabel?: string;\n /** Merchant-authored labels, sliced from the page/layout content entry; win over the label props\n * when present. Absent in a non-CMS store — the label props (and their defaults) are the fallback. */\n $content?: ContentValue<typeof BuyButtonLabels>;\n /** Injected by the compiler from `actions: { cart: ['addItem'] }`; the cart write surface. */\n $actions?: ComponentActions;\n};\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\nconst BuyButtonContext = createContext<BuyButtonContextValue | typeof BUYBUTTON_CONTEXT_UNSET>(BUYBUTTON_CONTEXT_UNSET);\n\nfunction useBuyButton(): BuyButtonContextValue {\n const ctx = useContext(BuyButtonContext);\n if (ctx === BUYBUTTON_CONTEXT_UNSET) {\n throw new Error('BuyButton components must be used inside BuyButtonRoot.');\n }\n return ctx;\n}\n\n// ---------------------------------------------------------------------------\n// Transform\n// ---------------------------------------------------------------------------\n\n// Builds the add-to-cart context from a variant: the SKU (its Node id), its default seller, and its\n// availability. A variant whose resolved seller has no purchasable offer yields null (the button does\n// not render). Shared by the product-level and selected-variant transforms so the default-seller +\n// offer gate lives in one place.\nfunction buyButtonContextFromVariant(variant: {\n id: string;\n availableForSale: boolean;\n sellers: readonly { id: string; isDefault: boolean; offer: unknown }[];\n}): BuyButtonContextData | null {\n const seller = resolveDefaultSeller(variant.sellers);\n if (!seller?.offer) return null;\n\n return {\n available: variant.availableForSale,\n variantId: variant.id,\n seller: seller.id,\n };\n}\n\nfunction transformBuyButtonData(data: RawBuyButtonData): BuyButtonContextData | null {\n const edge = data.variants.edges[0];\n if (!edge) return null;\n\n return buyButtonContextFromVariant(edge.node);\n}\n\nexport function transformVariantBuyButtonData(variant: VariantBuyButtonDataFragment): BuyButtonContextData | null {\n return buyButtonContextFromVariant(variant);\n}\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const BuyButtonRootDefinition = defineComponent<BuyButtonRootProps>({\n name: 'BuyButtonRoot',\n importPath: '@vtex/faststore/components',\n component: BuyButtonRoot,\n fragment,\n skeleton: BuyButtonSkeleton,\n editor: { label: 'Buy Button', category: 'commerce' },\n schema: {\n selectedVariantId: { type: 'string', label: 'Selected Variant ID' },\n },\n guideline:\n 'Data provider for the buy button; place it on PDPs with BuyButtonButton as a child. Adds the variant the shopper picks (it reads the published VariantSelector selection), sending that SKU and its seller to the cart; the selectedVariantId prop is an optional pre-selection fallback, and it falls back to the product first variant on a variant fetch error. Cart writes flow through $actions.cart, so no CartRoot ancestor is required. See buy-button.md.',\n});\n\nexport const BuyButtonButtonDefinition = defineComponent({\n name: 'BuyButtonButton',\n importPath: '@vtex/faststore/components',\n component: BuyButtonButton,\n content: BuyButtonLabels,\n editor: { label: 'Buy Button Action', category: 'commerce' },\n schema: {\n availableLabel: { type: 'string', default: 'Comprar', label: 'Available label' },\n unavailableLabel: { type: 'string', default: 'Indisponivel', label: 'Unavailable label' },\n },\n actions: { cart: ['addItem'] },\n guideline:\n 'The add-to-cart button; must be a descendant of BuyButtonRoot. Cart writes flow through $actions.cart, so no CartRoot ancestor is required.',\n});\n\n// ---------------------------------------------------------------------------\n// Implementations\n// ---------------------------------------------------------------------------\n\nexport function BuyButtonRoot({ $data, selectedVariantId, children, ...props }: BuyButtonRootProps) {\n const productSlice = $data ?? null;\n\n // Read the shopper's published variant selection so the button adds the variant the shopper picked\n // — the selected SKU, its seller, and its availability — without a page navigation, scoped to this\n // product. The explicit selectedVariantId prop is the pre-selection fallback; the live published\n // selection wins once set.\n const publishedSelection = useReactiveVar(selectedVariantVar);\n const publishedVariantId = publishedVariantIdFor(publishedSelection, productSlice?.id ?? null);\n const activeVariantId = publishedVariantId ?? selectedVariantId;\n\n if (activeVariantId) {\n return (\n <Suspense fallback={null}>\n <HotDataProvider\n query={variantQuery}\n variables={{ id: activeVariantId }}\n field=\"node\"\n fallbackData={productSlice}\n >\n <BuyButtonProvider fallbackProductSlice={productSlice} {...props}>\n {children}\n </BuyButtonProvider>\n </HotDataProvider>\n </Suspense>\n );\n }\n\n const contextValue = productSlice ? transformBuyButtonData(productSlice) : null;\n\n return (\n <BuyButtonView contextValue={contextValue} {...props}>\n {children}\n </BuyButtonView>\n );\n}\n\ntype BuyButtonProviderProps = Omit<BuyButtonRootProps, '$data' | 'selectedVariantId'> & {\n fallbackProductSlice: RawBuyButtonData | null;\n};\n\n// Reads the override written by the nested HotDataProvider: a Variant node when the variant fetch\n// succeeds, or the product slice (fallbackData) on error/null. Dispatch on __typename so the whole\n// add-to-cart context — availability, the SKU (variantId), and the seller — resolves from the\n// selected variant; always source the product path from the explicit fallback prop so a mis-passed\n// Product node id can never crash the variant transform.\nfunction BuyButtonProvider({ fallbackProductSlice, ...viewProps }: BuyButtonProviderProps) {\n const override = useDataOverride<unknown>(null);\n const overrideTypename = (override as { __typename?: string } | null)?.__typename;\n\n let contextValue: BuyButtonContextValue = null;\n if (overrideTypename === 'Variant') {\n contextValue = transformVariantBuyButtonData(override as VariantBuyButtonDataFragment);\n } else if (fallbackProductSlice) {\n contextValue = transformBuyButtonData(fallbackProductSlice);\n }\n\n return <BuyButtonView contextValue={contextValue} {...viewProps} />;\n}\n\ntype BuyButtonViewProps = Omit<BuyButtonRootProps, '$data' | 'selectedVariantId'> & {\n contextValue: BuyButtonContextValue;\n};\n\nfunction BuyButtonView({ contextValue, className, children, ...props }: BuyButtonViewProps) {\n return (\n <BuyButtonContext value={contextValue}>\n <div {...props} className={className} data-fs-buy-button=\"\">\n {children}\n </div>\n </BuyButtonContext>\n );\n}\n\nexport function BuyButtonButton({\n availableLabel = 'Comprar',\n unavailableLabel = 'Indisponivel',\n className,\n children,\n onClick,\n $content,\n $actions: $actionsProp,\n ...props\n}: BuyButtonButtonProps) {\n const $actions = useActionsOverride($actionsProp);\n const ctx = useBuyButton();\n // `addItem` is async with no optimistic response, so the button owns its pending state locally\n // (the action surface no longer exposes a shared `addItemPending`).\n const [addItemPending, setAddItemPending] = useState(false);\n const { quantity } = useProductQuantity();\n\n if (!ctx) return null;\n\n const { available, variantId, seller } = ctx;\n\n function handleClick(e: React.MouseEvent<HTMLButtonElement>) {\n const addItem = $actions?.cart?.addItem;\n if (addItem) {\n setAddItemPending(true);\n void addItem({ variantId, seller, quantity }).finally(() => setAddItemPending(false));\n }\n onClick?.(e);\n }\n\n const isUnavailable = !available;\n const defaultText = isUnavailable\n ? ($content?.unavailableLabel ?? unavailableLabel)\n : ($content?.availableLabel ?? availableLabel);\n\n return (\n <Button\n {...props}\n className={className}\n onClick={handleClick}\n disabled={isUnavailable || addItemPending}\n aria-busy={addItemPending || undefined}\n data-fs-buy-button-button=\"\"\n {...(isUnavailable ? { 'data-fs-buy-button-unavailable': '' } : {})}\n {...(addItemPending ? { 'data-fs-buy-button-pending': '' } : {})}\n >\n {children ?? defaultText}\n </Button>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Skeleton\n// ---------------------------------------------------------------------------\n\nexport function BuyButtonSkeleton({ className, ...props }: ComponentPropsWithRef<'div'>) {\n return (\n <div {...props} className={className} data-fs-buy-button-skeleton=\"\" aria-hidden=\"true\">\n <span data-fs-buy-button-skeleton-line=\"\" />\n </div>\n );\n}\n\nBuyButtonRoot.Skeleton = BuyButtonSkeleton;\n\n// ---------------------------------------------------------------------------\n// Namespace export\n// ---------------------------------------------------------------------------\n\nexport const BuyButton = {\n Root: BuyButtonRoot,\n Button: BuyButtonButton,\n Skeleton: BuyButtonSkeleton,\n};\n"],"mappings":";;;;;;;;;;;;;;;;AAqBA,MAAM,WAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCN,QAAQ;;;;;;;;;;;;;CAahC;AAED,MAAM,eAAe,QAAQ;;;;;;;;;CAS5B;AA8CD,MAAa,kBAAkB,kBAAkB;CAC/C,IAAI;CACJ,OAAO;CACP,aAAa;CACb,QAAQ;EACN,MAAM;EACN,sBAAsB;EACtB,UAAU,CAAC,kBAAkB,kBAAkB;EAC/C,YAAY;GACV,gBAAgB;IAAE,MAAM;IAAU,WAAW;IAAG,OAAO;IAAmB,SAAS;GAAU;GAC7F,kBAAkB;IAAE,MAAM;IAAU,WAAW;IAAG,OAAO;IAAqB,SAAS;GAAe;EACxG;CACF;AACF,CAAC;AAeD,MAAM,0BAA0B,OAAO,uBAAuB;AAuB9D,MAAM,mBAAmB,cAAsE,uBAAuB;AAEtH,SAAS,eAAsC;CAC7C,MAAM,MAAM,WAAW,gBAAgB;CACvC,IAAI,QAAQ,yBACV,MAAM,IAAI,MAAM,yDAAyD;CAE3E,OAAO;AACT;AAUA,SAAS,4BAA4B,SAIL;CAC9B,MAAM,SAAS,qBAAqB,QAAQ,OAAO;CACnD,IAAI,CAAC,QAAQ,OAAO,OAAO;CAE3B,OAAO;EACL,WAAW,QAAQ;EACnB,WAAW,QAAQ;EACnB,QAAQ,OAAO;CACjB;AACF;AAEA,SAAS,uBAAuB,MAAqD;CACnF,MAAM,OAAO,KAAK,SAAS,MAAM;CACjC,IAAI,CAAC,MAAM,OAAO;CAElB,OAAO,4BAA4B,KAAK,IAAI;AAC9C;AAEA,SAAgB,8BAA8B,SAAoE;CAChH,OAAO,4BAA4B,OAAO;AAC5C;AAMA,MAAa,0BAA0B,gBAAoC;CACzE,MAAM;CACN,YAAY;CACZ,WAAW;CACX;CACA,UAAU;CACV,QAAQ;EAAE,OAAO;EAAc,UAAU;CAAW;CACpD,QAAQ,EACN,mBAAmB;EAAE,MAAM;EAAU,OAAO;CAAsB,EACpE;CACA,WACE;AACJ,CAAC;AAED,MAAa,4BAA4B,gBAAgB;CACvD,MAAM;CACN,YAAY;CACZ,WAAW;CACX,SAAS;CACT,QAAQ;EAAE,OAAO;EAAqB,UAAU;CAAW;CAC3D,QAAQ;EACN,gBAAgB;GAAE,MAAM;GAAU,SAAS;GAAW,OAAO;EAAkB;EAC/E,kBAAkB;GAAE,MAAM;GAAU,SAAS;GAAgB,OAAO;EAAoB;CAC1F;CACA,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE;CAC7B,WACE;AACJ,CAAC;AAMD,SAAgB,cAAc,EAAE,OAAO,mBAAmB,UAAU,GAAG,SAA6B;CAClG,MAAM,eAAe,SAAS;CAM9B,MAAM,qBAAqB,eAAe,kBAAkB;CAE5D,MAAM,kBADqB,sBAAsB,oBAAoB,cAAc,MAAM,IAChD,KAAK;CAE9C,IAAI,iBACF,OACE,oBAAC,UAAD;EAAU,UAAU;EAClB,UAAA,oBAAC,iBAAD;GACE,OAAO;GACP,WAAW,EAAE,IAAI,gBAAgB;GACjC,OAAM;GACN,cAAc;GAEd,UAAA,oBAAC,mBAAD;IAAmB,sBAAsB;IAAc,GAAI;IACxD;GACgB,CAAA;EACJ,CAAA;CACT,CAAA;CAId,MAAM,eAAe,eAAe,uBAAuB,YAAY,IAAI;CAE3E,OACE,oBAAC,eAAD;EAA6B;EAAc,GAAI;EAC5C;CACY,CAAA;AAEnB;AAWA,SAAS,kBAAkB,EAAE,sBAAsB,GAAG,aAAqC;CACzF,MAAM,WAAW,gBAAyB,IAAI;CAC9C,MAAM,mBAAoB,UAA6C;CAEvE,IAAI,eAAsC;CAC1C,IAAI,qBAAqB,WACvB,eAAe,8BAA8B,QAAwC;MAChF,IAAI,sBACT,eAAe,uBAAuB,oBAAoB;CAG5D,OAAO,oBAAC,eAAD;EAA6B;EAAc,GAAI;CAAY,CAAA;AACpE;AAMA,SAAS,cAAc,EAAE,cAAc,WAAW,UAAU,GAAG,SAA6B;CAC1F,OACE,oBAAC,kBAAD;EAAkB,OAAO;EACvB,UAAA,oBAAC,OAAD;GAAK,GAAI;GAAkB;GAAW,sBAAmB;GACtD;EACE,CAAA;CACW,CAAA;AAEtB;AAEA,SAAgB,gBAAgB,EAC9B,iBAAiB,WACjB,mBAAmB,gBACnB,WACA,UACA,SACA,UACA,UAAU,cACV,GAAG,SACoB;CACvB,MAAM,WAAW,mBAAmB,YAAY;CAChD,MAAM,MAAM,aAAa;CAGzB,MAAM,CAAC,gBAAgB,qBAAqB,SAAS,KAAK;CAC1D,MAAM,EAAE,aAAa,mBAAmB;CAExC,IAAI,CAAC,KAAK,OAAO;CAEjB,MAAM,EAAE,WAAW,WAAW,WAAW;CAEzC,SAAS,YAAY,GAAwC;EAC3D,MAAM,UAAU,UAAU,MAAM;EAChC,IAAI,SAAS;GACX,kBAAkB,IAAI;GACtB,QAAa;IAAE;IAAW;IAAQ;GAAS,CAAC,CAAC,CAAC,cAAc,kBAAkB,KAAK,CAAC;EACtF;EACA,UAAU,CAAC;CACb;CAEA,MAAM,gBAAgB,CAAC;CACvB,MAAM,cAAc,gBACf,UAAU,oBAAoB,mBAC9B,UAAU,kBAAkB;CAEjC,OACE,oBAAC,QAAD;EACE,GAAI;EACO;EACX,SAAS;EACT,UAAU,iBAAiB;EAC3B,aAAW,kBAAkB,KAAA;EAC7B,6BAA0B;EAC1B,GAAK,gBAAgB,EAAE,kCAAkC,GAAG,IAAI,CAAC;EACjE,GAAK,iBAAiB,EAAE,8BAA8B,GAAG,IAAI,CAAC;EAE7D,UAAA,YAAY;CACP,CAAA;AAEZ;AAMA,SAAgB,kBAAkB,EAAE,WAAW,GAAG,SAAuC;CACvF,OACE,oBAAC,OAAD;EAAK,GAAI;EAAkB;EAAW,+BAA4B;EAAG,eAAY;EAC/E,UAAA,oBAAC,QAAD,EAAM,oCAAiC,GAAI,CAAA;CACxC,CAAA;AAET;AAEA,cAAc,WAAW;AAMzB,MAAa,YAAY;CACvB,MAAM;CACN,QAAQ;CACR,UAAU;AACZ"}
|
|
@@ -167,10 +167,10 @@ export declare function CartList({ className, children, skeletonCount, ...props
|
|
|
167
167
|
export declare function CartTotal({ label, className, children, ...props }: CartTotalProps): import("react/jsx-runtime").JSX.Element;
|
|
168
168
|
export declare function CartCount({ className, children, template, ...props }: CartCountProps): import("react/jsx-runtime").JSX.Element;
|
|
169
169
|
export declare function CartError({ className, children, ...props }: CartErrorProps): import("react/jsx-runtime").JSX.Element | null;
|
|
170
|
-
export declare function CartGoCheckout({ href, label, className, children, onClick, $content, $actions, ...props }: CartGoCheckoutProps): import("react/jsx-runtime").JSX.Element;
|
|
171
|
-
export declare function CartCouponForm({ placeholder, applyLabel, className, children, onSubmit, $actions, ...props }: CartCouponFormProps): import("react/jsx-runtime").JSX.Element;
|
|
172
|
-
export declare function CartAppliedCoupons({ className, children, $actions, ...props }: CartAppliedCouponsProps): import("react/jsx-runtime").JSX.Element | null;
|
|
173
|
-
export declare function CartCouponRemove({ code, className, children, onClick, $actions, ...props }: CartCouponRemoveProps): import("react/jsx-runtime").JSX.Element;
|
|
170
|
+
export declare function CartGoCheckout({ href, label, className, children, onClick, $content, $actions: $actionsProp, ...props }: CartGoCheckoutProps): import("react/jsx-runtime").JSX.Element;
|
|
171
|
+
export declare function CartCouponForm({ placeholder, applyLabel, className, children, onSubmit, $actions: $actionsProp, ...props }: CartCouponFormProps): import("react/jsx-runtime").JSX.Element;
|
|
172
|
+
export declare function CartAppliedCoupons({ className, children, $actions: $actionsProp, ...props }: CartAppliedCouponsProps): import("react/jsx-runtime").JSX.Element | null;
|
|
173
|
+
export declare function CartCouponRemove({ code, className, children, onClick, $actions: $actionsProp, ...props }: CartCouponRemoveProps): import("react/jsx-runtime").JSX.Element;
|
|
174
174
|
export declare function CartCouponError({ className, children, ...props }: CartCouponErrorProps): import("react/jsx-runtime").JSX.Element | null;
|
|
175
175
|
export declare const CartCheckoutGuardDefinition: ComponentDefinition<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>>;
|
|
176
176
|
export declare const CartLoadingDefinition: ComponentDefinition<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>>;
|
|
@@ -180,7 +180,7 @@ export declare const CartUnavailableCountDefinition: ComponentDefinition<CartUna
|
|
|
180
180
|
export declare const CartRemoveUnavailableDefinition: ComponentDefinition<CartRemoveUnavailableProps>;
|
|
181
181
|
export declare function CartCheckoutGuard({ className, children, ...props }: CartCheckoutGuardProps): import("react/jsx-runtime").JSX.Element;
|
|
182
182
|
export declare function CartUnavailableCount({ className, children, template, ...props }: CartUnavailableCountProps): import("react/jsx-runtime").JSX.Element | null;
|
|
183
|
-
export declare function CartRemoveUnavailable({ className, children, onClick, $actions, ...props }: CartRemoveUnavailableProps): import("react/jsx-runtime").JSX.Element | null;
|
|
183
|
+
export declare function CartRemoveUnavailable({ className, children, onClick, $actions: $actionsProp, ...props }: CartRemoveUnavailableProps): import("react/jsx-runtime").JSX.Element | null;
|
|
184
184
|
export declare const CartOpenOnAddDefinition: ComponentDefinition<CartOpenOnAddProps>;
|
|
185
185
|
export declare const CartRedirectOnAddDefinition: ComponentDefinition<CartRedirectOnAddProps>;
|
|
186
186
|
/**
|
|
@@ -191,7 +191,7 @@ export declare const CartRedirectOnAddDefinition: ComponentDefinition<CartRedire
|
|
|
191
191
|
* reads reactive vars and needs no `CartRoot` ancestor (ADR-0010). Expects a single `DrawerRoot`
|
|
192
192
|
* child; the wiring is encapsulated here, never exposed as authorable drawer props.
|
|
193
193
|
*/
|
|
194
|
-
export declare function CartOpenOnAdd({ children, $actions }: CartOpenOnAddProps): ReactNode;
|
|
194
|
+
export declare function CartOpenOnAdd({ children, $actions: $actionsProp }: CartOpenOnAddProps): ReactNode;
|
|
195
195
|
/**
|
|
196
196
|
* Navigates a no-mini-cart store to checkout after a successful add. Keys a `useEffect` on the
|
|
197
197
|
* add-success pulse, which `addItem` increments only on the clean branch after the `await`, so the
|
package/dist/matter/cart/cart.js
CHANGED
|
@@ -4,6 +4,7 @@ import { defineContentType } from "../../core/dist/define-content-type.js";
|
|
|
4
4
|
import "../../core/dist/index.js";
|
|
5
5
|
import { useStorefrontContext } from "../../sdk/context/storefront-context.js";
|
|
6
6
|
import { graphql } from "../../sdk/generated/gql.js";
|
|
7
|
+
import { useActionsOverride } from "../../sdk/actions/actions-override-context.js";
|
|
7
8
|
import { useDataOverride } from "../../sdk/data/data-override-context.js";
|
|
8
9
|
import { Icon } from "../../particles/icon/icon.js";
|
|
9
10
|
import { NavigationLink } from "../../particles/navigation-link/navigation-link.js";
|
|
@@ -365,7 +366,8 @@ function navigateToCheckout(href, push) {
|
|
|
365
366
|
if (/^https?:\/\//i.test(href)) globalThis.location.assign(href);
|
|
366
367
|
else push(href);
|
|
367
368
|
}
|
|
368
|
-
function CartGoCheckout({ href = "/checkout", label, className, children, onClick, $content, $actions, ...props }) {
|
|
369
|
+
function CartGoCheckout({ href = "/checkout", label, className, children, onClick, $content, $actions: $actionsProp, ...props }) {
|
|
370
|
+
const $actions = useActionsOverride($actionsProp);
|
|
369
371
|
const client = useApolloClient();
|
|
370
372
|
const router = useRouter();
|
|
371
373
|
const [validating, setValidating] = useState(false);
|
|
@@ -403,7 +405,8 @@ function CartGoCheckout({ href = "/checkout", label, className, children, onClic
|
|
|
403
405
|
children: /* @__PURE__ */ jsx(CartGoCheckoutPending, { children: resolvedLabel })
|
|
404
406
|
});
|
|
405
407
|
}
|
|
406
|
-
function CartCouponForm({ placeholder = "Coupon code", applyLabel = "Apply", className, children, onSubmit, $actions, ...props }) {
|
|
408
|
+
function CartCouponForm({ placeholder = "Coupon code", applyLabel = "Apply", className, children, onSubmit, $actions: $actionsProp, ...props }) {
|
|
409
|
+
const $actions = useActionsOverride($actionsProp);
|
|
407
410
|
const [code, setCode] = useState("");
|
|
408
411
|
const [pending, setPending] = useState(false);
|
|
409
412
|
async function handleSubmit(e) {
|
|
@@ -442,7 +445,8 @@ function CartCouponForm({ placeholder = "Coupon code", applyLabel = "Apply", cla
|
|
|
442
445
|
})] })
|
|
443
446
|
});
|
|
444
447
|
}
|
|
445
|
-
function CartAppliedCoupons({ className, children, $actions, ...props }) {
|
|
448
|
+
function CartAppliedCoupons({ className, children, $actions: $actionsProp, ...props }) {
|
|
449
|
+
const $actions = useActionsOverride($actionsProp);
|
|
446
450
|
const { cart } = useCart();
|
|
447
451
|
const coupons = cart?.appliedCoupons ?? [];
|
|
448
452
|
if (coupons.length === 0) return null;
|
|
@@ -476,7 +480,8 @@ function CartCouponChip({ children, $actions }) {
|
|
|
476
480
|
})]
|
|
477
481
|
});
|
|
478
482
|
}
|
|
479
|
-
function CartCouponRemove({ code, className, children, onClick, $actions, ...props }) {
|
|
483
|
+
function CartCouponRemove({ code, className, children, onClick, $actions: $actionsProp, ...props }) {
|
|
484
|
+
const $actions = useActionsOverride($actionsProp);
|
|
480
485
|
const coupon = useContext(CartCouponContext);
|
|
481
486
|
const targetCode = code ?? coupon?.code;
|
|
482
487
|
function handleClick(e) {
|
|
@@ -604,7 +609,8 @@ function CartUnavailableCount({ className, children, template = "{count} items u
|
|
|
604
609
|
children: children ?? template.replace("{count}", String(count))
|
|
605
610
|
});
|
|
606
611
|
}
|
|
607
|
-
function CartRemoveUnavailable({ className, children, onClick, $actions, ...props }) {
|
|
612
|
+
function CartRemoveUnavailable({ className, children, onClick, $actions: $actionsProp, ...props }) {
|
|
613
|
+
const $actions = useActionsOverride($actionsProp);
|
|
608
614
|
const { cart } = useCart();
|
|
609
615
|
const unavailableIds = (cart?.items ?? []).filter(isUnavailable).map((item) => item.id);
|
|
610
616
|
if (unavailableIds.length === 0) return null;
|
|
@@ -657,7 +663,8 @@ const CartRedirectOnAddDefinition = defineComponent({
|
|
|
657
663
|
* reads reactive vars and needs no `CartRoot` ancestor (ADR-0010). Expects a single `DrawerRoot`
|
|
658
664
|
* child; the wiring is encapsulated here, never exposed as authorable drawer props.
|
|
659
665
|
*/
|
|
660
|
-
function CartOpenOnAdd({ children, $actions }) {
|
|
666
|
+
function CartOpenOnAdd({ children, $actions: $actionsProp }) {
|
|
667
|
+
const $actions = useActionsOverride($actionsProp);
|
|
661
668
|
const open = useReactiveVar(miniCartOpenVar);
|
|
662
669
|
const pulse = useReactiveVar(cartLastAddVar);
|
|
663
670
|
useEffect(() => {
|