Spaces:
Sleeping
Sleeping
جيد اضف تسجيل الدخول ب google
Browse files- src/app/layout.tsx +9 -7
- src/app/login/page.tsx +39 -44
- src/app/page.tsx +11 -7
- src/components/header.tsx +43 -2
- src/contexts/ai-world-context.tsx +1 -3
- src/contexts/auth-context.tsx +107 -0
- src/firebase/client.ts +10 -0
- src/firebase/config.ts +13 -0
- src/lib/firebase.ts +2 -27
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 |
-
//
|
| 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 |
-
<
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
|
|
|
|
|
|
| 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 {
|
| 9 |
-
import
|
| 10 |
-
import {
|
|
|
|
| 11 |
|
| 12 |
export default function LoginPage() {
|
| 13 |
-
const {
|
|
|
|
|
|
|
| 14 |
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 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">
|
| 30 |
<CardDescription>
|
| 31 |
-
|
| 32 |
</CardDescription>
|
| 33 |
</CardHeader>
|
| 34 |
<CardContent>
|
| 35 |
-
<
|
| 36 |
-
<
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 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,
|
| 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 |
-
|
| 42 |
-
<
|
| 43 |
-
<
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
|
|
|
|
|
|
| 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-
|
| 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 '@/
|
| 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 |
-
//
|
| 2 |
-
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|