fontdue-js 3.5.1 → 3.7.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.
Files changed (151) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +2 -0
  3. package/dist/__generated__/CheckoutUpdateCustomerMutation.graphql.d.ts +3 -1
  4. package/dist/__generated__/CheckoutUpdateCustomerMutation.graphql.js +1 -1
  5. package/dist/__generated__/CustomerSignInRequestLoginCodeMutation.graphql.d.ts +24 -0
  6. package/dist/__generated__/CustomerSignInRequestLoginCodeMutation.graphql.js +71 -0
  7. package/dist/__generated__/CustomerSignInVerifyLoginCodeMutation.graphql.d.ts +30 -0
  8. package/dist/__generated__/CustomerSignInVerifyLoginCodeMutation.graphql.js +98 -0
  9. package/dist/__generated__/FontFamiliesQuery.graphql.d.ts +4 -1
  10. package/dist/__generated__/FontFamiliesQuery.graphql.js +46 -39
  11. package/dist/__generated__/NewsletterSignupUpdateCustomerMutation.graphql.d.ts +3 -1
  12. package/dist/__generated__/NewsletterSignupUpdateCustomerMutation.graphql.js +1 -1
  13. package/dist/__generated__/PriceBar_node.graphql.d.ts +4 -1
  14. package/dist/__generated__/PriceBar_node.graphql.js +23 -16
  15. package/dist/__generated__/StoreModalFamily_collection.graphql.d.ts +3 -2
  16. package/dist/__generated__/StoreModalFamily_collection.graphql.js +3 -3
  17. package/dist/__generated__/StoreModalIndexPaginationQuery.graphql.d.ts +23 -0
  18. package/dist/__generated__/StoreModalIndexPaginationQuery.graphql.js +303 -0
  19. package/dist/__generated__/StoreModalIndexQuery.graphql.d.ts +4 -9
  20. package/dist/__generated__/StoreModalIndexQuery.graphql.js +90 -53
  21. package/dist/__generated__/StoreModalIndex_viewer.graphql.d.ts +24 -0
  22. package/dist/__generated__/StoreModalIndex_viewer.graphql.js +134 -0
  23. package/dist/__generated__/StoreModalProductContent_collection.graphql.d.ts +4 -1
  24. package/dist/__generated__/StoreModalProductContent_collection.graphql.js +12 -5
  25. package/dist/__generated__/StoreModalProductLicenseSelection_collection.graphql.d.ts +2 -1
  26. package/dist/__generated__/StoreModalProductLicenseSelection_collection.graphql.js +27 -24
  27. package/dist/__generated__/StoreModalProductQuery.graphql.d.ts +2 -19
  28. package/dist/__generated__/StoreModalProductQuery.graphql.js +143 -174
  29. package/dist/__generated__/StoreModalProductRefetchQuery.graphql.d.ts +1 -1
  30. package/dist/__generated__/StoreModalProductRefetchQuery.graphql.js +54 -53
  31. package/dist/__generated__/StoreModalProductSlugQuery.graphql.d.ts +38 -0
  32. package/dist/__generated__/StoreModalProductSlugQuery.graphql.js +750 -0
  33. package/dist/__generated__/StoreModalProductViewer.graphql.d.ts +34 -0
  34. package/dist/__generated__/StoreModalProductViewer.graphql.js +149 -0
  35. package/dist/__generated__/StoreModalUnifiedCheckoutUpdateCustomerMutation.graphql.d.ts +3 -1
  36. package/dist/__generated__/StoreModalUnifiedCheckoutUpdateCustomerMutation.graphql.js +1 -1
  37. package/dist/__generated__/TestFontsFormUpdateCustomerMutation.graphql.d.ts +3 -1
  38. package/dist/__generated__/TestFontsFormUpdateCustomerMutation.graphql.js +1 -1
  39. package/dist/__generated__/TypeTestersIDQuery.graphql.d.ts +1 -1
  40. package/dist/__generated__/TypeTestersIDQuery.graphql.js +17 -10
  41. package/dist/__generated__/TypeTestersRefetchQuery.graphql.d.ts +1 -1
  42. package/dist/__generated__/TypeTestersRefetchQuery.graphql.js +17 -10
  43. package/dist/__generated__/TypeTestersSlugQuery.graphql.d.ts +1 -1
  44. package/dist/__generated__/TypeTestersSlugQuery.graphql.js +17 -10
  45. package/dist/__generated__/storefrontEventsTrackStorefrontEventMutation.graphql.d.ts +41 -0
  46. package/dist/__generated__/storefrontEventsTrackStorefrontEventMutation.graphql.js +65 -0
  47. package/dist/__generated__/useCurrentCustomerQuery.graphql.d.ts +23 -0
  48. package/dist/__generated__/{ServerConfigProviderQuery.graphql.js → useCurrentCustomerQuery.graphql.js} +24 -39
  49. package/dist/__tests__/bundleSelectionOrder.test.js +92 -0
  50. package/dist/__tests__/collectionBundleSelection.test.js +441 -1446
  51. package/dist/__tests__/customerSession.test.js +456 -0
  52. package/dist/__tests__/infiniteScroll.test.js +180 -0
  53. package/dist/__tests__/licenseExclusions.test.js +2 -1
  54. package/dist/__tests__/storeModalRoutes.test.js +118 -0
  55. package/dist/__tests__/storefrontEvents.test.js +167 -0
  56. package/dist/__tests__/stripeElements.test.js +173 -0
  57. package/dist/__tests__/typeTesterAutofitFeatures.test.js +138 -0
  58. package/dist/__tests__/typeTesterInitialSize.test.js +82 -0
  59. package/dist/__tests__/typeTesterTruncate.test.js +218 -0
  60. package/dist/auth/session.d.ts +42 -0
  61. package/dist/auth/session.js +139 -0
  62. package/dist/auth/tokenStore.d.ts +61 -0
  63. package/dist/auth/tokenStore.js +162 -0
  64. package/dist/components/Cart/Checkout.js +19 -9
  65. package/dist/components/Cart/storefrontEvents.d.ts +34 -0
  66. package/dist/components/Cart/storefrontEvents.js +138 -0
  67. package/dist/components/ConfigContext.d.ts +2 -2
  68. package/dist/components/CustomerSignIn/index.d.ts +33 -0
  69. package/dist/components/CustomerSignIn/index.js +170 -0
  70. package/dist/components/FontFamilies/index.js +4 -3
  71. package/dist/components/NewsletterSignup/index.js +5 -1
  72. package/dist/components/Precart/index.js +6 -0
  73. package/dist/components/PriceBar/index.js +5 -3
  74. package/dist/components/Root/index.js +4 -34
  75. package/dist/components/Root/productState.d.ts +2 -8
  76. package/dist/components/Root/productState.js +46 -57
  77. package/dist/components/SKUPrice/index.js +7 -8
  78. package/dist/components/StoreModal/StoreModalFamily.js +4 -2
  79. package/dist/components/StoreModal/StoreModalIndex.js +48 -7
  80. package/dist/components/StoreModal/StoreModalProduct.d.ts +5 -1
  81. package/dist/components/StoreModal/StoreModalProduct.js +69 -16
  82. package/dist/components/StoreModal/index.js +14 -14
  83. package/dist/components/StoreModal/routes.js +9 -1
  84. package/dist/components/StoreModalProductLicenseSelection/StoreModalProductLicense.d.ts +2 -1
  85. package/dist/components/StoreModalProductLicenseSelection/StoreModalProductLicense.js +8 -3
  86. package/dist/components/StoreModalProductLicenseSelection/index.js +3 -2
  87. package/dist/components/TestFontsForm/index.js +5 -1
  88. package/dist/components/TypeTester/TypeTesterContent.js +9 -2
  89. package/dist/components/TypeTester/TypeTesterContext.js +17 -4
  90. package/dist/components/TypeTester/TypeTesterFitter.js +7 -2
  91. package/dist/components/TypeTester/TypeTesterStandalone.d.ts +2 -0
  92. package/dist/components/TypeTester/TypeTesterStandalone.js +2 -0
  93. package/dist/components/TypeTester/TypeTesterStandaloneElement.d.ts +1 -0
  94. package/dist/components/TypeTester/TypeTesterStandaloneElement.js +4 -1
  95. package/dist/components/TypeTester/TypeTesterVariableAxes.js +16 -10
  96. package/dist/components/TypeTester/index.d.ts +3 -1
  97. package/dist/components/TypeTester/index.js +2 -1
  98. package/dist/components/TypeTester/truncate.d.ts +12 -0
  99. package/dist/components/TypeTester/truncate.js +95 -0
  100. package/dist/components/TypeTester/useTypeTesterStyler.d.ts +3 -1
  101. package/dist/components/TypeTester/useTypeTesterStyler.js +36 -7
  102. package/dist/components/TypeTesters/TypeTestersElement.d.ts +2 -1
  103. package/dist/components/TypeTesters/TypeTestersElement.js +4 -1
  104. package/dist/components/TypeTesters/index.d.ts +2 -0
  105. package/dist/components/elements/StoreModalContainer/StoreModalContainerContext.d.ts +1 -0
  106. package/dist/components/elements/StoreModalContainer/index.js +9 -4
  107. package/dist/components/elements/StoreModalIndexLoadMore/index.d.ts +8 -0
  108. package/dist/components/elements/StoreModalIndexLoadMore/index.js +21 -0
  109. package/dist/components/elements/StoreModalUnifiedCheckout.d.ts +1 -1
  110. package/dist/components/elements/StoreModalUnifiedCheckout.js +20 -17
  111. package/dist/components/useInfiniteScroll.d.ts +28 -0
  112. package/dist/components/useInfiniteScroll.js +50 -0
  113. package/dist/fontdue.css +63 -0
  114. package/dist/hooks/useAutofit.d.ts +2 -1
  115. package/dist/hooks/useAutofit.js +10 -4
  116. package/dist/hooks/useCurrentCustomer.d.ts +34 -0
  117. package/dist/hooks/useCurrentCustomer.js +93 -0
  118. package/dist/reducer.d.ts +5 -5
  119. package/dist/reducer.js +9 -87
  120. package/dist/relay/environment.js +91 -50
  121. package/dist/selection.d.ts +40 -0
  122. package/dist/selection.js +494 -0
  123. package/dist/storeModalRoutes.d.ts +38 -0
  124. package/dist/storeModalRoutes.js +110 -0
  125. package/dist/stripeElements.d.ts +18 -0
  126. package/dist/stripeElements.js +49 -0
  127. package/dist/utils.d.ts +1 -12
  128. package/dist/utils.js +1 -36
  129. package/dist/version.js +1 -1
  130. package/package.json +1 -1
  131. package/dist/__generated__/NodePasswordFormIDQuery.graphql.d.ts +0 -21
  132. package/dist/__generated__/NodePasswordFormIDQuery.graphql.js +0 -86
  133. package/dist/__generated__/NodePasswordFormSlugQuery.graphql.d.ts +0 -25
  134. package/dist/__generated__/NodePasswordFormSlugQuery.graphql.js +0 -108
  135. package/dist/__generated__/ServerConfigProviderQuery.graphql.d.ts +0 -24
  136. package/dist/__generated__/TestModeBannerQuery.graphql.d.ts +0 -20
  137. package/dist/__generated__/TestModeBannerQuery.graphql.js +0 -87
  138. package/dist/__generated__/ThemeConfigQuery.graphql.d.ts +0 -20
  139. package/dist/__generated__/ThemeConfigQuery.graphql.js +0 -87
  140. package/dist/components/CartButton/index.server.d.ts +0 -3
  141. package/dist/components/CartButton/index.server.js +0 -18
  142. package/dist/components/CorsErrorModal.d.ts +0 -1
  143. package/dist/components/CorsErrorModal.js +0 -92
  144. package/dist/components/FontdueProvider/FontdueProviderClientComponent.d.ts +0 -14
  145. package/dist/components/FontdueProvider/FontdueProviderClientComponent.js +0 -61
  146. package/dist/components/ThemeConfig/index.server.d.ts +0 -2
  147. package/dist/components/ThemeConfig/index.server.js +0 -17
  148. package/dist/components/TypeTester/features.d.ts +0 -5
  149. package/dist/components/TypeTester/features.js +0 -143
  150. package/dist/data/unicodeData.d.ts +0 -4
  151. package/dist/data/unicodeData.js +0 -1
