fontdue-js 3.0.0-alpha6 → 3.0.0-alpha8

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 (48) hide show
  1. package/dist/__generated__/orderTrackingUpdateOrderTrackingMutation.graphql.d.ts +27 -0
  2. package/dist/__generated__/orderTrackingUpdateOrderTrackingMutation.graphql.js +72 -0
  3. package/dist/__tests__/nextAdapter.test.js +307 -0
  4. package/dist/components/BuyButton/index.js +8 -2
  5. package/dist/components/Cart/orderTracking.d.ts +10 -0
  6. package/dist/components/Cart/orderTracking.js +43 -0
  7. package/dist/components/CartButton/index.js +16 -4
  8. package/dist/components/CharacterViewer/index.js +8 -2
  9. package/dist/components/CustomerLoginForm/index.js +17 -9
  10. package/dist/components/FontdueProvider/index.d.ts +10 -1
  11. package/dist/components/FontdueProvider/index.js +1 -0
  12. package/dist/components/FontdueProvider/index.server.d.ts +2 -1
  13. package/dist/components/FontdueProvider/index.server.js +16 -0
  14. package/dist/components/NewsletterSignup/index.js +4 -1
  15. package/dist/components/TestFontsForm/index.js +4 -1
  16. package/dist/components/TypeTesters/index.js +8 -2
  17. package/dist/next/config.d.ts +45 -0
  18. package/dist/next/config.js +180 -0
  19. package/dist/next/image-loader.d.ts +7 -0
  20. package/dist/next/image-loader.js +39 -0
  21. package/dist/next/index.d.ts +2 -0
  22. package/dist/next/index.js +10 -0
  23. package/dist/next/revalidate.d.ts +1 -0
  24. package/dist/next/revalidate.js +37 -0
  25. package/dist/next/tenant.d.ts +18 -0
  26. package/dist/next/tenant.js +105 -0
  27. package/dist/relay/environment.js +10 -4
  28. package/dist/relay/loadSerializableQuery.d.ts +3 -1
  29. package/dist/relay/loadSerializableQuery.js +2 -2
  30. package/dist/relay/serverConfig.d.ts +10 -0
  31. package/dist/relay/serverConfig.js +38 -0
  32. package/dist/vite.js +2 -0
  33. package/package.json +5 -1
  34. package/types/next-cache.d.ts +6 -0
  35. package/dist/__generated__/TypeTesterStyleSelectData_viewer.graphql.d.ts +0 -42
  36. package/dist/__generated__/TypeTesterStyleSelectData_viewer.graphql.js +0 -166
  37. package/dist/__generated__/TypeTester_viewer.graphql.d.ts +0 -17
  38. package/dist/__generated__/TypeTester_viewer.graphql.js +0 -40
  39. package/dist/__generated__/TypeTesters_viewer.graphql.d.ts +0 -17
  40. package/dist/__generated__/TypeTesters_viewer.graphql.js +0 -40
  41. package/dist/components/FontdueContextProvider/index.server.d.ts +0 -4
  42. package/dist/components/FontdueContextProvider/index.server.js +0 -7
  43. package/dist/components/FontdueProvider/useAuxUIOwner.d.ts +0 -1
  44. package/dist/components/FontdueProvider/useAuxUIOwner.js +0 -28
  45. package/dist/components/TypeTester/TypeTesterStandalone.preload.d.ts +0 -14
  46. package/dist/components/TypeTester/TypeTesterStandalone.preload.js +0 -20
  47. package/dist/config.d.ts +0 -7
  48. package/dist/config.js +0 -31
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @generated SignedSource<<017a8a724b3a0fd0918153ce04d07f68>>
3
+ * @lightSyntaxTransform
4
+ * @nogrep
5
+ */
6
+ import { ConcreteRequest } from 'relay-runtime';
7
+ export type UpdateOrderTrackingInput = {
8
+ analyticsConsent?: boolean | null;
9
+ anonymousId?: string | null;
10
+ fbc?: string | null;
11
+ fbp?: string | null;
12
+ url?: string | null;
13
+ };
14
+ export type orderTrackingUpdateOrderTrackingMutation$variables = {
15
+ input: UpdateOrderTrackingInput;
16
+ };
17
+ export type orderTrackingUpdateOrderTrackingMutation$data = {
18
+ readonly updateOrderTracking: {
19
+ readonly success: boolean | null;
20
+ } | null;
21
+ };
22
+ export type orderTrackingUpdateOrderTrackingMutation = {
23
+ response: orderTrackingUpdateOrderTrackingMutation$data;
24
+ variables: orderTrackingUpdateOrderTrackingMutation$variables;
25
+ };
26
+ declare const node: ConcreteRequest;
27
+ export default node;
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ /**
8
+ * @generated SignedSource<<017a8a724b3a0fd0918153ce04d07f68>>
9
+ * @lightSyntaxTransform
10
+ * @nogrep
11
+ */
12
+
13
+ /* tslint:disable */
14
+ /* eslint-disable */
15
+ // @ts-nocheck
16
+
17
+ const node = function () {
18
+ var v0 = [{
19
+ "defaultValue": null,
20
+ "kind": "LocalArgument",
21
+ "name": "input"
22
+ }],
23
+ v1 = [{
24
+ "alias": null,
25
+ "args": [{
26
+ "kind": "Variable",
27
+ "name": "input",
28
+ "variableName": "input"
29
+ }],
30
+ "concreteType": "UpdateOrderTrackingPayload",
31
+ "kind": "LinkedField",
32
+ "name": "updateOrderTracking",
33
+ "plural": false,
34
+ "selections": [{
35
+ "alias": null,
36
+ "args": null,
37
+ "kind": "ScalarField",
38
+ "name": "success",
39
+ "storageKey": null
40
+ }],
41
+ "storageKey": null
42
+ }];
43
+ return {
44
+ "fragment": {
45
+ "argumentDefinitions": v0 /*: any*/,
46
+ "kind": "Fragment",
47
+ "metadata": null,
48
+ "name": "orderTrackingUpdateOrderTrackingMutation",
49
+ "selections": v1 /*: any*/,
50
+ "type": "RootMutationType",
51
+ "abstractKey": null
52
+ },
53
+ "kind": "Request",
54
+ "operation": {
55
+ "argumentDefinitions": v0 /*: any*/,
56
+ "kind": "Operation",
57
+ "name": "orderTrackingUpdateOrderTrackingMutation",
58
+ "selections": v1 /*: any*/
59
+ },
60
+ "params": {
61
+ "cacheID": "c6d6059215c6ed30315c881dff80e9a7",
62
+ "id": null,
63
+ "metadata": {},
64
+ "name": "orderTrackingUpdateOrderTrackingMutation",
65
+ "operationKind": "mutation",
66
+ "text": "mutation orderTrackingUpdateOrderTrackingMutation(\n $input: UpdateOrderTrackingInput!\n) {\n updateOrderTracking(input: $input) {\n success\n }\n}\n"
67
+ }
68
+ };
69
+ }();
70
+ node.hash = "d59a127a7f140424f507ae549731bac7";
71
+ var _default = node;
72
+ exports.default = _default;
@@ -0,0 +1,307 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
2
+
3
+ // The next adapter modules read process.env at module load, so each test
4
+ // group stubs the env and re-imports them fresh.
5
+ async function importTenant() {
6
+ return await import("../next/tenant.js");
7
+ }
8
+ async function importConfig() {
9
+ return await import("../next/config.js");
10
+ }
11
+ async function importRevalidate() {
12
+ return await import("../next/revalidate.js");
13
+ }
14
+ const revalidateTag = vi.fn();
15
+ vi.mock('next/cache', () => ({
16
+ revalidateTag: tag => revalidateTag(tag)
17
+ }));
18
+ beforeEach(() => {
19
+ vi.resetModules();
20
+ revalidateTag.mockClear();
21
+ vi.unstubAllEnvs();
22
+ });
23
+ function stubSingleTenant() {
24
+ let url = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'https://acme.fontdue.com';
25
+ vi.stubEnv('NEXT_PUBLIC_FONTDUE_URL', url);
26
+ vi.stubEnv('FONTDUE_MULTI_TENANT', '');
27
+ vi.stubEnv('FONTDUE_ORIGIN', '');
28
+ vi.stubEnv('FONTDUE_PROXY_SECRET', '');
29
+ }
30
+ function stubMultiTenant() {
31
+ let {
32
+ origin = '',
33
+ secret = ''
34
+ } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
35
+ vi.stubEnv('NEXT_PUBLIC_FONTDUE_URL', '');
36
+ vi.stubEnv('FONTDUE_MULTI_TENANT', '1');
37
+ vi.stubEnv('FONTDUE_ORIGIN', origin);
38
+ vi.stubEnv('FONTDUE_PROXY_SECRET', secret);
39
+ }
40
+ describe('isValidDomain', () => {
41
+ it('accepts plain dotted hostnames and rejects everything else', async () => {
42
+ stubMultiTenant();
43
+ const {
44
+ isValidDomain
45
+ } = await importTenant();
46
+ expect(isValidDomain('acme.fontdue.com')).toBe(true);
47
+ expect(isValidDomain('a-b.example')).toBe(true);
48
+ expect(isValidDomain('localhost')).toBe(false); // no dot
49
+ expect(isValidDomain('acme.fontdue.com:3000')).toBe(false); // port
50
+ expect(isValidDomain('acme.fontdue.com/x')).toBe(false); // path
51
+ expect(isValidDomain('-bad.example')).toBe(false);
52
+ expect(isValidDomain('a'.repeat(254) + '.example')).toBe(false);
53
+ });
54
+ });
55
+ describe('fontdueEndpoint', () => {
56
+ it('single-tenant: targets NEXT_PUBLIC_FONTDUE_URL with no headers', async () => {
57
+ stubSingleTenant('https://acme.fontdue.com');
58
+ const {
59
+ fontdueEndpoint
60
+ } = await importTenant();
61
+ expect(fontdueEndpoint('acme.fontdue.com')).toEqual({
62
+ origin: 'https://acme.fontdue.com',
63
+ headers: {},
64
+ tags: ['graphql', 'graphql:acme.fontdue.com']
65
+ });
66
+ });
67
+ it('single-tenant: throws when NEXT_PUBLIC_FONTDUE_URL is missing', async () => {
68
+ vi.stubEnv('NEXT_PUBLIC_FONTDUE_URL', '');
69
+ vi.stubEnv('FONTDUE_MULTI_TENANT', '');
70
+ const {
71
+ fontdueEndpoint
72
+ } = await importTenant();
73
+ expect(() => fontdueEndpoint('acme.fontdue.com')).toThrow(/NEXT_PUBLIC_FONTDUE_URL/);
74
+ });
75
+ it('multi-tenant: forwards the host to FONTDUE_ORIGIN with the proxy secret', async () => {
76
+ stubMultiTenant({
77
+ origin: 'http://app:4000',
78
+ secret: 's3cret'
79
+ });
80
+ const {
81
+ fontdueEndpoint
82
+ } = await importTenant();
83
+ expect(fontdueEndpoint('acme.fontdue.com')).toEqual({
84
+ origin: 'http://app:4000',
85
+ headers: {
86
+ 'x-forwarded-host': 'acme.fontdue.com',
87
+ 'x-fontdue-proxy-secret': 's3cret'
88
+ },
89
+ tags: ['graphql', 'graphql:acme.fontdue.com']
90
+ });
91
+ });
92
+ it('multi-tenant: omits the secret header when unset', async () => {
93
+ stubMultiTenant({
94
+ origin: 'http://app:4000'
95
+ });
96
+ const {
97
+ fontdueEndpoint
98
+ } = await importTenant();
99
+ expect(fontdueEndpoint('acme.fontdue.com').headers).toEqual({
100
+ 'x-forwarded-host': 'acme.fontdue.com'
101
+ });
102
+ });
103
+ it('multi-tenant: falls back to the tenant public URL without FONTDUE_ORIGIN', async () => {
104
+ stubMultiTenant();
105
+ const {
106
+ fontdueEndpoint
107
+ } = await importTenant();
108
+ expect(fontdueEndpoint('acme.fontdue.com').origin).toBe('https://acme.fontdue.com');
109
+ });
110
+ });
111
+ describe('configureFontdueRender', () => {
112
+ it('rejects invalid domains with null and sets no config', async () => {
113
+ stubMultiTenant({
114
+ origin: 'http://app:4000'
115
+ });
116
+ const {
117
+ configureFontdueRender
118
+ } = await importTenant();
119
+ const {
120
+ getFontdueServerConfig
121
+ } = await import("../relay/serverConfig.js");
122
+ expect(configureFontdueRender('not a domain')).toBeNull();
123
+ expect(getFontdueServerConfig()).toBeUndefined();
124
+ });
125
+ it('sets the per-render server config and returns the endpoint', async () => {
126
+ stubMultiTenant({
127
+ origin: 'http://app:4000',
128
+ secret: 's3cret'
129
+ });
130
+ const {
131
+ configureFontdueRender
132
+ } = await importTenant();
133
+ const {
134
+ getFontdueServerConfig
135
+ } = await import("../relay/serverConfig.js");
136
+ const endpoint = configureFontdueRender('acme.fontdue.com');
137
+ expect(endpoint === null || endpoint === void 0 ? void 0 : endpoint.origin).toBe('http://app:4000');
138
+ // Outside an RSC render React.cache doesn't memoize, so the write is a
139
+ // no-op here — but the config passed must still be shaped correctly, so
140
+ // assert via fontdueServerConfig instead.
141
+ const {
142
+ fontdueServerConfig
143
+ } = await importTenant();
144
+ expect(fontdueServerConfig('acme.fontdue.com')).toEqual({
145
+ url: 'http://app:4000',
146
+ headers: {
147
+ 'x-forwarded-host': 'acme.fontdue.com',
148
+ 'x-fontdue-proxy-secret': 's3cret'
149
+ },
150
+ cacheTags: ['graphql:acme.fontdue.com']
151
+ });
152
+ });
153
+ });
154
+ describe('withFontdue', () => {
155
+ it('throws when neither mode is configured', async () => {
156
+ vi.stubEnv('NEXT_PUBLIC_FONTDUE_URL', '');
157
+ vi.stubEnv('FONTDUE_MULTI_TENANT', '');
158
+ const {
159
+ withFontdue
160
+ } = await importConfig();
161
+ expect(() => withFontdue({})).toThrow(/NEXT_PUBLIC_FONTDUE_URL/);
162
+ });
163
+ it('single-tenant: rewrites every path under the constant domain', async () => {
164
+ stubSingleTenant('https://acme.fontdue.com');
165
+ const {
166
+ withFontdue
167
+ } = await importConfig();
168
+ const rewrites = await withFontdue({}).rewrites();
169
+ expect(rewrites.afterFiles).toEqual([]);
170
+ expect(rewrites.fallback).toEqual([]);
171
+ const destinations = rewrites.beforeFiles.map(r => r.destination);
172
+ expect(destinations).toEqual(['/acme.fontdue.com', '/acme.fontdue.com/robots.txt', '/acme.fontdue.com/sitemap.xml', '/acme.fontdue.com/:path']);
173
+ });
174
+ it('multi-tenant: emits forwarded-host rules before host rules, mutually exclusive', async () => {
175
+ stubMultiTenant();
176
+ const {
177
+ withFontdue
178
+ } = await importConfig();
179
+ const rewrites = await withFontdue({}).rewrites();
180
+ expect(rewrites.beforeFiles).toHaveLength(8);
181
+ const [forwarded, hostBased] = [rewrites.beforeFiles.slice(0, 4), rewrites.beforeFiles.slice(4)];
182
+ for (const rule of forwarded) {
183
+ var _rule$has;
184
+ expect((_rule$has = rule.has) === null || _rule$has === void 0 ? void 0 : _rule$has[0]).toMatchObject({
185
+ type: 'header',
186
+ key: 'x-forwarded-host'
187
+ });
188
+ expect(rule.missing).toBeUndefined();
189
+ }
190
+ for (const rule of hostBased) {
191
+ var _rule$has2, _rule$missing;
192
+ expect((_rule$has2 = rule.has) === null || _rule$has2 === void 0 ? void 0 : _rule$has2[0]).toMatchObject({
193
+ type: 'host'
194
+ });
195
+ expect((_rule$missing = rule.missing) === null || _rule$missing === void 0 ? void 0 : _rule$missing[0]).toMatchObject({
196
+ type: 'header',
197
+ key: 'x-forwarded-host'
198
+ });
199
+ }
200
+ });
201
+ it("chains the app's beforeFiles rules ahead of the tenant rules", async () => {
202
+ stubSingleTenant();
203
+ const {
204
+ withFontdue
205
+ } = await importConfig();
206
+ const userRule = {
207
+ source: '/old',
208
+ destination: '/new'
209
+ };
210
+ const config = withFontdue({
211
+ rewrites: async () => ({
212
+ beforeFiles: [userRule]
213
+ })
214
+ });
215
+ const rewrites = await config.rewrites();
216
+ expect(rewrites.beforeFiles[0]).toEqual(userRule);
217
+ expect(rewrites.beforeFiles).toHaveLength(5);
218
+ });
219
+ it("treats a plain-array rewrites() result as afterFiles, per Next's contract", async () => {
220
+ stubSingleTenant();
221
+ const {
222
+ withFontdue
223
+ } = await importConfig();
224
+ const userRule = {
225
+ source: '/old',
226
+ destination: '/new'
227
+ };
228
+ const rewrites = await withFontdue({
229
+ rewrites: async () => [userRule]
230
+ }).rewrites();
231
+ expect(rewrites.afterFiles).toEqual([userRule]);
232
+ expect(rewrites.beforeFiles).toHaveLength(4);
233
+ });
234
+ it('merges image settings, keeping app remotePatterns and overrides', async () => {
235
+ stubMultiTenant();
236
+ const {
237
+ withFontdue
238
+ } = await importConfig();
239
+ const config = withFontdue({
240
+ images: {
241
+ dangerouslyAllowSVG: false,
242
+ remotePatterns: [{
243
+ protocol: 'https',
244
+ hostname: 'cdn.example'
245
+ }]
246
+ }
247
+ });
248
+ expect(config.images.dangerouslyAllowSVG).toBe(false);
249
+ // NODE_ENV is 'test' here, i.e. not production → dev workaround active.
250
+ expect(config.images.unoptimized).toBe(true);
251
+ const hostnames = config.images.remotePatterns.map(p => p.hostname);
252
+ expect(hostnames).toContain('cdn.example');
253
+ expect(hostnames).toContain('*.fontdue.com');
254
+ expect(hostnames).toContain('**');
255
+ });
256
+ it('passes unrelated config through and defaults htmlLimitedBots', async () => {
257
+ stubSingleTenant();
258
+ const {
259
+ withFontdue
260
+ } = await importConfig();
261
+ const config = withFontdue({
262
+ reactStrictMode: true
263
+ });
264
+ expect(config.reactStrictMode).toBe(true);
265
+ expect(config.htmlLimitedBots).toEqual(/.*/);
266
+ expect(withFontdue({
267
+ htmlLimitedBots: /Googlebot/
268
+ }).htmlLimitedBots).toEqual(/Googlebot/);
269
+ });
270
+ });
271
+ describe('revalidate POST', () => {
272
+ it('multi-tenant: purges only the tenant tag', async () => {
273
+ stubMultiTenant();
274
+ const {
275
+ POST
276
+ } = await importRevalidate();
277
+ const response = await POST(new Request('http://internal/api/revalidate?domain=Acme.Fontdue.com', {
278
+ method: 'POST'
279
+ }));
280
+ expect(response.status).toBe(200);
281
+ expect(revalidateTag).toHaveBeenCalledExactlyOnceWith('graphql:acme.fontdue.com');
282
+ });
283
+ it('multi-tenant: 400s on a missing or invalid domain', async () => {
284
+ stubMultiTenant();
285
+ const {
286
+ POST
287
+ } = await importRevalidate();
288
+ for (const url of ['http://internal/api/revalidate', 'http://internal/api/revalidate?domain=not%20a%20domain']) {
289
+ const response = await POST(new Request(url, {
290
+ method: 'POST'
291
+ }));
292
+ expect(response.status).toBe(400);
293
+ }
294
+ expect(revalidateTag).not.toHaveBeenCalled();
295
+ });
296
+ it('single-tenant: purges the global graphql tag', async () => {
297
+ stubSingleTenant();
298
+ const {
299
+ POST
300
+ } = await importRevalidate();
301
+ const response = await POST(new Request('http://internal/api/revalidate', {
302
+ method: 'POST'
303
+ }));
304
+ expect(response.status).toBe(200);
305
+ expect(revalidateTag).toHaveBeenCalledExactlyOnceWith('graphql');
306
+ });
307
+ });
@@ -85,7 +85,10 @@ export function BuyButtonPreloadedIDQueryRenderer(_ref3) {
85
85
  preloadedQuery,
86
86
  ...rest
87
87
  } = _ref3;
