Skip to content
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

인증부분 화면 구성을 진행합니다. #22

Open
wants to merge 8 commits into
base: develop
Choose a base branch
from
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ yarn-error.log*

# local env files
.env*.local
.env.*
.env

# vercel
.vercel
Expand Down
Binary file added public/images/buttons/kakao_login_large_wide.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 30 additions & 0 deletions src/components/logo/Logo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import styled from "@emotion/styled";

const Logo = () => {
return (
<EmotionWrapper>
<h1>쩝쩝대학</h1>
<p>우리들의 모든 맛집</p>
hoon5083 marked this conversation as resolved.
Show resolved Hide resolved
</EmotionWrapper>
);
};

export default Logo;

const EmotionWrapper = styled.div`
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;

h1 {
font-size: 36px;
font-weight: 700;
margin-bottom: 16px;
}

p {
font-size: 12px;
color: ${({ theme }) => theme.color.gray500};
}
`;
8 changes: 8 additions & 0 deletions src/constant/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const API_ENDPOINT = {
auth: {
kakao: {
login: "/auth/kakao/login",
logout: "/auth/kakao/logout",
},
},
};
Comment on lines +1 to +8
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이걸 사용하다보니 느낀건데, 최종 리소스 기준으로만 시작하는게 나은거 같더라구요.
지금 상황에서는 auth.kakao.login 으로 사용한다면, 맨 앞에 auth가 아닌 다른 네이밍으로 바뀌는 상황에 대응하기가 힘들어서, kakao.login 정도면 앞쪽 url 변화에 유연하게 사용할 수 있지 않을까 합니다.
다른 플젝에서 사용한 api 엔드포인트 파일 공유드려 봅니다...

export const API_ROUTES = {
  token: { root: () => "/token/", refresh: () => "/token/refresh/" },
  users: {
    my: () => "/users/my/",
    signUp: () => "/users/sign-up/",
    changePassword: () => "/users/my/change-password/",
  },
  boards: {
    root: () => "/boards/",
    summary: () => "/boards/summary/",
  },
  posts: {
    bySlug: (slug: string) => `/boards/${slug}/posts/`,
    bySlugAndId: (slug: string, id: number) => `/boards/${slug}/posts/${id}/`,
    uploadImage: (slug: string) => `/boards/${slug}/posts/upload-image/`,
  },
  comments: {
    bySlugAndPostId: (slug: string, postId: number) => `/boards/${slug}/posts/${postId}/comments/`,
    bySlugAndPostIdAndId: (slug: string, postId: number, id: number) =>
      `/boards/${slug}/posts/${postId}/comments/${id}/`,
  },
  categories: {
    bySlug: (slug: string) => `/boards/${slug}/categories/`,
  },
};

25 changes: 25 additions & 0 deletions src/constant/link.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* 이 파일에서는 링크 경로를 관리합니다.
* autoComplete 및 검색에 도움이 되도록 `LINK_` 로 시작하는 이름을 사용합니다.
*/

// 메인 페이지 경로
export const LINK_MAIN_PAGE = "/";

// 로그인 페이지 경로
export const LINK_LOGIN_PAGE = "/login";

// 회원가입 성공 시 페이지경로
export const LINK_REGISTER_SUCCESS = "/register/success";

// 마이 페이지
export const LINK_MYPAGE = "/mypage";

// 내 정보 수정 페이지
export const LINK_MYPAGE_EDIT = "/mypage/edit";

// 계정 탈퇴 페이지
export const LINK_ACCOUNT_DELETE = "/mypage/delete";

