Skip to content

Commit

Permalink
Merge pull request #399 from dkile/part3-정용준-week13
Browse files Browse the repository at this point in the history
[정용준] Week13
  • Loading branch information
JaeSang1998 authored Jan 19, 2024
2 parents ae3ffb7 + 975aa38 commit e1e746d
Show file tree
Hide file tree
Showing 30 changed files with 700 additions and 156 deletions.
Binary file modified bun.lockb
Binary file not shown.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@
"test": "vitest"
},
"dependencies": {
"@hookform/resolvers": "^3.3.4",
"dayjs": "^1.11.10",
"ky": "^1.1.3",
"next": "13.5.6",
"react": "^18",
"react-dom": "^18",
"react-hook-form": "^7.49.3",
"zod": "^3.22.4"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export default function App({ Component, pageProps }: AppProps) {
return (
<AuthProvider>
<UserProvider>
<Component {...pageProps} />;
<Component {...pageProps} />
</UserProvider>
</AuthProvider>
);
Expand Down
2 changes: 1 addition & 1 deletion pages/folder/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const getServerSideProps = async () => {
} catch (err) {
return {
redirect: {
destination: PAGE_ROUTES.SIGN_IN,
destination: PAGE_ROUTES.SIGNIN,
permanent: false,
},
};
Expand Down
20 changes: 20 additions & 0 deletions pages/signin/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { useRouter } from "next/router";
import SigninForm from "@/components/auth/SigninForm";
import SocialSigninNav from "@/components/auth/SocialSigninNav";
import { PAGE_ROUTES } from "@/routes";
import { checkAuthenticated } from "@/utils/auth";

export default function Page() {
const router = useRouter();

if (checkAuthenticated()) router.push(PAGE_ROUTES.FOLDER);

return (
<main className="flex h-full flex-col items-center justify-center bg-u-skyblue px-[3.2rem]">
<section className="flex w-full max-w-[40rem] flex-col gap-[3.2rem]">
<SigninForm />
<SocialSigninNav />
</section>
</main>
);
}
20 changes: 20 additions & 0 deletions pages/signup/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { useRouter } from "next/router";
import SignupForm from "@/components/auth/SignupForm";
import SocialSignupNav from "@/components/auth/SocialSignupNav";
import { PAGE_ROUTES } from "@/routes";
import { checkAuthenticated } from "@/utils/auth";

export default function Page() {
const router = useRouter();

if (checkAuthenticated()) router.push(PAGE_ROUTES.FOLDER);

return (
<main className="flex h-full flex-col items-center justify-center bg-u-skyblue px-[3.2rem]">
<section className="flex w-full max-w-[40rem] flex-col gap-[3.2rem]">
<SignupForm />
<SocialSignupNav />
</section>
</main>
);
}
23 changes: 23 additions & 0 deletions src/apis/auth/auth.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { z } from "zod";

export const AuthTokenVO = z.object({
accessToken: z.string(),
refreshToken: z.string(),
});
export type AuthToken = z.infer<typeof AuthTokenVO>;

export const SigninResponse = z.object({
data: AuthTokenVO,
});
export type SigninResponse = z.infer<typeof SigninResponse>;

export const AuthRequestBodyVO = z.object({
email: z.string(),
password: z.string(),
});
export type AuthRequestBodyVO = z.infer<typeof AuthRequestBodyVO>;

export const SignupResponse = z.object({
data: AuthTokenVO,
});
export type SignupResponse = z.infer<typeof SignupResponse>;
39 changes: 39 additions & 0 deletions src/apis/auth/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { apiRouteUtils } from "@/routes";
import { SigninResponse, SignupResponse } from "@/apis/auth/auth.schema";
import { fetcher } from "@/apis/fetcher";

export const postSignin = (email: string, password: string) => {
const res = fetcher
.post(apiRouteUtils.SIGNIN, {
json: {
email,
password,
},
})
.json()
.then(SigninResponse.parse)
.catch((err) => {
console.error(err);
throw err;
});

return res;
};

export const postSignup = (email: string, password: string) => {
const res = fetcher
.post(apiRouteUtils.SIGNUP, {
json: {
email,
password,
},
})
.json()
.then(SignupResponse.parse)
.catch((err) => {
console.error(err);
throw err;
});

return res;
};
10 changes: 8 additions & 2 deletions src/apis/fetcher.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { API_ROUTE } from "@/routes";
import ky from "ky";
import { API_ROUTE } from "@/routes";
import { getAccessToken } from "@/utils/auth";

export const fetcher = ky.create({
prefixUrl: API_ROUTE,
prefixUrl: API_ROUTE,
hooks: {
beforeRequest: [
(req) => req.headers.set("Authorization", `Bearer ${getAccessToken()}`),
],
},
});
7 changes: 7 additions & 0 deletions src/apis/user/user.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,10 @@ export const UserResponse = z.object({
data: z.array(UserVO),
});
export type UserResponse = z.infer<typeof UserResponse>;

export const CheckEmailResponse = z.object({
data: z.object({
isUsableNickname: z.boolean(),
}),
});
export type CheckEmailResponse = z.infer<typeof CheckEmailResponse>;
19 changes: 18 additions & 1 deletion src/apis/user/user.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { UserResponse } from "@/apis/user/user.schema";
import { CheckEmailResponse, UserResponse } from "@/apis/user/user.schema";
import { fetcher } from "@/apis/fetcher";
import { apiRouteUtils } from "@/routes";

Expand All @@ -14,3 +14,20 @@ export const getUser = async (userId: number) => {

return res;
};

export const postCheckEmail = async (email: string) => {
const res = await fetcher
.post(apiRouteUtils.CHECK_EMAIL, {
json: {
email,
},
})
.json()
.then(CheckEmailResponse.parse)
.catch((err) => {
console.error(err);
throw err;
});

return res;
};
122 changes: 122 additions & 0 deletions src/components/auth/SigninForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import Link from "next/link";
import Image from "next/image";
import { useRouter } from "next/router";
import { useForm } from "react-hook-form";
import { ASSET_ROUTES, PAGE_ROUTES } from "@/routes";
import Input from "@/components/common/Input";
import { RGX_EMAIL } from "@/utils/regex";
import { resolvers } from "@/resolvers/auth.resolver";

export type SigninFormField = {
email: string;
password: string;
};

export default function SigninForm() {
const router = useRouter();
const {
register,
handleSubmit,
formState: { errors },
setError,
trigger,
} = useForm<SigninFormField>();

const onSubmit = async ({ email, password }: SigninFormField) => {
try {
trigger(["email", "password"]);
await resolvers.resolveSignin(email, password);
router.push(PAGE_ROUTES.FOLDER);
} catch (err) {
setError("email", {
type: "custom",
message: "이메일을 확인해주세요.",
});
setError("password", {
type: "custom",
message: "비밀번호를 확인해주세요.",
});
}
};

const handleBlurEmailInput = () => {
trigger("email");
};

const handleBlurPasswordInput = () => {
trigger("password");
};

return (
<form
onSubmit={handleSubmit(onSubmit)}
className="flex flex-col gap-[3rem]"
>
<header className="flex flex-col items-center gap-[1.6rem]">
<Link href={PAGE_ROUTES.HOME}>
<h1 className="hidden">Linkbrary</h1>
<Image
src={`${ASSET_ROUTES.ICON}/logo.svg`}
alt="Linkbrary 로고"
width={210}
height={38}
priority
/>
</Link>
<div className="text-[1.6rem]">
회원이 아니신가요
<Link
href={PAGE_ROUTES.SIGNUP}
className="ml-[0.4rem] font-bold text-u-primary underline underline-offset-4"
>
회원 가입하기
</Link>
</div>
</header>
<div className="flex flex-col gap-[2.4rem]">
<div className="flex flex-col gap-[1.2rem] text-[1.4rem]">
<label htmlFor="email" className="cursor-pointer">
이메일
</label>
<Input<SigninFormField>
id="email"
type="text"
name="email"
placeholder="이메일을 입력해주세요."
error={errors.email?.message}
register={register}
options={{
required: "이메일을 입력해주세요.",
pattern: {
value: RGX_EMAIL,
message: "올바른 이메일 주소가 아닙니다.",
},
onBlur: handleBlurEmailInput,
}}
/>
</div>
<div className="flex flex-col gap-[1.2rem] text-[1.4rem]">
<label htmlFor="password">비밀번호</label>
<Input<SigninFormField>
id="password"
type="password"
name="password"
placeholder="비밀번호를 입력해주세요."
error={errors.password?.message}
register={register}
options={{
required: "비밀번호를 입력해주세요.",
onBlur: handleBlurPasswordInput,
}}
/>
</div>
</div>
<button
type="submit"
className="w-full justify-center rounded-[0.8rem] bg-gradient-purple-skyblue px-[2rem] py-[1.6rem] text-[1.8rem] text-u-white"
>
로그인
</button>
</form>
);
}
Loading

0 comments on commit e1e746d

Please sign in to comment.