Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
983f3d3
feat(voteboard): 투표 글 폼 스키마 및 뮤테이션 훅 추가
youdaeng2 Nov 22, 2025
4089ffa
feat(voteboard): 투표 글 작성/수정 폼 UI 구현
youdaeng2 Nov 22, 2025
405be7e
feat(voteboard): 투표 글 작성/수정 페이지 및 스켈레톤 추가
youdaeng2 Nov 22, 2025
75c0b62
Merge branch 'dev' into feat/vote/create-page
youdaeng2 Nov 26, 2025
379bcb9
feat: api 변경사항 반영
youdaeng2 Nov 26, 2025
900a193
feat(voteboard): api 카테고리 추가 반영 및 vote를 voteboard로 변경
youdaeng2 Nov 26, 2025
f3c3c18
feat(voteboard): 이미지 업로드, 수정 로직 추가
youdaeng2 Nov 26, 2025
6021b81
feat(voteboard): 카테고리 셀렉터 추가 및 라우트 경로 폴더에 맞춰 수정
youdaeng2 Nov 26, 2025
c8c7d42
fix: voteboard 생성 payload 구조 및 마감 기한 계산 로직을 백엔드 스펙에 맞게 변경
youdaeng2 Nov 26, 2025
0a0dfc0
feat: 투표 폼 수정모드에서 옵션 수정 제한 추가
youdaeng2 Nov 28, 2025
df50e26
feat: 투표 폼 스켈레톤에 카테고리 영역 추가
youdaeng2 Nov 28, 2025
4c3448f
fix: 투표 옵션 필드 placeholder 변경 (찬성 반대는 기본값으로 상위 컴포넌트에서 제공)
youdaeng2 Nov 28, 2025
c528a2c
chore: 투표 폼 주석 업데이트
youdaeng2 Nov 29, 2025
ade55fe
Merge branch 'dev' into feat/vote/create-page
youdaeng2 Nov 29, 2025
b425c6f
Merge branch 'dev' into feat/vote/create-page
youdaeng2 Dec 5, 2025
e83f8e1
fix: 투표 수정페이지 데이터가 없을 경우 404처리 로직 추가
youdaeng2 Dec 5, 2025
4357654
fix: 투표 마감기한 셀렉터에 2주 후 마감 아이템 추가
youdaeng2 Dec 5, 2025
26bf98d
fix: useVoteboardMutation 훅에서 orval 쿼리키만 사용하도록 수정
youdaeng2 Dec 5, 2025
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
@@ -0,0 +1,52 @@
// apps/web/src/app/main/community/votesboard/[votesboardId]/edit/page.tsx
'use client';

import { useParams, notFound } from 'next/navigation';
import { Header } from '@/components/header/Header';
import { useGetVotePost } from '@/generated/api/endpoints/voteboard/voteboard';
import { VoteboardFormSkeleton } from '../../components/VoteBoardForm.Skeleton';
import { VoteboardForm } from '../../components/VoteboardForm';

/**
* 투표 글 수정 페이지
*
* @description
* 기존 투표 게시글을 수정하는 페이지입니다.
* URL 파라미터에서 투표 게시글 ID를 받아 상세 데이터를 로딩하고,
* VoteboardForm 컴포넌트를 통해 수정 UI를 제공합니다.
*
* @remarks
* - 게시글 데이터 로딩 중에는 VoteboardFormSkeleton을 표시합니다.
* - 로딩 완료 후 VoteboardForm에 voteId와 initialData를 전달합니다.
*/

