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
Binary file modified src/assets/img/screenshot1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/img/screenshot10.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/img/screenshot2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/img/screenshot3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/img/screenshot4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/img/screenshot5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/img/screenshot6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/img/screenshot7.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/img/screenshot8.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/img/screenshot9.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
43 changes: 31 additions & 12 deletions src/components/Onboarding/MockUI.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,39 @@
import type { SlideData } from "./slides";
import alarmScreen from "@/assets/img/screenshot6.png";
import settingScreen from "@/assets/img/screenshot9.png";
import inquiryScreen from "@/assets/img/screenshot10.png";
import { useEffect, useState } from "react";

interface MockUIProps {
mockType: SlideData["mockType"];
}

export function NotificationMock() {
const screens = [alarmScreen, settingScreen, inquiryScreen];
const [current, setCurrent] = useState(0);

useEffect(() => {
const timer = setInterval(() => {
setCurrent((prev) => (prev + 1) % screens.length);
}, 2000);
return () => clearInterval(timer);
}, []);

Check warning on line 20 in src/components/Onboarding/MockUI.tsx

View workflow job for this annotation

GitHub Actions / build

React Hook useEffect has a missing dependency: 'screens.length'. Either include it or remove the dependency array

return (
<div className="relative w-full h-full overflow-hidden rounded-xl">
{screens.map((src, i) => (
<img
key={i}
src={src}
alt=""
className="absolute inset-0 w-full h-full object-cover transition-opacity duration-700"
style={{ opacity: i === current ? 1 : 0 }}
/>
))}
</div>
);
}

