GuestOps/web/src/MailboxPanel.tsx

22 lines
5.1 KiB
TypeScript

import { useEffect, useState } from 'react';
import { Mail, RefreshCw, ShieldCheck } from 'lucide-react';
import { api, type Mailboxes } from './api';
type Run=(action:()=>Promise<void>)=>Promise<void>;
const when=(value:string|null)=>value?new Date(value).toLocaleString():'Not yet';
export function MailboxPanel({data,owner,preview,busy,run,onChange}:{data:Mailboxes;owner:boolean;preview:boolean;busy:boolean;run:Run;onChange:(value:Mailboxes)=>void}){
const [pollError,setPollError]=useState('');
async function refresh(){onChange(await api<Mailboxes>('/mailboxes'));setPollError('');}
useEffect(()=>{let active=true;const timer=setInterval(()=>{if(document.hidden)return;api<Mailboxes>('/mailboxes').then(value=>{if(active){onChange(value);setPollError('');}}).catch(()=>{if(active)setPollError('Status could not be refreshed. Use Refresh status to check again.');});},30000);return()=>{active=false;clearInterval(timer);};},[onChange]);
async function act(id:string,version:number,action:string,email:string){
if(action==='disconnect'&&!window.confirm(`Disconnect ${email} from GuestOps? New import and reply work will stop and saved Google credentials will be removed. An in-flight request may finish. Imported emails and drafts stay in this workspace. Google account access must be removed separately.`))return;
await run(async()=>{const result=await api<{url?:string}>(`/mailboxes/${id}/${action}`,'POST',{version});if(result?.url)location.assign(result.url);else await refresh();});
}
return <section className="settings-card mailbox-panel"><div className="section-heading"><Mail size={21}/><div><h2>Google mailbox</h2><p>Keep guest messages flowing into your shared inbox.</p></div><button className="button text compact" disabled={busy} onClick={()=>run(refresh)}><RefreshCw size={15}/>Refresh status</button></div>{pollError&&<p className="alert" role="status">{pollError}</p>}
{data.items.map(m=>{const connected=m.status==='Connected';const waiting=!!m.nextAttemptAt&&new Date(m.nextAttemptAt)>new Date();const label=m.status==='NeedsReconnect'?'Reconnect needed':m.status==='Disconnected'?'Disconnected':m.syncError?'Waiting for retry':m.catchingUp?'Catching up':'Connected';return <article className="mailbox-detail" key={m.id}><div className="mailbox-title"><span className="google-mark">G</span><strong>{m.email}</strong><span className={'status '+(connected&&!m.syncError?'Completed':'NeedsAttention')}>{label}</span></div><div className="mailbox-health"><div><span>Last successful import</span><strong>{when(m.lastSyncAt)}</strong></div><div><span>Last attempt</span><strong>{when(m.lastAttemptAt)}</strong></div><div><span>Next check</span><strong>{waiting?when(m.nextAttemptAt):connected?'Next worker cycle':'After reconnection'}</strong></div></div>{m.syncError&&<p className="mailbox-explanation" role="status">{m.syncError}</p>}<p className="small muted">{connected?(m.canSend?'Google sending permission granted. Hotel and reply approval controls still apply.':'Read-only connection. Reconnect after administrator enablement to grant sending permission.'):'GuestOps cannot start new mailbox work while disconnected or awaiting reconnection.'}</p><div className="mailbox-buttons"><button className="button secondary compact" disabled={busy||preview||!owner||!data.configured} onClick={()=>act(m.id,m.version,'reconnect',m.email)}>Reconnect Google</button>{connected&&<button className="button secondary compact" disabled={busy||preview||!owner||waiting} onClick={()=>act(m.id,m.version,'retry',m.email)}>Restart import pass</button>}{m.status!=='Disconnected'&&<button className="button text compact" disabled={busy||preview||!owner} onClick={()=>act(m.id,m.version,'disconnect',m.email)}>Disconnect from GuestOps</button>}</div>{m.status==='Disconnected'&&<p className="small muted">To also remove Google's authorization, open <a href="https://myaccount.google.com/permissions" target="_blank" rel="noopener noreferrer">your Google account connections</a> and remove GuestOps access. This may affect other sessions using the same Google app.</p>}</article>;})}
{!data.items.length&&<div className="mailbox-empty"><Mail size={28}/><h3>Connect your hotel inbox</h3><p>Authorize the Google account your team uses for guest messages. The first import covers seven days.</p></div>}
<button className="button secondary" disabled={busy||preview||!owner||!data.configured} onClick={()=>run(async()=>{const result=await api<{url:string}>('/integrations/google/connect','POST');location.assign(result.url);})}><span className="google-mark">G</span>{data.items.length?'Connect another mailbox':'Connect Google mailbox'}</button>
{preview?<p className="small muted">Sample workspace only. Real mailbox connection and recovery controls are unavailable here.</p>:!data.configured&&<p className="small muted">Your administrator must configure Google before connection is available.</p>}
<div className="settings-note"><ShieldCheck size={17}/><span>Disconnecting keeps imported messages and drafts. Requests already in progress may finish. Reconnection keeps the same mailbox history; queued replies from an earlier connection require review.</span></div>
</section>;
}