export default function VoteboardEditPage() {
const params = useParams();
const voteId = Number(params.votesboardId);

// 투표 게시글 상세 데이터 조회
const { data, isLoading, error } = useGetVotePost(voteId);

if (!isLoading && (!data || error)) {
notFound();
}

return (
<div className="flex flex-col w-full h-full">
<Header>
<Header.Left>
<Header.CancelButton />
</Header.Left>
<Header.Center>투표 글 수정</Header.Center>
</Header>

<main className="flex-1 w-full overflow-hidden p-layout">
{isLoading ? (
<VoteboardFormSkeleton />
) : (
<VoteboardForm voteboardId={voteId} initialData={data} />
)}
</main>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/**
* VoteboardForm 로딩 스켈레톤
*
* @description
* VoteboardForm과 최대한 동일한 레이아웃의 로딩 스켈레톤입니다.
* 각 필드의 정확한 높이, 간격, 스타일을 반영하여 깜빡임(CLS)을 최소화합니다.
*
* @remarks
* - 다크모드 지원
* - 접근성 속성 포함 (role, aria-label)
* - 반응형 레이아웃
*/
export function VoteboardFormSkeleton() {
return (
<div
className="relative flex flex-col h-full w-full"
role="status"
aria-label="투표 폼을 불러오는 중..."
>
{/* Form 영역 스켈레톤 */}
<div className="flex flex-col gap-4 w-full flex-1 overflow-auto p-1 pb-16">
{/* 카테고리 필드 스켈레톤 */}
<div className="animate-pulse">
{/* Label 스켈레톤 */}
<div className="mb-2 flex items-center gap-1">
<div className="h-4 w-20 bg-gray-200 dark:bg-neutral-700 rounded" />
<div className="h-3 w-3 bg-red-200 dark:bg-red-900 rounded" />
</div>
{/* Select Trigger 스켈레톤 */}
<div className="h-10 w-full bg-gray-200 dark:bg-neutral-700 rounded-lg" />
</div>

{/* 제목 Input 스켈레톤 */}
<div className="animate-pulse">
{/* Label 스켈레톤 */}
<div className="mb-2 flex items-center gap-1">
<div className="h-4 w-12 bg-gray-200 dark:bg-neutral-700 rounded" />
<div className="h-3 w-3 bg-red-200 dark:bg-red-900 rounded" />
</div>
{/* Input 필드 스켈레톤 */}
<div className="h-11 w-full bg-gray-200 dark:bg-neutral-700 rounded-lg" />
{/* Message 영역 (공간 확보) */}
<div className="mt-1 min-h-[1.25rem]" />
</div>

{/* 내용 TextArea 스켈레톤 */}
<div className="animate-pulse">
{/* Label 스켈레톤 */}
<div className="mb-2 flex items-center gap-1">
<div className="h-4 w-12 bg-gray-200 dark:bg-neutral-700 rounded" />
<div className="h-3 w-3 bg-red-200 dark:bg-red-900 rounded" />
</div>
{/* TextArea 필드 스켈레톤 (rows={6} 근사) */}
<div className="relative">
<div className="h-28 w-full bg-gray-200 dark:bg-neutral-700 rounded-lg" />
{/* 글자 수 표시 위치 스켈레톤 (있다면 이 위치에 올 것) */}
<div className="absolute bottom-4 right-4 h-3 w-12 bg-gray-300 dark:bg-neutral-600 rounded" />
</div>
{/* Message 영역 (공간 확보) */}
<div className="mt-1 min-h-[1.25rem]" />
</div>

{/* 마감 기간 필드 스켈레톤 */}
<div className="animate-pulse">
{/* Label 스켈레톤 */}
<div className="mb-2 flex items-center gap-1">
<div className="h-4 w-16 bg-gray-200 dark:bg-neutral-700 rounded" />
<div className="h-3 w-3 bg-red-200 dark:bg-red-900 rounded" />
</div>
{/* duration Select Trigger 스켈레톤 */}
<div className="h-10 w-full bg-gray-200 dark:bg-neutral-700 rounded-lg" />
{/* Message 영역 */}
<div className="mt-1 min-h-[1.25rem]" />
</div>

{/* 투표 옵션 스켈레톤 */}
<div className="animate-pulse">
{/* 헤더 (라벨 + 옵션 추가 버튼 자리) */}
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-1">
<div className="h-4 w-16 bg-gray-200 dark:bg-neutral-700 rounded" />
<div className="h-3 w-3 bg-red-200 dark:bg-red-900 rounded" />
</div>
<div className="h-3 w-12 bg-gray-200 dark:bg-neutral-700 rounded" />
</div>
{/* 옵션 입력 필드 2~3개 정도 */}
<div className="flex flex-col gap-2">
<div className="h-10 w-full bg-gray-200 dark:bg-neutral-700 rounded-lg" />
<div className="h-10 w-full bg-gray-200 dark:bg-neutral-700 rounded-lg" />
<div className="h-10 w-3/4 bg-gray-200 dark:bg-neutral-700 rounded-lg" />
</div>
{/* 전체 에러 메시지 영역 */}
<div className="mt-1 min-h-[1.25rem]" />
</div>

{/* 설정 (복수 선택 / 재투표) 스켈레톤 */}
<div className="animate-pulse flex flex-col gap-2 text-sm">
<div className="flex items-center gap-2">
<div className="w-4 h-4 bg-gray-200 dark:bg-neutral-700 rounded" />
<div className="h-4 w-24 bg-gray-200 dark:bg-neutral-700 rounded" />
</div>
<div className="flex items-center gap-2">
<div className="w-4 h-4 bg-gray-200 dark:bg-neutral-700 rounded" />
<div className="h-4 w-24 bg-gray-200 dark:bg-neutral-700 rounded" />
</div>
</div>

{/* 이미지 업로더 스켈레톤 */}
<div className="animate-pulse">
{/* Label 스켈레톤 */}
<div className="mb-2">
<div className="h-4 w-28 bg-gray-200 dark:bg-neutral-700 rounded" />
</div>
{/* ImageUploader 영역 스켈레톤 */}
<div className="flex gap-2 p-2 pl-0">
{/* 추가 버튼 스켈레톤 */}
<div className="flex-shrink-0 w-20 h-20 bg-gray-200 dark:bg-neutral-700 rounded-[10px]" />
{/* 썸네일 자리 1~2개 정도 */}
<div className="flex-shrink-0 w-20 h-20 bg-gray-100 dark:bg-neutral-800 rounded-[10px]" />
<div className="flex-shrink-0 w-20 h-20 bg-gray-100 dark:bg-neutral-800 rounded-[10px]" />
</div>
</div>
</div>

{/* 제출 버튼 스켈레톤 */}
<div className="absolute bottom-0 w-full animate-pulse">
<div className="h-12 w-full bg-gray-300 dark:bg-neutral-600 rounded-lg" />
</div>

{/* 스크린 리더용 안내 */}
<span className="sr-only">
투표 게시글 데이터를 불러오는 중입니다. 잠시만 기다려주세요.
</span>
</div>
);
}
Loading