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
20 changes: 20 additions & 0 deletions app/(sub-page)/sign-in/components/sign-in-form-container.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use client';
import useDialog from '@/app/hooks/useDialog';
import SignInForm from '@/app/ui/user/sign-in-form/sign-in-form';
import { DIALOG_KEY } from '@/app/utils/key/dialog-key.util';
import React from 'react';
import UpdateInstruction from './update-instruction';
import { FormState } from '@/app/ui/view/molecule/form/form-root';

export default function SignInFormContainer() {
const { open } = useDialog(DIALOG_KEY.UPDATE_INSTRUCTION);
const handleSuccess = (formState?: FormState) => {
if (formState?.message === 'μž¬μ—…λ‘œλ“œ') open();
};
return (
<>
<SignInForm onSuccess={handleSuccess} />
<UpdateInstruction />
</>
);
}
36 changes: 36 additions & 0 deletions app/(sub-page)/sign-in/components/update-instruction.tsx
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 } from '@/app/ui/view/molecule/alert-dialog/alert-dialog';
import { DIALOG_KEY } from '@/app/utils/key/dialog-key.util';
import { useRouter } from 'next/navigation';

export default function UpdateInstruction() {
const router = useRouter();
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();
router.push('/grade-upload');
}}
/>
</div>
</AlertDialogContent>
</AlertDialog>
);
}
4 changes: 2 additions & 2 deletions app/(sub-page)/sign-in/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import SignInForm from '@/app/ui/user/sign-in-form/sign-in-form';
import ContentContainer from '@/app/ui/view/atom/content-container/content-container';

import TitleBox from '@/app/ui/view/molecule/title-box/title-box';
Expand All @@ -9,6 +8,7 @@ import Button from '@/app/ui/view/atom/button/button';
import Responsive from '@/app/ui/responsive';
import Link from 'next/link';
import type { Metadata } from 'next';
import SignInFormContainer from './components/sign-in-form-container';

export const metadata: Metadata = {
title: '둜그인',
Expand All @@ -27,7 +27,7 @@ export default function SignInPage() {
<div className="pb-12">
<TitleBox title={'둜그인'} />
</div>
<SignInForm />
<SignInFormContainer />
<div className="flex mt-12 space-x-4 h-6 items-center justify-center">
<Link href={'/'}>
<Button className="text-xs" label="아이디 μ°ΎκΈ°" variant={'text'} />
Expand Down
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
5 changes: 5 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,8 @@ 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;
return false;
}
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);
}}
/>
);
}
12 changes: 7 additions & 5 deletions app/ui/lecture/taken-lecture/delete-taken-lecture-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ import {
AlertDialogTitle,
AlertDialogTrigger,
} from '../../view/molecule/alert-dialog/alert-dialog';
import { TakenLectrueInfo } from '@/app/store/stores/custom-taken-lecture';

interface DeleteTakenLectureButtonProps {
lectureId: number;
onDelete: (lectureId: number) => void;
item: TakenLectrueInfo;
onDelete: (item: TakenLectrueInfo) => void;
}
export default function DeleteTakenLectureButton({ lectureId, onDelete }: DeleteTakenLectureButtonProps) {

export default function DeleteTakenLectureButton({ item, onDelete }: DeleteTakenLectureButtonProps) {
const [open, setOpen] = useState(false);
return (
<AlertDialog open={open} onOpenChange={setOpen}>
Expand All @@ -25,7 +27,7 @@ export default function DeleteTakenLectureButton({ lectureId, onDelete }: Delete
label="μ‚­μ œ"
variant="text"
size="default"
data-cy={`taken-lecture-delete-model-trigger-${lectureId}`}
data-cy={`taken-lecture-delete-model-trigger-${item.id}`}
data-testid="taken-lecture-delete-button"
/>
</div>
Expand All @@ -38,7 +40,7 @@ export default function DeleteTakenLectureButton({ lectureId, onDelete }: Delete
<AlertDialogCancel className="font-bold">μ·¨μ†Œ</AlertDialogCancel>
<form
action={() => {
onDelete(lectureId);
onDelete(item);
}}
>
<Button
Expand Down
16 changes: 13 additions & 3 deletions app/ui/lecture/taken-lecture/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
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 {
...lecture,
credit: lecture.lectureName === 'μ±„ν”Œ' ? 0.5 : lecture.credit,
};
});
};
export default async function TakenLecture() {
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
18 changes: 11 additions & 7 deletions app/ui/lecture/taken-lecture/taken-lecture-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,26 @@ import { useToast } from '../../view/molecule/toast/use-toast';
import Responsive from '../../responsive';
import { TAKEN_LECTURE_TABLE_HEADER_INFO } from './taken-lecture-constant';
import { useTakenLecture } from '@/app/business/hooks/use-taken-lecture.hook';
import { TakenLectrueInfo } from '@/app/store/stores/custom-taken-lecture';

export default function TakenLectureList() {
const { optimisticLecture, deleteOptimisticLecture, deleteLecture } = useTakenLecture();

const { toast } = useToast();

const handleDeleteTakenLecture = async (lectureId: number) => {
deleteOptimisticLecture(lectureId);
const result = await deleteTakenLecture(lectureId);
const handleDeleteTakenLecture = async (item: TakenLectrueInfo) => {
deleteOptimisticLecture(item.lectureId);
const result = await deleteTakenLecture(item.id);
if (!result.isSuccess) {
return toast({
title: 'κ³Όλͺ© μ‚­μ œμ— μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€',
title: `${item.lectureName} μ‚­μ œμ— μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€.`,
variant: 'destructive',
});
}
deleteLecture(lectureId);
deleteLecture(item.id);
toast({
title: `${item.lectureName} κ³Όλͺ©μ„ μ‚­μ œν–ˆμŠ΅λ‹ˆλ‹€.`,
});
};

return (
Expand All @@ -30,8 +34,8 @@ export default function TakenLectureList() {
<Table
headerInfo={TAKEN_LECTURE_TABLE_HEADER_INFO}
data={optimisticLecture}
renderActionButton={(id: number) => (
<DeleteTakenLectureButton lectureId={id} onDelete={handleDeleteTakenLecture} />
renderActionButton={(item: TakenLectrueInfo) => (
<DeleteTakenLectureButton item={item} onDelete={handleDeleteTakenLecture} />
)}
/>
</Responsive>
Expand Down
24 changes: 16 additions & 8 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,23 @@
'use client';
import UpdateInstruction from '@/app/(sub-page)/sign-in/components/update-instruction';
import Form from '../../view/molecule/form';
import { authenticate } from '@/app/business/services/user/user.command';
import { FormState } from '../../view/molecule/form/form-root';

export default function SignInForm() {
interface SignInForm {
onSuccess?: (formState?: FormState) => void;
}
export default function SignInForm({ onSuccess }: SignInForm) {
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={onSuccess}>
<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 />
</>
);
}
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
Loading
Loading