Hanzo Dev commited on
Commit
fc743bc
·
0 Parent(s):

Initial commit for ecommerce template

Browse files
.gitignore ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # dependencies
2
+ /node_modules
3
+ /.pnp
4
+ .pnp.js
5
+
6
+ # testing
7
+ /coverage
8
+
9
+ # next.js
10
+ /.next/
11
+ /out/
12
+
13
+ # production
14
+ /build
15
+
16
+ # misc
17
+ .DS_Store
18
+ *.pem
19
+
20
+ # debug
21
+ npm-debug.log*
22
+ yarn-debug.log*
23
+ yarn-error.log*
24
+
25
+ # local env files
26
+ .env*.local
27
+
28
+ # vercel
29
+ .vercel
30
+
31
+ # typescript
32
+ *.tsbuildinfo
33
+ next-env.d.ts
Dockerfile ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:20-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Copy package files
6
+ COPY package*.json ./
7
+
8
+ # Install dependencies
9
+ RUN npm ci --only=production
10
+
11
+ # Copy application files
12
+ COPY . .
13
+
14
+ # Build the application
15
+ RUN npm run build
16
+
17
+ # Expose port
18
+ EXPOSE 3000
19
+
20
+ # Start the application
21
+ CMD ["npm", "start"]
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Hanzo AI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Ecommerce Storefront
2
+
3
+ Complete online store with cart and product management
4
+
5
+ Built with [@hanzo/ui](https://github.com/hanzoai/ui) components - a modern React component library based on Radix UI and Tailwind CSS.
6
+
7
+ ## 🚀 Quick Start
8
+
9
+ ### Deploy to Hanzo Cloud
10
+
11
+ [![Deploy to Hanzo Cloud](https://img.shields.io/badge/Deploy%20to-Hanzo%20Cloud-purple?style=for-the-badge&logo=rocket)](https://hanzo.app/deploy?template=https://github.com/hanzoai/template-ecommerce-storefront)
12
+
13
+ **Instant deployment** - Click to deploy this template to Hanzo Cloud. If you're not signed in, we'll create a public repo for you and you can start editing immediately!
14
+
15
+ ### Edit on Hanzo
16
+
17
+ [![Edit on Hanzo](https://img.shields.io/badge/Edit%20on-Hanzo-blue?style=for-the-badge&logo=react)](https://hanzo.app/edit/github/hanzoai/template-ecommerce-storefront)
18
+
19
+ **Cloud IDE** - Click to open this template in Hanzo's cloud development environment. No local setup required!
20
+
21
+ ### Local Development
22
+
23
+ ```bash
24
+ # Clone this template
25
+ git clone https://github.com/hanzoai/template-ecommerce-storefront.git
26
+ cd ecommerce-storefront
27
+
28
+ # Install dependencies
29
+ npm install
30
+ # or
31
+ pnpm install
32
+
33
+ # Start development server
34
+ npm run dev
35
+ # or
36
+ pnpm dev
37
+
38
+ # Open http://localhost:3000
39
+ ```
40
+
41
+ ## 🚢 Deploy to Hugging Face
42
+
43
+ This template includes a built-in publish option for Hugging Face Spaces:
44
+
45
+ 1. **Login to Hugging Face** in your terminal:
46
+ ```bash
47
+ huggingface-cli login
48
+ ```
49
+
50
+ 2. **Use the built-in publish command**:
51
+ ```bash
52
+ npm run publish-hf
53
+ # or
54
+ pnpm publish-hf
55
+ ```
56
+
57
+ This will automatically:
58
+ - Create a new Space in your HF account
59
+ - Configure it for Next.js deployment
60
+ - Push all necessary files
61
+ - Your app will be live at: `https://huggingface.co/spaces/YOUR_USERNAME/ecommerce-storefront`
62
+
63
+ 3. **Or manually push** to an existing Space:
64
+ ```bash
65
+ git remote add hf https://huggingface.co/spaces/YOUR_USERNAME/ecommerce-storefront
66
+ git push hf main
67
+ ```
68
+
69
+ ## 🎨 Features
70
+
71
+ - **Product Grid**: Beautiful product showcase with filters
72
+ - **Shopping Cart**: Full cart functionality with quantity controls
73
+ - **Filters & Search**: Advanced product filtering
74
+ - **Responsive Design**: Works perfectly on all devices
75
+ - **Dark Mode**: Built-in dark mode support
76
+ - **TypeScript**: Full type safety
77
+
78
+ ## 📦 What's Included
79
+
80
+ - Next.js 14 with App Router
81
+ - React 18 with Server Components
82
+ - TypeScript configuration
83
+ - Tailwind CSS with custom theme
84
+ - ESLint and Prettier configs
85
+ - @hanzo/ui component library
86
+ - Lucide React icons
87
+ - Hugging Face deployment config
88
+
89
+ ## 🛠️ Customization
90
+
91
+ ### Theme Colors
92
+
93
+ Edit `tailwind.config.js` to customize the color scheme:
94
+
95
+ ```js
96
+ theme: {
97
+ extend: {
98
+ colors: {
99
+ primary: {
100
+ DEFAULT: "hsl(var(--primary))",
101
+ foreground: "hsl(var(--primary-foreground))",
102
+ },
103
+ // Add your custom colors
104
+ }
105
+ }
106
+ }
107
+ ```
108
+
109
+ ### Components
110
+
111
+ All UI components are in `components/ui/`. They're built with:
112
+ - Radix UI primitives for accessibility
113
+ - Tailwind CSS for styling
114
+ - Full TypeScript support
115
+
116
+ ## 📚 Documentation
117
+
118
+ - [Hanzo Documentation](https://hanzo.app/docs)
119
+ - [@hanzo/ui Components](https://github.com/hanzoai/ui)
120
+ - [Template Gallery](https://huggingface.co/spaces/hanzo-community/gallery)
121
+
122
+ ## 🤝 Contributing
123
+
124
+ Contributions are welcome! Please feel free to submit a Pull Request.
125
+
126
+ ## 📄 License
127
+
128
+ MIT License - see [LICENSE](LICENSE) file for details.
129
+
130
+ ---
131
+
132
+ Built with ❤️ by [Hanzo AI](https://hanzo.ai)
app/globals.css ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ @layer base {
6
+ :root {
7
+ --background: 0 0% 100%;
8
+ --foreground: 222.2 84% 4.9%;
9
+ --card: 0 0% 100%;
10
+ --card-foreground: 222.2 84% 4.9%;
11
+ --popover: 0 0% 100%;
12
+ --popover-foreground: 222.2 84% 4.9%;
13
+ --primary: 222.2 47.4% 11.2%;
14
+ --primary-foreground: 210 40% 98%;
15
+ --secondary: 210 40% 96.1%;
16
+ --secondary-foreground: 222.2 47.4% 11.2%;
17
+ --muted: 210 40% 96.1%;
18
+ --muted-foreground: 215.4 16.3% 46.9%;
19
+ --accent: 210 40% 96.1%;
20
+ --accent-foreground: 222.2 47.4% 11.2%;
21
+ --destructive: 0 84.2% 60.2%;
22
+ --destructive-foreground: 210 40% 98%;
23
+ --border: 214.3 31.8% 91.4%;
24
+ --input: 214.3 31.8% 91.4%;
25
+ --ring: 222.2 84% 4.9%;
26
+ --radius: 0.5rem;
27
+ }
28
+
29
+ .dark {
30
+ --background: 222.2 84% 4.9%;
31
+ --foreground: 210 40% 98%;
32
+ --card: 222.2 84% 4.9%;
33
+ --card-foreground: 210 40% 98%;
34
+ --popover: 222.2 84% 4.9%;
35
+ --popover-foreground: 210 40% 98%;
36
+ --primary: 210 40% 98%;
37
+ --primary-foreground: 222.2 47.4% 11.2%;
38
+ --secondary: 217.2 32.6% 17.5%;
39
+ --secondary-foreground: 210 40% 98%;
40
+ --muted: 217.2 32.6% 17.5%;
41
+ --muted-foreground: 215 20.2% 65.1%;
42
+ --accent: 217.2 32.6% 17.5%;
43
+ --accent-foreground: 210 40% 98%;
44
+ --destructive: 0 62.8% 30.6%;
45
+ --destructive-foreground: 210 40% 98%;
46
+ --border: 217.2 32.6% 17.5%;
47
+ --input: 217.2 32.6% 17.5%;
48
+ --ring: 212.7 26.8% 83.9%;
49
+ }
50
+ }
51
+
52
+ * {
53
+ @apply border-border;
54
+ }
55
+
56
+ body {
57
+ @apply bg-background text-foreground;
58
+ }
app/layout.tsx ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Metadata } from "next";
2
+ import { Inter } from "next/font/google";
3
+ import "./globals.css";
4
+
5
+ const inter = Inter({ subsets: ["latin"] });
6
+
7
+ export const metadata: Metadata = {
8
+ title: "Ecommerce Storefront - Hanzo UI Template",
9
+ description: "Complete online store with cart",
10
+ };
11
+
12
+ export default function RootLayout({
13
+ children,
14
+ }: {
15
+ children: React.ReactNode;
16
+ }) {
17
+ return (
18
+ <html lang="en" suppressHydrationWarning>
19
+ <body className={inter.className}>{children}</body>
20
+ </html>
21
+ );
22
+ }
app/page.tsx ADDED
@@ -0,0 +1,293 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { Card, CardContent, CardFooter } from "@/components/ui/card";
5
+ import { Button } from "@/components/ui/button";
6
+ import { Badge } from "@/components/ui/badge";
7
+ import { Input } from "@/components/ui/input";
8
+ import { AspectRatio } from "@/components/ui/aspect-ratio";
9
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
10
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
11
+ import { ShoppingCart, Search, Star, Filter, Heart, Share2 } from "lucide-react";
12
+
13
+ const products = [
14
+ {
15
+ id: "1",
16
+ name: "Premium Wireless Headphones",
17
+ price: 299.99,
18
+ image: "/api/placeholder/400/400",
19
+ rating: 4.5,
20
+ reviews: 234,
21
+ badge: "Best Seller",
22
+ variants: ["Black", "White", "Blue"]
23
+ },
24
+ {
25
+ id: "2",
26
+ name: "Smart Watch Pro",
27
+ price: 399.99,
28
+ image: "/api/placeholder/400/400",
29
+ rating: 4.8,
30
+ reviews: 567,
31
+ badge: "New",
32
+ variants: ["Silver", "Gold", "Space Gray"]
33
+ },
34
+ {
35
+ id: "3",
36
+ name: "Portable Speaker",
37
+ price: 149.99,
38
+ image: "/api/placeholder/400/400",
39
+ rating: 4.3,
40
+ reviews: 189,
41
+ badge: null,
42
+ variants: ["Red", "Black", "Green"]
43
+ },
44
+ {
45
+ id: "4",
46
+ name: "Laptop Stand",
47
+ price: 79.99,
48
+ image: "/api/placeholder/400/400",
49
+ rating: 4.6,
50
+ reviews: 432,
51
+ badge: "Sale",
52
+ variants: ["Aluminum", "Wood"]
53
+ }
54
+ ];
55
+
56
+ const categories = ["All Products", "Electronics", "Accessories", "Audio", "Computing"];
57
+
58
+ export default function EcommerceStorefront() {
59
+ const [selectedCategory, setSelectedCategory] = useState("All Products");
60
+ const [cart, setCart] = useState<{ id: string; quantity: number }[]>([]);
61
+
62
+ const addToCart = (productId: string) => {
63
+ setCart(prev => {
64
+ const existing = prev.find(item => item.id === productId);
65
+ if (existing) {
66
+ return prev.map(item =>
67
+ item.id === productId
68
+ ? { ...item, quantity: item.quantity + 1 }
69
+ : item
70
+ );
71
+ }
72
+ return [...prev, { id: productId, quantity: 1 }];
73
+ });
74
+ };
75
+
76
+ const cartItemsCount = cart.reduce((sum, item) => sum + item.quantity, 0);
77
+
78
+ return (
79
+ <div className="min-h-screen bg-background">
80
+ {/* Header */}
81
+ <header className="border-b sticky top-0 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 z-50">
82
+ <div className="container mx-auto px-6 py-4">
83
+ <div className="flex items-center justify-between">
84
+ <div className="flex items-center gap-8">
85
+ <h1 className="text-2xl font-bold">Store</h1>
86
+ <nav className="hidden md:flex items-center gap-6">
87
+ {categories.map(category => (
88
+ <button
89
+ key={category}
90
+ onClick={() => setSelectedCategory(category)}
91
+ className={`text-sm font-medium transition-colors hover:text-primary ${
92
+ selectedCategory === category
93
+ ? "text-primary"
94
+ : "text-muted-foreground"
95
+ }`}
96
+ >
97
+ {category}
98
+ </button>
99
+ ))}
100
+ </nav>
101
+ </div>
102
+
103
+ <div className="flex items-center gap-4">
104
+ <div className="relative hidden md:block">
105
+ <Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
106
+ <Input
107
+ placeholder="Search products..."
108
+ className="pl-9 w-[200px] lg:w-[300px]"
109
+ />
110
+ </div>
111
+
112
+ <Button variant="ghost" size="icon" className="relative">
113
+ <ShoppingCart className="w-5 h-5" />
114
+ {cartItemsCount > 0 && (
115
+ <Badge className="absolute -top-1 -right-1 h-5 w-5 rounded-full p-0 flex items-center justify-center">
116
+ {cartItemsCount}
117
+ </Badge>
118
+ )}
119
+ </Button>
120
+ </div>
121
+ </div>
122
+ </div>
123
+ </header>
124
+
125
+ {/* Hero Section - Orange/Pink Gradient Theme */}
126
+ <section className="bg-gradient-to-r from-orange-500 to-pink-500 text-white py-16">
127
+ <div className="container mx-auto px-6">
128
+ <div className="max-w-3xl">
129
+ <h2 className="text-4xl font-bold mb-4">
130
+ Summer Collection
131
+ </h2>
132
+ <p className="text-xl mb-6 opacity-90">
133
+ Discover our latest products built with @hanzo/ui components
134
+ </p>
135
+ <Button size="lg" variant="secondary">
136
+ Shop Now
137
+ </Button>
138
+ </div>
139
+ </div>
140
+ </section>
141
+
142
+ {/* Filters Bar */}
143
+ <div className="border-b">
144
+ <div className="container mx-auto px-6 py-4">
145
+ <div className="flex items-center justify-between">
146
+ <div className="flex items-center gap-4">
147
+ <Button variant="outline" className="gap-2">
148
+ <Filter className="w-4 h-4" />
149
+ Filters
150
+ </Button>
151
+ <Select defaultValue="featured">
152
+ <SelectTrigger className="w-[180px]">
153
+ <SelectValue />
154
+ </SelectTrigger>
155
+ <SelectContent>
156
+ <SelectItem value="featured">Featured</SelectItem>
157
+ <SelectItem value="price-low">Price: Low to High</SelectItem>
158
+ <SelectItem value="price-high">Price: High to Low</SelectItem>
159
+ <SelectItem value="rating">Highest Rated</SelectItem>
160
+ <SelectItem value="newest">Newest</SelectItem>
161
+ </SelectContent>
162
+ </Select>
163
+ </div>
164
+ <p className="text-sm text-muted-foreground">
165
+ Showing {products.length} products
166
+ </p>
167
+ </div>
168
+ </div>
169
+ </div>
170
+
171
+ {/* Products Grid */}
172
+ <section className="py-12">
173
+ <div className="container mx-auto px-6">
174
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
175
+ {products.map(product => (
176
+ <Card key={product.id} className="overflow-hidden group">
177
+ <div className="relative">
178
+ <AspectRatio ratio={1}>
179
+ <img
180
+ src={product.image}
181
+ alt={product.name}
182
+ className="object-cover w-full h-full group-hover:scale-105 transition-transform"
183
+ />
184
+ </AspectRatio>
185
+ {product.badge && (
186
+ <Badge className="absolute top-2 left-2">
187
+ {product.badge}
188
+ </Badge>
189
+ )}
190
+ <div className="absolute top-2 right-2 flex flex-col gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
191
+ <Button size="icon" variant="secondary" className="h-8 w-8">
192
+ <Heart className="w-4 h-4" />
193
+ </Button>
194
+ <Button size="icon" variant="secondary" className="h-8 w-8">
195
+ <Share2 className="w-4 h-4" />
196
+ </Button>
197
+ </div>
198
+ </div>
199
+
200
+ <CardContent className="p-4">
201
+ <h3 className="font-semibold mb-2">{product.name}</h3>
202
+ <div className="flex items-center gap-2 mb-2">
203
+ <div className="flex items-center">
204
+ {[...Array(5)].map((_, i) => (
205
+ <Star
206
+ key={i}
207
+ className={`w-4 h-4 ${
208
+ i < Math.floor(product.rating)
209
+ ? "fill-yellow-400 text-yellow-400"
210
+ : "text-muted-foreground"
211
+ }`}
212
+ />
213
+ ))}
214
+ </div>
215
+ <span className="text-sm text-muted-foreground">
216
+ ({product.reviews})
217
+ </span>
218
+ </div>
219
+ <div className="flex items-center justify-between mb-3">
220
+ <span className="text-2xl font-bold">
221
+ ${product.price}
222
+ </span>
223
+ </div>
224
+
225
+ {/* Variant Selector */}
226
+ <div className="mb-3">
227
+ <Select defaultValue={product.variants[0]}>
228
+ <SelectTrigger className="w-full h-8 text-sm">
229
+ <SelectValue />
230
+ </SelectTrigger>
231
+ <SelectContent>
232
+ {product.variants.map(variant => (
233
+ <SelectItem key={variant} value={variant}>
234
+ {variant}
235
+ </SelectItem>
236
+ ))}
237
+ </SelectContent>
238
+ </Select>
239
+ </div>
240
+ </CardContent>
241
+
242
+ <CardFooter className="p-4 pt-0">
243
+ <Button
244
+ className="w-full"
245
+ onClick={() => addToCart(product.id)}
246
+ >
247
+ <ShoppingCart className="w-4 h-4 mr-2" />
248
+ Add to Cart
249
+ </Button>
250
+ </CardFooter>
251
+ </Card>
252
+ ))}
253
+ </div>
254
+ </div>
255
+ </section>
256
+
257
+ {/* Features */}
258
+ <section className="py-12 bg-muted">
259
+ <div className="container mx-auto px-6">
260
+ <div className="grid md:grid-cols-3 gap-8">
261
+ <div className="text-center">
262
+ <div className="w-12 h-12 rounded-full bg-orange-500/10 flex items-center justify-center mx-auto mb-3">
263
+ <ShoppingCart className="w-6 h-6 text-orange-500" />
264
+ </div>
265
+ <h3 className="font-semibold mb-1">Free Shipping</h3>
266
+ <p className="text-sm text-muted-foreground">
267
+ On orders over $100
268
+ </p>
269
+ </div>
270
+ <div className="text-center">
271
+ <div className="w-12 h-12 rounded-full bg-pink-500/10 flex items-center justify-center mx-auto mb-3">
272
+ <Star className="w-6 h-6 text-pink-500" />
273
+ </div>
274
+ <h3 className="font-semibold mb-1">Quality Products</h3>
275
+ <p className="text-sm text-muted-foreground">
276
+ 100% authentic brands
277
+ </p>
278
+ </div>
279
+ <div className="text-center">
280
+ <div className="w-12 h-12 rounded-full bg-rose-500/10 flex items-center justify-center mx-auto mb-3">
281
+ <Heart className="w-6 h-6 text-rose-500" />
282
+ </div>
283
+ <h3 className="font-semibold mb-1">24/7 Support</h3>
284
+ <p className="text-sm text-muted-foreground">
285
+ Dedicated customer service
286
+ </p>
287
+ </div>
288
+ </div>
289
+ </div>
290
+ </section>
291
+ </div>
292
+ );
293
+ }
components/ui/avatar.tsx ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as AvatarPrimitive from "@radix-ui/react-avatar"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ function Avatar({
9
+ className,
10
+ ...props
11
+ }: React.ComponentProps<typeof AvatarPrimitive.Root>) {
12
+ return (
13
+ <AvatarPrimitive.Root
14
+ data-slot="avatar"
15
+ className={cn(
16
+ "relative flex size-8 shrink-0 overflow-hidden rounded-full",
17
+ className
18
+ )}
19
+ {...props}
20
+ />
21
+ )
22
+ }
23
+
24
+ function AvatarImage({
25
+ className,
26
+ ...props
27
+ }: React.ComponentProps<typeof AvatarPrimitive.Image>) {
28
+ return (
29
+ <AvatarPrimitive.Image
30
+ data-slot="avatar-image"
31
+ className={cn("aspect-square size-full", className)}
32
+ {...props}
33
+ />
34
+ )
35
+ }
36
+
37
+ function AvatarFallback({
38
+ className,
39
+ ...props
40
+ }: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
41
+ return (
42
+ <AvatarPrimitive.Fallback
43
+ data-slot="avatar-fallback"
44
+ className={cn(
45
+ "bg-muted flex size-full items-center justify-center rounded-full",
46
+ className
47
+ )}
48
+ {...props}
49
+ />
50
+ )
51
+ }
52
+
53
+ export { Avatar, AvatarImage, AvatarFallback }
components/ui/badge.tsx ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const badgeVariants = cva(
7
+ "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default:
12
+ "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
13
+ secondary:
14
+ "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15
+ destructive:
16
+ "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
17
+ outline: "text-foreground",
18
+ },
19
+ },
20
+ defaultVariants: {
21
+ variant: "default",
22
+ },
23
+ }
24
+ )
25
+
26
+ export interface BadgeProps
27
+ extends React.HTMLAttributes<HTMLDivElement>,
28
+ VariantProps<typeof badgeVariants> {}
29
+
30
+ function Badge({ className, variant, ...props }: BadgeProps) {
31
+ return (
32
+ <div className={cn(badgeVariants({ variant }), className)} {...props} />
33
+ )
34
+ }
35
+
36
+ export { Badge, badgeVariants }
components/ui/button.tsx ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+ import { Slot } from "@radix-ui/react-slot";
3
+ import { cva, type VariantProps } from "class-variance-authority";
4
+
5
+ import { cn } from "@/lib/utils";
6
+
7
+ const buttonVariants = cva(
8
+ "inline-flex items-center cursor-pointer justify-center gap-2 whitespace-nowrap rounded-full text-sm font-sans 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",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default:
13
+ "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
14
+ destructive:
15
+ "bg-red-500 text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 [&_svg]:!text-white",
16
+ outline:
17
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
18
+ secondary:
19
+ "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
20
+ ghost:
21
+ "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
22
+ lightGray: "bg-neutral-200/60 hover:bg-neutral-200",
23
+ link: "text-primary underline-offset-4 hover:underline",
24
+ ghostDarker:
25
+ "text-white shadow-xs focus-visible:ring-black/40 bg-black/40 hover:bg-black/70",
26
+ black: "bg-neutral-950 text-neutral-300 hover:brightness-110",
27
+ sky: "bg-sky-500 text-white hover:brightness-110",
28
+ },
29
+ size: {
30
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
31
+ sm: "h-8 rounded-full text-[13px] gap-1.5 px-3",
32
+ lg: "h-10 rounded-full px-6 has-[>svg]:px-4",
33
+ icon: "size-9",
34
+ iconXs: "size-7",
35
+ iconXss: "size-6",
36
+ iconXsss: "size-5",
37
+ xs: "h-6 text-xs rounded-full pl-2 pr-2 gap-1",
38
+ },
39
+ },
40
+ defaultVariants: {
41
+ variant: "default",
42
+ size: "default",
43
+ },
44
+ }
45
+ );
46
+
47
+ function Button({
48
+ className,
49
+ variant,
50
+ size,
51
+ asChild = false,
52
+ ...props
53
+ }: React.ComponentProps<"button"> &
54
+ VariantProps<typeof buttonVariants> & {
55
+ asChild?: boolean;
56
+ }) {
57
+ const Comp = asChild ? Slot : "button";
58
+
59
+ return (
60
+ <Comp
61
+ data-slot="button"
62
+ className={cn(buttonVariants({ variant, size, className }))}
63
+ {...props}
64
+ />
65
+ );
66
+ }
67
+
68
+ export { Button, buttonVariants };
components/ui/card.tsx ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ const Card = React.forwardRef<
6
+ HTMLDivElement,
7
+ React.HTMLAttributes<HTMLDivElement>
8
+ >(({ className, ...props }, ref) => (
9
+ <div
10
+ ref={ref}
11
+ className={cn(
12
+ "rounded-lg border bg-card text-card-foreground shadow-sm",
13
+ className
14
+ )}
15
+ {...props}
16
+ />
17
+ ))
18
+ Card.displayName = "Card"
19
+
20
+ const CardHeader = React.forwardRef<
21
+ HTMLDivElement,
22
+ React.HTMLAttributes<HTMLDivElement>
23
+ >(({ className, ...props }, ref) => (
24
+ <div
25
+ ref={ref}
26
+ className={cn("flex flex-col space-y-1.5 p-6", className)}
27
+ {...props}
28
+ />
29
+ ))
30
+ CardHeader.displayName = "CardHeader"
31
+
32
+ const CardTitle = React.forwardRef<
33
+ HTMLParagraphElement,
34
+ React.HTMLAttributes<HTMLHeadingElement>
35
+ >(({ className, ...props }, ref) => (
36
+ <h3
37
+ ref={ref}
38
+ className={cn(
39
+ "text-2xl font-semibold leading-none tracking-tight",
40
+ className
41
+ )}
42
+ {...props}
43
+ />
44
+ ))
45
+ CardTitle.displayName = "CardTitle"
46
+
47
+ const CardDescription = React.forwardRef<
48
+ HTMLParagraphElement,
49
+ React.HTMLAttributes<HTMLParagraphElement>
50
+ >(({ className, ...props }, ref) => (
51
+ <p
52
+ ref={ref}
53
+ className={cn("text-sm text-muted-foreground", className)}
54
+ {...props}
55
+ />
56
+ ))
57
+ CardDescription.displayName = "CardDescription"
58
+
59
+ const CardContent = React.forwardRef<
60
+ HTMLDivElement,
61
+ React.HTMLAttributes<HTMLDivElement>
62
+ >(({ className, ...props }, ref) => (
63
+ <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
64
+ ))
65
+ CardContent.displayName = "CardContent"
66
+
67
+ const CardFooter = React.forwardRef<
68
+ HTMLDivElement,
69
+ React.HTMLAttributes<HTMLDivElement>
70
+ >(({ className, ...props }, ref) => (
71
+ <div
72
+ ref={ref}
73
+ className={cn("flex items-center p-6 pt-0", className)}
74
+ {...props}
75
+ />
76
+ ))
77
+ CardFooter.displayName = "CardFooter"
78
+
79
+ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
components/ui/dialog.tsx ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as DialogPrimitive from "@radix-ui/react-dialog"
5
+ import { XIcon } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ function Dialog({
10
+ ...props
11
+ }: React.ComponentProps<typeof DialogPrimitive.Root>) {
12
+ return <DialogPrimitive.Root data-slot="dialog" {...props} />
13
+ }
14
+
15
+ function DialogTrigger({
16
+ ...props
17
+ }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
18
+ return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
19
+ }
20
+
21
+ function DialogPortal({
22
+ ...props
23
+ }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
24
+ return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
25
+ }
26
+
27
+ function DialogClose({
28
+ ...props
29
+ }: React.ComponentProps<typeof DialogPrimitive.Close>) {
30
+ return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
31
+ }
32
+
33
+ function DialogOverlay({
34
+ className,
35
+ ...props
36
+ }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
37
+ return (
38
+ <DialogPrimitive.Overlay
39
+ data-slot="dialog-overlay"
40
+ className={cn(
41
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
42
+ className
43
+ )}
44
+ {...props}
45
+ />
46
+ )
47
+ }
48
+
49
+ function DialogContent({
50
+ className,
51
+ children,
52
+ showCloseButton = true,
53
+ ...props
54
+ }: React.ComponentProps<typeof DialogPrimitive.Content> & {
55
+ showCloseButton?: boolean
56
+ }) {
57
+ return (
58
+ <DialogPortal data-slot="dialog-portal">
59
+ <DialogOverlay />
60
+ <DialogPrimitive.Content
61
+ data-slot="dialog-content"
62
+ className={cn(
63
+ "bg-background 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 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
64
+ className
65
+ )}
66
+ {...props}
67
+ >
68
+ {children}
69
+ {showCloseButton && (
70
+ <DialogPrimitive.Close
71
+ data-slot="dialog-close"
72
+ className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
73
+ >
74
+ <XIcon />
75
+ <span className="sr-only">Close</span>
76
+ </DialogPrimitive.Close>
77
+ )}
78
+ </DialogPrimitive.Content>
79
+ </DialogPortal>
80
+ )
81
+ }
82
+
83
+ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
84
+ return (
85
+ <div
86
+ data-slot="dialog-header"
87
+ className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
88
+ {...props}
89
+ />
90
+ )
91
+ }
92
+
93
+ function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
94
+ return (
95
+ <div
96
+ data-slot="dialog-footer"
97
+ className={cn(
98
+ "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
99
+ className
100
+ )}
101
+ {...props}
102
+ />
103
+ )
104
+ }
105
+
106
+ function DialogTitle({
107
+ className,
108
+ ...props
109
+ }: React.ComponentProps<typeof DialogPrimitive.Title>) {
110
+ return (
111
+ <DialogPrimitive.Title
112
+ data-slot="dialog-title"
113
+ className={cn("text-lg leading-none font-semibold", className)}
114
+ {...props}
115
+ />
116
+ )
117
+ }
118
+
119
+ function DialogDescription({
120
+ className,
121
+ ...props
122
+ }: React.ComponentProps<typeof DialogPrimitive.Description>) {
123
+ return (
124
+ <DialogPrimitive.Description
125
+ data-slot="dialog-description"
126
+ className={cn("text-muted-foreground text-sm", className)}
127
+ {...props}
128
+ />
129
+ )
130
+ }
131
+
132
+ export {
133
+ Dialog,
134
+ DialogClose,
135
+ DialogContent,
136
+ DialogDescription,
137
+ DialogFooter,
138
+ DialogHeader,
139
+ DialogOverlay,
140
+ DialogPortal,
141
+ DialogTitle,
142
+ DialogTrigger,
143
+ }
components/ui/input.tsx ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
6
+ return (
7
+ <input
8
+ type={type}
9
+ data-slot="input"
10
+ className={cn(
11
+ "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex 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",
12
+ "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
13
+ "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
14
+ className
15
+ )}
16
+ {...props}
17
+ />
18
+ )
19
+ }
20
+
21
+ export { Input }
components/ui/progress.tsx ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as ProgressPrimitive from "@radix-ui/react-progress"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const Progress = React.forwardRef<
7
+ React.ElementRef<typeof ProgressPrimitive.Root>,
8
+ React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root> & {
9
+ indicatorClassName?: string
10
+ }
11
+ >(({ className, value, indicatorClassName, ...props }, ref) => (
12
+ <ProgressPrimitive.Root
13
+ ref={ref}
14
+ className={cn(
15
+ "relative h-2 w-full overflow-hidden rounded-full bg-secondary",
16
+ className
17
+ )}
18
+ {...props}
19
+ >
20
+ <ProgressPrimitive.Indicator
21
+ className={cn("h-full w-full flex-1 bg-primary transition-all", indicatorClassName)}
22
+ style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
23
+ />
24
+ </ProgressPrimitive.Root>
25
+ ))
26
+ Progress.displayName = ProgressPrimitive.Root.displayName
27
+
28
+ export { Progress }
components/ui/select.tsx ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as SelectPrimitive from "@radix-ui/react-select"
5
+ import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ function Select({
10
+ ...props
11
+ }: React.ComponentProps<typeof SelectPrimitive.Root>) {
12
+ return <SelectPrimitive.Root data-slot="select" {...props} />
13
+ }
14
+
15
+ function SelectGroup({
16
+ ...props
17
+ }: React.ComponentProps<typeof SelectPrimitive.Group>) {
18
+ return <SelectPrimitive.Group data-slot="select-group" {...props} />
19
+ }
20
+
21
+ function SelectValue({
22
+ ...props
23
+ }: React.ComponentProps<typeof SelectPrimitive.Value>) {
24
+ return <SelectPrimitive.Value data-slot="select-value" {...props} />
25
+ }
26
+
27
+ function SelectTrigger({
28
+ className,
29
+ size = "default",
30
+ children,
31
+ ...props
32
+ }: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
33
+ size?: "sm" | "default"
34
+ }) {
35
+ return (
36
+ <SelectPrimitive.Trigger
37
+ data-slot="select-trigger"
38
+ data-size={size}
39
+ className={cn(
40
+ "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",
41
+ className
42
+ )}
43
+ {...props}
44
+ >
45
+ {children}
46
+ <SelectPrimitive.Icon asChild>
47
+ <ChevronDownIcon className="size-4 opacity-50" />
48
+ </SelectPrimitive.Icon>
49
+ </SelectPrimitive.Trigger>
50
+ )
51
+ }
52
+
53
+ function SelectContent({
54
+ className,
55
+ children,
56
+ position = "popper",
57
+ ...props
58
+ }: React.ComponentProps<typeof SelectPrimitive.Content>) {
59
+ return (
60
+ <SelectPrimitive.Portal>
61
+ <SelectPrimitive.Content
62
+ data-slot="select-content"
63
+ className={cn(
64
+ "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",
65
+ position === "popper" &&
66
+ "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
67
+ className
68
+ )}
69
+ position={position}
70
+ {...props}
71
+ >
72
+ <SelectScrollUpButton />
73
+ <SelectPrimitive.Viewport
74
+ className={cn(
75
+ "p-1",
76
+ position === "popper" &&
77
+ "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
78
+ )}
79
+ >
80
+ {children}
81
+ </SelectPrimitive.Viewport>
82
+ <SelectScrollDownButton />
83
+ </SelectPrimitive.Content>
84
+ </SelectPrimitive.Portal>
85
+ )
86
+ }
87
+
88
+ function SelectLabel({
89
+ className,
90
+ ...props
91
+ }: React.ComponentProps<typeof SelectPrimitive.Label>) {
92
+ return (
93
+ <SelectPrimitive.Label
94
+ data-slot="select-label"
95
+ className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
96
+ {...props}
97
+ />
98
+ )
99
+ }
100
+
101
+ function SelectItem({
102
+ className,
103
+ children,
104
+ ...props
105
+ }: React.ComponentProps<typeof SelectPrimitive.Item>) {
106
+ return (
107
+ <SelectPrimitive.Item
108
+ data-slot="select-item"
109
+ className={cn(
110
+ "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",
111
+ className
112
+ )}
113
+ {...props}
114
+ >
115
+ <span className="absolute right-2 flex size-3.5 items-center justify-center">
116
+ <SelectPrimitive.ItemIndicator>
117
+ <CheckIcon className="size-4" />
118
+ </SelectPrimitive.ItemIndicator>
119
+ </span>
120
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
121
+ </SelectPrimitive.Item>
122
+ )
123
+ }
124
+
125
+ function SelectSeparator({
126
+ className,
127
+ ...props
128
+ }: React.ComponentProps<typeof SelectPrimitive.Separator>) {
129
+ return (
130
+ <SelectPrimitive.Separator
131
+ data-slot="select-separator"
132
+ className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
133
+ {...props}
134
+ />
135
+ )
136
+ }
137
+
138
+ function SelectScrollUpButton({
139
+ className,
140
+ ...props
141
+ }: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
142
+ return (
143
+ <SelectPrimitive.ScrollUpButton
144
+ data-slot="select-scroll-up-button"
145
+ className={cn(
146
+ "flex cursor-default items-center justify-center py-1",
147
+ className
148
+ )}
149
+ {...props}
150
+ >
151
+ <ChevronUpIcon className="size-4" />
152
+ </SelectPrimitive.ScrollUpButton>
153
+ )
154
+ }
155
+
156
+ function SelectScrollDownButton({
157
+ className,
158
+ ...props
159
+ }: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
160
+ return (
161
+ <SelectPrimitive.ScrollDownButton
162
+ data-slot="select-scroll-down-button"
163
+ className={cn(
164
+ "flex cursor-default items-center justify-center py-1",
165
+ className
166
+ )}
167
+ {...props}
168
+ >
169
+ <ChevronDownIcon className="size-4" />
170
+ </SelectPrimitive.ScrollDownButton>
171
+ )
172
+ }
173
+
174
+ export {
175
+ Select,
176
+ SelectContent,
177
+ SelectGroup,
178
+ SelectItem,
179
+ SelectLabel,
180
+ SelectScrollDownButton,
181
+ SelectScrollUpButton,
182
+ SelectSeparator,
183
+ SelectTrigger,
184
+ SelectValue,
185
+ }
components/ui/tabs.tsx ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as TabsPrimitive from "@radix-ui/react-tabs"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ function Tabs({
9
+ className,
10
+ ...props
11
+ }: React.ComponentProps<typeof TabsPrimitive.Root>) {
12
+ return (
13
+ <TabsPrimitive.Root
14
+ data-slot="tabs"
15
+ className={cn("flex flex-col gap-2", className)}
16
+ {...props}
17
+ />
18
+ )
19
+ }
20
+
21
+ function TabsList({
22
+ className,
23
+ ...props
24
+ }: React.ComponentProps<typeof TabsPrimitive.List>) {
25
+ return (
26
+ <TabsPrimitive.List
27
+ data-slot="tabs-list"
28
+ className={cn(
29
+ "bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
30
+ className
31
+ )}
32
+ {...props}
33
+ />
34
+ )
35
+ }
36
+
37
+ function TabsTrigger({
38
+ className,
39
+ ...props
40
+ }: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
41
+ return (
42
+ <TabsPrimitive.Trigger
43
+ data-slot="tabs-trigger"
44
+ className={cn(
45
+ "data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
46
+ className
47
+ )}
48
+ {...props}
49
+ />
50
+ )
51
+ }
52
+
53
+ function TabsContent({
54
+ className,
55
+ ...props
56
+ }: React.ComponentProps<typeof TabsPrimitive.Content>) {
57
+ return (
58
+ <TabsPrimitive.Content
59
+ data-slot="tabs-content"
60
+ className={cn("flex-1 outline-none", className)}
61
+ {...props}
62
+ />
63
+ )
64
+ }
65
+
66
+ export { Tabs, TabsList, TabsTrigger, TabsContent }
lib/utils.ts ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ import { type ClassValue, clsx } from "clsx"
2
+ import { twMerge } from "tailwind-merge"
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }
next.config.js ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('next').NextConfig} */
2
+ const nextConfig = {
3
+ reactStrictMode: true,
4
+ images: {
5
+ domains: ['api.placeholder.com'],
6
+ },
7
+ }
8
+
9
+ module.exports = nextConfig
package.json ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "@hanzo/template-ecommerce-storefront",
3
+ "version": "1.0.0",
4
+ "description": "Complete online store with cart",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/hanzoai/template-ecommerce-storefront"
8
+ },
9
+ "scripts": {
10
+ "dev": "next dev",
11
+ "build": "next build",
12
+ "start": "next start",
13
+ "lint": "next lint",
14
+ "preview": "vite preview"
15
+ },
16
+ "dependencies": {
17
+ "next": "14.2.5",
18
+ "react": "^18.3.1",
19
+ "react-dom": "^18.3.1",
20
+ "lucide-react": "^0.400.0",
21
+ "clsx": "^2.1.1",
22
+ "tailwind-merge": "^2.3.0",
23
+ "class-variance-authority": "^0.7.0",
24
+ "@radix-ui/react-accordion": "^1.1.2",
25
+ "@radix-ui/react-alert-dialog": "^1.0.5",
26
+ "@radix-ui/react-aspect-ratio": "^1.0.3",
27
+ "@radix-ui/react-avatar": "^1.0.4",
28
+ "@radix-ui/react-checkbox": "^1.0.4",
29
+ "@radix-ui/react-dialog": "^1.0.5",
30
+ "@radix-ui/react-dropdown-menu": "^2.0.6",
31
+ "@radix-ui/react-label": "^2.0.2",
32
+ "@radix-ui/react-popover": "^1.0.7",
33
+ "@radix-ui/react-progress": "^1.0.3",
34
+ "@radix-ui/react-scroll-area": "^1.0.5",
35
+ "@radix-ui/react-select": "^2.0.0",
36
+ "@radix-ui/react-separator": "^1.0.3",
37
+ "@radix-ui/react-slot": "^1.0.2",
38
+ "@radix-ui/react-switch": "^1.0.3",
39
+ "@radix-ui/react-tabs": "^1.0.4",
40
+ "@radix-ui/react-toggle": "^1.0.3",
41
+ "@radix-ui/react-toggle-group": "^1.0.4",
42
+ "@radix-ui/react-tooltip": "^1.0.7"
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "^20",
46
+ "@types/react": "^18",
47
+ "@types/react-dom": "^18",
48
+ "autoprefixer": "^10.4.19",
49
+ "eslint": "^8",
50
+ "eslint-config-next": "14.2.5",
51
+ "postcss": "^8.4.39",
52
+ "tailwindcss": "^3.4.4",
53
+ "typescript": "^5",
54
+ "vite": "^5.0.0"
55
+ }
56
+ }
postcss.config.js ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ module.exports = {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
src/App.tsx ADDED
@@ -0,0 +1,293 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { Card, CardContent, CardFooter } from "@/components/ui/card";
5
+ import { Button } from "@/components/ui/button";
6
+ import { Badge } from "@/components/ui/badge";
7
+ import { Input } from "@/components/ui/input";
8
+ import { AspectRatio } from "@/components/ui/aspect-ratio";
9
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
10
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
11
+ import { ShoppingCart, Search, Star, Filter, Heart, Share2 } from "lucide-react";
12
+
13
+ const products = [
14
+ {
15
+ id: "1",
16
+ name: "Premium Wireless Headphones",
17
+ price: 299.99,
18
+ image: "/api/placeholder/400/400",
19
+ rating: 4.5,
20
+ reviews: 234,
21
+ badge: "Best Seller",
22
+ variants: ["Black", "White", "Blue"]
23
+ },
24
+ {
25
+ id: "2",
26
+ name: "Smart Watch Pro",
27
+ price: 399.99,
28
+ image: "/api/placeholder/400/400",
29
+ rating: 4.8,
30
+ reviews: 567,
31
+ badge: "New",
32
+ variants: ["Silver", "Gold", "Space Gray"]
33
+ },
34
+ {
35
+ id: "3",
36
+ name: "Portable Speaker",
37
+ price: 149.99,
38
+ image: "/api/placeholder/400/400",
39
+ rating: 4.3,
40
+ reviews: 189,
41
+ badge: null,
42
+ variants: ["Red", "Black", "Green"]
43
+ },
44
+ {
45
+ id: "4",
46
+ name: "Laptop Stand",
47
+ price: 79.99,
48
+ image: "/api/placeholder/400/400",
49
+ rating: 4.6,
50
+ reviews: 432,
51
+ badge: "Sale",
52
+ variants: ["Aluminum", "Wood"]
53
+ }
54
+ ];
55
+
56
+ const categories = ["All Products", "Electronics", "Accessories", "Audio", "Computing"];
57
+
58
+ export default function EcommerceStorefront() {
59
+ const [selectedCategory, setSelectedCategory] = useState("All Products");
60
+ const [cart, setCart] = useState<{ id: string; quantity: number }[]>([]);
61
+
62
+ const addToCart = (productId: string) => {
63
+ setCart(prev => {
64
+ const existing = prev.find(item => item.id === productId);
65
+ if (existing) {
66
+ return prev.map(item =>
67
+ item.id === productId
68
+ ? { ...item, quantity: item.quantity + 1 }
69
+ : item
70
+ );
71
+ }
72
+ return [...prev, { id: productId, quantity: 1 }];
73
+ });
74
+ };
75
+
76
+ const cartItemsCount = cart.reduce((sum, item) => sum + item.quantity, 0);
77
+
78
+ return (
79
+ <div className="min-h-screen bg-background">
80
+ {/* Header */}
81
+ <header className="border-b sticky top-0 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 z-50">
82
+ <div className="container mx-auto px-6 py-4">
83
+ <div className="flex items-center justify-between">
84
+ <div className="flex items-center gap-8">
85
+ <h1 className="text-2xl font-bold">Store</h1>
86
+ <nav className="hidden md:flex items-center gap-6">
87
+ {categories.map(category => (
88
+ <button
89
+ key={category}
90
+ onClick={() => setSelectedCategory(category)}
91
+ className={`text-sm font-medium transition-colors hover:text-primary ${
92
+ selectedCategory === category
93
+ ? "text-primary"
94
+ : "text-muted-foreground"
95
+ }`}
96
+ >
97
+ {category}
98
+ </button>
99
+ ))}
100
+ </nav>
101
+ </div>
102
+
103
+ <div className="flex items-center gap-4">
104
+ <div className="relative hidden md:block">
105
+ <Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
106
+ <Input
107
+ placeholder="Search products..."
108
+ className="pl-9 w-[200px] lg:w-[300px]"
109
+ />
110
+ </div>
111
+
112
+ <Button variant="ghost" size="icon" className="relative">
113
+ <ShoppingCart className="w-5 h-5" />
114
+ {cartItemsCount > 0 && (
115
+ <Badge className="absolute -top-1 -right-1 h-5 w-5 rounded-full p-0 flex items-center justify-center">
116
+ {cartItemsCount}
117
+ </Badge>
118
+ )}
119
+ </Button>
120
+ </div>
121
+ </div>
122
+ </div>
123
+ </header>
124
+
125
+ {/* Hero Section - Orange/Pink Gradient Theme */}
126
+ <section className="bg-gradient-to-r from-orange-500 to-pink-500 text-white py-16">
127
+ <div className="container mx-auto px-6">
128
+ <div className="max-w-3xl">
129
+ <h2 className="text-4xl font-bold mb-4">
130
+ Summer Collection
131
+ </h2>
132
+ <p className="text-xl mb-6 opacity-90">
133
+ Discover our latest products built with @hanzo/ui components
134
+ </p>
135
+ <Button size="lg" variant="secondary">
136
+ Shop Now
137
+ </Button>
138
+ </div>
139
+ </div>
140
+ </section>
141
+
142
+ {/* Filters Bar */}
143
+ <div className="border-b">
144
+ <div className="container mx-auto px-6 py-4">
145
+ <div className="flex items-center justify-between">
146
+ <div className="flex items-center gap-4">
147
+ <Button variant="outline" className="gap-2">
148
+ <Filter className="w-4 h-4" />
149
+ Filters
150
+ </Button>
151
+ <Select defaultValue="featured">
152
+ <SelectTrigger className="w-[180px]">
153
+ <SelectValue />
154
+ </SelectTrigger>
155
+ <SelectContent>
156
+ <SelectItem value="featured">Featured</SelectItem>
157
+ <SelectItem value="price-low">Price: Low to High</SelectItem>
158
+ <SelectItem value="price-high">Price: High to Low</SelectItem>
159
+ <SelectItem value="rating">Highest Rated</SelectItem>
160
+ <SelectItem value="newest">Newest</SelectItem>
161
+ </SelectContent>
162
+ </Select>
163
+ </div>
164
+ <p className="text-sm text-muted-foreground">
165
+ Showing {products.length} products
166
+ </p>
167
+ </div>
168
+ </div>
169
+ </div>
170
+
171
+ {/* Products Grid */}
172
+ <section className="py-12">
173
+ <div className="container mx-auto px-6">
174
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
175
+ {products.map(product => (
176
+ <Card key={product.id} className="overflow-hidden group">
177
+ <div className="relative">
178
+ <AspectRatio ratio={1}>
179
+ <img
180
+ src={product.image}
181
+ alt={product.name}
182
+ className="object-cover w-full h-full group-hover:scale-105 transition-transform"
183
+ />
184
+ </AspectRatio>
185
+ {product.badge && (
186
+ <Badge className="absolute top-2 left-2">
187
+ {product.badge}
188
+ </Badge>
189
+ )}
190
+ <div className="absolute top-2 right-2 flex flex-col gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
191
+ <Button size="icon" variant="secondary" className="h-8 w-8">
192
+ <Heart className="w-4 h-4" />
193
+ </Button>
194
+ <Button size="icon" variant="secondary" className="h-8 w-8">
195
+ <Share2 className="w-4 h-4" />
196
+ </Button>
197
+ </div>
198
+ </div>
199
+
200
+ <CardContent className="p-4">
201
+ <h3 className="font-semibold mb-2">{product.name}</h3>
202
+ <div className="flex items-center gap-2 mb-2">
203
+ <div className="flex items-center">
204
+ {[...Array(5)].map((_, i) => (
205
+ <Star
206
+ key={i}
207
+ className={`w-4 h-4 ${
208
+ i < Math.floor(product.rating)
209
+ ? "fill-yellow-400 text-yellow-400"
210
+ : "text-muted-foreground"
211
+ }`}
212
+ />
213
+ ))}
214
+ </div>
215
+ <span className="text-sm text-muted-foreground">
216
+ ({product.reviews})
217
+ </span>
218
+ </div>
219
+ <div className="flex items-center justify-between mb-3">
220
+ <span className="text-2xl font-bold">
221
+ ${product.price}
222
+ </span>
223
+ </div>
224
+
225
+ {/* Variant Selector */}
226
+ <div className="mb-3">
227
+ <Select defaultValue={product.variants[0]}>
228
+ <SelectTrigger className="w-full h-8 text-sm">
229
+ <SelectValue />
230
+ </SelectTrigger>
231
+ <SelectContent>
232
+ {product.variants.map(variant => (
233
+ <SelectItem key={variant} value={variant}>
234
+ {variant}
235
+ </SelectItem>
236
+ ))}
237
+ </SelectContent>
238
+ </Select>
239
+ </div>
240
+ </CardContent>
241
+
242
+ <CardFooter className="p-4 pt-0">
243
+ <Button
244
+ className="w-full"
245
+ onClick={() => addToCart(product.id)}
246
+ >
247
+ <ShoppingCart className="w-4 h-4 mr-2" />
248
+ Add to Cart
249
+ </Button>
250
+ </CardFooter>
251
+ </Card>
252
+ ))}
253
+ </div>
254
+ </div>
255
+ </section>
256
+
257
+ {/* Features */}
258
+ <section className="py-12 bg-muted">
259
+ <div className="container mx-auto px-6">
260
+ <div className="grid md:grid-cols-3 gap-8">
261
+ <div className="text-center">
262
+ <div className="w-12 h-12 rounded-full bg-orange-500/10 flex items-center justify-center mx-auto mb-3">
263
+ <ShoppingCart className="w-6 h-6 text-orange-500" />
264
+ </div>
265
+ <h3 className="font-semibold mb-1">Free Shipping</h3>
266
+ <p className="text-sm text-muted-foreground">
267
+ On orders over $100
268
+ </p>
269
+ </div>
270
+ <div className="text-center">
271
+ <div className="w-12 h-12 rounded-full bg-pink-500/10 flex items-center justify-center mx-auto mb-3">
272
+ <Star className="w-6 h-6 text-pink-500" />
273
+ </div>
274
+ <h3 className="font-semibold mb-1">Quality Products</h3>
275
+ <p className="text-sm text-muted-foreground">
276
+ 100% authentic brands
277
+ </p>
278
+ </div>
279
+ <div className="text-center">
280
+ <div className="w-12 h-12 rounded-full bg-rose-500/10 flex items-center justify-center mx-auto mb-3">
281
+ <Heart className="w-6 h-6 text-rose-500" />
282
+ </div>
283
+ <h3 className="font-semibold mb-1">24/7 Support</h3>
284
+ <p className="text-sm text-muted-foreground">
285
+ Dedicated customer service
286
+ </p>
287
+ </div>
288
+ </div>
289
+ </div>
290
+ </section>
291
+ </div>
292
+ );
293
+ }
tailwind.config.js ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('tailwindcss').Config} */
2
+ module.exports = {
3
+ darkMode: ["class"],
4
+ content: [
5
+ './pages/**/*.{ts,tsx}',
6
+ './components/**/*.{ts,tsx}',
7
+ './app/**/*.{ts,tsx}',
8
+ './src/**/*.{ts,tsx}',
9
+ ],
10
+ theme: {
11
+ container: {
12
+ center: true,
13
+ padding: "2rem",
14
+ screens: {
15
+ "2xl": "1400px",
16
+ },
17
+ },
18
+ extend: {
19
+ colors: {
20
+ border: "hsl(var(--border))",
21
+ input: "hsl(var(--input))",
22
+ ring: "hsl(var(--ring))",
23
+ background: "hsl(var(--background))",
24
+ foreground: "hsl(var(--foreground))",
25
+ primary: {
26
+ DEFAULT: "hsl(var(--primary))",
27
+ foreground: "hsl(var(--primary-foreground))",
28
+ },
29
+ secondary: {
30
+ DEFAULT: "hsl(var(--secondary))",
31
+ foreground: "hsl(var(--secondary-foreground))",
32
+ },
33
+ destructive: {
34
+ DEFAULT: "hsl(var(--destructive))",
35
+ foreground: "hsl(var(--destructive-foreground))",
36
+ },
37
+ muted: {
38
+ DEFAULT: "hsl(var(--muted))",
39
+ foreground: "hsl(var(--muted-foreground))",
40
+ },
41
+ accent: {
42
+ DEFAULT: "hsl(var(--accent))",
43
+ foreground: "hsl(var(--accent-foreground))",
44
+ },
45
+ popover: {
46
+ DEFAULT: "hsl(var(--popover))",
47
+ foreground: "hsl(var(--popover-foreground))",
48
+ },
49
+ card: {
50
+ DEFAULT: "hsl(var(--card))",
51
+ foreground: "hsl(var(--card-foreground))",
52
+ },
53
+ },
54
+ borderRadius: {
55
+ lg: "var(--radius)",
56
+ md: "calc(var(--radius) - 2px)",
57
+ sm: "calc(var(--radius) - 4px)",
58
+ },
59
+ keyframes: {
60
+ "accordion-down": {
61
+ from: { height: 0 },
62
+ to: { height: "var(--radix-accordion-content-height)" },
63
+ },
64
+ "accordion-up": {
65
+ from: { height: "var(--radix-accordion-content-height)" },
66
+ to: { height: 0 },
67
+ },
68
+ },
69
+ animation: {
70
+ "accordion-down": "accordion-down 0.2s ease-out",
71
+ "accordion-up": "accordion-up 0.2s ease-out",
72
+ },
73
+ },
74
+ },
75
+ plugins: [],
76
+ }
tsconfig.json ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "preserve",
15
+ "incremental": true,
16
+ "plugins": [
17
+ {
18
+ "name": "next"
19
+ }
20
+ ],
21
+ "paths": {
22
+ "@/*": ["./*"]
23
+ }
24
+ },
25
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26
+ "exclude": ["node_modules"]
27
+ }