create-ecom-app 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/index.js +101 -0
  2. package/package.json +25 -0
  3. package/templates/ecommerce/app/about/page.tsx +152 -0
  4. package/templates/ecommerce/app/cart/page.tsx +103 -0
  5. package/templates/ecommerce/app/checkout/page.tsx +126 -0
  6. package/templates/ecommerce/app/contact/page.tsx +124 -0
  7. package/templates/ecommerce/app/globals.css +137 -0
  8. package/templates/ecommerce/app/layout.tsx +31 -0
  9. package/templates/ecommerce/app/login/page.tsx +224 -0
  10. package/templates/ecommerce/app/not-found.tsx +14 -0
  11. package/templates/ecommerce/app/orders/[id]/page.tsx +138 -0
  12. package/templates/ecommerce/app/page.tsx +215 -0
  13. package/templates/ecommerce/app/product/[id]/page.tsx +417 -0
  14. package/templates/ecommerce/app/products/page.tsx +244 -0
  15. package/templates/ecommerce/app/profile/page.tsx +273 -0
  16. package/templates/ecommerce/app/register/page.tsx +221 -0
  17. package/templates/ecommerce/app/wishlist/page.tsx +89 -0
  18. package/templates/ecommerce/components/CartItem.tsx +74 -0
  19. package/templates/ecommerce/components/CheckoutForm.tsx +109 -0
  20. package/templates/ecommerce/components/Filters.tsx +142 -0
  21. package/templates/ecommerce/components/Footer.tsx +100 -0
  22. package/templates/ecommerce/components/Header.tsx +194 -0
  23. package/templates/ecommerce/components/ProductCard.tsx +158 -0
  24. package/templates/ecommerce/components/SearchBar.tsx +41 -0
  25. package/templates/ecommerce/data/products.json +427 -0
  26. package/templates/ecommerce/lib/localStorage.ts +27 -0
  27. package/templates/ecommerce/lib/utils.ts +25 -0
  28. package/templates/ecommerce/next-env.d.ts +5 -0
  29. package/templates/ecommerce/next.config.js +13 -0
  30. package/templates/ecommerce/next.config.ts +14 -0
  31. package/templates/ecommerce/package.json +30 -0
  32. package/templates/ecommerce/postcss.config.js +7 -0
  33. package/templates/ecommerce/public/images/about-2.jpg +0 -0
  34. package/templates/ecommerce/public/images/contact-bg.jpg +0 -0
  35. package/templates/ecommerce/public/images/hero-bg.jpg +0 -0
  36. package/templates/ecommerce/public/images/products/product-1.jpg +0 -0
  37. package/templates/ecommerce/public/images/products/product-10.jpg +0 -0
  38. package/templates/ecommerce/public/images/products/product-11.jpg +0 -0
  39. package/templates/ecommerce/public/images/products/product-12.jpg +0 -0
  40. package/templates/ecommerce/public/images/products/product-13.jpg +0 -0
  41. package/templates/ecommerce/public/images/products/product-14.jpg +0 -0
  42. package/templates/ecommerce/public/images/products/product-15.jpg +0 -0
  43. package/templates/ecommerce/public/images/products/product-16.jpg +0 -0
  44. package/templates/ecommerce/public/images/products/product-17.jpg +0 -0
  45. package/templates/ecommerce/public/images/products/product-18.jpg +0 -0
  46. package/templates/ecommerce/public/images/products/product-19.jpg +0 -0
  47. package/templates/ecommerce/public/images/products/product-2.jpg +0 -0
  48. package/templates/ecommerce/public/images/products/product-20.jpg +0 -0
  49. package/templates/ecommerce/public/images/products/product-21.jpg +0 -0
  50. package/templates/ecommerce/public/images/products/product-22.jpg +0 -0
  51. package/templates/ecommerce/public/images/products/product-23.jpg +0 -0
  52. package/templates/ecommerce/public/images/products/product-24.jpg +0 -0
  53. package/templates/ecommerce/public/images/products/product-25.jpg +0 -0
  54. package/templates/ecommerce/public/images/products/product-3.jpg +0 -0
  55. package/templates/ecommerce/public/images/products/product-4.jpg +0 -0
  56. package/templates/ecommerce/public/images/products/product-5.jpg +0 -0
  57. package/templates/ecommerce/public/images/products/product-6.jpg +0 -0
  58. package/templates/ecommerce/public/images/products/product-7.jpg +0 -0
  59. package/templates/ecommerce/public/images/products/product-8.jpg +0 -0
  60. package/templates/ecommerce/public/images/products/product-9.jpg +0 -0
  61. package/templates/ecommerce/public/service-worker.js +1 -0
  62. package/templates/ecommerce/store/useAuthStore.ts +56 -0
  63. package/templates/ecommerce/store/useCartStore.ts +63 -0
  64. package/templates/ecommerce/store/useOrderStore.ts +43 -0
  65. package/templates/ecommerce/store/useReviewStore.ts +60 -0
  66. package/templates/ecommerce/store/useWishlistStore.ts +43 -0
  67. package/templates/ecommerce/tailwind.config.ts +44 -0
  68. package/templates/ecommerce/tsconfig.json +22 -0
  69. package/templates/ecommerce/types/index.ts +69 -0
