Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export function PostDetailSection({ postId }: { postId: string }) {
onConfirm: () => {},
onCancel: () => {},
});
router.push("/albatalk");
},
});
},
Expand Down Expand Up @@ -82,12 +83,48 @@ export function PostDetailSection({ postId }: { postId: string }) {
</div>

{/* Author Info */}
<div className="mb-4 flex items-center gap-2">
<Image src="/icons/user/user-profile-sm.svg" alt="User Icon" className="rounded-full" width={32} height={32} />
<div className="mb-4 flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="font-medium text-black-400">{post?.writer.nickname}</span>
<span className="text-grayscale-400">|</span>
<span className="text-sm text-grayscale-400">{formatLocalDate(post?.createdAt || new Date())}</span>
<Image
src="/icons/user/user-profile-sm.svg"
alt="User Icon"
className="rounded-full"
width={32}
height={32}
/>
<div className="flex items-center gap-2">
<span className="font-medium text-black-400">{post?.writer.nickname}</span>
<span className="text-grayscale-400">|</span>
<span className="text-sm text-grayscale-400">{formatLocalDate(post?.createdAt || new Date())}</span>
</div>
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-1">
<Image
src={`/icons/comment/${isDesktop ? "comment-md.svg" : "comment-sm.svg"}`}
alt="Comment Icon"
width={24}
height={24}
/>
<span className="text-sm text-grayscale-500">{post?.commentCount}</span>
</div>
<div className="flex cursor-pointer items-center gap-1" onClick={handleLikeClick}>
<Image
src={`/icons/like/${
post?.isLiked
? isDesktop
? "like-md-active.svg"
: "like-sm-active.svg"
: isDesktop
? "like-md.svg"
: "like-sm.svg"
}`}
alt="Like Icon"
width={24}
height={24}
/>
<span className="text-sm text-grayscale-500">{post?.likeCount}</span>
</div>
</div>
</div>

Expand All @@ -111,36 +148,6 @@ export function PostDetailSection({ postId }: { postId: string }) {
</div>
) : null}
</div>

