Jules was unable to complete the task in time. Please review the work done so far and provide feedback for Jules to continue.

This commit is contained in:
google-labs-jules[bot] 2025-05-22 09:24:04 +00:00
parent f36e64c8e0
commit c85444b805
8 changed files with 769 additions and 203 deletions

151
app/cart-checkout/page.tsx Normal file
View File

@ -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 (
<div style={pageStyle}>
<h1 style={{ textAlign: 'center', color: '#222', marginBottom: '40px' }}>Shopping Cart & Checkout</h1>
{/* Cart Items Section */}
<section style={sectionStyle}>
<h2 style={subHeadingStyle}>Your Cart</h2>
{cartItems.length > 0 ? (
<>
{cartItems.map(item => (
<div key={item.id} style={cartItemStyle}>
<div style={{ flexGrow: 1 }}>
<p style={{ fontWeight: 'bold', margin: '0 0 5px 0' }}>{item.name}</p>
<p style={{ margin: '0 0 5px 0', fontSize: '0.9em' }}>Price: ${item.price.toFixed(2)}</p>
<p style={{ margin: '0', fontSize: '0.9em' }}>
Quantity:
<button style={smallButtonStyle} disabled>-</button> {item.quantity} <button style={smallButtonStyle} disabled>+</button>
</p>
</div>
<div style={{ textAlign: 'right' as const }}>
<p style={{ fontWeight: 'bold', margin: '0 0 10px 0' }}>Total: ${(item.price * item.quantity).toFixed(2)}</p>
<button style={{ ...smallButtonStyle, backgroundColor: '#dc3545', color: 'white', border: 'none', borderRadius: '3px' }} disabled>Remove</button>
</div>
</div>
))}
<div style={{ marginTop: '20px', textAlign: 'right' as const }}>
<p><strong>Subtotal:</strong> ${cartSubtotal.toFixed(2)}</p>
<p><strong>Shipping Estimate:</strong> ${shippingEstimate.toFixed(2)}</p>
<h3 style={{ marginTop: '10px' }}>Grand Total: ${grandTotal.toFixed(2)}</h3>
</div>
</>
) : (
<p>Your cart is currently empty.</p>
)}
</section>
{/* Checkout Form Section */}
{cartItems.length > 0 && ( // Only show checkout if cart is not empty
<section style={sectionStyle}>
<h2 style={subHeadingStyle}>Checkout</h2>
<form onSubmit={(e) => e.preventDefault()} > {/* Prevent actual submission */}
<h3 style={{ ...subHeadingStyle, fontSize: '1.1em', marginTop: '0' }}>Shipping Address</h3>
<div style={formGroupStyle}>
<label htmlFor="fullName" style={labelStyle}>Full Name</label>
<input type="text" id="fullName" name="fullName" style={inputStyle} required />
</div>
<div style={formGroupStyle}>
<label htmlFor="address1" style={labelStyle}>Address Line 1</label>
<input type="text" id="address1" name="address1" style={inputStyle} required />
</div>
<div style={formGroupStyle}>
<label htmlFor="city" style={labelStyle}>City</label>
<input type="text" id="city" name="city" style={inputStyle} required />
</div>
<div style={formGroupStyle}>
<label htmlFor="postalCode" style={labelStyle}>Postal Code</label>
<input type="text" id="postalCode" name="postalCode" style={inputStyle} required />
</div>
<div style={formGroupStyle}>
<label htmlFor="country" style={labelStyle}>Country</label>
<input type="text" id="country" name="country" style={inputStyle} required />
</div>
<h3 style={{ ...subHeadingStyle, fontSize: '1.1em', marginTop: '30px' }}>Billing Address</h3>
<div style={{ ...formGroupStyle, display: 'flex', alignItems: 'center' }}>
<input
type="checkbox"
id="billingSame"
name="billingSame"
checked={billingSameAsShipping}
onChange={(e) => setBillingSameAsShipping(e.target.checked)}
style={{ marginRight: '10px' }}
/>
<label htmlFor="billingSame" style={{ ...labelStyle, marginBottom: '0' }}>Same as shipping address</label>
</div>
{!billingSameAsShipping && (
<>
{/* Billing address fields would go here, similar to shipping */}
<p style={{ fontStyle: 'italic', color: '#666' }}>(Billing address fields would appear here if different)</p>
</>
)}
<h3 style={{ ...subHeadingStyle, fontSize: '1.1em', marginTop: '30px' }}>Payment Information</h3>
<div style={{ border: '1px dashed #ccc', padding: '15px', borderRadius: '4px', backgroundColor: '#f9f9f9' }}>
<p style={{ margin: '0 0 10px 0', fontWeight: 'bold' }}>Card Number:</p>
<p style={{ color: '#777', fontStyle: 'italic' }}> (Placeholder: Actual card input fields are not implemented for security reasons)</p>
<p style={{ margin: '10px 0 10px 0', fontWeight: 'bold' }}>Expiry Date (MM/YY):</p>
<p style={{ margin: '10px 0 0 0', fontWeight: 'bold' }}>CVV:</p>
</div>
<button type="submit" style={{ ...buttonStyle, marginTop: '30px', width: '100%', backgroundColor: '#28a745' }}
onMouseOver={(e) => (e.currentTarget.style.backgroundColor = '#218838')}
onMouseOut={(e) => (e.currentTarget.style.backgroundColor = '#28a745')}
disabled={cartItems.length === 0}
>
Place Order
</button>
</form>
</section>
)}
{/* Request a Quote Section */}
<section style={{ ...sectionStyle, borderBottom: 'none', textAlign: 'center' as const }}>
<h2 style={subHeadingStyle}>Need a Custom Quote?</h2>
<p style={{ marginBottom: '15px' }}>For bulk orders or special requirements, please request a quote.</p>
<button
style={{ ...buttonStyle, backgroundColor: '#17a2b8' }}
onClick={() => 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
</button>
</section>
</div>
);
}

