Skip to content

Commit fa0283d

Browse files
committed
refactor: simplify authentication flow and enhance profile data handling
- Replaced server-side user fetching with client-side session management using authClient. - Updated proxy function to streamline authentication checks and redirection. - Introduced new types for custom session responses to improve type safety. - Cleaned up ProfileData and ProfileDataClient components for better readability and performance.
1 parent 6557c91 commit fa0283d

6 files changed

Lines changed: 184 additions & 106 deletions

File tree

app/(landing)/me/profile-data.tsx

Lines changed: 16 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,22 @@
1-
import { getMeServer } from '@/lib/api/auth-server';
2-
import { getServerUser } from '@/lib/auth/server-auth';
1+
'use client';
32
import ProfileDataClient from '@/components/profile/ProfileDataClient';
3+
import { authClient } from '@/lib/auth-client';
44

5-
export async function ProfileData() {
6-
const user = await getServerUser();
7-
8-
// Check if user is authenticated
9-
if (!user) {
10-
return (
11-
<section className='flex min-h-screen items-center justify-center'>
12-
<div className='text-red-500'>Please sign in to view your profile</div>
13-
</section>
14-
);
5+
export function ProfileData() {
6+
const { data, error, isPending } = authClient.useSession();
7+
if (isPending) {
8+
return <div>Loading...</div>;
9+
}
10+
if (error) {
11+
return <div>Error: {error.message}</div>;
12+
}
13+
if (!data?.user) {
14+
return <div>Please sign in to view your profile</div>;
1515
}
1616

17-
try {
18-
// Fetch user profile data - Use server-side version that forwards cookies from request headers
19-
const userData = await getMeServer();
20-
21-
// Extract username from user data with proper fallback
22-
const username =
23-
userData.profile?.username || userData._id || user.id || 'me';
24-
25-
return <ProfileDataClient user={userData} username={username} />;
26-
} catch (error) {
27-
// Handle authentication errors
28-
if (
29-
error &&
30-
typeof error === 'object' &&
31-
'status' in error &&
32-
(error.status === 401 || error.status === 403)
33-
) {
34-
return (
35-
<section className='flex min-h-screen items-center justify-center'>
36-
<div className='text-red-500'>
37-
Session expired. Please sign in again.
38-
</div>
39-
</section>
40-
);
41-
}
42-
43-
// Handle other errors
44-
const errorMessage =
45-
error instanceof Error ? error.message : JSON.stringify(error);
17+
const username =
18+
data.user.profile?.username || data.user._id || data.user.id || 'me';
4619

47-
return (
48-
<section className='flex min-h-screen items-center justify-center'>
49-
<div className='text-red-500'>{errorMessage}</div>
50-
</section>
51-
);
52-
}
20+
// Type assertion since we know the session user now matches GetMeResponse structure
21+
return <ProfileDataClient user={data.user as any} username={username} />;
5322
}

components/auth/SignupForm.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ const SignupForm = ({
8282
password: string;
8383
name: string;
8484
invitation?: string;
85+
roles: string[];
8586
};
8687

8788
const { error } = await authClient.signUp.email(signUpData, {

components/profile/ProfileDataClient.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
import { Button } from '../ui/button';
1919
import ActivityHeatmap from './ActivityHeatMap';
2020
import { FutureFeature } from '../FeatureFuture';
21-
import { useAuthStatus } from '@/hooks/use-auth';
21+
import { authClient } from '@/lib/auth-client';
2222

2323
interface ProfileDataClientProps {
2424
user: GetMeResponse;
@@ -41,12 +41,16 @@ export default function ProfileDataClient({
4141
}: ProfileDataClientProps) {
4242
const [selectedFilter, setSelectedFilter] = useState('All');
4343

44-
// Get auth state from simplified hook
45-
const { user: authUser, isAuthenticated } = useAuthStatus();
44+
// Get current session user to determine if it's the user's own profile
45+
const { data: session } = authClient.useSession();
46+
const currentUser = session?.user;
4647

47-
// Determine if it's the user's own profile
48+
// Determine if it's the user's own profile by comparing IDs
4849
const isOwnProfile =
49-
isAuthenticated && authUser?.profile?.username === username;
50+
currentUser &&
51+
(currentUser.id === user._id ||
52+
currentUser._id === user._id ||
53+
currentUser.email === user.email);
5054

5155
const organizationsData =
5256
user.organizations?.map(org => ({
@@ -59,7 +63,7 @@ export default function ProfileDataClient({
5963
<ProfileOverview
6064
username={username}
6165
user={user}
62-
isAuthenticated={isAuthenticated}
66+
isAuthenticated={!!currentUser}
6367
isOwnProfile={isOwnProfile}
6468
/>
6569

lib/auth-client.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { createAuthClient } from 'better-auth/react';
22
import {
33
emailOTPClient,
4+
inferAdditionalFields,
45
lastLoginMethodClient,
56
oneTapClient,
67
organizationClient,
@@ -16,6 +17,28 @@ const getAuthBaseURL = () => {
1617
export const authClient = createAuthClient({
1718
baseURL: getAuthBaseURL(),
1819
plugins: [
20+
inferAdditionalFields({
21+
user: {
22+
profile: { type: 'json', required: false },
23+
organizations: { type: 'json', required: false },
24+
stats: { type: 'json', required: false },
25+
following: { type: 'json', required: false },
26+
followers: { type: 'json', required: false },
27+
projects: { type: 'json', required: false },
28+
activities: { type: 'json', required: false },
29+
_id: { type: 'string', required: false },
30+
isVerified: { type: 'boolean', required: false },
31+
contributedProjects: { type: 'json', required: false },
32+
createdAt: { type: 'date', required: false },
33+
updatedAt: { type: 'date', required: false },
34+
__v: { type: 'number', required: false },
35+
lastLogin: { type: 'string', required: false },
36+
isProfileComplete: { type: 'boolean', required: false },
37+
missingProfileFields: { type: 'string[]', required: false },
38+
profileCompletionPercentage: { type: 'number', required: false },
39+
roles: { type: 'json', required: true },
40+
},
41+
}),
1942
emailOTPClient(),
2043
lastLoginMethodClient(),
2144
oneTapClient({

proxy.ts

Lines changed: 9 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,15 @@
1-
import { NextRequest, NextResponse } from 'next/server';
2-
import { getBetterAuthSession } from '@/lib/auth/server-auth';
3-
4-
const protectedRoutes = ['/dashboard', '/user', '/admin'];
5-
6-
const authRoutes = ['/auth', '/auth/signup', '/auth/forgot-password'];
7-
8-
export async function proxy(req: NextRequest) {
9-
const { pathname } = req.nextUrl;
10-
11-
const cookieHeader = req.headers.get('cookie') || '';
12-
let isAuthenticated = false;
13-
try {
14-
const betterAuthSession = await getBetterAuthSession(cookieHeader);
15-
if (betterAuthSession?.user) {
16-
isAuthenticated = true;
17-
}
18-
} catch {
19-
isAuthenticated = false;
20-
}
21-
22-
const isProtectedRoute = protectedRoutes.some(route =>
23-
pathname.startsWith(route)
24-
);
25-
26-
const isAuthRoute = authRoutes.some(route => pathname.startsWith(route));
27-
28-
if (isAuthRoute && isAuthenticated) {
29-
return NextResponse.redirect(new URL('/', req.url));
30-
}
31-
32-
if (isProtectedRoute && !isAuthenticated) {
33-
const signinUrl = new URL('/auth', req.url);
34-
signinUrl.searchParams.set('callbackUrl', pathname);
35-
return NextResponse.redirect(signinUrl);
1+
import { getSessionCookie } from 'better-auth/cookies';
2+
import type { NextRequest } from 'next/server';
3+
import { NextResponse } from 'next/server';
4+
5+
export async function proxy(request: NextRequest) {
6+
const cookies = getSessionCookie(request);
7+
if (!cookies) {
8+
return NextResponse.redirect(new URL('/auth?mode=signin', request.url));
369
}
37-
38-
// if (isOtherUserProfile && !isAuthenticated) {
39-
// const signinUrl = new URL('/auth', req.url);
40-
// signinUrl.searchParams.set('callbackUrl', pathname);
41-
// return NextResponse.redirect(signinUrl);
42-
// }
43-
4410
return NextResponse.next();
4511
}
4612

4713
export const config = {
48-
matcher: [
49-
/*
50-
* Match all request paths except for the ones starting with:
51-
* - api (API routes)
52-
* - _next/static (static files)
53-
* - _next/image (image optimization files)
54-
* - favicon.ico (favicon file)
55-
* - public folder
56-
*/
57-
'/((?!api|_next/static|_next/image|favicon.ico|public).*)',
58-
],
14+
matcher: ['/organizations', '/me'],
5915
};

types/auth.ts

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// Types for custom session response
2+
3+
export interface UserProfile {
4+
firstName?: string;
5+
lastName?: string;
6+
username?: string;
7+
avatar?: string;
8+
bio?: string;
9+
location?: string;
10+
website?: string;
11+
socialLinks?: any; // You may want to define a more specific type for social links
12+
}
13+
14+
export interface UserStats {
15+
projectsCreated: number;
16+
projectsFunded: number;
17+
totalContributed: number;
18+
reputation: number;
19+
communityScore: number;
20+
commentsPosted: number;
21+
organizations: number;
22+
following: number;
23+
followers: number;
24+
votes: number;
25+
grants: number;
26+
hackathons: number;
27+
donations: number;
28+
}
29+
30+
export interface OrganizationData {
31+
id: string;
32+
name: string;
33+
avatar?: string;
34+
joinedAt: string;
35+
description?: string;
36+
tagline?: string;
37+
isProfileComplete: boolean;
38+
role: 'owner' | 'member';
39+
memberCount: number;
40+
hackathonCount: number;
41+
grantCount: number;
42+
createdAt: string;
43+
}
44+
45+
export interface FollowUser {
46+
id: string;
47+
profile: {
48+
firstName?: string;
49+
lastName?: string;
50+
username?: string;
51+
avatar?: string;
52+
bio?: string;
53+
};
54+
followedAt: Date;
55+
}
56+
57+
export interface ProjectData {
58+
id: string;
59+
name: string;
60+
description?: string;
61+
image?: string;
62+
link: string;
63+
tags: string[];
64+
category?: string;
65+
type?: string;
66+
amount?: number;
67+
status?: string;
68+
createdAt: Date;
69+
updatedAt: Date;
70+
owner?: string;
71+
ownerName?: string;
72+
ownerUsername?: string;
73+
ownerAvatar?: string;
74+
votes: {
75+
current: number;
76+
total: number;
77+
};
78+
daysLeft: number;
79+
progress: number;
80+
}
81+
82+
export interface ActivityData {
83+
id: string;
84+
type: string;
85+
description: string;
86+
projectName: string;
87+
projectId?: string;
88+
amount?: number | null;
89+
timestamp: Date;
90+
image?: string;
91+
emoji: string;
92+
}
93+
94+
export interface ContributedProject {
95+
// Define this based on your actual contributed projects structure
96+
[key: string]: any;
97+
}
98+
99+
export interface CustomSessionUser {
100+
id: string;
101+
email: string;
102+
name?: string;
103+
image?: string;
104+
profile?: UserProfile;
105+
stats?: UserStats;
106+
organizations?: OrganizationData[];
107+
following?: FollowUser[];
108+
followers?: FollowUser[];
109+
projects?: ProjectData[];
110+
activities?: ActivityData[];
111+
_id?: any; // MongoDB ObjectId
112+
isVerified?: boolean;
113+
contributedProjects?: ContributedProject[];
114+
createdAt?: Date;
115+
updatedAt?: Date;
116+
__v?: number;
117+
lastLogin?: Date;
118+
isProfileComplete?: boolean;
119+
missingProfileFields?: string[];
120+
profileCompletionPercentage?: number;
121+
}
122+
123+
export interface CustomSessionResponse {
124+
user: CustomSessionUser;
125+
}

0 commit comments

Comments
 (0)