create-frootkit 1.2.12 → 1.2.13
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.
- package/package.json +1 -1
- package/templates/auth/src/app/dashboard/layout.tsx +8 -11
- package/templates/auth/src/app/dashboard/page.tsx +31 -27
- package/templates/auth/src/app/layout.tsx +5 -5
- package/templates/auth/src/app/login/page.tsx +11 -4
- package/templates/auth/src/app/page.tsx +278 -120
- package/templates/auth/src/app/signup/page.tsx +12 -6
- package/templates/auth/src/components/AuthForm.tsx +27 -34
- package/templates/auth/src/components/DashboardNav.tsx +41 -61
- package/templates/auth/src/components/MetricCard.tsx +12 -22
- package/templates/auth/src/components/Navbar.tsx +18 -21
- package/templates/auth/src/styles.css +157 -27
- package/templates/blog/src/app/blog/[slug]/page.tsx +85 -46
- package/templates/blog/src/app/layout.tsx +5 -5
- package/templates/blog/src/app/page.tsx +39 -26
- package/templates/blog/src/components/Footer.tsx +11 -9
- package/templates/blog/src/components/Navbar.tsx +21 -26
- package/templates/blog/src/components/Newsletter.tsx +11 -33
- package/templates/blog/src/components/PostCard.tsx +43 -76
- package/templates/blog/src/styles.css +113 -25
- package/templates/ecommerce/src/app/cart/page.tsx +12 -4
- package/templates/ecommerce/src/app/layout.tsx +5 -5
- package/templates/ecommerce/src/app/page.tsx +92 -37
- package/templates/ecommerce/src/app/products/[id]/page.tsx +39 -31
- package/templates/ecommerce/src/components/CartDrawer.tsx +113 -45
- package/templates/ecommerce/src/components/Footer.tsx +11 -9
- package/templates/ecommerce/src/components/Navbar.tsx +32 -34
- package/templates/ecommerce/src/components/ProductCard.tsx +27 -38
- package/templates/ecommerce/src/styles.css +155 -36
- package/templates/starter/src/app/layout.tsx +5 -5
- package/templates/starter/src/app/page.tsx +114 -107
- package/templates/starter/src/components/FeatureCard.tsx +9 -15
- package/templates/starter/src/components/Footer.tsx +11 -9
- package/templates/starter/src/components/Header.tsx +15 -38
- package/templates/starter/src/styles.css +102 -27
|
@@ -1,71 +1,126 @@
|
|
|
1
|
-
import { getProducts
|
|
1
|
+
import { getProducts } from '../data/products.js';
|
|
2
2
|
import { Navbar } from '../components/Navbar.js';
|
|
3
3
|
import { ProductCard } from '../components/ProductCard.js';
|
|
4
4
|
import { Footer } from '../components/Footer.js';
|
|
5
5
|
|
|
6
6
|
export const metadata = {
|
|
7
|
-
title: '
|
|
8
|
-
description: '
|
|
7
|
+
title: 'Parcel — Everyday objects, well made',
|
|
8
|
+
description: 'Small-batch goods for your desk, kitchen, and commute. Built to last, priced fairly.',
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
export default async function StoreHomePage() {
|
|
12
12
|
const products = await getProducts();
|
|
13
|
-
const categories = ['All
|
|
13
|
+
const categories = ['All', 'Keyboards', 'Audio', 'Peripherals', 'Desk Setup', 'Accessories'];
|
|
14
14
|
|
|
15
15
|
return (
|
|
16
|
-
<div class="
|
|
16
|
+
<div class="bg-white font-sans text-[#425466]">
|
|
17
17
|
<Navbar />
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
<div class="
|
|
22
|
-
<div
|
|
23
|
-
<span class="
|
|
24
|
-
<
|
|
19
|
+
{/* HERO SECTION */}
|
|
20
|
+
<section class="relative overflow-hidden text-white" style="background: linear-gradient(160deg, #635bff 0%, #7a73ff 40%, #00d4ff 100%)">
|
|
21
|
+
<div class="mx-auto max-w-6xl px-6 grid items-center gap-8 py-16 md:grid-cols-2 md:py-24">
|
|
22
|
+
<div>
|
|
23
|
+
<span class="bd bg-white/15 text-white">Autumn collection</span>
|
|
24
|
+
<h1 class="mt-4 text-[40px] font-light leading-[1.08] tracking-tight md:text-[56px] md:leading-[64px]">
|
|
25
|
+
Everyday objects, well made
|
|
26
|
+
</h1>
|
|
27
|
+
<p class="mt-5 max-w-md text-[18px] font-light leading-7 text-white/90">
|
|
28
|
+
Small-batch goods for your desk, kitchen, and commute. Built to last, priced fairly.
|
|
29
|
+
</p>
|
|
30
|
+
<div class="mt-7 flex flex-wrap gap-3">
|
|
31
|
+
<a href="#shop" class="btn rounded-full bg-white px-5 py-2.5 text-[#635bff] shadow-[0_2px_5px_-1px_rgba(50,50,93,0.25),0_1px_3px_-1px_rgba(0,0,0,0.3)] hover:bg-[#f6f9fc]">
|
|
32
|
+
Shop the collection
|
|
33
|
+
</a>
|
|
34
|
+
<a href="/cart" class="btn rounded-full border border-white/40 px-5 py-2.5 text-white hover:bg-white/10">
|
|
35
|
+
View Cart (2)
|
|
36
|
+
</a>
|
|
37
|
+
</div>
|
|
25
38
|
</div>
|
|
26
39
|
|
|
27
|
-
<
|
|
28
|
-
|
|
29
|
-
|
|
40
|
+
<div class="relative hidden h-72 md:block" aria-hidden="true">
|
|
41
|
+
<div class="absolute right-4 top-0 h-56 w-44 rotate-6 rounded-lg bg-white shadow-[0_13px_27px_-5px_rgba(50,50,93,0.25),0_8px_16px_-8px_rgba(0,0,0,0.3)] p-3">
|
|
42
|
+
<div class="h-32 rounded-md bg-gradient-to-br from-[#635bff] to-[#a29bff]" />
|
|
43
|
+
<div class="mt-3 h-2 w-20 rounded bg-[#e6ebf1]" />
|
|
44
|
+
<div class="mt-2 h-2 w-12 rounded bg-[#e6ebf1]" />
|
|
45
|
+
</div>
|
|
46
|
+
<div class="absolute bottom-0 left-4 h-52 w-44 -rotate-6 rounded-lg bg-white shadow-[0_13px_27px_-5px_rgba(50,50,93,0.25),0_8px_16px_-8px_rgba(0,0,0,0.3)] p-3">
|
|
47
|
+
<div class="h-28 rounded-md bg-gradient-to-br from-[#00d4ff] to-[#0a2540]" />
|
|
48
|
+
<div class="mt-3 h-2 w-24 rounded bg-[#e6ebf1]" />
|
|
49
|
+
<div class="mt-2 h-2 w-10 rounded bg-[#e6ebf1]" />
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</section>
|
|
30
54
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
55
|
+
{/* TRUST ROW */}
|
|
56
|
+
<section class="border-b border-[#e6ebf1] bg-[#f6f9fc]">
|
|
57
|
+
<div class="mx-auto max-w-6xl px-6 grid gap-4 py-5 text-[14px] sm:grid-cols-3">
|
|
58
|
+
<p><b class="text-[#0a2540]">Free returns</b> for 30 days</p>
|
|
59
|
+
<p><b class="text-[#0a2540]">Secure checkout</b> with encrypted payments</p>
|
|
60
|
+
<p><b class="text-[#0a2540]">Ships in 24 hours</b> from our warehouse</p>
|
|
61
|
+
</div>
|
|
62
|
+
</section>
|
|
34
63
|
|
|
35
|
-
|
|
36
|
-
|
|
64
|
+
{/* SHOP SECTION */}
|
|
65
|
+
<section id="shop" class="scroll-mt-16 py-14">
|
|
66
|
+
<div class="mx-auto max-w-6xl px-6">
|
|
67
|
+
<div class="flex flex-wrap items-end justify-between gap-4">
|
|
68
|
+
<div>
|
|
69
|
+
<h2 class="text-[32px] font-light tracking-tight text-[#0a2540]">Shop all</h2>
|
|
70
|
+
<p class="text-[14px] text-[#425466] mt-0.5">{products.length} products available</p>
|
|
71
|
+
</div>
|
|
72
|
+
<div class="flex items-center gap-3">
|
|
73
|
+
<label for="sort" class="text-[13px] text-[#425466]">Sort</label>
|
|
74
|
+
<select id="sort" class="inp w-auto pr-8 py-1.5 text-xs">
|
|
75
|
+
<option value="f">Featured</option>
|
|
76
|
+
<option value="lo">Price: low to high</option>
|
|
77
|
+
<option value="hi">Price: high to low</option>
|
|
78
|
+
<option value="r">Top rated</option>
|
|
79
|
+
</select>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
{/* Categories Filter Pills */}
|
|
84
|
+
<div class="mt-5 flex flex-wrap gap-2">
|
|
37
85
|
{categories.map((c, i) => (
|
|
38
86
|
<a
|
|
39
87
|
key={c}
|
|
40
|
-
href="/#
|
|
41
|
-
class={`px-
|
|
88
|
+
href="/#shop"
|
|
89
|
+
class={`rounded-full px-4 py-1.5 text-[14px] font-medium transition ${
|
|
42
90
|
i === 0
|
|
43
|
-
? 'bg-
|
|
44
|
-
: 'bg-
|
|
91
|
+
? 'bg-[#0a2540] text-white shadow-sm'
|
|
92
|
+
: 'bg-[#f6f9fc] text-[#425466] hover:bg-[#e6ebf1]'
|
|
45
93
|
}`}
|
|
46
94
|
>
|
|
47
95
|
{c}
|
|
48
96
|
</a>
|
|
49
97
|
))}
|
|
50
98
|
</div>
|
|
51
|
-
</div>
|
|
52
99
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
{products.length} products available
|
|
60
|
-
</span>
|
|
100
|
+
{/* Products Grid */}
|
|
101
|
+
<div class="mt-8 grid grid-cols-2 gap-x-5 gap-y-9 lg:grid-cols-4">
|
|
102
|
+
{products.map((product) => (
|
|
103
|
+
<ProductCard key={product.id} product={product} />
|
|
104
|
+
))}
|
|
105
|
+
</div>
|
|
61
106
|
</div>
|
|
107
|
+
</section>
|
|
62
108
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
109
|
+
{/* NEWSLETTER */}
|
|
110
|
+
<section class="pb-16">
|
|
111
|
+
<div class="mx-auto max-w-6xl px-6">
|
|
112
|
+
<div class="grid items-center gap-6 rounded-lg bg-[#f6f9fc] p-8 md:grid-cols-2 md:p-12 border border-[#e6ebf1]">
|
|
113
|
+
<div>
|
|
114
|
+
<h2 class="text-[28px] font-light tracking-tight text-[#0a2540]">New arrivals, once a month</h2>
|
|
115
|
+
<p class="mt-1 text-sm text-[#425466]">No spam. Unsubscribe anytime.</p>
|
|
116
|
+
</div>
|
|
117
|
+
<form class="flex gap-2">
|
|
118
|
+
<input class="inp" type="email" placeholder="you@email.com" aria-label="Email" />
|
|
119
|
+
<button type="button" class="btn btn-p shrink-0">Subscribe</button>
|
|
120
|
+
</form>
|
|
121
|
+
</div>
|
|
67
122
|
</div>
|
|
68
|
-
</
|
|
123
|
+
</section>
|
|
69
124
|
|
|
70
125
|
<Footer />
|
|
71
126
|
</div>
|
|
@@ -11,13 +11,13 @@ export default async function ProductDetailPage({ params }: { params?: { id?: st
|
|
|
11
11
|
const product = (await getProduct(id)) || PRODUCTS[0]!;
|
|
12
12
|
|
|
13
13
|
return (
|
|
14
|
-
<div class="min-h-screen bg-[#
|
|
14
|
+
<div class="min-h-screen bg-white text-[#425466] flex flex-col justify-between">
|
|
15
15
|
<Navbar />
|
|
16
16
|
|
|
17
17
|
<main class="max-w-6xl mx-auto px-6 py-12 flex-1 w-full">
|
|
18
18
|
<a
|
|
19
|
-
href="
|
|
20
|
-
class="inline-flex items-center gap-2 text-xs text-
|
|
19
|
+
href="/#shop"
|
|
20
|
+
class="inline-flex items-center gap-2 text-xs text-[#425466] hover:text-[#0a2540] mb-8 transition-colors"
|
|
21
21
|
>
|
|
22
22
|
<span>←</span>
|
|
23
23
|
<span>Back to catalog</span>
|
|
@@ -25,67 +25,75 @@ export default async function ProductDetailPage({ params }: { params?: { id?: st
|
|
|
25
25
|
|
|
26
26
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-16 items-start">
|
|
27
27
|
{/* Product Gallery Image */}
|
|
28
|
-
<div class="rounded-
|
|
28
|
+
<div class="rounded-lg border border-[#e6ebf1] overflow-hidden bg-[#f6f9fc] aspect-[4/3] shadow-[0_2px_5px_-1px_rgba(50,50,93,0.25),0_1px_3px_-1px_rgba(0,0,0,0.3)]">
|
|
29
29
|
<img
|
|
30
30
|
src={product.image}
|
|
31
31
|
alt={product.name}
|
|
32
32
|
class="w-full h-full object-cover"
|
|
33
33
|
/>
|
|
34
|
-
<div class="absolute inset-0 bg-gradient-to-t from-black/30 via-transparent to-transparent pointer-events-none" />
|
|
35
34
|
</div>
|
|
36
35
|
|
|
37
36
|
{/* Details & Purchase */}
|
|
38
37
|
<div>
|
|
39
38
|
<div class="flex items-center gap-3 mb-3 text-xs">
|
|
40
|
-
<span class="
|
|
39
|
+
<span class="bd bg-[#f6f9fc] text-[#425466] border border-[#e6ebf1]">
|
|
41
40
|
{product.category}
|
|
42
41
|
</span>
|
|
43
|
-
<span class="text-
|
|
44
|
-
<div class="flex items-center gap-1.5 text-
|
|
45
|
-
<span class="text-
|
|
46
|
-
<span class="text-
|
|
47
|
-
<span class="text-
|
|
42
|
+
<span class="text-[#e6ebf1]">•</span>
|
|
43
|
+
<div class="flex items-center gap-1.5 text-xs text-[#425466]">
|
|
44
|
+
<span class="text-[#f5a623]">★</span>
|
|
45
|
+
<span class="text-[#0a2540] font-medium tabular-nums">{product.rating.toFixed(1)}</span>
|
|
46
|
+
<span class="text-[#425466]">({product.reviewsCount} reviews)</span>
|
|
48
47
|
</div>
|
|
49
48
|
</div>
|
|
50
49
|
|
|
51
|
-
<h1 class="text-3xl sm:text-4xl font-
|
|
50
|
+
<h1 class="text-3xl sm:text-4xl font-light text-[#0a2540] tracking-tight mb-4">
|
|
52
51
|
{product.name}
|
|
53
52
|
</h1>
|
|
54
53
|
|
|
55
|
-
<div class="text-3xl font-
|
|
54
|
+
<div class="text-3xl font-mono text-[#0a2540] font-light mb-6 tabular-nums">
|
|
56
55
|
${product.price.toFixed(2)}
|
|
57
56
|
</div>
|
|
58
57
|
|
|
59
|
-
<p class="text-
|
|
58
|
+
<p class="text-[#425466] text-sm sm:text-base leading-relaxed mb-8">
|
|
60
59
|
{product.description}
|
|
61
60
|
</p>
|
|
62
61
|
|
|
63
|
-
<div class="
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
<span class="w-1.5 h-1.5 rounded-full bg-zinc-400 shrink-0" />
|
|
71
|
-
<span>{f}</span>
|
|
62
|
+
<div class="space-y-4 mb-8">
|
|
63
|
+
<h4 class="text-xs uppercase tracking-wider text-[#0a2540] font-semibold">Features</h4>
|
|
64
|
+
<ul class="space-y-2 text-xs text-[#425466]">
|
|
65
|
+
{product.features.map((feat) => (
|
|
66
|
+
<li key={feat} class="flex items-center gap-2">
|
|
67
|
+
<span class="text-[#24b47e]">✓</span>
|
|
68
|
+
<span>{feat}</span>
|
|
72
69
|
</li>
|
|
73
70
|
))}
|
|
74
71
|
</ul>
|
|
75
72
|
</div>
|
|
76
73
|
|
|
77
|
-
<div class="flex
|
|
74
|
+
<div class="flex items-center gap-4">
|
|
78
75
|
<a
|
|
79
76
|
href="/cart"
|
|
80
|
-
class="flex-1 py-3
|
|
77
|
+
class="btn btn-p flex-1 py-3 text-sm font-medium"
|
|
81
78
|
>
|
|
82
|
-
|
|
83
|
-
<span class="text-zinc-500">•</span>
|
|
84
|
-
<span class="tabular-nums font-semibold">${product.price.toFixed(2)}</span>
|
|
79
|
+
Add to Cart
|
|
85
80
|
</a>
|
|
86
|
-
<
|
|
87
|
-
|
|
88
|
-
|
|
81
|
+
<a
|
|
82
|
+
href="/cart"
|
|
83
|
+
class="btn btn-s py-3 px-6 text-sm font-medium"
|
|
84
|
+
>
|
|
85
|
+
Buy Now
|
|
86
|
+
</a>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<div class="mt-8 pt-6 border-t border-[#e6ebf1] grid grid-cols-2 gap-4 text-xs text-[#425466]">
|
|
90
|
+
<div>
|
|
91
|
+
<b class="text-[#0a2540] block">Free Shipping</b>
|
|
92
|
+
<span>Orders over $75 qualify</span>
|
|
93
|
+
</div>
|
|
94
|
+
<div>
|
|
95
|
+
<b class="text-[#0a2540] block">30-Day Returns</b>
|
|
96
|
+
<span>No restocking fee</span>
|
|
89
97
|
</div>
|
|
90
98
|
</div>
|
|
91
99
|
</div>
|
|
@@ -7,61 +7,129 @@ export function CartDrawer() {
|
|
|
7
7
|
];
|
|
8
8
|
|
|
9
9
|
const subtotal = cartItems.reduce((acc, item) => acc + item.product.price * item.quantity, 0);
|
|
10
|
-
const
|
|
10
|
+
const freeShipThreshold = 75;
|
|
11
|
+
const isFreeShip = subtotal >= freeShipThreshold;
|
|
12
|
+
const shipping = isFreeShip ? 0 : 6.5;
|
|
11
13
|
const total = subtotal + shipping;
|
|
12
14
|
|
|
13
15
|
return (
|
|
14
|
-
<div class="
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
<div class="grid gap-8 lg:grid-cols-[1.3fr_1fr] items-start">
|
|
17
|
+
{/* Cart Items List */}
|
|
18
|
+
<div class="rounded-lg border border-[#e6ebf1] bg-white p-6 shadow-[0_2px_5px_-1px_rgba(50,50,93,0.25),0_1px_3px_-1px_rgba(0,0,0,0.3)]">
|
|
19
|
+
<div class="flex items-center justify-between pb-4 border-b border-[#e6ebf1]">
|
|
20
|
+
<h2 class="text-[18px] font-medium text-[#0a2540]">Your cart</h2>
|
|
21
|
+
<span class="bd bg-[#635bff] text-white">2 items</span>
|
|
22
|
+
</div>
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
{/* Free Shipping Progress */}
|
|
25
|
+
<div class="mt-4 pb-4 border-b border-[#e6ebf1]">
|
|
26
|
+
<p class="text-[13px] text-[#425466]">
|
|
27
|
+
{isFreeShip ? '🎉 You unlocked free shipping!' : `Add $${(freeShipThreshold - subtotal).toFixed(2)} more for free shipping.`}
|
|
28
|
+
</p>
|
|
29
|
+
<div class="mt-2 h-2 overflow-hidden rounded-full bg-[#e6ebf1]">
|
|
30
|
+
<div
|
|
31
|
+
class="h-full rounded-full bg-[#635bff] transition-all"
|
|
32
|
+
style={`width: ${Math.min(100, (subtotal / freeShipThreshold) * 100)}%`}
|
|
29
33
|
/>
|
|
30
|
-
<div class="flex-1 min-w-0">
|
|
31
|
-
<h4 class="text-xs sm:text-sm font-medium text-zinc-100 truncate">{product.name}</h4>
|
|
32
|
-
<div class="text-xs text-zinc-400 mt-0.5 tabular-nums">Qty: {quantity} × ${product.price.toFixed(2)}</div>
|
|
33
|
-
</div>
|
|
34
|
-
<div class="font-medium text-xs sm:text-sm text-white tabular-nums">
|
|
35
|
-
${(product.price * quantity).toFixed(2)}
|
|
36
|
-
</div>
|
|
37
34
|
</div>
|
|
38
|
-
))}
|
|
39
|
-
</div>
|
|
40
|
-
|
|
41
|
-
<div class="space-y-2.5 text-xs text-zinc-300 mb-6">
|
|
42
|
-
<div class="flex justify-between">
|
|
43
|
-
<span class="text-zinc-400">Subtotal</span>
|
|
44
|
-
<span class="text-white font-medium tabular-nums">${subtotal.toFixed(2)}</span>
|
|
45
|
-
</div>
|
|
46
|
-
<div class="flex justify-between">
|
|
47
|
-
<span class="text-zinc-400">Standard Shipping</span>
|
|
48
|
-
<span class="text-emerald-400 font-medium">{shipping === 0 ? 'FREE' : `$${shipping.toFixed(2)}`}</span>
|
|
49
35
|
</div>
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
36
|
+
|
|
37
|
+
<div class="divide-y divide-[#e6ebf1]">
|
|
38
|
+
{cartItems.map(({ product, quantity }) => (
|
|
39
|
+
<div key={product.id} class="flex gap-4 py-4">
|
|
40
|
+
<img
|
|
41
|
+
src={product.image}
|
|
42
|
+
alt={product.name}
|
|
43
|
+
class="w-16 h-16 rounded-md object-cover border border-[#e6ebf1] shrink-0"
|
|
44
|
+
/>
|
|
45
|
+
<div class="flex-1 min-w-0">
|
|
46
|
+
<div class="flex justify-between items-start">
|
|
47
|
+
<h4 class="font-medium text-[#0a2540] text-sm truncate">{product.name}</h4>
|
|
48
|
+
<span class="font-mono text-sm text-[#0a2540] font-medium">
|
|
49
|
+
${(product.price * quantity).toFixed(2)}
|
|
50
|
+
</span>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="mt-3 flex items-center justify-between text-xs">
|
|
53
|
+
<div class="inline-flex items-center rounded-md border border-[#e6ebf1]">
|
|
54
|
+
<span class="px-2.5 py-1 text-[#425466]">Qty</span>
|
|
55
|
+
<span class="w-6 text-center font-mono font-medium text-[#0a2540]">{quantity}</span>
|
|
56
|
+
</div>
|
|
57
|
+
<span class="text-xs text-[#425466]">{product.category}</span>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
))}
|
|
53
62
|
</div>
|
|
54
63
|
</div>
|
|
55
64
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
>
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
{/* Stripe Payment & Checkout Form */}
|
|
66
|
+
<div class="rounded-lg border border-[#e6ebf1] bg-white p-6 shadow-[0_13px_27px_-5px_rgba(50,50,93,0.25),0_8px_16px_-8px_rgba(0,0,0,0.3)]">
|
|
67
|
+
<h3 class="text-[18px] font-medium text-[#0a2540] mb-4 pb-2 border-b border-[#e6ebf1]">
|
|
68
|
+
Payment & Checkout
|
|
69
|
+
</h3>
|
|
70
|
+
|
|
71
|
+
<form action="/api/checkout" method="POST" class="space-y-3.5">
|
|
72
|
+
<div>
|
|
73
|
+
<label class="block text-[13px] font-medium text-[#0a2540] mb-1" for="email">Email</label>
|
|
74
|
+
<input id="email" name="email" type="email" placeholder="you@company.com" class="inp" defaultValue="buyer@frootkit.dev" required />
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<div>
|
|
78
|
+
<label class="block text-[13px] font-medium text-[#0a2540] mb-1" for="card">Card number</label>
|
|
79
|
+
<div class="relative">
|
|
80
|
+
<input
|
|
81
|
+
id="card"
|
|
82
|
+
name="cardNumber"
|
|
83
|
+
class="inp pr-20 font-mono text-[14px]"
|
|
84
|
+
defaultValue="4242 4242 4242 4242"
|
|
85
|
+
required
|
|
86
|
+
/>
|
|
87
|
+
<span class="bd absolute right-2 top-2 bg-[#f6f9fc] text-[#425466] border border-[#e6ebf1] text-[11px]">
|
|
88
|
+
Visa
|
|
89
|
+
</span>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<div class="grid grid-cols-2 gap-3">
|
|
94
|
+
<div>
|
|
95
|
+
<label class="block text-[13px] font-medium text-[#0a2540] mb-1" for="expiry">Expiry</label>
|
|
96
|
+
<input id="expiry" name="expiry" class="inp font-mono text-[14px]" defaultValue="12 / 28" required />
|
|
97
|
+
</div>
|
|
98
|
+
<div>
|
|
99
|
+
<label class="block text-[13px] font-medium text-[#0a2540] mb-1" for="cvc">CVC</label>
|
|
100
|
+
<input id="cvc" name="cvc" class="inp font-mono text-[14px]" defaultValue="123" required />
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<div>
|
|
105
|
+
<label class="block text-[13px] font-medium text-[#0a2540] mb-1" for="zip">Shipping postcode</label>
|
|
106
|
+
<input id="zip" name="postcode" class="inp" defaultValue="94103" required />
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<div class="pt-3 border-t border-[#e6ebf1] space-y-1.5 text-xs text-[#425466]">
|
|
110
|
+
<div class="flex justify-between">
|
|
111
|
+
<span>Subtotal</span>
|
|
112
|
+
<span class="font-mono text-[#0a2540]">${subtotal.toFixed(2)}</span>
|
|
113
|
+
</div>
|
|
114
|
+
<div class="flex justify-between">
|
|
115
|
+
<span>Shipping</span>
|
|
116
|
+
<span class="font-mono text-[#0a2540]">{shipping === 0 ? 'Free' : `$${shipping.toFixed(2)}`}</span>
|
|
117
|
+
</div>
|
|
118
|
+
<div class="flex justify-between text-base font-medium text-[#0a2540] pt-2 border-t border-[#e6ebf1]">
|
|
119
|
+
<span>Total</span>
|
|
120
|
+
<span class="font-mono">${total.toFixed(2)}</span>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<button type="submit" class="btn btn-p w-full py-3 mt-4 text-sm font-medium">
|
|
125
|
+
Pay ${total.toFixed(2)}
|
|
126
|
+
</button>
|
|
127
|
+
|
|
128
|
+
<p class="text-center text-[11px] text-[#425466] mt-2">
|
|
129
|
+
Test card 4242 4242 4242 4242, any future date. Zero real money moves.
|
|
130
|
+
</p>
|
|
131
|
+
</form>
|
|
132
|
+
</div>
|
|
65
133
|
</div>
|
|
66
134
|
);
|
|
67
135
|
}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
export function Footer() {
|
|
2
2
|
return (
|
|
3
|
-
<footer class="border-t border-
|
|
4
|
-
<div class="max-w-6xl
|
|
5
|
-
<
|
|
6
|
-
|
|
7
|
-
</
|
|
8
|
-
<div class="flex items-center gap-
|
|
9
|
-
<a href="
|
|
10
|
-
<
|
|
11
|
-
<a href="
|
|
3
|
+
<footer class="border-t border-[#e6ebf1] py-10 text-[14px] bg-white">
|
|
4
|
+
<div class="mx-auto max-w-6xl px-6 flex flex-wrap justify-between gap-4 text-xs text-[#425466]">
|
|
5
|
+
<span>
|
|
6
|
+
<b class="text-[#635bff] font-semibold text-sm">parcel</b> · Built with FrootKit & Preact SSR.
|
|
7
|
+
</span>
|
|
8
|
+
<div class="flex items-center gap-4">
|
|
9
|
+
<a href="/#shop" class="hover:text-[#0a2540]">Shipping</a>
|
|
10
|
+
<span>·</span>
|
|
11
|
+
<a href="/#shop" class="hover:text-[#0a2540]">Returns</a>
|
|
12
|
+
<span>·</span>
|
|
13
|
+
<a href="/api/products" class="font-mono hover:text-[#0a2540]">/api/products</a>
|
|
12
14
|
</div>
|
|
13
15
|
</div>
|
|
14
16
|
</footer>
|
|
@@ -1,44 +1,42 @@
|
|
|
1
1
|
export function Navbar() {
|
|
2
2
|
return (
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
<>
|
|
4
|
+
{/* Top Banner */}
|
|
5
|
+
<div class="bg-[#0a2540] py-2 text-center text-[13px] text-white">
|
|
6
|
+
Free shipping on orders over $75 · 30-day returns
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
{/* Main Sticky Navbar */}
|
|
10
|
+
<header class="sticky top-0 z-30 border-b border-[#e6ebf1] bg-white/90 backdrop-blur">
|
|
11
|
+
<div class="mx-auto max-w-6xl px-6 flex items-center gap-4 py-3">
|
|
12
|
+
<a href="/" class="text-[22px] font-semibold tracking-tight text-[#635bff]">
|
|
13
|
+
parcel
|
|
14
|
+
</a>
|
|
15
|
+
|
|
16
|
+
<nav class="ml-4 hidden gap-6 text-[14px] font-medium md:flex">
|
|
17
|
+
<a href="/#shop" class="text-[#0a2540] hover:text-[#635bff] transition-colors">Shop</a>
|
|
18
|
+
<a href="/#shop" class="text-[#425466] hover:text-[#635bff] transition-colors">Home</a>
|
|
19
|
+
<a href="/#shop" class="text-[#425466] hover:text-[#635bff] transition-colors">Office</a>
|
|
20
|
+
<a href="/#shop" class="text-[#425466] hover:text-[#635bff] transition-colors">Tech</a>
|
|
21
|
+
</nav>
|
|
22
|
+
|
|
23
|
+
<div class="relative ml-auto hidden w-64 sm:block">
|
|
24
|
+
<svg class="absolute left-3 top-2.5 h-4 w-4 text-[#425466]/60" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
25
|
+
<circle cx="11" cy="11" r="7" />
|
|
26
|
+
<path d="m20 20-3-3" />
|
|
11
27
|
</svg>
|
|
28
|
+
<input class="inp pl-9" placeholder="Search products" aria-label="Search products" />
|
|
12
29
|
</div>
|
|
13
|
-
<span>Froot<span class="text-zinc-400 font-normal">Store</span></span>
|
|
14
|
-
</a>
|
|
15
30
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
</a>
|
|
20
|
-
<a
|
|
21
|
-
href="/api/products"
|
|
22
|
-
class="text-zinc-400 hover:text-zinc-200 font-mono text-[11px] px-2 py-0.5 rounded bg-white/[0.03] border border-white/[0.06] transition-colors"
|
|
23
|
-
>
|
|
24
|
-
/api/products
|
|
25
|
-
</a>
|
|
26
|
-
<a
|
|
27
|
-
href="/cart"
|
|
28
|
-
class="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-white/[0.05] border border-white/[0.08] text-zinc-200 hover:text-white hover:bg-white/[0.08] transition-all text-xs font-medium"
|
|
29
|
-
>
|
|
30
|
-
<svg style="width: 13px; height: 13px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
31
|
-
<path d="M6 2L3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z" />
|
|
32
|
-
<line x1="3" y1="6" x2="21" y2="6" />
|
|
33
|
-
<path d="M16 10a4 4 0 0 1-8 0" />
|
|
31
|
+
<a href="/cart" class="btn btn-s relative ml-auto sm:ml-0" aria-label="Open cart">
|
|
32
|
+
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
33
|
+
<path d="M5 7h14l-1.5 11h-11zM9 7a3 3 0 0 1 6 0" stroke-linejoin="round" />
|
|
34
34
|
</svg>
|
|
35
35
|
<span>Cart</span>
|
|
36
|
-
<span class="
|
|
37
|
-
2
|
|
38
|
-
</span>
|
|
36
|
+
<span class="bd bg-[#635bff] text-white">2</span>
|
|
39
37
|
</a>
|
|
40
|
-
</
|
|
41
|
-
</
|
|
42
|
-
|
|
38
|
+
</div>
|
|
39
|
+
</header>
|
|
40
|
+
</>
|
|
43
41
|
);
|
|
44
42
|
}
|