diff --git a/.env.example b/.env.example
index f7fa990d4..89939b046 100644
--- a/.env.example
+++ b/.env.example
@@ -1,5 +1,6 @@
COMPANY_NAME="Vercel Inc."
-SITE_NAME="Next.js Commerce"
-SHOPIFY_REVALIDATION_SECRET=""
-SHOPIFY_STOREFRONT_ACCESS_TOKEN=""
-SHOPIFY_STORE_DOMAIN="[your-shopify-store-subdomain].myshopify.com"
+COMMERCE_PROVIDER=crystallize
+NEXT_PUBLIC_COMMERCE_PROVIDER=crystallize
+CRYSTALLIZE_API_URL=https://api.crystallize.com/6422a2c186ef95b31e1cd1e5/graphql
+CRYSTALLIZE_ACCESS_TOKEN=c3ab0fef20aadcbeb9919e6701f015cfb4ddf1ff
+NEXT_PUBLIC_USE_DUMMY_DATA=true
diff --git a/.env.production b/.env.production
new file mode 100644
index 000000000..afedb41e7
--- /dev/null
+++ b/.env.production
@@ -0,0 +1,9 @@
+COMMERCE_PROVIDER=shopify_local
+NEXT_PUBLIC_COMMERCE_PROVIDER=shopify_local
+
+SHOPIFY_STORE_DOMAIN=dummy.myshopify.com
+SHOPIFY_STOREFRONT_API_TOKEN=dummy
+SHOPIFY_STOREFRONT_API_VERSION=2023-01
+
+SHOPIFY_HEADER_MENU=next-js-frontend-header-menu
+SHOPIFY_FOOTER_MENU=next-js-frontend-footer-menu
diff --git a/.graphqlrc.yml b/.graphqlrc.yml
new file mode 100644
index 000000000..60997be82
--- /dev/null
+++ b/.graphqlrc.yml
@@ -0,0 +1,8 @@
+schema:
+ - https://api.crystallize.com/bykirken/graphql
+extensions:
+ endpoints:
+ default:
+ url: https://api.crystallize.com/bykirken/graphql
+ headers:
+ Authorization: Basic c3ab0fef20aadcbeb9919e6701f015cfb4ddf1ff
diff --git a/app/_not-found/page.tsx b/app/_not-found/page.tsx
new file mode 100644
index 000000000..9799278e6
--- /dev/null
+++ b/app/_not-found/page.tsx
@@ -0,0 +1,9 @@
+// app/_not-found/page.tsx
+export default function StubNotFound() {
+ return (
+
+
404
+
Page not found.
+
+ )
+}
diff --git a/app/cart-checkout/page.tsx b/app/cart-checkout/page.tsx
new file mode 100644
index 000000000..f0775904e
--- /dev/null
+++ b/app/cart-checkout/page.tsx
@@ -0,0 +1,151 @@
+// app/cart-checkout/page.tsx
+'use client'; // For useState if we were to make checkbox interactive
+
+import { useState } from 'react';
+
+export default function CartCheckoutPage() {
+ const [billingSameAsShipping, setBillingSameAsShipping] = useState(true);
+
+ // Dummy cart items
+ const cartItems = [
+ { id: 'p1', name: 'Awesome T-Shirt (Red, L)', quantity: 1, price: 29.99 },
+ { id: 'p2', name: 'Cool Cap - Black', quantity: 2, price: 15.00 },
+ { id: 'p3', name: 'Generic Gadget XL', quantity: 1, price: 199.50 },
+ ];
+
+ const cartSubtotal = cartItems.reduce((sum, item) => sum + item.price * item.quantity, 0);
+ const shippingEstimate = cartItems.length > 0 ? 5.00 : 0; // No shipping if cart is empty
+ const grandTotal = cartSubtotal + shippingEstimate;
+
+ // Inline styles
+ const pageStyle = { padding: '20px', fontFamily: 'Arial, sans-serif', maxWidth: '1000px', margin: '20px auto' };
+ const sectionStyle = { marginBottom: '40px', paddingBottom: '20px', borderBottom: '1px solid #eee' };
+ const headingStyle = { color: '#333', marginBottom: '20px', borderBottom: '1px solid #ddd', paddingBottom: '10px' };
+ const subHeadingStyle = { color: '#444', marginBottom: '15px' };
+ const inputStyle = { width: 'calc(100% - 22px)', padding: '10px', marginBottom: '10px', border: '1px solid #ccc', borderRadius: '4px', boxSizing: 'border-box' as const };
+ const buttonStyle = { padding: '12px 20px', backgroundColor: '#007bff', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer', fontSize: '1em' };
+ const smallButtonStyle = { padding: '5px 8px', margin: '0 5px', cursor: 'pointer' };
+ const cartItemStyle = { borderBottom: '1px solid #eee', padding: '15px 0', display: 'flex', justifyContent: 'space-between', alignItems: 'center' };
+ const formGroupStyle = { marginBottom: '15px' };
+ const labelStyle = { display: 'block', marginBottom: '5px', fontWeight: 'bold' as const };
+
+
+ return (
+
+
Shopping Cart & Checkout
+
+ {/* Cart Items Section */}
+
+ Your Cart
+ {cartItems.length > 0 ? (
+ <>
+ {cartItems.map(item => (
+
+
+
{item.name}
+
Price: ${item.price.toFixed(2)}
+
+ Quantity:
+ - {item.quantity} +
+
+
+
+
Total: ${(item.price * item.quantity).toFixed(2)}
+
Remove
+
+
+ ))}
+
+
Subtotal: ${cartSubtotal.toFixed(2)}
+
Shipping Estimate: ${shippingEstimate.toFixed(2)}
+
Grand Total: ${grandTotal.toFixed(2)}
+
+ >
+ ) : (
+ Your cart is currently empty.
+ )}
+
+
+ {/* Checkout Form Section */}
+ {cartItems.length > 0 && ( // Only show checkout if cart is not empty
+
+ )}
+
+ {/* Request a Quote Section */}
+
+ Need a Custom Quote?
+ For bulk orders or special requirements, please request a quote.
+ alert('Redirecting to quote request page... (placeholder)')} // Placeholder action
+ onMouseOver={(e) => (e.currentTarget.style.backgroundColor = '#138496')}
+ onMouseOut={(e) => (e.currentTarget.style.backgroundColor = '#17a2b8')}
+ >
+ Request a Quote
+
+
+
+ );
+}
diff --git a/app/content/[slug]/page.tsx b/app/content/[slug]/page.tsx
new file mode 100644
index 000000000..638d1a451
--- /dev/null
+++ b/app/content/[slug]/page.tsx
@@ -0,0 +1,65 @@
+// app/content/[slug]/page.tsx
+
+// Simulate fetching content (replace with actual CMS fetching later)
+async function getContent(slug: string) {
+ // In a real app, you'd fetch this from a CMS
+ const allContent: { [key: string]: { title: string; body: string[] } } = {
+ 'about-us': {
+ title: 'About Us',
+ body: [
+ 'This is the about us page.',
+ 'We are a company that does things.'
+ ]
+ },
+ 'contact-us': {
+ title: 'Contact Us',
+ body: [
+ 'You can contact us via email or phone.',
+ 'Email: contact@example.com',
+ 'Phone: 123-456-7890'
+ ]
+ },
+ 'privacy-policy': {
+ title: 'Privacy Policy',
+ body: [
+ 'This is our privacy policy.',
+ 'We respect your privacy and are committed to protecting your personal data.'
+ ]
+ }
+ };
+ // Ensure slug is a string before using it as an index
+ return allContent[String(slug)] || null;
+}
+
+// Define an interface for the page's props, with params and searchParams as Promises
+interface ContentPageProps {
+ params: Promise<{ slug: string }>;
+ searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
+}
+
+export default async function ContentPage({ params, searchParams }: ContentPageProps) {
+ // Await the params promise to get its value
+ const resolvedParams = await params;
+ // Await searchParams if you need to use them, e.g.:
+ // const resolvedSearchParams = await searchParams;
+
+ const content = await getContent(resolvedParams.slug);
+
+ if (!content) {
+ return Content not found for {resolvedParams.slug}
;
+ }
+
+ return (
+
+
{content.title}
+ {content.body.map((paragraph, index) => (
+
{paragraph}
+ ))}
+
+ );
+}
+
+// Optional: Generate static params still works the same way
+// export async function generateStaticParams() {
+// return [{ slug: 'about-us' }, { slug: 'contact-us' }, { slug: 'privacy-policy' }];
+// }
diff --git a/app/login/page.tsx b/app/login/page.tsx
new file mode 100644
index 000000000..435f3ea4a
--- /dev/null
+++ b/app/login/page.tsx
@@ -0,0 +1,58 @@
+// app/login/page.tsx
+export default function LoginPage() {
+ return (
+
+ );
+}
diff --git a/app/my-page/page.tsx b/app/my-page/page.tsx
new file mode 100644
index 000000000..e0b071573
--- /dev/null
+++ b/app/my-page/page.tsx
@@ -0,0 +1,139 @@
+// app/my-page/page.tsx
+
+export default function MyPage() {
+ // Dummy data
+ const orders = [
+ { id: '12345', date: '2023-10-26', total: '$150.00', status: 'Shipped' },
+ { id: '67890', date: '2023-11-05', total: '$75.50', status: 'Processing' },
+ { id: '10112', date: '2023-11-15', total: '$220.00', status: 'Delivered' },
+ ];
+ const quotes = [
+ { id: 'Q1001', date: '2023-10-20', total: '$500.00', status: 'Accepted' },
+ { id: 'Q1002', date: '2023-11-01', total: '$1250.75', status: 'Pending' },
+ ];
+ const downloads = [
+ { name: 'Product Manual X123.pdf', url: '#' },
+ { name: 'Software License - MyProduct v2.txt', url: '#' },
+ { name: 'Invoice_INV2023-10-26.pdf', url: '#' },
+ ];
+ const userProfile = {
+ name: 'Jane Doe',
+ email: 'jane.doe@example.com',
+ company: 'Innovate Solutions Ltd.',
+ memberSince: '2022-01-15',
+ };
+
+ const sectionStyle = {
+ marginBottom: '40px',
+ paddingBottom: '20px',
+ borderBottom: '1px solid #eee'
+ };
+
+ const headingStyle = {
+ color: '#333',
+ marginBottom: '15px'
+ };
+
+ const tableCellStyle = {
+ border: '1px solid #ddd',
+ padding: '10px',
+ textAlign: 'left' as const // Explicitly type textAlign
+ };
+
+ const buttonStyle = {
+ padding: '10px 15px',
+ backgroundColor: '#007bff',
+ color: 'white',
+ border: 'none',
+ borderRadius: '4px',
+ cursor: 'pointer',
+ fontSize: '1em'
+ };
+
+
+ return (
+
+
My Account
+
+ {/* Order History Section */}
+
+ Order History
+ {orders.length > 0 ? (
+
+
+
+ Order ID
+ Date
+ Total
+ Status
+
+
+
+ {orders.map(order => (
+
+ #{order.id}
+ {order.date}
+ {order.total}
+ {order.status}
+
+ ))}
+
+
+ ) : (
+ You have no past orders.
+ )}
+
+
+ {/* My Quotes Section */}
+
+ My Quotes
+ {quotes.length > 0 ? (
+
+ {quotes.map(quote => (
+
+ Quote #{quote.id} - Date: {quote.date} - Total: {quote.total} - Status: {quote.status}
+
+ ))}
+
+ ) : (
+ You have no active quotes.
+ )}
+
+
+ {/* My Downloads Section */}
+
+ My Downloads
+ {downloads.length > 0 ? (
+
+ {downloads.map((download, index) => ( // Added index for key as names might not be unique
+
+
+ {download.name}
+
+
+ ))}
+
+ ) : (
+ No downloads available.
+ )}
+
+
+ {/* Profile Information Section */}
+
+ My Profile
+
+
Name: {userProfile.name}
+
Email: {userProfile.email}
+
Company: {userProfile.company}
+
Member Since: {userProfile.memberSince}
+
+ (e.currentTarget.style.backgroundColor = '#0056b3')}
+ onMouseOut={(e) => (e.currentTarget.style.backgroundColor = '#007bff')}
+ >
+ Edit Profile
+
+
+
+ );
+}
diff --git a/app/not-found.tsx b/app/not-found.tsx
new file mode 100644
index 000000000..3021bd6b7
--- /dev/null
+++ b/app/not-found.tsx
@@ -0,0 +1,8 @@
+export default function NotFound() {
+ return (
+
+
404
+
This page could not be found.
+
+ );
+}
diff --git a/app/page.tsx b/app/page.tsx
index 7c4a7d74f..69aba7b80 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -10,11 +10,60 @@ export const metadata = {
}
};
+// Simulate fetching page configuration from a CMS
+const pageConfig = {
+ showFeaturedProducts: true,
+ showPromotions: true
+};
+
+// Placeholder Product Item Component
+function ProductItem({ name, price, imageUrl }: { name: string; price: string; imageUrl: string }) {
+ return (
+
+
+
{name}
+
{price}
+
+ );
+}
+
+// Placeholder Promotion Banner Component
+function PromotionBanner({ title, imageUrl }: { title: string; imageUrl: string }) {
+ return (
+
+
+
{title}
+
+ );
+}
+
export default function HomePage() {
return (
<>
+ {/* Existing components can remain if they are part of the desired layout */}
+
+ {pageConfig.showFeaturedProducts && (
+
+ )}
+
+ {pageConfig.showPromotions && (
+
+ )}
+
>
);
diff --git a/app/product/[handle]/page.tsx b/app/product/[handle]/page.tsx
index 33de3c0ba..e42eb34a7 100644
--- a/app/product/[handle]/page.tsx
+++ b/app/product/[handle]/page.tsx
@@ -1,149 +1,77 @@
-import type { Metadata } from 'next';
-import { notFound } from 'next/navigation';
+// app/product/[handle]/page.tsx
-import { GridTileImage } from 'components/grid/tile';
-import Footer from 'components/layout/footer';
-import { Gallery } from 'components/product/gallery';
-import { ProductProvider } from 'components/product/product-context';
-import { ProductDescription } from 'components/product/product-description';
-import { HIDDEN_PRODUCT_TAG } from 'lib/constants';
-import { getProduct, getProductRecommendations } from 'lib/shopify';
-import { Image } from 'lib/shopify/types';
-import Link from 'next/link';
-import { Suspense } from 'react';
+// Simulate fetching product data
+async function getProduct(handle: string) {
+ const allProducts: { [key: string]: any } = { // Use 'any' for simplicity in this mock
+ 'sample-product-1': { id: 'prod-1', name: 'Awesome T-Shirt', description: 'This is the best t-shirt ever. Made from 100% organic cotton.', price: { amount: '29.99', currencyCode: 'USD' }, images: [ { src: '/placeholder-tshirt-blue.jpg', alt: 'Awesome T-Shirt - Blue' }, { src: '/placeholder-tshirt-red.jpg', alt: 'Awesome T-Shirt - Red' } ], variants: [ { id: 'v1-color', name: 'Color', value: 'Blue' }, { id: 'v1-size', name: 'Size', value: 'L' }, { id: 'v1-material', name: 'Material', value: 'Cotton' } ] },
+ 'sample-product-2': { id: 'prod-2', name: 'Cool Gadget Pro', description: 'The latest and greatest gadget with amazing features.', price: { amount: '199.50', currencyCode: 'USD' }, images: [ { src: '/placeholder-gadget-main.jpg', alt: 'Cool Gadget Pro' }, { src: '/placeholder-gadget-angle.jpg', alt: 'Cool Gadget Pro - Angle View' } ], variants: [ { id: 'v2-color', name: 'Color', value: 'Black' }, { id: 'v2-storage', name: 'Storage', value: '256GB' } ] },
+ 'another-item': { id: 'prod-3', name: 'Simple Mug', description: 'A simple mug for your daily coffee or tea.', price: { amount: '12.00', currencyCode: 'USD' }, images: [ { src: '/placeholder-mug.jpg', alt: 'Simple Mug' } ], variants: [ { id: 'v3-color', name: 'Color', value: 'White' }, { id: 'v3-size', name: 'Size', value: 'Standard' } ] }
+ };
+ await new Promise(resolve => setTimeout(resolve, 50)); // Simulate network delay
+ return allProducts[String(handle)] || null; // Ensure handle is string
+}
-export async function generateMetadata(props: {
+interface ProductPageProps {
params: Promise<{ handle: string }>;
-}): Promise {
- const params = await props.params;
- const product = await getProduct(params.handle);
-
- if (!product) return notFound();
-
- const { url, width, height, altText: alt } = product.featuredImage || {};
- const indexable = !product.tags.includes(HIDDEN_PRODUCT_TAG);
-
- return {
- title: product.seo.title || product.title,
- description: product.seo.description || product.description,
- robots: {
- index: indexable,
- follow: indexable,
- googleBot: {
- index: indexable,
- follow: indexable
- }
- },
- openGraph: url
- ? {
- images: [
- {
- url,
- width,
- height,
- alt
- }
- ]
- }
- : null
- };
+ searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
}
-export default async function ProductPage(props: { params: Promise<{ handle: string }> }) {
- const params = await props.params;
- const product = await getProduct(params.handle);
+export default async function ProductPage({ params, searchParams }: ProductPageProps) {
+ const resolvedParams = await params;
+ // const resolvedSearchParams = await searchParams; // If needed
- if (!product) return notFound();
+ const product = await getProduct(resolvedParams.handle);
- const productJsonLd = {
- '@context': 'https://schema.org',
- '@type': 'Product',
- name: product.title,
- description: product.description,
- image: product.featuredImage.url,
- offers: {
- '@type': 'AggregateOffer',
- availability: product.availableForSale
- ? 'https://schema.org/InStock'
- : 'https://schema.org/OutOfStock',
- priceCurrency: product.priceRange.minVariantPrice.currencyCode,
- highPrice: product.priceRange.maxVariantPrice.amount,
- lowPrice: product.priceRange.minVariantPrice.amount
- }
- };
+ if (!product) {
+ return Product not found for handle: {resolvedParams.handle}
;
+ }
return (
-
-