Skip to content

Commit 3d8e184

Browse files
committed
fix: minor changes
1 parent 6fb6248 commit 3d8e184

12 files changed

Lines changed: 424 additions & 42 deletions

File tree

app/(landing)/me/layout.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Metadata } from 'next';
2+
3+
export const metadata: Metadata = {
4+
title: 'My Profile | Boundless',
5+
description: 'View and manage your profile on Boundless',
6+
};
7+
8+
export default function MeLayout({ children }: { children: React.ReactNode }) {
9+
return (
10+
<div className='min-h-screen flex-1 px-6 py-5 md:px-10 md:py-20 xl:px-[100px]'>
11+
{children}
12+
</div>
13+
);
14+
}

app/(landing)/me/loading.tsx

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { Skeleton } from '@/components/ui/skeleton';
2+
3+
export default function MeLoading() {
4+
return (
5+
<section className='min-h-screen'>
6+
<article className='flex w-[500px] flex-col gap-11 text-white'>
7+
{/* Profile Header Skeleton */}
8+
<main className='flex flex-col gap-6'>
9+
<header className='flex items-end gap-4'>
10+
<Skeleton className='aspect-square size-[150px] rounded-full' />
11+
<div className='flex flex-col gap-3 py-3'>
12+
<Skeleton className='h-8 w-48' />
13+
<Skeleton className='h-4 w-32' />
14+
</div>
15+
</header>
16+
<Skeleton className='h-4 w-full' />
17+
<Skeleton className='h-4 w-3/4' />
18+
19+
{/* Social Links Skeleton */}
20+
<div className='flex items-center space-x-4'>
21+
{[1, 2, 3, 4].map(i => (
22+
<Skeleton key={i} className='h-6 w-6 rounded' />
23+
))}
24+
</div>
25+
26+
{/* Buttons Skeleton */}
27+
<div className='flex gap-4'>
28+
<Skeleton className='h-10 w-24' />
29+
<Skeleton className='h-10 w-20' />
30+
</div>
31+
</main>
32+
33+
{/* Stats Skeleton */}
34+
<div className='flex items-center gap-8'>
35+
{[1, 2, 3, 4].map(i => (
36+
<div key={i} className='flex items-center gap-1'>
37+
<Skeleton className='h-4 w-8' />
38+
<Skeleton className='h-3 w-16' />
39+
</div>
40+
))}
41+
</div>
42+
43+
{/* Organizations Skeleton */}
44+
<main className='flex flex-col gap-3'>
45+
<Skeleton className='h-4 w-32' />
46+
<div className='flex flex-col gap-3'>
47+
{[1, 2].map(i => (
48+
<div key={i} className='flex items-center gap-3 px-3'>
49+
<Skeleton className='size-[46px] rounded-full' />
50+
<Skeleton className='h-4 w-32' />
51+
</div>
52+
))}
53+
</div>
54+
</main>
55+
</article>
56+
</section>
57+
);
58+
}

app/(landing)/me/page.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { ProfileData } from './profile-data';
2+
3+
export default async function MePage() {
4+
return (
5+
<section className='min-h-[70vh]'>
6+
<ProfileData />
7+
</section>
8+
);
9+
}

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

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { getMe } from '@/lib/api/auth';
2+
import { auth } from '@/auth';
3+
import ProfileOverview from '@/components/profile/ProfileOverview';
4+
5+
export async function ProfileData() {
6+
const session = await auth();
7+
8+
// Check if user is authenticated
9+
if (!session?.user?.accessToken) {
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+
);
15+
}
16+
17+
try {
18+
const userData = await getMe(session.user.accessToken);
19+
return (
20+
<ProfileOverview
21+
username={session.user.username || 'me'}
22+
user={userData}
23+
/>
24+
);
25+
} catch (error) {
26+
// Check if it's an authentication error
27+
if (
28+
error &&
29+
typeof error === 'object' &&
30+
'status' in error &&
31+
error.status === 401
32+
) {
33+
return (
34+
<section className='flex min-h-screen items-center justify-center'>
35+
<div className='text-red-500'>
36+
Session expired. Please sign in again.
37+
</div>
38+
</section>
39+
);
40+
}
41+
42+
return (
43+
<section className='flex min-h-screen items-center justify-center'>
44+
<div className='text-red-500'>Failed to load your profile</div>
45+
</section>
46+
);
47+
}
48+
}

app/(landing)/profile/[username]/profile-data.tsx

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,48 @@ interface ProfileDataProps {
88
}
99

