Preview
Full width desktop view
Code
bills-1.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 { Plus, Minus, Check, CreditCard, Calendar } from "lucide-react";
14
15interface Plan {
16 id: string;
17 name: string;
18 price: number;
19 billing: "monthly" | "yearly";
20 features: string[];
21 popular?: boolean;
22}
23
24interface AddOn {
25 id: string;
26 name: string;
27 description: string;
28 price: number;
29 billing: "monthly" | "yearly";
30 multipleUse: boolean;
31 maxQuantity?: number;
32}
33
34interface SelectedAddOn extends AddOn {
35 quantity: number;
36}
37
38interface Bill {
39 id: string;
40 description: string;
41 amount: number;
42 dueDate: string;
43 status: "pending" | "paid" | "overdue";
44}
45
46const CustomerAdminDashboard: React.FC = () => {
47 const [selectedPlan, setSelectedPlan] = useState<Plan | null>(null);
48 const [selectedAddOns, setSelectedAddOns] = useState<SelectedAddOn[]>([]);
49 const [billingCycle, setBillingCycle] = useState<"monthly" | "yearly">(
50 "monthly"
51 );
52
53 const plans: Plan[] = [
54 {
55 id: "starter",
56 name: "Starter",
57 price: 29,
58 billing: "monthly",
59 features: [
60 "Up to 5 pages",
61 "Basic analytics",
62 "Email support",
63 "1GB storage",
64 ],
65 },
66 {
67 id: "professional",
68 name: "Professional",
69 price: 79,
70 billing: "monthly",
71 features: [
72 "Up to 25 pages",
73 "Advanced analytics",
74 "Priority support",
75 "10GB storage",
76 "Custom integrations",
77 ],
78 popular: true,
79 },
80 {
81 id: "enterprise",
82 name: "Enterprise",
83 price: 199,
84 billing: "monthly",
85 features: [
86 "Unlimited pages",
87 "Enterprise analytics",
88 "24/7 phone support",
89 "100GB storage",
90 "White-label solution",
91 "API access",
92 ],
93 },
94 ];
95
96 const addOns: AddOn[] = [
97 {
98 id: "extra-page",
99 name: "Additional Page",
100 description: "Add extra pages to your plan",
101 price: 10,
102 billing: "monthly",
103 multipleUse: true,
104 maxQuantity: 50,
105 },
106 {
107 id: "custom-domain",
108 name: "Custom Domain",
109 description: "Connect your own domain",
110 price: 15,
111 billing: "monthly",
112 multipleUse: false,
113 },
114 {
115 id: "ssl-certificate",
116 name: "SSL Certificate",
117 description: "Secure your website with SSL",
118 price: 25,
119 billing: "monthly",
120 multipleUse: false,
121 },
122 {
123 id: "extra-storage",
124 name: "Additional Storage",
125 description: "Extra 5GB storage per unit",
126 price: 8,
127 billing: "monthly",
128 multipleUse: true,
129 maxQuantity: 20,
130 },
131 ];
132
133 const currentBills: Bill[] = [
134 {
135 id: "bill-1",
136 description: "Professional Plan - January 2024",
137 amount: 79,
138 dueDate: "2024-01-15",
139 status: "pending",
140 },
141 {
142 id: "bill-2",
143 description: "Custom Domain Add-on",
144 amount: 15,
145 dueDate: "2024-01-15",
146 status: "pending",
147 },
148 {
149 id: "bill-3",
150 description: "Additional Pages (3x)",
151 amount: 30,
152 dueDate: "2024-01-15",
153 status: "pending",
154 },
155 ];
156
157 const handlePlanSelect = (plan: Plan) => {
158 setSelectedPlan(plan);
159 };
160
161 const handleAddOnToggle = (addOn: AddOn) => {
162 const existingIndex = selectedAddOns.findIndex(
163 (item) => item.id === addOn.id
164 );
165
166 if (existingIndex >= 0) {
167 setSelectedAddOns((prev) => prev.filter((item) => item.id !== addOn.id));
168 } else {
169 setSelectedAddOns((prev) => [...prev, { ...addOn, quantity: 1 }]);
170 }
171 };
172
173 const handleQuantityChange = (addOnId: string, change: number) => {
174 setSelectedAddOns((prev) =>
175 prev.map((item) => {
176 if (item.id === addOnId) {
177 const newQuantity = Math.max(
178 1,
179 Math.min(item.maxQuantity || 999, item.quantity + change)
180 );
181 return { ...item, quantity: newQuantity };
182 }
183 return item;
184 })
185 );
186 };
187
188 const calculateTotal = () => {
189 const planCost = selectedPlan ? selectedPlan.price : 0;
190 const addOnsCost = selectedAddOns.reduce(
191 (total, addOn) => total + addOn.price * addOn.quantity,
192 0
193 );
194 return planCost + addOnsCost;
195 };
196
197 const totalBillAmount = currentBills.reduce(
198 (total, bill) => total + bill.amount,
199 0
200 );
201
202 const getBillStatusColor = (status: string) => {
203 switch (status) {
204 case "paid":
205 return "bg-green-100 text-green-800 border-green-200";
206 case "overdue":
207 return "bg-red-100 text-red-800 border-red-200";
208 default:
209 return "bg-yellow-100 text-yellow-800 border-yellow-200";
210 }
211 };
212
213 return (
214 <div className="min-h-screen bg-background p-6">
215 <div className="max-w-7xl mx-auto space-y-8">
216 {/* Header */}
217 <div className="space-y-2">
218 <h1 className="text-3xl font-bold text-foreground">
219 Customer Admin Dashboard
220 </h1>
221 <p className="text-muted-foreground">
222 Manage your subscription plans and billing
223 </p>
224 </div>
225
226 <div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
227 {/* Plans Section */}
228 <div className="lg:col-span-2 space-y-6">
229 {/* Billing Toggle */}
230 <div className="flex items-center justify-center space-x-4">
231 <span
232 className={
233 billingCycle === "monthly"
234 ? "text-foreground font-medium"
235 : "text-muted-foreground"
236 }
237 >
238 Monthly
239 </span>
240 <Button
241 variant="outline"
242 size="sm"
243 onClick={() =>
244 setBillingCycle(
245 billingCycle === "monthly" ? "yearly" : "monthly"
246 )
247 }
248 className="relative"
249 >
250 <div
251 className={`w-12 h-6 rounded-full transition-colors ${
252 billingCycle === "yearly" ? "bg-primary" : "bg-muted"
253 }`}
254 >
255 <div
256 className={`w-5 h-5 bg-white rounded-full shadow-sm transition-transform transform ${
257 billingCycle === "yearly"
258 ? "translate-x-6"
259 : "translate-x-0.5"
260 } mt-0.5`}
261 />
262 </div>
263 </Button>
264 <span
265 className={
266 billingCycle === "yearly"
267 ? "text-foreground font-medium"
268 : "text-muted-foreground"
269 }
270 >
271 Yearly{" "}
272 <Badge variant="secondary" className="ml-1">
273 Save 20%
274 </Badge>
275 </span>
276 </div>
277
278 {/* Plans Grid */}
279 <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
280 {plans.map((plan) => (
281 <Card
282 key={plan.id}
283 className={`relative cursor-pointer transition-all hover:shadow-lg ${
284 selectedPlan?.id === plan.id
285 ? "ring-2 ring-primary border-primary"
286 : "border-border"
287 }`}
288 onClick={() => handlePlanSelect(plan)}
289 >
290 {plan.popular && (
291 <Badge className="absolute -top-2 left-1/2 transform -translate-x-1/2 bg-primary">
292 Most Popular
293 </Badge>
294 )}
295 <CardHeader className="text-center">
296 <CardTitle className="text-xl">{plan.name}</CardTitle>
297 <div className="space-y-1">
298 <span className="text-3xl font-bold text-foreground">
299 $
300 {billingCycle === "yearly"
301 ? Math.round(plan.price * 0.8)
302 : plan.price}
303 </span>
304 <span className="text-muted-foreground">
305 /{billingCycle === "yearly" ? "year" : "month"}
306 </span>
307 </div>
308 </CardHeader>
309 <CardContent>
310 <ul className="space-y-2">
311 {plan.features.map((feature, index) => (
312 <li key={index} className="flex items-center space-x-2">
313 <Check className="h-4 w-4 text-green-500" />
314 <span className="text-sm text-muted-foreground">
315 {feature}
316 </span>
317 </li>
318 ))}
319 </ul>
320 {selectedPlan?.id === plan.id && (
321 <Badge
322 variant="secondary"
323 className="w-full justify-center mt-4"
324 >
325 Selected
326 </Badge>
327 )}
328 </CardContent>
329 </Card>
330 ))}
331 </div>
332
333 {/* Add-ons Section */}
334 <Card>
335 <CardHeader>
336 <CardTitle>Add-ons</CardTitle>
337 <CardDescription>
338 Enhance your plan with additional features
339 </CardDescription>
340 </CardHeader>
341 <CardContent className="space-y-4">
342 {addOns.map((addOn) => {
343 const selectedAddOn = selectedAddOns.find(
344 (item) => item.id === addOn.id
345 );
346 const isSelected = !!selectedAddOn;
347
348 return (
349 <div
350 key={addOn.id}
351 className="flex items-center justify-between p-4 border border-border rounded-lg"
352 >
353 <div className="flex-1">
354 <div className="flex items-center space-x-3">
355 <h4 className="font-medium text-foreground">
356 {addOn.name}
357 </h4>
358 <Badge variant="outline" className="text-xs">
359 ${addOn.price}/
360 {billingCycle === "yearly" ? "year" : "month"}
361 </Badge>
362 </div>
363 <p className="text-sm text-muted-foreground mt-1">
364 {addOn.description}
365 </p>
366 </div>
367
368 <div className="flex items-center space-x-3">
369 {isSelected && addOn.multipleUse && (
370 <div className="flex items-center space-x-2">
371 <Button
372 variant="outline"
373 size="sm"
374 onClick={() => handleQuantityChange(addOn.id, -1)}
375 disabled={selectedAddOn?.quantity === 1}
376 >
377 <Minus className="h-3 w-3" />
378 </Button>
379 <span className="w-8 text-center text-sm font-medium">
380 {selectedAddOn?.quantity}
381 </span>
382 <Button
383 variant="outline"
384 size="sm"
385 onClick={() => handleQuantityChange(addOn.id, 1)}
386 disabled={
387 selectedAddOn?.quantity === addOn.maxQuantity
388 }
389 >
390 <Plus className="h-3 w-3" />
391 </Button>
392 </div>
393 )}
394
395 <Button
396 variant={isSelected ? "default" : "outline"}
397 size="sm"
398 onClick={() => handleAddOnToggle(addOn)}
399 >
400 {isSelected ? "Remove" : "Add"}
401 </Button>
402 </div>
403 </div>
404 );
405 })}
406 </CardContent>
407 </Card>
408 </div>
409
410 {/* Sidebar */}
411 <div className="space-y-6">
412 {/* Current Selection Summary */}
413 <Card>
414 <CardHeader>
415 <CardTitle className="flex items-center space-x-2">
416 <CreditCard className="h-5 w-5" />
417 <span>Current Selection</span>
418 </CardTitle>
419 </CardHeader>
420 <CardContent className="space-y-4">
421 {selectedPlan ? (
422 <div className="space-y-3">
423 <div className="flex justify-between">
424 <span className="text-sm text-muted-foreground">
425 Plan
426 </span>
427 <span className="font-medium">{selectedPlan.name}</span>
428 </div>
429 <div className="flex justify-between">
430 <span className="text-sm text-muted-foreground">
431 Base Price
432 </span>
433 <span className="font-medium">
434 $
435 {billingCycle === "yearly"
436 ? Math.round(selectedPlan.price * 0.8)
437 : selectedPlan.price}
438 </span>
439 </div>
440
441 {selectedAddOns.length > 0 && (
442 <>
443 <Separator />
444 <div className="space-y-2">
445 <span className="text-sm font-medium">Add-ons:</span>
446 {selectedAddOns.map((addOn) => (
447 <div
448 key={addOn.id}
449 className="flex justify-between text-sm"
450 >
451 <span className="text-muted-foreground">
452 {addOn.name}{" "}
453 {addOn.quantity > 1 && `(${addOn.quantity}x)`}
454 </span>
455 <span>${addOn.price * addOn.quantity}</span>
456 </div>
457 ))}
458 </div>
459 </>
460 )}
461
462 <Separator />
463 <div className="flex justify-between font-semibold">
464 <span>Total</span>
465 <span>
466 ${calculateTotal()}/
467 {billingCycle === "yearly" ? "year" : "month"}
468 </span>
469 </div>
470 </div>
471 ) : (
472 <p className="text-sm text-muted-foreground">
473 Select a plan to see pricing
474 </p>
475 )}
476 </CardContent>
477 </Card>
478
479 {/* Current Bills */}
480 <Card>
481 <CardHeader>
482 <CardTitle className="flex items-center space-x-2">
483 <Calendar className="h-5 w-5" />
484 <span>Current Bills</span>
485 </CardTitle>
486 </CardHeader>
487 <CardContent className="space-y-4">
488 {currentBills.map((bill) => (
489 <div key={bill.id} className="space-y-2">
490 <div className="flex justify-between items-start">
491 <div className="flex-1">
492 <p className="text-sm font-medium text-foreground">
493 {bill.description}
494 </p>
495 <p className="text-xs text-muted-foreground">
496 Due: {bill.dueDate}
497 </p>
498 </div>
499 <div className="text-right">
500 <p className="text-sm font-medium">${bill.amount}</p>
501 <Badge
502 className={`text-xs ${getBillStatusColor(
503 bill.status
504 )}`}
505 >
506 {bill.status}
507 </Badge>
508 </div>
509 </div>
510 <Separator />
511 </div>
512 ))}
513
514 <div className="pt-2">
515 <div className="flex justify-between font-semibold">
516 <span>Total Due</span>
517 <span>${totalBillAmount}</span>
518 </div>
519 <Button className="w-full mt-3" size="sm">
520 Pay All Bills
521 </Button>
522 </div>
523 </CardContent>
524 </Card>
525 </div>
526 </div>
527 </div>
528 </div>
529 );
530};
531
532export default CustomerAdminDashboard;
Dependencies
External Libraries
lucide-reactreact
Shadcn/UI Components
badgebuttoncardseparator