// Simple placeholder pages for Properties list, Subscription, Settings
function PropertyCard({ p, onOpenProperty }) {
const activeCount = p.sessions.filter(s => s.status === "active").length;
const pendingCount = p.sessions.filter(s => s.status === "pending").length;
return (
Abbonamento
Piano personalizzato gestito dall'assistenza.
{billingStatus?.plan_display_name || "Piano custom"}
{customStatusInfo && hasSubscription && (
{customStatusInfo.text}
)}
{priceMonthly != null
? `€${priceMonthly.toFixed(2).replace(".00", "")}/mese`
: "Fatturazione personalizzata"}
{propertyLimit != null ? `${propertyLimit} camere` : "Camere illimitate"}
{dailyMsgLimit != null && (
{`${dailyMsgLimit} messaggi/giorno`}
)}
Fatture
{loadingInv ? (
Caricamento fatture…
) : invoices.length === 0 ? (
Nessuna fattura disponibile
Le fatture appariranno qui non appena emesse
) : (
Fattura
Periodo
Importo
Data emissione
{invoices.map(inv => (
{inv.filename}
{fmtPeriod(inv.period)}
{fmtAmount(inv.amount)}
{inv.issued_at ? new Date(inv.issued_at).toLocaleDateString("it-IT", { day: "2-digit", month: "short", year: "numeric" }) : "n.d."}
handleDownload(inv)}
disabled={downloadingId === inv.id}
>
{downloadingId === inv.id ? "Scaricando…" : "Scarica PDF"}
))}
)}
Per modifiche al piano o al metodo di pagamento, contatta l'assistenza.
);
}
return (
Abbonamento
Gestisci il tuo piano e le fatture.
{/* Banner pagamento in sospeso */}
{subStatus === "past_due" && (
⚠️ Il pagamento dell'abbonamento non è andato a buon fine. Aggiorna il metodo di pagamento per evitare interruzioni.
)}
{/* Card stato abbonamento */}
Piano {currentMeta.name}
{statusInfo
? {statusInfo.text}
: billingStatus && !billingStatus.has_subscription
? Nessun abbonamento
: null}
€{currentMeta.monthlyPrice}/mese
{billingStatus?.has_subscription && (
{portalLoading ? "Reindirizzamento…" : "Gestisci abbonamento"}
)}
{/* Tab switcher */}
{[["plan", "Piano"], ["invoices", "Fatture"]].map(([id, label]) => (
{ setTab(id); setCheckoutPlan(null); }}
style={{
background: "none", border: "none", cursor: "pointer",
padding: "8px 16px", fontSize: 14, fontWeight: tab === id ? 600 : 400,
color: tab === id ? "var(--gray-900)" : "var(--gray-500)",
borderBottom: tab === id ? "2px solid var(--green-600)" : "2px solid transparent",
marginBottom: -1,
}}>
{label}
))}
{tab === "plan" && (
Cambia piano
{/* Toggle mensile / annuale */}
{["monthly", "yearly"].map(iv => (
{ setInterval(iv); setCheckoutPlan(null); }}
style={{
padding: "5px 14px", borderRadius: 99, border: "none", cursor: "pointer",
fontSize: 12, fontWeight: 500,
background: interval === iv ? "white" : "transparent",
color: interval === iv ? "var(--gray-900)" : "var(--gray-500)",
boxShadow: interval === iv ? "0 1px 4px rgba(0,0,0,.1)" : "none",
transition: "all .15s",
}}>
{iv === "monthly" ? "Mensile" : (
Annuale −10%
)}
))}
{/* Layout: plan cards + checkout panel — responsive via classe CSS */}
{SUB_PLANS_META.map(p => {
const isCurrent = p.id === currentPlan;
const isSelected = checkoutPlan === p.id;
return (
{ if (!isCurrent) setCheckoutPlan(p.id); }}>
{isCurrent && (
Attuale
)}
{p.name}
{fmtMonthly(p.monthlyPrice)}
/mese
{interval === "yearly" && (
Risparmi €{annualSaving(p.monthlyPrice)}/anno
)}
{p.properties}
{p.features.map((f, i) => (
{f}
))}
{!isCurrent && (
{ e.stopPropagation(); setCheckoutPlan(p.id); }}
>
{isSelected ? "Selezionato ✓" : `Passa a ${p.name}`}
)}
{isCurrent && (
Piano attuale
)}
);
})}
{/* Pannello checkout embedded */}
{checkoutPlan && (
<>
Piano {SUB_PLANS_META.find(p => p.id === checkoutPlan)?.name} · {interval === "monthly" ? "mensile" : "annuale"}
setCheckoutPlan(null)} style={{ background: "none", border: "none", cursor: "pointer", color: "var(--gray-400)", fontSize: 18, lineHeight: 1 }}>×
>
)}
)}
{tab === "invoices" && (
{loadingInv ? (
Caricamento fatture…
) : invoices.length === 0 ? (
Nessuna fattura disponibile
Le fatture appariranno qui non appena emesse
) : (
Fattura
Periodo
Importo
Data emissione
{invoices.map(inv => (
{inv.filename}
{fmtPeriod(inv.period)}
{fmtAmount(inv.amount)}
{inv.issued_at ? new Date(inv.issued_at).toLocaleDateString("it-IT", { day: "2-digit", month: "short", year: "numeric" }) : "n.d."}
handleDownload(inv)}
disabled={downloadingId === inv.id}
>
{downloadingId === inv.id ? "Scaricando…" : "Scarica PDF"}
))}
)}
)}
);
}
function SettingsPage({ host, showToast, onHostUpdated }) {
const [fullName, setFullName] = React.useState(host?.full_name || "");
const [phone, setPhone] = React.useState(host?.phone_number || "");
const [saving, setSaving] = React.useState(false);
// Impostazione invio automatico schedine PayTourist
const [autoSubmit, setAutoSubmit] = React.useState(host?.paytourist_auto_submit ?? false);
React.useEffect(() => {
setAutoSubmit(host?.paytourist_auto_submit ?? false);
}, [host?.paytourist_auto_submit]);
// Approvazione automatica degli ospiti (piani chatbot)
const [autoApprove, setAutoApprove] = React.useState(host?.auto_approve_sessions ?? false);
React.useEffect(() => {
setAutoApprove(host?.auto_approve_sessions ?? false);
}, [host?.auto_approve_sessions]);
React.useEffect(() => {
setFullName(host?.full_name || "");
setPhone(host?.phone_number || "");
}, [host?.full_name, host?.phone_number]);
const [currentPw, setCurrentPw] = React.useState("");
const [newPw, setNewPw] = React.useState("");
const [confirmPw, setConfirmPw] = React.useState("");
const [pwError, setPwError] = React.useState("");
const [savingPw, setSavingPw] = React.useState(false);
const save = async () => {
setSaving(true);
try {
const updated = await window.API.updateHost({ full_name: fullName.trim() || null, phone_number: phone.trim() || null });
if (updated && onHostUpdated) onHostUpdated(updated);
if (showToast) showToast("Profilo aggiornato");
} catch (err) {
if (showToast) showToast(err.message || "Errore", "error");
} finally {
setSaving(false);
}
};
const changePassword = async () => {
setPwError("");
if (newPw !== confirmPw) { setPwError("Le password non coincidono."); return; }
if (newPw.length < 8) { setPwError("La nuova password deve essere di almeno 8 caratteri."); return; }
setSavingPw(true);
try {
await window.API.changePassword(currentPw, newPw);
setCurrentPw(""); setNewPw(""); setConfirmPw("");
if (showToast) showToast("Password aggiornata");
} catch (err) {
setPwError(err.message || "Errore");
} finally {
setSavingPw(false);
}
};
const toggleAutoApprove = async () => {
const newValue = !autoApprove;
setAutoApprove(newValue); // ottimistico
try {
const updated = await window.API.updateHost({ auto_approve_sessions: newValue });
if (updated && onHostUpdated) onHostUpdated(updated);
} catch (err) {
setAutoApprove(!newValue); // ripristina in caso di errore
if (showToast) showToast(err.message || "Errore aggiornamento impostazione", "error");
}
};
const toggleAutoSubmit = async () => {
const newValue = !autoSubmit;
setAutoSubmit(newValue); // ottimistico
try {
const updated = await window.API.updateHost({ paytourist_auto_submit: newValue });
if (updated && onHostUpdated) onHostUpdated(updated);
} catch (err) {
setAutoSubmit(!newValue); // ripristina in caso di errore
if (showToast) showToast(err.message || "Errore aggiornamento impostazione", "error");
}
};
return (
Impostazioni
Profilo, notifiche e preferenze.
Notifiche
{[
{ label: "Email per nuove richieste di accesso", on: true },
{ label: "Email per recensioni degli ospiti", on: true },
{ label: "Riepilogo settimanale", on: false },
].map((n, i) => (
))}
{["starter", "pro", "business"].includes(host?.plan) && (
Approvazione ospiti
Approvazione automatica degli ospiti
{autoApprove
? "Attiva — gli ospiti che scansionano il QR accedono subito al chatbot senza la tua approvazione e non ricevi l'email di richiesta accesso."
: "Disattivata — ogni ospite che scansiona il QR resta in attesa finché non approvi la richiesta dal link ricevuto via email."}
)}
{["pro", "business"].includes(host?.plan) && (
Invio schedine PayTourist
{autoSubmit ? "Invio automatico attivo" : "Revisione prima dell'invio"}
{autoSubmit
? "Invia automaticamente — le schedine vengono inviate a PayTourist appena l'ospite completa il check-in (se i dati sono completi, altrimenti ricevi comunque l'email di revisione)."
: "Rivedi prima di inviare — le schedine restano in bozza e ricevi un'email per controllarle e inviarle."}
)}
);
}
window.PropertiesListPage = PropertiesListPage;
window.SubscriptionPage = SubscriptionPage;
window.SettingsPage = SettingsPage;