Skip to content

Commit

Permalink
Merge pull request #1080 from ayoung-iya/part3-윤아영-week20
Browse files Browse the repository at this point in the history
[윤아영] week19 & week20
  • Loading branch information
arthurkimdev authored May 24, 2024
2 parents 3beaf53 + 9398480 commit f28a0d4
Show file tree
Hide file tree
Showing 12 changed files with 223 additions and 33 deletions.
5 changes: 2 additions & 3 deletions api/api.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { USER_ID, totalFolderId } from '@/util/constants';
import type { Folder, FolderAPI, LinkTypes, LinkAPITypes, User } from '@/types/types';

const BASE_URL = 'https://bootcamp-api.codeit.kr/api';
import { BASE_URL_LEGACY } from '@/util/apiConstants';

async function getAPI(query: string) {
const response = await fetch(`${BASE_URL}/${query}`);
const response = await fetch(`${BASE_URL_LEGACY}/${query}`);

if (!response?.ok) {
throw new Error('데이터를 불러오는데 실패했습니다.');
Expand Down
69 changes: 69 additions & 0 deletions api/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { BASE_URL } from '@/util/apiConstants';

export const postSignIn = async ({ email, password }: { email: string; password: string }) => {
const response = await fetch(`${BASE_URL}/auth/sign-in`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email, password }),
});

if (!response?.ok) {
throw new Error('로그인을 실패했습니다.');
}

const token = await response.json();

return token;
};

export const postCheckEmail = async (email: string) => {
const response = await fetch(`${BASE_URL}/users/check-email`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email }),
});

const body = await response.json();

if (!response?.ok) {
throw new Error(body.message);
}
};

export const postSignUp = async ({ email, password }: { email: string; password: string }) => {
const response = await fetch(`${BASE_URL}/auth/sign-up`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email, password }),
});

if (!response?.ok) {
throw new Error('회원가입을 실패했습니다.');
}

const token = await response.json();

return token;
};

export const getUser = async () => {
const token = window.localStorage.getItem('accessToken');

const response = await fetch(`${BASE_URL}/users`, {
headers: {
Authorization: `Bearer ${token}`,
},
});

const body = await response.json();

if (!response?.ok) {
throw new Error(body.message);
}

return {...body[0], imageSource: body[0]['image_source']};
};
21 changes: 3 additions & 18 deletions components/layout/header/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,15 @@
import Link from 'next/link';
import styles from './Header.module.css';
import { useEffect, useState } from 'react';
import { useContext } from 'react';
import { useRouter } from 'next/router';
import type { User } from '@/types/types';
import { getUser } from '@/api/api';
import Image from 'next/image';
import { authContext } from '@/context/authProvider';

