Skip to content

Commit

Permalink
Merge pull request #121 from Myongji-Graduate/use-suspense-query-error/
Browse files Browse the repository at this point in the history
…#120

Use suspense query error/#120
  • Loading branch information
gahyuun authored Jun 30, 2024
2 parents 27001fd + cd88b24 commit c1bef52
Show file tree
Hide file tree
Showing 17 changed files with 133 additions and 125 deletions.
3 changes: 2 additions & 1 deletion app/business/services/auth.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use server';
import { cookies } from 'next/headers';

export const getToken = (): string | undefined => {
export const getToken = async (): Promise<string | undefined> => {
return cookies().get('accessToken')?.value;
};
// server action은 async를 써야함
106 changes: 55 additions & 51 deletions app/business/services/lecture/taken-lecture.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { httpErrorHandler } from '@/app/utils/http/http-error-handler';
import { BadRequestError } from '@/app/utils/http/http-error';
import { revalidateTag } from 'next/cache';
import { TAG } from '@/app/utils/http/tag';
import { cookies } from 'next/headers';
import { getToken } from '../auth';

export const registerUserGrade = async (prevState: FormState, formData: FormData) => {
const parsingText = await parsePDFtoText(formData);
Expand Down Expand Up @@ -43,60 +45,62 @@ export const parsePDFtoText = async (formData: FormData) => {
};

export const deleteTakenLecture = async (lectureId: number) => {
try {
const response = await fetch(API_PATH.takenLectures, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ lectureId }),
});
const result = await response.json();
httpErrorHandler(response, result);
} catch (error) {
if (error instanceof BadRequestError) {
return {
isSuccess: false,
};
} else {
throw error;
}
// 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,
};
}
revalidateTag(TAG.GET_TAKEN_LECTURES);
return {
isSuccess: true,
};
// } catch (error) {
// if (error instanceof BadRequestError) {
// return {
// isSuccess: false,
// };
// } else {
// throw error;
// }
// }
};

export const addTakenLecture = async (lectureId: number) => {
try {
const response = await fetch(API_PATH.takenLectures, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ lectureId }),
});
const result = await response.json();
httpErrorHandler(response, result);
} catch (error) {
if (error instanceof BadRequestError) {
return {
isSuccess: false,
isFailure: true,
validationError: {},
message: '과목 추가에 실패했습니다',
};
} else {
throw error;
}
const token = await getToken();
const response = await fetch(API_PATH.takenLectures, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ lectureId }),
});
// delete taken lecture과 비슷한 이유로 코드 수정
if (response.ok) {
revalidateTag(TAG.GET_TAKEN_LECTURES);
return {
isSuccess: true,
isFailure: false,
validationError: {},
message: '과목 추가에 성공했습니다',
};
} else {
return {
isSuccess: false,
isFailure: true,
validationError: {},
message: '과목 추가에 실패했습니다',
};
}

revalidateTag(TAG.GET_TAKEN_LECTURES);
return {
isSuccess: true,
isFailure: false,
validationError: {},
message: '과목 추가에 성공했습니다',
};
};
4 changes: 2 additions & 2 deletions app/business/services/lecture/taken-lecture.query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { cookies } from 'next/headers';

export interface TakenLecturesResponse {
totalCredit: number;
takenLectures: TakenLectrueInfoResponse[];
takenLectures: TakenLectureInfoResponse[];
}

