Skip to content

Commit

Permalink
Merge pull request #463 from SeungRyeolBaek/part3
Browse files Browse the repository at this point in the history
[백승렬] Week14
  • Loading branch information
kiJu2 authored May 22, 2024
2 parents 6e0fb52 + ba5c1ef commit a86654c
Show file tree
Hide file tree
Showing 134 changed files with 3,112 additions and 648 deletions.
238 changes: 209 additions & 29 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,26 @@
"lint": "next lint"
},
"dependencies": {
"@popperjs/core": "^2.11.8",
"axios": "^1.6.8",
"classnames": "^2.5.1",
"date-fns": "^3.6.0",
"next": "13.5.6",
"react": "^18",
"react-dom": "^18",
"react-hook-form": "^7.51.4",
"react-icons": "^5.2.1",
"react-popper": "^2.3.0",
"react-query": "^3.39.3",
"react-transition-group": "^4.4.5",
"sass": "^1.77.0"
},
"devDependencies": {
"@svgr/webpack": "^8.1.0",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"@types/react-transition-group": "^4.4.10",
"eslint": "^8",
"eslint-config-next": "13.5.6",
"typescript": "^5"
Expand Down
21 changes: 21 additions & 0 deletions pages/signin/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { SignInForm, Oauth } from "@/src/feature";
import { SignHeader } from "@/src/ui";
import { SignLayout } from "@/src/page-layout/SignLayout";
import { ROUTE } from "@/src/util";

const SignInPage = () => {
return (
<SignLayout
header={
<SignHeader
message="회원이 아니신가요?"
link={{ text: "회원 가입하기", href: ROUTE.회원가입 }}
/>
}
form={<SignInForm />}
oauth={<Oauth description="소셜 로그인" />}
/>
);
};

export default SignInPage;
21 changes: 21 additions & 0 deletions pages/signup/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Oauth, SignUpForm } from "@/src/feature";
import { SignHeader } from "@/src/ui";
import { SignLayout } from "@/src/page-layout/SignLayout";
import { ROUTE } from "@/src/util";

const SignUpPage = () => {
return (
<SignLayout
header={
<SignHeader
message="이미 회원이신가요?"
link={{ text: "로그인 하기", href: ROUTE.로그인 }}
/>
}
form={<SignUpForm />}
oauth={<Oauth description="다른 방식으로 가입하기" />}
/>
);
};

export default SignUpPage;
Binary file added public/images/google-oauth.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/kakao-oauth.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 0 additions & 7 deletions public/images/kebab.svg

This file was deleted.

8 changes: 0 additions & 8 deletions public/images/search.svg

This file was deleted.

10 changes: 0 additions & 10 deletions public/images/star.svg

This file was deleted.

3 changes: 3 additions & 0 deletions src/data-access/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ export * from "./useGetUser";
export * from "./useGetFolder";
export * from "./useGetFolders";
export * from "./useGetLinks"
export * from "./useSignIn";
export * from "./useSignUp";
export * from "./useCheckEmailDuplicate";
37 changes: 37 additions & 0 deletions src/data-access/useCheckEmailDuplicate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { instance, useAsync } from "@/src/util";
import { useCallback } from "react";

/**
* useCheckEmailDuplicate 훅은 이메일 중복 확인을 위한 비동기 요청을 처리합니다.
*
* @param email - 중복 확인할 이메일 주소입니다.
* @returns 훅의 반환 객체입니다.
* @returns return.execute - 이메일 중복 확인을 실행하는 함수입니다.
* @returns return.loading - 이메일 중복 확인 요청의 로딩 상태입니다.
* @returns return.error - 이메일 중복 확인 요청 중 발생한 오류입니다.
* @returns return.data - 이메일 중복 확인 응답 데이터입니다.
*
* @example
* const { execute, loading, error, data } = useCheckEmailDuplicate("[email protected]");
*
* useEffect(() => {
* execute();
* }, [execute]);
*/
export const useCheckEmailDuplicate = (email: string) => {
const checkEmailDuplicate = useCallback(
() =>
instance.post<{ data: { isUsableNickname: boolean } }>("check-email", {
email,
}),
[email]
);
const { execute, loading, error, data } = useAsync(checkEmailDuplicate, true);

return {
execute,
loading,
error,
data,
};
};
38 changes: 38 additions & 0 deletions src/data-access/useGetFolder.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,42 @@
import { useAsync, instance, mapFolderData } from "@/src/util";
import { SampleFolderRawData } from "@/src/type";