View File

@ -0,0 +1,54 @@
// 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.'
]
}
};
return allContent[slug] || null;
}
export default async function ContentPage({ params }: { params: { slug: string } }) {
const content = await getContent(params.slug);
if (!content) {
// Handle case where content is not found, e.g., by returning a 404 or a specific message
return <div>Content not found for {params.slug}</div>;
}
return (
<div style={{ padding: '20px' }}>
<h1>{content.title}</h1>
{content.body.map((paragraph, index) => (
<p key={index}>{paragraph}</p>
))}
</div>
);
}
// Optional: Generate static paths if you have a known set of content pages
// export async function generateStaticParams() {
// return [{ slug: 'about-us' }, { slug: 'contact-us' }, { slug: 'privacy-policy' }];
// }

58
app/login/page.tsx Normal file
View File

@ -0,0 +1,58 @@
// app/login/page.tsx
export default function LoginPage() {
return (
<div style={{ maxWidth: '400px', margin: '50px auto', padding: '40px', border: '1px solid #ddd', borderRadius: '8px', boxShadow: '0 4px 8px rgba(0,0,0,0.1)' }}>
<h1 style={{ textAlign: 'center', marginBottom: '30px', color: '#333' }}>Sign In</h1>
<form onSubmit={(e) => e.preventDefault()} > {/* Prevent actual submission for this mock */}
<div style={{ marginBottom: '15px' }}>
<label htmlFor="email" style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold', color: '#555' }}>Email Address</label>
<input
type="email"
id="email"
name="email"
required
placeholder="you@example.com"
style={{ width: '100%', padding: '10px', border: '1px solid #ccc', borderRadius: '4px', boxSizing: 'border-box' }}
/>
</div>
<div style={{ marginBottom: '20px' }}>
<label htmlFor="password" style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold', color: '#555' }}>Password</label>
<input
type="password"
id="password"
name="password"
required
placeholder="Your password"
style={{ width: '100%', padding: '10px', border: '1px solid #ccc', borderRadius: '4px', boxSizing: 'border-box' }}
/>
</div>
<button
type="submit"
style={{
width: '100%',
padding: '12px',
backgroundColor: '#007bff',
color: 'white',
border: 'none',
borderRadius: '4px',
fontSize: '16px',
cursor: 'pointer',
transition: 'background-color 0.2s'
}}
onMouseOver={(e) => (e.currentTarget.style.backgroundColor = '#0056b3')}
onMouseOut={(e) => (e.currentTarget.style.backgroundColor = '#007bff')}
>
Sign In
</button>
</form>
<div style={{ marginTop: '25px', textAlign: 'center', fontSize: '14px' }}>
<p style={{ marginBottom: '10px' }}>
New customer? <a href="/signup" style={{ color: '#007bff', textDecoration: 'none' }}>Create an account</a>
</p>
<p>
<a href="/reset-password" style={{ color: '#007bff', textDecoration: 'none' }}>Forgot your password?</a>
</p>
</div>
</div>
);
}

