Initial commit: Shopify storefront Next.js template

Next.js 16 + React 19 storefront template with Shopify Storefront API
integration, Tailwind v4, and shadcn/ui components.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0111qSr3KopyGRsJ6LznR1xZ
This commit is contained in:
Rami Bitar
2026-07-31 22:12:19 -04:00
co-authored by Claude Opus 5
commit e3d5e75299
69 changed files with 7960 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
import Header from '@/components/shopify/header';
import Footer from '@/components/shopify/footer';
import CollectionDetail from '@/components/shopify/collection-detail';
export default function Page() {
return (
<>
<Header
storeName="Stride"
logoUrl=""
links={[
{ label: 'Products', url: '/' },
{ label: 'Collections', url: '/collections' },
]}
/>
<CollectionDetail />
<Footer
storeName="Stride"
logoUrl=""
tagline="Your premium shopping destination"
copyright="© 2026 Stride. All rights reserved."
links={[]}
/>
</>
);
}
+26
View File
@@ -0,0 +1,26 @@
import Header from '@/components/shopify/header';
import Footer from '@/components/shopify/footer';
import Collections from '@/components/shopify/collections';
export default function Page() {
return (
<>
<Header
storeName="Stride"
logoUrl=""
links={[
{ label: 'Products', url: '/' },
{ label: 'Collections', url: '/collections' },
]}
/>
<Collections title="Our Collections" />
<Footer
storeName="Stride"
logoUrl=""
tagline="Your premium shopping destination"
copyright="© 2026 Stride. All rights reserved."
links={[]}
/>
</>
);
}
+96
View File
@@ -0,0 +1,96 @@
import { cn } from "@/lib/utils";
import type { HTMLAttributes } from "react";
interface LoaderIconProps {
size?: number;
}
const LoaderIcon = ({ size = 16 }: LoaderIconProps) => (
<svg
height={size}
strokeLinejoin="round"
style={{ color: "currentcolor" }}
viewBox="0 0 16 16"
width={size}
>
<title>Loader</title>
<g clipPath="url(#clip0_2393_1490)">
<path d="M8 0V4" stroke="currentColor" strokeWidth="1.5" />
<path
d="M8 16V12"
opacity="0.5"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
d="M3.29773 1.52783L5.64887 4.7639"
opacity="0.9"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
d="M12.7023 1.52783L10.3511 4.7639"
opacity="0.1"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
d="M12.7023 14.472L10.3511 11.236"
opacity="0.4"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
d="M3.29773 14.472L5.64887 11.236"
opacity="0.6"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
d="M15.6085 5.52783L11.8043 6.7639"
opacity="0.2"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
d="M0.391602 10.472L4.19583 9.23598"
opacity="0.7"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
d="M15.6085 10.4722L11.8043 9.2361"
opacity="0.3"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
d="M0.391602 5.52783L4.19583 6.7639"
opacity="0.8"
stroke="currentColor"
strokeWidth="1.5"
/>
</g>
<defs>
<clipPath id="clip0_2393_1490">
<rect fill="white" height="16" width="16" />
</clipPath>
</defs>
</svg>
);
export type LoaderProps = HTMLAttributes<HTMLDivElement> & {
size?: number;
};
export const Loader = ({ className, size = 16, ...props }: LoaderProps) => (
<div
className={cn(
"inline-flex animate-spin items-center justify-center",
className
)}
{...props}
>
<LoaderIcon size={size} />
</div>
);
+25
View File
@@ -0,0 +1,25 @@
"use client";
import React from 'react';
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="flex items-center gap-6">
<div className="text-lg font-medium text-black font-sans whitespace-nowrap">
Something went wrong
</div>
<div className="border-l border-gray-300 h-6"></div>
<div className="text-sm text-gray-700 font-sans max-w-lg">
{error.message}
</div>
</div>
</div>
);
}
+112
View File
@@ -0,0 +1,112 @@
@import 'tailwindcss';
@theme {
/* Refined neutral modern palette */
--color-background: hsl(0 0% 100%);
--color-foreground: hsl(240 10% 3.9%);
/* Card */
--color-card: hsl(0 0% 100%);
--color-card-foreground: hsl(240 10% 3.9%);
/* Popover */
--color-popover: hsl(0 0% 100%);
--color-popover-foreground: hsl(240 10% 3.9%);
/* Primary - Deep neutral slate for professional look */
--color-primary: hsl(240 5.9% 10%);
--color-primary-foreground: hsl(0 0% 98%);
/* Secondary */
--color-secondary: hsl(240 4.8% 95.9%);
--color-secondary-foreground: hsl(240 5.9% 10%);
/* Muted */
--color-muted: hsl(240 4.8% 95.9%);
--color-muted-foreground: hsl(240 3.8% 46.1%);
/* Accent - Subtle warm gray */
--color-accent: hsl(30 6.7% 95%);
--color-accent-foreground: hsl(240 5.9% 10%);
/* Destructive */
--color-destructive: hsl(0 84.2% 60.2%);
--color-destructive-foreground: hsl(0 0% 98%);
/* Border */
--color-border: hsl(240 5.9% 90%);
--color-input: hsl(240 5.9% 90%);
--color-ring: hsl(240 5.9% 10%);
/* Radius - Modern larger corners */
--radius-sm: 0.5rem;
--radius-md: 0.75rem;
--radius-lg: 1rem;
--radius-xl: 1.25rem;
/* Enhanced typography scale */
--font-heading: 'Space Grotesk', system-ui, sans-serif;
--font-body: 'Inter', system-ui, sans-serif;
--font-poppins: 'Poppins', system-ui, sans-serif;
}
/* Base styles for light, modern Shopify storefront */
body {
font-feature-settings:
'kern' 1,
'tnum' 1;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* Modern card design for products and collections */
.card-modern {
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
border: 1px solid var(--color-border);
}
.card-modern:hover {
transform: translateY(-8px);
box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.1);
border-color: hsl(240 5.9% 85%);
}
/* Bold heading styles */
h1,
h2,
.font-heading {
font-weight: 700;
letter-spacing: -0.025em;
}
/* Enhanced button styles */
button,
.btn-modern {
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
.btn-modern:hover {
transform: translateY(-1px);
box-shadow:
0 10px 15px -3px rgb(0 0 0 / 0.1),
0 4px 6px -4px rgb(0 0 0 / 0.1);
}
/* Hero section styles */
.hero-bg {
background: linear-gradient(135deg, #f8f9fa 0%, #f1f3f5 100%);
}
/* Product gallery improvements */
.product-gallery-main {
transition: box-shadow 0.3s ease;
}
.product-gallery-main:hover {
box-shadow: 0 0 0 1px hsl(240 5.9% 80%) inset;
}
/* Clean price display */
.price-display {
font-feature-settings: 'tnum';
}
+40
View File
@@ -0,0 +1,40 @@
'use client';
import React from 'react';
import './globals.css';
import { Space_Grotesk, Inter, Poppins } from 'next/font/google';
const poppins = Poppins({
subsets: ['latin'],
weight: ['400', '500', '600', '700'],
variable: '--font-poppins',
});
const spaceGrotesk = Space_Grotesk({
subsets: ['latin'],
weight: ['400', '500', '600', '700'],
variable: '--font-heading',
});
const inter = Inter({
subsets: ['latin'],
weight: ['400', '500', '600'],
variable: '--font-body',
});
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html
lang="en"
className={`${poppins.variable} ${spaceGrotesk.variable} ${inter.variable}`}
>
<body className="font-body antialiased bg-background text-foreground m-0 p-0">
{children}
</body>
</html>
);
}
+20
View File
@@ -0,0 +1,20 @@
"use client";
import React from 'react';
import Link from 'next/link';
export default function NotFound() {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="flex items-center gap-6">
<div className="text-lg font-medium text-black font-sans whitespace-nowrap">
404
</div>
<div className="border-l border-gray-300 h-6"></div>
<div className="text-sm text-gray-700 font-sans max-w-lg">
This page could not be found.
</div>
</div>
</div>
);
}
+26
View File
@@ -0,0 +1,26 @@
import Header from '@/components/shopify/header';
import Footer from '@/components/shopify/footer';
import Products from '@/components/shopify/products';
export default function Page() {
return (
<>
<Header
storeName="Stride"
logoUrl=""
links={[
{ label: 'Products', url: '/' },
{ label: 'Collections', url: '/collections' },
]}
/>
<Products title="Shop All" />
<Footer
storeName="Stride"
logoUrl=""
tagline="Your premium shopping destination"
copyright="© 2026 Stride. All rights reserved."
links={[]}
/>
</>
);
}
+35
View File
@@ -0,0 +1,35 @@
import Header from '@/components/shopify/header';
import Footer from '@/components/shopify/footer';
import ProductDetail from '@/components/shopify/product-detail';
import ProductRecommendations from '@/components/shopify/product-recommendations';
export default function Page() {
return (
<>
<Header
storeName="Stride"
logoUrl=""
links={[
{ label: 'Products', url: '/' },
{ label: 'Collections', url: '/collections' },
]}
/>
<ProductDetail
addToCartLabel="Add to Cart"
features={[
{ icon: 'RiTruckLine', label: 'Free shipping on orders over $100' },
{ icon: 'RiArrowGoBackLine', label: '30-day return policy' },
{ icon: 'RiSecurePaymentLine', label: 'Secure payment' },
]}
/>
<ProductRecommendations title="You Might Also Like" />
<Footer
storeName="Stride"
logoUrl=""
tagline="Your premium shopping destination"
copyright="© 2026 Stride. All rights reserved."
links={[]}
/>
</>
);
}
+26
View File
@@ -0,0 +1,26 @@
import Header from '@/components/shopify/header';
import Footer from '@/components/shopify/footer';
import Collections from '@/components/shopify/collections';
export default function Page() {
return (
<>
<Header
storeName="Stride"
logoUrl=""
links={[
{ label: 'Products', url: '/' },
{ label: 'Collections', url: '/shop/collections' },
]}
/>
<Collections title="Our Collections" />
<Footer
storeName="Stride"
logoUrl=""
tagline="Your premium shopping destination"
copyright="© 2026 Stride. All rights reserved."
links={[]}
/>
</>
);
}