Skip to content

Commit 3e0a9fb

Browse files
authored
Migrate project to next 16 and update pkgs (#33)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **New Features** * Added OAuth authentication callback support for GitHub login integration * Introduced async parameter handling for improved routing * **Bug Fixes** * Improved session synchronization and dark mode initialization * Enhanced GitHub OAuth redirect URL handling * **Performance & Infrastructure** * Updated core dependencies including Next.js 16 and Node 20 support * Modernized ESLint configuration and added turbopack support <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 802d713 commit 3e0a9fb

59 files changed

Lines changed: 8206 additions & 5795 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.eslintignore

Lines changed: 0 additions & 2 deletions
This file was deleted.

.eslintrc.js

Lines changed: 0 additions & 8 deletions
This file was deleted.

.github/workflows/ci.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ jobs:
3333
steps:
3434
- uses: actions/checkout@v4
3535

36-
- uses: actions/setup-node@v3
36+
- uses: actions/setup-node@v4
3737
with:
38-
# update the Node version to meet your needs
39-
node-version: 18
38+
# Next.js 16 requires Node >= 20.9
39+
node-version: '20'
4040

4141
- uses: oven-sh/setup-bun@v1
4242
with:
@@ -49,10 +49,10 @@ jobs:
4949
run: bun install
5050

5151
- name: Check lint
52-
run: bun lint
52+
run: bun run lint
5353

5454
- name: Test
55-
run: bun run test --coverage
55+
run: bun run test --run --coverage
5656

5757
- name: Upload coverage to Codecov
5858
uses: codecov/codecov-action@v2

app/[lang]/(common)/Button.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ const Button: FC<ButtonProps> = ({
6161
>
6262
<>
6363
{startElement
64-
? cloneElement(startElement, {
64+
? cloneElement(startElement as ReactElement<any>, {
6565
style: {
6666
position: 'absolute',
6767
left: 0,

app/[lang]/(common)/Dropdown.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@ function Dropdown<T extends {domain: string; icon: ReactElement}>({
1717
setSelected,
1818
data = [],
1919
}: DropdownProps<T>): ReactElement {
20-
const dropdownRef = useRef<HTMLDivElement>(null);
20+
const dropdownRef = useRef<HTMLDivElement>(null!);
2121
const [isOpened, setIsOpened] = useState(false);
2222

2323
const handleClickOutside = (): void => {
2424
setIsOpened(false);
2525
};
2626

27-
useOnClickOutside(dropdownRef, handleClickOutside);
27+
useOnClickOutside<HTMLDivElement>(dropdownRef, handleClickOutside);
2828

2929
const onClick: MouseEventHandler<HTMLButtonElement> | undefined = (
3030
e,

app/[lang]/(common)/GreatFrontEnd.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use client';
22

3+
import type {ReactElement} from 'react';
34
import React from 'react';
45
import Image from 'next/image';
56
import clsx from 'clsx';
@@ -16,7 +17,7 @@ export default function GreatFrontEnd({
1617
className,
1718
href,
1819
title,
19-
}: GreatFrontEndProps): JSX.Element {
20+
}: GreatFrontEndProps): ReactElement {
2021
return (
2122
<a
2223
href={href}

app/[lang]/(common)/Header/index.tsx

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
'use client';
22

33
import type {Dispatch, ReactElement, SetStateAction} from 'react';
4-
import {useEffect, useState} from 'react';
4+
import {useEffect, useMemo, useState} from 'react';
5+
import type {Session} from '@supabase/supabase-js';
56
import HamburgerMenu from 'react-hamburger-menu';
67
import clsx from 'clsx';
78
import {Inter} from 'next/font/google';
@@ -232,9 +233,9 @@ function MobileNavMenus(
232233

233234
export default function Header(props: Props): ReactElement {
234235
const {t, lang} = props;
235-
const supabase = getSupabaseBrowserClient();
236+
const supabase = useMemo(() => getSupabaseBrowserClient(), []);
236237

237-
const [isDark, setIsDark] = useState(false);
238+
const [isDark, setIsDark] = useState(() => isDarkMode());
238239
const {login, changeLogin} = useAuthContext();
239240

240241
const navLinks: NavLink[] = !!login
@@ -260,20 +261,44 @@ export default function Header(props: Props): ReactElement {
260261
];
261262

262263
useEffect(() => {
264+
const applySession = (session: Session | null): void => {
265+
const username = session?.user.user_metadata?.user_name || '';
266+
267+
changeLogin(username);
268+
};
269+
270+
const syncSession = async (): Promise<void> => {
271+
const {
272+
data: {session},
273+
} = await supabase.auth.getSession();
274+
275+
applySession(session);
276+
};
277+
278+
void syncSession();
279+
263280
const {
264281
data: {subscription},
265282
} = supabase.auth.onAuthStateChange((evt, session) => {
266-
const isLoggedIn = !!session?.access_token && evt === 'SIGNED_IN';
267-
changeLogin(isLoggedIn && session.user.user_metadata.user_name);
283+
if (evt === 'SIGNED_OUT') {
284+
changeLogin('');
285+
286+
return;
287+
}
288+
289+
if (
290+
evt === 'INITIAL_SESSION' ||
291+
evt === 'SIGNED_IN' ||
292+
evt === 'TOKEN_REFRESHED'
293+
) {
294+
applySession(session);
295+
}
268296
});
269297

270298
return () => {
271299
subscription.unsubscribe();
272300
};
273-
// eslint-disable-next-line react-hooks/exhaustive-deps
274-
}, []);
275-
276-
useEffect(() => setIsDark(isDarkMode()), []);
301+
}, [changeLogin, supabase]);
277302

278303
return (
279304
<header

app/[lang]/(home)/Hero/StatsSymbol.tsx

Lines changed: 36 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
'use client';
22

33
import type {CSSProperties, ReactElement} from 'react';
4-
import {cloneElement, useState} from 'react';
5-
import TextTransition, {presets} from 'react-text-transition';
4+
import {cloneElement, useEffect, useRef, useState} from 'react';
65
import {track} from '@amplitude/analytics-browser';
76
import clsx from 'clsx';
87

@@ -47,51 +46,37 @@ type StatName = (typeof statTypes)[number]['name'];
4746
const PluginStatsInfo = ({
4847
statsInfo,
4948
selectedStatName,
49+
isTransitioning,
5050
}: {
5151
statsInfo: StatsInfo;
5252
selectedStatName: StatName;
53+
isTransitioning: boolean;
5354
}): React.ReactElement => {
55+
const {name, description} = statsInfo[selectedStatName];
56+
5457
return (
5558
<div
5659
className={clsx(
5760
'ml-[8px] py-[24px] max-w-[600px] w-full',
5861
'flex-1 flex flex-col relative',
5962
)}
6063
>
61-
<TextTransition
62-
springConfig={presets.gentle}
63-
direction="down"
64-
className="body1 font-bold mb-3 text-left"
64+
<div
65+
className={clsx(
66+
'body1 font-bold mb-3 text-left transition-opacity duration-300',
67+
isTransitioning ? 'opacity-0' : 'opacity-100'
68+
)}
6569
>
66-
{selectedStatName === 'fire'
67-
? statsInfo.fire.name
68-
: selectedStatName === 'earth'
69-
? statsInfo.earth.name
70-
: selectedStatName === 'gold'
71-
? statsInfo.gold.name
72-
: selectedStatName === 'water'
73-
? statsInfo.water.name
74-
: selectedStatName === 'people'
75-
? statsInfo.people.name
76-
: statsInfo.tree.name}
77-
</TextTransition>
78-
<TextTransition
79-
className="body3 leading-[160%] text-left"
80-
springConfig={presets.wobbly}
81-
direction="down"
70+
{name}
71+
</div>
72+
<div
73+
className={clsx(
74+
'body3 leading-[160%] text-left transition-opacity duration-500',
75+
isTransitioning ? 'opacity-0' : 'opacity-100'
76+
)}
8277
>
83-
{selectedStatName === 'fire'
84-
? statsInfo.fire.description
85-
: selectedStatName === 'earth'
86-
? statsInfo.earth.description
87-
: selectedStatName === 'gold'
88-
? statsInfo.gold.description
89-
: selectedStatName === 'water'
90-
? statsInfo.water.description
91-
: selectedStatName === 'people'
92-
? statsInfo.people.description
93-
: statsInfo.tree.description}
94-
</TextTransition>
78+
{description}
79+
</div>
9580
</div>
9681
);
9782
};
@@ -106,12 +91,28 @@ const StatsSymbols = ({
10691
statsInfo: StatsInfo;
10792
}): ReactElement => {
10893
const [selectedStatName, setSelectedStatName] = useState<StatName>('tree');
94+
const [isTransitioning, setIsTransitioning] = useState(false);
95+
const transitionTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
10996

11097
const pressStat = (type: StatName): void => {
98+
if (transitionTimer.current) {
99+
clearTimeout(transitionTimer.current);
100+
}
101+
102+
setIsTransitioning(true);
103+
transitionTimer.current = setTimeout(() => setIsTransitioning(false), 50);
111104
setSelectedStatName(type);
112105
track('Press Stat Info', {type});
113106
};
114107

108+
useEffect(() => {
109+
return () => {
110+
if (transitionTimer.current) {
111+
clearTimeout(transitionTimer.current);
112+
}
113+
};
114+
}, []);
115+
115116
return (
116117
<div className={`flex flex-col ${className}`} style={style}>
117118
<div className={clsx('max-w-[220px]', 'flex flex-row')}>
@@ -138,6 +139,7 @@ const StatsSymbols = ({
138139
<PluginStatsInfo
139140
selectedStatName={selectedStatName}
140141
statsInfo={statsInfo}
142+
isTransitioning={isTransitioning}
141143
/>
142144
</div>
143145
);

app/[lang]/(home)/Hero/StatsUrlCards.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use client';
22

3+
import type {ReactElement} from 'react';
34
import {CopyToClipboard} from 'react-copy-to-clipboard';
45
import {useSnackbar} from 'react-simple-snackbar';
56
import {track} from '@amplitude/analytics-browser';
@@ -26,7 +27,7 @@ export default function StatsUrlCard({
2627
login,
2728
svgStatsURL,
2829
svgTrophiesURL,
29-
}: Props): JSX.Element {
30+
}: Props): ReactElement {
3031
const router = useRouter();
3132
const [openSnackbar] = useSnackbar();
3233
const {login: authLogin} = useAuthContext();

app/[lang]/(home)/SectionFooter.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,11 @@ export default function SectionFooter({t}: Props): ReactElement {
4747
{t.enjoyStats}
4848
</p>
4949
<a
50-
href="https://github.com/hyochan"
51-
target="_blank"
52-
rel="noopener noreferrer"
5350
className="cursor-pointer"
51+
href="https://github.com/hyochan/github-stats"
52+
aria-label="github-stats scouter"
53+
target="_blank"
54+
rel="noreferrer"
5455
>
5556
<SvgScouter className="w-screen px-12 max-w-3xl" />
5657
</a>

0 commit comments

Comments
 (0)