Initialized repository for project Paper presents recreation
Co-authored-by: Jeff Emmett <46964190+Jeff-Emmett@users.noreply.github.com>
|
|
@ -0,0 +1,27 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# env files
|
||||
.env*
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
# Paper presents recreation
|
||||
|
||||
*Automatically synced with your [v0.app](https://v0.app) deployments*
|
||||
|
||||
[](https://vercel.com/jeff-emmetts-projects/v0-paper-presents-recreation)
|
||||
[](https://v0.app/chat/projects/r4IOtZAcYAn)
|
||||
|
||||
## Overview
|
||||
|
||||
This repository will stay in sync with your deployed chats on [v0.app](https://v0.app).
|
||||
Any changes you make to your deployed app will be automatically pushed to this repository from [v0.app](https://v0.app).
|
||||
|
||||
## Deployment
|
||||
|
||||
Your project is live at:
|
||||
|
||||
**[https://vercel.com/jeff-emmetts-projects/v0-paper-presents-recreation](https://vercel.com/jeff-emmetts-projects/v0-paper-presents-recreation)**
|
||||
|
||||
## Build your app
|
||||
|
||||
Continue building your app on:
|
||||
|
||||
**[https://v0.app/chat/projects/r4IOtZAcYAn](https://v0.app/chat/projects/r4IOtZAcYAn)**
|
||||
|
||||
## How It Works
|
||||
|
||||
1. Create and modify your project using [v0.app](https://v0.app)
|
||||
2. Deploy your chats from the v0 interface
|
||||
3. Changes are automatically pushed to this repository
|
||||
4. Vercel deploys the latest version from this repository
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
import { notFound } from "next/navigation"
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
import { cards } from "@/data/cards"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { ChevronRight } from "lucide-react"
|
||||
|
||||
export function generateStaticParams() {
|
||||
return cards.map((card) => ({
|
||||
id: card.id,
|
||||
}))
|
||||
}
|
||||
|
||||
export default function CardDetailPage({
|
||||
params,
|
||||
}: {
|
||||
params: { id: string }
|
||||
}) {
|
||||
const card = cards.find((c) => c.id === params.id)
|
||||
|
||||
if (!card) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
const categoryName = card.category.charAt(0).toUpperCase() + card.category.slice(1)
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
{/* Breadcrumb */}
|
||||
<nav className="flex items-center gap-2 text-sm text-muted-foreground mb-8">
|
||||
<Link href="/" className="hover:text-primary transition-colors">
|
||||
Home
|
||||
</Link>
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
<Link href={`/category/${card.category}`} className="hover:text-primary transition-colors">
|
||||
{categoryName}
|
||||
</Link>
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
<span className="text-foreground">{card.name}</span>
|
||||
</nav>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-12">
|
||||
{/* Product Image */}
|
||||
<div className="space-y-4">
|
||||
<div className="relative aspect-square bg-card rounded-lg overflow-hidden border">
|
||||
<Image
|
||||
src={card.image || "/placeholder.svg"}
|
||||
alt={card.name}
|
||||
fill
|
||||
className="object-contain p-8"
|
||||
priority
|
||||
/>
|
||||
<div className="absolute top-4 right-4 bg-primary text-primary-foreground px-4 py-2 rounded-md font-semibold">
|
||||
${card.price.toFixed(2)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Product Details */}
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-4xl font-bold text-foreground mb-2">{card.name}</h1>
|
||||
<p className="text-2xl font-semibold text-primary">${card.price.toFixed(2)}</p>
|
||||
</div>
|
||||
|
||||
{/* Product Information */}
|
||||
<div className="space-y-3 text-muted-foreground">
|
||||
<p>
|
||||
<span className="font-semibold text-foreground">Order Code:</span> {card.orderCode}
|
||||
</p>
|
||||
{card.description && (
|
||||
<p>
|
||||
<span className="font-semibold text-foreground">Details:</span> {card.description}
|
||||
</p>
|
||||
)}
|
||||
<p>
|
||||
<span className="font-semibold text-foreground">Dimensions:</span> 12 cm × 12 cm
|
||||
</p>
|
||||
<p>
|
||||
<span className="font-semibold text-foreground">Weight:</span> 25 grams
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Color Selection */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="color">Available Colors:</Label>
|
||||
<Select defaultValue="red">
|
||||
<SelectTrigger id="color" className="w-full">
|
||||
<SelectValue placeholder="Select a color" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="red">Red</SelectItem>
|
||||
<SelectItem value="green">Green</SelectItem>
|
||||
<SelectItem value="blue">Blue</SelectItem>
|
||||
<SelectItem value="brown">Brown</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Quantity Selection */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="quantity">Quantity:</Label>
|
||||
<Input id="quantity" type="number" min="1" defaultValue="1" className="w-24" />
|
||||
</div>
|
||||
|
||||
{/* Add to Cart Button */}
|
||||
<Button size="lg" className="w-full">
|
||||
Add To Cart
|
||||
</Button>
|
||||
|
||||
{/* Additional Info */}
|
||||
<div className="border-t pt-6 space-y-2 text-sm text-muted-foreground">
|
||||
<p>All cards are handcrafted 3D pop-up greeting cards made with premium materials.</p>
|
||||
<p>Each card comes with a matching envelope.</p>
|
||||
<p>Satisfaction Guaranteed - Quality you can trust.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
import { PageHero } from "@/components/page-hero"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { getCardsByCategory, getCategoryBySlug, categories } from "@/data/cards"
|
||||
import { notFound } from "next/navigation"
|
||||
import Link from "next/link"
|
||||
|
||||
export function generateStaticParams() {
|
||||
return categories.map((category) => ({
|
||||
slug: category.slug,
|
||||
}))
|
||||
}
|
||||
|
||||
export default function CategoryPage({ params }: { params: { slug: string } }) {
|
||||
const category = getCategoryBySlug(params.slug)
|
||||
|
||||
if (!category) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
const categoryCards = getCardsByCategory(params.slug)
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHero
|
||||
title={category.name}
|
||||
breadcrumbs={[
|
||||
{ label: "Home", href: "/" },
|
||||
{ label: "Our Cards", href: "/our-cards" },
|
||||
]}
|
||||
/>
|
||||
|
||||
<main className="flex-1">
|
||||
<section className="py-16">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="text-center mb-12">
|
||||
<p className="text-lg text-muted-foreground max-w-3xl mx-auto">{category.description}</p>
|
||||
<p className="text-sm text-muted-foreground mt-4">
|
||||
{categoryCards.length} {categoryCards.length === 1 ? "design" : "designs"} available
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{categoryCards.map((card) => (
|
||||
<Link key={card.id} href={`/card/${card.id}`}>
|
||||
<Card className="h-full hover:shadow-lg transition-shadow group cursor-pointer">
|
||||
<CardContent className="p-0">
|
||||
<div className="aspect-[3/4] relative overflow-hidden rounded-t-lg bg-muted">
|
||||
<img
|
||||
src={card.image || "/placeholder.svg"}
|
||||
alt={card.name}
|
||||
className="object-cover w-full h-full group-hover:scale-105 transition-transform duration-300"
|
||||
/>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<div className="flex items-start justify-between gap-2 mb-2">
|
||||
<h3 className="text-lg font-semibold leading-tight">{card.name}</h3>
|
||||
{card.price > 0 ? (
|
||||
<Badge variant="secondary" className="shrink-0">
|
||||
${card.price.toFixed(2)}
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge variant="outline" className="shrink-0">
|
||||
Inquire
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mb-2">Order Code: {card.orderCode}</p>
|
||||
{card.description && (
|
||||
<p className="text-xs text-muted-foreground mt-2 italic">{card.description}</p>
|
||||
)}
|
||||
{card.variants && card.variants.length > 0 && (
|
||||
<p className="text-xs text-muted-foreground mt-2">
|
||||
Also available: {card.variants.join(", ")}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
import { PageHero } from "@/components/page-hero"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import Link from "next/link"
|
||||
import { Palette, MessageSquare, CheckCircle, Users } from "lucide-react"
|
||||
|
||||
export default function CustomCardsPage() {
|
||||
const features = [
|
||||
{
|
||||
icon: Palette,
|
||||
title: "Custom Design",
|
||||
description: "Our talented artists will work with you to create the perfect design",
|
||||
},
|
||||
{
|
||||
icon: MessageSquare,
|
||||
title: "Your Message",
|
||||
description: "Choose your font and style for a personalized message",
|
||||
},
|
||||
{
|
||||
icon: CheckCircle,
|
||||
title: "Digital Proof",
|
||||
description: "Review and approve before we send to manufacture",
|
||||
},
|
||||
{
|
||||
icon: Users,
|
||||
title: "Bulk Orders",
|
||||
description: "Wholesale discounts available for orders of 100+ cards",
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHero title="Custom Cards" breadcrumbs={[{ label: "Home", href: "/" }]} />
|
||||
|
||||
<main className="flex-1">
|
||||
<section className="py-16">
|
||||
<div className="container mx-auto px-4 max-w-4xl">
|
||||
<div className="prose prose-lg max-w-none">
|
||||
<p className="text-lg leading-relaxed mb-6">
|
||||
If you are interested in customizing any of our cards for your personal events, our talented artists are
|
||||
happy to take on any challenge.
|
||||
</p>
|
||||
|
||||
<p className="text-lg leading-relaxed mb-6">
|
||||
Whether you want a card for that special someone or a personalized invitation to your birthday, baby
|
||||
shower, graduation ceremony, or any other occasion to which you might invite your friends and family, we
|
||||
can accommodate any of your needs!
|
||||
</p>
|
||||
|
||||
<p className="text-lg leading-relaxed mb-6">
|
||||
Our design staff will work with you to provide your customized message in a font and style of your
|
||||
choosing, which will be confirmed by digital proof before being sent to manufacture.
|
||||
</p>
|
||||
|
||||
<p className="text-lg leading-relaxed mb-8">
|
||||
Due to set up and design costs, minimum orders of 50 cards must be placed for custom orders or
|
||||
additional design fees apply. Wholesale discounts apply on orders of 100 cards or more, ask for more
|
||||
details!
|
||||
</p>
|
||||
|
||||
<div className="text-center">
|
||||
<Button size="lg" asChild>
|
||||
<Link href="/contact">Ask Us</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Features Grid */}
|
||||
<section className="py-16 bg-muted">
|
||||
<div className="container mx-auto px-4">
|
||||
<h3 className="text-3xl font-bold text-center mb-12">How Custom Cards Work</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
{features.map((feature) => (
|
||||
<Card key={feature.title}>
|
||||
<CardContent className="p-6 text-center">
|
||||
<div className="bg-primary text-primary-foreground w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<feature.icon className="h-8 w-8" />
|
||||
</div>
|
||||
<h4 className="text-xl font-semibold mb-2">{feature.title}</h4>
|
||||
<p className="text-muted-foreground text-sm">{feature.description}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Occasions Section */}
|
||||
<section className="py-16">
|
||||
<div className="container mx-auto px-4">
|
||||
<h3 className="text-3xl font-bold text-center mb-8">Perfect for Any Occasion</h3>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-4 max-w-5xl mx-auto">
|
||||
{[
|
||||
"Weddings",
|
||||
"Birthdays",
|
||||
"Baby Showers",
|
||||
"Graduations",
|
||||
"Anniversaries",
|
||||
"Corporate Events",
|
||||
"Holidays",
|
||||
"Thank You Cards",
|
||||
"Invitations",
|
||||
"Save the Dates",
|
||||
"Baptisms",
|
||||
"Realtors", // Changed from "Real Estate" to "Realtors"
|
||||
].map((occasion) => (
|
||||
<div
|
||||
key={occasion}
|
||||
className="bg-card border rounded-lg p-4 text-center hover:shadow-md transition-shadow"
|
||||
>
|
||||
<p className="font-medium text-sm">{occasion}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA Section */}
|
||||
<section className="py-16 bg-secondary text-secondary-foreground">
|
||||
<div className="container mx-auto px-4 text-center">
|
||||
<h3 className="text-3xl font-bold mb-4">Ready to Create Your Custom Card?</h3>
|
||||
<p className="text-lg mb-8 max-w-2xl mx-auto opacity-90">
|
||||
Contact us today to discuss your custom card needs and get started on your unique design
|
||||
</p>
|
||||
<div className="flex gap-4 justify-center flex-wrap">
|
||||
<Button
|
||||
size="lg"
|
||||
variant="outline"
|
||||
asChild
|
||||
className="bg-secondary-foreground/10 hover:bg-secondary-foreground/20 border-secondary-foreground/20"
|
||||
>
|
||||
<Link href="/contact">Contact Us</Link>
|
||||
</Button>
|
||||
<Button
|
||||
size="lg"
|
||||
variant="outline"
|
||||
asChild
|
||||
className="bg-secondary-foreground/10 hover:bg-secondary-foreground/20 border-secondary-foreground/20"
|
||||
>
|
||||
<Link href="/our-cards">Browse Our Cards</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
@import "tailwindcss";
|
||||
@import "tw-animate-css";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
:root {
|
||||
/* Warm cream background */
|
||||
--background: oklch(0.98 0.01 85);
|
||||
--foreground: oklch(0.25 0.02 30);
|
||||
|
||||
/* Card backgrounds */
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.25 0.02 30);
|
||||
|
||||
/* Popover */
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.25 0.02 30);
|
||||
|
||||
/* Primary - Red brand color */
|
||||
--primary: oklch(0.52 0.21 25);
|
||||
--primary-foreground: oklch(0.99 0 0);
|
||||
|
||||
/* Secondary - Blue accent */
|
||||
--secondary: oklch(0.45 0.12 250);
|
||||
--secondary-foreground: oklch(0.99 0 0);
|
||||
|
||||
/* Muted */
|
||||
--muted: oklch(0.95 0.01 85);
|
||||
--muted-foreground: oklch(0.5 0.02 30);
|
||||
|
||||
/* Accent - Light blue */
|
||||
--accent: oklch(0.92 0.03 240);
|
||||
--accent-foreground: oklch(0.25 0.02 30);
|
||||
|
||||
/* Destructive */
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--destructive-foreground: oklch(0.99 0 0);
|
||||
|
||||
/* Border */
|
||||
--border: oklch(0.88 0.01 85);
|
||||
--input: oklch(0.88 0.01 85);
|
||||
--ring: oklch(0.52 0.21 25);
|
||||
|
||||
--radius: 0.375rem;
|
||||
--sidebar: oklch(1 0 0);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.205 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.97 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||
--sidebar-border: oklch(0.922 0 0);
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.145 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.145 0 0);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.985 0 0);
|
||||
--primary-foreground: oklch(0.205 0 0);
|
||||
--secondary: oklch(0.269 0 0);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.269 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.396 0.141 25.723);
|
||||
--destructive-foreground: oklch(0.637 0.237 25.331);
|
||||
--border: oklch(0.269 0 0);
|
||||
--input: oklch(0.269 0 0);
|
||||
--ring: oklch(0.439 0 0);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.205 0 0);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.269 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(0.269 0 0);
|
||||
--sidebar-ring: oklch(0.439 0 0);
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-destructive-foreground: var(--destructive-foreground);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-5: var(--chart-5);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import type React from "react"
|
||||
import type { Metadata } from "next"
|
||||
import { GeistSans } from "geist/font/sans"
|
||||
import { GeistMono } from "geist/font/mono"
|
||||
import { Analytics } from "@vercel/analytics/next"
|
||||
import { Suspense } from "react"
|
||||
import { SiteHeader } from "@/components/site-header"
|
||||
import { SiteFooter } from "@/components/site-footer"
|
||||
import "./globals.css"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Paper Presents - Canadian Greeting Cards",
|
||||
description: "Quality greeting cards for every occasion, satisfaction guaranteed",
|
||||
generator: "v0.app",
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={`font-sans ${GeistSans.variable} ${GeistMono.variable}`}>
|
||||
<SiteHeader />
|
||||
<Suspense fallback={null}>{children}</Suspense>
|
||||
<SiteFooter />
|
||||
<Analytics />
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
import { PageHero } from "@/components/page-hero"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import Link from "next/link"
|
||||
import { categories, getCardsByCategory } from "@/data/cards"
|
||||
|
||||
export default function OurCardsPage() {
|
||||
const categoryData = categories.map((category) => ({
|
||||
...category,
|
||||
count: getCardsByCategory(category.slug).length,
|
||||
image: `/category-${category.slug}.jpg`,
|
||||
href: `/category/${category.slug}`,
|
||||
}))
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHero title="Our Cards" breadcrumbs={[{ label: "Home", href: "/" }]} />
|
||||
|
||||
<main className="flex-1">
|
||||
<section className="py-16">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="text-center mb-12">
|
||||
<p className="text-lg text-muted-foreground max-w-3xl mx-auto">
|
||||
Browse our extensive collection of greeting cards for every occasion. Each card is thoughtfully designed
|
||||
and printed on premium paper stock.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{categoryData.map((category) => (
|
||||
<Link key={category.name} href={category.href}>
|
||||
<Card className="h-full hover:shadow-lg transition-shadow group">
|
||||
<CardContent className="p-0">
|
||||
<div className="aspect-square relative overflow-hidden rounded-t-lg">
|
||||
<img
|
||||
src={category.image || "/placeholder.svg"}
|
||||
alt={category.name}
|
||||
className="object-cover w-full h-full group-hover:scale-105 transition-transform duration-300"
|
||||
/>
|
||||
</div>
|
||||
<div className="p-6">
|
||||
<h3 className="text-xl font-semibold mb-2 group-hover:text-primary transition-colors">
|
||||
{category.name}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground">{category.count} designs available</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Featured Section */}
|
||||
<section className="py-16 bg-muted">
|
||||
<div className="container mx-auto px-4">
|
||||
<h3 className="text-3xl font-bold text-center mb-12">Why Our Cards Stand Out</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 max-w-5xl mx-auto">
|
||||
<div className="text-center">
|
||||
<div className="text-4xl mb-4">🇨🇦</div>
|
||||
<h4 className="text-xl font-semibold mb-2">Satisfaction Guaranteed</h4>
|
||||
<p className="text-muted-foreground">We stand behind the quality of every card we sell</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-4xl mb-4">🎨</div>
|
||||
<h4 className="text-xl font-semibold mb-2">Unique Designs</h4>
|
||||
<p className="text-muted-foreground">Original artwork created by talented Canadian artists</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-4xl mb-4">♻️</div>
|
||||
<h4 className="text-xl font-semibold mb-2">Eco-Friendly</h4>
|
||||
<p className="text-muted-foreground">
|
||||
Printed on recycled paper with environmentally conscious practices
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA Section */}
|
||||
<section className="py-16">
|
||||
<div className="container mx-auto px-4 text-center">
|
||||
<h3 className="text-3xl font-bold mb-4">Need Something Custom?</h3>
|
||||
<p className="text-lg text-muted-foreground mb-8 max-w-2xl mx-auto">
|
||||
We can create personalized cards for your special events and occasions
|
||||
</p>
|
||||
<Button size="lg" asChild>
|
||||
<Link href="/custom-cards">Learn About Custom Cards</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import Link from "next/link"
|
||||
import { Heart, Gift, Sparkles } from "lucide-react"
|
||||
|
||||
export default function HomePage() {
|
||||
const featuredCategories = [
|
||||
{
|
||||
title: "Birthday Cards",
|
||||
description: "Celebrate special moments with our unique birthday designs",
|
||||
icon: Gift,
|
||||
href: "/category/birthday",
|
||||
image: "/cards/birthday/BDY-001.jpg",
|
||||
},
|
||||
{
|
||||
title: "Wedding Cards",
|
||||
description: "Elegant designs for your special day",
|
||||
icon: Heart,
|
||||
href: "/category/wedding",
|
||||
image: "/cards/wedding/WED-001.jpg",
|
||||
},
|
||||
{
|
||||
title: "3D Monuments",
|
||||
description: "Unique 3D cards featuring iconic world landmarks",
|
||||
icon: Sparkles,
|
||||
href: "/category/monuments",
|
||||
image: "/cards/monuments/MON-001.jpg",
|
||||
},
|
||||
{
|
||||
title: "Valentine's Day",
|
||||
description: "Share love and romance with our Valentine collection",
|
||||
icon: Heart,
|
||||
href: "/category/valentines",
|
||||
image: "/cards/valentines/VAL-003.jpg",
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<main className="flex-1">
|
||||
{/* Hero Section */}
|
||||
<section className="bg-secondary text-secondary-foreground py-20">
|
||||
<div className="container mx-auto px-4 text-center">
|
||||
<h2 className="text-5xl font-bold mb-6 text-balance">Welcome to Paper Presents</h2>
|
||||
<p className="text-xl mb-8 max-w-2xl mx-auto text-balance opacity-90">
|
||||
Discover our collection of beautifully crafted greeting cards for every occasion. Satisfaction Guaranteed
|
||||
with every purchase.
|
||||
</p>
|
||||
<div className="flex gap-4 justify-center flex-wrap">
|
||||
<Button size="lg" asChild>
|
||||
<Link href="/our-cards">Browse Our Cards</Link>
|
||||
</Button>
|
||||
<Button
|
||||
size="lg"
|
||||
variant="outline"
|
||||
asChild
|
||||
className="bg-secondary-foreground/10 hover:bg-secondary-foreground/20 border-secondary-foreground/20"
|
||||
>
|
||||
<Link href="/custom-cards">Create Custom Card</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Featured Categories */}
|
||||
<section className="py-16">
|
||||
<div className="container mx-auto px-4">
|
||||
<h3 className="text-3xl font-bold text-center mb-12">Shop by Category</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{featuredCategories.map((category) => (
|
||||
<Link key={category.title} href={category.href}>
|
||||
<Card className="h-full hover:shadow-lg transition-shadow">
|
||||
<CardContent className="p-0">
|
||||
<div className="aspect-[4/3] relative overflow-hidden rounded-t-lg">
|
||||
<img
|
||||
src={category.image || "/placeholder.svg"}
|
||||
alt={category.title}
|
||||
className="object-cover w-full h-full"
|
||||
/>
|
||||
</div>
|
||||
<div className="p-6">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<category.icon className="h-6 w-6 text-primary" />
|
||||
<h4 className="text-xl font-semibold">{category.title}</h4>
|
||||
</div>
|
||||
<p className="text-muted-foreground text-sm">{category.description}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Why Choose Us */}
|
||||
<section className="py-16 bg-muted">
|
||||
<div className="container mx-auto px-4">
|
||||
<h3 className="text-3xl font-bold text-center mb-12">Why Choose Paper Presents?</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 max-w-5xl mx-auto">
|
||||
<div className="text-center">
|
||||
<div className="bg-primary text-primary-foreground w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<Heart className="h-8 w-8" />
|
||||
</div>
|
||||
<h4 className="text-xl font-semibold mb-2">Quality Craftsmanship</h4>
|
||||
<p className="text-muted-foreground">
|
||||
Each card is carefully designed and printed with premium materials
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="bg-primary text-primary-foreground w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<Sparkles className="h-8 w-8" />
|
||||
</div>
|
||||
<h4 className="text-xl font-semibold mb-2">Custom Options</h4>
|
||||
<p className="text-muted-foreground">Personalize any card to make it uniquely yours</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="bg-primary text-primary-foreground w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<Gift className="h-8 w-8" />
|
||||
</div>
|
||||
<h4 className="text-xl font-semibold mb-2">Satisfaction Guaranteed</h4>
|
||||
<p className="text-muted-foreground">We stand behind the quality of every card we sell</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA Section */}
|
||||
<section className="py-16">
|
||||
<div className="container mx-auto px-4 text-center">
|
||||
<h3 className="text-3xl font-bold mb-4">Ready to Find the Perfect Card?</h3>
|
||||
<p className="text-lg text-muted-foreground mb-8 max-w-2xl mx-auto">
|
||||
Browse our extensive collection or create a custom design that's uniquely yours
|
||||
</p>
|
||||
<Button size="lg" asChild>
|
||||
<Link href="/our-cards">Start Shopping</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
import { SiteHeader } from "@/components/site-header"
|
||||
import { SiteFooter } from "@/components/site-footer"
|
||||
import { PageHero } from "@/components/page-hero"
|
||||
import Link from "next/link"
|
||||
import Image from "next/image"
|
||||
|
||||
const categories = [
|
||||
{
|
||||
name: "Birthday",
|
||||
slug: "birthday",
|
||||
image: "/cards/birthday/BDY-001.jpg",
|
||||
description: "Celebrate with our pop-up birthday cards",
|
||||
},
|
||||
{
|
||||
name: "Christmas",
|
||||
slug: "christmas",
|
||||
image: "/cards/christmas/CHR-001.jpg",
|
||||
description: "Festive 3D Christmas greetings",
|
||||
},
|
||||
{
|
||||
name: "Valentine's Day",
|
||||
slug: "valentines-day",
|
||||
image: "/cards/valentines/VAL-001.jpg",
|
||||
description: "Express your love with our Valentine cards",
|
||||
},
|
||||
{
|
||||
name: "Wedding",
|
||||
slug: "wedding",
|
||||
image: "/cards/wedding/WED-001.jpg",
|
||||
description: "Elegant wedding celebration cards",
|
||||
},
|
||||
{
|
||||
name: "Baby",
|
||||
slug: "baby",
|
||||
image: "/cards/baby/BBY-001.jpg",
|
||||
description: "Welcome the new arrival",
|
||||
},
|
||||
{
|
||||
name: "3D Monuments",
|
||||
slug: "monuments",
|
||||
image: "/cards/monuments/MON-001.jpg",
|
||||
description: "Iconic landmarks in 3D",
|
||||
},
|
||||
{
|
||||
name: "Sentiments",
|
||||
slug: "sentiments",
|
||||
image: "/cards/sentiments/SEN-001.jpg",
|
||||
description: "Thank you, get well, and more",
|
||||
},
|
||||
{
|
||||
name: "Specialty",
|
||||
slug: "specialty",
|
||||
image: "/cards/specialty/SPC-002.jpg",
|
||||
description: "Unique cards for special moments",
|
||||
},
|
||||
]
|
||||
|
||||
export default function YourOccasionsPage() {
|
||||
return (
|
||||
<>
|
||||
<SiteHeader />
|
||||
<PageHero title="Your Occasions" breadcrumbs={[{ label: "Home", href: "/" }]} />
|
||||
|
||||
<div className="container mx-auto px-4 py-12">
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-8">
|
||||
{categories.map((category) => (
|
||||
<Link key={category.slug} href={`/category/${category.slug}`} className="group">
|
||||
<div className="relative aspect-[150/185] overflow-hidden rounded-lg border-2 border-border hover:border-primary transition-colors">
|
||||
<Image
|
||||
src={category.image || "/placeholder.svg"}
|
||||
alt={category.name}
|
||||
fill
|
||||
className="object-cover group-hover:scale-105 transition-transform duration-300"
|
||||
/>
|
||||
</div>
|
||||
<h3 className="mt-3 text-center font-semibold text-lg group-hover:text-primary transition-colors">
|
||||
{category.name}
|
||||
</h3>
|
||||
<p className="text-center text-sm text-muted-foreground mt-1">{category.description}</p>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<SiteFooter />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": true,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "",
|
||||
"css": "app/globals.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"iconLibrary": "lucide"
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
interface PageHeroProps {
|
||||
title: string
|
||||
breadcrumbs?: { label: string; href: string }[]
|
||||
}
|
||||
|
||||
export function PageHero({ title, breadcrumbs }: PageHeroProps) {
|
||||
return (
|
||||
<div className="bg-secondary text-secondary-foreground py-12">
|
||||
<div className="container mx-auto px-4">
|
||||
<h1 className="text-4xl font-bold text-center mb-4">{title}</h1>
|
||||
{breadcrumbs && (
|
||||
<nav className="flex items-center justify-center gap-2 text-sm">
|
||||
{breadcrumbs.map((crumb, index) => (
|
||||
<div key={crumb.href} className="flex items-center gap-2">
|
||||
<a href={crumb.href} className="hover:underline opacity-90">
|
||||
{crumb.label}
|
||||
</a>
|
||||
{index < breadcrumbs.length - 1 && <span className="opacity-70">»</span>}
|
||||
</div>
|
||||
))}
|
||||
<span className="opacity-70">»</span>
|
||||
<span className="font-medium">{title}</span>
|
||||
</nav>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import Link from "next/link"
|
||||
import Image from "next/image"
|
||||
|
||||
export function SiteFooter() {
|
||||
const categories = [
|
||||
{ name: "3D Monuments", href: "/category/3d-monuments" },
|
||||
{ name: "Baby", href: "/category/baby" },
|
||||
{ name: "Basket", href: "/category/basket" },
|
||||
{ name: "Birthday", href: "/category/birthday" },
|
||||
{ name: "Christmas", href: "/category/christmas" },
|
||||
{ name: "My Account", href: "/account" },
|
||||
{ name: "Sentiments", href: "/category/sentiments" },
|
||||
{ name: "Shop", href: "/shop" },
|
||||
{ name: "Specialty", href: "/category/specialty" },
|
||||
{ name: "Valentines Day", href: "/category/valentines" },
|
||||
{ name: "Wedding Cards", href: "/category/wedding" },
|
||||
]
|
||||
|
||||
return (
|
||||
<footer className="border-t bg-card mt-16">
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<div className="flex justify-center mb-6">
|
||||
<Link href="/">
|
||||
<Image src="/logo.png" alt="Paper Presents" width={240} height={43} className="h-10 w-auto" />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<nav className="flex flex-wrap items-center justify-center gap-4 text-sm">
|
||||
{categories.map((category, index) => (
|
||||
<div key={category.name} className="flex items-center gap-4">
|
||||
<Link href={category.href} className="hover:text-primary transition-colors">
|
||||
{category.name}
|
||||
</Link>
|
||||
{index < categories.length - 1 && <span className="text-muted-foreground">•</span>}
|
||||
</div>
|
||||
))}
|
||||
</nav>
|
||||
<div className="text-center mt-6 text-sm text-muted-foreground">
|
||||
Paper Presents | © {new Date().getFullYear()}
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
import Link from "next/link"
|
||||
import Image from "next/image"
|
||||
import { Search, ShoppingCart } from "lucide-react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
|
||||
export function SiteHeader() {
|
||||
return (
|
||||
<header className="border-b bg-card">
|
||||
<div className="container mx-auto px-4">
|
||||
{/* Top bar with logo and utility links */}
|
||||
<div className="flex items-center justify-between py-4">
|
||||
<Link href="/" className="flex items-center">
|
||||
<Image src="/logo.png" alt="Paper Presents" width={280} height={50} className="h-12 w-auto" priority />
|
||||
</Link>
|
||||
|
||||
<nav className="hidden md:flex items-center gap-6">
|
||||
<Link href="/our-promise" className="text-sm hover:text-primary transition-colors">
|
||||
Our Promise
|
||||
</Link>
|
||||
<Link href="/faq" className="text-sm hover:text-primary transition-colors">
|
||||
FAQ
|
||||
</Link>
|
||||
<Link href="/about" className="text-sm hover:text-primary transition-colors">
|
||||
About Us
|
||||
</Link>
|
||||
<Link href="/contact" className="text-sm hover:text-primary transition-colors">
|
||||
Contact Us
|
||||
</Link>
|
||||
</nav>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="hidden md:flex items-center gap-2">
|
||||
<Input type="search" placeholder="Search..." className="w-48" />
|
||||
<Button size="icon" variant="ghost">
|
||||
<Search className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<Button size="icon" variant="ghost" className="relative">
|
||||
<ShoppingCart className="h-5 w-5" />
|
||||
<span className="absolute -top-1 -right-1 bg-primary text-primary-foreground text-xs rounded-full h-5 w-5 flex items-center justify-center">
|
||||
0
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main navigation */}
|
||||
<nav className="border-t py-3">
|
||||
<ul className="flex flex-wrap items-center justify-center gap-6 text-sm">
|
||||
<li></li>
|
||||
|
||||
<li>
|
||||
<Link href="/our-cards" className="hover:text-primary transition-colors font-medium">
|
||||
Our Cards
|
||||
</Link>
|
||||
</li>
|
||||
<li className="text-muted-foreground">|</li>
|
||||
<li>
|
||||
<Link href="/your-occasions" className="hover:text-primary transition-colors font-medium">
|
||||
Your Occasions
|
||||
</Link>
|
||||
</li>
|
||||
<li className="text-muted-foreground">|</li>
|
||||
<li>
|
||||
<Link href="/custom-cards" className="hover:text-primary transition-colors font-medium">
|
||||
Custom Cards
|
||||
</Link>
|
||||
</li>
|
||||
<li className="text-muted-foreground">|</li>
|
||||
<li>
|
||||
<Link href="/wholesale" className="hover:text-primary transition-colors font-medium">
|
||||
Wholesale Orders
|
||||
</Link>
|
||||
</li>
|
||||
<li className="ml-auto">
|
||||
<Button asChild className="bg-primary hover:bg-primary/90 text-primary-foreground">
|
||||
<Link href="/checkout">Checkout</Link>
|
||||
</Button>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import {
|
||||
ThemeProvider as NextThemesProvider,
|
||||
type ThemeProviderProps,
|
||||
} from 'next-themes'
|
||||
|
||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import * as React from 'react'
|
||||
import { Slot } from '@radix-ui/react-slot'
|
||||
import { cva, type VariantProps } from 'class-variance-authority'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const badgeVariants = cva(
|
||||
'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90',
|
||||
secondary:
|
||||
'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90',
|
||||
destructive:
|
||||
'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
|
||||
outline:
|
||||
'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
function Badge({
|
||||
className,
|
||||
variant,
|
||||
asChild = false,
|
||||
...props
|
||||
}: React.ComponentProps<'span'> &
|
||||
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
||||
const Comp = asChild ? Slot : 'span'
|
||||
|
||||
return (
|
||||
<Comp
|
||||
data-slot="badge"
|
||||
className={cn(badgeVariants({ variant }), className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Badge, badgeVariants }
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
import * as React from 'react'
|
||||
import { Slot } from '@radix-ui/react-slot'
|
||||
import { cva, type VariantProps } from 'class-variance-authority'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
||||
destructive:
|
||||
'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
|
||||
outline:
|
||||
'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
|
||||
secondary:
|
||||
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
||||
ghost:
|
||||
'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
|
||||
link: 'text-primary underline-offset-4 hover:underline',
|
||||
},
|
||||
size: {
|
||||
default: 'h-9 px-4 py-2 has-[>svg]:px-3',
|
||||
sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
|
||||
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
|
||||
icon: 'size-9',
|
||||
'icon-sm': 'size-8',
|
||||
'icon-lg': 'size-10',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
size: 'default',
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
function Button({
|
||||
className,
|
||||
variant,
|
||||
size,
|
||||
asChild = false,
|
||||
...props
|
||||
}: React.ComponentProps<'button'> &
|
||||
VariantProps<typeof buttonVariants> & {
|
||||
asChild?: boolean
|
||||
}) {
|
||||
const Comp = asChild ? Slot : 'button'
|
||||
|
||||
return (
|
||||
<Comp
|
||||
data-slot="button"
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Button, buttonVariants }
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
import * as React from 'react'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
function Card({ className, ...props }: React.ComponentProps<'div'>) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card"
|
||||
className={cn(
|
||||
'bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardHeader({ className, ...props }: React.ComponentProps<'div'>) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-header"
|
||||
className={cn(
|
||||
'@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardTitle({ className, ...props }: React.ComponentProps<'div'>) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-title"
|
||||
className={cn('leading-none font-semibold', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardDescription({ className, ...props }: React.ComponentProps<'div'>) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-description"
|
||||
className={cn('text-muted-foreground text-sm', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardAction({ className, ...props }: React.ComponentProps<'div'>) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-action"
|
||||
className={cn(
|
||||
'col-start-2 row-span-2 row-start-1 self-start justify-self-end',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardContent({ className, ...props }: React.ComponentProps<'div'>) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-content"
|
||||
className={cn('px-6', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardFooter({ className, ...props }: React.ComponentProps<'div'>) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-footer"
|
||||
className={cn('flex items-center px-6 [.border-t]:pt-6', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardFooter,
|
||||
CardTitle,
|
||||
CardAction,
|
||||
CardDescription,
|
||||
CardContent,
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import * as React from 'react'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
function Input({ className, type, ...props }: React.ComponentProps<'input'>) {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
data-slot="input"
|
||||
className={cn(
|
||||
'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
|
||||
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
|
||||
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Input }
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import * as LabelPrimitive from '@radix-ui/react-label'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
function Label({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
||||
return (
|
||||
<LabelPrimitive.Root
|
||||
data-slot="label"
|
||||
className={cn(
|
||||
'flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Label }
|
||||
|
|
@ -0,0 +1,185 @@
|
|||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import * as SelectPrimitive from '@radix-ui/react-select'
|
||||
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from 'lucide-react'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
function Select({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
|
||||
return <SelectPrimitive.Root data-slot="select" {...props} />
|
||||
}
|
||||
|
||||
function SelectGroup({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
|
||||
return <SelectPrimitive.Group data-slot="select-group" {...props} />
|
||||
}
|
||||
|
||||
function SelectValue({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
|
||||
return <SelectPrimitive.Value data-slot="select-value" {...props} />
|
||||
}
|
||||
|
||||
function SelectTrigger({
|
||||
className,
|
||||
size = 'default',
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
|
||||
size?: 'sm' | 'default'
|
||||
}) {
|
||||
return (
|
||||
<SelectPrimitive.Trigger
|
||||
data-slot="select-trigger"
|
||||
data-size={size}
|
||||
className={cn(
|
||||
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SelectPrimitive.Icon asChild>
|
||||
<ChevronDownIcon className="size-4 opacity-50" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
)
|
||||
}
|
||||
|
||||
function SelectContent({
|
||||
className,
|
||||
children,
|
||||
position = 'popper',
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
|
||||
return (
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Content
|
||||
data-slot="select-content"
|
||||
className={cn(
|
||||
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md',
|
||||
position === 'popper' &&
|
||||
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
|
||||
className,
|
||||
)}
|
||||
position={position}
|
||||
{...props}
|
||||
>
|
||||
<SelectScrollUpButton />
|
||||
<SelectPrimitive.Viewport
|
||||
className={cn(
|
||||
'p-1',
|
||||
position === 'popper' &&
|
||||
'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1',
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</SelectPrimitive.Viewport>
|
||||
<SelectScrollDownButton />
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
)
|
||||
}
|
||||
|
||||
function SelectLabel({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Label>) {
|
||||
return (
|
||||
<SelectPrimitive.Label
|
||||
data-slot="select-label"
|
||||
className={cn('text-muted-foreground px-2 py-1.5 text-xs', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function SelectItem({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
|
||||
return (
|
||||
<SelectPrimitive.Item
|
||||
data-slot="select-item"
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute right-2 flex size-3.5 items-center justify-center">
|
||||
<SelectPrimitive.ItemIndicator>
|
||||
<CheckIcon className="size-4" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
)
|
||||
}
|
||||
|
||||
function SelectSeparator({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
|
||||
return (
|
||||
<SelectPrimitive.Separator
|
||||
data-slot="select-separator"
|
||||
className={cn('bg-border pointer-events-none -mx-1 my-1 h-px', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function SelectScrollUpButton({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
|
||||
return (
|
||||
<SelectPrimitive.ScrollUpButton
|
||||
data-slot="select-scroll-up-button"
|
||||
className={cn(
|
||||
'flex cursor-default items-center justify-center py-1',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronUpIcon className="size-4" />
|
||||
</SelectPrimitive.ScrollUpButton>
|
||||
)
|
||||
}
|
||||
|
||||
function SelectScrollDownButton({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
|
||||
return (
|
||||
<SelectPrimitive.ScrollDownButton
|
||||
data-slot="select-scroll-down-button"
|
||||
className={cn(
|
||||
'flex cursor-default items-center justify-center py-1',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronDownIcon className="size-4" />
|
||||
</SelectPrimitive.ScrollDownButton>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectLabel,
|
||||
SelectScrollDownButton,
|
||||
SelectScrollUpButton,
|
||||
SelectSeparator,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
}
|
||||
|
|
@ -0,0 +1,845 @@
|
|||
export interface Card {
|
||||
id: string
|
||||
name: string
|
||||
orderCode: string
|
||||
price: number
|
||||
category: string
|
||||
image: string
|
||||
description?: string
|
||||
variants?: string[]
|
||||
}
|
||||
|
||||
export const cards: Card[] = [
|
||||
// Baby Cards
|
||||
{
|
||||
id: "bby-001",
|
||||
name: "Baby Congratulations",
|
||||
orderCode: "BBY-001",
|
||||
price: 6.99,
|
||||
category: "baby",
|
||||
image: "/cards/baby/BBY-001.jpg",
|
||||
},
|
||||
{
|
||||
id: "bby-002",
|
||||
name: "New Baby Celebration",
|
||||
orderCode: "BBY-002",
|
||||
price: 6.99,
|
||||
category: "baby",
|
||||
image: "/cards/baby/BBY-002.jpg",
|
||||
},
|
||||
{
|
||||
id: "bby-003",
|
||||
name: "It's a Girl",
|
||||
orderCode: "BBY-003",
|
||||
price: 5.99,
|
||||
category: "baby",
|
||||
image: "/cards/baby/BBY-003.jpg",
|
||||
variants: ["It's a Boy - BBY-004"],
|
||||
},
|
||||
{
|
||||
id: "bby-004",
|
||||
name: "It's a Boy",
|
||||
orderCode: "BBY-004",
|
||||
price: 5.99,
|
||||
category: "baby",
|
||||
image: "/cards/baby/BBY-004.jpg",
|
||||
},
|
||||
{
|
||||
id: "bby-005",
|
||||
name: "Welcome Baby",
|
||||
orderCode: "BBY-005",
|
||||
price: 5.99,
|
||||
category: "baby",
|
||||
image: "/cards/baby/BBY-005.jpg",
|
||||
},
|
||||
|
||||
// Birthday Cards
|
||||
{
|
||||
id: "bdy-001",
|
||||
name: "Cake",
|
||||
orderCode: "BDY-001",
|
||||
price: 6.99,
|
||||
category: "birthday",
|
||||
image: "/cards/birthday/BDY-001.jpg",
|
||||
description: 'Card Inscription: "Happy Birthday"',
|
||||
},
|
||||
{
|
||||
id: "bdy-002",
|
||||
name: "Piano",
|
||||
orderCode: "BDY-002",
|
||||
price: 6.99,
|
||||
category: "birthday",
|
||||
image: "/cards/birthday/BDY-002.jpg",
|
||||
description: 'Card Inscription: "Happy Birthday"',
|
||||
},
|
||||
{
|
||||
id: "bdy-003",
|
||||
name: "Birthday Star",
|
||||
orderCode: "BDY-003",
|
||||
price: 6.99,
|
||||
category: "birthday",
|
||||
image: "/cards/birthday/BDY-003.jpg",
|
||||
},
|
||||
{
|
||||
id: "bdy-004",
|
||||
name: "Train",
|
||||
orderCode: "BDY-004",
|
||||
price: 6.99,
|
||||
category: "birthday",
|
||||
image: "/cards/birthday/BDY-004.jpg",
|
||||
description: 'Card Inscription: "Happy Birthday"',
|
||||
},
|
||||
{
|
||||
id: "bdy-005",
|
||||
name: "Hummingbird",
|
||||
orderCode: "BDY-005",
|
||||
price: 6.99,
|
||||
category: "birthday",
|
||||
image: "/cards/birthday/BDY-005.jpg",
|
||||
description: 'Card Inscription: "Happy Birthday"',
|
||||
},
|
||||
{
|
||||
id: "bdy-006",
|
||||
name: "Fortress",
|
||||
orderCode: "BDY-006",
|
||||
price: 7.99,
|
||||
category: "birthday",
|
||||
image: "/cards/birthday/BDY-006.jpg",
|
||||
description: 'Card Inscription: "Happy Birthday!"',
|
||||
},
|
||||
{
|
||||
id: "bdy-007",
|
||||
name: "Ferris Wheel",
|
||||
orderCode: "BDY-007",
|
||||
price: 7.99,
|
||||
category: "birthday",
|
||||
image: "/cards/birthday/BDY-007.jpg",
|
||||
},
|
||||
{
|
||||
id: "bdy-008",
|
||||
name: "Train 1",
|
||||
orderCode: "BDY-008",
|
||||
price: 6.99,
|
||||
category: "birthday",
|
||||
image: "/cards/birthday/BDY-008.jpg",
|
||||
},
|
||||
{
|
||||
id: "bdy-009",
|
||||
name: "Sailboat",
|
||||
orderCode: "BDY-009",
|
||||
price: 6.99,
|
||||
category: "birthday",
|
||||
image: "/cards/birthday/BDY-009.jpg",
|
||||
description: 'Card Inscription: "Happy Birthday!"',
|
||||
},
|
||||
{
|
||||
id: "bdy-010",
|
||||
name: "Presents",
|
||||
orderCode: "BDY-010",
|
||||
price: 6.99,
|
||||
category: "birthday",
|
||||
image: "/cards/birthday/BDY-010.jpg",
|
||||
description: 'Card Inscription: "Happy Birthday"',
|
||||
},
|
||||
{
|
||||
id: "bdy-011",
|
||||
name: "Train 2",
|
||||
orderCode: "BDY-011",
|
||||
price: 6.99,
|
||||
category: "birthday",
|
||||
image: "/cards/birthday/BDY-011.jpg",
|
||||
description: 'Card Inscription: "Happy Birthday!"',
|
||||
},
|
||||
{
|
||||
id: "bdy-012",
|
||||
name: "Owl",
|
||||
orderCode: "BDY-012",
|
||||
price: 6.99,
|
||||
category: "birthday",
|
||||
image: "/cards/birthday/BDY-012.jpg",
|
||||
description: 'Card Inscription: "Happy Birthday"',
|
||||
},
|
||||
{
|
||||
id: "bdy-013",
|
||||
name: "Bday Bear",
|
||||
orderCode: "BDY-013",
|
||||
price: 6.99,
|
||||
category: "birthday",
|
||||
image: "/cards/birthday/BDY-013.jpg",
|
||||
},
|
||||
{
|
||||
id: "bdy-014",
|
||||
name: "Cake 2",
|
||||
orderCode: "BDY-014",
|
||||
price: 7.99,
|
||||
category: "birthday",
|
||||
image: "/cards/birthday/BDY-014.jpg",
|
||||
},
|
||||
{
|
||||
id: "bdy-015",
|
||||
name: "Cat Love",
|
||||
orderCode: "BDY-015",
|
||||
price: 6.99,
|
||||
category: "birthday",
|
||||
image: "/cards/birthday/BDY-015.jpg",
|
||||
description: 'Card Inscription: "Happy Birthday"',
|
||||
},
|
||||
{
|
||||
id: "bdy-016",
|
||||
name: "Gift Tractor",
|
||||
orderCode: "BDY-016",
|
||||
price: 6.99,
|
||||
category: "birthday",
|
||||
image: "/cards/birthday/BDY-016.jpg",
|
||||
},
|
||||
|
||||
// Christmas Cards
|
||||
{
|
||||
id: "chr-001",
|
||||
name: "Christmas Greetings",
|
||||
orderCode: "CHR-001",
|
||||
price: 5.99,
|
||||
category: "christmas",
|
||||
image: "/cards/christmas/CHR-001.jpg",
|
||||
},
|
||||
{
|
||||
id: "chr-002",
|
||||
name: "Holiday Wishes",
|
||||
orderCode: "CHR-002",
|
||||
price: 5.99,
|
||||
category: "christmas",
|
||||
image: "/cards/christmas/CHR-002.jpg",
|
||||
},
|
||||
{
|
||||
id: "chr-003",
|
||||
name: "Merry Christmas",
|
||||
orderCode: "CHR-003",
|
||||
price: 5.99,
|
||||
category: "christmas",
|
||||
image: "/cards/christmas/CHR-003.jpg",
|
||||
},
|
||||
{
|
||||
id: "chr-004",
|
||||
name: "Season's Greetings",
|
||||
orderCode: "CHR-004",
|
||||
price: 5.99,
|
||||
category: "christmas",
|
||||
image: "/cards/christmas/CHR-004.jpg",
|
||||
},
|
||||
{
|
||||
id: "chr-005",
|
||||
name: "Christmas Joy",
|
||||
orderCode: "CHR-005",
|
||||
price: 5.99,
|
||||
category: "christmas",
|
||||
image: "/cards/christmas/CHR-005.jpg",
|
||||
},
|
||||
{
|
||||
id: "chr-006",
|
||||
name: "Holiday Cheer",
|
||||
orderCode: "CHR-006",
|
||||
price: 5.99,
|
||||
category: "christmas",
|
||||
image: "/cards/christmas/CHR-006.jpg",
|
||||
},
|
||||
{
|
||||
id: "chr-007",
|
||||
name: "Christmas Blessings",
|
||||
orderCode: "CHR-007",
|
||||
price: 5.99,
|
||||
category: "christmas",
|
||||
image: "/cards/christmas/CHR-007.jpg",
|
||||
},
|
||||
{
|
||||
id: "chr-008",
|
||||
name: "Winter Wishes",
|
||||
orderCode: "CHR-008",
|
||||
price: 5.99,
|
||||
category: "christmas",
|
||||
image: "/cards/christmas/CHR-008.jpg",
|
||||
},
|
||||
{
|
||||
id: "chr-009",
|
||||
name: "Christmas Magic",
|
||||
orderCode: "CHR-009",
|
||||
price: 5.99,
|
||||
category: "christmas",
|
||||
image: "/cards/christmas/CHR-009.jpg",
|
||||
},
|
||||
{
|
||||
id: "chr-010",
|
||||
name: "Holiday Spirit",
|
||||
orderCode: "CHR-010",
|
||||
price: 6.99,
|
||||
category: "christmas",
|
||||
image: "/cards/christmas/CHR-010.jpg",
|
||||
},
|
||||
{
|
||||
id: "chr-011",
|
||||
name: "Christmas Wonder",
|
||||
orderCode: "CHR-011",
|
||||
price: 5.99,
|
||||
category: "christmas",
|
||||
image: "/cards/christmas/CHR-011.jpg",
|
||||
},
|
||||
{
|
||||
id: "chr-012",
|
||||
name: "Festive Greetings",
|
||||
orderCode: "CHR-012",
|
||||
price: 6.99,
|
||||
category: "christmas",
|
||||
image: "/cards/christmas/CHR-012.jpg",
|
||||
},
|
||||
{
|
||||
id: "chr-013",
|
||||
name: "Christmas Peace",
|
||||
orderCode: "CHR-013",
|
||||
price: 5.99,
|
||||
category: "christmas",
|
||||
image: "/cards/christmas/CHR-013.jpg",
|
||||
},
|
||||
{
|
||||
id: "chr-014",
|
||||
name: "Holiday Happiness",
|
||||
orderCode: "CHR-014",
|
||||
price: 5.99,
|
||||
category: "christmas",
|
||||
image: "/cards/christmas/CHR-014.jpg",
|
||||
},
|
||||
{
|
||||
id: "chr-015",
|
||||
name: "Christmas Elegance",
|
||||
orderCode: "CHR-015",
|
||||
price: 6.99,
|
||||
category: "christmas",
|
||||
image: "/cards/christmas/CHR-015.jpg",
|
||||
},
|
||||
{
|
||||
id: "chr-016",
|
||||
name: "Winter Celebration",
|
||||
orderCode: "CHR-016",
|
||||
price: 5.99,
|
||||
category: "christmas",
|
||||
image: "/cards/christmas/CHR-016.jpg",
|
||||
},
|
||||
{
|
||||
id: "chr-017",
|
||||
name: "Christmas Delight",
|
||||
orderCode: "CHR-017",
|
||||
price: 5.99,
|
||||
category: "christmas",
|
||||
image: "/cards/christmas/CHR-017.jpg",
|
||||
},
|
||||
{
|
||||
id: "chr-018",
|
||||
name: "Holiday Joy",
|
||||
orderCode: "CHR-018",
|
||||
price: 5.99,
|
||||
category: "christmas",
|
||||
image: "/cards/christmas/CHR-018.jpg",
|
||||
},
|
||||
{
|
||||
id: "chr-019",
|
||||
name: "Christmas Sparkle",
|
||||
orderCode: "CHR-019",
|
||||
price: 5.99,
|
||||
category: "christmas",
|
||||
image: "/cards/christmas/CHR-019.jpg",
|
||||
},
|
||||
{
|
||||
id: "chr-020",
|
||||
name: "Festive Season",
|
||||
orderCode: "CHR-020",
|
||||
price: 5.99,
|
||||
category: "christmas",
|
||||
image: "/cards/christmas/CHR-020.jpg",
|
||||
},
|
||||
{
|
||||
id: "chr-021",
|
||||
name: "Christmas Warmth",
|
||||
orderCode: "CHR-021",
|
||||
price: 5.99,
|
||||
category: "christmas",
|
||||
image: "/cards/christmas/CHR-021.jpg",
|
||||
},
|
||||
{
|
||||
id: "chr-022",
|
||||
name: "Holiday Elegance",
|
||||
orderCode: "CHR-022",
|
||||
price: 6.99,
|
||||
category: "christmas",
|
||||
image: "/cards/christmas/CHR-022.jpg",
|
||||
},
|
||||
{
|
||||
id: "chr-023",
|
||||
name: "Christmas Charm",
|
||||
orderCode: "CHR-023",
|
||||
price: 5.99,
|
||||
category: "christmas",
|
||||
image: "/cards/christmas/CHR-023.jpg",
|
||||
},
|
||||
{
|
||||
id: "chr-024",
|
||||
name: "Winter Blessings",
|
||||
orderCode: "CHR-024",
|
||||
price: 5.99,
|
||||
category: "christmas",
|
||||
image: "/cards/christmas/CHR-024.jpg",
|
||||
},
|
||||
|
||||
// Monuments Cards
|
||||
{
|
||||
id: "mon-001",
|
||||
name: "Notre Dame",
|
||||
orderCode: "MON-001",
|
||||
price: 9.99,
|
||||
category: "monuments",
|
||||
image: "/cards/monuments/MON-001.jpg",
|
||||
description: 'Card Inscription: "Notre Dame"',
|
||||
},
|
||||
{
|
||||
id: "mon-002",
|
||||
name: "Petronas Towers",
|
||||
orderCode: "MON-002",
|
||||
price: 7.99,
|
||||
category: "monuments",
|
||||
image: "/cards/monuments/MON-002.jpg",
|
||||
description: 'Card Inscription: "Malaysia"',
|
||||
},
|
||||
{
|
||||
id: "mon-003",
|
||||
name: "Le Louvre",
|
||||
orderCode: "MON-003",
|
||||
price: 7.99,
|
||||
category: "monuments",
|
||||
image: "/cards/monuments/MON-003.jpg",
|
||||
description: 'Card Inscription: "Le Louvre"',
|
||||
},
|
||||
{
|
||||
id: "mon-004",
|
||||
name: "Coliseum",
|
||||
orderCode: "MON-004",
|
||||
price: 7.99,
|
||||
category: "monuments",
|
||||
image: "/cards/monuments/MON-004.jpg",
|
||||
description: "Card Inscription: none",
|
||||
},
|
||||
{
|
||||
id: "mon-005",
|
||||
name: "Eiffel Tower",
|
||||
orderCode: "MON-005",
|
||||
price: 7.99,
|
||||
category: "monuments",
|
||||
image: "/cards/monuments/MON-005.jpg",
|
||||
description: 'Card Inscription: "France"',
|
||||
},
|
||||
{
|
||||
id: "mon-006",
|
||||
name: "Sydney Opera House",
|
||||
orderCode: "MON-006",
|
||||
price: 6.99,
|
||||
category: "monuments",
|
||||
image: "/cards/monuments/MON-006.jpg",
|
||||
description: "Card Inscription: none",
|
||||
},
|
||||
{
|
||||
id: "mon-007",
|
||||
name: "Arc de Triomphe",
|
||||
orderCode: "MON-007",
|
||||
price: 6.99,
|
||||
category: "monuments",
|
||||
image: "/cards/monuments/MON-007.jpg",
|
||||
description: "Card Inscription: none",
|
||||
},
|
||||
{
|
||||
id: "mon-008",
|
||||
name: "Ancient Library",
|
||||
orderCode: "MON-008",
|
||||
price: 6.99,
|
||||
category: "monuments",
|
||||
image: "/cards/monuments/MON-008.jpg",
|
||||
description: "Card Inscription: none",
|
||||
},
|
||||
|
||||
// Sentiments Cards
|
||||
{
|
||||
id: "sen-001",
|
||||
name: "Thank You",
|
||||
orderCode: "SEN-001",
|
||||
price: 5.99,
|
||||
category: "sentiments",
|
||||
image: "/cards/sentiments/SEN-001.jpg",
|
||||
description: "Available: Thank you, Get Well Soon & Happy Mother's Day",
|
||||
},
|
||||
{
|
||||
id: "sen-002",
|
||||
name: "Get Well Soon",
|
||||
orderCode: "SEN-002",
|
||||
price: 5.99,
|
||||
category: "sentiments",
|
||||
image: "/cards/sentiments/SEN-002.jpg",
|
||||
description: "Available: Thank you, Get Well Soon & Happy Mother's Day",
|
||||
},
|
||||
{
|
||||
id: "sen-003",
|
||||
name: "Happy Mother's Day",
|
||||
orderCode: "SEN-003",
|
||||
price: 5.99,
|
||||
category: "sentiments",
|
||||
image: "/cards/sentiments/SEN-003.jpg",
|
||||
description: "Available: Thank you, Get Well Soon & Happy Mother's Day",
|
||||
},
|
||||
{
|
||||
id: "sen-004",
|
||||
name: "Thank You Collection",
|
||||
orderCode: "SEN-004",
|
||||
price: 5.99,
|
||||
category: "sentiments",
|
||||
image: "/cards/sentiments/SEN-004.jpg",
|
||||
description: "Available: Thank you, Get Well Soon & Happy Mother's Day",
|
||||
},
|
||||
{
|
||||
id: "sen-005",
|
||||
name: "Get Well Collection",
|
||||
orderCode: "SEN-005",
|
||||
price: 5.99,
|
||||
category: "sentiments",
|
||||
image: "/cards/sentiments/SEN-005.jpg",
|
||||
description: "Available: Thank you, Get Well Soon & Happy Mother's Day",
|
||||
},
|
||||
{
|
||||
id: "sen-006",
|
||||
name: "Mother's Day Collection",
|
||||
orderCode: "SEN-006",
|
||||
price: 5.99,
|
||||
category: "sentiments",
|
||||
image: "/cards/sentiments/SEN-006.jpg",
|
||||
description: "Available: Thank you, Get Well Soon & Happy Mother's Day",
|
||||
},
|
||||
{
|
||||
id: "sen-007",
|
||||
name: "Heartfelt Sentiments",
|
||||
orderCode: "SEN-007",
|
||||
price: 5.99,
|
||||
category: "sentiments",
|
||||
image: "/cards/sentiments/SEN-007.jpg",
|
||||
},
|
||||
{
|
||||
id: "sen-008",
|
||||
name: "Warm Wishes",
|
||||
orderCode: "SEN-008",
|
||||
price: 5.99,
|
||||
category: "sentiments",
|
||||
image: "/cards/sentiments/SEN-008.jpg",
|
||||
},
|
||||
{
|
||||
id: "sen-009",
|
||||
name: "Special Thoughts",
|
||||
orderCode: "SEN-009",
|
||||
price: 5.99,
|
||||
category: "sentiments",
|
||||
image: "/cards/sentiments/SEN-009.jpg",
|
||||
},
|
||||
|
||||
// Specialty Cards
|
||||
{
|
||||
id: "spc-002",
|
||||
name: "Special Occasion",
|
||||
orderCode: "SPC-002",
|
||||
price: 5.99,
|
||||
category: "specialty",
|
||||
image: "/cards/specialty/SPC-002.jpg",
|
||||
},
|
||||
{
|
||||
id: "spc-004",
|
||||
name: "Unique Design",
|
||||
orderCode: "SPC-004",
|
||||
price: 5.99,
|
||||
category: "specialty",
|
||||
image: "/cards/specialty/SPC-004.jpg",
|
||||
},
|
||||
{
|
||||
id: "spc-005",
|
||||
name: "Premium Specialty",
|
||||
orderCode: "SPC-005",
|
||||
price: 6.99,
|
||||
category: "specialty",
|
||||
image: "/cards/specialty/SPC-005.jpg",
|
||||
},
|
||||
{
|
||||
id: "spc-006",
|
||||
name: "Elegant Specialty",
|
||||
orderCode: "SPC-006",
|
||||
price: 6.99,
|
||||
category: "specialty",
|
||||
image: "/cards/specialty/SPC-006.jpg",
|
||||
},
|
||||
{
|
||||
id: "spc-007",
|
||||
name: "Special Moments",
|
||||
orderCode: "SPC-007",
|
||||
price: 5.99,
|
||||
category: "specialty",
|
||||
image: "/cards/specialty/SPC-007.jpg",
|
||||
},
|
||||
{
|
||||
id: "spc-008",
|
||||
name: "Distinctive Design",
|
||||
orderCode: "SPC-008",
|
||||
price: 6.99,
|
||||
category: "specialty",
|
||||
image: "/cards/specialty/SPC-008.jpg",
|
||||
},
|
||||
{
|
||||
id: "spc-009",
|
||||
name: "Specialty Collection",
|
||||
orderCode: "SPC-009",
|
||||
price: 0.99,
|
||||
category: "specialty",
|
||||
image: "/cards/specialty/SPC-009.jpg",
|
||||
},
|
||||
|
||||
// Valentine's Day Cards
|
||||
{
|
||||
id: "val-001",
|
||||
name: "Valentine Love",
|
||||
orderCode: "VAL-001",
|
||||
price: 5.99,
|
||||
category: "valentines",
|
||||
image: "/cards/valentines/VAL-001.jpg",
|
||||
},
|
||||
{
|
||||
id: "val-002",
|
||||
name: "Be My Valentine",
|
||||
orderCode: "VAL-002",
|
||||
price: 5.99,
|
||||
category: "valentines",
|
||||
image: "/cards/valentines/VAL-002.jpg",
|
||||
},
|
||||
{
|
||||
id: "val-003",
|
||||
name: "Love & Romance",
|
||||
orderCode: "VAL-003",
|
||||
price: 6.99,
|
||||
category: "valentines",
|
||||
image: "/cards/valentines/VAL-003.jpg",
|
||||
},
|
||||
{
|
||||
id: "val-004",
|
||||
name: "Valentine Wishes",
|
||||
orderCode: "VAL-004",
|
||||
price: 6.99,
|
||||
category: "valentines",
|
||||
image: "/cards/valentines/VAL-004.jpg",
|
||||
},
|
||||
{
|
||||
id: "val-005",
|
||||
name: "Sweet Valentine",
|
||||
orderCode: "VAL-005",
|
||||
price: 6.99,
|
||||
category: "valentines",
|
||||
image: "/cards/valentines/VAL-005.jpg",
|
||||
},
|
||||
{
|
||||
id: "val-006",
|
||||
name: "Valentine Joy",
|
||||
orderCode: "VAL-006",
|
||||
price: 5.99,
|
||||
category: "valentines",
|
||||
image: "/cards/valentines/VAL-006.jpg",
|
||||
},
|
||||
{
|
||||
id: "val-007",
|
||||
name: "Love Always",
|
||||
orderCode: "VAL-007",
|
||||
price: 5.99,
|
||||
category: "valentines",
|
||||
image: "/cards/valentines/VAL-007.jpg",
|
||||
},
|
||||
{
|
||||
id: "val-008",
|
||||
name: "Valentine Hearts",
|
||||
orderCode: "VAL-008",
|
||||
price: 5.99,
|
||||
category: "valentines",
|
||||
image: "/cards/valentines/VAL-008.jpg",
|
||||
},
|
||||
{
|
||||
id: "val-009",
|
||||
name: "Romantic Wishes",
|
||||
orderCode: "VAL-009",
|
||||
price: 5.99,
|
||||
category: "valentines",
|
||||
image: "/cards/valentines/VAL-009.jpg",
|
||||
},
|
||||
{
|
||||
id: "val-010",
|
||||
name: "Valentine Bliss",
|
||||
orderCode: "VAL-010",
|
||||
price: 5.99,
|
||||
category: "valentines",
|
||||
image: "/cards/valentines/VAL-010.jpg",
|
||||
},
|
||||
{
|
||||
id: "val-011",
|
||||
name: "Love & Happiness",
|
||||
orderCode: "VAL-011",
|
||||
price: 5.99,
|
||||
category: "valentines",
|
||||
image: "/cards/valentines/VAL-011.jpg",
|
||||
},
|
||||
{
|
||||
id: "val-012",
|
||||
name: "Valentine Charm",
|
||||
orderCode: "VAL-012",
|
||||
price: 5.99,
|
||||
category: "valentines",
|
||||
image: "/cards/valentines/VAL-012.jpg",
|
||||
},
|
||||
{
|
||||
id: "val-013",
|
||||
name: "Sweetheart",
|
||||
orderCode: "VAL-013",
|
||||
price: 5.99,
|
||||
category: "valentines",
|
||||
image: "/cards/valentines/VAL-013.jpg",
|
||||
},
|
||||
{
|
||||
id: "val-014",
|
||||
name: "Valentine Delight",
|
||||
orderCode: "VAL-014",
|
||||
price: 5.99,
|
||||
category: "valentines",
|
||||
image: "/cards/valentines/VAL-014.jpg",
|
||||
},
|
||||
{
|
||||
id: "val-015",
|
||||
name: "Love Notes",
|
||||
orderCode: "VAL-015",
|
||||
price: 5.99,
|
||||
category: "valentines",
|
||||
image: "/cards/valentines/VAL-015.jpg",
|
||||
},
|
||||
{
|
||||
id: "val-016",
|
||||
name: "Valentine Elegance",
|
||||
orderCode: "VAL-016",
|
||||
price: 6.99,
|
||||
category: "valentines",
|
||||
image: "/cards/valentines/VAL-016.jpg",
|
||||
},
|
||||
{
|
||||
id: "val-017",
|
||||
name: "Forever Love",
|
||||
orderCode: "VAL-017",
|
||||
price: 5.99,
|
||||
category: "valentines",
|
||||
image: "/cards/valentines/VAL-017.jpg",
|
||||
},
|
||||
|
||||
// Wedding Cards
|
||||
{
|
||||
id: "wed-001",
|
||||
name: "Wedding Congratulations",
|
||||
orderCode: "WED-001",
|
||||
price: 5.99,
|
||||
category: "wedding",
|
||||
image: "/cards/wedding/WED-001.jpg",
|
||||
},
|
||||
{
|
||||
id: "wed-002",
|
||||
name: "Happy Wedding Day",
|
||||
orderCode: "WED-002",
|
||||
price: 5.99,
|
||||
category: "wedding",
|
||||
image: "/cards/wedding/WED-002.jpg",
|
||||
},
|
||||
{
|
||||
id: "wed-003",
|
||||
name: "Wedding Wishes",
|
||||
orderCode: "WED-003",
|
||||
price: 5.99,
|
||||
category: "wedding",
|
||||
image: "/cards/wedding/WED-003.jpg",
|
||||
},
|
||||
{
|
||||
id: "wed-004",
|
||||
name: "Wedding Celebration",
|
||||
orderCode: "WED-004",
|
||||
price: 5.99,
|
||||
category: "wedding",
|
||||
image: "/cards/wedding/WED-004.jpg",
|
||||
},
|
||||
{
|
||||
id: "wed-005",
|
||||
name: "Custom Wedding Card",
|
||||
orderCode: "WED-005",
|
||||
price: 0,
|
||||
category: "wedding",
|
||||
image: "/cards/wedding/WED-005.jpg",
|
||||
description: "Custom messages available - Please inquire for pricing",
|
||||
},
|
||||
{
|
||||
id: "wed-006",
|
||||
name: "Wedding Bliss",
|
||||
orderCode: "WED-006",
|
||||
price: 5.99,
|
||||
category: "wedding",
|
||||
image: "/cards/wedding/WED-006.jpg",
|
||||
},
|
||||
]
|
||||
|
||||
export const categories = [
|
||||
{
|
||||
slug: "baby",
|
||||
name: "Baby",
|
||||
description: "Celebrate new arrivals with our adorable baby cards",
|
||||
},
|
||||
{
|
||||
slug: "birthday",
|
||||
name: "Birthday",
|
||||
description: "Make their special day memorable with our birthday collection",
|
||||
},
|
||||
{
|
||||
slug: "christmas",
|
||||
name: "Christmas",
|
||||
description: "Spread holiday cheer with our festive Christmas cards",
|
||||
},
|
||||
{
|
||||
slug: "monuments",
|
||||
name: "3D Monuments",
|
||||
description: "Unique 3D cards featuring iconic landmarks",
|
||||
},
|
||||
{
|
||||
slug: "sentiments",
|
||||
name: "Sentiments",
|
||||
description: "Express your feelings with our heartfelt sentiment cards",
|
||||
},
|
||||
{
|
||||
slug: "specialty",
|
||||
name: "Specialty",
|
||||
description: "Distinctive designs for unique occasions",
|
||||
},
|
||||
{
|
||||
slug: "valentines",
|
||||
name: "Valentine's Day",
|
||||
description: "Share love and romance with our Valentine collection",
|
||||
},
|
||||
{
|
||||
slug: "wedding",
|
||||
name: "Wedding",
|
||||
description: "Congratulate the happy couple with elegant wedding cards",
|
||||
},
|
||||
]
|
||||
|
||||
export function getCardsByCategory(category: string): Card[] {
|
||||
return cards.filter((card) => card.category === category)
|
||||
}
|
||||
|
||||
export function getCategoryBySlug(slug: string) {
|
||||
return categories.find((cat) => cat.slug === slug)
|
||||
}
|
||||
|
||||
export function getCardById(id: string): Card | undefined {
|
||||
return cards.find((card) => card.id === id)
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import { clsx, type ClassValue } from 'clsx'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
eslint: {
|
||||
ignoreDuringBuilds: true,
|
||||
},
|
||||
typescript: {
|
||||
ignoreBuildErrors: true,
|
||||
},
|
||||
images: {
|
||||
unoptimized: true,
|
||||
},
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
{
|
||||
"name": "my-v0-project",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "next build",
|
||||
"dev": "next dev",
|
||||
"lint": "next lint",
|
||||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.10.0",
|
||||
"@radix-ui/react-accordion": "1.2.2",
|
||||
"@radix-ui/react-alert-dialog": "1.1.4",
|
||||
"@radix-ui/react-aspect-ratio": "1.1.1",
|
||||
"@radix-ui/react-avatar": "1.1.2",
|
||||
"@radix-ui/react-checkbox": "1.1.3",
|
||||
"@radix-ui/react-collapsible": "1.1.2",
|
||||
"@radix-ui/react-context-menu": "2.2.4",
|
||||
"@radix-ui/react-dialog": "1.1.4",
|
||||
"@radix-ui/react-dropdown-menu": "2.1.4",
|
||||
"@radix-ui/react-hover-card": "1.1.4",
|
||||
"@radix-ui/react-label": "2.1.1",
|
||||
"@radix-ui/react-menubar": "1.1.4",
|
||||
"@radix-ui/react-navigation-menu": "1.2.3",
|
||||
"@radix-ui/react-popover": "1.1.4",
|
||||
"@radix-ui/react-progress": "1.1.1",
|
||||
"@radix-ui/react-radio-group": "1.2.2",
|
||||
"@radix-ui/react-scroll-area": "1.2.2",
|
||||
"@radix-ui/react-select": "2.1.4",
|
||||
"@radix-ui/react-separator": "1.1.1",
|
||||
"@radix-ui/react-slider": "1.2.2",
|
||||
"@radix-ui/react-slot": "1.1.1",
|
||||
"@radix-ui/react-switch": "1.1.2",
|
||||
"@radix-ui/react-tabs": "1.1.2",
|
||||
"@radix-ui/react-toast": "1.2.4",
|
||||
"@radix-ui/react-toggle": "1.1.1",
|
||||
"@radix-ui/react-toggle-group": "1.1.1",
|
||||
"@radix-ui/react-tooltip": "1.1.6",
|
||||
"@vercel/analytics": "latest",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "1.0.4",
|
||||
"date-fns": "4.1.0",
|
||||
"embla-carousel-react": "8.5.1",
|
||||
"geist": "latest",
|
||||
"input-otp": "1.4.1",
|
||||
"lucide-react": "^0.454.0",
|
||||
"next": "15.2.4",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "^19",
|
||||
"react-day-picker": "9.8.0",
|
||||
"react-dom": "^19",
|
||||
"react-hook-form": "^7.60.0",
|
||||
"react-resizable-panels": "^2.1.7",
|
||||
"recharts": "2.15.4",
|
||||
"sonner": "^1.7.4",
|
||||
"tailwind-merge": "^2.5.5",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"vaul": "^0.9.9",
|
||||
"zod": "3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4.1.9",
|
||||
"@types/node": "^22",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"postcss": "^8.5",
|
||||
"tailwindcss": "^4.1.9",
|
||||
"tw-animate-css": "1.3.3",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
/** @type {import('postcss-load-config').Config} */
|
||||
const config = {
|
||||
plugins: {
|
||||
'@tailwindcss/postcss': {},
|
||||
},
|
||||
}
|
||||
|
||||
export default config
|
||||
|
After Width: | Height: | Size: 242 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 99 KiB |
|
After Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 171 KiB |
|
After Width: | Height: | Size: 184 KiB |
|
After Width: | Height: | Size: 123 KiB |
|
After Width: | Height: | Size: 126 KiB |
|
After Width: | Height: | Size: 172 KiB |
|
After Width: | Height: | Size: 135 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |