diff --git a/Components/Join/JoinForm.tsx b/Components/Join/JoinForm.tsx new file mode 100644 index 00000000..030bc68a --- /dev/null +++ b/Components/Join/JoinForm.tsx @@ -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(); + + // 로그인 시 api 요청 + const onSubmit: SubmitHandler = ({ + id, + password, + name, + picture, + }) => { + fetchJoin(id, password, name, picture); + }; + + const image = watch('picture'); + console.log(image); + + return ( + <> +
+
+ {/* 이미지 */} + {image ? ( + + ) : ( +
+ )} + {/* 이미지 url */} +
+ + +
+ {/* id */} +
+ + +
+ {/* 비밀번호 */} +
+ + + {errors?.password?.type === 'minLength' && ( +

입력은 최소 5자 이상이어야 합니다.

+ )} +
+ {/* 이름 */} +
+ + + {errors?.name?.type === 'maxLength' && ( +

입력은 최대 20자 이상이어야 합니다.

+ )} + + {errors?.id ?

{errors.id?.message}

: null} +
+
+ + + + + ); +}; + +export default JoinForm; diff --git a/Components/Login/Cookie.tsx b/Components/Login/Cookie.tsx new file mode 100644 index 00000000..0203d67c --- /dev/null +++ b/Components/Login/Cookie.tsx @@ -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 }); +}; diff --git a/Components/Login/LoginForm.tsx b/Components/Login/LoginForm.tsx new file mode 100644 index 00000000..d58002df --- /dev/null +++ b/Components/Login/LoginForm.tsx @@ -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(); + + // 로그인 버튼 클릭 시 + const onSubmit: SubmitHandler = 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 ( +
+ + {/* 영어와 숫자만 */} + + {errors?.id ?

{errors.id?.message}

: null} + + + {/* 5자 이상 */} + + +
+ ); +}; + +export default LoginForm; diff --git a/app/join/join.utils.ts b/app/join/join.utils.ts new file mode 100644 index 00000000..e9ee7d32 --- /dev/null +++ b/app/join/join.utils.ts @@ -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; +}; diff --git a/app/join/page.tsx b/app/join/page.tsx new file mode 100644 index 00000000..7de1dd7f --- /dev/null +++ b/app/join/page.tsx @@ -0,0 +1,12 @@ +import JoinForm from '@/Components/Join/JoinForm'; +import React from 'react'; + +const Join = () => { + return ( +
+ +
+ ); +}; + +export default Join; diff --git a/app/login/login.utils.ts b/app/login/login.utils.ts new file mode 100644 index 00000000..d40dbb85 --- /dev/null +++ b/app/login/login.utils.ts @@ -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; +}; diff --git a/app/login/page.tsx b/app/login/page.tsx new file mode 100644 index 00000000..70a30123 --- /dev/null +++ b/app/login/page.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import LoginForm from '../../Components/Login/LoginForm'; + +const Login = () => { + return ( +
+ +
+ ); +}; + +export default Login; diff --git a/app/open/open.utils.ts b/app/open/open.utils.ts index 8554931d..fb0057b2 100644 --- a/app/open/open.utils.ts +++ b/app/open/open.utils.ts @@ -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(); diff --git a/app/open/page.tsx b/app/open/page.tsx index 3e2296b4..40b2ff35 100644 --- a/app/open/page.tsx +++ b/app/open/page.tsx @@ -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 (
diff --git a/package-lock.json b/package-lock.json index f1afe716..bcf9f5ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,9 +12,9 @@ "@tanstack/react-query": "^5.7.2", "@tanstack/react-query-devtools": "^5.7.4", "axios": "^1.6.0", - "husky": "^8.0.3", "next": "14.0.1", "react": "^18", + "react-cookie": "^6.1.1", "react-dom": "^18", "react-hook-form": "^7.48.2", "recoil": "^0.7.7", @@ -30,7 +30,7 @@ "eslint": "^8", "eslint-config-next": "14.0.1", "eslint-config-prettier": "^9.0.0", - "husky": "^8.0.0", + "husky": "^8.0.3", "postcss": "^8", "tailwindcss": "^3.3.0", "typescript": "^5" @@ -623,6 +623,15 @@ "@types/node": "*" } }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz", + "integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/json-schema": { "version": "7.0.14", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.14.tgz", @@ -646,14 +655,12 @@ "node_modules/@types/prop-types": { "version": "15.7.9", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.9.tgz", - "integrity": "sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g==", - "dev": true + "integrity": "sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g==" }, "node_modules/@types/react": { "version": "18.2.36", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.36.tgz", "integrity": "sha512-o9XFsHYLLZ4+sb9CWUYwHqFVoG61SesydF353vFMMsQziiyRu8np4n2OYMUSDZ8XuImxDr9c5tR7gidlH29Vnw==", - "dev": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -672,8 +679,7 @@ "node_modules/@types/scheduler": { "version": "0.16.5", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.5.tgz", - "integrity": "sha512-s/FPdYRmZR8SjLWGMCuax7r3qCWQw9QKHzXVukAuuIJkXkDRwp+Pu5LMIVFi0Fxbav35WURicYr8u1QsoybnQw==", - "dev": true + "integrity": "sha512-s/FPdYRmZR8SjLWGMCuax7r3qCWQw9QKHzXVukAuuIJkXkDRwp+Pu5LMIVFi0Fxbav35WURicYr8u1QsoybnQw==" }, "node_modules/@types/semver": { "version": "7.5.4", @@ -1524,8 +1530,7 @@ "node_modules/csstype": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", - "dev": true + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" }, "node_modules/damerau-levenshtein": { "version": "1.0.8", @@ -2755,6 +2760,14 @@ "resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz", "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==" }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, "node_modules/husky": { "version": "8.0.3", "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", @@ -4014,6 +4027,19 @@ "node": ">=0.10.0" } }, + "node_modules/react-cookie": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-6.1.1.tgz", + "integrity": "sha512-fuFRpf8LH6SfmVMowDUIRywJF5jAUDUWrm0EI5VdXfTl5bPcJ7B0zWbuYpT0Tvikx7Gs18MlvAT+P+744dUz2g==", + "dependencies": { + "@types/hoist-non-react-statics": "^3.3.1", + "hoist-non-react-statics": "^3.3.2", + "universal-cookie": "^6.0.0" + }, + "peerDependencies": { + "react": ">= 16.3.0" + } + }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -4840,6 +4866,28 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, + "node_modules/universal-cookie": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-6.1.1.tgz", + "integrity": "sha512-33S9x3CpdUnnjwTNs2Fgc41WGve2tdLtvaK2kPSbZRc5pGpz2vQFbRWMxlATsxNNe/Cy8SzmnmbuBM85jpZPtA==", + "dependencies": { + "@types/cookie": "^0.5.1", + "cookie": "^0.5.0" + } + }, + "node_modules/universal-cookie/node_modules/@types/cookie": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.5.4.tgz", + "integrity": "sha512-7z/eR6O859gyWIAjuvBWFzNURmf2oPBmJlfVWkwehU5nzIyjwBsTh7WMmEEV4JFnHuQ3ex4oyTvfKzcyJVDBNA==" + }, + "node_modules/universal-cookie/node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/update-browserslist-db": { "version": "1.0.13", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", diff --git a/package.json b/package.json index 1c4b1367..7ccb4d6b 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,9 @@ "@tanstack/react-query": "^5.7.2", "@tanstack/react-query-devtools": "^5.7.4", "axios": "^1.6.0", - "husky": "^8.0.3", "next": "14.0.1", "react": "^18", + "react-cookie": "^6.1.1", "react-dom": "^18", "react-hook-form": "^7.48.2", "recoil": "^0.7.7", @@ -32,7 +32,7 @@ "eslint": "^8", "eslint-config-next": "14.0.1", "eslint-config-prettier": "^9.0.0", - "husky": "^8.0.0", + "husky": "^8.0.3", "postcss": "^8", "tailwindcss": "^3.3.0", "typescript": "^5" diff --git a/tailwind.config.ts b/tailwind.config.ts index 79d51a5f..c91030d7 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -19,6 +19,7 @@ const config: Config = withMT({ extend: { colors: { primary: '#FF6363', + main: '#FEE500', }, backgroundImage: { 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',