Authentic Nigerian home cooking — handcrafted with passion, delivered with care.
Live URL: https://chucks-kitchen-gamma.vercel.app
- Overview
- Tech Stack
- Getting Started
- Project Structure
- Screens & Features
- Reusable Component Library
- SEO & Metadata Strategy
- Design System
- Required Assets
- Known Limitations & Roadmap
- Contributing
Chuks Kitchen is a Nigerian home cooking delivery web application. Users can browse an authentic menu, explore categories, and place orders. The frontend is built with a strong focus on accessibility (WCAG 2.1 AA), SEO performance, and reusable component architecture to support future scaling.
This is a pure frontend implementation. There is no backend or database connected yet — data is currently hardcoded and ready to be swapped out for API calls.
| Technology | Version | Purpose |
|---|---|---|
| Next.js | 14 (App Router) | Framework — SSR, file-based routing, image optimisation, metadata API |
| TypeScript | 5 | Type safety — discriminated unions enforce correct component props at compile time |
| Tailwind CSS | 3 | Utility-first styling — fast to write, no unused CSS shipped |
Add these to your root layout.tsx:
<link
href="https://fonts.googleapis.com/css2?family=Island+Moments&family=Jost:wght@400;500;600&family=Poppins:wght@400;500;600&family=Inter:wght@400;500&display=swap"
rel="stylesheet"
/>| Font | Used For |
|---|---|
| Island Moments | Brand wordmark / logo |
| Jost | Section headings |
| Poppins | Body text, inputs, prices |
| Inter | Secondary labels, "View All" links |
# 1. Clone the repository
git clone https://github.com/your-org/chucks-kitchen.git
cd chucks-kitchen
# 2. Install dependencies
npm install
# 3. Start the development server
npm run devOpen http://localhost:3000 in your browser.
No environment variables are required for the current frontend-only implementation. When the backend is connected, create a .env.local file at the root:
# Example — update when API is ready
NEXT_PUBLIC_API_URL=https://api.chuckskitchen.comchucks-kitchen/
├── app/
│ ├── auth/ # Authentication screens (isolated architecture)
│ │ ├── _components/ # All auth UI components (sign-in form, sign-up form, SVGs)
│ │ ├── sign-in/
│ │ │ └── page.tsx # /auth/sign-in — noindex, build-time rendered
│ │ └── sign-up/
│ │ └── page.tsx # /auth/sign-up — noindex, build-time rendered
│ │
│ ├── home/
│ │ └── page.tsx # / — fully indexed, OG + JSON-LD configured
│ │
│ ├── explore/
│ │ └── page.tsx # /explore — fully indexed, Restaurant schema JSON-LD
│ │
│ ├── pages/ # All screen-level UI components
│ │ ├── home-screen/ # Hero, Popular Categories, Chef's Specials
│ │ ├── explore-screen/ # Discover Hero, Menu Categories dropdown, food sections
│ │ └── onboarding-screen/ # Onboarding layout and feature cards
│ │
│ ├── layout.tsx # Root layout — font imports, Navbar, global styles
│ └── globals.css # Tailwind base + custom CSS variables
│
├── components/
│ └── reusables/ # Shared component library (see section below)
│ ├── app-button.tsx
│ ├── app-card.tsx
│ ├── app-heading.tsx
│ ├── app-text-input.tsx
│ ├── auth-button.tsx
│ ├── brand-logo.tsx
│ └── navbar.tsx
│
└── public/
├── images/ # All page images (see Required Assets)
├── icons/ # Favicon variants + Apple touch icon
├── logo.png # Brand logo
├── favicon.ico
└── manifest.json # PWA manifest
Why is
authseparated? Authentication is treated as its own architecture — its own components folder, its own routing group, and its ownnoindexmetadata. This keeps auth logic completely isolated from the main app and makes it easy to swap in a full auth library (e.g. NextAuth, Clerk) later without touching any other screen.
First visit → Onboarding Screen
↓
"Start Your Order" → (authenticated?) → Home Page
→ Sign In
"Sign In" button → Sign In Screen
↓
Don't have account? → Sign Up Screen
- Full-viewport hero image with brand tagline and feature cards
- Mobile: Sign In button overlaid on image top-right
- Desktop: Left image panel + right content panel split
- CTA: Start Your Order → redirects based on auth state
- Email/phone + password form
- Password visibility toggle (show/hide)
- Forgot Password link
- Social auth: Continue with Google / Continue with Apple
- Link to Sign Up
- Email, phone number, password, confirm password fields
- Independent password and confirm-password visibility toggles
- Terms & Conditions checkbox (required before form submits)
- Social auth: Continue with Google / Continue with Apple
- Link to Sign In
- Hero section: Full-viewport food image, headline, CTA button, floating search bar
- Popular Categories: 6 food categories in a responsive grid
- Chef's Specials: Featured dishes with price and Add to Cart
- Discover Hero: Rating (4.8 ⭐, 1.2k reviews), search, location context
- Menu Categories dropdown: Accessible combobox — click a category to filter
- Popular section: Horizontal cards on mobile, vertical grid on desktop
- Jollof Rice & Entrees section
- Swallow & Soups section
All shared components live in components/reusables/. They are built for maximum reusability — no prop drilling, TypeScript-enforced variants, and native HTML attribute spreading.
Polymorphic heading with visual style decoupled from semantic tag.
<AppHeading variant="h1" as="h2" colorStyle="light" className="text-center">
Your Authentic Taste of Nigeria
</AppHeading>| Prop | Type | Default | Description |
|---|---|---|---|
variant |
h1 | h2 | h3 | h4 |
required | Controls typography size/weight |
as |
any HTML tag | same as variant |
Overrides the rendered HTML element |
colorStyle |
dark | lightDark | light |
dark |
Controls text colour |
className |
string |
'' |
Tailwind override/extension |
<AppButton variant="primary" ariaLabel="Start your order" className="w-full">
Start Your Order
</AppButton>| Prop | Type | Default | Description |
|---|---|---|---|
variant |
primary | secondary |
required | Orange fill vs white/blue outlined |
type |
button | submit | reset |
button |
HTML button type |
disabled |
boolean |
false |
Disables interaction + visual opacity |
ariaLabel |
string |
— | Screen reader label |
onClick |
() => void |
— | Click handler |
className |
string |
'' |
Tailwind override |
Three variants enforced by a TypeScript discriminated union — missing required props are caught at compile time.
// Explore variant — horizontal on mobile, vertical on desktop
<AppCard
variant="explore"
title="Jollof Rice & Fried Chicken"
imageSrc="/images/jollof.png"
description="Our signature Jollof rice..."
price="₦3,500"
onAddToCart={() => handleAddToCart(item)}
/>
// Popular variant — vertical, Explore button CTA
<AppCard variant="popular" title="Jollof Delights" imageSrc="/images/jollof.png" />
// Menu variant — vertical, price + Add to Cart CTA
<AppCard variant="menu" title="..." description="..." price="₦3,500" onAddToCart={fn} imageSrc="..." />| Variant | Required Extra Props | CTA |
|---|---|---|
popular |
none | Explore button |
menu |
description, price, onAddToCart |
Price + Add to Cart button |
explore |
description, price, onAddToCart |
Price + orange + button |
exploremobile layout: Renders as a horizontal card (image left, content right) on screens belowmd. Switches to the standard vertical card onmd+.
Extends React.InputHTMLAttributes<HTMLInputElement> — all native input props work out of the box.
<AppTextInput
label="Email"
icon={AuthSvg.mail}
placeholder="name@gmail.com"
type="email"
autoComplete="email"
error="Please enter a valid email"
/>| Prop | Type | Description |
|---|---|---|
label |
string |
Renders a <label> above the input |
icon |
React.ReactNode |
Right-aligned icon slot (supports toggle buttons) |
error |
string |
Red border + error message below input |
Social authentication button.
<AuthButton provider="google" icon={AuthSvg.google} onClick={handleGoogleAuth}>
Continue with Google
</AuthButton>| Prop | Type | Description |
|---|---|---|
provider |
google | apple |
Derives accessible aria-label |
icon |
React.ReactNode |
Provider icon (set aria-hidden on the SVG) |
onClick |
() => void |
Auth handler |
<BrandLogo size="md" className="text-center" />| Prop | Type | Default |
|---|---|---|
size |
sm | md | lg |
md |
className |
string |
'' |
Self-contained — manages its own open/close state and reads the active route internally via usePathname(). No props required.
<Navbar />Behaviours:
- Sticky (
top-0 z-40) with shadow - Mobile drawer closes on route change and Escape key
- Body scroll locked when drawer is open
aria-expanded+aria-controlson hamburger for screen readers
| Page | Indexed | OG Image | JSON-LD |
|---|---|---|---|
Home (/) |
✅ Yes | og-image.png |
— |
Explore (/explore) |
✅ Yes | og-explore.png |
Restaurant schema with aggregateRating |
Sign In (/auth/sign-in) |
❌ No | ✅ | — |
Sign Up (/auth/sign-up) |
❌ No | ✅ | — |
The title.template: "%s | Chuks Kitchen" in the home page.tsx means every child page only needs to export a short title string — the brand suffix is appended automatically by Next.js.
The Explore page JSON-LD aggregateRating enables Google rich result star snippets — the 4.8 ⭐ rating displays directly in search results below the URL.
Add these to your globals.css to replace arbitrary values:
colors: {
'primary-orange': '#FF7A18',
'primary-blue': '#1E88E5',
'input-text-color': '#3B4758',
'sub-text-on-black': '#BDBDBD',
}| Prefix | Min Width | Target |
|---|---|---|
| (none) | 0px | Mobile |
md: |
768px | Tablet + Desktop |
lg: |
1024px | Large desktop |
xl: |
1280px | Wide desktop |
Ensure these files exist in /public before deploying:
public/
├── images/
│ ├── hero-image.png # Home hero full-viewport background
│ ├── onboarding-desktop.png # Onboarding + auth left panel image
│ ├── onboarding-mobile.png # Onboarding mobile hero image
│ ├── jollof-delight.png
│ ├── swallow.png
│ ├── grills.png
│ ├── sweet-treats.png
│ ├── egusi-soup.png
│ ├── spicy.png
│ ├── eba.png
│ ├── pounded-yam.png
│ ├── peppered-snail.png
│ └── tilapia.png
├── icons/
│ ├── icon-16.png
│ ├── icon-32.png
│ └── apple-touch-icon.png # 180×180px
├── logo.png
| Area | Detail |
|---|---|
| Data | All menu items and categories are hardcoded arrays. No API connected yet. |
| Auth | Sign in / sign up forms have UI only — no authentication logic is wired up. |
| Cart | "Add to Cart" buttons trigger alert('coming soon') — no cart state exists yet. |
| Search | The hero and menu category search inputs are UI only — no filtering logic. |
| Routing | "Start Your Order" and nav links point to placeholder routes. |
- API integration — Replace hardcoded arrays with
fetchcalls to a backend REST or GraphQL API - Authentication — Implement NextAuth.js or Clerk for Google/Apple OAuth + email auth
- Dynamic item pages — Add
/explore/[slug]for individual dish detail pages - Cart & checkout — Global cart state (Zustand or Context), checkout flow, order summary
- Search & filter — Wire
MenuCategoriesdropdown and hero search to filter rendered items - Performance — Audit and reduce image payload sizes, implement
next/fontto replace Google Fonts<link>tag, addloading="lazy"wherepriorityis not needed - Animations — Add scroll-triggered reveals on section entry using Framer Motion
- Error states — Add empty states and error boundaries to all data-dependent sections
- Testing — Unit tests for reusable components (Jest + React Testing Library)
- Branch from
produsing the conventionfeature/your-feature-nameorfix/bug-description - Keep components in
components/reusables/truly generic — no screen-specific logic inside them - All new interactive elements must meet WCAG 2.1 AA —
focus-visiblestyles andaria-labelare non-negotiable - Data arrays that are currently hardcoded are intentionally at module level (outside component functions) — keep them there until API integration
- The discriminated union pattern in
AppCardis intentional — if you add a new card variant, add a new type branch; do not add optional props to existing variants - Run
npm run buildbefore opening a PR to catch TypeScript errors early