@faststore/core 4.5.0-dev.5 → 4.5.0-dev.7
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/.turbo/turbo-generate.log +3 -3
- package/.turbo/turbo-test.log +77 -74
- package/CHANGELOG.md +12 -0
- package/package.json +6 -6
- package/src/experimental/myAccountCmsServerSideProps.ts +146 -0
- package/src/experimental/myAccountServerSideProps.ts +1 -1
- package/src/pages/pvt/account/404.tsx +2 -2
- package/src/sdk/account/getMyAccountRoutes.ts +6 -0
- package/src/server/cms/fetchMyAccountPageContent.ts +13 -12
- package/src/server/cms/myAccountDefaultSections.ts +8 -3
- package/src/utils/myAccountRedirect.tsx +15 -6
- package/test/experimental/myAccountCmsServerSideProps.test.ts +221 -0
- package/test/sdk/account/getMyAccountRoutes.test.ts +39 -0
- package/test/server/cms/myAccountDefaultSections.test.ts +49 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
|
|
2
|
-
> @faststore/core@4.5.0-dev.
|
|
2
|
+
> @faststore/core@4.5.0-dev.6 generate /home/runner/work/faststore/faststore/packages/core
|
|
3
3
|
> pnpm run gen-types && pnpm run cache-graphql
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
> @faststore/core@4.5.0-dev.
|
|
6
|
+
> @faststore/core@4.5.0-dev.6 gen-types /home/runner/work/faststore/faststore/packages/core
|
|
7
7
|
> node ../cli/bin/run generate-types .
|
|
8
8
|
|
|
9
9
|
[33m[STARTED][39m Parse Configuration
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
[32m[COMPLETED][39m Generate to /home/runner/work/faststore/faststore/packages/core/@generated/
|
|
20
20
|
[32m[COMPLETED][39m Generate outputs
|
|
21
21
|
|
|
22
|
-
> @faststore/core@4.5.0-dev.
|
|
22
|
+
> @faststore/core@4.5.0-dev.6 cache-graphql /home/runner/work/faststore/faststore/packages/core
|
|
23
23
|
> node ../cli/bin/run cache-graphql --config=./discovery.config.default.js --queries=./@generated/persisted-documents.json
|
|
24
24
|
|
|
25
25
|
[Info] - Config file location: /home/runner/work/faststore/faststore/packages/core/discovery.config.default.js
|
package/.turbo/turbo-test.log
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
|
|
2
|
-
> @faststore/core@4.5.0-dev.
|
|
2
|
+
> @faststore/core@4.5.0-dev.6 test /home/runner/work/faststore/faststore/packages/core
|
|
3
3
|
> vitest run
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
[1m[46m RUN [49m[22m [36mv4.0.7 [39m[90m/home/runner/work/faststore/faststore/packages/core[39m
|
|
7
7
|
|
|
8
|
-
[32m✓[39m [30m[46m browser [49m[39m test/sdk/session/reconcileSessionLocale.browser.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m
|
|
8
|
+
[32m✓[39m [30m[46m browser [49m[39m test/sdk/session/reconcileSessionLocale.browser.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 46[2mms[22m[39m
|
|
9
9
|
[90mstderr[2m | test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx
|
|
10
10
|
[22m[39mError in optimistic validation: TypeError: Failed to parse URL from /api/graphql?operationName=ValidateCartMutation&operationHash=8199d03debb0a6751ec53d8ee8987b78d0292acb
|
|
11
11
|
[90m at node:internal/deps/undici/undici:16416:13[39m
|
|
@@ -32,13 +32,15 @@
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
Browserslist: browsers data (caniuse-lite) is 6 months old. Please run:
|
|
36
|
+
npx update-browserslist-db@latest
|
|
37
|
+
Why you should do it regularly: https://github.com/browserslist/update-db#readme
|
|
35
38
|
[90mstderr[2m | test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx[2m > [22m[2mRecommendationShelf[2m > [22m[2mrenders nothing when there is an error
|
|
36
39
|
[22m[39mError fetching recommendations [90mundefined[39m boom for campaign vrn:recommendations:acc:rec-top-items-v2:campaign-1
|
|
37
40
|
|
|
38
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx [2m([22m[2m8 tests[22m[2m)[22m[32m
|
|
39
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/account/ContractSwitcher.browser.test.tsx [2m([22m[2m13 tests[22m[2m)[22m[33m
|
|
40
|
-
[33m[2m✓[22m[39m shows an error state when the list fails to load [33m
|
|
41
|
-
[33m[2m✓[22m[39m requires selecting a contract before Confirm is enabled, then switches (REQ-03) [33m 420[2mms[22m[39m
|
|
41
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx [2m([22m[2m8 tests[22m[2m)[22m[32m 237[2mms[22m[39m
|
|
42
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/account/ContractSwitcher.browser.test.tsx [2m([22m[2m13 tests[22m[2m)[22m[33m 1804[2mms[22m[39m
|
|
43
|
+
[33m[2m✓[22m[39m shows an error state when the list fails to load [33m 571[2mms[22m[39m
|
|
42
44
|
[90mstderr[2m | test/pages/password-protection.browser.test.tsx[2m > [22m[2mPasswordProtectionLogin[2m > [22m[2mposts the password and redirects to the requested return path
|
|
43
45
|
[22m[39mWarning: An update to PasswordProtectionLogin inside a test was not wrapped in act(...).
|
|
44
46
|
|
|
@@ -268,10 +270,12 @@ act(() => {
|
|
|
268
270
|
This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act
|
|
269
271
|
at PasswordProtectionLogin (/home/runner/work/faststore/faststore/packages/core/src/pages/password-protection/index.tsx:19:69)
|
|
270
272
|
|
|
271
|
-
[32m✓[39m [30m[46m browser [49m[39m test/
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
[32m✓[39m [30m[46m browser [49m[39m test/sdk/account/changeContractToken.browser.test.ts [2m([22m[2m9 tests[22m[2m)[22m[32m
|
|
273
|
+
[32m✓[39m [30m[46m browser [49m[39m test/pages/password-protection.browser.test.tsx [2m([22m[2m6 tests[22m[2m)[22m[33m 1941[2mms[22m[39m
|
|
274
|
+
[33m[2m✓[22m[39m renders the password protection form [33m 822[2mms[22m[39m
|
|
275
|
+
[32m✓[39m [30m[46m browser [49m[39m test/sdk/session/getInitialSession.browser.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 25[2mms[22m[39m
|
|
276
|
+
[32m✓[39m [30m[46m browser [49m[39m test/sdk/account/changeContractToken.browser.test.ts [2m([22m[2m9 tests[22m[2m)[22m[32m 59[2mms[22m[39m
|
|
277
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/account/OrganizationDrawer.browser.test.tsx [2m([22m[2m5 tests[22m[2m)[22m[33m 1651[2mms[22m[39m
|
|
278
|
+
[33m[2m✓[22m[39m renders the menu view with contract details and manage footer [33m 974[2mms[22m[39m
|
|
275
279
|
[90mstderr[2m | test/sdk/localization/store-url.browser.test.ts
|
|
276
280
|
[22m[39mError in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
|
|
277
281
|
at validateCart [90m(/home/runner/work/faststore/faststore/packages/core/[39msrc/sdk/cart/index.ts:121:27[90m)[39m
|
|
@@ -279,12 +283,9 @@ This ensures that you're testing the behavior the user would see in the browser.
|
|
|
279
283
|
at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:354:23)
|
|
280
284
|
[90m at processTicksAndRejections (node:internal/process/task_queues:103:5)[39m
|
|
281
285
|
|
|
282
|
-
[32m✓[39m [30m[46m browser [49m[39m test/sdk/localization/store-url.browser.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m
|
|
283
|
-
[32m✓[39m [30m[46m browser [49m[39m test/
|
|
284
|
-
|
|
285
|
-
[33m[2m✓[22m[39m opens the contract switcher and returns to the menu on back [33m 505[2mms[22m[39m
|
|
286
|
-
[33m[2m✓[22m[39m resets to the menu when the drawer closes [33m 303[2mms[22m[39m
|
|
287
|
-
[32m✓[39m [30m[46m browser [49m[39m test/sdk/account/useAvailableContracts.browser.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 85[2mms[22m[39m
|
|
286
|
+
[32m✓[39m [30m[46m browser [49m[39m test/sdk/localization/store-url.browser.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 36[2mms[22m[39m
|
|
287
|
+
[32m✓[39m [30m[46m browser [49m[39m test/sdk/account/useAvailableContracts.browser.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 91[2mms[22m[39m
|
|
288
|
+
[32m✓[39m [30m[46m browser [49m[39m test/sdk/account/useSwitchContract.browser.test.ts [2m([22m[2m4 tests[22m[2m)[22m[32m 53[2mms[22m[39m
|
|
288
289
|
[90mstderr[2m | test/components/account/doLogout.browser.test.ts
|
|
289
290
|
[22m[39mError in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
|
|
290
291
|
at validateCart [90m(/home/runner/work/faststore/faststore/packages/core/[39msrc/sdk/cart/index.ts:121:27[90m)[39m
|
|
@@ -292,9 +293,8 @@ This ensures that you're testing the behavior the user would see in the browser.
|
|
|
292
293
|
at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:354:23)
|
|
293
294
|
[90m at processTicksAndRejections (node:internal/process/task_queues:103:5)[39m
|
|
294
295
|
|
|
295
|
-
[32m✓[39m [30m[46m browser [49m[39m test/
|
|
296
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/account/
|
|
297
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/account/OrganizationDrawerHeader.browser.test.tsx [2m([22m[2m3 tests[22m[2m)[22m[32m 263[2mms[22m[39m
|
|
296
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/account/doLogout.browser.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m 24[2mms[22m[39m
|
|
297
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/account/OrganizationDrawerHeader.browser.test.tsx [2m([22m[2m3 tests[22m[2m)[22m[32m 249[2mms[22m[39m
|
|
298
298
|
[90mstderr[2m | test/sdk/account/clearPersistedSessionState.browser.test.ts
|
|
299
299
|
[22m[39mError in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
|
|
300
300
|
at validateCart [90m(/home/runner/work/faststore/faststore/packages/core/[39msrc/sdk/cart/index.ts:121:27[90m)[39m
|
|
@@ -302,12 +302,12 @@ This ensures that you're testing the behavior the user would see in the browser.
|
|
|
302
302
|
at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:354:23)
|
|
303
303
|
[90m at processTicksAndRejections (node:internal/process/task_queues:103:5)[39m
|
|
304
304
|
|
|
305
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/account/OrganizationDrawerFooter.browser.test.tsx [2m([22m[2m3 tests[22m[2m)[22m[32m 248[2mms[22m[39m
|
|
305
306
|
[32m✓[39m [30m[46m browser [49m[39m test/sdk/account/clearPersistedSessionState.browser.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m 8[2mms[22m[39m
|
|
306
|
-
[32m✓[39m [30m[46m browser [49m[39m test/
|
|
307
|
-
[32m✓[39m [30m[
|
|
308
|
-
[32m✓[39m [30m[43m node [49m[39m test/utils/localization/bindingPaths.test.ts [2m([22m[2m71 tests[22m[2m)[22m[32m 19[2mms[22m[39m
|
|
307
|
+
[32m✓[39m [30m[46m browser [49m[39m test/utils/isLocalHost.browser.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 4[2mms[22m[39m
|
|
308
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/localization/bindingPaths.test.ts [2m([22m[2m71 tests[22m[2m)[22m[32m 23[2mms[22m[39m
|
|
309
309
|
[32m✓[39m [30m[46m browser [49m[39m test/sdk/session/reconcileSessionLocale.disabled.browser.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m 3[2mms[22m[39m
|
|
310
|
-
[32m✓[39m [30m[43m node [49m[39m test/server/password-protection-service.test.ts [2m([22m[2m22 tests[22m[2m)[22m[32m
|
|
310
|
+
[32m✓[39m [30m[43m node [49m[39m test/server/password-protection-service.test.ts [2m([22m[2m22 tests[22m[2m)[22m[32m 30[2mms[22m[39m
|
|
311
311
|
[90mstderr[2m | test/sdk/account/refreshToken.browser.test.ts
|
|
312
312
|
[22m[39mError in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
|
|
313
313
|
at validateCart [90m(/home/runner/work/faststore/faststore/packages/core/[39msrc/sdk/cart/index.ts:121:27[90m)[39m
|
|
@@ -315,11 +315,11 @@ This ensures that you're testing the behavior the user would see in the browser.
|
|
|
315
315
|
at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:354:23)
|
|
316
316
|
[90m at processTicksAndRejections (node:internal/process/task_queues:103:5)[39m
|
|
317
317
|
|
|
318
|
-
[32m✓[39m [30m[
|
|
319
|
-
[32m✓[39m [30m[
|
|
320
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/localization/useBindingSelector.test.tsx [2m([22m[2m22 tests[22m[2m)[22m[32m
|
|
321
|
-
[32m✓[39m [30m[43m node [49m[39m test/pages/api/unlock.test.ts [2m([22m[2m15 tests[22m[2m)[22m[32m
|
|
322
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/localization/bindingSelector.test.ts [2m([22m[2m18 tests[22m[2m)[22m[32m
|
|
318
|
+
[32m✓[39m [30m[46m browser [49m[39m test/sdk/account/refreshToken.browser.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m 6[2mms[22m[39m
|
|
319
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/match-url.test.ts [2m([22m[2m10 tests[22m[2m)[22m[32m 10[2mms[22m[39m
|
|
320
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/localization/useBindingSelector.test.tsx [2m([22m[2m22 tests[22m[2m)[22m[32m 10[2mms[22m[39m
|
|
321
|
+
[32m✓[39m [30m[43m node [49m[39m test/pages/api/unlock.test.ts [2m([22m[2m15 tests[22m[2m)[22m[32m 201[2mms[22m[39m
|
|
322
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/localization/bindingSelector.test.ts [2m([22m[2m18 tests[22m[2m)[22m[32m 6[2mms[22m[39m
|
|
323
323
|
[90mstderr[2m | test/components/search/SearchInputComponent.test.tsx
|
|
324
324
|
[22m[39mWarning: forwardRef render functions accept exactly two parameters: props and ref. Did you forget to use the ref parameter?
|
|
325
325
|
|
|
@@ -383,22 +383,23 @@ Warning: React does not recognize the `providerProps` prop on a DOM element. If
|
|
|
383
383
|
at Suspense
|
|
384
384
|
at Wrapper (/home/runner/work/faststore/faststore/packages/core/test/components/search/SearchInputComponent.test.tsx:106:20)
|
|
385
385
|
|
|
386
|
-
[32m✓[39m [30m[43m node [49m[39m test/components/search/SearchInputComponent.test.tsx [2m([22m[2m6 tests[22m[2m)[22m[32m
|
|
387
|
-
[32m✓[39m [30m[43m node [49m[39m test/utils/cookieCacheBusting.test.ts [2m([22m[2m10 tests[22m[2m)[22m[32m
|
|
386
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/search/SearchInputComponent.test.tsx [2m([22m[2m6 tests[22m[2m)[22m[32m 73[2mms[22m[39m
|
|
387
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/cookieCacheBusting.test.ts [2m([22m[2m10 tests[22m[2m)[22m[32m 13[2mms[22m[39m
|
|
388
388
|
[32m✓[39m [30m[43m node [49m[39m test/sdk/search/useSearchHistory.test.ts [2m([22m[2m13 tests[22m[2m)[22m[32m 51[2mms[22m[39m
|
|
389
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
390
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
389
|
+
[32m✓[39m [30m[43m node [49m[39m test/experimental/myAccountCmsServerSideProps.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 9[2mms[22m[39m
|
|
390
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/clearCookies.test.ts [2m([22m[2m20 tests[22m[2m)[22m[32m 27[2mms[22m[39m
|
|
391
391
|
[90mstderr[2m | test/sdk/account/useSetPassword.test.ts[2m > [22m[2museSetPassword[2m > [22m[2mmaps wrongcredentials error responses to the existing message
|
|
392
392
|
[22m[39mSet password request failed: Bad Request
|
|
393
393
|
|
|
394
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/account/useSetPassword.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m
|
|
395
|
-
[32m✓[39m [30m[43m node [49m[39m test/components/quotes/
|
|
396
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
397
|
-
[32m✓[39m [30m[43m node [49m[39m test/utils/
|
|
398
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
399
|
-
|
|
400
|
-
[33m[2m✓[22m[39m navigates
|
|
401
|
-
|
|
394
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/account/useSetPassword.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 48[2mms[22m[39m
|
|
395
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/quotes/MyAccountListQuotesTable.test.tsx [2m([22m[2m12 tests[22m[2m)[22m[33m 354[2mms[22m[39m
|
|
396
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/quotes/quoteFilters.test.ts [2m([22m[2m23 tests[22m[2m)[22m[32m 32[2mms[22m[39m
|
|
397
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/multipleTemplates.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m 4[2mms[22m[39m
|
|
398
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/localization/localizeRedirectDestination.test.ts [2m([22m[2m11 tests[22m[2m)[22m[32m 4[2mms[22m[39m
|
|
399
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/quotes/MyAccountListQuotes.test.tsx [2m([22m[2m9 tests[22m[2m)[22m[33m 833[2mms[22m[39m
|
|
400
|
+
[33m[2m✓[22m[39m navigates with the search term on blur [33m 363[2mms[22m[39m
|
|
401
|
+
[33m[2m✓[22m[39m navigates without a label param when the search is cleared [33m 356[2mms[22m[39m
|
|
402
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/orderEntry/useOrderEntryOperation.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m 43[2mms[22m[39m
|
|
402
403
|
[90mstderr[2m | test/pages/api/graphql.test.ts[2m > [22m[2m/api/graphql error status propagation[2m > [22m[2mpropagates the upstream 400 from a wrapped BadRequestError instead of 500
|
|
403
404
|
[22m[39mGraphql execution returned with error: [
|
|
404
405
|
{
|
|
@@ -446,45 +447,47 @@ Warning: React does not recognize the `providerProps` prop on a DOM element. If
|
|
|
446
447
|
]
|
|
447
448
|
|
|
448
449
|
[32m✓[39m [30m[43m node [49m[39m test/pages/api/graphql.test.ts [2m([22m[2m10 tests[22m[2m)[22m[32m 24[2mms[22m[39m
|
|
449
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/orderEntry/useOrderEntryUpload.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m
|
|
450
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/analytics/useStartRecommendationSession.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m
|
|
451
|
-
[32m✓[39m [30m[43m node [49m[39m test/components/quotes/MyAccountQuotesFilterSlider.test.tsx [2m([22m[2m5 tests[22m[2m)[22m[32m
|
|
452
|
-
[32m✓[39m [30m[43m node [49m[39m test/proxy.localization.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m
|
|
453
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
454
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/orderEntry/useOrderEntry.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m
|
|
450
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/orderEntry/useOrderEntryUpload.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m 52[2mms[22m[39m
|
|
451
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/analytics/useStartRecommendationSession.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 58[2mms[22m[39m
|
|
452
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/quotes/MyAccountQuotesFilterSlider.test.tsx [2m([22m[2m5 tests[22m[2m)[22m[32m 145[2mms[22m[39m
|
|
453
|
+
[32m✓[39m [30m[43m node [49m[39m test/proxy.localization.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 73[2mms[22m[39m
|
|
454
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/account/getMyAccountRoutes.test.ts [2m([22m[2m10 tests[22m[2m)[22m[32m 5[2mms[22m[39m
|
|
455
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/orderEntry/useOrderEntry.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m 37[2mms[22m[39m
|
|
456
|
+
[32m✓[39m [30m[43m node [49m[39m test/server/index.test.ts [2m([22m[2m7 tests[22m[2m)[22m[33m 1241[2mms[22m[39m
|
|
457
|
+
[33m[2m✓[22m[39m should handle options and execute [33m 743[2mms[22m[39m
|
|
455
458
|
[32m✓[39m [30m[43m node [49m[39m test/proxy.test.ts [2m([22m[2m4 tests[22m[2m)[22m[32m 25[2mms[22m[39m
|
|
456
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
457
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
458
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
459
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/
|
|
460
|
-
[32m✓[39m [30m[43m node [49m[39m test/server/cms/global.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m
|
|
461
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
462
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
463
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/localization/store-url.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m 5[2mms[22m[39m
|
|
459
|
+
[32m✓[39m [30m[43m node [49m[39m test/server/webops-api.test.ts [2m([22m[2m9 tests[22m[2m)[22m[32m 22[2mms[22m[39m
|
|
460
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/quotes/MyAccountQuotesSelectedTags.test.tsx [2m([22m[2m8 tests[22m[2m)[22m[32m 72[2mms[22m[39m
|
|
461
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/account/refreshToken.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 7[2mms[22m[39m
|
|
462
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/orderEntry/useOrderFormItems.test.ts [2m([22m[2m7 tests[22m[2m)[22m[32m 37[2mms[22m[39m
|
|
463
|
+
[32m✓[39m [30m[43m node [49m[39m test/server/cms/global.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 3[2mms[22m[39m
|
|
464
|
+
[32m✓[39m [30m[43m node [49m[39m test/server/content/service.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 5[2mms[22m[39m
|
|
465
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/localization/store-url.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m 4[2mms[22m[39m
|
|
464
466
|
[32m✓[39m [30m[43m node [49m[39m test/sdk/analytics/hasEnabledRecommendationShelf.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 4[2mms[22m[39m
|
|
465
467
|
[32m✓[39m [30m[43m node [49m[39m test/utils/getRequestHostname.test.ts [2m([22m[2m12 tests[22m[2m)[22m[32m 6[2mms[22m[39m
|
|
466
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/auth/useAuth.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m
|
|
467
|
-
[32m✓[39m [30m[43m node [49m[39m test/components/quotes/MyAccountQuotesStatusSelector.test.tsx [2m([22m[2m6 tests[22m[2m)[22m[32m
|
|
468
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
469
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
468
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/auth/useAuth.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 29[2mms[22m[39m
|
|
469
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/quotes/MyAccountQuotesStatusSelector.test.tsx [2m([22m[2m6 tests[22m[2m)[22m[32m 135[2mms[22m[39m
|
|
470
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/validateSessionRefreshToken.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 3[2mms[22m[39m
|
|
471
|
+
[32m✓[39m [30m[43m node [49m[39m test/pages/api/preview.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m 16[2mms[22m[39m
|
|
470
472
|
[32m✓[39m [30m[43m node [49m[39m test/components/search/SearchInput.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 8[2mms[22m[39m
|
|
471
|
-
[32m✓[39m [30m[43m node [49m[39m test/components/RecommendationShelf/vrn.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m
|
|
472
|
-
[32m✓[39m [30m[43m node [49m[39m test/server/cms/index.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m
|
|
473
|
-
[32m✓[39m [30m[43m node [49m[39m test/utils/retry.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m
|
|
474
|
-
[32m✓[39m [30m[43m node [49m[39m test/components/account/Layout.test.tsx [2m([22m[2m4 tests[22m[2m)[22m[32m
|
|
473
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/RecommendationShelf/vrn.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 5[2mms[22m[39m
|
|
474
|
+
[32m✓[39m [30m[43m node [49m[39m test/server/cms/index.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m 8[2mms[22m[39m
|
|
475
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/retry.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 13[2mms[22m[39m
|
|
476
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/account/Layout.test.tsx [2m([22m[2m4 tests[22m[2m)[22m[32m 39[2mms[22m[39m
|
|
475
477
|
[32m✓[39m [30m[43m node [49m[39m test/components/RecommendationShelf/useRecommendationUserId.test.ts [2m([22m[2m4 tests[22m[2m)[22m[32m 190[2mms[22m[39m
|
|
476
|
-
[32m✓[39m [30m[43m node [49m[39m test/utils/unlockResponse.test.ts [2m([22m[2m11 tests[22m[2m)[22m[32m
|
|
478
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/unlockResponse.test.ts [2m([22m[2m11 tests[22m[2m)[22m[32m 5[2mms[22m[39m
|
|
479
|
+
[32m✓[39m [30m[43m node [49m[39m test/server/cms/myAccountDefaultSections.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 4[2mms[22m[39m
|
|
477
480
|
[32m✓[39m [30m[43m node [49m[39m test/components/RecommendationShelf/useRecommendations.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m 5[2mms[22m[39m
|
|
478
|
-
[32m✓[39m [30m[43m node [49m[39m test/utils/isLocalHost.test.ts [2m([22m[2m7 tests[22m[2m)[22m[32m
|
|
481
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/isLocalHost.test.ts [2m([22m[2m7 tests[22m[2m)[22m[32m 6[2mms[22m[39m
|
|
479
482
|
[32m✓[39m [30m[43m node [49m[39m test/utils/userOrderStatus.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 4[2mms[22m[39m
|
|
480
|
-
[32m✓[39m [30m[43m node [49m[39m test/components/account/OrganizationDrawerBody.test.tsx [2m([22m[2m3 tests[22m[2m)[22m[32m
|
|
481
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/analytics/utils.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m
|
|
482
|
-
[32m✓[39m [30m[43m node [49m[39m test/components/auth/ProfileChallenge.test.tsx [2m([22m[2m3 tests[22m[2m)[22m[32m
|
|
483
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/account/clearPersistedSessionState.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m
|
|
484
|
-
[32m✓[39m [30m[43m node [49m[39m test/components/quotes/MyAccountQuoteStatusBadge.test.tsx [2m([22m[2m2 tests[22m[2m)[22m[32m
|
|
485
|
-
|
|
486
|
-
[2m Test Files [22m [1m[
|
|
487
|
-
[2m Tests [22m [1m[
|
|
488
|
-
[2m Start at [22m
|
|
489
|
-
[2m Duration [22m
|
|
483
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/account/OrganizationDrawerBody.test.tsx [2m([22m[2m3 tests[22m[2m)[22m[32m 40[2mms[22m[39m
|
|
484
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/analytics/utils.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m 3[2mms[22m[39m
|
|
485
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/auth/ProfileChallenge.test.tsx [2m([22m[2m3 tests[22m[2m)[22m[32m 26[2mms[22m[39m
|
|
486
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/account/clearPersistedSessionState.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m 13[2mms[22m[39m
|
|
487
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/quotes/MyAccountQuoteStatusBadge.test.tsx [2m([22m[2m2 tests[22m[2m)[22m[32m 23[2mms[22m[39m
|
|
488
|
+
|
|
489
|
+
[2m Test Files [22m [1m[32m73 passed[39m[22m[90m (73)[39m
|
|
490
|
+
[2m Tests [22m [1m[32m570 passed[39m[22m[90m (570)[39m
|
|
491
|
+
[2m Start at [22m 21:42:33
|
|
492
|
+
[2m Duration [22m 33.01s[2m (transform 7.79s, setup 0ms, collect 26.92s, tests 10.65s, environment 41.89s, prepare 1.09s)[22m
|
|
490
493
|
|
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,18 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [4.5.0-dev.7](https://github.com/vtex/faststore/compare/v4.5.0-dev.6...v4.5.0-dev.7) (2026-08-03)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
- CMS-driven custom My Account pages ([#3411](https://github.com/vtex/faststore/issues/3411)) ([e38b278](https://github.com/vtex/faststore/commit/e38b2780bb7386a8ef7b83a9e3de5956bf6de50e))
|
|
11
|
+
|
|
12
|
+
# [4.5.0-dev.6](https://github.com/vtex/faststore/compare/v4.5.0-dev.5...v4.5.0-dev.6) (2026-07-28)
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
- prevent My Account 500s from empty session profile and missing CMS content ([#3417](https://github.com/vtex/faststore/issues/3417)) ([2c2a18f](https://github.com/vtex/faststore/commit/2c2a18fe8f4127404d6634933cce778a64169c08))
|
|
17
|
+
|
|
6
18
|
# [4.5.0-dev.5](https://github.com/vtex/faststore/compare/v4.5.0-dev.4...v4.5.0-dev.5) (2026-07-27)
|
|
7
19
|
|
|
8
20
|
### Features
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@faststore/core",
|
|
3
|
-
"version": "4.5.0-dev.
|
|
3
|
+
"version": "4.5.0-dev.7",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -71,11 +71,11 @@
|
|
|
71
71
|
"style-loader": "^3.3.1",
|
|
72
72
|
"swr": "^2.2.5",
|
|
73
73
|
"use-sync-external-store": "^1.6.0",
|
|
74
|
-
"@faststore/
|
|
75
|
-
"@faststore/diagnostics": "4.5.0-dev.
|
|
76
|
-
"@faststore/
|
|
77
|
-
"@faststore/
|
|
78
|
-
"@faststore/
|
|
74
|
+
"@faststore/lighthouse": "4.5.0-dev.7",
|
|
75
|
+
"@faststore/diagnostics": "4.5.0-dev.7",
|
|
76
|
+
"@faststore/api": "4.5.0-dev.7",
|
|
77
|
+
"@faststore/ui": "4.5.0-dev.7",
|
|
78
|
+
"@faststore/sdk": "4.5.0-dev.7"
|
|
79
79
|
},
|
|
80
80
|
"devDependencies": {
|
|
81
81
|
"@cypress/code-coverage": "^3.12.1",
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { gql } from '@generated/gql'
|
|
2
|
+
import type {
|
|
3
|
+
ServerAccountPageQueryQuery,
|
|
4
|
+
ServerAccountPageQueryQueryVariables,
|
|
5
|
+
} from '@generated/graphql'
|
|
6
|
+
import type { Locator } from '@vtex/client-cms'
|
|
7
|
+
import type { GetServerSideProps } from 'next'
|
|
8
|
+
|
|
9
|
+
import { getGlobalSectionsData } from 'src/components/cms/GlobalSections'
|
|
10
|
+
import { getIsRepresentative } from 'src/sdk/account/getIsRepresentative'
|
|
11
|
+
import type { AccountNavigationLabels } from 'src/sdk/account/getMyAccountRoutes'
|
|
12
|
+
import { validateUser } from 'src/sdk/account/validateUser'
|
|
13
|
+
import { execute } from 'src/server'
|
|
14
|
+
import { fetchMyAccountPageContent } from 'src/server/cms/fetchMyAccountPageContent'
|
|
15
|
+
import { injectGlobalSections } from 'src/server/cms/global'
|
|
16
|
+
import { extractAccountNavigationData } from 'src/server/cms/myAccountDefaultSections'
|
|
17
|
+
import { localizeRedirectDestination } from 'src/utils/localization/localizeRedirectDestination'
|
|
18
|
+
import { withLocaleValidationSSR } from 'src/utils/localization/withLocaleValidation'
|
|
19
|
+
import { getMyAccountRedirect } from 'src/utils/myAccountRedirect'
|
|
20
|
+
import storeConfig from '../../discovery.config'
|
|
21
|
+
import type { MyAccountProps } from './myAccountServerSideProps'
|
|
22
|
+
|
|
23
|
+
export type MyAccountCmsPageProps = MyAccountProps & {
|
|
24
|
+
pageSections: Array<{
|
|
25
|
+
name: string
|
|
26
|
+
data: Record<string, unknown>
|
|
27
|
+
$componentKey?: string
|
|
28
|
+
}>
|
|
29
|
+
navigationLabels: AccountNavigationLabels
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const query = gql(`
|
|
33
|
+
query ServerAccountPageQuery {
|
|
34
|
+
accountProfile {
|
|
35
|
+
name
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
`)
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Reusable SSR factory for CMS-driven custom My Account pages.
|
|
42
|
+
* Keeps the same auth/redirect guards as `myAccountServerSideProps`.
|
|
43
|
+
*/
|
|
44
|
+
export function myAccountCmsServerSideProps(
|
|
45
|
+
contentType: string,
|
|
46
|
+
pagePath: string
|
|
47
|
+
): GetServerSideProps<MyAccountCmsPageProps, Record<string, string>, Locator> {
|
|
48
|
+
const handler: GetServerSideProps<
|
|
49
|
+
MyAccountCmsPageProps,
|
|
50
|
+
Record<string, string>,
|
|
51
|
+
Locator
|
|
52
|
+
> = async (context) => {
|
|
53
|
+
const contentContext = {
|
|
54
|
+
previewData: context.previewData,
|
|
55
|
+
locale: context.locale,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const validationResult = await validateUser(context)
|
|
59
|
+
|
|
60
|
+
if (!validationResult.isValid && !validationResult.needsRefresh) {
|
|
61
|
+
return {
|
|
62
|
+
redirect: {
|
|
63
|
+
destination: localizeRedirectDestination('/login', context),
|
|
64
|
+
permanent: false,
|
|
65
|
+
},
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!validationResult.isValid && validationResult.needsRefresh) {
|
|
70
|
+
const currentPath = context.req.url || '/pvt/account'
|
|
71
|
+
return {
|
|
72
|
+
redirect: {
|
|
73
|
+
destination: localizeRedirectDestination(
|
|
74
|
+
`/pvt/account/403?from=${encodeURIComponent(currentPath)}`,
|
|
75
|
+
context
|
|
76
|
+
),
|
|
77
|
+
permanent: false,
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const { isFaststoreMyAccountEnabled, redirect } = getMyAccountRedirect({
|
|
83
|
+
query: context.query,
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
if (!isFaststoreMyAccountEnabled) {
|
|
87
|
+
return { redirect }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const isRepresentative = getIsRepresentative({
|
|
91
|
+
headers: context.req.headers as Record<string, string>,
|
|
92
|
+
account: storeConfig.api.storeId,
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
const [
|
|
96
|
+
globalSectionsPromise,
|
|
97
|
+
globalSectionsHeaderPromise,
|
|
98
|
+
globalSectionsFooterPromise,
|
|
99
|
+
] = getGlobalSectionsData(contentContext)
|
|
100
|
+
|
|
101
|
+
const [
|
|
102
|
+
pageContent,
|
|
103
|
+
account,
|
|
104
|
+
globalSections,
|
|
105
|
+
globalSectionsHeader,
|
|
106
|
+
globalSectionsFooter,
|
|
107
|
+
] = await Promise.all([
|
|
108
|
+
fetchMyAccountPageContent(contentType, contentContext, pagePath),
|
|
109
|
+
execute<
|
|
110
|
+
ServerAccountPageQueryQueryVariables,
|
|
111
|
+
ServerAccountPageQueryQuery
|
|
112
|
+
>(
|
|
113
|
+
{
|
|
114
|
+
variables: {},
|
|
115
|
+
operation: query,
|
|
116
|
+
},
|
|
117
|
+
{ headers: { ...context.req.headers } }
|
|
118
|
+
),
|
|
119
|
+
globalSectionsPromise,
|
|
120
|
+
globalSectionsHeaderPromise,
|
|
121
|
+
globalSectionsFooterPromise,
|
|
122
|
+
])
|
|
123
|
+
|
|
124
|
+
const { pageSections, navigationData } = extractAccountNavigationData(
|
|
125
|
+
pageContent.sections
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
const globalSectionsResult = injectGlobalSections({
|
|
129
|
+
globalSections,
|
|
130
|
+
globalSectionsHeader,
|
|
131
|
+
globalSectionsFooter,
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
props: {
|
|
136
|
+
globalSections: globalSectionsResult,
|
|
137
|
+
accountName: account.data?.accountProfile?.name ?? '',
|
|
138
|
+
isRepresentative,
|
|
139
|
+
pageSections,
|
|
140
|
+
navigationLabels: navigationData as AccountNavigationLabels,
|
|
141
|
+
},
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return withLocaleValidationSSR(handler)
|
|
146
|
+
}
|
|
@@ -114,7 +114,7 @@ const getServerSidePropsBase: GetServerSideProps<
|
|
|
114
114
|
return {
|
|
115
115
|
props: {
|
|
116
116
|
globalSections: globalSectionsResult,
|
|
117
|
-
accountName: account.data
|
|
117
|
+
accountName: account.data?.accountProfile?.name ?? '',
|
|
118
118
|
isRepresentative,
|
|
119
119
|
},
|
|
120
120
|
}
|
|
@@ -147,9 +147,9 @@ const getServerSidePropsBase: GetServerSideProps<
|
|
|
147
147
|
|
|
148
148
|
return {
|
|
149
149
|
props: {
|
|
150
|
-
page,
|
|
150
|
+
page: page ?? ({ sections: [], settings: {} } as PageContentType),
|
|
151
151
|
globalSections: globalSectionsResult,
|
|
152
|
-
accountName: account.data
|
|
152
|
+
accountName: account.data?.accountProfile?.name ?? '',
|
|
153
153
|
},
|
|
154
154
|
}
|
|
155
155
|
}
|
|
@@ -3,6 +3,12 @@ export interface Route {
|
|
|
3
3
|
title: string
|
|
4
4
|
/* Accessible path within My Account */
|
|
5
5
|
route: string
|
|
6
|
+
/**
|
|
7
|
+
* When set, the page body renders from this CMS content-type.
|
|
8
|
+
* Presence of this field is the single opt-in for a CMS-driven page.
|
|
9
|
+
* Pure CMS vs hybrid is derived by the CLI from whether a code body exists.
|
|
10
|
+
*/
|
|
11
|
+
contentType?: string
|
|
6
12
|
}
|
|
7
13
|
|
|
8
14
|
export type AccountNavigationLabels = Partial<{
|
|
@@ -16,22 +16,23 @@ export async function fetchMyAccountPageContent(
|
|
|
16
16
|
context: FetchMyAccountPageContentContext,
|
|
17
17
|
pagePath: string
|
|
18
18
|
) {
|
|
19
|
-
const pageContent =
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
previewData: context.previewData,
|
|
23
|
-
locale: context.locale,
|
|
24
|
-
})
|
|
25
|
-
.catch((err) => {
|
|
26
|
-
console.warn('[MyAccount CMS fallback]', {
|
|
19
|
+
const pageContent =
|
|
20
|
+
(await contentService
|
|
21
|
+
.getSingleContent<PageContentType>({
|
|
27
22
|
contentType,
|
|
23
|
+
previewData: context.previewData,
|
|
28
24
|
locale: context.locale,
|
|
29
|
-
fallbackReason: err instanceof Error ? err.message : String(err),
|
|
30
|
-
page: pagePath,
|
|
31
25
|
})
|
|
26
|
+
.catch((err) => {
|
|
27
|
+
console.warn('[MyAccount CMS fallback]', {
|
|
28
|
+
contentType,
|
|
29
|
+
locale: context.locale,
|
|
30
|
+
fallbackReason: err instanceof Error ? err.message : String(err),
|
|
31
|
+
page: pagePath,
|
|
32
|
+
})
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
return { sections: [], settings: {} } as PageContentType
|
|
35
|
+
})) ?? ({ sections: [], settings: {} } as PageContentType)
|
|
35
36
|
|
|
36
37
|
const sections = withDefaultMyAccountSections(
|
|
37
38
|
contentType,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type
|
|
1
|
+
export type NativeMyAccountContentType =
|
|
2
2
|
| 'myAccountProfile'
|
|
3
3
|
| 'myAccountOrders'
|
|
4
4
|
| 'myAccountOrderDetails'
|
|
@@ -6,13 +6,16 @@ export type MyAccountContentType =
|
|
|
6
6
|
| 'myAccountSecurity'
|
|
7
7
|
| 'myAccountUnauthorized'
|
|
8
8
|
|
|
9
|
+
/** Store content-types are opaque strings; native literals keep autocompletion. */
|
|
10
|
+
export type MyAccountContentType = NativeMyAccountContentType | (string & {})
|
|
11
|
+
|
|
9
12
|
export type DefaultMyAccountSection = {
|
|
10
13
|
name: string
|
|
11
14
|
$componentKey: string
|
|
12
15
|
data: Record<string, unknown>
|
|
13
16
|
}
|
|
14
17
|
|
|
15
|
-
const DEFAULT_SECTION_KEYS: Record<
|
|
18
|
+
const DEFAULT_SECTION_KEYS: Record<NativeMyAccountContentType, string[]> = {
|
|
16
19
|
myAccountProfile: ['AccountNavigation', 'AccountProfile'],
|
|
17
20
|
myAccountOrders: ['AccountNavigation', 'AccountOrdersList'],
|
|
18
21
|
myAccountOrderDetails: [
|
|
@@ -34,7 +37,9 @@ const DEFAULT_SECTION_KEYS: Record<MyAccountContentType, string[]> = {
|
|
|
34
37
|
export function getDefaultMyAccountSections(
|
|
35
38
|
contentType: MyAccountContentType
|
|
36
39
|
): DefaultMyAccountSection[] {
|
|
37
|
-
return (
|
|
40
|
+
return (
|
|
41
|
+
DEFAULT_SECTION_KEYS[contentType as NativeMyAccountContentType] ?? []
|
|
42
|
+
).map((key) => ({
|
|
38
43
|
name: key,
|
|
39
44
|
$componentKey: key,
|
|
40
45
|
data: {},
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import storeConfig from 'discovery.config'
|
|
2
2
|
import type { ParsedUrlQuery } from 'node:querystring'
|
|
3
3
|
|
|
4
|
+
type MyAccountRedirect = { destination: string; permanent: boolean }
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Discriminated union so callers that guard on `isFaststoreMyAccountEnabled`
|
|
8
|
+
* get a non-nullable `redirect`.
|
|
9
|
+
*/
|
|
10
|
+
export type MyAccountRedirectResult =
|
|
11
|
+
| { isFaststoreMyAccountEnabled: false; redirect: MyAccountRedirect }
|
|
12
|
+
| { isFaststoreMyAccountEnabled: true; redirect: null }
|
|
13
|
+
|
|
4
14
|
/**
|
|
5
15
|
* Check if the Faststore My Account feature flag is enabled.
|
|
6
16
|
* If not, redirect to the legacy My Account URL with the query parameters.
|
|
@@ -8,10 +18,9 @@ import type { ParsedUrlQuery } from 'node:querystring'
|
|
|
8
18
|
* @param {ParsedUrlQuery} query - The query parameters from the request.
|
|
9
19
|
* @returns {Object} - An object containing the feature flag status and redirect information.
|
|
10
20
|
*/
|
|
11
|
-
export function getMyAccountRedirect({
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
} {
|
|
21
|
+
export function getMyAccountRedirect({
|
|
22
|
+
query,
|
|
23
|
+
}: { query: ParsedUrlQuery }): MyAccountRedirectResult {
|
|
15
24
|
const isFaststoreMyAccountEnabled =
|
|
16
25
|
storeConfig.experimental?.enableFaststoreMyAccount
|
|
17
26
|
|
|
@@ -29,8 +38,8 @@ export function getMyAccountRedirect({ query }: { query: ParsedUrlQuery }): {
|
|
|
29
38
|
permanent: false,
|
|
30
39
|
}
|
|
31
40
|
|
|
32
|
-
return { isFaststoreMyAccountEnabled, redirect }
|
|
41
|
+
return { isFaststoreMyAccountEnabled: false, redirect }
|
|
33
42
|
}
|
|
34
43
|
|
|
35
|
-
return { isFaststoreMyAccountEnabled, redirect: null }
|
|
44
|
+
return { isFaststoreMyAccountEnabled: true, redirect: null }
|
|
36
45
|
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
const mockValidateUser = vi.fn()
|
|
4
|
+
const mockGetMyAccountRedirect = vi.fn()
|
|
5
|
+
const mockGetIsRepresentative = vi.fn()
|
|
6
|
+
const mockFetchMyAccountPageContent = vi.fn()
|
|
7
|
+
const mockGetGlobalSectionsData = vi.fn()
|
|
8
|
+
const mockExecute = vi.fn()
|
|
9
|
+
const mockInjectGlobalSections = vi.fn()
|
|
10
|
+
const mockLocalizeRedirectDestination = vi.fn(
|
|
11
|
+
(destination: string) => destination
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
vi.mock('src/sdk/account/validateUser', () => ({
|
|
15
|
+
validateUser: (...args: unknown[]) => mockValidateUser(...args),
|
|
16
|
+
}))
|
|
17
|
+
|
|
18
|
+
vi.mock('src/utils/myAccountRedirect', () => ({
|
|
19
|
+
getMyAccountRedirect: (...args: unknown[]) =>
|
|
20
|
+
mockGetMyAccountRedirect(...args),
|
|
21
|
+
}))
|
|
22
|
+
|
|
23
|
+
vi.mock('src/sdk/account/getIsRepresentative', () => ({
|
|
24
|
+
getIsRepresentative: (...args: unknown[]) => mockGetIsRepresentative(...args),
|
|
25
|
+
}))
|
|
26
|
+
|
|
27
|
+
vi.mock('src/server/cms/fetchMyAccountPageContent', () => ({
|
|
28
|
+
fetchMyAccountPageContent: (...args: unknown[]) =>
|
|
29
|
+
mockFetchMyAccountPageContent(...args),
|
|
30
|
+
}))
|
|
31
|
+
|
|
32
|
+
vi.mock('src/components/cms/GlobalSections', () => ({
|
|
33
|
+
getGlobalSectionsData: (...args: unknown[]) =>
|
|
34
|
+
mockGetGlobalSectionsData(...args),
|
|
35
|
+
}))
|
|
36
|
+
|
|
37
|
+
vi.mock('src/server', () => ({
|
|
38
|
+
execute: (...args: unknown[]) => mockExecute(...args),
|
|
39
|
+
}))
|
|
40
|
+
|
|
41
|
+
vi.mock('src/server/cms/global', () => ({
|
|
42
|
+
injectGlobalSections: (...args: unknown[]) =>
|
|
43
|
+
mockInjectGlobalSections(...args),
|
|
44
|
+
}))
|
|
45
|
+
|
|
46
|
+
vi.mock('src/utils/localization/localizeRedirectDestination', () => ({
|
|
47
|
+
localizeRedirectDestination: (...args: unknown[]) =>
|
|
48
|
+
mockLocalizeRedirectDestination(...(args as [string])),
|
|
49
|
+
}))
|
|
50
|
+
|
|
51
|
+
vi.mock('src/utils/localization/withLocaleValidation', () => ({
|
|
52
|
+
withLocaleValidationSSR: <T>(fn: T) => fn,
|
|
53
|
+
}))
|
|
54
|
+
|
|
55
|
+
vi.mock('../../../discovery.config', () => ({
|
|
56
|
+
default: { api: { storeId: 'teststore' } },
|
|
57
|
+
}))
|
|
58
|
+
|
|
59
|
+
vi.mock('@generated/gql', () => ({
|
|
60
|
+
gql: (source: string) => source,
|
|
61
|
+
}))
|
|
62
|
+
|
|
63
|
+
import { myAccountCmsServerSideProps } from '../../../src/experimental/myAccountCmsServerSideProps'
|
|
64
|
+
|
|
65
|
+
function buildContext(overrides: Record<string, unknown> = {}) {
|
|
66
|
+
return {
|
|
67
|
+
previewData: undefined,
|
|
68
|
+
locale: 'en-US',
|
|
69
|
+
query: {},
|
|
70
|
+
req: {
|
|
71
|
+
url: '/pvt/account/wishlist',
|
|
72
|
+
headers: {},
|
|
73
|
+
},
|
|
74
|
+
...overrides,
|
|
75
|
+
} as never
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
describe('myAccountCmsServerSideProps', () => {
|
|
79
|
+
beforeEach(() => {
|
|
80
|
+
vi.clearAllMocks()
|
|
81
|
+
mockGetIsRepresentative.mockReturnValue(false)
|
|
82
|
+
mockGetGlobalSectionsData.mockReturnValue([
|
|
83
|
+
Promise.resolve({ sections: [], settings: {} }),
|
|
84
|
+
Promise.resolve({ sections: [] }),
|
|
85
|
+
Promise.resolve({ sections: [] }),
|
|
86
|
+
])
|
|
87
|
+
mockInjectGlobalSections.mockReturnValue({
|
|
88
|
+
sections: [{ name: 'Children', data: {} }],
|
|
89
|
+
settings: {},
|
|
90
|
+
})
|
|
91
|
+
mockExecute.mockResolvedValue({
|
|
92
|
+
data: { accountProfile: { name: 'Ada' } },
|
|
93
|
+
errors: undefined,
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('redirects to login when the user is invalid and does not need refresh', async () => {
|
|
98
|
+
mockValidateUser.mockResolvedValue({
|
|
99
|
+
isValid: false,
|
|
100
|
+
needsRefresh: false,
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
const result = await myAccountCmsServerSideProps(
|
|
104
|
+
'myAccountWishlist',
|
|
105
|
+
'/pvt/account/wishlist'
|
|
106
|
+
)(buildContext())
|
|
107
|
+
|
|
108
|
+
expect(result).toEqual({
|
|
109
|
+
redirect: { destination: '/login', permanent: false },
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('redirects to 403 when the user is invalid and needs refresh', async () => {
|
|
114
|
+
mockValidateUser.mockResolvedValue({
|
|
115
|
+
isValid: false,
|
|
116
|
+
needsRefresh: true,
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
const result = await myAccountCmsServerSideProps(
|
|
120
|
+
'myAccountWishlist',
|
|
121
|
+
'/pvt/account/wishlist'
|
|
122
|
+
)(buildContext())
|
|
123
|
+
|
|
124
|
+
expect(result).toEqual({
|
|
125
|
+
redirect: {
|
|
126
|
+
destination: `/pvt/account/403?from=${encodeURIComponent('/pvt/account/wishlist')}`,
|
|
127
|
+
permanent: false,
|
|
128
|
+
},
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it('returns the My Account disabled redirect', async () => {
|
|
133
|
+
mockValidateUser.mockResolvedValue({ isValid: true })
|
|
134
|
+
mockGetMyAccountRedirect.mockReturnValue({
|
|
135
|
+
isFaststoreMyAccountEnabled: false,
|
|
136
|
+
redirect: { destination: '/login', permanent: false },
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
const result = await myAccountCmsServerSideProps(
|
|
140
|
+
'myAccountWishlist',
|
|
141
|
+
'/pvt/account/wishlist'
|
|
142
|
+
)(buildContext())
|
|
143
|
+
|
|
144
|
+
expect(result).toEqual({
|
|
145
|
+
redirect: { destination: '/login', permanent: false },
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('returns pageSections, navigationLabels and accountName on success', async () => {
|
|
150
|
+
mockValidateUser.mockResolvedValue({ isValid: true })
|
|
151
|
+
mockGetMyAccountRedirect.mockReturnValue({
|
|
152
|
+
isFaststoreMyAccountEnabled: true,
|
|
153
|
+
redirect: { destination: '/login', permanent: false },
|
|
154
|
+
})
|
|
155
|
+
mockFetchMyAccountPageContent.mockResolvedValue({
|
|
156
|
+
sections: [
|
|
157
|
+
{
|
|
158
|
+
name: 'AccountNavigation',
|
|
159
|
+
$componentKey: 'AccountNavigation',
|
|
160
|
+
data: { profileLabel: 'Profile' },
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: 'HelloAccount',
|
|
164
|
+
$componentKey: 'HelloAccount',
|
|
165
|
+
data: { label: 'Hi' },
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
settings: {},
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
const result = await myAccountCmsServerSideProps(
|
|
172
|
+
'myAccountWishlist',
|
|
173
|
+
'/pvt/account/wishlist'
|
|
174
|
+
)(buildContext())
|
|
175
|
+
|
|
176
|
+
expect(result).toMatchObject({
|
|
177
|
+
props: {
|
|
178
|
+
accountName: 'Ada',
|
|
179
|
+
isRepresentative: false,
|
|
180
|
+
navigationLabels: { profileLabel: 'Profile' },
|
|
181
|
+
pageSections: [
|
|
182
|
+
{
|
|
183
|
+
name: 'HelloAccount',
|
|
184
|
+
$componentKey: 'HelloAccount',
|
|
185
|
+
data: { label: 'Hi' },
|
|
186
|
+
},
|
|
187
|
+
],
|
|
188
|
+
},
|
|
189
|
+
})
|
|
190
|
+
expect(mockFetchMyAccountPageContent).toHaveBeenCalledWith(
|
|
191
|
+
'myAccountWishlist',
|
|
192
|
+
{ previewData: undefined, locale: 'en-US' },
|
|
193
|
+
'/pvt/account/wishlist'
|
|
194
|
+
)
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
it('returns empty pageSections when the content-type is unpublished', async () => {
|
|
198
|
+
mockValidateUser.mockResolvedValue({ isValid: true })
|
|
199
|
+
mockGetMyAccountRedirect.mockReturnValue({
|
|
200
|
+
isFaststoreMyAccountEnabled: true,
|
|
201
|
+
redirect: { destination: '/login', permanent: false },
|
|
202
|
+
})
|
|
203
|
+
mockFetchMyAccountPageContent.mockResolvedValue({
|
|
204
|
+
sections: [],
|
|
205
|
+
settings: {},
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
const result = await myAccountCmsServerSideProps(
|
|
209
|
+
'myAccountWishlist',
|
|
210
|
+
'/pvt/account/wishlist'
|
|
211
|
+
)(buildContext())
|
|
212
|
+
|
|
213
|
+
expect(result).toMatchObject({
|
|
214
|
+
props: {
|
|
215
|
+
pageSections: [],
|
|
216
|
+
navigationLabels: {},
|
|
217
|
+
accountName: 'Ada',
|
|
218
|
+
},
|
|
219
|
+
})
|
|
220
|
+
})
|
|
221
|
+
})
|
|
@@ -73,6 +73,45 @@ describe('getMyAccountRoutes', () => {
|
|
|
73
73
|
expect(result.find((r) => r.route === QUOTES_ROUTE)?.title).toBe('Quotes')
|
|
74
74
|
})
|
|
75
75
|
|
|
76
|
+
it('preserves contentType through getMyAccountRoutes', () => {
|
|
77
|
+
const result = getMyAccountRoutes({
|
|
78
|
+
routes: [
|
|
79
|
+
{
|
|
80
|
+
title: 'Wishlist',
|
|
81
|
+
route: '/pvt/account/wishlist',
|
|
82
|
+
contentType: 'myAccountWishlist',
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
const wishlist = result.find((r) => r.route === '/pvt/account/wishlist')
|
|
88
|
+
expect(wishlist?.contentType).toBe('myAccountWishlist')
|
|
89
|
+
expect(wishlist?.title).toBe('Wishlist')
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('keeps custom-route titles when CMS labels override native routes (FR-011)', () => {
|
|
93
|
+
const result = getMyAccountRoutes({
|
|
94
|
+
routes: [
|
|
95
|
+
{
|
|
96
|
+
title: 'Wishlist',
|
|
97
|
+
route: '/pvt/account/wishlist',
|
|
98
|
+
contentType: 'myAccountWishlist',
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
labels: { profileLabel: 'Meu Perfil', ordersLabel: 'Meus Pedidos' },
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
expect(result.find((r) => r.route === PROFILE_ROUTE)?.title).toBe(
|
|
105
|
+
'Meu Perfil'
|
|
106
|
+
)
|
|
107
|
+
expect(result.find((r) => r.route === ORDERS_ROUTE)?.title).toBe(
|
|
108
|
+
'Meus Pedidos'
|
|
109
|
+
)
|
|
110
|
+
expect(result.find((r) => r.route === '/pvt/account/wishlist')?.title).toBe(
|
|
111
|
+
'Wishlist'
|
|
112
|
+
)
|
|
113
|
+
})
|
|
114
|
+
|
|
76
115
|
it('marks Quotes as a B2B-only route', () => {
|
|
77
116
|
expect(ROUTES_ONLY_FOR_B2B_MEMBERS).toContain(QUOTES_ROUTE)
|
|
78
117
|
})
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
getDefaultMyAccountSections,
|
|
5
|
+
withDefaultMyAccountSections,
|
|
6
|
+
} from '../../../src/server/cms/myAccountDefaultSections'
|
|
7
|
+
|
|
8
|
+
describe('getDefaultMyAccountSections', () => {
|
|
9
|
+
it('returns the native section list for myAccountProfile', () => {
|
|
10
|
+
expect(getDefaultMyAccountSections('myAccountProfile')).toEqual([
|
|
11
|
+
{
|
|
12
|
+
name: 'AccountNavigation',
|
|
13
|
+
$componentKey: 'AccountNavigation',
|
|
14
|
+
data: {},
|
|
15
|
+
},
|
|
16
|
+
{ name: 'AccountProfile', $componentKey: 'AccountProfile', data: {} },
|
|
17
|
+
])
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('returns an empty list for store (unknown) content-types', () => {
|
|
21
|
+
expect(getDefaultMyAccountSections('myAccountWishlist')).toEqual([])
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
describe('withDefaultMyAccountSections', () => {
|
|
26
|
+
it('returns published sections when present', () => {
|
|
27
|
+
const sections = [
|
|
28
|
+
{
|
|
29
|
+
name: 'HelloAccount',
|
|
30
|
+
$componentKey: 'HelloAccount',
|
|
31
|
+
data: { label: 'Hi' },
|
|
32
|
+
},
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
expect(withDefaultMyAccountSections('myAccountWishlist', sections)).toEqual(
|
|
36
|
+
sections
|
|
37
|
+
)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('falls back to defaults for native types when sections are empty', () => {
|
|
41
|
+
expect(withDefaultMyAccountSections('myAccountSecurity', [])).toEqual(
|
|
42
|
+
getDefaultMyAccountSections('myAccountSecurity')
|
|
43
|
+
)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('falls back to [] for store types when sections are empty', () => {
|
|
47
|
+
expect(withDefaultMyAccountSections('myAccountWishlist', null)).toEqual([])
|
|
48
|
+
})
|
|
49
|
+
})
|