Skip to content

Commit

Permalink
FE-43 ✨ 상세페이지 에피그램 조회 (#18)
Browse files Browse the repository at this point in the history
* FE-43✨ 상세페이지 Epigram API연동

* FE-43⚡ ️axios 에러 핸들링 추가

* FE-43🏗️ 상세페이지 Layout 구조개선

* FE-43📝 주석 추가

* FE-43🔥 사용안하는 파일 삭제

* FE-43✏️ 오타 수정

* FE-43 🐛 id없을때 useQuery실행되는 문제 해결

* FE-43♻️  interface->zod 변경

---------

Co-authored-by: 우지석 <[email protected]>
  • Loading branch information
jisurk and 우지석 committed Jul 15, 2024
1 parent ca07c02 commit 2c7f789
Show file tree
Hide file tree
Showing 11 changed files with 168 additions and 49 deletions.
47 changes: 47 additions & 0 deletions src/apis/epigram.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import axios, { AxiosError } from 'axios';
import { GetEpigramResponseType, GetEpigramRequestType } from '@/schema/epigram';

const BASE_URL = 'https://fe-project-epigram-api.vercel.app/5-9';

const getEpigram = async (request: GetEpigramRequestType): Promise<GetEpigramResponseType> => {
const { id } = request;

// id가 undefined인 경우 에러 throw
if (id === undefined) {
throw new Error('Epigram ID가 제공되지 않았습니다.');
}

// NOTE : 임시로 테스트계정의 토큰을 변수로 선언해서 사용
const TOKEN =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MjIsInRlYW1JZCI6IjUtOSIsInNjb3BlIjoiYWNjZXNzIiwiaWF0IjoxNzIwODM4NTE5LCJleHAiOjE3MjA4NDAzMTksImlzcyI6InNwLWVwaWdyYW0ifQ.cPwm4T2LRi985p9NDqXrR0fSY5n-cvxzjGh8vmshoSM';

try {
const response = await axios.get(`${BASE_URL}/epigrams/${id}`, {
headers: {
Authorization: `Bearer ${TOKEN}`,
'Content-Type': 'application/json',
},
});
return response.data;
} catch (error) {
// 에러가 Axios에러인지 확인
if (axios.isAxiosError(error)) {
const axiosError = error as AxiosError;
if (axiosError.response) {
// 서버에 요청 성공, 응답으로 200번대 이외의 상태를 응답으로 받았을때
throw new Error(`API 에러: ${axiosError.response.status}`);
} else if (axiosError.request) {
// 요청이 이루어졌으나 응답을 받지 못한 경우
throw new Error('서버로부터 응답을 받지 못했습니다.');
} else {
// 요청 설정 중에 오류가 발생한 경우
throw new Error('요청 설정 중 오류가 발생했습니다.');
}
} else {
// axios 에러가 아닌 경우
throw new Error('예상치 못한 오류가 발생했습니다.');
}
}
};

export default getEpigram;
19 changes: 17 additions & 2 deletions src/apis/queries.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { createQueryKeyStore } from '@lukemorales/query-key-factory';
import { GetUserRequestType } from '@/schema/user';
import { GetEpigramRequestType } from '@/schema/epigram';
import { getMe, getUser } from './user';
import getEpigram from './epigram';

const quries = createQueryKeyStore({
const queries = createQueryKeyStore({
user: {
getMe: () => ({
queryKey: ['getMe'],
Expand All @@ -13,6 +15,19 @@ const quries = createQueryKeyStore({
queryFn: () => getUser(request),
}),
},
// NOTE: Epigram 관련 query함수
epigram: {
getEpigram: (request: GetEpigramRequestType) => ({
queryKey: ['epigram', request.id, request],
queryFn: () => {
if (request.id === undefined) {
throw new Error('Epigram ID가 제공되지 않았습니다.');
}
return getEpigram(request);
},
enabled: request.id !== undefined,
}),
},
});

export default quries;
export default queries;
8 changes: 4 additions & 4 deletions src/apis/user.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import type { GetUserReponseType, GetUserRequestType, PatchMeRequestType } from '@/schema/user';
import type { GetUserResponseType, GetUserRequestType, PatchMeRequestType } from '@/schema/user';
import httpClient from '.';

export const getMe = async (): Promise<GetUserReponseType> => {
export const getMe = async (): Promise<GetUserResponseType> => {
const response = await httpClient.get('/users/me');
return response.data;
};

export const getUser = async (request: GetUserRequestType): Promise<GetUserReponseType> => {
export const getUser = async (request: GetUserRequestType): Promise<GetUserResponseType> => {
const { id } = request;
const response = await httpClient.get(`/users/${id}`);
return response.data;
};

export const updateMe = async (request: PatchMeRequestType): Promise<GetUserReponseType> => {
export const updateMe = async (request: PatchMeRequestType): Promise<GetUserResponseType> => {
const response = await httpClient.patch('/users/me', { ...request });
return response.data;
};
11 changes: 11 additions & 0 deletions src/hooks/epigramQueryHook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { useQuery } from '@tanstack/react-query';
import queries from '@/apis/queries';
import { GetEpigramRequestType } from '@/schema/epigram';

const useEpigramQuery = (request: GetEpigramRequestType | undefined, enabled = true) =>
useQuery({
...queries.epigram.getEpigram(request ?? { id: undefined }),
enabled: enabled && request?.id !== undefined,
});

export default useEpigramQuery;
12 changes: 6 additions & 6 deletions src/hooks/userQueryHooks.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import quries from '@/apis/queries';
import queries from '@/apis/queries';
import { updateMe } from '@/apis/user';
import { GetUserReponseType, GetUserRequestType, PatchMeRequestType } from '@/schema/user';
import { GetUserResponseType, GetUserRequestType, PatchMeRequestType } from '@/schema/user';
import { MutationOptions } from '@/types/query';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';

export const useMeQuery = () => useQuery(quries.user.getMe());
export const useMeQuery = () => useQuery(queries.user.getMe());

export const useUserQuery = (requset: GetUserRequestType) => useQuery(quries.user.getUser(requset));
export const useUserQuery = (request: GetUserRequestType) => useQuery(queries.user.getUser(request));

export const useUpdateMe = (options: MutationOptions<GetUserReponseType>) => {
export const useUpdateMe = (options: MutationOptions<GetUserResponseType>) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (request: PatchMeRequestType) => updateMe(request),
...options,
onSuccess: (...arg) => {
queryClient.invalidateQueries(quries.user.getMe());
queryClient.invalidateQueries(queries.user.getMe());
if (options?.onSuccess) {
options?.onSuccess(...arg);
}
Expand Down
4 changes: 2 additions & 2 deletions src/pageLayout/Epigram/EpigramComment.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Textarea } from '@/components/ui/textarea';
import Image from 'next/image';

function CommentSection() {
function EpigramComment() {
return (
<div className='bg-background-100 flex justify-center h-[500px]'>
<div className='w-80 md:w-96 lg:w-[640px] pt-6 lg:pt-12 pb-36'>
Expand All @@ -22,4 +22,4 @@ function CommentSection() {
);
}

export default CommentSection;
export default EpigramComment;
30 changes: 18 additions & 12 deletions src/pageLayout/Epigram/EpigramFigure.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,36 @@
import { GetEpigramResponseType } from '@/schema/epigram';
import Image from 'next/image';

function EpigramFigure() {
function EpigramFigure({ epigram }: { epigram: GetEpigramResponseType }) {
return (
<div className='bg-[length:100%_2.5em] bg-[linear-gradient(#eee_.1em,transparent_.1em)] w-full flex justify-center py-6'>
<figure className='w-80 md:w-96 lg:w-[640px] flex flex-col lg: gap-8'>
<div className='flex gap-2'>
<p className='text-gray-400 text-base lg:text-xl font-normal'>#꿈을 이루고싶을때</p>
<p className='text-gray-400 text-base lg:text-xl font-normal'>#나아가야할때</p>
{epigram.tags.map((tag) => (
<p key={tag.id} className='text-gray-400 text-base lg:text-xl font-normal'>
#{tag.name}
</p>
))}
</div>
<blockquote className=''>
<p className='text-2xl lg:text-3xl font-normal'>오랫동안 꿈을 그리는 사람은 마침내 그 꿈을 닮아 간다.</p>
<p className='text-2xl lg:text-3xl font-normal'>{epigram.content}</p>
</blockquote>
<figcaption className='text-gray-400 text-right text-base lg:text-2xl font-normal'>-앙드레 말로-</figcaption>
<figcaption className='text-gray-400 text-right text-base lg:text-2xl font-normal'>-{epigram.author}-</figcaption>
<div className='flex justify-center gap-4'>
<button type='button'>
<div className='w-20 lg:w-28 h-9 lg:h-11 flex items-center justify-center text-white rounded-full bg-black'>
<Image src='/likeIcon.svg' alt='좋아요 아이콘' width={20} height={20} className='lg:w-9 lg:h-9' />
<p className='text-sm lg:text-xl'>123</p>
</div>
</button>
<button type='button'>
<div className='w-32 lg:w-44 h-9 lg:h-11 flex items-center justify-center rounded-full bg-line-100'>
<p className='text-gray-300 text-sm lg:text-xl'>왕도로 가는길</p>
<Image src='/placeLink.svg' alt='지도링크' width={20} height={20} className='lg:w-9 lg:h-9' />
<p className='text-sm lg:text-xl'>{epigram.likeCount}</p>
</div>
</button>
{epigram.referenceTitle && (
<button type='button'>
<div className='w-32 lg:w-44 h-9 lg:h-11 flex items-center justify-center rounded-full bg-line-100'>
<p className='text-gray-300 text-sm lg:text-xl'>{epigram.referenceTitle}</p>
<Image src='/placeLink.svg' alt='지도링크' width={20} height={20} className='lg:w-9 lg:h-9' />
</div>
</button>
)}
</div>
</figure>
</div>
Expand Down
19 changes: 0 additions & 19 deletions src/pageLayout/Epigram/EpigramLayout.tsx

This file was deleted.

31 changes: 29 additions & 2 deletions src/pages/epigram/[id].tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,34 @@
import EpigramLayout from '@/pageLayout/Epigram/EpigramLayout';
import { GetEpigramRequestSchema } from '@/schema/epigram';
import useEpigramQuery from '@/hooks/epigramQueryHook';
import CommentSection from '@/pageLayout/Epigram/EpigramComment';
import EpigramFigure from '@/pageLayout/Epigram/EpigramFigure';
import Image from 'next/image';
import { useRouter } from 'next/router';

function DetailPage() {
return <EpigramLayout />;
const router = useRouter();
const { id } = router.query;

const parsedId = GetEpigramRequestSchema.safeParse({ id });

const { data: epigram, isLoading, error } = useEpigramQuery(parsedId.success ? parsedId.data : undefined, parsedId.success);

if (isLoading) return <div>로딩 중...</div>;
if (!parsedId.success) return <div>잘못된 Epigram ID입니다.</div>;
if (error) return <div>에러 발생!! {(error as Error).message}</div>;
if (!epigram) return <div>Epigram을 찾을 수 없습니다.</div>;

return (
<div className='flex flex-col '>
<nav className='flex justify-between border-b-2 px-6 py-4'>
<Image src='/arrow-left.svg' alt='뒤로가기 버튼' width={36} height={36} />
<Image src='/logo.svg' alt='Epigram 로고' width={172} height={48} />
<Image src='/share.svg' alt='공유 버튼' width={36} height={36} />
</nav>
<EpigramFigure epigram={epigram} />
<CommentSection />
</div>
);
}

export default DetailPage;
32 changes: 32 additions & 0 deletions src/schema/epigram.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { z } from 'zod';

// Tag 스키마
const TagSchema = z.object({
name: z.string().min(1).max(10),
id: z.number().int().positive(),
});

// GetEpigramResponseType 스키마
const GetEpigramResponseSchema = z.object({
id: z.number().int().positive(),
content: z.string().min(1).max(500),
author: z.string().min(1).max(30),
referenceTitle: z.string().max(100).nullable().optional(),
referenceUrl: z.string().url().nullable().optional(),
writerId: z.number().int().positive(),
tags: z.array(TagSchema),
likeCount: z.number(),
isLiked: z.boolean().optional(),
});

// GetEpigramRequestType 스키마
const GetEpigramRequestSchema = z.object({
id: z.union([z.string(), z.number(), z.undefined()]),
});

// 타입 추론
export type Tag = z.infer<typeof TagSchema>;
export type GetEpigramResponseType = z.infer<typeof GetEpigramResponseSchema>;
export type GetEpigramRequestType = z.infer<typeof GetEpigramRequestSchema>;

export { TagSchema, GetEpigramResponseSchema, GetEpigramRequestSchema };
4 changes: 2 additions & 2 deletions src/schema/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const GetUserRequest = z.object({
id: z.number(),
});

export const GetUserReponse = z.object({
export const GetUserResponse = z.object({
image: z.string(),
updatedAt: z.coerce.date(),
createdAt: z.coerce.date(),
Expand All @@ -18,6 +18,6 @@ export const GetUserReponse = z.object({
id: z.number(),
});

export type GetUserReponseType = z.infer<typeof GetUserReponse>;
export type GetUserResponseType = z.infer<typeof GetUserResponse>;
export type GetUserRequestType = z.infer<typeof GetUserRequest>;
export type PatchMeRequestType = z.infer<typeof PatchMeRequest>;

0 comments on commit 2c7f789

Please sign in to comment.