Skip to content

Commit d9d4201

Browse files
feat: signin page
1 parent 1d09289 commit d9d4201

File tree

6 files changed

+247
-12
lines changed

6 files changed

+247
-12
lines changed

async-code-web/app/layout.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Metadata } from "next";
22
import { Geist, Geist_Mono } from "next/font/google";
3+
import { AuthProvider } from "@/contexts/auth-context";
34
import "./globals.css";
45

56
const geistSans = Geist({
@@ -27,7 +28,9 @@ export default function RootLayout({
2728
<body
2829
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
2930
>
30-
{children}
31+
<AuthProvider>
32+
{children}
33+
</AuthProvider>
3134
</body>
3235
</html>
3336
);

async-code-web/app/page.tsx

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"use client";
22

33
import { useState, useEffect } from "react";
4-
import { Github, GitBranch, Code2, ExternalLink, CheckCircle, Clock, XCircle, AlertCircle, FileText, Eye, GitCommit, Bell, Settings } from "lucide-react";
4+
import { Github, GitBranch, Code2, ExternalLink, CheckCircle, Clock, XCircle, AlertCircle, FileText, Eye, GitCommit, Bell, Settings, LogOut, User } from "lucide-react";
55
import Link from "next/link";
66
import { Button } from "@/components/ui/button";
77
import { Input } from "@/components/ui/input";
@@ -11,12 +11,16 @@ import { Badge } from "@/components/ui/badge";
1111
import { Label } from "@/components/ui/label";
1212
import { Separator } from "@/components/ui/separator";
1313
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
14+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
1415
import { Task } from "@/types";
1516
import { formatDiff, parseDiffStats } from "@/lib/utils";
17+
import { ProtectedRoute } from "@/components/protected-route";
18+
import { useAuth } from "@/contexts/auth-context";
1619

1720

1821

1922
export default function Home() {
23+
const { user, signOut } = useAuth();
2024
const [prompt, setPrompt] = useState("");
2125
const [repoUrl, setRepoUrl] = useState("https://github.com/ObservedObserver/streamlit-react");
2226
const [branch, setBranch] = useState("main");
@@ -200,7 +204,8 @@ export default function Home() {
200204
};
201205

202206
return (
203-
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100">
207+
<ProtectedRoute>
208+
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100">
204209
{/* Notification Banner */}
205210
{showNotification && (
206211
<div className="bg-green-600 text-white px-6 py-3 text-center relative">
@@ -230,15 +235,37 @@ export default function Home() {
230235
<p className="text-sm text-slate-500">Claude Code & Codex CLI Integration</p>
231236
</div>
232237
</div>
233-
<Link href="/settings">
234-
<Button
235-
variant="outline"
236-
className="gap-2"
237-
>
238-
<Settings className="w-4 h-4" />
239-
Settings
240-
</Button>
241-
</Link>
238+
<div className="flex items-center gap-4">
239+
<Link href="/settings">
240+
<Button
241+
variant="outline"
242+
className="gap-2"
243+
>
244+
<Settings className="w-4 h-4" />
245+
Settings
246+
</Button>
247+
</Link>
248+
249+
<DropdownMenu>
250+
<DropdownMenuTrigger asChild>
251+
<Button variant="outline" className="gap-2">
252+
<User className="w-4 h-4" />
253+
{user?.email?.split('@')[0] || 'User'}
254+
</Button>
255+
</DropdownMenuTrigger>
256+
<DropdownMenuContent align="end" className="w-56">
257+
<div className="p-2">
258+
<p className="text-sm font-medium">{user?.email}</p>
259+
<p className="text-xs text-slate-500">Signed in</p>
260+
</div>
261+
<DropdownMenuSeparator />
262+
<DropdownMenuItem onClick={signOut} className="gap-2 text-red-600">
263+
<LogOut className="w-4 h-4" />
264+
Sign Out
265+
</DropdownMenuItem>
266+
</DropdownMenuContent>
267+
</DropdownMenu>
268+
</div>
242269
</div>
243270
</div>
244271
</header>
@@ -540,5 +567,6 @@ export default function Home() {
540567
</div>
541568
</main>
542569
</div>
570+
</ProtectedRoute>
543571
);
544572
}

async-code-web/app/signin/page.tsx

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
'use client'
2+
3+
import { Auth } from '@supabase/auth-ui-react'
4+
import { ThemeSupa } from '@supabase/auth-ui-shared'
5+
import { supabase } from '@/lib/supabase'
6+
import { useAuth } from '@/contexts/auth-context'
7+
import { useRouter } from 'next/navigation'
8+
import { useEffect } from 'react'
9+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
10+
import { Code2 } from 'lucide-react'
11+
12+
export default function SignIn() {
13+
const { user, loading } = useAuth()
14+
const router = useRouter()
15+
16+
useEffect(() => {
17+
if (user && !loading) {
18+
router.push('/')
19+
}
20+
}, [user, loading, router])
21+
22+
if (loading) {
23+
return (
24+
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 flex items-center justify-center">
25+
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-slate-900"></div>
26+
</div>
27+
)
28+
}
29+
30+
if (user) {
31+
return null // Will redirect
32+
}
33+
34+
return (
35+
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 flex items-center justify-center p-6">
36+
<div className="w-full max-w-md">
37+
{/* Header */}
38+
<div className="text-center mb-8">
39+
<div className="w-12 h-12 bg-black rounded-lg flex items-center justify-center mx-auto mb-4">
40+
<Code2 className="w-6 h-6 text-white" />
41+
</div>
42+
<h1 className="text-2xl font-bold text-slate-900 mb-2">
43+
Welcome to AI Code Automation
44+
</h1>
45+
<p className="text-slate-600">
46+
Sign in to start automating your code with Claude Code & Codex CLI
47+
</p>
48+
</div>
49+
50+
{/* Auth Card */}
51+
<Card>
52+
<CardHeader>
53+
<CardTitle>Sign In</CardTitle>
54+
<CardDescription>
55+
Sign in to your account to continue
56+
</CardDescription>
57+
</CardHeader>
58+
<CardContent>
59+
<Auth
60+
supabaseClient={supabase}
61+
appearance={{
62+
theme: ThemeSupa,
63+
variables: {
64+
default: {
65+
colors: {
66+
brand: '#0f172a',
67+
brandAccent: '#1e293b',
68+
},
69+
},
70+
},
71+
className: {
72+
button: 'w-full px-4 py-2 rounded-md font-medium',
73+
input: 'w-full px-3 py-2 border border-slate-300 rounded-md',
74+
}
75+
}}
76+
providers={['github']}
77+
redirectTo={typeof window !== 'undefined' ? `${window.location.origin}/` : '/'}
78+
onlyThirdPartyProviders={false}
79+
/>
80+
</CardContent>
81+
</Card>
82+
83+
{/* Footer */}
84+
<div className="text-center mt-6 text-sm text-slate-600">
85+
<p>
86+
By signing in, you agree to our terms of service and privacy policy.
87+
</p>
88+
</div>
89+
</div>
90+
</div>
91+
)
92+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
'use client'
2+
3+
import { useAuth } from '@/contexts/auth-context'
4+
import { useRouter } from 'next/navigation'
5+
import { useEffect } from 'react'
6+
7+
interface ProtectedRouteProps {
8+
children: React.ReactNode
9+
}
10+
11+
export function ProtectedRoute({ children }: ProtectedRouteProps) {
12+
const { user, loading } = useAuth()
13+
const router = useRouter()
14+
15+
useEffect(() => {
16+
if (!loading && !user) {
17+
router.push('/signin')
18+
}
19+
}, [user, loading, router])
20+
21+
if (loading) {
22+
return (
23+
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 flex items-center justify-center">
24+
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-slate-900"></div>
25+
</div>
26+
)
27+
}
28+
29+
if (!user) {
30+
return null // Will redirect
31+
}
32+
33+
return <>{children}</>
34+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
'use client'
2+
3+
import React, { createContext, useContext, useEffect, useState } from 'react'
4+
import { User, Session } from '@supabase/supabase-js'
5+
import { supabase } from '@/lib/supabase'
6+
7+
interface AuthContextType {
8+
user: User | null
9+
session: Session | null
10+
loading: boolean
11+
signOut: () => Promise<void>
12+
}
13+
14+
const AuthContext = createContext<AuthContextType | undefined>(undefined)
15+
16+
export function useAuth() {
17+
const context = useContext(AuthContext)
18+
if (context === undefined) {
19+
throw new Error('useAuth must be used within an AuthProvider')
20+
}
21+
return context
22+
}
23+
24+
interface AuthProviderProps {
25+
children: React.ReactNode
26+
}
27+
28+
export function AuthProvider({ children }: AuthProviderProps) {
29+
const [user, setUser] = useState<User | null>(null)
30+
const [session, setSession] = useState<Session | null>(null)
31+
const [loading, setLoading] = useState(true)
32+
33+
useEffect(() => {
34+
// Get initial session
35+
supabase.auth.getSession().then(({ data: { session } }) => {
36+
setSession(session)
37+
setUser(session?.user ?? null)
38+
setLoading(false)
39+
})
40+
41+
// Listen for auth changes
42+
const {
43+
data: { subscription },
44+
} = supabase.auth.onAuthStateChange(async (event, session) => {
45+
setSession(session)
46+
setUser(session?.user ?? null)
47+
setLoading(false)
48+
})
49+
50+
return () => subscription.unsubscribe()
51+
}, [])
52+
53+
const signOut = async () => {
54+
await supabase.auth.signOut()
55+
}
56+
57+
const value = {
58+
user,
59+
session,
60+
loading,
61+
signOut,
62+
}
63+
64+
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
65+
}

async-code-web/lib/supabase.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { createClient } from '@supabase/supabase-js'
2+
import { Database } from '@/types/supabase'
3+
4+
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
5+
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
6+
7+
export const supabase = createClient<Database>(supabaseUrl, supabaseAnonKey, {
8+
auth: {
9+
autoRefreshToken: true,
10+
persistSession: true,
11+
detectSessionInUrl: true
12+
}
13+
})

0 commit comments

Comments
 (0)