ShadCN Vaults

Back to Blocks

Backup Monitoring Dashboard

Unknown Block

UnknownComponent

Backup Monitoring Dashboard

Real-time backup monitoring dashboard with status indicators, compression ratios, and retention management.

Preview

Full width desktop view

Code

monitoring-4.tsx
1"use client";
2
3import * as React from "react";
4import { format } from "date-fns";
5import {
6  Database,
7  Server,
8  Calendar,
9  MapPin,
10  CheckCircle,
11  XCircle,
12  Clock,
13  Download,
14  Eye,
15  MoreHorizontal,
16  AlertTriangle,
17  FileText,
18  HardDrive,
19  Activity,
20} from "lucide-react";
21import { cn } from "@/lib/utils";
22
23// Types
24interface BackupLog {
25  id: string;
26  timestamp: string;
27  level: "info" | "warning" | "error" | "success";
28  message: string;
29}
30
31interface DatabaseBackup {
32  id: string;
33  databaseName: string;
34  backupDate: string;
35  vpsRegion: string;
36  status: "completed" | "failed" | "in-progress" | "scheduled";
37  size: string;
38  duration: string;
39  backupType: "full" | "incremental" | "differential";
40  retentionDays: number;
41  compressionRatio: string;
42  logs: BackupLog[];
43  nextScheduled?: string;
44}
45
46// Mock data
47const mockBackups: DatabaseBackup[] = [
48  {
49    id: "backup-001",
50    databaseName: "production_db",
51    backupDate: "2024-01-15T02:00:00Z",
52    vpsRegion: "US-East-1",
53    status: "completed",
54    size: "2.4 GB",
55    duration: "12m 34s",
56    backupType: "full",
57    retentionDays: 30,
58    compressionRatio: "65%",
59    nextScheduled: "2024-01-16T02:00:00Z",
60    logs: [
61      {
62        id: "log-1",
63        timestamp: "2024-01-15T02:00:00Z",
64        level: "info",
65        message: "Backup started",
66      },
67      {
68        id: "log-2",
69        timestamp: "2024-01-15T02:05:00Z",
70        level: "info",
71        message: "Database locked for backup",
72      },
73      {
74        id: "log-3",
75        timestamp: "2024-01-15T02:12:34Z",
76        level: "success",
77        message: "Backup completed successfully",
78      },
79    ],
80  },
81  {
82    id: "backup-002",
83    databaseName: "analytics_db",
84    backupDate: "2024-01-15T03:00:00Z",
85    vpsRegion: "EU-West-1",
86    status: "failed",
87    size: "1.8 GB",
88    duration: "8m 12s",
89    backupType: "incremental",
90    retentionDays: 14,
91    compressionRatio: "72%",
92    nextScheduled: "2024-01-16T03:00:00Z",
93    logs: [
94      {
95        id: "log-4",
96        timestamp: "2024-01-15T03:00:00Z",
97        level: "info",
98        message: "Backup started",
99      },
100      {
101        id: "log-5",
102        timestamp: "2024-01-15T03:05:00Z",
103        level: "warning",
104        message: "High memory usage detected",
105      },
106      {
107        id: "log-6",
108        timestamp: "2024-01-15T03:08:12Z",
109        level: "error",
110        message: "Backup failed: Connection timeout",
111      },
112    ],
113  },
114  {
115    id: "backup-003",
116    databaseName: "user_sessions",
117    backupDate: "2024-01-15T04:00:00Z",
118    vpsRegion: "Asia-Pacific-1",
119    status: "in-progress",
120    size: "856 MB",
121    duration: "5m 23s",
122    backupType: "differential",
123    retentionDays: 7,
124    compressionRatio: "58%",
125    logs: [
126      {
127        id: "log-7",
128        timestamp: "2024-01-15T04:00:00Z",
129        level: "info",
130        message: "Backup started",
131      },
132      {
133        id: "log-8",
134        timestamp: "2024-01-15T04:03:00Z",
135        level: "info",
136        message: "Processing table: user_sessions",
137      },
138    ],
139  },
140  {
141    id: "backup-004",
142    databaseName: "inventory_db",
143    backupDate: "2024-01-14T02:00:00Z",
144    vpsRegion: "US-West-2",
145    status: "completed",
146    size: "4.2 GB",
147    duration: "18m 45s",
148    backupType: "full",
149    retentionDays: 90,
150    compressionRatio: "68%",
151    logs: [
152      {
153        id: "log-9",
154        timestamp: "2024-01-14T02:00:00Z",
155        level: "info",
156        message: "Backup started",
157      },
158      {
159        id: "log-10",
160        timestamp: "2024-01-14T02:18:45Z",
161        level: "success",
162        message: "Backup completed successfully",
163      },
164    ],
165  },
166];
167
168// Components
169const Badge = React.forwardRef<
170  HTMLDivElement,
171  React.HTMLAttributes<HTMLDivElement> & {
172    variant?: "default" | "secondary" | "destructive" | "outline";
173  }
174>(({ className, variant = "default", ...props }, ref) => {
175  const variants = {
176    default: "bg-primary text-primary-foreground",
177    secondary: "bg-secondary text-secondary-foreground",
178    destructive: "bg-destructive text-destructive-foreground",
179    outline: "text-foreground border border-border",
180  };
181
182  return (
183    <div
184      ref={ref}
185      className={cn(
186        "inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold transition-colors",
187        variants[variant],
188        className
189      )}
190      {...props}
191    />
192  );
193});
194Badge.displayName = "Badge";
195
196const Button = React.forwardRef<
197  HTMLButtonElement,
198  React.ButtonHTMLAttributes<HTMLButtonElement> & {
199    variant?:
200      | "default"
201      | "destructive"
202      | "outline"
203      | "secondary"
204      | "ghost"
205      | "link";
206    size?: "default" | "sm" | "lg" | "icon";
207  }
208>(({ className, variant = "default", size = "default", ...props }, ref) => {
209  const variants = {
210    default: "bg-primary text-primary-foreground hover:bg-primary/90",
211    destructive:
212      "bg-destructive text-destructive-foreground hover:bg-destructive/90",
213    outline:
214      "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
215    secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
216    ghost: "hover:bg-accent hover:text-accent-foreground",
217    link: "text-primary underline-offset-4 hover:underline",
218  };
219
220  const sizes = {
221    default: "h-10 px-4 py-2",
222    sm: "h-9 rounded-md px-3",
223    lg: "h-11 rounded-md px-8",
224    icon: "h-10 w-10",
225  };
226
227  return (
228    <button
229      ref={ref}
230      className={cn(
231        "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
232        variants[variant],
233        sizes[size],
234        className
235      )}
236      {...props}
237    />
238  );
239});
240Button.displayName = "Button";
241
242const Card = React.forwardRef<
243  HTMLDivElement,
244  React.HTMLAttributes<HTMLDivElement>
245>(({ className, ...props }, ref) => (
246  <div
247    ref={ref}
248    className={cn(
249      "rounded-lg border border-border bg-card text-card-foreground shadow-sm",
250      className
251    )}
252    {...props}
253  />
254));
255Card.displayName = "Card";
256
257const CardHeader = React.forwardRef<
258  HTMLDivElement,
259  React.HTMLAttributes<HTMLDivElement>
260>(({ className, ...props }, ref) => (
261  <div
262    ref={ref}
263    className={cn("flex flex-col space-y-1.5 p-6", className)}
264    {...props}
265  />
266));
267CardHeader.displayName = "CardHeader";
268
269const CardTitle = React.forwardRef<
270  HTMLParagraphElement,
271  React.HTMLAttributes<HTMLHeadingElement>
272>(({ className, ...props }, ref) => (
273  <h3
274    ref={ref}
275    className={cn(
276      "text-2xl font-semibold leading-none tracking-tight",
277      className
278    )}
279    {...props}
280  />
281));
282CardTitle.displayName = "CardTitle";
283
284const CardContent = React.forwardRef<
285  HTMLDivElement,
286  React.HTMLAttributes<HTMLDivElement>
287>(({ className, ...props }, ref) => (
288  <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
289));
290CardContent.displayName = "CardContent";
291
292const Tabs = React.forwardRef<
293  HTMLDivElement,
294  React.HTMLAttributes<HTMLDivElement> & {
295    defaultValue?: string;
296    value?: string;
297    onValueChange?: (value: string) => void;
298  }
299>(({ className, defaultValue, value, onValueChange, ...props }, ref) => {
300  const [internalValue, setInternalValue] = React.useState(defaultValue || "");
301  const currentValue = value !== undefined ? value : internalValue;
302
303  const handleValueChange = (newValue: string) => {
304    if (value === undefined) {
305      setInternalValue(newValue);
306    }
307    onValueChange?.(newValue);
308  };
309
310  return (
311    <div
312      ref={ref}
313      className={cn("w-full", className)}
314      data-value={currentValue}
315      {...props}
316    >
317      {React.Children.map(props.children, (child) => {
318        if (!React.isValidElement(child)) return child;
319        // Only pass value/onValueChange/currentValue to TabsTrigger and TabsContent
320        const displayName = (child.type as any).displayName;
321        if (displayName === "TabsTrigger") {
322          return React.cloneElement(child as React.ReactElement<any>, {
323            value: (child.props as any).value,
324            onValueChange: handleValueChange,
325            currentValue: currentValue,
326          });
327        }
328        if (displayName === "TabsContent") {
329          return React.cloneElement(child as React.ReactElement<any>, {
330            value: (child.props as any).value,
331            currentValue: currentValue,
332          });
333        }
334        // For TabsList and others, just return as is
335        return child;
336      })}
337    </div>
338  );
339});
340Tabs.displayName = "Tabs";
341
342const TabsList = React.forwardRef<
343  HTMLDivElement,
344  React.HTMLAttributes<HTMLDivElement>
345>(({ className, ...props }, ref) => (
346  <div
347    ref={ref}
348    className={cn(
349      "inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
350      className
351    )}
352    {...props}
353  />
354));
355TabsList.displayName = "TabsList";
356
357const TabsTrigger = React.forwardRef<
358  HTMLButtonElement,
359  React.ButtonHTMLAttributes<HTMLButtonElement> & {
360    value: string;
361    onValueChange?: (value: string) => void;
362    currentValue?: string;
363  }
364>(({ className, value, onValueChange, currentValue, ...props }, ref) => (
365  <button
366    ref={ref}
367    className={cn(
368      "inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
369      currentValue === value
370        ? "bg-background text-foreground shadow-sm"
371        : "hover:bg-background/50",
372      className
373    )}
374    onClick={() => onValueChange?.(value)}
375    {...props}
376  />
377));
378TabsTrigger.displayName = "TabsTrigger";
379
380const TabsContent = React.forwardRef<
381  HTMLDivElement,
382  React.HTMLAttributes<HTMLDivElement> & {
383    value: string;
384    currentValue?: string;
385  }
386>(({ className, value, currentValue, ...props }, ref) => {
387  if (currentValue !== value) return null;
388
389  return (
390    <div
391      ref={ref}
392      className={cn(
393        "mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
394        className
395      )}
396      {...props}
397    />
398  );
399});
400TabsContent.displayName = "TabsContent";
401
402// Status components
403const StatusIcon = ({ status }: { status: DatabaseBackup["status"] }) => {
404  switch (status) {
405    case "completed":
406      return <CheckCircle className="h-4 w-4 text-green-500" />;
407    case "failed":
408      return <XCircle className="h-4 w-4 text-red-500" />;
409    case "in-progress":
410      return <Clock className="h-4 w-4 text-blue-500" />;
411    case "scheduled":
412      return <Calendar className="h-4 w-4 text-yellow-500" />;
413    default:
414      return <Clock className="h-4 w-4 text-gray-500" />;
415  }
416};
417
418const StatusBadge = ({ status }: { status: DatabaseBackup["status"] }) => {
419  const variants = {
420    completed: "bg-green-100 text-green-800 border-green-200",
421    failed: "bg-red-100 text-red-800 border-red-200",
422    "in-progress": "bg-blue-100 text-blue-800 border-blue-200",
423    scheduled: "bg-yellow-100 text-yellow-800 border-yellow-200",
424  };
425
426  return (
427    <Badge className={cn("border", variants[status])}>
428      <StatusIcon status={status} />
429      <span className="ml-1 capitalize">{status.replace("-", " ")}</span>
430    </Badge>
431  );
432};
433
434// Log level components
435const LogLevelIcon = ({ level }: { level: BackupLog["level"] }) => {
436  switch (level) {
437    case "success":
438      return <CheckCircle className="h-3 w-3 text-green-500" />;
439    case "error":
440      return <XCircle className="h-3 w-3 text-red-500" />;
441    case "warning":
442      return <AlertTriangle className="h-3 w-3 text-yellow-500" />;
443    case "info":
444    default:
445      return <FileText className="h-3 w-3 text-blue-500" />;
446  }
447};
448
449// Backup card component
450const BackupCard = ({ backup }: { backup: DatabaseBackup }) => {
451  const [selectedTab, setSelectedTab] = React.useState("overview");
452
453  return (
454    <Card className="w-full">
455      <CardHeader className="pb-4">
456        <div className="flex items-center justify-between">
457          <div className="flex items-center space-x-3">
458            <div className="p-2 bg-blue-100 rounded-lg">
459              <Database className="h-5 w-5 text-blue-600" />
460            </div>
461            <div>
462              <CardTitle className="text-lg">{backup.databaseName}</CardTitle>
463              <p className="text-sm text-muted-foreground">
464                {format(new Date(backup.backupDate), "PPp")}
465              </p>
466            </div>
467          </div>
468          <div className="flex items-center space-x-2">
469            <StatusBadge status={backup.status} />
470            <Button variant="ghost" size="icon">
471              <MoreHorizontal className="h-4 w-4" />
472            </Button>
473          </div>
474        </div>
475      </CardHeader>
476
477      <CardContent>
478        <Tabs value={selectedTab} onValueChange={setSelectedTab}>
479          <TabsList className="grid w-full grid-cols-3">
480            <TabsTrigger value="overview" currentValue={selectedTab}>
481              Overview
482            </TabsTrigger>
483            <TabsTrigger value="details" currentValue={selectedTab}>
484              Details
485            </TabsTrigger>
486            <TabsTrigger value="logs" currentValue={selectedTab}>
487              Logs
488            </TabsTrigger>
489          </TabsList>
490
491          <TabsContent value="overview" currentValue={selectedTab}>
492            <div className="grid grid-cols-2 md:grid-cols-4 gap-4 mt-4">
493              <div className="space-y-1">
494                <div className="flex items-center text-sm text-muted-foreground">
495                  <MapPin className="h-3 w-3 mr-1" />
496                  Region
497                </div>
498                <p className="font-medium">{backup.vpsRegion}</p>
499              </div>
500              <div className="space-y-1">
501                <div className="flex items-center text-sm text-muted-foreground">
502                  <HardDrive className="h-3 w-3 mr-1" />
503                  Size
504                </div>
505                <p className="font-medium">{backup.size}</p>
506              </div>
507              <div className="space-y-1">
508                <div className="flex items-center text-sm text-muted-foreground">
509                  <Clock className="h-3 w-3 mr-1" />
510                  Duration
511                </div>
512                <p className="font-medium">{backup.duration}</p>
513              </div>
514              <div className="space-y-1">
515                <div className="flex items-center text-sm text-muted-foreground">
516                  <Activity className="h-3 w-3 mr-1" />
517                  Type
518                </div>
519                <p className="font-medium capitalize">{backup.backupType}</p>
520              </div>
521            </div>
522
523            {backup.nextScheduled && (
524              <div className="mt-4 p-3 bg-blue-50 rounded-lg border border-blue-200">
525                <p className="text-sm text-blue-800">
526                  <Calendar className="h-4 w-4 inline mr-1" />
527                  Next backup scheduled:{" "}
528                  {format(new Date(backup.nextScheduled), "PPp")}
529                </p>
530              </div>
531            )}
532          </TabsContent>
533
534          <TabsContent value="details" currentValue={selectedTab}>
535            <div className="space-y-4 mt-4">
536              <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
537                <div className="space-y-3">
538                  <h4 className="font-medium text-sm text-muted-foreground">
539                    Backup Configuration
540                  </h4>
541                  <div className="space-y-2">
542                    <div className="flex justify-between">
543                      <span className="text-sm">Backup Type:</span>
544                      <span className="text-sm font-medium capitalize">
545                        {backup.backupType}
546                      </span>
547                    </div>
548                    <div className="flex justify-between">
549                      <span className="text-sm">Retention Period:</span>
550                      <span className="text-sm font-medium">
551                        {backup.retentionDays} days
552                      </span>
553                    </div>
554                    <div className="flex justify-between">
555                      <span className="text-sm">Compression Ratio:</span>
556                      <span className="text-sm font-medium">
557                        {backup.compressionRatio}
558                      </span>
559                    </div>
560                  </div>
561                </div>
562                <div className="space-y-3">
563                  <h4 className="font-medium text-sm text-muted-foreground">
564                    Infrastructure
565                  </h4>
566                  <div className="space-y-2">
567                    <div className="flex justify-between">
568                      <span className="text-sm">VPS Region:</span>
569                      <span className="text-sm font-medium">
570                        {backup.vpsRegion}
571                      </span>
572                    </div>
573                    <div className="flex justify-between">
574                      <span className="text-sm">Backup Size:</span>
575                      <span className="text-sm font-medium">{backup.size}</span>
576                    </div>
577                    <div className="flex justify-between">
578                      <span className="text-sm">Duration:</span>
579                      <span className="text-sm font-medium">
580                        {backup.duration}
581                      </span>
582                    </div>
583                  </div>
584                </div>
585              </div>
586
587              <div className="flex space-x-2 pt-4">
588                <Button variant="outline" size="sm">
589                  <Download className="h-4 w-4 mr-2" />
590                  Download Backup
591                </Button>
592                <Button variant="outline" size="sm">
593                  <Eye className="h-4 w-4 mr-2" />
594                  View Details
595                </Button>
596              </div>
597            </div>
598          </TabsContent>
599
600          <TabsContent value="logs" currentValue={selectedTab}>
601            <div className="space-y-2 mt-4 max-h-64 overflow-y-auto">
602              {backup.logs.map((log) => (
603                <div
604                  key={log.id}
605                  className="flex items-start space-x-3 p-2 rounded-lg hover:bg-muted/50"
606                >
607                  <LogLevelIcon level={log.level} />
608                  <div className="flex-1 min-w-0">
609                    <div className="flex items-center justify-between">
610                      <p className="text-sm font-medium">{log.message}</p>
611                      <span className="text-xs text-muted-foreground">
612                        {format(new Date(log.timestamp), "HH:mm:ss")}
613                      </span>
614                    </div>
615                  </div>
616                </div>
617              ))}
618            </div>
619          </TabsContent>
620        </Tabs>
621      </CardContent>
622    </Card>
623  );
624};
625
626// Main component
627const DatabaseBackupDashboard = ({
628  backups = mockBackups,
629}: {
630  backups?: DatabaseBackup[];
631}) => {
632  const [filter, setFilter] = React.useState<"all" | DatabaseBackup["status"]>(
633    "all"
634  );
635
636  const filteredBackups = React.useMemo(() => {
637    if (filter === "all") return backups;
638    return backups.filter((backup) => backup.status === filter);
639  }, [backups, filter]);
640
641  const stats = React.useMemo(() => {
642    const total = backups.length;
643    const completed = backups.filter((b) => b.status === "completed").length;
644    const failed = backups.filter((b) => b.status === "failed").length;
645    const inProgress = backups.filter((b) => b.status === "in-progress").length;
646
647    return { total, completed, failed, inProgress };
648  }, [backups]);
649
650  return (
651    <div className="w-full max-w-7xl mx-auto p-6 space-y-6">
652      {/* Header */}
653      <div className="flex items-center justify-between">
654        <div>
655          <h1 className="text-3xl font-bold tracking-tight">
656            Database Backups
657          </h1>
658          <p className="text-muted-foreground">
659            Monitor and manage your database backup operations
660          </p>
661        </div>
662        <Button>
663          <Database className="h-4 w-4 mr-2" />
664          New Backup
665        </Button>
666      </div>
667
668      {/* Stats Cards */}
669      <div className="grid grid-cols-1 md:grid-cols-4 gap-4">
670        <Card>
671          <CardContent className="p-6">
672            <div className="flex items-center space-x-2">
673              <Server className="h-5 w-5 text-blue-500" />
674              <div>
675                <p className="text-2xl font-bold">{stats.total}</p>
676                <p className="text-sm text-muted-foreground">Total Backups</p>
677              </div>
678            </div>
679          </CardContent>
680        </Card>
681
682        <Card>
683          <CardContent className="p-6">
684            <div className="flex items-center space-x-2">
685              <CheckCircle className="h-5 w-5 text-green-500" />
686              <div>
687                <p className="text-2xl font-bold">{stats.completed}</p>
688                <p className="text-sm text-muted-foreground">Completed</p>
689              </div>
690            </div>
691          </CardContent>
692        </Card>
693
694        <Card>
695          <CardContent className="p-6">
696            <div className="flex items-center space-x-2">
697              <XCircle className="h-5 w-5 text-red-500" />
698              <div>
699                <p className="text-2xl font-bold">{stats.failed}</p>
700                <p className="text-sm text-muted-foreground">Failed</p>
701              </div>
702            </div>
703          </CardContent>
704        </Card>
705
706        <Card>
707          <CardContent className="p-6">
708            <div className="flex items-center space-x-2">
709              <Clock className="h-5 w-5 text-blue-500" />
710              <div>
711                <p className="text-2xl font-bold">{stats.inProgress}</p>
712                <p className="text-sm text-muted-foreground">In Progress</p>
713              </div>
714            </div>
715          </CardContent>
716        </Card>
717      </div>
718
719      {/* Filters */}
720      <div className="flex space-x-2">
721        <Button
722          variant={filter === "all" ? "default" : "outline"}
723          size="sm"
724          onClick={() => setFilter("all")}
725        >
726          All
727        </Button>
728        <Button
729          variant={filter === "completed" ? "default" : "outline"}
730          size="sm"
731          onClick={() => setFilter("completed")}
732        >
733          Completed
734        </Button>
735        <Button
736          variant={filter === "failed" ? "default" : "outline"}
737          size="sm"
738          onClick={() => setFilter("failed")}
739        >
740          Failed
741        </Button>
742        <Button
743          variant={filter === "in-progress" ? "default" : "outline"}
744          size="sm"
745          onClick={() => setFilter("in-progress")}
746        >
747          In Progress
748        </Button>
749      </div>
750
751      {/* Backup Cards */}
752      <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
753        {filteredBackups.map((backup) => (
754          <BackupCard key={backup.id} backup={backup} />
755        ))}
756      </div>
757
758      {filteredBackups.length === 0 && (
759        <div className="text-center py-12">
760          <Database className="h-12 w-12 text-muted-foreground mx-auto mb-4" />
761          <h3 className="text-lg font-medium text-muted-foreground">
762            No backups found
763          </h3>
764          <p className="text-sm text-muted-foreground">
765            {filter === "all"
766              ? "No database backups have been created yet."
767              : `No backups with status "${filter}" found.`}
768          </p>
769        </div>
770      )}
771    </div>
772  );
773};
774
775export default DatabaseBackupDashboard;

