Skip to content

Commit df17cf7

Browse files
authored
Merge pull request #239 from LellowMellow/feature/231209-change-signin-up-page
Feature(#236): ๋กœ๊ทธ์ธ/ํšŒ์›๊ฐ€์ž… ํŽ˜์ด์ง€ ์ž‘์—…
2 parents c098181 + 15776e8 commit df17cf7

File tree

7 files changed

+250
-54
lines changed

7 files changed

+250
-54
lines changed

โ€Žfrontend/src/App.tsx

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
1-
import { BrowserRouter, Navigate, Routes, Route } from "react-router-dom";
21
import "./global.css";
2+
import { BrowserRouter, Navigate, Routes, Route } from "react-router-dom";
3+
import { RecoilRoot } from "recoil";
4+
import ToastContainer from "./components/Toast/ToastContainer";
5+
36
import Home from "@/pages/Home/Home";
4-
import Login from "./pages/Login/Login";
7+
import UserAuth from "./pages/UserAuth/UserAuth";
8+
import Start from "./pages/Start/Start";
9+
import MyPage from "./pages/MyPage/MyPage";
510
import Test from "./pages/Test/Test";
611
import Instructor from "./pages/Instructor/Instructor";
712
import Participant from "./pages/Participant/Participant";
813
import Review from "./pages/Review/Review";
9-
10-
import { RecoilRoot } from "recoil";
14+
import LectureEnd from "./pages/LectureEnd/LectureEnd";
1115
import Example from "./pages/Example/Example";
12-
13-
import ToastContainer from "./components/Toast/ToastContainer";
14-
import Start from "./pages/Start/Start";
15-
import MyPage from "./pages/MyPage/MyPage";
1616
import ErrorPage from "./pages/ErrorPage/ErrorPage";
17-
import LectureEnd from "./pages/LectureEnd/LectureEnd";
1817

1918
const App = () => {
2019
return (
@@ -23,7 +22,7 @@ const App = () => {
2322
<BrowserRouter>
2423
<Routes>
2524
<Route path="/" element={<Home />} />
26-
<Route path="/login" element={<Login />} />
25+
<Route path="/userauth" element={<UserAuth />} />
2726
<Route path="/start" element={<Start />} />
2827
<Route path="/mypage" element={<MyPage />} />
2928
<Route path="/test" element={<Test />} />
62.9 KB
Loading
93.8 KB
Loading

โ€Žfrontend/src/pages/Login/Login.tsx

Lines changed: 0 additions & 13 deletions
This file was deleted.

โ€Žfrontend/src/pages/Login/components/LoginSection.tsx

Lines changed: 0 additions & 31 deletions
This file was deleted.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { useState } from "react";
2+
import Header from "@/components/Header/Header";
3+
import UserAuthSection from "./components/UserAuthSection";
4+
5+
const UserAuth = () => {
6+
const [isSignIn, setIsSignIn] = useState(true);
7+
8+
return (
9+
<>
10+
<Header type="login" />
11+
<UserAuthSection isSignIn={isSignIn} setIsSignIn={setIsSignIn} />
12+
</>
13+
);
14+
};
15+
16+
export default UserAuth;
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
import { useState, useEffect } from "react";
2+
import { useNavigate } from "react-router-dom";
3+
import axios from "axios";
4+
import EnterIcon from "@/assets/svgs/enter.svg?react";
5+
import UserIcon from "@/assets/svgs/user.svg?react";
6+
import CloseIcon from "@/assets/svgs/close.svg?react";
7+
import Button from "@/components/Button/Button";
8+
import LogoOriginal from "@/assets/imgs/logoOriginal.png";
9+
import SubLogoOriginal from "@/assets/imgs/subLogoOriginal.png";
10+
import { useToast } from "@/components/Toast/useToast";
11+
12+
interface UserAuthSectionProps {
13+
isSignIn: boolean;
14+
setIsSignIn: React.Dispatch<React.SetStateAction<boolean>>;
15+
}
16+
17+
const EMAIL_REGEXP = /^[a-zA-Z0-9._+]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$/;
18+
const NICKNAME_REGEXP = /^[a-zA-Z0-9๊ฐ€-ํžฃ]{3,15}$/;
19+
const PASSWORD_REGEXP = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
20+
21+
const UserAuthSection = ({ isSignIn, setIsSignIn }: UserAuthSectionProps) => {
22+
const navigate = useNavigate();
23+
const showToast = useToast();
24+
const [email, setEmail] = useState<string>("");
25+
const [username, setUsername] = useState<string>("");
26+
const [password, setPassword] = useState<string>("");
27+
const [passwordConfirm, setPasswordConfirm] = useState<string>("");
28+
const [isEmailValid, setIsEmailValid] = useState<boolean | null>(null);
29+
const [isUsernameValid, setIsUsernameValid] = useState<boolean | null>(null);
30+
const [isPasswordValid, setIsPasswordValid] = useState<boolean | null>(null);
31+
const [isPasswordConfirm, setIsPasswordConfirm] = useState<boolean | null>(null);
32+
33+
useEffect(() => {
34+
setEmail("");
35+
setUsername("");
36+
setPassword("");
37+
setPasswordConfirm("");
38+
setIsEmailValid(null);
39+
setIsUsernameValid(null);
40+
setIsPasswordValid(null);
41+
setIsPasswordConfirm(null);
42+
}, [isSignIn]);
43+
44+
const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
45+
setEmail(e.target.value);
46+
setIsEmailValid(EMAIL_REGEXP.test(e.target.value));
47+
};
48+
49+
const handleUsernameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
50+
setUsername(e.target.value);
51+
setIsUsernameValid(NICKNAME_REGEXP.test(e.target.value));
52+
};
53+
54+
const handlePasswordChange = (e: React.ChangeEvent<HTMLInputElement>) => {
55+
setPassword(e.target.value);
56+
setIsPasswordValid(PASSWORD_REGEXP.test(e.target.value));
57+
setIsPasswordConfirm(passwordConfirm === e.target.value);
58+
};
59+
60+
const handlePasswordConfirm = (e: React.ChangeEvent<HTMLInputElement>) => {
61+
setPasswordConfirm(e.target.value);
62+
setIsPasswordConfirm(e.target.value !== "" && e.target.value === password);
63+
};
64+
65+
const handleLeftButtonClicked = () => {
66+
if (isSignIn) {
67+
setIsSignIn(false);
68+
} else {
69+
setIsSignIn(true);
70+
}
71+
};
72+
73+
const handleRightButtonClicked = () => {
74+
if (isSignIn) {
75+
if (isEmailValid && isPasswordValid) {
76+
axios
77+
.post(`${import.meta.env.VITE_API_SERVER_URL}/auth/signin`, {
78+
email,
79+
password
80+
})
81+
.then((result) => {
82+
localStorage.setItem("token", result.data.token);
83+
showToast({ message: "๋กœ๊ทธ์ธ์— ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค.", type: "success" });
84+
navigate("/");
85+
})
86+
.catch((error) => {
87+
if (error.response.status === 404) showToast({ message: "์‚ฌ์šฉ์ž๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์•„์š”.", type: "alert" });
88+
else showToast({ message: "์ž˜๋ชป๋œ ์š”์ฒญ์ด์—์š”.", type: "alert" });
89+
});
90+
} else showToast({ message: "์˜ฌ๋ฐ”๋ฅธ ์ •๋ณด๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.", type: "alert" });
91+
} else {
92+
if (isEmailValid && isUsernameValid && isPasswordValid && isPasswordConfirm) {
93+
axios
94+
.post(`${import.meta.env.VITE_API_SERVER_URL}/auth/signup`, {
95+
email,
96+
username,
97+
password
98+
})
99+
.then(() => {
100+
showToast({ message: "ํšŒ์›๊ฐ€์ž…์— ์„ฑ๊ณตํ–ˆ์–ด์š”.", type: "success" });
101+
setIsSignIn(true);
102+
})
103+
.catch((error) => {
104+
if (error.response.status === 409) showToast({ message: "์ด๋ฏธ ๊ฐ€์ž…ํ•œ ์ ์ด ์žˆ์–ด์š”.", type: "alert" });
105+
else showToast({ message: "์ž˜๋ชป๋œ ์š”์ฒญ์ด์—์š”.", type: "alert" });
106+
});
107+
} else showToast({ message: "์˜ฌ๋ฐ”๋ฅธ ์ •๋ณด๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.", type: "alert" });
108+
}
109+
};
110+
111+
const getDescriptionColor = (isValid: boolean | null) => {
112+
if (isValid === null) return "text-grayscale-darkgray";
113+
return isValid ? "text-boarlog-100" : "text-alert-100";
114+
};
115+
116+
return (
117+
<div className="flex flex-col relative py-20 sm:py-32 items-center justify-center w-full overflow-x-hidden overflow-y-visible">
118+
<section className="flex relative w-11/12 sm:max-w-md p-8 bg-grayscale-white flex-col items-start gap-8 rounded-xl border-default shadow-xl">
119+
<img src={LogoOriginal} className="absolute hidden -top-2 -left-52 rotate-[-20deg] z-[-1] w-64 sm:block" />
120+
<img src={SubLogoOriginal} className="absolute hidden -bottom-16 -right-80 rotate-[20deg] z-[-1] sm:block" />
121+
122+
<div className="flex flex-col">
123+
<h1 className="semibold-64">Boarlog</h1>
124+
<h2 className="semibold-20 text-grayscale-darkgray">๊ธฐ๋ก์œผ๋กœ ๋‚จ๊ธฐ๋Š” ์‹ค์‹œ๊ฐ„ ๊ฐ•์˜</h2>
125+
</div>
126+
<div className="flex flex-col gap-4 w-full">
127+
<h3 className="semibold-32">{isSignIn ? "๋กœ๊ทธ์ธ" : "ํšŒ์›๊ฐ€์ž…"}</h3>
128+
<div className="flex flex-col gap-2 w-full">
129+
<p className="semibold-18">์ด๋ฉ”์ผ</p>
130+
{!isSignIn && (
131+
<p className={`medium-12 ${getDescriptionColor(isEmailValid)}`}>์˜ฌ๋ฐ”๋ฅธ ์ด๋ฉ”์ผ ํ˜•์‹์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.</p>
132+
)}
133+
<input
134+
type="text"
135+
className="rounded-xl border-black w-full flex-grow medium-12 p-3 focus:outline-none focus:ring-1 focus:ring-boarlog-100 focus:border-transparent"
136+
placeholder="์ด๋ฉ”์ผ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”"
137+
maxLength={50}
138+
value={email}
139+
onChange={handleEmailChange}
140+
/>
141+
</div>
142+
{!isSignIn && (
143+
<div className="flex flex-col gap-2 w-full">
144+
<p className="semibold-18">๋‹‰๋„ค์ž„</p>
145+
<p className={`medium-12 ${getDescriptionColor(isUsernameValid)}`}>
146+
3์ž ์ด์ƒ 15์ž ์ดํ•˜์˜ ์˜๋ฌธ, ์ˆซ์ž, ํ•œ๊ธ€๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
147+
</p>
148+
<input
149+
type="text"
150+
className="rounded-xl border-black w-full flex-grow medium-12 p-3 focus:outline-none focus:ring-1 focus:ring-boarlog-100 focus:border-transparent"
151+
placeholder="๋‹‰๋„ค์ž„์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”"
152+
maxLength={15}
153+
value={username}
154+
onChange={handleUsernameChange}
155+
/>
156+
</div>
157+
)}
158+
<div className="flex flex-col gap-2 w-full">
159+
<p className="semibold-18">๋น„๋ฐ€๋ฒˆํ˜ธ</p>
160+
{!isSignIn && (
161+
<p className={`medium-12 ${getDescriptionColor(isPasswordValid)}`}>
162+
8์ž ์ด์ƒ, ๋Œ€์†Œ๋ฌธ์ž, ์ˆซ์ž, ํŠน์ˆ˜ ๋ฌธ์ž(@$!%*?&)๋ฅผ ์ตœ์†Œ ํ•˜๋‚˜์”ฉ ํฌํ•จํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
163+
</p>
164+
)}
165+
<input
166+
type="password"
167+
className="rounded-xl border-black w-full flex-grow medium-12 p-3 focus:outline-none focus:ring-1 focus:ring-boarlog-100 focus:border-transparent"
168+
placeholder="๋น„๋ฐ€๋ฒˆํ˜ธ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”"
169+
maxLength={50}
170+
value={password}
171+
onChange={handlePasswordChange}
172+
/>
173+
</div>
174+
{!isSignIn && (
175+
<div className="flex flex-col gap-2 w-full">
176+
<p className="semibold-18">๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ</p>
177+
<p className={`medium-12 ${getDescriptionColor(isPasswordConfirm)}`}>
178+
๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜๋„๋ก ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.
179+
</p>
180+
<input
181+
type="password"
182+
className="rounded-xl border-black w-full flex-grow medium-12 p-3 focus:outline-none focus:ring-1 focus:ring-boarlog-100 focus:border-transparent"
183+
placeholder="๋น„๋ฐ€๋ฒˆํ˜ธ์„ ๋‹ค์‹œ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”"
184+
maxLength={50}
185+
value={passwordConfirm}
186+
onChange={handlePasswordConfirm}
187+
/>
188+
</div>
189+
)}
190+
</div>
191+
192+
<div className="flex flex-col w-full gap-4 sm:flex-row">
193+
<Button type="grow" buttonStyle="black" onClick={handleLeftButtonClicked}>
194+
{isSignIn ? (
195+
<>
196+
<UserIcon className="fill-grayscale-white" />
197+
ํšŒ์›๊ฐ€์ž…
198+
</>
199+
) : (
200+
<>
201+
<CloseIcon className="fill-grayscale-white" />
202+
๋’ค๋กœ๊ฐ€๊ธฐ
203+
</>
204+
)}
205+
</Button>
206+
<Button type="grow" buttonStyle="black" onClick={handleRightButtonClicked}>
207+
{isSignIn ? (
208+
<>
209+
<EnterIcon className="fill-grayscale-white" />
210+
์‹œ์ž‘ํ•˜๊ธฐ
211+
</>
212+
) : (
213+
<>
214+
<EnterIcon className="fill-grayscale-white" />
215+
๊ฐ€์ž…ํ•˜๊ธฐ
216+
</>
217+
)}
218+
</Button>
219+
</div>
220+
</section>
221+
</div>
222+
);
223+
};
224+
225+
export default UserAuthSection;

0 commit comments

Comments
ย (0)