Update to v1.0 of website code from kottgard-production-v1.zip

This commit is contained in:
root
2026-06-23 09:24:46 +00:00
parent e0273c46d7
commit 582a2f33ad
60 changed files with 658 additions and 160 deletions
+4 -7
View File
@@ -1,7 +1,6 @@
'use client';
import AppImage from '@/components/ui/AppImage';
import Link from 'next/link';
import { ArrowRight } from 'lucide-react';
import Button from '@/components/ui/Button';
import { useTranslation } from '@/hooks/useTranslation';
@@ -42,12 +41,10 @@ export default function AboutPreview() {
))}
</div>
<Link href="/about">
<Button variant="secondary">
{t('aboutPreview.readMore')}
<ArrowRight className="h-4 w-4 rtl:rotate-180" />
</Button>
</Link>
<Button href="/about" variant="secondary">
{t('aboutPreview.readMore')}
<ArrowRight className="h-4 w-4 rtl:rotate-180" />
</Button>
</div>
<div className="relative aspect-[4/5] overflow-hidden rounded-2xl shadow-premium-lg">
+17 -20
View File
@@ -1,6 +1,5 @@
'use client';
import Link from 'next/link';
import Button from '@/components/ui/Button';
import { ArrowRight, MessageCircle } from 'lucide-react';
import { useTranslation } from '@/hooks/useTranslation';
@@ -13,31 +12,29 @@ export default function CTA() {
<section className="py-20">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="relative overflow-hidden rounded-3xl bg-gradient-to-br from-brand-700 to-brand-900 px-8 py-16 text-center sm:px-16">
<div className="absolute -right-20 -top-20 h-60 w-60 rounded-full bg-gold-500/10" />
<div className="absolute -bottom-16 -left-16 h-48 w-48 rounded-full bg-gold-500/10" />
<div className="pointer-events-none absolute -right-20 -top-20 h-60 w-60 rounded-full bg-gold-500/10" />
<div className="pointer-events-none absolute -bottom-16 -left-16 h-48 w-48 rounded-full bg-gold-500/10" />
<div className="relative">
<div className="relative z-10">
<h2 className="mb-4 font-display text-3xl font-bold text-white md:text-4xl">
{t('cta.title')}
</h2>
<p className="mx-auto mb-8 max-w-xl text-brand-100">{t('cta.subtitle')}</p>
<div className="flex flex-col items-center justify-center gap-4 sm:flex-row">
<Link href="/shop">
<Button variant="gold" size="lg">
{t('cta.button')}
<ArrowRight className="h-4 w-4 rtl:rotate-180" />
</Button>
</Link>
<a href={WHATSAPP_URL} target="_blank" rel="noopener noreferrer">
<Button
variant="secondary"
size="lg"
className="border-white/30 bg-white/10 text-white hover:border-gold-400/50 hover:bg-white/15"
>
<MessageCircle className="h-4 w-4" />
{t('cta.whatsapp')}
</Button>
</a>
<Button href="/shop" variant="gold" size="lg">
{t('cta.button')}
<ArrowRight className="h-4 w-4 rtl:rotate-180" />
</Button>
<Button
href={WHATSAPP_URL}
external
variant="secondary"
size="lg"
className="border-white/30 bg-white/10 text-white hover:border-gold-400/50 hover:bg-white/15"
>
<MessageCircle className="h-4 w-4" />
{t('cta.whatsapp')}
</Button>
</div>
</div>
</div>
+21 -26
View File
@@ -1,6 +1,5 @@
'use client';
import Link from 'next/link';
import { Phone, MapPin, Clock, MessageCircle, ExternalLink } from 'lucide-react';
import Button from '@/components/ui/Button';
import Logo from '@/components/ui/Logo';
@@ -64,31 +63,27 @@ export default function ContactPreview() {
</ul>
<div className="flex flex-col gap-3 sm:flex-row">
<Link href="/about#contact">
<Button
variant="secondary"
size="lg"
className="w-full border-white/30 bg-white/10 text-white hover:border-gold-400/50 hover:bg-white/15 sm:w-auto"
>
{t('contact.learnMore')}
</Button>
</Link>
<a href={WHATSAPP_URL} target="_blank" rel="noopener noreferrer">
<Button variant="gold" size="lg" className="w-full sm:w-auto">
<MessageCircle className="h-4 w-4" />
{t('contact.writeUs')}
</Button>
</a>
<a href={telHref}>
<Button
variant="secondary"
size="lg"
className="w-full border-white/30 bg-white/10 text-white hover:border-gold-400/50 hover:bg-white/15 sm:w-auto"
>
<Phone className="h-4 w-4" />
{t('contact.callUs')}
</Button>
</a>
<Button
href="/about#contact"
variant="secondary"
size="lg"
className="w-full border-white/30 bg-white/10 text-white hover:border-gold-400/50 hover:bg-white/15 sm:w-auto"
>
{t('contact.learnMore')}
</Button>
<Button href={WHATSAPP_URL} external variant="gold" size="lg" className="w-full sm:w-auto">
<MessageCircle className="h-4 w-4" />
{t('contact.writeUs')}
</Button>
<Button
href={telHref}
variant="secondary"
size="lg"
className="w-full border-white/30 bg-white/10 text-white hover:border-gold-400/50 hover:bg-white/15 sm:w-auto"
>
<Phone className="h-4 w-4" />
{t('contact.callUs')}
</Button>
</div>
</div>
+4 -7
View File
@@ -1,6 +1,5 @@
'use client';
import Link from 'next/link';
import ProductCard from '@/components/product/ProductCard';
import { getFeaturedProducts } from '@/lib/products';
import Button from '@/components/ui/Button';
@@ -25,12 +24,10 @@ export default function FeaturedProducts() {
<h2 className="section-heading">{t('featured.title')}</h2>
<p className="mt-2 max-w-lg text-gray-500">{t('featured.subtitle')}</p>
</div>
<Link href="/shop">
<Button variant="secondary">
{t('featured.viewAll')}
<ArrowRight className="h-4 w-4 rtl:rotate-180" />
</Button>
</Link>
<Button href="/shop" variant="secondary">
{t('featured.viewAll')}
<ArrowRight className="h-4 w-4 rtl:rotate-180" />
</Button>
</div>
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
+18 -21
View File
@@ -1,6 +1,5 @@
'use client';
import Link from 'next/link';
import AppImage from '@/components/ui/AppImage';
import Button from '@/components/ui/Button';
import Logo from '@/components/ui/Logo';
@@ -14,7 +13,7 @@ export default function Hero() {
return (
<section className="relative min-h-[85vh] overflow-hidden bg-brand-950">
<div className="absolute inset-0">
<div className="pointer-events-none absolute inset-0">
<AppImage
src={PAGE_IMAGES.hero}
alt=""
@@ -26,10 +25,10 @@ export default function Hero() {
blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAIAAoDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAUH/8QAIhAAAgEDBQAAAAAAAAAAAAAAAQIDAAQRBQYhIjFB/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAZEQACAwEAAAAAAAAAAAAAAAAAAQIRITH/2gAMAwEAAhEDEEA/ALextba0t0t7eJI4kHVEU4AqT/9k="
/>
</div>
<div className="absolute inset-0 bg-gradient-to-r from-brand-950/95 via-brand-900/85 to-brand-900/40" />
<div className="absolute inset-0 bg-gradient-to-t from-brand-950/60 via-transparent to-transparent" />
<div className="pointer-events-none absolute inset-0 bg-gradient-to-r from-brand-950/95 via-brand-900/85 to-brand-900/40" />
<div className="pointer-events-none absolute inset-0 bg-gradient-to-t from-brand-950/60 via-transparent to-transparent" />
<div className="relative mx-auto flex min-h-[85vh] max-w-7xl flex-col justify-center px-4 py-20 sm:px-6 lg:px-8 lg:py-28">
<div className="relative z-10 mx-auto flex min-h-[85vh] max-w-7xl flex-col justify-center px-4 py-20 sm:px-6 lg:px-8 lg:py-28">
<div className="max-w-2xl animate-slide-up">
<div className="mb-8 flex items-center gap-4">
<Logo size="lg" className="ring-gold-400/40 shadow-premium-lg" />
@@ -70,26 +69,24 @@ export default function Hero() {
</div>
<div className="flex flex-col gap-4 sm:flex-row">
<Link href="/shop">
<Button variant="gold" size="lg">
{t('hero.shopNow')}
<ArrowRight className="h-4 w-4 rtl:rotate-180" />
</Button>
</Link>
<a href={WHATSAPP_URL} target="_blank" rel="noopener noreferrer">
<Button
variant="secondary"
size="lg"
className="border-white/30 bg-white/10 text-white hover:border-gold-400/50 hover:bg-white/15"
>
{t('hero.whatsapp')}
</Button>
</a>
<Button href="/shop" variant="gold" size="lg">
{t('hero.shopNow')}
<ArrowRight className="h-4 w-4 rtl:rotate-180" />
</Button>
<Button
href={WHATSAPP_URL}
external
variant="secondary"
size="lg"
className="border-white/30 bg-white/10 text-white hover:border-gold-400/50 hover:bg-white/15"
>
{t('hero.whatsapp')}
</Button>
</div>
</div>
</div>
<div className="absolute bottom-0 left-0 right-0 h-20 bg-gradient-to-t from-cream-50 to-transparent" />
<div className="pointer-events-none absolute bottom-0 left-0 right-0 z-[1] h-20 bg-gradient-to-t from-cream-50 to-transparent" />
</section>
);
}
+27
View File
@@ -0,0 +1,27 @@
import Hero from '@/components/home/Hero';
import TrustBadges from '@/components/home/TrustBadges';
import AboutPreview from '@/components/home/AboutPreview';
import CategoryGrid from '@/components/home/CategoryGrid';
import FeaturedProducts from '@/components/home/FeaturedProducts';
import HowItWorks from '@/components/home/HowItWorks';
import WeeklyOffers from '@/components/home/WeeklyOffers';
import SocialFollow from '@/components/home/SocialFollow';
import ContactPreview from '@/components/home/ContactPreview';
import CTA from '@/components/home/CTA';
export default function HomeView() {
return (
<>
<Hero />
<TrustBadges />
<AboutPreview />
<CategoryGrid />
<FeaturedProducts />
<HowItWorks />
<WeeklyOffers />
<CTA />
<SocialFollow />
<ContactPreview />
</>
);
}
+4 -6
View File
@@ -57,12 +57,10 @@ export default function HowItWorks() {
</div>
<div className="text-center">
<a href={WHATSAPP_URL} target="_blank" rel="noopener noreferrer">
<Button variant="gold" size="lg">
<MessageCircle className="h-4 w-4" />
{t('howItWorks.whatsapp')}
</Button>
</a>
<Button href={WHATSAPP_URL} external variant="gold" size="lg">
<MessageCircle className="h-4 w-4" />
{t('howItWorks.whatsapp')}
</Button>
</div>
</div>
</section>
+1
View File
@@ -25,6 +25,7 @@ export default function Footer() {
{ label: t('nav.fish'), href: '/shop?category=fish' },
],
company: [
{ label: t('nav.home'), href: '/' },
{ label: t('nav.about'), href: '/about' },
{ label: t('nav.halalCert'), href: '/about#halal' },
{ label: t('nav.delivery'), href: '/about#delivery' },
+45 -19
View File
@@ -1,8 +1,10 @@
'use client';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { useState } from 'react';
import { ShoppingCart, Heart, User, Menu, X, Search } from 'lucide-react';
import { cn } from '@/lib/utils';
import { useCart } from '@/presentation/hooks/useCart';
import { useWishlist } from '@/presentation/hooks/useWishlist';
import { useAuth } from '@/presentation/hooks/useAuth';
@@ -11,6 +13,7 @@ import Logo from '@/components/ui/Logo';
import { SITE_LOCATION } from '@/lib/constants';
export default function Header() {
const pathname = usePathname();
const [mobileOpen, setMobileOpen] = useState(false);
const { summary } = useCart();
const { items: wishlistItems } = useWishlist();
@@ -19,7 +22,10 @@ export default function Header() {
const wishlistCount = wishlistItems.length;
const { t } = useTranslation();
const isHome = pathname === '/' || pathname === '/home';
const navLinks = [
{ href: '/', label: t('nav.home'), matchHome: true },
{ href: '/shop', label: t('nav.shop') },
{ href: '/shop?category=chicken', label: t('nav.chicken') },
{ href: '/shop?category=beef', label: t('nav.beef') },
@@ -46,15 +52,25 @@ export default function Header() {
</Link>
<nav className="hidden items-center gap-8 lg:flex">
{navLinks.map((link) => (
<Link
key={link.href}
href={link.href}
className="text-sm font-medium text-gray-600 transition-colors hover:text-brand-700"
>
{link.label}
</Link>
))}
{navLinks.map((link) => {
const active =
'matchHome' in link && link.matchHome
? isHome
: pathname === link.href || pathname.startsWith(`${link.href}?`);
return (
<Link
key={link.href}
href={link.href}
aria-current={active ? 'page' : undefined}
className={cn(
'text-sm font-medium transition-colors hover:text-brand-700',
active ? 'text-brand-800' : 'text-gray-600'
)}
>
{link.label}
</Link>
);
})}
</nav>
<div className="flex items-center gap-1 sm:gap-2">
@@ -114,16 +130,26 @@ export default function Header() {
{mobileOpen && (
<div className="border-t border-gray-100 bg-white lg:hidden">
<nav className="flex flex-col px-4 py-4">
{navLinks.map((link) => (
<Link
key={link.href}
href={link.href}
className="rounded-lg px-4 py-3 text-sm font-medium text-gray-600 transition-colors hover:bg-brand-50 hover:text-brand-700"
onClick={() => setMobileOpen(false)}
>
{link.label}
</Link>
))}
{navLinks.map((link) => {
const active =
'matchHome' in link && link.matchHome
? isHome
: pathname === link.href || pathname.startsWith(`${link.href}?`);
return (
<Link
key={link.href}
href={link.href}
aria-current={active ? 'page' : undefined}
className={cn(
'rounded-lg px-4 py-3 text-sm font-medium transition-colors hover:bg-brand-50 hover:text-brand-700',
active ? 'bg-brand-50 text-brand-800' : 'text-gray-600'
)}
onClick={() => setMobileOpen(false)}
>
{link.label}
</Link>
);
})}
</nav>
</div>
)}
+2 -3
View File
@@ -9,7 +9,6 @@ import { useWishlist } from '@/presentation/hooks/useWishlist';
import { useTranslation } from '@/hooks/useTranslation';
import { cn } from '@/lib/utils';
import { products } from '@/lib/products';
interface ProductCardProps {
product: LocalizedProduct;
}
@@ -22,13 +21,13 @@ export default function ProductCard({ product }: ProductCardProps) {
return (
<div className="card-premium group overflow-hidden">
<div className="relative aspect-[4/3] overflow-hidden bg-gray-50">
<div className="relative aspect-[4/3] overflow-hidden bg-white">
<Link href={`/product/${product.slug}`}>
<AppImage
src={product.image}
alt={product.name}
fill
className="object-cover transition-transform duration-500 group-hover:scale-105"
className="object-contain p-2 transition-transform duration-500 group-hover:scale-[1.03]"
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 320px"
/>
</Link>
+94 -16
View File
@@ -1,31 +1,109 @@
import Link from 'next/link';
import { cn } from '@/lib/utils';
import { ButtonHTMLAttributes, forwardRef } from 'react';
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'gold' | 'ghost';
size?: 'sm' | 'md' | 'lg';
href?: string;
external?: boolean;
}
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant = 'primary', size = 'md', children, ...props }, ref) => {
const variants = {
primary: 'btn-primary',
secondary: 'btn-secondary',
gold: 'btn-gold',
ghost:
'inline-flex items-center justify-center gap-2 rounded-lg px-4 py-2 text-sm font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-brand-700',
};
function getButtonClasses(
variant: ButtonProps['variant'],
size: ButtonProps['size'],
className?: string
) {
const variants = {
primary: 'btn-primary',
secondary: 'btn-secondary',
gold: 'btn-gold',
ghost:
'inline-flex items-center justify-center gap-2 rounded-lg px-4 py-2 text-sm font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-brand-700',
};
const sizes = {
sm: 'px-4 py-2 text-xs',
md: '',
lg: 'px-8 py-4 text-base',
};
const sizes = {
sm: 'px-4 py-2 text-xs',
md: '',
lg: 'px-8 py-4 text-base',
};
return cn(variants[variant ?? 'primary'], sizes[size ?? 'md'], className);
}
function isHttpHref(href: string) {
return /^https?:\/\//i.test(href);
}
const Button = forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonProps>(
(
{
className,
variant = 'primary',
size = 'md',
children,
href,
external,
type = 'button',
disabled,
form,
formAction,
formEncType,
formMethod,
formNoValidate,
formTarget,
name,
value,
...props
},
ref
) => {
const classes = getButtonClasses(variant, size, className);
if (href) {
const openInNewTab = external || isHttpHref(href);
const linkClassName = cn(classes, disabled && 'pointer-events-none opacity-50');
if (href.startsWith('/') && !openInNewTab) {
return (
<Link
href={href}
ref={ref as React.Ref<HTMLAnchorElement>}
className={linkClassName}
aria-disabled={disabled || undefined}
>
{children}
</Link>
);
}
return (
<a
href={disabled ? undefined : href}
ref={ref as React.Ref<HTMLAnchorElement>}
className={linkClassName}
aria-disabled={disabled || undefined}
{...(openInNewTab ? { target: '_blank' as const, rel: 'noopener noreferrer' } : {})}
>
{children}
</a>
);
}
return (
<button
ref={ref}
className={cn(variants[variant], sizes[size], className)}
ref={ref as React.Ref<HTMLButtonElement>}
type={type}
className={classes}
disabled={disabled}
form={form}
formAction={formAction}
formEncType={formEncType}
formMethod={formMethod}
formNoValidate={formNoValidate}
formTarget={formTarget}
name={name}
value={value}
{...props}
>
{children}