@akinon/pz-theme 2.0.38-rc.0 → 2.0.38

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.
@@ -0,0 +1,159 @@
1
+ import { PLACEHOLDER_SLUGS } from './placeholder-slugs';
2
+
3
+ export type RouteSlugRule = [RegExp, string];
4
+
5
+ /**
6
+ * Maps a storefront path to the placeholder slug that composes its body.
7
+ *
8
+ * A brand mounts one `ChromeSlot` per *layout* rather than per page — the
9
+ * account layout alone covers sixteen screens — and a layout only knows the
10
+ * current path. Resolving here keeps that lookup out of brand code and keeps
11
+ * the editor's route map and the storefront agreeing on one table.
12
+ *
13
+ * Brands rename these paths freely (`/users/orders` on one, `/account/orders`
14
+ * on the next, `/baskets/basket` on a third), so the defaults accept the
15
+ * variants that actually ship, and anything else is registered with
16
+ * `registerRouteSlugRules` instead of forking this file.
17
+ *
18
+ * Order matters: the most specific pattern wins, so `/users/orders/123`
19
+ * resolves to the order detail body and not to the order list.
20
+ */
21
+ const DEFAULT_RULES: RouteSlugRule[] = [
22
+ // Account — deepest first
23
+ [
24
+ /^\/(account|users)\/orders\/[^/]+/,
25
+ PLACEHOLDER_SLUGS.accountOrderDetail
26
+ ],
27
+ [/^\/(account|users)\/orders\/?$/, PLACEHOLDER_SLUGS.accountOrders],
28
+ [/^\/account\/address/, PLACEHOLDER_SLUGS.accountAddresses],
29
+ [/^\/account\/profile/, PLACEHOLDER_SLUGS.accountProfile],
30
+ [/^\/account\/change-password/, PLACEHOLDER_SLUGS.accountChangePassword],
31
+ [/^\/account\/change-email/, PLACEHOLDER_SLUGS.accountChangeEmail],
32
+ [/^\/account\/coupons/, PLACEHOLDER_SLUGS.accountCoupons],
33
+ [
34
+ /^\/account\/(favourite-products|favorites|wishlist)/,
35
+ PLACEHOLDER_SLUGS.accountFavourites
36
+ ],
37
+ [
38
+ /^\/account\/stock-(waiting-products|alerts)/,
39
+ PLACEHOLDER_SLUGS.accountStockAlerts
40
+ ],
41
+ [
42
+ /^\/account\/notification-settings/,
43
+ PLACEHOLDER_SLUGS.accountNotifications
44
+ ],
45
+ [/^\/account\/faq/, PLACEHOLDER_SLUGS.accountFaq],
46
+ // Any other account screen composes the shell body, which is what the
47
+ // account layout mounts for it.
48
+ [/^\/account(\/|$)/, PLACEHOLDER_SLUGS.accountShell],
49
+
50
+ // Auth
51
+ [/^\/(users\/)?auth\/register/, PLACEHOLDER_SLUGS.register],
52
+ [/^\/(users\/)?register/, PLACEHOLDER_SLUGS.register],
53
+ [/^\/(users\/)?auth\/forgot-password/, PLACEHOLDER_SLUGS.forgotPassword],
54
+ [/^\/users\/password\/reset/, PLACEHOLDER_SLUGS.forgotPassword],
55
+ [/^\/(users\/)?auth\/guest/, PLACEHOLDER_SLUGS.guestLogin],
56
+ [/^\/(users\/)?auth/, PLACEHOLDER_SLUGS.auth],
57
+
58
+ // Order flow
59
+ [/^\/orders\/completed/, PLACEHOLDER_SLUGS.orderSuccess],
60
+ // Checkout is code-owned; the designer composes the zone above the flow
61
+ // (the zone below shares the page and is added by the editor's scope).
62
+ [/^\/orders\/checkout/, PLACEHOLDER_SLUGS.checkoutTop],
63
+ [/^\/anonymous-(orders|tracking)/, PLACEHOLDER_SLUGS.orderTracking],
64
+
65
+ // Commerce
66
+ [/^\/(baskets\/basket|basket|sepet)/, PLACEHOLDER_SLUGS.basket],
67
+ [/^\/(group-)?product\//, PLACEHOLDER_SLUGS.productDetail],
68
+ [/^\/category\//, PLACEHOLDER_SLUGS.productListing],
69
+ [/^\/special-page\//, PLACEHOLDER_SLUGS.productListing],
70
+ [/^\/list\/?$/, PLACEHOLDER_SLUGS.productListing],
71
+
72
+ // Content
73
+ [/^\/(address\/)?stores/, PLACEHOLDER_SLUGS.stores],
74
+ [/^\/faq/, PLACEHOLDER_SLUGS.faq],
75
+ [/^\/sitemap/, PLACEHOLDER_SLUGS.sitemap],
76
+ [/^\/(markalar|brands)/, PLACEHOLDER_SLUGS.brands],
77
+ [/^\/flat-page\//, PLACEHOLDER_SLUGS.flatPage],
78
+ [/^\/?$/, PLACEHOLDER_SLUGS.homepage]
79
+ ];
80
+
81
+ /**
82
+ * Slugs whose *page* mounts the slot itself, because the blocks there need
83
+ * commerce data the page already fetched (product, listing, basket, account,
84
+ * order recap). Everything else can be mounted once in the root layout with
85
+ * `children` as the fallback — which is also the only way to compose a page
86
+ * that is a client component, since a slot is an async server component.
87
+ */
88
+ const PAGE_MOUNTED_SLUGS = new Set<string>([
89
+ PLACEHOLDER_SLUGS.productDetail,
90
+ PLACEHOLDER_SLUGS.productListing,
91
+ PLACEHOLDER_SLUGS.basket,
92
+ PLACEHOLDER_SLUGS.orderSuccess,
93
+ PLACEHOLDER_SLUGS.accountShell,
94
+ PLACEHOLDER_SLUGS.accountMenu,
95
+ PLACEHOLDER_SLUGS.accountOrders,
96
+ PLACEHOLDER_SLUGS.accountOrderDetail,
97
+ PLACEHOLDER_SLUGS.accountAddresses,
98
+ PLACEHOLDER_SLUGS.accountProfile,
99
+ PLACEHOLDER_SLUGS.accountChangePassword,
100
+ PLACEHOLDER_SLUGS.accountChangeEmail,
101
+ PLACEHOLDER_SLUGS.accountCoupons,
102
+ PLACEHOLDER_SLUGS.accountFavourites,
103
+ PLACEHOLDER_SLUGS.accountStockAlerts,
104
+ PLACEHOLDER_SLUGS.accountNotifications,
105
+ PLACEHOLDER_SLUGS.accountFaq,
106
+ PLACEHOLDER_SLUGS.flatPage
107
+ ]);
108
+
109
+ /** True when a deeper page/layout already mounts this slug's slot. */
110
+ export const isPageMountedSlug = (slug: string | null): boolean =>
111
+ slug !== null && PAGE_MOUNTED_SLUGS.has(slug);
112
+
113
+ let extraRules: RouteSlugRule[] = [];
114
+
115
+ /**
116
+ * Adds brand-specific paths. Registered rules are matched before the
117
+ * defaults, so a brand can redirect any path to any placeholder.
118
+ */
119
+ export const registerRouteSlugRules = (rules: RouteSlugRule[]) => {
120
+ extraRules = [...rules, ...extraRules];
121
+ };
122
+
123
+ /** Test helper: drops everything `registerRouteSlugRules` added. */
124
+ export const resetRouteSlugRules = () => {
125
+ extraRules = [];
126
+ };
127
+
128
+ /** Strips query/hash and any trailing slash, keeping the leading one. */
129
+ export const normalizePath = (pathname: string): string => {
130
+ const withoutQuery = (pathname || '/').split('?')[0].split('#')[0];
131
+ const prefixed = withoutQuery.startsWith('/')
132
+ ? withoutQuery
133
+ : `/${withoutQuery}`;
134
+ return prefixed.length > 1 ? prefixed.replace(/\/+$/, '') : '/';
135
+ };
136
+
137
+ /**
138
+ * Returns the body placeholder slug for a path, or `null` when the path has
139
+ * no dedicated placeholder (the brand's own page then renders untouched).
140
+ */
141
+ export const resolveBodySlug = (pathname: string): string | null => {
142
+ const path = normalizePath(pathname);
143
+ const match = [...extraRules, ...DEFAULT_RULES].find(([pattern]) =>
144
+ pattern.test(path)
145
+ );
146
+ return match ? match[1] : null;
147
+ };
148
+
149
+ /**
150
+ * Same as `resolveBodySlug` but accepts the full URL a route segment carries
151
+ * (`params.url` on brands built with commerce/locale/currency segments).
152
+ */
153
+ export const resolveBodySlugFromUrl = (url: string): string | null => {
154
+ try {
155
+ return resolveBodySlug(new URL(decodeURIComponent(url)).pathname);
156
+ } catch {
157
+ return resolveBodySlug(url);
158
+ }
159
+ };
package/src/index.ts CHANGED
@@ -19,3 +19,23 @@ export { WithDesignerFeatures } from './components/with-designer-features';
19
19
 
20
20
  export type { Section } from './theme-section';
21
21
  export type { Block } from './theme-block';
22
+
23
+ export { default as ChromeSlot } from './chrome/chrome-slot';
24
+ export { ChromeFallback } from './chrome/chrome-fallback';
25
+
26
+ export {
27
+ PLACEHOLDER_SLUGS,
28
+ customPageSlug,
29
+ customPageSlugFromPath
30
+ } from './chrome/placeholder-slugs';
31
+ export type { PlaceholderSlug } from './chrome/placeholder-slugs';
32
+
33
+ export {
34
+ resolveBodySlug,
35
+ resolveBodySlugFromUrl,
36
+ registerRouteSlugRules,
37
+ resetRouteSlugRules,
38
+ isPageMountedSlug,
39
+ normalizePath
40
+ } from './chrome/route-slug';
41
+ export type { RouteSlugRule } from './chrome/route-slug';