-
Notifications
You must be signed in to change notification settings - Fork 182
Harrison/changes #67
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Harrison/changes #67
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,28 @@ | ||
import "./App.css"; | ||
import { Routes, Route } from 'react-router-dom'; | ||
import { Login } from './components/auth/Login'; | ||
import { AuthCallback } from './components/auth/AuthCallback'; | ||
import { ProtectedRoute } from './components/auth/ProtectedRoute'; | ||
import { Thread } from "@/components/thread"; | ||
import { StreamProvider } from "./providers/Stream.tsx"; | ||
|
||
function App() { | ||
return <Thread />; | ||
return ( | ||
<Routes> | ||
<Route path="/login" element={<Login />} /> | ||
<Route path="/auth/callback" element={<AuthCallback />} /> | ||
<Route | ||
path="/*" | ||
element={ | ||
<ProtectedRoute> | ||
<StreamProvider> | ||
<Thread /> | ||
</StreamProvider> | ||
</ProtectedRoute> | ||
} | ||
/> | ||
</Routes> | ||
); | ||
} | ||
|
||
export default App; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import { useEffect } from 'react'; | ||
import { useNavigate } from 'react-router-dom'; | ||
import { createSupabaseClient } from '@/lib/supabase/client'; | ||
|
||
export function AuthCallback() { | ||
const navigate = useNavigate(); | ||
|
||
useEffect(() => { | ||
const handleCallback = async () => { | ||
const supabase = createSupabaseClient(); | ||
|
||
try { | ||
// Get the error query parameter | ||
const params = new URLSearchParams(window.location.search); | ||
const error = params.get('error'); | ||
const error_description = params.get('error_description'); | ||
|
||
if (error) { | ||
throw new Error(error_description || 'Authentication error'); | ||
} | ||
|
||
const { data, error: sessionError } = await supabase.auth.getSession(); | ||
|
||
if (sessionError) { | ||
throw sessionError; | ||
} | ||
|
||
if (!data.session) { | ||
// If no session, try to exchange the code for a session | ||
const { error: exchangeError } = await supabase.auth.exchangeCodeForSession(window.location.search); | ||
if (exchangeError) { | ||
throw exchangeError; | ||
} | ||
} | ||
|
||
// Get the intended destination | ||
const next = params.get('next') ?? '/'; | ||
navigate(next); | ||
} catch (error) { | ||
console.error('Auth callback error:', error); | ||
navigate('/login'); | ||
} | ||
}; | ||
|
||
handleCallback(); | ||
}, [navigate]); | ||
|
||
return ( | ||
<div className="flex items-center justify-center min-h-screen"> | ||
<div className="text-center"> | ||
<h2 className="text-xl font-semibold mb-2">Completing sign in...</h2> | ||
<p className="text-gray-600">Please wait while we verify your credentials.</p> | ||
</div> | ||
</div> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import { createSupabaseClient } from "@/lib/supabase/client"; | ||
import { useUser } from "@/contexts/UserContext"; | ||
import { useNavigate } from "react-router-dom"; | ||
import { useEffect } from "react"; | ||
|
||
export function Login() { | ||
try { | ||
const { session, loading } = useUser(); | ||
const navigate = useNavigate(); | ||
|
||
useEffect(() => { | ||
// If user is already logged in, redirect to home | ||
if (!loading && session?.access_token) { | ||
navigate('/', { replace: true }); | ||
} | ||
}, [session, loading, navigate]); | ||
|
||
const handleLogin = async (provider: 'google' | 'github') => { | ||
try { | ||
const supabase = createSupabaseClient(); | ||
|
||
const { error } = await supabase.auth.signInWithOAuth({ | ||
provider, | ||
options: { | ||
redirectTo: `${window.location.origin}/auth/callback`, | ||
skipBrowserRedirect: false, | ||
queryParams: { | ||
access_type: 'offline', | ||
prompt: 'consent', | ||
} | ||
}, | ||
}); | ||
|
||
if (error) { | ||
console.error('[Login] Login error:', error.message); | ||
} | ||
} catch (error) { | ||
console.error('[Login] Error creating Supabase client or signing in:', error); | ||
} | ||
}; | ||
|
||
// Show loading state while checking session | ||
if (loading) { | ||
return ( | ||
<div className="flex items-center justify-center min-h-screen"> | ||
<div className="text-center"> | ||
<h2 className="text-xl font-semibold mb-2">Loading...</h2> | ||
<p className="text-gray-600">Please wait while we verify your session.</p> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
// Don't show login page if already logged in | ||
if (session?.access_token) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<div className="flex flex-col gap-4 items-center justify-center min-h-screen"> | ||
<h1 className="text-2xl font-bold mb-4">Sign In</h1> | ||
<button | ||
onClick={() => handleLogin('google')} | ||
className="w-64 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600" | ||
> | ||
Sign in with Google | ||
</button> | ||
<button | ||
onClick={() => handleLogin('github')} | ||
className="w-64 px-4 py-2 bg-gray-800 text-white rounded hover:bg-gray-900" | ||
> | ||
Sign in with GitHub | ||
</button> | ||
</div> | ||
); | ||
} catch (error) { | ||
return ( | ||
<div className="flex items-center justify-center min-h-screen"> | ||
<div className="text-center text-red-600"> | ||
<h2 className="text-xl font-semibold mb-2">Error</h2> | ||
<p>An error occurred while loading the login page.</p> | ||
<p className="mt-2 text-sm">{error instanceof Error ? error.message : 'Unknown error'}</p> | ||
</div> | ||
</div> | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { Navigate, useLocation } from 'react-router-dom'; | ||
import { useUser } from '@/contexts/UserContext'; | ||
|
||
interface ProtectedRouteProps { | ||
children: React.ReactNode; | ||
} | ||
|
||
export function ProtectedRoute({ children }: ProtectedRouteProps) { | ||
const { session, loading } = useUser(); | ||
const location = useLocation(); | ||
|
||
if (loading) { | ||
return ( | ||
<div className="flex items-center justify-center min-h-screen"> | ||
<div className="text-center"> | ||
<h2 className="text-xl font-semibold mb-2">Loading...</h2> | ||
<p className="text-gray-600">Please wait while we verify your session.</p> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
if (!session) { | ||
// Redirect to login but save the current location to redirect back after login | ||
return <Navigate to="/login" state={{ from: location }} replace />; | ||
} | ||
|
||
return <>{children}</>; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { useEffect, useState } from 'react'; | ||
import { useUser } from '@/contexts/UserContext'; | ||
import { createLanggraphClient } from '@/lib/langgraph-client'; | ||
|
||
export function Thread() { | ||
const { session } = useUser(); | ||
const [data, setData] = useState(null); | ||
Check warning on line 7 in src/components/thread/Thread.tsx
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like this isn't used |
||
|
||
useEffect(() => { | ||
if (!session?.access_token) return; | ||
|
||
const client = createLanggraphClient(session.access_token); | ||
// Use the authenticated client to make API calls | ||
// Example: | ||
// client.someApiCall().then(setData); | ||
}, [session]); | ||
|
||
return ( | ||
<div> | ||
{/* Your thread component UI */} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Beautiful UI! |
||
</div> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import { createSupabaseClient } from "@/lib/supabase/client"; | ||
import { Session } from "@supabase/supabase-js"; | ||
import React, { createContext, useContext, useEffect, useState } from "react"; | ||
|
||
type UserContextType = { | ||
session: Session | null; | ||
loading: boolean; | ||
}; | ||
|
||
const UserContext = createContext<UserContextType | undefined>(undefined); | ||
|
||
export function UserProvider({ children }: { children: React.ReactNode }) { | ||
const [session, setSession] = useState<Session | null>(null); | ||
const [loading, setLoading] = useState(true); | ||
|
||
useEffect(() => { | ||
let isMounted = true; | ||
|
||
const initializeAuth = async () => { | ||
try { | ||
const supabase = createSupabaseClient(); | ||
|
||
// Get initial session | ||
const { data, error } = await supabase.auth.getSession(); | ||
if (error) throw error; | ||
|
||
if (isMounted) { | ||
setSession(data.session); | ||
setLoading(false); | ||
} | ||
|
||
// Listen for auth changes | ||
const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => { | ||
if (isMounted) { | ||
setSession(session); | ||
setLoading(false); | ||
} | ||
}); | ||
|
||
return () => { | ||
isMounted = false; | ||
subscription.unsubscribe(); | ||
}; | ||
} catch (error) { | ||
console.error("[UserProvider] Error initializing auth:", error); | ||
if (isMounted) { | ||
setLoading(false); | ||
} | ||
} | ||
}; | ||
|
||
initializeAuth(); | ||
}, []); | ||
|
||
const value = { session, loading }; | ||
|
||
return ( | ||
<UserContext.Provider value={value}> | ||
{children} | ||
</UserContext.Provider> | ||
); | ||
} | ||
|
||
export const useUser = () => { | ||
try { | ||
const context = useContext(UserContext); | ||
if (!context) { | ||
throw new Error("useUser must be used within a UserProvider"); | ||
} | ||
return context; | ||
} catch (error) { | ||
console.error("[useUser] Error accessing context:", error); | ||
throw error; | ||
} | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Try catch inside a react component is a bit odd, not usual
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Try catch on line 7 is weird. But this one is quite usual. I've used try catch within handler function.
Although hooks must not be wrapped in try catch. You can use try catch inside the hooks
It's an onclick handler function and it's completely fine. But Can use currying function to ensure component doesn't rerender
eg
and on the button