88
- const queryRef = useSerializablePreloadedQuery(preloadedQuery);
88
+ // The query node lets the hook commit the payload into the store, so
89
+ // usePreloadedQuery resolves synchronously during SSR instead of
90
+ // refetching (the response cache only exists in the browser).
91
+ const queryRef = useSerializablePreloadedQuery(preloadedQuery, 'store-or-network', idQuery);
89
92
  const data = usePreloadedQuery(idQuery, queryRef);
90
93
  return /*#__PURE__*/React.createElement(BuyButtonComponent, _extends({}, rest, data));
91
94
  }
@@ -108,7 +111,10 @@ export function BuyButtonPreloadedSlugQueryRenderer(_ref5) {
108
111
  preloadedQuery,
109
112
  ...rest
110
113
  } = _ref5;
111
- const queryRef = useSerializablePreloadedQuery(preloadedQuery);
114
+ // The query node lets the hook commit the payload into the store, so
115
+ // usePreloadedQuery resolves synchronously during SSR instead of
116
+ // refetching (the response cache only exists in the browser).
117
+ const queryRef = useSerializablePreloadedQuery(preloadedQuery, 'store-or-network', slugQuery);
112
118
  const data = usePreloadedQuery(slugQuery, queryRef);
113
119
  return /*#__PURE__*/React.createElement(BuyButtonComponent, _extends({}, rest, {
114
120
  collection: ((_data$viewer2 = data.viewer) === null || _data$viewer2 === void 0 ? void 0 : (_data$viewer2$slug = _data$viewer2.slug) === null || _data$viewer2$slug === void 0 ? void 0 : _data$viewer2$slug.collection) ?? null
@@ -0,0 +1,10 @@
1
+ import { Environment } from 'relay-runtime';
2
+ /**
3
+ * Stores the buyer's analytics context (cookie consent, Meta browser IDs,
4
+ * checkout page URL) on the current order. The server emits the purchase
5
+ * event from a Stripe webhook — outside the browser — so this is how that
6
+ * event respects the consent banner and carries attribution.
7
+ *
8
+ * Fire-and-forget: tracking must never break checkout.
9
+ */
10
+ export declare function sendOrderTracking(environment: Environment): void;
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.sendOrderTracking = sendOrderTracking;
7
+ var _orderTrackingUpdateOrderTrackingMutation2 = _interopRequireDefault(require("../../__generated__/orderTrackingUpdateOrderTrackingMutation.graphql"));
8
+ var _reactRelay = require("react-relay");
9
+ var _consent = require("../ConsentBanner/consent");
10
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
11
+ function readCookie(name) {
12
+ const match = document.cookie.match(new RegExp('(?:^|;\\s*)' + name + '=([^;]*)'));
13
+ return match ? decodeURIComponent(match[1]) : undefined;
14
+ }
15
+
16
+ /**
17
+ * Stores the buyer's analytics context (cookie consent, Meta browser IDs,
18
+ * checkout page URL) on the current order. The server emits the purchase
19
+ * event from a Stripe webhook — outside the browser — so this is how that
20
+ * event respects the consent banner and carries attribution.
21
+ *
22
+ * Fire-and-forget: tracking must never break checkout.
23
+ */
24
+ function sendOrderTracking(environment) {
25
+ try {
26
+ (0, _reactRelay.commitMutation)(environment, {
27
+ mutation: (_orderTrackingUpdateOrderTrackingMutation2.default.hash && _orderTrackingUpdateOrderTrackingMutation2.default.hash !== "d59a127a7f140424f507ae549731bac7" && console.error("The definition of 'orderTrackingUpdateOrderTrackingMutation' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _orderTrackingUpdateOrderTrackingMutation2.default),
28
+ variables: {
29
+ input: {
30
+ analyticsConsent: (0, _consent.hasConsent)('analytics'),
31
+ anonymousId: (0, _consent.getClientAnonymousId)(),
32
+ fbp: readCookie('_fbp'),
33
+ fbc: readCookie('_fbc'),
34
+ url: window.location.href
35
+ }
36
+ },
37
+ onCompleted: () => undefined,
38
+ onError: () => undefined
39
+ });
40
+ } catch {
41
+ // Ignore — see above.
42
+ }
43
+ }
@@ -3,7 +3,7 @@
3
3
  function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
4
4
  import _CartButtonQuery from "../../__generated__/CartButtonQuery.graphql.js";
5
5
  import _CartButton_order from "../../__generated__/CartButton_order.graphql.js";
6
- import React, { useEffect, useCallback, useContext, useState } from 'react';
6
+ import React, { Suspense, useEffect, useCallback, useContext, useState } from 'react';
7
7
  import { useDispatch } from 'react-redux';
8
8
  import { graphql, useFragment, useLazyLoadQuery } from 'react-relay';
9
9
  import ComponentsContext from '../ComponentsContext.js';
@@ -101,14 +101,26 @@ function CartButtonLazyQueryRenderer(props) {
101
101
  }
102
102
  // Cart contents are per-customer-session, so a server-side preload from a
103
103
  // CDN-cached SSG/prerender environment (no session cookies) just caches an
104
- // empty cart and delays the real one. CartButton always lazy-fetches on
105
- // hydration same pattern as StoreModal.
104
+ // empty cart and delays the real one. CartButton lazy-fetches on hydration —
105
+ // and only after mount: useLazyLoadQuery would otherwise also run during the
106
+ // SSR pass, where the fetch is sessionless (and in multi-tenant setups may
107
+ // have no resolvable GraphQL URL at all). Until mount it renders the empty
108
+ // cart state, which is also the Suspense fallback while the real fetch runs,
109
+ // so server HTML and first client render agree.
106
110
  export default function CartButton(_ref2) {
107
111
  let {
108
112
  config,
109
113
  ...props
110
114
  } = _ref2;
115
+ const [mounted, setMounted] = useState(false);
116
+ useEffect(() => setMounted(true), []);
111
117
  return /*#__PURE__*/React.createElement(EnsureFontdueContext, {
112
118
  config: config
113
- }, /*#__PURE__*/React.createElement(CartButtonLazyQueryRenderer, props));
119
+ }, mounted ? /*#__PURE__*/React.createElement(Suspense, {
120
+ fallback: /*#__PURE__*/React.createElement(CartButtonComponent, _extends({}, props, {
121
+ order: null
122
+ }))
123
+ }, /*#__PURE__*/React.createElement(CartButtonLazyQueryRenderer, props)) : /*#__PURE__*/React.createElement(CartButtonComponent, _extends({}, props, {
124
+ order: null
125
+ })));
114
126
  }
@@ -446,7 +446,10 @@ export function CharacterViewerPreloadedIDQueryRenderer(_ref8) {
446
446
  preloadedQuery,
447
447
  ...rest
448
448
  } = _ref8;
449
- const queryRef = useSerializablePreloadedQuery(preloadedQuery);
449
+ // The query node lets the hook commit the payload into the store, so
450
+ // usePreloadedQuery resolves synchronously during SSR instead of
451
+ // refetching (the response cache only exists in the browser).
452
+ const queryRef = useSerializablePreloadedQuery(preloadedQuery, 'store-or-network', idQuery);
450
453
  const data = usePreloadedQuery(idQuery, queryRef);
451
454
  if (!data.node) return null;
452
455
  return /*#__PURE__*/React.createElement(CharacterViewerComponent, _extends({}, rest, {
@@ -473,7 +476,10 @@ export function CharacterViewerPreloadedSlugQueryRenderer(_ref10) {
473
476
  preloadedQuery,
474
477
  ...rest
475
478
  } = _ref10;
476
- const queryRef = useSerializablePreloadedQuery(preloadedQuery);
479
+ // The query node lets the hook commit the payload into the store, so
480
+ // usePreloadedQuery resolves synchronously during SSR instead of
481
+ // refetching (the response cache only exists in the browser).
482
+ const queryRef = useSerializablePreloadedQuery(preloadedQuery, 'store-or-network', slugQuery);
477
483
  const data = usePreloadedQuery(slugQuery, queryRef);
478
484
  const collection = data === null || data === void 0 ? void 0 : (_data$viewer2 = data.viewer) === null || _data$viewer2 === void 0 ? void 0 : (_data$viewer2$slug = _data$viewer2.slug) === null || _data$viewer2$slug === void 0 ? void 0 : _data$viewer2$slug.fontCollection;
479
485
  if (!collection) return null;
@@ -9,7 +9,6 @@ const loginMutation = (_CustomerLoginFormLoginMutation.hash && _CustomerLoginFor
9
9
  const query = (_CustomerLoginFormQuery.hash && _CustomerLoginFormQuery.hash !== "f32c7b0b485a2b2a02ddf2cbc5b87a3c" && console.error("The definition of 'CustomerLoginFormQuery' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _CustomerLoginFormQuery);
10
10
  const DEFAULT_SUBMITTED_LABEL = '<p>Submitted!</p><p>Please check your email inbox for a link to log in.</p><p>If you don\u2019t receive an email, please contact us to retrieve your order information.</p>';
11
11
  const CustomerLoginForm = _ref => {
12
- var _data$viewer, _data$viewer$settings;
13
12
  let {
14
13
  submitLabel = 'Submit'
15
14
  } = _ref;
@@ -18,8 +17,6 @@ const CustomerLoginForm = _ref => {
18
17
  const [submitting, setSubmitting] = useState(false);
19
18
  const [submitted, setSubmitted] = useState(false);
20
19
  const environment = useRelayEnvironment();
21
- const data = useLazyLoadQuery(query, {});
22
- const submittedLabel = ((_data$viewer = data.viewer) === null || _data$viewer === void 0 ? void 0 : (_data$viewer$settings = _data$viewer.settings) === null || _data$viewer$settings === void 0 ? void 0 : _data$viewer$settings.customerLoginSubmittedLabel) || DEFAULT_SUBMITTED_LABEL;
23
20
  const handleSubmit = e => {
24
21
  e.preventDefault();
25
22
  setSubmitting(true);
@@ -54,12 +51,7 @@ const CustomerLoginForm = _ref => {
54
51
  className: "login-form"
55
52
  }, error && /*#__PURE__*/React.createElement("div", {
56
53
  className: "login-form__errors"
57
- }, error), submitted ? /*#__PURE__*/React.createElement("div", {
58
- className: "login-form__submitted",
59
- dangerouslySetInnerHTML: {
60
- __html: submittedLabel
61
- }
62
- }) : /*#__PURE__*/React.createElement("form", {
54
+ }, error), submitted ? /*#__PURE__*/React.createElement(SubmittedMessage, null) : /*#__PURE__*/React.createElement("form", {
63
55
  className: "login-form__form",
64
56
  onSubmit: handleSubmit
65
57
  }, /*#__PURE__*/React.createElement("div", {
@@ -79,4 +71,20 @@ const CustomerLoginForm = _ref => {
79
71
  className: "submit-button__arrow"
80
72
  }, " \u2192")))));
81
73
  };
74
+
75
+ // The settings label is only shown after a submit, which can only happen in
76
+ // the browser — fetching it here (not in the form component) keeps the lazy
77
+ // query out of the SSR pass, where it has no session and, in multi-tenant
78
+ // setups, possibly no resolvable GraphQL URL.
79
+ function SubmittedMessage() {
80
+ var _data$viewer, _data$viewer$settings;
81
+ const data = useLazyLoadQuery(query, {});
82
+ const submittedLabel = ((_data$viewer = data.viewer) === null || _data$viewer === void 0 ? void 0 : (_data$viewer$settings = _data$viewer.settings) === null || _data$viewer$settings === void 0 ? void 0 : _data$viewer$settings.customerLoginSubmittedLabel) || DEFAULT_SUBMITTED_LABEL;
83
+ return /*#__PURE__*/React.createElement("div", {
84
+ className: "login-form__submitted",
85
+ dangerouslySetInnerHTML: {
86
+ __html: submittedLabel
87
+ }
88
+ });
89
+ }
82
90
  export default CustomerLoginForm;
@@ -2,11 +2,20 @@ import React from 'react';
2
2
  export { default as loadFontdueProviderQuery } from '../../loadFontdueProviderQuery.js';
3
3
  import type { FontdueContextProvider_props } from '../FontdueContextProvider/index.js';
4
4
  import { SerializablePreloadedQuery } from '../../relay/loadSerializableQuery.js';
5
+ import type { FontdueServerConfig } from '../../relay/serverConfig.js';
5
6
  import { FontdueProviderQuery } from '../../__generated__/FontdueProviderQuery.graphql.js';
6
7
  export declare const fontdueProviderQuery: import("react-relay").GraphQLTaggedNode;
7
8
  export type FontdueProviderPreloadedQuery = SerializablePreloadedQuery<FontdueProviderQuery>;
8
9
  export interface FontdueProvider_props extends FontdueContextProvider_props {
9
10
  preloadedQuery?: FontdueProviderPreloadedQuery;
11
+ /**
12
+ * Per-render config for server-side fetches (url, headers, cacheTags).
13
+ * Only honoured by the React Server Components entrypoint, which writes it
14
+ * to the render-scoped config store and strips it before this client
15
+ * component — it may carry internal headers that must never reach the
16
+ * browser. Ignored when the client provider is used directly.
17
+ */
18
+ serverConfig?: FontdueServerConfig;
10
19
  }
11
- declare const FontdueProvider: ({ children, preloadedQuery, ...rest }: FontdueProvider_props) => React.JSX.Element;
20
+ declare const FontdueProvider: ({ children, preloadedQuery, serverConfig: _serverConfig, ...rest }: FontdueProvider_props) => React.JSX.Element;
12
21
  export default FontdueProvider;
@@ -22,6 +22,7 @@ const FontdueProvider = _ref => {
22
22
  let {
23
23
  children,
24
24
  preloadedQuery,
25
+ serverConfig: _serverConfig,
25
26
  ...rest
26
27
  } = _ref;
27
28
  return /*#__PURE__*/React.createElement(FontdueContextProvider, rest, /*#__PURE__*/React.createElement(ErrorBoundary, {
@@ -2,5 +2,6 @@ import React from 'react';
2
2
  import type { FontdueProvider_props } from './index.js';
3
3
  export type { FontdueProvider_props } from './index.js';
4
4
  export type { FontdueProviderPreloadedQuery } from '../../loadFontdueProviderQuery.js';
5
+ export type { FontdueServerConfig } from '../../relay/serverConfig.js';
5
6
  export declare function loadFontdueProviderQuery(): never;
6
- export default function FontdueProviderServer({ preloadedQuery, ...rest }: FontdueProvider_props): Promise<React.JSX.Element>;
7
+ export default function FontdueProviderServer({ preloadedQuery, serverConfig, ...rest }: FontdueProvider_props): Promise<React.JSX.Element>;