Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
NEXT_PUBLIC_API_BASE_URL=
NEXT_PUBLIC_KAKAO_CLIENT_ID=
NEXT_PUBLIC_GOOGLE_CLIENT_ID=
NEXT_PUBLIC_GOOGLE_CLIENT_ID=
NEXT_PUBLIC_TOSS_CLIENT_KEY=
5 changes: 3 additions & 2 deletions src/app/(service)/home/page.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -15,7 +16,7 @@ export const metadata: Metadata = generateSEOMetadata({

export default async function Home() {
return (
<div className="flex gap-600 py-600">
<PageContainer className="flex gap-600 py-600">
<div className="flex flex-1 flex-col gap-500">
<Banner />
<StudyCard />
Expand All @@ -24,6 +25,6 @@ export default async function Home() {
<aside className="w-[335px] shrink-0">
<Sidebar />
</aside>
</div>
</PageContainer>
);
}
5 changes: 3 additions & 2 deletions src/app/(service)/insights/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -58,7 +59,7 @@ export default async function BlogPage({ searchParams }: BlogPageProps) {
const articles = articlesRes.data ?? [];

return (
<div className="flex w-full gap-600 py-600">
<PageContainer className="flex gap-600 py-600">
<div className="flex flex-1 flex-col gap-500">
<div className="flex justify-between">
<span className="font-designer-28b text-[#181D27]">
Expand Down Expand Up @@ -144,6 +145,6 @@ export default async function BlogPage({ searchParams }: BlogPageProps) {
)}
</div>
{isLoggedIn && <Sidebar />}
</div>
</PageContainer>
);
}
7 changes: 4 additions & 3 deletions src/app/(service)/payment/complete/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -26,8 +27,8 @@ export default function PaymentCompletePage() {
const formatKRW = (n: number) => `${n.toLocaleString('ko-KR')}원`;

return (
<div className="mx-auto">
<div className="bg-fill-neutral-subtle-default border-border-default rounded-150 w-[840px] border p-500">
<PageContainer className="py-600">
<div className="bg-fill-neutral-subtle-default border-border-default rounded-150 mx-auto w-[840px] border p-500">
{/* 아이콘 */}
<div className="flex justify-center">
<div className="relative h-[90px] w-[140px]">
Expand Down Expand Up @@ -93,7 +94,7 @@ export default function PaymentCompletePage() {
</div>
</div>
</div>
</div>
</PageContainer>
);
}

Expand Down
25 changes: 12 additions & 13 deletions src/components/card/mission-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,24 +123,14 @@ export default function MissionCard({
<li className="border-border-default rounded-100 flex items-center justify-between border bg-[#fff] p-300">
<MissionCardContent
title={mission.title}
weekNum={mission.weekNum}
statusConfig={statusConfig}
startDate={mission.startDate}
endDate={mission.endDate}
deadlineInfo={null}
/>
<div className="flex flex-col gap-100">
<EditMissionModal
missionId={mission.missionId}
defaultValue={{
title: mission.title,
description: mission.description,
guide: mission.description,
dateRange: {
from: new Date(mission.startDate),
to: new Date(mission.endDate),
},
}}
/>
<EditMissionModal missionId={mission.missionId} />
<DeleteMissionModal
missionId={mission.missionId}
groupStudyId={groupStudyId}
Expand All @@ -159,6 +149,7 @@ export default function MissionCard({
>
<MissionCardContent
title={mission.title}
weekNum={mission.weekNum}
statusConfig={statusConfig}
startDate={mission.startDate}
endDate={mission.endDate}
Expand Down Expand Up @@ -192,6 +183,7 @@ export default function MissionCard({
>
<MissionCardContent
title={mission.title}
weekNum={mission.weekNum}
statusConfig={statusConfig}
startDate={mission.startDate}
endDate={mission.endDate}
Expand All @@ -213,6 +205,7 @@ export default function MissionCard({
>
<MissionCardContent
title={mission.title}
weekNum={mission.weekNum}
statusConfig={statusConfig}
startDate={mission.startDate}
endDate={mission.endDate}
Expand All @@ -224,17 +217,21 @@ export default function MissionCard({

function MissionCardContent({
title,
weekNum,
statusConfig,
startDate,
endDate,
deadlineInfo,
}: {
title?: string;
weekNum?: number;
statusConfig: { label: string; color: ComponentProps<typeof Badge>['color'] };
startDate?: string;
endDate?: string;
deadlineInfo: { text: string; isUrgent: boolean } | null;
}) {
const displayTitle = weekNum ? `${weekNum}주차 미션 : ${title}` : title;

return (
<div className="flex flex-col gap-100">
{deadlineInfo && (
Expand All @@ -243,7 +240,9 @@ function MissionCardContent({
</span>
)}
<div className="flex items-center gap-100">
<span className="font-designer-16b text-text-default">{title}</span>
<span className="font-designer-16b text-text-default">
{displayTitle}
</span>
<Badge color={statusConfig.color}>{statusConfig.label}</Badge>
</div>
<span className="text-text-subtlest font-designer-12r">
Expand Down
7 changes: 4 additions & 3 deletions src/components/contents/payment-page-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,9 @@ export default function PaymentPageContent({ id }: PaymentPageContentProps) {

const data = result.data;

console.log(data);

return (
<div className="bg-background-alternative min-h-dvh">
<div className="mx-auto max-w-[840px] pt-600">
<div className="mx-auto max-w-[840px] py-600">
<div className="space-y-200">
{/* 선택한 스터디 */}
<section className="rounded-150 border-border-default bg-fill-neutral-subtle-default border px-500 py-400">
Expand All @@ -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
}
/>
</section>

Expand Down
4 changes: 2 additions & 2 deletions src/components/filtering/study-filter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,10 @@ function FilterDropdown({
<button
type="button"
className={[
'rounded-100 h-500ems-center flex gap-50 border px-150',
'h-500ems-center flex gap-50 rounded-full border px-200 py-100',
hasSelection
? 'border-border-brand bg-fill-brand-subtle-default text-text-brand'
: 'border-border-default bg-background-default text-text-default',
: 'border-border-default bg-fill-neutral-subtle-default text-text-default',
].join(' ')}
>
<span className="font-designer-14m">{label}</span>
Expand Down
45 changes: 24 additions & 21 deletions src/components/filtering/study-search.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';

import { Search } from 'lucide-react';
import { useState, useCallback } from 'react';
import { useState, useCallback, useEffect, useRef } from 'react';

interface StudySearchProps {
value: string;
Expand All @@ -12,41 +12,44 @@ interface StudySearchProps {
export default function StudySearch({
value,
onChange,
placeholder = '제목, 스터디명을 검색해주세요.',
placeholder = '스터디명을 검색해주세요.',
}: StudySearchProps) {
const [inputValue, setInputValue] = useState(value);
const debounceRef = useRef<NodeJS.Timeout | null>(null);

const handleKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
onChange(inputValue);
// 디바운스된 검색 실행
useEffect(() => {
if (debounceRef.current) {
clearTimeout(debounceRef.current);
}

debounceRef.current = setTimeout(() => {
onChange(inputValue);
}, 300);

return () => {
if (debounceRef.current) {
clearTimeout(debounceRef.current);
}
},
[inputValue, onChange],
);
};
}, [inputValue, onChange]);

// 외부에서 value가 변경되면 inputValue 동기화
useEffect(() => {
setInputValue(value);
}, [value]);

const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setInputValue(e.target.value);
}, []);

const handleSearchClick = useCallback(() => {
onChange(inputValue);
}, [inputValue, onChange]);

return (
<div className="border-border-default bg-background-default rounded-100 flex h-500 w-[280px] items-center gap-100 border px-150">
<button
type="button"
onClick={handleSearchClick}
className="text-text-subtlest hover:text-text-subtle"
>
<Search className="size-4" />
</button>
<Search className="text-text-subtlest size-4" />
<input
type="text"
value={inputValue}
onChange={handleChange}
onKeyDown={handleKeyDown}
placeholder={placeholder}
className="font-designer-14r text-text-default placeholder:text-text-subtlest w-full bg-transparent outline-none"
/>
Expand Down
39 changes: 39 additions & 0 deletions src/components/layout/header-nav.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<nav className="font-designer-14m flex flex-grow items-center gap-300 px-600">
{NAV_ITEMS.map((item) => {
const href = item.loginRequired && !isLoggedIn ? '/login' : item.href;
const isActive = pathname.startsWith(item.href);

return (
<Link
key={item.href}
href={href}
className={cn(isActive ? 'text-text-brand' : 'text-text-subtle')}
>
{item.label}
</Link>
);
})}
</nav>
);
}
18 changes: 18 additions & 0 deletions src/components/layout/page-container.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className={clsx('mx-auto w-full max-w-[1164px]', className)}>
{children}
</div>
);
}
1 change: 1 addition & 0 deletions src/components/modals/delete-mission-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export default function DeleteMissionModal({
onSuccess,
}: DeleteMissionModalProps) {
const [open, setOpen] = useState<boolean>(false);
console.log('missionId', missionId);

const { mutate: deleteMission } = useDeleteMission();

Expand Down
Loading
Loading