학생 정보를 불러오는 데
diff --git a/frontend/src/app/(withSidebar)/my-page/milestone/components/MilestoneDetail/index.tsx b/frontend/src/app/(withSidebar)/my-page/milestone/components/MilestoneDetail/index.tsx
index d40cdb57..4699d258 100644
--- a/frontend/src/app/(withSidebar)/my-page/milestone/components/MilestoneDetail/index.tsx
+++ b/frontend/src/app/(withSidebar)/my-page/milestone/components/MilestoneDetail/index.tsx
@@ -14,7 +14,7 @@ import MilestoneRowBarTable from '../../../components/MilestoneRowBarTable';
const MilestoneDetail = ({ startDate, endDate }: Period) => {
const auth = useAppSelector((state) => state.auth).value;
const [selectedGroup, setSelectedGroup] = useState(MilestoneGroup.ACTIVITY);
- const { data: milestoneScores } = useMilestoneScoresOfStudentQuery(auth.uid, startDate, endDate);
+ const { data: milestoneScores } = useMilestoneScoresOfStudentQuery(auth.id, startDate, endDate);
return (
diff --git a/frontend/src/app/(withSidebar)/my-page/milestone/components/MilestoneHistoryTable/index.tsx b/frontend/src/app/(withSidebar)/my-page/milestone/components/MilestoneHistoryTable/index.tsx
index 3b73d217..c9a98ad2 100644
--- a/frontend/src/app/(withSidebar)/my-page/milestone/components/MilestoneHistoryTable/index.tsx
+++ b/frontend/src/app/(withSidebar)/my-page/milestone/components/MilestoneHistoryTable/index.tsx
@@ -19,7 +19,7 @@ const MilestoneHistoryTable = ({ searchFilterPeriod, pageNumber, pageSize }: Mil
const pathname = usePathname();
const auth = useAppSelector((state) => state.auth).value;
const { data: milestoneHistoriesOfStudent } = useMilestoneHistoriesOfStudentQuery(
- auth.uid,
+ auth.id,
searchFilterPeriod.startDate,
searchFilterPeriod.endDate,
MilestoneHistoryStatus.APPROVED,
diff --git a/frontend/src/app/(withSidebar)/my-page/milestone/components/MilestoneOverview/index.tsx b/frontend/src/app/(withSidebar)/my-page/milestone/components/MilestoneOverview/index.tsx
index b50241af..992366a1 100644
--- a/frontend/src/app/(withSidebar)/my-page/milestone/components/MilestoneOverview/index.tsx
+++ b/frontend/src/app/(withSidebar)/my-page/milestone/components/MilestoneOverview/index.tsx
@@ -19,7 +19,7 @@ interface MilestoneOverviewProps {
const MilestoneOverview = ({ searchFilterPeriod }: MilestoneOverviewProps) => {
const auth = useAppSelector((state) => state.auth).value;
const { data: milestoneScoresOfStudent } = useMilestoneScoresOfStudentQuery(
- auth.uid,
+ auth.id,
searchFilterPeriod.startDate,
searchFilterPeriod.endDate,
);
diff --git a/frontend/src/app/(withSidebar)/my-page/milestone/register/components/MilestoneHistoryTable/index.tsx b/frontend/src/app/(withSidebar)/my-page/milestone/register/components/MilestoneHistoryTable/index.tsx
index 2f7a4706..6c99e0a7 100644
--- a/frontend/src/app/(withSidebar)/my-page/milestone/register/components/MilestoneHistoryTable/index.tsx
+++ b/frontend/src/app/(withSidebar)/my-page/milestone/register/components/MilestoneHistoryTable/index.tsx
@@ -19,15 +19,20 @@ const MilestoneHistoryTable = async ({ pageNumber }: MilestoneHistoryTableProp)
const pathname = headersList.get('x-pathname') || '';
const auth = getAuthFromCookie();
- const milestoneHistories = await getMilestoneHistoriesOfStudent(
- auth.uid,
- undefined,
- undefined,
- undefined,
- MilestoneHistorySortCriteria.CREATED_AT,
- SortDirection.DESC,
- pageNumber - 1,
- );
+ let milestoneHistories;
+ try {
+ milestoneHistories = await getMilestoneHistoriesOfStudent(
+ auth.id,
+ undefined,
+ undefined,
+ undefined,
+ MilestoneHistorySortCriteria.CREATED_AT,
+ SortDirection.DESC,
+ pageNumber - 1,
+ );
+ } catch (e) {
+ // TODO: server api error handling...
+ }
return (
diff --git a/frontend/src/app/admin/milestone/list/[slug]/page.tsx b/frontend/src/app/admin/milestone/list/[slug]/page.tsx
index db6e08e0..cd6ebbe6 100644
--- a/frontend/src/app/admin/milestone/list/[slug]/page.tsx
+++ b/frontend/src/app/admin/milestone/list/[slug]/page.tsx
@@ -16,7 +16,12 @@ interface MilestoneHistoryDetailPageProps {
}
const Page = async ({ params: { slug } }: MilestoneHistoryDetailPageProps) => {
- const history = await getMilestoneHistory(slug);
+ let history;
+ try {
+ history = await getMilestoneHistory(slug);
+ } catch (e) {
+ // TODO: server api error handling...
+ }
if (!history) notFound();
diff --git a/frontend/src/app/admin/milestone/list/page.tsx b/frontend/src/app/admin/milestone/list/page.tsx
index 0a19e011..2106c310 100644
--- a/frontend/src/app/admin/milestone/list/page.tsx
+++ b/frontend/src/app/admin/milestone/list/page.tsx
@@ -17,13 +17,18 @@ const Page = async ({ searchParams }: { searchParams?: { [key: string]: string |
const field = searchParams?.field ? parseInt(searchParams.field, 10) : 0;
const keyword = searchParams?.keyword ? searchParams.keyword : '';
- const milstoneHistories = await getMilestoneHistories(field, keyword, page - 1);
+ let milestoneHistories;
+ try {
+ milestoneHistories = await getMilestoneHistories(field, keyword, page - 1);
+ } catch {
+ // TODO: server api error handling...
+ }
return (
- 총 {milstoneHistories?.totalElements ?? 0}건의 내역이
+ 총 {milestoneHistories?.totalElements ?? 0}건의 내역이
있습니다.
-
+
-
+
);
};
diff --git a/frontend/src/app/components/ExternalLink/index.tsx b/frontend/src/app/components/ExternalLink/index.tsx
index bfeda878..196b7d35 100644
--- a/frontend/src/app/components/ExternalLink/index.tsx
+++ b/frontend/src/app/components/ExternalLink/index.tsx
@@ -9,9 +9,9 @@ const ExternalLink = () => (
{externalLinkInfos.map((link) => {
const markup = { __html: link.title };
return (
-
+
-
+
diff --git a/frontend/src/app/components/PnuLink/index.tsx b/frontend/src/app/components/PnuLink/index.tsx
index 5469b79d..8d2ee2e1 100644
--- a/frontend/src/app/components/PnuLink/index.tsx
+++ b/frontend/src/app/components/PnuLink/index.tsx
@@ -53,9 +53,9 @@ const PnuLink = () => {
loop
>
{pnuLinkInfos.map((link) => (
-
+
-
+
))}
diff --git a/frontend/src/app/components/SignIn/components/InputUserInfo/index.tsx b/frontend/src/app/components/SignIn/components/InputUserInfo/index.tsx
index 16c8292b..dc94e70b 100644
--- a/frontend/src/app/components/SignIn/components/InputUserInfo/index.tsx
+++ b/frontend/src/app/components/SignIn/components/InputUserInfo/index.tsx
@@ -7,45 +7,66 @@ import { useAppDispatch } from '@/lib/hooks/redux';
import { signIn } from '@/store/auth.slice';
import { FixedEmail, InputID, InputPW, SignInButton } from './styled';
+import { useSignInMutation } from '@/lib/hooks/useApi';
+import { toast } from 'react-toastify';
const InputUserInfo = () => {
- const [userID, setUserID] = useState('');
- const [userPW, setUserPW] = useState('');
+ const [userInfo, setUserInfo] = useState({
+ email: '',
+ password: '',
+ });
+
+ const { mutate: signInMutation } = useSignInMutation();
const router = useRouter();
const dispatch = useAppDispatch();
- const handleSignInClick = () => {
- // TODO: api 연결
- dispatch(
- signIn({
- token: 'token',
- username: 'name',
- uid: 202055558,
- isModerator: true,
- }),
- );
- setTimeout(() => {
- router.refresh();
- }, 0);
+ const handleInputChange = (e: React.ChangeEvent) => {
+ setUserInfo((prev) => {
+ return {
+ ...prev,
+ [e.target.id]: e.target.value,
+ };
+ });
+ };
+
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+
+ signInMutation(userInfo, {
+ onSuccess(data, variables, context) {
+ dispatch(
+ signIn({
+ id: data.member_id,
+ token: `Bearer ${data.token}`,
+ name: data.name,
+ email: data.email,
+ isModerator: data.is_moderator,
+ }),
+ );
+ router.refresh();
+ },
+ onError(error, variables, context) {
+ toast.error(error.message);
+ },
+ });
};
return (
-
+ 로그인
+
);
};
diff --git a/frontend/src/components/Header/index.tsx b/frontend/src/components/Header/index.tsx
index 32a37b2d..ba0bf5f7 100644
--- a/frontend/src/components/Header/index.tsx
+++ b/frontend/src/components/Header/index.tsx
@@ -27,7 +27,13 @@ const Header = () => {
-
+
{headerInfos.map(
@@ -62,7 +68,13 @@ const Header = () => {
/>
-
+
{auth.isAuth ? (
diff --git a/frontend/src/data/queryKey.ts b/frontend/src/data/queryKey.ts
index a3cef194..34b45b90 100644
--- a/frontend/src/data/queryKey.ts
+++ b/frontend/src/data/queryKey.ts
@@ -36,4 +36,5 @@ export const QueryKeys = {
) => ['milestone-scores', startDate, endDate, page, size],
MILESTONE_HISTORY_SCORE_EXCEL: (startDate: string, endDate: string) => ['milestone-score-excel', startDate, endDate],
FILE: (fileName: string | null) => ['file', fileName],
+ DUPLICATE_STUDENT_ID: ['duplicate_student_id'],
};
diff --git a/frontend/src/data/signUp.ts b/frontend/src/data/signUp.ts
index 2674f0a0..b24b9e67 100644
--- a/frontend/src/data/signUp.ts
+++ b/frontend/src/data/signUp.ts
@@ -1,8 +1,8 @@
-export const careerCategory: { id: number; name: string }[] = [
- { id: 1, name: '대학원 진학' },
- { id: 2, name: '기업 취업' },
- { id: 3, name: '공공기관 취업' },
- { id: 4, name: '창업' },
+export const careerCategory: { id: number; name: string; type: string }[] = [
+ { id: 1, name: '대학원 진학', type: 'GRADUATE_SCHOOL' },
+ { id: 2, name: '기업 취업', type: 'EMPLOYMENT_COMPANY' },
+ { id: 3, name: '공공기관 취업', type: 'EMPLOYMENT_PUBLIC_INSTITUTION' },
+ { id: 4, name: '창업', type: 'FOUNDATION' },
];
export enum SignUpPhase {
diff --git a/frontend/src/lib/api/server.api.ts b/frontend/src/lib/api/server.api.ts
index 89d7caf7..27d375a2 100644
--- a/frontend/src/lib/api/server.api.ts
+++ b/frontend/src/lib/api/server.api.ts
@@ -19,40 +19,67 @@ export async function getMilestoneHistoriesOfStudent(
page: number = 0,
size: number = 10,
) {
- const response = await server.get
(`/milestones/histories/members/${memberId}`, {
- params: removeEmptyField({
- start_date: startDate,
- end_date: endDate,
- filter,
- sort_by: sortBy,
- sort_direction: sortDirection,
- page,
- size,
- }),
- });
- return response?.data;
+ const response = await server
+ .get(`/milestones/histories/members/${memberId}`, {
+ params: removeEmptyField({
+ start_date: startDate,
+ end_date: endDate,
+ filter,
+ sort_by: sortBy,
+ sort_direction: sortDirection,
+ page,
+ size,
+ }),
+ })
+ .then((res) => res.data)
+ .catch((err) => Promise.reject(err));
+ return response;
}
export async function getMilestoneHistories(field?: number, keyword?: string, page: number = 0, size: number = 10) {
- const response = await server.get('/admin/milestones/histories', {
- params: removeEmptyField({
- field,
- keyword,
- page,
- size,
- }),
- });
- return response?.data;
+ const response = await server
+ .get('/admin/milestones/histories', {
+ params: removeEmptyField({
+ field,
+ keyword,
+ page,
+ size,
+ }),
+ })
+ .then((res) => res.data)
+ .catch((err) => Promise.reject(err));
+ return response;
}
export async function getMilestoneHistory(historyId: number) {
- const response = await server.get(`/admin/milestones/histories/${historyId}`);
- return response?.data;
+ const response = await server
+ .get(`/admin/milestones/histories/${historyId}`)
+ .then((res) => res.data)
+ .catch((err) => Promise.reject(err));
+ return response;
}
export async function getFile(fileName: string | null) {
- const response = await server.get(`/files/${fileName}`, {
- responseType: 'blob',
- });
- return response.data;
+ const response = await server
+ .get(`/files/${fileName}`, {
+ responseType: 'blob',
+ })
+ .then((res) => res.data)
+ .catch((err) => Promise.reject(err));
+ return response;
+}
+
+export async function getValidationStudentId(studentId: string) {
+ const response = await server
+ .get(`/sign-up/exists/student-id`, {
+ params: removeEmptyField({
+ student_id: studentId,
+ }),
+ })
+ .then((res) => res.data)
+ .catch((err) => {
+ return Promise.reject(err);
+ });
+
+ return response;
}
diff --git a/frontend/src/lib/api/server.axios.ts b/frontend/src/lib/api/server.axios.ts
index a440847c..306964b5 100644
--- a/frontend/src/lib/api/server.axios.ts
+++ b/frontend/src/lib/api/server.axios.ts
@@ -6,7 +6,5 @@ export const server = axios.create({ baseURL: process.env.NEXT_PUBLIC_SERVER_URL
server.interceptors.response.use(
(response) => response,
- (error) => {
- Promise.reject(categorizeError(error));
- },
+ (error) => Promise.reject(categorizeError(error)),
);
diff --git a/frontend/src/lib/hooks/useApi.ts b/frontend/src/lib/hooks/useApi.ts
index f059404f..557f072a 100644
--- a/frontend/src/lib/hooks/useApi.ts
+++ b/frontend/src/lib/hooks/useApi.ts
@@ -14,7 +14,9 @@ import {
import { BusinessError } from '@/types/error';
import { MilestoneHistorySortCriteria, SortDirection } from '@/types/milestone';
-import { removeEmptyField } from '../utils/utils';
+import { convertNumToCareer, removeEmptyField } from '../utils/utils';
+import { FirstInfo } from '@/app/(auth)/sign-up/components/SignUpFirstPage';
+import { SecondInfo } from '@/app/(auth)/sign-up/components/SignUpSecondPage';
export const useCollegeQuery = () =>
useAxiosQuery({
@@ -165,3 +167,47 @@ export function useMilestoneHistoryDeleteMutation() {
},
});
}
+
+export function useSignUpMutation() {
+ return useAxiosMutation({
+ mutationFn: async (userInfo: FirstInfo & SecondInfo) => {
+ const data = {
+ email: userInfo.email + '@pusan.ac.kr',
+ password: userInfo.password,
+ name: userInfo.name,
+ student_id: userInfo.studentId,
+ phone_number: userInfo.phoneNumber,
+ major_id: userInfo.majorId,
+ minor_id: userInfo.minorId === 0 ? null : userInfo.minorId,
+ double_major_id: userInfo.doubleMajorId === 0 ? null : userInfo.doubleMajorId,
+ career: convertNumToCareer(userInfo.career),
+ career_detail: userInfo.careerDetail,
+ auth_code: userInfo.authCode,
+ };
+ await client
+ .post(`/sign-up`, data)
+ .then((res) => res.data)
+ .catch((err) => Promise.reject(err));
+ },
+ });
+}
+
+export function useSendAuthCodeMutation() {
+ return useAxiosMutation({
+ mutationFn: async (email: string) =>
+ await client
+ .post(`/sign-up/send-auth-code`, { email })
+ .then((res) => res.data)
+ .catch((err) => Promise.reject(err)),
+ });
+}
+
+export function useSignInMutation() {
+ return useAxiosMutation({
+ mutationFn: async ({ email, password }: { email: string; password: string }) =>
+ await client
+ .post(`/sign-in`, { email: email + '@pusan.ac.kr', password })
+ .then((res) => res.data)
+ .catch((err) => Promise.reject(err)),
+ });
+}
diff --git a/frontend/src/lib/utils/utils.tsx b/frontend/src/lib/utils/utils.tsx
index 86e202b0..7368cf20 100644
--- a/frontend/src/lib/utils/utils.tsx
+++ b/frontend/src/lib/utils/utils.tsx
@@ -43,13 +43,28 @@ export const convertMilestoneHistoryStatus = (status: string) => {
}
};
-export const convertCareer = (enumValue: string) => {
+export const convertNumToCareer = (num: number) => {
+ switch (num) {
+ case 1:
+ return 'GRADUATE_SCHOOL';
+ case 2:
+ return 'EMPLOYMENT_COMPANY';
+ case 3:
+ return 'EMPLOYMENT_PUBLIC_INSTITUTION';
+ case 4:
+ return 'FOUNDATION';
+ default:
+ return 'ETC';
+ }
+};
+
+export const convertCareerToStr = (enumValue: string) => {
switch (enumValue) {
case 'GRADUATE_SCHOOL':
return '대학원 진학';
case 'EMPLOYMENT_COMPANY':
return '취업(기업체)';
- case 'EMPLOYMENT_PUBLIC_"INSTITUTION':
+ case 'EMPLOYMENT_PUBLIC_INSTITUTION':
return '취업(공공기관)';
case 'FOUNDATION':
return '취업(창업)';
diff --git a/frontend/src/store/auth.slice.ts b/frontend/src/store/auth.slice.ts
index d67fce2d..235d4f6a 100644
--- a/frontend/src/store/auth.slice.ts
+++ b/frontend/src/store/auth.slice.ts
@@ -3,18 +3,15 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import cookie from 'react-cookies';
export interface AuthState {
+ readonly id: number;
token: string;
- username: string;
- uid: number;
- isModerator: boolean;
+ name: string;
+ email: string;
+ isModerator: boolean; // 관리자 계정인지.
}
export interface AuthSliceState extends AuthState {
- token: string;
isAuth: boolean; // 로그인되어 있는지.
- username: string;
- uid: number;
- isModerator: boolean; // 관리자 계정인지.
}
interface InitialState {
@@ -23,10 +20,11 @@ interface InitialState {
const initialState: InitialState = {
value: {
+ id: -1,
token: '',
+ name: '',
+ email: '',
isAuth: false,
- username: '',
- uid: -1,
isModerator: false,
},
};
diff --git a/frontend/src/types/error.ts b/frontend/src/types/error.ts
index 974dc2f8..53e666e2 100644
--- a/frontend/src/types/error.ts
+++ b/frontend/src/types/error.ts
@@ -5,100 +5,100 @@ import { AxiosError } from 'axios';
export class ApplicationError extends Error {
originalError?: AxiosError;
- constructor(error?: AxiosError) {
+ constructor(error?: AxiosError, message?: string) {
super();
this.originalError = error;
this.name = 'ApplicationError';
- this.message = 'ApplicationError';
+ this.message = message ? message : 'ApplicationError';
}
}
export class BusinessError extends Error {
originalError?: AxiosError;
- constructor(error?: AxiosError) {
+ constructor(error?: AxiosError, message?: string) {
super();
this.originalError = error;
this.name = 'BusinessError';
- this.message = 'BusinessError';
+ this.message = message ? message : 'MemberRoleNotMatchedError';
}
}
export class AuthError extends BusinessError {
- constructor(error?: AxiosError) {
- super(error);
+ constructor(error?: AxiosError, message?: string) {
+ super(error, message);
this.name = 'AuthError';
- this.message = 'AuthError';
+ this.message = message ? message : 'MemberRoleNotMatchedError';
}
}
export class NotFoundError extends AuthError {
- constructor(error?: AxiosError) {
- super(error);
+ constructor(error?: AxiosError, message?: string) {
+ super(error, message);
this.name = 'NotFoundError';
- this.message = 'NotFoundError';
+ this.message = message ? message : 'MemberRoleNotMatchedError';
}
}
export class AccessDeniedError extends AuthError {
- constructor(error?: AxiosError) {
- super(error);
+ constructor(error?: AxiosError, message?: string) {
+ super(error, message);
this.name = 'AccessDeniedError';
- this.message = 'AccessDeniedError';
+ this.message = message ? message : 'MemberRoleNotMatchedError';
}
}
export class UnauthorizedError extends AuthError {
- constructor(error?: AxiosError) {
- super(error);
+ constructor(error?: AxiosError, message?: string) {
+ super(error, message);
this.name = 'UnauthorizedError';
- this.message = 'UnauthorizedError';
+ this.message = message ? message : 'MemberRoleNotMatchedError';
}
}
export class MemberRoleNotMatchedError extends AuthError {
- constructor(error?: AxiosError) {
- super(error);
+ constructor(error?: AxiosError, message?: string) {
+ super(error, message);
this.name = 'MemberRoleNotMatchedError';
- this.message = 'MemberRoleNotMatchedError';
+ this.message = message ? message : 'MemberRoleNotMatchedError';
}
}
export class MemberNotFoundError extends AuthError {
- constructor(error?: AxiosError) {
- super(error);
+ constructor(error?: AxiosError, message?: string) {
+ super(error, message);
this.name = 'MemberNotFoundError';
- this.message = 'MemberNotFoundError';
+ this.message = message ? message : 'MemberNotFoundError';
}
}
export const categorizeError = (error: Error) => {
if (error instanceof AxiosError && error.response) {
if (error.response.status === 401) {
- return new UnauthorizedError(error);
+ return new UnauthorizedError(error, error.response.data.message);
}
if (error.response.status === 403) {
- return new AccessDeniedError(error);
+ return new AccessDeniedError(error, error.response.data.message);
}
if (error.response.status === 404) {
if (error.response.data.title === 'MEMBER_NOT_FOUND') {
- return new MemberNotFoundError(error);
+ return new MemberNotFoundError(error, error.response.data.message);
}
- return new NotFoundError(error);
+ return new NotFoundError(error, error.response.data.message);
}
if (error.response.status === 409 && error.response.data.title === 'MEMBER_ROLE_NOT_MATCHED') {
- return new MemberRoleNotMatchedError(error);
+ return new MemberRoleNotMatchedError(error, error.response.data.message);
}
if (error.response.status >= 400 && error.response.status < 500) {
- return new BusinessError(error);
+ return new BusinessError(error, error.response.data.message);
}
if (error.response.status >= 500) {
- return new ApplicationError(error);
+ return new ApplicationError(error, error.response.data.message);
}
}
return error;