@doswiftly/cli 0.1.24 → 0.2.1
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/dist/commands/check.js +2 -2
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +8 -5
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev.d.ts +13 -0
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +155 -63
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +3 -4
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +271 -166
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/sdk.d.ts +1 -1
- package/dist/commands/sdk.js +3 -3
- package/dist/commands/sdk.js.map +1 -1
- package/dist/commands/template.d.ts.map +1 -1
- package/dist/commands/template.js +4 -31
- package/dist/commands/template.js.map +1 -1
- package/dist/commands/verify.js +5 -5
- package/dist/commands/verify.js.map +1 -1
- package/dist/index.js +2 -3
- package/dist/index.js.map +1 -1
- package/dist/lib/i18n.d.ts +12 -0
- package/dist/lib/i18n.d.ts.map +1 -1
- package/dist/lib/i18n.js +24 -0
- package/dist/lib/i18n.js.map +1 -1
- package/dist/lib/proxy-server.d.ts +22 -6
- package/dist/lib/proxy-server.d.ts.map +1 -1
- package/dist/lib/proxy-server.js +174 -75
- package/dist/lib/proxy-server.js.map +1 -1
- package/package.json +1 -1
- package/dist/commands/types.d.ts +0 -5
- package/dist/commands/types.d.ts.map +0 -1
- package/dist/commands/types.js +0 -82
- package/dist/commands/types.js.map +0 -1
- package/templates/storefront-minimal/.env.example +0 -10
- package/templates/storefront-minimal/.github/workflows/build-template.yml +0 -119
- package/templates/storefront-minimal/app/globals.css +0 -18
- package/templates/storefront-minimal/app/layout.tsx +0 -26
- package/templates/storefront-minimal/app/page.tsx +0 -93
- package/templates/storefront-minimal/lib/graphql-client.ts +0 -23
- package/templates/storefront-minimal/next.config.ts +0 -15
- package/templates/storefront-minimal/open-next.config.ts +0 -3
- package/templates/storefront-minimal/package.json +0 -30
- package/templates/storefront-minimal/postcss.config.mjs +0 -5
- package/templates/storefront-minimal/tailwind.config.ts +0 -14
- package/templates/storefront-minimal/tsconfig.json +0 -27
- package/templates/storefront-minimal/wrangler.toml +0 -24
- package/templates/storefront-nextjs/.env.example +0 -68
- package/templates/storefront-nextjs/.github/workflows/build-template.yml +0 -119
- package/templates/storefront-nextjs/README.md +0 -524
- package/templates/storefront-nextjs/app/account/orders/page.tsx +0 -216
- package/templates/storefront-nextjs/app/account/page.tsx +0 -167
- package/templates/storefront-nextjs/app/auth/login/page.tsx +0 -135
- package/templates/storefront-nextjs/app/auth/register/page.tsx +0 -212
- package/templates/storefront-nextjs/app/cart/page.tsx +0 -263
- package/templates/storefront-nextjs/app/categories/[slug]/page.tsx +0 -200
- package/templates/storefront-nextjs/app/categories/page.tsx +0 -58
- package/templates/storefront-nextjs/app/checkout/page.tsx +0 -351
- package/templates/storefront-nextjs/app/collections/[slug]/page.tsx +0 -158
- package/templates/storefront-nextjs/app/collections/page.tsx +0 -61
- package/templates/storefront-nextjs/app/globals.css +0 -98
- package/templates/storefront-nextjs/app/layout.tsx +0 -39
- package/templates/storefront-nextjs/app/page.tsx +0 -136
- package/templates/storefront-nextjs/app/products/[slug]/page.tsx +0 -119
- package/templates/storefront-nextjs/app/products/page.tsx +0 -107
- package/templates/storefront-nextjs/app/search/page.tsx +0 -127
- package/templates/storefront-nextjs/components/auth/auth-guard.tsx +0 -94
- package/templates/storefront-nextjs/components/commerce/add-to-cart-button.tsx +0 -77
- package/templates/storefront-nextjs/components/commerce/cart-icon.tsx +0 -29
- package/templates/storefront-nextjs/components/commerce/currency-selector.tsx +0 -217
- package/templates/storefront-nextjs/components/commerce/pagination.tsx +0 -62
- package/templates/storefront-nextjs/components/commerce/product-actions.tsx +0 -135
- package/templates/storefront-nextjs/components/commerce/product-filters.tsx +0 -109
- package/templates/storefront-nextjs/components/commerce/product-price.tsx +0 -375
- package/templates/storefront-nextjs/components/commerce/search-input.tsx +0 -178
- package/templates/storefront-nextjs/components/commerce/sort-select.tsx +0 -64
- package/templates/storefront-nextjs/components/commerce/variant-selector.tsx +0 -210
- package/templates/storefront-nextjs/components/layout/footer.tsx +0 -107
- package/templates/storefront-nextjs/components/layout/header.tsx +0 -104
- package/templates/storefront-nextjs/components/providers.tsx +0 -62
- package/templates/storefront-nextjs/lib/auth/routes.ts +0 -52
- package/templates/storefront-nextjs/lib/currency.tsx +0 -140
- package/templates/storefront-nextjs/lib/format.ts +0 -159
- package/templates/storefront-nextjs/lib/graphql-queries.ts +0 -629
- package/templates/storefront-nextjs/lib/hooks.ts +0 -30
- package/templates/storefront-nextjs/middleware.ts +0 -80
- package/templates/storefront-nextjs/next.config.ts +0 -37
- package/templates/storefront-nextjs/open-next.config.ts +0 -3
- package/templates/storefront-nextjs/package.dev.json +0 -30
- package/templates/storefront-nextjs/package.json +0 -32
- package/templates/storefront-nextjs/package.json.template +0 -32
- package/templates/storefront-nextjs/postcss.config.mjs +0 -8
- package/templates/storefront-nextjs/tailwind.config.ts +0 -111
- package/templates/storefront-nextjs/tsconfig.json +0 -27
- package/templates/storefront-nextjs/wrangler.toml +0 -24
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
@import "tailwindcss";
|
|
2
|
-
|
|
3
|
-
@theme {
|
|
4
|
-
--color-primary: rgb(59 130 246); /* blue-500 */
|
|
5
|
-
--color-primary-foreground: rgb(255 255 255);
|
|
6
|
-
--color-secondary: rgb(100 116 139); /* slate-500 */
|
|
7
|
-
--color-secondary-foreground: rgb(255 255 255);
|
|
8
|
-
--color-accent: rgb(249 115 22); /* orange-500 */
|
|
9
|
-
--color-accent-foreground: rgb(255 255 255);
|
|
10
|
-
--color-background: rgb(255 255 255);
|
|
11
|
-
--color-foreground: rgb(15 23 42); /* slate-900 */
|
|
12
|
-
--color-muted: rgb(241 245 249); /* slate-100 */
|
|
13
|
-
--color-muted-foreground: rgb(100 116 139); /* slate-500 */
|
|
14
|
-
--color-border: rgb(226 232 240); /* slate-200 */
|
|
15
|
-
--color-ring: rgb(59 130 246); /* blue-500 */
|
|
16
|
-
--radius: 0.5rem;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
@layer base {
|
|
20
|
-
* {
|
|
21
|
-
border-color: var(--color-border);
|
|
22
|
-
}
|
|
23
|
-
body {
|
|
24
|
-
background-color: var(--color-background);
|
|
25
|
-
color: var(--color-foreground);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
@utility btn {
|
|
30
|
-
display: inline-flex;
|
|
31
|
-
align-items: center;
|
|
32
|
-
justify-content: center;
|
|
33
|
-
border-radius: 0.5rem;
|
|
34
|
-
padding: 0.5rem 1rem;
|
|
35
|
-
font-weight: 500;
|
|
36
|
-
transition: color 150ms;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
@utility btn-primary {
|
|
40
|
-
background-color: var(--color-primary);
|
|
41
|
-
color: white;
|
|
42
|
-
&:hover {
|
|
43
|
-
background-color: rgb(59 130 246 / 0.9);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
@utility btn-secondary {
|
|
48
|
-
background-color: var(--color-secondary);
|
|
49
|
-
color: white;
|
|
50
|
-
&:hover {
|
|
51
|
-
background-color: rgb(100 116 139 / 0.9);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
@utility btn-outline {
|
|
56
|
-
border: 1px solid var(--color-border);
|
|
57
|
-
background-color: transparent;
|
|
58
|
-
&:hover {
|
|
59
|
-
background-color: var(--color-muted);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
@utility input {
|
|
64
|
-
display: flex;
|
|
65
|
-
height: 2.5rem;
|
|
66
|
-
width: 100%;
|
|
67
|
-
border-radius: 0.5rem;
|
|
68
|
-
border: 1px solid var(--color-border);
|
|
69
|
-
background-color: var(--color-background);
|
|
70
|
-
padding: 0.5rem 0.75rem;
|
|
71
|
-
font-size: 0.875rem;
|
|
72
|
-
&::placeholder {
|
|
73
|
-
color: var(--color-muted-foreground);
|
|
74
|
-
}
|
|
75
|
-
&:focus-visible {
|
|
76
|
-
outline: none;
|
|
77
|
-
ring: 2px solid var(--color-ring);
|
|
78
|
-
ring-offset: 2px;
|
|
79
|
-
}
|
|
80
|
-
&:disabled {
|
|
81
|
-
cursor: not-allowed;
|
|
82
|
-
opacity: 0.5;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
@utility card {
|
|
87
|
-
border-radius: 0.5rem;
|
|
88
|
-
border: 1px solid var(--color-border);
|
|
89
|
-
background-color: var(--color-background);
|
|
90
|
-
padding: 1rem;
|
|
91
|
-
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
@layer utilities {
|
|
95
|
-
.text-balance {
|
|
96
|
-
text-wrap: balance;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import type { Metadata } from "next";
|
|
2
|
-
import { Inter } from "next/font/google";
|
|
3
|
-
import "./globals.css";
|
|
4
|
-
import { Header } from "@/components/layout/header";
|
|
5
|
-
import { Footer } from "@/components/layout/footer";
|
|
6
|
-
import { Providers } from "@/components/providers";
|
|
7
|
-
|
|
8
|
-
const inter = Inter({ subsets: ["latin"] });
|
|
9
|
-
|
|
10
|
-
const siteName = process.env.NEXT_PUBLIC_SITE_NAME || "My Store";
|
|
11
|
-
|
|
12
|
-
export const metadata: Metadata = {
|
|
13
|
-
title: `${siteName} - E-commerce Store`,
|
|
14
|
-
description: "Welcome to our online store powered by DoSwiftly Commerce",
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
// Force dynamic rendering because Header uses React Query hooks (CurrencySelector, CartIcon)
|
|
18
|
-
// TODO: Optimize by lazy-loading Header components that use hooks for better SSG support
|
|
19
|
-
export const dynamic = "force-dynamic";
|
|
20
|
-
|
|
21
|
-
export default function RootLayout({
|
|
22
|
-
children,
|
|
23
|
-
}: {
|
|
24
|
-
children: React.ReactNode;
|
|
25
|
-
}) {
|
|
26
|
-
return (
|
|
27
|
-
<html lang="en">
|
|
28
|
-
<body className={inter.className}>
|
|
29
|
-
<Providers>
|
|
30
|
-
<div className="flex min-h-screen flex-col">
|
|
31
|
-
<Header />
|
|
32
|
-
<main className="flex-1">{children}</main>
|
|
33
|
-
<Footer />
|
|
34
|
-
</div>
|
|
35
|
-
</Providers>
|
|
36
|
-
</body>
|
|
37
|
-
</html>
|
|
38
|
-
);
|
|
39
|
-
}
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import Link from "next/link";
|
|
2
|
-
import {
|
|
3
|
-
useProducts,
|
|
4
|
-
useCategories,
|
|
5
|
-
} from "@doswiftly/storefront-sdk/graphql/server";
|
|
6
|
-
|
|
7
|
-
export default async function HomePage() {
|
|
8
|
-
// Fetch featured products
|
|
9
|
-
const { products } = await useProducts({ first: 8 });
|
|
10
|
-
|
|
11
|
-
// Fetch categories (roots are the top-level categories)
|
|
12
|
-
const { categories } = await useCategories();
|
|
13
|
-
const categoryRoots = categories.slice(0, 4);
|
|
14
|
-
|
|
15
|
-
return (
|
|
16
|
-
<div className="container mx-auto px-4 py-8">
|
|
17
|
-
{/* Hero Section */}
|
|
18
|
-
<section className="mb-16 text-center">
|
|
19
|
-
<h1 className="mb-4 text-4xl font-bold tracking-tight text-gray-900 sm:text-5xl md:text-6xl">
|
|
20
|
-
Welcome to{" "}
|
|
21
|
-
<span className="text-primary">
|
|
22
|
-
{process.env.NEXT_PUBLIC_SITE_NAME || "My Store"}
|
|
23
|
-
</span>
|
|
24
|
-
</h1>
|
|
25
|
-
<p className="mx-auto mb-8 max-w-2xl text-lg text-gray-600">
|
|
26
|
-
Discover our curated collection of products. Quality meets
|
|
27
|
-
affordability.
|
|
28
|
-
</p>
|
|
29
|
-
<div className="flex justify-center gap-4">
|
|
30
|
-
<Link
|
|
31
|
-
href="/products"
|
|
32
|
-
className="rounded-lg bg-primary px-6 py-3 font-medium text-white transition hover:bg-primary/90"
|
|
33
|
-
>
|
|
34
|
-
Shop Now
|
|
35
|
-
</Link>
|
|
36
|
-
<Link
|
|
37
|
-
href="/collections"
|
|
38
|
-
className="rounded-lg border border-gray-300 px-6 py-3 font-medium text-gray-700 transition hover:bg-gray-50"
|
|
39
|
-
>
|
|
40
|
-
Browse Collections
|
|
41
|
-
</Link>
|
|
42
|
-
</div>
|
|
43
|
-
</section>
|
|
44
|
-
|
|
45
|
-
{/* Featured Products Section */}
|
|
46
|
-
<section className="mb-16">
|
|
47
|
-
<h2 className="mb-8 text-2xl font-bold text-gray-900">
|
|
48
|
-
Featured Products
|
|
49
|
-
</h2>
|
|
50
|
-
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4">
|
|
51
|
-
{products.length === 0 ? (
|
|
52
|
-
<div className="col-span-full rounded-lg border border-gray-200 p-8 text-center">
|
|
53
|
-
<div className="mb-4 h-48 rounded-lg bg-gray-100" />
|
|
54
|
-
<p className="text-gray-500">No products available</p>
|
|
55
|
-
<p className="mt-2 text-sm text-gray-400">
|
|
56
|
-
{process.env.NEXT_PUBLIC_SHOP_SLUG === "demo-shop"
|
|
57
|
-
? "Start backend to load demo products"
|
|
58
|
-
: "Connect your shop to display products"}
|
|
59
|
-
</p>
|
|
60
|
-
</div>
|
|
61
|
-
) : (
|
|
62
|
-
products.slice(0, 4).map((product) => (
|
|
63
|
-
<Link
|
|
64
|
-
key={product.id}
|
|
65
|
-
href={`/products/${product.handle}`}
|
|
66
|
-
className="group card transition hover:shadow-lg"
|
|
67
|
-
>
|
|
68
|
-
<div className="mb-4 aspect-square overflow-hidden rounded-lg bg-gray-100">
|
|
69
|
-
{product.featuredImage?.url ? (
|
|
70
|
-
<img
|
|
71
|
-
src={product.featuredImage.url}
|
|
72
|
-
alt={product.featuredImage.altText || product.title}
|
|
73
|
-
className="h-full w-full object-cover"
|
|
74
|
-
/>
|
|
75
|
-
) : (
|
|
76
|
-
<div className="flex h-full items-center justify-center text-gray-400">
|
|
77
|
-
{product.title}
|
|
78
|
-
</div>
|
|
79
|
-
)}
|
|
80
|
-
</div>
|
|
81
|
-
<h3 className="font-medium text-gray-900 group-hover:text-primary">
|
|
82
|
-
{product.title}
|
|
83
|
-
</h3>
|
|
84
|
-
<p className="mt-1 text-sm text-gray-500 line-clamp-2">
|
|
85
|
-
{product.description || "No description"}
|
|
86
|
-
</p>
|
|
87
|
-
<p className="mt-2 font-semibold text-primary">
|
|
88
|
-
{product.priceRange?.minVariantPrice?.amount}{" "}
|
|
89
|
-
{product.priceRange?.minVariantPrice?.currencyCode}
|
|
90
|
-
</p>
|
|
91
|
-
</Link>
|
|
92
|
-
))
|
|
93
|
-
)}
|
|
94
|
-
</div>
|
|
95
|
-
</section>
|
|
96
|
-
|
|
97
|
-
{/* Categories Section */}
|
|
98
|
-
<section>
|
|
99
|
-
<h2 className="mb-8 text-2xl font-bold text-gray-900">
|
|
100
|
-
Shop by Category
|
|
101
|
-
</h2>
|
|
102
|
-
<div className="grid grid-cols-2 gap-4 md:grid-cols-4">
|
|
103
|
-
{categoryRoots.length > 0
|
|
104
|
-
? categoryRoots.map((category) => (
|
|
105
|
-
<Link
|
|
106
|
-
key={category.id}
|
|
107
|
-
href={`/categories/${category.slug}`}
|
|
108
|
-
className="group rounded-lg border border-gray-200 p-6 text-center transition hover:border-primary hover:shadow-md"
|
|
109
|
-
>
|
|
110
|
-
<div className="mb-2 text-3xl">🛍️</div>
|
|
111
|
-
<span className="font-medium text-gray-700 group-hover:text-primary">
|
|
112
|
-
{category.name}
|
|
113
|
-
</span>
|
|
114
|
-
</Link>
|
|
115
|
-
))
|
|
116
|
-
: ["Electronics", "Clothing", "Home & Garden", "Sports"].map(
|
|
117
|
-
(category) => (
|
|
118
|
-
<Link
|
|
119
|
-
key={category}
|
|
120
|
-
href={`/collections/${category
|
|
121
|
-
.toLowerCase()
|
|
122
|
-
.replace(/ & /g, "-")}`}
|
|
123
|
-
className="group rounded-lg border border-gray-200 p-6 text-center transition hover:border-primary hover:shadow-md"
|
|
124
|
-
>
|
|
125
|
-
<div className="mb-2 text-3xl">🛍️</div>
|
|
126
|
-
<span className="font-medium text-gray-700 group-hover:text-primary">
|
|
127
|
-
{category}
|
|
128
|
-
</span>
|
|
129
|
-
</Link>
|
|
130
|
-
)
|
|
131
|
-
)}
|
|
132
|
-
</div>
|
|
133
|
-
</section>
|
|
134
|
-
</div>
|
|
135
|
-
);
|
|
136
|
-
}
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import Link from "next/link";
|
|
2
|
-
import { notFound } from "next/navigation";
|
|
3
|
-
import { useProduct } from "@doswiftly/storefront-sdk/graphql/server";
|
|
4
|
-
import { ProductActions } from "@/components/commerce/product-actions";
|
|
5
|
-
|
|
6
|
-
interface ProductPageProps {
|
|
7
|
-
params: Promise<{ slug: string }>;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
interface ProductImage {
|
|
11
|
-
id?: string | null;
|
|
12
|
-
url: string;
|
|
13
|
-
altText?: string | null;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export default async function ProductPage({ params }: ProductPageProps) {
|
|
17
|
-
const { slug } = await params;
|
|
18
|
-
|
|
19
|
-
// Fetch product - normalized (direct object)
|
|
20
|
-
const { product } = await useProduct({ handle: slug });
|
|
21
|
-
|
|
22
|
-
if (!product) {
|
|
23
|
-
notFound();
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
<div className="container mx-auto px-4 py-8">
|
|
28
|
-
{/* Breadcrumb */}
|
|
29
|
-
<nav className="mb-8 text-sm text-gray-500">
|
|
30
|
-
<Link href="/" className="hover:text-primary">
|
|
31
|
-
Home
|
|
32
|
-
</Link>
|
|
33
|
-
<span className="mx-2">/</span>
|
|
34
|
-
<Link href="/products" className="hover:text-primary">
|
|
35
|
-
Products
|
|
36
|
-
</Link>
|
|
37
|
-
<span className="mx-2">/</span>
|
|
38
|
-
<span className="text-gray-900">{product.title}</span>
|
|
39
|
-
</nav>
|
|
40
|
-
|
|
41
|
-
<div className="grid gap-12 lg:grid-cols-2">
|
|
42
|
-
{/* Product Images */}
|
|
43
|
-
<div className="space-y-4">
|
|
44
|
-
<div className="aspect-square overflow-hidden rounded-lg bg-gray-100">
|
|
45
|
-
{product.featuredImage?.url ? (
|
|
46
|
-
<img
|
|
47
|
-
src={product.featuredImage.url}
|
|
48
|
-
alt={product.featuredImage.altText || product.title}
|
|
49
|
-
className="h-full w-full object-cover"
|
|
50
|
-
/>
|
|
51
|
-
) : (
|
|
52
|
-
<div className="flex h-full items-center justify-center text-gray-400">
|
|
53
|
-
Product Image
|
|
54
|
-
</div>
|
|
55
|
-
)}
|
|
56
|
-
</div>
|
|
57
|
-
<div className="grid grid-cols-4 gap-4">
|
|
58
|
-
{product.images
|
|
59
|
-
?.slice(0, 4)
|
|
60
|
-
.map((image: ProductImage, i: number) => (
|
|
61
|
-
<button
|
|
62
|
-
key={image.id || i}
|
|
63
|
-
className="aspect-square overflow-hidden rounded-lg bg-gray-100"
|
|
64
|
-
>
|
|
65
|
-
<img
|
|
66
|
-
src={image.url}
|
|
67
|
-
alt={image.altText || `Image ${i + 1}`}
|
|
68
|
-
className="h-full w-full object-cover"
|
|
69
|
-
/>
|
|
70
|
-
</button>
|
|
71
|
-
))}
|
|
72
|
-
</div>
|
|
73
|
-
</div>
|
|
74
|
-
|
|
75
|
-
{/* Product Info */}
|
|
76
|
-
<div>
|
|
77
|
-
<h1 className="text-3xl font-bold text-gray-900">{product.title}</h1>
|
|
78
|
-
|
|
79
|
-
<p className="mt-6 text-gray-600">
|
|
80
|
-
{product.description || "No description available"}
|
|
81
|
-
</p>
|
|
82
|
-
|
|
83
|
-
{/* Variant Selection, Quantity & Add to Cart */}
|
|
84
|
-
<div className="mt-8">
|
|
85
|
-
<ProductActions
|
|
86
|
-
variants={product.variants || []}
|
|
87
|
-
productId={product.id}
|
|
88
|
-
/>
|
|
89
|
-
</div>
|
|
90
|
-
|
|
91
|
-
{/* Product Details */}
|
|
92
|
-
<div className="mt-12 border-t border-gray-200 pt-8">
|
|
93
|
-
<h2 className="text-lg font-semibold text-gray-900">
|
|
94
|
-
Product Details
|
|
95
|
-
</h2>
|
|
96
|
-
<dl className="mt-4 space-y-4 text-sm">
|
|
97
|
-
{product.vendor && (
|
|
98
|
-
<div className="flex justify-between">
|
|
99
|
-
<dt className="text-gray-500">Vendor</dt>
|
|
100
|
-
<dd className="text-gray-900">{product.vendor}</dd>
|
|
101
|
-
</div>
|
|
102
|
-
)}
|
|
103
|
-
{product.productType && (
|
|
104
|
-
<div className="flex justify-between">
|
|
105
|
-
<dt className="text-gray-500">Type</dt>
|
|
106
|
-
<dd className="text-gray-900">{product.productType}</dd>
|
|
107
|
-
</div>
|
|
108
|
-
)}
|
|
109
|
-
<div className="flex justify-between">
|
|
110
|
-
<dt className="text-gray-500">Handle</dt>
|
|
111
|
-
<dd className="text-gray-900">{product.handle}</dd>
|
|
112
|
-
</div>
|
|
113
|
-
</dl>
|
|
114
|
-
</div>
|
|
115
|
-
</div>
|
|
116
|
-
</div>
|
|
117
|
-
</div>
|
|
118
|
-
);
|
|
119
|
-
}
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import Link from "next/link";
|
|
2
|
-
import {
|
|
3
|
-
useProducts,
|
|
4
|
-
useCategories,
|
|
5
|
-
} from "@doswiftly/storefront-sdk/graphql/server";
|
|
6
|
-
import { ProductSortKeys } from "@doswiftly/storefront-sdk/graphql";
|
|
7
|
-
import { ProductFilters } from "@/components/commerce/product-filters";
|
|
8
|
-
import { Pagination } from "@/components/commerce/pagination";
|
|
9
|
-
|
|
10
|
-
interface ProductsPageProps {
|
|
11
|
-
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export default async function ProductsPage({
|
|
15
|
-
searchParams,
|
|
16
|
-
}: ProductsPageProps) {
|
|
17
|
-
// Parse search params (Next.js 15 requires await)
|
|
18
|
-
const params = await searchParams;
|
|
19
|
-
const cursor = params.after as string | undefined;
|
|
20
|
-
const limit = 12;
|
|
21
|
-
const sortKey =
|
|
22
|
-
(params.sort as keyof typeof ProductSortKeys) || ProductSortKeys.CreatedAt;
|
|
23
|
-
const query = params.q as string | undefined;
|
|
24
|
-
const category = params.category as string | undefined;
|
|
25
|
-
|
|
26
|
-
// Build query string for category filtering
|
|
27
|
-
const searchQuery = category ? `category:${category}` : query;
|
|
28
|
-
|
|
29
|
-
// Fetch products
|
|
30
|
-
const { products, pageInfo } = await useProducts({
|
|
31
|
-
first: limit,
|
|
32
|
-
after: cursor || undefined,
|
|
33
|
-
query: searchQuery || undefined,
|
|
34
|
-
sortKey: ProductSortKeys[sortKey as keyof typeof ProductSortKeys],
|
|
35
|
-
reverse: params.order === "desc",
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
// Fetch categories (roots are the top-level categories)
|
|
39
|
-
const { categories } = await useCategories();
|
|
40
|
-
|
|
41
|
-
// Calculate total (approximation since GraphQL uses cursor pagination)
|
|
42
|
-
const hasMore = pageInfo?.hasNextPage ?? false;
|
|
43
|
-
|
|
44
|
-
return (
|
|
45
|
-
<div className="container mx-auto px-4 py-8">
|
|
46
|
-
{/* Page Header */}
|
|
47
|
-
<div className="mb-8">
|
|
48
|
-
<h1 className="text-3xl font-bold text-gray-900">All Products</h1>
|
|
49
|
-
<p className="mt-2 text-gray-600">
|
|
50
|
-
Browse our complete collection of products
|
|
51
|
-
</p>
|
|
52
|
-
</div>
|
|
53
|
-
|
|
54
|
-
{/* Filters & Sort */}
|
|
55
|
-
<ProductFilters categories={categories} />
|
|
56
|
-
|
|
57
|
-
{/* Products Grid */}
|
|
58
|
-
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
|
59
|
-
{products.length === 0 ? (
|
|
60
|
-
<div className="col-span-full py-12 text-center text-gray-500">
|
|
61
|
-
No products found
|
|
62
|
-
</div>
|
|
63
|
-
) : (
|
|
64
|
-
products.map((product) => (
|
|
65
|
-
<Link
|
|
66
|
-
key={product.id}
|
|
67
|
-
href={`/products/${product.handle}`}
|
|
68
|
-
className="group card transition hover:shadow-lg"
|
|
69
|
-
>
|
|
70
|
-
<div className="mb-4 aspect-square overflow-hidden rounded-lg bg-gray-100">
|
|
71
|
-
{product.featuredImage?.url ? (
|
|
72
|
-
<img
|
|
73
|
-
src={product.featuredImage.url}
|
|
74
|
-
alt={product.featuredImage.altText || product.title}
|
|
75
|
-
className="h-full w-full object-cover"
|
|
76
|
-
/>
|
|
77
|
-
) : (
|
|
78
|
-
<div className="flex h-full items-center justify-center text-gray-400">
|
|
79
|
-
{product.title}
|
|
80
|
-
</div>
|
|
81
|
-
)}
|
|
82
|
-
</div>
|
|
83
|
-
<h3 className="font-medium text-gray-900 group-hover:text-primary">
|
|
84
|
-
{product.title}
|
|
85
|
-
</h3>
|
|
86
|
-
<p className="mt-1 line-clamp-2 text-sm text-gray-500">
|
|
87
|
-
{product.description || "No description"}
|
|
88
|
-
</p>
|
|
89
|
-
<p className="mt-2 font-semibold text-primary">
|
|
90
|
-
{product.priceRange?.minVariantPrice?.amount}{" "}
|
|
91
|
-
{product.priceRange?.minVariantPrice?.currencyCode}
|
|
92
|
-
</p>
|
|
93
|
-
</Link>
|
|
94
|
-
))
|
|
95
|
-
)}
|
|
96
|
-
</div>
|
|
97
|
-
|
|
98
|
-
{/* Pagination */}
|
|
99
|
-
<Pagination
|
|
100
|
-
hasMore={hasMore}
|
|
101
|
-
endCursor={pageInfo?.endCursor}
|
|
102
|
-
currentCursor={cursor}
|
|
103
|
-
totalShown={products.length}
|
|
104
|
-
/>
|
|
105
|
-
</div>
|
|
106
|
-
);
|
|
107
|
-
}
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import Link from "next/link";
|
|
2
|
-
import { useProducts } from "@doswiftly/storefront-sdk/graphql/server";
|
|
3
|
-
import { SearchInput } from "@/components/commerce/search-input";
|
|
4
|
-
import { Pagination } from "@/components/commerce/pagination";
|
|
5
|
-
|
|
6
|
-
interface SearchPageProps {
|
|
7
|
-
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export default async function SearchPage({ searchParams }: SearchPageProps) {
|
|
11
|
-
const params = await searchParams;
|
|
12
|
-
const query = (params.q as string) || "";
|
|
13
|
-
const cursor = params.after as string | undefined;
|
|
14
|
-
const limit = 12;
|
|
15
|
-
|
|
16
|
-
// Fetch search results
|
|
17
|
-
const result = query
|
|
18
|
-
? await useProducts({
|
|
19
|
-
first: limit,
|
|
20
|
-
after: cursor || undefined,
|
|
21
|
-
query: query,
|
|
22
|
-
})
|
|
23
|
-
: null;
|
|
24
|
-
const products = result?.products ?? [];
|
|
25
|
-
const pageInfo = result?.pageInfo ?? null;
|
|
26
|
-
|
|
27
|
-
const hasMore = pageInfo?.hasNextPage ?? false;
|
|
28
|
-
|
|
29
|
-
return (
|
|
30
|
-
<div className="container mx-auto px-4 py-8">
|
|
31
|
-
{/* Search Header */}
|
|
32
|
-
<div className="mb-8">
|
|
33
|
-
<h1 className="mb-4 text-3xl font-bold text-gray-900">Search</h1>
|
|
34
|
-
<div className="max-w-xl">
|
|
35
|
-
<SearchInput placeholder="Search products..." />
|
|
36
|
-
</div>
|
|
37
|
-
</div>
|
|
38
|
-
|
|
39
|
-
{/* Results */}
|
|
40
|
-
{query ? (
|
|
41
|
-
<>
|
|
42
|
-
<p className="mb-6 text-gray-600">
|
|
43
|
-
{products.length > 0 ? (
|
|
44
|
-
<>
|
|
45
|
-
Showing results for "<strong>{query}</strong>"
|
|
46
|
-
</>
|
|
47
|
-
) : (
|
|
48
|
-
<>
|
|
49
|
-
No results found for "<strong>{query}</strong>"
|
|
50
|
-
</>
|
|
51
|
-
)}
|
|
52
|
-
</p>
|
|
53
|
-
|
|
54
|
-
{products.length > 0 ? (
|
|
55
|
-
<>
|
|
56
|
-
{/* Products Grid */}
|
|
57
|
-
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
|
58
|
-
{products.map((product) => (
|
|
59
|
-
<Link
|
|
60
|
-
key={product.id}
|
|
61
|
-
href={`/products/${product.handle}`}
|
|
62
|
-
className="group card transition hover:shadow-lg"
|
|
63
|
-
>
|
|
64
|
-
<div className="mb-4 aspect-square overflow-hidden rounded-lg bg-gray-100">
|
|
65
|
-
{product.featuredImage?.url ? (
|
|
66
|
-
<img
|
|
67
|
-
src={product.featuredImage.url}
|
|
68
|
-
alt={product.featuredImage.altText || product.title}
|
|
69
|
-
className="h-full w-full object-cover"
|
|
70
|
-
/>
|
|
71
|
-
) : (
|
|
72
|
-
<div className="flex h-full items-center justify-center text-gray-400">
|
|
73
|
-
{product.title}
|
|
74
|
-
</div>
|
|
75
|
-
)}
|
|
76
|
-
</div>
|
|
77
|
-
<h3 className="font-medium text-gray-900 group-hover:text-primary">
|
|
78
|
-
{product.title}
|
|
79
|
-
</h3>
|
|
80
|
-
<p className="mt-1 line-clamp-2 text-sm text-gray-500">
|
|
81
|
-
{product.description || "No description"}
|
|
82
|
-
</p>
|
|
83
|
-
<p className="mt-2 font-semibold text-primary">
|
|
84
|
-
{product.priceRange?.minVariantPrice?.amount}{" "}
|
|
85
|
-
{product.priceRange?.minVariantPrice?.currencyCode}
|
|
86
|
-
</p>
|
|
87
|
-
</Link>
|
|
88
|
-
))}
|
|
89
|
-
</div>
|
|
90
|
-
|
|
91
|
-
{/* Pagination */}
|
|
92
|
-
<Pagination
|
|
93
|
-
hasMore={hasMore}
|
|
94
|
-
endCursor={pageInfo?.endCursor}
|
|
95
|
-
currentCursor={cursor}
|
|
96
|
-
totalShown={products.length}
|
|
97
|
-
/>
|
|
98
|
-
</>
|
|
99
|
-
) : (
|
|
100
|
-
<div className="py-16 text-center">
|
|
101
|
-
<div className="mb-4 text-6xl">🔍</div>
|
|
102
|
-
<h2 className="mb-2 text-xl font-semibold text-gray-900">
|
|
103
|
-
No products found
|
|
104
|
-
</h2>
|
|
105
|
-
<p className="mb-8 text-gray-600">
|
|
106
|
-
Try adjusting your search terms or browse our products.
|
|
107
|
-
</p>
|
|
108
|
-
<Link href="/products" className="btn btn-primary">
|
|
109
|
-
Browse Products
|
|
110
|
-
</Link>
|
|
111
|
-
</div>
|
|
112
|
-
)}
|
|
113
|
-
</>
|
|
114
|
-
) : (
|
|
115
|
-
<div className="py-16 text-center">
|
|
116
|
-
<div className="mb-4 text-6xl">🔎</div>
|
|
117
|
-
<h2 className="mb-2 text-xl font-semibold text-gray-900">
|
|
118
|
-
Search for products
|
|
119
|
-
</h2>
|
|
120
|
-
<p className="text-gray-600">
|
|
121
|
-
Enter a search term above to find products.
|
|
122
|
-
</p>
|
|
123
|
-
</div>
|
|
124
|
-
)}
|
|
125
|
-
</div>
|
|
126
|
-
);
|
|
127
|
-
}
|