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
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { RewardActivityType } from "@/apis/reward";
import { ContentCard } from "./ContentCard";
import { ContentCard } from "../ContentCard";
import { Target, Wallet, Award, Mail, MapPin, Medal } from "lucide-react";

const notificationStyles = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { ContentCard } from "../ContentCard";

interface NotificationCardSkeletonProps {
showDescription?: boolean;
showAmount?: boolean;
}

export function NotificationCardSkeleton({
showDescription = true,
showAmount = true,
}: NotificationCardSkeletonProps) {
return (
<ContentCard className="relative bg-white" aria-hidden="true">
<div className="flex animate-pulse gap-3 motion-reduce:animate-none">
<div className="h-12 w-12 shrink-0 rounded-2xl bg-slate-100" />

<div className="flex min-w-0 flex-1 flex-col pr-4">
<div className="my-1 h-3 w-3/4 rounded bg-slate-200" />

{showDescription && (
<div className="mt-1 h-3 w-full rounded bg-slate-100" />
)}

<div className="mt-2 h-2.5 w-16 rounded bg-slate-100" />
</div>

{showAmount && (
<div className="flex shrink-0 items-center">
<div className="h-5 w-12 rounded bg-slate-100" />
</div>
)}
</div>
</ContentCard>
);
}
13 changes: 13 additions & 0 deletions src/components/map/MapMissionSummarySkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export default function MapMissionSummarySkeleton() {
return (
<div className="w-[140px] animate-pulse motion-reduce:animate-none" aria-hidden="true">
<div className="flex h-[18px] items-center">
<div className="h-3.5 w-20 rounded bg-slate-200" />
</div>
<div className="mt-[4px] flex h-[18px] items-center gap-2">
<div className="h-3.5 w-16 rounded bg-slate-100" />
<div className="h-3.5 w-14 rounded bg-slate-100" />
</div>
</div>
);
}
55 changes: 55 additions & 0 deletions src/components/map/MissionListCard/MissionListCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import type { DistrictMission } from "@/apis/missionSpot";
import Button from "@/components/common/Button";
import { PATH } from "@/routes/paths";
import { ChevronRight } from "lucide-react";
import { useNavigate } from "react-router-dom";

const missionStatusLabel = {
AVAILABLE: "시작가능",
IN_PROGRESS: "진행중",
COMPLETED: "완료",
LOCKED: "잠김",
} as const;

export default function MissionListCard({
mission,
}: {
mission: DistrictMission;
}) {
const navigate = useNavigate();

return (
<div className="shrink-0 rounded-[18px] border border-[#E5EDF7] p-4 flex justify-between bg-white">
<div className="flex gap-2">
<img
src={mission.imageUrl}
alt={`${mission.spotName} 사진`}
className="size-10 rounded-md"
/>
<div>
<p className="type-body1 !font-black">{mission.title}</p>
<p className="type-body3 text-[#A2A9B2]">
{missionStatusLabel[mission.userMissionStatus]} |{" "}
{mission.estimatedMinutes}분 | +{mission.rewardPoint}
</p>
</div>
</div>
<Button
size="sm"
variant="secondary"
className="size-10 p-3!"
aria-label={`${mission.title} 상세 보기`}
onClick={() =>
navigate(
PATH.MISSION_DETAIL.replace(
":missionId",
String(mission.missionId),
),
)
}
>
<ChevronRight />
</Button>
</div>
);
}
17 changes: 17 additions & 0 deletions src/components/map/MissionListCard/MissionListCardSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export default function MissionListCardSkeleton() {
return (
<div
className="flex shrink-0 animate-pulse bg-white justify-between gap-3 rounded-[18px] border border-[#E5EDF7] p-4 motion-reduce:animate-none"
aria-hidden="true"
>
<div className="flex min-w-0 flex-1 gap-2">
<div className="size-10 shrink-0 rounded-md bg-slate-100" />
<div className="flex min-w-0 flex-1 flex-col justify-center gap-2">
<div className="h-3.5 w-3/4 rounded bg-slate-200" />
<div className="h-3 w-full max-w-[160px] rounded bg-slate-100" />
</div>
</div>
<div className="size-10 shrink-0 rounded-xl bg-slate-100" />
</div>
);
}
66 changes: 66 additions & 0 deletions src/components/map/SpotSummaryCard/SpotSummaryCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { MissionSpot } from "@/apis/missionSpot";
import { MapPin } from "lucide-react";

type SpotSummaryCardProps = {
spot: MissionSpot;
selected: boolean;
onClick: () => void;
};

