From 4b2e7a0eb57e10a48a89801761706e1102d1670d Mon Sep 17 00:00:00 2001 From: kuum97 Date: Tue, 21 May 2024 12:10:02 +0900 Subject: [PATCH 01/14] Add link at home for move page --- pages/index.page.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pages/index.page.tsx b/pages/index.page.tsx index 15340008c..9b325dfa8 100644 --- a/pages/index.page.tsx +++ b/pages/index.page.tsx @@ -1,3 +1,12 @@ +import Link from "next/link"; + export default function Home() { - return <>Hello; + return ( +
+ 로그인 + 회원가입 + 공유페이지 + 폴더페이지 +
+ ); } From 6d88bf328f33518525dc57e280c5d7a4dacc6d49 Mon Sep 17 00:00:00 2001 From: kuum97 Date: Tue, 21 May 2024 12:23:22 +0900 Subject: [PATCH 02/14] Refactor for feedback --- api.ts | 6 ++---- common/Auth/Header/index.tsx | 3 ++- components/SocialAuthBox/index.tsx | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/api.ts b/api.ts index d90905864..bc74b77b6 100644 --- a/api.ts +++ b/api.ts @@ -62,10 +62,8 @@ export async function getLinksByUserIdAndFolderId({ userId, folderId, }: Params): Promise { - let url = `${CODEIT_BASE_URL}/users/${userId}/links`; - if (folderId) { - url += `?folderId=${folderId}`; - } + const defaultUrl = `${CODEIT_BASE_URL}/users/${userId}/links;`; + const url = folderId ? `${defaultUrl}?folderId=${folderId}` : `${defaultUrl}`; const response = await fetch(url); if (!response.ok) { diff --git a/common/Auth/Header/index.tsx b/common/Auth/Header/index.tsx index a862778cf..c369c4832 100644 --- a/common/Auth/Header/index.tsx +++ b/common/Auth/Header/index.tsx @@ -2,8 +2,9 @@ import Image from "next/image"; import Link from "next/link"; import styles from "./index.module.css"; +type TProps = "signin" | "signup"; interface AuthHeaderProps { - purpose: string; + purpose: TProps; } function AuthHeader({ purpose }: AuthHeaderProps) { diff --git a/components/SocialAuthBox/index.tsx b/components/SocialAuthBox/index.tsx index e8dc3ddb9..244d5afce 100644 --- a/components/SocialAuthBox/index.tsx +++ b/components/SocialAuthBox/index.tsx @@ -10,7 +10,7 @@ function SocialAuthBox() { 소셜 로그인
    {SOCIALLINKS.map(({ src, href }, i: number) => ( -
  • +
  • logo From c94172bd838cd5242b51b74c4126f70f2393b77f Mon Sep 17 00:00:00 2001 From: kuum97 Date: Tue, 21 May 2024 14:27:50 +0900 Subject: [PATCH 03/14] Implement signin logic --- api.ts | 20 +++++++++++++++++++ common/Auth/Form/index.tsx | 7 ++++--- common/Auth/Header/index.tsx | 2 +- pages/signin/index.page.tsx | 37 ++++++++++++++++++++++++++++++++++-- pages/signup/index.page.tsx | 7 ++++--- 5 files changed, 64 insertions(+), 9 deletions(-) diff --git a/api.ts b/api.ts index bc74b77b6..c4d873090 100644 --- a/api.ts +++ b/api.ts @@ -116,3 +116,23 @@ export async function postSignup({ return; } + +export async function postSignin({ + email, + password, +}: FormValues): Promise { + const response = await fetch(`${CODEIT_BASE_URL}/sign-in`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ email, password }), + }); + + if (!response.ok) { + const data = await response.json(); + return data.error.message; + } + + return; +} diff --git a/common/Auth/Form/index.tsx b/common/Auth/Form/index.tsx index e13e1863c..242ddbd87 100644 --- a/common/Auth/Form/index.tsx +++ b/common/Auth/Form/index.tsx @@ -1,18 +1,19 @@ import { SubmitHandler, UseFormHandleSubmit, useForm } from "react-hook-form"; import styles from "./index.module.css"; import { ReactNode } from "react"; +import { TProps } from "../Header"; export interface FormValues { email: string; - password: number; - passwordConfirm?: number; + password: string; + passwordConfirm?: string; } export interface AuthFormProps { children?: ReactNode; onSubmit: SubmitHandler; handleSubmit: UseFormHandleSubmit; - purpose: string; + purpose: TProps; } function AuthForm({ diff --git a/common/Auth/Header/index.tsx b/common/Auth/Header/index.tsx index c369c4832..8b320a70e 100644 --- a/common/Auth/Header/index.tsx +++ b/common/Auth/Header/index.tsx @@ -2,7 +2,7 @@ import Image from "next/image"; import Link from "next/link"; import styles from "./index.module.css"; -type TProps = "signin" | "signup"; +export type TProps = "signin" | "signup"; interface AuthHeaderProps { purpose: TProps; } diff --git a/pages/signin/index.page.tsx b/pages/signin/index.page.tsx index d11762f75..94523d28b 100644 --- a/pages/signin/index.page.tsx +++ b/pages/signin/index.page.tsx @@ -3,21 +3,54 @@ import SocialAuthBox from "@/components/SocialAuthBox"; import styles from "@/styles/Auth.module.css"; import AuthForm, { FormValues } from "@/common/Auth/Form"; import AuthInput from "@/common/Auth/Input"; -import { useForm } from "react-hook-form"; +import { SubmitHandler, useForm } from "react-hook-form"; import { emailPattern } from "@/constants"; +import { postSignin } from "@/api"; +import { useRouter } from "next/router"; function Signin() { + const router = useRouter(); const { register, formState: { errors }, handleSubmit, + setError, } = useForm(); + const handleSignin: SubmitHandler = async (data) => { + const { email, password } = data; + + try { + const result = await postSignin({ email, password }); + + if (typeof result === "string") { + const fields: (keyof FormValues)[] = ["email", "password"]; + + fields.forEach((field) => { + setError(field, { + type: "manual", + message: "이메일 또는 비밀번호를 확인해 주세요", + }); + }); + + return; + } + + router.push("/folder"); + } catch (error) { + console.error(error); + } + }; + return (
    - + = async (data) => { - const { email, password } = data; - + const handleSignup: SubmitHandler = async ({ + email, + password, + }) => { try { await postSignup({ email, password }); From f5cb2e994e3f37d4869035e26df237436f96a77f Mon Sep 17 00:00:00 2001 From: kuum97 Date: Tue, 21 May 2024 15:50:58 +0900 Subject: [PATCH 04/14] Implement password visibility logic --- common/Auth/Input/index.tsx | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/common/Auth/Input/index.tsx b/common/Auth/Input/index.tsx index 00d7cc63a..53859c964 100644 --- a/common/Auth/Input/index.tsx +++ b/common/Auth/Input/index.tsx @@ -1,5 +1,5 @@ -import { HTMLInputTypeAttribute } from "react"; -import { FaEyeSlash } from "react-icons/fa"; +import { HTMLInputTypeAttribute, useRef, useState } from "react"; +import { FaEyeSlash, FaEye } from "react-icons/fa"; import styles from "./index.module.css"; import { FieldError, @@ -24,6 +24,16 @@ function AuthInput({ register, error, }: AuthInputProps) { + const [isVisible, setIsVisible] = useState(false); + const inputRef = useRef(null); + + const handleToggleVisibility = () => { + if (inputRef.current) { + inputRef.current.type = isVisible ? "password" : "text"; + } + setIsVisible(!isVisible); + }; + return (
    From 0595c66e2237c6b78900f80e1db759dd30f7db67 Mon Sep 17 00:00:00 2001 From: kuum97 Date: Tue, 21 May 2024 21:37:58 +0900 Subject: [PATCH 05/14] Implement token logic --- api.ts | 19 +++++++++----- common/Auth/Form/index.tsx | 2 +- common/Auth/Input/index.tsx | 29 +++++++++++---------- pages/index.page.tsx | 10 ++++++-- pages/signin/index.page.tsx | 30 +++++++++++----------- pages/signup/index.page.tsx | 51 +++++++++++++++++-------------------- 6 files changed, 76 insertions(+), 65 deletions(-) diff --git a/api.ts b/api.ts index c4d873090..f8b1c69a5 100644 --- a/api.ts +++ b/api.ts @@ -97,10 +97,17 @@ export async function postEmailCheck(email: string): Promise { return; } +interface postData { + data: { + accessToken: string; + refreshToken: string; + }; +} + export async function postSignup({ email, password, -}: FormValues): Promise { +}: FormValues): Promise { const response = await fetch(`${CODEIT_BASE_URL}/sign-up`, { method: "POST", headers: { @@ -108,19 +115,19 @@ export async function postSignup({ }, body: JSON.stringify({ email, password }), }); + const data = await response.json(); if (!response.ok) { - const data = await response.json(); return data.error.message; } - return; + return data; } export async function postSignin({ email, password, -}: FormValues): Promise { +}: FormValues): Promise { const response = await fetch(`${CODEIT_BASE_URL}/sign-in`, { method: "POST", headers: { @@ -128,11 +135,11 @@ export async function postSignin({ }, body: JSON.stringify({ email, password }), }); + const data = await response.json(); if (!response.ok) { - const data = await response.json(); return data.error.message; } - return; + return data; } diff --git a/common/Auth/Form/index.tsx b/common/Auth/Form/index.tsx index 242ddbd87..30018ddd6 100644 --- a/common/Auth/Form/index.tsx +++ b/common/Auth/Form/index.tsx @@ -1,4 +1,4 @@ -import { SubmitHandler, UseFormHandleSubmit, useForm } from "react-hook-form"; +import { SubmitHandler, UseFormHandleSubmit } from "react-hook-form"; import styles from "./index.module.css"; import { ReactNode } from "react"; import { TProps } from "../Header"; diff --git a/common/Auth/Input/index.tsx b/common/Auth/Input/index.tsx index 53859c964..bc60a6ca8 100644 --- a/common/Auth/Input/index.tsx +++ b/common/Auth/Input/index.tsx @@ -1,4 +1,4 @@ -import { HTMLInputTypeAttribute, useRef, useState } from "react"; +import React, { HTMLInputTypeAttribute, forwardRef, useState } from "react"; import { FaEyeSlash, FaEye } from "react-icons/fa"; import styles from "./index.module.css"; import { @@ -17,19 +17,15 @@ interface AuthInputProps { error?: FieldError | Merge; } -function AuthInput({ - label, - type, - placeholder, - register, - error, -}: AuthInputProps) { +function AuthInput( + { label, type, placeholder, register, error }: AuthInputProps, + ref: React.ForwardedRef +) { const [isVisible, setIsVisible] = useState(false); - const inputRef = useRef(null); const handleToggleVisibility = () => { - if (inputRef.current) { - inputRef.current.type = isVisible ? "password" : "text"; + if (typeof ref !== "function" && ref?.current) { + ref.current.type = isVisible ? "password" : "text"; } setIsVisible(!isVisible); }; @@ -46,7 +42,14 @@ function AuthInput({ [styles.errorBorder]: error, })} {...register} - ref={inputRef} + ref={(e) => { + register.ref(e); + if (typeof ref === "function") { + ref(e); + } else if (ref) { + ref.current = e; + } + }} /> {error && (

    {error.message?.toString()}

    @@ -64,4 +67,4 @@ function AuthInput({ ); } -export default AuthInput; +export default forwardRef(AuthInput); diff --git a/pages/index.page.tsx b/pages/index.page.tsx index 9b325dfa8..d05fa3829 100644 --- a/pages/index.page.tsx +++ b/pages/index.page.tsx @@ -1,10 +1,16 @@ import Link from "next/link"; export default function Home() { + let token; + + if (typeof window !== "undefined") { + token = localStorage.getItem("accessToken"); + } + return (
    - 로그인 - 회원가입 + 로그인 + 회원가입 공유페이지 폴더페이지
    diff --git a/pages/signin/index.page.tsx b/pages/signin/index.page.tsx index 94523d28b..d6fba3b3e 100644 --- a/pages/signin/index.page.tsx +++ b/pages/signin/index.page.tsx @@ -7,15 +7,17 @@ import { SubmitHandler, useForm } from "react-hook-form"; import { emailPattern } from "@/constants"; import { postSignin } from "@/api"; import { useRouter } from "next/router"; +import { useRef } from "react"; function Signin() { const router = useRouter(); + const passwordRef = useRef(null); const { register, formState: { errors }, handleSubmit, setError, - } = useForm(); + } = useForm({ mode: "onBlur" }); const handleSignin: SubmitHandler = async (data) => { const { email, password } = data; @@ -36,6 +38,7 @@ function Signin() { return; } + localStorage.setItem("accessToken", result.data.accessToken); router.push("/folder"); } catch (error) { console.error(error); @@ -55,27 +58,24 @@ function Signin() { label="이메일" type="text" placeholder="이메일을 입력해 주세요" - register={{ - ...register("email", { - required: "이메일을 입력해 주세요", - pattern: { - value: emailPattern, - message: "올바른 형식의 이메일을 입력해 주세요", - }, - }), - }} + register={register("email", { + required: "이메일을 입력해 주세요", + pattern: { + value: emailPattern, + message: "올바른 형식의 이메일을 입력해 주세요", + }, + })} error={errors.email} />
    diff --git a/pages/signup/index.page.tsx b/pages/signup/index.page.tsx index 72d5b6095..452ed143a 100644 --- a/pages/signup/index.page.tsx +++ b/pages/signup/index.page.tsx @@ -27,7 +27,7 @@ function Signup() { const result = await postEmailCheck(email); console.log("result", result); - if (typeof result === "string") { + if (result) { setError( "email", { type: "onBlur", message: result }, @@ -46,8 +46,9 @@ function Signup() { password, }) => { try { - await postSignup({ email, password }); + const result = await postSignup({ email, password }); + localStorage.setItem("accessToken", result.data.accessToken); router.push("/folder"); } catch (error) { console.error(error); @@ -67,44 +68,38 @@ function Signup() { label="이메일" type="text" placeholder="이메일을 입력해 주세요" - register={{ - ...register("email", { - required: "이메일을 입력해 주세요", - pattern: { - value: emailPattern, - message: "올바른 형식의 이메일을 입력해 주세요", - }, - onBlur: (e) => handleEmailCheck(e), - }), - }} + register={register("email", { + required: "이메일을 입력해 주세요", + pattern: { + value: emailPattern, + message: "올바른 형식의 이메일을 입력해 주세요", + }, + onBlur: (e) => handleEmailCheck(e), + })} error={errors.email} /> - value === password || "비밀번호와 일치하지 않습니다", - }), - }} + register={register("passwordConfirm", { + required: "비밀번호와 일치하는 값을 입력해 주세요", + validate: (value) => + value === password || "비밀번호와 일치하지 않습니다", + })} error={errors.passwordConfirm} />
    From 1dd7f9ebc87b33463c7e859be1561bff241abbe6 Mon Sep 17 00:00:00 2001 From: kuum97 Date: Wed, 22 May 2024 17:09:15 +0900 Subject: [PATCH 06/14] Add useUser customhook for react query --- common/Header/index.tsx | 7 ++----- hooks/useInitializeUser.ts | 33 ------------------------------- hooks/useUser.ts | 10 ++++++++++ pages/folder/index.page.tsx | 39 ++++++++----------------------------- pages/shared/index.page.tsx | 23 ---------------------- 5 files changed, 20 insertions(+), 92 deletions(-) delete mode 100644 hooks/useInitializeUser.ts create mode 100644 hooks/useUser.ts diff --git a/common/Header/index.tsx b/common/Header/index.tsx index 986a16386..3f521a1d5 100644 --- a/common/Header/index.tsx +++ b/common/Header/index.tsx @@ -1,13 +1,10 @@ import Image from "next/image"; import Avatar from "@/common/Avatar"; import styles from "./index.module.css"; -import { useInitializeUser } from "@/hooks/useInitializeUser"; -import { useUserState } from "@/hooks/useUserState"; -import { SAMPLE_USER_ID } from "@/constants"; +import { useUser } from "@/hooks/useUser"; function Header() { - useInitializeUser({ SAMPLE_USER_ID }); - const { user } = useUserState(); + const { data: user } = useUser(); return (
    diff --git a/hooks/useInitializeUser.ts b/hooks/useInitializeUser.ts deleted file mode 100644 index 6eda94c08..000000000 --- a/hooks/useInitializeUser.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { useEffect } from "react"; -import { Params, getUser, getUserById } from "@/api"; -import { useUserState } from "./useUserState"; -import { SampleUser, UserData } from "@/types/user"; - -const login = async (userId: Params): Promise => { - try { - let user; - if (!userId) { - user = await getUser(); - } - user = await getUserById(userId); - return user; - } catch (error) { - console.error(error); - return null; - } -}; - -export const useInitializeUser = (userId: Params) => { - const { setUser } = useUserState(); - - useEffect(() => { - const initializeUser = async () => { - const user = await login(userId); - if (user) { - setUser(user); - } - }; - - initializeUser(); - }, [setUser, userId]); -}; diff --git a/hooks/useUser.ts b/hooks/useUser.ts new file mode 100644 index 000000000..839a8e095 --- /dev/null +++ b/hooks/useUser.ts @@ -0,0 +1,10 @@ +import { getUserById } from "@/api"; +import { SAMPLE_USER_ID } from "@/constants"; +import { useQuery } from "@tanstack/react-query"; + +export const useUser = () => { + return useQuery({ + queryKey: ["user", SAMPLE_USER_ID], + queryFn: () => getUserById({ SAMPLE_USER_ID }), + }); +}; diff --git a/pages/folder/index.page.tsx b/pages/folder/index.page.tsx index ee66f0ea0..7ae0bf914 100644 --- a/pages/folder/index.page.tsx +++ b/pages/folder/index.page.tsx @@ -1,43 +1,20 @@ -import useAsync from "@/hooks/useAsync"; -import { getFoldersByUserId } from "@/api"; import LinkAddForm from "@/components/LinkAddForm"; import FoldersController from "@/components/FolderController"; -import { FolderData } from "@/types/folder"; import { SAMPLE_USER_ID } from "@/constants"; -import { useUserState } from "@/hooks/useUserState"; +import CardListPagesLayout from "@/components/CardListPagesLayout"; +import { ReactElement } from "react"; function FolderPage() { - const { user } = useUserState(); - // const { - // value: foldersData, - // isLoading: isLoadingFolders, - // error: foldersError, - // } = useAsync(getFoldersByUserId, { SAMPLE_USER_ID }); - - if (isLoadingFolders) { - return
    Loading...
    ; - } - - if (foldersError) { - return
    Error loading data.
    ; - } - - if (!foldersData) { - return
    No Data Available
    ; - } - return ( <> - {user ? ( - <> - - - - ) : ( -
    로그인해주세요.
    - )} + + ); } +FolderPage.getLayout = function getLayout(page: ReactElement) { + return {page}; +}; + export default FolderPage; diff --git a/pages/shared/index.page.tsx b/pages/shared/index.page.tsx index 81cbb9d5f..9543d4204 100644 --- a/pages/shared/index.page.tsx +++ b/pages/shared/index.page.tsx @@ -1,32 +1,9 @@ import { ReactElement } from "react"; -import useAsync from "@/hooks/useAsync"; -import { useUserState } from "@/hooks/useUserState"; -import { getFolder } from "@/api"; -import { SampleFolder } from "@/types/folder"; import SharedLinkCards from "@/components/SharedLinkCards"; import UserProfileAndTitle from "@/components/UserProfileAndTitle"; import CardListPagesLayout from "@/components/CardListPagesLayout"; function SharedPage() { - const { user } = useUserState(); - // const { - // value: folderData, - // isLoading: isLoadingFolders, - // error: foldersError, - // } = useAsync(getFolder, {}); - - if (isLoadingFolders) { - return
    Loading...
    ; - } - - if (foldersError) { - return
    Error loading data.
    ; - } - - if (!folderData) { - return
    No Data Available
    ; - } - return ( <> {user && ( From 0c2aa6697944c6e6c21a356e8438598f4eda7450 Mon Sep 17 00:00:00 2001 From: kuum97 Date: Wed, 22 May 2024 21:28:04 +0900 Subject: [PATCH 07/14] Refactor folder page for react query --- api.ts | 2 +- common/Header/index.tsx | 4 +- components/CardListPagesLayout/index.tsx | 26 ------ components/CardsPageLayout/index.tsx | 19 ++++ components/FolderController/index.tsx | 75 ---------------- components/FoldersList/index.tsx | 6 +- .../index.module.css | 0 components/FoldersNavigation/index.tsx | 56 ++++++++++++ .../{FolderLinkCard => LinkCard}/index.tsx | 6 +- .../{FolderLinkCards => LinkCards}/index.tsx | 16 ++-- hooks/useUser.ts | 10 --- hooks/useUserState.ts | 6 +- pages/_app.page.tsx | 16 +--- pages/folder/index.page.tsx | 90 ++++++++++++++++--- 14 files changed, 176 insertions(+), 156 deletions(-) delete mode 100644 components/CardListPagesLayout/index.tsx create mode 100644 components/CardsPageLayout/index.tsx delete mode 100644 components/FolderController/index.tsx rename components/{FolderController => FoldersNavigation}/index.module.css (100%) create mode 100644 components/FoldersNavigation/index.tsx rename components/{FolderLinkCard => LinkCard}/index.tsx (96%) rename components/{FolderLinkCards => LinkCards}/index.tsx (61%) delete mode 100644 hooks/useUser.ts diff --git a/api.ts b/api.ts index f8b1c69a5..804c11726 100644 --- a/api.ts +++ b/api.ts @@ -62,7 +62,7 @@ export async function getLinksByUserIdAndFolderId({ userId, folderId, }: Params): Promise { - const defaultUrl = `${CODEIT_BASE_URL}/users/${userId}/links;`; + const defaultUrl = `${CODEIT_BASE_URL}/users/${userId}/links`; const url = folderId ? `${defaultUrl}?folderId=${folderId}` : `${defaultUrl}`; const response = await fetch(url); diff --git a/common/Header/index.tsx b/common/Header/index.tsx index 3f521a1d5..997a1de38 100644 --- a/common/Header/index.tsx +++ b/common/Header/index.tsx @@ -1,10 +1,10 @@ import Image from "next/image"; import Avatar from "@/common/Avatar"; import styles from "./index.module.css"; -import { useUser } from "@/hooks/useUser"; +import { useUserState } from "@/hooks/useUserState"; function Header() { - const { data: user } = useUser(); + const user = useUserState((state) => state.user); return (
    diff --git a/components/CardListPagesLayout/index.tsx b/components/CardListPagesLayout/index.tsx deleted file mode 100644 index 20294b069..000000000 --- a/components/CardListPagesLayout/index.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import Footer from "@/common/Footer"; -import Header from "@/common/Header"; -import { useUserState } from "@/hooks/useUserState"; -import { ReactNode } from "react"; - -interface CardListPagesLayoutProp { - children: ReactNode; -} - -function CardListPagesLayout({ children }: CardListPagesLayoutProp) { - const { user } = useUserState(); - - if (!user) { -
    로그인 해주세요!
    ; - } - - return ( - <> -
    -
    {children}
    -