@orderlyshop/web-components 0.1.0-build.7045

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 (71) hide show
  1. package/AGENTS.md +110 -0
  2. package/README.md +685 -0
  3. package/bin/orderly-build-category-pages.mjs +160 -0
  4. package/bin/orderly-generate-category-pages.mjs +308 -0
  5. package/bin/orderly-hydrate-static-pages.mjs +595 -0
  6. package/bin/orderly-init-navigation.mjs +327 -0
  7. package/bin/orderly-init-shop.mjs +876 -0
  8. package/bin/orderly-init-taxonomy.mjs +2 -0
  9. package/bin/orderly-publish-site.mjs +342 -0
  10. package/custom-elements.json +505 -0
  11. package/dist/browser/orderly-web-components.define.global.js +3551 -0
  12. package/dist/browser/orderly-web-components.define.global.js.map +1 -0
  13. package/dist/browser/orderly-web-components.global.js +3551 -0
  14. package/dist/browser/orderly-web-components.global.js.map +1 -0
  15. package/dist/default-shop-DgX6uy10.d.ts +221 -0
  16. package/dist/default-shop.d.ts +6 -0
  17. package/dist/default-shop.js +762 -0
  18. package/dist/default-shop.js.map +1 -0
  19. package/dist/define-BNMhl19n.d.ts +9 -0
  20. package/dist/define.d.ts +2 -0
  21. package/dist/define.js +11094 -0
  22. package/dist/define.js.map +1 -0
  23. package/dist/index.d.ts +683 -0
  24. package/dist/index.js +11417 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/navigation.d.ts +61 -0
  27. package/dist/navigation.js +1125 -0
  28. package/dist/navigation.js.map +1 -0
  29. package/dist/query.d.ts +31 -0
  30. package/dist/query.js +115 -0
  31. package/dist/query.js.map +1 -0
  32. package/dist/registry-CPDecU3g.d.ts +6 -0
  33. package/dist/shop-BgQhGRzS.d.ts +173 -0
  34. package/dist/shop-query.d.ts +8 -0
  35. package/dist/shop-query.js +100 -0
  36. package/dist/shop-query.js.map +1 -0
  37. package/dist/shop.d.ts +8 -0
  38. package/dist/shop.js +11187 -0
  39. package/dist/shop.js.map +1 -0
  40. package/dist/stores.d.ts +46 -0
  41. package/dist/stores.js +145 -0
  42. package/dist/stores.js.map +1 -0
  43. package/dist/taxonomy.d.ts +35 -0
  44. package/dist/taxonomy.js +247 -0
  45. package/dist/taxonomy.js.map +1 -0
  46. package/dist/types-Bjez59Hr.d.ts +96 -0
  47. package/docs/components/README.md +708 -0
  48. package/docs/components/product-grid.md +182 -0
  49. package/docs/components/product-rail.md +174 -0
  50. package/examples/shop/README.md +72 -0
  51. package/examples/shop/package.json +28 -0
  52. package/examples/shop/src/category.html +20 -0
  53. package/examples/shop/src/checkout.html +21 -0
  54. package/examples/shop/src/forretningsbetingelser.html +81 -0
  55. package/examples/shop/src/includes/body-end.html +1 -0
  56. package/examples/shop/src/includes/body-start.html +3 -0
  57. package/examples/shop/src/includes/head.html +32 -0
  58. package/examples/shop/src/index.html +25 -0
  59. package/examples/shop/src/navigation.ts +154 -0
  60. package/examples/shop/src/product.html +24 -0
  61. package/examples/shop/src/templates/page-layouts.html +162 -0
  62. package/examples/shop/src/templates/shop-footer.html +76 -0
  63. package/examples/shop/tsconfig.json +32 -0
  64. package/examples/shop/vite.config.mjs +190 -0
  65. package/html-custom-data.json +279 -0
  66. package/package.json +118 -0
  67. package/server/README.md +111 -0
  68. package/server/apache/.htaccess +18 -0
  69. package/server/nginx/orderly-products.conf +24 -0
  70. package/server/node/product-snapshot-server.mjs +133 -0
  71. package/server/php/orderly-product.php +204 -0
