diff --git a/src/features/preOpinions/components/PreOpinionDetail.tsx b/src/features/preOpinions/components/PreOpinionDetail.tsx
new file mode 100644
index 0000000..2339bab
--- /dev/null
+++ b/src/features/preOpinions/components/PreOpinionDetail.tsx
@@ -0,0 +1,140 @@
+import { useAuth } from '@/features/auth'
+import { StarRate } from '@/shared/components/StarRate'
+import { Avatar, AvatarFallback, AvatarImage, Badge, TextButton } from '@/shared/ui'
+import { Chip } from '@/shared/ui/Chip'
+import { useGlobalModalStore } from '@/store'
+
+import { useDeleteMyPreOpinionAnswer } from '../hooks/useDeleteMyPreOpinionAnswer'
+import { ROLE_TO_AVATAR_VARIANT } from '../preOpinions.constants'
+import type { PreOpinionMember, PreOpinionTopic } from '../preOpinions.types'
+
+type PreOpinionDetailProps = {
+ member: PreOpinionMember
+ topics: PreOpinionTopic[]
+ gatheringId: number
+ meetingId: number
+}
+
+/**
+ * 사전 의견 상세 (선택된 멤버의 책 평가 + 주제별 의견)
+ *
+ * @description
+ * 선택된 멤버의 책 평가(별점, 키워드)와 주제별 의견을 표시합니다.
+ *
+ * @example
+ * ```tsx
+ *
+ * ```
+ */
+function PreOpinionDetail({ member, topics, gatheringId, meetingId }: PreOpinionDetailProps) {
+ const { data: currentUser } = useAuth()
+ const { openConfirm, openError } = useGlobalModalStore()
+ const { bookReview, topicOpinions, memberInfo } = member
+ const isMyOpinion = currentUser?.userId === memberInfo?.userId
+ const deleteMutation = useDeleteMyPreOpinionAnswer({ gatheringId, meetingId })
+
+ const handleDelete = async () => {
+ const confirmed = await openConfirm(
+ '내 의견 삭제하기',
+ '내 의견을 삭제하면 다른 멤버들의 의견을 보는 권한도 함께 사라져요.\n삭제를 진행할까요?',
+ { confirmText: '삭제', variant: 'danger' }
+ )
+ if (!confirmed) return
+
+ deleteMutation.mutate(undefined, {
+ onError: (error) => openError('에러', error.userMessage),
+ })
+ }
+
+ return (
+
+ {/* 회원 정보 섹션 */}
+ {memberInfo && (
+
+
+
+
+ {memberInfo.nickname[0]}
+
+
{memberInfo.nickname} 님의 의견
+
+ {isMyOpinion &&
handleDelete()}>내 의견 삭제하기}
+
+ )}
+ {/* 책 평가 섹션 */}
+ {bookReview && (
+
+ {/* 별점 */}
+
+
별점
+
+
+ {bookReview.rating.toFixed(1)}
+
+
+
+ {/* 책 키워드 */}
+ {bookReview.keywordInfo.filter((k) => k.type === 'BOOK').length > 0 && (
+
+
책 키워드
+
+ {bookReview.keywordInfo
+ .filter((k) => k.type === 'BOOK')
+ .map((keyword) => (
+
+ {keyword.name}
+
+ ))}
+
+
+ )}
+
+ {/* 감상 키워드 */}
+ {bookReview.keywordInfo.filter((k) => k.type === 'IMPRESSION').length > 0 && (
+
+
감상 키워드
+
+ {bookReview.keywordInfo
+ .filter((k) => k.type === 'IMPRESSION')
+ .map((keyword) => (
+
+ {keyword.name}
+
+ ))}
+
+
+ )}
+
+ )}
+
+ {/* 주제별 의견 섹션 */}
+ {topicOpinions.length > 0 && (
+
+ {topics.map((topic) => {
+ const opinion = topicOpinions.find((o) => o.topicId === topic.topicId)
+ if (!opinion) return null
+
+ return (
+
+
+
+
+ 주제 {topic.confirmOrder}. {topic.title}
+
+ {topic.topicTypeLabel}
+
+
{topic.description}
+
+ {opinion.content && (
+
{opinion.content}
+ )}
+
+ )
+ })}
+
+ )}
+
+ )
+}
+
+export { PreOpinionDetail }
diff --git a/src/features/preOpinions/components/PreOpinionMemberList.tsx b/src/features/preOpinions/components/PreOpinionMemberList.tsx
new file mode 100644
index 0000000..26e843d
--- /dev/null
+++ b/src/features/preOpinions/components/PreOpinionMemberList.tsx
@@ -0,0 +1,54 @@
+import { UserChip } from '@/shared/ui/UserChip'
+
+import { ROLE_TO_AVATAR_VARIANT } from '../preOpinions.constants'
+import type { PreOpinionMember } from '../preOpinions.types'
+
+type PreOpinionMemberListProps = {
+ members: PreOpinionMember[]
+ selectedMemberId: number | null
+ onSelectMember: (memberId: number) => void
+}
+
+/**
+ * 사전 의견 멤버 리스트
+ *
+ * @description
+ * 사전 의견을 작성한/작성하지 않은 멤버들을 UserChip 형태로 표시합니다.
+ * 사전 의견을 제출하지 않은 멤버는 disabled 상태로 표시됩니다.
+ *
+ * @example
+ * ```tsx
+ * setSelectedMemberId(id)}
+ * />
+ * ```
+ */
+function PreOpinionMemberList({
+ members,
+ selectedMemberId,
+ onSelectMember,
+}: PreOpinionMemberListProps) {
+ return (
+
+ {members.map((member) => (
+ {
+ if (member.isSubmitted) {
+ onSelectMember(member.memberInfo.userId)
+ }
+ }}
+ />
+ ))}
+
+ )
+}
+
+export { PreOpinionMemberList }
diff --git a/src/features/preOpinions/components/index.ts b/src/features/preOpinions/components/index.ts
new file mode 100644
index 0000000..317611c
--- /dev/null
+++ b/src/features/preOpinions/components/index.ts
@@ -0,0 +1,2 @@
+export * from './PreOpinionDetail'
+export * from './PreOpinionMemberList'
diff --git a/src/features/preOpinions/hooks/index.ts b/src/features/preOpinions/hooks/index.ts
new file mode 100644
index 0000000..305c6e7
--- /dev/null
+++ b/src/features/preOpinions/hooks/index.ts
@@ -0,0 +1,3 @@
+export * from './preOpinionQueryKeys'
+export * from './useDeleteMyPreOpinionAnswer'
+export * from './usePreOpinionAnswers'
diff --git a/src/features/preOpinions/hooks/preOpinionQueryKeys.ts b/src/features/preOpinions/hooks/preOpinionQueryKeys.ts
new file mode 100644
index 0000000..d4d25d6
--- /dev/null
+++ b/src/features/preOpinions/hooks/preOpinionQueryKeys.ts
@@ -0,0 +1,21 @@
+/**
+ * @file preOpinionQueryKeys.ts
+ * @description 사전 의견 관련 Query Key Factory
+ */
+
+import type { GetPreOpinionAnswersParams } from '../preOpinions.types'
+
+/**
+ * Query Key Factory
+ *
+ * @description
+ * 사전 의견 관련 Query Key를 일관되게 관리하기 위한 팩토리 함수
+ */
+export const preOpinionQueryKeys = {
+ all: ['preOpinions'] as const,
+
+ // 사전 의견 목록 관련
+ answers: () => [...preOpinionQueryKeys.all, 'answers'] as const,
+ answerList: (params: GetPreOpinionAnswersParams) =>
+ [...preOpinionQueryKeys.answers(), params] as const,
+}
diff --git a/src/features/preOpinions/hooks/useDeleteMyPreOpinionAnswer.ts b/src/features/preOpinions/hooks/useDeleteMyPreOpinionAnswer.ts
new file mode 100644
index 0000000..555376d
--- /dev/null
+++ b/src/features/preOpinions/hooks/useDeleteMyPreOpinionAnswer.ts
@@ -0,0 +1,32 @@
+/**
+ * @file useDeleteMyPreOpinionAnswer.ts
+ * @description 내 사전 의견 삭제 뮤테이션 훅
+ */
+
+import { useMutation, useQueryClient } from '@tanstack/react-query'
+
+import type { ApiError } from '@/api'
+
+import { deleteMyPreOpinionAnswer } from '../preOpinions.api'
+import type { DeleteMyPreOpinionAnswerParams } from '../preOpinions.types'
+import { preOpinionQueryKeys } from './preOpinionQueryKeys'
+
+/**
+ * 내 사전 의견을 삭제하는 뮤테이션 훅
+ *
+ * @example
+ * ```tsx
+ * const deleteMutation = useDeleteMyPreOpinionAnswer({ gatheringId, meetingId })
+ * deleteMutation.mutate()
+ * ```
+ */
+export function useDeleteMyPreOpinionAnswer(params: DeleteMyPreOpinionAnswerParams) {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: () => deleteMyPreOpinionAnswer(params),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: preOpinionQueryKeys.answers() })
+ },
+ })
+}
diff --git a/src/features/preOpinions/hooks/usePreOpinionAnswers.ts b/src/features/preOpinions/hooks/usePreOpinionAnswers.ts
new file mode 100644
index 0000000..2c0860b
--- /dev/null
+++ b/src/features/preOpinions/hooks/usePreOpinionAnswers.ts
@@ -0,0 +1,38 @@
+/**
+ * @file usePreOpinionAnswers.ts
+ * @description 사전 의견 목록 조회 훅
+ */
+
+import { useQuery } from '@tanstack/react-query'
+
+import type { ApiError } from '@/api'
+
+import { getPreOpinionAnswers } from '../preOpinions.api'
+import type { GetPreOpinionAnswersParams, PreOpinionAnswersData } from '../preOpinions.types'
+import { preOpinionQueryKeys } from './preOpinionQueryKeys'
+
+/**
+ * 사전 의견 목록 조회 훅
+ *
+ * @description
+ * TanStack Query를 사용하여 약속의 사전 의견 목록을 조회합니다.
+ * 멤버별 책 평가, 주제별 의견 등을 포함합니다.
+ *
+ * @param params - 조회 파라미터
+ * @param params.gatheringId - 모임 식별자
+ * @param params.meetingId - 약속 식별자
+ *
+ * @returns TanStack Query 결과 객체
+ */
+export const usePreOpinionAnswers = (params: GetPreOpinionAnswersParams) => {
+ const { gatheringId, meetingId } = params
+ const isValidParams =
+ !Number.isNaN(gatheringId) && gatheringId > 0 && !Number.isNaN(meetingId) && meetingId > 0
+
+ return useQuery({
+ queryKey: preOpinionQueryKeys.answerList({ gatheringId, meetingId }),
+ queryFn: () => getPreOpinionAnswers({ gatheringId, meetingId }),
+ enabled: isValidParams,
+ gcTime: 10 * 60 * 1000,
+ })
+}
diff --git a/src/features/preOpinions/index.ts b/src/features/preOpinions/index.ts
new file mode 100644
index 0000000..e804ad9
--- /dev/null
+++ b/src/features/preOpinions/index.ts
@@ -0,0 +1,13 @@
+// Components
+export * from './components'
+
+// Hooks
+export * from './hooks'
+
+// API
+export * from './preOpinions.api'
+export * from './preOpinions.endpoints'
+export * from './preOpinions.mock'
+
+// Types
+export * from './preOpinions.types'
diff --git a/src/features/preOpinions/preOpinions.api.ts b/src/features/preOpinions/preOpinions.api.ts
new file mode 100644
index 0000000..158a104
--- /dev/null
+++ b/src/features/preOpinions/preOpinions.api.ts
@@ -0,0 +1,59 @@
+/**
+ * @file preOpinions.api.ts
+ * @description 사전 의견 API 요청 함수
+ */
+
+import { api } from '@/api/client'
+
+import { PRE_OPINIONS_ENDPOINTS } from './preOpinions.endpoints'
+import { getMockPreOpinionAnswers } from './preOpinions.mock'
+import type {
+ DeleteMyPreOpinionAnswerParams,
+ GetPreOpinionAnswersParams,
+ PreOpinionAnswersData,
+} from './preOpinions.types'
+
+/** 목데이터 사용 여부 플래그 */
+const USE_MOCK = import.meta.env.VITE_USE_MOCK === 'true'
+
+/**
+ * 사전 의견 목록 조회
+ *
+ * @description
+ * 약속의 사전 의견 목록(멤버별 책 평가 + 주제 의견)을 조회합니다.
+ *
+ * @param params - 조회 파라미터
+ * @param params.gatheringId - 모임 식별자
+ * @param params.meetingId - 약속 식별자
+ *
+ * @returns 사전 의견 목록 데이터 (topics + members)
+ */
+export const getPreOpinionAnswers = async (
+ params: GetPreOpinionAnswersParams
+): Promise => {
+ const { gatheringId, meetingId } = params
+
+ if (USE_MOCK) {
+ await new Promise((resolve) => setTimeout(resolve, 500))
+ return getMockPreOpinionAnswers()
+ }
+
+ return api.get(PRE_OPINIONS_ENDPOINTS.ANSWERS(gatheringId, meetingId))
+}
+
+/**
+ * 내 사전 의견 삭제
+ *
+ * @description
+ * 현재 로그인한 사용자의 사전 의견을 삭제합니다.
+ *
+ * @param params - 삭제 파라미터
+ * @param params.gatheringId - 모임 식별자
+ * @param params.meetingId - 약속 식별자
+ */
+export const deleteMyPreOpinionAnswer = async (
+ params: DeleteMyPreOpinionAnswerParams
+): Promise => {
+ const { gatheringId, meetingId } = params
+ return api.delete(PRE_OPINIONS_ENDPOINTS.DELETE_MY_ANSWER(gatheringId, meetingId))
+}
diff --git a/src/features/preOpinions/preOpinions.constants.ts b/src/features/preOpinions/preOpinions.constants.ts
new file mode 100644
index 0000000..1671b60
--- /dev/null
+++ b/src/features/preOpinions/preOpinions.constants.ts
@@ -0,0 +1,8 @@
+import type { MemberRole } from './preOpinions.types'
+
+/** API MemberRole → Avatar variant 매핑 */
+export const ROLE_TO_AVATAR_VARIANT: Record = {
+ GATHERING_LEADER: 'leader',
+ MEETING_LEADER: 'host',
+ MEMBER: 'member',
+}
diff --git a/src/features/preOpinions/preOpinions.endpoints.ts b/src/features/preOpinions/preOpinions.endpoints.ts
new file mode 100644
index 0000000..d4a748b
--- /dev/null
+++ b/src/features/preOpinions/preOpinions.endpoints.ts
@@ -0,0 +1,11 @@
+import { API_PATHS } from '@/api'
+
+export const PRE_OPINIONS_ENDPOINTS = {
+ // 사전 의견 목록 조회 (GET /api/gatherings/{gatheringId}/meetings/{meetingId}/answers)
+ ANSWERS: (gatheringId: number, meetingId: number) =>
+ `${API_PATHS.GATHERINGS}/${gatheringId}/meetings/${meetingId}/answers`,
+
+ // 내 사전 의견 삭제 (DELETE /api/gatherings/{gatheringId}/meetings/{meetingId}/topics/answers/me)
+ DELETE_MY_ANSWER: (gatheringId: number, meetingId: number) =>
+ `${API_PATHS.GATHERINGS}/${gatheringId}/meetings/${meetingId}/topics/answers/me`,
+} as const
diff --git a/src/features/preOpinions/preOpinions.mock.ts b/src/features/preOpinions/preOpinions.mock.ts
new file mode 100644
index 0000000..3b7eef8
--- /dev/null
+++ b/src/features/preOpinions/preOpinions.mock.ts
@@ -0,0 +1,102 @@
+/**
+ * @file preOpinions.mock.ts
+ * @description 사전 의견 API 목데이터
+ */
+
+import type { PreOpinionAnswersData } from './preOpinions.types'
+
+/**
+ * 사전 의견 목록 목데이터
+ */
+const mockPreOpinionAnswers: PreOpinionAnswersData = {
+ topics: [
+ {
+ topicId: 1,
+ title: '책의 주요 메시지',
+ description: '이 책에서 전달하고자 하는 핵심 메시지는 무엇인가요?',
+ topicType: 'DISCUSSION',
+ topicTypeLabel: '토론형',
+ confirmOrder: 1,
+ },
+ {
+ topicId: 2,
+ title: '가장 인상 깊었던 장면',
+ description: '책을 읽으며 가장 기억에 남았던 장면은 무엇인가요?',
+ topicType: 'DISCUSSION',
+ topicTypeLabel: '토론형',
+ confirmOrder: 2,
+ },
+ ],
+ members: [
+ {
+ memberInfo: {
+ userId: 1,
+ nickname: '독서왕',
+ profileImage: 'https://picsum.photos/seed/user1/100/100',
+ role: 'GATHERING_LEADER',
+ },
+ isSubmitted: true,
+ bookReview: {
+ rating: 4.5,
+ keywordInfo: [
+ { id: 3, name: '성장', type: 'BOOK' },
+ { id: 7, name: '여운이 남는', type: 'IMPRESSION' },
+ ],
+ },
+ topicOpinions: [
+ {
+ topicId: 1,
+ content: '이 책의 핵심 메시지는 자기 성찰이라고 생각합니다.',
+ },
+ {
+ topicId: 2,
+ content: '주인공이 선택의 기로에 서는 장면이 가장 인상 깊었습니다.',
+ },
+ ],
+ },
+ {
+ memberInfo: {
+ userId: 10,
+ nickname: '밤독서',
+ profileImage: 'https://picsum.photos/seed/user3/100/100',
+ role: 'MEETING_LEADER',
+ },
+ isSubmitted: true,
+ bookReview: {
+ rating: 3.0,
+ keywordInfo: [
+ { id: 5, name: '관계', type: 'BOOK' },
+ { id: 7, name: '여운이 남는', type: 'IMPRESSION' },
+ ],
+ },
+ topicOpinions: [
+ {
+ topicId: 1,
+ content: null,
+ },
+ {
+ topicId: 2,
+ content: '잔잔하지만 오래 남는 장면들이 많았습니다.',
+ },
+ ],
+ },
+ {
+ memberInfo: {
+ userId: 2,
+ nickname: '페이지러버',
+ profileImage: 'https://picsum.photos/seed/user2/100/100',
+ role: 'MEMBER',
+ },
+ isSubmitted: false,
+ bookReview: null,
+ topicOpinions: [],
+ },
+ ],
+}
+
+/**
+ * 사전 의견 목록 목데이터 반환 함수
+ */
+export const getMockPreOpinionAnswers = (): PreOpinionAnswersData => {
+ return mockPreOpinionAnswers
+}
diff --git a/src/features/preOpinions/preOpinions.types.ts b/src/features/preOpinions/preOpinions.types.ts
new file mode 100644
index 0000000..8e5fbd2
--- /dev/null
+++ b/src/features/preOpinions/preOpinions.types.ts
@@ -0,0 +1,73 @@
+/**
+ * @file preOpinions.types.ts
+ * @description 사전 의견 API 관련 타입 정의
+ */
+
+import type { KeywordType } from '@/features/keywords/keywords.types'
+import type { TopicType } from '@/features/topics/topics.types'
+
+/** 멤버 역할 타입 */
+export type MemberRole = 'GATHERING_LEADER' | 'MEETING_LEADER' | 'MEMBER'
+
+/** 사전 의견 키워드 */
+export type PreOpinionKeyword = {
+ id: number
+ name: string
+ type: KeywordType
+}
+
+/** 책 평가 정보 */
+export type BookReviewSummary = {
+ rating: number
+ keywordInfo: PreOpinionKeyword[]
+}
+
+/** 멤버 정보 */
+export type PreOpinionMemberInfo = {
+ userId: number
+ nickname: string
+ profileImage: string
+ role: MemberRole
+}
+
+/** 주제별 의견 */
+export type TopicOpinion = {
+ topicId: number
+ content: string | null
+}
+
+/** 확정된 주제 항목 */
+export type PreOpinionTopic = {
+ topicId: number
+ title: string
+ description: string
+ topicType: TopicType
+ topicTypeLabel: string
+ confirmOrder: number
+}
+
+/** 멤버별 사전 의견 */
+export type PreOpinionMember = {
+ memberInfo: PreOpinionMemberInfo
+ isSubmitted: boolean
+ bookReview: BookReviewSummary | null
+ topicOpinions: TopicOpinion[]
+}
+
+/** 사전 의견 목록 조회 응답 데이터 */
+export type PreOpinionAnswersData = {
+ topics: PreOpinionTopic[]
+ members: PreOpinionMember[]
+}
+
+/** 사전 의견 목록 조회 파라미터 */
+export type GetPreOpinionAnswersParams = {
+ gatheringId: number
+ meetingId: number
+}
+
+/** 내 사전 의견 삭제 파라미터 */
+export type DeleteMyPreOpinionAnswerParams = {
+ gatheringId: number
+ meetingId: number
+}
diff --git a/src/pages/PreOpinions/PreOpinionListPage.tsx b/src/pages/PreOpinions/PreOpinionListPage.tsx
new file mode 100644
index 0000000..9095786
--- /dev/null
+++ b/src/pages/PreOpinions/PreOpinionListPage.tsx
@@ -0,0 +1,88 @@
+import { useEffect, useMemo, useRef, useState } from 'react'
+import { useNavigate, useParams } from 'react-router-dom'
+
+import {
+ PreOpinionDetail,
+ PreOpinionMemberList,
+ usePreOpinionAnswers,
+} from '@/features/preOpinions'
+import SubPageHeader from '@/shared/components/SubPageHeader'
+import { useGlobalModalStore } from '@/store'
+
+export default function PreOpinionListPage() {
+ const { gatheringId, meetingId } = useParams<{ gatheringId: string; meetingId: string }>()
+ const navigate = useNavigate()
+ const openError = useGlobalModalStore((state) => state.openError)
+ const sentinelRef = useRef(null)
+ const [isStuck, setIsStuck] = useState(false)
+ const [selectedMemberId, setSelectedMemberId] = useState(null)
+
+ const { data, isLoading, error } = usePreOpinionAnswers({
+ gatheringId: Number(gatheringId),
+ meetingId: Number(meetingId),
+ })
+
+ useEffect(() => {
+ if (error) {
+ openError('조회 불가', error.userMessage, () => navigate(-1))
+ }
+ }, [error, openError, navigate])
+
+ useEffect(() => {
+ const sentinel = sentinelRef.current
+ if (!sentinel) return
+
+ const observer = new IntersectionObserver(
+ ([entry]) => {
+ setIsStuck(!entry.isIntersecting)
+ },
+ { threshold: 0 }
+ )
+
+ observer.observe(sentinel)
+ return () => observer.disconnect()
+ }, [isLoading])
+
+ // 선택된 멤버 ID: 유저가 선택한 값이 있으면 사용, 없으면 첫 번째 제출한 멤버를 기본값으로
+ const activeMemberId = useMemo(() => {
+ if (selectedMemberId !== null) return selectedMemberId
+ const firstSubmitted = data?.members.find((m) => m.isSubmitted)
+ return firstSubmitted?.memberInfo.userId ?? null
+ }, [selectedMemberId, data])
+
+ const selectedMember = data?.members.find((m) => m.memberInfo.userId === activeMemberId)
+
+ if (isLoading) return 로딩중...
+
+ return (
+ <>
+
+
+ 사전 의견
+
+ {/* 왼쪽: 멤버 리스트 */}
+ {data && (
+
+ )}
+
+ {/* 오른쪽: 선택된 멤버의 의견 상세 */}
+ {selectedMember && data ? (
+
+ ) : (
+
+ )}
+
+ >
+ )
+}
diff --git a/src/pages/PreOpinions/index.ts b/src/pages/PreOpinions/index.ts
new file mode 100644
index 0000000..53bc8ef
--- /dev/null
+++ b/src/pages/PreOpinions/index.ts
@@ -0,0 +1 @@
+export { default as PreOpinionListPage } from './PreOpinionListPage'
diff --git a/src/pages/index.ts b/src/pages/index.ts
index a3942e4..7b02f30 100644
--- a/src/pages/index.ts
+++ b/src/pages/index.ts
@@ -4,4 +4,5 @@ export * from './ComponentGuide'
export * from './Gatherings'
export * from './Home'
export * from './Meetings'
+export * from './PreOpinions'
export * from './Records'
diff --git a/src/routes/index.tsx b/src/routes/index.tsx
index 8d08899..b4a4293 100644
--- a/src/routes/index.tsx
+++ b/src/routes/index.tsx
@@ -15,6 +15,7 @@ import {
MeetingDetailPage,
MeetingSettingPage,
OnboardingPage,
+ PreOpinionListPage,
RecordListPage,
} from '@/pages'
import { ROUTES } from '@/shared/constants'
@@ -100,6 +101,10 @@ export const router = createBrowserRouter([
path: `${ROUTES.GATHERINGS}/:gatheringId/meetings/:meetingId`,
element: ,
},
+ {
+ path: ROUTES.PRE_OPINIONS(':gatheringId', ':meetingId'),
+ element: ,
+ },
{
path: `${ROUTES.GATHERINGS}/:gatheringId/meetings/setting`,
element: ,
diff --git a/src/shared/components/SubPageHeader.tsx b/src/shared/components/SubPageHeader.tsx
index 369a015..3f292b2 100644
--- a/src/shared/components/SubPageHeader.tsx
+++ b/src/shared/components/SubPageHeader.tsx
@@ -1,6 +1,7 @@
import { ChevronLeft } from 'lucide-react'
import { useNavigate } from 'react-router-dom'
+import { cn } from '@/shared/lib/utils'
import { TextButton } from '@/shared/ui/TextButton'
export interface SubPageHeaderProps {
@@ -8,6 +9,8 @@ export interface SubPageHeaderProps {
label?: string
/** 이동할 경로. 지정하지 않으면 navigate(-1)로 뒤로가기 */
to?: string
+ /** 외부에서 전달하는 추가 클래스 */
+ className?: string
}
/**
@@ -24,7 +27,7 @@ export interface SubPageHeaderProps {
*
* ```
*/
-export default function SubPageHeader({ label = '뒤로가기', to }: SubPageHeaderProps) {
+export default function SubPageHeader({ label = '뒤로가기', to, className }: SubPageHeaderProps) {
const navigate = useNavigate()
const handleClick = () => {
@@ -38,7 +41,7 @@ export default function SubPageHeader({ label = '뒤로가기', to }: SubPageHea
return (