/**
* useGetFolder 훅은 샘플 폴더 데이터를 가져와서 매핑된 폴더 데이터를 반환합니다.
*
* @returns 훅의 반환 객체입니다.
* @returns return.loading - 폴더 데이터를 가져오는 요청의 로딩 상태입니다.
* @returns return.error - 폴더 데이터를 가져오는 요청 중 발생한 오류입니다.
* @returns return.data - 매핑된 폴더 데이터 객체입니다.
* @returns return.data.profileImage - 폴더 소유자의 프로필 이미지 URL입니다.
* @returns return.data.ownerName - 폴더 소유자의 이름입니다.
* @returns return.data.folderName - 폴더의 이름입니다.
* @returns return.data.links - 폴더에 포함된 링크의 배열입니다.
*
* @example
* const { loading, error, data } = useGetFolder();
*
* if (loading) {
* return <div>Loading...</div>;
* }
*
* if (error) {
* return <div>Error occurred: {error.message}</div>;
* }
*
* return (
* <div>
* <h1>{data.folderName}</h1>
* <p>{data.ownerName}</p>
* <img src={data.profileImage} alt={`${data.ownerName}'s profile`} />
* <ul>
* {data.links.map(link => (
* <li key={link.id}>{link.title}</li>
* ))}
* </ul>
* </div>
* );
*/
export const useGetFolder = () => {
const getFolder = () =>
instance.get<{ folder: SampleFolderRawData }>("sample/folder");
Expand All @@ -10,3 +46,5 @@ export const useGetFolder = () => {

return { loading, error, data: folderData };
};


44 changes: 42 additions & 2 deletions src/data-access/useGetFolders.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,53 @@
import { useMemo } from "react";
import { instance, useAsync, mapFoldersData } from "@/src/util";
import { FolderRawData } from "@/src/type";

/**
* useGetFolders 훅은 사용자 폴더 데이터를 가져와서 매핑된 폴더 데이터 배열을 반환합니다.
*
* @returns 훅의 반환 객체입니다.
* @returns return.loading - 폴더 데이터를 가져오는 요청의 로딩 상태입니다.
* @returns return.error - 폴더 데이터를 가져오는 요청 중 발생한 오류입니다.
* @returns return.data - 매핑되고 정렬된 폴더 데이터 배열입니다.
* @returns return.data[].id - 폴더의 고유 ID입니다.
* @returns return.data[].createdAt - 폴더 생성 일자입니다.
* @returns return.data[].name - 폴더의 이름입니다.
* @returns return.data[].userId - 폴더 소유자의 사용자 ID입니다.
* @returns return.data[].linkCount - 폴더에 포함된 링크의 개수입니다.
*
* @example
* const { loading, error, data } = useGetFolders();
*
* if (loading) {
* return <div>Loading...</div>;
* }
*
* if (error) {
* return <div>Error occurred: {error.message}</div>;
* }
*
* return (
* <div>
* {data.map(folder => (
* <div key={folder.id}>
* <h1>{folder.name}</h1>
* <p>{folder.createdAt}</p>
* <p>{folder.linkCount} links</p>
* </div>
* ))}
* </div>
* );
*/
export const useGetFolders = () => {
const getFolders = () =>
instance.get<{ data: FolderRawData[] }>("users/1/folders");
const { loading, error, data } = useAsync(getFolders);

const folders = mapFoldersData(data?.data);
const sortedFolders = folders.sort((a, b) => a?.id - b?.id);
const folders = useMemo(() => mapFoldersData(data?.data), [data?.data]);
const sortedFolders = useMemo(
() => folders.sort((a, b) => a?.id - b?.id),
[folders]
);

return { loading, error, data: sortedFolders };
};
46 changes: 46 additions & 0 deletions src/data-access/useGetLinks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,52 @@ import { useCallback, useEffect } from "react";
import { instance, useAsync, mapLinksData, ALL_LINKS_ID } from "@/src/util";
import { SelectedFolderId, LinkRawData } from "@/src/type";


/**
* useGetLinks 훅은 주어진 폴더 ID에 따라 링크 데이터를 가져와 매핑된 링크 데이터를 반환합니다.
*
* @param [folderId=ALL_LINKS_ID] - 링크를 가져올 폴더의 ID입니다. 기본값은 모든 링크를 가져오는 "ALL_LINKS_ID"입니다.
* @returns 훅의 반환 객체입니다.
* @returns return.execute - 링크 데이터를 수동으로 가져오는 함수입니다.
* @returns return.loading - 링크 데이터를 가져오는 요청의 로딩 상태입니다.
* @returns return.error - 링크 데이터를 가져오는 요청 중 발생한 오류입니다.
* @returns return.data - 매핑된 링크 데이터 배열입니다.
* @returns return.data[].id - 링크의 고유 ID입니다.
* @returns return.data[].createdAt - 링크 생성 일자입니다.
* @returns return.data[].updatedAt - 링크 업데이트 일자입니다.
* @returns return.data[].url - 링크 URL입니다.
* @returns return.data[].imageSource - 링크 이미지 소스입니다.
* @returns return.data[].title - 링크의 제목입니다.
* @returns return.data[].description - 링크의 설명입니다.
* @returns return.data[].elapsedTime - 링크 생성 이후 경과 시간입니다.
*
* @example
* const { execute, loading, error, data } = useGetLinks("folderId");
*
* useEffect(() => {
* execute();
* }, [execute]);
*
* if (loading) {
* return <div>Loading...</div>;
* }
*
* if (error) {
* return <div>Error occurred: {error.message}</div>;
* }
*
* return (
* <div>
* {data.map(link => (
* <div key={link.id}>
* <h1>{link.title}</h1>
* <p>{link.description}</p>
* <a href={link.url}>{link.url}</a>
* </div>
* ))}
* </div>
* );
*/
export const useGetLinks = (folderId: SelectedFolderId = ALL_LINKS_ID) => {
const queryString = folderId === ALL_LINKS_ID ? "" : `?folderId=${folderId}`;
const getLinks = useCallback(
Expand Down
27 changes: 27 additions & 0 deletions src/data-access/useGetUser.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,33 @@
import { useAsync, instance } from "@/src/util";
import { UserRawData } from "@/src/type";


/**
* useGetUser 훅은 사용자 데이터를 가져와 반환합니다.
*
* @returns 훅의 반환 객체입니다.
* @returns return.loading - 사용자 데이터를 가져오는 요청의 로딩 상태입니다.
* @returns return.error - 사용자 데이터를 가져오는 요청 중 발생한 오류입니다.
* @returns return.data - 가져온 사용자 데이터입니다.
*
* @example
* const { loading, error, data } = useGetUser();
*
* if (loading) {
* return <div>Loading...</div>;
* }
*
* if (error) {
* return <div>Error occurred: {error.message}</div>;
* }
*
* return (
* <div>
* <h1>{data.name}</h1>
* <p>{data.email}</p>
* </div>
* );
*/
export const useGetUser = () => {
const getUser = () => instance.get<{ data: UserRawData }>("sample/user");
const { loading, error, data } = useAsync(getUser);
Expand Down
55 changes: 55 additions & 0 deletions src/data-access/useSignIn.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { instance, useAsync } from "@/src/util";
import { useCallback, useEffect } from "react";
import { Token } from "@/src/type";

type UseSignInParams = { email: string; password: string };

/**
* useSignIn 훅은 사용자 인증을 처리하고, 토큰을 로컬 스토리지에 저장합니다.
*
* @param params - 사용자 인증에 필요한 이메일과 비밀번호입니다.
* @param params.email - 사용자 이메일 주소입니다.
* @param params.password - 사용자 비밀번호입니다.
* @returns 훅의 반환 객체입니다.
* @returns return.execute - 사용자 인증을 수동으로 실행하는 함수입니다.
* @returns return.loading - 사용자 인증 요청의 로딩 상태입니다.
* @returns return.error - 사용자 인증 요청 중 발생한 오류입니다.
* @returns return.data - 사용자 인증 응답 데이터입니다.
*
* @example
* const { execute, loading, error, data } = useSignIn({ email: "[email protected]", password: "password" });
*
* const handleSignIn = async () => {
* await execute();
* };
*
* useEffect(() => {
* if (data) {
* console.log("로그인 성공:", data);
* }
* }, [data]);
*/
export const useSignIn = ({ email, password }: UseSignInParams) => {
const signIn = useCallback(
() =>
instance.post<{ data: Token }>("sign-in", {
email,
password,
}),
[email, password]
);
const { execute, loading, error, data } = useAsync(signIn, true);

useEffect(() => {
if (data?.data.accessToken) {
localStorage.setItem("accessToken", data.data.accessToken);
}
}, [data?.data.accessToken]);

return {
execute,
loading,
error,
data,
};
};
Loading

0 comments on commit a86654c

Please sign in to comment.