feat: BizGaze Connect home, BizGaze login, modular backend, /api/v1
User-facing - New post-login home (/home): chat rail + Share/Connect (embedded) + Meeting; login lives here when logged out - Landing: "Log in with BizGaze" + no-login screen share - Console replaced by a role-scoped Dashboard (/dashboard): admins see all team sessions, others see only their own; stats + CSV/PDF export - Recordings saved as MP4 (H.264/AAC) with WebM fallback; old .webm still downloadable - Fix: duplicate "Sign in" on the login card Auth / integration - BizGaze as identity provider: /api/login validates against BIZGAZE_LOGIN_URL (env-gated) and provisions a local user - Phase 2 start: /api/v1 alias for all /api routes; Authorization: Bearer accepted across HTTP + WS; login returns a token (for native desktop/mobile clients) Backend refactor (Phase 1, behavior-preserving) - Split server.js into config/lib/session/presence/routes/static/signaling + repos (data-access) + bizgaze (service) - All SQL behind repos.js, tenant-scoped (tenantId == team_id for now) - e2e updated to current flow (21/21 pass before and after) Docs: ARCHITECTURE.md (target architecture + phased plan), CLAUDE.md repo layout, .env.example BIZGAZE_LOGIN_URL Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
このコミットが含まれているのは:
+27
-19
@@ -17,14 +17,19 @@
|
||||
.inner{max-width:780px;width:100%;text-align:center;}
|
||||
h1{color:var(--blue);font-size:1.8rem;margin:0 0 .4rem;}
|
||||
.sub{color:var(--muted);margin-bottom:2.2rem;}
|
||||
.ssobtn{display:inline-flex;align-items:center;justify-content:center;gap:.6rem;background:var(--blue);color:#fff;text-decoration:none;font-weight:700;font-size:1.02rem;padding:.95rem 2rem;border-radius:12px;box-shadow:0 10px 26px rgba(31,59,115,.28);transition:transform .12s,box-shadow .12s,background .12s;}
|
||||
.ssobtn:hover{transform:translateY(-2px);box-shadow:0 16px 34px rgba(31,59,115,.34);background:var(--blue-d);}
|
||||
.ssobtn .bmark{width:26px;height:26px;border-radius:7px;background:var(--brand);color:var(--blue);display:grid;place-items:center;font-weight:800;font-size:.9rem;}
|
||||
.divider{display:flex;align-items:center;gap:1rem;color:var(--muted);font-size:.85rem;max-width:360px;margin:1.8rem auto;}
|
||||
.divider::before,.divider::after{content:"";flex:1;height:1px;background:var(--line);}
|
||||
.choices{display:flex;gap:1.4rem;flex-wrap:wrap;justify-content:center;}
|
||||
.choice{flex:1;min-width:260px;max-width:340px;background:#fff;border:1px solid var(--line);border-radius:18px;padding:2.2rem 1.6rem;text-decoration:none;color:var(--ink);box-shadow:0 10px 30px rgba(20,30,60,.06);transition:transform .12s,box-shadow .12s;}
|
||||
.choice{flex:1;min-width:260px;max-width:360px;background:#fff;border:1px solid var(--line);border-radius:18px;padding:1.8rem 1.6rem;text-decoration:none;color:var(--ink);box-shadow:0 10px 30px rgba(20,30,60,.06);transition:transform .12s,box-shadow .12s,border-color .12s;display:flex;align-items:center;gap:1.1rem;text-align:left;}
|
||||
.choice:hover{transform:translateY(-3px);box-shadow:0 16px 38px rgba(20,30,60,.12);border-color:var(--brand);}
|
||||
.icon{width:66px;height:66px;border-radius:18px;display:grid;place-items:center;margin:0 auto 1.1rem;}
|
||||
.icon{width:56px;height:56px;flex:0 0 56px;border-radius:16px;display:grid;place-items:center;}
|
||||
.icon.share{background:#FFF7DA;} .icon.connect{background:var(--blue-soft);}
|
||||
.icon svg{width:34px;height:34px;}
|
||||
.choice h3{margin:.2rem 0 .4rem;color:var(--blue);font-size:1.15rem;}
|
||||
.choice p{margin:0;color:var(--muted);font-size:.9rem;line-height:1.5;}
|
||||
.icon svg{width:30px;height:30px;}
|
||||
.choice h3{margin:0 0 .25rem;color:var(--blue);font-size:1.1rem;}
|
||||
.choice p{margin:0;color:var(--muted);font-size:.88rem;line-height:1.45;}
|
||||
.foot{color:var(--muted);font-size:.82rem;margin-top:2.4rem;}
|
||||
footer{text-align:center;color:#9aa3b2;font-size:.8rem;padding:1rem;}
|
||||
.profile{position:relative}
|
||||
@@ -43,35 +48,38 @@
|
||||
<img src="/logo.png" alt="" style="height:40px;width:auto;max-width:170px;border-radius:8px;object-fit:contain;background:#fff;padding:4px 10px" onerror="this.style.display='none'">
|
||||
<div class="brand">BizGaze <span>Support</span></div>
|
||||
</div>
|
||||
<div id="authArea"><a class="signin" href="/console">Staff sign in</a></div>
|
||||
<div id="authArea"></div>
|
||||
</header>
|
||||
<div class="wrap">
|
||||
<div class="inner">
|
||||
<h1>How can we help you today?</h1>
|
||||
<div class="sub">Secure remote support — no downloads, you stay in control.</div>
|
||||
<div class="choices">
|
||||
<h1>Welcome to BizGaze Connect</h1>
|
||||
<div class="sub">Chat, meetings and secure remote support — for the BizGaze ecosystem.</div>
|
||||
<!-- Stub SSO: routes to staff login for now; swap href to /sso once BizGaze SSO is wired. -->
|
||||
<a class="ssobtn" id="ssoBtn" href="/home"><span class="bmark">B</span> Log in with BizGaze</a>
|
||||
<div class="divider">need support? no account required</div>
|
||||
<div class="choices" style="max-width:400px;margin:0 auto">
|
||||
<a class="choice" href="/share">
|
||||
<div class="icon share"><svg viewBox="0 0 24 24" fill="none" stroke="#BA7515" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg></div>
|
||||
<h3>Share my screen</h3>
|
||||
<p>You need help. Get a one-time code and show your screen to a BizGaze support agent.</p>
|
||||
</a>
|
||||
<a class="choice" href="/connect">
|
||||
<div class="icon connect"><svg viewBox="0 0 24 24" fill="none" stroke="#1F3B73" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 17V7a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v10"/><path d="M2 21h20"/><path d="m9 9 3 3-3 3"/></svg></div>
|
||||
<h3>Connect to a screen</h3>
|
||||
<p>You're a support agent. Sign in, then enter the customer's code to view their screen.</p>
|
||||
<div><h3>Share my screen</h3><p>Get a one-time code and show your screen to a BizGaze support agent — no login, no download.</p></div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="foot">🔒 Screen sharing only starts after the customer approves it, and can be stopped anytime.</div>
|
||||
<div class="foot">🔒 Screen sharing only starts after you approve it, and can be stopped anytime.</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer>© BizGaze · Remote Support</footer>
|
||||
<script>
|
||||
function pEsc(s){return String(s==null?'':s).replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c]));}
|
||||
function profileHTML(name){return '<div class="profile"><button class="pbtn" id="pbtn">'+pEsc(name)+' <span style="font-size:.65rem">▾</span></button><div class="pmenu" id="pmenu"><a href="/console">Console / Dashboard</a><a id="plogout" class="danger">Logout</a></div></div>';}
|
||||
function profileHTML(name){return '<div class="profile"><button class="pbtn" id="pbtn">'+pEsc(name)+' <span style="font-size:.65rem">▾</span></button><div class="pmenu" id="pmenu"><a href="/home">Home</a><a href="/dashboard">Dashboard</a><a id="plogout" class="danger">Logout</a></div></div>';}
|
||||
function wireProfile(){const btn=document.getElementById('pbtn'),menu=document.getElementById('pmenu');if(!btn)return;btn.onclick=(e)=>{e.stopPropagation();menu.classList.toggle('open');};document.addEventListener('click',()=>menu.classList.remove('open'));const lo=document.getElementById('plogout');if(lo)lo.onclick=async()=>{try{await fetch('/api/logout',{method:'POST'});}catch(_){}location.href='/';};}
|
||||
function makeBrandClickable(){document.querySelectorAll('.brandrow,.wordmark').forEach(el=>{el.style.cursor='pointer';el.addEventListener('click',()=>{location.href='/';});});}
|
||||
makeBrandClickable();
|
||||
(async function(){try{const r=await fetch('/api/me');if(r.ok){const me=await r.json();document.getElementById('authArea').innerHTML=profileHTML(me.name||me.email);wireProfile();}}catch(_){}})();
|
||||
(async function(){try{const r=await fetch('/api/me');if(r.ok){const me=await r.json();
|
||||
document.getElementById('authArea').innerHTML=profileHTML(me.name||me.email);wireProfile();
|
||||
// Already signed in: swap the login CTA for an "enter app" CTA.
|
||||
const b=document.getElementById('ssoBtn'); if(b){ b.innerHTML='Open BizGaze Connect →'; b.href='/home'; }
|
||||
const h=document.querySelector('.inner h1'); if(h){ const fn=String(me.name||'').trim().split(/\s+/)[0]; h.textContent='Welcome back'+(fn?', '+fn:'')+'!'; }
|
||||
const dv=document.querySelector('.divider'); if(dv) dv.textContent='need to help someone? share your screen';
|
||||
}}catch(_){}})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
新しいイシューから参照
ユーザーをブロックする