interface TakenLectrueInfoResponse {
interface TakenLectureInfoResponse {
[index: string]: string | number;
id: number;
year: string;
Expand Down
3 changes: 1 addition & 2 deletions app/business/services/user/user.query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export async function auth(): Promise<InitUserInfoResponse | UserInfoResponse |

export async function fetchUser(): Promise<InitUserInfoResponse | UserInfoResponse> {
try {
const response = await fetch(`${API_PATH.user}`, {
const response = await fetch(`${API_PATH.user}/me`, {
headers: {
Authorization: `Bearer ${cookies().get('accessToken')?.value}`,
},
Expand All @@ -29,7 +29,6 @@ export async function fetchUser(): Promise<InitUserInfoResponse | UserInfoRespon
const result = await response.json();

httpErrorHandler(response, result);

if (isValidation(result, UserInfoResponseSchema || InitUserInfoResponseSchema)) {
return result;
} else {
Expand Down
4 changes: 2 additions & 2 deletions app/business/services/user/user.validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { UserInfoResponse, InitUserInfoResponse } from './user.type';
export const UserInfoResponseSchema = z.object({
studentNumber: z.string(),
studentName: z.string(),
completionDivision: z.array(
completeDivision: z.array(
z.object({
majorType: z.enum(['PRIMARY', 'DUAL', 'SUB']),
major: z.string(),
Expand All @@ -18,7 +18,7 @@ export const UserInfoResponseSchema = z.object({
export const InitUserInfoResponseSchema = z.object({
studentNumber: z.string(),
studentName: z.null(),
completionDivision: z.null(),
completeDivision: z.null(),
totalCredit: z.null(),
takenCredit: z.null(),
graduated: z.null(),
Expand Down
80 changes: 42 additions & 38 deletions app/mocks/data.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export const takenLectures = JSON.parse(`{
export const userInfo = JSON.parse(`{
"studentNumber": "60181666",
"studentName": "장진욱",
"completionDivision" : [
"completeDivision" : [
{
"majorType" : "PRIMARY",
"major": "디지털콘텐츠디자인학과"
Expand Down Expand Up @@ -279,41 +279,45 @@ export const credits = JSON.parse(`[
}
]`);

export const searchLectures = JSON.parse(`{
"lectures": [
{
"id": 1,
"lectureCode": "KMA02106",
"name": "영어1",
"credit": 2,
"isTaken" : false
},
{
"id": 2,
"lectureCode": "KMA02106",
"name": "영어2",
"credit": 2,
"isTaken" : true
},
{
"id": 3,
"lectureCode": "KMA02136",
"name": "영어무역이론",
"credit": 3,
"isTaken" : false
},
{
"id": 1,
"lectureCode": "KMA02106",
"name": "영어회화3",
"credit": 1,
"isTaken" : false
},
export const searchLectures = [
{
"id": 1,
"lectureCode": "KMA02106",
"name": "영어회화4",
"credit": 2,
"isTaken" : true
}]
}`);
id: 1,
lectureCode: 'KMA02106',
name: '영어1',
credit: 2,
taken: false,
revoked: true,
},
{
id: 2,
lectureCode: 'KMA02106',
name: '영어2',
credit: 2,
taken: true,
revoked: false,
},
{
id: 3,
lectureCode: 'KMA02136',
name: '영어무역이론',
credit: 3,
taken: false,
revoked: false,
},
{
id: 4,
lectureCode: 'KMA02106',
name: '영어회화3',
credit: 1,
taken: false,
revoked: false,
},
{
id: 6,
lectureCode: 'KMA02106',
name: '영어회화4',
credit: 2,
taken: true,
revoked: false,
},
];
10 changes: 5 additions & 5 deletions app/mocks/db.mock.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { SearchLecturesResponse } from '../store/querys/lecture';
import { TakenLecturesResponse } from '../business/services/lecture/taken-lecture.query';
import { CreditResponse, ResultCategoryDetailResponse } from '../store/querys/result';
import {
Expand All @@ -8,20 +7,21 @@ import {
InitUserInfoResponse,
} from '../business/services/user/user.type';
import { takenLectures, credits, searchLectures, userInfo, users, resultCategoryDetailInfo } from './data.mock';
import { SearchedLectureInfoResponse } from '../store/querys/lecture';

interface MockDatabaseState {
takenLectures: TakenLecturesResponse;
resultCategoryDetailInfo: ResultCategoryDetailResponse;
credits: CreditResponse[];
users: SignUpRequestBody[];
searchLectures: SearchLecturesResponse;
searchLectures: SearchedLectureInfoResponse[];
userInfo: UserInfoResponse;
}

type MockDatabaseAction = {
reset: () => void;
getTakenLectures: () => TakenLecturesResponse;
getSearchLectures: () => SearchLecturesResponse;
getSearchLectures: () => SearchedLectureInfoResponse[];
addTakenLecture: (lectureId: number) => boolean;
deleteTakenLecture: (lectureId: number) => boolean;
createUser: (user: SignUpRequestBody) => boolean;
Expand Down Expand Up @@ -49,7 +49,7 @@ export const mockDatabase: MockDatabaseAction = {
return false;
},
addTakenLecture: (lectureId) => {
const lecture = mockDatabaseStore.searchLectures.lectures.find((lecture) => lecture.id === lectureId);
const lecture = mockDatabaseStore.searchLectures.find((lecture) => lecture.id === lectureId);
if (!lecture) {
return false;
}
Expand Down Expand Up @@ -85,7 +85,7 @@ export const mockDatabase: MockDatabaseAction = {
return {
studentNumber: '',
studentName: null,
completionDivision: null,
completeDivision: null,
totalCredit: null,
takenCredit: null,
graduated: null,
Expand Down
8 changes: 5 additions & 3 deletions app/mocks/handlers/taken-lecture-handler.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ export const takenLectureHandlers = [
if (isAdded) return HttpResponse.json({ message: '과목 추가에 성공했습니다' }, { status: 200 });
return HttpResponse.json({ errorCode: 400, message: '추가에 실패했습니다' }, { status: 400 });
}),
http.delete<never, { lectureId: number }>(API_PATH.takenLectures, async ({ request }) => {
const body = await request.json();
const isDeleted = mockDatabase.deleteTakenLecture(body.lectureId);
http.delete<never, { lectureId: number }>(`${API_PATH.takenLectures}/:id`, async ({ request }) => {
const url = new URL(request.url);
// url.pathname.split("/") 의 결과 : ['','taken-lectures',120]
const lectureId = Number(url.pathname.split('/')[2]);
const isDeleted = mockDatabase.deleteTakenLecture(lectureId);
await delay(1000);
if (isDeleted) {
return HttpResponse.json({ message: '삭제되었습니다' }, { status: 200 });
Expand Down
2 changes: 1 addition & 1 deletion app/mocks/handlers/user-handler.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export const userHandlers = [
}
}),
http.get<never, never, UserInfoResponse | InitUserInfoResponse | ErrorResponseData>(
API_PATH.user,
`${API_PATH.user}/me`,
async ({ request }) => {
const accessToken = request.headers.get('Authorization')?.replace('Bearer ', '');
if (accessToken === 'undefined' || !accessToken) {
Expand Down
13 changes: 5 additions & 8 deletions app/store/querys/lecture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { API_PATH } from '@/app/business/api-path';
import { getToken } from '@/app/business/services/auth';
import { LectureInfoResponse } from './result';

export type SearchedLectureInfoResponse = LectureInfoResponse & { isTaken: boolean };
export type SearchedLectureInfoResponse = LectureInfoResponse & { taken: boolean; revoked: boolean };

export const useFetchSearchLecture = () => {
const searchWord = useAtomValue(searchWordAtom);
Expand All @@ -20,15 +20,12 @@ export const useFetchSearchLecture = () => {
});
};

export interface SearchLecturesResponse {
lectures: SearchedLectureInfoResponse[];
}

export const fetchSearchLectures = async (type: string, keyword: string) => {
const response = await axios<SearchLecturesResponse>(`${API_PATH.lectures}?type=${type}&&keyword=${keyword}`, {
const token = await getToken();
const response = await axios<SearchedLectureInfoResponse[]>(`${API_PATH.lectures}?type=${type}&&keyword=${keyword}`, {
headers: {
Authorization: `Bearer ${getToken()}`,
Authorization: `Bearer ${token}`,
},
});
return response.data.lectures;
return response.data;
};
2 changes: 1 addition & 1 deletion app/ui/lecture/lecture-search/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default function LectureSearch() {
const searchable = searchWord.keyword && searchWord.keyword.length > 1;

return (
<div className="bg-white w-full h-[500px] sm:h-[400px] z-[10] flex justify-center" data-testid="lecture-search">
<div className="bg-white w-full h-[520px] sm:h-[420px] z-[10] flex justify-center" data-testid="lecture-search">
<div className="w-[800px] mx-auto my-7 flex flex-col gap-10 sm:gap-6">
<LectureSearchBar />
{searchable ? (
Expand Down
3 changes: 2 additions & 1 deletion app/ui/lecture/lecture-search/lecture-search-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,15 @@ export default function LectureSearchBar() {
<Select.Item value="lectureCode" placeholder="과목코드" />
</Select>
</div>
<div className="w-[60%] sm:w-[40%] flex justify-between">
<div className="w-[60%] sm:w-[40%] flex justify-between flex-col gap-1">
<TextInput
data-cy="search-lecture-input"
placeholder="검색어를 입력해주세요"
icon={MagnifyingGlassIcon}
onValueChange={handleDebounceKeywordSearch}
data-testid="lecture-search-input"
/>
<div className="text-zinc-400 text-xs text-end">※ 회색으로 표기된 과목은 폐강과목입니다</div>
</div>
</div>
);
Expand Down
Loading

0 comments on commit c1bef52

Please sign in to comment.