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.
- package/CHANGELOG.md +19 -0
- package/README.md +2 -0
- package/dist/__generated__/CheckoutUpdateCustomerMutation.graphql.d.ts +3 -1
- package/dist/__generated__/CheckoutUpdateCustomerMutation.graphql.js +1 -1
- package/dist/__generated__/CustomerSignInRequestLoginCodeMutation.graphql.d.ts +24 -0
- package/dist/__generated__/CustomerSignInRequestLoginCodeMutation.graphql.js +71 -0
- package/dist/__generated__/CustomerSignInVerifyLoginCodeMutation.graphql.d.ts +30 -0
- package/dist/__generated__/CustomerSignInVerifyLoginCodeMutation.graphql.js +98 -0
- package/dist/__generated__/FontFamiliesQuery.graphql.d.ts +4 -1
- package/dist/__generated__/FontFamiliesQuery.graphql.js +46 -39
- package/dist/__generated__/NewsletterSignupUpdateCustomerMutation.graphql.d.ts +3 -1
- package/dist/__generated__/NewsletterSignupUpdateCustomerMutation.graphql.js +1 -1
- package/dist/__generated__/PriceBar_node.graphql.d.ts +4 -1
- package/dist/__generated__/PriceBar_node.graphql.js +23 -16
- package/dist/__generated__/StoreModalFamily_collection.graphql.d.ts +3 -2
- package/dist/__generated__/StoreModalFamily_collection.graphql.js +3 -3
- package/dist/__generated__/StoreModalIndexPaginationQuery.graphql.d.ts +23 -0
- package/dist/__generated__/StoreModalIndexPaginationQuery.graphql.js +303 -0
- package/dist/__generated__/StoreModalIndexQuery.graphql.d.ts +4 -9
- package/dist/__generated__/StoreModalIndexQuery.graphql.js +90 -53
- package/dist/__generated__/StoreModalIndex_viewer.graphql.d.ts +24 -0
- package/dist/__generated__/StoreModalIndex_viewer.graphql.js +134 -0
- package/dist/__generated__/StoreModalProductContent_collection.graphql.d.ts +4 -1
- package/dist/__generated__/StoreModalProductContent_collection.graphql.js +12 -5
- package/dist/__generated__/StoreModalProductLicenseSelection_collection.graphql.d.ts +2 -1
- package/dist/__generated__/StoreModalProductLicenseSelection_collection.graphql.js +27 -24
- package/dist/__generated__/StoreModalProductQuery.graphql.d.ts +2 -19
- package/dist/__generated__/StoreModalProductQuery.graphql.js +143 -174
- package/dist/__generated__/StoreModalProductRefetchQuery.graphql.d.ts +1 -1
- package/dist/__generated__/StoreModalProductRefetchQuery.graphql.js +54 -53
- package/dist/__generated__/StoreModalProductSlugQuery.graphql.d.ts +38 -0
- package/dist/__generated__/StoreModalProductSlugQuery.graphql.js +750 -0
- package/dist/__generated__/StoreModalProductViewer.graphql.d.ts +34 -0
- package/dist/__generated__/StoreModalProductViewer.graphql.js +149 -0
- package/dist/__generated__/StoreModalUnifiedCheckoutUpdateCustomerMutation.graphql.d.ts +3 -1
- package/dist/__generated__/StoreModalUnifiedCheckoutUpdateCustomerMutation.graphql.js +1 -1
- package/dist/__generated__/TestFontsFormUpdateCustomerMutation.graphql.d.ts +3 -1
- package/dist/__generated__/TestFontsFormUpdateCustomerMutation.graphql.js +1 -1
- package/dist/__generated__/TypeTestersIDQuery.graphql.d.ts +1 -1
- package/dist/__generated__/TypeTestersIDQuery.graphql.js +17 -10
- package/dist/__generated__/TypeTestersRefetchQuery.graphql.d.ts +1 -1
- package/dist/__generated__/TypeTestersRefetchQuery.graphql.js +17 -10
- package/dist/__generated__/TypeTestersSlugQuery.graphql.d.ts +1 -1
- package/dist/__generated__/TypeTestersSlugQuery.graphql.js +17 -10
- package/dist/__generated__/storefrontEventsTrackStorefrontEventMutation.graphql.d.ts +41 -0
- package/dist/__generated__/storefrontEventsTrackStorefrontEventMutation.graphql.js +65 -0
- package/dist/__generated__/useCurrentCustomerQuery.graphql.d.ts +23 -0
- package/dist/__generated__/{ServerConfigProviderQuery.graphql.js → useCurrentCustomerQuery.graphql.js} +24 -39
- package/dist/__tests__/bundleSelectionOrder.test.js +92 -0
- package/dist/__tests__/collectionBundleSelection.test.js +441 -1446
- package/dist/__tests__/customerSession.test.js +456 -0
- package/dist/__tests__/infiniteScroll.test.js +180 -0
- package/dist/__tests__/licenseExclusions.test.js +2 -1
- package/dist/__tests__/storeModalRoutes.test.js +118 -0
- package/dist/__tests__/storefrontEvents.test.js +167 -0
- package/dist/__tests__/stripeElements.test.js +173 -0
- package/dist/__tests__/typeTesterAutofitFeatures.test.js +138 -0
- package/dist/__tests__/typeTesterInitialSize.test.js +82 -0
- package/dist/__tests__/typeTesterTruncate.test.js +218 -0
- package/dist/auth/session.d.ts +42 -0
- package/dist/auth/session.js +139 -0
- package/dist/auth/tokenStore.d.ts +61 -0
- package/dist/auth/tokenStore.js +162 -0
- package/dist/components/Cart/Checkout.js +19 -9
- package/dist/components/Cart/storefrontEvents.d.ts +34 -0
- package/dist/components/Cart/storefrontEvents.js +138 -0
- package/dist/components/ConfigContext.d.ts +2 -2
- package/dist/components/CustomerSignIn/index.d.ts +33 -0
- package/dist/components/CustomerSignIn/index.js +170 -0
- package/dist/components/FontFamilies/index.js +4 -3
- package/dist/components/NewsletterSignup/index.js +5 -1
- package/dist/components/Precart/index.js +6 -0
- package/dist/components/PriceBar/index.js +5 -3
- package/dist/components/Root/index.js +4 -34
- package/dist/components/Root/productState.d.ts +2 -8
- package/dist/components/Root/productState.js +46 -57
- package/dist/components/SKUPrice/index.js +7 -8
- package/dist/components/StoreModal/StoreModalFamily.js +4 -2
- package/dist/components/StoreModal/StoreModalIndex.js +48 -7
- package/dist/components/StoreModal/StoreModalProduct.d.ts +5 -1
- package/dist/components/StoreModal/StoreModalProduct.js +69 -16
- package/dist/components/StoreModal/index.js +14 -14
- package/dist/components/StoreModal/routes.js +9 -1
- package/dist/components/StoreModalProductLicenseSelection/StoreModalProductLicense.d.ts +2 -1
- package/dist/components/StoreModalProductLicenseSelection/StoreModalProductLicense.js +8 -3
- package/dist/components/StoreModalProductLicenseSelection/index.js +3 -2
- package/dist/components/TestFontsForm/index.js +5 -1
- package/dist/components/TypeTester/TypeTesterContent.js +9 -2
- package/dist/components/TypeTester/TypeTesterContext.js +17 -4
- package/dist/components/TypeTester/TypeTesterFitter.js +7 -2
- package/dist/components/TypeTester/TypeTesterStandalone.d.ts +2 -0
- package/dist/components/TypeTester/TypeTesterStandalone.js +2 -0
- package/dist/components/TypeTester/TypeTesterStandaloneElement.d.ts +1 -0
- package/dist/components/TypeTester/TypeTesterStandaloneElement.js +4 -1
- package/dist/components/TypeTester/TypeTesterVariableAxes.js +16 -10
- package/dist/components/TypeTester/index.d.ts +3 -1
- package/dist/components/TypeTester/index.js +2 -1
- package/dist/components/TypeTester/truncate.d.ts +12 -0
- package/dist/components/TypeTester/truncate.js +95 -0
- package/dist/components/TypeTester/useTypeTesterStyler.d.ts +3 -1
- package/dist/components/TypeTester/useTypeTesterStyler.js +36 -7
- package/dist/components/TypeTesters/TypeTestersElement.d.ts +2 -1
- package/dist/components/TypeTesters/TypeTestersElement.js +4 -1
- package/dist/components/TypeTesters/index.d.ts +2 -0
- package/dist/components/elements/StoreModalContainer/StoreModalContainerContext.d.ts +1 -0
- package/dist/components/elements/StoreModalContainer/index.js +9 -4
- package/dist/components/elements/StoreModalIndexLoadMore/index.d.ts +8 -0
- package/dist/components/elements/StoreModalIndexLoadMore/index.js +21 -0
- package/dist/components/elements/StoreModalUnifiedCheckout.d.ts +1 -1
- package/dist/components/elements/StoreModalUnifiedCheckout.js +20 -17
- package/dist/components/useInfiniteScroll.d.ts +28 -0
- package/dist/components/useInfiniteScroll.js +50 -0
- package/dist/fontdue.css +63 -0
- package/dist/hooks/useAutofit.d.ts +2 -1
- package/dist/hooks/useAutofit.js +10 -4
- package/dist/hooks/useCurrentCustomer.d.ts +34 -0
- package/dist/hooks/useCurrentCustomer.js +93 -0
- package/dist/reducer.d.ts +5 -5
- package/dist/reducer.js +9 -87
- package/dist/relay/environment.js +91 -50
- package/dist/selection.d.ts +40 -0
- package/dist/selection.js +494 -0
- package/dist/storeModalRoutes.d.ts +38 -0
- package/dist/storeModalRoutes.js +110 -0
- package/dist/stripeElements.d.ts +18 -0
- package/dist/stripeElements.js +49 -0
- package/dist/utils.d.ts +1 -12
- package/dist/utils.js +1 -36
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/dist/__generated__/NodePasswordFormIDQuery.graphql.d.ts +0 -21
- package/dist/__generated__/NodePasswordFormIDQuery.graphql.js +0 -86
- package/dist/__generated__/NodePasswordFormSlugQuery.graphql.d.ts +0 -25
- package/dist/__generated__/NodePasswordFormSlugQuery.graphql.js +0 -108
- package/dist/__generated__/ServerConfigProviderQuery.graphql.d.ts +0 -24
- package/dist/__generated__/TestModeBannerQuery.graphql.d.ts +0 -20
- package/dist/__generated__/TestModeBannerQuery.graphql.js +0 -87
- package/dist/__generated__/ThemeConfigQuery.graphql.d.ts +0 -20
- package/dist/__generated__/ThemeConfigQuery.graphql.js +0 -87
- package/dist/components/CartButton/index.server.d.ts +0 -3
- package/dist/components/CartButton/index.server.js +0 -18
- package/dist/components/CorsErrorModal.d.ts +0 -1
- package/dist/components/CorsErrorModal.js +0 -92
- package/dist/components/FontdueProvider/FontdueProviderClientComponent.d.ts +0 -14
- package/dist/components/FontdueProvider/FontdueProviderClientComponent.js +0 -61
- package/dist/components/ThemeConfig/index.server.d.ts +0 -2
- package/dist/components/ThemeConfig/index.server.js +0 -17
- package/dist/components/TypeTester/features.d.ts +0 -5
- package/dist/components/TypeTester/features.js +0 -143
- package/dist/data/unicodeData.d.ts +0 -4
- 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:
|
|
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:
|
|
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 !== "
|
|
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 !== "
|
|
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
|
|
137
|
-
|
|
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
|
|
6
|
-
|
|
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;
|