diff --git a/package-lock.json b/package-lock.json index df56867..7d5f043 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,6 +36,7 @@ "@radix-ui/react-toggle": "^1.1.0", "@radix-ui/react-toggle-group": "^1.1.0", "@radix-ui/react-tooltip": "^1.1.4", + "@supabase/supabase-js": "^2.75.0", "@tanstack/react-query": "^5.56.2", "axios": "^1.11.0", "class-variance-authority": "^0.7.1", @@ -2548,6 +2549,80 @@ "win32" ] }, + "node_modules/@supabase/auth-js": { + "version": "2.75.0", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.75.0.tgz", + "integrity": "sha512-J8TkeqCOMCV4KwGKVoxmEBuDdHRwoInML2vJilthOo7awVCro2SM+tOcpljORwuBQ1vHUtV62Leit+5wlxrNtw==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "2.6.15" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.75.0", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.75.0.tgz", + "integrity": "sha512-18yk07Moj/xtQ28zkqswxDavXC3vbOwt1hDuYM3/7xPnwwpKnsmPyZ7bQ5th4uqiJzQ135t74La9tuaxBR6e7w==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "2.6.15" + } + }, + "node_modules/@supabase/node-fetch": { + "version": "2.6.15", + "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz", + "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "2.75.0", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.75.0.tgz", + "integrity": "sha512-YfBz4W/z7eYCFyuvHhfjOTTzRrQIvsMG2bVwJAKEVVUqGdzqfvyidXssLBG0Fqlql1zJFgtsPpK1n4meHrI7tg==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "2.6.15" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.75.0", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.75.0.tgz", + "integrity": "sha512-B4Xxsf2NHd5cEnM6MGswOSPSsZKljkYXpvzKKmNxoUmNQOfB7D8HOa6NwHcUBSlxcjV+vIrYKcYXtavGJqeGrw==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "2.6.15", + "@types/phoenix": "^1.6.6", + "@types/ws": "^8.18.1", + "ws": "^8.18.2" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.75.0", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.75.0.tgz", + "integrity": "sha512-wpJMYdfFDckDiHQaTpK+Ib14N/O2o0AAWWhguKvmmMurB6Unx17GGmYp5rrrqCTf8S1qq4IfIxTXxS4hzrUySg==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "2.6.15" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.75.0", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.75.0.tgz", + "integrity": "sha512-8UN/vATSgS2JFuJlMVr51L3eUDz+j1m7Ww63wlvHLKULzCDaVWYzvacCjBTLW/lX/vedI2LBI4Vg+01G9ufsJQ==", + "license": "MIT", + "dependencies": { + "@supabase/auth-js": "2.75.0", + "@supabase/functions-js": "2.75.0", + "@supabase/node-fetch": "2.6.15", + "@supabase/postgrest-js": "2.75.0", + "@supabase/realtime-js": "2.75.0", + "@supabase/storage-js": "2.75.0" + } + }, "node_modules/@swc/core": { "version": "1.7.39", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.7.39.tgz", @@ -2909,12 +2984,17 @@ "version": "22.7.9", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.9.tgz", "integrity": "sha512-jrTfRC7FM6nChvU7X2KqcrgquofrWLFDeYC1hKfwNWomVvrn7JIksqf344WN2X/y8xrgqBd2dJATZV4GbatBfg==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.19.2" } }, + "node_modules/@types/phoenix": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.6.tgz", + "integrity": "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==", + "license": "MIT" + }, "node_modules/@types/prop-types": { "version": "15.7.13", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", @@ -2943,6 +3023,15 @@ "@types/react": "*" } }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.11.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.11.0.tgz", @@ -7606,6 +7695,12 @@ "node": ">=0.6" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/ts-api-utils": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", @@ -7700,7 +7795,6 @@ "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true, "license": "MIT" }, "node_modules/unpipe": { @@ -7906,6 +8000,22 @@ } } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -8025,6 +8135,27 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/yaml": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz", diff --git a/package.json b/package.json index e8f1faa..659679a 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "@radix-ui/react-toggle": "^1.1.0", "@radix-ui/react-toggle-group": "^1.1.0", "@radix-ui/react-tooltip": "^1.1.4", + "@supabase/supabase-js": "^2.75.0", "@tanstack/react-query": "^5.56.2", "axios": "^1.11.0", "class-variance-authority": "^0.7.1", diff --git a/src/App.tsx b/src/App.tsx index 1f4aca9..bba7a78 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,7 +3,15 @@ import { Toaster } from "@/components/ui/toaster"; import { Toaster as Sonner } from "@/components/ui/sonner"; import { TooltipProvider } from "@/components/ui/tooltip"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +<<<<<<< Updated upstream import { BrowserRouter, Routes, Route, useLocation } from "react-router-dom"; +======= +import { BrowserRouter, Routes, Route } from "react-router-dom"; +import { AuthProvider } from "@/contexts/AuthContext"; +<<<<<<< Updated upstream +>>>>>>> Stashed changes +======= +>>>>>>> Stashed changes import Index from "./pages/Index"; import NotFound from "./pages/NotFound"; import GitHubGuidePage from "./pages/GitHubGuidePage"; @@ -15,11 +23,19 @@ import ResumeTemplateDetail from "./pages/ResumeTemplateDetail"; import ProfilePage from "./pages/ProfilePage"; import SettingsPage from "./pages/SettingsPage"; import ComingSoon from './pages/ComingSoon'; +<<<<<<< Updated upstream +<<<<<<< Updated upstream import PrivacyPolicy from './pages/PrivacyPolicy'; import Terms from './pages/Terms'; import ProgressTracker from './components/ProgressTracker'; import FAQBot from './components/FAQBot'; import React from 'react'; +======= +import AuthenticationPage from './pages/AuthenticationPage'; +>>>>>>> Stashed changes +======= +import AuthenticationPage from './pages/AuthenticationPage'; +>>>>>>> Stashed changes const queryClient = new QueryClient(); @@ -33,6 +49,8 @@ function ScrollToTop() { const App = () => ( +<<<<<<< Updated upstream +<<<<<<< Updated upstream @@ -58,6 +76,36 @@ const App = () => ( +======= +======= +>>>>>>> Stashed changes + + + + + + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} + } /> + + + + +<<<<<<< Updated upstream +>>>>>>> Stashed changes +======= +>>>>>>> Stashed changes ); diff --git a/src/components/ProtectedRoute.tsx b/src/components/ProtectedRoute.tsx new file mode 100644 index 0000000..1a47d51 --- /dev/null +++ b/src/components/ProtectedRoute.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { Navigate } from 'react-router-dom'; +import { useAuth } from '@/contexts/AuthContext'; + +interface ProtectedRouteProps { + children: React.ReactNode; +} + +const ProtectedRoute: React.FC = ({ children }) => { + const { user, loading } = useAuth(); + + if (loading) { + return ( +
+
+
+

Loading...

+
+
+ ); + } + + if (!user) { + return ; + } + + return <>{children}; +}; + +export default ProtectedRoute; \ No newline at end of file diff --git a/src/components/UserProfile.tsx b/src/components/UserProfile.tsx new file mode 100644 index 0000000..61fbf7c --- /dev/null +++ b/src/components/UserProfile.tsx @@ -0,0 +1,97 @@ +import React from 'react'; +import { useAuth } from '@/contexts/AuthContext'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { LogOut, User, Mail, Calendar } from 'lucide-react'; +import { useToast } from '@/hooks/use-toast'; + +const UserProfile: React.FC = () => { + const { user, signOut } = useAuth(); + const { toast } = useToast(); + + const handleSignOut = async () => { + try { + const { error } = await signOut(); + if (error) { + toast({ + title: "Sign out failed", + description: "Please try again.", + variant: "destructive", + }); + } else { + toast({ + title: "Signed out successfully", + description: "You have been signed out.", + }); + } + } catch (error) { + toast({ + title: "Sign out failed", + description: "An unexpected error occurred.", + variant: "destructive", + }); + } + }; + + if (!user) { + return null; + } + + const userInitials = user.user_metadata?.name + ? user.user_metadata.name.split(' ').map((n: string) => n[0]).join('').toUpperCase() + : user.email?.[0].toUpperCase() || 'U'; + + return ( + + + + + User Profile + + + Your account information + + + +
+ + + {userInitials} + +
+

+ {user.user_metadata?.name || 'User'} +

+

+ + {user.email} +

+
+
+ +
+
+ + Member since: {new Date(user.created_at).toLocaleDateString()} +
+
+ + ID: {user.id.slice(0, 8)}... +
+
+ + +
+
+ ); +}; + +export default UserProfile; diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx new file mode 100644 index 0000000..902448f --- /dev/null +++ b/src/contexts/AuthContext.tsx @@ -0,0 +1,101 @@ +import React, { createContext, useContext, useEffect, useState } from 'react'; +import { User, Session, AuthError } from '@supabase/supabase-js'; +import { auth } from '@/lib/supabase'; + +interface AuthContextType { + user: User | null; + session: Session | null; + loading: boolean; + signUp: (email: string, password: string, name?: string) => Promise<{ error: AuthError | null }>; + signIn: (email: string, password: string) => Promise<{ error: AuthError | null }>; + signOut: () => Promise<{ error: AuthError | null }>; +} + +const AuthContext = createContext(undefined); + +export const useAuth = () => { + const context = useContext(AuthContext); + if (context === undefined) { + throw new Error('useAuth must be used within an AuthProvider'); + } + return context; +}; + +interface AuthProviderProps { + children: React.ReactNode; +} + +export const AuthProvider: React.FC = ({ children }) => { + const [user, setUser] = useState(null); + const [session, setSession] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + // Get initial session + const getInitialSession = async () => { + const { session, error } = await auth.getCurrentSession(); + if (error) { + console.error('Error getting initial session:', error); + } else { + setSession(session); + setUser(session?.user ?? null); + } + setLoading(false); + }; + + getInitialSession(); + + // Listen for auth state changes + const { data: { subscription } } = auth.onAuthStateChange( + async (event, session) => { + setSession(session); + setUser(session?.user ?? null); + setLoading(false); + } + ); + + return () => subscription.unsubscribe(); + }, []); + + const signUp = async (email: string, password: string, name?: string) => { + try { + const { error } = await auth.signUp(email, password, { name }); + return { error }; + } catch (error) { + return { error: error as AuthError }; + } + }; + + const signIn = async (email: string, password: string) => { + try { + const { error } = await auth.signIn(email, password); + return { error }; + } catch (error) { + return { error: error as AuthError }; + } + }; + + const signOut = async () => { + try { + const { error } = await auth.signOut(); + return { error }; + } catch (error) { + return { error: error as AuthError }; + } + }; + + const value = { + user, + session, + loading, + signUp, + signIn, + signOut, + }; + + return ( + + {children} + + ); +}; diff --git a/src/lib/env.example b/src/lib/env.example new file mode 100644 index 0000000..96082d3 --- /dev/null +++ b/src/lib/env.example @@ -0,0 +1,3 @@ +# Supabase Configuration +VITE_SUPABASE_URL=your-supabase-project-url +VITE_SUPABASE_ANON_KEY=your-supabase-anon-key diff --git a/src/lib/supabase.ts b/src/lib/supabase.ts new file mode 100644 index 0000000..6becfee --- /dev/null +++ b/src/lib/supabase.ts @@ -0,0 +1,61 @@ +import { createClient } from '@supabase/supabase-js'; + +// Supabase configuration +const supabaseUrl = import.meta.env.VITE_SUPABASE_URL || 'https://hkbtlauzrzwqtolyumiy.supabase.co'; +const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImhrYnRsYXV6cnp3cXRvbHl1bWl5Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjA0NDEwNzUsImV4cCI6MjA3NjAxNzA3NX0.UWEzZnyRhOdm0Y_m87ALLViGs849wxsOkVtCjPJuMOQ'; + +// Create Supabase client +export const supabase = createClient(supabaseUrl, supabaseAnonKey, { + auth: { + autoRefreshToken: true, + persistSession: true, + detectSessionInUrl: true + } +}); + +// Auth helper functions +export const auth = { + // Sign up with email and password + signUp: async (email: string, password: string, metadata?: { name?: string }) => { + const { data, error } = await supabase.auth.signUp({ + email, + password, + options: { + data: metadata + } + }); + return { data, error }; + }, + + // Sign in with email and password + signIn: async (email: string, password: string) => { + const { data, error } = await supabase.auth.signInWithPassword({ + email, + password + }); + return { data, error }; + }, + + // Sign out + signOut: async () => { + const { error } = await supabase.auth.signOut(); + return { error }; + }, + + // Get current user + getCurrentUser: async () => { + const { data: { user }, error } = await supabase.auth.getUser(); + return { user, error }; + }, + + // Get current session + getCurrentSession: async () => { + const { data: { session }, error } = await supabase.auth.getSession(); + return { session, error }; + }, + + // Listen to auth state changes + onAuthStateChange: (callback: (event: string, session: any) => void) => { + return supabase.auth.onAuthStateChange(callback); + } +}; diff --git a/src/pages/AuthenticationPage.tsx b/src/pages/AuthenticationPage.tsx new file mode 100644 index 0000000..4ba7b1e --- /dev/null +++ b/src/pages/AuthenticationPage.tsx @@ -0,0 +1,448 @@ +import React, { useState, useEffect } from 'react'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import * as z from 'zod'; +import { useNavigate } from 'react-router-dom'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Separator } from '@/components/ui/separator'; +import { Alert, AlertDescription } from '@/components/ui/alert'; +import { Eye, EyeOff, Mail, Lock, User, Chrome } from 'lucide-react'; +import { useToast } from '@/hooks/use-toast'; +import { useAuth } from '@/contexts/AuthContext'; + +// Validation schemas +const loginSchema = z.object({ + email: z.string().email('Please enter a valid email address'), + password: z.string().min(6, 'Password must be at least 6 characters'), +}); + +const signupSchema = z.object({ + name: z.string().min(2, 'Name must be at least 2 characters'), + email: z.string().email('Please enter a valid email address'), + password: z.string().min(6, 'Password must be at least 6 characters'), + confirmPassword: z.string(), +}).refine((data) => data.password === data.confirmPassword, { + message: "Passwords don't match", + path: ["confirmPassword"], +}); + +type LoginFormData = z.infer; +type SignupFormData = z.infer; + +const AuthenticationPage = () => { + const [showPassword, setShowPassword] = useState(false); + const [showConfirmPassword, setShowConfirmPassword] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const { toast } = useToast(); + const { user, signUp, signIn, loading: authLoading } = useAuth(); + const navigate = useNavigate(); + + // Redirect if user is already authenticated + useEffect(() => { + if (user && !authLoading) { + navigate('/'); + } + }, [user, authLoading, navigate]); + + // Login form + const loginForm = useForm({ + resolver: zodResolver(loginSchema), + defaultValues: { + email: '', + password: '', + }, + }); + + // Signup form + const signupForm = useForm({ + resolver: zodResolver(signupSchema), + defaultValues: { + name: '', + email: '', + password: '', + confirmPassword: '', + }, + }); + + // Handle login + const onLoginSubmit = async (data: LoginFormData) => { + setIsLoading(true); + try { + const { error } = await signIn(data.email, data.password); + + if (error) { + // Handle specific error cases + if (error.message.includes('Invalid login credentials')) { + toast({ + title: "Invalid credentials", + description: "Please check your email and password and try again.", + variant: "destructive", + }); + } else if (error.message.includes('Email not confirmed')) { + toast({ + title: "Email not confirmed", + description: "Please check your email and click the confirmation link.", + variant: "destructive", + }); + } else { + toast({ + title: "Login failed", + description: error.message || "Please try again later.", + variant: "destructive", + }); + } + } else { + toast({ + title: "Login successful", + description: "Welcome back!", + }); + navigate('/'); + } + } catch (error) { + toast({ + title: "Login failed", + description: "An unexpected error occurred. Please try again.", + variant: "destructive", + }); + } finally { + setIsLoading(false); + } + }; + + // Handle signup + const onSignupSubmit = async (data: SignupFormData) => { + setIsLoading(true); + try { + const { error } = await signUp(data.email, data.password, data.name); + + if (error) { + // Handle specific error cases + if (error.message.includes('User already registered')) { + toast({ + title: "User already exists", + description: "An account with this email already exists. Please sign in instead.", + variant: "destructive", + }); + } else if (error.message.includes('Password should be at least')) { + toast({ + title: "Password too weak", + description: "Please choose a stronger password with at least 6 characters.", + variant: "destructive", + }); + } else if (error.message.includes('Invalid email')) { + toast({ + title: "Invalid email", + description: "Please enter a valid email address.", + variant: "destructive", + }); + } else { + toast({ + title: "Signup failed", + description: error.message || "Please try again later.", + variant: "destructive", + }); + } + } else { + toast({ + title: "Account created successfully", + description: "Please check your email to confirm your account.", + }); + // Clear the form + signupForm.reset(); + } + } catch (error) { + toast({ + title: "Signup failed", + description: "An unexpected error occurred. Please try again.", + variant: "destructive", + }); + } finally { + setIsLoading(false); + } + }; + + // Handle Google OAuth + const handleGoogleAuth = async () => { + setIsLoading(true); + try { + // For now, show a message that Google OAuth needs to be configured + toast({ + title: "Google OAuth", + description: "Google OAuth integration requires additional Supabase configuration. Please use email/password for now.", + variant: "destructive", + }); + } catch (error) { + toast({ + title: "Google authentication failed", + description: "Please try again later.", + variant: "destructive", + }); + } finally { + setIsLoading(false); + } + }; + + // Show loading state while checking authentication + if (authLoading) { + return ( +
+
+
+

Loading...

+
+
+ ); + } + + return ( +
+ + + Welcome + + Choose your preferred authentication method + + + + + + Login + Sign Up + + + {/* Login Tab */} + +
+
+ +
+ + +
+ {loginForm.formState.errors.email && ( + + + {loginForm.formState.errors.email.message} + + + )} +
+ +
+ +
+ + + +
+ {loginForm.formState.errors.password && ( + + + {loginForm.formState.errors.password.message} + + + )} +
+ + +
+ +
+
+ +
+
+ + Or continue with + +
+
+ + +
+ + {/* Signup Tab */} + +
+
+ +
+ + +
+ {signupForm.formState.errors.name && ( + + + {signupForm.formState.errors.name.message} + + + )} +
+ +
+ +
+ + +
+ {signupForm.formState.errors.email && ( + + + {signupForm.formState.errors.email.message} + + + )} +
+ +
+ +
+ + + +
+ {signupForm.formState.errors.password && ( + + + {signupForm.formState.errors.password.message} + + + )} +
+ +
+ +
+ + + +
+ {signupForm.formState.errors.confirmPassword && ( + + + {signupForm.formState.errors.confirmPassword.message} + + + )} +
+ + +
+ +
+
+ +
+
+ + Or continue with + +
+
+ + +
+
+
+
+
+ ); +}; + +export default AuthenticationPage; \ No newline at end of file diff --git a/src/pages/ComingSoon.tsx b/src/pages/ComingSoon.tsx index 2b07550..4db346f 100644 --- a/src/pages/ComingSoon.tsx +++ b/src/pages/ComingSoon.tsx @@ -1,12 +1,15 @@ -import React from 'react'; +import React from "react"; -const ComingSoon = () => ( -
-
-

Coming Soon

-

This page is under construction. Stay tuned for updates!

+const ComingSoon: React.FC = () => { + return ( +
+

Coming Soon

+

We're building this page. Check back shortly.

-
-); + ); +}; + +export default ComingSoon; + + -export default ComingSoon; \ No newline at end of file diff --git a/src/pages/ProtectedRoute.tsx b/src/pages/ProtectedRoute.tsx new file mode 100644 index 0000000..cf97dd0 --- /dev/null +++ b/src/pages/ProtectedRoute.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { Navigate } from 'react-router-dom'; +import { useAuth } from '@/contexts/AuthContext'; + +interface ProtectedRouteProps { + children: React.ReactNode; +} + +const ProtectedRoute: React.FC = ({ children }) => { + const { user, loading } = useAuth(); + + if (loading) { + return ( +
+
+
+

Loading...

+
+
+ ); + } + + if (!user) { + return ; + } + + return <>{children}; +}; + +export default ProtectedRoute; diff --git a/src/pages/ResumeTemplateDetail.tsx b/src/pages/ResumeTemplateDetail.tsx index 5a678b7..73203ca 100644 --- a/src/pages/ResumeTemplateDetail.tsx +++ b/src/pages/ResumeTemplateDetail.tsx @@ -498,3 +498,19 @@ const ResumeTemplateDetail: React.FC = () => { }; export default ResumeTemplateDetail; + + + + + + + + + + + + + + + +