Preview
Full width desktop view
Code
marketplace-2.tsx
1"use client";
2import React, { useState } from "react";
3import {
4 Card,
5 CardContent,
6 CardDescription,
7 CardHeader,
8 CardTitle,
9} from "@/components/ui/card";
10import { Button } from "@/components/ui/button";
11import { Badge } from "@/components/ui/badge";
12import { Separator } from "@/components/ui/separator";
13import {
14 Plus,
15 Minus,
16 Check,
17 Star,
18 Zap,
19 Globe,
20 Shield,
21 BarChart3,
22} from "lucide-react";
23
24interface AddOn {
25 id: string;
26 name: string;
27 description: string;
28 price: number;
29 period: "month" | "year";
30 icon: React.ReactNode;
31 category: string;
32 isMultiple: boolean;
33 maxQuantity?: number;
34 popular?: boolean;
35 features: string[];
36}
37
38interface CartItem {
39 addOnId: string;
40 quantity: number;
41}
42
43const AddOnsMarketplace: React.FC = () => {
44 const [cart, setCart] = useState<CartItem[]>([]);
45
46 const addOns: AddOn[] = [
47 {
48 id: "extra-pages",
49 name: "Extra Pages",
50 description: "Add additional pages to your website",
51 price: 10,
52 period: "month",
53 icon: <Plus className="w-5 h-5" />,
54 category: "Content",
55 isMultiple: true,
56 maxQuantity: 50,
57 features: ["Custom page design", "SEO optimization", "Mobile responsive"],
58 },
59 {
60 id: "custom-domain",
61 name: "Custom Domain",
62 description: "Connect your own domain name",
63 price: 15,
64 period: "month",
65 icon: <Globe className="w-5 h-5" />,
66 category: "Domain",
67 isMultiple: false,
68 popular: true,
69 features: [
70 "Free SSL certificate",
71 "Domain management",
72 "DNS configuration",
73 ],
74 },
75 {
76 id: "analytics-pro",
77 name: "Analytics Pro",
78 description: "Advanced analytics and reporting",
79 price: 25,
80 period: "month",
81 icon: <BarChart3 className="w-5 h-5" />,
82 category: "Analytics",
83 isMultiple: false,
84 features: ["Advanced metrics", "Custom reports", "Data export"],
85 },
86 {
87 id: "premium-support",
88 name: "Premium Support",
89 description: "Priority customer support",
90 price: 20,
91 period: "month",
92 icon: <Shield className="w-5 h-5" />,
93 category: "Support",
94 isMultiple: false,
95 features: ["24/7 support", "Phone support", "Dedicated account manager"],
96 },
97 {
98 id: "storage-boost",
99 name: "Storage Boost",
100 description: "Additional storage space (10GB per unit)",
101 price: 5,
102 period: "month",
103 icon: <Zap className="w-5 h-5" />,
104 category: "Storage",
105 isMultiple: true,
106 maxQuantity: 20,
107 features: [
108 "10GB additional storage",
109 "Automatic backups",
110 "CDN acceleration",
111 ],
112 },
113 ];
114
115 const getCartItem = (addOnId: string): CartItem | undefined => {
116 return cart.find((item) => item.addOnId === addOnId);
117 };
118
119 const updateQuantity = (addOnId: string, quantity: number) => {
120 const addOn = addOns.find((a) => a.id === addOnId);
121 if (!addOn) return;
122
123 if (quantity <= 0) {
124 setCart(cart.filter((item) => item.addOnId !== addOnId));
125 return;
126 }
127
128 if (!addOn.isMultiple && quantity > 1) return;
129 if (addOn.maxQuantity && quantity > addOn.maxQuantity) return;
130
131 const existingItem = cart.find((item) => item.addOnId === addOnId);
132 if (existingItem) {
133 setCart(
134 cart.map((item) =>
135 item.addOnId === addOnId ? { ...item, quantity } : item
136 )
137 );
138 } else {
139 setCart([...cart, { addOnId, quantity }]);
140 }
141 };
142
143 const getTotalPrice = (): number => {
144 return cart.reduce((total, item) => {
145 const addOn = addOns.find((a) => a.id === item.addOnId);
146 return total + (addOn ? addOn.price * item.quantity : 0);
147 }, 0);
148 };
149
150 const categories = Array.from(new Set(addOns.map((addon) => addon.category)));
151
152 return (
153 <div className="min-h-screen bg-background p-6">
154 <div className="max-w-7xl mx-auto">
155 <div className="text-center mb-8">
156 <h1 className="text-4xl font-bold text-foreground mb-4">
157 Add-Ons Marketplace
158 </h1>
159 <p className="text-muted-foreground text-lg max-w-2xl mx-auto">
160 Enhance your experience with our premium add-ons. Choose from a
161 variety of features to customize your plan.
162 </p>
163 </div>
164
165 <div className="grid grid-cols-1 lg:grid-cols-4 gap-8">
166 <div className="lg:col-span-3">
167 {categories.map((category) => (
168 <div key={category} className="mb-8">
169 <h2 className="text-2xl font-semibold text-foreground mb-4">
170 {category}
171 </h2>
172 <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
173 {addOns
174 .filter((addon) => addon.category === category)
175 .map((addon) => {
176 const cartItem = getCartItem(addon.id);
177 const quantity = cartItem?.quantity || 0;
178
179 return (
180 <Card
181 key={addon.id}
182 className="relative border-border hover:shadow-lg transition-shadow"
183 >
184 {addon.popular && (
185 <Badge className="absolute -top-2 -right-2 bg-primary text-primary-foreground">
186 <Star className="w-3 h-3 mr-1" />
187 Popular
188 </Badge>
189 )}
190
191 <CardHeader>
192 <div className="flex items-center gap-3">
193 <div className="p-2 bg-primary/10 rounded-lg text-primary">
194 {addon.icon}
195 </div>
196 <div>
197 <CardTitle className="text-lg">
198 {addon.name}
199 </CardTitle>
200 <CardDescription>
201 {addon.description}
202 </CardDescription>
203 </div>
204 </div>
205 </CardHeader>
206
207 <CardContent>
208 <div className="space-y-4">
209 <div className="flex items-center justify-between">
210 <div className="text-2xl font-bold text-foreground">
211 ${addon.price}
212 <span className="text-sm text-muted-foreground">
213 /{addon.period}
214 </span>
215 {addon.isMultiple && (
216 <span className="text-sm text-muted-foreground ml-1">
217 per unit
218 </span>
219 )}
220 </div>
221
222 <div className="flex items-center gap-2">
223 {addon.isMultiple ? (
224 <>
225 <Button
226 variant="outline"
227 size="sm"
228 onClick={() =>
229 updateQuantity(addon.id, quantity - 1)
230 }
231 disabled={quantity === 0}
232 >
233 <Minus className="w-4 h-4" />
234 </Button>
235 <span className="w-8 text-center font-medium">
236 {quantity}
237 </span>
238 <Button
239 variant="outline"
240 size="sm"
241 onClick={() =>
242 updateQuantity(addon.id, quantity + 1)
243 }
244 disabled={
245 addon.maxQuantity
246 ? quantity >= addon.maxQuantity
247 : false
248 }
249 >
250 <Plus className="w-4 h-4" />
251 </Button>
252 </>
253 ) : (
254 <Button
255 variant={
256 quantity > 0 ? "default" : "outline"
257 }
258 size="sm"
259 onClick={() =>
260 updateQuantity(
261 addon.id,
262 quantity > 0 ? 0 : 1
263 )
264 }
265 >
266 {quantity > 0 ? (
267 <>
268 <Check className="w-4 h-4 mr-1" />
269 Added
270 </>
271 ) : (
272 "Add to Cart"
273 )}
274 </Button>
275 )}
276 </div>
277 </div>
278
279 <div className="space-y-2">
280 <h4 className="font-medium text-sm text-foreground">
281 Features:
282 </h4>
283 <ul className="space-y-1">
284 {addon.features.map((feature, index) => (
285 <li
286 key={index}
287 className="text-sm text-muted-foreground flex items-center gap-2"
288 >
289 <Check className="w-3 h-3 text-primary" />
290 {feature}
291 </li>
292 ))}
293 </ul>
294 </div>
295
296 {addon.maxQuantity && addon.isMultiple && (
297 <p className="text-xs text-muted-foreground">
298 Maximum {addon.maxQuantity} units
299 </p>
300 )}
301 </div>
302 </CardContent>
303 </Card>
304 );
305 })}
306 </div>
307 </div>
308 ))}
309 </div>
310
311 <div className="lg:col-span-1">
312 <Card className="sticky top-6 border-border">
313 <CardHeader>
314 <CardTitle>Order Summary</CardTitle>
315 <CardDescription>Review your selected add-ons</CardDescription>
316 </CardHeader>
317
318 <CardContent>
319 <div className="space-y-4">
320 {cart.length === 0 ? (
321 <p className="text-muted-foreground text-center py-4">
322 No add-ons selected
323 </p>
324 ) : (
325 <>
326 {cart.map((item) => {
327 const addon = addOns.find((a) => a.id === item.addOnId);
328 if (!addon) return null;
329
330 return (
331 <div
332 key={item.addOnId}
333 className="flex justify-between items-center"
334 >
335 <div>
336 <p className="font-medium text-sm">
337 {addon.name}
338 </p>
339 {addon.isMultiple && (
340 <p className="text-xs text-muted-foreground">
341 Quantity: {item.quantity}
342 </p>
343 )}
344 </div>
345 <p className="font-medium">
346 ${addon.price * item.quantity}/{addon.period}
347 </p>
348 </div>
349 );
350 })}
351
352 <Separator />
353
354 <div className="flex justify-between items-center font-bold">
355 <span>Total</span>
356 <span>${getTotalPrice()}/month</span>
357 </div>
358
359 <Button className="w-full" size="lg">
360 Proceed to Checkout
361 </Button>
362 </>
363 )}
364 </div>
365 </CardContent>
366 </Card>
367 </div>
368 </div>
369 </div>
370 </div>
371 );
372};
373
374export default AddOnsMarketplace;
Dependencies
External Libraries
lucide-reactreact
Shadcn/UI Components
badgebuttoncardseparator