@fanfare-io/fanfare-sdk-solid 0.23.0 → 0.25.0
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/README.md +50 -0
- package/dist/components/auth/auth-form.d.ts +14 -1
- package/dist/components/compositions/journey-gate.d.ts +8 -1
- package/dist/components/internal/draw-module.d.ts +1 -0
- package/dist/components/internal/queue-module.d.ts +3 -0
- package/dist/components/module/fanfair-attribution.d.ts +3 -0
- package/dist/components/primitives/checkbox.d.ts +6 -0
- package/dist/components/primitives/index.d.ts +1 -0
- package/dist/components/widgets/experience-widget.d.ts +11 -1
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/use-auth-gate-experience-details.d.ts +10 -0
- package/dist/hooks/use-experience-timing.d.ts +27 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +4166 -3861
- package/dist/styles/base.css +1 -1
- package/package.json +12 -11
package/README.md
CHANGED
|
@@ -44,6 +44,27 @@ Solid applications can use `FanfareProvider`, `ExperienceWidget`, and `useExperi
|
|
|
44
44
|
|
|
45
45
|
`slots` override targeted states while keeping the default widget flow. `children` receives `{ journey, view, snapshot, error, start, isStarting }` when you want to render the full state yourself. Solid `ExperienceWidget` accepts both `class` and `className`.
|
|
46
46
|
|
|
47
|
+
Marketing consent in `AuthForm` requires both `showMarketingSmsConsent` and a nonempty
|
|
48
|
+
`marketingSmsMerchantName`. The built-in widget reads the merchant name from the public experience
|
|
49
|
+
response, including before an authenticated experience session exists. Custom auth slots receive
|
|
50
|
+
`marketingSmsMerchantName`; pass it through to `AuthForm` or `JourneyGate` to offer consent for that
|
|
51
|
+
merchant. Sign-in remains available when the merchant name is unavailable.
|
|
52
|
+
|
|
53
|
+
`AuthForm` keeps marketing copy off the initial phone screen. Continue opens an optional panel
|
|
54
|
+
with the named merchant, Fanfare sender, phone number and full disclosure before any OTP request.
|
|
55
|
+
Only "Yes, text me" opts in. It captures the displayed merchant, phone, disclosure, legal text,
|
|
56
|
+
policy links and affirmative label in `marketingSmsConsentText`, then preserves that snapshot through
|
|
57
|
+
OTP verification and resend. "No thanks" continues without marketing consent. Change number,
|
|
58
|
+
returning from verification or changing the consent scope clears the choice and snapshot.
|
|
59
|
+
Implicit submission can open the panel but cannot choose consent. Custom auth handlers must
|
|
60
|
+
preserve the received choice and text through verification; a custom consent UI supplies its own
|
|
61
|
+
actual displayed agreement. The server requires a nonblank text snapshot of at most 4,096 characters
|
|
62
|
+
to record consent. Missing or invalid evidence leaves sign-in available without granting consent.
|
|
63
|
+
This is client-reported audit evidence, not proof of what pixels were displayed.
|
|
64
|
+
Release a matching core and adapter together: the built-in panel uses core's shared policy URLs
|
|
65
|
+
and consent-text formatter. Deploy the generated database migration and server support before
|
|
66
|
+
releasing SDK callers that supply the text.
|
|
67
|
+
|
|
47
68
|
Mounted inside a shadow root, the widget's styleable elements export CSS parts named after their
|
|
48
69
|
`data-slot`, so a page styles them with `::part()` — `fanfare-host::part(auction-cta)`. The
|
|
49
70
|
attribution footer and every element that contains its "Powered by Fanfare" row are
|
|
@@ -52,6 +73,35 @@ card field is not a part either. A panel whose footer holds no attribution row k
|
|
|
52
73
|
as a part — `auction-module` is one; [`docs/STYLING.md`](./docs/STYLING.md#parts) lists every
|
|
53
74
|
part and every exception.
|
|
54
75
|
|
|
76
|
+
### Experience timing
|
|
77
|
+
|
|
78
|
+
`useExperienceTiming(() => experienceId)` takes the experience id as an accessor and reads that experience's public
|
|
79
|
+
timing once when it mounts, and again when the id changes or you call `refresh()`:
|
|
80
|
+
|
|
81
|
+
| `status()` | `timing()` |
|
|
82
|
+
| ----------- | ------------------------------------------------------------------------- |
|
|
83
|
+
| `"loading"` | `undefined` — the first read for this experience has not finished |
|
|
84
|
+
| `"error"` | `undefined` — the read failed; call `refresh()` to try again |
|
|
85
|
+
| `"ready"` | the `ExperienceTiming`, or `null` when the experience publishes no timing |
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
import { useExperienceTiming } from "@fanfare-io/fanfare-sdk-solid";
|
|
89
|
+
import { Show } from "solid-js";
|
|
90
|
+
|
|
91
|
+
function DropStatus(props: { experienceId: string }) {
|
|
92
|
+
const timing = useExperienceTiming(() => props.experienceId);
|
|
93
|
+
return <Show when={timing.status() === "ready" && timing.timing()}>{(t) => <span>{t().status}</span>}</Show>;
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`ExperienceTiming` is a type exported from `@fanfare-io/fanfare-sdk-core/experiences`. Its `status` is `upcoming`,
|
|
98
|
+
`open` or `closed`, with `nextOpensAt` and `lastClosedAt` around it. `closesAt` is
|
|
99
|
+
present only while the experience is open, and is `null` when the open run has no end. The primitive never polls
|
|
100
|
+
or schedules a re-read at a boundary, and adds no retry of its own: once `ready`, the result stays as read until
|
|
101
|
+
you call `refresh()`.
|
|
102
|
+
During a refresh the previous result stays in place — a `ready` result keeps its timing and an `error` stays
|
|
103
|
+
`error` — until the new read settles. Primitives that read the same experience at the same time share one request.
|
|
104
|
+
|
|
55
105
|
## Selection
|
|
56
106
|
|
|
57
107
|
Some experiences capture which product and variant a consumer gets. `ExperienceWidget` renders that
|
|
@@ -2,7 +2,10 @@ import { JSX } from 'solid-js';
|
|
|
2
2
|
import { PanelHeadingVariant } from '../module';
|
|
3
3
|
import { AuthInputMode } from './auth-input';
|
|
4
4
|
export interface AuthFormProps {
|
|
5
|
-
onSubmit?: (credential: string, type: AuthInputMode
|
|
5
|
+
onSubmit?: (credential: string, type: AuthInputMode, options?: {
|
|
6
|
+
marketingSmsConsent: boolean;
|
|
7
|
+
marketingSmsConsentText?: string;
|
|
8
|
+
}) => Promise<void>;
|
|
6
9
|
onVerify?: (otp: string) => Promise<void>;
|
|
7
10
|
onSkip?: () => void | Promise<void>;
|
|
8
11
|
onSuccess?: () => void;
|
|
@@ -14,11 +17,21 @@ export interface AuthFormProps {
|
|
|
14
17
|
description?: string;
|
|
15
18
|
/** When set, renders a countdown above the form. */
|
|
16
19
|
countdownTo?: Date;
|
|
20
|
+
/** When set and `countdownTo` is not, renders the close time and a sign-in hint above the form. */
|
|
21
|
+
endedAt?: Date;
|
|
17
22
|
submitLabel?: string;
|
|
18
23
|
verifyLabel?: string;
|
|
19
24
|
allowSkip?: boolean;
|
|
20
25
|
skipLabel?: string;
|
|
21
26
|
showBranding?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Offers a separate optional marketing SMS panel after credential entry in
|
|
29
|
+
* phone mode. Off by default: set it only when `onSubmit` preserves its third argument through
|
|
30
|
+
* verification, so the explicit consent choice and displayed text are not silently dropped.
|
|
31
|
+
*/
|
|
32
|
+
showMarketingSmsConsent?: boolean;
|
|
33
|
+
/** Merchant named in the consent disclosure. Consent is offered only when this is nonempty. */
|
|
34
|
+
marketingSmsMerchantName?: string;
|
|
22
35
|
/** Initial step when mounting. Useful for SSR-resume or storybook lanes that need the verify step. */
|
|
23
36
|
defaultStep?: "credential" | "verify";
|
|
24
37
|
/** Initial credential (email or E.164 phone) — pairs with `defaultStep="verify"`. */
|
|
@@ -3,7 +3,10 @@ import { AuthInputMode } from '../auth/auth-input';
|
|
|
3
3
|
export type JourneyGateStage = "auth" | "access_code" | "loading" | "none";
|
|
4
4
|
export interface JourneyGateProps {
|
|
5
5
|
stage: JourneyGateStage;
|
|
6
|
-
onAuthSubmit?: (credential: string, type: AuthInputMode
|
|
6
|
+
onAuthSubmit?: (credential: string, type: AuthInputMode, options?: {
|
|
7
|
+
marketingSmsConsent: boolean;
|
|
8
|
+
marketingSmsConsentText?: string;
|
|
9
|
+
}) => Promise<void>;
|
|
7
10
|
onAuthVerify?: (otp: string) => Promise<void>;
|
|
8
11
|
onAuthSkip?: () => void | Promise<void>;
|
|
9
12
|
onAuthSuccess?: () => void;
|
|
@@ -12,12 +15,16 @@ export interface JourneyGateProps {
|
|
|
12
15
|
allowAuthSkip?: boolean;
|
|
13
16
|
authSkipLabel?: string;
|
|
14
17
|
allowAccessCodeSkip?: boolean;
|
|
18
|
+
/** Merchant identity from the experience for marketing SMS consent. */
|
|
19
|
+
marketingSmsMerchantName?: string;
|
|
15
20
|
authTitle?: string;
|
|
16
21
|
authDescription?: string;
|
|
17
22
|
accessCodeTitle?: string;
|
|
18
23
|
accessCodeDescription?: string;
|
|
19
24
|
/** When the upcoming sequence opens — drives the countdown above the form. */
|
|
20
25
|
startsAt?: Date;
|
|
26
|
+
/** When the drop closed — drives the ended line on the auth stage. */
|
|
27
|
+
endedAt?: Date;
|
|
21
28
|
class?: string;
|
|
22
29
|
className?: string;
|
|
23
30
|
children?: JSX.Element;
|
|
@@ -8,6 +8,7 @@ import { OutcomeReason } from '../module';
|
|
|
8
8
|
export interface DrawModuleProps {
|
|
9
9
|
display$?: ReadableAtom<DrawDisplayState>;
|
|
10
10
|
sequencePhase: "enterable" | "participating" | "settling" | "granted" | "ended";
|
|
11
|
+
loyaltyBiasEnabled?: boolean;
|
|
11
12
|
canEnter: boolean;
|
|
12
13
|
canLeave: boolean;
|
|
13
14
|
/** Enter action handler; carries the entry pick on a selection-bearing draw. */
|
|
@@ -8,6 +8,9 @@ import { OutcomeReason } from '../module';
|
|
|
8
8
|
export interface QueueModuleProps {
|
|
9
9
|
display$?: ReadableAtom<QueueDisplayState>;
|
|
10
10
|
sequencePhase: "enterable" | "participating" | "granted" | "ended";
|
|
11
|
+
/** Hide live rank-derived queue metrics while the canonical queue is still pre-open. */
|
|
12
|
+
hideQueueMetrics?: boolean;
|
|
13
|
+
loyaltyBiasEnabled?: boolean;
|
|
11
14
|
canEnter: boolean;
|
|
12
15
|
canLeave: boolean;
|
|
13
16
|
onEnter: () => Promise<void>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { JSX } from 'solid-js';
|
|
2
|
+
export interface CheckboxProps extends Omit<JSX.InputHTMLAttributes<HTMLInputElement>, "type"> {
|
|
3
|
+
/** Label rendered beside the control. */
|
|
4
|
+
label: JSX.Element;
|
|
5
|
+
}
|
|
6
|
+
export declare function Checkbox(props: CheckboxProps): JSX.Element;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { Badge, badgeVariants, type BadgeProps, type BadgeVariant } from './badge';
|
|
2
2
|
export { Button, buttonVariants, type ButtonProps } from './button';
|
|
3
3
|
export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, cardVariants, type CardContentProps, type CardDescriptionProps, type CardFooterProps, type CardHeaderProps, type CardProps, type CardTitleProps, } from './card';
|
|
4
|
+
export { Checkbox, type CheckboxProps } from './checkbox';
|
|
4
5
|
export { Countdown, type CountdownProps, type CountdownSize, type CountdownTone } from './countdown';
|
|
5
6
|
export { FadePresence, type FadePresenceProps } from './fade-presence';
|
|
6
7
|
export { FanfareLogo, type FanfareLogoProps } from './fanfare-logo';
|
|
@@ -31,9 +31,19 @@ export interface StartSlotProps extends SlotProps {
|
|
|
31
31
|
isStarting: boolean;
|
|
32
32
|
}
|
|
33
33
|
export interface AuthSlotProps extends SlotProps {
|
|
34
|
-
onSubmit: (credential: string, type: AuthInputMode
|
|
34
|
+
onSubmit: (credential: string, type: AuthInputMode, options?: {
|
|
35
|
+
marketingSmsConsent: boolean;
|
|
36
|
+
marketingSmsConsentText?: string;
|
|
37
|
+
}) => Promise<void>;
|
|
35
38
|
onVerify: (otp: string) => Promise<void>;
|
|
36
39
|
onSkip?: () => void;
|
|
40
|
+
/**
|
|
41
|
+
* Whether the sign-in should offer the optional marketing SMS panel. The widget sets it because its
|
|
42
|
+
* `onSubmit` reads the consent from its third argument; pass it to `AuthForm` with `onSubmit`.
|
|
43
|
+
*/
|
|
44
|
+
showMarketingSmsConsent?: boolean;
|
|
45
|
+
/** Merchant identity to pass to AuthForm beside showMarketingSmsConsent. */
|
|
46
|
+
marketingSmsMerchantName?: string;
|
|
37
47
|
}
|
|
38
48
|
export interface AccessCodeSlotProps extends SlotProps {
|
|
39
49
|
onSubmit: (code: string) => Promise<void>;
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export { useFanfareAuth } from './use-auth';
|
|
|
9
9
|
export { createBeaconTracker } from './use-beacon-tracker';
|
|
10
10
|
export { useExperienceJourney } from './use-experience-journey';
|
|
11
11
|
export type { ExperienceJourneyOptions, UseExperienceJourneyOptions, UseExperienceJourneyResult, } from './use-experience-journey';
|
|
12
|
+
export { useExperienceTiming } from './use-experience-timing';
|
|
13
|
+
export type { UseExperienceTimingResult } from './use-experience-timing';
|
|
12
14
|
export { useJourneySnapshot } from './use-journey-snapshot';
|
|
13
15
|
export { useNanostore } from './use-nanostore';
|
|
14
16
|
export { useSdkEvent } from './use-sdk-event';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ExperienceTiming } from '@fanfare-io/fanfare-sdk-core/experiences';
|
|
2
|
+
import { Accessor } from 'solid-js';
|
|
3
|
+
/**
|
|
4
|
+
* Auth can gate routing before an experience session exists, so its merchant identity and timing
|
|
5
|
+
* come from the public read until the session's own details name the merchant.
|
|
6
|
+
*/
|
|
7
|
+
export declare function useAuthGateExperienceDetails(experienceId: Accessor<string>, sessionName: Accessor<string | undefined>, sessionTiming: Accessor<ExperienceTiming | null | undefined>, enabled: Accessor<boolean>): {
|
|
8
|
+
merchantName: Accessor<string | undefined>;
|
|
9
|
+
timing: Accessor<ExperienceTiming | null | undefined>;
|
|
10
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ExperienceTiming } from '@fanfare-io/fanfare-sdk-core/experiences';
|
|
2
|
+
import { Accessor } from 'solid-js';
|
|
3
|
+
/**
|
|
4
|
+
* Result of {@link useExperienceTiming}.
|
|
5
|
+
*
|
|
6
|
+
* - `status() === "loading"`: the first read for this experience has not settled; `timing()` is `undefined`.
|
|
7
|
+
* - `status() === "error"`: the last read was rejected; `timing()` is `undefined` and `refresh()` reads again.
|
|
8
|
+
* - `status() === "ready"`: `timing()` is the experience's public timing, or `null` when it publishes none.
|
|
9
|
+
*/
|
|
10
|
+
export interface UseExperienceTimingResult {
|
|
11
|
+
status: Accessor<"loading" | "error" | "ready">;
|
|
12
|
+
timing: Accessor<ExperienceTiming | null | undefined>;
|
|
13
|
+
refresh(): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Reads an experience's public timing once when it attaches, and again whenever
|
|
17
|
+
* `experienceId` changes or `refresh()` is called.
|
|
18
|
+
*
|
|
19
|
+
* The primitive never polls, retries or schedules work around the timing's
|
|
20
|
+
* boundaries; a caller that needs fresher timing calls `refresh()`. While a
|
|
21
|
+
* refresh is in flight the result is unchanged: a `ready` result keeps its last
|
|
22
|
+
* timing and an `error` result stays `error` until the read settles. Primitives
|
|
23
|
+
* that read the same experience at the same time on one SDK instance share a
|
|
24
|
+
* single request. Results from a superseded read, or from one still in flight
|
|
25
|
+
* after disposal, are dropped.
|
|
26
|
+
*/
|
|
27
|
+
export declare function useExperienceTiming(experienceId: Accessor<string>): UseExperienceTimingResult;
|
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
export { FanfareProvider, useFanfare } from './components/provider';
|
|
9
9
|
export type { FanfareProviderProps } from './components/provider';
|
|
10
|
-
export { createBeaconTracker, useExperienceJourney, useFanfareAuth, useJourneySnapshot, useNanostore, useSdkEvent, } from './hooks';
|
|
10
|
+
export { createBeaconTracker, useExperienceJourney, useExperienceTiming, useFanfareAuth, useJourneySnapshot, useNanostore, useSdkEvent, } from './hooks';
|
|
11
|
+
export type { UseExperienceTimingResult } from './hooks';
|
|
11
12
|
export { I18nProvider, RTL_LOCALES, isRTLLocale, useTranslations } from './i18n';
|
|
12
13
|
export type { I18nConfig, I18nProviderProps, Locale, PartialTranslationMessages, RTLLocale, TextDirection, TranslateFunction, TranslateOptions, TranslationKey, TranslationMessageKey, TranslationMessages, } from './i18n';
|
|
13
14
|
export { THEME_CSS_VARS, ThemeProvider, buildThemeCssVars } from './theme';
|