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

[권주현] Week14 #449

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,9 @@

1. 소셜공유 로직을 분리합니다.
2. 모달 컴포넌트를 더 명확하고 간단하게 리팩토링합니다.
3. 폴더 페이지에서 각 폴더로 버튼으로 이동 가능하지만 브라우저 동작으로는 되지않는 문제를 해결합니다.
4. 로그인, 회원가입 폼을 구현합니다.
5. 상수파일에 상수들을 정리합니다.
6. 홈(/)에서 각 페이지로 이동 가능한 버튼을 추가합니다.
7. 타입과 로직을 분리하여 가독성을 향상시킵니다.
8. 정적렌더링과 서버사이드렌더링할 페이지를 구분하고 사전렌더링을 구현합니다.
120 changes: 120 additions & 0 deletions api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { SampleUser, UserData } from "@/types/user";
import { FolderData, SampleFolder } from "@/types/folder";
import { Response, SampleFolderResponse } from "@/types/response";
import { LinkData } from "@/types/link";
import { CODEIT_BASE_URL } from "@/constants";
import { FormValues } from "./common/Auth/Form";

export interface Params {
[key: string]: number | null;
}

export async function getUser(): Promise<SampleUser> {
const response = await fetch(`${CODEIT_BASE_URL}/sample/user`);
if (!response.ok) {
throw new Error("잘못된 요청입니다.");
}

const user: SampleUser = await response.json();

return user;
}

export async function getFolder(): Promise<SampleFolder> {
const response = await fetch(`${CODEIT_BASE_URL}/sample/folder`);
if (!response.ok) {
throw new Error("잘못된 요청입니다.");
}

const data: SampleFolderResponse = await response.json();
const { folder } = data;

return folder;
}

export async function getUserById({ userId }: Params): Promise<UserData> {
const response = await fetch(`${CODEIT_BASE_URL}/users/${userId}`);
if (!response.ok) {
throw new Error("잘못된 요청입니다.");
}

const user: Response<UserData> = await response.json();
const { data } = user;

return data[0];
}

export async function getFoldersByUserId({
userId,
}: Params): Promise<FolderData[]> {
const response = await fetch(`${CODEIT_BASE_URL}/users/${userId}/folders`);
if (!response.ok) {
throw new Error("잘못된 요청입니다.");
}

const folders: Response<FolderData> = await response.json();
const { data } = folders;

return data;
}

export async function getLinksByUserIdAndFolderId({
userId,
folderId,
}: Params): Promise<LinkData[]> {
let url = `${CODEIT_BASE_URL}/users/${userId}/links`;
if (folderId) {
url += `?folderId=${folderId}`;
}
Comment on lines +65 to +68
Copy link
Collaborator

Choose a reason for hiding this comment

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

삼항연산자를 사용하면 url 을 const 로 쓸 수 있을 거 같아요!


const response = await fetch(url);
if (!response.ok) {
throw new Error("잘못된 요청입니다.");
}

const links: Response<LinkData> = await response.json();
const { data } = links;

return data;
}

export async function postEmailCheck(email: string): Promise<void | string> {
const response = await fetch(`${CODEIT_BASE_URL}/check-email`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ email }),
});

if (response.status === 409) {
const data = await response.json();
return data.error.message;
}

if (!response.ok) {
throw new Error("잘못된 요청입니다.");
}

return;
}