139
app/my-page/page.tsx Normal file
View File

@ -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 (
<div style={{ padding: '20px', fontFamily: 'Arial, sans-serif', maxWidth: '900px', margin: '20px auto' }}>
<h1 style={{ textAlign: 'center', color: '#222', marginBottom: '40px' }}>My Account</h1>
{/* Order History Section */}
<section style={sectionStyle}>
<h2 style={headingStyle}>Order History</h2>
{orders.length > 0 ? (
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead>
<tr>
<th style={tableCellStyle}>Order ID</th>
<th style={tableCellStyle}>Date</th>
<th style={tableCellStyle}>Total</th>
<th style={tableCellStyle}>Status</th>
</tr>
</thead>
<tbody>
{orders.map(order => (
<tr key={order.id}>
<td style={tableCellStyle}>#{order.id}</td>
<td style={tableCellStyle}>{order.date}</td>
<td style={tableCellStyle}>{order.total}</td>
<td style={tableCellStyle}>{order.status}</td>
</tr>
))}
</tbody>
</table>
) : (
<p>You have no past orders.</p>
)}
</section>
{/* My Quotes Section */}
<section style={sectionStyle}>
<h2 style={headingStyle}>My Quotes</h2>
{quotes.length > 0 ? (
<ul style={{ listStyle: 'none', padding: 0 }}>
{quotes.map(quote => (
<li key={quote.id} style={{ marginBottom: '10px', padding: '10px', border: '1px solid #eee', borderRadius: '4px' }}>
Quote #{quote.id} - Date: {quote.date} - Total: {quote.total} - Status: <span style={{ fontWeight: 'bold' }}>{quote.status}</span>
</li>
))}
</ul>
) : (
<p>You have no active quotes.</p>
)}
</section>
{/* My Downloads Section */}
<section style={sectionStyle}>
<h2 style={headingStyle}>My Downloads</h2>
{downloads.length > 0 ? (
<ul style={{ listStyle: 'none', padding: 0 }}>
{downloads.map((download, index) => ( // Added index for key as names might not be unique
<li key={`${download.name}-${index}`} style={{ marginBottom: '8px' }}>
<a href={download.url} download style={{ color: '#007bff', textDecoration: 'none' }}>
{download.name}
</a>
</li>
))}
</ul>
) : (
<p>No downloads available.</p>
)}
</section>
{/* Profile Information Section */}
<section style={{ ...sectionStyle, borderBottom: 'none' }}>
<h2 style={headingStyle}>My Profile</h2>
<div style={{ lineHeight: '1.8' }}>
<p><strong>Name:</strong> {userProfile.name}</p>
<p><strong>Email:</strong> {userProfile.email}</p>
<p><strong>Company:</strong> {userProfile.company}</p>
<p><strong>Member Since:</strong> {userProfile.memberSince}</p>
</div>
<button style={{ ...buttonStyle, marginTop: '15px' }}
onMouseOver={(e) => (e.currentTarget.style.backgroundColor = '#0056b3')}
onMouseOut={(e) => (e.currentTarget.style.backgroundColor = '#007bff')}
>
Edit Profile
</button>
</section>
</div>
);
}