1010
export async function ProfileData({ username }: ProfileDataProps) {
11+
const session = await auth();
12+
13+
// Check if user is authenticated
14+
if (!session?.user?.accessToken) {
15+
return (
16+
<section className='flex min-h-screen items-center justify-center'>
17+
<div className='text-red-500'>Please sign in to view profiles</div>
18+
</section>
19+
);
20+
}
21+
1122
try {
12-
const session = await auth();
1323
const isOwnProfile = session?.user?.username === username;
1424
let userData: GetMeResponse;
1525

16-
if (isOwnProfile && session?.user?.accessToken) {
26+
if (isOwnProfile) {
1727
userData = await getMe(session.user.accessToken);
1828
} else {
1929
userData = await getUserProfileByUsername(
2030
username,
21-
session?.user?.accessToken
31+
session.user.accessToken
2232
);
2333
}
2434

2535
return <ProfileOverview username={username} user={userData} />;
26-
} catch {
36+
} catch (error) {
37+
// Check if it's an authentication error
38+
if (
39+
error &&
40+
typeof error === 'object' &&
41+
'status' in error &&
42+
error.status === 401
43+
) {
44+
return (
45+
<section className='flex min-h-screen items-center justify-center'>
46+
<div className='text-red-500'>
47+
Session expired. Please sign in again.
48+
</div>
49+
</section>
50+
);
51+
}
52+
2753
return (
2854
<section className='flex min-h-screen items-center justify-center'>
2955
<div className='text-red-500'>Failed to load user profile</div>

auth.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,63 @@ export const authConfig = {
228228
token.username = extendedUser.username;
229229
token.profile = extendedUser.profile;
230230
}
231+
232+
// Check if access token is expired and try to refresh
233+
if (token.accessToken && typeof token.accessToken === 'string') {
234+
try {
235+
const base64Url = token.accessToken.split('.')[1];
236+
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
237+
const jsonPayload = decodeURIComponent(
238+
atob(base64)
239+
.split('')
240+
.map(function (c) {
241+
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
242+
})
243+
.join('')
244+
);
245+
const payload = JSON.parse(jsonPayload);
246+
const currentTime = Math.floor(Date.now() / 1000);
247+
248+
// If token is expired and we have a refresh token, try to refresh
249+
if (payload.exp < currentTime && token.refreshToken) {
250+
try {
251+
const response = await fetch(
252+
`${process.env.NEXT_PUBLIC_API_URL}/auth/refresh`,
253+
{
254+
method: 'POST',
255+
headers: {
256+
'Content-Type': 'application/json',
257+
},
258+
body: JSON.stringify({ refreshToken: token.refreshToken }),
259+
}
260+
);
261+
262+
if (response.ok) {
263+
const data = await response.json();
264+
token.accessToken = data.data.accessToken;
265+
token.refreshToken = data.data.refreshToken;
266+
AuthLogger.log('Token refreshed successfully');
267+
} else {
268+
// Refresh failed, clear tokens
269+
token.accessToken = null;
270+
token.refreshToken = null;
271+
AuthLogger.warn('Token refresh failed');
272+
}
273+
} catch (error) {
274+
// Refresh failed, clear tokens
275+
token.accessToken = null;
276+
token.refreshToken = null;
277+
AuthLogger.error('Token refresh error', error as Error);
278+
}
279+
}
280+
} catch (error) {
281+
// Token parsing failed, clear tokens
282+
token.accessToken = null;
283+
token.refreshToken = null;
284+
AuthLogger.error('Token parsing error', error as Error);
285+
}
286+
}
287+
231288
return token;
232289
},
233290

@@ -253,6 +310,11 @@ export const authConfig = {
253310
};
254311
};
255312

313+
// If no valid tokens, return empty session
314+
if (!extendedToken.accessToken) {
315+
return session;
316+
}
317+
256318
// Populate session with extended user data
257319
session.user = {
258320
...session.user,

components/landing-page/Hero.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export default function Hero({ className = '' }: HeroProps) {
5656
});
5757

5858
// Rotation animation per ellipse
59-
const direction = i === 0 || i === 1 ? 2 : -1;
59+
const direction = i === 1 || i === 1 ? 2 : -1;
6060
gsap.to(img, {
6161
rotation: direction * 360,
6262
ease: 'none',
@@ -93,21 +93,21 @@ export default function Hero({ className = '' }: HeroProps) {
9393
const ellipses = [
9494
{
9595
src: '/elipse1.svg',
96-
width: 'w-[50%]',
96+
width: 'w-[35%]',
9797
z: 'z-10',
9898
opacity: '',
9999
sizes: '50vw',
100100
},
101101
{
102102
src: '/elipse2.svg',
103-
width: 'w-[65%]',
103+
width: 'w-[45%]',
104104
z: 'z-10',
105105
opacity: '',
106106
sizes: '90vw',
107107
},
108108
{
109109
src: '/elipse3.svg',
110-
width: 'w-[80%]',
110+
width: 'w-[55%]',
111111
z: 'z-10',
112112
opacity: '',
113113
sizes: '100vw',

components/landing-page/LooperSVG.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export default function LooperSVG() {
1313
ellipses.forEach(ellipse => {
1414
ellipse.addEventListener('mouseenter', () => {
1515
gsap.to(ellipse, {
16-
scale: 1.2,
16+
scale: 1.1,
1717
duration: 0.3,
1818
ease: 'power2.out',
1919
transformOrigin: 'center center',
@@ -40,7 +40,7 @@ export default function LooperSVG() {
4040
viewBox='0 0 403 452'
4141
fill='none'
4242
xmlns='http://www.w3.org/2000/svg'
43-
className='sphere-image absolute top-1/2 left-1/2 z-20 h-[75%] w-[75%] -translate-x-1/2 -translate-y-1/2 opacity-70'
43+
className='sphere-image absolute top-1/2 left-1/2 z-20 h-[50%] w-[50%] -translate-x-1/2 -translate-y-1/2 opacity-70'
4444
>
4545
<ellipse
4646
opacity='0.0169492'

0 commit comments

Comments
 (0)