From 2d2aa14a35a0ac5c8871555447c429ec08e968d1 Mon Sep 17 00:00:00 2001 From: Yoon seokchan Date: Mon, 17 Nov 2025 21:22:52 +0900 Subject: [PATCH 1/3] =?UTF-8?q?refactor:=20=EB=84=A4=EC=9D=B4=EB=B2=84=20?= =?UTF-8?q?=EB=A7=B5=20Application=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index 0d26d5d..b4e55ad 100644 --- a/index.html +++ b/index.html @@ -44,7 +44,7 @@ - +
From 3c3317666adc84d046f4fb56b07becc130aa8267 Mon Sep 17 00:00:00 2001 From: Yoon seokchan Date: Tue, 18 Nov 2025 17:06:21 +0900 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20=EB=84=A4=EC=9D=B4=EB=B2=84=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=20=EC=95=84=EC=9D=B4=EB=94=94=20=EC=98=A4?= =?UTF-8?q?=ED=83=80=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index b4e55ad..7e6ccec 100644 --- a/index.html +++ b/index.html @@ -44,7 +44,7 @@ - +
From f07ec757dd8f955f8ec73ccea058a813302902c7 Mon Sep 17 00:00:00 2001 From: Yoon seokchan <72729277+PaleBlueNote@users.noreply.github.com> Date: Thu, 16 Apr 2026 14:52:13 +0900 Subject: [PATCH 3/3] =?UTF-8?q?[#78]=20perf:=20=EB=9E=9C=EB=94=A9=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=B2=88=EB=93=A4=20=EC=B5=9C?= =?UTF-8?q?=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 네이버 맵 Application 변경 * fix: 네이버 유저 아이디 오타수정 * hotfix: 사업자번호 인증 API 오류로 인한 임시 모킹 처리 * docs: revise README with project overview and details Updated README to include project details, features, tech stack, installation instructions, performance optimizations, architecture, security, and team information. * docs: Update README to include service demo video Removed duplicate image tag and added service demo video link. * refactor: 랜딩 페이지 번들 최적화 개선 (#78) - LandingIntroSection/PaySection/DocSection 아이콘 8개 lazy 분리 chunk-landing 2,487kB → 11kB (99.6% 감소) - LandingIntroLazy/PayLazy/DocLazy min-height 추가 height:0 div로 Intersection Observer 즉시 trigger 되던 버그 수정 - FeatureCard Suspense 누락 버그 수정 lazy 아이콘이 상위 App Suspense로 탈출해 전체 화면 교체되던 문제 해결 - Landing.tsx LogoIcon을 static import으로 전환 (CLS 방지) - App.tsx HomeBoss/HomeStaff/HomeAdmin lazy 전환 manualChunks chunk-home 규칙과 static import 불일치 해소 - vite.config.ts vendor-others 세분화 firebase/charts/toss 별도 청크 분리 (921kB → 673kB) Lighthouse Mobile 3회 평균 기준 FCP 13.3s → 7.0s (▼47%), TBT 1,407ms → 710ms (▼50%) --- README.md | 149 +++++++++++++++++++++- src/App.tsx | 7 +- src/api/boss/store.ts | 14 ++ src/pages/landing/FeatureCard.tsx | 6 +- src/pages/landing/Landing.tsx | 14 +- src/pages/landing/LandingDocLazy.tsx | 8 +- src/pages/landing/LandingDocSection.tsx | 36 +++++- src/pages/landing/LandingIntroLazy.tsx | 8 +- src/pages/landing/LandingIntroSection.tsx | 30 ++++- src/pages/landing/LandingPayLazy.tsx | 8 +- src/pages/landing/LandingPaySection.tsx | 24 +++- vite.config.ts | 4 + 12 files changed, 266 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 53c088d..6bcd746 100644 --- a/README.md +++ b/README.md @@ -1 +1,148 @@ -# mango-boss-fe \ No newline at end of file +# 🥭 MangoBoss (망고보스) + +> [image (10)](https://drive.google.com/file/d/1avQXxRjYqYMZdZu0MLEnuBWkBaB1wWlD/view) +> 서비스 시연 영상 (Service Demo) + + +> **"복잡한 매장 관리, 이제 망고보스 하나로 끝."** +> 소상공인 사장님들을 위한 **올인원 매장 관리 솔루션(SaaS)** 프론트엔드 저장소입니다. + +
+ +## 🔗 배포 및 문서 (Links) +* **Service URL**: [https://www.mangoboss.com](https://www.mangoboss.com) +* **Team Notion**: [MangoBoss Team Docs](https://www.notion.so/1af52a9c0c5380e6b73beb5fc5e64aea) +* **Personal Docs**: [Dev Progress](https://www.notion.so/MangoBoss-1aea0b0c7d4c805c8204eba5bb540b55) + +
+ +## 📖 프로젝트 배경 (Background) + +**"사장님은 장사에만 집중하세요, 관리는 망고보스가 할게요."** + +많은 자영업자분들이 재고 관리, 직원 스케줄링, 매출 정산을 위해 엑셀을 켜거나 여러 개의 앱을 번갈아 사용하며 시간을 낭비하고 있습니다. +**MangoBoss**는 흩어져 있는 매장 관리 기능을 하나의 웹 대시보드로 통합하여, IT에 익숙하지 않은 사장님들도 직관적으로 사용할 수 있도록 UX를 설계했습니다. + +
+ +## ✨ 핵심 기능 (Key Features) + +* **📊 실시간 매출 대시보드**: 차트 라이브러리를 활용하여 일간/주간/월간 매출 추이를 시각화 +* **📦 스마트 재고 관리**: 재고 부족 시 알림 제공 및 원클릭 발주 시스템 +* **📅 직원 스케줄링**: 드래그 앤 드롭(DnD)으로 간편하게 알바생 근무표 작성 +* **📱 반응형 웹 (Mobile First)**: PC 포스기는 물론, 이동 중인 사장님의 스마트폰에서도 완벽 호환 +* **🔔 실시간 주문 알림**: 웹 소켓(Web Socket)을 연동하여 주문 접수 시 즉각적인 알림 제공 + +
+ +## 🛠️ 기술 스택 (Tech Stack) + +| 분류 | 기술 | +| :-- | :-- | +| **Framework** | ![React](https://img.shields.io/badge/React-20232A?style=flat&logo=react&logoColor=61DAFB) ![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?style=flat&logo=TypeScript&logoColor=white) | +| **State Mgt** | `Zustand` (전역 상태) | +| **Styling** | ![TailwindCSS](https://img.shields.io/badge/Tailwind_CSS-38B2AC?style=flat&logo=tailwind-css&logoColor=white) | +| **Visualization** | `Recharts` (매출 데이터 시각화) | +| **Build Tool** | ![Vite](https://img.shields.io/badge/Vite-646CFF?style=flat&logo=Vite&logoColor=white) | + +
+ +## 📂 폴더 구조 (Directory Structure) + +유지보수성과 확장성을 고려하여 **기능(Feature)** 단위와 **공통(Shared)** 단위를 분리하여 설계했습니다. + +```bash +dev-fe/ +├── src/ +│ ├── api/ # Axios 인스턴스 및 API 요청 함수 모음 +│ ├── assets/ # 이미지, 폰트, 아이콘 리소스 +│ ├── components/ # 재사용 가능한 공통 UI (Button, Input 등) +│ ├── constants/ # 상수 데이터 (메뉴 리스트, 에러 메시지 등) +│ ├── hooks/ # 커스텀 훅 (useAuth, useSalesData 등) +│ ├── layout/ # 페이지 레이아웃 (Header, Sidebar) +│ ├── pages/ # 라우팅 페이지 (Dashboard, Inventory, Schedule) +│ ├── store/ # Zustand 전역 상태 관리 +│ ├── types/ # TypeScript 타입 정의 +│ ├── utils/ # 날짜 변환, 화폐 단위 포맷팅 등 유틸 함수 +│ ├── App.tsx +│ └── main.tsx +├── .env +├── package.json +└── README.md +``` + +
+ +## 🚀 설치 및 실행 (Getting Started) + +```bash +# 1. 저장소 클론 +git clone [https://github.com/Mango-Butter/dev-fe.git](https://github.com/Mango-Butter/dev-fe.git) + +# 2. 패키지 설치 +yarn install + +# 3. 개발 서버 실행 +yarn dev +``` + +
+ +## 🚀 성능 최적화 (Performance Optimization) + +초기 로딩 속도 개선과 렌더링 최적화를 위해 Vite의 빌드 전략을 수정하고 코드 분할을 적극적으로 도입했습니다. + +* **번들 사이즈 96.8% 감소**: `React.lazy`, `Suspense` 및 `manualChunks` 전략을 적용하여 모놀리식 번들을 분리. + * **Before**: 5MB (초기 단일 번들) + * **After**: 161KB (Core Chunk) + * **Result**: 전체 Chunk 구조를 1개 → 60개 이상으로 분산하여 TTI(Time to Interactive) 대폭 개선. +* **Lazy Loading 적용**: 랜딩 페이지 및 주요 섹션에 `Intersection Observer`를 도입하여 뷰포트에 진입할 때만 리소스를 로드하도록 개선. +* **비동기 UX 강화**: 데이터 페칭 대기 시간에 **Skeleton UI**와 **Lottie JSON 애니메이션**을 노출하여 체감 로딩 시간을 단축하고 끊김 없는 사용자 경험 제공. + +
+ +## 🏗️ 아키텍처 및 기술적 특징 (Architecture & Tech) + +### 1. PWA & FCM 알림 시스템 +* **PWA (Progressive Web App)**: `vite-plugin-pwa`를 활용하여 모바일 홈 화면 설치 지원 및 서비스 워커(Service Worker) 기반의 오프라인 캐시 전략 적용. 네트워크가 불안정한 환경에서도 핵심 기능 사용 가능. +* **Push Notification**: FCM(Firebase Cloud Messaging)을 연동하여 브라우저의 Foreground/Background 상태에 관계없이 실시간 주문/스케줄 알림 전송. + +### 2. 상태 관리 및 라우팅 (State & Routing) +* **Zustand 기반 상태 분리**: 사장님(Admin)과 알바생(Staff)의 데이터 접근 범위가 다름을 고려하여, 전역 상태 스토어를 역할별로 분리 및 경량화. +* **Role-Based Access Control (RBAC)**: + * `PublicRoute`: 비로그인 사용자 접근 + * `ProtectedRoute`: 로그인 사용자 전용 + * `RoleRoute`: 특정 권한(사장님/알바생)에 따른 페이지 접근 제어 및 리디렉션 처리 + +### 3. 디자인 시스템 (Design System) +* **Component-Driven Development**: **Tailwind CSS**와 **JSX in SVG**를 기반으로 유연한 디자인 시스템 구축. +* **Storybook 도입**: 공통 UI 컴포넌트(Button, Input, Modal 등)를 문서화하여 디자이너-개발자 간 협업 효율 증대 및 UI 일관성 유지. + +### 4. SEO & 마케팅 (SEO & Meta) +* 상용 서비스 수준의 **SEO 메타태그**, **OpenGraph**, **PWA Manifest**를 완벽하게 구성하여 SNS 공유 시 미리보기 최적화 및 검색 엔진 노출 대응. + +
+ +## 🔒 보안 및 배포 파이프라인 (Security & DevOps) + +### 🔐 클라이언트 보안 (Client Security) +* **AES 기반 데이터 암호화**: 전자근로계약서 등 민감 데이터 전송 시, 클라이언트에서 생성한 **Random Session Key**와 **Encryption Key**를 조합하여 AES 암호화 적용. +* **일회성 키 전략**: 전송 후 키를 파기하는 방식으로 데이터 탈취 및 중간자 공격(MITM) 방어. + +### ☁️ CI/CD & Infrastructure +* **Automated Deployment**: GitHub Actions를 활용하여 `dev` (개발) → `test` (테스트) → `main` (운영) 브랜치별 자동 배포 파이프라인 구축. +* **AWS Serverless**: **Amazon S3**(정적 호스팅)와 **CloudFront**(CDN)를 연동하여 전 세계 어디서든 빠른 속도로 콘텐츠를 전송하고 SSL(HTTPS) 보안 적용. + +
+ +## 📬 Team Mango-Butter + +| 이름 | 역할 | +| :-- | :-- | +| **[윤석찬](https://github.com/PaleBlueNote)** | Frontend Lead +| **정현지** | Backend Lead +| **심재엽** | Backend +| **이명건** | Designer + +--- +© 2026 Mango-Butter. All rights reserved. diff --git a/src/App.tsx b/src/App.tsx index 7fbbbae..a40e65b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -14,9 +14,10 @@ import Signup from "./pages/signup/Signup.tsx"; import AddressSearchPopup from "./pages/store/boss/AddressSearchPopup.tsx"; import RoleRoute from "./routes/RoleRoute.tsx"; import FullScreenLoading from "./components/common/FullScreenLoading.tsx"; -import HomeBoss from "./pages/home/boss/HomeBoss.tsx"; -import HomeStaff from "./pages/home/staff/HomeStaff.tsx"; -import HomeAdmin from "./pages/home/admin/HomeAdmin.tsx"; +// Lazy-loaded home pages (role-based: no need to load all roles on initial bundle) +const HomeBoss = lazy(() => import("./pages/home/boss/HomeBoss.tsx")); +const HomeStaff = lazy(() => import("./pages/home/staff/HomeStaff.tsx")); +const HomeAdmin = lazy(() => import("./pages/home/admin/HomeAdmin.tsx")); import UnifiedPWAPrompt from "./libs/fcm/UnifiedPWAPrompt.tsx"; import PdfViewerPage from "./pages/PdfViewerPage.tsx"; import SubscribePage from "./pages/mypage/boss/SubscribePage.tsx"; diff --git a/src/api/boss/store.ts b/src/api/boss/store.ts index bee3ee0..0f8643b 100644 --- a/src/api/boss/store.ts +++ b/src/api/boss/store.ts @@ -11,9 +11,23 @@ import { } from "../../types/store.ts"; export const validateBusinessNumber = async (businessNumber: string) => { + // [임시 수정] API 오류로 인해 백엔드 호출을 주석 처리하고 무조건 성공시킴 + /* return await axiosAuth.post("/api/boss/stores/validations/business-number", { businessNumber, }); + */ + + // 가짜 응답 반환 (0.5초 딜레이 후 성공 처리) + return new Promise((resolve) => { + console.log(`[Mock API] 사업자번호(${businessNumber}) 인증 통과 처리됨`); + setTimeout(() => { + resolve({ + status: 200, + data: { message: "사용 가능한 사업자 번호입니다." }, + }); + }, 500); + }); }; // 매장 등록 diff --git a/src/pages/landing/FeatureCard.tsx b/src/pages/landing/FeatureCard.tsx index 61c6726..30719a9 100644 --- a/src/pages/landing/FeatureCard.tsx +++ b/src/pages/landing/FeatureCard.tsx @@ -1,4 +1,4 @@ -import { lazy } from "react"; +import { lazy, Suspense } from "react"; interface FeatureCardProps { title: string; @@ -22,7 +22,9 @@ export const FeatureCard = ({ title, description, icon }: FeatureCardProps) => {
{title}
{description}
- + }> + + ); }; diff --git a/src/pages/landing/Landing.tsx b/src/pages/landing/Landing.tsx index c68fc9c..803271b 100644 --- a/src/pages/landing/Landing.tsx +++ b/src/pages/landing/Landing.tsx @@ -6,9 +6,9 @@ import { FeatureCard } from "./FeatureCard.tsx"; import LandingIntroLazy from "./LandingIntroLazy.tsx"; import LandingPayLazy from "./LandingPayLazy.tsx"; import LandingDocLazy from "./LandingDocLazy.tsx"; +import LogoIcon from "../../components/icons/LogoIcon.tsx"; -// 아이콘 최소화: 큰 사이즈 / 하단 렌더링만 lazy import -const LogoIcon = lazy(() => import("../../components/icons/LogoIcon.tsx")); +// 히어로 아래쪽 요소만 lazy: 초기 뷰포트 밖에 위치 const PartyIcon = lazy(() => import("../../components/icons/PartyIcon.tsx")); const Footer = lazy(() => import("../../components/layouts/Footer.tsx")); @@ -27,12 +27,10 @@ const Landing = () => {
- - - +
사장님의 부담을 줄여주는
알바생 관리의 새로운 diff --git a/src/pages/landing/LandingDocLazy.tsx b/src/pages/landing/LandingDocLazy.tsx index 32222a7..e4fd4fd 100644 --- a/src/pages/landing/LandingDocLazy.tsx +++ b/src/pages/landing/LandingDocLazy.tsx @@ -8,16 +8,16 @@ const LandingDocLazy = () => { const { ref, inView } = useInView({ triggerOnce: true, threshold: 0.2 }); return ( -
- {inView && ( +
+ {inView ? ( 문서 정보 불러오는 중...
+
문서 정보 불러오는 중...
} > - )} + ) : null}
); }; diff --git a/src/pages/landing/LandingDocSection.tsx b/src/pages/landing/LandingDocSection.tsx index 1681f8a..dd133c4 100644 --- a/src/pages/landing/LandingDocSection.tsx +++ b/src/pages/landing/LandingDocSection.tsx @@ -1,7 +1,13 @@ // src/pages/LandingDocSection.tsx -import BriefCaseIcon from "../../components/icons/BriefCaseIcon.tsx"; -import LandingDoc from "../../components/icons/LandingDoc.tsx"; -import LandingSign from "../../components/icons/LandingSign.tsx"; +import { lazy, Suspense } from "react"; + +const BriefCaseIcon = lazy( + () => import("../../components/icons/BriefCaseIcon.tsx"), +); +const LandingDoc = lazy(() => import("../../components/icons/LandingDoc.tsx")); +const LandingSign = lazy( + () => import("../../components/icons/LandingSign.tsx"), +); const LandingDocSection = () => { return ( @@ -9,7 +15,13 @@ const LandingDocSection = () => {
- + + } + > + +
@@ -26,8 +38,20 @@ const LandingDocSection = () => {
- - + + } + > + + + + } + > + +
); diff --git a/src/pages/landing/LandingIntroLazy.tsx b/src/pages/landing/LandingIntroLazy.tsx index 49e08f0..e8845f9 100644 --- a/src/pages/landing/LandingIntroLazy.tsx +++ b/src/pages/landing/LandingIntroLazy.tsx @@ -8,16 +8,16 @@ const LandingIntroLazy = () => { const { ref, inView } = useInView({ triggerOnce: true, threshold: 0.2 }); return ( -
- {inView && ( +
+ {inView ? ( 소개 섹션 불러오는 중...
+
소개 섹션 불러오는 중...
} > - )} + ) : null}
); }; diff --git a/src/pages/landing/LandingIntroSection.tsx b/src/pages/landing/LandingIntroSection.tsx index b7c592b..c4f337d 100644 --- a/src/pages/landing/LandingIntroSection.tsx +++ b/src/pages/landing/LandingIntroSection.tsx @@ -1,7 +1,19 @@ // src/pages/LandingIntroSection.tsx -import LandingProblem from "../../components/icons/LandingProblem.tsx"; -import LandingArrow from "../../components/icons/LandingArrow.tsx"; -import LandingGroup from "../../components/icons/LandingGroup.tsx"; +import { lazy, Suspense } from "react"; + +const LandingProblem = lazy( + () => import("../../components/icons/LandingProblem.tsx"), +); +const LandingArrow = lazy( + () => import("../../components/icons/LandingArrow.tsx"), +); +const LandingGroup = lazy( + () => import("../../components/icons/LandingGroup.tsx"), +); + +const IconSkeleton = ({ height = "h-48" }: { height?: string }) => ( +
+); const LandingIntroSection = () => { return ( @@ -10,9 +22,15 @@ const LandingIntroSection = () => { 망고보스는
이렇게 만들어졌어요!
- - - + }> + + + }> + + + }> + +
); }; diff --git a/src/pages/landing/LandingPayLazy.tsx b/src/pages/landing/LandingPayLazy.tsx index 7cc6948..b08d05f 100644 --- a/src/pages/landing/LandingPayLazy.tsx +++ b/src/pages/landing/LandingPayLazy.tsx @@ -8,16 +8,16 @@ const LandingPayLazy = () => { const { ref, inView } = useInView({ triggerOnce: true, threshold: 0.2 }); return ( -
- {inView && ( +
+ {inView ? ( 급여 정보 불러오는 중...
+
급여 정보 불러오는 중...
} > - )} + ) : null}
); }; diff --git a/src/pages/landing/LandingPaySection.tsx b/src/pages/landing/LandingPaySection.tsx index 68af8aa..dd97b27 100644 --- a/src/pages/landing/LandingPaySection.tsx +++ b/src/pages/landing/LandingPaySection.tsx @@ -1,6 +1,10 @@ // src/pages/LandingPaySection.tsx -import MoneyIcon from "../../components/icons/MoneyIcon.tsx"; -import LandingCoin from "../../components/icons/LandingCoin.tsx"; +import { lazy, Suspense } from "react"; + +const MoneyIcon = lazy(() => import("../../components/icons/MoneyIcon.tsx")); +const LandingCoin = lazy( + () => import("../../components/icons/LandingCoin.tsx"), +); const LandingPaySection = () => { return ( @@ -8,7 +12,13 @@ const LandingPaySection = () => {
- + + } + > + +
@@ -25,7 +35,13 @@ const LandingPaySection = () => {
- + + } + > + +
); diff --git a/vite.config.ts b/vite.config.ts index 4cfbe78..ae90b52 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -50,6 +50,10 @@ export default defineConfig({ if (id.includes("react-hook-form")) return "vendor-form"; if (id.includes("zod")) return "vendor-zod"; if (id.includes("axios")) return "vendor-axios"; + // 대형 라이브러리 별도 분리: vendor-others 과부하 방지 + if (id.includes("firebase")) return "vendor-firebase"; + if (id.includes("chart.js") || id.includes("recharts")) return "vendor-charts"; + if (id.includes("tosspayments")) return "vendor-toss"; return "vendor-others"; }