/* ============================================================
Login screen + App shell (sidebar / topbar / project tabs)
============================================================ */
const { useState: useS1, useEffect: useE1 } = React;
/* ---------------- LOGIN ---------------- */
const SSO_DOMAINS = ['lge.com', 'concentrix.com', '직접입력'];
/* role display helpers (ADMIN > CNX > LGE) */
const roleColor = (r) => r === 'ADMIN' ? 'var(--ink)' : r === 'CNX' ? 'var(--cnx-blue)' : 'var(--lg-red)';
const orgOf = (r) => r === 'ADMIN' ? '운영 관리자' : r === 'CNX' ? 'Concentrix' : 'LG Electronics';
const roleDesc = (r) => r === 'ADMIN' ? '시스템 관리' : r === 'CNX' ? '콘텐츠 관리' : '조회·선택·제출';
/* Shared SSO email field: local-part + domain select, or direct full-email entry.
Parent owns the state and derives the final email from these three pieces. */
function SsoEmailField({ localPart, setLocalPart, domain, setDomain, customEmail, setCustomEmail, autoComplete }) {
const isDirect = domain === '직접입력';
return (
{isDirect
?
setCustomEmail(e.target.value)} placeholder="name@company.com" autoComplete={autoComplete} />
:
setLocalPart(e.target.value)} placeholder="이름" autoComplete={autoComplete} />
@
}
{!isDirect
?
:
}
);
}
function ssoEmail(localPart, domain, customEmail) {
return domain === '직접입력' ? customEmail : (localPart + '@' + domain);
}
function SignUp({ onSignUp, onBack }) {
const [localPart, setLocalPart] = useS1('');
const [domain, setDomain] = useS1('lge.com');
const [customEmail, setCustomEmail] = useS1('');
const [name, setName] = useS1('');
const [pw, setPw] = useS1('');
const [pwConfirm, setPwConfirm] = useS1('');
const [inviteCode, setInviteCode] = useS1('');
const [err, setErr] = useS1('');
const [busy, setBusy] = useS1(false);
const email = ssoEmail(localPart, domain, customEmail);
const submit = async (e) => {
e.preventDefault();
if (pw !== pwConfirm) { setErr('비밀번호가 일치하지 않습니다.'); return; }
setBusy(true); setErr('');
try { const r = await window.api.register({ email, name, password: pw, inviteCode: inviteCode.trim() }); onSignUp(r); }
catch (ex) { setErr(ex.message); setBusy(false); }
};
return (
);
}
function Login({ onLogin }) {
const [localPart, setLocalPart] = useS1('');
const [domain, setDomain] = useS1('lge.com');
const [customEmail, setCustomEmail] = useS1('');
const [pw, setPw] = useS1('');
const [err, setErr] = useS1('');
const [busy, setBusy] = useS1(false);
const [showSignUp, setShowSignUp] = useS1(false);
const email = ssoEmail(localPart, domain, customEmail);
const submit = async (e) => {
e.preventDefault();
setBusy(true);setErr('');
try {const r = await window.api.login({ email, password: pw });onLogin(r);}
catch (ex) {setErr(ex.message);setBusy(false);}
};
if (showSignUp) return setShowSignUp(false)} />;
return (
);
}
/* ---------------- SHELL ---------------- */
function AppShell({ user, route, navigate, onLogout, project, children }) {
const [mobileOpen, setMobileOpen] = useS1(false);
const isAdmin = user.role === 'ADMIN';
const isStaff = user.role === 'ADMIN' || user.role === 'CNX'; // content + audit access
useE1(() => {setMobileOpen(false);}, [route]);
// 사이드바 항목과 상단 제목의 단일 출처 — 둘이 따로 놀며 어긋나지 않게 한 테이블에서 파생.
// 운영/에피소드는 같은 라우트(home·project)를 tab으로 가르므로 탭 항목으로 별도 취급.
const onProject = route.name === 'home' || route.name === 'project';
const isEpisodes = onProject && route.tab === 'episodes';
const tabPages = [
{ ttl: '운영 일정', icon: , active: onProject && !isEpisodes, nav: { name: 'home', tab: 'ops' } },
{ ttl: '에피소드 선택', icon: , active: isEpisodes, nav: { name: 'project', tab: 'episodes' } },
];
const navPages = [
{ name: 'eplog', ttl: '에피소드 로그', icon: , show: isStaff },
{ name: 'epadmin', ttl: '에피소드 관리', icon: , show: isStaff },
{ name: 'audit', ttl: '감사 로그', icon: , show: isStaff },
{ name: 'users', ttl: '사용자 관리', icon: , show: isAdmin },
];
const page = navPages.find((p) => p.name === route.name) || (isEpisodes ? tabPages[1] : tabPages[0]);
return (
{mobileOpen &&
setMobileOpen(false)} />}
);
}
Object.assign(window, { Login, AppShell });