@doswiftly/cli 0.1.18 → 0.1.19

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 (210) hide show
  1. package/README.md +23 -323
  2. package/dist/commands/check.js +1 -1
  3. package/dist/commands/check.js.map +1 -1
  4. package/dist/commands/deploy.d.ts.map +1 -1
  5. package/dist/commands/deploy.js +39 -20
  6. package/dist/commands/deploy.js.map +1 -1
  7. package/dist/commands/doctor.js +3 -3
  8. package/dist/commands/doctor.js.map +1 -1
  9. package/dist/commands/init.js +4 -4
  10. package/dist/commands/sdk.js +5 -5
  11. package/dist/commands/sdk.js.map +1 -1
  12. package/dist/commands/template.js +4 -4
  13. package/dist/commands/template.js.map +1 -1
  14. package/dist/commands/types.js +5 -5
  15. package/dist/commands/types.js.map +1 -1
  16. package/dist/commands/verify.js +2 -2
  17. package/dist/commands/verify.js.map +1 -1
  18. package/dist/lib/package-manager.d.ts +1 -1
  19. package/dist/lib/package-manager.js +1 -1
  20. package/package.json +1 -1
  21. package/templates/storefront-nextjs/README.md +16 -12
  22. package/templates/storefront-nextjs/app/account/orders/page.tsx +2 -2
  23. package/templates/storefront-nextjs/app/account/page.tsx +2 -2
  24. package/templates/storefront-nextjs/app/auth/login/page.tsx +1 -1
  25. package/templates/storefront-nextjs/app/auth/register/page.tsx +1 -1
  26. package/templates/storefront-nextjs/app/cart/page.tsx +1 -1
  27. package/templates/storefront-nextjs/app/categories/[slug]/page.tsx +2 -2
  28. package/templates/storefront-nextjs/app/categories/page.tsx +1 -1
  29. package/templates/storefront-nextjs/app/collections/[slug]/page.tsx +1 -1
  30. package/templates/storefront-nextjs/app/collections/page.tsx +1 -1
  31. package/templates/storefront-nextjs/app/page.tsx +1 -1
  32. package/templates/storefront-nextjs/app/products/[slug]/page.tsx +1 -1
  33. package/templates/storefront-nextjs/app/products/page.tsx +2 -2
  34. package/templates/storefront-nextjs/app/search/page.tsx +1 -1
  35. package/templates/storefront-nextjs/components/auth/auth-guard.tsx +1 -1
  36. package/templates/storefront-nextjs/components/commerce/add-to-cart-button.tsx +1 -1
  37. package/templates/storefront-nextjs/components/commerce/cart-icon.tsx +1 -1
  38. package/templates/storefront-nextjs/components/commerce/currency-selector.tsx +2 -2
  39. package/templates/storefront-nextjs/components/commerce/product-filters.tsx +1 -1
  40. package/templates/storefront-nextjs/components/commerce/product-price.tsx +1 -1
  41. package/templates/storefront-nextjs/components/commerce/search-input.tsx +1 -1
  42. package/templates/storefront-nextjs/components/commerce/sort-select.tsx +1 -1
  43. package/templates/storefront-nextjs/components/providers.tsx +1 -1
  44. package/templates/storefront-nextjs/lib/currency.tsx +3 -3
  45. package/templates/storefront-nextjs/lib/format.ts +1 -1
  46. package/templates/storefront-nextjs/lib/graphql-queries.ts +3 -3
  47. package/templates/storefront-nextjs/package.dev.json +1 -1
  48. package/templates/storefront-nextjs/package.json +1 -1
  49. package/templates/storefront-nextjs/package.json.template +1 -1
  50. package/templates/storefront-nextjs-shadcn/.github/workflows/deploy.yml +47 -0
  51. package/templates/storefront-nextjs-shadcn/.github/workflows/preview.yml +47 -0
  52. package/templates/storefront-nextjs-shadcn/CLAUDE.md +148 -35
  53. package/templates/storefront-nextjs-shadcn/README.md +29 -162
  54. package/templates/storefront-nextjs-shadcn/app/account/addresses/page.tsx +98 -91
  55. package/templates/storefront-nextjs-shadcn/app/account/error.tsx +43 -0
  56. package/templates/storefront-nextjs-shadcn/app/account/loading.tsx +19 -0
  57. package/templates/storefront-nextjs-shadcn/app/account/loyalty/page.tsx +53 -162
  58. package/templates/storefront-nextjs-shadcn/app/account/orders/[id]/loading.tsx +60 -0
  59. package/templates/storefront-nextjs-shadcn/app/account/orders/[id]/page.tsx +36 -47
  60. package/templates/storefront-nextjs-shadcn/app/account/orders/page.tsx +46 -29
  61. package/templates/storefront-nextjs-shadcn/app/account/page.tsx +8 -5
  62. package/templates/storefront-nextjs-shadcn/app/account/settings/page.tsx +108 -71
  63. package/templates/storefront-nextjs-shadcn/app/api/auth/clear-token/route.ts +2 -86
  64. package/templates/storefront-nextjs-shadcn/app/api/auth/set-token/route.ts +2 -124
  65. package/templates/storefront-nextjs-shadcn/app/auth/forgot-password/page.tsx +10 -5
  66. package/templates/storefront-nextjs-shadcn/app/blog/[slug]/loading.tsx +17 -0
  67. package/templates/storefront-nextjs-shadcn/app/blog/[slug]/page.tsx +43 -2
  68. package/templates/storefront-nextjs-shadcn/app/blog/loading.tsx +19 -0
  69. package/templates/storefront-nextjs-shadcn/app/brands/page.tsx +2 -1
  70. package/templates/storefront-nextjs-shadcn/app/cart/loading.tsx +26 -0
  71. package/templates/storefront-nextjs-shadcn/app/cart/page.tsx +6 -3
  72. package/templates/storefront-nextjs-shadcn/app/categories/[slug]/category-products-client.tsx +56 -0
  73. package/templates/storefront-nextjs-shadcn/app/categories/[slug]/loading.tsx +32 -0
  74. package/templates/storefront-nextjs-shadcn/app/categories/[slug]/page.tsx +76 -59
  75. package/templates/storefront-nextjs-shadcn/app/categories/page.tsx +8 -4
  76. package/templates/storefront-nextjs-shadcn/app/checkout/error.tsx +43 -0
  77. package/templates/storefront-nextjs-shadcn/app/checkout/loading.tsx +31 -0
  78. package/templates/storefront-nextjs-shadcn/app/checkout/page.tsx +116 -79
  79. package/templates/storefront-nextjs-shadcn/app/collections/[handle]/loading.tsx +19 -0
  80. package/templates/storefront-nextjs-shadcn/app/collections/[handle]/page.tsx +1 -1
  81. package/templates/storefront-nextjs-shadcn/app/collections/loading.tsx +18 -0
  82. package/templates/storefront-nextjs-shadcn/app/collections/page.tsx +7 -4
  83. package/templates/storefront-nextjs-shadcn/app/global-error.tsx +117 -0
  84. package/templates/storefront-nextjs-shadcn/app/globals.css +8 -0
  85. package/templates/storefront-nextjs-shadcn/app/layout.tsx +46 -11
  86. package/templates/storefront-nextjs-shadcn/app/products/[slug]/error.tsx +43 -0
  87. package/templates/storefront-nextjs-shadcn/app/products/[slug]/loading.tsx +29 -0
  88. package/templates/storefront-nextjs-shadcn/app/products/[slug]/page.tsx +6 -6
  89. package/templates/storefront-nextjs-shadcn/app/products/[slug]/product-client.tsx +15 -61
  90. package/templates/storefront-nextjs-shadcn/app/products/loading.tsx +32 -0
  91. package/templates/storefront-nextjs-shadcn/app/products/products-client.tsx +405 -151
  92. package/templates/storefront-nextjs-shadcn/app/search/loading.tsx +18 -0
  93. package/templates/storefront-nextjs-shadcn/app/wishlist/page.tsx +8 -5
  94. package/templates/storefront-nextjs-shadcn/codegen.ts +48 -31
  95. package/templates/storefront-nextjs-shadcn/components/account/customer-info.fragment.graphql +36 -0
  96. package/templates/storefront-nextjs-shadcn/components/account/order-details.tsx +3 -1
  97. package/templates/storefront-nextjs-shadcn/components/account/order-history.tsx +26 -24
  98. package/templates/storefront-nextjs-shadcn/components/account/order-summary.fragment.graphql +36 -0
  99. package/templates/storefront-nextjs-shadcn/components/auth/account-menu.tsx +9 -9
  100. package/templates/storefront-nextjs-shadcn/components/auth/login-form.tsx +11 -37
  101. package/templates/storefront-nextjs-shadcn/components/auth/register-form.tsx +37 -23
  102. package/templates/storefront-nextjs-shadcn/components/cart/cart-drawer.tsx +4 -3
  103. package/templates/storefront-nextjs-shadcn/components/cart/cart-icon.tsx +8 -5
  104. package/templates/storefront-nextjs-shadcn/components/cart/cart-item.tsx +1 -1
  105. package/templates/storefront-nextjs-shadcn/components/cart/cart-line.fragment.graphql +53 -0
  106. package/templates/storefront-nextjs-shadcn/components/cart/cart-summary.tsx +1 -1
  107. package/templates/storefront-nextjs-shadcn/components/cart/shipping-estimator.tsx +22 -7
  108. package/templates/storefront-nextjs-shadcn/components/commerce/currency-selector.tsx +2 -2
  109. package/templates/storefront-nextjs-shadcn/components/commerce/product-actions.tsx +1 -1
  110. package/templates/storefront-nextjs-shadcn/components/commerce/search-input.tsx +2 -2
  111. package/templates/storefront-nextjs-shadcn/components/common/price-display.tsx +35 -11
  112. package/templates/storefront-nextjs-shadcn/components/discount/discount-breakdown.tsx +1 -1
  113. package/templates/storefront-nextjs-shadcn/components/discount/discount-code-input.tsx +3 -3
  114. package/templates/storefront-nextjs-shadcn/components/filters/range-slider-filter.tsx +5 -5
  115. package/templates/storefront-nextjs-shadcn/components/gift-card/gift-card-input.tsx +2 -2
  116. package/templates/storefront-nextjs-shadcn/components/home/category-grid.tsx +2 -1
  117. package/templates/storefront-nextjs-shadcn/components/home/collection-card.fragment.graphql +21 -0
  118. package/templates/storefront-nextjs-shadcn/components/home/featured-collections.tsx +2 -12
  119. package/templates/storefront-nextjs-shadcn/components/home/index.ts +0 -1
  120. package/templates/storefront-nextjs-shadcn/components/hydrated.tsx +24 -0
  121. package/templates/storefront-nextjs-shadcn/components/layout/breadcrumbs.tsx +4 -4
  122. package/templates/storefront-nextjs-shadcn/components/layout/category-node.fragment.graphql +22 -0
  123. package/templates/storefront-nextjs-shadcn/components/layout/currency-selector.tsx +2 -2
  124. package/templates/storefront-nextjs-shadcn/components/layout/header.tsx +33 -23
  125. package/templates/storefront-nextjs-shadcn/components/loyalty/points-balance.tsx +2 -11
  126. package/templates/storefront-nextjs-shadcn/components/loyalty/points-history.tsx +8 -25
  127. package/templates/storefront-nextjs-shadcn/components/loyalty/referral-section.tsx +10 -19
  128. package/templates/storefront-nextjs-shadcn/components/loyalty/rewards-catalog.tsx +17 -41
  129. package/templates/storefront-nextjs-shadcn/components/loyalty/tier-progress.tsx +2 -29
  130. package/templates/storefront-nextjs-shadcn/components/order/index.ts +6 -1
  131. package/templates/storefront-nextjs-shadcn/components/product/b2b-price-display.tsx +3 -1
  132. package/templates/storefront-nextjs-shadcn/components/product/filter-active-pills.tsx +69 -0
  133. package/templates/storefront-nextjs-shadcn/components/product/filter-mobile-sheet.tsx +84 -0
  134. package/templates/storefront-nextjs-shadcn/components/product/filter-price-range.tsx +138 -0
  135. package/templates/storefront-nextjs-shadcn/components/product/index.ts +9 -2
  136. package/templates/storefront-nextjs-shadcn/components/product/product-card.fragment.graphql +49 -0
  137. package/templates/storefront-nextjs-shadcn/components/product/product-card.tsx +3 -31
  138. package/templates/storefront-nextjs-shadcn/components/product/product-detail.fragment.graphql +52 -0
  139. package/templates/storefront-nextjs-shadcn/components/product/product-filters.tsx +176 -123
  140. package/templates/storefront-nextjs-shadcn/components/product/product-grid.tsx +3 -5
  141. package/templates/storefront-nextjs-shadcn/components/product/product-image.tsx +2 -2
  142. package/templates/storefront-nextjs-shadcn/components/product/product-price.tsx +2 -2
  143. package/templates/storefront-nextjs-shadcn/components/product/product-reviews.tsx +5 -4
  144. package/templates/storefront-nextjs-shadcn/components/product/product-sort.tsx +19 -7
  145. package/templates/storefront-nextjs-shadcn/components/product/product-variant-selector.tsx +8 -23
  146. package/templates/storefront-nextjs-shadcn/components/product/product-variant.fragment.graphql +51 -0
  147. package/templates/storefront-nextjs-shadcn/components/product/review-card.tsx +1 -1
  148. package/templates/storefront-nextjs-shadcn/components/product/review-form.tsx +1 -7
  149. package/templates/storefront-nextjs-shadcn/components/product/savings-display.tsx +17 -2
  150. package/templates/storefront-nextjs-shadcn/components/product/similar-products.tsx +3 -2
  151. package/templates/storefront-nextjs-shadcn/components/providers/index.ts +1 -1
  152. package/templates/storefront-nextjs-shadcn/components/providers/stores-provider.tsx +30 -0
  153. package/templates/storefront-nextjs-shadcn/components/providers/theme-provider.tsx +1 -1
  154. package/templates/storefront-nextjs-shadcn/components/returns/index.ts +2 -2
  155. package/templates/storefront-nextjs-shadcn/components/returns/return-request-form.tsx +3 -2
  156. package/templates/storefront-nextjs-shadcn/components/search/search-results.tsx +3 -2
  157. package/templates/storefront-nextjs-shadcn/components/ui/form.tsx +174 -0
  158. package/templates/storefront-nextjs-shadcn/components/ui/index.ts +30 -2
  159. package/templates/storefront-nextjs-shadcn/components/ui/progress.tsx +40 -0
  160. package/templates/storefront-nextjs-shadcn/components/ui/sheet.tsx +107 -0
  161. package/templates/storefront-nextjs-shadcn/components/ui/slider.tsx +33 -0
  162. package/templates/storefront-nextjs-shadcn/components/ui/textarea.tsx +24 -0
  163. package/templates/storefront-nextjs-shadcn/components/wishlist/wishlist-icon.tsx +3 -1
  164. package/templates/storefront-nextjs-shadcn/generated/graphql.ts +12779 -0
  165. package/templates/storefront-nextjs-shadcn/graphql/custom.example.graphql +159 -0
  166. package/templates/storefront-nextjs-shadcn/hooks/index.ts +2 -0
  167. package/templates/storefront-nextjs-shadcn/hooks/use-auth-sync.ts +42 -0
  168. package/templates/storefront-nextjs-shadcn/hooks/use-auth.ts +17 -295
  169. package/templates/storefront-nextjs-shadcn/hooks/use-cart-actions.ts +51 -19
  170. package/templates/storefront-nextjs-shadcn/hooks/use-cart-sync.ts +13 -9
  171. package/templates/storefront-nextjs-shadcn/lib/auth/routes.ts +4 -17
  172. package/templates/storefront-nextjs-shadcn/lib/graphql/client.ts +22 -99
  173. package/templates/storefront-nextjs-shadcn/lib/graphql/config.ts +32 -0
  174. package/templates/storefront-nextjs-shadcn/lib/graphql/fragments.ts +34 -0
  175. package/templates/storefront-nextjs-shadcn/lib/graphql/hooks.ts +687 -632
  176. package/templates/storefront-nextjs-shadcn/lib/graphql/query-keys.ts +86 -0
  177. package/templates/storefront-nextjs-shadcn/lib/graphql/server.ts +131 -182
  178. package/templates/storefront-nextjs-shadcn/lib/graphql/types.ts +62 -0
  179. package/templates/storefront-nextjs-shadcn/lib/theme/theme-config.ts +0 -17
  180. package/templates/storefront-nextjs-shadcn/next-env.d.ts +6 -0
  181. package/templates/storefront-nextjs-shadcn/package.dev.json +1 -3
  182. package/templates/storefront-nextjs-shadcn/package.json +12 -13
  183. package/templates/storefront-nextjs-shadcn/package.json.template +6 -7
  184. package/templates/storefront-nextjs-shadcn/proxy.ts +3 -4
  185. package/templates/storefront-nextjs-shadcn/stores/cart-store.ts +41 -39
  186. package/templates/storefront-nextjs-shadcn/stores/checkout-store.ts +64 -75
  187. package/templates/storefront-nextjs-shadcn/stores/wishlist-store.ts +178 -177
  188. package/templates/storefront-nextjs-shadcn/tsconfig.json +23 -5
  189. package/templates/storefront-nextjs-shadcn/CART_INTEGRATION.md +0 -282
  190. package/templates/storefront-nextjs-shadcn/GRAPHQL_DOCUMENT_NAMES.md +0 -190
  191. package/templates/storefront-nextjs-shadcn/GRAPHQL_ERROR_HANDLING.md +0 -263
  192. package/templates/storefront-nextjs-shadcn/GRAPHQL_FIXES_SUMMARY.md +0 -135
  193. package/templates/storefront-nextjs-shadcn/GRAPHQL_INTEGRATION_COMPLETE.md +0 -142
  194. package/templates/storefront-nextjs-shadcn/INTEGRATION_CHECKLIST.md +0 -448
  195. package/templates/storefront-nextjs-shadcn/PRODUCT_DETAIL_PAGE_IMPLEMENTATION.md +0 -307
  196. package/templates/storefront-nextjs-shadcn/THEME_CUSTOMIZATION.md +0 -245
  197. package/templates/storefront-nextjs-shadcn/components/providers/currency-provider.tsx +0 -103
  198. package/templates/storefront-nextjs-shadcn/graphql/collections.example.ts +0 -168
  199. package/templates/storefront-nextjs-shadcn/graphql/products.example.ts +0 -160
  200. package/templates/storefront-nextjs-shadcn/lib/auth/cookies.ts +0 -220
  201. package/templates/storefront-nextjs-shadcn/lib/config.ts +0 -46
  202. package/templates/storefront-nextjs-shadcn/lib/currency/IMPLEMENTATION_SUMMARY.md +0 -254
  203. package/templates/storefront-nextjs-shadcn/lib/currency/README.md +0 -464
  204. package/templates/storefront-nextjs-shadcn/lib/currency/cookie-manager.test.ts +0 -328
  205. package/templates/storefront-nextjs-shadcn/lib/currency/cookie-manager.ts +0 -295
  206. package/templates/storefront-nextjs-shadcn/lib/currency/index.ts +0 -27
  207. package/templates/storefront-nextjs-shadcn/lib/format.ts +0 -226
  208. package/templates/storefront-nextjs-shadcn/lib/hooks.ts +0 -30
  209. package/templates/storefront-nextjs-shadcn/stores/auth-store.ts +0 -66
  210. package/templates/storefront-nextjs-shadcn/stores/currency-store.ts +0 -103