export async function postSignup({
email,
password,
}: FormValues): Promise<void | string> {
const response = await fetch(`${CODEIT_BASE_URL}/sign-up`, {
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;
}
18 changes: 18 additions & 0 deletions common/Auth/Form/index.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.formContainer {
width: 100%;
max-width: 400px;
display: flex;
flex-direction: column;
gap: 15px;
}

.submitBtn {
font-size: 20px;
width: 100%;
height: 60px;
border: none;
border-radius: 8px;
color: var(--white);
background: var(--primary-gradient);
margin-bottom: 15px;
}
35 changes: 35 additions & 0 deletions common/Auth/Form/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { SubmitHandler, UseFormHandleSubmit, useForm } from "react-hook-form";
import styles from "./index.module.css";
import { ReactNode } from "react";

export interface FormValues {
email: string;
password: number;
passwordConfirm?: number;
}

export interface AuthFormProps {
children?: ReactNode;
onSubmit: SubmitHandler<FormValues>;
handleSubmit: UseFormHandleSubmit<FormValues, undefined>;
purpose: string;
}

function AuthForm({
children,
onSubmit,
handleSubmit,
purpose,
}: AuthFormProps) {
return (
<form className={styles.formContainer} onSubmit={handleSubmit(onSubmit)}>
{children}
<button className={styles.submitBtn} type="submit">
{(purpose === "signin" && "로그인") ||
(purpose === "signup" && "회원가입")}
</button>
</form>
);
}

export default AuthForm;
22 changes: 22 additions & 0 deletions common/Auth/Header/index.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.header {
margin-bottom: 30px;
display: flex;
flex-direction: column;
align-items: center;
}

.homeLink {
width: 210px;
height: 40px;
position: relative;
}

.headerSecondLine {
margin-top: 20px;
}

.toSignupLink {
margin-left: 10px;
color: var(--primary);
font-weight: 600;
}
43 changes: 43 additions & 0 deletions common/Auth/Header/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import Image from "next/image";
import Link from "next/link";
import styles from "./index.module.css";

interface AuthHeaderProps {
purpose: string;
Copy link
Collaborator

Choose a reason for hiding this comment

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

purpose 가 "signin" 아니면 "signup" 등 정해진 문자라서 조금 더 좁은 타입으로 정의해주심 좋을 거 같아요!
type TProps = "signin" | "signup"

}

function AuthHeader({ purpose }: AuthHeaderProps) {
return (
<header className={styles.header}>
<Link className={styles.homeLink} href="/">
<Image
fill
src="/images/Linkbrary.png"
alt="logo"
style={{ objectFit: "contain" }}
priority
/>
</Link>
<div className={styles.headerSecondLine}>
{(purpose === "signin" && (
<>
<span>회원이 아니신가요?</span>
<Link className={styles.toSignupLink} href="/signup">
회원 가입하기
</Link>
</>
)) ||
(purpose === "signup" && (
<>
<span>이미 회원이신가요?</span>
<Link className={styles.toSignupLink} href="/signin">
로그인 하기
</Link>
</>
))}
</div>
</header>
);
}

export default AuthHeader;
Original file line number Diff line number Diff line change
@@ -1,35 +1,40 @@
.container {
width: 350px;
width: 100%;
position: relative;
}

.inputWrapper {
width: 100%;
height: 60px;
padding: 18px 15px;
border: 1px solid var(--gray-500);
border-radius: 8px;
margin-bottom: 10px;
}

.inputWrapper:focus {
border: 1px solid var(--primary);
}

.inputLabel {
display: inline-block;
margin-bottom: 15px;
}

.eyeSlash {
border: none;
background: none;
position: absolute;
right: 16px;
top: 17px;
top: 53px;
}

.errorBorder {
border: 1px solid var(--red);
}

.errorMessage {
display: inline-block;
color: var(--red);
font-size: 14px;
font-weight: 400;
color: var(--red);
margin-top: 5px;
}
52 changes: 52 additions & 0 deletions common/Auth/Input/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { HTMLInputTypeAttribute } from "react";
import { FaEyeSlash } from "react-icons/fa";
import styles from "./index.module.css";
import {
FieldError,
FieldErrorsImpl,
FieldValues,
Merge,
} from "react-hook-form";
import classNames from "classnames";

interface AuthInputProps {
label: string;
type: HTMLInputTypeAttribute;
placeholder: string;
register: FieldValues;
error?: FieldError | Merge<FieldError, FieldErrorsImpl>;
}

function AuthInput({
label,
type,
placeholder,
register,
error,
}: AuthInputProps) {
return (
<div className={styles.container}>
<label className={styles.inputLabel} htmlFor={type}>
{label}
</label>
<input
type={type}
placeholder={placeholder}
className={classNames(styles.inputWrapper, {
[styles.errorBorder]: error,
})}
{...register}
/>
{error && (
<p className={styles.errorMessage}>{error.message?.toString()}</p>
)}
{type === "password" && (
<button className={styles.eyeSlash} type="button">
<FaEyeSlash />
</button>
)}
</div>
);
}

export default AuthInput;
File renamed without changes.
2 changes: 1 addition & 1 deletion components/Avatar/index.tsx → common/Avatar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Image from "next/image";
import classNames from "classnames";
import styles from "@/components/Avatar/Avatar.module.css";
import styles from "./index.module.css";

interface AvatarProps {
src: string;
Expand Down
8 changes: 8 additions & 0 deletions common/Button/SocialButton/index.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.shareButton {
background: none;
border: none;
border-radius: 100%;
display: flex;
align-items: center;
justify-content: center;
}
19 changes: 19 additions & 0 deletions common/Button/SocialButton/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ReactNode } from "react";
import styles from "./index.module.css";

interface SocialButtonProp {
children: ReactNode;
}

function SocialButton({ children }: SocialButtonProp) {
return (
<button
className={styles.shareButton}
// onClick={() => handleShareToKakao(title)}
>
{children}
</button>
);
}

export default SocialButton;
File renamed without changes.
2 changes: 1 addition & 1 deletion components/Footer/index.tsx → common/Footer/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FaFacebook, FaInstagram, FaTwitter, FaYoutube } from "react-icons/fa";
import styles from "@/components/Footer/Footer.module.css";
import styles from "./index.module.css";

function Footer() {
return (
Expand Down
File renamed without changes.
Loading
Loading