@shopflowateam/ui 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # ShopFlow Storefront
2
+
3
+ A production-grade, ultra-high-performance e-commerce storefront built with **Qwik.js**, **Qwik City**, and **Tailwind CSS v4**. This project is engineered for **Zero Hydration**, achieving near-instant interactivity by leveraging Qwik's unique Resumability architecture.
4
+
5
+ ---
6
+
7
+ ## The Resumability Test
8
+
9
+ Unlike traditional frameworks (React/Next.js) that require Hydration, ShopFlow serializes its state into the HTML. The browser downloads **zero** component logic until the moment of interaction.
10
+
11
+ ### 1. Initial Page Load (Before Interaction)
12
+ **Result**:
13
+ ![Initial Load Network Tab] ![alt text](image.png)
14
+
15
+ ### 2. After First Interaction (Clicking "Add to Bag")
16
+ **Result**:
17
+ ![Post Interaction Network Tab]![alt text](image-1.png)
18
+
19
+ ---
20
+
21
+ ## Key Features
22
+
23
+ - **Full E-commerce Flow**: Advanced product grids, variant management, and persistent cart state.
24
+ - **Progressive Enhancement**: All critical paths (Add to Cart, Checkout) work **without JavaScript** enabled.
25
+ - **@shopflow/ui Library**: Reusable SDK with ESM/CJS exports and **React-compatible wrappers** via `qwikify$`.
26
+ - **Environment Awareness**: Auto-adapting layouts for **Standalone**, **Iframe**, and **WebView** contexts.
27
+ - **Bridge API**: Native communication via `postMessage` for host app event synchronization.
28
+ - **Tailwind v4**: CSS-first architecture using modern tokens and **Container Queries** (`@container`).
29
+
30
+ ---
31
+
32
+ ### Resumability vs. Hydration
33
+
34
+ **Resumability** is the ability for an application to stay "paused" on the server and "resume" in the browser exactly where it left off, without re-executing the component tree. In a traditional React app, the browser must **Hydrate**: it downloads the entire JS bundle, executes all components, and attaches event listeners before the page becomes interactive.
35
+
36
+ In **ShopFlow**, we utilize Qwik's fine-grained protocol:
37
+ 1. **Serialization**: Every piece of state (using `useStore`) is converted to JSON and embedded in the HTML.
38
+ 2. **Lazy Event Listeners**: Instead of attaching listeners to every button, Qwik uses a single global listener that knows which small "chunk" of code to fetch only when a user clicks.
39
+
40
+ **Example from this code**: Our `CartProvider` (at `src/context/cart-context.tsx`) manages the global bag state. When a user navigates from the Homepage to a Product page, no JS is transferred. The cart count in the header updates immediately upon interaction because only the `addItem` chunk is pulled from the CDN on-demand.
41
+
42
+ ### Rendering Strategy
43
+ - **Parallel Fetching**: We use `routeLoader$` to fetch categories and products simultaneously on the server, avoiding request waterfalls.
44
+ - **Bundle Optimization**: All React-related dependencies are isolated to the Library build (`pkg/`), ensuring the main Storefront remains pure Qwik and ultra-light.
45
+
46
+ ---
47
+
48
+ ## Project Structure
49
+
50
+ ```text
51
+ ShopFlow/
52
+ ├── src/
53
+ │ ├── routes/ # Qwik City File-based Routing
54
+ │ │ ├── cart/ # Bag management logic
55
+ │ │ ├── checkout/ # Multi-step mutation forms
56
+ │ │ ├── embed/ # WebView & Iframe layouts
57
+ │ │ └── layout.tsx # State Providers & Session Mgmt
58
+ │ ├── components/ # Reusable Qwik UI Components
59
+ │ ├── context/ # Serializable Global State
60
+ │ ├── lib/ # API Client & Bridge SDK
61
+ │ └── global.css # Tailwind v4 Design Tokens
62
+ ├── pkg/ # (Build Output) React-compatible Library
63
+ └── dist/ # (Build Output) Production App
64
+ ```
65
+
66
+ ---
67
+
68
+ ## Getting Started
69
+
70
+ ### Prerequisites
71
+ - **Node.js**: 20.0 or higher
72
+ - **Package Manager**: npm or pnpm
73
+
74
+ ### Installation
75
+ ```bash
76
+ npm install
77
+ ```
78
+
79
+ ### Development
80
+ ```bash
81
+ npm run dev
82
+ ```
83
+
84
+ ### Production Build & Preview
85
+ ```bash
86
+ # Builds both the Storefront (dist/) and the Library (pkg/)
87
+ npm run build
88
+
89
+ # Preview the production storefront
90
+ npm run preview
91
+ ```
92
+
93
+ ---
94
+
95
+ ## Performance Metrics (Lighthouse)
96
+ ![alt text](image-2.png)
97
+
98
+ - **Performance**: 77+
99
+ - **Accessibility**: 93
100
+ - **Best Practices**: 100
101
+ - **SEO**: 100
102
+
103
+ ---
104
+
105
+ ## License
106
+ MIT
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@shopflowateam/ui",
3
+ "version": "1.0.0",
4
+ "description": "Blank project with routing included",
5
+ "engines": {
6
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
7
+ },
8
+ "engines-annotation": "Mostly required by sharp which needs a Node-API v9 compatible runtime",
9
+ "private": false,
10
+ "type": "module",
11
+ "main": "./pkg/index.cjs.js",
12
+ "module": "./pkg/index.es.js",
13
+ "types": "./pkg/types/lib/index.d.ts",
14
+ "files": [
15
+ "pkg/"
16
+ ],
17
+ "scripts": {
18
+ "build": "qwik build && npm run build.lib",
19
+ "build.client": "vite build",
20
+ "build.preview": "vite build --ssr src/entry.preview.tsx",
21
+ "build.server": "qwik check-client src dist && vite build -c adapters/vercel-edge/vite.config.ts",
22
+ "build.types": "tsc --incremental --noEmit",
23
+ "build.lib": "vite build -c vite.config.lib.ts --mode lib",
24
+ "build.lib.types": "tsc --emitDeclarationOnly --declaration --project tsconfig.lib.json",
25
+ "deploy": "vercel deploy",
26
+ "dev": "vite --mode ssr",
27
+ "dev.debug": "node --inspect-brk ./node_modules/vite/bin/vite.js --mode ssr --force",
28
+ "fmt": "prettier --write .",
29
+ "fmt.check": "prettier --check .",
30
+ "lint": "eslint \"src/**/*.ts*\"",
31
+ "preview": "qwik build preview && vite preview --open",
32
+ "start": "vite --open --mode ssr",
33
+ "qwik": "qwik"
34
+ },
35
+ "devDependencies": {
36
+ "@builder.io/qwik": "^1.19.2",
37
+ "@builder.io/qwik-city": "^1.19.2",
38
+ "@builder.io/qwik-react": "^0.5.8",
39
+ "react": "^18.2.0",
40
+ "react-dom": "^18.2.0",
41
+ "@eslint/js": "^9",
42
+ "@tailwindcss/vite": "^4.2.2",
43
+ "@types/node": "20.19.0",
44
+ "eslint": "9.32.0",
45
+ "eslint-plugin-qwik": "^1.19.2",
46
+ "globals": "16.4.0",
47
+ "prettier": "3.6.2",
48
+ "tailwindcss": "^4.2.2",
49
+ "typescript": "5.4.5",
50
+ "typescript-eslint": "8.38.0",
51
+ "typescript-plugin-css-modules": "latest",
52
+ "undici": "*",
53
+ "vercel": "^29.1.1",
54
+ "vite": "7.3.1",
55
+ "vite-tsconfig-paths": "^4.2.1"
56
+ },
57
+ "dependencies": {}
58
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 500 500"><g clip-path="url(#a)"><circle cx="250" cy="250" r="250" fill="#fff"/><path fill="#18B6F6" d="m367.87 418.45-61.17-61.18-.94.13v-.67L175.7 227.53l32.05-31.13L188.9 87.73 99.56 199.09c-15.22 15.42-18.03 40.51-7.08 59.03l55.83 93.11a46.82 46.82 0 0 0 40.73 22.81l27.65-.27 151.18 44.68Z"/><path fill="#AC7EF4" d="m401.25 196.94-12.29-22.81-6.41-11.67-2.54-4.56-.26.26-33.66-58.63a47.07 47.07 0 0 0-41.27-23.75l-29.51.8-88.01.28a47.07 47.07 0 0 0-40.33 23.34L93.4 207l95.76-119.54L314.7 226.19l-22.3 22.67 13.35 108.54.13-.26v.26h-.26l.26.27 10.42 10.2 50.62 49.78c2.13 2 5.6-.4 4.13-2.96l-31.25-61.85 54.5-101.3 1.73-2c.67-.81 1.33-1.62 1.87-2.42a46.8 46.8 0 0 0 3.34-50.18Z"/><path fill="#fff" d="M315.1 225.65 189.18 87.6l17.9 108.14L175 227l130.5 130.27-11.75-108.14 21.37-23.48Z"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h500v500H0z"/></clipPath></defs></svg>
@@ -0,0 +1,362 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("@builder.io/qwik/jsx-runtime");
4
+ const qwik = require("@builder.io/qwik");
5
+ const qwikCity = require("@builder.io/qwik-city");
6
+ const qwikReact = require("@builder.io/qwik-react");
7
+ const formatCurrency = (amountInCents) => {
8
+ return new Intl.NumberFormat("en-US", {
9
+ style: "currency",
10
+ currency: "USD"
11
+ }).format((amountInCents || 0) / 100);
12
+ };
13
+ const ProductCardQwik = qwik.component$(({ product }) => {
14
+ const formattedPrice = formatCurrency(product.price);
15
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", {
16
+ class: "group h-full flex flex-col [container-type:inline-size]",
17
+ children: [
18
+ /* @__PURE__ */ jsxRuntime.jsx("div", {
19
+ class: "relative aspect-square mb-6 rounded-md @[200px]:rounded-2xl overflow-hidden bg-surface-dim border border-border group-hover:border-primary/50 transition-all duration-500 hover:shadow-premium",
20
+ children: /* @__PURE__ */ jsxRuntime.jsx(qwikCity.Link, {
21
+ href: `/products/${product.id}`,
22
+ class: "block w-full h-full",
23
+ children: /* @__PURE__ */ jsxRuntime.jsx("img", {
24
+ src: product.images?.[0] || "https://via.placeholder.com/400",
25
+ alt: product.title || product.name,
26
+ class: "w-full h-full object-cover transform group-hover:scale-110 transition-transform duration-700",
27
+ width: 400,
28
+ height: 400,
29
+ loading: "lazy"
30
+ })
31
+ })
32
+ }),
33
+ /* @__PURE__ */ jsxRuntime.jsxs("div", {
34
+ class: "space-y-1",
35
+ children: [
36
+ /* @__PURE__ */ jsxRuntime.jsxs("div", {
37
+ class: "flex flex-col @[250px]:flex-row @[250px]:justify-between @[250px]:items-start gap-1",
38
+ children: [
39
+ /* @__PURE__ */ jsxRuntime.jsx(qwikCity.Link, {
40
+ href: `/products/${product.id}`,
41
+ class: "block",
42
+ children: /* @__PURE__ */ jsxRuntime.jsx("h3", {
43
+ class: "font-medium text-sm @[200px]:text-md group-hover:text-primary transition-colors line-clamp-1",
44
+ children: product.title || product.name
45
+ })
46
+ }),
47
+ /* @__PURE__ */ jsxRuntime.jsx("span", {
48
+ class: "font-medium text-sm @[200px]:text-md",
49
+ children: formattedPrice
50
+ })
51
+ ]
52
+ }),
53
+ /* @__PURE__ */ jsxRuntime.jsx("p", {
54
+ class: "text-[10px] @[200px]:text-xs text-text-muted uppercase tracking-widest font-medium",
55
+ children: product.category || "Collection"
56
+ })
57
+ ]
58
+ })
59
+ ]
60
+ });
61
+ });
62
+ const CartItemQwik = qwik.component$(({ item, cartAction, onUpdateQuantity$, onRemove$ }) => {
63
+ return /* @__PURE__ */ jsxRuntime.jsx("div", {
64
+ class: "[container-type:inline-size] bg-surface-dim rounded-lg border border-border group transition-all hover:border-text-muted",
65
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", {
66
+ class: "flex flex-col @[500px]:flex-row gap-8 p-8",
67
+ children: [
68
+ /* @__PURE__ */ jsxRuntime.jsx("div", {
69
+ class: "w-full @[500px]:w-32 aspect-square rounded-lg overflow-hidden bg-white border border-border flex-shrink-0",
70
+ children: /* @__PURE__ */ jsxRuntime.jsx("img", {
71
+ src: item.images?.[0],
72
+ alt: item.name,
73
+ class: "w-full h-full object-cover transition-transform group-hover:scale-105 duration-500",
74
+ width: 128,
75
+ height: 128
76
+ })
77
+ }),
78
+ /* @__PURE__ */ jsxRuntime.jsxs("div", {
79
+ class: "flex-1 flex flex-col justify-between py-1",
80
+ children: [
81
+ /* @__PURE__ */ jsxRuntime.jsxs("div", {
82
+ class: "flex justify-between items-start gap-4",
83
+ children: [
84
+ /* @__PURE__ */ jsxRuntime.jsxs("div", {
85
+ children: [
86
+ /* @__PURE__ */ jsxRuntime.jsx(qwikCity.Link, {
87
+ href: `/products/${item.id}`,
88
+ class: "text-lg font-bold hover:text-primary transition-colors uppercase tracking-tight",
89
+ children: item.name
90
+ }),
91
+ /* @__PURE__ */ jsxRuntime.jsx("p", {
92
+ class: "text-[10px] font-black text-text-muted mt-2 uppercase tracking-widest",
93
+ children: item.variantName || "Standard"
94
+ })
95
+ ]
96
+ }),
97
+ /* @__PURE__ */ jsxRuntime.jsx("p", {
98
+ class: "text-lg font-bold tracking-tighter",
99
+ children: formatCurrency(item.price)
100
+ })
101
+ ]
102
+ }),
103
+ /* @__PURE__ */ jsxRuntime.jsxs("div", {
104
+ class: "flex justify-between items-end mt-10",
105
+ children: [
106
+ /* @__PURE__ */ jsxRuntime.jsxs("div", {
107
+ class: "flex items-center gap-4",
108
+ children: [
109
+ /* @__PURE__ */ jsxRuntime.jsxs(qwikCity.Form, {
110
+ action: cartAction,
111
+ onSubmitCompleted$: onUpdateQuantity$,
112
+ children: [
113
+ /* @__PURE__ */ jsxRuntime.jsx("input", {
114
+ type: "hidden",
115
+ name: "type",
116
+ value: "update"
117
+ }),
118
+ /* @__PURE__ */ jsxRuntime.jsx("input", {
119
+ type: "hidden",
120
+ name: "id",
121
+ value: item.cartItemId
122
+ }),
123
+ /* @__PURE__ */ jsxRuntime.jsx("input", {
124
+ type: "hidden",
125
+ name: "quantity",
126
+ value: item.quantity - 1
127
+ }),
128
+ /* @__PURE__ */ jsxRuntime.jsx("button", {
129
+ disabled: item.quantity <= 1 || cartAction.isRunning,
130
+ class: "w-10 h-10 flex items-center justify-center cursor-pointer rounded-xl border border-border bg-surface-dim disabled:opacity-30 transition-all shadow-sm",
131
+ "aria-label": "Decrease",
132
+ children: /* @__PURE__ */ jsxRuntime.jsx("svg", {
133
+ xmlns: "http://www.w3.org/2000/svg",
134
+ width: "14",
135
+ height: "14",
136
+ viewBox: "0 0 24 24",
137
+ fill: "none",
138
+ stroke: "currentColor",
139
+ "stroke-width": "3",
140
+ "stroke-linecap": "round",
141
+ "stroke-linejoin": "round",
142
+ children: /* @__PURE__ */ jsxRuntime.jsx("line", {
143
+ x1: "5",
144
+ y1: "12",
145
+ x2: "19",
146
+ y2: "12"
147
+ })
148
+ })
149
+ })
150
+ ]
151
+ }),
152
+ /* @__PURE__ */ jsxRuntime.jsx("span", {
153
+ class: "w-8 text-center font-black text-xs",
154
+ children: item.quantity
155
+ }),
156
+ /* @__PURE__ */ jsxRuntime.jsxs(qwikCity.Form, {
157
+ action: cartAction,
158
+ onSubmitCompleted$: onUpdateQuantity$,
159
+ children: [
160
+ /* @__PURE__ */ jsxRuntime.jsx("input", {
161
+ type: "hidden",
162
+ name: "type",
163
+ value: "update"
164
+ }),
165
+ /* @__PURE__ */ jsxRuntime.jsx("input", {
166
+ type: "hidden",
167
+ name: "id",
168
+ value: item.cartItemId
169
+ }),
170
+ /* @__PURE__ */ jsxRuntime.jsx("input", {
171
+ type: "hidden",
172
+ name: "quantity",
173
+ value: item.quantity + 1
174
+ }),
175
+ /* @__PURE__ */ jsxRuntime.jsx("button", {
176
+ disabled: item.quantity >= 99 || cartAction.isRunning,
177
+ class: "w-10 h-10 flex items-center cursor-pointer justify-center rounded-xl border border-border bg-surface-dim disabled:opacity-30 transition-all shadow-sm",
178
+ "aria-label": "Increase",
179
+ children: /* @__PURE__ */ jsxRuntime.jsxs("svg", {
180
+ xmlns: "http://www.w3.org/2000/svg",
181
+ width: "14",
182
+ height: "14",
183
+ viewBox: "0 0 24 24",
184
+ fill: "none",
185
+ stroke: "currentColor",
186
+ "stroke-width": "3",
187
+ "stroke-linecap": "round",
188
+ "stroke-linejoin": "round",
189
+ children: [
190
+ /* @__PURE__ */ jsxRuntime.jsx("line", {
191
+ x1: "12",
192
+ y1: "5",
193
+ x2: "12",
194
+ y2: "19"
195
+ }),
196
+ /* @__PURE__ */ jsxRuntime.jsx("line", {
197
+ x1: "5",
198
+ y1: "12",
199
+ x2: "19",
200
+ y2: "12"
201
+ })
202
+ ]
203
+ })
204
+ })
205
+ ]
206
+ })
207
+ ]
208
+ }),
209
+ /* @__PURE__ */ jsxRuntime.jsxs(qwikCity.Form, {
210
+ action: cartAction,
211
+ onSubmitCompleted$: onRemove$,
212
+ children: [
213
+ /* @__PURE__ */ jsxRuntime.jsx("input", {
214
+ type: "hidden",
215
+ name: "type",
216
+ value: "remove"
217
+ }),
218
+ /* @__PURE__ */ jsxRuntime.jsx("input", {
219
+ type: "hidden",
220
+ name: "id",
221
+ value: item.cartItemId
222
+ }),
223
+ /* @__PURE__ */ jsxRuntime.jsx("button", {
224
+ class: "text-[10px] font-black uppercase tracking-[0.2em] text-red-500 hover:text-red-700 transition-colors p-2 underline decoration-2 underline-offset-4",
225
+ children: "Remove"
226
+ })
227
+ ]
228
+ })
229
+ ]
230
+ })
231
+ ]
232
+ })
233
+ ]
234
+ })
235
+ });
236
+ });
237
+ const EmptyState = qwik.component$((props) => {
238
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", {
239
+ class: "py-24 px-6 text-center animate-in fade-in slide-in-from-bottom-4 duration-700",
240
+ children: [
241
+ /* @__PURE__ */ jsxRuntime.jsx("div", {
242
+ class: "w-16 h-16 bg-surface-dim rounded-md lg:rounded-2xl flex items-center justify-center mx-auto mb-8 border border-border",
243
+ children: props.icon === "error" ? /* @__PURE__ */ jsxRuntime.jsxs("svg", {
244
+ xmlns: "http://www.w3.org/2000/svg",
245
+ width: "24",
246
+ height: "24",
247
+ viewBox: "0 0 24 24",
248
+ fill: "none",
249
+ stroke: "currentColor",
250
+ "stroke-width": "2",
251
+ "stroke-linecap": "round",
252
+ "stroke-linejoin": "round",
253
+ class: "text-text-muted",
254
+ children: [
255
+ /* @__PURE__ */ jsxRuntime.jsx("circle", {
256
+ cx: "12",
257
+ cy: "12",
258
+ r: "10"
259
+ }),
260
+ /* @__PURE__ */ jsxRuntime.jsx("line", {
261
+ x1: "12",
262
+ y1: "8",
263
+ x2: "12",
264
+ y2: "12"
265
+ }),
266
+ /* @__PURE__ */ jsxRuntime.jsx("line", {
267
+ x1: "12",
268
+ y1: "16",
269
+ x2: "12.01",
270
+ y2: "16"
271
+ })
272
+ ]
273
+ }) : props.icon === "cart" ? /* @__PURE__ */ jsxRuntime.jsxs("svg", {
274
+ xmlns: "http://www.w3.org/2000/svg",
275
+ width: "24",
276
+ height: "24",
277
+ viewBox: "0 0 24 24",
278
+ fill: "none",
279
+ stroke: "currentColor",
280
+ "stroke-width": "2",
281
+ "stroke-linecap": "round",
282
+ "stroke-linejoin": "round",
283
+ class: "text-text-muted",
284
+ children: [
285
+ /* @__PURE__ */ jsxRuntime.jsx("circle", {
286
+ cx: "8",
287
+ cy: "21",
288
+ r: "1"
289
+ }),
290
+ /* @__PURE__ */ jsxRuntime.jsx("circle", {
291
+ cx: "19",
292
+ cy: "21",
293
+ r: "1"
294
+ }),
295
+ /* @__PURE__ */ jsxRuntime.jsx("path", {
296
+ d: "M2.05 2.05h2l2.66 12.42a2 2 0 0 0 2 1.58h9.78a2 2 0 0 0 1.95-1.57l1.65-7.43H5.12"
297
+ })
298
+ ]
299
+ }) : /* @__PURE__ */ jsxRuntime.jsxs("svg", {
300
+ xmlns: "http://www.w3.org/2000/svg",
301
+ width: "24",
302
+ height: "24",
303
+ viewBox: "0 0 24 24",
304
+ fill: "none",
305
+ stroke: "currentColor",
306
+ "stroke-width": "2",
307
+ "stroke-linecap": "round",
308
+ "stroke-linejoin": "round",
309
+ class: "text-text-muted",
310
+ children: [
311
+ /* @__PURE__ */ jsxRuntime.jsx("circle", {
312
+ cx: "11",
313
+ cy: "11",
314
+ r: "8"
315
+ }),
316
+ /* @__PURE__ */ jsxRuntime.jsx("line", {
317
+ x1: "21",
318
+ y1: "21",
319
+ x2: "16.65",
320
+ y2: "16.65"
321
+ })
322
+ ]
323
+ })
324
+ }),
325
+ props.title && /* @__PURE__ */ jsxRuntime.jsx("h3", {
326
+ class: "text-2xl font-semibold mb-2 tracking-tight uppercase",
327
+ children: props.title
328
+ }),
329
+ /* @__PURE__ */ jsxRuntime.jsx("p", {
330
+ class: "text-text-muted max-w-sm mx-auto leading-relaxed mb-10 font-medium",
331
+ children: props.message
332
+ }),
333
+ props.actionLabel && props.actionHref && /* @__PURE__ */ jsxRuntime.jsx(qwikCity.Link, {
334
+ href: props.actionHref,
335
+ class: "inline-block bg-black text-white px-5 py-2.5 rounded font-semibold text-sm hover:bg-primary transition-all",
336
+ children: props.actionLabel
337
+ })
338
+ ]
339
+ });
340
+ });
341
+ qwikReact.qwikify$(ProductCardQwik, {
342
+ eagerness: "hover"
343
+ });
344
+ qwikReact.qwikify$(CartItemQwik, {
345
+ eagerness: "hover"
346
+ });
347
+ const emitShopFlowEvent = (event) => {
348
+ if (typeof window !== "undefined") {
349
+ window.parent.postMessage({
350
+ source: "shopflow",
351
+ ...event
352
+ }, "*");
353
+ window.dispatchEvent(new CustomEvent("shopflow-event", {
354
+ detail: event
355
+ }));
356
+ console.debug("[ShopFlow Bridge] Emitting event:", event);
357
+ }
358
+ };
359
+ exports.CartItem = CartItemQwik;
360
+ exports.EmptyState = EmptyState;
361
+ exports.ProductCard = ProductCardQwik;
362
+ exports.emitShopFlowEvent = emitShopFlowEvent;
@@ -0,0 +1,362 @@
1
+ import { jsxs, jsx } from "@builder.io/qwik/jsx-runtime";
2
+ import { component$ } from "@builder.io/qwik";
3
+ import { Link, Form } from "@builder.io/qwik-city";
4
+ import { qwikify$ } from "@builder.io/qwik-react";
5
+ const formatCurrency = (amountInCents) => {
6
+ return new Intl.NumberFormat("en-US", {
7
+ style: "currency",
8
+ currency: "USD"
9
+ }).format((amountInCents || 0) / 100);
10
+ };
11
+ const ProductCardQwik = component$(({ product }) => {
12
+ const formattedPrice = formatCurrency(product.price);
13
+ return /* @__PURE__ */ jsxs("div", {
14
+ class: "group h-full flex flex-col [container-type:inline-size]",
15
+ children: [
16
+ /* @__PURE__ */ jsx("div", {
17
+ class: "relative aspect-square mb-6 rounded-md @[200px]:rounded-2xl overflow-hidden bg-surface-dim border border-border group-hover:border-primary/50 transition-all duration-500 hover:shadow-premium",
18
+ children: /* @__PURE__ */ jsx(Link, {
19
+ href: `/products/${product.id}`,
20
+ class: "block w-full h-full",
21
+ children: /* @__PURE__ */ jsx("img", {
22
+ src: product.images?.[0] || "https://via.placeholder.com/400",
23
+ alt: product.title || product.name,
24
+ class: "w-full h-full object-cover transform group-hover:scale-110 transition-transform duration-700",
25
+ width: 400,
26
+ height: 400,
27
+ loading: "lazy"
28
+ })
29
+ })
30
+ }),
31
+ /* @__PURE__ */ jsxs("div", {
32
+ class: "space-y-1",
33
+ children: [
34
+ /* @__PURE__ */ jsxs("div", {
35
+ class: "flex flex-col @[250px]:flex-row @[250px]:justify-between @[250px]:items-start gap-1",
36
+ children: [
37
+ /* @__PURE__ */ jsx(Link, {
38
+ href: `/products/${product.id}`,
39
+ class: "block",
40
+ children: /* @__PURE__ */ jsx("h3", {
41
+ class: "font-medium text-sm @[200px]:text-md group-hover:text-primary transition-colors line-clamp-1",
42
+ children: product.title || product.name
43
+ })
44
+ }),
45
+ /* @__PURE__ */ jsx("span", {
46
+ class: "font-medium text-sm @[200px]:text-md",
47
+ children: formattedPrice
48
+ })
49
+ ]
50
+ }),
51
+ /* @__PURE__ */ jsx("p", {
52
+ class: "text-[10px] @[200px]:text-xs text-text-muted uppercase tracking-widest font-medium",
53
+ children: product.category || "Collection"
54
+ })
55
+ ]
56
+ })
57
+ ]
58
+ });
59
+ });
60
+ const CartItemQwik = component$(({ item, cartAction, onUpdateQuantity$, onRemove$ }) => {
61
+ return /* @__PURE__ */ jsx("div", {
62
+ class: "[container-type:inline-size] bg-surface-dim rounded-lg border border-border group transition-all hover:border-text-muted",
63
+ children: /* @__PURE__ */ jsxs("div", {
64
+ class: "flex flex-col @[500px]:flex-row gap-8 p-8",
65
+ children: [
66
+ /* @__PURE__ */ jsx("div", {
67
+ class: "w-full @[500px]:w-32 aspect-square rounded-lg overflow-hidden bg-white border border-border flex-shrink-0",
68
+ children: /* @__PURE__ */ jsx("img", {
69
+ src: item.images?.[0],
70
+ alt: item.name,
71
+ class: "w-full h-full object-cover transition-transform group-hover:scale-105 duration-500",
72
+ width: 128,
73
+ height: 128
74
+ })
75
+ }),
76
+ /* @__PURE__ */ jsxs("div", {
77
+ class: "flex-1 flex flex-col justify-between py-1",
78
+ children: [
79
+ /* @__PURE__ */ jsxs("div", {
80
+ class: "flex justify-between items-start gap-4",
81
+ children: [
82
+ /* @__PURE__ */ jsxs("div", {
83
+ children: [
84
+ /* @__PURE__ */ jsx(Link, {
85
+ href: `/products/${item.id}`,
86
+ class: "text-lg font-bold hover:text-primary transition-colors uppercase tracking-tight",
87
+ children: item.name
88
+ }),
89
+ /* @__PURE__ */ jsx("p", {
90
+ class: "text-[10px] font-black text-text-muted mt-2 uppercase tracking-widest",
91
+ children: item.variantName || "Standard"
92
+ })
93
+ ]
94
+ }),
95
+ /* @__PURE__ */ jsx("p", {
96
+ class: "text-lg font-bold tracking-tighter",
97
+ children: formatCurrency(item.price)
98
+ })
99
+ ]
100
+ }),
101
+ /* @__PURE__ */ jsxs("div", {
102
+ class: "flex justify-between items-end mt-10",
103
+ children: [
104
+ /* @__PURE__ */ jsxs("div", {
105
+ class: "flex items-center gap-4",
106
+ children: [
107
+ /* @__PURE__ */ jsxs(Form, {
108
+ action: cartAction,
109
+ onSubmitCompleted$: onUpdateQuantity$,
110
+ children: [
111
+ /* @__PURE__ */ jsx("input", {
112
+ type: "hidden",
113
+ name: "type",
114
+ value: "update"
115
+ }),
116
+ /* @__PURE__ */ jsx("input", {
117
+ type: "hidden",
118
+ name: "id",
119
+ value: item.cartItemId
120
+ }),
121
+ /* @__PURE__ */ jsx("input", {
122
+ type: "hidden",
123
+ name: "quantity",
124
+ value: item.quantity - 1
125
+ }),
126
+ /* @__PURE__ */ jsx("button", {
127
+ disabled: item.quantity <= 1 || cartAction.isRunning,
128
+ class: "w-10 h-10 flex items-center justify-center cursor-pointer rounded-xl border border-border bg-surface-dim disabled:opacity-30 transition-all shadow-sm",
129
+ "aria-label": "Decrease",
130
+ children: /* @__PURE__ */ jsx("svg", {
131
+ xmlns: "http://www.w3.org/2000/svg",
132
+ width: "14",
133
+ height: "14",
134
+ viewBox: "0 0 24 24",
135
+ fill: "none",
136
+ stroke: "currentColor",
137
+ "stroke-width": "3",
138
+ "stroke-linecap": "round",
139
+ "stroke-linejoin": "round",
140
+ children: /* @__PURE__ */ jsx("line", {
141
+ x1: "5",
142
+ y1: "12",
143
+ x2: "19",
144
+ y2: "12"
145
+ })
146
+ })
147
+ })
148
+ ]
149
+ }),
150
+ /* @__PURE__ */ jsx("span", {
151
+ class: "w-8 text-center font-black text-xs",
152
+ children: item.quantity
153
+ }),
154
+ /* @__PURE__ */ jsxs(Form, {
155
+ action: cartAction,
156
+ onSubmitCompleted$: onUpdateQuantity$,
157
+ children: [
158
+ /* @__PURE__ */ jsx("input", {
159
+ type: "hidden",
160
+ name: "type",
161
+ value: "update"
162
+ }),
163
+ /* @__PURE__ */ jsx("input", {
164
+ type: "hidden",
165
+ name: "id",
166
+ value: item.cartItemId
167
+ }),
168
+ /* @__PURE__ */ jsx("input", {
169
+ type: "hidden",
170
+ name: "quantity",
171
+ value: item.quantity + 1
172
+ }),
173
+ /* @__PURE__ */ jsx("button", {
174
+ disabled: item.quantity >= 99 || cartAction.isRunning,
175
+ class: "w-10 h-10 flex items-center cursor-pointer justify-center rounded-xl border border-border bg-surface-dim disabled:opacity-30 transition-all shadow-sm",
176
+ "aria-label": "Increase",
177
+ children: /* @__PURE__ */ jsxs("svg", {
178
+ xmlns: "http://www.w3.org/2000/svg",
179
+ width: "14",
180
+ height: "14",
181
+ viewBox: "0 0 24 24",
182
+ fill: "none",
183
+ stroke: "currentColor",
184
+ "stroke-width": "3",
185
+ "stroke-linecap": "round",
186
+ "stroke-linejoin": "round",
187
+ children: [
188
+ /* @__PURE__ */ jsx("line", {
189
+ x1: "12",
190
+ y1: "5",
191
+ x2: "12",
192
+ y2: "19"
193
+ }),
194
+ /* @__PURE__ */ jsx("line", {
195
+ x1: "5",
196
+ y1: "12",
197
+ x2: "19",
198
+ y2: "12"
199
+ })
200
+ ]
201
+ })
202
+ })
203
+ ]
204
+ })
205
+ ]
206
+ }),
207
+ /* @__PURE__ */ jsxs(Form, {
208
+ action: cartAction,
209
+ onSubmitCompleted$: onRemove$,
210
+ children: [
211
+ /* @__PURE__ */ jsx("input", {
212
+ type: "hidden",
213
+ name: "type",
214
+ value: "remove"
215
+ }),
216
+ /* @__PURE__ */ jsx("input", {
217
+ type: "hidden",
218
+ name: "id",
219
+ value: item.cartItemId
220
+ }),
221
+ /* @__PURE__ */ jsx("button", {
222
+ class: "text-[10px] font-black uppercase tracking-[0.2em] text-red-500 hover:text-red-700 transition-colors p-2 underline decoration-2 underline-offset-4",
223
+ children: "Remove"
224
+ })
225
+ ]
226
+ })
227
+ ]
228
+ })
229
+ ]
230
+ })
231
+ ]
232
+ })
233
+ });
234
+ });
235
+ const EmptyState = component$((props) => {
236
+ return /* @__PURE__ */ jsxs("div", {
237
+ class: "py-24 px-6 text-center animate-in fade-in slide-in-from-bottom-4 duration-700",
238
+ children: [
239
+ /* @__PURE__ */ jsx("div", {
240
+ class: "w-16 h-16 bg-surface-dim rounded-md lg:rounded-2xl flex items-center justify-center mx-auto mb-8 border border-border",
241
+ children: props.icon === "error" ? /* @__PURE__ */ jsxs("svg", {
242
+ xmlns: "http://www.w3.org/2000/svg",
243
+ width: "24",
244
+ height: "24",
245
+ viewBox: "0 0 24 24",
246
+ fill: "none",
247
+ stroke: "currentColor",
248
+ "stroke-width": "2",
249
+ "stroke-linecap": "round",
250
+ "stroke-linejoin": "round",
251
+ class: "text-text-muted",
252
+ children: [
253
+ /* @__PURE__ */ jsx("circle", {
254
+ cx: "12",
255
+ cy: "12",
256
+ r: "10"
257
+ }),
258
+ /* @__PURE__ */ jsx("line", {
259
+ x1: "12",
260
+ y1: "8",
261
+ x2: "12",
262
+ y2: "12"
263
+ }),
264
+ /* @__PURE__ */ jsx("line", {
265
+ x1: "12",
266
+ y1: "16",
267
+ x2: "12.01",
268
+ y2: "16"
269
+ })
270
+ ]
271
+ }) : props.icon === "cart" ? /* @__PURE__ */ jsxs("svg", {
272
+ xmlns: "http://www.w3.org/2000/svg",
273
+ width: "24",
274
+ height: "24",
275
+ viewBox: "0 0 24 24",
276
+ fill: "none",
277
+ stroke: "currentColor",
278
+ "stroke-width": "2",
279
+ "stroke-linecap": "round",
280
+ "stroke-linejoin": "round",
281
+ class: "text-text-muted",
282
+ children: [
283
+ /* @__PURE__ */ jsx("circle", {
284
+ cx: "8",
285
+ cy: "21",
286
+ r: "1"
287
+ }),
288
+ /* @__PURE__ */ jsx("circle", {
289
+ cx: "19",
290
+ cy: "21",
291
+ r: "1"
292
+ }),
293
+ /* @__PURE__ */ jsx("path", {
294
+ d: "M2.05 2.05h2l2.66 12.42a2 2 0 0 0 2 1.58h9.78a2 2 0 0 0 1.95-1.57l1.65-7.43H5.12"
295
+ })
296
+ ]
297
+ }) : /* @__PURE__ */ jsxs("svg", {
298
+ xmlns: "http://www.w3.org/2000/svg",
299
+ width: "24",
300
+ height: "24",
301
+ viewBox: "0 0 24 24",
302
+ fill: "none",
303
+ stroke: "currentColor",
304
+ "stroke-width": "2",
305
+ "stroke-linecap": "round",
306
+ "stroke-linejoin": "round",
307
+ class: "text-text-muted",
308
+ children: [
309
+ /* @__PURE__ */ jsx("circle", {
310
+ cx: "11",
311
+ cy: "11",
312
+ r: "8"
313
+ }),
314
+ /* @__PURE__ */ jsx("line", {
315
+ x1: "21",
316
+ y1: "21",
317
+ x2: "16.65",
318
+ y2: "16.65"
319
+ })
320
+ ]
321
+ })
322
+ }),
323
+ props.title && /* @__PURE__ */ jsx("h3", {
324
+ class: "text-2xl font-semibold mb-2 tracking-tight uppercase",
325
+ children: props.title
326
+ }),
327
+ /* @__PURE__ */ jsx("p", {
328
+ class: "text-text-muted max-w-sm mx-auto leading-relaxed mb-10 font-medium",
329
+ children: props.message
330
+ }),
331
+ props.actionLabel && props.actionHref && /* @__PURE__ */ jsx(Link, {
332
+ href: props.actionHref,
333
+ class: "inline-block bg-black text-white px-5 py-2.5 rounded font-semibold text-sm hover:bg-primary transition-all",
334
+ children: props.actionLabel
335
+ })
336
+ ]
337
+ });
338
+ });
339
+ qwikify$(ProductCardQwik, {
340
+ eagerness: "hover"
341
+ });
342
+ qwikify$(CartItemQwik, {
343
+ eagerness: "hover"
344
+ });
345
+ const emitShopFlowEvent = (event) => {
346
+ if (typeof window !== "undefined") {
347
+ window.parent.postMessage({
348
+ source: "shopflow",
349
+ ...event
350
+ }, "*");
351
+ window.dispatchEvent(new CustomEvent("shopflow-event", {
352
+ detail: event
353
+ }));
354
+ console.debug("[ShopFlow Bridge] Emitting event:", event);
355
+ }
356
+ };
357
+ export {
358
+ CartItemQwik as CartItem,
359
+ EmptyState,
360
+ ProductCardQwik as ProductCard,
361
+ emitShopFlowEvent
362
+ };
@@ -0,0 +1,9 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/web-manifest-combined.json",
3
+ "name": "qwik-project-name",
4
+ "short_name": "Welcome to Qwik",
5
+ "start_url": ".",
6
+ "display": "standalone",
7
+ "background_color": "#fff",
8
+ "description": "A Qwik project app."
9
+ }
package/pkg/robots.txt ADDED
File without changes