looda3131 commited on
Commit
94bfa98
·
1 Parent(s): 672c6be

جيد اضف تسجيل الدخول ب google

Browse files
src/app/layout.tsx CHANGED
@@ -1,12 +1,12 @@
1
  "use client";
2
 
3
- import type { Metadata } from 'next';
4
  import { ThemeProvider } from '@/components/theme-provider';
5
  import { Toaster } from '@/components/ui/toaster';
6
- import { AIWorldProvider } from '@/contexts/ai-world-context'; // استيراد البروفايدر
 
7
  import './globals.css';
8
 
9
- // لا يمكن استخدام Metadata في ملف 'use client'، ولكن سنحتفظ بالهيكل العام
10
  // export const metadata: Metadata = {
11
  // title: 'ProtoChat',
12
  // description: 'An AI-powered chat application',
@@ -31,10 +31,12 @@ export default function RootLayout({
31
  </head>
32
  <body className="font-body antialiased">
33
  <ThemeProvider>
34
- <AIWorldProvider> {/* تغليف التطبيق بالكامل */}
35
- {children}
36
- <Toaster />
37
- </AIWorldProvider>
 
 
38
  </ThemeProvider>
39
  </body>
40
  </html>
 
1
  "use client";
2
 
 
3
  import { ThemeProvider } from '@/components/theme-provider';
4
  import { Toaster } from '@/components/ui/toaster';
5
+ import { AIWorldProvider } from '@/contexts/ai-world-context';
6
+ import { AuthProvider } from '@/contexts/auth-context';
7
  import './globals.css';
8
 
9
+ // No Metadata for 'use client' components
10
  // export const metadata: Metadata = {
11
  // title: 'ProtoChat',
12
  // description: 'An AI-powered chat application',
 
31
  </head>
32
  <body className="font-body antialiased">
33
  <ThemeProvider>
34
+ <AuthProvider>
35
+ <AIWorldProvider>
36
+ {children}
37
+ <Toaster />
38
+ </AIWorldProvider>
39
+ </AuthProvider>
40
  </ThemeProvider>
41
  </body>
42
  </html>
src/app/login/page.tsx CHANGED
@@ -2,68 +2,63 @@
2
 
3
  import { Button } from "@/components/ui/button";
4
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
5
- import { Input } from "@/components/ui/input";
6
- import { Label } from "@/components/ui/label";
7
  import { Header } from "@/components/header";
8
- import { useToast } from "@/hooks/use-toast";
9
- import Link from "next/link";
10
- import { LogIn } from "lucide-react";
 
11
 
12
  export default function LoginPage() {
13
- const { toast } = useToast();
 
 
14
 
15
- const handleLogin = (e: React.FormEvent) => {
16
- e.preventDefault();
17
- toast({
18
- title: "قريباً",
19
- description: "ميزة تسجيل الدخول قيد التطوير حاليًا.",
20
- });
 
 
 
 
 
21
  };
22
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  return (
24
  <div className="flex h-screen w-full flex-col bg-background">
25
  <Header title="تسجيل الدخول" />
26
  <div className="flex flex-1 items-center justify-center bg-muted/40">
27
  <Card className="mx-auto max-w-sm">
28
  <CardHeader>
29
- <CardTitle className="text-2xl">تسجيل الدخول</CardTitle>
30
  <CardDescription>
31
- أدخل بريدك الإلكتروني أدناه لتسجيل الدخول إلى حسابك
32
  </CardDescription>
33
  </CardHeader>
34
  <CardContent>
35
- <form onSubmit={handleLogin} className="grid gap-4">
36
- <div className="grid gap-2">
37
- <Label htmlFor="email">البريد الإلكتروني</Label>
38
- <Input
39
- id="email"
40
- type="email"
41
- placeholder="m@example.com"
42
- required
43
- />
44
- </div>
45
- <div className="grid gap-2">
46
- <div className="flex items-center">
47
- <Label htmlFor="password">كلمة المرور</Label>
48
- <Link href="#" className="mr-auto inline-block text-sm underline">
49
- نسيت كلمة المرور؟
50
- </Link>
51
- </div>
52
- <Input id="password" type="password" required />
53
- </div>
54
- <Button type="submit" className="w-full">
55
- <LogIn className="ml-2 h-4 w-4" />
56
- تسجيل الدخول
57
- </Button>
58
- <Button variant="outline" className="w-full">
59
  تسجيل الدخول باستخدام جوجل
60
  </Button>
61
- </form>
62
- <div className="mt-4 text-center text-sm">
63
- ليس لديك حساب؟{" "}
64
- <Link href="#" className="underline">
65
- إنشاء حساب
66
- </Link>
67
  </div>
68
  </CardContent>
69
  </Card>
 
2
 
3
  import { Button } from "@/components/ui/button";
4
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
 
 
5
  import { Header } from "@/components/header";
6
+ import { Loader2 } from "lucide-react";
7
+ import { useAuth } from "@/contexts/auth-context";
8
+ import { useEffect, useState } from "react";
9
+ import { useRouter } from "next/navigation";
10
 
11
  export default function LoginPage() {
12
+ const { user, loading, signInWithGoogle } = useAuth();
13
+ const [isSigningIn, setIsSigningIn] = useState(false);
14
+ const router = useRouter();
15
 
16
+ useEffect(() => {
17
+ if (!loading && user) {
18
+ router.push('/');
19
+ }
20
+ }, [user, loading, router]);
21
+
22
+ const handleGoogleLogin = async () => {
23
+ setIsSigningIn(true);
24
+ await signInWithGoogle();
25
+ // The context handles navigation and errors, but we can set loading state
26
+ setIsSigningIn(false);
27
  };
28
 
29
+ if (loading || user) {
30
+ return (
31
+ <div className="flex h-screen w-full flex-col bg-background">
32
+ <Header title="تسجيل الدخول" />
33
+ <div className="flex flex-1 items-center justify-center bg-background">
34
+ <Loader2 className="h-16 w-16 animate-spin text-primary" />
35
+ <p className="mr-4">يتم توجيهك...</p>
36
+ </div>
37
+ </div>
38
+ );
39
+ }
40
+
41
  return (
42
  <div className="flex h-screen w-full flex-col bg-background">
43
  <Header title="تسجيل الدخول" />
44
  <div className="flex flex-1 items-center justify-center bg-muted/40">
45
  <Card className="mx-auto max-w-sm">
46
  <CardHeader>
47
+ <CardTitle className="text-2xl">أهلاً بك</CardTitle>
48
  <CardDescription>
49
+ اختر طريقة تسجيل الدخول للمتابعة
50
  </CardDescription>
51
  </CardHeader>
52
  <CardContent>
53
+ <div className="grid gap-4">
54
+ <Button variant="outline" className="w-full" onClick={handleGoogleLogin} disabled={isSigningIn}>
55
+ {isSigningIn ? (
56
+ <Loader2 className="ml-2 h-4 w-4 animate-spin" />
57
+ ) : (
58
+ <svg className="ml-2 h-4 w-4" aria-hidden="true" focusable="false" data-prefix="fab" data-icon="google" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 488 512"><path fill="currentColor" d="M488 261.8C488 403.3 381.5 512 244 512 111.8 512 0 400.2 0 261.8 0 123.8 111.8 12.8 244 12.8c70.3 0 129.8 27.8 175.4 72.4l-66 66C314.6 114.5 282.8 96 244 96c-85.6 0-154.5 68.9-154.5 153.8s68.9 153.8 154.5 153.8c99.3 0 131.2-82.3 134.8-120.9H244V254h244v7.8z"></path></svg>
59
+ )}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  تسجيل الدخول باستخدام جوجل
61
  </Button>
 
 
 
 
 
 
62
  </div>
63
  </CardContent>
64
  </Card>
src/app/page.tsx CHANGED
@@ -2,16 +2,18 @@
2
  import { Chat } from '@/components/chat/chat';
3
  import { useState, useContext } from 'react';
4
  import { Button } from '@/components/ui/button';
5
- import { Globe, MessageSquare, Users, Mic, LogIn } from 'lucide-react';
6
  import { AIWorldFeed } from '@/components/ai-world/ai-world-feed';
7
  import { AIWorldContext } from '@/contexts/ai-world-context';
8
  import Link from 'next/link';
 
9
 
10
  export default function Home() {
11
  const [showWorld, setShowWorld] = useState(false);
12
  // We can access the context here to show a loading spinner on the button
13
  // but for now, we'll keep it simple.
14
  const { isLoading } = useContext(AIWorldContext);
 
15
 
16
  if (showWorld) {
17
  // The AIWorldFeed component is self-contained and handles its own state
@@ -38,12 +40,14 @@ export default function Home() {
38
  محادثة صوتية
39
  </Link>
40
  </Button>
41
- <Button asChild variant="outline" size="lg" className="rounded-full">
42
- <Link href="/login">
43
- <LogIn className="ml-2 h-5 w-5" />
44
- تسجيل الدخول
45
- </Link>
46
- </Button>
 
 
47
  </div>
48
  </div>
49
  );
 
2
  import { Chat } from '@/components/chat/chat';
3
  import { useState, useContext } from 'react';
4
  import { Button } from '@/components/ui/button';
5
+ import { Globe, Users, Mic, LogIn } from 'lucide-react';
6
  import { AIWorldFeed } from '@/components/ai-world/ai-world-feed';
7
  import { AIWorldContext } from '@/contexts/ai-world-context';
8
  import Link from 'next/link';
9
+ import { useAuth } from '@/contexts/auth-context';
10
 
11
  export default function Home() {
12
  const [showWorld, setShowWorld] = useState(false);
13
  // We can access the context here to show a loading spinner on the button
14
  // but for now, we'll keep it simple.
15
  const { isLoading } = useContext(AIWorldContext);
16
+ const { user } = useAuth();
17
 
18
  if (showWorld) {
19
  // The AIWorldFeed component is self-contained and handles its own state
 
40
  محادثة صوتية
41
  </Link>
42
  </Button>
43
+ {!user && (
44
+ <Button asChild variant="outline" size="lg" className="rounded-full">
45
+ <Link href="/login">
46
+ <LogIn className="ml-2 h-5 w-5" />
47
+ تسجيل الدخول
48
+ </Link>
49
+ </Button>
50
+ )}
51
  </div>
52
  </div>
53
  );
src/components/header.tsx CHANGED
@@ -1,13 +1,27 @@
 
1
  import { ThemeToggle } from '@/components/theme-toggle';
2
- import { MessageSquare, Globe, Users, Mic, LogIn } from 'lucide-react';
3
  import Link from 'next/link';
4
  import { Button } from './ui/button';
 
 
 
 
 
 
 
 
 
 
 
5
 
6
  interface HeaderProps {
7
  title: string;
8
  }
9
 
10
  export function Header({ title }: HeaderProps) {
 
 
11
  const getIcon = () => {
12
  switch (title) {
13
  case 'بروتو شات':
@@ -33,7 +47,7 @@ export function Header({ title }: HeaderProps) {
33
  </div>
34
  <h1 className="text-xl font-semibold tracking-tight">{title}</h1>
35
  </div>
36
- <div className="flex items-center gap-2">
37
  <Button asChild variant="outline">
38
  <Link href="/">
39
  <MessageSquare className="ml-2 h-4 w-4" />
@@ -41,6 +55,33 @@ export function Header({ title }: HeaderProps) {
41
  </Link>
42
  </Button>
43
  <ThemeToggle />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  </div>
45
  </header>
46
  );
 
1
+ 'use client';
2
  import { ThemeToggle } from '@/components/theme-toggle';
3
+ import { MessageSquare, Globe, Users, Mic, LogIn, LogOut } from 'lucide-react';
4
  import Link from 'next/link';
5
  import { Button } from './ui/button';
6
+ import { useAuth } from '@/contexts/auth-context';
7
+ import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
8
+ import {
9
+ DropdownMenu,
10
+ DropdownMenuContent,
11
+ DropdownMenuItem,
12
+ DropdownMenuLabel,
13
+ DropdownMenuSeparator,
14
+ DropdownMenuTrigger,
15
+ } from "@/components/ui/dropdown-menu"
16
+
17
 
18
  interface HeaderProps {
19
  title: string;
20
  }
21
 
22
  export function Header({ title }: HeaderProps) {
23
+ const { user, signOut } = useAuth();
24
+
25
  const getIcon = () => {
26
  switch (title) {
27
  case 'بروتو شات':
 
47
  </div>
48
  <h1 className="text-xl font-semibold tracking-tight">{title}</h1>
49
  </div>
50
+ <div className="flex items-center gap-4">
51
  <Button asChild variant="outline">
52
  <Link href="/">
53
  <MessageSquare className="ml-2 h-4 w-4" />
 
55
  </Link>
56
  </Button>
57
  <ThemeToggle />
58
+ {user && (
59
+ <DropdownMenu>
60
+ <DropdownMenuTrigger asChild>
61
+ <Button variant="ghost" className="relative h-8 w-8 rounded-full">
62
+ <Avatar className="h-9 w-9">
63
+ <AvatarImage src={user.photoURL!} alt={user.displayName!} />
64
+ <AvatarFallback>{user.displayName?.charAt(0)}</AvatarFallback>
65
+ </Avatar>
66
+ </Button>
67
+ </DropdownMenuTrigger>
68
+ <DropdownMenuContent className="w-56" align="end" forceMount>
69
+ <DropdownMenuLabel className="font-normal">
70
+ <div className="flex flex-col space-y-1">
71
+ <p className="text-sm font-medium leading-none">{user.displayName}</p>
72
+ <p className="text-xs leading-none text-muted-foreground">
73
+ {user.email}
74
+ </p>
75
+ </div>
76
+ </DropdownMenuLabel>
77
+ <DropdownMenuSeparator />
78
+ <DropdownMenuItem onClick={signOut}>
79
+ <LogOut className="ml-2 h-4 w-4" />
80
+ <span>تسجيل الخروج</span>
81
+ </DropdownMenuItem>
82
+ </DropdownMenuContent>
83
+ </DropdownMenu>
84
+ )}
85
  </div>
86
  </header>
87
  );
src/contexts/ai-world-context.tsx CHANGED
@@ -9,7 +9,7 @@ import { aiUsers } from '@/lib/ai-world-data';
9
  import { allNames } from '@/lib/ai-world-names';
10
  import type { PostWithUIState, AIComment, AIUser } from '@/lib/types';
11
  import type { GeneratePostIdeasOutput } from '@/ai/flows/types';
12
- import { database } from '@/lib/firebase';
13
  import { ref, update, set, push, query, orderByKey, limitToFirst, startAt, get, onChildChanged } from 'firebase/database';
14
  import { checkConsumption, recordConsumption } from '@/lib/consumption';
15
  import { commentBank } from '@/lib/ai-world-comments';
@@ -553,5 +553,3 @@ export const AIWorldProvider: React.FC<{ children: React.ReactNode }> = ({ child
553
  </AIWorldContext.Provider>
554
  );
555
  };
556
-
557
-
 
9
  import { allNames } from '@/lib/ai-world-names';
10
  import type { PostWithUIState, AIComment, AIUser } from '@/lib/types';
11
  import type { GeneratePostIdeasOutput } from '@/ai/flows/types';
12
+ import { database } from '@/firebase/client';
13
  import { ref, update, set, push, query, orderByKey, limitToFirst, startAt, get, onChildChanged } from 'firebase/database';
14
  import { checkConsumption, recordConsumption } from '@/lib/consumption';
15
  import { commentBank } from '@/lib/ai-world-comments';
 
553
  </AIWorldContext.Provider>
554
  );
555
  };
 
 
src/contexts/auth-context.tsx ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { useState, useEffect, createContext, useContext, type ReactNode } from 'react';
4
+ import type { User } from 'firebase/auth';
5
+ import { auth, database } from '@/firebase/client';
6
+ import { onAuthStateChanged, signInWithPopup, GoogleAuthProvider, signOut as firebaseSignOut } from 'firebase/auth';
7
+ import { ref, set, get } from 'firebase/database';
8
+ import { useRouter } from 'next/navigation';
9
+ import { useToast } from '@/hooks/use-toast';
10
+ import { Loader2 } from 'lucide-react';
11
+
12
+ interface AuthContextType {
13
+ user: User | null;
14
+ loading: boolean;
15
+ signInWithGoogle: () => Promise<void>;
16
+ signOut: () => Promise<void>;
17
+ }
18
+
19
+ const AuthContext = createContext<AuthContextType | undefined>(undefined);
20
+
21
+ export const AuthProvider = ({ children }: { children: ReactNode }) => {
22
+ const [user, setUser] = useState<User | null>(null);
23
+ const [loading, setLoading] = useState(true);
24
+ const router = useRouter();
25
+ const { toast } = useToast();
26
+
27
+ useEffect(() => {
28
+ const unsubscribe = onAuthStateChanged(auth, (user) => {
29
+ setUser(user);
30
+ setLoading(false);
31
+ });
32
+
33
+ return () => unsubscribe();
34
+ }, []);
35
+
36
+ const signInWithGoogle = async () => {
37
+ const provider = new GoogleAuthProvider();
38
+ try {
39
+ const result = await signInWithPopup(auth, provider);
40
+ const user = result.user;
41
+
42
+ // Save user to Realtime Database
43
+ const userRef = ref(database, 'users/' + user.uid);
44
+ const snapshot = await get(userRef);
45
+ if (!snapshot.exists()) {
46
+ await set(userRef, {
47
+ displayName: user.displayName,
48
+ email: user.email,
49
+ photoURL: user.photoURL,
50
+ createdAt: new Date().toISOString(),
51
+ });
52
+ }
53
+
54
+ toast({
55
+ title: "تم تسجيل الدخول بنجاح",
56
+ description: `مرحباً بك مجدداً، ${user.displayName}!`,
57
+ });
58
+ router.push('/');
59
+ } catch (error: any) {
60
+ console.error("Error signing in with Google: ", error);
61
+ toast({
62
+ title: "خطأ في تسجيل الدخول",
63
+ description: "فشل تسجيل الدخول باستخدام جوجل. الرجاء المحاولة مرة أخرى.",
64
+ variant: "destructive",
65
+ });
66
+ }
67
+ };
68
+
69
+ const signOut = async () => {
70
+ try {
71
+ await firebaseSignOut(auth);
72
+ toast({
73
+ title: "تم تسجيل الخروج بنجاح",
74
+ });
75
+ router.push('/login');
76
+ } catch (error) {
77
+ console.error("Error signing out: ", error);
78
+ toast({
79
+ title: "خطأ في تسجيل الخروج",
80
+ description: "حدث خطأ أثناء تسجيل الخروج.",
81
+ variant: "destructive",
82
+ });
83
+ }
84
+ };
85
+
86
+ if (loading) {
87
+ return (
88
+ <div className="flex h-screen w-full items-center justify-center bg-background">
89
+ <Loader2 className="h-16 w-16 animate-spin text-primary" />
90
+ </div>
91
+ );
92
+ }
93
+
94
+ return (
95
+ <AuthContext.Provider value={{ user, loading, signInWithGoogle, signOut }}>
96
+ {children}
97
+ </AuthContext.Provider>
98
+ );
99
+ };
100
+
101
+ export const useAuth = () => {
102
+ const context = useContext(AuthContext);
103
+ if (context === undefined) {
104
+ throw new Error('useAuth must be used within an AuthProvider');
105
+ }
106
+ return context;
107
+ };
src/firebase/client.ts ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import { initializeApp, getApp, getApps } from "firebase/app";
2
+ import { getAuth } from "firebase/auth";
3
+ import { getDatabase } from "firebase/database";
4
+ import { firebaseConfig } from "./config";
5
+
6
+ const app = !getApps().length ? initializeApp(firebaseConfig) : getApp();
7
+ const auth = getAuth(app);
8
+ const database = getDatabase(app);
9
+
10
+ export { app, auth, database };
src/firebase/config.ts ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { FirebaseOptions } from "firebase/app";
2
+
3
+ // Your web app's Firebase configuration
4
+ export const firebaseConfig: FirebaseOptions = {
5
+ apiKey: "AIzaSyAOG4b86lnMDidl7UcbUEEyPEinyS4xD_g",
6
+ authDomain: "hhjij-be5c6.firebaseapp.com",
7
+ databaseURL: "https://hhjij-be5c6-default-rtdb.firebaseio.com",
8
+ projectId: "hhjij-be5c6",
9
+ storageBucket: "hhjij-be5c6.firebasestorage.app",
10
+ messagingSenderId: "56446735767",
11
+ appId: "1:56446735767:web:6fb56e6476489cba6760d2",
12
+ measurementId: "G-103FCK8LKB"
13
+ };
src/lib/firebase.ts CHANGED
@@ -1,27 +1,2 @@
1
- // Import the functions you need from the SDKs you need
2
- import { initializeApp, getApp, type FirebaseOptions } from "firebase/app";
3
- import { getDatabase } from "firebase/database";
4
-
5
- // Your web app's Firebase configuration
6
- const firebaseConfig: FirebaseOptions = {
7
- apiKey: "AIzaSyAOG4b86lnMDidl7UcbUEEyPEinyS4xD_g",
8
- authDomain: "hhjij-be5c6.firebaseapp.com",
9
- databaseURL: "https://hhjij-be5c6-default-rtdb.firebaseio.com",
10
- projectId: "hhjij-be5c6",
11
- storageBucket: "hhjij-be5c6.firebasestorage.app",
12
- messagingSenderId: "56446735767",
13
- appId: "1:56446735767:web:6fb56e6476489cba6760d2",
14
- measurementId: "G-103FCK8LKB"
15
- };
16
-
17
- // Initialize Firebase
18
- function createFirebaseApp(config: FirebaseOptions) {
19
- try {
20
- return getApp();
21
- } catch {
22
- return initializeApp(config);
23
- }
24
- }
25
-
26
- const firebaseApp = createFirebaseApp(firebaseConfig);
27
- export const database = getDatabase(firebaseApp);
 
1
+ // This file is deprecated. Firebase setup is now in /src/firebase/
2
+ // The database is exported from /src/firebase/client.ts