function Header() {
const [user, setUser] = useState<User | null>(null);
const { user } = useContext(authContext);
const { pathname } = useRouter();
const headerPosition = pathname === '/folder' ? styles.static : '';

useEffect(() => {
const fetchUser = async () => {
try {
const user = await getUser();
setUser(user);
} catch (err) {
const error = err as Error;
console.error(error.message);
}
};

fetchUser();
}, []);

return (
<header className={`${styles.headerArea} ${headerPosition}`}>
<div className={styles.headerGroup}>
Expand Down
10 changes: 8 additions & 2 deletions components/pages/sign/InputGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useFormContext } from 'react-hook-form';
import styles from '@/styles/sign.module.css';
import { useState } from 'react';

const InputGroup = ({ info }: any) => {
const InputGroup = ({ info, onBlur }: any) => {
const {
register,
trigger,
Expand All @@ -22,7 +22,13 @@ const InputGroup = ({ info }: any) => {
<input
type={type}
id={info.id}
{...register(info.id, { ...info.validation, onBlur: async () => await trigger(info.id) })}
{...register(info.id, {
...info.validation,
onBlur: async e => {
await trigger(info.id);
errors[info.id]?.message || onBlur && (await onBlur(e.target.value));
},
})}
placeholder={info.placeholder}
className={inputClassName}
/>
Expand Down
2 changes: 1 addition & 1 deletion constants/sign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const INPUT_INFO = {
validation: {
validate: (value: any, formValues: any) => {
if (value.length === 0 && formValues.password.length === 0) return ERROR_MESSAGE.password.required;
return value !== formValues.password && ERROR_MESSAGE.password.checkSame;
if (value !== formValues.password) return ERROR_MESSAGE.password.checkSame;
},
pattern: {
value: /^(?=.*[a-zA-Z])(?=.*[0-9]).{8,}$/,
Expand Down
61 changes: 61 additions & 0 deletions context/authProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { getUser, postSignIn, postSignUp } from '@/api/auth';
import { PropsWithChildren, createContext, useEffect, useState } from 'react';

interface User {
id: number;
name: string;
imageSource: string;
email: string;
}

interface AuthContext {
user?: User;
signIn: (data: any) => void;
signUp: (data: any) => void;
signOut: () => void;
}

export const authContext = createContext<AuthContext>({
user: undefined,
signIn: () => {},
signUp: () => {},
signOut: () => {},
});

const AuthProvider = ({ children }: PropsWithChildren) => {
const [user, setUser] = useState<User>();

const signIn = async (data: any) => {
const { accessToken, refreshToken } = await postSignIn(data);

window.localStorage.setItem('accessToken', accessToken);
window.localStorage.setItem('refreshToken', refreshToken);
};
const signUp = async (data: any) => {
const { accessToken, refreshToken } = await postSignUp(data);

window.localStorage.setItem('accessToken', accessToken);
window.localStorage.setItem('refreshToken', refreshToken);
};

const signOut = () => {
// TODO: 로그아웃 기능 구현
};

useEffect(() => {
const fetchUser = async () => {
try {
const user = await getUser();
setUser(user);
} catch (error) {
console.error((error as Error).message);
}
};

fetchUser();
}, []);

return <authContext.Provider value={{ user, signIn, signUp, signOut }}>{children}</authContext.Provider>;
};

export default AuthProvider;
11 changes: 11 additions & 0 deletions hooks/useAuth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { authContext } from '@/context/authProvider';
import { useContext } from 'react';

export const useAuth = () => {
const context = useContext(authContext);
if (!context) {
throw new Error('반드시 AuthProvider 안에서 사용해야 합니다.');
}

return context;
};
6 changes: 6 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ const nextConfig = {
port: '',
pathname: '/badges/**',
},
{
protocol: 'https',
hostname: 'avatars.githubusercontent.com',
port: '',
pathname: '/u/**',
},
],
},
};
Expand Down
9 changes: 6 additions & 3 deletions pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import '@/styles/globals.css';
import { Noto_Sans_KR } from 'next/font/google';
import type { AppProps } from 'next/app';
import Layout from '@/components/layout/Layout';
import AuthProvider from '@/context/authProvider';

const notoSansKR = Noto_Sans_KR({
subsets: ['latin'],
Expand All @@ -11,9 +12,11 @@ const notoSansKR = Noto_Sans_KR({
export default function App({ Component, pageProps }: AppProps) {
return (
<main className={notoSansKR.className}>
<Layout>
<Component {...pageProps} />
</Layout>
<AuthProvider>
<Layout>
<Component {...pageProps} />
</Layout>
</AuthProvider>
</main>
);
}
23 changes: 20 additions & 3 deletions pages/signin.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,32 @@
import { postSignIn } from '@/api/auth';
import InputGroup from '@/components/pages/sign/InputGroup';
import { INPUT_INFO } from '@/constants/sign';
import { ERROR_MESSAGE, INPUT_INFO } from '@/constants/sign';
import { useAuth } from '@/hooks/useAuth';
import styles from '@/styles/sign.module.css';
import Image from 'next/image';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { FormProvider, useForm } from 'react-hook-form';

export default function SignIn() {
const methods = useForm();
const router = useRouter();
const { signIn } = useAuth();

const onSubmit = (data: any) => {
console.log(data);
const onSubmit = async (data: any) => {
try {
signIn(data);
router.push('/folder');
} catch {
methods.setError(INPUT_INFO.email.id, {
type: 'failed',
message: ERROR_MESSAGE.email.checkRight,
});
methods.setError(INPUT_INFO.password.signIn.id, {
type: 'failed',
message: ERROR_MESSAGE.password.checkRight,
});
}
};

return (
Expand Down
37 changes: 34 additions & 3 deletions pages/signup.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,46 @@
import { postCheckEmail, postSignUp } from '@/api/auth';
import InputGroup from '@/components/pages/sign/InputGroup';
import { INPUT_INFO } from '@/constants/sign';
import { useAuth } from '@/hooks/useAuth';
import styles from '@/styles/sign.module.css';
import Image from 'next/image';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';

export default function SignIn() {
const { signUp } = useAuth();
const methods = useForm();
const [emailCheckFailed, setEmailCheckFailed] = useState(false);
const router = useRouter();

const onSubmit = (data: any) => {
console.log(data);
const checkEmailDuplicate = async (email: string) => {
try {
await postCheckEmail(email);
setEmailCheckFailed(false);
} catch (error) {
methods.setError(INPUT_INFO.email.id, {
type: 'duplication',
message: (error as Error).message,
});
setEmailCheckFailed(true);
}
};

const onSubmit = async ({ email, password }: any) => {
if (emailCheckFailed) {
await checkEmailDuplicate(email);
return;
}

try {
signUp({ email, password });

router.push('/folder');
} catch (error) {
console.error((error as Error).message);
}
};

return (
Expand All @@ -28,7 +59,7 @@ export default function SignIn() {

<FormProvider {...methods}>
<form action="" className={styles.formArea} onSubmit={methods.handleSubmit(onSubmit)}>
<InputGroup info={INPUT_INFO.email} />
<InputGroup info={INPUT_INFO.email} onBlur={checkEmailDuplicate} />
<InputGroup info={INPUT_INFO.password.signUp} />
<InputGroup info={INPUT_INFO.passwordCheck} />

Expand Down
2 changes: 2 additions & 0 deletions util/apiConstants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const BASE_URL_LEGACY = 'https://bootcamp-api.codeit.kr/api';
export const BASE_URL = 'https://bootcamp-api.codeit.kr/api/linkbrary/v1';

0 comments on commit f28a0d4

Please sign in to comment.