@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,20 +1,27 @@
1
1
  import type { Metadata } from "next";
2
2
  import { Inter } from "next/font/google";
3
+ import { cookies } from "next/headers";
3
4
  import "./globals.css";
4
5
  import { Header } from "@/components/layout/header";
5
6
  import { Footer } from "@/components/layout/footer";
6
7
  import { QueryProvider } from "@/components/providers/query-provider";
7
- import { CurrencyProvider } from "@/components/providers/currency-provider";
8
+ import { StorefrontProvider } from "@doswiftly/storefront-sdk/react";
8
9
  import { ThemeProvider } from "@/components/providers/theme-provider";
10
+ import { StoresProvider } from "@/components/providers/stores-provider";
9
11
  import { Toaster } from "sonner";
10
12
  import { fetchShop } from "@/lib/graphql/server";
13
+ import type { ShopQuery } from "@/generated/graphql";
11
14
  import { themeConfig } from "@/lib/theme/theme-config";
15
+ import { graphqlConfig } from "@/lib/graphql/config";
16
+ import { AUTH_COOKIE_NAME } from "@doswiftly/storefront-sdk";
12
17
 
13
18
  const inter = Inter({ subsets: ["latin"] });
14
19
 
15
20
  const siteName = process.env.NEXT_PUBLIC_SITE_NAME || "My Store";
21
+ const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || "http://localhost:3001";
16
22
 
17
23
  export const metadata: Metadata = {
24
+ metadataBase: new URL(siteUrl),
18
25
  title: `${siteName} - E-commerce Store`,
19
26
  description: "Welcome to our online store powered by DoSwiftly Commerce",
20
27
  };
