From b6acb960af6a453fd56e262e047fb71a651389f0 Mon Sep 17 00:00:00 2001 From: Sinji Date: Wed, 28 May 2025 16:26:39 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=EC=BD=9C=EB=B0=B1=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(auth)/callback/page.tsx | 3 +++ src/app/(auth)/layout.tsx | 14 ++++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) create mode 100644 src/app/(auth)/callback/page.tsx diff --git a/src/app/(auth)/callback/page.tsx b/src/app/(auth)/callback/page.tsx new file mode 100644 index 0000000..98ae6f7 --- /dev/null +++ b/src/app/(auth)/callback/page.tsx @@ -0,0 +1,3 @@ +export default function CallbackPage() { + return
콜백 페이지
; +} diff --git a/src/app/(auth)/layout.tsx b/src/app/(auth)/layout.tsx index cb688f8..410f7f9 100644 --- a/src/app/(auth)/layout.tsx +++ b/src/app/(auth)/layout.tsx @@ -1,16 +1,22 @@ import { ReactNode } from 'react'; type Props = { + children: ReactNode; image: ReactNode; content: ReactNode; }; -export default function AuthLayout({ content, image }: Props) { +export default function AuthLayout({ children, content, image }: Props) { return (
-
{content}
- -
{image}
+ {children ? ( +
{children}
+ ) : ( + <> +
{content}
+
{image}
+ + )}
); } From 7b055b0d9a9e9b1efd241d4fd8d29e2d80405d31 Mon Sep 17 00:00:00 2001 From: Sinji Date: Wed, 28 May 2025 17:51:42 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20=EC=86=8C=EC=85=9C=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20API=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- next.config.ts | 2 +- package.json | 1 + src/app/(auth)/@content/login/page.tsx | 16 ++++++++++-- src/app/(auth)/callback/page.tsx | 3 --- src/app/(auth)/layout.tsx | 13 +++------- src/app/callback/page.tsx | 36 ++++++++++++++++++++++++++ src/utils/loginHandler.ts | 15 +++++++++++ yarn.lock | 8 ++++++ 8 files changed, 78 insertions(+), 16 deletions(-) delete mode 100644 src/app/(auth)/callback/page.tsx create mode 100644 src/app/callback/page.tsx create mode 100644 src/utils/loginHandler.ts diff --git a/next.config.ts b/next.config.ts index 189df27..6a574df 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,6 +1,6 @@ import type { NextConfig } from 'next'; -const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL; +const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URI; const nextConfig: NextConfig = { reactStrictMode: true, diff --git a/package.json b/package.json index 90949a4..80a0928 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@svgr/cli": "^8.1.0", "@tanstack/react-query": "^5.75.5", "clsx": "^2.1.1", + "js-base64": "^3.7.7", "next": "15.2.4", "react": "^19.0.0", "react-dom": "^19.0.0", diff --git a/src/app/(auth)/@content/login/page.tsx b/src/app/(auth)/@content/login/page.tsx index 9d549bb..8b57c81 100644 --- a/src/app/(auth)/@content/login/page.tsx +++ b/src/app/(auth)/@content/login/page.tsx @@ -1,3 +1,5 @@ +'use client'; + import { Spacing } from '@/components/ui/Spacing'; import Logo from '@/assets/logo.svg'; import IconKaKao from '@/assets/icons/icon-kakao.svg'; @@ -14,11 +16,21 @@ export default function LoginPage() {
{/* TODO : api 연결할 때 onClick 연결 필요 */} - - diff --git a/src/app/(auth)/callback/page.tsx b/src/app/(auth)/callback/page.tsx deleted file mode 100644 index 98ae6f7..0000000 --- a/src/app/(auth)/callback/page.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function CallbackPage() { - return
콜백 페이지
; -} diff --git a/src/app/(auth)/layout.tsx b/src/app/(auth)/layout.tsx index 410f7f9..6c56eab 100644 --- a/src/app/(auth)/layout.tsx +++ b/src/app/(auth)/layout.tsx @@ -1,22 +1,15 @@ import { ReactNode } from 'react'; type Props = { - children: ReactNode; image: ReactNode; content: ReactNode; }; -export default function AuthLayout({ children, content, image }: Props) { +export default function AuthLayout({ content, image }: Props) { return (
- {children ? ( -
{children}
- ) : ( - <> -
{content}
-
{image}
- - )} +
{content}
+
{image}
); } diff --git a/src/app/callback/page.tsx b/src/app/callback/page.tsx new file mode 100644 index 0000000..3f7fc4b --- /dev/null +++ b/src/app/callback/page.tsx @@ -0,0 +1,36 @@ +'use client'; + +import { useEffect, Suspense } from 'react'; +import { useSearchParams, useRouter } from 'next/navigation'; +import { handleLoginSuccess } from '@/utils/loginHandler'; + +function CallbackContent() { + const searchParams = useSearchParams(); + const router = useRouter(); + + useEffect(() => { + const type = searchParams.get('type'); + const accessToken = searchParams.get('token'); + + switch (type) { + case 'NEW_USER': + router.replace('/sign-up/step1'); + break; + case 'SUCCESS': + default: + handleLoginSuccess(accessToken); + router.replace('/advice'); + break; + } + }, [searchParams, router]); + + return

Redirecting...

; +} + +export default function Callback() { + return ( + Loading...

}> + +
+ ); +} diff --git a/src/utils/loginHandler.ts b/src/utils/loginHandler.ts new file mode 100644 index 0000000..7020e3c --- /dev/null +++ b/src/utils/loginHandler.ts @@ -0,0 +1,15 @@ +import { decode } from 'js-base64'; + +export const handleLoginSuccess = (accessToken: string | null) => { + if (accessToken) { + const payload = accessToken.split('.')[1] || ''; + const decodedPayload = decode(payload); + const payloadObject = JSON.parse(decodedPayload); + const { name: tokenName } = payloadObject; + + const userInfo = { name: tokenName }; + + localStorage.setItem('token', accessToken); + localStorage.setItem('user', JSON.stringify(userInfo)); + } +}; diff --git a/yarn.lock b/yarn.lock index 7265896..cbaf6d7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3876,6 +3876,7 @@ __metadata: eslint-plugin-react: "npm:^7.37.5" eslint-plugin-react-hooks: "npm:^5.2.0" husky: "npm:^9.1.7" + js-base64: "npm:^3.7.7" lint-staged: "npm:^15.5.0" next: "npm:15.2.4" postcss: "npm:^8.5.3" @@ -4564,6 +4565,13 @@ __metadata: languageName: node linkType: hard +"js-base64@npm:^3.7.7": + version: 3.7.7 + resolution: "js-base64@npm:3.7.7" + checksum: 10c0/3c905a7e78b601e4751b5e710edd0d6d045ce2d23eb84c9df03515371e1b291edc72808dc91e081cb9855aef6758292a2407006f4608ec3705373dd8baf2f80f + languageName: node + linkType: hard + "js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" From cd486133af98dddf8177b341d178893ff86b4e37 Mon Sep 17 00:00:00 2001 From: Sinji Date: Wed, 28 May 2025 18:18:27 +0900 Subject: [PATCH 3/4] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20POST?= =?UTF-8?q?=20API=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(auth)/@content/sign-up/step3/page.tsx | 29 +++++++++++++++++-- src/hooks/users/useSignUp.ts | 7 +++++ src/services/users/postSignUp.ts | 14 +++++++++ 3 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 src/hooks/users/useSignUp.ts create mode 100644 src/services/users/postSignUp.ts diff --git a/src/app/(auth)/@content/sign-up/step3/page.tsx b/src/app/(auth)/@content/sign-up/step3/page.tsx index a6f48d2..ed2cf1f 100644 --- a/src/app/(auth)/@content/sign-up/step3/page.tsx +++ b/src/app/(auth)/@content/sign-up/step3/page.tsx @@ -6,6 +6,7 @@ import { Spacing } from '@/components/ui/Spacing'; import { Button } from '@/components/ui/Button'; import { StepBar } from '@/components/ui/StepBar'; import { useSignupStore } from '@/stores/signUpStore'; +import { useSignUp } from '@/hooks/users/useSignUp'; import IconArrowDown from '@/assets/icons/icon-arrow-down.svg'; import IconArrowBack from '@/assets/icons/icon-arrow-back.svg'; @@ -35,7 +36,8 @@ const category = [ export default function Step1() { const router = useRouter(); - const { preferences, setPreferences, clearPreferences } = useSignupStore(); + const { username, role, preferences, setPreferences, clearPreferences } = useSignupStore(); + const { mutate: signUp } = useSignUp(); const [currentIndex, setCurrentIndex] = useState(0); @@ -52,6 +54,14 @@ export default function Step1() { router.push('/sign-up-complete'); }; + const handleSignUp = () => { + signUp({ + name: username, + job: role, + tags: preferences, + }); + }; + return (
@@ -111,11 +121,24 @@ export default function Step1() {
- {preferences.length >= 3 ? ( - ) : ( diff --git a/src/hooks/users/useSignUp.ts b/src/hooks/users/useSignUp.ts new file mode 100644 index 0000000..134df54 --- /dev/null +++ b/src/hooks/users/useSignUp.ts @@ -0,0 +1,7 @@ +import { SignupRequest, userSignUp } from '@/services/users/postSignUp'; +import { useMutation } from '@tanstack/react-query'; + +export const useSignUp = () => + useMutation({ + mutationFn: (data) => userSignUp(data), + }); diff --git a/src/services/users/postSignUp.ts b/src/services/users/postSignUp.ts new file mode 100644 index 0000000..6482a4e --- /dev/null +++ b/src/services/users/postSignUp.ts @@ -0,0 +1,14 @@ +import { customFetch } from '@/utils/customFetch'; + +export interface SignupRequest { + name: string; + job: string; + tags: string[]; +} + +export const userSignUp = async (payload: SignupRequest): Promise => { + return customFetch('/users/sign-up', { + method: 'POST', + body: JSON.stringify(payload), + }); +}; From 952bdb4fe05b2d3aae5ec15fac32d2096397f82d Mon Sep 17 00:00:00 2001 From: Sinji Date: Wed, 28 May 2025 18:44:48 +0900 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20=ED=97=A4=EB=8D=94=20=EB=A0=8C?= =?UTF-8?q?=EB=8D=94=EB=A7=81=20=EC=9D=B4=EC=8A=88=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ui/Header/index.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/components/ui/Header/index.tsx b/src/components/ui/Header/index.tsx index 6b0a34f..734431d 100644 --- a/src/components/ui/Header/index.tsx +++ b/src/components/ui/Header/index.tsx @@ -1,5 +1,6 @@ 'use client'; +import { useEffect, useState } from 'react'; import Link from 'next/link'; import { usePathname } from 'next/navigation'; import { cn } from '@/lib/utils'; @@ -8,6 +9,7 @@ import Logo from '@/assets/logo.svg'; export default function Header() { const pathname = usePathname(); + const [isLogin, setIsLogin] = useState(false); const menus = [ { label: '조언받기', href: '/advice' }, @@ -15,10 +17,11 @@ export default function Header() { { label: '보관함', href: '/archive' }, ]; - { - /* TODO : 쿠키에서 로그인 여부 받아서 수정할 수 있도록 하기 */ - } - const isLogin = true; + useEffect(() => { + const token = localStorage.getItem('token'); + const user = localStorage.getItem('user'); + setIsLogin(!!(token && user)); + }, []); return (
@@ -29,7 +32,6 @@ export default function Header() {