Skip to content

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
"@radix-ui/react-slot": "^1.1.2",
"@radix-ui/react-switch": "^1.1.3",
"@radix-ui/react-tooltip": "^1.1.8",
"@supabase/ssr": "^0.5.2",
"@supabase/supabase-js": "^2.49.1",
"@tailwindcss/postcss": "^4.0.9",
"@tailwindcss/vite": "^4.0.9",
"class-variance-authority": "^0.7.1",
Expand Down
6,774 changes: 2,374 additions & 4,400 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

22 changes: 21 additions & 1 deletion src/App.tsx
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;
56 changes: 56 additions & 0 deletions src/components/auth/AuthCallback.tsx
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>
);
}
87 changes: 87 additions & 0 deletions src/components/auth/Login.tsx
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();

Check failure on line 8 in src/components/auth/Login.tsx

View workflow job for this annotation

GitHub Actions / Check linting

React Hook "useUser" is called conditionally. React Hooks must be called in the exact same order in every component render
const navigate = useNavigate();

Check failure on line 9 in src/components/auth/Login.tsx

View workflow job for this annotation

GitHub Actions / Check linting

React Hook "useNavigate" is called conditionally. React Hooks must be called in the exact same order in every component render

useEffect(() => {

Check failure on line 11 in src/components/auth/Login.tsx

View workflow job for this annotation

GitHub Actions / Check linting

React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render
// 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);

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

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

const handleLogin = useCallback((loginMethod: string) => () {

}, [])

and on the button

onClick={handleLogin('google')}

}
};

// 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>
);
}
}
29 changes: 29 additions & 0 deletions src/components/auth/ProtectedRoute.tsx
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}</>;
}
23 changes: 23 additions & 0 deletions src/components/thread/Thread.tsx
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

View workflow job for this annotation

GitHub Actions / Check linting

'data' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 7 in src/components/thread/Thread.tsx

View workflow job for this annotation

GitHub Actions / Check linting

'setData' is assigned a value but never used. Allowed unused vars must match /^_/u

Choose a reason for hiding this comment

The 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);

Check warning on line 12 in src/components/thread/Thread.tsx

View workflow job for this annotation

GitHub Actions / Check linting

'client' is assigned a value but never used. Allowed unused vars must match /^_/u
// Use the authenticated client to make API calls
// Example:
// client.someApiCall().then(setData);
}, [session]);

return (
<div>
{/* Your thread component UI */}

Choose a reason for hiding this comment

The 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
Expand Up @@ -87,14 +87,6 @@ export default function useInterruptedActions({
{
command: {
resume: response,
update: {
messages: [
{
type: "human",
content: `Sending type '${response[0].type}' to interrupt...`,
},
],
},
},
},
);
Expand Down
12 changes: 9 additions & 3 deletions src/components/thread/history/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,16 @@
if (typeof window === "undefined") return;
setThreadsLoading(true);
getThreads()
.then(setThreads)
.catch(console.error)
.finally(() => setThreadsLoading(false));
.then(threads => {
setThreads(threads);
})
.catch(error => {
console.error("[ThreadHistory] Error during initial thread load:", error);
})
.finally(() => {
setThreadsLoading(false);
});
}, []);

Check warning on line 95 in src/components/thread/history/index.tsx

View workflow job for this annotation

GitHub Actions / Check linting

React Hook useEffect has missing dependencies: 'getThreads', 'setThreads', and 'setThreadsLoading'. Either include them or remove the dependency array

return (
<>
Expand Down
6 changes: 4 additions & 2 deletions src/components/thread/markdown-text.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ const defaultComponents = memoizeMarkdownComponents({
"text-primary font-medium underline underline-offset-4",
className,
)}
target="_blank"
rel="noopener noreferrer"
{...props}
/>
),
Expand All @@ -146,13 +148,13 @@ const defaultComponents = memoizeMarkdownComponents({
),
ul: ({ className, ...props }) => (
<ul
className={cn("my-5 ml-6 list-disc [&>li]:mt-2", className)}
className={cn("my-6 ml-8 list-disc space-y-3 [&>li]:pl-2", className)}
{...props}
/>
),
ol: ({ className, ...props }) => (
<ol
className={cn("my-5 ml-6 list-decimal [&>li]:mt-2", className)}
className={cn("my-6 ml-8 list-decimal space-y-3 [&>li]:pl-2", className)}
{...props}
/>
),
Expand Down
75 changes: 75 additions & 0 deletions src/contexts/UserContext.tsx
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 = () => {

Check warning on line 64 in src/contexts/UserContext.tsx

View workflow job for this annotation

GitHub Actions / Check linting

Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components
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;
}
};
Loading
Loading