@@ -1,125 +1,3 @@
1
- /**
2
- * API Route: Set Authentication Token
3
- *
4
- * Sets the customer access token in an httpOnly cookie.
5
- * This provides security by preventing client-side JavaScript from accessing the token.
6
- *
7
- * Security Features:
8
- * 1. httpOnly cookie - Cannot be accessed via JavaScript (XSS protection)
9
- * 2. Secure flag - Only sent over HTTPS in production
10
- * 3. SameSite=Lax - CSRF protection
11
- * 4. Origin validation - Only accepts requests from same origin
12
- * 5. Content-Type validation - Only accepts JSON
13
- *
14
- * @see lib/auth/cookies.ts - Cookie configuration
15
- * @see hooks/use-auth.ts - Client-side usage
16
- *
17
- * @example
18
- * ```tsx
19
- * // Client-side usage (via setAuthToken helper)
20
- * await setAuthToken(customerAccessToken.accessToken);
21
- * ```
22
- */
1
+ import { createSetTokenHandler } from '@doswiftly/storefront-sdk';
23
2
 
24
- import { NextRequest, NextResponse } from "next/server";
25
- import { AUTH_COOKIE_CONFIG } from "@/lib/auth/cookies";
26
-
27
- /**
28
- * Request body schema
29
- */
30
- interface SetTokenRequest {
31
- token: string;
32
- }
33
-
34
- /**
35
- * POST /api/auth/set-token
36
- *
37
- * Sets the authentication token in an httpOnly cookie.
38
- *
39
- * @param request - Next.js request object
40
- * @returns Response with Set-Cookie header
41
- */
42
- export async function POST(request: NextRequest) {
43
- try {
44
- // 1. CSRF Protection: Validate origin
45
- const origin = request.headers.get("origin");
46
- const host = request.headers.get("host");
47
-
48
- // Only allow requests from same origin
49
- if (origin && !origin.includes(host || "")) {
50
- return NextResponse.json(
51
- { error: "Invalid origin" },
52
- { status: 403 }
53
- );
54
- }
55
-
56
- // 2. Validate Content-Type
57
- const contentType = request.headers.get("content-type");
58
- if (!contentType?.includes("application/json")) {
59
- return NextResponse.json(
60
- { error: "Content-Type must be application/json" },
61
- { status: 400 }
62
- );
63
- }
64
-
65
- // 3. Parse and validate request body
66
- let body: SetTokenRequest;
67
- try {
68
- body = await request.json();
69
- } catch {
70
- return NextResponse.json(
71
- { error: "Invalid JSON body" },
72
- { status: 400 }
73
- );
74
- }
75
-
76
- const { token } = body;
77
-
78
- if (!token || typeof token !== "string" || token.trim() === "") {
79
- return NextResponse.json(
80
- { error: "Token is required and must be a non-empty string" },
81
- { status: 400 }
82
- );
83
- }
84
-
85
- // 4. Create response with Set-Cookie header
86
- const response = NextResponse.json(
87
- { success: true, message: "Token set successfully" },
88
- { status: 200 }
89
- );
90
-
91
- // 5. Set httpOnly cookie
92
- response.cookies.set({
93
- name: AUTH_COOKIE_CONFIG.name,
94
- value: token,
95
- maxAge: AUTH_COOKIE_CONFIG.maxAge,
96
- path: AUTH_COOKIE_CONFIG.path,
97
- sameSite: AUTH_COOKIE_CONFIG.sameSite,
98
- secure: AUTH_COOKIE_CONFIG.secure,
99
- httpOnly: AUTH_COOKIE_CONFIG.httpOnly,
100
- });
101
-
102
- return response;
103
- } catch (error) {
104
- console.error("Error setting auth token:", error);
105
- return NextResponse.json(
106
- { error: "Internal server error" },
107
- { status: 500 }
108
- );
109
- }
110
- }
111
-
112
- /**
113
- * OPTIONS /api/auth/set-token
114
- *
115
- * Handle preflight requests for CORS.
116
- */
117
- export async function OPTIONS() {
118
- return new NextResponse(null, {
119
- status: 204,
120
- headers: {
121
- "Access-Control-Allow-Methods": "POST, OPTIONS",
122
- "Access-Control-Allow-Headers": "Content-Type",
123
- },
124
- });
125
- }
3
+ export const POST = createSetTokenHandler();
@@ -3,22 +3,27 @@
3
3
  import { useState } from "react";
