From 7a6fee70c18a119e65caea72720325eea3897ace Mon Sep 17 00:00:00 2001 From: Mina Choi Date: Fri, 4 Sep 2026 17:29:13 +0900 Subject: [PATCH] =?UTF-8?q?[fix]=20solution/frontend:=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=ED=95=B4=EB=8F=84=20=EB=9E=9C=EB=94=A9=C2=B7=EC=9A=94?= =?UTF-8?q?=EA=B8=88=EC=97=90=20=EA=B0=88=20=EA=B8=B8=EC=9D=84=20=EB=82=B8?= =?UTF-8?q?=EB=8B=A4=20=E2=80=94=20'/'=20=EA=B4=80=EB=AC=B8=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 로그인하면 '/' 가 무조건 /sites 로 튕겼다. 그래서 **로고를 눌러도 랜딩이 안 뜨고**, /pricing 은 살아 있는데 링크가 랜딩과 MarketingShell 에만 있어 로그인한 사장님은 주소를 직접 쳐야 했다. 요금은 쓰는 도중에 확인하는 값이지 가입 전에만 보는 값이 아니다. - app/router.tsx: Home 의 리다이렉트 제거. '/' 는 누구에게나 랜딩이다. "로그인 직후엔 내 사이트로" 는 로그인·가입 화면이 직접 보낸다. - pages/LoginPage.tsx: 도착지를 `homePath` prop 으로 받는다. 기본값 '/' 라 내부 운영 앱(자기 '/' 가 사업장 목록으로 간다)은 그대로다. 사장님 앱만 '/sites'. - pages/SignupPage.tsx: 가입 후 '/' → '/sites'. 안 그러면 관문이 없어진 지금 랜딩에 떨어진다. - 로그인·가입 화면 로고에 '/' 링크. 그 화면에서 빠져나갈 길이 하나도 없었다. - layout/AppShell.tsx: 로그아웃 → '/login' 이 아니라 '/'. 나간 사람에게 로그인 폼을 다시 들이밀지 않는다. 사이드바에 [요금] 추가. - layout/MarketingShell.tsx: 헤더 [무료로 만들기] 제거 — 히어로 입력 카드가 이미 그 자리다. 시작하는 문이 한 화면에 둘이면 어느 쪽이 진짜인지 고르게 만든다. 검증: tsc·eslint 통과. docker compose up -d --build solution-site 로 띄워 눌러 확인 — 로그인 상태에서 로고 → 랜딩, 헤더는 [이렇게 나옵니다 · 요금 · 내 사이트]. --- solution/frontend/src/app/router.tsx | 10 +++++-- .../src/components/layout/AppShell.tsx | 14 +++++++-- .../src/components/layout/MarketingShell.tsx | 26 +++++++--------- solution/frontend/src/pages/LoginPage.tsx | 30 +++++++++++++------ solution/frontend/src/pages/SignupPage.tsx | 11 ++++--- 5 files changed, 59 insertions(+), 32 deletions(-) diff --git a/solution/frontend/src/app/router.tsx b/solution/frontend/src/app/router.tsx index f8f6066..2621c72 100644 --- a/solution/frontend/src/app/router.tsx +++ b/solution/frontend/src/app/router.tsx @@ -32,12 +32,18 @@ function Home() { ); } - if (user) return ; + /* + * ★ 로그인해도 랜딩을 보여 준다 (2026-09-04, 사장님 지적) + * 예전엔 여기서 /sites 로 튕겼다. 그러면 **로그인한 사장님은 랜딩·요금에 갈 길이 없다** — + * 로고가 '/' 를 걸고 있어도 눌리는 순간 목록으로 돌아온다. + * "로그인 직후엔 내 사이트로" 는 로그인·가입 화면이 직접 보낸다(LoginPage homePath). + */ + void user; return ; } export const router = createBrowserRouter([ - {path: '/login', element: }, + {path: '/login', element: }, // 로그인 화면의 [회원가입] 이 여기로 온다. 이 줄이 없으면 링크는 있고 목적지만 404 다. {path: '/signup', element: }, diff --git a/solution/frontend/src/components/layout/AppShell.tsx b/solution/frontend/src/components/layout/AppShell.tsx index 990aba0..8fa7763 100644 --- a/solution/frontend/src/components/layout/AppShell.tsx +++ b/solution/frontend/src/components/layout/AppShell.tsx @@ -1,6 +1,6 @@ import type {ComponentType, ReactNode} from 'react'; import {Link, NavLink, useLocation, useNavigate} from 'react-router'; -import {LayoutGrid, LogIn, LogOut, Search, Store, Wand2} from 'lucide-react'; +import {LayoutGrid, LogIn, LogOut, Receipt, Search, Store, Wand2} from 'lucide-react'; import {cn} from '@/lib/utils'; import {userLabel, useAuthStore} from '@/stores/auth'; @@ -25,6 +25,15 @@ export type NavItem = { const OWNER_NAV: NavItem[] = [ {to: '/sites', match: '/sites', label: '내 사이트', icon: Store}, {to: '/builder?new=1', match: '/builder', label: '새 사이트', icon: Wand2}, + /* + * ★ 요금은 **로그인한 뒤에도** 갈 길이 있어야 한다 (2026-09-04, 사장님 지적) + * /pricing 은 살아 있는데 링크가 MarketingShell 과 랜딩에만 있었다. 둘 다 로그인하면 + * 안 보이는 화면이라(로그인하면 / 가 /sites 로 간다), 사장님은 주소를 직접 쳐야 했다. + * 요금은 쓰는 도중에 확인하는 값이지 가입 전에만 보는 값이 아니다. + * ★ 목적지가 MarketingShell(사이드바 없는 문서형)이라 사이드바가 사라진다. 그래도 갇히지 + * 않는다 — 저쪽 헤더가 로그인 상태를 알아보고 [내 사이트] 버튼을 세운다. + */ + {to: '/pricing', match: '/pricing', label: '요금', icon: Receipt}, ]; export function AppShell({children, nav = OWNER_NAV}: {children: ReactNode; nav?: NavItem[]}) { @@ -87,7 +96,8 @@ export function AppShell({children, nav = OWNER_NAV}: {children: ReactNode; nav? // 눌러도 아무 일이 없는 것처럼 보인다 — 로그인 화면으로 보낸다. onClick={() => { signOut(); - navigate('/login'); + // ★ 로그인 화면이 아니라 랜딩으로. 나간 사람에게 다시 로그인 폼을 들이밀지 않는다. + navigate('/'); }} className="flex w-full cursor-pointer items-center gap-1.5 rounded-md px-2 py-1.5 text-xs text-sidebar-foreground transition-colors hover:bg-sidebar-accent/60" > diff --git a/solution/frontend/src/components/layout/MarketingShell.tsx b/solution/frontend/src/components/layout/MarketingShell.tsx index e52aa77..2acf72b 100644 --- a/solution/frontend/src/components/layout/MarketingShell.tsx +++ b/solution/frontend/src/components/layout/MarketingShell.tsx @@ -47,6 +47,11 @@ export function MarketingShell({children}: {children: ReactNode}) {
{/* ★ 이미 사이트를 가진 사장님에게 [로그인] 을 다시 보여주지 않는다 — 갈 곳은 내 사이트다. */} + {/* + ★ [무료로 만들기] 를 뺐다 (2026-09-04, 사장님 지시) + 히어로의 큰 입력 카드가 이미 그 자리다 — 시작하는 문이 한 화면에 둘이면 + 어느 쪽이 진짜인지 고르게 만든다. 헤더는 로그인만 받는다. + */} {user ? ( ) : ( - <> - {/* 아임웹처럼 둘 다 버튼이다 — 로그인만 맨 텍스트면 눌리는 것으로 안 보인다. */} - - 로그인 - - - 무료로 만들기 - - + + 로그인 + )}
diff --git a/solution/frontend/src/pages/LoginPage.tsx b/solution/frontend/src/pages/LoginPage.tsx index 863eb26..08478b3 100644 --- a/solution/frontend/src/pages/LoginPage.tsx +++ b/solution/frontend/src/pages/LoginPage.tsx @@ -17,7 +17,21 @@ import {useAuthStore} from '@/stores/auth'; * 우리가 만들어 준다. 가입 링크를 두 곳에 다 두면 admin 라우터에 없는 `/signup` 으로 보내 * 404 가 난다(admin/src/app/router.tsx 에 그 경로는 없다). */ -export function LoginPage({selfServe = true}: {selfServe?: boolean}) { +export function LoginPage({ + selfServe = true, + homePath = '/', +}: { + selfServe?: boolean; + /** + * 로그인 직후 도착지. + * + * ★ 예전엔 '/' 로 보내고 각 앱의 '/' 리다이렉트가 알아서 가르게 했다. 그런데 사장님 앱에서 + * 그 관문이 **로그인한 사람에게 랜딩을 영영 못 보게** 만들었다 — 로고를 눌러도 '/' 가 + * /sites 로 튕긴다(사장님 지적). 관문을 걷고 도착지를 여기서 받는다. + * ★ 기본값은 '/' 다. 내부 운영 앱은 자기 '/' 가 사업장 목록으로 보내므로 그대로 맞는다. + */ + homePath?: string; +}) { const navigate = useNavigate(); const location = useLocation(); const user = useAuthStore((s) => s.user); @@ -28,9 +42,8 @@ export function LoginPage({selfServe = true}: {selfServe?: boolean}) { const [password, setPassword] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); - // ★ 기본 도착지를 '/' 로 둔다. 앱마다 홈이 다르고(사장님 → 빌더, 내부 → 사업장 목록) - // 각 라우터의 '/' 리다이렉트가 이미 그걸 안다. 여기 경로를 박으면 한쪽에서 404 다. - const from = (location.state as {from?: string} | null)?.from ?? '/'; + // 원래 가려던 곳이 있으면 그리로, 없으면 앱이 준 홈으로. + const from = (location.state as {from?: string} | null)?.from ?? homePath; if (user) return ; @@ -82,11 +95,10 @@ export function LoginPage({selfServe = true}: {selfServe?: boolean}) { className="w-full max-w-sm space-y-4 rounded-2xl border border-border bg-card p-7" >
- Web4Ai + {/* 로고는 어디서든 랜딩으로 돌아가는 문이다. 로그인 화면에서 나갈 길이 여기뿐이었다. */} + + Web4Ai +

{selfServe ? '로그인' : '관리자'}

diff --git a/solution/frontend/src/pages/SignupPage.tsx b/solution/frontend/src/pages/SignupPage.tsx index 4d2691b..0ec2026 100644 --- a/solution/frontend/src/pages/SignupPage.tsx +++ b/solution/frontend/src/pages/SignupPage.tsx @@ -35,7 +35,7 @@ export function SignupPage() { }); const [isSubmitting, setIsSubmitting] = useState(false); - if (user) return ; + if (user) return ; const set = (key: keyof typeof form) => (e: {target: {value: string}}) => setForm((prev) => ({...prev, [key]: e.target.value})); @@ -76,7 +76,7 @@ export function SignupPage() { return; } notify.success('가입이 완료되었습니다.'); - navigate('/', {replace: true}); + navigate('/sites', {replace: true}); } catch (error) { notifyApiError(error, '가입하지 못했습니다.'); } finally { @@ -97,7 +97,7 @@ export function SignupPage() { notifyApiError({data: res}, '로그인 응답에 토큰이 없습니다.'); return; } - navigate('/', {replace: true}); + navigate('/sites', {replace: true}); } catch (error) { notifyApiError(error, '구글로 가입하지 못했습니다.'); } finally { @@ -112,7 +112,10 @@ export function SignupPage() { className="w-full max-w-sm space-y-4 rounded-2xl border border-border bg-card p-7" >
- Web4Ai + {/* 로고는 어디서든 랜딩으로 돌아가는 문이다. */} + + Web4Ai +

회원가입

내 가게 사이트를 만들려면 계정이 필요합니다.