Dependencies

External Libraries

date-fnslucide-reactreact

Local Components

/lib/utils

LICENSE

MIT License

Copyright (c) 2025 Aldhaneka

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Shadcn Vaults Project (CC BY-NC 4.0 with Internal Use Exception)

All user-submitted components in the '/blocks' directory are licensed under the Creative Commons Attribution-NonCommercial 4.0 International License (CC BY-NC 4.0),
with the following clarification and exception:

You are free to:
- Share — copy and redistribute the material in any medium or format
- Adapt — remix, transform, and build upon the material

Under these conditions:
- Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made.
- NonCommercial — You may NOT use the material for commercial redistribution, resale, or monetization.

🚫 You MAY NOT:
- Sell or redistribute the components individually or as part of a product (e.g. a UI kit, template marketplace, SaaS component library)
- Offer the components or derivative works in any paid tool, theme pack, or design system

✅ You MAY:
- Use the components in internal company tools, dashboards, or applications that are not sold as products
- Remix or adapt components for private or enterprise projects
- Use them in open-source non-commercial projects

This license encourages sharing, learning, and internal innovation — but prohibits using these components as a basis for monetized products.

Full license text: https://creativecommons.org/licenses/by-nc/4.0/

By submitting a component, contributors agree to these terms.

Contributors

Ramiro Godoy@milogodoy

Review Form Block

Aldhaneka@Aldhanekaa

Project Creator

For questions about licensing, please contact the project maintainers.