4
4
  import Link from "next/link";
5
5
  import { useMutation } from "@tanstack/react-query";
6
- import { getGraphQLClient } from "@/lib/graphql/client";
7
- import { CustomerPasswordRecoverDocument } from "@/generated/graphql";
6
+ import { useExecute } from "@/lib/graphql/client";
7
+ import {
8
+ CustomerPasswordRecoverDocument,
9
+ type CustomerPasswordRecoverMutation,
10
+ } from "@/generated/graphql";
8
11
  import { Button } from "@/components/ui/button";
9
12
  import { Input } from "@/components/ui/input";
10
13
  import { ArrowLeft } from "lucide-react";
11
14
 
12
15
  export default function ForgotPasswordPage() {
16
+ const execute = useExecute();
13
17
  const [email, setEmail] = useState("");
14
18
  const [isSubmitted, setIsSubmitted] = useState(false);
15
19
  const [error, setError] = useState("");
16
20
 
17
- const client = getGraphQLClient();
18
-
19
21
  const recoverMutation = useMutation({
20
22
  mutationFn: async (email: string) => {
21
- return client.request(CustomerPasswordRecoverDocument, { email });
23
+ return execute<CustomerPasswordRecoverMutation>(
24
+ CustomerPasswordRecoverDocument.toString(),
25
+ { email },
26
+ );
22
27
  },
23
28
  onSuccess: () => {
24
29
  setIsSubmitted(true);
@@ -0,0 +1,17 @@
1
+ import { Skeleton } from "@/components/ui/skeleton";
2
+
3
+ export default function BlogPostLoading() {
4
+ return (
5
+ <div className="container mx-auto max-w-3xl px-4 py-8">
6
+ <Skeleton className="mb-4 h-5 w-64" />
7
+ <Skeleton className="mb-2 h-10 w-full" />
8
+ <Skeleton className="mb-8 h-5 w-48" />
9
+ <Skeleton className="mb-8 aspect-video w-full rounded-lg" />
10
+ <div className="space-y-4">
11
+ {[...Array(8)].map((_, i) => (
12
+ <Skeleton key={i} className="h-4 w-full" />
13
+ ))}
14
+ </div>
15
+ </div>
16
+ );
17
+ }
@@ -16,9 +16,50 @@ import { Card, CardContent } from '@/components/ui/card';
16
16
  import { Separator } from '@/components/ui/separator';
17
17
  import { BlogCard } from '@/components/blog/blog-card';
18
18
  import { BlogSidebar } from '@/components/blog/blog-sidebar';
19
+ import { sanitizeHtml } from '@doswiftly/storefront-sdk';
20
+
21
+ interface BlogImage {
22
+ url: string;
23
+ altText?: string;
24
+ }
25
+
26
+ interface BlogPost {
27
+ id: string;
28
+ title: string;
29
+ slug: string;
30
+ excerpt: string;
31
+ content: string;
32
+ contentType: string;
33
+ featuredImage: BlogImage | null;
34
+ author: {
35
+ id: string;
36
+ name: string;
37
+ bio: string;
38
+ avatar: BlogImage | null;
39
+ };
40
+ category: {
41
+ id: string;
42
+ name: string;
43
+ slug: string;
44
+ } | null;
45
+ tags: { id: string; name: string; slug: string; postCount: number }[];
46
+ status: string;
47
+ publishedAt: string;
48
+ readingTime: number;
49
+ viewCount: number;
50
+ commentCount: number;
51
+ allowComments: boolean;
52
+ isFeatured: boolean;
53
+ seo: {
54
+ title: string;
55
+ description: string;
56
+ } | null;
57
+ createdAt: string;
58
+ updatedAt: string;
59
+ }
19
60
 
20
61
  // Mock data - replace with actual GraphQL fetch
21
- const mockPost = {
62
+ const mockPost: BlogPost = {
22
63
  id: '1',
23
64
  title: 'Jak wybrać idealny produkt dla siebie',
24
65
  slug: 'jak-wybrac-idealny-produkt',
@@ -234,7 +275,7 @@ export default async function BlogPostPage({ params }: BlogPostPageProps) {
234
275
  {/* Content */}
235
276
  <div
236
277
  className="prose prose-lg max-w-none dark:prose-invert mb-8"
237
- dangerouslySetInnerHTML={{ __html: post.content }}
278
+ dangerouslySetInnerHTML={{ __html: sanitizeHtml(post.content) }}
238
279
  />
239
280
 
240
281
  {/* Tags */}
@@ -0,0 +1,19 @@
1
+ import { Skeleton } from "@/components/ui/skeleton";
2
+
3
+ export default function BlogLoading() {
4
+ return (
5
+ <div className="container mx-auto px-4 py-8">
6
+ <Skeleton className="mb-8 h-10 w-32" />
7
+ <div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
8
+ {[...Array(6)].map((_, i) => (
9
+ <div key={i} className="space-y-3">
10
+ <Skeleton className="aspect-video w-full rounded-lg" />
11
+ <Skeleton className="h-4 w-24" />
12
+ <Skeleton className="h-6 w-full" />
13
+ <Skeleton className="h-4 w-5/6" />
14
+ </div>
15
+ ))}
16
+ </div>
17
+ </div>
18
+ );
19
+ }
@@ -1,7 +1,8 @@
1
1
  "use client";
2
2
 
3
3
  import { Breadcrumbs } from "@/components/layout/breadcrumbs";
4
- import { BrandGrid, type BrandCardProps } from "@/components/brand/brand-grid";
4
+ import { BrandGrid } from "@/components/brand/brand-grid";
5
+ import type { BrandCardProps } from "@/components/brand/brand-card";
5
6
 
6
7
  export default function BrandsPage() {
7
8
  // TODO: Fetch brands from backend using GraphQL
@@ -0,0 +1,26 @@
1
+ import { Skeleton } from "@/components/ui/skeleton";
2
+
3
+ export default function CartLoading() {
4
+ return (
5
+ <div className="container mx-auto px-4 py-8">
6
+ <Skeleton className="mb-8 h-10 w-48" />
7
+ <div className="grid gap-8 lg:grid-cols-3">
8
+ <div className="space-y-4 lg:col-span-2">
9
+ {[...Array(3)].map((_, i) => (
10
+ <div key={i} className="flex gap-4 rounded-lg border p-4">
11
+ <Skeleton className="h-24 w-24 shrink-0 rounded-md" />
12
+ <div className="flex-1 space-y-2">
13
+ <Skeleton className="h-5 w-3/4" />
14
+ <Skeleton className="h-4 w-1/3" />
15
+ <Skeleton className="h-8 w-24" />
16
+ </div>
17
+ </div>
18
+ ))}
19
+ </div>
20
+ <div>
21
+ <Skeleton className="h-64 w-full rounded-lg" />
22
+ </div>
23
+ </div>
24
+ </div>
25
+ );
26
+ }
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import Link from "next/link";
4
- import { useCartStore } from "@/stores/cart-store";
4
+ import { useCartStoreApi } from "@/stores/cart-store";
5
5
  import { useCartSync } from "@/hooks/use-cart-sync";
6
6
  import { useCartActions } from "@/hooks/use-cart-actions";
7
7
  import { useCartDiscountCodesUpdate } from "@/lib/graphql/hooks";
@@ -26,6 +26,9 @@ export default function CartPage() {
26
26
  isLoading,
27
27
  } = useCartSync();
28
28
 
29
+ // Cart store API for getState() in callbacks
30
+ const cartStoreApi = useCartStoreApi();
31
+
29
32
  // Actions that mutate via GraphQL
30
33
  const { updateQuantity, removeFromCart } = useCartActions();
31
34
 
@@ -49,7 +52,7 @@ export default function CartPage() {
49
52
 
50
53
  const handleApplyPromo = async (code: string) => {
51
54
  try {
52
- const cartId = useCartStore.getState().cartId;
55
+ const cartId = cartStoreApi.getState().cartId;
53
56
  if (!cartId) {
54
57
  return { success: false, message: "Your cart is empty" };
55
58
  }
@@ -86,7 +89,7 @@ export default function CartPage() {
86
89
 
87
90
  const handleRemovePromo = async () => {
88
91
  try {
89
- const cartId = useCartStore.getState().cartId;
92
+ const cartId = cartStoreApi.getState().cartId;
90
93
  if (!cartId) return;
91
94
 
92
95
  await discountMutation.mutateAsync({
@@ -0,0 +1,56 @@
1
+ "use client";
2
+
3
+ import { ProductGrid } from "@/components/product/product-grid";
4
+ import { Skeleton } from "@/components/ui/skeleton";
5
+ import { useProducts } from "@/lib/graphql/hooks";
6
+
7
+ interface CategoryProductsClientProps {
8
+ categoryId: string;
9
+ }
10
+
11
+ /**
12
+ * Client component for category product listing.
13
+ *
14
+ * Receives categoryId from server and uses filters.categoryId
15
+ * for correct backend filtering.
16
+ */
17
+ export function CategoryProductsClient({
18
+ categoryId,
19
+ }: CategoryProductsClientProps) {
20
+ const { data, isLoading, error } = useProducts({
21
+ first: 20,
22
+ filters: { categoryId },
23
+ });
24
+
25
+ const products = data?.products ?? [];
26
+
27
+ if (isLoading) {
28
+ return (
29
+ <div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4">
30
+ {Array.from({ length: 8 }).map((_, i) => (
31
+ <Skeleton key={i} className="aspect-square w-full rounded-lg" />
32
+ ))}
33
+ </div>
34
+ );
35
+ }
36
+
37
+ if (error) {
38
+ return (
39
+ <div className="rounded-lg border border-border bg-muted/50 p-12 text-center">
40
+ <p className="text-muted-foreground">
41
+ Failed to load products. Please try again.
42
+ </p>
43
+ </div>
44
+ );
45
+ }
46
+
47
+ return (
48
+ <ProductGrid
49
+ products={products}
50
+ columns={4}
51
+ priorityCount={8}
52
+ showBadges
53
+ emptyMessage="No products in this category"
54
+ />
55
+ );
56
+ }
@@ -0,0 +1,32 @@
1
+ import { Skeleton } from "@/components/ui/skeleton";
2
+
3
+ export default function CategoryLoading() {
4
+ return (
5
+ <div className="container mx-auto px-4 py-8">
6
+ {/* Breadcrumbs */}
7
+ <Skeleton className="mb-6 h-5 w-64" />
8
+
9
+ {/* Category title */}
10
+ <Skeleton className="mb-2 h-9 w-48" />
11
+ <Skeleton className="mb-6 h-5 w-96" />
12
+
13
+ {/* Subcategory chips */}
14
+ <div className="mb-8 flex gap-2">
15
+ {[...Array(4)].map((_, i) => (
16
+ <Skeleton key={i} className="h-8 w-24 rounded-full" />
17
+ ))}
18
+ </div>
19
+
20
+ {/* Product grid 2x4 */}
21
+ <div className="grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-4">
22
+ {[...Array(8)].map((_, i) => (
23
+ <div key={i} className="space-y-3">
24
+ <Skeleton className="aspect-square w-full rounded-lg" />
25
+ <Skeleton className="h-4 w-3/4" />
26
+ <Skeleton className="h-4 w-1/2" />
27
+ </div>
28
+ ))}
29
+ </div>
30
+ </div>
31
+ );
32
+ }
@@ -1,78 +1,95 @@
1
- "use client";
2
-
3
- import { useParams } from "next/navigation";
4
- import { ProductGrid } from "@/components/product/product-grid";
1
+ import { Metadata } from "next";
2
+ import { notFound } from "next/navigation";
5
3
  import { Breadcrumbs } from "@/components/layout/breadcrumbs";
6
- import { Skeleton } from "@/components/ui/skeleton";
7
- import { useProducts } from "@/lib/graphql/hooks";
4
+ import { fetchCategory } from "@/lib/graphql/server";
5
+ import { CategoryProductsClient } from "./category-products-client";
8
6
 
9
- export default function CategoryPage() {
10
- const params = useParams();
11
- const slug = params.slug as string;
7
+ /**
8
+ * Generate metadata for category pages (SEO)
9
+ */
10
+ export async function generateMetadata({
11
+ params,
12
+ }: {
13
+ params: Promise<{ slug: string }>;
14
+ }): Promise<Metadata> {
15
+ try {
16
+ const { slug } = await params;
17
+ const data = await fetchCategory(slug);
18
+ const category = data?.category;
12
19
 
13
- // Fetch products for this category using GraphQL query
14
- const { data, isLoading, error } = useProducts({
15
- first: 20,
16
- query: `category:${slug}`,
17
- });
20
+ if (!category) {
21
+ return { title: "Category Not Found" };
22
+ }
18
23
 
19
- const products = data?.products ?? [];
24
+ return {
25
+ title: category.name,
26
+ description:
27
+ category.description || `Browse ${category.name} products`,
28
+ };
29
+ } catch {
30
+ return { title: "Category" };
31
+ }
32
+ }
20
33
 
21
- if (isLoading) {
22
- return (
23
- <div className="container mx-auto px-4 py-8">
24
- <Breadcrumbs className="mb-6" />
25
- <div className="mb-8">
26
- <Skeleton className="h-10 w-64 mb-4" />
27
- <Skeleton className="h-6 w-96" />
28
- </div>
29
- <div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4">
30
- {Array.from({ length: 8 }).map((_, i) => (
31
- <Skeleton key={i} className="aspect-square w-full" />
32
- ))}
33
- </div>
34
- </div>
35
- );
34
+ /**
35
+ * Category detail page — Server Component
36
+ *
37
+ * Fetches category metadata on the server for SEO,
38
+ * passes category ID to client component for product filtering.
39
+ */
40
+ export default async function CategoryPage({
41
+ params,
42
+ }: {
43
+ params: Promise<{ slug: string }>;
44
+ }) {
45
+ const { slug } = await params;
46
+
47
+ let category;
48
+ try {
49
+ const data = await fetchCategory(slug);
50
+ category = data?.category;
51
+ } catch (error) {
52
+ console.error('[CategoryPage] Failed to fetch category:', error instanceof Error ? error.message : error);
36
53
  }
37
54
 
38
- if (error) {
39
- return (
40
- <div className="container mx-auto px-4 py-8">
41
- <Breadcrumbs className="mb-6" />
42
- <div className="rounded-lg border border-border bg-muted/50 p-12 text-center">
43
- <h1 className="text-2xl font-bold text-foreground mb-2">
44
- Category Not Found
45
- </h1>
46
- <p className="text-muted-foreground">
47
- The category you're looking for doesn't exist.
48
- </p>
49
- </div>
50
- </div>
51
- );
55
+ if (!category) {
56
+ notFound();
52
57
  }
53
58
 
54
59
  return (
55
60
  <div className="container mx-auto px-4 py-8">
56
61
  <Breadcrumbs className="mb-6" />
57
62
 
58
- {/* Category Header */}
63
+ {/* Category Header — SSR, SEO-friendly */}
59
64
  <div className="mb-8">
60
- <h1 className="text-3xl font-bold text-foreground capitalize">
61
- {slug.replace(/-/g, " ")}
62
- </h1>
63
- <p className="mt-2 text-muted-foreground">
64
- Browse products in this category
65
- </p>
65
+ <h1 className="text-3xl font-bold text-foreground">{category.name}</h1>
66
+ {category.description && (
67
+ <p className="mt-2 text-muted-foreground">{category.description}</p>
68
+ )}
69
+ {category.children.length > 0 && (
70
+ <div className="mt-4 flex flex-wrap gap-2">
71
+ {category.children.map((child) => (
72
+ <a
73
+ key={child.id}
74
+ href={`/categories/${child.slug}`}
75
+ className="rounded-full border border-border bg-muted px-3 py-1 text-sm text-foreground hover:bg-accent transition-colors"
76
+ >
77
+ {child.name}
78
+ {child.productCount > 0 && (
79
+ <span className="ml-1 text-muted-foreground">
80
+ ({child.productCount})
81
+ </span>
82
+ )}
83
+ </a>
84
+ ))}
85
+ </div>
86
+ )}
66
87
  </div>
67
88
 
68
- {/* Products Grid */}
69
- <ProductGrid
70
- products={products}
71
- columns={4}
72
- priorityCount={8}
73
- showBadges
74
- emptyMessage="No products in this category"
75
- />
89
+ {/* Client component for product listing with filters */}
90
+ <CategoryProductsClient categoryId={category.id} />
76
91
  </div>
77
92
  );
78
93
  }
94
+
95
+ export const revalidate = 60;
@@ -2,6 +2,7 @@ import { Metadata } from "next";
2
2
  import Link from "next/link";
3
3
  import { Card } from "@/components/ui/card";
4
4
  import { fetchCategories } from "@/lib/graphql/server";
5
+ import type { CategoryNodeFields } from "@/lib/graphql/fragments";
5
6
 
6
7
  export const metadata: Metadata = {
7
8
  title: "Categories",
@@ -12,11 +13,14 @@ export const metadata: Metadata = {
12
13
  export const revalidate = 60;
13
14
 
14
15
  export default async function CategoriesPage() {
15
- // Fetch categories from GraphQL API
16
- // Response structure: { categories: { roots: [...], totalCount } }
17
- const data = await fetchCategories();
16
+ let categoryList: CategoryNodeFields[] = [];
18
17
 
19
- const categoryList = data?.categories?.roots || [];
18
+ try {
19
+ const data = await fetchCategories();
20
+ categoryList = data?.categories?.roots || [];
21
+ } catch (error) {
22
+ console.error('[CategoriesPage] Failed to fetch categories:', error instanceof Error ? error.message : error);
23
+ }
20
24
 
21
25
  return (
22
26
  <div className="container mx-auto px-4 py-8">
@@ -0,0 +1,43 @@
1
+ "use client";
2
+
3
+ import { useEffect } from "react";
4
+ import Link from "next/link";
5
+ import { AlertCircle } from "lucide-react";
6
+ import { Button } from "@/components/ui/button";
7
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
8
+
9
+ export default function CheckoutError({
10
+ error,
11
+ reset,
12
+ }: {
13
+ error: Error & { digest?: string };
14
+ reset: () => void;
15
+ }) {
16
+ useEffect(() => {
17
+ console.error("[Checkout Error]", error);
18
+ }, [error]);
19
+
20
+ return (
21
+ <div className="container mx-auto flex min-h-[400px] items-center justify-center px-4 py-16">
22
+ <Card className="max-w-md">
23
+ <CardHeader>
24
+ <CardTitle className="flex items-center gap-2 text-destructive">
25
+ <AlertCircle className="h-5 w-5" />
26
+ Checkout error
27
+ </CardTitle>
28
+ </CardHeader>
29
+ <CardContent className="space-y-4">
30
+ <p className="text-sm text-muted-foreground">
31
+ Something went wrong during checkout. Your cart items are safe.
32
+ </p>
33
+ <div className="flex gap-3">
34
+ <Button onClick={reset}>Try again</Button>
35
+ <Button variant="outline" asChild>
36
+ <Link href="/cart">Back to cart</Link>
37
+ </Button>
38
+ </div>
39
+ </CardContent>
40
+ </Card>
41
+ </div>
42
+ );
43
+ }