// 계정 탈퇴 성공 시 이동할 경로
export const LINK_ACCOUNT_DELETE_SUCCESS = "/mypage/delete/success";
4 changes: 4 additions & 0 deletions src/constant/processEnv.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const processEnv = {
NODE_ENV: process.env.NODE_ENV,
NEXT_PUBLIC_API_ENDPOINT: process.env.NEXT_PUBLIC_API_ENDPOINT,
};
20 changes: 20 additions & 0 deletions src/feature/auth/auth.login/components/ButtonKakaoLogin.tsx
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이건 그냥 궁금해서 여쭤보는 건데, feature 하위 폴더명을 CamelCase가 아닌 dot notation으로 표기하신 이유가 궁금합니다. 이렇게 하는게 일반적인 컨벤션인가요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sera2002 오... 넵넵 좋은 질문입니다. [일반적인 컨벤션] 은 아니고... 제가 최근에 진행한 프로젝트가 이렇게 했다 보니 무의식중에 한 것 같네요 😓

음... 글쎄요...ㅋㅋㅋ 이유는 없습니다만 세라님 말씀처럼 하나로 통일하는 것이 좋을 것 같아요 혹시 세라님 작업분 중 feature 안에 또 저렇게 이중 폴더가 들어가는 부분이 있나용?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://askubuntu.com/questions/413150/is-it-bad-practice-for-folder-name-to-contain-a-dot-how-about-a-file-name-w

음.. 이런 배경도 있을 수 있겠군요.. CLI 상에서 조작하기 어렵다는 점도? 있을 것 같고 한데 뭔가 조금 '이 폴더는 일반적인 폴더보다는 조금 다른 폴더이다' 라는 느낌을 강조하고 싶었던 것 같습니다. 😅

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 저는 feature 내에 위치한 이중 폴더 이름을 CamelCase로 해놨었는데, dot notation으로 통일하는 것도 뭔가 보기에도 깔끔하고 괜찮을 것 같습니다!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 넵 근데 뭔가 컨벤션(?) 에 예외를 두는 것 같아서 그냥 전부 camelCase 로 통일하는 것은 조금 그럴까용? 뭐가 좋을지 모르겠네유 @sera2002 @hoon5083

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 camelCase와 dot notation의 용도가 다른거 같긴 합니다. dot notation으로 하면 좀더 계층적임을 잘 보여주지 않나 싶어요
camelCase는 단순히 여러단어를 구분하기 위한 용도 같구요

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Image from "next/image";
import { API_ENDPOINT } from "constant/api";
import { processEnv } from "constant/processEnv";

const KAKAO_LOGIN_URL = processEnv.NEXT_PUBLIC_API_ENDPOINT + API_ENDPOINT.auth.kakao.login;

const ButtonKakaoLogin = () => {
return (
<a href={KAKAO_LOGIN_URL}>
<Image
alt="카카오 로그인 버튼"
src="/images/buttons/kakao_login_large_wide.png"
width={300}
height={45}
/>
</a>
);
};

export default ButtonKakaoLogin;
28 changes: 28 additions & 0 deletions src/feature/auth/auth.login/views/ViewLogin.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import styled from "@emotion/styled";
import Logo from "components/logo/Logo";
import ButtonKakaoLogin from "feature/auth/auth.login/components/ButtonKakaoLogin";

const ViewLogin = () => {
return (
<EmotionWrapper>
<Logo />
<ButtonKakaoLogin />
</EmotionWrapper>
);
};

export default ViewLogin;

