Spaces:
Sleeping
Sleeping
طيب اريدك ان تضيف معلومات كثير في الملف الشخصي يضيفها المستخدم تساعدنا ع
Browse files- src/app/login/page.tsx +98 -21
- src/app/profile/page.tsx +94 -33
- src/contexts/auth-context.tsx +29 -25
- src/lib/translations.ts +18 -0
src/app/login/page.tsx
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
|
|
| 1 |
'use client';
|
| 2 |
|
| 3 |
import { Button } from "@/components/ui/button";
|
| 4 |
import { Header } from "@/components/header";
|
| 5 |
-
import { Loader2, LogIn, MessageSquare, Chrome, UserPlus, MapPin, Calendar } from "lucide-react";
|
| 6 |
import { useAuth } from "@/contexts/auth-context";
|
| 7 |
import { useEffect, useState } from "react";
|
| 8 |
import { useRouter } from "next/navigation";
|
| 9 |
import { Input } from "@/components/ui/input";
|
| 10 |
import { Label } from "@/components/ui/label";
|
|
|
|
|
|
|
| 11 |
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
| 12 |
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
| 13 |
import { useToast } from "@/hooks/use-toast";
|
|
@@ -17,10 +20,19 @@ export default function LoginPage() {
|
|
| 17 |
const { user, userData, loading: authLoading, signInWithGoogle, signInWithUsername, completeProfile } = useAuth();
|
| 18 |
const { t } = useLanguage();
|
| 19 |
const [isSigningIn, setIsSigningIn] = useState(false);
|
|
|
|
|
|
|
| 20 |
const [username, setUsername] = useState('');
|
| 21 |
const [password, setPassword] = useState('');
|
| 22 |
const [age, setAge] = useState('');
|
| 23 |
const [location, setLocation] = useState('');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
const [showCompleteProfile, setShowCompleteProfile] = useState(false);
|
| 25 |
|
| 26 |
const router = useRouter();
|
|
@@ -52,7 +64,9 @@ export default function LoginPage() {
|
|
| 52 |
}
|
| 53 |
setIsSigningIn(true);
|
| 54 |
try {
|
| 55 |
-
await signInWithUsername(username, password, parseInt(age), location
|
|
|
|
|
|
|
| 56 |
} catch (error) {
|
| 57 |
console.error(error);
|
| 58 |
} finally {
|
|
@@ -68,7 +82,14 @@ export default function LoginPage() {
|
|
| 68 |
return;
|
| 69 |
}
|
| 70 |
setIsSigningIn(true);
|
| 71 |
-
await completeProfile(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
setIsSigningIn(false);
|
| 73 |
};
|
| 74 |
|
|
@@ -84,38 +105,84 @@ export default function LoginPage() {
|
|
| 84 |
return (
|
| 85 |
<div className="flex h-screen w-full flex-col bg-muted/30">
|
| 86 |
<Header title={t.loginTitle} />
|
| 87 |
-
<div className="flex flex-1 items-center justify-center p-4">
|
| 88 |
-
<Card className="w-full max-w-
|
| 89 |
<CardHeader className="bg-primary text-primary-foreground p-8">
|
| 90 |
-
<CardTitle className="text-
|
| 91 |
-
<CardDescription className="text-primary-foreground/
|
| 92 |
</CardHeader>
|
| 93 |
<form onSubmit={handleCompleteProfile}>
|
| 94 |
<CardContent className="space-y-6 p-8">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
<div className="space-y-2">
|
| 96 |
-
<Label className="font-bold flex items-center gap-2"><
|
| 97 |
<Input
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
required
|
| 102 |
className="rounded-xl h-12 bg-muted/50 border-none"
|
| 103 |
-
placeholder="18"
|
| 104 |
/>
|
| 105 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
<div className="space-y-2">
|
| 107 |
-
<Label className="font-bold flex items-center gap-2"><
|
| 108 |
<Input
|
| 109 |
-
value={
|
| 110 |
-
onChange={e =>
|
| 111 |
-
placeholder={t.
|
| 112 |
-
required
|
| 113 |
className="rounded-xl h-12 bg-muted/50 border-none"
|
| 114 |
/>
|
| 115 |
</div>
|
| 116 |
</CardContent>
|
| 117 |
<CardFooter className="p-8 pt-0">
|
| 118 |
-
<Button type="submit" className="w-full h-
|
| 119 |
{isSigningIn ? <Loader2 className="animate-spin" /> : t.loginAction}
|
| 120 |
</Button>
|
| 121 |
</CardFooter>
|
|
@@ -168,7 +235,7 @@ export default function LoginPage() {
|
|
| 168 |
</TabsContent>
|
| 169 |
|
| 170 |
<TabsContent value="username">
|
| 171 |
-
<Card className="radiant-card border-none rounded-[
|
| 172 |
<form onSubmit={handleUsernameLogin}>
|
| 173 |
<CardHeader className="pb-2">
|
| 174 |
<CardTitle className="text-xl font-bold">{t.manual}</CardTitle>
|
|
@@ -215,6 +282,16 @@ export default function LoginPage() {
|
|
| 215 |
/>
|
| 216 |
</div>
|
| 217 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 218 |
</CardContent>
|
| 219 |
<CardFooter className="p-6 pt-0">
|
| 220 |
<Button type="submit" className="w-full h-14 rounded-2xl font-black text-lg shadow-xl shadow-primary/20" disabled={isSigningIn}>
|
|
@@ -230,4 +307,4 @@ export default function LoginPage() {
|
|
| 230 |
</div>
|
| 231 |
</div>
|
| 232 |
);
|
| 233 |
-
}
|
|
|
|
| 1 |
+
|
| 2 |
'use client';
|
| 3 |
|
| 4 |
import { Button } from "@/components/ui/button";
|
| 5 |
import { Header } from "@/components/header";
|
| 6 |
+
import { Loader2, LogIn, MessageSquare, Chrome, UserPlus, MapPin, Calendar, Briefcase, Heart, BookOpen } from "lucide-react";
|
| 7 |
import { useAuth } from "@/contexts/auth-context";
|
| 8 |
import { useEffect, useState } from "react";
|
| 9 |
import { useRouter } from "next/navigation";
|
| 10 |
import { Input } from "@/components/ui/input";
|
| 11 |
import { Label } from "@/components/ui/label";
|
| 12 |
+
import { Textarea } from "@/components/ui/textarea";
|
| 13 |
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
| 14 |
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
| 15 |
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
| 16 |
import { useToast } from "@/hooks/use-toast";
|
|
|
|
| 20 |
const { user, userData, loading: authLoading, signInWithGoogle, signInWithUsername, completeProfile } = useAuth();
|
| 21 |
const { t } = useLanguage();
|
| 22 |
const [isSigningIn, setIsSigningIn] = useState(false);
|
| 23 |
+
|
| 24 |
+
// Basic Fields
|
| 25 |
const [username, setUsername] = useState('');
|
| 26 |
const [password, setPassword] = useState('');
|
| 27 |
const [age, setAge] = useState('');
|
| 28 |
const [location, setLocation] = useState('');
|
| 29 |
+
|
| 30 |
+
// Extended Fields for Personalization
|
| 31 |
+
const [bio, setBio] = useState('');
|
| 32 |
+
const [gender, setGender] = useState('');
|
| 33 |
+
const [occupation, setOccupation] = useState('');
|
| 34 |
+
const [interests, setInterests] = useState('');
|
| 35 |
+
|
| 36 |
const [showCompleteProfile, setShowCompleteProfile] = useState(false);
|
| 37 |
|
| 38 |
const router = useRouter();
|
|
|
|
| 64 |
}
|
| 65 |
setIsSigningIn(true);
|
| 66 |
try {
|
| 67 |
+
await signInWithUsername(username, password, parseInt(age), location, {
|
| 68 |
+
bio, gender, occupation, interests
|
| 69 |
+
});
|
| 70 |
} catch (error) {
|
| 71 |
console.error(error);
|
| 72 |
} finally {
|
|
|
|
| 82 |
return;
|
| 83 |
}
|
| 84 |
setIsSigningIn(true);
|
| 85 |
+
await completeProfile({
|
| 86 |
+
age: parseInt(age),
|
| 87 |
+
location,
|
| 88 |
+
bio,
|
| 89 |
+
gender,
|
| 90 |
+
occupation,
|
| 91 |
+
interests
|
| 92 |
+
});
|
| 93 |
setIsSigningIn(false);
|
| 94 |
};
|
| 95 |
|
|
|
|
| 105 |
return (
|
| 106 |
<div className="flex h-screen w-full flex-col bg-muted/30">
|
| 107 |
<Header title={t.loginTitle} />
|
| 108 |
+
<div className="flex flex-1 items-center justify-center p-4 pt-20">
|
| 109 |
+
<Card className="w-full max-w-lg radiant-card rounded-[2.5rem] border-none shadow-2xl overflow-hidden overflow-y-auto max-h-[85vh] scrollbar-hide">
|
| 110 |
<CardHeader className="bg-primary text-primary-foreground p-8">
|
| 111 |
+
<CardTitle className="text-3xl font-black">{t.completeProfile}</CardTitle>
|
| 112 |
+
<CardDescription className="text-primary-foreground/90 font-bold">{t.chooseLogin}</CardDescription>
|
| 113 |
</CardHeader>
|
| 114 |
<form onSubmit={handleCompleteProfile}>
|
| 115 |
<CardContent className="space-y-6 p-8">
|
| 116 |
+
<div className="grid grid-cols-2 gap-4">
|
| 117 |
+
<div className="space-y-2">
|
| 118 |
+
<Label className="font-bold flex items-center gap-2"><Calendar className="h-4 w-4 text-primary" /> {t.age}</Label>
|
| 119 |
+
<Input
|
| 120 |
+
type="number"
|
| 121 |
+
value={age}
|
| 122 |
+
onChange={e => setAge(e.target.value)}
|
| 123 |
+
required
|
| 124 |
+
className="rounded-xl h-12 bg-muted/50 border-none"
|
| 125 |
+
placeholder="18"
|
| 126 |
+
/>
|
| 127 |
+
</div>
|
| 128 |
+
<div className="space-y-2">
|
| 129 |
+
<Label className="font-bold flex items-center gap-2"><MapPin className="h-4 w-4 text-primary" /> {t.location}</Label>
|
| 130 |
+
<Input
|
| 131 |
+
value={location}
|
| 132 |
+
onChange={e => setLocation(e.target.value)}
|
| 133 |
+
placeholder={t.location}
|
| 134 |
+
required
|
| 135 |
+
className="rounded-xl h-12 bg-muted/50 border-none"
|
| 136 |
+
/>
|
| 137 |
+
</div>
|
| 138 |
+
</div>
|
| 139 |
+
|
| 140 |
<div className="space-y-2">
|
| 141 |
+
<Label className="font-bold flex items-center gap-2"><Briefcase className="h-4 w-4 text-primary" /> {t.occupation}</Label>
|
| 142 |
<Input
|
| 143 |
+
value={occupation}
|
| 144 |
+
onChange={e => setOccupation(e.target.value)}
|
| 145 |
+
placeholder={t.occupation}
|
|
|
|
| 146 |
className="rounded-xl h-12 bg-muted/50 border-none"
|
|
|
|
| 147 |
/>
|
| 148 |
</div>
|
| 149 |
+
|
| 150 |
+
<div className="space-y-2">
|
| 151 |
+
<Label className="font-bold">{t.gender}</Label>
|
| 152 |
+
<Select value={gender} onValueChange={setGender}>
|
| 153 |
+
<SelectTrigger className="rounded-xl h-12 bg-muted/50 border-none">
|
| 154 |
+
<SelectValue placeholder={t.gender} />
|
| 155 |
+
</SelectTrigger>
|
| 156 |
+
<SelectContent className="rounded-xl">
|
| 157 |
+
<SelectItem value="male">{t.male}</SelectItem>
|
| 158 |
+
<SelectItem value="female">{t.female}</SelectItem>
|
| 159 |
+
<SelectItem value="other">{t.other}</SelectItem>
|
| 160 |
+
</SelectContent>
|
| 161 |
+
</Select>
|
| 162 |
+
</div>
|
| 163 |
+
|
| 164 |
+
<div className="space-y-2">
|
| 165 |
+
<Label className="font-bold flex items-center gap-2"><BookOpen className="h-4 w-4 text-primary" /> {t.bio}</Label>
|
| 166 |
+
<Textarea
|
| 167 |
+
value={bio}
|
| 168 |
+
onChange={e => setBio(e.target.value)}
|
| 169 |
+
placeholder={t.bioPlaceholder}
|
| 170 |
+
className="rounded-xl bg-muted/50 border-none min-h-[80px]"
|
| 171 |
+
/>
|
| 172 |
+
</div>
|
| 173 |
+
|
| 174 |
<div className="space-y-2">
|
| 175 |
+
<Label className="font-bold flex items-center gap-2"><Heart className="h-4 w-4 text-primary" /> {t.interests}</Label>
|
| 176 |
<Input
|
| 177 |
+
value={interests}
|
| 178 |
+
onChange={e => setInterests(e.target.value)}
|
| 179 |
+
placeholder={t.interestsPlaceholder}
|
|
|
|
| 180 |
className="rounded-xl h-12 bg-muted/50 border-none"
|
| 181 |
/>
|
| 182 |
</div>
|
| 183 |
</CardContent>
|
| 184 |
<CardFooter className="p-8 pt-0">
|
| 185 |
+
<Button type="submit" className="w-full h-16 rounded-2xl font-black text-xl shadow-xl shadow-primary/20" disabled={isSigningIn}>
|
| 186 |
{isSigningIn ? <Loader2 className="animate-spin" /> : t.loginAction}
|
| 187 |
</Button>
|
| 188 |
</CardFooter>
|
|
|
|
| 235 |
</TabsContent>
|
| 236 |
|
| 237 |
<TabsContent value="username">
|
| 238 |
+
<Card className="radiant-card border-none rounded-[2.5rem] shadow-2xl max-h-[60vh] overflow-y-auto scrollbar-hide">
|
| 239 |
<form onSubmit={handleUsernameLogin}>
|
| 240 |
<CardHeader className="pb-2">
|
| 241 |
<CardTitle className="text-xl font-bold">{t.manual}</CardTitle>
|
|
|
|
| 282 |
/>
|
| 283 |
</div>
|
| 284 |
</div>
|
| 285 |
+
|
| 286 |
+
<div className="space-y-2">
|
| 287 |
+
<Label className="font-bold">{t.occupation}</Label>
|
| 288 |
+
<Input
|
| 289 |
+
value={occupation}
|
| 290 |
+
onChange={e => setOccupation(e.target.value)}
|
| 291 |
+
placeholder={t.occupation}
|
| 292 |
+
className="rounded-xl h-12 bg-muted/50 border-none"
|
| 293 |
+
/>
|
| 294 |
+
</div>
|
| 295 |
</CardContent>
|
| 296 |
<CardFooter className="p-6 pt-0">
|
| 297 |
<Button type="submit" className="w-full h-14 rounded-2xl font-black text-lg shadow-xl shadow-primary/20" disabled={isSigningIn}>
|
|
|
|
| 307 |
</div>
|
| 308 |
</div>
|
| 309 |
);
|
| 310 |
+
}
|
src/app/profile/page.tsx
CHANGED
|
@@ -5,12 +5,14 @@ import { useState, useEffect } from 'react';
|
|
| 5 |
import { useAuth } from '@/contexts/auth-context';
|
| 6 |
import { useRouter } from 'next/navigation';
|
| 7 |
import { Header } from '@/components/header';
|
| 8 |
-
import { Card, CardContent,
|
| 9 |
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
| 10 |
import { Button } from '@/components/ui/button';
|
| 11 |
import { Input } from '@/components/ui/input';
|
| 12 |
import { Label } from '@/components/ui/label';
|
| 13 |
-
import {
|
|
|
|
|
|
|
| 14 |
import { useToast } from '@/hooks/use-toast';
|
| 15 |
import { useLanguage } from '@/contexts/language-context';
|
| 16 |
import {
|
|
@@ -34,6 +36,10 @@ export default function ProfilePage() {
|
|
| 34 |
const [displayName, setDisplayName] = useState('');
|
| 35 |
const [age, setAge] = useState('');
|
| 36 |
const [location, setLocation] = useState('');
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
const [isSaving, setIsSaving] = useState(false);
|
| 38 |
const [isDeleting, setIsDeleting] = useState(false);
|
| 39 |
|
|
@@ -47,6 +53,10 @@ export default function ProfilePage() {
|
|
| 47 |
if (userData) {
|
| 48 |
setAge(userData.age?.toString() || '');
|
| 49 |
setLocation(userData.location || '');
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
}
|
| 51 |
}, [user, userData, loading, router]);
|
| 52 |
|
|
@@ -59,8 +69,12 @@ export default function ProfilePage() {
|
|
| 59 |
setIsSaving(true);
|
| 60 |
await updateUserProfile({
|
| 61 |
displayName: displayName.trim(),
|
| 62 |
-
age: parseInt(age),
|
| 63 |
-
location: location.trim()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
});
|
| 65 |
setIsSaving(false);
|
| 66 |
};
|
|
@@ -88,77 +102,124 @@ export default function ProfilePage() {
|
|
| 88 |
return (
|
| 89 |
<div className="flex h-screen w-full flex-col bg-background">
|
| 90 |
<Header title={t.profileTitle} />
|
| 91 |
-
<main className="flex-1 overflow-y-auto bg-muted/40 p-4 md:p-8">
|
| 92 |
<div className="mx-auto max-w-2xl space-y-6">
|
| 93 |
-
<Card className="border-border/50 shadow-sm">
|
| 94 |
-
<CardHeader>
|
| 95 |
-
<CardTitle>
|
|
|
|
|
|
|
|
|
|
| 96 |
</CardHeader>
|
| 97 |
-
<CardContent className="space-y-6">
|
| 98 |
<div className="flex items-center gap-4">
|
| 99 |
-
<Avatar className="h-
|
| 100 |
<AvatarImage src={user.photoURL!} alt={user.displayName!} />
|
| 101 |
-
<AvatarFallback className="text-
|
| 102 |
{user.displayName?.charAt(0)}
|
| 103 |
</AvatarFallback>
|
| 104 |
</Avatar>
|
| 105 |
<div>
|
| 106 |
-
<h3 className="text-
|
| 107 |
-
<p className="text-sm text-muted-foreground">{user.email || 'Manual Account'}</p>
|
| 108 |
</div>
|
| 109 |
</div>
|
| 110 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
<div className="space-y-2">
|
| 112 |
-
<Label htmlFor="
|
| 113 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
</div>
|
| 115 |
-
|
|
|
|
| 116 |
<div className="space-y-2">
|
| 117 |
-
<Label htmlFor="
|
| 118 |
-
<Input id="
|
| 119 |
</div>
|
| 120 |
<div className="space-y-2">
|
| 121 |
-
<Label htmlFor="location">{t.location}</Label>
|
| 122 |
-
<Input id="location" value={location} onChange={(e) => setLocation(e.target.value)} />
|
| 123 |
</div>
|
| 124 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
</div>
|
| 126 |
</CardContent>
|
| 127 |
-
<CardFooter className="border-t px-6 py-
|
| 128 |
-
<Button onClick={handleSave} disabled={isSaving} className="rounded-xl px-
|
| 129 |
{isSaving && <Loader2 className="mx-2 h-4 w-4 animate-spin" />}
|
| 130 |
{t.saveChanges}
|
| 131 |
</Button>
|
| 132 |
</CardFooter>
|
| 133 |
</Card>
|
| 134 |
|
| 135 |
-
<Card className="border-destructive/20 shadow-sm overflow-hidden">
|
| 136 |
<CardHeader className="bg-destructive/5">
|
| 137 |
<div className="flex items-center gap-2 text-destructive">
|
| 138 |
<AlertTriangle className="h-5 w-5" />
|
| 139 |
-
<CardTitle className="text-lg">{t.dangerZone}</CardTitle>
|
| 140 |
</div>
|
| 141 |
</CardHeader>
|
| 142 |
<CardContent className="pt-6">
|
| 143 |
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
| 144 |
<div className="space-y-1">
|
| 145 |
-
<h4 className="font-bold">{t.deleteAccount}</h4>
|
| 146 |
<p className="text-sm text-muted-foreground">{t.deleteConfirmDesc}</p>
|
| 147 |
</div>
|
| 148 |
<AlertDialog>
|
| 149 |
<AlertDialogTrigger asChild>
|
| 150 |
-
<Button variant="destructive" className="rounded-xl flex items-center gap-2">
|
| 151 |
<Trash2 className="h-4 w-4" /> {t.deleteAccount}
|
| 152 |
</Button>
|
| 153 |
</AlertDialogTrigger>
|
| 154 |
-
<AlertDialogContent className="rounded-
|
| 155 |
<AlertDialogHeader>
|
| 156 |
-
<AlertDialogTitle>{t.deleteConfirmTitle}</AlertDialogTitle>
|
| 157 |
-
<AlertDialogDescription>{t.deleteConfirmDesc}</AlertDialogDescription>
|
| 158 |
</AlertDialogHeader>
|
| 159 |
-
<AlertDialogFooter>
|
| 160 |
-
<AlertDialogCancel>{t.cancel}</AlertDialogCancel>
|
| 161 |
-
<AlertDialogAction onClick={handleDeleteAccount} className="bg-destructive hover:bg-destructive/90" disabled={isDeleting}>
|
| 162 |
{t.confirmDelete}
|
| 163 |
</AlertDialogAction>
|
| 164 |
</AlertDialogFooter>
|
|
|
|
| 5 |
import { useAuth } from '@/contexts/auth-context';
|
| 6 |
import { useRouter } from 'next/navigation';
|
| 7 |
import { Header } from '@/components/header';
|
| 8 |
+
import { Card, CardContent, CardHeader, CardTitle, CardFooter } from '@/components/ui/card';
|
| 9 |
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
| 10 |
import { Button } from '@/components/ui/button';
|
| 11 |
import { Input } from '@/components/ui/input';
|
| 12 |
import { Label } from '@/components/ui/label';
|
| 13 |
+
import { Textarea } from '@/components/ui/textarea';
|
| 14 |
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
| 15 |
+
import { Loader2, Trash2, AlertTriangle, User, Briefcase, Heart, BookOpen } from 'lucide-react';
|
| 16 |
import { useToast } from '@/hooks/use-toast';
|
| 17 |
import { useLanguage } from '@/contexts/language-context';
|
| 18 |
import {
|
|
|
|
| 36 |
const [displayName, setDisplayName] = useState('');
|
| 37 |
const [age, setAge] = useState('');
|
| 38 |
const [location, setLocation] = useState('');
|
| 39 |
+
const [bio, setBio] = useState('');
|
| 40 |
+
const [gender, setGender] = useState('');
|
| 41 |
+
const [occupation, setOccupation] = useState('');
|
| 42 |
+
const [interests, setInterests] = useState('');
|
| 43 |
const [isSaving, setIsSaving] = useState(false);
|
| 44 |
const [isDeleting, setIsDeleting] = useState(false);
|
| 45 |
|
|
|
|
| 53 |
if (userData) {
|
| 54 |
setAge(userData.age?.toString() || '');
|
| 55 |
setLocation(userData.location || '');
|
| 56 |
+
setBio(userData.bio || '');
|
| 57 |
+
setGender(userData.gender || '');
|
| 58 |
+
setOccupation(userData.occupation || '');
|
| 59 |
+
setInterests(userData.interests || '');
|
| 60 |
}
|
| 61 |
}, [user, userData, loading, router]);
|
| 62 |
|
|
|
|
| 69 |
setIsSaving(true);
|
| 70 |
await updateUserProfile({
|
| 71 |
displayName: displayName.trim(),
|
| 72 |
+
age: parseInt(age) || 0,
|
| 73 |
+
location: location.trim(),
|
| 74 |
+
bio: bio.trim(),
|
| 75 |
+
gender,
|
| 76 |
+
occupation: occupation.trim(),
|
| 77 |
+
interests: interests.trim()
|
| 78 |
});
|
| 79 |
setIsSaving(false);
|
| 80 |
};
|
|
|
|
| 102 |
return (
|
| 103 |
<div className="flex h-screen w-full flex-col bg-background">
|
| 104 |
<Header title={t.profileTitle} />
|
| 105 |
+
<main className="flex-1 overflow-y-auto bg-muted/40 p-4 md:p-8 pt-20">
|
| 106 |
<div className="mx-auto max-w-2xl space-y-6">
|
| 107 |
+
<Card className="border-border/50 shadow-sm rounded-[2rem] overflow-hidden">
|
| 108 |
+
<CardHeader className="bg-primary/5 border-b border-primary/10">
|
| 109 |
+
<CardTitle className="flex items-center gap-2">
|
| 110 |
+
<User className="h-5 w-5 text-primary" />
|
| 111 |
+
{t.settings}
|
| 112 |
+
</CardTitle>
|
| 113 |
</CardHeader>
|
| 114 |
+
<CardContent className="space-y-6 pt-6">
|
| 115 |
<div className="flex items-center gap-4">
|
| 116 |
+
<Avatar className="h-24 w-24 border-4 border-primary/10 shadow-lg">
|
| 117 |
<AvatarImage src={user.photoURL!} alt={user.displayName!} />
|
| 118 |
+
<AvatarFallback className="text-2xl bg-primary/5 text-primary font-bold">
|
| 119 |
{user.displayName?.charAt(0)}
|
| 120 |
</AvatarFallback>
|
| 121 |
</Avatar>
|
| 122 |
<div>
|
| 123 |
+
<h3 className="text-2xl font-black tracking-tight">{user.displayName}</h3>
|
| 124 |
+
<p className="text-sm text-muted-foreground font-medium">{user.email || 'Manual Account'}</p>
|
| 125 |
</div>
|
| 126 |
</div>
|
| 127 |
+
|
| 128 |
+
<div className="space-y-5">
|
| 129 |
+
<div className="space-y-2">
|
| 130 |
+
<Label htmlFor="displayName" className="font-bold">{t.username}</Label>
|
| 131 |
+
<Input id="displayName" value={displayName} onChange={(e) => setDisplayName(e.target.value)} className="rounded-xl bg-background/50" />
|
| 132 |
+
</div>
|
| 133 |
+
|
| 134 |
<div className="space-y-2">
|
| 135 |
+
<Label htmlFor="bio" className="font-bold flex items-center gap-2"><BookOpen className="h-4 w-4" /> {t.bio}</Label>
|
| 136 |
+
<Textarea
|
| 137 |
+
id="bio"
|
| 138 |
+
placeholder={t.bioPlaceholder}
|
| 139 |
+
value={bio}
|
| 140 |
+
onChange={(e) => setBio(e.target.value)}
|
| 141 |
+
className="rounded-xl bg-background/50 min-h-[100px]"
|
| 142 |
+
/>
|
| 143 |
+
</div>
|
| 144 |
+
|
| 145 |
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
| 146 |
+
<div className="space-y-2">
|
| 147 |
+
<Label htmlFor="age" className="font-bold">{t.age}</Label>
|
| 148 |
+
<Input id="age" type="number" value={age} onChange={(e) => setAge(e.target.value)} className="rounded-xl bg-background/50" />
|
| 149 |
+
</div>
|
| 150 |
+
<div className="space-y-2">
|
| 151 |
+
<Label htmlFor="gender" className="font-bold">{t.gender}</Label>
|
| 152 |
+
<Select value={gender} onValueChange={setGender}>
|
| 153 |
+
<SelectTrigger className="rounded-xl bg-background/50">
|
| 154 |
+
<SelectValue placeholder={t.gender} />
|
| 155 |
+
</SelectTrigger>
|
| 156 |
+
<SelectContent className="rounded-xl">
|
| 157 |
+
<SelectItem value="male">{t.male}</SelectItem>
|
| 158 |
+
<SelectItem value="female">{t.female}</SelectItem>
|
| 159 |
+
<SelectItem value="other">{t.other}</SelectItem>
|
| 160 |
+
</SelectContent>
|
| 161 |
+
</Select>
|
| 162 |
+
</div>
|
| 163 |
</div>
|
| 164 |
+
|
| 165 |
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
| 166 |
<div className="space-y-2">
|
| 167 |
+
<Label htmlFor="occupation" className="font-bold flex items-center gap-2"><Briefcase className="h-4 w-4" /> {t.occupation}</Label>
|
| 168 |
+
<Input id="occupation" value={occupation} onChange={(e) => setOccupation(e.target.value)} className="rounded-xl bg-background/50" />
|
| 169 |
</div>
|
| 170 |
<div className="space-y-2">
|
| 171 |
+
<Label htmlFor="location" className="font-bold">{t.location}</Label>
|
| 172 |
+
<Input id="location" value={location} onChange={(e) => setLocation(e.target.value)} className="rounded-xl bg-background/50" />
|
| 173 |
</div>
|
| 174 |
</div>
|
| 175 |
+
|
| 176 |
+
<div className="space-y-2">
|
| 177 |
+
<Label htmlFor="interests" className="font-bold flex items-center gap-2"><Heart className="h-4 w-4" /> {t.interests}</Label>
|
| 178 |
+
<Input
|
| 179 |
+
id="interests"
|
| 180 |
+
placeholder={t.interestsPlaceholder}
|
| 181 |
+
value={interests}
|
| 182 |
+
onChange={(e) => setInterests(e.target.value)}
|
| 183 |
+
className="rounded-xl bg-background/50"
|
| 184 |
+
/>
|
| 185 |
+
</div>
|
| 186 |
</div>
|
| 187 |
</CardContent>
|
| 188 |
+
<CardFooter className="border-t px-6 py-6 bg-muted/20">
|
| 189 |
+
<Button onClick={handleSave} disabled={isSaving} className="w-full md:w-auto rounded-xl px-12 font-bold h-12 shadow-lg shadow-primary/20">
|
| 190 |
{isSaving && <Loader2 className="mx-2 h-4 w-4 animate-spin" />}
|
| 191 |
{t.saveChanges}
|
| 192 |
</Button>
|
| 193 |
</CardFooter>
|
| 194 |
</Card>
|
| 195 |
|
| 196 |
+
<Card className="border-destructive/20 shadow-sm rounded-[2rem] overflow-hidden">
|
| 197 |
<CardHeader className="bg-destructive/5">
|
| 198 |
<div className="flex items-center gap-2 text-destructive">
|
| 199 |
<AlertTriangle className="h-5 w-5" />
|
| 200 |
+
<CardTitle className="text-lg font-bold">{t.dangerZone}</CardTitle>
|
| 201 |
</div>
|
| 202 |
</CardHeader>
|
| 203 |
<CardContent className="pt-6">
|
| 204 |
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
| 205 |
<div className="space-y-1">
|
| 206 |
+
<h4 className="font-bold text-destructive">{t.deleteAccount}</h4>
|
| 207 |
<p className="text-sm text-muted-foreground">{t.deleteConfirmDesc}</p>
|
| 208 |
</div>
|
| 209 |
<AlertDialog>
|
| 210 |
<AlertDialogTrigger asChild>
|
| 211 |
+
<Button variant="destructive" className="rounded-xl flex items-center gap-2 px-6 h-12 font-bold">
|
| 212 |
<Trash2 className="h-4 w-4" /> {t.deleteAccount}
|
| 213 |
</Button>
|
| 214 |
</AlertDialogTrigger>
|
| 215 |
+
<AlertDialogContent className="rounded-3xl border-none radiant-glass p-8">
|
| 216 |
<AlertDialogHeader>
|
| 217 |
+
<AlertDialogTitle className="text-2xl font-black">{t.deleteConfirmTitle}</AlertDialogTitle>
|
| 218 |
+
<AlertDialogDescription className="text-base font-medium">{t.deleteConfirmDesc}</AlertDialogDescription>
|
| 219 |
</AlertDialogHeader>
|
| 220 |
+
<AlertDialogFooter className="mt-6 gap-3">
|
| 221 |
+
<AlertDialogCancel className="rounded-xl h-12 font-bold">{t.cancel}</AlertDialogCancel>
|
| 222 |
+
<AlertDialogAction onClick={handleDeleteAccount} className="bg-destructive hover:bg-destructive/90 rounded-xl h-12 font-bold px-8" disabled={isDeleting}>
|
| 223 |
{t.confirmDelete}
|
| 224 |
</AlertDialogAction>
|
| 225 |
</AlertDialogFooter>
|
src/contexts/auth-context.tsx
CHANGED
|
@@ -7,19 +7,16 @@ import { auth, database } from '@/firebase/client';
|
|
| 7 |
import {
|
| 8 |
onAuthStateChanged,
|
| 9 |
signInWithPopup,
|
| 10 |
-
signInWithRedirect,
|
| 11 |
-
getRedirectResult,
|
| 12 |
GoogleAuthProvider,
|
| 13 |
signOut as firebaseSignOut,
|
| 14 |
updateProfile,
|
| 15 |
-
signInWithCredential,
|
| 16 |
signInWithEmailAndPassword,
|
| 17 |
createUserWithEmailAndPassword,
|
| 18 |
deleteUser
|
| 19 |
} from 'firebase/auth';
|
| 20 |
import { ref, set, get, update, remove } from 'firebase/database';
|
| 21 |
import { useToast } from '@/hooks/use-toast';
|
| 22 |
-
import { Loader2
|
| 23 |
|
| 24 |
export interface UserMetadata {
|
| 25 |
age: number;
|
|
@@ -28,6 +25,10 @@ export interface UserMetadata {
|
|
| 28 |
displayName: string;
|
| 29 |
email: string;
|
| 30 |
photoURL: string;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
}
|
| 32 |
|
| 33 |
interface AuthContextType {
|
|
@@ -35,17 +36,15 @@ interface AuthContextType {
|
|
| 35 |
userData: UserMetadata | null;
|
| 36 |
loading: boolean;
|
| 37 |
signInWithGoogle: () => Promise<void>;
|
| 38 |
-
signInWithUsername: (username: string, pass: string, age: number, location: string) => Promise<void>;
|
| 39 |
signOut: () => Promise<void>;
|
| 40 |
-
updateUserProfile: (data:
|
| 41 |
deleteUserAccount: () => Promise<void>;
|
| 42 |
-
completeProfile: (
|
| 43 |
}
|
| 44 |
|
| 45 |
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
| 46 |
|
| 47 |
-
const GOOGLE_CLIENT_ID = process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID;
|
| 48 |
-
|
| 49 |
export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
| 50 |
const [user, setUser] = useState<User | null>(null);
|
| 51 |
const [userData, setUserData] = useState<UserMetadata | null>(null);
|
|
@@ -76,15 +75,19 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
|
| 76 |
return () => unsubscribe();
|
| 77 |
}, []);
|
| 78 |
|
| 79 |
-
const handleNewUser = async (user: User, data:
|
| 80 |
const userRef = ref(database, 'users/' + user.uid);
|
| 81 |
const payload: UserMetadata = {
|
| 82 |
displayName: data.customName || user.displayName || 'مستخدم جديد',
|
| 83 |
email: user.email || '',
|
| 84 |
photoURL: user.photoURL || `https://api.dicebear.com/7.x/avataaars/svg?seed=${user.uid}`,
|
| 85 |
createdAt: new Date().toISOString(),
|
| 86 |
-
age: data.age,
|
| 87 |
-
location: data.location
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
};
|
| 89 |
await set(userRef, payload);
|
| 90 |
if (data.customName) {
|
|
@@ -97,11 +100,9 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
|
| 97 |
const provider = new GoogleAuthProvider();
|
| 98 |
try {
|
| 99 |
const result = await signInWithPopup(auth, provider);
|
| 100 |
-
// Check if user already exists in DB
|
| 101 |
const userRef = ref(database, 'users/' + result.user.uid);
|
| 102 |
const snapshot = await get(userRef);
|
| 103 |
if (!snapshot.exists()) {
|
| 104 |
-
// We will need a second step in the UI for new Google users to fill age/location
|
| 105 |
toast({ title: "يرجى إكمال بياناتك للمتابعة" });
|
| 106 |
} else {
|
| 107 |
setUserData(snapshot.val());
|
|
@@ -113,16 +114,16 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
|
| 113 |
}
|
| 114 |
};
|
| 115 |
|
| 116 |
-
const signInWithUsername = async (username: string, pass: string, age: number, location: string) => {
|
| 117 |
const fakeEmail = `${username.toLowerCase().trim()}@proto.chat`;
|
| 118 |
try {
|
| 119 |
setLoading(true);
|
| 120 |
const result = await signInWithEmailAndPassword(auth, fakeEmail, pass);
|
| 121 |
await fetchUserData(result.user.uid);
|
| 122 |
} catch (error: any) {
|
| 123 |
-
if (error.code === 'auth/user-not-found' || error.code === 'auth/invalid-credential') {
|
| 124 |
const result = await createUserWithEmailAndPassword(auth, fakeEmail, pass);
|
| 125 |
-
await handleNewUser(result.user, { age, location, customName: username });
|
| 126 |
} else {
|
| 127 |
toast({ title: "خطأ في تسجيل الدخول", variant: "destructive" });
|
| 128 |
}
|
|
@@ -131,19 +132,20 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
|
| 131 |
}
|
| 132 |
};
|
| 133 |
|
| 134 |
-
const completeProfile = async (
|
| 135 |
if (!user) return;
|
| 136 |
const userRef = ref(database, 'users/' + user.uid);
|
|
|
|
| 137 |
const newData = {
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
displayName: user.displayName || 'مستخدم',
|
| 141 |
email: user.email || '',
|
| 142 |
photoURL: user.photoURL || `https://api.dicebear.com/7.x/avataaars/svg?seed=${user.uid}`,
|
| 143 |
-
createdAt: new Date().toISOString(),
|
| 144 |
};
|
| 145 |
await set(userRef, newData);
|
| 146 |
-
setUserData(newData);
|
| 147 |
};
|
| 148 |
|
| 149 |
const signOut = async () => {
|
|
@@ -152,9 +154,11 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
|
| 152 |
setUserData(null);
|
| 153 |
};
|
| 154 |
|
| 155 |
-
const updateUserProfile = async (data:
|
| 156 |
if (!user) return;
|
| 157 |
-
|
|
|
|
|
|
|
| 158 |
const userRef = ref(database, 'users/' + user.uid);
|
| 159 |
await update(userRef, data);
|
| 160 |
await fetchUserData(user.uid);
|
|
|
|
| 7 |
import {
|
| 8 |
onAuthStateChanged,
|
| 9 |
signInWithPopup,
|
|
|
|
|
|
|
| 10 |
GoogleAuthProvider,
|
| 11 |
signOut as firebaseSignOut,
|
| 12 |
updateProfile,
|
|
|
|
| 13 |
signInWithEmailAndPassword,
|
| 14 |
createUserWithEmailAndPassword,
|
| 15 |
deleteUser
|
| 16 |
} from 'firebase/auth';
|
| 17 |
import { ref, set, get, update, remove } from 'firebase/database';
|
| 18 |
import { useToast } from '@/hooks/use-toast';
|
| 19 |
+
import { Loader2 } from 'lucide-react';
|
| 20 |
|
| 21 |
export interface UserMetadata {
|
| 22 |
age: number;
|
|
|
|
| 25 |
displayName: string;
|
| 26 |
email: string;
|
| 27 |
photoURL: string;
|
| 28 |
+
bio?: string;
|
| 29 |
+
gender?: string;
|
| 30 |
+
occupation?: string;
|
| 31 |
+
interests?: string;
|
| 32 |
}
|
| 33 |
|
| 34 |
interface AuthContextType {
|
|
|
|
| 36 |
userData: UserMetadata | null;
|
| 37 |
loading: boolean;
|
| 38 |
signInWithGoogle: () => Promise<void>;
|
| 39 |
+
signInWithUsername: (username: string, pass: string, age: number, location: string, extra?: Partial<UserMetadata>) => Promise<void>;
|
| 40 |
signOut: () => Promise<void>;
|
| 41 |
+
updateUserProfile: (data: Partial<UserMetadata>) => Promise<void>;
|
| 42 |
deleteUserAccount: () => Promise<void>;
|
| 43 |
+
completeProfile: (data: Partial<UserMetadata>) => Promise<void>;
|
| 44 |
}
|
| 45 |
|
| 46 |
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
| 47 |
|
|
|
|
|
|
|
| 48 |
export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
| 49 |
const [user, setUser] = useState<User | null>(null);
|
| 50 |
const [userData, setUserData] = useState<UserMetadata | null>(null);
|
|
|
|
| 75 |
return () => unsubscribe();
|
| 76 |
}, []);
|
| 77 |
|
| 78 |
+
const handleNewUser = async (user: User, data: Partial<UserMetadata> & { customName?: string }) => {
|
| 79 |
const userRef = ref(database, 'users/' + user.uid);
|
| 80 |
const payload: UserMetadata = {
|
| 81 |
displayName: data.customName || user.displayName || 'مستخدم جديد',
|
| 82 |
email: user.email || '',
|
| 83 |
photoURL: user.photoURL || `https://api.dicebear.com/7.x/avataaars/svg?seed=${user.uid}`,
|
| 84 |
createdAt: new Date().toISOString(),
|
| 85 |
+
age: data.age || 18,
|
| 86 |
+
location: data.location || '',
|
| 87 |
+
bio: data.bio || '',
|
| 88 |
+
gender: data.gender || '',
|
| 89 |
+
occupation: data.occupation || '',
|
| 90 |
+
interests: data.interests || ''
|
| 91 |
};
|
| 92 |
await set(userRef, payload);
|
| 93 |
if (data.customName) {
|
|
|
|
| 100 |
const provider = new GoogleAuthProvider();
|
| 101 |
try {
|
| 102 |
const result = await signInWithPopup(auth, provider);
|
|
|
|
| 103 |
const userRef = ref(database, 'users/' + result.user.uid);
|
| 104 |
const snapshot = await get(userRef);
|
| 105 |
if (!snapshot.exists()) {
|
|
|
|
| 106 |
toast({ title: "يرجى إكمال بياناتك للمتابعة" });
|
| 107 |
} else {
|
| 108 |
setUserData(snapshot.val());
|
|
|
|
| 114 |
}
|
| 115 |
};
|
| 116 |
|
| 117 |
+
const signInWithUsername = async (username: string, pass: string, age: number, location: string, extra?: Partial<UserMetadata>) => {
|
| 118 |
const fakeEmail = `${username.toLowerCase().trim()}@proto.chat`;
|
| 119 |
try {
|
| 120 |
setLoading(true);
|
| 121 |
const result = await signInWithEmailAndPassword(auth, fakeEmail, pass);
|
| 122 |
await fetchUserData(result.user.uid);
|
| 123 |
} catch (error: any) {
|
| 124 |
+
if (error.code === 'auth/user-not-found' || error.code === 'auth/invalid-credential' || error.code === 'auth/invalid-email') {
|
| 125 |
const result = await createUserWithEmailAndPassword(auth, fakeEmail, pass);
|
| 126 |
+
await handleNewUser(result.user, { age, location, customName: username, ...extra });
|
| 127 |
} else {
|
| 128 |
toast({ title: "خطأ في تسجيل الدخول", variant: "destructive" });
|
| 129 |
}
|
|
|
|
| 132 |
}
|
| 133 |
};
|
| 134 |
|
| 135 |
+
const completeProfile = async (data: Partial<UserMetadata>) => {
|
| 136 |
if (!user) return;
|
| 137 |
const userRef = ref(database, 'users/' + user.uid);
|
| 138 |
+
const existingData = userData || {};
|
| 139 |
const newData = {
|
| 140 |
+
...existingData,
|
| 141 |
+
...data,
|
| 142 |
+
displayName: data.displayName || user.displayName || 'مستخدم',
|
| 143 |
email: user.email || '',
|
| 144 |
photoURL: user.photoURL || `https://api.dicebear.com/7.x/avataaars/svg?seed=${user.uid}`,
|
| 145 |
+
createdAt: existingData.createdAt || new Date().toISOString(),
|
| 146 |
};
|
| 147 |
await set(userRef, newData);
|
| 148 |
+
setUserData(newData as UserMetadata);
|
| 149 |
};
|
| 150 |
|
| 151 |
const signOut = async () => {
|
|
|
|
| 154 |
setUserData(null);
|
| 155 |
};
|
| 156 |
|
| 157 |
+
const updateUserProfile = async (data: Partial<UserMetadata>) => {
|
| 158 |
if (!user) return;
|
| 159 |
+
if (data.displayName) {
|
| 160 |
+
await updateProfile(user, { displayName: data.displayName });
|
| 161 |
+
}
|
| 162 |
const userRef = ref(database, 'users/' + user.uid);
|
| 163 |
await update(userRef, data);
|
| 164 |
await fetchUserData(user.uid);
|
src/lib/translations.ts
CHANGED
|
@@ -71,6 +71,15 @@ export const translations = {
|
|
| 71 |
primaryEngine: 'الأساسي (Gemini)',
|
| 72 |
fallbackEngine: 'الاحتياطي (Groq)',
|
| 73 |
advancedEngine: 'المتقدم (OpenRouter)',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
},
|
| 75 |
en: {
|
| 76 |
appTitle: 'AvadoraWorld',
|
|
@@ -141,5 +150,14 @@ export const translations = {
|
|
| 141 |
primaryEngine: 'Primary (Gemini)',
|
| 142 |
fallbackEngine: 'Fallback (Groq)',
|
| 143 |
advancedEngine: 'Advanced (OpenRouter)',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
}
|
| 145 |
};
|
|
|
|
| 71 |
primaryEngine: 'الأساسي (Gemini)',
|
| 72 |
fallbackEngine: 'الاحتياطي (Groq)',
|
| 73 |
advancedEngine: 'المتقدم (OpenRouter)',
|
| 74 |
+
bio: 'نبذة شخصية',
|
| 75 |
+
gender: 'الجنس',
|
| 76 |
+
occupation: 'المهنة',
|
| 77 |
+
interests: 'الاهتمامات (افصل بينها بفاصلة)',
|
| 78 |
+
interestsPlaceholder: 'فلسفة، تقنية، فن، رياضة...',
|
| 79 |
+
male: 'ذكر',
|
| 80 |
+
female: 'أنثى',
|
| 81 |
+
other: 'آخر',
|
| 82 |
+
bioPlaceholder: 'أخبرنا عن فلسفتك في الحياة...',
|
| 83 |
},
|
| 84 |
en: {
|
| 85 |
appTitle: 'AvadoraWorld',
|
|
|
|
| 150 |
primaryEngine: 'Primary (Gemini)',
|
| 151 |
fallbackEngine: 'Fallback (Groq)',
|
| 152 |
advancedEngine: 'Advanced (OpenRouter)',
|
| 153 |
+
bio: 'Bio',
|
| 154 |
+
gender: 'Gender',
|
| 155 |
+
occupation: 'Occupation',
|
| 156 |
+
interests: 'Interests (comma separated)',
|
| 157 |
+
interestsPlaceholder: 'Philosophy, Tech, Art, Sports...',
|
| 158 |
+
male: 'Male',
|
| 159 |
+
female: 'Female',
|
| 160 |
+
other: 'Other',
|
| 161 |
+
bioPlaceholder: 'Tell us about your philosophy...',
|
| 162 |
}
|
| 163 |
};
|