-
-
4.8
-
참여자 만족도
+
+ {step.label}
+
+
+
+ {index + 1}
+
+
+
+ );
+ })}
+
+
+
+
+ }
+ iconClassName="bg-[#EAF5FF] text-[#5BB5F8]"
+ title="주변 로컬"
+ description="맛집 · 카페 추천"
+ onClick={() => navigate(PATH.MISSION_LOCAL_RECOMMEND)}
+ />
+
+ }
+ iconClassName="bg-[#FFF6D9] text-[#F59E0B]"
+ title="보너스 지역"
+ description="추가 포인트 안내"
+ highlighted
+ onClick={() => navigate(PATH.MISSION_BONUS_REGION)}
+ />
-
“짧은 시간 안에 지역 분위기를 느낄 수 있어서 좋아요.”
+
+
+
+
+
+
+
+
+ 미션 완료!
+
+
+ +{mission.points}P 보상이 지급됐어요
+
+
+
+
+
+
+
+
+
+ );
+}
+
+type InfoCardProps = {
+ icon: ReactNode;
+ iconClassName: string;
+ value: string;
+ label: string;
+};
+
+function InfoCard({ icon, iconClassName, value, label }: InfoCardProps) {
+ return (
+
+
+ {icon}
+
- }
- onClick={() => navigate(PATH.MISSION_PHOTO)}
- >
- 미션 시작하기
-
-
- >
+
+ {value}
+
+
+
+ {label}
+
+
);
}
+
+type SmallFeatureCardProps = {
+ icon: ReactNode;
+ iconClassName: string;
+ title: string;
+ description: string;
+ highlighted?: boolean;
+ onClick?: () => void;
+};
+
+function SmallFeatureCard({
+ icon,
+ iconClassName,
+ title,
+ description,
+ highlighted = false,
+ onClick,
+}: SmallFeatureCardProps) {
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/src/pages/mission/MissionPage.tsx b/src/pages/mission/MissionPage.tsx
index 311a9d4..73010e2 100644
--- a/src/pages/mission/MissionPage.tsx
+++ b/src/pages/mission/MissionPage.tsx
@@ -1,3 +1,370 @@
+import { useMemo, useState } from "react";
+import { useNavigate } from "react-router-dom";
+import {
+ Building2,
+ ChevronRight,
+ Check,
+ Landmark,
+ Leaf,
+ Lock,
+ MapPin,
+ Search,
+ Sunrise,
+ Utensils,
+ type LucideIcon,
+} from "lucide-react";
+
+import HomeHeader from "@/components/home/HomeHeader";
+import QuespotPageLayout, {
+ QuespotDivider,
+ QuespotPageContent,
+} from "@/layouts/QuespotPageLayout";
+import { PATH } from "@/routes/paths";
+import QuestySvg from "@/assets/icons/Questy.svg";
+
+type MissionCategory = "all" | "history" | "nature" | "food" | "night";
+type MissionStatus = "completed" | "available" | "locked";
+
+export type MissionItem = {
+ id: number;
+ title: string;
+ location: string;
+ address: string;
+ distance: string;
+ category: Exclude
;
+ categoryLabel: string;
+ iconKey: "building" | "landmark" | "leaf" | "food" | "night" | "lock";
+ status: MissionStatus;
+ points?: number;
+ duration?: string;
+};
+
+const CATEGORY_TABS: {
+ id: MissionCategory;
+ label: string;
+ icon?: LucideIcon;
+}[] = [
+ { id: "all", label: "전체" },
+ { id: "history", label: "역사", icon: Building2 },
+ { id: "nature", label: "자연", icon: Leaf },
+ { id: "food", label: "음식", icon: Utensils },
+ { id: "night", label: "야경", icon: Sunrise },
+];
+
+const iconMap: Record = {
+ building: Building2,
+ landmark: Landmark,
+ leaf: Leaf,
+ food: Utensils,
+ night: Sunrise,
+ lock: Lock,
+};
+
+const MISSIONS: MissionItem[] = [
+ {
+ id: 1,
+ title: "경복궁 정문 인증샷",
+ location: "경복궁",
+ address: "서울 종로구",
+ distance: "1.2km",
+ category: "history",
+ categoryLabel: "역사",
+ iconKey: "building",
+ status: "completed",
+ points: 150,
+ duration: "약 30분",
+ },
+ {
+ id: 2,
+ title: "북촌 한옥 골목 탐험",
+ location: "북촌 한옥마을",
+ address: "서울 종로구",
+ distance: "2.1km",
+ category: "history",
+ categoryLabel: "문화",
+ iconKey: "landmark",
+ status: "completed",
+ points: 150,
+ duration: "약 30분",
+ },
+ {
+ id: 3,
+ title: "인사동 전통찻집 방문",
+ location: "인사동",
+ address: "서울 종로구",
+ distance: "2.5km",
+ category: "food",
+ categoryLabel: "음식",
+ iconKey: "food",
+ status: "available",
+ points: 120,
+ duration: "약 25분",
+ },
+ {
+ id: 4,
+ title: "남산타워 야경 포착",
+ location: "남산서울타워",
+ address: "서울 용산구",
+ distance: "4.8km",
+ category: "night",
+ categoryLabel: "야경",
+ iconKey: "night",
+ status: "available",
+ points: 300,
+ duration: "약 40분",
+ },
+ {
+ id: 5,
+ title: "해운대 일출 인증",
+ location: "해운대 해수욕장",
+ address: "부산 해운대구",
+ distance: "325km",
+ category: "nature",
+ categoryLabel: "자연",
+ iconKey: "lock",
+ status: "locked",
+ points: 200,
+ duration: "약 30분",
+ },
+];
+
+const CATEGORY_STYLE: Record<
+ Exclude,
+ {
+ chip: string;
+ iconPanel: string;
+ iconText: string;
+ }
+> = {
+ history: {
+ chip: "bg-[#EEE1FF] text-[#8B5CF6]",
+ iconPanel: "bg-[#EDE9FE]",
+ iconText: "text-[#8B5CF6]",
+ },
+ nature: {
+ chip: "bg-[#DFFBEE] text-[#65D3AE]",
+ iconPanel: "bg-[#E7F5F0]",
+ iconText: "text-[#B9C0CC]",
+ },
+ food: {
+ chip: "bg-[#FFF0C9] text-[#E58A14]",
+ iconPanel: "bg-[#FFF9DF]",
+ iconText: "text-[#E58A14]",
+ },
+ night: {
+ chip: "bg-[#DCE8FF] text-[#4A7DFF]",
+ iconPanel: "bg-[#DCE8F5]",
+ iconText: "text-[#4A7DFF]",
+ },
+};
+
export default function MissionPage() {
- return 미션페이지입니다.
;
+ const navigate = useNavigate();
+ const [selectedCategory, setSelectedCategory] =
+ useState("all");
+
+ const filteredMissions = useMemo(() => {
+ if (selectedCategory === "all") return MISSIONS;
+
+ return MISSIONS.filter((mission) => mission.category === selectedCategory);
+ }, [selectedCategory]);
+
+ return (
+
+ {
+ // TODO: 알림함 연결
+ }}
+ />
+
+
+
+
+
+
+ {filteredMissions.map((mission) => (
+ {
+ if (mission.status === "locked") return;
+
+ navigate(PATH.MISSION_DETAIL, {
+ state: {
+ mission,
+ },
+ });
+ }}
+ />
+ ))}
+
+
+ );
}
+
+type MissionListCardProps = {
+ mission: MissionItem;
+ onClick: () => void;
+};
+
+function MissionListCard({ mission, onClick }: MissionListCardProps) {
+ const Icon = iconMap[mission.iconKey];
+ const isCompleted = mission.status === "completed";
+ const isLocked = mission.status === "locked";
+ const style = CATEGORY_STYLE[mission.category];
+
+ return (
+
+
+
+
+ {isCompleted ? (
+
+
+
+ ) : null}
+
+
+
+
+
+ {mission.categoryLabel}
+
+
+
+
+ {mission.title}
+
+
+
+
+ {mission.location}
+ ·
+ {mission.distance}
+
+
+
+ {isCompleted ? (
+
+
+ 완료
+
+ ) : isLocked ? (
+
+
+ 잠김
+
+ ) : (
+
+ +{mission.points}P
+
+ )}
+
+
+
+
+
+
+ );
+}
+
+type CategoryMiniIconProps = {
+ category: Exclude;
+};
+
+function CategoryMiniIcon({ category }: CategoryMiniIconProps) {
+ const iconMap: Record, LucideIcon> = {
+ history: Building2,
+ nature: Leaf,
+ food: Utensils,
+ night: Sunrise,
+ };
+
+ const Icon = iconMap[category];
+
+ return ;
+}
\ No newline at end of file
diff --git a/src/routes/paths.ts b/src/routes/paths.ts
index 03f08a6..dba8bf4 100644
--- a/src/routes/paths.ts
+++ b/src/routes/paths.ts
@@ -12,6 +12,8 @@ export const PATH = {
MISSION_DETAIL: "/missions/:missionId",
MISSION_PHOTO: "/mission/photo",
MISSION_RECORD: "/mission/record",
+ MISSION_LOCAL_RECOMMEND: "/mission/local-recommend",
+ MISSION_BONUS_REGION: "/mission/bonus-region",
//MapPage
MAP: "/map",
diff --git a/src/routes/router.tsx b/src/routes/router.tsx
index 0b18267..b3df17f 100644
--- a/src/routes/router.tsx
+++ b/src/routes/router.tsx
@@ -10,6 +10,8 @@ import MissionsPage from "@/pages/mission/MissionPage";
import MissionDetailPage from "@/pages/mission/MissionDetailPage";
import MissionPhotoPage from "@/pages/mission/MissionPhotoPage";
import MissionRecordPage from "@/pages/mission/MissionRecordPage";
+import LocalRecommendPage from "@/pages/mission/LocalRecommendPage";
+import BonusRegionPage from "@/pages/mission/BonusRegionPage";
import MapPage from "@/pages/MapPage";
import RewardPage from "@/pages/RewardPage";
import MyPage from "@/pages/MyPage";
@@ -30,8 +32,14 @@ export const router = createBrowserRouter([
path: PATH.LOGIN,
element: ,
},
- { path: PATH.SIGNUP, element: },
- { path: PATH.SIGNUP_CHECK, element: },
+ {
+ path: PATH.SIGNUP,
+ element: ,
+ },
+ {
+ path: PATH.SIGNUP_CHECK,
+ element: ,
+ },
{
element: ,
@@ -76,6 +84,14 @@ export const router = createBrowserRouter([
path: PATH.MISSION_RECORD,
element: ,
},
+ {
+ path: PATH.MISSION_LOCAL_RECOMMEND,
+ element: ,
+ },
+ {
+ path: PATH.MISSION_BONUS_REGION,
+ element: ,
+ },
{
path: PATH.QUESTY_CUSTOMIZE,
element: ,
@@ -84,4 +100,4 @@ export const router = createBrowserRouter([
},
],
},
-]);
+]);
\ No newline at end of file