feat: Implement identity management and safety checks

This commit is contained in:
gpt-engineer-app[bot]
2025-10-14 14:41:18 +00:00
parent e42853b797
commit 6430777dc0
4 changed files with 589 additions and 68 deletions

View File

@@ -0,0 +1,159 @@
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { AlertCircle, CheckCircle2, Key } from 'lucide-react';
import { addPasswordToAccount } from '@/lib/identityService';
import type { OAuthProvider } from '@/types/identity';
interface PasswordSetupDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onSuccess: () => void;
provider: OAuthProvider;
}
export function PasswordSetupDialog({
open,
onOpenChange,
onSuccess,
provider
}: PasswordSetupDialogProps) {
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const passwordsMatch = password === confirmPassword && password.length > 0;
const passwordValid = password.length >= 8;
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
if (!passwordValid) {
setError('Password must be at least 8 characters long');
return;
}
if (!passwordsMatch) {
setError('Passwords do not match');
return;
}
setLoading(true);
const result = await addPasswordToAccount(password);
setLoading(false);
if (result.success) {
setPassword('');
setConfirmPassword('');
onSuccess();
onOpenChange(false);
} else {
setError(result.error || 'Failed to set password');
}
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<div className="flex items-center gap-2">
<Key className="w-5 h-5" />
<DialogTitle>Set Up Password</DialogTitle>
</div>
<DialogDescription>
You need to set a password before disconnecting your {provider} account.
This ensures you can still access your account.
</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="password">New Password</Label>
<Input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Enter new password"
disabled={loading}
required
/>
{password.length > 0 && (
<div className="flex items-center gap-2 text-sm">
{passwordValid ? (
<CheckCircle2 className="w-4 h-4 text-green-600" />
) : (
<AlertCircle className="w-4 h-4 text-destructive" />
)}
<span className={passwordValid ? 'text-green-600' : 'text-muted-foreground'}>
At least 8 characters
</span>
</div>
)}
</div>
<div className="space-y-2">
<Label htmlFor="confirmPassword">Confirm Password</Label>
<Input
id="confirmPassword"
type="password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
placeholder="Confirm new password"
disabled={loading}
required
/>
{confirmPassword.length > 0 && (
<div className="flex items-center gap-2 text-sm">
{passwordsMatch ? (
<CheckCircle2 className="w-4 h-4 text-green-600" />
) : (
<AlertCircle className="w-4 h-4 text-destructive" />
)}
<span className={passwordsMatch ? 'text-green-600' : 'text-muted-foreground'}>
Passwords match
</span>
</div>
)}
</div>
{error && (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
<DialogFooter>
<Button
type="button"
variant="outline"
onClick={() => onOpenChange(false)}
disabled={loading}
>
Cancel
</Button>
<Button
type="submit"
disabled={!passwordValid || !passwordsMatch || loading}
>
{loading ? 'Setting Password...' : 'Set Password'}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
);
}