Skip to content

Commit

Permalink
Merge pull request #15 from MOVIEJOJO7/Feature/#2
Browse files Browse the repository at this point in the history
Feat : 로그인 & 회원가입 구현
  • Loading branch information
hhjs2 authored Nov 10, 2023
2 parents e808990 + a6d2c27 commit 7d70bd7
Show file tree
Hide file tree
Showing 12 changed files with 359 additions and 14 deletions.
120 changes: 120 additions & 0 deletions Components/Join/JoinForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
'use client';

import React from 'react';
import { useForm, SubmitHandler } from 'react-hook-form';
import { Button } from '@material-tailwind/react';
import { fetchJoin } from '@/app/join/join.utils';

type RequestBody = {
id: string; // 사용자 아이디 (필수!, 영어와 숫자만)
password: string; // 사용자 비밀번호, 5자 이상 (필수!)
name: string; // 사용자 이름, 20자 이하 (필수!)
picture: string; // 사용자 이미지(url or base64, under 1MB)
};

const JoinForm = () => {
const {
register,
handleSubmit,
watch,
formState: { errors },
} = useForm<RequestBody>();

// 로그인 시 api 요청
const onSubmit: SubmitHandler<RequestBody> = ({
id,
password,
name,
picture,
}) => {
fetchJoin(id, password, name, picture);
};

const image = watch('picture');
console.log(image);

return (
<>
<form
onSubmit={handleSubmit(onSubmit)}
className="w-[300px] h-[500px] bg-white flex flex-col items-center "
>
<div className="flex flex-col items-center justify-center h-screen">
{/* 이미지 */}
{image ? (
<img src={image} className="h-14 w-14 rounded-full bg-blue-500" />
) : (
<div className="h-20 w-20 rounded-full bg-blue-500" />
)}
{/* 이미지 url */}
<div className="relative m-3 group">
<input
placeholder=" "
{...register('picture', {
required: true,
})}
className="border-b py-1 focus:outline-none focus:border-purple-600 focus:border-b-2 transition-colors peer"
/>
<label className="absolute left-0 top-1 text-gray-600 cursor-text peer-focus:text-xs peer-focus:-top-4 peer-focus:text-purple-600 transition-all">
Image URL
</label>
</div>
{/* id */}
<div className="relative m-3 group">
<input
placeholder=" "
{...register('id', {
required: true,
pattern: /^[a-zA-Z0-9]*$/,
})}
className="border-b py-1 focus:outline-none focus:border-purple-600 focus:border-b-2 transition-colors peer"
/>
<label className="absolute left-0 top-1 text-gray-600 cursor-text peer-focus:text-xs peer-focus:-top-4 peer-focus:text-purple-600 transition-all">
id
</label>
</div>
{/* 비밀번호 */}
<div className="relative m-3">
<input
{...register('password', {
required: true,
minLength: 5,
})}
className="border-b py-1 focus:outline-none focus:border-purple-600 focus:border-b-2 transition-colors peer"
/>
<label className="absolute left-0 top-1 text-gray-600 cursor-text peer-focus:text-xs peer-focus:-top-4 peer-focus:text-purple-600 transition-all">
password
</label>
{errors?.password?.type === 'minLength' && (
<p>입력은 최소 5자 이상이어야 합니다.</p>
)}
</div>
{/* 이름 */}
<div className="relative m-3">
<input
{...register('name', {
required: true,
maxLength: 20,
})}
className="border-b py-1 focus:outline-none focus:border-purple-600 focus:border-b-2 transition-colors peer"
/>
<label className="absolute left-0 top-1 text-gray-600 cursor-text peer-focus:text-xs peer-focus:-top-4 peer-focus:text-purple-600 transition-all">
name
</label>
{errors?.name?.type === 'maxLength' && (
<p>입력은 최대 20자 이상이어야 합니다.</p>
)}

{errors?.id ? <p className="error">{errors.id?.message}</p> : null}
</div>
</div>

<Button type="submit" className="w-full bg-main">
회원가입
</Button>
</form>
</>
);
};

export default JoinForm;
31 changes: 31 additions & 0 deletions Components/Login/Cookie.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Cookies } from 'react-cookie';

const cookies = new Cookies();

type CookieOptions = {
expires?: Date;
path?: string;
domain?: string;
secure?: boolean; // true : 웹 브라우저와 웹 서버가 https로 통신하는 경우에만 쿠키 저장
httpOnly?: boolean; // true : document.cookie라는 자바스크립트 코드로 쿠키에 비정상적으로 접속하는 것을 막는 옵션
// 다른 속성이 있다면 추가할 수 있습니다.
};

// setCookie: 쿠키를 저장하는 함수
export const setCookie = (
name: string,
value: string,
option: CookieOptions,
) => {
return cookies.set(name, value, { ...option });
};

