Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: my page refactor #139

Merged
merged 11 commits into from
Sep 10, 2024
36 changes: 36 additions & 0 deletions app/(sub-page)/sign-in/components/updateInstruction.tsx
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

파일 이름을 케밥 케이스로 변경하는게 좋겠습니다

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수정했습니다!
f8b52be

Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'use client';
import useDialog from '@/app/hooks/useDialog';
import Button from '@/app/ui/view/atom/button/button';
import { AlertDialog, AlertDialogContent, AlertDialogHeader } from '@/app/ui/view/molecule/alert-dialog/alert-dialog';
import { DIALOG_KEY } from '@/app/utils/key/dialog-key.util';
import { redirect } from 'next/navigation';

export default function UpdateInstruction() {
const { isOpen, close } = useDialog(DIALOG_KEY.UPDATE_INSTRUCTION);

return (
<AlertDialog open={isOpen}>
<AlertDialogContent>
<div className="flex flex-col gap-4">
<div className="text-xl font-bold">업데이트 안내문</div>
<p className="text-gray-800 leading-6">
<span className="text-primary font-bold">졸업을 부탁해</span>가 2.0.2 버전으로 업데이트됨에 따라, 2024년 9월
3일 이전에 성적표를 업로드하신 모든 사용자께서는 성적표를 재업로드해 주시기 바랍니다.
<br />
감사합니다.
</p>
<Button
label="확인"
size="xs"
variant="list"
style={{ alignSelf: 'flex-end' }}
onClick={() => {
close();
redirect('/grade-upload');
}}
/>
</div>
</AlertDialogContent>
</AlertDialog>
);
}
54 changes: 26 additions & 28 deletions app/business/services/lecture/taken-lecture.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,35 +44,33 @@ export const parsePDFtoText = async (formData: FormData) => {
};

export const deleteTakenLecture = async (lectureId: number) => {
// try {
const response = await fetch(`${API_PATH.takenLectures}/${lectureId}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${cookies().get('accessToken')?.value}`,
},
});
// http error handling에서 result가 필수값이므로 사용할 수 없음
// 하지만 fetch 가 수정되면서 바꿀 예정이므로 현재는 작동만 되도록
if (response.ok) {
revalidateTag(TAG.GET_TAKEN_LECTURES);
return {
isSuccess: true,
};
} else {
return {
isSuccess: false,
};
try {
const response = await fetch(`${API_PATH.takenLectures}/${lectureId}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${cookies().get('accessToken')?.value}`,
},
});
if (response.ok) {
revalidateTag(TAG.GET_TAKEN_LECTURES);
return {
isSuccess: true,
};
} else {
return {
isSuccess: false,
};
}
} catch (error) {
if (error instanceof BadRequestError) {
return {
isSuccess: false,
};
} else {
throw error;
}
}
// } catch (error) {
// if (error instanceof BadRequestError) {
// return {
// isSuccess: false,
// };
// } else {
// throw error;
// }
// }
};

