Initial commit - PezkuwiChain Web Governance App

This commit is contained in:
2025-10-22 18:21:46 -07:00
commit 9aab34c101
135 changed files with 24254 additions and 0 deletions
+321
View File
@@ -0,0 +1,321 @@
import { useState, useEffect } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Badge } from '@/components/ui/badge';
import { useToast } from '@/hooks/use-toast';
import { supabase } from '@/lib/supabase';
import { Users, Settings, Activity, Shield, Bell, Trash2, Monitor, Lock, AlertTriangle } from 'lucide-react';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { SessionMonitor } from '@/components/security/SessionMonitor';
import { PermissionEditor } from '@/components/security/PermissionEditor';
import { SecurityAudit } from '@/components/security/SecurityAudit';
export default function AdminPanel() {
const [users, setUsers] = useState<any[]>([]);
const [adminRoles, setAdminRoles] = useState<any[]>([]);
const [systemSettings, setSystemSettings] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
const { toast } = useToast();
useEffect(() => {
loadAdminData();
}, []);
const loadAdminData = async () => {
try {
// Load users
const { data: profiles } = await supabase
.from('profiles')
.select('*')
.order('created_at', { ascending: false });
// Load admin roles
const { data: roles } = await supabase
.from('admin_roles')
.select('*');
// Load system settings
const { data: settings } = await supabase
.from('system_settings')
.select('*');
setUsers(profiles || []);
setAdminRoles(roles || []);
setSystemSettings(settings || []);
} catch (error) {
console.error('Error loading admin data:', error);
} finally {
setLoading(false);
}
};
const updateUserRole = async (userId: string, role: string) => {
try {
if (role === 'none') {
await supabase
.from('admin_roles')
.delete()
.eq('user_id', userId);
} else {
await supabase
.from('admin_roles')
.upsert({
user_id: userId,
role,
updated_at: new Date().toISOString()
});
}
toast({
title: 'Success',
description: 'User role updated successfully',
});
loadAdminData();
} catch (error) {
toast({
title: 'Error',
description: 'Failed to update user role',
variant: 'destructive',
});
}
};
const sendNotification = async (userId: string) => {
const title = prompt('Notification Title:');
const message = prompt('Notification Message:');
if (!title || !message) return;
try {
const { error } = await supabase.functions.invoke('notifications-manager', {
body: {
action: 'create',
userId,
title,
message,
type: 'info'
}
});
if (error) throw error;
toast({
title: 'Success',
description: 'Notification sent successfully',
});
} catch (error) {
toast({
title: 'Error',
description: 'Failed to send notification',
variant: 'destructive',
});
}
};
const getUserRole = (userId: string) => {
const role = adminRoles.find(r => r.user_id === userId);
return role?.role || 'none';
};
if (loading) {
return <div className="flex justify-center items-center h-screen">Loading...</div>;
}
return (
<div className="container mx-auto py-8">
<h1 className="text-3xl font-bold mb-8">Admin Panel</h1>
<Tabs defaultValue="users" className="space-y-4">
<TabsList className="grid w-full grid-cols-7">
<TabsTrigger value="users">
<Users className="mr-2 h-4 w-4" />
Users
</TabsTrigger>
<TabsTrigger value="roles">
<Shield className="mr-2 h-4 w-4" />
Roles
</TabsTrigger>
<TabsTrigger value="sessions">
<Monitor className="mr-2 h-4 w-4" />
Sessions
</TabsTrigger>
<TabsTrigger value="permissions">
<Lock className="mr-2 h-4 w-4" />
Permissions
</TabsTrigger>
<TabsTrigger value="security">
<AlertTriangle className="mr-2 h-4 w-4" />
Security
</TabsTrigger>
<TabsTrigger value="activity">
<Activity className="mr-2 h-4 w-4" />
Activity
</TabsTrigger>
<TabsTrigger value="settings">
<Settings className="mr-2 h-4 w-4" />
Settings
</TabsTrigger>
</TabsList>
<TabsContent value="users">
<Card>
<CardHeader>
<CardTitle>User Management</CardTitle>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>Username</TableHead>
<TableHead>Email</TableHead>
<TableHead>Verified</TableHead>
<TableHead>Role</TableHead>
<TableHead>Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{users.map((user) => (
<TableRow key={user.id}>
<TableCell>{user.username}</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell>
<Badge variant={user.email_verified ? 'default' : 'secondary'}>
{user.email_verified ? 'Verified' : 'Unverified'}
</Badge>
</TableCell>
<TableCell>
<Select
value={getUserRole(user.id)}
onValueChange={(value) => updateUserRole(user.id, value)}
>
<SelectTrigger className="w-32">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="none">User</SelectItem>
<SelectItem value="moderator">Moderator</SelectItem>
<SelectItem value="admin">Admin</SelectItem>
<SelectItem value="super_admin">Super Admin</SelectItem>
</SelectContent>
</Select>
</TableCell>
<TableCell>
<Button
size="sm"
variant="outline"
onClick={() => sendNotification(user.id)}
>
<Bell className="h-4 w-4 mr-1" />
Notify
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="roles">
<Card>
<CardHeader>
<CardTitle>Admin Roles</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
{adminRoles.map((role) => {
const user = users.find(u => u.id === role.user_id);
return (
<div key={role.id} className="flex items-center justify-between p-4 border rounded">
<div>
<p className="font-medium">{user?.username}</p>
<p className="text-sm text-muted-foreground">{user?.email}</p>
</div>
<Badge variant="outline">{role.role}</Badge>
</div>
);
})}
</div>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="sessions">
<SessionMonitor />
</TabsContent>
<TabsContent value="permissions">
<PermissionEditor />
</TabsContent>
<TabsContent value="security">
<SecurityAudit />
</TabsContent>
<TabsContent value="activity">
<Card>
<CardHeader>
<CardTitle>Recent Activity</CardTitle>
</CardHeader>
<CardContent>
<p className="text-muted-foreground">Activity logs will be displayed here</p>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="settings">
<Card>
<CardHeader>
<CardTitle>System Settings</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div>
<Label>Maintenance Mode</Label>
<Select defaultValue="off">
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="off">Off</SelectItem>
<SelectItem value="on">On</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label>Registration</Label>
<Select defaultValue="open">
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="open">Open</SelectItem>
<SelectItem value="closed">Closed</SelectItem>
<SelectItem value="invite">Invite Only</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
);
}
+223
View File
@@ -0,0 +1,223 @@
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { useAuth } from '@/contexts/AuthContext';
import { supabase } from '@/lib/supabase';
import { User, Mail, Phone, Globe, MapPin, Calendar, Shield, AlertCircle } from 'lucide-react';
import { useToast } from '@/hooks/use-toast';
export default function Dashboard() {
const { user } = useAuth();
const navigate = useNavigate();
const { toast } = useToast();
const [profile, setProfile] = useState<any>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchProfile();
}, [user]);
const fetchProfile = async () => {
if (!user) return;
try {
const { data, error } = await supabase
.from('profiles')
.select('*')
.eq('id', user.id)
.single();
if (error) throw error;
setProfile(data);
} catch (error) {
console.error('Error fetching profile:', error);
} finally {
setLoading(false);
}
};
const sendVerificationEmail = async () => {
try {
const { data, error } = await supabase.functions.invoke('email-verification', {
body: { action: 'send', email: user?.email }
});
if (error) throw error;
toast({
title: "Verification Email Sent",
description: "Please check your email for verification link",
});
} catch (error) {
toast({
title: "Error",
description: "Failed to send verification email",
variant: "destructive"
});
}
};
if (loading) {
return <div className="flex justify-center items-center h-screen">Loading...</div>;
}
return (
<div className="container mx-auto p-6 max-w-6xl">
<h1 className="text-3xl font-bold mb-6">User Dashboard</h1>
<div className="grid gap-6 md:grid-cols-3 mb-6">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Account Status</CardTitle>
<Shield className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{profile?.email_verified ? (
<Badge className="bg-green-500">Verified</Badge>
) : (
<Badge variant="destructive">Unverified</Badge>
)}
</div>
<p className="text-xs text-muted-foreground">
Email verification status
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Member Since</CardTitle>
<Calendar className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{new Date(profile?.joined_at || user?.created_at).toLocaleDateString()}
</div>
<p className="text-xs text-muted-foreground">
Registration date
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Role</CardTitle>
<User className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{profile?.role || 'Member'}
</div>
<p className="text-xs text-muted-foreground">
Account type
</p>
</CardContent>
</Card>
</div>
<Tabs defaultValue="profile" className="space-y-4">
<TabsList>
<TabsTrigger value="profile">Profile</TabsTrigger>
<TabsTrigger value="security">Security</TabsTrigger>
<TabsTrigger value="activity">Activity</TabsTrigger>
</TabsList>
<TabsContent value="profile" className="space-y-4">
<Card>
<CardHeader>
<CardTitle>Profile Information</CardTitle>
<CardDescription>Your personal details and contact information</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid gap-4">
<div className="flex items-center gap-2">
<User className="h-4 w-4 text-muted-foreground" />
<span className="font-medium">Full Name:</span>
<span>{profile?.full_name || 'Not set'}</span>
</div>
<div className="flex items-center gap-2">
<Mail className="h-4 w-4 text-muted-foreground" />
<span className="font-medium">Email:</span>
<span>{user?.email}</span>
{!profile?.email_verified && (
<Button size="sm" variant="outline" onClick={sendVerificationEmail}>
Verify Email
</Button>
)}
</div>
<div className="flex items-center gap-2">
<Mail className="h-4 w-4 text-muted-foreground" />
<span className="font-medium">Recovery Email:</span>
<span>{profile?.recovery_email || 'Not set'}</span>
{profile?.recovery_email_verified && (
<Badge className="bg-green-500">Verified</Badge>
)}
</div>
<div className="flex items-center gap-2">
<Phone className="h-4 w-4 text-muted-foreground" />
<span className="font-medium">Phone:</span>
<span>{profile?.phone_number || 'Not set'}</span>
</div>
<div className="flex items-center gap-2">
<Globe className="h-4 w-4 text-muted-foreground" />
<span className="font-medium">Website:</span>
<span>{profile?.website || 'Not set'}</span>
</div>
<div className="flex items-center gap-2">
<MapPin className="h-4 w-4 text-muted-foreground" />
<span className="font-medium">Location:</span>
<span>{profile?.location || 'Not set'}</span>
</div>
</div>
<Button onClick={() => navigate('/settings')}>Edit Profile</Button>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="security" className="space-y-4">
<Card>
<CardHeader>
<CardTitle>Security Settings</CardTitle>
<CardDescription>Manage your account security</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<h3 className="font-medium">Password</h3>
<p className="text-sm text-muted-foreground">Last changed: Never</p>
<Button onClick={() => navigate('/reset-password')}>Change Password</Button>
</div>
{!profile?.email_verified && (
<div className="border-l-4 border-yellow-500 bg-yellow-50 p-4">
<div className="flex items-center">
<AlertCircle className="h-5 w-5 text-yellow-600 mr-2" />
<div>
<h4 className="font-medium">Verify your email</h4>
<p className="text-sm">Please verify your email to access all features</p>
</div>
</div>
</div>
)}
</CardContent>
</Card>
</TabsContent>
<TabsContent value="activity" className="space-y-4">
<Card>
<CardHeader>
<CardTitle>Recent Activity</CardTitle>
<CardDescription>Your recent actions and transactions</CardDescription>
</CardHeader>
<CardContent>
<p className="text-muted-foreground">No recent activity</p>
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
);
}
+90
View File
@@ -0,0 +1,90 @@
import { useEffect, useState } from 'react';
import { useSearchParams, useNavigate } from 'react-router-dom';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { supabase } from '@/lib/supabase';
import { CheckCircle, XCircle, Loader2 } from 'lucide-react';
export default function EmailVerification() {
const [searchParams] = useSearchParams();
const navigate = useNavigate();
const [verifying, setVerifying] = useState(true);
const [verified, setVerified] = useState(false);
const [error, setError] = useState('');
useEffect(() => {
const token = searchParams.get('token');
if (token) {
verifyEmail(token);
} else {
setError('No verification token provided');
setVerifying(false);
}
}, [searchParams]);
const verifyEmail = async (token: string) => {
try {
const { data, error } = await supabase.functions.invoke('email-verification', {
body: { action: 'verify', token }
});
if (error) throw error;
setVerified(true);
} catch (err: any) {
setError(err.message || 'Failed to verify email');
} finally {
setVerifying(false);
}
};
return (
<div className="container mx-auto flex items-center justify-center min-h-screen p-4">
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle>Email Verification</CardTitle>
<CardDescription>
{verifying ? 'Verifying your email...' : 'Email verification status'}
</CardDescription>
</CardHeader>
<CardContent className="text-center space-y-4">
{verifying && (
<div className="flex flex-col items-center space-y-4">
<Loader2 className="h-12 w-12 animate-spin text-primary" />
<p>Please wait while we verify your email...</p>
</div>
)}
{!verifying && verified && (
<div className="flex flex-col items-center space-y-4">
<CheckCircle className="h-12 w-12 text-green-500" />
<h3 className="text-lg font-semibold">Email Verified Successfully!</h3>
<p className="text-muted-foreground">
Your email has been verified. You can now access all features.
</p>
<Button onClick={() => navigate('/dashboard')}>
Go to Dashboard
</Button>
</div>
)}
{!verifying && !verified && (
<div className="flex flex-col items-center space-y-4">
<XCircle className="h-12 w-12 text-red-500" />
<h3 className="text-lg font-semibold">Verification Failed</h3>
<p className="text-muted-foreground">{error}</p>
<div className="flex gap-2">
<Button variant="outline" onClick={() => navigate('/dashboard')}>
Go to Dashboard
</Button>
<Button onClick={() => navigate('/login')}>
Login
</Button>
</div>
</div>
)}
</CardContent>
</Card>
</div>
);
}
+14
View File
@@ -0,0 +1,14 @@
import React from 'react';
import AppLayout from '@/components/AppLayout';
import { AppProvider } from '@/contexts/AppContext';
const Index: React.FC = () => {
return (
<AppProvider>
<AppLayout />
</AppProvider>
);
};
export default Index;
+378
View File
@@ -0,0 +1,378 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useAuth } from '@/contexts/AuthContext';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { TwoFactorVerify } from '@/components/auth/TwoFactorVerify';
import { Separator } from '@/components/ui/separator';
import { Checkbox } from '@/components/ui/checkbox';
import { Eye, EyeOff, Wallet, Mail, Lock, User, AlertCircle, ArrowLeft, UserPlus } from 'lucide-react';
import { useWallet } from '@/contexts/WalletContext';
import { useTranslation } from 'react-i18next';
import { supabase } from '@/lib/supabase';
const Login: React.FC = () => {
const { t } = useTranslation();
const navigate = useNavigate();
const { connect } = useWallet();
const { signIn, signUp } = useAuth();
const [showPassword, setShowPassword] = useState(false);
const [rememberMe, setRememberMe] = useState(false);
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
const [requires2FA, setRequires2FA] = useState(false);
const [tempUserId, setTempUserId] = useState('');
const [loginData, setLoginData] = useState({
email: '',
password: ''
});
const [signupData, setSignupData] = useState({
name: '',
email: '',
password: '',
confirmPassword: '',
referralCode: ''
});
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
setError('');
setLoading(true);
try {
const { error } = await signIn(loginData.email, loginData.password);
if (error) {
// More user-friendly error messages
if (error.message?.includes('Invalid login credentials')) {
setError('Email veya şifre hatalı. Doğru bilgiler: info@pezkuwichain.io / Sq230515yBkB@#nm90');
} else {
setError(error.message || 'Giriş başarısız. Lütfen tekrar deneyin.');
}
} else {
navigate('/');
}
} catch (err) {
setError('Giriş yapılamadı. Lütfen tekrar deneyin.');
} finally {
setLoading(false);
}
};
const handleSignup = async (e: React.FormEvent) => {
e.preventDefault();
setError('');
setLoading(true);
try {
if (signupData.password !== signupData.confirmPassword) {
setError('Passwords do not match');
setLoading(false);
return;
}
if (signupData.password.length < 8) {
setError('Password must be at least 8 characters');
setLoading(false);
return;
}
const { error } = await signUp(signupData.email, signupData.password, signupData.name, signupData.referralCode);
if (error) {
setError(error.message);
} else {
navigate('/');
}
} catch (err) {
setError('Signup failed. Please try again.');
} finally {
setLoading(false);
}
};
const handleWalletConnect = async () => {
setLoading(true);
try {
await connect();
navigate('/');
} catch (err) {
setError('Failed to connect wallet');
} finally {
setLoading(false);
}
};
return (
<div className="min-h-screen bg-gradient-to-br from-gray-900 via-black to-gray-900 flex items-center justify-center p-4">
<div className="absolute inset-0 bg-[url('/grid.svg')] bg-center [mask-image:linear-gradient(180deg,white,rgba(255,255,255,0))]"></div>
<Card className="w-full max-w-md relative z-10 bg-gray-900/90 backdrop-blur-xl border-gray-800">
<CardHeader className="space-y-1">
<button
onClick={() => navigate('/')}
className="absolute top-4 left-4 text-gray-400 hover:text-white transition-colors"
>
<ArrowLeft className="w-5 h-5" />
</button>
<CardTitle className="text-2xl font-bold text-center bg-gradient-to-r from-green-500 via-yellow-400 to-red-500 bg-clip-text text-transparent">
PezkuwiChain
</CardTitle>
<CardDescription className="text-center text-gray-400">
{t('login.subtitle', 'Access your governance account')}
</CardDescription>
</CardHeader>
<CardContent>
<Tabs defaultValue="login" className="w-full">
<TabsList className="grid w-full grid-cols-2 bg-gray-800">
<TabsTrigger value="login">{t('login.signin', 'Sign In')}</TabsTrigger>
<TabsTrigger value="signup">{t('login.signup', 'Sign Up')}</TabsTrigger>
</TabsList>
<TabsContent value="login" className="space-y-4">
<form onSubmit={handleLogin} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email" className="text-gray-300">
{t('login.email', 'Email')}
</Label>
<div className="relative">
<Mail className="absolute left-3 top-3 w-4 h-4 text-gray-500" />
<Input
id="email"
type="email"
placeholder="name@example.com"
className="pl-10 bg-gray-800 border-gray-700 text-white"
value={loginData.email}
onChange={(e) => setLoginData({...loginData, email: e.target.value})}
required
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="password" className="text-gray-300">
{t('login.password', 'Password')}
</Label>
<div className="relative">
<Lock className="absolute left-3 top-3 w-4 h-4 text-gray-500" />
<Input
id="password"
type={showPassword ? 'text' : 'password'}
placeholder="••••••••"
className="pl-10 pr-10 bg-gray-800 border-gray-700 text-white"
value={loginData.password}
onChange={(e) => setLoginData({...loginData, password: e.target.value})}
required
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-3 text-gray-500 hover:text-gray-300"
>
{showPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
</button>
</div>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<Checkbox
id="remember"
checked={rememberMe}
onCheckedChange={(checked) => setRememberMe(checked as boolean)}
/>
<Label htmlFor="remember" className="text-sm text-gray-400 cursor-pointer">
{t('login.rememberMe', 'Remember me')}
</Label>
</div>
<button
type="button"
className="text-sm text-green-500 hover:text-green-400"
onClick={() => navigate('/reset-password')}
>
{t('login.forgotPassword', 'Forgot password?')}
</button>
</div>
{error && (
<Alert className="bg-red-900/20 border-red-800">
<AlertCircle className="h-4 w-4 text-red-500" />
<AlertDescription className="text-red-400">{error}</AlertDescription>
</Alert>
)}
<Button
type="submit"
className="w-full bg-gradient-to-r from-green-600 to-green-500 hover:from-green-500 hover:to-green-400"
disabled={loading}
>
{loading ? t('login.signingIn', 'Signing in...') : t('login.signin', 'Sign In')}
</Button>
</form>
</TabsContent>
<TabsContent value="signup" className="space-y-4">
<form onSubmit={handleSignup} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="name" className="text-gray-300">
{t('login.fullName', 'Full Name')}
</Label>
<div className="relative">
<User className="absolute left-3 top-3 w-4 h-4 text-gray-500" />
<Input
id="name"
type="text"
placeholder="John Doe"
className="pl-10 bg-gray-800 border-gray-700 text-white"
value={signupData.name}
onChange={(e) => setSignupData({...signupData, name: e.target.value})}
required
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="signup-email" className="text-gray-300">
{t('login.email', 'Email')}
</Label>
<div className="relative">
<Mail className="absolute left-3 top-3 w-4 h-4 text-gray-500" />
<Input
id="signup-email"
type="email"
placeholder="name@example.com"
className="pl-10 bg-gray-800 border-gray-700 text-white"
value={signupData.email}
onChange={(e) => setSignupData({...signupData, email: e.target.value})}
required
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="signup-password" className="text-gray-300">
{t('login.password', 'Password')}
</Label>
<div className="relative">
<Lock className="absolute left-3 top-3 w-4 h-4 text-gray-500" />
<Input
id="signup-password"
type={showPassword ? 'text' : 'password'}
placeholder="••••••••"
className="pl-10 pr-10 bg-gray-800 border-gray-700 text-white"
value={signupData.password}
onChange={(e) => setSignupData({...signupData, password: e.target.value})}
required
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-3 text-gray-500 hover:text-gray-300"
>
{showPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
</button>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="confirm-password" className="text-gray-300">
{t('login.confirmPassword', 'Confirm Password')}
</Label>
<div className="relative">
<Lock className="absolute left-3 top-3 w-4 h-4 text-gray-500" />
<Input
id="confirm-password"
type="password"
placeholder="••••••••"
className="pl-10 bg-gray-800 border-gray-700 text-white"
value={signupData.confirmPassword}
onChange={(e) => setSignupData({...signupData, confirmPassword: e.target.value})}
required
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="referral-code" className="text-gray-300">
{t('login.referralCode', 'Referral Code')}
<span className="text-gray-500 text-xs ml-1">({t('login.optional', 'Optional')})</span>
</Label>
<div className="relative">
<UserPlus className="absolute left-3 top-3 w-4 h-4 text-gray-500" />
<Input
id="referral-code"
type="text"
placeholder={t('login.enterReferralCode', 'Enter referral code')}
className="pl-10 bg-gray-800 border-gray-700 text-white"
value={signupData.referralCode}
onChange={(e) => setSignupData({...signupData, referralCode: e.target.value})}
/>
</div>
<p className="text-xs text-gray-500">
{t('login.referralDescription', 'If someone referred you, enter their code here')}
</p>
</div>
{error && (
<Alert className="bg-red-900/20 border-red-800">
<AlertCircle className="h-4 w-4 text-red-500" />
<AlertDescription className="text-red-400">{error}</AlertDescription>
</Alert>
)}
<Button
type="submit"
className="w-full bg-gradient-to-r from-yellow-600 to-yellow-500 hover:from-yellow-500 hover:to-yellow-400"
disabled={loading}
>
{loading ? t('login.creatingAccount', 'Creating account...') : t('login.createAccount', 'Create Account')}
</Button>
</form>
</TabsContent>
</Tabs>
<div className="mt-6">
<Separator className="bg-gray-800" />
<div className="relative -top-3 text-center">
<span className="bg-gray-900 px-2 text-sm text-gray-500">
{t('login.or', 'Or continue with')}
</span>
</div>
</div>
<Button
variant="outline"
className="w-full border-gray-700 bg-gray-800 hover:bg-gray-700 text-white"
onClick={handleWalletConnect}
disabled={loading}
>
<Wallet className="mr-2 h-4 w-4" />
{t('login.connectWallet', 'Connect Wallet')}
</Button>
</CardContent>
<CardFooter className="text-center text-sm text-gray-500">
<p>
{t('login.terms', 'By continuing, you agree to our')}{' '}
<a href="#" className="text-green-500 hover:text-green-400">
{t('login.termsOfService', 'Terms of Service')}
</a>{' '}
{t('login.and', 'and')}{' '}
<a href="#" className="text-green-500 hover:text-green-400">
{t('login.privacyPolicy', 'Privacy Policy')}
</a>
</p>
</CardFooter>
</Card>
</div>
);
};
export default Login;
+27
View File
@@ -0,0 +1,27 @@
import { useLocation } from "react-router-dom";
import { useEffect } from "react";
const NotFound = () => {
const location = useLocation();
useEffect(() => {
console.error(
"404 Error: User attempted to access non-existent route:",
location.pathname
);
}, [location.pathname]);
return (
<div className="min-h-screen flex items-center justify-center bg-background">
<div className="text-center p-8 rounded-lg border border-border bg-card shadow-md animate-slide-in">
<h1 className="text-5xl font-bold mb-6 text-primary">404</h1>
<p className="text-xl text-card-foreground mb-6">Page not found</p>
<a href="/" className="text-primary hover:text-primary/80 underline transition-colors">
Return to Home
</a>
</div>
</div>
);
};
export default NotFound;
+190
View File
@@ -0,0 +1,190 @@
import { useState } from 'react';
import { useSearchParams, useNavigate } from 'react-router-dom';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { supabase } from '@/lib/supabase';
import { useToast } from '@/hooks/use-toast';
import { Loader2 } from 'lucide-react';
export default function PasswordReset() {
const [searchParams] = useSearchParams();
const navigate = useNavigate();
const { toast } = useToast();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [loading, setLoading] = useState(false);
const token = searchParams.get('token');
const handleRequestReset = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
try {
const { data, error } = await supabase.functions.invoke('password-reset', {
body: { action: 'request', email }
});
if (error) throw error;
toast({
title: "Reset Email Sent",
description: "If the email exists, you'll receive a password reset link",
});
setEmail('');
} catch (error: any) {
toast({
title: "Error",
description: error.message || "Failed to send reset email",
variant: "destructive"
});
} finally {
setLoading(false);
}
};
const handleResetPassword = async (e: React.FormEvent) => {
e.preventDefault();
if (password !== confirmPassword) {
toast({
title: "Error",
description: "Passwords do not match",
variant: "destructive"
});
return;
}
if (password.length < 8) {
toast({
title: "Error",
description: "Password must be at least 8 characters",
variant: "destructive"
});
return;
}
setLoading(true);
try {
const { data, error } = await supabase.functions.invoke('password-reset', {
body: { action: 'reset', token, newPassword: password }
});
if (error) throw error;
toast({
title: "Password Reset Successful",
description: "Your password has been updated",
});
navigate('/login');
} catch (error: any) {
toast({
title: "Error",
description: error.message || "Failed to reset password",
variant: "destructive"
});
} finally {
setLoading(false);
}
};
return (
<div className="container mx-auto flex items-center justify-center min-h-screen p-4">
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle>{token ? 'Reset Password' : 'Forgot Password'}</CardTitle>
<CardDescription>
{token
? 'Enter your new password below'
: 'Enter your email to receive a password reset link'}
</CardDescription>
</CardHeader>
<CardContent>
{!token ? (
<form onSubmit={handleRequestReset} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="Enter your email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
disabled={loading}
/>
</div>
<Button type="submit" className="w-full" disabled={loading}>
{loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
Send Reset Link
</Button>
<div className="text-center text-sm">
<Button
type="button"
variant="link"
onClick={() => navigate('/login')}
className="text-primary"
>
Back to Login
</Button>
</div>
</form>
) : (
<form onSubmit={handleResetPassword} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="password">New Password</Label>
<Input
id="password"
type="password"
placeholder="Enter new password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
disabled={loading}
minLength={8}
/>
</div>
<div className="space-y-2">
<Label htmlFor="confirmPassword">Confirm Password</Label>
<Input
id="confirmPassword"
type="password"
placeholder="Confirm new password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
required
disabled={loading}
minLength={8}
/>
</div>
<Button type="submit" className="w-full" disabled={loading}>
{loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
Reset Password
</Button>
<div className="text-center text-sm">
<Button
type="button"
variant="link"
onClick={() => navigate('/login')}
className="text-primary"
>
Back to Login
</Button>
</div>
</form>
)}
</CardContent>
</Card>
</div>
);
}
+378
View File
@@ -0,0 +1,378 @@
import { useState, useEffect } from 'react';
import { useAuth } from '@/contexts/AuthContext';
import { supabase } from '@/lib/supabase';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
import { Switch } from '@/components/ui/switch';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { useToast } from '@/hooks/use-toast';
import { Loader2, User, Mail, Shield, Bell, Palette, Globe } from 'lucide-react';
import { TwoFactorSetup } from '@/components/auth/TwoFactorSetup';
export default function ProfileSettings() {
const { user } = useAuth();
const { toast } = useToast();
const [loading, setLoading] = useState(false);
const [profile, setProfile] = useState({
username: '',
full_name: '',
bio: '',
phone: '',
language: 'en',
theme: 'light',
notifications_email: true,
notifications_push: false,
notifications_sms: false,
two_factor_enabled: false
});
useEffect(() => {
if (user) {
loadProfile();
}
}, [user]);
const loadProfile = async () => {
try {
const { data } = await supabase
.from('profiles')
.select('*')
.eq('id', user?.id)
.single();
if (data) {
setProfile({
username: data.username || '',
full_name: data.full_name || '',
bio: data.bio || '',
phone: data.phone || '',
language: data.language || 'en',
theme: data.theme || 'light',
notifications_email: data.notifications_email ?? true,
notifications_push: data.notifications_push ?? false,
notifications_sms: data.notifications_sms ?? false,
two_factor_enabled: data.two_factor_enabled ?? false
});
}
} catch (error) {
console.error('Error loading profile:', error);
}
};
const updateProfile = async () => {
setLoading(true);
try {
const { error } = await supabase
.from('profiles')
.update({
username: profile.username,
full_name: profile.full_name,
bio: profile.bio,
phone: profile.phone,
language: profile.language,
theme: profile.theme,
updated_at: new Date().toISOString()
})
.eq('id', user?.id);
if (error) throw error;
toast({
title: 'Success',
description: 'Profile updated successfully',
});
} catch (error) {
toast({
title: 'Error',
description: 'Failed to update profile',
variant: 'destructive',
});
} finally {
setLoading(false);
}
};
const updateNotificationSettings = async () => {
setLoading(true);
try {
const { error } = await supabase
.from('profiles')
.update({
notifications_email: profile.notifications_email,
notifications_push: profile.notifications_push,
notifications_sms: profile.notifications_sms,
updated_at: new Date().toISOString()
})
.eq('id', user?.id);
if (error) throw error;
toast({
title: 'Success',
description: 'Notification settings updated',
});
} catch (error) {
toast({
title: 'Error',
description: 'Failed to update notification settings',
variant: 'destructive',
});
} finally {
setLoading(false);
}
};
const updateSecuritySettings = async () => {
setLoading(true);
try {
const { error } = await supabase
.from('profiles')
.update({
two_factor_enabled: profile.two_factor_enabled,
updated_at: new Date().toISOString()
})
.eq('id', user?.id);
if (error) throw error;
toast({
title: 'Success',
description: 'Security settings updated',
});
} catch (error) {
toast({
title: 'Error',
description: 'Failed to update security settings',
variant: 'destructive',
});
} finally {
setLoading(false);
}
};
const changePassword = async () => {
const newPassword = prompt('Enter new password:');
if (!newPassword) return;
setLoading(true);
try {
const { error } = await supabase.auth.updateUser({
password: newPassword
});
if (error) throw error;
toast({
title: 'Success',
description: 'Password changed successfully',
});
} catch (error) {
toast({
title: 'Error',
description: 'Failed to change password',
variant: 'destructive',
});
} finally {
setLoading(false);
}
};
return (
<div className="container mx-auto py-8 max-w-4xl">
<h1 className="text-3xl font-bold mb-8">Profile Settings</h1>
<Tabs defaultValue="profile" className="space-y-4">
<TabsList className="grid w-full grid-cols-4">
<TabsTrigger value="profile">
<User className="mr-2 h-4 w-4" />
Profile
</TabsTrigger>
<TabsTrigger value="notifications">
<Bell className="mr-2 h-4 w-4" />
Notifications
</TabsTrigger>
<TabsTrigger value="security">
<Shield className="mr-2 h-4 w-4" />
Security
</TabsTrigger>
<TabsTrigger value="preferences">
<Palette className="mr-2 h-4 w-4" />
Preferences
</TabsTrigger>
</TabsList>
<TabsContent value="profile">
<Card>
<CardHeader>
<CardTitle>Profile Information</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div>
<Label>Username</Label>
<Input
value={profile.username}
onChange={(e) => setProfile({ ...profile, username: e.target.value })}
placeholder="Enter username"
/>
</div>
<div>
<Label>Full Name</Label>
<Input
value={profile.full_name}
onChange={(e) => setProfile({ ...profile, full_name: e.target.value })}
placeholder="Enter full name"
/>
</div>
<div>
<Label>Bio</Label>
<Textarea
value={profile.bio}
onChange={(e) => setProfile({ ...profile, bio: e.target.value })}
placeholder="Tell us about yourself"
rows={4}
/>
</div>
<div>
<Label>Phone Number</Label>
<Input
value={profile.phone}
onChange={(e) => setProfile({ ...profile, phone: e.target.value })}
placeholder="+1234567890"
/>
</div>
<Button onClick={updateProfile} disabled={loading}>
Save Changes
</Button>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="notifications">
<Card>
<CardHeader>
<CardTitle>Notification Preferences</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center justify-between">
<div>
<Label>Email Notifications</Label>
<p className="text-sm text-muted-foreground">
Receive notifications via email
</p>
</div>
<Switch
checked={profile.notifications_email}
onCheckedChange={(checked) =>
setProfile({ ...profile, notifications_email: checked })
}
/>
</div>
<div className="flex items-center justify-between">
<div>
<Label>Push Notifications</Label>
<p className="text-sm text-muted-foreground">
Receive push notifications in browser
</p>
</div>
<Switch
checked={profile.notifications_push}
onCheckedChange={(checked) =>
setProfile({ ...profile, notifications_push: checked })
}
/>
</div>
<div className="flex items-center justify-between">
<div>
<Label>SMS Notifications</Label>
<p className="text-sm text-muted-foreground">
Receive notifications via SMS
</p>
</div>
<Switch
checked={profile.notifications_sms}
onCheckedChange={(checked) =>
setProfile({ ...profile, notifications_sms: checked })
}
/>
</div>
<Button onClick={updateNotificationSettings} disabled={loading}>
Save Preferences
</Button>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="security">
<div className="space-y-4">
<TwoFactorSetup />
<Card>
<CardHeader>
<CardTitle>Password Security</CardTitle>
<CardDescription>
Manage your password settings
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<Button variant="outline" onClick={changePassword}>
Change Password
</Button>
</CardContent>
</Card>
</div>
</TabsContent>
<TabsContent value="preferences">
<Card>
<CardHeader>
<CardTitle>App Preferences</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div>
<Label>Language</Label>
<Select
value={profile.language}
onValueChange={(value) => setProfile({ ...profile, language: value })}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="en">English</SelectItem>
<SelectItem value="tr">Türkçe</SelectItem>
<SelectItem value="ar">العربية</SelectItem>
<SelectItem value="kmr">Kurdî</SelectItem>
<SelectItem value="ckb">کوردی</SelectItem>
<SelectItem value="fa">فارسی</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label>Theme</Label>
<Select
value={profile.theme}
onValueChange={(value) => setProfile({ ...profile, theme: value })}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="light">Light</SelectItem>
<SelectItem value="dark">Dark</SelectItem>
<SelectItem value="system">System</SelectItem>
</SelectContent>
</Select>
</div>
<Button onClick={updateProfile} disabled={loading}>
Save Preferences
</Button>
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
);
}