Skip to content
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
8 changes: 4 additions & 4 deletions components/addboard/AddBoardForm.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { ChangeEvent, useState, useEffect, MouseEvent } from "react";
import { useRouter } from "next/router";
import useAuth from "@/hooks/useAuth";
import FileInput from "../ui/FileInput";
import Input from "../ui/Input";
import Textarea from "../ui/Textarea";
import Button from "../ui/Button";
import { fetchData } from "@/lib/fetchData";
import { ARTICLE_URL, IMAGE_URL } from "@/constants/url";
import { useAuth } from "@/contexts/AuthProvider";
import styles from "./AddBoardForm.module.css";

interface Board {
Expand All @@ -26,8 +26,8 @@ const INITIAL_BOARD: Board = {
const AddBoardForm = () => {
const [isDisabled, setIsDisabled] = useState(true);
const [values, setValues] = useState<Board>(INITIAL_BOARD);
const { accessToken } = useAuth();
const router = useRouter();
const { accessToken } = useAuth(true);

const handleChange = (name: BoardField, value: Board[BoardField]): void => {
setValues((prevValues) => {
Expand Down Expand Up @@ -57,7 +57,7 @@ const AddBoardForm = () => {
const formData = new FormData();
formData.append("image", imgFile);

const response = await fetchData(IMAGE_URL, {
const response = await fetchData<Record<string, string>>(IMAGE_URL, {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
Expand All @@ -67,7 +67,7 @@ const AddBoardForm = () => {
url = response.url;
}

const { id } = await fetchData(ARTICLE_URL, {
const { id } = await fetchData<Record<string, string>>(ARTICLE_URL, {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
Expand Down
95 changes: 95 additions & 0 deletions components/auth/AuthForm.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
.authForm {
max-width: 400px;
display: flex;
flex-direction: column;
gap: 16px;
}

.input {
gap: 8px;
}

.authForm label {
font-size: 0.875rem;
line-height: 1.5rem;
}

.btnEye {
position: absolute;
width: 24px;
height: 24px;
top: 16px;
right: 16px;
cursor: pointer;
}

.button {
padding: 12px 145px;
border-radius: 40px;
font-size: 1.25rem;
line-height: 2rem;
cursor: pointer;
margin-bottom: 8px;
}

.button.active {
background-color: var(--blue);
}

.validationFocus {
outline: 1px solid var(--red);
}

.validationMessage {
display: none;
font-size: 0.875rem;
font-weight: 600;
line-height: 1.5rem;
color: var(--red);
padding-left: 16px;
margin-top: 8px;
}

.authLink {
font-size: 0.875rem;
font-weight: 500;
line-height: 1.5rem;
text-align: center;
color: var(--gary800);
}

.authLink > a {
color: var(--blue);
text-decoration: underline;
padding-left: 4px;
}

@media screen and (min-width: 768px) {
.authForm {
max-width: 100%;
width: 640px;
gap: 24px;
}

.input {
gap: 16px;
}

.authForm label {
font-size: 1.125rem;
line-height: 1.625rem;
}

.authForm .authInput {
padding: 16px 24px;
}

.button {
padding: 16px 124px;
margin-bottom: 0;
}

.authForm > .otherAccount {
margin-bottom: 0;
}
}
99 changes: 99 additions & 0 deletions components/auth/LoginForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { useState } from "react";
import Link from "next/link";
import Image from "next/image";
import { useRouter } from "next/router";
import { useForm } from "react-hook-form";
import useAuth from "@/hooks/useAuth";
import FormInput from "../ui/FormInput";
import SocialLogin from "./SocialLogin";
import Button from "../ui/Button";
import styles from "./AuthForm.module.css";
import hideIcon from "@/public/btn_hide.svg";
import showIcon from "@/public/btn_show.svg";

interface FormValues extends Record<string, string> {
email: string;
password: string;
}

const LoginForm = () => {
const [showPassword, setShowPassword] = useState(false);
const {
register,
handleSubmit,
formState: { errors, isValid },
setError,
} = useForm<FormValues>({ mode: "onChange" });
const router = useRouter();
const { login } = useAuth();

const onSubmit = async (data: FormValues) => {
try {
await login(data);
router.push("/");
} catch (err: unknown) {
if (err instanceof Error) {
if (err.message === "존재하지 않는 이메일입니다.") {
setError("email", { type: "manual", message: err.message });
}
if (err.message === "비밀번호가 일치하지 않습니다.") {
setError("password", { type: "manual", message: err.message });
}
}
}
};

const togglePasswordVisibility = () => {
setShowPassword(!showPassword);
};

return (
<form className={styles.authForm} onSubmit={handleSubmit(onSubmit)}>
<FormInput
className={styles.input}
type="text"
name="email"
label="이메일"
placeholder="이메일을 입력해주세요"
register={register}
required="이메일을 입력해주세요."
pattern={{
value: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
Copy link
Collaborator

Choose a reason for hiding this comment

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

P2:
정규 표현식이 로그인, 회원가입에서 공용으로 쓰이니 따로 객체에 저장해두시고 같이 쓰시면 더 좋을 것 같습니다.
에러메시지도 여러 페이지에서 반복적으로 사용된다면 동일합니다~

message: "잘못된 이메일 형식입니다.",
}}
error={errors.email}
/>
<FormInput
className={styles.input}
type={showPassword ? "text" : "password"}
name="password"
label="비밀번호"
placeholder="비밀번호를 입력해주세요"
register={register}
required="비밀번호를 입력해주세요."
minLength={{
value: 8,
message: "비밀번호를 8자 이상 입력해주세요.",
}}
error={errors.password}
>
<Image
src={showPassword ? showIcon : hideIcon}
className={styles.btnEye}
onClick={togglePasswordVisibility}
alt="비밀번호 표시"
/>
</FormInput>
<Button className={styles.button} type="submit" disabled={!isValid}>
로그인
</Button>
<SocialLogin />
<span className={styles.authLink}>
판다마켓이 처음이신가요?
<Link href="/signup">회원가입</Link>
</span>
</form>
);
};

export default LoginForm;
143 changes: 143 additions & 0 deletions components/auth/SignUpForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { useState, useEffect } from "react";
import Link from "next/link";
import Image from "next/image";
import { useRouter } from "next/router";
import { useForm } from "react-hook-form";
import useAuth from "@/hooks/useAuth";
import FormInput from "../ui/FormInput";
import SocialLogin from "./SocialLogin";
import Button from "../ui/Button";
import styles from "./AuthForm.module.css";
import hideIcon from "@/public/btn_hide.svg";
import showIcon from "@/public/btn_show.svg";

interface FormValues extends Record<string, string> {
email: string;
nickname: string;
password: string;
passwordConfirmation: string;
}

const LoginForm = () => {
const [showPassword, setShowPassword] = useState(false);
const [showPasswordConfirmation, setShowPasswordConfirmation] =
useState(false);
const {
register,
handleSubmit,
formState: { errors, isValid },
watch,
setError,
trigger,
} = useForm<FormValues>({ mode: "onChange" });
const router = useRouter();
const { signup } = useAuth();
const watchedPassword = watch("password");

const onSubmit = async (data: FormValues) => {
try {
await signup(data);
router.push("/login");
} catch (err: unknown) {
if (err instanceof Error) {
if (err.message === "이미 사용중인 이메일입니다.") {
setError("email", { type: "manual", message: err.message });
}
if (err.message === "이미 사용중인 닉네임입니다.") {
setError("nickname", { type: "manual", message: err.message });
}
}
}
};

const togglePasswordVisibility = () => setShowPassword(!showPassword);
const togglePasswordConfirmationVisibility = () =>
setShowPasswordConfirmation(!showPasswordConfirmation);

useEffect(() => {
if (watchedPassword) {
trigger("passwordConfirmation");
}
}, [watchedPassword, trigger]);

return (
<form className={styles.authForm} onSubmit={handleSubmit(onSubmit)}>
<FormInput
className={styles.input}
type="text"
name="email"
label="이메일"
placeholder="이메일을 입력해주세요"
register={register}
required="이메일을 입력해주세요"
pattern={{
value: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
message: "잘못된 이메일 형식입니다.",
}}
error={errors.email}
/>
<FormInput
className={styles.input}
type="text"
name="nickname"
label="닉네임"
placeholder="닉네임을 입력해주세요"
register={register}
required="닉네임을 입력해주세요"
error={errors.nickname}
/>
<FormInput
className={styles.input}
type={showPassword ? "text" : "password"}
name="password"
label="비밀번호"
placeholder="비밀번호를 입력해주세요"
register={register}
required="비밀번호를 입력해주세요."
minLength={{
value: 8,
message: "비밀번호를 8자 이상 입력해주세요.",
}}
error={errors.password}
>
<Image
src={showPassword ? showIcon : hideIcon}
className={styles.btnEye}
onClick={togglePasswordVisibility}
alt="비밀번호 표시"
/>
</FormInput>
<FormInput
className={styles.input}
type={showPasswordConfirmation ? "text" : "password"}
name="passwordConfirmation"
label="비밀번호 확인"
placeholder="비밀번호를 다시 한 번 입력해주세요"
register={register}
validate={{
matchesPassword: (value) =>
value === watchedPassword || "비밀번호가 일치하지 않습니다.",
required: (value) => value !== "" || "비밀번호 확인을 입력해주세요.",
}}
error={errors.passwordConfirmation}
>
<Image
src={showPasswordConfirmation ? showIcon : hideIcon}
className={styles.btnEye}
onClick={togglePasswordConfirmationVisibility}
alt="비밀번호 표시"
/>
</FormInput>
<Button className={styles.button} type="submit" disabled={!isValid}>
회원가입
</Button>
<SocialLogin />
<span className={styles.authLink}>
이미 회원이신가요?
<Link href="/login">로그인</Link>
</span>
</form>
);
};

export default LoginForm;
Loading
Loading