export const addTakenLecture = async (lectureId: number) => {
Expand Down
2 changes: 1 addition & 1 deletion app/business/services/lecture/taken-lecture.query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export interface TakenLecturesResponse {
takenLectures: TakenLectureInfoResponse[];
}

interface TakenLectureInfoResponse {
export interface TakenLectureInfoResponse {
[index: string]: string | number;
id: number;
year: string;
Expand Down
13 changes: 11 additions & 2 deletions app/business/services/user/user.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@ import {
SignInResponseSchema,
ValidateTokenResponseSchema,
ResetPasswordFormSchema,
isExpiredGradeUser,
} from './user.validation';
import { cookies } from 'next/headers';
import { isValidation } from '@/app/utils/zod/validation.util';
import { redirect } from 'next/navigation';
import { auth } from './user.query';
import { fetchUser } from './user.query';

function deleteCookies() {
cookies().delete('accessToken');
Expand Down Expand Up @@ -132,8 +133,16 @@ export async function authenticate(prevState: FormState, formData: FormData): Pr
}
}

const user = await fetchUser();
if (isExpiredGradeUser(user)) {
return {
isSuccess: true,
isFailure: false,
validationError: {},
message: '재업로드',
};
}
redirect('/my');

return {
isSuccess: true,
isFailure: false,
Expand Down
4 changes: 4 additions & 0 deletions app/business/services/user/user.validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,7 @@ export const SignUpFormSchema = z
export function isInitUser(x: UserInfoResponse | InitUserInfoResponse): x is InitUserInfoResponse {
return x.studentName === null;
}

export function isExpiredGradeUser(x: UserInfoResponse | InitUserInfoResponse): x is InitUserInfoResponse {
return x.studentName !== null && x.takenCredit === 0 && x.totalCredit === 0;
}
2 changes: 1 addition & 1 deletion app/store/querys/lecture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const useFetchSearchLecture = () => {
const searchWord = useAtomValue(searchWordAtom);

return useSuspenseQuery<SearchedLectureInfoResponse[]>({
queryKey: [QUERY_KEY.SEARCH_LECTURE],
queryKey: [QUERY_KEY.SEARCH_LECTURE, searchWord.type, searchWord.keyword],
queryFn: () => {
return fetchSearchLectures(searchWord.type, searchWord.keyword as string);
},
Expand Down
1 change: 1 addition & 0 deletions app/store/stores/dialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const initialState = {
[DIALOG_KEY.LECTURE_SEARCH]: false,
[DIALOG_KEY.USER_DELETE]: false,
[DIALOG_KEY.SIDE_NAVIGATION]: false,
[DIALOG_KEY.UPDATE_INSTRUCTION]: false,
};

const dialogAtom = atom(initialState);
Expand Down
24 changes: 23 additions & 1 deletion app/ui/lecture/lecture-search/lecture-search-result.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,22 @@ import List from '@/app/ui/view/molecule/list';
import Grid from '@/app/ui/view/molecule/grid';
import AddTakenLectureButton from '../taken-lecture/add-taken-lecture-button';
import { useFetchSearchLecture } from '@/app/store/querys/lecture';
import { useAtomValue } from 'jotai';
import { searchWordAtom } from '@/app/store/stores/search-word';

const emptyDataRender = (searchWord: string) => {
return (
<div className="flex flex-col items-center justify-center gap-4">
<div className="text-md font-medium text-gray-400 text-center whitespace-pre-wrap">
<span className="text-light-blue-7 text-lg font-semibold">{searchWord}</span> 에 대한 검색 결과가 없습니다.
</div>
</div>
);
};

export default function LectureSearchResult() {
const { data } = useFetchSearchLecture();
const searchWord = useAtomValue(searchWordAtom);

const renderAddActionButton = (item: SearchedLectureInfoResponse, taken: boolean) => {
return <AddTakenLectureButton lectureItem={item} isTaken={taken} />;
Expand All @@ -28,5 +41,14 @@ export default function LectureSearchResult() {
);
};

return <List data={data} render={render} isScrollList={true} />;
return (
<List
data={data}
render={render}
isScrollList={true}
emptyDataRender={() => {
return emptyDataRender(searchWord.keyword as string);
}}
/>
);
}
20 changes: 17 additions & 3 deletions app/ui/lecture/taken-lecture/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
import { fetchTakenLectures } from '@/app/business/services/lecture/taken-lecture.query';
import { fetchTakenLectures, TakenLectureInfoResponse } from '@/app/business/services/lecture/taken-lecture.query';
import TakenLectureList from './taken-lecture-list';
import TakenLectureLabel from './taken-lecture-label';
import TakenLectureAtomHydrator from '@/app/store/stores/taken-lecture-atom-hydrator';
import UpdateTakenLecture from './update-taken-lecture';

const processChapel = (takenLectures: TakenLectureInfoResponse[]) => {
return takenLectures.map((lecture) => {
return {
id: lecture.id,
year: lecture.year,
semester: lecture.semester,
lectureCode: lecture.lectureCode,
lectureName: lecture.lectureName,
credit: lecture.lectureName === '채플' ? 0.5 : lecture.credit,
};
});
};
export default async function TakenLecture() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

credit 외 속성은 모두 그대로 사용하니, 구조 분해 할당을 사용하면 코드가 더 보기 편할 것 같아요.

Suggested change
const processChapel = (takenLectures: TakenLectureInfoResponse[]) => {
return takenLectures.map((lecture) => {
return {
id: lecture.id,
year: lecture.year,
semester: lecture.semester,
lectureCode: lecture.lectureCode,
lectureName: lecture.lectureName,
credit: lecture.lectureName === '채플' ? 0.5 : lecture.credit,
};
});
};
const processChapel = (takenLectures: TakenLectureInfoResponse[]) => {
return takenLectures.map((lecture) => {
return {
...lecture,
credit: lecture.lectureName === '채플' ? 0.5 : lecture.credit,
};
});
};

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

알려주셔서 감사합니다
f0c4e62

const data = await fetchTakenLectures();
const takenLectures = processChapel(data.takenLectures);

return (
<div className="flex flex-col gap-2">
<TakenLectureAtomHydrator initialValue={data.takenLectures}>
<UpdateTakenLecture data={data.takenLectures}>
<TakenLectureAtomHydrator initialValue={takenLectures}>
<UpdateTakenLecture data={takenLectures}>
<TakenLectureLabel />
<TakenLectureList />
</UpdateTakenLecture>
Expand Down
3 changes: 3 additions & 0 deletions app/ui/lecture/taken-lecture/taken-lecture-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ export default function TakenLectureList() {
});
}
deleteLecture(lectureId);
toast({
title: '과목 삭제에 성공했습니다',
});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

과목을 여러 개 삭제하면 어떤 과목이 삭제되었는지 확인하기 어려울 것 같아서 lectureName이나 코드 정보를 ID로 가져오는 것이 어렵지 않다면, 토스트 메시지에 추가하는 것도 좋아보여요.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

f0c4e62
기존 delete logic을 수정함으로써 해결했습니다
이전에 lecture 컴포넌트 관련해서 어떻게 구현할지 고민을 많이 했어서 그런지 간단하게 수정할 수 있었습니다-!

};

return (
Expand Down
28 changes: 21 additions & 7 deletions app/ui/user/sign-in-form/sign-in-form.tsx
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

로그인시에 만료유저여부를 식별하는만큼 SignInPage에 위치시키는 것은 어떨까요?
제가 생각하는 방법도 선배가 생각하시는 그림과 동일한 것 같은데, 모달을 키는 함수를 props로 전달하는 방법을 통해 로그인 성공시에 모달을 노출하는 방식이 좋아보여요. SignUpForm에서 해당 구조를 사용하고 있어요!

Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
'use client';
import UpdateInstruction from '@/app/(sub-page)/sign-in/components/updateInstruction';
import Form from '../../view/molecule/form';
import { authenticate } from '@/app/business/services/user/user.command';
import useDialog from '@/app/hooks/useDialog';
import { DIALOG_KEY } from '@/app/utils/key/dialog-key.util';

export default function SignInForm() {
const { open } = useDialog(DIALOG_KEY.UPDATE_INSTRUCTION);
return (
<Form id="로그인" className="space-y-6" action={authenticate}>
<Form.TextInput required={true} label="아이디" id="authId" placeholder="아이디를 입력하세요" />
<Form.PasswordInput required={true} label="비밀번호" id="password" placeholder="비밀번호를 입력하세요" />
<div className="pt-6">
<Form.SubmitButton label="로그인" position="center" variant="primary" />
</div>
</Form>
<>
<Form
id="로그인"
className="space-y-6"
action={authenticate}
onSuccess={(formState) => {
if (formState?.message === '재업로드') open();
}}
>
<Form.TextInput required={true} label="아이디" id="authId" placeholder="아이디를 입력하세요" />
<Form.PasswordInput required={true} label="비밀번호" id="password" placeholder="비밀번호를 입력하세요" />
<div className="pt-6">
<Form.SubmitButton label="로그인" position="center" variant="primary" />
</div>
</Form>
<UpdateInstruction />
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • useDialog 훅은 유틸 훅이므로 계층과 상관없이 사용할 수 있습니다. 제 생각에는 UpdateInstriction 컴포넌트가 이 위치에 있을 필요는 없다고 봅니다. 페이지 수준에 위치시켜도 useDialog로 켜고 닫을 수 있으니, 페이지 컴포넌트로 올려도 괜찮다고 생각해요.
  • 또 다른 방법이자 가장 좋다고 생각하는 방법은, Sign-in-form 컴포넌트가 성공 시 작동하는 props를 받고 호출하는 겁니다. 그리고 페이지 컴포넌트에서 dialog를 여는 open 함수를 핸들러로 넘겨주는 거죠. 이렇게 하면 Sign-in-form 컴포넌트는 재업로드 dialog에 대해 모르고, 페이지 수준에서 전부 처리할 수 있을 것 같습니다.

</>
);
}
2 changes: 1 addition & 1 deletion app/ui/view/atom/button/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const ButtonVariants = cva(`flex justify-center items-center`, {
dark: 'bg-dark rounded-[100px] text-white border-0 hover:bg-dark-hover',
secondary: 'bg-white rounded-[100px] border-solid border-[1px] border-gray-6 hover:bg-white-hover',
text: 'font-medium text-slate-400 text-sm hover:text-slate-600',
list: 'py-1 px-3 bg-blue-500 rounded-[7px] text-white leading-5 font-medium text-base hover:bg-blue-500',
list: 'py-1 px-3 bg-blue-500 rounded-[7px] text-white leading-5 font-medium text-base hover:bg-blue-600',
},
size: {
default: '',
Expand Down
1 change: 1 addition & 0 deletions app/utils/key/dialog-key.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export const DIALOG_KEY = {
LECTURE_SEARCH: 'LECTURE_SEARCH',
USER_DELETE: 'USER_DELETE',
SIDE_NAVIGATION: 'SIDE_NAVIGATION',
UPDATE_INSTRUCTION: 'UPDATE_INSTRUCTION',
} as const;

export type DialogKey = (typeof DIALOG_KEY)[keyof typeof DIALOG_KEY];
4 changes: 2 additions & 2 deletions middleware.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NextResponse, type NextRequest } from 'next/server';
import { auth } from './app/business/services/user/user.query';
import { isInitUser } from './app/business/services/user/user.validation';
import { isExpiredGradeUser, isInitUser } from './app/business/services/user/user.validation';
import { refreshToken } from './app/business/services/user/user.command';

async function getAuth(request: NextRequest): Promise<{
Expand All @@ -22,7 +22,7 @@ async function getAuth(request: NextRequest): Promise<{
}

return {
role: isInitUser(user) ? 'init' : 'user',
role: isInitUser(user) || isExpiredGradeUser(user) ? 'init' : 'user',
};
}

Expand Down
Loading