275 lines
12 KiB
TypeScript
275 lines
12 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import type { ReactElement } from 'react';
|
|
import Head from 'next/head';
|
|
import Link from 'next/link';
|
|
import axios from 'axios';
|
|
import BaseButton from '../components/BaseButton';
|
|
import { getPageTitle } from '../config';
|
|
import LayoutGuest from '../layouts/Guest';
|
|
import { mdiCart, mdiArrowRight, mdiStar } from '@mdi/js';
|
|
import BaseIcon from '../components/BaseIcon';
|
|
import { useAppDispatch, useAppSelector } from '../stores/hooks';
|
|
import { addToCart } from '../stores/shoppingCartSlice';
|
|
|
|
export default function Home() {
|
|
const [categories, setCategories] = useState([]);
|
|
const [products, setProducts] = useState([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const dispatch = useAppDispatch();
|
|
const cartItems = useAppSelector((state) => state.shoppingCart.items);
|
|
|
|
useEffect(() => {
|
|
const fetchData = async () => {
|
|
try {
|
|
const [catRes, prodRes] = await Promise.all([
|
|
axios.get('/categories'),
|
|
axios.get('/products?limit=8'),
|
|
]);
|
|
setCategories(catRes.data.rows || []);
|
|
setProducts(prodRes.data.rows || []);
|
|
} catch (error) {
|
|
console.error('Error fetching storefront data:', error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
fetchData();
|
|
}, []);
|
|
|
|
const handleQuickAdd = (product: any) => {
|
|
dispatch(addToCart({
|
|
id: Math.random().toString(36).substr(2, 9),
|
|
productId: product.id,
|
|
title: product.title,
|
|
price: product.price,
|
|
quantity: 1,
|
|
image: product.images?.[0]?.url
|
|
}));
|
|
};
|
|
|
|
const cartCount = cartItems.reduce((acc, item) => acc + item.quantity, 0);
|
|
|
|
const getAvgRating = (reviews: any[]) => {
|
|
if (!reviews || reviews.length === 0) return 0;
|
|
return reviews.reduce((acc, r) => acc + r.rating, 0) / reviews.length;
|
|
};
|
|
|
|
return (
|
|
<div className="bg-white min-h-screen">
|
|
<Head>
|
|
<title>{getPageTitle('Home')}</title>
|
|
</Head>
|
|
|
|
{/* Navigation */}
|
|
<nav className="border-b border-gray-100 py-4 px-6 flex justify-between items-center sticky top-0 bg-white z-50">
|
|
<div className="flex items-center space-x-8">
|
|
<Link href="/" className="text-2xl font-bold text-blue-600">
|
|
Storefront
|
|
</Link>
|
|
<div className="hidden md:flex space-x-6">
|
|
<Link href="/products" className="text-gray-600 hover:text-blue-600 font-medium">
|
|
Products
|
|
</Link>
|
|
<Link href="/categories" className="text-gray-600 hover:text-blue-600 font-medium">
|
|
Categories
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center space-x-4">
|
|
<Link href="/cart" className="relative p-2 text-gray-600 hover:text-blue-600 mr-2">
|
|
<BaseIcon path={mdiCart} size={24} />
|
|
{cartCount > 0 && (
|
|
<span className="absolute top-0 right-0 bg-blue-600 text-white text-xs font-bold rounded-full h-5 w-5 flex items-center justify-center">
|
|
{cartCount}
|
|
</span>
|
|
)}
|
|
</Link>
|
|
<Link href="/login" className="text-gray-600 hover:text-blue-600 font-medium">
|
|
Login
|
|
</Link>
|
|
<Link
|
|
href="/register"
|
|
className="bg-blue-600 text-white px-5 py-2 rounded-full font-medium hover:bg-blue-700 transition"
|
|
>
|
|
Sign Up
|
|
</Link>
|
|
</div>
|
|
</nav>
|
|
|
|
{/* Hero Section */}
|
|
<section className="relative bg-gray-50 py-20 px-6">
|
|
<div className="max-w-7xl mx-auto flex flex-col md:flex-row items-center">
|
|
<div className="md:w-1/2 space-y-6">
|
|
<h1 className="text-5xl md:text-6xl font-extrabold text-gray-900 leading-tight">
|
|
Upgrade Your Lifestyle with <span className="text-blue-600">Premium Goods</span>
|
|
</h1>
|
|
<p className="text-xl text-gray-600 max-w-lg">
|
|
Discover our curated collection of high-quality products designed for the modern world.
|
|
</p>
|
|
<div className="flex space-x-4">
|
|
<BaseButton
|
|
href="/products"
|
|
label="Shop Now"
|
|
color="info"
|
|
className="px-8 py-3 rounded-xl text-lg shadow-lg hover:shadow-xl transition-all"
|
|
/>
|
|
<BaseButton
|
|
href="/register"
|
|
label="Get Started"
|
|
outline
|
|
color="info"
|
|
className="px-8 py-3 rounded-xl text-lg"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="md:w-1/2 mt-12 md:mt-0 relative">
|
|
<div className="w-full h-96 bg-blue-100 rounded-3xl overflow-hidden shadow-2xl relative">
|
|
<img
|
|
src="https://images.pexels.com/photos/1350789/pexels-photo-1350789.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1"
|
|
alt="Hero"
|
|
className="w-full h-full object-cover"
|
|
/>
|
|
<div className="absolute inset-0 bg-gradient-to-t from-blue-900/40 to-transparent"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Categories Grid */}
|
|
<section className="py-20 px-6 max-w-7xl mx-auto">
|
|
<div className="flex justify-between items-end mb-10">
|
|
<div>
|
|
<h2 className="text-3xl font-bold text-gray-900">Shop by Category</h2>
|
|
<p className="text-gray-500 mt-2">Explore our wide range of products across different categories.</p>
|
|
</div>
|
|
<Link href="/categories" className="text-blue-600 font-semibold flex items-center hover:underline">
|
|
View All <BaseIcon path={mdiArrowRight} size={20} className="ml-1" />
|
|
</Link>
|
|
</div>
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
|
|
{loading
|
|
? Array(4)
|
|
.fill(0)
|
|
.map((_, i) => (
|
|
<div key={i} className="h-48 bg-gray-100 rounded-2xl animate-pulse"></div>
|
|
))
|
|
: categories.slice(0, 4).map((cat) => (
|
|
<Link
|
|
key={cat.id}
|
|
href={`/products?category=${cat.id}`}
|
|
className="group relative h-48 rounded-2xl overflow-hidden shadow-md hover:shadow-xl transition-all"
|
|
>
|
|
<div className="absolute inset-0 bg-blue-600 opacity-10 group-hover:opacity-20 transition-opacity"></div>
|
|
<div className="absolute inset-0 flex flex-col items-center justify-center p-4">
|
|
<span className="text-xl font-bold text-gray-900 group-hover:text-blue-600 transition-colors">
|
|
{cat.name}
|
|
</span>
|
|
<span className="text-sm text-gray-500 mt-1 uppercase tracking-wider">Explore</span>
|
|
</div>
|
|
</Link>
|
|
))}
|
|
</div>
|
|
</section>
|
|
|
|
{/* Featured Products */}
|
|
<section className="py-20 px-6 bg-gray-50">
|
|
<div className="max-w-7xl mx-auto">
|
|
<div className="flex justify-between items-end mb-10">
|
|
<div>
|
|
<h2 className="text-3xl font-bold text-gray-900">Featured Products</h2>
|
|
<p className="text-gray-500 mt-2">Our handpicked selection for you this season.</p>
|
|
</div>
|
|
<Link href="/products" className="text-blue-600 font-semibold flex items-center hover:underline">
|
|
View All <BaseIcon path={mdiArrowRight} size={20} className="ml-1" />
|
|
</Link>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
|
|
{loading
|
|
? Array(4)
|
|
.fill(0)
|
|
.map((_, i) => (
|
|
<div key={i} className="bg-white rounded-2xl p-4 shadow-sm h-80 animate-pulse"></div>
|
|
))
|
|
: products.map((product) => {
|
|
const avg = getAvgRating(product.reviews);
|
|
return (
|
|
<div
|
|
key={product.id}
|
|
className="bg-white rounded-2xl overflow-hidden shadow-sm hover:shadow-xl transition-all group flex flex-col"
|
|
>
|
|
<div className="h-48 bg-gray-200 relative overflow-hidden">
|
|
<img
|
|
src={`https://images.pexels.com/photos/1350789/pexels-photo-1350789.jpeg?auto=compress&cs=tinysrgb&w=300`}
|
|
alt={product.title}
|
|
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
|
|
/>
|
|
<button
|
|
onClick={() => handleQuickAdd(product)}
|
|
className="absolute top-4 right-4 bg-white/90 backdrop-blur-sm p-2 rounded-full shadow-md hover:bg-white hover:text-blue-600 transition-colors"
|
|
>
|
|
<BaseIcon path={mdiCart} size={20} />
|
|
</button>
|
|
</div>
|
|
<div className="p-5 flex-grow flex flex-col">
|
|
<div className="flex items-center justify-between mb-2">
|
|
<h3 className="text-lg font-bold text-gray-900 line-clamp-1">{product.title}</h3>
|
|
{avg > 0 && (
|
|
<div className="flex items-center text-yellow-400 bg-yellow-50 px-2 py-1 rounded-lg">
|
|
<BaseIcon path={mdiStar} size={14} className="mr-1" />
|
|
<span className="text-xs font-bold">{avg.toFixed(1)}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<p className="text-gray-500 text-sm mt-1 line-clamp-2 flex-grow">
|
|
{product.description || 'No description available.'}
|
|
</p>
|
|
<div className="mt-4 flex justify-between items-center">
|
|
<span className="text-xl font-extrabold text-blue-600">${product.price}</span>
|
|
<Link
|
|
href={`/products/${product.id}`}
|
|
className="text-sm font-semibold text-gray-700 hover:text-blue-600"
|
|
>
|
|
View Details
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Footer */}
|
|
<footer className="bg-white border-t border-gray-100 py-12 px-6">
|
|
<div className="max-w-7xl mx-auto flex flex-col md:flex-row justify-between items-center space-y-6 md:space-y-0">
|
|
<div className="flex flex-col items-center md:items-start">
|
|
<Link href="/" className="text-2xl font-bold text-blue-600">
|
|
Storefront
|
|
</Link>
|
|
<p className="text-gray-500 mt-2 text-sm text-center md:text-left">
|
|
© 2026 Storefront platform. All rights reserved.
|
|
</p>
|
|
</div>
|
|
<div className="flex space-x-8">
|
|
<Link href="/privacy-policy" className="text-gray-500 hover:text-blue-600 text-sm">
|
|
Privacy Policy
|
|
</Link>
|
|
<Link href="/terms-of-use" className="text-gray-500 hover:text-blue-600 text-sm">
|
|
Terms of Use
|
|
</Link>
|
|
<Link href="/dashboard" className="text-blue-600 hover:text-blue-700 text-sm font-bold">
|
|
Admin Interface
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
Home.getLayout = function getLayout(page: ReactElement) {
|
|
return <LayoutGuest>{page}</LayoutGuest>;
|
|
};
|