//하단 주변 미션 스팟 카드
export default function SpotSummaryCard({
spot,
selected,
onClick,
}: SpotSummaryCardProps) {
const isCompleted = spot.completionStatus === "COMPLETE";

return (
<button
type="button"
onClick={onClick}
className={[
"flex min-w-[82px] shrink-0 flex-col items-center rounded-[16px] border-2 px-[12px] py-[12px] transition active:scale-[0.98]",
selected
? "border-[#5BB5F8] bg-primary text-white"
: isCompleted
? "border-[#BBF7D0] bg-[#F0FDF9]"
: "border-[#C8E8FF] bg-[#EAF5FF]",
].join(" ")}
>
<span className="text-[24px] leading-none">
<MapPin
className={
selected
? "text-white"
: isCompleted
? "text-[#22C983]"
: "text-primary"
}
/>
</span>

<strong
className={[
"mt-[8px] text-[13px] font-black leading-[17px]",
selected ? "text-white" : "text-[#1C1C3A]",
].join(" ")}
>
{spot.districtName}
</strong>

<span
className={[
"mt-[3px] text-[11px] font-bold leading-[15px]",
selected
? "text-white/90"
: isCompleted
? "text-[#00C950]"
: "text-[#A2A9B2]",
].join(" ")}
>
{isCompleted ? "완료" : `${spot.missionCount ?? 0}개`}
</span>
</button>
);
}
16 changes: 16 additions & 0 deletions src/components/map/SpotSummaryCard/SpotSummaryCardSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export default function SpotSummaryCardSkeleton() {
return (
<div
className="flex min-w-[82px] shrink-0 animate-pulse flex-col items-center rounded-[16px] border-2 border-[#C8E8FF] bg-[#EAF5FF] px-[12px] py-[12px] motion-reduce:animate-none"
aria-hidden="true"
>
<div className="h-6 w-6 rounded-lg bg-slate-200" />
<div className="mt-[8px] flex h-[17px] items-center">
<div className="h-3 w-12 rounded bg-slate-200" />
</div>
<div className="mt-[3px] flex h-[15px] items-center">
<div className="h-2.5 w-7 rounded bg-slate-200" />
</div>
</div>
);
}
15 changes: 15 additions & 0 deletions src/components/reward/BadgeCard/BadgeCardSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export default function BadgeCardSkeleton() {
return (
<div
className="flex animate-pulse flex-col items-center motion-reduce:animate-none"
aria-hidden="true"
>
<div className="grid h-[92px] w-[92px] place-items-center rounded-[16px] border border-[#EAF5FF] bg-white shadow-[0_1px_3px_rgba(0,0,0,0.08)]">
<div className="h-10 w-10 rounded-xl bg-slate-100" />
</div>
<div className="mt-[12px] flex h-[17px] items-center">
<div className="h-3 w-16 rounded bg-slate-200" />
</div>
</div>
);
}
61 changes: 0 additions & 61 deletions src/components/reward/HistoryCard.tsx

This file was deleted.

39 changes: 0 additions & 39 deletions src/components/reward/PointSummaryCard.tsx

This file was deleted.

60 changes: 60 additions & 0 deletions src/components/reward/PointSummaryCard/PointSummaryCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import PointSummaryCardSkeleton from "./PointSummaryCardSkeleton";
import RewardError from "../RewardError";

type PointSummaryCardProps = {
currentPoint: number;
earnedPoint: number;
usedPoint: number;
isLoading: boolean;
isError: boolean;
hasData: boolean;
};

export default function PointSummaryCard({
currentPoint,
earnedPoint,
usedPoint,
isLoading,
isError,
hasData,
}: PointSummaryCardProps) {
return (
<section
className="mt-[44px] rounded-[16px] border border-white/80 bg-white/70 p-[16px] shadow-[0_1px_3px_rgba(0,0,0,0.12)] backdrop-blur"
aria-busy={isLoading}
>
<p className="m-0 text-[13px] font-medium leading-[18px] text-[#A2A9B2]">
보유 포인트
</p>
{isLoading && <PointSummaryCardSkeleton />}
{!isLoading && isError && (
<RewardError
message="포인트를 불러오지 못했어요."
/>
)}
{hasData && !isLoading && (
<>
<div className="mt-[8px] flex items-end gap-[5px]">
<strong className="text-[42px] font-black leading-none text-[#5BB5F8]">
{currentPoint.toLocaleString()}
</strong>

<span className="pb-[6px] text-[18px] font-black leading-none text-[#9CA3AF]">
P
</span>
</div>

<div className="mt-[14px] flex items-center gap-[16px]">
<span className="text-[13px] font-bold leading-none text-[#7B8794]">
획득 <b className="font-black">{earnedPoint.toLocaleString()}P</b>
</span>

<span className="text-[13px] font-bold leading-none text-[#7B8794]">
사용 <b className="font-black">{usedPoint.toLocaleString()}P</b>
</span>
</div>
</>
)}
</section>
);
}
Loading
Loading