@@ -22,13 +29,32 @@ export const metadata: Metadata = {
22
29
  // Enable ISR with 60 second revalidation for shop data (currencies)
23
30
  export const revalidate = 60;
24
31
 
32
+ const FALLBACK_SHOP: ShopQuery = {
33
+ shop: {
34
+ id: 'fallback',
35
+ name: process.env.NEXT_PUBLIC_SITE_NAME || 'Store',
36
+ currencyCode: 'PLN',
37
+ supportedCurrencies: ['PLN'],
38
+ paymentCurrencies: ['PLN'],
39
+ },
40
+ };
41
+
25
42
  export default async function RootLayout({
26
43
  children,
27
44
  }: {
28
45
  children: React.ReactNode;
29
46
  }) {
30
- // Fetch shop data (includes currencies) for CurrencyProvider
31
- const shopData = await fetchShop();
47
+ let shopData: ShopQuery;
48
+ try {
49
+ shopData = await fetchShop();
50
+ } catch (error) {
51
+ console.error('[RootLayout] Failed to fetch shop data, using fallback:', error instanceof Error ? error.message : error);
52
+ shopData = FALLBACK_SHOP;
53
+ }
54
+
55
+ // Read httpOnly auth cookie — invisible to JS, so we pass it as a prop
56
+ const cookieStore = await cookies();
57
+ const hasAuthToken = !!cookieStore.get(AUTH_COOKIE_NAME)?.value;
32
58
 
33
59
  return (
34
60
  <html lang="en" suppressHydrationWarning>
@@ -41,14 +67,23 @@ export default async function RootLayout({
41
67
  storageKey={themeConfig.storageKey}
42
68
  >
43
69
  <QueryProvider>
44
- <CurrencyProvider shopData={shopData.shop}>
45
- <div className="flex min-h-screen flex-col">
46
- <Header />
47
- <main className="flex-1">{children}</main>
48
- <Footer />
49
- </div>
50
- <Toaster position="bottom-right" richColors />
51
- </CurrencyProvider>
70
+ <StorefrontProvider
71
+ config={{
72
+ apiUrl: graphqlConfig.apiUrl,
73
+ shopSlug: graphqlConfig.shopSlug,
74
+ }}
75
+ shopData={shopData.shop}
76
+ initialIsAuthenticated={hasAuthToken}
77
+ >
78
+ <StoresProvider>
79
+ <div className="flex min-h-screen flex-col">
80
+ <Header />
81
+ <main className="flex-1">{children}</main>
82
+ <Footer />
83
+ </div>
84
+ <Toaster position="bottom-right" richColors />
85
+ </StoresProvider>
86
+ </StorefrontProvider>
52
87
  </QueryProvider>
53
88
  </ThemeProvider>
54
89
  </body>
@@ -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 ProductError({
10
+ error,
11
+ reset,
12
+ }: {
13
+ error: Error & { digest?: string };
14
+ reset: () => void;
15
+ }) {
16
+ useEffect(() => {
17
+ console.error("[Product 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
+ Product not available
27
+ </CardTitle>
28
+ </CardHeader>
29
+ <CardContent className="space-y-4">
30
+ <p className="text-sm text-muted-foreground">
31
+ We couldn&apos;t load this product. It may have been removed or is temporarily unavailable.
32
+ </p>
33
+ <div className="flex gap-3">
34
+ <Button onClick={reset}>Try again</Button>
35
+ <Button variant="outline" asChild>
36
+ <Link href="/products">Browse products</Link>
37
+ </Button>
38
+ </div>
39
+ </CardContent>
40
+ </Card>
41
+ </div>
42
+ );
43
+ }
@@ -0,0 +1,29 @@
1
+ import { Skeleton } from "@/components/ui/skeleton";
2
+
3
+ export default function ProductLoading() {
4
+ return (
5
+ <div className="container mx-auto px-4 py-8">
6
+ <Skeleton className="mb-6 h-5 w-64" />
7
+ <div className="grid gap-8 lg:grid-cols-2">
8
+ {/* Image gallery */}
9
+ <Skeleton className="aspect-square w-full rounded-lg" />
10
+ {/* Product info */}
11
+ <div className="space-y-4">
12
+ <Skeleton className="h-8 w-3/4" />
13
+ <Skeleton className="h-6 w-32" />
14
+ <Skeleton className="h-4 w-full" />
15
+ <Skeleton className="h-4 w-5/6" />
16
+ <div className="space-y-2 pt-4">
17
+ <Skeleton className="h-5 w-20" />
18
+ <div className="flex gap-2">
19
+ {[...Array(4)].map((_, i) => (
20
+ <Skeleton key={i} className="h-10 w-16 rounded-md" />
21
+ ))}
22
+ </div>
23
+ </div>
24
+ <Skeleton className="mt-6 h-12 w-full rounded-md" />
25
+ </div>
26
+ </div>
27
+ </div>
28
+ );
29
+ }
@@ -3,6 +3,7 @@ import { notFound } from "next/navigation";
3
3
  import { Breadcrumbs } from "@/components/layout/breadcrumbs";
4
4
  import { ProductClient } from "./product-client";
5
5
  import { fetchProduct, fetchProducts } from "@/lib/graphql/server";
6
+ import type { ProductQuery } from "@/generated/graphql";
6
7
 
7
8
  // ============================================================================
8
9
  // METADATA GENERATION
@@ -115,7 +116,7 @@ export async function generateStaticParams() {
115
116
  sortKey: "BEST_SELLING",
116
117
  });
117
118
 
118
- return products.map((product: any) => ({
119
+ return products.map((product) => ({
119
120
  slug: product.handle,
120
121
  }));
121
122
  } catch (error) {
@@ -199,7 +200,9 @@ export default async function ProductPage({
199
200
  *
200
201
  * @see https://schema.org/Product
201
202
  */
202
- function generateProductJsonLd(product: any) {
203
+ type Product = NonNullable<ProductQuery['product']>;
204
+
205
+ function generateProductJsonLd(product: Product) {
203
206
  const firstImage = product.images?.[0];
204
207
  const price = product.priceRange?.minVariantPrice;
205
208
  const firstVariant = product.variants?.[0];
@@ -223,10 +226,7 @@ function generateProductJsonLd(product: any) {
223
226
  availability: firstVariant?.available
224
227
  ? "https://schema.org/InStock"
225
228
  : "https://schema.org/OutOfStock",
226
- url:
227
- typeof window !== "undefined"
228
- ? window.location.href
229
- : `/products/${product.handle}`,
229
+ url: `/products/${product.handle}`,
230
230
  },
231
231
  sku: product.id,
232
232
  productID: product.id,
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { useState, useEffect } from "react";
4
4
  import { useProduct } from "@/lib/graphql/hooks";
5
- import { useCurrencyStore } from "@/stores/currency-store";
5
+ import { useCurrencyStore } from "@doswiftly/storefront-sdk/react";
6
6
  import { ProductGallery } from "@/components/product/product-gallery";
7
7
  import { ProductPrice } from "@/components/product/product-price";
8
8
  import { ProductVariantSelector } from "@/components/product/product-variant-selector";
@@ -13,58 +13,12 @@ import { SimilarProducts } from "@/components/product/similar-products";
13
13
  import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
14
14
  import { Badge } from "@/components/ui/badge";
15
15
  import { Gift } from "lucide-react";
16
- import { shopConfig } from "@/lib/config";
17
-
18
- export interface Product {
19
- id: string;
20
- handle: string;
21
- title: string;
22
- description?: string | null;
23
- vendor?: string | null;
24
- productType?: string | null;
25
- type?: string | null;
26
- collectRecipientInfo?: boolean;
27
- tags?: string[];
28
- images: Array<{
29
- url: string;
30
- altText?: string | null;
31
- }>;
32
- variants: Array<{
33
- id: string;
34
- title: string;
35
- available: boolean;
36
- selectedOptions: Array<{
37
- name: string;
38
- value: string;
39
- }>;
40
- price: {
41
- amount: string;
42
- currencyCode: string;
43
- };
44
- compareAtPrice?: {
45
- amount: string;
46
- currencyCode: string;
47
- } | null;
48
- image?: {
49
- url: string;
50
- altText?: string | null;
51
- } | null;
52
- }>;
53
- priceRange: {
54
- minVariantPrice: {
55
- amount: string;
56
- currencyCode: string;
57
- };
58
- maxVariantPrice: {
59
- amount: string;
60
- currencyCode: string;
61
- };
62
- };
63
- }
16
+ import type { ProductQuery } from "@/generated/graphql";
17
+ import type { ProductCardFields, ProductDetailFields } from "@/lib/graphql/fragments";
64
18
 
65
19
  export interface ProductClientProps {
66
- product: Product;
67
- similarProducts?: any[];
20
+ product: ProductDetailFields;
21
+ similarProducts?: ProductCardFields[];
68
22
  }
69
23
 
70
24
  /**
@@ -85,8 +39,8 @@ export function ProductClient({ product: initialProduct, similarProducts = [] }:
85
39
  const [quantity, setQuantity] = useState(1);
86
40
 
87
41
  // Get user's preferred currency from store
88
- const currency = useCurrencyStore((s: any) => s.currency);
89
- const isHydrated = useCurrencyStore((s: any) => s.isHydrated);
42
+ const currency = useCurrencyStore((s) => s.currency);
43
+ const isHydrated = useCurrencyStore((s) => s.isLoaded);
90
44
 
91
45
  // Check if SSR currency matches user's preferred currency
92
46
  const ssrCurrency = initialProduct.priceRange?.minVariantPrice?.currencyCode;
@@ -99,7 +53,7 @@ export function ProductClient({ product: initialProduct, similarProducts = [] }:
99
53
  enabled: isHydrated,
100
54
  // Only use SSR data as placeholder if currency matches
101
55
  // Otherwise, don't show stale data from wrong currency
102
- placeholderData: currencyMatches ? { product: initialProduct } : undefined,
56
+ placeholderData: currencyMatches ? { product: initialProduct } as ProductQuery : undefined,
103
57
  // Don't use stale data - always refetch when currency changes
104
58
  staleTime: 0,
105
59
  });
@@ -111,10 +65,10 @@ export function ProductClient({ product: initialProduct, similarProducts = [] }:
111
65
  // IMPORTANT: This useEffect must be called BEFORE any conditional returns
112
66
  // to comply with React Hooks rules (hooks must be called in the same order every render)
113
67
  useEffect(() => {
114
- if (product?.variants.length > 0) {
68
+ if (product && product.variants.length > 0) {
115
69
  // Try to maintain the same variant by matching ID
116
70
  const matchingVariant = product.variants.find(
117
- (v: any) => v.id === selectedVariant.id
71
+ (v) => v.id === selectedVariant.id
118
72
  );
119
73
  if (matchingVariant) {
120
74
  setSelectedVariant(matchingVariant);
@@ -318,13 +272,13 @@ export function ProductClient({ product: initialProduct, similarProducts = [] }:
318
272
  <TabsContent value="shipping" className="mt-6">
319
273
  <div className="space-y-4 text-sm">
320
274
  <p className="text-muted-foreground">
321
- Free shipping on orders over ${shopConfig.shipping.freeShippingThreshold}.
322
- Standard delivery takes {shopConfig.shipping.standardDeliveryDays.min}-{shopConfig.shipping.standardDeliveryDays.max} business days.
275
+ Free shipping on orders over $50.
276
+ Standard delivery takes 3-5 business days.
323
277
  </p>
324
278
  <ul className="list-inside list-disc space-y-2 text-muted-foreground">
325
- {shopConfig.shipping.features.map((feature, index) => (
326
- <li key={index}>{feature}</li>
327
- ))}
279
+ <li>Express shipping available at checkout</li>
280
+ <li>International shipping available</li>
281
+ <li>30-day return policy</li>
328
282
  </ul>
329
283
  </div>
330
284
  </TabsContent>
@@ -0,0 +1,32 @@
1
+ import { Skeleton } from "@/components/ui/skeleton";
2
+
3
+ export default function ProductsLoading() {
4
+ return (
5
+ <div className="container mx-auto px-4 py-8">
6
+ <Skeleton className="mb-6 h-10 w-48" />
7
+ <div className="flex gap-8">
8
+ {/* Filters sidebar */}
9
+ <div className="hidden w-64 shrink-0 lg:block">
10
+ <Skeleton className="mb-4 h-8 w-32" />
11
+ <div className="space-y-3">
12
+ {[...Array(5)].map((_, i) => (
13
+ <Skeleton key={i} className="h-6 w-full" />
14
+ ))}
15
+ </div>
16
+ </div>
17
+ {/* Product grid */}
18
+ <div className="flex-1">
19
+ <div className="grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-4">
20
+ {[...Array(12)].map((_, i) => (
21
+ <div key={i} className="space-y-3">
22
+ <Skeleton className="aspect-square w-full rounded-lg" />
23
+ <Skeleton className="h-4 w-3/4" />
24
+ <Skeleton className="h-4 w-1/2" />
25
+ </div>
26
+ ))}
27
+ </div>
28
+ </div>
29
+ </div>
30
+ </div>
31
+ );
32
+ }