[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 {Link, NavLink, useLocation} from 'react-router';
|
||||||
import {LayoutGrid, LogOut, Search, Wand2} from 'lucide-react';
|
import {LayoutGrid, LogOut, Search, Wand2} from 'lucide-react';
|
||||||
import {cn} from '@/lib/utils';
|
import {cn} from '@/lib/utils';
|
||||||
import {useAuthStore} from '@/stores/auth';
|
import {userLabel, useAuthStore} from '@/stores/auth';
|
||||||
|
|
||||||
export type NavItem = {
|
export type NavItem = {
|
||||||
to: string;
|
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="border-t border-sidebar-border p-3">
|
||||||
<div className="mb-2 truncate text-[11px] text-sidebar-foreground">
|
<div className="mb-2 truncate text-[11px] text-sidebar-foreground">
|
||||||
{user?.name ?? user?.id}
|
{user ? userLabel(user) : ''}
|
||||||
{user?.companyName ? ` · ${user.companyName}` : ''}
|
{user?.companyName ? ` · ${user.companyName}` : ''}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import {useEffect, useRef} from 'react';
|
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 {Link, useSearchParams} from 'react-router';
|
||||||
import {SiteStatus} from '@o2o/shared';
|
import {SiteStatus} from '@o2o/shared';
|
||||||
import {AppShell} from '@/components/layout/AppShell';
|
import {AppShell} from '@/components/layout/AppShell';
|
||||||
@ -13,6 +13,7 @@ import {
|
|||||||
import {EditorLayout} from '@/features/builder';
|
import {EditorLayout} from '@/features/builder';
|
||||||
import {usePlaceSync} from '@/hooks/usePlaceSync';
|
import {usePlaceSync} from '@/hooks/usePlaceSync';
|
||||||
import {EDITOR_STEP, useBuilderStore} from '@/stores/builder';
|
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;
|
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 step = useBuilderStore((s) => s.step);
|
||||||
const goToStep = useBuilderStore((s) => s.goToStep);
|
const goToStep = useBuilderStore((s) => s.goToStep);
|
||||||
const storeName = useBuilderStore((s) => s.storeName);
|
const storeName = useBuilderStore((s) => s.storeName);
|
||||||
|
// 에디터는 AppShell(사이드바)을 안 쓴다 — 누구로 로그인했는지·나가는 길이 여기 없으면 아예 없다.
|
||||||
|
const user = useAuthStore((s) => s.user);
|
||||||
|
const signOut = useAuthStore((s) => s.signOut);
|
||||||
// 배지는 주소창이 아니라 스토어가 기준이다 — [처음부터]로 데모로 돌아간 뒤에도
|
// 배지는 주소창이 아니라 스토어가 기준이다 — [처음부터]로 데모로 돌아간 뒤에도
|
||||||
// 주소창에는 placeId 가 남아 있어서, 그걸 믿으면 데모를 실사업장이라고 표시한다.
|
// 주소창에는 placeId 가 남아 있어서, 그걸 믿으면 데모를 실사업장이라고 표시한다.
|
||||||
const wiredPlaceId = useBuilderStore((s) => s.placeId);
|
const wiredPlaceId = useBuilderStore((s) => s.placeId);
|
||||||
@ -144,6 +148,7 @@ export function BuilderPage() {
|
|||||||
{/* 캔버스는 미리보기다. 진짜 발행본은 별도 렌더러(site)가 굽는다 —
|
{/* 캔버스는 미리보기다. 진짜 발행본은 별도 렌더러(site)가 굽는다 —
|
||||||
같은 화면을 두 번 구현하지 않고, 그쪽을 새 탭으로 연다.
|
같은 화면을 두 번 구현하지 않고, 그쪽을 새 탭으로 연다.
|
||||||
★ 발행 전에는 열지 않는다 — 굽지 않은 주소를 열면 404 다. */}
|
★ 발행 전에는 열지 않는다 — 굽지 않은 주소를 열면 404 다. */}
|
||||||
|
<div className="flex shrink-0 items-center gap-2">
|
||||||
{publishedUrl ? (
|
{publishedUrl ? (
|
||||||
<a
|
<a
|
||||||
href={publishedUrl}
|
href={publishedUrl}
|
||||||
@ -164,6 +169,28 @@ export function BuilderPage() {
|
|||||||
<span>발행본 사이트 열기</span>
|
<span>발행본 사이트 열기</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>
|
||||||
|
|
||||||
<div className="min-h-0 flex-1">
|
<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 {
|
interface AuthState {
|
||||||
user: AuthUser | null;
|
user: AuthUser | null;
|
||||||
/** 부팅 시 저장된 토큰을 확인하기 전까지 true. 가드가 이 동안 리다이렉트를 미룬다. */
|
/** 부팅 시 저장된 토큰을 확인하기 전까지 true. 가드가 이 동안 리다이렉트를 미룬다. */
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user