const EmotionWrapper = styled.div`
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;

padding-top: 100px;

a {
display: block;
margin-top: 120px;
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const NICKNAME_LENGTH_LIMIT_MIN = 3;
export const NICKNAME_LENGTH_LIMIT_MAX = 20;
10 changes: 10 additions & 0 deletions src/feature/auth/auth.register/functions/validateNicknameLength.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {
NICKNAME_LENGTH_LIMIT_MAX,
NICKNAME_LENGTH_LIMIT_MIN,
} from "feature/auth/auth.register/constants/nicknameLengthLimit";

export const validateNicknameLength = (nickname: string): boolean => {
return (
nickname.length >= NICKNAME_LENGTH_LIMIT_MIN && nickname.length <= NICKNAME_LENGTH_LIMIT_MAX
);
};
81 changes: 81 additions & 0 deletions src/feature/auth/auth.register/views/ViewRegister.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import styled from "@emotion/styled";
import Button from "components/button/Button";
import TextInput from "components/inputs/TextInput/TextInput";
import { LINK_REGISTER_SUCCESS } from "constant/link";
import {
NICKNAME_LENGTH_LIMIT_MAX,
NICKNAME_LENGTH_LIMIT_MIN,
} from "feature/auth/auth.register/constants/nicknameLengthLimit";
import { validateNicknameLength } from "feature/auth/auth.register/functions/validateNicknameLength";
import { useRouter } from "next/router";
import { useCallback, useState } from "react";

const ViewRegister = () => {
const { push } = useRouter();
const [foodhubNickname, setFoodhubNickname] = useState({
value: "",
isValid: false,
});

const registerSuccessLink = LINK_REGISTER_SUCCESS;

const handleFoodhubNicknameChange = useCallback((value: string, isValid: boolean) => {
setFoodhubNickname({
value,
isValid,
});
}, []);

const handleRegisterFoodhub = () => {
if (!foodhubNickname.isValid) {
alert("닉네임을 확인해주세요."); // TODO: 모달 머지 후 모달로 변경
return;
}
// TODO: API 연동 , foodhubNickname.value 를 이용하여 닉네임 등록
// 성공 시 가입 성공 페이지로 이동
push(registerSuccessLink);
};

const messageOnErrorNicknameLength = `닉네임은 ${NICKNAME_LENGTH_LIMIT_MIN}자 이상 ${NICKNAME_LENGTH_LIMIT_MAX}자 이하로 입력해주세요.`;
const kakaoName = "홍길동"; // TODO: API 에서 받아오는 닉네임으로 변경
return (
<EmotionWrapper>
<p className="greeting">안녕하세요 {kakaoName}님, 👋</p>
<p className="instruction">푸드허브에서 사용할 닉네임을 설정해주세요!</p>

<TextInput
label="닉네임"
placeholder="닉네임을 설정해주세요!"
value={foodhubNickname.value}
onTextChange={handleFoodhubNicknameChange}
conditionCheckList={[
{ condition: validateNicknameLength, messageOnError: messageOnErrorNicknameLength },
]}
/>
{/* TODO: 각 공통 컴포넌트들이 className 을 받을 수 있게 된 후로 제거 */}
<div className="space" />
<Button fullWidth onClick={handleRegisterFoodhub}>
가입하기
</Button>
</EmotionWrapper>
);
};

export default ViewRegister;

const EmotionWrapper = styled.div`
p.greeting {
font-size: 24px;
font-weight: 700;
margin-bottom: 16px;
line-height: 1.5;
}

p.instruction {
margin-bottom: 64px;
}

.space {
height: 64px;
}
`;
46 changes: 46 additions & 0 deletions src/feature/auth/auth.register/views/ViewRegisterSuccess.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import styled from "@emotion/styled";
import Button from "components/button/Button";
import { LINK_MAIN_PAGE } from "constant/link";
import { useRouter } from "next/router";

const ViewRegisterSuccess = () => {
const { push } = useRouter();

const foodhubNickname = "을지문덕"; // TODO: API 에서 받아오는 닉네임으로 변경

const handleGotoMain = () => {
push(LINK_MAIN_PAGE);
};
return (
<EmotionWrapper>
<p className="greeting">{foodhubNickname}님, 환영합니다!</p>
<p className="instruction">쩝쩝대학의 다양한 맛집을 탐험해보세요!</p>

{/* TODO: 각 공통 컴포넌트들이 className 을 받을 수 있게 된 후로 제거 */}
<div className="space" />

<Button fullWidth onClick={handleGotoMain}>
쩝쩝대학 메인으로 가기
</Button>
</EmotionWrapper>
);
};

export default ViewRegisterSuccess;

const EmotionWrapper = styled.div`
p.greeting {
font-size: 24px;
font-weight: 700;
margin-bottom: 16px;
line-height: 1.5;
}

