diff --git a/.env.example b/.env.example index 26b15ac4..a1e86cf2 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,4 @@ NEXT_PUBLIC_API_BASE_URL= NEXT_PUBLIC_KAKAO_CLIENT_ID= -NEXT_PUBLIC_GOOGLE_CLIENT_ID= \ No newline at end of file +NEXT_PUBLIC_GOOGLE_CLIENT_ID= +NEXT_PUBLIC_TOSS_CLIENT_KEY= \ No newline at end of file diff --git a/src/app/(service)/home/page.tsx b/src/app/(service)/home/page.tsx index f2f26da0..59934ad0 100644 --- a/src/app/(service)/home/page.tsx +++ b/src/app/(service)/home/page.tsx @@ -1,4 +1,5 @@ import { Metadata } from 'next'; +import PageContainer from '@/components/layout/page-container'; import StudyCard from '@/features/study/schedule/ui/study-card'; import { generateMetadata as generateSEOMetadata } from '@/utils/seo'; import Banner from '@/widgets/home/banner'; @@ -15,7 +16,7 @@ export const metadata: Metadata = generateSEOMetadata({ export default async function Home() { return ( -
+
@@ -24,6 +25,6 @@ export default async function Home() { -
+
); } diff --git a/src/app/(service)/insights/page.tsx b/src/app/(service)/insights/page.tsx index 682f9bf7..c725e109 100644 --- a/src/app/(service)/insights/page.tsx +++ b/src/app/(service)/insights/page.tsx @@ -6,6 +6,7 @@ import { fetchArticles, fetchCategories, } from '@/api/strapi/api/fetch-articles'; +import PageContainer from '@/components/layout/page-container'; import { generateMetadata as generateSEOMetadata } from '@/utils/seo'; import { getServerCookie } from '@/utils/server-cookie'; import Sidebar from '@/widgets/home/sidebar'; @@ -58,7 +59,7 @@ export default async function BlogPage({ searchParams }: BlogPageProps) { const articles = articlesRes.data ?? []; return ( -
+
@@ -144,6 +145,6 @@ export default async function BlogPage({ searchParams }: BlogPageProps) { )}
{isLoggedIn && } -
+
); } diff --git a/src/app/(service)/payment/complete/page.tsx b/src/app/(service)/payment/complete/page.tsx index 2d07c9ed..2a6def22 100644 --- a/src/app/(service)/payment/complete/page.tsx +++ b/src/app/(service)/payment/complete/page.tsx @@ -2,6 +2,7 @@ import Image from 'next/image'; import { useRouter } from 'next/navigation'; +import PageContainer from '@/components/layout/page-container'; import Button from '@/components/ui/button'; interface PaymentResult { @@ -26,8 +27,8 @@ export default function PaymentCompletePage() { const formatKRW = (n: number) => `${n.toLocaleString('ko-KR')}원`; return ( -
-
+ +
{/* 아이콘 */}
@@ -93,7 +94,7 @@ export default function PaymentCompletePage() {
-
+ ); } diff --git a/src/components/card/mission-card.tsx b/src/components/card/mission-card.tsx index a880ece8..17ebb103 100644 --- a/src/components/card/mission-card.tsx +++ b/src/components/card/mission-card.tsx @@ -123,24 +123,14 @@ export default function MissionCard({
  • - + ['color'] }; startDate?: string; endDate?: string; deadlineInfo: { text: string; isUrgent: boolean } | null; }) { + const displayTitle = weekNum ? `${weekNum}주차 미션 : ${title}` : title; + return (
    {deadlineInfo && ( @@ -243,7 +240,9 @@ function MissionCardContent({ )}
    - {title} + + {displayTitle} + {statusConfig.label}
    diff --git a/src/components/contents/payment-page-content.tsx b/src/components/contents/payment-page-content.tsx index 15abf023..359c32eb 100644 --- a/src/components/contents/payment-page-content.tsx +++ b/src/components/contents/payment-page-content.tsx @@ -26,11 +26,9 @@ export default function PaymentPageContent({ id }: PaymentPageContentProps) { const data = result.data; - console.log(data); - return (
    -
    +
    {/* 선택한 스터디 */}
    @@ -40,6 +38,9 @@ export default function PaymentPageContent({ id }: PaymentPageContentProps) { groupStudyTitle={data?.groupStudyTitle} amount={data.amount} description={data.groupStudyDescription} + thumbnailUrl={ + data?.groupStudyImage.resizedImages[0].resizedImageUrl + } />
    diff --git a/src/components/filtering/study-filter.tsx b/src/components/filtering/study-filter.tsx index 75165a41..94b6522c 100644 --- a/src/components/filtering/study-filter.tsx +++ b/src/components/filtering/study-filter.tsx @@ -82,10 +82,10 @@ function FilterDropdown({ + diff --git a/src/components/layout/header-nav.tsx b/src/components/layout/header-nav.tsx new file mode 100644 index 00000000..05a6a613 --- /dev/null +++ b/src/components/layout/header-nav.tsx @@ -0,0 +1,39 @@ +'use client'; + +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; +import { cn } from '@/components/ui/(shadcn)/lib/utils'; + +interface HeaderNavProps { + isLoggedIn: boolean; +} + +const NAV_ITEMS = [ + { href: '/home', loginRequired: true, label: '1:1 스터디' }, + { href: '/group-study', loginRequired: false, label: '그룹스터디' }, + { href: '/premium-study', loginRequired: false, label: '멘토스터디' }, + { href: '/insights', loginRequired: false, label: '인사이트' }, +]; + +export default function HeaderNav({ isLoggedIn }: HeaderNavProps) { + const pathname = usePathname(); + + return ( + + ); +} diff --git a/src/components/layout/page-container.tsx b/src/components/layout/page-container.tsx new file mode 100644 index 00000000..034ba0a2 --- /dev/null +++ b/src/components/layout/page-container.tsx @@ -0,0 +1,18 @@ +import { clsx } from 'clsx'; +import { ReactNode } from 'react'; + +interface PageContainerProps { + children: ReactNode; + className?: string; +} + +export default function PageContainer({ + children, + className, +}: PageContainerProps) { + return ( +
    + {children} +
    + ); +} diff --git a/src/components/modals/delete-mission-modal.tsx b/src/components/modals/delete-mission-modal.tsx index 82a3ddbe..50610add 100644 --- a/src/components/modals/delete-mission-modal.tsx +++ b/src/components/modals/delete-mission-modal.tsx @@ -18,6 +18,7 @@ export default function DeleteMissionModal({ onSuccess, }: DeleteMissionModalProps) { const [open, setOpen] = useState(false); + console.log('missionId', missionId); const { mutate: deleteMission } = useDeleteMission(); diff --git a/src/components/modals/edit-mission-modal.tsx b/src/components/modals/edit-mission-modal.tsx index 2d0a9d17..3b8090ae 100644 --- a/src/components/modals/edit-mission-modal.tsx +++ b/src/components/modals/edit-mission-modal.tsx @@ -1,6 +1,6 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { XIcon } from 'lucide-react'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { Controller, FormProvider, useForm } from 'react-hook-form'; import { z } from 'zod'; import Button from '@/components/ui/button'; @@ -8,7 +8,7 @@ import DatePicker from '@/components/ui/date-picker'; import FormField from '@/components/ui/form/form-field'; import { BaseInput, TextAreaInput } from '@/components/ui/input'; import { Modal } from '@/components/ui/modal'; -import { useUpdateMission } from '@/hooks/queries/mission-api'; +import { useGetMission, useUpdateMission } from '@/hooks/queries/mission-api'; // Form Schema const EditMissionFormSchema = z.object({ @@ -29,15 +29,12 @@ const EditMissionFormSchema = z.object({ type EditMissionFormValues = z.infer; interface EditMissionModalProps { - defaultValue: EditMissionFormValues; missionId: number; } -export default function EditMissionModal({ - defaultValue, - missionId, -}: EditMissionModalProps) { +export default function EditMissionModal({ missionId }: EditMissionModalProps) { const [open, setOpen] = useState(false); + const { data: missionData, isLoading } = useGetMission(missionId); return ( @@ -63,11 +60,17 @@ export default function EditMissionModal({ - setOpen(false)} - /> + {isLoading ? ( + + 로딩 중... + + ) : missionData ? ( + setOpen(false)} + /> + ) : null} @@ -75,22 +78,59 @@ export default function EditMissionModal({ } interface EditMissionFormProps { - defaultValue: EditMissionFormValues; + missionData: { + missionTitle?: string; + missionDescription?: string; + weekNum?: number; + missionGuide?: string; + missionStartDate?: string; + missionEndDate?: string; + }; missionId: number; onClose: () => void; } function EditMissionForm({ - defaultValue, + missionData, missionId, onClose, }: EditMissionFormProps) { const methods = useForm({ resolver: zodResolver(EditMissionFormSchema), mode: 'onChange', - defaultValues: defaultValue, + defaultValues: { + title: missionData.missionTitle || '', + description: missionData.missionDescription || '', + weekNum: missionData.weekNum?.toString() || '', + guide: missionData.missionGuide || '', + dateRange: { + from: missionData.missionStartDate + ? new Date(missionData.missionStartDate) + : new Date(), + to: missionData.missionEndDate + ? new Date(missionData.missionEndDate) + : new Date(), + }, + }, }); + useEffect(() => { + methods.reset({ + title: missionData.missionTitle || '', + description: missionData.missionDescription || '', + weekNum: missionData.weekNum?.toString() || '', + guide: missionData.missionGuide || '', + dateRange: { + from: missionData.missionStartDate + ? new Date(missionData.missionStartDate) + : new Date(), + to: missionData.missionEndDate + ? new Date(missionData.missionEndDate) + : new Date(), + }, + }); + }, [missionData, methods]); + const { handleSubmit, formState, control } = methods; const { mutate: updateMission } = useUpdateMission(); diff --git a/src/components/pages/group-study-list-page.tsx b/src/components/pages/group-study-list-page.tsx index 686d3305..81b340ef 100644 --- a/src/components/pages/group-study-list-page.tsx +++ b/src/components/pages/group-study-list-page.tsx @@ -2,7 +2,7 @@ import { Plus } from 'lucide-react'; import { useRouter, useSearchParams } from 'next/navigation'; -import { useCallback, useMemo } from 'react'; +import { useCallback, useMemo, useState } from 'react'; import type { GetGroupStudiesTypeEnum, GetGroupStudiesTargetRolesEnum, @@ -12,16 +12,22 @@ import StudyFilter, { StudyFilterValues, } from '@/components/filtering/study-filter'; import StudySearch from '@/components/filtering/study-search'; +import PageContainer from '@/components/layout/page-container'; import Button from '@/components/ui/button'; import { useGetStudies } from '@/hooks/queries/study-query'; import GroupStudyFormModal from '../../features/study/group/ui/group-study-form-modal'; import GroupStudyPagination from '../../features/study/group/ui/group-study-pagination'; import GroupStudyList from '../lists/group-study-list'; +const PAGE_SIZE = 15; + export default function GroupStudyListPage() { const router = useRouter(); const searchParams = useSearchParams(); + // 로컬 검색 상태 + const [searchQuery, setSearchQuery] = useState(''); + // URL에서 필터 값 읽기 const filterValues = useMemo(() => { const type = searchParams.get('type')?.split(',').filter(Boolean) ?? []; @@ -33,15 +39,13 @@ export default function GroupStudyListPage() { return { type, targetRoles, method, inProgress }; }, [searchParams]); - // URL에서 검색어 읽기 - const searchQuery = searchParams.get('search') ?? ''; const currentPage = Number(searchParams.get('page')) || 1; - // API 호출 + // 검색어가 있으면 전체 데이터를 가져오고, 없으면 페이지네이션된 데이터를 가져옴 const { data, isLoading } = useGetStudies({ classification: 'GROUP_STUDY', - page: currentPage, - pageSize: 15, + page: searchQuery ? 1 : currentPage, + pageSize: searchQuery ? 10000 : PAGE_SIZE, type: filterValues.type.length > 0 ? (filterValues.type as GetGroupStudiesTypeEnum[]) @@ -57,8 +61,7 @@ export default function GroupStudyListPage() { inProgress: filterValues.inProgress || undefined, }); - const studies = data?.content ?? []; - const totalPages = data?.totalPages ?? 1; + const allStudies = useMemo(() => data?.content ?? [], [data?.content]); // URL 파라미터 업데이트 함수 const updateSearchParams = useCallback( @@ -100,39 +103,47 @@ export default function GroupStudyListPage() { [updateSearchParams], ); - // 검색 핸들러 - const handleSearch = useCallback( - (query: string) => { - updateSearchParams({ search: query || undefined }); - }, - [updateSearchParams], - ); + // 검색 핸들러 (로컬 상태만 업데이트) + const handleSearch = useCallback((query: string) => { + setSearchQuery(query); + }, []); - // 클라이언트 사이드 검색 필터링 (API에서 검색을 지원하지 않는 경우) + // 클라이언트 사이드 검색 필터링 (스터디명만 검색) const filteredStudies = useMemo(() => { - if (!searchQuery) return studies; + if (!searchQuery) return allStudies; const lowerQuery = searchQuery.toLowerCase(); - return studies.filter( - (study) => - study.simpleDetailInfo?.title?.toLowerCase().includes(lowerQuery) || - study.simpleDetailInfo?.summary?.toLowerCase().includes(lowerQuery), + return allStudies.filter((study) => + study.simpleDetailInfo?.title?.toLowerCase().includes(lowerQuery), ); - }, [studies, searchQuery]); + }, [allStudies, searchQuery]); + + // 검색어가 있을 때는 클라이언트에서 페이지네이션, 없으면 서버 페이지네이션 사용 + const totalPages = searchQuery + ? Math.ceil(filteredStudies.length / PAGE_SIZE) || 1 + : (data?.totalPages ?? 1); + + const displayStudies = useMemo(() => { + if (!searchQuery) return filteredStudies; + + const startIndex = (currentPage - 1) * PAGE_SIZE; + + return filteredStudies.slice(startIndex, startIndex + PAGE_SIZE); + }, [filteredStudies, searchQuery, currentPage]); if (isLoading) { return ( -
    +
    로딩 중...
    -
    + ); } return ( -
    + {/* 헤더 */}

    @@ -161,7 +172,7 @@ export default function GroupStudyListPage() {

    {/* 스터디 카드 그리드 */} - + {/* 페이지네이션 */} {totalPages > 1 && ( @@ -170,6 +181,6 @@ export default function GroupStudyListPage() { totalPages={totalPages} /> )} -
    + ); } diff --git a/src/components/pages/premium-study-list-page.tsx b/src/components/pages/premium-study-list-page.tsx index caa8bf2d..8a5018b7 100644 --- a/src/components/pages/premium-study-list-page.tsx +++ b/src/components/pages/premium-study-list-page.tsx @@ -2,26 +2,32 @@ import { Plus } from 'lucide-react'; import { useRouter, useSearchParams } from 'next/navigation'; -import { useCallback, useMemo } from 'react'; +import { useCallback, useMemo, useState } from 'react'; import type { GetGroupStudiesTypeEnum, GetGroupStudiesTargetRolesEnum, GetGroupStudiesMethodEnum, } from '@/api/openapi/api/group-study-management-api'; -import PremiumStudyList from '@/components/premium/premium-study-list'; -import PremiumStudyPagination from '@/components/premium/premium-study-pagination'; import StudyFilter, { StudyFilterValues, } from '@/components/filtering/study-filter'; import StudySearch from '@/components/filtering/study-search'; +import PageContainer from '@/components/layout/page-container'; +import PremiumStudyList from '@/components/premium/premium-study-list'; +import PremiumStudyPagination from '@/components/premium/premium-study-pagination'; import Button from '@/components/ui/button'; import GroupStudyFormModal from '@/features/study/group/ui/group-study-form-modal'; import { useGetStudies } from '@/hooks/queries/study-query'; +const PAGE_SIZE = 15; + export default function PremiumStudyListPage() { const router = useRouter(); const searchParams = useSearchParams(); + // 로컬 검색 상태 + const [searchQuery, setSearchQuery] = useState(''); + // URL에서 필터 값 읽기 const filterValues = useMemo(() => { const type = searchParams.get('type')?.split(',').filter(Boolean) ?? []; @@ -33,15 +39,13 @@ export default function PremiumStudyListPage() { return { type, targetRoles, method, inProgress }; }, [searchParams]); - // URL에서 검색어 읽기 - const searchQuery = searchParams.get('search') ?? ''; const currentPage = Number(searchParams.get('page')) || 1; - // API 호출 + // 검색어가 있으면 전체 데이터를 가져오고, 없으면 페이지네이션된 데이터를 가져옴 const { data, isLoading } = useGetStudies({ classification: 'PREMIUM_STUDY', - page: currentPage, - pageSize: 9, + page: searchQuery ? 1 : currentPage, + pageSize: searchQuery ? 10000 : PAGE_SIZE, type: filterValues.type.length > 0 ? (filterValues.type as GetGroupStudiesTypeEnum[]) @@ -57,8 +61,7 @@ export default function PremiumStudyListPage() { inProgress: filterValues.inProgress || undefined, }); - const studies = data?.content ?? []; - const totalPages = data?.totalPages ?? 1; + const allStudies = useMemo(() => data?.content ?? [], [data?.content]); // URL 파라미터 업데이트 함수 const updateSearchParams = useCallback( @@ -100,39 +103,47 @@ export default function PremiumStudyListPage() { [updateSearchParams], ); - // 검색 핸들러 - const handleSearch = useCallback( - (query: string) => { - updateSearchParams({ search: query || undefined }); - }, - [updateSearchParams], - ); + // 검색 핸들러 (로컬 상태만 업데이트) + const handleSearch = useCallback((query: string) => { + setSearchQuery(query); + }, []); - // 클라이언트 사이드 검색 필터링 (API에서 검색을 지원하지 않는 경우) + // 클라이언트 사이드 검색 필터링 (스터디명만 검색) const filteredStudies = useMemo(() => { - if (!searchQuery) return studies; + if (!searchQuery) return allStudies; const lowerQuery = searchQuery.toLowerCase(); - return studies.filter( - (study) => - study.simpleDetailInfo?.title?.toLowerCase().includes(lowerQuery) || - study.simpleDetailInfo?.summary?.toLowerCase().includes(lowerQuery), + return allStudies.filter((study) => + study.simpleDetailInfo?.title?.toLowerCase().includes(lowerQuery), ); - }, [studies, searchQuery]); + }, [allStudies, searchQuery]); + + // 검색어가 있을 때는 클라이언트에서 페이지네이션, 없으면 서버 페이지네이션 사용 + const totalPages = searchQuery + ? Math.ceil(filteredStudies.length / PAGE_SIZE) || 1 + : (data?.totalPages ?? 1); + + const displayStudies = useMemo(() => { + if (!searchQuery) return filteredStudies; + + const startIndex = (currentPage - 1) * PAGE_SIZE; + + return filteredStudies.slice(startIndex, startIndex + PAGE_SIZE); + }, [filteredStudies, searchQuery, currentPage]); if (isLoading) { return ( -
    +
    로딩 중...
    -
    + ); } return ( -
    + {/* 헤더 */}

    @@ -161,7 +172,7 @@ export default function PremiumStudyListPage() {

    {/* 스터디 카드 그리드 */} - + {/* 페이지네이션 */} {totalPages > 1 && ( @@ -170,6 +181,6 @@ export default function PremiumStudyListPage() { totalPages={totalPages} /> )} -
    + ); } diff --git a/src/components/payment/paymentActionClient.tsx b/src/components/payment/paymentActionClient.tsx index 0d863c66..12a4accb 100644 --- a/src/components/payment/paymentActionClient.tsx +++ b/src/components/payment/paymentActionClient.tsx @@ -14,7 +14,7 @@ interface Props { } type PaymentMethod = 'CARD' | 'VIRTUAL_ACCOUNT'; -const clientKey = process.env.NEXT_PUBLIC_TOSS_CLIENT_KEY!; +const clientKey = process.env.NEXT_PUBLIC_TOSS_CLIENT_KEY ?? ''; const methods: { id: PaymentMethod; label: string }[] = [ { id: 'CARD', label: '신용카드 결제' }, @@ -103,6 +103,14 @@ export default function PaymentCheckoutPage({ study }: Props) { useEffect(() => { async function fetchPayment() { + if (!clientKey) { + console.error( + 'NEXT_PUBLIC_TOSS_CLIENT_KEY 환경변수가 설정되지 않았습니다.', + ); + + return; + } + try { const tossPayments = await loadTossPayments(clientKey); diff --git a/src/components/section/mission-section.tsx b/src/components/section/mission-section.tsx index 49f10ab9..ee7b9e05 100644 --- a/src/components/section/mission-section.tsx +++ b/src/components/section/mission-section.tsx @@ -9,6 +9,7 @@ import { useUserStore } from '@/stores/useUserStore'; import MissionCard from '../card/mission-card'; import HomeworkDetailContent from '../contents/homework-detail-content'; import MissionDetailContent from '../contents/mission-detail-content'; +import PageContainer from '../layout/page-container'; import CreateMissionModal from '../modals/create-mission-modal'; import { cn } from '../ui/(shadcn)/lib/utils'; @@ -137,7 +138,7 @@ export default function MissionSection({ groupStudyId }: MissionSectionProps) { // 미션 상세 보기 if (missionId) { return ( -
    +
    + ); } // 미션 목록 (기본) return (
    -
    +
    미션 목록 {isLeader && } diff --git a/src/components/section/premium-study-info-section.tsx b/src/components/section/premium-study-info-section.tsx index 022edfba..6fa5a355 100644 --- a/src/components/section/premium-study-info-section.tsx +++ b/src/components/section/premium-study-info-section.tsx @@ -51,7 +51,10 @@ export default function PremiumStudyInfoSection({
    썸네일
    + {/* {thumbnailUrl ? ( + {groupStudyTitle} + ) : ( +
    + )} */}
    diff --git a/src/components/summary/study-info-summary.tsx b/src/components/summary/study-info-summary.tsx index 0601f140..854c0f89 100644 --- a/src/components/summary/study-info-summary.tsx +++ b/src/components/summary/study-info-summary.tsx @@ -5,9 +5,10 @@ import { ChevronDown, ChevronUp } from 'lucide-react'; import { useRouter } from 'next/navigation'; import { useState } from 'react'; import Button from '@/components/ui/button'; -import { useUserStore } from '@/stores/useUserStore'; import { GroupStudyFullResponse } from '@/features/study/group/api/group-study-types'; import ApplyGroupStudyModal from '@/features/study/group/ui/apply-group-study-modal'; +import { useGetGroupStudyMyStatus } from '@/hooks/queries/group-study-member-api'; +import { useUserStore } from '@/stores/useUserStore'; import { EXPERIENCE_LEVEL_LABELS, REGULAR_MEETING_LABELS, @@ -16,7 +17,6 @@ import { STUDY_STATUS_LABELS, STUDY_TYPE_LABELS, } from '../../features/study/group/const/group-study-const'; -import { useGetGroupStudyMyStatus } from '@/hooks/queries/group-study-member-api'; interface Props { data: GroupStudyFullResponse; @@ -47,6 +47,7 @@ export default function SummaryStudyInfo({ data }: Props) { const { title } = detailInfo ?? {}; const { interviewPost: questions } = interviewPost ?? {}; + const isLoggedIn = !!memberId; const isLeader = leader?.memberId === memberId; const { data: myApplicationStatus } = useGetGroupStudyMyStatus({ @@ -76,6 +77,18 @@ export default function SummaryStudyInfo({ data }: Props) { label: '주제', value: targetRoles.map((role) => ROLE_LABELS[role]).join(', '), }, + { + label: '스터디 기간', + value: `${dayjs(startDate).format('YYYY.MM.DD')} ~ ${dayjs(endDate).format('YYYY.MM.DD')}`, + }, + { + label: '참가비', + value: price === 0 ? '무료' : `${price.toLocaleString()}원`, + }, + { + label: '모집인원', + value: `${maxMembersCount}명`, + }, { label: '진행 방식', value: STUDY_METHOD_LABELS[method], @@ -103,14 +116,6 @@ export default function SummaryStudyInfo({ data }: Props) { label: '모집인원', value: `${maxMembersCount}명`, }, - { - label: '스터디 기간', - value: `${dayjs(startDate).format('YYYY.MM.DD')} ~ ${dayjs(endDate).format('YYYY.MM.DD')}`, - }, - { - label: '참가비', - value: price === 0 ? '무료' : `${price.toLocaleString()}원`, - }, ]; const visibleItems = isExpanded ? infoItems : infoItems.slice(0, 4); @@ -127,25 +132,32 @@ export default function SummaryStudyInfo({ data }: Props) { }; const isApplyDisabled = + !isLoggedIn || isLeader || myApplicationStatus?.status !== 'NONE' || - groupStudyStatus === 'IN_PROGRESS' || + groupStudyStatus !== 'RECRUITING' || approvedCount >= maxMembersCount; const getButtonText = () => { - if ( - myApplicationStatus?.status === 'APPROVED' && - groupStudyStatus === 'IN_PROGRESS' - ) { + if (myApplicationStatus?.status === 'APPROVED') { return '참여 중인 스터디'; } if (myApplicationStatus?.status === 'PENDING') { return '승인 대기중'; } + if (myApplicationStatus?.status === 'REJECTED') { + return '신청 거절됨'; + } return '신청하기'; }; + const handleApplyClick = () => { + if (!isLoggedIn) { + router.push('/login'); + } + }; + return (
    {/* 제목 */} @@ -192,22 +204,33 @@ export default function SummaryStudyInfo({ data }: Props) {
    {/* 스터디 신청 모달 (유료/무료 공통) */} - - {getButtonText()} - - } - /> + {isLoggedIn ? ( + + {getButtonText()} + + } + /> + ) : ( + + )}
    - {/* 1차 MVP에선 사용하지 않아 제외 */} - + {accessTokenStr && }