@@ -0,0 +1,34 @@
1
+ import { Environment } from 'relay-runtime';
2
+ import type { FontdueState } from '../../reducer.js';
3
+ import type { LicenseSelectionInput } from '__generated__/storefrontEventsTrackStorefrontEventMutation.graphql.js';
4
+ /** Reports the store modal opening on a collection. */
5
+ export declare function trackProductViewed(environment: Environment, collectionId: string): void;
6
+ type LicenseOptions = FontdueState['licenseOptions'];
7
+ /**
8
+ * The licence choices in the store's state, in the shape `createOrderItems`
9
+ * takes them – one selection per licence variable, or one bare selection for a
10
+ * licence without variables. Sorted, so the same configuration reached in a
11
+ * different order reads the same.
12
+ */
13
+ export declare function licenseSelectionsFromOptions(licenseOptions: LicenseOptions): LicenseSelectionInput[];
14
+ /**
15
+ * Reports the buyer changing their licence choices for a collection, once the
16
+ * choices settle. Call it from the handler of the buyer's own click, never from
17
+ * an effect: licences a foundry marks as selected by default are applied on
18
+ * mount, and that isn't the buyer customizing anything.
19
+ *
20
+ * `currentLicenseOptions` is read when the report fires, so it sees the
21
+ * configuration the buyer settled on.
22
+ */
23
+ export declare function trackProductCustomized(environment: Environment, collectionId: string, currentLicenseOptions: () => LicenseOptions): void;
24
+ /**
25
+ * `trackProductCustomized` bound to the store and Relay environment in
26
+ * context, for a licence control's click handlers.
27
+ */
28
+ export declare function useTrackProductCustomized(collectionId: string | null | undefined): () => void;
29
+ /**
30
+ * Reports the checkout's payment details as complete. The server records it
31
+ * once per order, however often this is called.
32
+ */
33
+ export declare function trackPaymentInfoEntered(environment: Environment): void;
34
+ export {};
@@ -0,0 +1,138 @@
1
+ import _storefrontEventsTrackStorefrontEventMutation from "../../__generated__/storefrontEventsTrackStorefrontEventMutation.graphql.js";
2
+ import { useCallback } from 'react';
3
+ import { commitMutation, graphql, useRelayEnvironment } from 'react-relay';
4
+ import { useStore } from 'react-redux';
5
+ import { orderTrackingInput } from './orderTracking.js';
6
+ /**
7
+ * The storefront moments that happen entirely in the browser – a collection
8
+ * opened in the store modal, its licences changed, the payment details
9
+ * completed. Nothing reaches the server at those moments on its own, so they
10
+ * are reported through `trackStorefrontEvent`, which records them as "Product
11
+ * Viewed", "Product Customized" and "Payment Info Entered" with the same
12
+ * consent and identifiers as the order events around them (Meta reads them as
13
+ * ViewContent, CustomizeProduct and AddPaymentInfo).
14
+ *
15
+ * Every call is fire-and-forget: tracking must never break the store.
16
+ */
17
+ function track(environment, input) {
18
+ const tracking = orderTrackingInput();
19
+ // Outside a browser there is no moment to report.
20
+ if (!tracking) return;
21
+ try {
22
+ commitMutation(environment, {
23
+ mutation: (_storefrontEventsTrackStorefrontEventMutation.hash && _storefrontEventsTrackStorefrontEventMutation.hash !== "340cc13477a15b926e2307d36c644053" && console.error("The definition of 'storefrontEventsTrackStorefrontEventMutation' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _storefrontEventsTrackStorefrontEventMutation),
24
+ variables: {
25
+ input: {
26
+ ...input,
27
+ tracking
28
+ }
29
+ },
30
+ onCompleted: () => undefined,
31
+ onError: () => undefined
32
+ });
33
+ } catch {
34
+ // Ignore — see above.
35
+ }
36
+ }
37
+
38
+ // React can run an effect twice for one mount – StrictMode in development, a
39
+ // Suspense retry – and each run would count as a second look.
40
+ const VIEW_REPEAT_MS = 2000;
41
+ let lastView = null;
42
+
43
+ /** Reports the store modal opening on a collection. */
44
+ export function trackProductViewed(environment, collectionId) {
45
+ var _lastView;
46
+ const now = Date.now();
47
+ if (((_lastView = lastView) === null || _lastView === void 0 ? void 0 : _lastView.collectionId) === collectionId && now - lastView.at < VIEW_REPEAT_MS) return;
48
+ lastView = {
49
+ collectionId,
50
+ at: now
51
+ };
52
+ track(environment, {
53
+ event: 'PRODUCT_VIEWED',
54
+ collectionId
55
+ });
56
+ }
57
+ /**
58
+ * The licence choices in the store's state, in the shape `createOrderItems`
59
+ * takes them – one selection per licence variable, or one bare selection for a
60
+ * licence without variables. Sorted, so the same configuration reached in a
61
+ * different order reads the same.
62
+ */
63
+ export function licenseSelectionsFromOptions(licenseOptions) {
64
+ const selections = licenseOptions.flatMap(_ref => {
65
+ let {
66
+ licenseId,
67
+ licenseOptions: options
68
+ } = _ref;
69
+ const variableIds = Object.keys(options ?? {});
70
+ if (variableIds.length === 0) return [{
71
+ licenseId
72
+ }];
73
+ return variableIds.map(licenseVariableId => ({
74
+ licenseId,
75
+ licenseVariableId,
76
+ licenseOptionId: options[licenseVariableId]
77
+ }));
78
+ });
79
+ const key = s => `${s.licenseId}:${s.licenseVariableId ?? ''}`;
80
+ return selections.sort((a, b) => key(a).localeCompare(key(b)));
81
+ }
82
+
83
+ // A buyer clicks through a few licences and sizes before settling. Report the
84
+ // configuration they settle on rather than every step on the way, and only
85
+ // when it differs from the last one reported for that collection.
86
+ const CUSTOMIZE_SETTLE_MS = 1500;
87
+ const pendingCustomizations = new Map();
88
+ const reportedConfigurations = new Map();
89
+
90
+ /**
91
+ * Reports the buyer changing their licence choices for a collection, once the
92
+ * choices settle. Call it from the handler of the buyer's own click, never from
93
+ * an effect: licences a foundry marks as selected by default are applied on
94
+ * mount, and that isn't the buyer customizing anything.
95
+ *
96
+ * `currentLicenseOptions` is read when the report fires, so it sees the
97
+ * configuration the buyer settled on.
98
+ */
99
+ export function trackProductCustomized(environment, collectionId, currentLicenseOptions) {
100
+ clearTimeout(pendingCustomizations.get(collectionId));
101
+ pendingCustomizations.set(collectionId, setTimeout(() => {
102
+ pendingCustomizations.delete(collectionId);
103
+ const licenseSelections = licenseSelectionsFromOptions(currentLicenseOptions());
104
+ // Unticking everything configures nothing.
105
+ if (licenseSelections.length === 0) return;
106
+ const configuration = JSON.stringify(licenseSelections);
107
+ if (reportedConfigurations.get(collectionId) === configuration) return;
108
+ reportedConfigurations.set(collectionId, configuration);
109
+ track(environment, {
110
+ event: 'PRODUCT_CUSTOMIZED',
111
+ collectionId,
112
+ licenseSelections
113
+ });
114
+ }, CUSTOMIZE_SETTLE_MS));
115
+ }
116
+
117
+ /**
118
+ * `trackProductCustomized` bound to the store and Relay environment in
119
+ * context, for a licence control's click handlers.
120
+ */
121
+ export function useTrackProductCustomized(collectionId) {
122
+ const environment = useRelayEnvironment();
123
+ const store = useStore();
124
+ return useCallback(() => {
125
+ if (!collectionId) return;
126
+ trackProductCustomized(environment, collectionId, () => store.getState().licenseOptions);
127
+ }, [environment, store, collectionId]);
128
+ }
129
+
130
+ /**
131
+ * Reports the checkout's payment details as complete. The server records it
132
+ * once per order, however often this is called.
133
+ */
134
+ export function trackPaymentInfoEntered(environment) {
135
+ track(environment, {
136
+ event: 'PAYMENT_INFO_ENTERED'
137
+ });
138
+ }
@@ -42,7 +42,7 @@ export declare const mergeConfig: (base?: Config, override?: Config) => Config;
42
42
  export declare const makeConfig: (config?: Config) => {
43
43
  typeTester: {
44
44
  autofitOnChange: boolean;
45
- truncate: number | boolean;
45
+ truncate: import("./TypeTester/truncate.js").TruncateOption;
46
46
  priceText: boolean;
47
47
  selectButtonStyle: "inline" | "outlined";
48
48
  selectButtonLabel: string;
@@ -120,7 +120,7 @@ export declare const makeConfig: (config?: Config) => {
120
120
  declare const _default: React.Context<{
121
121
  typeTester: {
122
122
  autofitOnChange: boolean;
123
- truncate: number | boolean;
123
+ truncate: import("./TypeTester/truncate.js").TruncateOption;
124
124
  priceText: boolean;
125
125
  selectButtonStyle: "inline" | "outlined";
126
126
  selectButtonLabel: string;
@@ -0,0 +1,33 @@
1
+ import React from 'react';
2
+ /** The customer as `verifyLoginCode` returns them. */
3
+ export interface SignedInCustomer {
4
+ id: string;
5
+ name: string | null;
6
+ email: string | null;
7
+ }
8
+ export interface CustomerSignIn_props {
9
+ /** Prefills the email field – e.g. the address already typed at checkout. */
10
+ email?: string;
11
+ /** Called once the customer is signed in and their tokens are stored. */
12
+ onSignedIn?: (customer: SignedInCustomer | null) => void;
13
+ /** Label on the button that asks for a code. */
14
+ requestLabel?: string;
15
+ /** Label on the button that submits the code. */
16
+ verifyLabel?: string;
17
+ }
18
+ /**
19
+ * Sign a returning customer in with a one-time code.
20
+ *
21
+ * Two steps: an email address, then the six-digit code Fontdue emails them. On
22
+ * success the session is stored in this browser and every Fontdue component on
23
+ * the page sees it – `useCurrentCustomer()` updates, and GraphQL requests carry
24
+ * the customer's token from then on.
25
+ *
26
+ * The code step is shown whether or not the address belongs to a customer.
27
+ * Fontdue never says which, because saying so would tell anyone who asked
28
+ * whether a given person has bought from this foundry.
29
+ *
30
+ * Self-wraps in `EnsureFontdueContext`, so it works as a standalone island and
31
+ * no-ops inside an outer `<FontdueProvider>`.
32
+ */
33
+ export default function CustomerSignIn(props: CustomerSignIn_props): React.JSX.Element;
@@ -0,0 +1,170 @@
1
+ 'use client';
2
+
3
+ import _CustomerSignInVerifyLoginCodeMutation from "../../__generated__/CustomerSignInVerifyLoginCodeMutation.graphql.js";
4
+ import _CustomerSignInRequestLoginCodeMutation from "../../__generated__/CustomerSignInRequestLoginCodeMutation.graphql.js";
5
+ import React, { useState } from 'react';
6
+ import { graphql, useMutation } from 'react-relay';
7
+ import TextField from '../TextField/index.js';
8
+ import { EnsureFontdueContext } from '../FontdueContextProvider/index.js';
9
+ import { setTokens } from '../../auth/tokenStore.js';
10
+ import { useFontdueUrl } from '../UrlContext.js';
11
+ const requestLoginCodeMutation = (_CustomerSignInRequestLoginCodeMutation.hash && _CustomerSignInRequestLoginCodeMutation.hash !== "36fccde5eb4502dcd8d3269bb5713547" && console.error("The definition of 'CustomerSignInRequestLoginCodeMutation' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _CustomerSignInRequestLoginCodeMutation);
12
+ const verifyLoginCodeMutation = (_CustomerSignInVerifyLoginCodeMutation.hash && _CustomerSignInVerifyLoginCodeMutation.hash !== "0342b61905975bf0b0112270e8706435" && console.error("The definition of 'CustomerSignInVerifyLoginCodeMutation' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _CustomerSignInVerifyLoginCodeMutation);
13
+
14
+ /** The customer as `verifyLoginCode` returns them. */
15
+
16
+ /**
17
+ * Sign a returning customer in with a one-time code.
18
+ *
19
+ * Two steps: an email address, then the six-digit code Fontdue emails them. On
20
+ * success the session is stored in this browser and every Fontdue component on
21
+ * the page sees it – `useCurrentCustomer()` updates, and GraphQL requests carry
22
+ * the customer's token from then on.
23
+ *
24
+ * The code step is shown whether or not the address belongs to a customer.
25
+ * Fontdue never says which, because saying so would tell anyone who asked
26
+ * whether a given person has bought from this foundry.
27
+ *
28
+ * Self-wraps in `EnsureFontdueContext`, so it works as a standalone island and
29
+ * no-ops inside an outer `<FontdueProvider>`.
30
+ */
31
+ export default function CustomerSignIn(props) {
32
+ return /*#__PURE__*/React.createElement(EnsureFontdueContext, null, /*#__PURE__*/React.createElement(CustomerSignInFields, props));
33
+ }
34
+ function CustomerSignInFields(_ref) {
35
+ let {
36
+ email: initialEmail = '',
37
+ onSignedIn,
38
+ requestLabel = 'Email me a code',
39
+ verifyLabel = 'Sign in'
40
+ } = _ref;
41
+ const url = useFontdueUrl();
42
+ const [email, setEmail] = useState(initialEmail);
43
+ const [code, setCode] = useState('');
44
+ const [step, setStep] = useState('email');
45
+ const [error, setError] = useState(null);
46
+ const [requestCode, requesting] = useMutation(requestLoginCodeMutation);
47
+ const [verifyCode, verifying] = useMutation(verifyLoginCodeMutation);
48
+ const handleRequest = e => {
49
+ e.preventDefault();
50
+ setError(null);
51
+ requestCode({
52
+ variables: {
53
+ input: {
54
+ email
55
+ }
56
+ },
57
+ onCompleted: res => {
58
+ var _res$requestLoginCode;
59
+ if ((_res$requestLoginCode = res.requestLoginCode) !== null && _res$requestLoginCode !== void 0 && _res$requestLoginCode.success) {
60
+ setStep('code');
61
+ } else {
62
+ // The only unsuccessful answer is the rate limit, and it is about the
63
+ // address, not about whether it has an account.
64
+ setError('Too many codes requested. Please wait a few minutes and try again.');
65
+ }
66
+ },
67
+ onError: mutationError => setError(mutationError.message)
68
+ });
69
+ };
70
+ const handleVerify = e => {
71
+ e.preventDefault();
72
+ setError(null);
73
+ verifyCode({
74
+ variables: {
75
+ input: {
76
+ email,
77
+ code
78
+ }
79
+ },
80
+ onCompleted: (res, errors) => {
81
+ const payload = res.verifyLoginCode;
82
+ if (errors && errors.length > 0) {
83
+ setError(errors[0].message);
84
+ return;
85
+ }
86
+ if ((payload === null || payload === void 0 ? void 0 : payload.accessToken) == null || (payload === null || payload === void 0 ? void 0 : payload.refreshToken) == null) {
87
+ setError('That code isn’t right, or it has expired. Ask for a new one.');
88
+ return;
89
+ }
90
+
91
+ // Stored before the callback runs, so anything `onSignedIn` triggers
92
+ // already sends the customer's token.
93
+ setTokens({
94
+ accessToken: payload.accessToken,
95
+ refreshToken: payload.refreshToken
96
+ }, url);
97
+ onSignedIn === null || onSignedIn === void 0 ? void 0 : onSignedIn(payload.customer ?? null);
98
+ },
99
+ onError: mutationError => setError(mutationError.message)
100
+ });
101
+ };
102
+ const handleStartOver = () => {
103
+ setStep('email');
104
+ setCode('');
105
+ setError(null);
106
+ };
107
+ if (step === 'code') {
108
+ return /*#__PURE__*/React.createElement("div", {
109
+ className: "customer-sign-in",
110
+ "data-step": "code"
111
+ }, error && /*#__PURE__*/React.createElement("div", {
112
+ className: "customer-sign-in__errors"
113
+ }, error), /*#__PURE__*/React.createElement("form", {
114
+ className: "customer-sign-in__form",
115
+ onSubmit: handleVerify
116
+ }, /*#__PURE__*/React.createElement("p", {
117
+ className: "customer-sign-in__intro"
118
+ }, "If ", email, " has an account, a six-digit code is on its way. It works once, and for ten minutes."), /*#__PURE__*/React.createElement("div", {
119
+ className: "customer-sign-in__section"
120
+ }, /*#__PURE__*/React.createElement(TextField, {
121
+ label: "Code *"
122
+ // `one-time-code` is what lets a phone offer the code straight
123
+ // from the notification rather than making people switch apps.
124
+ ,
125
+ autocomplete: "one-time-code",
126
+ inputMode: "numeric",
127
+ value: code,
128
+ onChange: value => setCode(value),
129
+ placeholder: "000000"
130
+ })), /*#__PURE__*/React.createElement("div", {
131
+ className: "customer-sign-in__actions"
132
+ }, /*#__PURE__*/React.createElement("button", {
133
+ className: "submit-button",
134
+ type: "submit",
135
+ disabled: verifying || !code
136
+ }, verifying ? 'Signing in...' : verifyLabel, /*#__PURE__*/React.createElement("span", {
137
+ className: "submit-button__arrow"
138
+ }, " \u2192")), /*#__PURE__*/React.createElement("button", {
139
+ className: "customer-sign-in__back",
140
+ type: "button",
141
+ onClick: handleStartOver
142
+ }, "Use a different email"))));
143
+ }
144
+ return /*#__PURE__*/React.createElement("div", {
145
+ className: "customer-sign-in",
146
+ "data-step": "email"
147
+ }, error && /*#__PURE__*/React.createElement("div", {
148
+ className: "customer-sign-in__errors"
149
+ }, error), /*#__PURE__*/React.createElement("form", {
150
+ className: "customer-sign-in__form",
151
+ onSubmit: handleRequest
152
+ }, /*#__PURE__*/React.createElement("div", {
153
+ className: "customer-sign-in__section"
154
+ }, /*#__PURE__*/React.createElement(TextField, {
155
+ label: "Email *",
156
+ type: "email",
157
+ value: email,
158
+ onChange: value => setEmail(value),
159
+ autocomplete: "email",
160
+ placeholder: "Your email"
161
+ })), /*#__PURE__*/React.createElement("div", {
162
+ className: "customer-sign-in__actions"
163
+ }, /*#__PURE__*/React.createElement("button", {
164
+ className: "submit-button",
165
+ type: "submit",
166
+ disabled: requesting || !email
167
+ }, requesting ? 'Sending...' : requestLabel, /*#__PURE__*/React.createElement("span", {
168
+ className: "submit-button__arrow"
169
+ }, " \u2192")))));
170
+ }
@@ -24,20 +24,21 @@ function FontFamilies(_ref) {
24
24
  } = useLicenseAndOrderVariables();
25
25
  const {
26
26
  node
27
- } = useLazyLoadQuery((_FontFamiliesQuery.hash && _FontFamiliesQuery.hash !== "18eba6b750d3f714b964af66ecfcfa3b" && console.error("The definition of 'FontFamiliesQuery' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _FontFamiliesQuery), {
27
+ } = useLazyLoadQuery((_FontFamiliesQuery.hash && _FontFamiliesQuery.hash !== "1ccff8f69fae99e65e6007bf25e4f8a3" && console.error("The definition of 'FontFamiliesQuery' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _FontFamiliesQuery), {
28
28
  collectionId,
29
29
  licenseOptions: licenseOptionsSpecs,
30
30
  orderVariables: orderVariableSelections
31
31
  });
32
32
  if (!node) return null;
33
+ // Unlisted collections are only sold through an upgrade – see StoreModalProduct.
33
34
  return /*#__PURE__*/React.createElement("div", {
34
35
  className: "font-families"
35
36
  }, /*#__PURE__*/React.createElement("div", {
36
37
  className: "font-families__items"
37
- }, /*#__PURE__*/React.createElement(Family, {
38
+ }, node.visibility !== 'UNLISTED' && /*#__PURE__*/React.createElement(Family, {
38
39
  node: node,
39
40
  size: "wide"
40
- }), (_node$families = node.families) === null || _node$families === void 0 ? void 0 : _node$families.map((family, index) => /*#__PURE__*/React.createElement(Family, {
41
+ }), (_node$families = node.families) === null || _node$families === void 0 ? void 0 : _node$families.filter(family => family.visibility !== 'UNLISTED').map((family, index) => /*#__PURE__*/React.createElement(Family, {
41
42
  key: index,
42
43
  node: family
43
44
  }))));
@@ -57,7 +57,11 @@ function NewsletterSignupComponent(_ref) {
57
57
  name,
58
58
  email,
59
59
  newsletterOptIn,
60
- recaptchaToken: token ?? undefined
60
+ recaptchaToken: token ?? undefined,
61
+ // Tells the server which form this was, so it can emit the
62
+ // matching analytics event: updateCustomer is shared with the
63
+ // checkout contact step and is otherwise indistinguishable.
64
+ form: 'NEWSLETTER'
61
65
  }
62
66
  },
63
67
  onCompleted: (_res, errors) => {
@@ -13,6 +13,7 @@ import { Price } from '../Price/index.js';
13
13
  import { pluralize } from '../../utils.js';
14
14
  import ComponentsContext from '../ComponentsContext.js';
15
15
  import { orderTrackingInput } from '../Cart/orderTracking.js';
16
+ import { useTrackProductCustomized } from '../Cart/storefrontEvents.js';
16
17
  function skuName(sku) {
17
18
  if (sku.product && 'name' in sku.product) return sku.product.name;
18
19
  return null;
@@ -79,6 +80,7 @@ function Precart(_ref3) {
79
80
  let {
80
81
  viewer: viewerKey,
81
82
  collection: collectionKey,
83
+ collectionId,
82
84
  isLoading
83
85
  } = _ref3;
84
86
  const viewer = useFragment((_Precart_viewer.hash && _Precart_viewer.hash !== "fe66a5cdbee38cae7bfb11922f945fd5" && console.error("The definition of 'Precart_viewer' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _Precart_viewer), viewerKey);
@@ -111,6 +113,7 @@ function Precart(_ref3) {
111
113
  }
112
114
  });
113
115
  }, [environment]);
116
+ const trackCustomized = useTrackProductCustomized(collectionId);
114
117
  const onSelectLicense = (id, checked) => {
115
118
  var _collection$licenses, _license$excludedLice;
116
119
  const license = collection === null || collection === void 0 ? void 0 : (_collection$licenses = collection.licenses) === null || _collection$licenses === void 0 ? void 0 : _collection$licenses.find(node => node.id === id);
@@ -123,6 +126,7 @@ function Precart(_ref3) {
123
126
  licenseOptionSpec,
124
127
  excludedLicenseIds: (_license$excludedLice = license.excludedLicenses) === null || _license$excludedLice === void 0 ? void 0 : _license$excludedLice.map(l => l.id)
125
128
  });
129
+ trackCustomized();
126
130
  };
127
131
  const onSelectLicenseOption = (licenseId, variableId, optionId) => {
128
132
  dispatch({
@@ -131,6 +135,7 @@ function Precart(_ref3) {
131
135
  variableId,
132
136
  optionId
133
137
  });
138
+ trackCustomized();
134
139
  };
135
140
  const selectedItemsArray = () => Object.keys(selectedSkuIds).filter(id => selectedSkuIds[id] === true);
136
141
  const licenseSelectionsArray = () => licenseOptions.reduce((res, _ref4) => {
@@ -340,6 +345,7 @@ function PrecartQueryRenderer(_ref6) {
340
345
  collectionId
341
346
  });
342
347
  return /*#__PURE__*/React.createElement(Precart, _extends({}, data, rest, {
348
+ collectionId: collectionId,
343
349
  isLoading: licenseOptionsSpecs !== deferredLicenseOptionsSpecs || selectedSkuIds !== deferredSelectedSkuIds
344
350
  }));
345
351
  }
@@ -6,7 +6,7 @@ function PriceBar(_ref) {
6
6
  let {
7
7
  node: nodeKey
8
8
  } = _ref;
9
- const node = useFragment((_PriceBar_node.hash && _PriceBar_node.hash !== "60c5de3c786db43860478f1b0e00f3df" && console.error("The definition of 'PriceBar_node' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _PriceBar_node), nodeKey);
9
+ const node = useFragment((_PriceBar_node.hash && _PriceBar_node.hash !== "ccde4ddfe0fb771462c7a0eccf4db454" && console.error("The definition of 'PriceBar_node' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _PriceBar_node), nodeKey);
10
10
  const [visible, setVisible] = useState(false);
11
11
  const $pricebar = useRef(null);
12
12
  useEffect(() => {
@@ -25,15 +25,17 @@ function PriceBar(_ref) {
25
25
  };
26
26
  }, [setVisible, $pricebar]);
27
27
  if (!node) return null;
28
+
29
+ // Unlisted collections are only sold through an upgrade – see StoreModalProduct.
28
30
  return /*#__PURE__*/React.createElement("div", {
29
31
  className: "price-bar",
30
32
  ref: $pricebar,
31
33
  "data-visible": visible
32
34
  }, /*#__PURE__*/React.createElement("div", {
33
35
  className: "price-bar__wrapper"
34
- }, node.parent && /*#__PURE__*/React.createElement(PriceBarSection, {
36
+ }, node.parent && node.parent.visibility !== 'UNLISTED' && /*#__PURE__*/React.createElement(PriceBarSection, {
35
37
  node: node.parent
36
- }), /*#__PURE__*/React.createElement(PriceBarSection, {
38
+ }), node.visibility !== 'UNLISTED' && /*#__PURE__*/React.createElement(PriceBarSection, {
37
39
  node: node
38
40
  })));
39
41
  }
@@ -7,6 +7,8 @@ import { createDefaultStore } from '../../reducer.js';
7
7
  import retryImport from '../../retryImport.js';
8
8
  import { kebabToCamel, notEmpty } from '../../utils.js';
9
9
  import FontdueProvider from '../FontdueProvider/index.js';
10
+ import { makeConfig } from '../ConfigContext.js';
11
+ import { storeModalRouteAction } from '../../storeModalRoutes.js';
10
12
  const customElementMap = {
11
13
  // @ts-ignore
12
14
  'fontdue-add-to-cart-banner': /*#__PURE__*/lazy(() => retryImport(() => import('../AddToCartBanner/index.js'))),
@@ -133,40 +135,8 @@ const Root = props => {
133
135
  childList: true
134
136
  });
135
137
  function handleRoute(url) {
136
- const routes = {
137
- '^index$': handleIndex,
138
- '^cart/?$': handleCart,
139
- '^product/([a-zA-Z0-9+/=]+)/?$': handleProduct
140
- };
141
- for (const [path, handler] of Object.entries(routes)) {
142
- const match = url.match(new RegExp(path));
143
- if (match) {
144
- const params = match.slice(1);
145
- handler(...params);
146
- return;
147
- }
148
- }
149
- }
150
- function handleIndex() {
151
- store.current.dispatch({
152
- type: 'STORE_MODAL_REPLACE',
153
- route: 'index'
154
- });
155
- }
156
- function handleCart() {
157
- if (store.current.getState().storeModalHistory) store.current.dispatch({
158
- type: 'STORE_MODAL_REPLACE',
159
- route: 'cart'
160
- });
161
- }
162
- function handleProduct(productId) {
163
- store.current.dispatch({
164
- type: 'STORE_MODAL_REPLACE',
165
- route: 'product',
166
- variables: {
167
- productId
168
- }
169
- });
138
+ const action = storeModalRouteAction(url, makeConfig(config));
139
+ if (action) store.current.dispatch(action);
170
140
  }
171
141
  function fontdueClickHandler(target, _event) {
172
142
  if (target.getAttribute('fontdue-click') === 'open-store-modal') {
@@ -2,13 +2,7 @@ import { Environment } from 'react-relay';
2
2
  import { Store } from 'redux';
3
3
  import { FontdueState, FontdueAction } from '../../reducer.js';
4
4
  export declare const populateProductState: (store: Store<FontdueState, FontdueAction>, environment: Environment, id: string) => import("relay-runtime").Subscription | undefined;
5
- export interface CollectionStyleSkus {
6
- fontStyleSkuIds: string[];
7
- fontStyleIds: string[];
8
- childrenSkuIds: string[];
9
- name: string;
10
- }
11
- export interface CollectionStyleSkusMap {
12
- [collectionSkuId: string]: CollectionStyleSkus | undefined;
5
+ export interface SkuStyleIds {
6
+ [skuId: string]: string[];
13
7
  }
14
8
  export declare const populateProductStateReducer: (state: FontdueState, action: FontdueAction) => FontdueState;