p.instruction {
margin-bottom: 64px;
}

.space {
height: 64px;
}
`;
3 changes: 2 additions & 1 deletion src/pages/_error.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Link from "next/link";
import styled from "@emotion/styled";
import { NextPageContext } from "next";
import { LINK_MAIN_PAGE } from "constant/link";

interface Props {
statusCode?: number;
Expand All @@ -13,7 +14,7 @@ const CustomError = ({ statusCode, title }: Props) => {
<EmotionWrapper>
<p className="status-code">{statusCode}</p>
<p className="title">{title}</p>
<Link href="/">홈으로</Link>
<Link href={LINK_MAIN_PAGE}>홈으로</Link>
</EmotionWrapper>
);
};
Expand Down
10 changes: 4 additions & 6 deletions src/pages/login/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import styled from "@emotion/styled";
import PageMarker from "components/pageMarker/PageMarker";
import { LINK_MAIN_PAGE } from "constant/link";
import ViewLogin from "feature/auth/auth.login/views/ViewLogin";
import { GetServerSideProps } from "next";

const PageLogin = () => {
return (
<EmotionWrapper>
<PageMarker
title="로그인 페이지"
description="카카오 계정을 이용해 로그인할 수 있는 페이지"
/>
<ViewLogin />
</EmotionWrapper>
);
};
Expand All @@ -25,7 +23,7 @@ export const getServerSideProps: GetServerSideProps = async ({ req, res, query }
const isLoggedIn = req.cookies.accessToken;

if (isLoggedIn) {
res.writeHead(302, { Location: "/" });
res.writeHead(302, { Location: LINK_MAIN_PAGE });
res.end();
}

Expand Down
7 changes: 4 additions & 3 deletions src/pages/register/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import styled from "@emotion/styled";
import PageMarker from "components/pageMarker/PageMarker";
import { LINK_MAIN_PAGE } from "constant/link";
import ViewRegister from "feature/auth/auth.register/views/ViewRegister";
import { GetServerSideProps } from "next";

const PageRegister = () => {
return (
<EmotionWrapper>
<PageMarker title="회원가입 페이지" description="회원가입 (닉네임 세팅) 하는 페이지" />
<ViewRegister />
</EmotionWrapper>
);
};
Expand All @@ -24,7 +25,7 @@ export const getServerSideProps: GetServerSideProps = async ({ req, res, query }
const hasAuthenticated = true;

if (isNicknameAlreadySet || !hasAuthenticated) {
res.writeHead(302, { Location: "/" });
res.writeHead(302, { Location: LINK_MAIN_PAGE });
res.end();
}

Expand Down
10 changes: 4 additions & 6 deletions src/pages/register/success.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import styled from "@emotion/styled";
import PageMarker from "components/pageMarker/PageMarker";
import { LINK_MAIN_PAGE } from "constant/link";
import ViewRegisterSuccess from "feature/auth/auth.register/views/ViewRegisterSuccess";
import { GetServerSideProps } from "next";

const PageRegisterSuccess = () => {
return (
<EmotionWrapper>
<PageMarker
title="회원가입 성공 페이지"
description="회원가입 (닉네임 세팅) 하고 나서 랜딩되는 페이지"
/>
<ViewRegisterSuccess />
</EmotionWrapper>
);
};
Expand All @@ -24,7 +22,7 @@ export const getServerSideProps: GetServerSideProps = async ({ req, res, query }
const hasUserAlreadyViewedSuccessPage = false;

if (hasUserAlreadyViewedSuccessPage) {
res.writeHead(302, { Location: "/" });
res.writeHead(302, { Location: LINK_MAIN_PAGE });
res.end();
}

Expand Down