export default function MockUI({ mockType }: MockUIProps) {
switch (mockType) {
case "welcome":
Expand Down Expand Up @@ -110,18 +140,7 @@
);

case "notification":
return (
<div className="flex flex-col items-center gap-3 p-5 w-full">
<div className="w-12 h-12 rounded-full bg-white/35 flex items-center justify-center text-2xl">
🔔
</div>
<div className="w-full flex flex-col gap-2">
<div className="h-2.5 bg-white/30 rounded-full w-[90%]" />
<div className="h-2.5 bg-white/30 rounded-full w-[75%]" />
<div className="h-2.5 bg-white/30 rounded-full w-[60%]" />
</div>
</div>
);
return <NotificationMock />;

case "permission":
return (
Expand Down
87 changes: 53 additions & 34 deletions src/components/Onboarding/OnboardingModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,22 @@ import { motion, AnimatePresence } from "framer-motion";
import type { PanInfo } from "framer-motion";
import { getSlides } from "./slides";
import type { SlideData } from "./slides";
import { NotificationMock } from "./MockUI";
import screenshot1 from "@/assets/img/screenshot1.png";
import screenshot2 from "@/assets/img/screenshot1.png";
import screenshot3 from "@/assets/img/screenshot1.png";
import screenshot4 from "@/assets/img/screenshot1.png";
import screenshot5 from "@/assets/img/screenshot1.png";
import screenshot6 from "@/assets/img/screenshot1.png";
import screenshot7 from "@/assets/img/screenshot1.png";
import screenshot8 from "@/assets/img/screenshot1.png";
import screenshot2 from "@/assets/img/screenshot2.png";
import screenshot3 from "@/assets/img/screenshot3.png";
import screenshot4 from "@/assets/img/screenshot4.png";
import screenshot5 from "@/assets/img/screenshot5.png";
import screenshot7 from "@/assets/img/screenshot7.png";
import screenshot8 from "@/assets/img/screenshot8.png";

const SCREENSHOT_PATHS: Record<number, string> = {
2: screenshot1,
3: screenshot2,
4: screenshot3,
5: screenshot4,
6: screenshot5,
7: screenshot6,
// 7번은 NotificationMock으로 대체 — 여기서 제거
8: screenshot7,
9: screenshot8,
};
Expand Down Expand Up @@ -52,7 +52,6 @@ function useSequentialTyping(title: string, desc: string) {
const [titleDone, setTitleDone] = useState(false);

useEffect(() => {
// mount될 때 한 번만 실행
let cancelled = false;
let i = 0;

Expand All @@ -79,7 +78,7 @@ function useSequentialTyping(title: string, desc: string) {
clearInterval(titleTimer);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); // ← 빈 배열: mount 시 1회만
}, []);

return { typedTitle, typedDesc, titleDone };
}
Expand Down Expand Up @@ -189,7 +188,7 @@ function PoolDeco() {
);
}

// ── 커서 컴포넌트 (SlideContent 밖에 선언) ───────────────────────────────────
// ── 커서 컴포넌트 ─────────────────────────────────────────────────────────────
function Cursor({
height = "h-5",
color = "bg-white",
Expand All @@ -206,7 +205,7 @@ function Cursor({
);
}

// ── 슬라이드 컨텐츠 (타이핑을 여기서 시작) ───────────────────────────────────
// ── 슬라이드 컨텐츠 ───────────────────────────────────────────────────────────
function SlideContent({
slide,
direction,
Expand All @@ -216,12 +215,12 @@ function SlideContent({
}) {
const muneoConfig = MUNEO_CONFIGS[slide.id] ?? MUNEO_CONFIGS[1];
const screenshotSrc = SCREENSHOT_PATHS[slide.id];
const isNotificationSlide = slide.id === 7;
const isCentered = muneoConfig.left === "50%";

const titleText = slide.title.replace(/\n/g, " ");
const descText = slide.description.replace(/\n/g, " ");

// mount될 때 타이핑 시작 — 슬라이드마다 독립적
const { typedTitle, typedDesc, titleDone } = useSequentialTyping(
titleText,
descText,
Expand All @@ -244,9 +243,9 @@ function SlideContent({
transition={{ duration: 0.3, ease: [0.4, 0, 0.2, 1] }}
className="absolute inset-0 flex flex-col"
>
{screenshotSrc ? (
{screenshotSrc || isNotificationSlide ? (
<>
{/* 상단 3/4: 스크린샷 */}
{/* 상단 3/4: 스크린샷 or NotificationMock */}
<div
className="relative flex items-center justify-center"
style={{
Expand All @@ -256,24 +255,44 @@ function SlideContent({
}}
>
<PoolDeco />
<motion.img
src={screenshotSrc}
alt="화면 미리보기"
className="relative z-10 object-contain"
style={{
height: "90%",
width: "auto",
maxWidth: "80%",
borderRadius: "16px",
boxShadow: "0 8px 32px rgba(0,0,0,0.15)",
}}
initial={{ opacity: 0, y: 16, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
transition={{ duration: 0.45, ease: "easeOut" }}
onError={(e) => {
(e.target as HTMLImageElement).style.opacity = "0";
}}
/>

{/* 7번 슬라이드: NotificationMock */}
{isNotificationSlide ? (
<div
className="relative z-10"
style={{
height: "90%",
width: "auto",
maxWidth: "80%",
aspectRatio: "9 / 19.5", // 스크린샷 비율에 맞게 조정
borderRadius: "16px",
overflow: "hidden",
boxShadow: "0 8px 32px rgba(0,0,0,0.15)",
}}
>
<NotificationMock />
</div>
) : (
<motion.img
src={screenshotSrc}
alt="화면 미리보기"
className="relative z-10 object-contain"
style={{
height: "90%",
width: "auto",
maxWidth: "80%",
borderRadius: "16px",
boxShadow: "0 8px 32px rgba(0,0,0,0.15)",
}}
initial={{ opacity: 0, y: 16, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
transition={{ duration: 0.45, ease: "easeOut" }}
onError={(e) => {
(e.target as HTMLImageElement).style.opacity = "0";
}}
/>
)}

<motion.img
src={slide.muneoImg}
alt="무너"
Expand Down Expand Up @@ -323,7 +342,7 @@ function SlideContent({
}}
>
{slide.repOnly && (
<span className="border border-yellow-300/60 bg-yellow-300/15 text-yellow-200 text-[11px] font-semibold rounded-full px-4 py-1 mb-1">
<span className="border border-yellow-300/60 bg-yellow-300/15 text-yellow-500 text-[11px] font-semibold rounded-full px-4 py-1 mb-1">
⭐ 대표자 전용
</span>
)}
Expand Down
2 changes: 1 addition & 1 deletion src/components/Onboarding/slides.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export const SLIDES: SlideData[] = [
id: 8,
muneoImg: muneo8,
title: "권한 설정 & 양도",
description: "대표자 권한을 설정하고\n구성원에게 양도할 수 있어요",
description: "구성원 별로 권한을 설정하고\n대표자 권한을 양도할 수 있어요",
repOnly: true,
mockType: "permission",
},
Expand Down
8 changes: 4 additions & 4 deletions src/page/Log/LogPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useInfiniteQuery, useQuery } from "@tanstack/react-query";
import GlassCard from "../../components/common/GlassCard";
import { sharedPoolService } from "@/api";
import type { HistoryEntry } from "@/types/SharedData";
import { formatData, formatDataLabel } from "@/utils/dataFormat";
import { formatDataLabel } from "@/utils/dataFormat";
import Avatar from "@/components/common/Avatar";
import { useEffect, useRef } from "react";
import { motion } from "framer-motion";
Expand All @@ -29,8 +29,8 @@ function getPrevYearMonth(yearMonth: string): string {
}

function formatAmount(entry: HistoryEntry): string {
const gb = formatData(Math.abs(entry.amount));
return entry.eventType === "USAGE" ? `- ${gb}GB` : `+ ${gb}GB`;
const gb = formatDataLabel(Math.abs(entry.amount));
return entry.eventType === "USAGE" ? `- ${gb}` : `+ ${gb}`;
}

function getRemainingPercent(remaining: number, total: number): number {
Expand Down Expand Up @@ -161,7 +161,7 @@ export default function LogPage() {
<p className="text-3xl font-bold text-gray-800">
{formatDataLabel(remaining)}
<span className="text-base font-normal text-gray-400 ml-1">
/ {formatDataLabel(total)}GB
/ {formatDataLabel(total)}
</span>
</p>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/page/Main/MainPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export default function Main() {
useQuery<FamilyApiResponse>({
queryKey: ["familyMembers"],
queryFn: () => familyService.getMembers().then((res) => res.data),
refetchInterval: 51000000,
refetchInterval: 10000,
refetchIntervalInBackground: true,
placeholderData: keepPreviousData, // ← v5 방식
});
Expand Down
Loading