"use client"; import { Badge, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@mdxui/primitives"; import { cn } from "@mdxui/primitives/lib/utils"; import { Copy, Edit, Eye, EyeOff, Filter, Plus, Search, Trash2, } from "lucide-react"; import * as React from "react"; import { toast } from "sonner"; import { SecretForm } from "./secret-form"; import type { Secret, SecretsManagerProps } from "./types"; export function SecretsManager({ secrets: externalSecrets = [], environments = ["development", "staging", "production"], currentEnvironment = "development", onEnvironmentChange, onCreate, onUpdate, onDelete, hideValues: initialHideValues = true, editable = true, className, }: SecretsManagerProps) { const [localSecrets, setLocalSecrets] = React.useState([]); const [hideValues, setHideValues] = React.useState(initialHideValues); const [searchQuery, setSearchQuery] = React.useState(""); const [environment, setEnvironment] = React.useState(currentEnvironment); const [visibleSecrets, setVisibleSecrets] = React.useState>( new Set(), ); const [editingSecret, setEditingSecret] = React.useState(null); const [isCreateDialogOpen, setIsCreateDialogOpen] = React.useState(false); const [isEditDialogOpen, setIsEditDialogOpen] = React.useState(false); const secrets = externalSecrets.length > 0 ? externalSecrets : localSecrets; React.useEffect(() => { setEnvironment(currentEnvironment); }, [currentEnvironment]); const filteredSecrets = React.useMemo(() => { return secrets.filter((secret) => { const matchesSearch = searchQuery ? secret.key.toLowerCase().includes(searchQuery.toLowerCase()) || secret.description?.toLowerCase().includes(searchQuery.toLowerCase()) : true; const matchesEnvironment = environment === "all" || secret.environment === environment; return matchesSearch && matchesEnvironment; }); }, [secrets, searchQuery, environment]); const handleEnvironmentChange = (newEnvironment: string) => { setEnvironment(newEnvironment); onEnvironmentChange?.(newEnvironment); }; const handleCreate = async ( secretData: Omit, ) => { const newSecret: Secret = { ...secretData, id: crypto.randomUUID(), createdAt: new Date(), updatedAt: new Date(), }; try { await onCreate?.(secretData); if (externalSecrets.length === 0) { setLocalSecrets((prev) => [...prev, newSecret]); } setIsCreateDialogOpen(false); toast.success("Secret created successfully"); } catch (error) { toast.error("Failed to create secret"); } }; const handleUpdate = async (id: string, updates: Partial) => { try { await onUpdate?.(id, { ...updates, updatedAt: new Date() }); if (externalSecrets.length === 0) { setLocalSecrets((prev) => prev.map((secret) => secret.id === id ? { ...secret, ...updates, updatedAt: new Date() } : secret, ), ); } setIsEditDialogOpen(false); setEditingSecret(null); toast.success("Secret updated successfully"); } catch (error) { toast.error("Failed to update secret"); } }; const handleDelete = async (id: string) => { if (!confirm("Are you sure you want to delete this secret?")) return; try { await onDelete?.(id); if (externalSecrets.length === 0) { setLocalSecrets((prev) => prev.filter((secret) => secret.id !== id)); } toast.success("Secret deleted"); } catch (error) { toast.error("Failed to delete secret"); } }; const handleCopy = (value: string) => { navigator.clipboard.writeText(value); toast.success("Value copied to clipboard"); }; const toggleSecretVisibility = (id: string) => { setVisibleSecrets((prev) => { const next = new Set(prev); if (next.has(id)) { next.delete(id); } else { next.add(id); } return next; }); }; const toggleAllVisibility = () => { setHideValues(!hideValues); if (!hideValues) { setVisibleSecrets(new Set()); } }; const isSecretVisible = (id: string) => { return hideValues ? visibleSecrets.has(id) : true; }; return (
Environment Variables Manage your application secrets and configuration
{editable && ( Create New Secret setIsCreateDialogOpen(false)} /> )}
setSearchQuery(e.target.value)} className="pl-9" />
{filteredSecrets.length > 0 ? (
Key Value Environment Description Actions {filteredSecrets.map((secret) => ( {secret.key}
{isSecretVisible(secret.id) ? secret.value : "•".repeat(Math.min(secret.value.length, 20))}
{secret.environment} {secret.description || "-"} {editable && (
)}
))}
) : (

{searchQuery ? "No secrets found matching your search" : "No secrets yet. Add your first secret to get started."}

)}
{editingSecret && ( Edit Secret handleUpdate(editingSecret.id, data)} onCancel={() => { setIsEditDialogOpen(false); setEditingSecret(null); }} /> )}
); }