[feat] solution/frontend: 에디터에 로그인 사용자 표시 — 6단계엔 신원도 나가는 길도 없었다
위저드(1~5단계)는 AppShell 사이드바가 사용자와 [로그아웃]을 들고 있는데, 에디터(6단계)는 전체 화면이라 AppShell 을 안 쓴다. 그래서 편집 화면에 들어가는 순간 **누구로 로그인했는지도, 나가는 방법도 화면에서 사라졌다.** - BuilderPage: 에디터 상단 바 오른쪽에 사용자 · 상호와 [로그아웃] 추가 - stores/auth.userLabel: 이름 → 이메일 → 아이디 순. 구글 계정의 로그인 아이디는 google_<sub> 라 그대로 보이면 안 된다. AppShell 도 같은 규칙을 쓰게 바꿨다 (기존 `name ?? id` 는 이름이 빈 문자열이면 그대로 통과시켰다) 브라우저 확인: 가입 → 로그인 → 사이드바 '김사장 · 달빛스테이', 에디터 상단 바 동일 표시, [로그아웃] 클릭 시 RequireAuth 가 /login 으로 되돌림. tsc·eslint·vite build 통과.
This commit is contained in:
parent
5fa83933e4
commit
4d6802b4f2
@ -2,7 +2,7 @@ import type {ComponentType, ReactNode} from 'react';
|
||||
import {Link, NavLink, useLocation} from 'react-router';
|
||||
import {LayoutGrid, LogOut, Search, Wand2} from 'lucide-react';
|
||||
import {cn} from '@/lib/utils';
|
||||
import {useAuthStore} from '@/stores/auth';
|
||||
import {userLabel, useAuthStore} from '@/stores/auth';
|
||||
|
||||
export type NavItem = {
|
||||
to: string;
|
||||
@ -60,7 +60,7 @@ export function AppShell({children, nav = OWNER_NAV}: {children: ReactNode; nav?
|
||||
|
||||
<div className="border-t border-sidebar-border p-3">
|
||||
<div className="mb-2 truncate text-[11px] text-sidebar-foreground">
|
||||
{user?.name ?? user?.id}
|
||||
{user ? userLabel(user) : ''}
|
||||
{user?.companyName ? ` · ${user.companyName}` : ''}
|
||||
</div>
|
||||
<button
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import {useEffect, useRef} from 'react';
|
||||
import {ArrowLeft, ExternalLink, Loader2, TriangleAlert} from 'lucide-react';
|
||||
import {ArrowLeft, ExternalLink, Loader2, LogOut, TriangleAlert} from 'lucide-react';
|
||||
import {Link, useSearchParams} from 'react-router';
|
||||
import {SiteStatus} from '@o2o/shared';
|
||||
import {AppShell} from '@/components/layout/AppShell';
|
||||
@ -13,6 +13,7 @@ import {
|
||||
import {EditorLayout} from '@/features/builder';
|
||||
import {usePlaceSync} from '@/hooks/usePlaceSync';
|
||||
import {EDITOR_STEP, useBuilderStore} from '@/stores/builder';
|
||||
import {userLabel, useAuthStore} from '@/stores/auth';
|
||||
|
||||
/** 발행 사이트 렌더러의 개발 서버. 프로덕션에서는 실제 발행 주소로 바뀐다. */
|
||||
const SITE_PREVIEW_URL = import.meta.env.VITE_SITE_PREVIEW_URL ?? window.location.origin;
|
||||
@ -80,6 +81,9 @@ export function BuilderPage() {
|
||||
const step = useBuilderStore((s) => s.step);
|
||||
const goToStep = useBuilderStore((s) => s.goToStep);
|
||||
const storeName = useBuilderStore((s) => s.storeName);
|
||||
// 에디터는 AppShell(사이드바)을 안 쓴다 — 누구로 로그인했는지·나가는 길이 여기 없으면 아예 없다.
|
||||
const user = useAuthStore((s) => s.user);
|
||||
const signOut = useAuthStore((s) => s.signOut);
|
||||
// 배지는 주소창이 아니라 스토어가 기준이다 — [처음부터]로 데모로 돌아간 뒤에도
|
||||
// 주소창에는 placeId 가 남아 있어서, 그걸 믿으면 데모를 실사업장이라고 표시한다.
|
||||
const wiredPlaceId = useBuilderStore((s) => s.placeId);
|
||||
@ -144,26 +148,49 @@ export function BuilderPage() {
|
||||
{/* 캔버스는 미리보기다. 진짜 발행본은 별도 렌더러(site)가 굽는다 —
|
||||
같은 화면을 두 번 구현하지 않고, 그쪽을 새 탭으로 연다.
|
||||
★ 발행 전에는 열지 않는다 — 굽지 않은 주소를 열면 404 다. */}
|
||||
{publishedUrl ? (
|
||||
<a
|
||||
href={publishedUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex shrink-0 items-center gap-1 rounded-md bg-warning px-2.5 py-1 font-bold text-black transition-all hover:opacity-90"
|
||||
>
|
||||
<ExternalLink className="size-3" />
|
||||
<span>발행본 사이트 열기</span>
|
||||
</a>
|
||||
) : (
|
||||
<span
|
||||
title="아직 발행 전입니다 — [사이트 발행] 을 마치면 열립니다."
|
||||
aria-disabled="true"
|
||||
className="flex shrink-0 cursor-not-allowed items-center gap-1 rounded-md bg-white/10 px-2.5 py-1 font-bold text-background/50"
|
||||
>
|
||||
<ExternalLink className="size-3" />
|
||||
<span>발행본 사이트 열기</span>
|
||||
</span>
|
||||
)}
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
{publishedUrl ? (
|
||||
<a
|
||||
href={publishedUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex shrink-0 items-center gap-1 rounded-md bg-warning px-2.5 py-1 font-bold text-black transition-all hover:opacity-90"
|
||||
>
|
||||
<ExternalLink className="size-3" />
|
||||
<span>발행본 사이트 열기</span>
|
||||
</a>
|
||||
) : (
|
||||
<span
|
||||
title="아직 발행 전입니다 — [사이트 발행] 을 마치면 열립니다."
|
||||
aria-disabled="true"
|
||||
className="flex shrink-0 cursor-not-allowed items-center gap-1 rounded-md bg-white/10 px-2.5 py-1 font-bold text-background/50"
|
||||
>
|
||||
<ExternalLink className="size-3" />
|
||||
<span>발행본 사이트 열기</span>
|
||||
</span>
|
||||
)}
|
||||
|
||||
{user && (
|
||||
<>
|
||||
<span className="h-3.5 w-px bg-white/20" />
|
||||
<span
|
||||
className="max-w-[14rem] truncate text-background/70"
|
||||
title={user.companyName ? `${userLabel(user)} · ${user.companyName}` : userLabel(user)}
|
||||
>
|
||||
{userLabel(user)}
|
||||
{user.companyName ? ` · ${user.companyName}` : ''}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={signOut}
|
||||
className="flex shrink-0 cursor-pointer items-center gap-1 rounded-md px-1.5 py-1 text-background/70 transition-colors hover:bg-white/10 hover:text-background"
|
||||
>
|
||||
<LogOut className="size-3" />
|
||||
<span>로그아웃</span>
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="min-h-0 flex-1">
|
||||
|
||||
@ -31,6 +31,14 @@ export function toAuthUser(res: ResMe): AuthUser {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 화면에 띄울 이름. 구글 계정의 로그인 아이디는 `google_<sub>` 라 그대로 보이면 안 된다 —
|
||||
* 이름 → 이메일 순으로 떨어뜨리고 아이디는 마지막이다.
|
||||
*/
|
||||
export function userLabel(user: AuthUser): string {
|
||||
return user.name || user.email || user.id;
|
||||
}
|
||||
|
||||
interface AuthState {
|
||||
user: AuthUser | null;
|
||||
/** 부팅 시 저장된 토큰을 확인하기 전까지 true. 가드가 이 동안 리다이렉트를 미룬다. */
|
||||
|
||||
Loading…
Reference in New Issue
Block a user