Skip to content

Commit df5ab70

Browse files
committed
feat: integrate Google One Tap authentication and enhance dashboard UI
1 parent 8e05d02 commit df5ab70

12 files changed

Lines changed: 1208 additions & 652 deletions

File tree

app/(landing)/layout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Metadata } from 'next';
22
import { ReactNode } from 'react';
33
import { Footer, Navbar } from '@/components/landing-page';
44
import { generatePageMetadata } from '@/lib/metadata';
5+
import { GoogleOneTap } from '@/components/auth/GoogleOneTap';
56

67
// Generate metadata for the landing layout (home page)
78
export const metadata: Metadata = generatePageMetadata('home');
@@ -16,6 +17,7 @@ export default function LandingLayout({ children }: LandingLayoutProps) {
1617
<Navbar />
1718
<main className='flex-1'>{children}</main>
1819
<Footer />
20+
<GoogleOneTap />
1921
</div>
2022
);
2123
}

app/dashboard/page.tsx

Lines changed: 127 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,14 @@ import {
1111
CardTitle,
1212
} from '@/components/ui/card';
1313
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
14-
import { Loader2, LogOut, User, Mail, Shield } from 'lucide-react';
14+
import {
15+
Loader2,
16+
LogOut,
17+
User,
18+
Mail,
19+
Shield,
20+
CheckCircle2,
21+
} from 'lucide-react';
1522
import { authClient } from '@/lib/auth-client';
1623
import { useAuthActions } from '@/hooks/use-auth';
1724