@@ -0,0 +1,279 @@
1
+ {
2
+ "version": 1.1,
3
+ "tags": [
4
+ {
5
+ "name": "orderly-page-layout",
6
+ "description": "Reusable storefront page structure with header, navigation, sidebars, content, footer, and overlay regions.",
7
+ "references": [{ "name": "Component reference", "url": "./docs/components/README.md#orderly-page-layout" }],
8
+ "attributes": [
9
+ { "name": "logo-src", "description": "Logo image URL." },
10
+ { "name": "logo-alt", "description": "Logo alt text and logo link accessible label." },
11
+ { "name": "logo-href", "description": "Logo link URL." }
12
+ ]
13
+ },
14
+ {
15
+ "name": "orderly-category-page",
16
+ "description": "Default navigation-driven shop category page.",
17
+ "references": [{ "name": "Component reference", "url": "./docs/components/README.md#orderly-category-page" }],
18
+ "attributes": [
19
+ { "name": "base-url", "description": "Backend URL used when no client property is assigned." },
20
+ { "name": "slug", "description": "Category slug from the configured navigation." },
21
+ { "name": "brand-label", "description": "Brand text." },
22
+ { "name": "product-href", "description": "Product detail page URL used by the popup expand link." },
23
+ { "name": "checkout-href", "description": "Checkout page URL used by the basket." },
24
+ { "name": "checkout-label", "description": "Checkout link label." },
25
+ { "name": "search-placeholder", "description": "Search box placeholder." },
26
+ { "name": "empty-label", "description": "Empty result text." }
27
+ ]
28
+ },
29
+ {
30
+ "name": "orderly-home-page",
31
+ "description": "Default storefront homepage that renders one product rail per configured top-level category.",
32
+ "references": [{ "name": "Component reference", "url": "./docs/components/README.md#orderly-home-page" }],
33
+ "attributes": [
34
+ { "name": "base-url", "description": "Backend URL used when no client property is assigned." },
35
+ { "name": "brand-label", "description": "Brand text." },
36
+ { "name": "eyebrow", "description": "Intro eyebrow text." },
37
+ { "name": "title", "description": "Homepage heading." },
38
+ { "name": "rail-cta-label", "description": "CTA label used by every category rail." },
39
+ { "name": "empty-label", "description": "Empty result text used by category rails." },
40
+ { "name": "product-href", "description": "Product detail page URL used by the popup expand link." },
41
+ { "name": "checkout-href", "description": "Checkout page URL used by the basket." },
42
+ { "name": "checkout-label", "description": "Checkout link label." }
43
+ ]
44
+ },
45
+ {
46
+ "name": "orderly-checkout-page",
47
+ "description": "Default checkout page composed from page layout, navigation, checkout form, basket summary, footer, and shared basket state.",
48
+ "references": [{ "name": "Component reference", "url": "./docs/components/README.md#orderly-checkout-page" }],
49
+ "attributes": [
50
+ { "name": "base-url", "description": "Backend URL used when no client property is assigned." },
51
+ { "name": "brand-label", "description": "Brand text." },
52
+ { "name": "title", "description": "Checkout page heading." },
53
+ { "name": "description", "description": "Checkout page intro text." },
54
+ { "name": "order-title", "description": "Order summary heading." },
55
+ { "name": "terms-href", "description": "Terms link URL passed to the checkout form." }
56
+ ]
57
+ },
58
+ {
59
+ "name": "orderly-product-grid",
60
+ "description": "Search-backed product grid with sorting and continuation-token paging.",
61
+ "references": [{ "name": "Product grid reference", "url": "./docs/components/product-grid.md" }],
62
+ "attributes": [
63
+ { "name": "base-url", "description": "Backend URL used when no client property is assigned." },
64
+ { "name": "paging", "description": "Paging mode: button, manual, dynamic, or infinite.", "values": [{ "name": "button" }, { "name": "manual" }, { "name": "dynamic" }, { "name": "infinite" }] },
65
+ { "name": "sort-label", "description": "Sort control label." },
66
+ { "name": "clear-search-label", "description": "Clear search button label for custom context templates." },
67
+ { "name": "error-label", "description": "Error text." },
68
+ { "name": "loading-label", "description": "Loading text." },
69
+ { "name": "results-label", "description": "Result count suffix for custom context templates." },
70
+ { "name": "searching-label", "description": "Active query prefix for custom context templates." },
71
+ { "name": "keywords", "description": "Declarative query text mapped to SearchQuery.Query." },
72
+ { "name": "tags", "description": "Comma-separated values mapped to SearchQuery.Tags." },
73
+ { "name": "hidden-query", "description": "Declarative query text mapped to SearchQuery.HiddenQuery." },
74
+ { "name": "order-by", "description": "Comma-separated values mapped to SearchQuery.OrderBy." },
75
+ { "name": "store-id", "description": "Value mapped to SearchQuery.StoreId." },
76
+ { "name": "featured", "description": "Boolean mapped to SearchQuery.Featured.", "valueSet": "v" }
77
+ ]
78
+ },
79
+ {
80
+ "name": "orderly-product-rail",
81
+ "description": "Horizontal product rail that renders a SearchQuery by composing orderly-product-grid.",
82
+ "references": [{ "name": "Product rail reference", "url": "./docs/components/product-rail.md" }],
83
+ "attributes": [
84
+ { "name": "base-url", "description": "Backend URL used when no client property is assigned." },
85
+ { "name": "title", "description": "Rail heading." },
86
+ { "name": "cta-label", "description": "CTA link label." },
87
+ { "name": "cta-href", "description": "CTA link URL." },
88
+ { "name": "empty-label", "description": "Empty result text." },
89
+ { "name": "loading-label", "description": "Loading text forwarded to the grid." },
90
+ { "name": "error-label", "description": "Error text forwarded to the grid." },
91
+ { "name": "keywords", "description": "Declarative query text mapped to SearchQuery.Query." },
92
+ { "name": "tags", "description": "Comma-separated values mapped to SearchQuery.Tags." },
93
+ { "name": "hidden-query", "description": "Declarative query text mapped to SearchQuery.HiddenQuery." },
94
+ { "name": "order-by", "description": "Comma-separated values mapped to SearchQuery.OrderBy." },
95
+ { "name": "store-id", "description": "Value mapped to SearchQuery.StoreId." },
96
+ { "name": "featured", "description": "Boolean mapped to SearchQuery.Featured.", "valueSet": "v" }
97
+ ]
98
+ },
99
+ {
100
+ "name": "query",
101
+ "description": "Declarative query child element for orderly-product-grid, orderly-product-rail, and orderly-collection-page.",
102
+ "attributes": [
103
+ { "name": "keywords", "description": "Declarative query text mapped to SearchQuery.Query." },
104
+ { "name": "tags", "description": "Comma-separated values mapped to SearchQuery.Tags." },
105
+ { "name": "hidden-query", "description": "Declarative query text mapped to SearchQuery.HiddenQuery." },
106
+ { "name": "order-by", "description": "Comma-separated values mapped to SearchQuery.OrderBy." },
107
+ { "name": "store-id", "description": "Value mapped to SearchQuery.StoreId." },
108
+ { "name": "featured", "description": "Boolean mapped to SearchQuery.Featured.", "valueSet": "v" }
109
+ ]
110
+ },
111
+ {
112
+ "name": "orderly-collection-page",
113
+ "description": "Collection page with a full-width hero and search-backed product grid.",
114
+ "references": [{ "name": "Component reference", "url": "./docs/components/README.md#orderly-collection-page" }],
115
+ "attributes": [
116
+ { "name": "base-url", "description": "Backend URL used when no client property is assigned." },
117
+ { "name": "title", "description": "Collection heading." },
118
+ { "name": "description", "description": "Collection description." },
119
+ { "name": "hero-image", "description": "Hero image URL." },
120
+ { "name": "cta-label", "description": "CTA link label." },
121
+ { "name": "cta-href", "description": "CTA link URL." },
122
+ { "name": "keywords", "description": "Declarative query text mapped to SearchQuery.Query." },
123
+ { "name": "tags", "description": "Comma-separated values mapped to SearchQuery.Tags." },
124
+ { "name": "hidden-query", "description": "Declarative query text mapped to SearchQuery.HiddenQuery." },
125
+ { "name": "order-by", "description": "Comma-separated values mapped to SearchQuery.OrderBy." },
126
+ { "name": "store-id", "description": "Value mapped to SearchQuery.StoreId." },
127
+ { "name": "featured", "description": "Boolean mapped to SearchQuery.Featured.", "valueSet": "v" }
128
+ ]
129
+ },
130
+ {
131
+ "name": "orderly-product-detail-page",
132
+ "description": "Default product detail page that resolves SearchObject by SearchQuery.ShareUrl from share-url or #url.",
133
+ "references": [{ "name": "Component reference", "url": "./docs/components/README.md#orderly-product-detail-page" }],
134
+ "attributes": [
135
+ { "name": "base-url", "description": "Backend URL used when no client property is assigned." },
136
+ { "name": "share-url", "description": "Product share URL mapped to SearchQuery.ShareUrl." },
137
+ { "name": "brand-label", "description": "Brand text." },
138
+ { "name": "checkout-href", "description": "Checkout page URL used by the basket." },
139
+ { "name": "checkout-label", "description": "Checkout link label." },
140
+ { "name": "add-label", "description": "Add-to-basket button label." },
141
+ { "name": "loading-label", "description": "Product loading text." },
142
+ { "name": "not-found-label", "description": "Product not found text." },
143
+ { "name": "error-label", "description": "Product loading error text." }
144
+ ]
145
+ },
146
+ {
147
+ "name": "orderly-product-tile",
148
+ "description": "Product tile for SearchObject with selection and basket behavior.",
149
+ "attributes": [
150
+ { "name": "add-label", "description": "Add-to-basket accessible label." },
151
+ { "name": "remove-label", "description": "Remove-from-basket accessible label." }
152
+ ]
153
+ },
154
+ {
155
+ "name": "orderly-product-page",
156
+ "description": "Product detail view with thumbnails and add-to-basket behavior.",
157
+ "attributes": [{ "name": "add-label", "description": "Add-to-basket button label." }]
158
+ },
159
+ {
160
+ "name": "orderly-credit",
161
+ "description": "Formats and renders a Core Credit value as stylable money markup.",
162
+ "attributes": [
163
+ { "name": "amount", "description": "Declarative amount used when no credit property is assigned." },
164
+ { "name": "currency", "description": "Declarative currency code used when no credit property is assigned." },
165
+ { "name": "locale", "description": "Locale used for amount formatting. Defaults to da-DK." },
166
+ { "name": "empty-label", "description": "Text shown when no Credit value is available." }
167
+ ]
168
+ },
169
+ {
170
+ "name": "orderly-stored-image",
171
+ "description": "Renders StoredImage with URL resolution, rotation, crop, and fit handling.",
172
+ "attributes": [
173
+ { "name": "fit", "description": "Image fit.", "values": [{ "name": "contain" }, { "name": "cover" }] },
174
+ { "name": "variant", "description": "Image URL variant.", "values": [{ "name": "thumbnail" }, { "name": "object" }] },
175
+ { "name": "alt", "description": "Image alt text." }
176
+ ]
177
+ },
178
+ {
179
+ "name": "orderly-basket-icon",
180
+ "description": "Basket icon button with count badge.",
181
+ "attributes": [{ "name": "label", "description": "Accessible basket label." }]
182
+ },
183
+ {
184
+ "name": "orderly-basket",
185
+ "description": "DraftOrder basket overview.",
186
+ "attributes": [
187
+ { "name": "base-url", "description": "Backend URL used when no client property is assigned." },
188
+ { "name": "empty-label", "description": "Empty basket text." },
189
+ { "name": "clear-label", "description": "Clear basket button label." },
190
+ { "name": "continue-label", "description": "Continue shopping link label." },
191
+ { "name": "continue-href", "description": "Continue shopping link URL." },
192
+ { "name": "quantity-label", "description": "Quantity label." },
193
+ { "name": "remove-label", "description": "Remove line label." },
194
+ { "name": "checkout-href", "description": "Checkout link URL." },
195
+ { "name": "checkout-label", "description": "Checkout link label." },
196
+ { "name": "checkout-action", "description": "Checkout action behavior." }
197
+ ]
198
+ },
199
+ {
200
+ "name": "orderly-checkout",
201
+ "description": "Checkout form for profile, delivery methods, service points, draft verification, and order creation.",
202
+ "attributes": [
203
+ { "name": "base-url", "description": "Backend URL used when no client property is assigned." },
204
+ { "name": "terms-href", "description": "Terms link URL." },
205
+ { "name": "delivery-address-prompt-label", "description": "Text shown before enough address data is available to load delivery methods." },
206
+ { "name": "no-delivery-methods-label", "description": "Text shown when no delivery methods are available." },
207
+ { "name": "service-point-help-label", "description": "Help text shown above service point choices." },
208
+ { "name": "no-service-points-label", "description": "Text shown when no service points are available." }
209
+ ]
210
+ },
211
+ {
212
+ "name": "orderly-navigation",
213
+ "description": "Navigation menu with vertical or horizontal layouts, nested disclosure behavior, desktop tiered navigation support, horizontal overflow scrolling, and inline slot content.",
214
+ "references": [
215
+ {
216
+ "name": "Navigation reference",
217
+ "url": "./docs/components/README.md"
218
+ }
219
+ ],
220
+ "attributes": [
221
+ {
222
+ "name": "layout",
223
+ "description": "Navigation layout. Use vertical for a side menu or horizontal for a tiered desktop menu with a scrolling top row, full-width second row, and third-level dropdowns."
224
+ },
225
+ {
226
+ "name": "sticky",
227
+ "description": "Set to false to opt out of sticky positioning.",
228
+ "valueSet": "v"
229
+ }
230
+ ]
231
+ },
232
+ {
233
+ "name": "orderly-search-box",
234
+ "description": "Search input that binds to an orderly-product-grid. Icon mode opens a full-page overlay, shows latest products immediately, then searches automatically with debounce and renders product results below the search field.",
235
+ "attributes": [
236
+ { "name": "base-url", "description": "Backend URL used when no client property is assigned." },
237
+ { "name": "for", "description": "Target product grid id." },
238
+ { "name": "mode", "description": "Render mode: textbox or icon. Icon mode opens a full-page search overlay with immediate initial results." },
239
+ { "name": "placeholder", "description": "Input placeholder." },
240
+ { "name": "label", "description": "Accessible search label and submit label." },
241
+ { "name": "close-label", "description": "Accessible close label for icon overlay mode." },
242
+ { "name": "prompt-label", "description": "Prompt text shown before a search query is entered in icon overlay mode." },
243
+ { "name": "loading-label", "description": "Loading label passed to overlay search results." },
244
+ { "name": "error-label", "description": "Error label passed to overlay search results." },
245
+ { "name": "empty-label", "description": "Empty-state label for overlay search results." },
246
+ { "name": "debounce-ms", "description": "Automatic search debounce delay in milliseconds. Defaults to 500." }
247
+ ]
248
+ },
249
+ {
250
+ "name": "orderly-sort-select",
251
+ "description": "Sort select that binds to an orderly-product-grid.",
252
+ "attributes": [{ "name": "for", "description": "Target product grid id." }]
253
+ },
254
+ {
255
+ "name": "orderly-filter-panel",
256
+ "description": "Filter panel placeholder that can bind to an orderly-product-grid.",
257
+ "attributes": [{ "name": "for", "description": "Target product grid id." }]
258
+ },
259
+ {
260
+ "name": "orderly-load-more",
261
+ "description": "Load-more control that calls loadNextPage on an orderly-product-grid.",
262
+ "attributes": [{ "name": "for", "description": "Target product grid id." }]
263
+ },
264
+ {
265
+ "name": "orderly-shop-footer",
266
+ "description": "Configurable shop footer with logo, about text, address, contact information, opening hours, and links."
267
+ }
268
+ ],
269
+ "globalAttributes": [],
270
+ "valueSets": [
271
+ {
272
+ "name": "v",
273
+ "values": [
274
+ { "name": "true" },
275
+ { "name": "false" }
276
+ ]
277
+ }
278
+ ]
279
+ }
package/package.json ADDED
@@ -0,0 +1,118 @@
1
+ {
2
+ "name": "@orderlyshop/web-components",
3
+ "version": "0.1.0-build.7045",
4
+ "description": "Headless native web components for Orderly storefronts.",
5
+ "license": "UNLICENSED",
6
+ "author": "Orderly",
7
+ "keywords": [
8
+ "orderly",
9
+ "web-components",
10
+ "custom-elements",
11
+ "storefront",
12
+ "headless-commerce",
13
+ "grpc-web"
14
+ ],
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+ssh://git@github.com/orderly-ai/orderly.git",
18
+ "directory": "clients/libraries/npm/web-components"
19
+ },
20
+ "homepage": "https://github.com/orderly-ai/orderly/tree/development/clients/libraries/npm/web-components#readme",
21
+ "bugs": {
22
+ "url": "https://github.com/orderly-ai/orderly/issues"
23
+ },
24
+ "type": "module",
25
+ "customElements": "custom-elements.json",
26
+ "sideEffects": [
27
+ "./dist/define.js"
28
+ ],
29
+ "bin": {
30
+ "orderly-init-shop": "./bin/orderly-init-shop.mjs",
31
+ "orderly-init-navigation": "./bin/orderly-init-navigation.mjs",
32
+ "orderly-init-taxonomy": "./bin/orderly-init-taxonomy.mjs",
33
+ "orderly-publish-site": "./bin/orderly-publish-site.mjs",
34
+ "orderly-generate-category-pages": "./bin/orderly-generate-category-pages.mjs",
35
+ "orderly-build-category-pages": "./bin/orderly-build-category-pages.mjs",
36
+ "orderly-hydrate-static-pages": "./bin/orderly-hydrate-static-pages.mjs"
37
+ },
38
+ "files": [
39
+ "AGENTS.md",
40
+ "bin",
41
+ "custom-elements.json",
42
+ "dist",
43
+ "docs",
44
+ "examples",
45
+ "html-custom-data.json",
46
+ "server",
47
+ "README.md",
48
+ "package.json"
49
+ ],
50
+ "exports": {
51
+ ".": {
52
+ "types": "./dist/index.d.ts",
53
+ "import": "./dist/index.js"
54
+ },
55
+ "./define": {
56
+ "types": "./dist/define.d.ts",
57
+ "import": "./dist/define.js"
58
+ },
59
+ "./default-shop": {
60
+ "types": "./dist/default-shop.d.ts",
61
+ "import": "./dist/default-shop.js"
62
+ },
63
+ "./navigation": {
64
+ "types": "./dist/navigation.d.ts",
65
+ "import": "./dist/navigation.js"
66
+ },
67
+ "./query": {
68
+ "types": "./dist/query.d.ts",
69
+ "import": "./dist/query.js"
70
+ },
71
+ "./shop": {
72
+ "types": "./dist/shop.d.ts",
73
+ "import": "./dist/shop.js"
74
+ },
75
+ "./shop-query": {
76
+ "types": "./dist/shop-query.d.ts",
77
+ "import": "./dist/shop-query.js"
78
+ },
79
+ "./stores": {
80
+ "types": "./dist/stores.d.ts",
81
+ "import": "./dist/stores.js"
82
+ },
83
+ "./taxonomy": {
84
+ "types": "./dist/taxonomy.d.ts",
85
+ "import": "./dist/taxonomy.js"
86
+ }
87
+ },
88
+ "main": "./dist/index.js",
89
+ "module": "./dist/index.js",
90
+ "types": "./dist/index.d.ts",
91
+ "browser": "./dist/index.js",
92
+ "unpkg": "./dist/browser/orderly-web-components.define.global.js",
93
+ "jsdelivr": "./dist/browser/orderly-web-components.define.global.js",
94
+ "engines": {
95
+ "node": ">=20"
96
+ },
97
+ "publishConfig": {
98
+ "access": "public"
99
+ },
100
+ "scripts": {
101
+ "typecheck": "tsc --noEmit",
102
+ "build": "tsup && node scripts/verify-browser-bundle.mjs",
103
+ "test": "vitest run",
104
+ "prepack": "npm run build",
105
+ "pack:dry-run": "npm pack --dry-run"
106
+ },
107
+ "dependencies": {
108
+ "@bufbuild/protobuf": "^2.11.0",
109
+ "@orderlyshop/core-client": "0.1.0-build.7045",
110
+ "basic-ftp": "^5.3.0"
111
+ },
112
+ "devDependencies": {
113
+ "happy-dom": "^20.0.10",
114
+ "tsup": "^8.5.1",
115
+ "typescript": "^5.9.3",
116
+ "vitest": "^3.2.4"
117
+ }
118
+ }
@@ -0,0 +1,111 @@
1
+ # Server Side Product URLs
2
+
3
+ The browser components can render product details from `product.html#url=<share-url>`, but SEO-friendly shops often need real product URLs such as:
4
+
5
+ ```text
6
+ /mickey-mouse-billedramme-fra-danish-design-str-40x50-cm-p7yxrjsfbq.html
7
+ ```
8
+
9
+ The files in this folder are copyable server helpers for that setup. The goal is not to replace the browser web components. The server emits a small, declarative SSR fallback inside `<orderly-product-detail-page>`, then the web component loads the canonical `SearchObject` through `SearchService.Search` and hides the fallback when live data is ready.
10
+
11
+ ## Apache + PHP
12
+
13
+ Copy these files into the built static site output:
14
+
15
+ ```sh
16
+ cp node_modules/@orderlyshop/web-components/server/apache/.htaccess dist/.htaccess
17
+ cp node_modules/@orderlyshop/web-components/server/php/orderly-product.php dist/orderly-product.php
18
+ ```
19
+
20
+ Deploy `dist` to Apache with `AllowOverride FileInfo` or equivalent enabled. Existing files such as `index.html`, `category.html`, `checkout.html`, `product.html`, `/assets/*`, and `/categories/*` are served normally. Unknown `.html` paths are rewritten to:
21
+
22
+ ```text
23
+ /orderly-product.php?url=<requested-product-path>
24
+ ```
25
+
26
+ `orderly-product.php` reads the built `product.html`, injects SSR markup into `<orderly-product-detail-page>`, and preserves the built script references generated by Vite or another bundler.
27
+
28
+ ## Product Snapshot Data
29
+
30
+ The PHP page can render with only the URL, but product title, image, price, and description require a server-side product snapshot. Configure PHP with an internal JSON endpoint:
31
+
32
+ ```sh
33
+ export ORDERLY_PRODUCT_SNAPSHOT_ENDPOINT=http://127.0.0.1:4107/orderly-product-snapshot
34
+ ```
35
+
36
+ The included Node helper implements that endpoint with the generated Orderly client:
37
+
38
+ ```sh
39
+ ORDERLY_BACKEND_URL=https://service.orderly.shop \
40
+ node node_modules/@orderlyshop/web-components/server/node/product-snapshot-server.mjs
41
+ ```
42
+
43
+ The endpoint returns:
44
+
45
+ ```json
46
+ {
47
+ "shareUrl": "https://orderly.shop/example.html",
48
+ "title": "Example product",
49
+ "brand": "Example brand",
50
+ "description": "Short product description",
51
+ "priceText": "kr. 100,00",
52
+ "imageUrl": "https://cdn.example/product.jpg"
53
+ }
54
+ ```
55
+
56
+ Advanced PHP shops can replace the endpoint with native PHP gRPC/protobuf code or any internal cache. Keep it server-side. Do not expose API keys, OAuth tokens, refresh tokens, or cookie values to browser JavaScript.
57
+
58
+ ## Nginx + PHP-FPM
59
+
60
+ Copy `nginx/orderly-products.conf` next to your deployment configuration and include it from the `server` block that serves the built shop:
61
+
62
+ ```nginx
63
+ server {
64
+ root /var/www/shop/dist;
65
+
66
+ include /var/www/shop/orderly-products.conf;
67
+
68
+ location ~ \.php$ {
69
+ include fastcgi_params;
70
+ fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
71
+ fastcgi_pass unix:/run/php/php8.3-fpm.sock;
72
+ }
73
+ }
74
+ ```
75
+
76
+ Then copy the PHP renderer:
77
+
78
+ ```sh
79
+ cp node_modules/@orderlyshop/web-components/server/php/orderly-product.php dist/orderly-product.php
80
+ ```
81
+
82
+ ## Caching
83
+
84
+ Product SSR can be cached at the web-server or CDN layer. A normal setup is:
85
+
86
+ - Cache successful product HTML for a short period, for example 5-30 minutes.
87
+ - Let the browser component refresh the live product data after hydration.
88
+ - Keep category pages generated and hydrated by `orderly-build-category-pages` in a nightly job.
89
+ - Purge product URL cache entries when the shop knows a product changed.
90
+
91
+ ## Declarative SSR Shape
92
+
93
+ The PHP helper emits markup like this:
94
+
95
+ ```html
96
+ <orderly-product-detail-page data-orderly-ssr share-url="https://orderly.shop/example.html">
97
+ <article slot="content-before" class="orderly-ssr-product-detail" data-orderly-ssr-fallback data-orderly-product>
98
+ <div class="orderly-ssr-product-detail__image">
99
+ <img src="https://cdn.example/product.jpg" alt="Example product" loading="eager">
100
+ </div>
101
+ <section class="orderly-ssr-product-detail__text">
102
+ <p data-orderly-field="brand">Example brand</p>
103
+ <h1 data-orderly-field="title">Example product</h1>
104
+ <p data-orderly-field="price">kr. 100,00</p>
105
+ <p data-orderly-field="description">Short product description</p>
106
+ </section>
107
+ </article>
108
+ </orderly-product-detail-page>
109
+ ```
110
+
111
+ The default component CSS styles this fallback enough to avoid an abrupt blank page. When the web component loads the live `SearchObject`, it hides the fallback and renders the normal product detail view.
@@ -0,0 +1,18 @@
1
+ # Orderly product URL rewrite helper for Apache.
2
+ #
3
+ # Copy this file next to the built static shop files, then copy
4
+ # ../php/orderly-product.php to the same directory.
5
+ #
6
+ # Existing files and directories are served normally. Unknown root-level .html
7
+ # URLs are treated as product share URLs and are rendered by orderly-product.php.
8
+
9
+ RewriteEngine On
10
+
11
+ RewriteCond %{REQUEST_FILENAME} -f [OR]
12
+ RewriteCond %{REQUEST_FILENAME} -d
13
+ RewriteRule ^ - [L]
14
+
15
+ RewriteRule ^(index|category|checkout|product|forretningsbetingelser)\.html$ - [L]
16
+ RewriteRule ^(assets|categories|node_modules|api|static|images|img|css|js)/ - [L]
17
+
18
+ RewriteRule ^(.+\.html)$ orderly-product.php?url=$1 [QSA,L]
@@ -0,0 +1,24 @@
1
+ # Orderly product URL rewrite helper for Nginx + PHP-FPM.
2
+ #
3
+ # Include this from the server block that serves the built static shop. Copy
4
+ # ../php/orderly-product.php to the same document root as product.html.
5
+ #
6
+ # Example:
7
+ # server {
8
+ # root /var/www/shop/dist;
9
+ # include /var/www/shop/orderly-products.conf;
10
+ # location ~ \.php$ {
11
+ # include fastcgi_params;
12
+ # fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
13
+ # fastcgi_pass unix:/run/php/php8.3-fpm.sock;
14
+ # }
15
+ # }
16
+
17
+ location / {
18
+ try_files $uri $uri/ @orderly_product;
19
+ }
20
+
21
+ location @orderly_product {
22
+ rewrite ^/(.+\.html)$ /orderly-product.php?url=$1 last;
23
+ return 404;
24
+ }