ClaudeKeep
by sdairs
'use client';
import { createClient } from '@/lib/supabase/client';
import { getUser } from '@/lib/supabase/queries';
import { refreshUserToken } from '@/lib/jwt/client';
import { useEffect, useState } from 'react';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { User } from '@supabase/supabase-js';
import { getURL } from '@/lib/supabase/auth';
import { Copy, Check, CircleUser, MessageSquareText } from 'lucide-react';
import { ModeToggle } from './theme-toggle';
import { RefreshTokenPopover } from './refresh-token-popover';
import { Button } from './ui/button';
export function Header() {
const supabase = createClient();
const router = useRouter();
const [user, setUser] = useState<User | null>(null);
const [token, setToken] = useState<string | null>(null);
const [copying, setCopying] = useState(false);
const [isLoadingToken, setIsLoadingToken] = useState(false);
useEffect(() => {
// Listen for auth state changes
const { data: { subscription } } = supabase.auth.onAuthStateChange((event) => {
if (event === 'SIGNED_OUT') {
setUser(null);
setToken(null);
router.push('/');
}
});
// Cleanup subscription
return () => {
subscription.unsubscribe();
};
}, [supabase, router]);
useEffect(() => {
const fetchUser = async () => {
const user = await getUser(supabase);
setUser(user);
if (user) {
setIsLoadingToken(true);
let attempts = 0;
const maxAttempts = 10; // Try for 10 seconds
const interval = setInterval(async () => {
const { data } = await supabase
.from('user_tokens')
.select('token')
.eq('user_id', user.id)
.single();
if (data?.token) {
setToken(data.token);
setIsLoadingToken(false);
clearInterval(interval);
} else if (++attempts >= maxAttempts) {
setIsLoadingToken(false);
clearInterval(interval);
console.error('Failed to load token after multiple attempts');
}
}, 1000); // Check every second
// Cleanup
return () => clearInterval(interval);
}
};
fetchUser();
}, [supabase]);
const handleCopyToken = async () => {
if (token) {
await navigator.clipboard.writeText(token);
setCopying(true);
setTimeout(() => setCopying(false), 2000);
}
};
const handleRefreshToken = async () => {
if (!user) return;
setIsLoadingToken(true);
try {
const newToken = await refreshUserToken(supabase, user.id);
setToken(newToken);
} finally {
setIsLoadingToken(false);
}
};
const handleLogin = async () => {
await supabase.auth.signInWithOAuth({
provider: 'github',
options: {
redirectTo: `${getURL()}auth/callback`
}
});
};
const handleLogout = async () => {
await supabase.auth.signOut();
};
return (
<header>
<nav className="container mx-auto px-4 py-3 flex items-center justify-between">
<Link href="/" className="text-2xl font-bold text-purple-400">
ClaudeKeep
</Link>
<div className="flex items-center gap-4">
{user && (
<div className="flex items-center gap-2">
<button
onClick={handleCopyToken}
disabled={isLoadingToken}
className="p-2"
>
{copying ? (
<Check className="w-5 h-5 text-green-500" />
) : (
<Copy className="w-5 h-5" />
)}
</button>
<input
type="text"
value={token || ''}
readOnly
disabled={isLoadingToken}
className={`w-64 px-3 py-1 border rounded`}
/>
<RefreshTokenPopover
isLoading={isLoadingToken}
onRefresh={handleRefreshToken}
/>
</div>
)}
{user ? (
<div className="flex items-center gap-4">
<Button asChild variant={"outline"}>
<Link href="/chats">
<MessageSquareText className="h-4 w-4" />
My Chats
</Link>
</Button>
<Button
variant="outline"
onClick={handleLogout}
>
<CircleUser className="h-4 w-4" />
Logout
</Button>
</div>
) : (
<Button
variant="outline"
onClick={handleLogin}
>
<CircleUser className="h-4 w-4" />
Login
</Button>
)}
<ModeToggle />
</div>
</nav>
</header>
);
}