Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"date-fns": "^3.6.0",
"dayjs": "^1.11.11",
"dotenv": "^16.4.5",
"js-base64": "^3.7.7",
"lodash": "^4.17.21",
"lucide-react": "^0.475.0",
"next": "^15.0.3",
Expand Down
50 changes: 50 additions & 0 deletions src/app/callback/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
'use client';

import { useEffect } from 'react';
import { useSearchParams, useRouter } from 'next/navigation';
import { decode } from 'js-base64';

function Callback() {
const searchParams = useSearchParams();
const router = useRouter();

useEffect(() => {
const status = searchParams.get('status');
const email = searchParams.get('email');
const accessToken = searchParams.get('accessToken');

localStorage.setItem('email', email || '');

if (!status) return;

switch (status) {
case 'NEW_MEMBER':
router.replace('/mobile/sign-up');
break;
case 'INVALID_EMAIL':
alert('국민대 이메일로 로그인 해 주세요.');
router.replace('/mobile/sign-in');
break;
case 'SUCCESS':
default:
if (accessToken) {
localStorage.setItem('token', accessToken);
const payload = accessToken.split('.')[1] || '';
const decodedPayload = decode(payload);
const payloadObject = JSON.parse(decodedPayload);

const tokenRole = payloadObject.role;

localStorage.setItem('token', accessToken);
localStorage.setItem('role', tokenRole);
}
router.replace('/mobile/main');

break;
}
}, [searchParams, router]);

return <p>Redirecting...</p>;
}

export default Callback;
5 changes: 1 addition & 4 deletions src/app/mobile/sign-in/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ import IconGoogle from 'public/assets/icons/icon-google.svg';

export default function SignIn() {
const handleLogin = () => {
// 구글 로그인 화면으로 이동시키기
// window.location.href =
// TODO : 현재 서버 오류 -> API 연결할 때 함께 연결
window.location.href = `${process.env.NEXT_PUBLIC_API_BASE_URI}/oauth2/authorization/google`;
};

return (
Expand All @@ -19,7 +17,6 @@ export default function SignIn() {
<div className="text-3xl font-semibold">복지물품 대여 시스템</div>
</section>
<ImageLoginLogo className="absolute top-24" />
{/* TODO : onClick 구글 로그인 연결하기 */}
<button
className="absolute bottom-44 flex w-11/12 justify-between rounded-2xl border bg-white-primary px-6 py-4"
type="button"
Expand Down
77 changes: 74 additions & 3 deletions src/app/mobile/sign-up/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,75 @@
'use client';

import axios from 'axios';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import postSignUp from '@/services/sign-up';
import { decode } from 'js-base64';

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

const [studentName, setStudentName] = useState('');
const [studentId, setStudentId] = useState('');

const handleStudentNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setStudentName(e.target.value);
};

const handleStudentIdChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setStudentId(e.target.value);
};

const validateForm = () => {
const nameRegex = /^[가-힣]{2,5}$/;
if (!nameRegex.test(studentName)) {
alert('이름은 한글 2~5자로 입력해 주세요.');
return false;
}

const idRegex = /^\d{8}$/;
if (!idRegex.test(studentId)) {
alert('학번은 숫자 8자리여야 합니다.');
return false;
}

return true;
};

const handleSignUp = async () => {
if (!validateForm()) return;

const email = localStorage.getItem('email');
if (!email) {
alert('이메일 정보가 없습니다. 다시 로그인해 주세요.');
return;
}

try {
const data = await postSignUp({
email,
studentId,
name: studentName,
});

const payload = data.token.split('.')[1] || '';
const decodedPayload = decode(payload);
const payloadObject = JSON.parse(decodedPayload);

const tokenRole = payloadObject.role;

localStorage.setItem('role', tokenRole);
localStorage.setItem('token', data.token);

router.push('/mobile/main');
} catch (e) {
if (axios.isAxiosError(e)) {
alert('회원가입에 실패했습니다. 다시 시도해 주세요.');
}
console.error(e);
}
};

return (
<section className="relative flex h-dvh w-full flex-col items-center justify-start overflow-hidden">
<section className="mt-20 flex w-11/12 flex-col items-start">
Expand All @@ -14,26 +83,28 @@ export default function SignUp() {
<section className="mt-[50px] flex w-11/12 flex-col items-start gap-6">
<section className="flex w-full flex-col gap-1.5">
<div>이름을 입력해 주세요.</div>
{/* TODO : 이름 유효성 검사 */}
<input
placeholder="이름을 정확히 입력해 주세요."
className="flex w-full rounded-xl border px-3.5 py-4"
value={studentName}
onChange={handleStudentNameChange}
/>
</section>
<section className="flex w-full flex-col gap-1.5">
<div>학번을 입력해 주세요.</div>
{/* TODO : 학번 8자 + 유효성 검사사 */}
<input
placeholder="8자 모두 입력해 주세요."
className="flex w-full rounded-xl border px-3.5 py-4"
value={studentId}
onChange={handleStudentIdChange}
/>
</section>
</section>

{/* TODO : onClick 연결하기 */}
<button
className="absolute bottom-7 flex w-11/12 justify-between rounded-2xl border bg-on-kookmin px-6 py-4 text-white-primary"
type="button"
onClick={handleSignUp}
>
<div className="h-6 w-6" />
<div className="flex font-medium">작성 완료</div>
Expand Down
19 changes: 19 additions & 0 deletions src/services/publicAxiosInstance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import axios, { InternalAxiosRequestConfig } from 'axios';

const PublicAxiosInstance = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_BASE_URI,
headers: { 'Content-Type': 'application/json' },
withCredentials: true,
});

PublicAxiosInstance.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
return {
...config,
withCredentials: false,
};
},
(error) => Promise.reject(error),
);

export default PublicAxiosInstance;
18 changes: 18 additions & 0 deletions src/services/sign-up.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import PublicAxiosInstance from '@/services/publicAxiosInstance';

interface SignUpProps {
email: string;
studentId: string;
name: string;
}

const postSignUp = async ({ email, studentId, name }: SignUpProps) => {
const response = await PublicAxiosInstance.post('/auth/sign-up', {
email,
studentId,
name,
});
return response.data;
};

export default postSignUp;
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5350,6 +5350,11 @@ jose@^5.9.3:
resolved "https://registry.yarnpkg.com/jose/-/jose-5.9.6.tgz#77f1f901d88ebdc405e57cce08d2a91f47521883"
integrity sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==

js-base64@^3.7.7:
version "3.7.7"
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-3.7.7.tgz#e51b84bf78fbf5702b9541e2cb7bfcb893b43e79"
integrity sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==

"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
Expand Down