Preview
Full width desktop view
Code
bills-4.tsx
1"use client";
2
3import * as React from "react";
4import { useState } from "react";
5import { Button } from "@/components/ui/button";
6import { Card } from "@/components/ui/card";
7import { Badge } from "@/components/ui/badge";
8import { Separator } from "@/components/ui/separator";
9import {
10 Plus,
11 Minus,
12 Check,
13 CreditCard,
14 Calendar,
15 DollarSign,
16 Users,
17 Globe,
18 Zap,
19 Shield,
20 Database,
21 Mail,
22} from "lucide-react";
23import { cn } from "@/lib/utils";
24
25interface Plan {
26 id: string;
27 name: string;
28 description: string;
29 price: number;
30 interval: "monthly" | "yearly";
31 features: string[];
32 popular?: boolean;
33}
34
35interface AddOn {
36 id: string;
37 name: string;
38 description: string;
39 price: number;
40 interval: "monthly" | "yearly";
41 icon: React.ComponentType<{ className?: string }>;
42 multipleUse: boolean;
43 maxQuantity?: number;
44}
45
46interface Bill {
47 id: string;
48 description: string;
49 amount: number;
50 dueDate: string;
51 status: "pending" | "paid" | "overdue";
52 type: "plan" | "addon";
53}
54
55interface CustomerAdminDashboardProps {
56 className?: string;
57}
58
59const CustomerAdminDashboard: React.FC<CustomerAdminDashboardProps> = ({
60 className = "",
61}) => {
62 const [selectedPlan, setSelectedPlan] = useState<string>("pro");
63 const [addOnQuantities, setAddOnQuantities] = useState<
64 Record<string, number>
65 >({
66 "extra-pages": 2,
67 "custom-domain": 1,
68 "priority-support": 1,
69 });
70
71 const plans: Plan[] = [
72 {
73 id: "starter",
74 name: "Starter",
75 description: "Perfect for small projects",
76 price: 29,
77 interval: "monthly",
78 features: [
79 "Up to 5 pages",
80 "Basic analytics",
81 "Email support",
82 "SSL certificate",
83 "Mobile responsive",
84 ],
85 },
86 {
87 id: "pro",
88 name: "Professional",
89 description: "Best for growing businesses",
90 price: 79,
91 interval: "monthly",
92 popular: true,
93 features: [
94 "Up to 25 pages",
95 "Advanced analytics",
96 "Priority support",
97 "SSL certificate",
98 "Mobile responsive",
99 "SEO optimization",
100 "Custom forms",
101 ],
102 },
103 {
104 id: "enterprise",
105 name: "Enterprise",
106 description: "For large organizations",
107 price: 199,
108 interval: "monthly",
109 features: [
110 "Unlimited pages",
111 "Advanced analytics",
112 "24/7 phone support",
113 "SSL certificate",
114 "Mobile responsive",
115 "SEO optimization",
116 "Custom forms",
117 "API access",
118 "White-label options",
119 ],
120 },
121 ];
122
123 const addOns: AddOn[] = [
124 {
125 id: "extra-pages",
126 name: "Extra Pages",
127 description: "Additional pages for your website",
128 price: 10,
129 interval: "monthly",
130 icon: Globe,
131 multipleUse: true,
132 maxQuantity: 50,
133 },
134 {
135 id: "custom-domain",
136 name: "Custom Domain",
137 description: "Use your own domain name",
138 price: 15,
139 interval: "monthly",
140 icon: Globe,
141 multipleUse: false,
142 },
143 {
144 id: "priority-support",
145 name: "Priority Support",
146 description: "Get faster response times",
147 price: 25,
148 interval: "monthly",
149 icon: Shield,
150 multipleUse: false,
151 },
152 {
153 id: "database-addon",
154 name: "Database Storage",
155 description: "Additional database storage (per 10GB)",
156 price: 8,
157 interval: "monthly",
158 icon: Database,
159 multipleUse: true,
160 maxQuantity: 20,
161 },
162 {
163 id: "email-marketing",
164 name: "Email Marketing",
165 description: "Email campaigns (per 1000 contacts)",
166 price: 12,
167 interval: "monthly",
168 icon: Mail,
169 multipleUse: true,
170 maxQuantity: 10,
171 },
172 ];
173
174 const bills: Bill[] = [
175 {
176 id: "bill-1",
177 description: "Professional Plan - December 2024",
178 amount: 79,
179 dueDate: "2024-12-15",
180 status: "pending",
181 type: "plan",
182 },
183 {
184 id: "bill-2",
185 description: "Extra Pages (2x) - December 2024",
186 amount: 20,
187 dueDate: "2024-12-15",
188 status: "pending",
189 type: "addon",
190 },
191 {
192 id: "bill-3",
193 description: "Custom Domain - December 2024",
194 amount: 15,
195 dueDate: "2024-12-15",
196 status: "pending",
197 type: "addon",
198 },
199 {
200 id: "bill-4",
201 description: "Professional Plan - November 2024",
202 amount: 79,
203 dueDate: "2024-11-15",
204 status: "paid",
205 type: "plan",
206 },
207 ];
208
209 const updateAddOnQuantity = (addOnId: string, change: number) => {
210 setAddOnQuantities((prev) => {
211 const addOn = addOns.find((a) => a.id === addOnId);
212 if (!addOn) return prev;
213
214 const currentQuantity = prev[addOnId] || 0;
215 let newQuantity = currentQuantity + change;
216
217 if (!addOn.multipleUse) {
218 newQuantity = Math.max(0, Math.min(1, newQuantity));
219 } else {
220 newQuantity = Math.max(0, newQuantity);
221 if (addOn.maxQuantity) {
222 newQuantity = Math.min(addOn.maxQuantity, newQuantity);
223 }
224 }
225
226 return {
227 ...prev,
228 [addOnId]: newQuantity,
229 };
230 });
231 };
232
233 const calculateTotal = () => {
234 const selectedPlanData = plans.find((p) => p.id === selectedPlan);
235 const planPrice = selectedPlanData?.price || 0;
236
237 const addOnTotal = addOns.reduce((total, addOn) => {
238 const quantity = addOnQuantities[addOn.id] || 0;
239 return total + addOn.price * quantity;
240 }, 0);
241
242 return planPrice + addOnTotal;
243 };
244
245 const getStatusColor = (status: string) => {
246 switch (status) {
247 case "paid":
248 return "bg-green-100 text-green-800 border-green-200";
249 case "pending":
250 return "bg-yellow-100 text-yellow-800 border-yellow-200";
251 case "overdue":
252 return "bg-red-100 text-red-800 border-red-200";
253 default:
254 return "bg-gray-100 text-gray-800 border-gray-200";
255 }
256 };
257
258 return (
259 <div className={cn("min-h-screen bg-background p-6", className)}>
260 <div className="max-w-7xl mx-auto space-y-8">
261 {/* Header */}
262 <div className="text-center space-y-4">
263 <h1 className="text-4xl font-bold text-foreground">
264 Customer Admin Dashboard
265 </h1>
266 <p className="text-xl text-muted-foreground">
267 Manage your subscription plans and billing
268 </p>
269 </div>
270
271 {/* Current Plan & Total */}
272 <Card className="p-6">
273 <div className="flex flex-col lg:flex-row justify-between items-start lg:items-center gap-4">
274 <div>
275 <h2 className="text-2xl font-semibold mb-2">
276 Current Configuration
277 </h2>
278 <p className="text-muted-foreground">
279 Plan: {plans.find((p) => p.id === selectedPlan)?.name} • Monthly
280 Total:{" "}
281 <span className="font-semibold text-primary">
282 ${calculateTotal()}
283 </span>
284 </p>
285 </div>
286 <Button size="lg" className="gap-2">
287 <CreditCard className="w-4 h-4" />
288 Pay Now
289 </Button>
290 </div>
291 </Card>
292
293 <div className="grid lg:grid-cols-2 gap-8">
294 {/* Plans Section */}
295 <div className="space-y-6">
296 <h2 className="text-2xl font-semibold">Available Plans</h2>
297 <div className="space-y-4">
298 {plans.map((plan) => (
299 <Card
300 key={plan.id}
301 className={cn(
302 "p-6 cursor-pointer transition-all border-2",
303 selectedPlan === plan.id
304 ? "border-primary bg-primary/5"
305 : "border-border hover:border-primary/50"
306 )}
307 onClick={() => setSelectedPlan(plan.id)}
308 >
309 <div className="flex justify-between items-start mb-4">
310 <div>
311 <div className="flex items-center gap-2 mb-2">
312 <h3 className="text-xl font-semibold">{plan.name}</h3>
313 {plan.popular && (
314 <Badge variant="default">Popular</Badge>
315 )}
316 </div>
317 <p className="text-muted-foreground">
318 {plan.description}
319 </p>
320 </div>
321 <div className="text-right">
322 <div className="text-2xl font-bold">${plan.price}</div>
323 <div className="text-sm text-muted-foreground">
324 /{plan.interval}
325 </div>
326 </div>
327 </div>
328
329 <div className="space-y-2">
330 {plan.features.map((feature, index) => (
331 <div key={index} className="flex items-center gap-2">
332 <Check className="w-4 h-4 text-green-600" />
333 <span className="text-sm">{feature}</span>
334 </div>
335 ))}
336 </div>
337 </Card>
338 ))}
339 </div>
340
341 {/* Add-ons Section */}
342 <div className="space-y-4">
343 <h2 className="text-2xl font-semibold">Add-ons</h2>
344 {addOns.map((addOn) => {
345 const Icon = addOn.icon;
346 const quantity = addOnQuantities[addOn.id] || 0;
347
348 return (
349 <Card key={addOn.id} className="p-4">
350 <div className="flex items-center justify-between">
351 <div className="flex items-center gap-3">
352 <div className="p-2 bg-primary/10 rounded-lg">
353 <Icon className="w-5 h-5 text-primary" />
354 </div>
355 <div>
356 <h4 className="font-semibold">{addOn.name}</h4>
357 <p className="text-sm text-muted-foreground">
358 {addOn.description}
359 </p>
360 <p className="text-sm font-medium">
361 ${addOn.price}/{addOn.interval}
362 </p>
363 </div>
364 </div>
365
366 <div className="flex items-center gap-2">
367 <Button
368 variant="outline"
369 size="icon"
370 onClick={() => updateAddOnQuantity(addOn.id, -1)}
371 disabled={quantity === 0}
372 >
373 <Minus className="w-4 h-4" />
374 </Button>
375
376 <span className="w-8 text-center font-medium">
377 {quantity}
378 </span>
379
380 <Button
381 variant="outline"
382 size="icon"
383 onClick={() => updateAddOnQuantity(addOn.id, 1)}
384 disabled={!addOn.multipleUse && quantity >= 1}
385 >
386 <Plus className="w-4 h-4" />
387 </Button>
388 </div>
389 </div>
390
391 {quantity > 0 && (
392 <div className="mt-3 pt-3 border-t">
393 <div className="flex justify-between text-sm">
394 <span>Subtotal ({quantity}x):</span>
395 <span className="font-medium">
396 ${addOn.price * quantity}/{addOn.interval}
397 </span>
398 </div>
399 </div>
400 )}
401 </Card>
402 );
403 })}
404 </div>
405 </div>
406
407 {/* Bills Section */}
408 <div className="space-y-6">
409 <h2 className="text-2xl font-semibold">Current Bills</h2>
410
411 <div className="space-y-4">
412 {bills.map((bill) => (
413 <Card key={bill.id} className="p-4">
414 <div className="flex justify-between items-start mb-3">
415 <div>
416 <h4 className="font-semibold">{bill.description}</h4>
417 <div className="flex items-center gap-2 mt-1">
418 <Calendar className="w-4 h-4 text-muted-foreground" />
419 <span className="text-sm text-muted-foreground">
420 Due: {new Date(bill.dueDate).toLocaleDateString()}
421 </span>
422 </div>
423 </div>
424 <div className="text-right">
425 <div className="text-lg font-bold">${bill.amount}</div>
426 <Badge
427 variant="outline"
428 className={cn("text-xs", getStatusColor(bill.status))}
429 >
430 {bill.status.charAt(0).toUpperCase() +
431 bill.status.slice(1)}
432 </Badge>
433 </div>
434 </div>
435
436 {bill.status === "pending" && (
437 <Button size="sm" className="w-full">
438 <DollarSign className="w-4 h-4 mr-2" />
439 Pay ${bill.amount}
440 </Button>
441 )}
442 </Card>
443 ))}
444 </div>
445
446 <Separator />
447
448 {/* Summary */}
449 <Card className="p-6 bg-primary/5">
450 <h3 className="text-lg font-semibold mb-4">Billing Summary</h3>
451 <div className="space-y-2">
452 <div className="flex justify-between">
453 <span>Pending Bills:</span>
454 <span className="font-medium">
455 $
456 {bills
457 .filter((b) => b.status === "pending")
458 .reduce((sum, b) => sum + b.amount, 0)}
459 </span>
460 </div>
461 <div className="flex justify-between">
462 <span>Next Month Estimate:</span>
463 <span className="font-medium">${calculateTotal()}</span>
464 </div>
465 <Separator />
466 <div className="flex justify-between text-lg font-semibold">
467 <span>Total Due:</span>
468 <span>
469 $
470 {bills
471 .filter((b) => b.status === "pending")
472 .reduce((sum, b) => sum + b.amount, 0)}
473 </span>
474 </div>
475 </div>
476
477 <Button className="w-full mt-4" size="lg">
478 <CreditCard className="w-4 h-4 mr-2" />
479 Pay All Pending Bills
480 </Button>
481 </Card>
482 </div>
483 </div>
484 </div>
485 </div>
486 );
487};
488
489export default CustomerAdminDashboard;
Dependencies
External Libraries
lucide-reactreact
Shadcn/UI Components
badgebuttoncardseparator
Local Components
/lib/utils