forked from abdlelahalwali8-a11y/dr-appointments-hub
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSecuritySettings.tsx
More file actions
1 lines (1 loc) · 17.1 KB
/
Copy pathSecuritySettings.tsx
File metadata and controls
1 lines (1 loc) · 17.1 KB
1
import React, { useState, useEffect } from 'react';\nimport { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';\nimport { Button } from '@/components/ui/button';\nimport { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog';\nimport { Alert, AlertDescription } from '@/components/ui/alert';\nimport { Badge } from '@/components/ui/badge';\nimport {\n Shield, Lock, Smartphone, Key, AlertTriangle, CheckCircle2,\n Eye, EyeOff, Copy, Download, Trash2\n} from 'lucide-react';\nimport { supabase } from '@/integrations/supabase/client';\nimport { toast } from '@/hooks/use-toast';\nimport { use2FA } from '@/hooks/use2FA';\nimport Layout from '@/components/layout/Layout';\nimport { TextInput } from '@/components/common/FormField';\n\nconst SecuritySettings = () => {\n const [loading, setLoading] = useState(false);\n const [showPassword, setShowPassword] = useState(false);\n const [currentPassword, setCurrentPassword] = useState('');\n const [newPassword, setNewPassword] = useState('');\n const [confirmPassword, setConfirmPassword] = useState('');\n const [is2FASetupOpen, setIs2FASetupOpen] = useState(false);\n const [twoFACode, setTwoFACode] = useState('');\n const [showBackupCodes, setShowBackupCodes] = useState(false);\n const [activeSessions, setActiveSessions] = useState<any[]>([]);\n\n const { twoFAState, generateSecret, enable2FA, disable2FA, verify2FACode } = use2FA();\n\n useEffect(() => {\n fetchActiveSessions();\n }, []);\n\n const fetchActiveSessions = async () => {\n try {\n const { data: { user } } = await supabase.auth.getUser();\n if (!user) return;\n\n // Fetch active sessions from database\n const { data: sessions } = await supabase\n .from('user_sessions')\n .select('*')\n .eq('user_id', user.id)\n .eq('is_active', true);\n\n setActiveSessions(sessions || []);\n } catch (error) {\n console.error('Error fetching sessions:', error);\n }\n };\n\n const handleChangePassword = async () => {\n try {\n if (!currentPassword || !newPassword || !confirmPassword) {\n toast({ title: \"خطأ\", description: \"جميع الحقول مطلوبة\", variant: \"destructive\" });\n return;\n }\n\n if (newPassword !== confirmPassword) {\n toast({ title: \"خطأ\", description: \"كلمات المرور غير متطابقة\", variant: \"destructive\" });\n return;\n }\n\n if (newPassword.length < 8) {\n toast({ title: \"خطأ\", description: \"كلمة المرور يجب أن تكون 8 أحرف على الأقل\", variant: \"destructive\" });\n return;\n }\n\n setLoading(true);\n\n const { error } = await supabase.auth.updateUser({\n password: newPassword,\n });\n\n if (error) throw error;\n\n toast({ title: \"تم\", description: \"تم تحديث كلمة المرور بنجاح\" });\n setCurrentPassword('');\n setNewPassword('');\n setConfirmPassword('');\n } catch (error: any) {\n toast({ \n title: \"خطأ\", \n description: error.message || \"فشل في تحديث كلمة المرور\", \n variant: \"destructive\" \n });\n } finally {\n setLoading(false);\n }\n };\n\n const handleSetup2FA = async () => {\n try {\n const result = await generateSecret();\n if (result) {\n setIs2FASetupOpen(true);\n }\n } catch (error) {\n console.error('Error setting up 2FA:', error);\n }\n };\n\n const handleVerify2FA = async () => {\n try {\n if (twoFACode.length !== 6) {\n toast({ title: \"خطأ\", description: \"الرمز يجب أن يكون 6 أرقام\", variant: \"destructive\" });\n return;\n }\n\n const isValid = await verify2FACode(twoFACode);\n if (!isValid) {\n toast({ title: \"خطأ\", description: \"الرمز غير صحيح\", variant: \"destructive\" });\n return;\n }\n\n const success = await enable2FA(twoFACode);\n if (success) {\n setIs2FASetupOpen(false);\n setTwoFACode('');\n }\n } catch (error) {\n console.error('Error verifying 2FA:', error);\n }\n };\n\n const handleDisable2FA = async () => {\n try {\n const success = await disable2FA();\n if (success) {\n setShowBackupCodes(false);\n }\n } catch (error) {\n console.error('Error disabling 2FA:', error);\n }\n };\n\n const handleLogoutSession = async (sessionId: string) => {\n try {\n const { error } = await supabase\n .from('user_sessions')\n .update({ is_active: false })\n .eq('id', sessionId);\n\n if (error) throw error;\n\n toast({ title: \"تم\", description: \"تم تسجيل الخروج من الجلسة\" });\n fetchActiveSessions();\n } catch (error: any) {\n toast({ \n title: \"خطأ\", \n description: error.message || \"فشل في تسجيل الخروج\", \n variant: \"destructive\" \n });\n }\n };\n\n const copyToClipboard = (text: string) => {\n navigator.clipboard.writeText(text);\n toast({ title: \"تم\", description: \"تم نسخ النص\" });\n };\n\n return (\n <Layout>\n <div className=\"p-4 md:p-6 space-y-6 max-w-4xl\">\n {/* Header */}\n <div>\n <h1 className=\"text-2xl md:text-3xl font-bold text-foreground\">إعدادات الأمان</h1>\n <p className=\"text-muted-foreground mt-1\">\n إدارة أمان حسابك والمصادقة\n </p>\n </div>\n\n {/* Change Password */}\n <Card className=\"medical-shadow\">\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Lock className=\"w-5 h-5 text-primary\" />\n تغيير كلمة المرور\n </CardTitle>\n <CardDescription>\n تحديث كلمة المرور الخاصة بك بانتظام لضمان أمان حسابك\n </CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div>\n <label className=\"text-sm font-medium\">كلمة المرور الحالية</label>\n <div className=\"relative mt-1\">\n <input\n type={showPassword ? 'text' : 'password'}\n value={currentPassword}\n onChange={(e) => setCurrentPassword(e.target.value)}\n className=\"w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent\"\n placeholder=\"أدخل كلمة المرور الحالية\"\n />\n <button\n onClick={() => setShowPassword(!showPassword)}\n className=\"absolute right-3 top-1/2 transform -translate-y-1/2\"\n >\n {showPassword ? <EyeOff className=\"w-4 h-4\" /> : <Eye className=\"w-4 h-4\" />}\n </button>\n </div>\n </div>\n\n <div>\n <label className=\"text-sm font-medium\">كلمة المرور الجديدة</label>\n <input\n type=\"password\"\n value={newPassword}\n onChange={(e) => setNewPassword(e.target.value)}\n className=\"w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent mt-1\"\n placeholder=\"أدخل كلمة مرور جديدة\"\n />\n <p className=\"text-xs text-muted-foreground mt-1\">\n يجب أن تكون 8 أحرف على الأقل\n </p>\n </div>\n\n <div>\n <label className=\"text-sm font-medium\">تأكيد كلمة المرور</label>\n <input\n type=\"password\"\n value={confirmPassword}\n onChange={(e) => setConfirmPassword(e.target.value)}\n className=\"w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent mt-1\"\n placeholder=\"أعد إدخال كلمة المرور\"\n />\n </div>\n\n <Button\n onClick={handleChangePassword}\n disabled={loading}\n variant=\"medical\"\n >\n {loading ? \"جاري التحديث...\" : \"تحديث كلمة المرور\"}\n </Button>\n </CardContent>\n </Card>\n\n {/* Two-Factor Authentication */}\n <Card className=\"medical-shadow\">\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Smartphone className=\"w-5 h-5 text-primary\" />\n المصادقة الثنائية (2FA)\n </CardTitle>\n <CardDescription>\n أضف طبقة إضافية من الأمان إلى حسابك\n </CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <Alert>\n <Shield className=\"h-4 w-4\" />\n <AlertDescription>\n {twoFAState.isEnabled\n ? \"المصادقة الثنائية مفعلة. سيُطلب منك إدخال رمز من تطبيق المصادقة عند تسجيل الدخول.\"\n : \"المصادقة الثنائية معطلة. قم بتفعيلها لزيادة أمان حسابك.\"}\n </AlertDescription>\n </Alert>\n\n {twoFAState.isEnabled ? (\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between p-3 bg-green-50 rounded-lg border border-green-200\">\n <div className=\"flex items-center gap-2\">\n <CheckCircle2 className=\"w-5 h-5 text-green-600\" />\n <span className=\"font-semibold text-green-900\">المصادقة الثنائية مفعلة</span>\n </div>\n <Badge variant=\"outline\" className=\"bg-green-100 text-green-800\">\n نشط\n </Badge>\n </div>\n\n <Button\n variant=\"outline\"\n onClick={() => setShowBackupCodes(!showBackupCodes)}\n >\n <Key className=\"w-4 h-4 ml-2\" />\n عرض رموز النسخ الاحتياطية\n </Button>\n\n {showBackupCodes && twoFAState.backupCodes && (\n <div className=\"p-3 bg-yellow-50 rounded-lg border border-yellow-200 space-y-2\">\n <p className=\"text-sm font-semibold text-yellow-900\">\n احفظ هذه الرموز في مكان آمن:\n </p>\n <div className=\"grid grid-cols-2 gap-2\">\n {twoFAState.backupCodes.map((code, idx) => (\n <div\n key={idx}\n className=\"flex items-center justify-between p-2 bg-white rounded border\"\n >\n <code className=\"font-mono text-sm\">{code}</code>\n <button\n onClick={() => copyToClipboard(code)}\n className=\"text-primary hover:text-primary/80\"\n >\n <Copy className=\"w-4 h-4\" />\n </button>\n </div>\n ))}\n </div>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => {\n const text = twoFAState.backupCodes?.join('\\n') || '';\n const element = document.createElement('a');\n element.setAttribute('href', `data:text/plain;charset=utf-8,${encodeURIComponent(text)}`);\n element.setAttribute('download', 'backup-codes.txt');\n element.style.display = 'none';\n document.body.appendChild(element);\n element.click();\n document.body.removeChild(element);\n }}\n >\n <Download className=\"w-4 h-4 ml-2\" />\n تحميل الرموز\n </Button>\n </div>\n )}\n\n <Button\n variant=\"destructive\"\n onClick={handleDisable2FA}\n >\n <Trash2 className=\"w-4 h-4 ml-2\" />\n تعطيل المصادقة الثنائية\n </Button>\n </div>\n ) : (\n <Button\n onClick={handleSetup2FA}\n variant=\"medical\"\n >\n <Smartphone className=\"w-4 h-4 ml-2\" />\n تفعيل المصادقة الثنائية\n </Button>\n )}\n </CardContent>\n </Card>\n\n {/* Active Sessions */}\n <Card className=\"medical-shadow\">\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Key className=\"w-5 h-5 text-primary\" />\n الجلسات النشطة\n </CardTitle>\n <CardDescription>\n إدارة جلسات تسجيل الدخول النشطة\n </CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-3\">\n {activeSessions.length === 0 ? (\n <p className=\"text-muted-foreground text-center py-4\">لا توجد جلسات نشطة</p>\n ) : (\n activeSessions.map((session) => (\n <div key={session.id} className=\"flex items-center justify-between p-3 border rounded-lg\">\n <div>\n <p className=\"font-semibold\">{session.device_name}</p>\n <p className=\"text-sm text-muted-foreground\">\n {session.ip_address} • {new Date(session.created_at).toLocaleDateString('ar-SA')}\n </p>\n </div>\n <Button\n size=\"sm\"\n variant=\"destructive\"\n onClick={() => handleLogoutSession(session.id)}\n >\n تسجيل خروج\n </Button>\n </div>\n ))\n )}\n </div>\n </CardContent>\n </Card>\n\n {/* 2FA Setup Dialog */}\n <Dialog open={is2FASetupOpen} onOpenChange={setIs2FASetupOpen}>\n <DialogContent className=\"max-w-md\">\n <DialogHeader>\n <DialogTitle>تفعيل المصادقة الثنائية</DialogTitle>\n </DialogHeader>\n <div className=\"space-y-4\">\n <Alert>\n <AlertTriangle className=\"h-4 w-4\" />\n <AlertDescription>\n استخدم تطبيق المصادقة مثل Google Authenticator أو Microsoft Authenticator\n </AlertDescription>\n </Alert>\n\n {twoFAState.qrCode && (\n <div className=\"p-4 bg-gray-100 rounded-lg text-center\">\n <p className=\"text-sm text-muted-foreground mb-2\">امسح رمز QR باستخدام تطبيق المصادقة:</p>\n {/* QR Code would be rendered here */}\n <div className=\"w-32 h-32 bg-white rounded mx-auto border-2 border-gray-300 flex items-center justify-center\">\n <p className=\"text-xs text-gray-500\">QR Code</p>\n </div>\n </div>\n )}\n\n <div>\n <label className=\"text-sm font-medium\">أدخل الرمز من تطبيق المصادقة</label>\n <input\n type=\"text\"\n value={twoFACode}\n onChange={(e) => setTwoFACode(e.target.value.replace(/\\D/g, '').slice(0, 6))}\n maxLength=\"6\"\n placeholder=\"000000\"\n className=\"w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent mt-1 text-center text-2xl tracking-widest\"\n />\n </div>\n\n <DialogFooter>\n <Button\n variant=\"outline\"\n onClick={() => setIs2FASetupOpen(false)}\n >\n إلغاء\n </Button>\n <Button\n variant=\"medical\"\n onClick={handleVerify2FA}\n >\n تأكيد\n </Button>\n </DialogFooter>\n </div>\n </DialogContent>\n </Dialog>\n </div>\n </Layout>\n );\n};\n\nexport default SecuritySettings;\n