@@ -33,10 +40,10 @@ export default function DashboardPage() {
3340

3441
if (sessionPending) {
3542
return (
36-
<div className='flex min-h-screen items-center justify-center'>
37-
<div className='flex items-center space-x-2'>
38-
<Loader2 className='h-6 w-6 animate-spin' />
39-
<span>Loading...</span>
43+
<div className='flex min-h-screen items-center justify-center bg-[#030303]'>
44+
<div className='flex items-center space-x-3'>
45+
<Loader2 className='h-6 w-6 animate-spin text-[#a7f950]' />
46+
<span className='text-white/80'>Loading...</span>
4047
</div>
4148
</div>
4249
);
@@ -47,96 +54,164 @@ export default function DashboardPage() {
4754
}
4855

4956
return (
50-
<div className='min-h-screen bg-gray-50 py-8'>
51-
<div className='mx-auto max-w-4xl px-4 sm:px-6 lg:px-8'>
52-
<div className='mb-8 flex items-center justify-between'>
53-
<h1 className='text-3xl font-bold text-gray-900'>Dashboard</h1>
54-
<Button onClick={handleSignOut} variant='outline'>
57+
<div className='min-h-screen bg-[#030303] px-4 py-8 sm:px-6 lg:px-8'>
58+
<div className='mx-auto max-w-6xl'>
59+
{/* Header */}
60+
<div className='mb-8 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between'>
61+
<div>
62+
<h1 className='text-4xl font-bold text-white'>Dashboard</h1>
63+
<p className='mt-2 text-sm text-white/60'>
64+
Welcome back, {session.user.name || session.user.email}
65+
</p>
66+
</div>
67+
<Button
68+
onClick={handleSignOut}
69+
variant='outline'
70+
className='border-white/20 bg-white/5 text-white/80 transition-all duration-200 hover:border-white/30 hover:bg-white/10 hover:text-white'
71+
>
5572
<LogOut className='mr-2 h-4 w-4' />
5673
Sign Out
5774
</Button>
5875
</div>
5976

77+
{/* Stats Cards Grid */}
6078
<div className='grid gap-6 md:grid-cols-2 lg:grid-cols-3'>
61-
<Card>
62-
<CardHeader>
63-
<CardTitle className='flex items-center'>
64-
<User className='mr-2 h-5 w-5' />
79+
{/* Profile Card */}
80+
<Card className='group relative overflow-hidden border-white/10 bg-gradient-to-br from-white/5 via-white/5 to-transparent backdrop-blur-xl transition-all duration-300 hover:border-white/20 hover:shadow-lg hover:shadow-[#a7f950]/10'>
81+
<div className='pointer-events-none absolute inset-0 rounded-xl bg-gradient-to-br from-white/5 via-transparent to-white/10 opacity-50'></div>
82+
<CardHeader className='relative z-10'>
83+
<CardTitle className='flex items-center text-white'>
84+
<div className='mr-3 flex h-10 w-10 items-center justify-center rounded-lg bg-[#a7f950]/10'>
85+
<User className='h-5 w-5 text-[#a7f950]' />
86+
</div>
6587
Profile
6688
</CardTitle>
6789
</CardHeader>
68-
<CardContent>
90+
<CardContent className='relative z-10'>
6991
<div className='flex items-center space-x-4'>
70-
<Avatar className='h-12 w-12'>
92+
<Avatar className='h-14 w-14 border-2 border-white/10'>
7193
<AvatarImage src={session.user.image || ''} />
72-
<AvatarFallback>
94+
<AvatarFallback className='bg-white/10 text-white'>
7395
{session.user.name?.charAt(0) ||
7496
session.user.email.charAt(0)}
7597
</AvatarFallback>
7698
</Avatar>
77-
<div>
78-
<p className='font-medium'>
99+
<div className='min-w-0 flex-1'>
100+
<p className='truncate font-semibold text-white'>
79101
{session.user.name || 'No name'}
80102
</p>
81-
<p className='text-sm text-gray-500'>{session.user.email}</p>
103+
<p className='truncate text-sm text-white/60'>
104+
{session.user.email}
105+
</p>
82106
</div>
83107
</div>
84108
</CardContent>
85109
</Card>
86110

87-
<Card>
88-
<CardHeader>
89-
<CardTitle className='flex items-center'>
90-
<Mail className='mr-2 h-5 w-5' />
111+
{/* Account Details Card */}
112+
<Card className='group relative overflow-hidden border-white/10 bg-gradient-to-br from-white/5 via-white/5 to-transparent backdrop-blur-xl transition-all duration-300 hover:border-white/20 hover:shadow-lg hover:shadow-[#a7f950]/10'>
113+
<div className='pointer-events-none absolute inset-0 rounded-xl bg-gradient-to-br from-white/5 via-transparent to-white/10 opacity-50'></div>
114+
<CardHeader className='relative z-10'>
115+
<CardTitle className='flex items-center text-white'>
116+
<div className='mr-3 flex h-10 w-10 items-center justify-center rounded-lg bg-[#a7f950]/10'>
117+
<Mail className='h-5 w-5 text-[#a7f950]' />
118+
</div>
91119
Account Details
92120
</CardTitle>
93121
</CardHeader>
94-
<CardContent className='space-y-2'>
95-
<div className='flex justify-between'>
96-
<span className='text-sm text-gray-500'>User ID:</span>
97-
<span className='font-mono text-sm'>{session.user.id}</span>
122+
<CardContent className='relative z-10 space-y-3'>
123+
<div className='flex flex-col gap-1'>
124+
<span className='text-xs text-white/50'>User ID</span>
125+
<span className='font-mono text-sm text-white/90'>
126+
{session.user.id}
127+
</span>
98128
</div>
99-
<div className='flex justify-between'>
100-
<span className='text-sm text-gray-500'>Email:</span>
101-
<span className='text-sm'>{session.user.email}</span>
129+
<div className='flex flex-col gap-1'>
130+
<span className='text-xs text-white/50'>Email</span>
131+
<span className='text-sm text-white/90'>
132+
{session.user.email}
133+
</span>
102134
</div>
103135
</CardContent>
104136
</Card>
105137

106-
<Card>
107-
<CardHeader>
108-
<CardTitle className='flex items-center'>
109-
<Shield className='mr-2 h-5 w-5' />
110-
Role & Permissions
138+
{/* Status Card */}
139+
<Card className='group relative overflow-hidden border-white/10 bg-gradient-to-br from-white/5 via-white/5 to-transparent backdrop-blur-xl transition-all duration-300 hover:border-white/20 hover:shadow-lg hover:shadow-[#a7f950]/10'>
140+
<div className='pointer-events-none absolute inset-0 rounded-xl bg-gradient-to-br from-white/5 via-transparent to-white/10 opacity-50'></div>
141+
<CardHeader className='relative z-10'>
142+
<CardTitle className='flex items-center text-white'>
143+
<div className='mr-3 flex h-10 w-10 items-center justify-center rounded-lg bg-[#a7f950]/10'>
144+
<Shield className='h-5 w-5 text-[#a7f950]' />
145+
</div>
146+
Status & Verification
111147
</CardTitle>
112148
</CardHeader>
113-
<CardContent>
114-
<div className='space-y-2'>
115-
<div className='flex justify-between'>
116-
<span className='text-sm text-gray-500'>Role:</span>
117-
<span className='text-sm font-medium capitalize'>
118-
{session.user.emailVerified ? 'Verified' : 'Unverified'}
119-
</span>
149+
<CardContent className='relative z-10 space-y-3'>
150+
<div className='flex items-center justify-between'>
151+
<span className='text-sm text-white/60'>Email Status</span>
152+
<div className='flex items-center gap-2'>
153+
{session.user.emailVerified ? (
154+
<>
155+
<CheckCircle2 className='h-4 w-4 text-[#a7f950]' />
156+
<span className='text-sm font-medium text-[#a7f950]'>
157+
Verified
158+
</span>
159+
</>
160+
) : (
161+
<span className='text-sm font-medium text-white/60'>
162+
Unverified
163+
</span>
164+
)}
120165
</div>
121-
<div className='flex justify-between'>
122-
<span className='text-sm text-gray-500'>Status:</span>
123-
<span className='text-sm text-green-600'>Active</span>
166+
</div>
167+
<div className='flex items-center justify-between'>
168+
<span className='text-sm text-white/60'>Account Status</span>
169+
<div className='flex items-center gap-2'>
170+
<div className='h-2 w-2 rounded-full bg-[#a7f950]'></div>
171+
<span className='text-sm font-medium text-[#a7f950]'>
172+
Active
173+
</span>
124174
</div>
125175
</div>
176+
{(session.user as { lastLoginMethod?: string | null })
177+
?.lastLoginMethod && (
178+
<div className='flex items-center justify-between border-t border-white/10 pt-2'>
179+
<span className='text-sm text-white/60'>
180+
Last Login Method
181+
</span>
182+
<span className='text-sm font-medium text-white/90 capitalize'>
183+
{(() => {
184+
const method = (
185+
session.user as { lastLoginMethod?: string | null }
186+
).lastLoginMethod;
187+
return method === 'google'
188+
? 'Google'
189+
: method === 'email'
190+
? 'Email'
191+
: method || 'N/A';
192+
})()}
193+
</span>
194+
</div>
195+
)}
126196
</CardContent>
127197
</Card>
128198
</div>
129199

200+
{/* Welcome Card */}
130201
<div className='mt-8'>
131-
<Card>
132-
<CardHeader>
133-
<CardTitle>Welcome to Boundless Project</CardTitle>
134-
<CardDescription>
202+
<Card className='group relative overflow-hidden border-white/10 bg-gradient-to-br from-white/10 via-white/5 to-transparent backdrop-blur-xl transition-all duration-300 hover:border-white/20 hover:shadow-xl hover:shadow-[#a7f950]/10'>
203+
<div className='pointer-events-none absolute inset-0 rounded-xl bg-gradient-to-br from-white/10 via-transparent to-white/5 opacity-50'></div>
204+
<div className='pointer-events-none absolute inset-0 rounded-xl bg-gradient-to-br from-[#a7f950]/5 via-transparent to-transparent opacity-30'></div>
205+
<CardHeader className='relative z-10'>
206+
<CardTitle className='text-2xl text-white'>
207+
Welcome to Boundless
208+
</CardTitle>
209+
<CardDescription className='text-white/60'>
135210
Your platform for crowdfunding and grants
136211
</CardDescription>
137212
</CardHeader>
138-
<CardContent>
139-
<p className='text-gray-600'>
213+
<CardContent className='relative z-10'>
214+
<p className='leading-relaxed text-white/80'>
140215
This is your dashboard where you can manage your projects, view
141216
contributions, and access all the features of the platform. The
142217
authentication system is now working properly!

components/auth/GoogleOneTap.tsx

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
'use client';
2+
3+
import { useEffect, useRef } from 'react';
4+
import { useAuthStatus } from '@/hooks/use-auth';
5+
import { authClient } from '@/lib/auth-client';
6+
7+
export function GoogleOneTap() {
8+
const { isAuthenticated, isLoading } = useAuthStatus();
9+
const hasTriggeredRef = useRef(false);
10+
11+
useEffect(() => {
12+
// Don't trigger if user is authenticated or still loading
13+
if (isAuthenticated || isLoading) {
14+
return;
15+
}
16+
17+
// Only trigger once per component mount
18+
if (hasTriggeredRef.current) {
19+
return;
20+
}
21+
22+
// Small delay to ensure page is fully loaded
23+
const timer = setTimeout(() => {
24+
hasTriggeredRef.current = true;
25+
26+
// Call One Tap to show the popup
27+
authClient
28+
.oneTap({
29+
callbackURL: window.location.href,
30+
fetchOptions: {
31+
onSuccess: () => {
32+
// On success, reload the page to reflect auth state
33+
// Better Auth will handle the redirect via callbackURL
34+
window.location.reload();
35+
},
36+
onError: () => {
37+
// Silently handle errors - user may have dismissed the prompt
38+
},
39+
},
40+
onPromptNotification: () => {
41+
// Called when max attempts are reached
42+
// User has dismissed/skipped the prompt multiple times
43+
// You could show an alternative sign-in button here if needed
44+
},
45+
})
46+
.catch(() => {
47+
// Silently handle errors in calling oneTap
48+
});
49+
}, 1000); // 1 second delay for better UX
50+
51+
return () => {
52+
clearTimeout(timer);
53+
};
54+
}, [isAuthenticated, isLoading]);
55+
56+
// This component doesn't render anything visible
57+
// Google One Tap handles its own UI in the top-right corner
58+
return null;
59+
}

0 commit comments

Comments
 (0)