From 7d5f7f040263abae9f2a59f0ac95da484ed8fd18 Mon Sep 17 00:00:00 2001 From: yj-leee Date: Sat, 1 Mar 2025 02:56:26 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=EC=95=BD=EA=B4=80=EB=8F=99?= =?UTF-8?q?=EC=9D=98,=20=ED=9C=B4=EB=8C=80=ED=8F=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/router.tsx | 10 ++ src/app/routes/auth/agreement.tsx | 7 + src/app/routes/auth/phone-number.tsx | 7 + src/config/paths.ts | 8 + src/features/auth/api/auth.ts | 12 ++ src/features/auth/components/Agreement.tsx | 155 +++++++++++++++++++ src/features/auth/components/PhoneNumber.tsx | 85 ++++++++++ src/features/auth/components/icons.tsx | 93 +++++++++++ 8 files changed, 377 insertions(+) create mode 100644 src/app/routes/auth/agreement.tsx create mode 100644 src/app/routes/auth/phone-number.tsx create mode 100644 src/features/auth/components/Agreement.tsx create mode 100644 src/features/auth/components/PhoneNumber.tsx create mode 100644 src/features/auth/components/icons.tsx diff --git a/src/app/router.tsx b/src/app/router.tsx index b4bb4bb..1eeabd2 100644 --- a/src/app/router.tsx +++ b/src/app/router.tsx @@ -2,8 +2,10 @@ import Layout from "@/components/layouts/layout"; import { useMemo } from "react"; +import AgreementView from "@/app/routes/auth/agreement"; import KakaoCallback from "@/app/routes/auth/kakao-callback"; import LoginView from "@/app/routes/auth/login"; +import PhoneNumberInputView from "@/app/routes/auth/phone-number"; import { default as AppRoot, ErrorBoundary as RootErrorBoundary @@ -83,6 +85,14 @@ const createAppRouter = (queryClient: QueryClient) => { path: paths.auth.login.path, element: + }, + { + path: paths.auth.agreement.path, + element: + }, + { + path: paths.auth.phoneNumber.path, + element: } ]); diff --git a/src/app/routes/auth/agreement.tsx b/src/app/routes/auth/agreement.tsx new file mode 100644 index 0000000..640856d --- /dev/null +++ b/src/app/routes/auth/agreement.tsx @@ -0,0 +1,7 @@ +import Agreement from "@/features/auth/components/Agreement"; + +const AgreementView = () => { + return ; +}; + +export default AgreementView; diff --git a/src/app/routes/auth/phone-number.tsx b/src/app/routes/auth/phone-number.tsx new file mode 100644 index 0000000..5a470a1 --- /dev/null +++ b/src/app/routes/auth/phone-number.tsx @@ -0,0 +1,7 @@ +import PhoneNumber from "@/features/auth/components/PhoneNumber"; + +const PhoneNumberInputView = () => { + return ; +}; + +export default PhoneNumberInputView; diff --git a/src/config/paths.ts b/src/config/paths.ts index 5ab60a4..3c937d9 100644 --- a/src/config/paths.ts +++ b/src/config/paths.ts @@ -44,6 +44,14 @@ export const paths = { googleCallback: { path: "/auth/callback/google", getHref: () => "/auth/callback/google" + }, + agreement: { + path: "/auth/agreement", + getHref: () => "/auth/agreement" + }, + phoneNumber: { + path: "/auth/phoneNumber", + getHref: () => "/auth/phoneNumber" } } } as const; diff --git a/src/features/auth/api/auth.ts b/src/features/auth/api/auth.ts index 3ab491a..f125a5a 100644 --- a/src/features/auth/api/auth.ts +++ b/src/features/auth/api/auth.ts @@ -17,3 +17,15 @@ export function postRefreshAccessToken({ }) { return POST({ url: `${BASE_PATH}/refresh`, data }); } + +export function postPhoneNumber({ data }: { data: { phoneNumber: string } }) { + return POST({ url: `${BASE_PATH}/user/phone`, data }); +} + +export function postTermsAgree({ + data +}: { + data: { data: { userId: number } }; +}) { + return POST({ url: `/terms/agree`, data }); +} diff --git a/src/features/auth/components/Agreement.tsx b/src/features/auth/components/Agreement.tsx new file mode 100644 index 0000000..a8f70b6 --- /dev/null +++ b/src/features/auth/components/Agreement.tsx @@ -0,0 +1,155 @@ +import { useState } from "react"; + +import { postTermsAgree } from "@/features/auth/api/auth"; +import { + CheckedAllIcon, + CheckedItemIcon, + UncheckedAllIcon, + UncheckedItemIcon +} from "@/features/auth/components/icons"; +import { useNavigate } from "react-router"; + +import { useAuthStore } from "@/stores/auth-store"; +import { paths } from "@/config/paths"; + +const AgreementCheckbox = ({ + checked, + onChange, + CheckedIcon, + UncheckedIcon +}) => { + return ( +
+ {checked ? : } +
+ ); +}; + +const Agreement = () => { + const navigate = useNavigate(); + + const [isAllChecked, setIsAllChecked] = useState(false); + const [isTermsChecked, setIsTermsChecked] = useState(false); + const [isPrivacyChecked, setIsPrivacyChecked] = useState(false); + + const handleAllCheck = () => { + const newChecked = !isAllChecked; + setIsAllChecked(newChecked); + setIsTermsChecked(newChecked); + setIsPrivacyChecked(newChecked); + }; + + const handleTermsCheck = () => setIsTermsChecked((prev) => !prev); + const handlePrivacyCheck = () => setIsPrivacyChecked((prev) => !prev); + + const isNextEnabled = isTermsChecked && isPrivacyChecked; + + const handleSubmit = async () => { + try { + const userId = useAuthStore((state) => state.userId); + const response = await postTermsAgree({ + data: { userId } + }); + + if (response.data.success) { + navigate(paths.auth.phoneNumber.path); + } else { + throw new Error("약관 동의에 실패했습니다."); + } + } catch (error) { + console.error(error); + } + }; + + return ( +
+

+ 윌고 서비스 이용을 위한
+ 약관에 동의해주세요 +

+ +
+
+ + +
+ +
+ +
+
+
+ + + [필수] + + +
+ + +
+ +
+
+ + + [필수] + + +
+ + +
+
+
+ + +
+ ); +}; + +export default Agreement; diff --git a/src/features/auth/components/PhoneNumber.tsx b/src/features/auth/components/PhoneNumber.tsx new file mode 100644 index 0000000..93e632c --- /dev/null +++ b/src/features/auth/components/PhoneNumber.tsx @@ -0,0 +1,85 @@ +import { useState } from "react"; + +import { postPhoneNumber } from "@/features/auth/api/auth"; +import { useNavigate } from "react-router"; + +import { paths } from "@/config/paths"; + +const PhoneNumber = () => { + const navigate = useNavigate(); + + const [phoneNumber, setPhoneNumber] = useState(""); + const [isButtonEnabled, setIsButtonEnabled] = useState(false); + + const handleInputChange = (e: React.ChangeEvent) => { + const input = e.target.value; + setPhoneNumber(input); + if (input.length === 8) { + setIsButtonEnabled(true); + } else { + setIsButtonEnabled(false); + } + }; + + const handleSubmit = async () => { + try { + const response = await postPhoneNumber({ + data: { phoneNumber } + }); + + if (response.data.success) { + navigate(paths.home.path); + } else { + throw new Error("약관 동의에 실패했습니다."); + } + } catch (error) { + console.error(error); + } + }; + + return ( +
+
+

+ 리워드 전달을 위해 +

+

+ 휴대폰 번호 +

+

+ 를 입력해주세요. +

+
+ +
+ + 010 + + +
+ +
+ +
+
+ ); +}; + +export default PhoneNumber; diff --git a/src/features/auth/components/icons.tsx b/src/features/auth/components/icons.tsx new file mode 100644 index 0000000..06c8a64 --- /dev/null +++ b/src/features/auth/components/icons.tsx @@ -0,0 +1,93 @@ +export const UncheckedAllIcon = () => ( + + + + + +); + +export const CheckedAllIcon = () => ( + + + + + +); + +export const UncheckedItemIcon = () => ( + + + + + + +); + +export const CheckedItemIcon = () => ( + + + + + + +); From 8ce3b06afd4efde39462321da5fbf8a5a0d671ec Mon Sep 17 00:00:00 2001 From: yj-leee Date: Sat, 1 Mar 2025 03:07:20 +0900 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20=EB=84=A4=EB=B8=8C=EB=B0=94=20?= =?UTF-8?q?=EB=B0=8F=20=ED=97=A4=EB=8D=94=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/router.tsx | 16 ++++++------- src/components/ui/header/index.const.ts | 4 +++- src/components/ui/navbar/index.const.ts | 6 ++++- src/features/auth/components/Agreement.tsx | 2 +- src/features/auth/components/PhoneNumber.tsx | 2 +- src/features/auth/components/icons.tsx | 24 ++++++++++---------- 6 files changed, 30 insertions(+), 24 deletions(-) diff --git a/src/app/router.tsx b/src/app/router.tsx index 1eeabd2..31b11f5 100644 --- a/src/app/router.tsx +++ b/src/app/router.tsx @@ -71,6 +71,14 @@ const createAppRouter = (queryClient: QueryClient) => { path: "*", lazy: () => import("./routes/not-found").then(convert(queryClient)) + }, + { + path: paths.auth.agreement.path, + element: + }, + { + path: paths.auth.phoneNumber.path, + element: } ] }, @@ -85,14 +93,6 @@ const createAppRouter = (queryClient: QueryClient) => { path: paths.auth.login.path, element: - }, - { - path: paths.auth.agreement.path, - element: - }, - { - path: paths.auth.phoneNumber.path, - element: } ]); diff --git a/src/components/ui/header/index.const.ts b/src/components/ui/header/index.const.ts index 977bfe7..73e9d30 100644 --- a/src/components/ui/header/index.const.ts +++ b/src/components/ui/header/index.const.ts @@ -5,5 +5,7 @@ export const NOT_VISIBLE_HEADER_PAGES = [paths.map.search.getHref()]; export const HEADER_TITLE_MAP = new Map([ [paths.map.certification.getHref(), "목표인증"], [paths.goal.getHref(), "목표"], - [paths.profile.reward.getHref(), "리워드 신청"] + [paths.profile.reward.getHref(), "리워드 신청"], + [paths.auth.agreement.getHref(), "약관동의"], + [paths.auth.phoneNumber.getHref(), "정보입력"] ]); diff --git a/src/components/ui/navbar/index.const.ts b/src/components/ui/navbar/index.const.ts index 0798ba0..d438cef 100644 --- a/src/components/ui/navbar/index.const.ts +++ b/src/components/ui/navbar/index.const.ts @@ -7,7 +7,11 @@ import User from "@/asset/navbar/user.svg?react"; import { paths } from "@/config/paths"; -export const NOT_VISIBLE_NAVBAR_PAGES = [paths.profile.reward.getHref()]; +export const NOT_VISIBLE_NAVBAR_PAGES = [ + paths.profile.reward.getHref(), + paths.auth.agreement.getHref(), + paths.auth.phoneNumber.getHref() +]; const isEqualPath = (locationPath: string, path: string) => { return locationPath === path; diff --git a/src/features/auth/components/Agreement.tsx b/src/features/auth/components/Agreement.tsx index a8f70b6..46bb6fc 100644 --- a/src/features/auth/components/Agreement.tsx +++ b/src/features/auth/components/Agreement.tsx @@ -65,7 +65,7 @@ const Agreement = () => { }; return ( -
+

윌고 서비스 이용을 위한
약관에 동의해주세요 diff --git a/src/features/auth/components/PhoneNumber.tsx b/src/features/auth/components/PhoneNumber.tsx index 93e632c..4e86360 100644 --- a/src/features/auth/components/PhoneNumber.tsx +++ b/src/features/auth/components/PhoneNumber.tsx @@ -39,7 +39,7 @@ const PhoneNumber = () => { return (
-
+

리워드 전달을 위해

diff --git a/src/features/auth/components/icons.tsx b/src/features/auth/components/icons.tsx index 06c8a64..25275c0 100644 --- a/src/features/auth/components/icons.tsx +++ b/src/features/auth/components/icons.tsx @@ -14,9 +14,9 @@ export const UncheckedAllIcon = () => ( ); @@ -37,9 +37,9 @@ export const CheckedAllIcon = () => ( ); @@ -61,9 +61,9 @@ export const UncheckedItemIcon = () => ( ); @@ -85,9 +85,9 @@ export const CheckedItemIcon = () => ( ); From 9df88a2c0c9af871b0514445b043ce5d61542c01 Mon Sep 17 00:00:00 2001 From: yj-leee Date: Sat, 1 Mar 2025 17:04:35 +0900 Subject: [PATCH 3/4] =?UTF-8?q?fix:=20=EB=A6=AC=EB=B7=B0=20=EB=B0=98?= =?UTF-8?q?=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/asset/agreement/checked-all.svg | 5 ++ src/asset/agreement/checked-item.svg | 6 ++ src/asset/agreement/unchecked-all.svg | 5 ++ src/asset/agreement/unchecked-item.svg | 6 ++ src/features/auth/api/auth.ts | 6 +- src/features/auth/components/Agreement.tsx | 66 ++++++++------ src/features/auth/components/PhoneNumber.tsx | 6 +- src/features/auth/components/icons.tsx | 93 -------------------- 8 files changed, 65 insertions(+), 128 deletions(-) create mode 100644 src/asset/agreement/checked-all.svg create mode 100644 src/asset/agreement/checked-item.svg create mode 100644 src/asset/agreement/unchecked-all.svg create mode 100644 src/asset/agreement/unchecked-item.svg delete mode 100644 src/features/auth/components/icons.tsx diff --git a/src/asset/agreement/checked-all.svg b/src/asset/agreement/checked-all.svg new file mode 100644 index 0000000..686545f --- /dev/null +++ b/src/asset/agreement/checked-all.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/asset/agreement/checked-item.svg b/src/asset/agreement/checked-item.svg new file mode 100644 index 0000000..ecdd10b --- /dev/null +++ b/src/asset/agreement/checked-item.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/asset/agreement/unchecked-all.svg b/src/asset/agreement/unchecked-all.svg new file mode 100644 index 0000000..2fa079d --- /dev/null +++ b/src/asset/agreement/unchecked-all.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/asset/agreement/unchecked-item.svg b/src/asset/agreement/unchecked-item.svg new file mode 100644 index 0000000..0a25d0c --- /dev/null +++ b/src/asset/agreement/unchecked-item.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/features/auth/api/auth.ts b/src/features/auth/api/auth.ts index f125a5a..2a5a49c 100644 --- a/src/features/auth/api/auth.ts +++ b/src/features/auth/api/auth.ts @@ -22,10 +22,6 @@ export function postPhoneNumber({ data }: { data: { phoneNumber: string } }) { return POST({ url: `${BASE_PATH}/user/phone`, data }); } -export function postTermsAgree({ - data -}: { - data: { data: { userId: number } }; -}) { +export function postTermsAgree({ data }: { data: { userId: number } }) { return POST({ url: `/terms/agree`, data }); } diff --git a/src/features/auth/components/Agreement.tsx b/src/features/auth/components/Agreement.tsx index 46bb6fc..e35c54c 100644 --- a/src/features/auth/components/Agreement.tsx +++ b/src/features/auth/components/Agreement.tsx @@ -1,12 +1,10 @@ import { useState } from "react"; +import CheckedAllIcon from "@/asset/agreement/checked-all.svg?url"; +import CheckedItemIcon from "@/asset/agreement/checked-item.svg?url"; +import UncheckedAllIcon from "@/asset/agreement/unchecked-all.svg?url"; +import UncheckedItemIcon from "@/asset/agreement/unchecked-item.svg?url"; import { postTermsAgree } from "@/features/auth/api/auth"; -import { - CheckedAllIcon, - CheckedItemIcon, - UncheckedAllIcon, - UncheckedItemIcon -} from "@/features/auth/components/icons"; import { useNavigate } from "react-router"; import { useAuthStore } from "@/stores/auth-store"; @@ -23,33 +21,51 @@ const AgreementCheckbox = ({ onClick={onChange} style={{ cursor: "pointer", display: "inline-block" }} > - {checked ? : } + Checkbox Icon
); }; const Agreement = () => { const navigate = useNavigate(); + const userId = useAuthStore((state) => state.userId); - const [isAllChecked, setIsAllChecked] = useState(false); - const [isTermsChecked, setIsTermsChecked] = useState(false); - const [isPrivacyChecked, setIsPrivacyChecked] = useState(false); + const [checkState, setCheckState] = useState({ + all: false, + terms: false, + privacy: false + }); const handleAllCheck = () => { - const newChecked = !isAllChecked; - setIsAllChecked(newChecked); - setIsTermsChecked(newChecked); - setIsPrivacyChecked(newChecked); + setCheckState((prev) => { + const newChecked = !prev.all; + return { + all: newChecked, + terms: newChecked, + privacy: newChecked + }; + }); }; + const handleSingleCheck = (key: "terms" | "privacy") => { + setCheckState((prev) => { + const newState = { + ...prev, + [key]: !prev[key] + }; - const handleTermsCheck = () => setIsTermsChecked((prev) => !prev); - const handlePrivacyCheck = () => setIsPrivacyChecked((prev) => !prev); + newState.all = newState.terms && newState.privacy; - const isNextEnabled = isTermsChecked && isPrivacyChecked; + return newState; + }); + }; const handleSubmit = async () => { try { - const userId = useAuthStore((state) => state.userId); const response = await postTermsAgree({ data: { userId } }); @@ -74,7 +90,7 @@ const Agreement = () => {
{
handleSingleCheck("terms")} CheckedIcon={CheckedItemIcon} UncheckedIcon={UncheckedItemIcon} /> @@ -114,8 +130,8 @@ const Agreement = () => {
handleSingleCheck("privacy")} CheckedIcon={CheckedItemIcon} UncheckedIcon={UncheckedItemIcon} /> @@ -138,10 +154,10 @@ const Agreement = () => {