// getCookie: 쿠키를 가지고 오는 함수
export const getCookie = (name: string) => {
return cookies.get(name);
};

// removeCookie: 쿠키를 삭제하는 함수
export const removeCookie = (name: string, option: CookieOptions) => {
return cookies.remove(name, { ...option });
};
57 changes: 57 additions & 0 deletions Components/Login/LoginForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
'use client';

import React from 'react';
import { useForm, SubmitHandler } from 'react-hook-form';
import { fetchLogin } from '../../app/login/login.utils';
import { setCookie } from '@/Components/Login/Cookie';
import { Button } from '@material-tailwind/react';

type IFormInput = {
id: string; // 사용자 아이디 (필수!, 영어와 숫자만)
password: string; // 사용자 비밀번호, 5자 이상 (필수!)
};

const LoginForm = () => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<IFormInput>();

// 로그인 버튼 클릭 시
const onSubmit: SubmitHandler<IFormInput> = async ({ id, password }) => {
console.log('id: ', id, 'password:', password);
const { accessToken, refreshToken } = await fetchLogin(id, password);
console.log('accessToken:', accessToken);
console.log('refreshToken:', refreshToken);
// 현재 시간
const time = new Date();
// 1일 뒤
time.setMinutes(time.getMinutes() + 60 * 24);

setCookie('accessToken', accessToken, { path: '/', expires: time });
setCookie('refreshToken', refreshToken, { path: '/' });
};

return (
<form onSubmit={handleSubmit(onSubmit)}>
<label>id</label>
{/* 영어와 숫자만 */}
<input
{...register('id', {
required: true,
})}
/>
{errors?.id ? <p className="error">{errors.id?.message}</p> : null}

<label>password</label>
{/* 5자 이상 */}
<input {...register('password')} />
<Button type="submit" className=" bg-pink-200">
로그인
</Button>
</form>
);
};

export default LoginForm;
32 changes: 32 additions & 0 deletions app/join/join.utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
type RequestBody = {
id: string;
password: string;
name: string;
picture: string;
};

export const fetchJoin = async (
id: string,
password: string,
name: string,
picture: string,
) => {
const requestData: RequestBody = {
id,
password,
name,
picture,
};

const res = await fetch('https://fastcampus-chat.net/signup', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
serverId: process.env.NEXT_PUBLIC_SERVER_ID as string,
},
body: JSON.stringify(requestData),
});
const data = await res.json();
console.log(data);
return data;
};
12 changes: 12 additions & 0 deletions app/join/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import JoinForm from '@/Components/Join/JoinForm';
import React from 'react';

const Join = () => {
return (
<div>
<JoinForm></JoinForm>
</div>
);
};

export default Join;
32 changes: 32 additions & 0 deletions app/login/login.utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
type RequestBody = {
id: string;
password: string;
};

type LoginResult = {
accessToken: string;
refreshToken: string;
// 다른 필드들도 있을 수 있습니다.
};

export const fetchLogin = async (id: string, password: string) => {
const requestData: RequestBody = {
id,
password,
};
console.log(process.env.NEXT_PUBLIC_SERVER_ID);
const res = await fetch('https://fastcampus-chat.net/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
serverId: process.env.NEXT_PUBLIC_SERVER_ID as string,
},
// Content-Type이 JSON이니까 JSON.stringify
body: JSON.stringify(requestData),
});
// 응답 데이터를 JSON 형식으로 파싱한 다음 data 변수 저장
const data: LoginResult = await res.json();
// const { accessToken, refreshToken } = await res.json();
// console.log('accessToken:', accessToken, 'refreshToken:', refreshToken);
return data;
};
12 changes: 12 additions & 0 deletions app/login/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';
import LoginForm from '../../Components/Login/LoginForm';

const Login = () => {
return (
<div>
<LoginForm></LoginForm>
</div>
);
};

export default Login;
4 changes: 2 additions & 2 deletions app/open/open.utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
export const fetchAllChat = async (token: string, userId: string) => {
export const fetchAllChat = async (token: string) => {
const res = await fetch('https://fastcampus-chat.net/chat', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
serverId: process.env.SERVER_KEY as string,
serverId: process.env.SERVER_ID as string,
},
});
const data = await res.json();
Expand Down
2 changes: 1 addition & 1 deletion app/open/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type ChatData = {

const Open = async () => {
const accessToken = process.env.ACCESS_TOKEN as string;
const result = await fetchAllChat(accessToken, 'minseob');
const result = await fetchAllChat(accessToken);
console.log(result);
return (
<div className="flex flex-col bg-red-300">
Expand Down
Loading

0 comments on commit 7d70bd7

Please sign in to comment.