Advertisement

← vibesdk

src/components/vault/AddSecretModal.tsx

3.6 KB Download file
import { useState } from 'react';
import { useVault } from '@/contexts/vault-context';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import {
	Dialog,
	DialogContent,
	DialogDescription,
	DialogHeader,
	DialogTitle,
} from '@/components/ui/dialog';
import { AlertCircle, Loader2, Key } from 'lucide-react';

interface Props {
	open: boolean;
	onOpenChange: (open: boolean) => void;
	onSecretAdded?: () => void;
}

export function AddSecretModal({ open, onOpenChange, onSecretAdded }: Props) {
	const { encryptAndStoreSecret, isUnlocked, state } = useVault();
	const [name, setName] = useState('');
	const [value, setValue] = useState('');
	const [error, setError] = useState<string | null>(null);
	const [isSaving, setIsSaving] = useState(false);

	const handleSave = async () => {
		setError(null);

		if (!isUnlocked) {
			setError('Please unlock your vault first');
			return;
		}

		if (!name.trim()) {
			setError('Please enter a name for the secret');
			return;
		}

		if (!value.trim()) {
			setError('Please enter the secret value');
			return;
		}

		setIsSaving(true);

		try {
			const secretId = await encryptAndStoreSecret(name.trim(), value.trim());

			if (secretId) {
				setName('');
				setValue('');
				onOpenChange(false);
				onSecretAdded?.();
			} else {
				setError(state.error || 'Failed to save secret');
			}
		} catch (err) {
			setError(err instanceof Error ? err.message : 'Failed to save secret');
		} finally {
			setIsSaving(false);
		}
	};

	const handleClose = (open: boolean) => {
		if (!open) {
			setName('');
			setValue('');
			setError(null);
		}
		onOpenChange(open);
	};

	return (
		<Dialog open={open} onOpenChange={handleClose}>
			<DialogContent className="sm:max-w-md">
				<DialogHeader>
					<DialogTitle className="flex items-center gap-2">
						<Key className="h-5 w-5" />
						Add Secret
					</DialogTitle>
					<DialogDescription>
						Your secret will be encrypted locally before being stored. Only you can decrypt it.
					</DialogDescription>
				</DialogHeader>

				<div className="space-y-4 py-4">
					{/* Name */}
					<div className="space-y-2">
						<Label htmlFor="secret-name">Name</Label>
						<Input
							id="secret-name"
							value={name}
							onChange={(e) => setName(e.target.value)}
							placeholder="e.g., API Key, Database Password"
							autoFocus
						/>
					</div>

					{/* Value */}
					<div className="space-y-2">
						<Label htmlFor="secret-value">Secret Value</Label>
						<Input
							id="secret-value"
							type="password"
							value={value}
							onChange={(e) => setValue(e.target.value)}
							placeholder="Enter the secret value"
							onKeyDown={(e) => e.key === 'Enter' && handleSave()}
						/>
					</div>

					{/* Error message */}
					{error && (
						<div className="flex items-center gap-2 text-sm text-destructive">
							<AlertCircle className="h-4 w-4 flex-shrink-0" />
							{error}
						</div>
					)}

					{/* Save button */}
					<div className="flex gap-2">
						<Button variant="outline" onClick={() => handleClose(false)} className="flex-1">
							Cancel
						</Button>
						<Button onClick={handleSave} disabled={isSaving || !isUnlocked} className="flex-1">
							{isSaving ? (
								<>
									<Loader2 className="h-4 w-4 mr-2 animate-spin" />
									Encrypting...
								</>
							) : (
								'Add Secret'
							)}
						</Button>
					</div>
				</div>
			</DialogContent>
		</Dialog>
	);
}