View File

@ -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 (
<div style={{ border: '1px solid #eee', padding: '16px', textAlign: 'center' }}>
<img src={imageUrl} alt={name} style={{ maxWidth: '100%', height: 'auto', marginBottom: '8px' }} />
<h3>{name}</h3>
<p>{price}</p>
</div>
);
}
// Placeholder Promotion Banner Component
function PromotionBanner({ title, imageUrl }: { title: string; imageUrl: string }) {
return (
<div style={{ border: '1px solid #eee', padding: '16px', textAlign: 'center', margin: '16px 0' }}>
<img src={imageUrl} alt={title} style={{ maxWidth: '100%', height: 'auto', marginBottom: '8px' }} />
<h2>{title}</h2>
</div>
);
}
export default function HomePage() {
return (
<>
{/* Existing components can remain if they are part of the desired layout */}
<ThreeItemGrid />
<Carousel />
{pageConfig.showFeaturedProducts && (
<section style={{ padding: '20px' }}>
<h2>Featured Products</h2>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))', gap: '16px', marginTop: '16px' }}>
<ProductItem name="Awesome T-Shirt" price="$29.99" imageUrl="/placeholder-product1.jpg" />
<ProductItem name="Cool Gadget" price="$99.50" imageUrl="/placeholder-product2.jpg" />
<ProductItem name="Stylish Hat" price="$19.75" imageUrl="/placeholder-product3.jpg" />
<ProductItem name="Generic Item" price="$10.00" imageUrl="/placeholder-product4.jpg" />
</div>
</section>
)}
{pageConfig.showPromotions && (
<section style={{ padding: '20px' }}>
<h2>Promotions</h2>
<PromotionBanner title="Summer Sale!" imageUrl="/placeholder-promo1.jpg" />
<PromotionBanner title="New Arrivals" imageUrl="/placeholder-promo2.jpg" />
</section>
)}
<Footer />
</>
);

View File