{/* Footer */}
<div className="flex items-center gap-4">
<div className="flex items-center gap-1">
<Image
src={`/icons/comment/${isDesktop ? "comment-md.svg" : "comment-sm.svg"}`}
alt="Comment Icon"
width={24}
height={24}
/>
<span className="text-sm text-grayscale-500">{post?.commentCount}</span>
</div>
<div className="flex cursor-pointer items-center gap-1" onClick={handleLikeClick}>
<Image
src={`/icons/like/${
post?.isLiked
? isDesktop
? "like-md-active.svg"
: "like-sm-active.svg"
: isDesktop
? "like-md.svg"
: "like-sm.svg"
}`}
alt="Like Icon"
width={24}
height={24}
/>
<span className="text-sm text-grayscale-500">{post?.likeCount}</span>
</div>
</div>
</section>
);
}
19 changes: 10 additions & 9 deletions src/app/api/posts/[postId]/like/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import apiClient from "@/lib/apiClient";
// 게시글 좋아요 API
export async function POST(request: Request, { params }: { params: { postId: string } }) {
try {
// 쿠키에서 액세스 토큰 가져오기
const accessToken = cookies().get("accessToken")?.value;

if (!accessToken) {
Expand All @@ -15,12 +14,16 @@ export async function POST(request: Request, { params }: { params: { postId: str

const postId = params.postId;

// 게시글 좋아요 요청
const response = await apiClient.post(`/posts/${postId}/like`, null, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
// body를 빈 객체로 전송
const response = await apiClient.post(
`/posts/${postId}/like`,
{},
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
}
);

return NextResponse.json(response.data);
} catch (error: unknown) {
Expand All @@ -37,7 +40,6 @@ export async function POST(request: Request, { params }: { params: { postId: str
// 게시글 좋아요 취소 API
export async function DELETE(request: Request, { params }: { params: { postId: string } }) {
try {
// 쿠키에서 액세스 토큰 가져오기
const accessToken = cookies().get("accessToken")?.value;

if (!accessToken) {
Expand All @@ -46,7 +48,6 @@ export async function DELETE(request: Request, { params }: { params: { postId: s

const postId = params.postId;

// 게시글 좋아요 취소 요청
const response = await apiClient.delete(`/posts/${postId}/like`, {
headers: {
Authorization: `Bearer ${accessToken}`,
Expand Down
14 changes: 12 additions & 2 deletions src/app/api/posts/[postId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,20 @@ import apiClient from "@/lib/apiClient";
export async function GET(request: Request, { params }: { params: { postId: string } }) {
try {
const postId = params.postId;
const accessToken = cookies().get("accessToken")?.value;

// 게시글 상세 조회 요청
const response = await apiClient.get(`/posts/${postId}`);
// 로그인한 유저의 경우 토큰과 함께 요청
if (accessToken) {
const response = await apiClient.get(`/posts/${postId}`, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
return NextResponse.json(response.data);
}

// 로그인하지 않은 유저의 경우 토큰 없이 요청
const response = await apiClient.get(`/posts/${postId}`);
return NextResponse.json(response.data);
} catch (error: unknown) {
if (error instanceof AxiosError) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ const ImageInputPlaceHolder: React.FC<ImageInputPlaceHolderProps> = ({
const fileInputRef = useRef<HTMLInputElement>(null);

useEffect(() => {
setImageList(initialImages);
if (JSON.stringify(imageList) !== JSON.stringify(initialImages)) {
setImageList(initialImages);
}
}, [initialImages]);

const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
Expand Down
46 changes: 30 additions & 16 deletions src/app/components/pagination/Pagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,41 +61,55 @@ const Pagination = ({ totalPage, currentPage, onPageChange }: PaginationProps):

return (
<nav className="flex gap-1" aria-label="페이지네이션">
<button onClick={handleClickPrevBtn} disabled={prevDisabled} aria-label="이전 페이지">
<PaginationBtn extraStyle="mr-[6px]" disabled={prevDisabled}>
<IoIosArrowBack className={cn(prevDisabled ? defaultStyle : activeStyle)} />
</PaginationBtn>
</button>
<PaginationBtn
onClick={handleClickPrevBtn}
disabled={prevDisabled}
extraStyle="mr-[6px]"
aria-label="이전 페이지"
>
<IoIosArrowBack className={cn(prevDisabled ? defaultStyle : activeStyle)} />
</PaginationBtn>

<ul className="flex gap-1">
{pageList.map((page) => (
<li key={page}>
<button
<PaginationBtn
onClick={() => handleChangePage(page)}
extraStyle={page === currentPage ? activeStyle : ""}
aria-label={`${page}페이지`}
aria-current={page === currentPage}
>
<PaginationBtn extraStyle={page === currentPage ? activeStyle : ""}>{page}</PaginationBtn>
</button>
{page}
</PaginationBtn>
</li>
))}
</ul>

{totalPage > maxPageShow + 2 && lastPage < totalPage - 1 && (
<>
<li>
<PaginationBtn>...</PaginationBtn>
</li>
<li>
<button onClick={() => handleChangePage(totalPage)} aria-label={`${totalPage}페이지`}>
<PaginationBtn extraStyle={totalPage === currentPage ? activeStyle : ""}>{totalPage}</PaginationBtn>
</button>
<PaginationBtn
onClick={() => handleChangePage(totalPage)}
extraStyle={totalPage === currentPage ? activeStyle : ""}
aria-label={`${totalPage}페이지`}
>
{totalPage}
</PaginationBtn>
</li>
</>
)}
<button onClick={handleClickNextBtn} disabled={nextDisabled} aria-label="다음 페이지">
<PaginationBtn extraStyle="ml-[6px]" disabled={nextDisabled}>
<IoIosArrowForward className={cn(nextDisabled ? defaultStyle : activeStyle)} />
</PaginationBtn>
</button>

<PaginationBtn
onClick={handleClickNextBtn}
disabled={nextDisabled}
extraStyle="ml-[6px]"
aria-label="다음 페이지"
>
<IoIosArrowForward className={cn(nextDisabled ? defaultStyle : activeStyle)} />
</PaginationBtn>
</nav>
);
};
Expand Down
33 changes: 21 additions & 12 deletions src/app/components/pagination/paginationComponent/PaginationBtn.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
import { cn } from "@/lib/tailwindUtil";

const PaginationBtn = ({
children,
extraStyle,
disabled,
}: {
interface PaginationBtnProps {
children: React.ReactNode;
extraStyle?: string;
onClick?: () => void;
disabled?: boolean;
}) => {
const wrapperStyle =
"size-[34px] lg:radius-lg flex items-center justify-center rounded-md lg:size-[48px] bg-background-200";
const textStyle = "leading-[24px] lg:text-lg text-sm lg:leading-[26px]";
const defaultStyle = "text-grayscale-200 font-medium lg:font-normal";
extraStyle?: string;
"aria-label"?: string;
"aria-current"?: boolean;
}

const PaginationBtn = ({
children,
onClick,
disabled,
extraStyle,
"aria-label": ariaLabel,
"aria-current": ariaCurrent,
}: PaginationBtnProps) => {
return (
<button type="button" disabled={disabled} className={cn(wrapperStyle, textStyle, defaultStyle, extraStyle)}>
<button
onClick={onClick}
disabled={disabled}
className={`flex h-8 w-8 items-center justify-center rounded-lg text-sm hover:bg-grayscale-100 disabled:cursor-not-allowed disabled:opacity-50 lg:h-10 lg:w-10 lg:text-base ${extraStyle}`}
aria-label={ariaLabel}
aria-current={ariaCurrent}
>
{children}
</button>
);
Expand Down
27 changes: 15 additions & 12 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,20 @@

/* 넥슨 폰트 */
@font-face {
font-family: "NEXON Lv1 Gothic OTF Light";
src: url("https://fastly.jsdelivr.net/gh/projectnoonnu/noonfonts_20-04@2.1/NEXON Lv1 Gothic OTF Light.woff")
format("woff");
font-weight: 300;
}
@font-face {
font-family: "NEXON Lv1 Gothic OTF Regular";
src: url("https://fastly.jsdelivr.net/gh/projectnoonnu/noonfonts_20-04@2.1/NEXON Lv1 Gothic OTF Regular.woff")
format("woff");
font-family: "NEXON Lv1 Gothic OTF";
src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_20-04@2.1/NEXON Lv1 Gothic OTF.woff") format("woff");
font-weight: 400;
font-style: normal;
font-display: swap;
}

@font-face {
font-family: "NEXON Lv1 Gothic OTF Bold";
src: url("https://fastly.jsdelivr.net/gh/projectnoonnu/noonfonts_20-04@2.1/NEXON Lv1 Gothic OTF Bold.woff")
font-family: "NEXON Lv1 Gothic OTF";
src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_20-04@2.1/NEXON Lv1 Gothic OTF Bold.woff")
format("woff");
font-weight: 700;
font-style: normal;
font-display: swap;
Comment on lines +21 to +34
Copy link
Collaborator

Choose a reason for hiding this comment

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

뭐때문에 적용이 안되었던 건가요 ?

}

/* 기본적인 스타일을 정의 */
Expand Down Expand Up @@ -114,7 +111,7 @@ input[type="radio"].radio-custom:checked {
box-shadow: 0 0 0 1.6px #fbaf37;
}

/* 체크박스 기본 스타일 */
/* 체크박스 스타일 */
input[type="checkbox"] {
appearance: none;
width: 16px;
Expand Down Expand Up @@ -330,3 +327,9 @@ input[type="checkbox"]:disabled {
.bg-black50 {
@apply !bg-[#141414] !bg-opacity-50;
}

@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard.css");

/* 또는 */

@import url("https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;700&display=swap");
6 changes: 4 additions & 2 deletions src/hooks/queries/post/useAddPost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { PostSchema } from "@/schemas/postSchema";
export const useAddPost = () => {
const queryClient = useQueryClient();

const mutation = useMutation({
const mutation = useMutation<PostDetailResponse, Error, PostSchema>({
mutationFn: async (data: PostSchema) => {
const response = await axios.post<PostDetailResponse>("/api/posts", data, {
headers: {
Expand All @@ -17,14 +17,15 @@ export const useAddPost = () => {
});
return response.data;
},
onSuccess: () => {
onSuccess: (data) => {
toast.success("게시글이 등록되었습니다!", {
style: {
textAlign: "center",
},
});
// 게시글 목록 캐시 무효화
queryClient.invalidateQueries({ queryKey: ["posts"] });
return data; // 응답 데이터 반환
},
onError: (error) => {
if (axios.isAxiosError(error)) {
Expand All @@ -41,6 +42,7 @@ export const useAddPost = () => {
},
});
}
throw error; // 에러를 다시 throw하여 컴포넌트에서 처리할 수 있도록 함
},
});

Expand Down
Loading
Loading