package/index.js ADDED
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env node
2
+
3
+ 'use strict';
4
+
5
+ const prompts = require('prompts');
6
+ const fs = require('fs-extra');
7
+ const path = require('path');
8
+ const chalk = require('chalk');
9
+ const ora = require('ora');
10
+
11
+ const TEMPLATE_DIR = path.join(__dirname, 'templates', 'ecommerce');
12
+
13
+ async function main() {
14
+ console.log('');
15
+ console.log(chalk.bold.cyan(' ╔══════════════════════════════════╗'));
16
+ console.log(chalk.bold.cyan(' ║ 🛒 create-ecom-app v1.0 ║'));
17
+ console.log(chalk.bold.cyan(' ╚══════════════════════════════════╝'));
18
+ console.log('');
19
+
20
+ // Get project name from args or prompt
21
+ let projectName = process.argv[2];
22
+
23
+ if (!projectName) {
24
+ const response = await prompts({
25
+ type: 'text',
26
+ name: 'name',
27
+ message: 'Project name:',
28
+ initial: 'my-store',
29
+ validate: (v) => v.length > 0 || 'Please enter a project name',
30
+ });
31
+ if (!response.name) {
32
+ console.log(chalk.red('\n ✗ Cancelled.'));
33
+ process.exit(1);
34
+ }
35
+ projectName = response.name;
36
+ }
37
+
38
+ const targetDir = path.resolve(process.cwd(), projectName);
39
+
40
+ // Validate: folder must not already exist
41
+ if (fs.existsSync(targetDir)) {
42
+ console.log(chalk.red(`\n ✗ Folder "${projectName}" already exists. Choose a different name.\n`));
43
+ process.exit(1);
44
+ }
45
+
46
+ // Check template exists
47
+ if (!fs.existsSync(TEMPLATE_DIR)) {
48
+ console.log(chalk.red('\n ✗ Template not found. Please reinstall create-ecom-app.\n'));
49
+ process.exit(1);
50
+ }
51
+
52
+ console.log('');
53
+ console.log(chalk.gray(` Creating project in: ${chalk.white(targetDir)}`));
54
+ console.log('');
55
+
56
+ // Copy template
57
+ const copySpinner = ora(chalk.cyan(' Copying template files...')).start();
58
+ try {
59
+ const EXCLUDE = ['node_modules', '.next', '.turbo', 'dist'];
60
+ await fs.copy(TEMPLATE_DIR, targetDir, {
61
+ filter: (src) => {
62
+ const relative = path.relative(TEMPLATE_DIR, src);
63
+ return !EXCLUDE.some((ex) => relative.startsWith(ex) || relative === ex);
64
+ },
65
+ });
66
+ // Update package.json name field
67
+ const pkgPath = path.join(targetDir, 'package.json');
68
+ if (fs.existsSync(pkgPath)) {
69
+ const pkg = await fs.readJson(pkgPath);
70
+ pkg.name = projectName;
71
+ await fs.writeJson(pkgPath, pkg, { spaces: 2 });
72
+ }
73
+ copySpinner.succeed(chalk.green(' Template files copied!'));
74
+ } catch (err) {
75
+ copySpinner.fail(chalk.red(' Failed to copy template.'));
76
+ console.error(err);
77
+ process.exit(1);
78
+ }
79
+
80
+ // Skipping auto npm install — let user run it (faster UX, no stuck spinner)
81
+ console.log(chalk.green(' ✔ Template files ready! '));
82
+
83
+ // Success output
84
+ console.log('');
85
+ console.log(chalk.bold.green(' ✅ Project created: ') + chalk.bold.white(projectName));
86
+ console.log('');
87
+ console.log(chalk.bold(' Next steps:'));
88
+ console.log(chalk.cyan(` cd ${projectName}`));
89
+ console.log(chalk.cyan(' npm install'));
90
+ console.log(chalk.cyan(' npm run dev'));
91
+ console.log('');
92
+ console.log(chalk.gray(' Open: ') + chalk.underline.blue('http://localhost:3000'));
93
+ console.log('');
94
+ console.log(chalk.bold.magenta(' 🚀 Happy building!'));
95
+ console.log('');
96
+ }
97
+
98
+ main().catch((err) => {
99
+ console.error(chalk.red('\n Unexpected error:'), err);
100
+ process.exit(1);
101
+ });
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "create-ecom-app",
3
+ "version": "1.0.0",
4
+ "description": "Zero-config CLI to bootstrap a full-featured Next.js ecommerce app",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "create-ecom-app": "./index.js"
8
+ },
9
+ "files": [
10
+ "index.js",
11
+ "templates/"
12
+ ],
13
+ "scripts": {
14
+ "test": "node index.js test-store"
15
+ },
16
+ "dependencies": {
17
+ "chalk": "^4.1.2",
18
+ "fs-extra": "^11.2.0",
19
+ "ora": "^5.4.1",
20
+ "prompts": "^2.4.2"
21
+ },
22
+ "keywords": ["ecommerce", "nextjs", "cli", "scaffold", "starter"],
23
+ "author": "Antigravity",
24
+ "license": "MIT"
25
+ }
@@ -0,0 +1,152 @@
1
+ import type { Metadata } from 'next';
2
+ import Image from 'next/image';
3
+ import Link from 'next/link';
4
+ import { Target, Leaf, Zap, Heart, ArrowRight, Users, Package, Star } from 'lucide-react';
5
+
6
+ export const metadata: Metadata = {
7
+ title: 'About Us',
8
+ description: 'Learn about GenZ Shop — our story, mission and the team behind it.',
9
+ };
10
+
11
+ const values = [
12
+ { icon: Target, title: 'Quality First', desc: 'Every product vetted for quality before listing.' },
13
+ { icon: Leaf, title: 'Sustainability', desc: 'Eco-conscious brands and recyclable packaging.' },
14
+ { icon: Zap, title: 'Fast Delivery', desc: 'Orders delivered in 2-3 business days.' },
15
+ { icon: Heart, title: 'Customer First', desc: '30-day hassle-free return policy.' },
16
+ ];
17
+
18
+ const stats = [
19
+ { icon: Package, val: '50K+', label: 'Orders Shipped' },
20
+ { icon: Users, val: '98%', label: 'Satisfaction Rate' },
21
+ { icon: Star, val: '4.9', label: 'Average Rating' },
22
+ ];
23
+
24
+ const team = [
25
+ { name: 'Arjun Mehta', role: 'Founder & CEO', color: 'bg-violet-100 text-violet-700' },
26
+ { name: 'Priya Sharma', role: 'Head of Design', color: 'bg-blue-100 text-blue-700' },
27
+ { name: 'Rohan Kumar', role: 'Tech Lead', color: 'bg-emerald-100 text-emerald-700' },
28
+ { name: 'Ananya Singh', role: 'Marketing Lead', color: 'bg-amber-100 text-amber-700' },
29
+ ];
30
+
31
+ export default function AboutPage() {
32
+ return (
33
+ <div className="bg-white">
34
+ {/* ── HERO (HubSpot style: text left, image right) ── */}
35
+ <section className="container-main py-20 md:py-28">
36
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-12 items-center">
37
+ <div>
38
+ <p className="text-sm font-bold text-violet-600 uppercase tracking-widest mb-3">Our Story</p>
39
+ <h1 className="text-4xl md:text-5xl font-black text-gray-900 leading-tight mb-5">
40
+ About<br />GenZ Shop
41
+ </h1>
42
+ <p className="text-gray-500 text-lg leading-relaxed">
43
+ GenZ Shop&apos;s culture is a lot like our product. Crafted, not cobbled — for a delightful shopping experience made for the next generation.
44
+ </p>
45
+ </div>
46
+ <div className="relative rounded-3xl overflow-hidden aspect-[4/3]">
47
+ <Image
48
+ src="/images/about-1.jpg"
49
+ alt="Our team"
50
+ fill
51
+ className="object-cover"
52
+ sizes="(max-width: 768px) 100vw, 50vw"
53
+ />
54
+ </div>
55
+ </div>
56
+ </section>
57
+
58
+ {/* ── STATS ── */}
59
+ <section className="bg-gray-900 text-white py-14">
60
+ <div className="container-main">
61
+ <div className="grid grid-cols-3 divide-x divide-gray-700">
62
+ {stats.map(({ icon: Icon, val, label }) => (
63
+ <div key={label} className="text-center px-6 py-2">
64
+ <Icon className="w-6 h-6 text-gray-400 mx-auto mb-2" />
65
+ <p className="text-3xl font-black mb-1">{val}</p>
66
+ <p className="text-gray-400 text-sm">{label}</p>
67
+ </div>
68
+ ))}
69
+ </div>
70
+ </div>
71
+ </section>
72
+
73
+ {/* ── MISSION (HubSpot: image left, text right) ── */}
74
+ <section className="container-main py-20">
75
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-12 items-center">
76
+ <div className="relative rounded-3xl overflow-hidden aspect-[4/3] order-2 md:order-1">
77
+ <Image
78
+ src="/images/about-2.jpg"
79
+ alt="Our office culture"
80
+ fill
81
+ className="object-cover"
82
+ sizes="(max-width: 768px) 100vw, 50vw"
83
+ />
84
+ </div>
85
+ <div className="order-1 md:order-2">
86
+ <p className="text-sm font-bold text-violet-600 uppercase tracking-widest mb-3">Our Mission</p>
87
+ <h2 className="text-3xl font-black text-gray-900 leading-tight mb-5">
88
+ Helping Everyone Shop Smarter
89
+ </h2>
90
+ <p className="text-gray-500 leading-relaxed mb-5">
91
+ We believe not just in selling products, but in building a community. Growing better means aligning the success of our store with the satisfaction of our customers. Win-win!
92
+ </p>
93
+ <p className="text-gray-500 leading-relaxed">
94
+ Since 2022, we&apos;ve worked directly with brands and manufacturers to cut out middlemen and pass savings directly to you.
95
+ </p>
96
+ </div>
97
+ </div>
98
+ </section>
99
+
100
+ {/* ── VALUES ── */}
101
+ <section className="bg-gray-50 py-16">
102
+ <div className="container-main">
103
+ <div className="text-center mb-12">
104
+ <h2 className="text-3xl font-black text-gray-900 mb-2">What We Stand For</h2>
105
+ <p className="text-gray-500">The principles that drive everything we do</p>
106
+ </div>
107
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
108
+ {values.map(({ icon: Icon, title, desc }) => (
109
+ <div key={title} className="bg-white rounded-2xl p-6 shadow-sm hover:shadow-md transition-shadow">
110
+ <div className="w-12 h-12 bg-gray-900 rounded-xl flex items-center justify-center mb-4">
111
+ <Icon className="w-6 h-6 text-white" />
112
+ </div>
113
+ <h3 className="font-bold text-gray-900 mb-2">{title}</h3>
114
+ <p className="text-sm text-gray-500 leading-relaxed">{desc}</p>
115
+ </div>
116
+ ))}
117
+ </div>
118
+ </div>
119
+ </section>
120
+
121
+ {/* ── TEAM ── */}
122
+ <section className="container-main py-20">
123
+ <div className="text-center mb-12">
124
+ <h2 className="text-3xl font-black text-gray-900 mb-2">Meet the Team</h2>
125
+ <p className="text-gray-500">The humans building GenZ Shop</p>
126
+ </div>
127
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-6">
128
+ {team.map(({ name, role, color }) => (
129
+ <div key={name} className="bg-white rounded-2xl p-6 text-center shadow-sm hover:shadow-md transition-shadow">
130
+ <div className={`w-16 h-16 ${color} rounded-2xl flex items-center justify-center mx-auto mb-4`}>
131
+ <span className="text-2xl font-black">{name.charAt(0)}</span>
132
+ </div>
133
+ <h3 className="font-bold text-gray-900">{name}</h3>
134
+ <p className="text-sm text-gray-500 mt-0.5">{role}</p>
135
+ </div>
136
+ ))}
137
+ </div>
138
+ </section>
139
+
140
+ {/* ── CTA ── */}
141
+ <section className="bg-gray-900 py-16 text-center text-white">
142
+ <div className="container-main max-w-xl mx-auto">
143
+ <h2 className="text-3xl font-black mb-3">Ready to shop smarter?</h2>
144
+ <p className="text-gray-400 mb-8">Join thousands of happy GenZ customers today.</p>
145
+ <Link href="/products" className="inline-flex items-center gap-2 bg-white text-gray-900 font-bold px-10 py-3.5 rounded-xl hover:bg-gray-100 transition-all active:scale-95">
146
+ Explore Products <ArrowRight className="w-4 h-4" />
147
+ </Link>
148
+ </div>
149
+ </section>
150
+ </div>
151
+ );
152
+ }
@@ -0,0 +1,103 @@
1
+ 'use client';
2
+
3
+ import Link from 'next/link';
4
+ import { ShoppingCart, Trash2, ArrowRight } from 'lucide-react';
5
+ import { useCartStore } from '@/store/useCartStore';
6
+ import CartItem from '@/components/CartItem';
7
+ import { formatPrice } from '@/lib/utils';
8
+
9
+ const SHIPPING_THRESHOLD = 999;
10
+
11
+ export default function CartPage() {
12
+ const { items, clearCart, total, itemCount } = useCartStore();
13
+ const subtotal = total();
14
+ const count = itemCount();
15
+ const shipping = subtotal > 0 && subtotal < SHIPPING_THRESHOLD ? 99 : 0;
16
+ const grandTotal = subtotal + shipping;
17
+
18
+ if (count === 0) {
19
+ return (
20
+ <div className="container-main py-24 text-center animate-fade-in">
21
+ <div className="w-24 h-24 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-6">
22
+ <ShoppingCart className="w-12 h-12 text-gray-400" />
23
+ </div>
24
+ <h1 className="page-title">Your cart is empty</h1>
25
+ <p className="text-gray-500 mb-8">Looks like you haven&apos;t added anything yet.</p>
26
+ <Link href="/products" className="btn-primary inline-flex items-center gap-2">
27
+ Browse Products <ArrowRight className="w-4 h-4" />
28
+ </Link>
29
+ </div>
30
+ );
31
+ }
32
+
33
+ return (
34
+ <div className="container-main py-10 animate-fade-in">
35
+ <div className="flex items-center justify-between mb-8">
36
+ <div>
37
+ <h1 className="page-title">Your Cart</h1>
38
+ <p className="text-gray-500">{count} {count === 1 ? 'item' : 'items'}</p>
39
+ </div>
40
+ <button
41
+ onClick={clearCart}
42
+ className="flex items-center gap-1.5 text-sm text-red-400 hover:text-red-500 font-medium transition-colors"
43
+ id="clear-cart-btn"
44
+ >
45
+ <Trash2 className="w-4 h-4" /> Clear Cart
46
+ </button>
47
+ </div>
48
+
49
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
50
+ {/* Cart Items */}
51
+ <div className="lg:col-span-2 space-y-4">
52
+ {items.map((item) => (
53
+ <CartItem key={item.product.id} item={item} />
54
+ ))}
55
+ </div>
56
+
57
+ {/* Order Summary */}
58
+ <div className="lg:col-span-1">
59
+ <div className="bg-white rounded-2xl shadow-sm p-6 sticky top-24">
60
+ <h2 className="text-lg font-bold text-gray-900 mb-5">Order Summary</h2>
61
+
62
+ <div className="space-y-3 mb-5">
63
+ <div className="flex justify-between text-sm text-gray-600">
64
+ <span>Subtotal ({count} items)</span>
65
+ <span className="font-medium text-gray-900">{formatPrice(subtotal)}</span>
66
+ </div>
67
+ <div className="flex justify-between text-sm text-gray-600">
68
+ <span>Shipping</span>
69
+ <span className={`font-medium ${shipping === 0 ? 'text-green-500' : 'text-gray-900'}`}>
70
+ {shipping === 0 ? 'FREE' : formatPrice(shipping)}
71
+ </span>
72
+ </div>
73
+ {subtotal > 0 && subtotal < SHIPPING_THRESHOLD && (
74
+ <p className="text-xs text-gray-400">
75
+ Add {formatPrice(SHIPPING_THRESHOLD - subtotal)} more for free shipping
76
+ </p>
77
+ )}
78
+ </div>
79
+
80
+ <div className="border-t border-gray-100 pt-4 mb-6">
81
+ <div className="flex justify-between">
82
+ <span className="font-bold text-gray-900">Total</span>
83
+ <span className="font-bold text-xl text-gray-900">{formatPrice(grandTotal)}</span>
84
+ </div>
85
+ </div>
86
+
87
+ <Link
88
+ href="/checkout"
89
+ className="btn-primary w-full text-center block py-3.5 text-base"
90
+ id="checkout-btn"
91
+ >
92
+ Proceed to Checkout →
93
+ </Link>
94
+
95
+ <Link href="/products" className="block text-center text-sm text-gray-400 hover:text-gray-700 mt-4 transition-colors">
96
+ ← Continue Shopping
97
+ </Link>
98
+ </div>
99
+ </div>
100
+ </div>
101
+ </div>
102
+ );
103
+ }
@@ -0,0 +1,126 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { useRouter } from 'next/navigation';
5
+ import Link from 'next/link';
6
+ import { CheckCircle, Loader2 } from 'lucide-react';
7
+ import { useCartStore } from '@/store/useCartStore';
8
+ import { useAuthStore } from '@/store/useAuthStore';
9
+ import { useOrderStore } from '@/store/useOrderStore';
10
+ import CheckoutForm from '@/components/CheckoutForm';
11
+ import { formatPrice, generateId } from '@/lib/utils';
12
+ import type { Address } from '@/types';
13
+
14
+ export default function CheckoutPage() {
15
+ const router = useRouter();
16
+ const { items, total, clearCart } = useCartStore();
17
+ const { isAuthenticated, user } = useAuthStore();
18
+ const { addOrder } = useOrderStore();
19
+ const [success, setSuccess] = useState(false);
20
+ const [orderId, setOrderId] = useState('');
21
+ const [isLoading, setIsLoading] = useState(false);
22
+ const subtotal = total();
23
+ const shipping = subtotal > 0 && subtotal < 999 ? 99 : 0;
24
+ const grandTotal = subtotal + shipping;
25
+
26
+ if (!isAuthenticated) {
27
+ return (
28
+ <div className="container-main py-24 text-center">
29
+ <div className="text-6xl mb-6">🔐</div>
30
+ <h1 className="page-title">Login Required</h1>
31
+ <p className="text-gray-500 mb-8">Please login to proceed to checkout.</p>
32
+ <Link href="/login" className="btn-primary inline-block">Login</Link>
33
+ </div>
34
+ );
35
+ }
36
+
37
+ if (items.length === 0 && !success) {
38
+ return (
39
+ <div className="container-main py-24 text-center">
40
+ <div className="text-8xl mb-6">🛒</div>
41
+ <h1 className="page-title">Your cart is empty</h1>
42
+ <Link href="/products" className="btn-primary inline-block mt-4">Browse Products</Link>
43
+ </div>
44
+ );
45
+ }
46
+
47
+ if (success) {
48
+ return (
49
+ <div className="container-main py-24 text-center">
50
+ <div className="max-w-md mx-auto">
51
+ <div className="w-24 h-24 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-6">
52
+ <CheckCircle className="w-14 h-14 text-green-500" />
53
+ </div>
54
+ <h1 className="text-3xl font-bold text-gray-900 mb-3">Order Placed! 🎉</h1>
55
+ <p className="text-gray-500 mb-1">Thank you for shopping with ShopVibe!</p>
56
+ {orderId && (
57
+ <p className="text-sm font-mono text-gray-400 mb-8">Order ID: {orderId}</p>
58
+ )}
59
+ <div className="flex flex-col sm:flex-row gap-3 justify-center">
60
+ <Link href="/profile?tab=orders" className="btn-primary">View My Orders</Link>
61
+ <Link href="/products" className="btn-secondary">Continue Shopping</Link>
62
+ </div>
63
+ </div>
64
+ </div>
65
+ );
66
+ }
67
+
68
+ const handlePlaceOrder = (address: Address) => {
69
+ setIsLoading(true);
70
+ setTimeout(() => {
71
+ const order = addOrder({ items, address, subtotal, shipping, total: grandTotal });
72
+ clearCart();
73
+ setOrderId(order.id);
74
+ setIsLoading(false);
75
+ setSuccess(true);
76
+ }, 1200);
77
+ };
78
+
79
+ return (
80
+ <div className="container-main py-10">
81
+ <div className="mb-8">
82
+ <h1 className="page-title">Checkout</h1>
83
+ <p className="text-gray-500">Almost there! Fill in your delivery details.</p>
84
+ </div>
85
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
86
+ <div className="lg:col-span-2">
87
+ <div className="bg-white rounded-2xl shadow-sm p-6">
88
+ <h2 className="font-bold text-gray-900 mb-5 text-lg">Delivery Address</h2>
89
+ <CheckoutForm onSubmit={handlePlaceOrder} isLoading={isLoading} />
90
+ </div>
91
+ </div>
92
+ <div>
93
+ <div className="bg-white rounded-2xl shadow-sm p-6 sticky top-24">
94
+ <h2 className="font-bold text-gray-900 mb-5 text-lg">Order Summary</h2>
95
+ <div className="space-y-3 mb-5">
96
+ {items.map((item) => (
97
+ <div key={item.product.id} className="flex justify-between text-sm">
98
+ <span className="text-gray-600 line-clamp-1 flex-1 mr-2">
99
+ {item.product.name} × {item.quantity}
100
+ </span>
101
+ <span className="font-medium text-gray-900 flex-shrink-0">
102
+ {formatPrice(item.product.price * item.quantity)}
103
+ </span>
104
+ </div>
105
+ ))}
106
+ </div>
107
+ <div className="border-t border-gray-100 pt-4 space-y-2">
108
+ <div className="flex justify-between text-sm text-gray-600">
109
+ <span>Subtotal</span>
110
+ <span>{formatPrice(subtotal)}</span>
111
+ </div>
112
+ <div className="flex justify-between text-sm text-gray-600">
113
+ <span>Shipping</span>
114
+ <span className={shipping === 0 ? 'text-green-500 font-medium' : ''}>{shipping === 0 ? 'FREE' : formatPrice(shipping)}</span>
115
+ </div>
116
+ <div className="flex justify-between font-bold text-gray-900 pt-2 border-t border-gray-100">
117
+ <span>Total</span>
118
+ <span className="text-lg">{formatPrice(grandTotal)}</span>
119
+ </div>
120
+ </div>
121
+ </div>
122
+ </div>
123
+ </div>
124
+ </div>
125
+ );
126
+ }
@@ -0,0 +1,124 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { Mail, Phone, MapPin, Clock, Loader2, CheckCircle, Send, Twitter, Instagram, Youtube } from 'lucide-react';
5
+
6
+ const contactInfo = [
7
+ { icon: Phone, label: 'Call Us', value: '+91 98765 43210' },
8
+ { icon: Mail, label: 'Email Us', value: 'hello@genzshop.in' },
9
+ { icon: MapPin, label: 'Address', value: 'Chennai, Tamil Nadu, India 600001' },
10
+ { icon: Clock, label: 'Hours', value: 'Mon–Sat, 9AM–6PM IST' },
11
+ ];
12
+
13
+ const socials = [
14
+ { icon: Twitter, href: '#', bg: 'bg-sky-500' },
15
+ { icon: Instagram, href: '#', bg: 'bg-pink-500' },
16
+ { icon: Youtube, href: '#', bg: 'bg-red-500' },
17
+ ];
18
+
19
+ export default function ContactPage() {
20
+ const [form, setForm] = useState({ name: '', email: '', subject: '', message: '' });
21
+ const [sent, setSent] = useState(false);
22
+ const [isLoading, setIsLoading] = useState(false);
23
+
24
+ const handleSubmit = (e: React.FormEvent) => {
25
+ e.preventDefault();
26
+ setIsLoading(true);
27
+ setTimeout(() => { setIsLoading(false); setSent(true); }, 1000);
28
+ };
29
+
30
+ return (
31
+ <div>
32
+ {/* Hero Banner */}
33
+ <section className="relative bg-gray-900 text-white py-20 overflow-hidden">
34
+ <div className="absolute inset-0 opacity-30" style={{
35
+ backgroundImage: `url('/images/contact-bg.jpg')`,
36
+ backgroundSize: 'cover',
37
+ backgroundPosition: 'center',
38
+ }} />
39
+ <div className="absolute inset-0 bg-gray-900/70" />
40
+ <div className="relative z-10 container-main text-center">
41
+ <h1 className="text-5xl font-black mb-3">Contact <span className="text-violet-400">Us</span></h1>
42
+ <p className="text-gray-300">Home / Contact Us</p>
43
+ </div>
44
+ </section>
45
+
46
+ {/* Main Content */}
47
+ <section className="container-main py-16">
48
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-10">
49
+ {/* Form — left 2 cols */}
50
+ <div className="lg:col-span-2 bg-white rounded-2xl shadow-sm p-8">
51
+ {sent ? (
52
+ <div className="text-center py-16">
53
+ <CheckCircle className="w-16 h-16 text-green-500 mx-auto mb-5" />
54
+ <h3 className="text-2xl font-black text-gray-900 mb-2">Message Sent!</h3>
55
+ <p className="text-gray-500 mb-6">We&apos;ll reply within 24 hours.</p>
56
+ <button onClick={() => { setSent(false); setForm({ name: '', email: '', subject: '', message: '' }); }} className="btn-secondary">
57
+ Send Another
58
+ </button>
59
+ </div>
60
+ ) : (
61
+ <form onSubmit={handleSubmit} className="space-y-5">
62
+ <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
63
+ <div>
64
+ <label htmlFor="contact-name" className="block text-sm font-medium text-gray-700 mb-1.5">Your Name</label>
65
+ <input id="contact-name" type="text" value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} placeholder="Full Name" className="input" required />
66
+ </div>
67
+ <div>
68
+ <label htmlFor="contact-email" className="block text-sm font-medium text-gray-700 mb-1.5">Your Email</label>
69
+ <input id="contact-email" type="email" value={form.email} onChange={(e) => setForm({ ...form, email: e.target.value })} placeholder="Email Address" className="input" required />
70
+ </div>
71
+ </div>
72
+ <div>
73
+ <label htmlFor="contact-subject" className="block text-sm font-medium text-gray-700 mb-1.5">Subject</label>
74
+ <input id="contact-subject" type="text" value={form.subject} onChange={(e) => setForm({ ...form, subject: e.target.value })} placeholder="Subject" className="input" required />
75
+ </div>
76
+ <div>
77
+ <label htmlFor="contact-message" className="block text-sm font-medium text-gray-700 mb-1.5">Your Message</label>
78
+ <textarea id="contact-message" value={form.message} onChange={(e) => setForm({ ...form, message: e.target.value })} placeholder="Message..." rows={6} className="input resize-none" required />
79
+ </div>
80
+ <button type="submit" disabled={isLoading} className="btn-primary px-8 py-3 flex items-center gap-2" id="contact-submit-btn">
81
+ {isLoading ? <><Loader2 className="w-4 h-4 animate-spin" /> Sending...</> : <><Send className="w-4 h-4" /> Send Message</>}
82
+ </button>
83
+ </form>
84
+ )}
85
+ </div>
86
+
87
+ {/* Right: Contact Info */}
88
+ <div className="space-y-5">
89
+ <div>
90
+ <p className="text-sm font-bold text-violet-600 uppercase tracking-widest mb-2">Contact Us</p>
91
+ <h2 className="text-3xl font-black text-gray-900 mb-4">Get In Touch</h2>
92
+ <p className="text-gray-500 text-sm leading-relaxed">
93
+ Have a question or feedback? We&apos;re here to help. Fill in the form or reach us directly through the channels below.
94
+ </p>
95
+ </div>
96
+
97
+ {contactInfo.map(({ icon: Icon, label, value }) => (
98
+ <div key={label} className="flex items-start gap-4">
99
+ <div className="w-11 h-11 bg-gray-900 rounded-xl flex items-center justify-center flex-shrink-0 mt-0.5">
100
+ <Icon className="w-5 h-5 text-white" />
101
+ </div>
102
+ <div>
103
+ <p className="text-xs font-bold text-gray-400 uppercase tracking-wider">{label}</p>
104
+ <p className="text-sm font-semibold text-gray-900 mt-0.5">{value}</p>
105
+ </div>
106
+ </div>
107
+ ))}
108
+
109
+ <div>
110
+ <p className="text-sm font-bold text-gray-700 mb-3">Follow Us On</p>
111
+ <div className="flex gap-3">
112
+ {socials.map(({ icon: Icon, href, bg }) => (
113
+ <a key={bg} href={href} className={`${bg} w-9 h-9 rounded-full flex items-center justify-center text-white hover:opacity-90 transition-opacity`}>
114
+ <Icon className="w-4 h-4" />
115
+ </a>
116
+ ))}
117
+ </div>
118
+ </div>
119
+ </div>
120
+ </div>
121
+ </section>
122
+ </div>
123
+ );
124
+ }