@ -1,149 +1,110 @@
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';
export async function generateMetadata(props: {
params: Promise<{ handle: string }>;
}): Promise<Metadata> {
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
}
// 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' }
]
},
openGraph: url
? {
images: [
{
url,
width,
height,
alt
}
]
}
: null
};
}
export default async function ProductPage(props: { params: Promise<{ handle: string }> }) {
const params = await props.params;
const product = await getProduct(params.handle);
if (!product) return notFound();
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
'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' }
]
}
};
return (
<ProductProvider>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(productJsonLd)
}}
/>
<div className="mx-auto max-w-(--breakpoint-2xl) px-4">
<div className="flex flex-col rounded-lg border border-neutral-200 bg-white p-8 md:p-12 lg:flex-row lg:gap-8 dark:border-neutral-800 dark:bg-black">
<div className="h-full w-full basis-full lg:basis-4/6">
<Suspense
fallback={
<div className="relative aspect-square h-full max-h-[550px] w-full overflow-hidden" />
}
>
<Gallery
images={product.images.slice(0, 5).map((image: Image) => ({
src: image.url,
altText: image.altText
}))}
/>
</Suspense>
</div>
<div className="basis-full lg:basis-2/6">
<Suspense fallback={null}>
<ProductDescription product={product} />
</Suspense>
</div>
</div>
<RelatedProducts id={product.id} />
</div>
<Footer />
</ProductProvider>
);
// Simulate network delay
await new Promise(resolve => setTimeout(resolve, 50));
return allProducts[handle] || null;
}
async function RelatedProducts({ id }: { id: string }) {
const relatedProducts = await getProductRecommendations(id);
export default async function ProductPage({ params }: { params: { handle: string } }) {
const product = await getProduct(params.handle);
if (!relatedProducts.length) return null;
if (!product) {
// In a real app, you might use Next.js's notFound() function here
return <div style={{ padding: '20px', textAlign: 'center' }}>Product not found for handle: {params.handle}</div>;
}
return (
<div className="py-8">
<h2 className="mb-4 text-2xl font-bold">Related Products</h2>
<ul className="flex w-full gap-4 overflow-x-auto pt-1">
{relatedProducts.map((product) => (
<li
key={product.handle}
className="aspect-square w-full flex-none min-[475px]:w-1/2 sm:w-1/3 md:w-1/4 lg:w-1/5"
>
<Link
className="relative h-full w-full"
href={`/product/${product.handle}`}
prefetch={true}
>
<GridTileImage
alt={product.title}
label={{
title: product.title,
amount: product.priceRange.maxVariantPrice.amount,
currencyCode: product.priceRange.maxVariantPrice.currencyCode
}}
src={product.featuredImage?.url}
fill
sizes="(min-width: 1024px) 20vw, (min-width: 768px) 25vw, (min-width: 640px) 33vw, (min-width: 475px) 50vw, 100vw"
/>
</Link>
</li>
))}
</ul>
<div style={{ padding: '20px', fontFamily: 'Arial, sans-serif', maxWidth: '800px', margin: '0 auto' }}>
<h1>{product.name}</h1>
{product.images && product.images.length > 0 && (
<img
src={product.images[0].src}
alt={product.images[0].alt || product.name}
style={{ maxWidth: '100%', height: 'auto', maxHeight: '400px', marginBottom: '20px', border: '1px solid #ddd' }}
/>
)}
<p style={{ fontSize: '1.1em', lineHeight: '1.6' }}>{product.description}</p>
<p style={{ fontSize: '1.2em', fontWeight: 'bold', margin: '20px 0' }}>
Price: {product.price.amount} {product.price.currencyCode}
</p>
<h2>Variants:</h2>
{product.variants && product.variants.length > 0 ? (
<ul style={{ listStyle: 'none', padding: 0 }}>
{product.variants.map((variant: any) => ( // Using any for mock simplicity
<li key={variant.id} style={{ marginBottom: '8px', borderBottom: '1px solid #eee', paddingBottom: '8px' }}>
<strong>{variant.name}:</strong> {variant.value}
</li>
))}
</ul>
) : (
<p>No variants available for this product.</p>
)}
{/* Add to cart button can be a simple placeholder */}
<button
style={{
padding: '10px 20px',
fontSize: '1em',
color: 'white',
backgroundColor: '#007bff',
border: 'none',
borderRadius: '5px',
cursor: 'pointer',
marginTop: '20px'
}}
>
Add to Cart
</button>
</div>
);
}

View File

@ -1,45 +1,108 @@
import { getCollection, getCollectionProducts } from 'lib/shopify';
import { Metadata } from 'next';
import { notFound } from 'next/navigation';
// app/search/[collection]/page.tsx
import Grid from 'components/grid';
import ProductGridItems from 'components/layout/product-grid-items';
import { defaultSort, sorting } from 'lib/constants';
// Simulate fetching products for a collection
async function getProductsByCollection(collectionName: string) {
// Simulate network delay
await new Promise(resolve => setTimeout(resolve, 50));
export async function generateMetadata(props: {
params: Promise<{ collection: string }>;
}): Promise<Metadata> {
const params = await props.params;
const collection = await getCollection(params.collection);
if (!collection) return notFound();
return {
title: collection.seo?.title || collection.title,
description:
collection.seo?.description || collection.description || `${collection.title} products`
const allProducts: { [key: string]: any[] } = {
't-shirts': [
{ id: 'ts1', name: 'Cool T-Shirt', price: { amount: '19.99', currencyCode: 'USD' }, image: { src: '/placeholder-tshirt1.jpg', alt: 'T-Shirt 1' } },
{ id: 'ts2', name: 'Graphic Tee', price: { amount: '24.99', currencyCode: 'USD' }, image: { src: '/placeholder-tshirt2.jpg', alt: 'T-Shirt 2' } },
{ id: 'ts3', name: 'Plain V-Neck', price: { amount: '15.50', currencyCode: 'USD' }, image: { src: '/placeholder-tshirt3.jpg', alt: 'Plain V-Neck' } },
],
'accessories': [
{ id: 'ac1', name: 'Stylish Cap', price: { amount: '15.00', currencyCode: 'USD' }, image: { src: '/placeholder-cap.jpg', alt: 'Cap' } },
{ id: 'ac2', name: 'Leather Belt', price: { amount: '35.00', currencyCode: 'USD' }, image: { src: '/placeholder-belt.jpg', alt: 'Leather Belt' } },
],
'footwear': [
{ id: 'fw1', name: 'Running Shoes', price: { amount: '79.99', currencyCode: 'USD' }, image: { src: '/placeholder-shoes1.jpg', alt: 'Running Shoes' } },
{ id: 'fw2', name: 'Casual Sneakers', price: { amount: '65.00', currencyCode: 'USD' }, image: { src: '/placeholder-sneakers1.jpg', alt: 'Casual Sneakers' } },
{ id: 'fw3', name: 'Formal Shoes', price: { amount: '120.00', currencyCode: 'USD' }, image: { src: '/placeholder-shoes2.jpg', alt: 'Formal Shoes' } },
]
// Add more dummy collections and products as needed
};
return allProducts[collectionName.toLowerCase()] || [];
}
export default async function CategoryPage(props: {
params: Promise<{ collection: string }>;
searchParams?: Promise<{ [key: string]: string | string[] | undefined }>;
}) {
const searchParams = await props.searchParams;
const params = await props.params;
const { sort } = searchParams as { [key: string]: string };
const { sortKey, reverse } = sorting.find((item) => item.slug === sort) || defaultSort;
const products = await getCollectionProducts({ collection: params.collection, sortKey, reverse });
export default async function CollectionPage({ params }: { params: { collection: string } }) {
const products = await getProductsByCollection(params.collection);
const collectionName = params.collection.charAt(0).toUpperCase() + params.collection.slice(1);
return (
<section>
{products.length === 0 ? (
<p className="py-3 text-lg">{`No products found in this collection`}</p>
) : (
<Grid className="grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
<ProductGridItems products={products} />
</Grid>
)}
</section>
<div style={{ padding: '20px', fontFamily: 'Arial, sans-serif' }}>
<h1 style={{ marginBottom: '20px', borderBottom: '2px solid #eee', paddingBottom: '10px' }}>
Products in: {collectionName}
</h1>
<div style={{ display: 'flex', gap: '20px', marginBottom: '30px' }}>
{/* Placeholder Filters */}
<div style={{ border: '1px solid #ddd', padding: '15px', borderRadius: '5px', width: '250px' }}>
<h3 style={{ marginTop: '0', marginBottom: '15px' }}>Filters</h3>
<div style={{ marginBottom: '10px' }}>
<label htmlFor="category-filter" style={{ display: 'block', marginBottom: '5px' }}>Category:</label>
<select id="category-filter" style={{ width: '100%', padding: '8px' }}>
<option value="">All {collectionName}</option>
<option value="cat1">Sub-Category 1</option>
<option value="cat2">Sub-Category 2</option>
<option value="cat3">Sub-Category 3</option>
</select>
</div>
<div>
<label htmlFor="price-range-filter" style={{ display: 'block', marginBottom: '5px' }}>Price Range:</label>
<input type="range" id="price-range-filter" min="0" max="200" defaultValue="100" style={{ width: '100%' }} />
{/* Basic display of range value - not functional */}
<div style={{ textAlign: 'center', fontSize: '0.9em', marginTop: '5px' }}>$0 - $200</div>
</div>
</div>
{/* Main Content Area (Search + Product List) */}
<div style={{ flex: 1 }}>
{/* Placeholder Search Input */}
<div style={{ marginBottom: '20px' }}>
<input
type="search"
placeholder={`Search within ${collectionName}...`}
style={{ width: '100%', padding: '10px', boxSizing: 'border-box', fontSize: '1em' }}
/>
</div>
{/* Product List */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))', gap: '20px' }}>
{products.length > 0 ? (
products.map((product: any) => ( // Using any for mock simplicity
<div key={product.id} style={{ border: '1px solid #ccc', padding: '16px', borderRadius: '5px', textAlign: 'center' }}>
<img
src={product.image.src}
alt={product.image.alt}
style={{ width: '100%', height: '180px', objectFit: 'cover', marginBottom: '10px', borderBottom: '1px solid #eee', paddingBottom: '10px' }}
/>
<h2 style={{ fontSize: '1.1em', margin: '0 0 10px 0' }}>{product.name}</h2>
<p style={{ fontSize: '1em', fontWeight: 'bold', margin: '0 0 10px 0' }}>
{product.price.amount} {product.price.currencyCode}
</p>
{/* Use a generic link for now; specific product pages are handled by [handle] route */}
<a
href={`/product/${product.id}`} // Assuming product.id can be a handle
style={{
display: 'inline-block',
padding: '8px 15px',
backgroundColor: '#007bff',
color: 'white',
textDecoration: 'none',
borderRadius: '3px'
}}
>
View Details
</a>
</div>
))
) : (
<p style={{ gridColumn: '1 / -1' }}>No products found in this collection.</p>
)}
</div>
</div>
</div>
</div>
);
}

View File

@ -1,38 +1,129 @@
import Grid from 'components/grid';
import ProductGridItems from 'components/layout/product-grid-items';
import { defaultSort, sorting } from 'lib/constants';
import { getProducts } from 'lib/shopify';
// app/search/page.tsx
'use client'; // Required for using client-side features like useState
export const metadata = {
title: 'Search',
description: 'Search for products in the store.'
};
import { useState, useEffect } from 'react';
import { useSearchParams } from 'next/navigation'; // To get query from URL
export default async function SearchPage(props: {
searchParams?: Promise<{ [key: string]: string | string[] | undefined }>;
}) {
const searchParams = await props.searchParams;
const { sort, q: searchValue } = searchParams as { [key: string]: string };
const { sortKey, reverse } = sorting.find((item) => item.slug === sort) || defaultSort;
// Simulate fetching search results
async function getSearchResults(query: string | null) {
// Simulate network delay
await new Promise(resolve => setTimeout(resolve, 50));
if (!query) return [];
const mockResults: { [key: string]: any[] } = {
'shirt': [
{ id: 'sr1', name: 'Search Result Shirt 1', price: { amount: '29.99', currencyCode: 'USD' }, image: { src: '/placeholder-shirt1.jpg', alt: 'Shirt 1' } },
{ id: 'sr2', name: 'Search Result Shirt 2', price: { amount: '35.50', currencyCode: 'USD' }, image: { src: '/placeholder-shirt2.jpg', alt: 'Shirt 2' } },
],
'accessory': [
{ id: 'sa1', name: 'Search Result Accessory', price: { amount: '12.00', currencyCode: 'USD' }, image: { src: '/placeholder-accessory.jpg', alt: 'Accessory' } },
],
'generic': [ // Added a generic list for queries that don't match specific terms
{ id: 'g1', name: 'Generic Product A', price: { amount: '10.00', currencyCode: 'USD' }, image: { src: '/placeholder-generic1.jpg', alt: 'Generic A' } },
{ id: 'g2', name: 'Generic Product B', price: { amount: '15.00', currencyCode: 'USD' }, image: { src: '/placeholder-generic2.jpg', alt: 'Generic B' } },
]
};
return mockResults[query.toLowerCase()] || mockResults['generic']; // Fallback to generic results
}
export default function SearchPage() {
const searchParams = useSearchParams();
const initialQuery = searchParams.get('q');
const [query, setQuery] = useState(initialQuery || '');
const [results, setResults] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
const handleSearch = async (currentQuery: string) => {
if (!currentQuery.trim()) { // Trim query to avoid searching for empty spaces
setResults([]);
return;
}
setLoading(true);
const fetchedResults = await getSearchResults(currentQuery);
setResults(fetchedResults);
setLoading(false);
};
// Perform search when initialQuery (from URL) changes or when component mounts with an initialQuery
useEffect(() => {
if (initialQuery) {
setQuery(initialQuery); // Ensure input field is updated if query comes from URL
handleSearch(initialQuery);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
// Disabled exhaustive-deps because handleSearch reference might change but we only care about initialQuery
}, [initialQuery]);
const products = await getProducts({ sortKey, reverse, query: searchValue });
const resultsText = products.length > 1 ? 'results' : 'result';
return (
<>
{searchValue ? (
<p className="mb-4">
{products.length === 0
? 'There are no products that match '
: `Showing ${products.length} ${resultsText} for `}
<span className="font-bold">&quot;{searchValue}&quot;</span>
<div style={{ padding: '20px', fontFamily: 'Arial, sans-serif', maxWidth: '900px', margin: '0 auto' }}>
<h1 style={{ textAlign: 'center', marginBottom: '20px' }}>Search Products</h1>
<form
onSubmit={(e) => {
e.preventDefault();
// Update URL with the new search query
window.history.pushState(null, '', `?q=${encodeURIComponent(query)}`);
handleSearch(query);
}}
style={{ display: 'flex', justifyContent: 'center', marginBottom: '20px' }}
>
<input
type="search"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search for products..."
style={{ padding: '10px', fontSize: '1em', width: '300px', marginRight: '10px' }}
/>
<button type="submit" style={{ padding: '10px 15px', fontSize: '1em' }}>Search</button>
</form>
<p style={{ textAlign: 'center', fontStyle: 'italic', marginBottom: '20px', color: '#555' }}>
Personalization by Relewise will be implemented here.
</p>
{loading && <p style={{ textAlign: 'center' }}>Loading...</p>}
{!loading && results.length > 0 && (
<div>
<h2 style={{ marginBottom: '15px' }}>Results for "{query}"</h2>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))', gap: '20px' }}>
{results.map((product) => (
<div key={product.id} style={{ border: '1px solid #ccc', padding: '16px', borderRadius: '5px', textAlign: 'center' }}>
<img src={product.image.src} alt={product.image.alt} style={{ width: '100%', height: '180px', objectFit: 'cover', marginBottom: '10px', borderBottom: '1px solid #eee', paddingBottom: '10px' }} />
<h3 style={{ fontSize: '1.1em', margin: '0 0 10px 0', minHeight: '44px' }}>{product.name}</h3>
<p style={{ fontSize: '1em', fontWeight: 'bold', margin: '0 0 10px 0' }}>
{product.price.amount} {product.price.currencyCode}
</p>
<a
href={`/product/${product.id}`} // Assuming product.id can be a handle
style={{
display: 'inline-block',
padding: '8px 15px',
backgroundColor: '#007bff',
color: 'white',
textDecoration: 'none',
borderRadius: '3px'
}}
>
View Details
</a>
</div>
))}
</div>
</div>
)}
{!loading && results.length === 0 && query && (
<div style={{ textAlign: 'center', marginTop: '30px' }}>
<p>No specific results found for "{query}".</p>
<p style={{ color: '#777' }}>Did you mean: t-shirt, accessory, cap, generic?</p> {/* Placeholder suggestions */}
</div>
)}
{!loading && results.length === 0 && !query && (
<p style={{ textAlign: 'center', marginTop: '30px', color: '#777' }}>
Enter a search term above to find products.
</p>
) : null}
{products.length > 0 ? (
<Grid className="grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
<ProductGridItems products={products} />
</Grid>
) : null}
</>
)}
</div>
);
}