-
Notifications
You must be signed in to change notification settings - Fork 0
Feature/search result2 #11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Walkthrough๊ฒ์ ๊ฒฐ๊ณผ ๊ธฐ๋ฅ์ ์ฌ๊ตฌ์ฑํด ๊ฒฐ๊ณผ ๊ฒฝ๋ก๋ฅผ Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant Home as Home(/)
participant Form as ServerSearchForm
participant Sites as SiteAPI
participant Router as Next Router
participant Result as /search-result
participant TimeAPI as /api/time/compare
User->>Home: ํ์ด์ง ๋ก๋
Home->>Form: ๋ ๋๋ง (onSubmit ์ ๊ณต)
User->>Form: URL ๋๋ ํค์๋ ์
๋ ฅ โ ์ ์ถ
Form->>Sites: searchSites(term, autoDiscover)
Sites-->>Form: SiteSearchResult (์ฑ๊ณต/์คํจ)
alt ์ฌ์ดํธ ๊ฒ์ ์ฑ๊ณต
Form->>Router: push('/search-result?url=...')
Router-->>Result: ํ์ด์ง ๋ก๋
Result->>TimeAPI: POST /api/time/compare(finalUrl, metadata)
TimeAPI-->>Result: ์๋ฒ์๊ฐ ๋น๊ต ๊ฒฐ๊ณผ ๋ฐ ๋คํธ์ํฌ ๋ฉํ
Result-->>User: ์๋ฒ์๊ฐ, ์ฐจ์ด, ์ธ๋ถ์ ๋ณด ๋ ๋๋ง
else ์คํจ/๊ฒฐ๊ณผ ์์
Form-->>User: ์๋ฌ ๋ฉ์์ง ๋
ธ์ถ
end
sequenceDiagram
autonumber
actor User
participant Result as ServerTimeResult
participant AModal as AlarmModal
participant ACount as AlarmCountdown
User->>Result: "์๋ ์ค์ " ํด๋ฆญ
Result->>AModal: ๋ชจ๋ฌ ์คํ
User->>AModal: ์/๋ถ/์ด ๋ฐ ์ต์
์ ํ
AModal-->>Result: onConfirm(AlarmData with targetTime)
Result->>ACount: ์นด์ดํธ๋ค์ด ์์ (์๋ฆผ/์ฌ์ ์๋ฆผ ํธ๋ฆฌ๊ฑฐ)
ACount-->>User: ์ค์๊ฐ ๋จ์์๊ฐ ์
๋ฐ์ดํธ / ์๋ฆผ ์คํ
Estimated code review effort๐ฏ 4 (Complex) | โฑ๏ธ ~60 minutes Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touchesโ Failed checks (1 warning)
โ Passed checks (2 passed)
โจ Finishing touches
๐งช Generate unit tests
๐ Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro ๐ Files selected for processing (1)
๐ง Files skipped from review as they are similar to previous changes (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and canโt be posted inline due to platform limitations.
โ ๏ธ Outside diff range comments (1)
src/app/bookmarks/page.tsx (1)
112-112: ๊ฒฝ๋ก ๋ณ๊ฒฝ ๋๋ฝ: /result โ /search-result๊ฒ์ ๊ฒฐ๊ณผ ๊ฒฝ๋ก๊ฐ /search-result๋ก ๋ณ๊ฒฝ๋์์ผ๋ ์๋ ์์น๋ค์ด ์ฌ์ ํ /result๋ก ์ด๋ฆฝ๋๋ค. ๋ชจ๋ /search-result๋ก ์์ ํ์ธ์.
- src/app/bookmarks/page.tsx (line 112)
- window.open(`/result?url=${encodeURIComponent(selectedBookmark.custom_url)}`, '_blank'); + window.open(`/search-result?url=${encodeURIComponent(selectedBookmark.custom_url)}`, '_blank');
- src/components/bookmarks/BookmarkList.tsx (line 89)
- window.open(`/result?url=${encodeURIComponent(selectedBookmark.custom_url)}`, '_blank'); + window.open(`/search-result?url=${encodeURIComponent(selectedBookmark.custom_url)}`, '_blank');
๐งน Nitpick comments (17)
src/components/ClientHeader.tsx (5)
78-86: ํ๋ก ํธ ๋ณ์๋ช ๋ username์ผ๋ก ํต์ผ ์ ์ํ๋ก ํธ ๋ด๋ถ ๋ณ์๋
username์ผ๋ก ๋ง์ถ๋ฉด ๋งคํ ํผ๋์ ์ค์ผ ์ ์์ต๋๋ค.- async function handleSignupSubmit({ - userName, + async function handleSignupSubmit({ + username, email, password, }: { - userName: string; + username: string; email: string; password: string; }) { @@ - body: JSON.stringify({ username: userName, email, password }), + body: JSON.stringify({ username, email, password }),Also applies to: 93-94
31-41: ์คํ ๋ฆฌ์ง ๋๊ธฐํ ๋ฒ์ ํ์ฅ๋ค๋ฅธ ํญ์์
userName๋ง ๋ณ๊ฒฝ๋๋ ๊ฒฝ์ฐ ๋ฐ์์ด ๋ฆ์ ์ ์์ต๋๋ค. ๋ ํค ๋ชจ๋์ ๋ฐ์ํ๋๋ก ๋ณด์์ ๊ถ์ฅํฉ๋๋ค.- const onStorage = (e: StorageEvent) => { - if (e.key === 'accessToken') { + const onStorage = (e: StorageEvent) => { + if (e.key === 'accessToken' || e.key === 'userName') { const has = !!localStorage.getItem('accessToken'); setIsAuthed(has); setUserName(localStorage.getItem('userName') || undefined); } };
63-67: ํ ํฐ์ localStorage ์ ์ฅ์ XSS์ ์ทจ์ฝ โ httpOnly ์ฟ ํค ์ ํ ๊ฒํ๊ฐ๋ฅํ๋ฉด ์๋ฒ์์ httpOnly+Secure ์ฟ ํค๋ก ๋ฐ๊ธ/ํ์ํ๋๋ก ์ ํํ์ธ์. Next.js Route Handler์์ ์ฟ ํค ์ค์ , ํด๋ผ์ด์ธํธ๋
Authorizationํค๋ ๋์ ์ฟ ํค ๊ธฐ๋ฐ ์ธ์ฆ์ผ๋ก ๋จ์ํํ๋ ๋ฐฉ์์ ๊ถ์ฅํฉ๋๋ค.
51-60: ๋คํธ์ํฌ ํ์์์ ๋ถ์ฌ์ฅ์๊ฐ ๋๊ธฐ ๋ฐฉ์ง๋ฅผ ์ํด
AbortController๊ธฐ๋ฐ ํ์์์(์: 10s) ์ ์ฉ์ ๊ณ ๋ คํด ์ฃผ์ธ์. ์ค๋ณต ์ ์ถ ๋ฐฉ์ง๋ ํจ๊ป ์ฒ๋ฆฌํ๋ฉด UX๊ฐ ์ข์์ง๋๋ค.Also applies to: 87-96
109-118: ๋ก๊ทธ์์ ํ ๋ด๋น๊ฒ์ด์ ์ replace ๊ถ์ฅ
router.push('/')๋์router.replace('/')๋ฅผ ์ฐ๋ฉด ๋ค๋ก ๊ฐ๊ธฐ๋ก ๋ณดํธ๋ ํ๋ฉด์ผ๋ก ๋ณต๊ทํ๋ ์ํฉ์ ์ค์ผ ์ ์์ต๋๋ค.- router.push('/'); + router.replace('/');src/components/search-result/AlarmCountdown.tsx (1)
29-53: alarm.targetTime์ ์ง์ ์ฌ์ฉํ๊ณ ํ์ด๋จธ ๋๋ฆฌํํธ ์ค์ด๊ธฐํ์ฌ time-of-day(hour/minute/second)๋ก ์ค๋ ๋ ์ง ๊ธฐ์ค target์ ์ฌ๊ตฌ์ฑํฉ๋๋ค. AlarmModal์ด ์ด๋ฏธ ISO
targetTime์ ๋๊ธฐ๋ฏ๋ก ์ด๋ฅผ ๊ทธ๋๋ก ์ฌ์ฉํ๋ฉด ๋ ์ง/ํ์์กด ์คํด๋ฅผ ์ค์ผ ์ ์๊ณ , ๋งค tick๋ง๋คDate.now()๋ก ๋จ์ ์๊ฐ์ ๊ณ์ฐํ๋ฉด ๋๋ฆฌํํธ๊ฐ ์ค์ด๋ญ๋๋ค.- useEffect(() => { - const now = new Date(); - const target = new Date(); - - target.setHours(parseInt(alarm.time.hour)); - target.setMinutes(parseInt(alarm.time.minute)); - target.setSeconds(parseInt(alarm.time.second)); - target.setMilliseconds(0); - - let seconds = Math.floor((target.getTime() - now.getTime()) / 1000); - if (seconds < 0) seconds = 0; - setRemainingSeconds(seconds); - - const interval = setInterval(() => { - seconds -= 1; - setRemainingSeconds(seconds); - if (seconds <= 0) { - clearInterval(interval); - setRemainingSeconds(0); - onComplete?.(); - } - }, 1000); - - return () => clearInterval(interval); - }, [alarm, onComplete]); + useEffect(() => { + const target = new Date(alarm.targetTime); + const update = () => { + const seconds = Math.max( + 0, + Math.floor((target.getTime() - Date.now()) / 1000), + ); + setRemainingSeconds(seconds); + }; + update(); + const id = setInterval(() => { + const seconds = Math.max( + 0, + Math.floor((target.getTime() - Date.now()) / 1000), + ); + setRemainingSeconds(seconds); + if (seconds === 0) { + clearInterval(id); + onComplete?.(); + } + }, 1000); + return () => clearInterval(id); + }, [alarm.targetTime, onComplete]);src/app/page.tsx (1)
11-11: ์ค๋ณต ๋ค๋น๊ฒ์ด์ ๊ฐ๋ฅ์ฑ
ServerSearchForm๋ด๋ถ์์๋router.push๋ฅผ ์ํํฉ๋๋ค. ๋ถ๋ชจ์์๋ pushํ๋ฉด ๋์ผ ๊ฒฝ๋ก๋ก ์ด์ค ๋ค๋น๊ฒ์ด์ ์ด ๋ฐ์ํ ์ ์์ต๋๋ค. ํผ ์ชฝ์ โonSubmit์ด ์์ผ๋ฉด pushํ์ง ์์โ์ผ๋ก ๋ฐ๊พธ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค. ํด๋น ์์ ์ ServerSearchForm.tsx ์ฝ๋ฉํธ๋ฅผ ์ฐธ์กฐํ์ธ์.src/app/search-result/page.tsx (3)
53-60: URL ํ์ ๋ก์ง์ false positive ์ ๊ฑฐ
catch์์startsWith('http')๋ก true๋ฅผ ๋ฐํํ๋ฉด ์ ํจํ์ง ์์ URL๋ URL๋ก ์ค์ธ๋ ์ ์์ต๋๋ค. ํ์ฑ์ด ์คํจํ๋ฉด ๋จ์ํ false๋ฅผ ๋ฐํํ์ธ์.- const isValidUrl = (str: string) => { + const isValidUrl = (str: string) => { try { new URL(str); return true; } catch { - return str.startsWith('http://') || str.startsWith('https://'); + return false; } };
187-188: ์ค๋ณต ๊ฒ์/๋ค๋น๊ฒ์ด์ ๊ฐ๋ฅ์ฑ์ด ํ์ด์ง์์
onSubmit={handleSubmit}๋ก ๋๊ธฐ๋ฉด ํผ ๋ด๋ถ ๊ฒ์ + ๋ถ๋ชจ์ ์ฒ๋ฆฌ(๊ทธ๋ฆฌ๊ณ ํผ์router.push)๊ฐ ์ค๋ณต ์ํ๋ ์ ์์ต๋๋ค. ServerSearchForm์ดonSubmit์ด ์ฃผ์ด์ง ๊ฒฝ์ฐ์๋router.push๋ฅผ ํ์ง ์๋๋ก ๋ฐ๊พธ๋ฉด ์ค๋ณต ํธ์ถ์ ๋ฐฉ์งํ ์ ์์ต๋๋ค. (ServerSearchForm.tsx ์์ ์ ์ฐธ์กฐ)
217-218: ๋ฐ๋ฆฌ์ด ํ ๊ธ UI ์ค๋ณต ์ ๊ฑฐ
TimeDisplay์๋ ํ ๊ธ์ด ์๊ณ ์ฌ๊ธฐKoreanStandardTime์๋ ๊ธฐ๋ณธ ํ ๊ธ์ด ์์ต๋๋ค. ํ ๊ณณ๋ง ๋ ธ์ถ๋๋๋กshowToggle={false}๊ถ์ฅ.- <KoreanStandardTime showMilliseconds={showMilliseconds} /> + <KoreanStandardTime showMilliseconds={showMilliseconds} showToggle={false} />src/components/search-result/ServerSearchForm.tsx (2)
36-38: ๋ถ๋ชจ ์ ๋ฌ onSubmit๊ณผ ๋ผ์ฐํ ์ด ์ค๋ณต ์คํ๋จ๋ถ๋ชจ๊ฐ ๋ผ์ฐํ ์ ๋ด๋นํ๋๋ก,
onSubmit์ด ์ ๊ณต๋๋ฉด ๋ด๋ถ์์router.push๋ฅผ ์ํํ์ง ์๋๋ก ๋ณ๊ฒฝํ์ธ์. ์ฌ์ฌ์ฉ์ฑ๋ ์ข์์ง๋๋ค.- // ๊ฒ์๋ URL๋ก ์ด๋ - onSubmit?.(finalUrl); - router.push(`/search-result?url=${encodeURIComponent(finalUrl)}`); + // ๊ฒ์๋ URL ์ ๋ฌ ๋ฐ ํ์ ์ ๋ผ์ฐํ + if (onSubmit) { + onSubmit(finalUrl); + } else { + router.push(`/search-result?url=${encodeURIComponent(finalUrl)}`); + }
74-76: ์ฌ๋ฌ ์ค ์๋ฌ ๋ฉ์์ง ๊ฐ๋ ์ฑ ๊ฐ์
\n์ ๋ ๋๋งํ๋ ค๋ฉดwhitespace-pre-lineํด๋์ค ์ถ๊ฐ๊ฐ ํ์ํฉ๋๋ค.- <div className="mt-4 text-red-500 text-sm text-center max-w-6xl mx-auto"> + <div className="mt-4 text-red-500 text-sm text-center max-w-6xl mx-auto whitespace-pre-line">src/components/search-result/AlarmModal.tsx (1)
118-141: ์ง๋ ์๊ฐ ์ฒ๋ฆฌ UX ๊ฐ์ ๋ฐ parseInt ๊ธฐ์ ์ง์
- ์ง๋ ์๊ฐ์ด๋ฉด ๊ฒฝ๊ณ ํ ์ข ๋ฃ ๋์ ๋ค์๋ ๋ก rolloverํ๋ฉด UX๊ฐ ์ข์์ง๋๋ค.
parseInt๋ ๊ธฐ์(10)๋ฅผ ๋ช ์ํ์ธ์.- targetTime.setHours(parseInt(hour)); - targetTime.setMinutes(parseInt(minute)); - targetTime.setSeconds(parseInt(second)); + targetTime.setHours(parseInt(hour, 10)); + targetTime.setMinutes(parseInt(minute, 10)); + targetTime.setSeconds(parseInt(second, 10)); targetTime.setMilliseconds(0); const timeUntilTarget = targetTime.getTime() - now.getTime(); - if (timeUntilTarget < 0) { - alert('โ ์ด๋ฏธ ์ง๋ ์๊ฐ์ ๋๋ค. ๋ค์ ์ค์ ํด ์ฃผ์ธ์.'); - return; - } + if (timeUntilTarget < 0) { + // ์ง๋ ์๊ฐ์ด๋ฉด ๋ค์๋ ๊ฐ์ ์๊ฐ์ผ๋ก ์ค์ + targetTime.setDate(targetTime.getDate() + 1); + }src/components/search-result/ServerTimeResult.tsx (3)
133-135: 10ms ์ธํฐ๋ฒ์ ๊ณผํฉ๋๋ค โ 30fps(โ33ms) ๊ถ์ฅUI ์ ๋ฐ์ดํธ๋ 10ms(100fps)๊น์ง ํ์ํ์ง ์์ต๋๋ค. 33ms๋ก ๋ฎ์ถฐ๋ ์ถฉ๋ถํ ๋ถ๋๋ฝ๊ณ ๋ฐฐํฐ๋ฆฌ/CPU ์๋ชจ๋ฅผ ์ค์ ๋๋ค.
- const timer = setInterval(() => { + const timer = setInterval(() => { setCurrentServerTime(new Date(Date.now() + timeDiff)); - }, 10); + }, 33);
166-168: URL ํ์ฑ ์คํจ ์ ๋ฐํ์ ์์ธ ๊ฐ๋ฅ
new URL(data.url)์ ์๋ชป๋ URL์ด๋ฉด throwํฉ๋๋ค. try/catch๋ก ๊ฐ์ธ๊ณ ํด๋ฐฑ์ ๋์ธ์.- const serverUrl = new URL(data.url); - const serverName = serverUrl.hostname; + let serverName = data.url; + try { + serverName = new URL(data.url).hostname; + } catch { + // invalid URL์ผ ๊ฒฝ์ฐ ์๋ฌธ ํ์ + }
259-267: 0๊ฐ ์จ๊น ๋ฒ๊ทธ: truthy ์ฒดํฌ ๋์ ํ์ ์ฒดํฌ ์ฌ์ฉ
networkDelay๊ฐ 0์ด๋ฉด falsy๋ก ์ฒ๋ฆฌ๋์ด ํ์๋์ง ์์ต๋๋ค.typeof === 'number'๋ก ์กฐ๊ฑด์ ๋ฐ๊พธ์ธ์.- {data.networkInfo.networkDelay && ( + {typeof data.networkInfo.networkDelay === 'number' && ( <span className="ml-4"> ๋คํธ์ํฌ ์ง์ฐ: {data.networkInfo.networkDelay.toFixed(1)}ms </span> )}src/libs/api/sites.ts (1)
45-76: ๋คํธ์ํฌ ์ ๋ขฐ์ฑ ๊ฐํ ์ ์(์ต์ )์ฅ์๊ฐ ์๋ต ์ง์ฐ์ ๋๋นํด
AbortController๋ก ํ์์์์ ๋๋ฉด UX๊ฐ ๊ฐ์ ๋ฉ๋๋ค. ํ์ ์ ๊ณตํต fetch wrapper๋ก ์ ์ฉ ๊ถ์ฅ.
๐ Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
๐ Files selected for processing (12)
src/app/bookmarks/page.tsx(2 hunks)src/app/page.tsx(2 hunks)src/app/result/page.tsx(0 hunks)src/app/search-result/page.tsx(1 hunks)src/components/AlarmModal.tsx(0 hunks)src/components/ClientHeader.tsx(1 hunks)src/components/ServerSearchForm.tsx(0 hunks)src/components/search-result/AlarmCountdown.tsx(1 hunks)src/components/search-result/AlarmModal.tsx(1 hunks)src/components/search-result/ServerSearchForm.tsx(1 hunks)src/components/search-result/ServerTimeResult.tsx(1 hunks)src/libs/api/sites.ts(1 hunks)
๐ค Files with no reviewable changes (3)
- src/components/ServerSearchForm.tsx
- src/components/AlarmModal.tsx
- src/app/result/page.tsx
๐งฐ Additional context used
๐งฌ Code graph analysis (5)
src/app/search-result/page.tsx (5)
src/components/search-result/ServerTimeResult.tsx (2)
ServerTimeData(504-504)ServerTimeResult(97-502)src/components/search-result/AlarmModal.tsx (1)
AlarmData(18-22)src/libs/api/sites.ts (1)
SiteAPI(45-221)src/components/search-result/ServerSearchForm.tsx (1)
ServerSearchForm(14-80)src/components/search-result/KoreanStandardTime.tsx (1)
KoreanStandardTime(5-133)
src/components/search-result/ServerSearchForm.tsx (3)
src/libs/api/sites.ts (1)
SiteAPI(45-221)src/components/ui/Input.tsx (1)
Input(5-19)src/components/ui/Button.tsx (1)
Button(4-18)
src/components/search-result/ServerTimeResult.tsx (2)
src/components/search-result/AlarmModal.tsx (2)
AlarmData(18-22)AlarmModal(94-274)src/components/search-result/AlarmCountdown.tsx (1)
AlarmCountdown(12-69)
src/app/page.tsx (1)
src/components/search-result/ServerSearchForm.tsx (1)
ServerSearchForm(14-80)
src/libs/api/sites.ts (1)
src/libs/auth.ts (1)
AuthUtils(3-37)
| // 1. /api/time/compare ์๋ํฌ์ธํธ ํธ์ถ | ||
| const compareResponse = await fetch( | ||
| `${process.env.NEXT_PUBLIC_API_BASE}/api/time/compare`, | ||
| { | ||
| method: 'POST', | ||
| headers: { 'Content-Type': 'application/json' }, | ||
| body: JSON.stringify({ targetUrl: finalUrl }), | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ํ๊ฒฝ๋ณ์ ๋ฏธ์ค์ ์ API ํธ์ถ ์คํจ ์ํ
process.env.NEXT_PUBLIC_API_BASE๊ฐ ๋น์ด ์์ผ๋ฉด undefined/api/time/compare๋ก ํธ์ถ๋ฉ๋๋ค. ๋น ๋ฌธ์์ด fallback์ ๋๊ณ ์๋ ๊ฒฝ๋ก๋ก ํธ์ถ๋๊ฒ ํ์ธ์.
- // 1. /api/time/compare ์๋ํฌ์ธํธ ํธ์ถ
- const compareResponse = await fetch(
- `${process.env.NEXT_PUBLIC_API_BASE}/api/time/compare`,
- {
+ // 1. /api/time/compare ์๋ํฌ์ธํธ ํธ์ถ
+ const base = process.env.NEXT_PUBLIC_API_BASE ?? '';
+ const compareResponse = await fetch(
+ `${base}/api/time/compare`,
+ {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ targetUrl: finalUrl }),
},
);๐ Committable suggestion
โผ๏ธ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // 1. /api/time/compare ์๋ํฌ์ธํธ ํธ์ถ | |
| const compareResponse = await fetch( | |
| `${process.env.NEXT_PUBLIC_API_BASE}/api/time/compare`, | |
| { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ targetUrl: finalUrl }), | |
| }, | |
| // 1. /api/time/compare ์๋ํฌ์ธํธ ํธ์ถ | |
| const base = process.env.NEXT_PUBLIC_API_BASE ?? ''; | |
| const compareResponse = await fetch( | |
| `${base}/api/time/compare`, | |
| { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ targetUrl: finalUrl }), | |
| }, |
๐ค Prompt for AI Agents
In src/app/search-result/page.tsx around lines 94 to 101, the fetch uses
process.env.NEXT_PUBLIC_API_BASE directly which can be undefined and produce
"undefined/api/..."; update the code to default the env var to an empty string
and normalize it (trim and remove any trailing slash) before constructing the
URL so the call falls back to a relative path when the env var is not set.
| method: 'POST', | ||
| headers: { 'Content-Type': 'application/json' }, | ||
| body: JSON.stringify({ userName, email, password }), | ||
| body: JSON.stringify({ username: userName, email, password }), | ||
| }, | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
๐งฉ Analysis chain
ํ์๊ฐ์ payload ํค ์ ํ(username) โ ๋ณ๊ฒฝ ์์ฒด๋ ์ ์ , ํธ์ถ๋ถ ์ผ๊ด ๊ฒ์ฆ ์์ฒญ
BE๊ฐ username(์๋ฌธ์)๋ก ๋ฐ๋ ๊ณ์ฝ๊ณผ ์ผ์นํฉ๋๋ค. ๊ฐ์ ์๋ํฌ์ธํธ๋ฅผ ํธ์ถํ๋ ๋ค๋ฅธ ๊ณณ์ userName ํค๊ฐ ๋จ์ ์์ง ์์์ง ํ์ธ ๋ถํ๋๋ฆฝ๋๋ค.
๋ค์ ์คํฌ๋ฆฝํธ๋ก ์ ์ฒด ๋ ํฌ์์ ์ ๊ฒํด ์ฃผ์ธ์:
๐ Script executed:
#!/bin/bash
set -euo pipefail
# ํ์๊ฐ์
์๋ํฌ์ธํธ ํธ์ถ๋ถ ๋งฅ๋ฝ ํ์ธ
rg -nP -C2 "api/auth/register" --type ts --type tsx
# payload์ userName ํค ์ฌ์ฉ ํ์ ํ์ธ
rg -nP --type ts --type tsx '\{[^}]*\buserName\s*:'
# ๋ก๊ทธ์ธ ์๋ต์์ user.username ์ฌ์ฉ ์์น ํ์ธ
rg -nP --type ts --type tsx '\buser\.username\b'Length of output: 27
ํ์๊ฐ์ payload ํค ์ ํ(userName โ username) โ ์ ์ฒด ํธ์ถ๋ถ ์ฌ๊ฒ์ฆ ํ์
rg๊ฐ 'unrecognized file type: tsx' ์ค๋ฅ๋ก ๊ฒ์์ด ์ค๋จ๋์ต๋๋ค. ๋ ํฌ ์ ์ฒด์์ userName ์์ฌ์ ์๋ํฌ์ธํธ ํธ์ถ์ ์ฌํ์ธํ๋ ค๋ฉด ์๋ ์คํฌ๋ฆฝํธ๋ก ์ฌ์คํํ์ธ์.
๐ Script executed:
#!/bin/bash
set -euo pipefail
# ์๋ํฌ์ธํธ ํธ์ถ ๋งฅ๋ฝ ํ์ธ
rg -nP -C2 "api/auth/register" || true
# payload์ userName ํค ์ฌ์ฉ ํ์ ํ์ธ
rg -nP '\buserName\b' || true
# ๋ก๊ทธ์ธ/์๋ต์์ user.username ์ฌ์ฉ ์์น ํ์ธ
rg -nP '\buser\.username\b' || true
# JSON.stringify ๋ด๋ถ ๋๋ fetch ์ต์
์ userName/username ํฌํจ ์ฌ๋ถ ํ์ธ
rg -nP 'JSON\.stringify\([^)]*\b(?:userName|username)\b' || true
rg -nP 'fetch\([^,)]*,\s*{[^}]*\b(?:userName|username)\b' || trueLength of output: 2325
ํ์๊ฐ์ payload ํค ํต์ผ ํ์ (userName โ username) โ ์์ ํ์
- ์์ ๋์: src/components/auth/SignupModal.tsx:70 โ ํ์ฌ payload์
userName: userName.trim()์ฌ์ฉ,username์ผ๋ก ๋ณ๊ฒฝ ํ์. - ํ์ธ: src/components/ClientHeader.tsx๋ ์ด๋ฏธ
username์ผ๋ก ์ ์ก๋จ. localStorage์'userName'ํค(libs/auth.ts ๋ฑ)๋ ๋ด๋ถ ์ ์ฅ์ฉ์ผ๋ก ์ ์ง ๊ฐ๋ฅํ๋, API๋ก ์ ์ก๋๋ ๋ชจ๋ payload ํค๋username์ผ๋ก ํต์ผํ ๊ฒ.
๐ค Prompt for AI Agents
In src/components/auth/SignupModal.tsx around line 70 (and note
src/components/ClientHeader.tsx lines 91-95 is already using `username`), the
signup payload currently uses the key `userName`; change that key to `username`
when building the JSON body (e.g., username: userName.trim()) so the API payload
matches ClientHeader's `username` key; keep localStorage key 'userName'
unchanged if used only for internal storage, but ensure every network request
sends `username`.
| @@ -0,0 +1,221 @@ | |||
| import { AuthUtils } from '@/libs/auth'; | |||
|
|
|||
| const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ํ๊ฒฝ๋ณ์ ๋ฏธ์ค์ ์ ๋ชจ๋ API๊ฐ ๊นจ์ง
NEXT_PUBLIC_API_BASE๊ฐ ๋น๋ฉด undefined/...๋ก ํธ์ถ๋ฉ๋๋ค. ๋น ๋ฌธ์์ด fallback ๋ฐ ํธ๋ ์ผ๋ง ์ฌ๋์ ์ ๊ฑฐ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
-const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE;
+const API_BASE_URL = (process.env.NEXT_PUBLIC_API_BASE ?? '').replace(/\/$/, '');๐ Committable suggestion
โผ๏ธ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE; | |
| const API_BASE_URL = (process.env.NEXT_PUBLIC_API_BASE ?? '').replace(/\/$/, ''); |
๐ค Prompt for AI Agents
In src/libs/api/sites.ts around line 3, the API base constant is assigned
directly from process.env.NEXT_PUBLIC_API_BASE which can be undefined and
produce requests like "undefined/โฆ"; change it to default to an empty string
when the env var is missing and normalize by trimming whitespace and removing
any trailing slash so callers get a stable base (e.g. use
(process.env.NEXT_PUBLIC_API_BASE ?? '').trim().replace(/\/+$/, '') to produce a
safe API_BASE_URL).
๐ ์์ ๋ด์ฉ
๐ธ ์คํฌ๋ฆฐ์ท
๐ ๊ธฐํ
Summary by CodeRabbit
New Features
Bug Fixes
Refactor