From af391b4c567a71608a810611ea7274e8679159a7 Mon Sep 17 00:00:00 2001 From: Mina Choi Date: Thu, 3 Sep 2026 10:21:16 +0900 Subject: [PATCH] =?UTF-8?q?[feat]=20solution/frontend,docs:=20=EB=9E=9C?= =?UTF-8?q?=EB=94=A9=20=C2=B7=20=EC=9A=94=EA=B8=88=20=C2=B7=20=EC=87=BC?= =?UTF-8?q?=EC=BC=80=EC=9D=B4=EC=8A=A4=20=E2=80=94=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EC=A0=84=20=ED=99=94=EB=A9=B4=EC=9D=B4=20=EC=97=86?= =?UTF-8?q?=EC=97=88=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `/` 가 곧장 위저드로 튀어서 이 제품이 무엇을 파는 물건인지 말할 자리가 없었다. 처음 온 사람이 업종 선택 화면부터 만난다. - router: `/` 는 비로그인 랜딩 · 로그인 /sites. /pricing · /showcase 추가 - MarketingShell: 사이드바 없는 문서형 껍데기. AppShell(작업 화면)과 나눴다 - 랜딩 상단은 상호명 한 칸. 문구는 SEO·AEO 축으로만 쓴다 — '쉽게·빠르게'로 말하면 홈페이지 빌더와 같은 자리에서 비교당한다(PRODUCT 1절) - ShowcaseGrid: 발행 썸네일을 그대로 건다. 예시 데이터로 채우지 않고, 없으면 섹션을 감춘다. ★ 생성 클라이언트를 안 쓴다 — 토큰 길목을 지나면 비로그인에서 못 부른다 - 요금은 플랜 하나(70만원/월) + 월 산출물. 비교표를 만들지 않는다 tsc·eslint·vite build 통과 --- docs/DEVLOG.md | 18 ++ solution/frontend/src/app/router.tsx | 21 +- .../src/components/layout/AppShell.tsx | 4 +- .../src/components/layout/MarketingShell.tsx | 117 ++++++++ .../src/features/marketing/ShowcaseGrid.tsx | 102 +++++++ .../src/features/marketing/showcaseApi.ts | 46 +++ solution/frontend/src/pages/LandingPage.tsx | 264 ++++++++++++++++++ solution/frontend/src/pages/PricingPage.tsx | 109 ++++++++ solution/frontend/src/pages/ShowcasePage.tsx | 37 +++ 9 files changed, 710 insertions(+), 8 deletions(-) create mode 100644 solution/frontend/src/components/layout/MarketingShell.tsx create mode 100644 solution/frontend/src/features/marketing/ShowcaseGrid.tsx create mode 100644 solution/frontend/src/features/marketing/showcaseApi.ts create mode 100644 solution/frontend/src/pages/LandingPage.tsx create mode 100644 solution/frontend/src/pages/PricingPage.tsx create mode 100644 solution/frontend/src/pages/ShowcasePage.tsx diff --git a/docs/DEVLOG.md b/docs/DEVLOG.md index fcbfb82..663b230 100644 --- a/docs/DEVLOG.md +++ b/docs/DEVLOG.md @@ -5,6 +5,24 @@ --- +## 2026-09-03 — 랜딩 · 요금 · 쇼케이스 — 로그인 전 화면이 생겼다 + +**왜** +`/` 가 곧장 위저드로 튀어서, 이 제품이 무엇을 파는 물건인지 말할 자리가 한 곳도 없었다. +처음 온 사람이 업종 선택 화면부터 만난다. + +**한 일** +- `/` 는 비로그인이면 랜딩, 로그인이면 `/sites`. `/pricing` · `/showcase` 신설 +- `MarketingShell` — 사이드바 없는 문서형 껍데기. `AppShell` 은 작업 화면이라 나눴다 + (b07ade2 가 온보딩에서 사이드바를 뺀 것과 같은 판단) +- 랜딩 상단은 **상호명 한 칸**이다. 업종 칩은 "누구를 위한 서비스인가"를 말하는 용도이고 + 고르지 않아도 된다 — 업종은 검색 결과가 정한다 +- 쇼케이스는 발행 썸네일을 그대로 건다. **예시 데이터로 채우지 않는다** — 이 섹션이 파는 건 + "진짜로 나갔다"는 사실 하나라, 가짜를 걸면 그 자리에서 가치가 0 이다. 없으면 섹션을 감춘다 +- 요금은 플랜 하나(70만원/월). 비교표를 만들지 않는다 — 고를 것이 가격대가 아니다 + +**검증** — tsc·eslint·vite build 통과. + ## 2026-09-03 — 상호명 검색을 로그인 앞으로 · 업종은 LLM 없이 정한다 **왜** diff --git a/solution/frontend/src/app/router.tsx b/solution/frontend/src/app/router.tsx index ccd6b79..f8f6066 100644 --- a/solution/frontend/src/app/router.tsx +++ b/solution/frontend/src/app/router.tsx @@ -3,8 +3,11 @@ import {Loader2} from 'lucide-react'; import {AccountPage} from '@/pages/AccountPage'; import {BuilderPage} from '@/pages/BuilderPage'; import {DevShowcasePage} from '@/pages/DevShowcasePage'; +import {LandingPage} from '@/pages/LandingPage'; import {LoginPage} from '@/pages/LoginPage'; import {NotFoundPage} from '@/pages/NotFoundPage'; +import {PricingPage} from '@/pages/PricingPage'; +import {ShowcasePage} from '@/pages/ShowcasePage'; import {SignupPage} from '@/pages/SignupPage'; import {SitesPage} from '@/pages/SitesPage'; import {RequireAuth} from '@/components/layout/RequireAuth'; @@ -12,9 +15,11 @@ import {useAuthStore} from '@/stores/auth'; /** * 첫 화면은 로그인 여부로 갈린다 — 이미 사이트를 가진 사장님의 용건은 "새로 만들기"가 아니라 - * "내 것 고치기"다. 비로그인은 그대로 위저드로 보낸다(관문은 에디터 진입이다). + * "내 것 고치기"다. 비로그인은 **랜딩**을 본다. * - * ★ 복구가 끝나기 전에 판단하면 새로고침할 때마다 위저드가 한 번 번쩍이고 목록으로 튄다. + * ★ 예전엔 비로그인을 곧장 위저드로 보냈다. 그러면 이 제품이 무엇을 파는 물건인지 말할 자리가 + * 한 곳도 없다 — 처음 온 사람이 업종 선택 화면부터 만난다. + * ★ 복구가 끝나기 전에 판단하면 새로고침할 때마다 화면이 한 번 번쩍이고 목록으로 튄다. */ function Home() { const isRestoring = useAuthStore((s) => s.isRestoring); @@ -27,7 +32,8 @@ function Home() { ); } - return ; + if (user) return ; + return ; } export const router = createBrowserRouter([ @@ -35,12 +41,13 @@ export const router = createBrowserRouter([ // 로그인 화면의 [회원가입] 이 여기로 온다. 이 줄이 없으면 링크는 있고 목적지만 404 다. {path: '/signup', element: }, - // ★ 비로그인의 첫 화면은 업종 선택(위저드 1단계)이다. - // `?new=1` 을 붙이는 이유: 위저드 상태는 새로고침을 넘기려고 저장돼 있어서(stores/builder persist), - // 그냥 /builder 로 보내면 지난번에 만들다 만 **에디터**가 복원돼 뜬다. 처음 들어오는 사람에게는 - // 그게 "왜 자꾸 빌더로 튀냐"로 보인다. 그래서 진입 경로에서 한 번 비우고 시작한다. + // 비로그인 = 랜딩, 로그인 = 내 사이트. Home 이 그걸 가른다. {path: '/', element: }, + // 로그인 전 화면. ★ 랜딩과 같은 껍데기(MarketingShell)를 쓴다 — 사이드바 없는 문서형이다. + {path: '/pricing', element: }, + {path: '/showcase', element: }, + // 로그인한 사장님의 홈. 만든 사이트를 열고 고치는 자리다. { path: '/sites', diff --git a/solution/frontend/src/components/layout/AppShell.tsx b/solution/frontend/src/components/layout/AppShell.tsx index 55f4384..990aba0 100644 --- a/solution/frontend/src/components/layout/AppShell.tsx +++ b/solution/frontend/src/components/layout/AppShell.tsx @@ -36,7 +36,9 @@ export function AppShell({children, nav = OWNER_NAV}: {children: ReactNode; nav? return (