/* gc-entitle.jsx — vyerna för planspärr, trappa och villkorsgrind.
EntLock: gråad funktion bakom en panel med Uppgradera (K2/K3/K4, 15/9).
LockedPage: EN sida som tar access + reason (trappan T2, 20/9) och väljer
rubrik, styrningsbesked, nedräkning och knapp. Ersätter BlockedPage.
TermsPage: godkännandesidan (villkor K3, 20/9). Samma skal som LockedPage.
Ingen av dem hämtar data; allt går via window.HemsEnt. */
function EntUpgradeBtn({ label = 'Uppgradera' }){
const [busy, setBusy] = React.useState(false);
const [err, setErr] = React.useState(null);
const go = async () => {
setErr(null); setBusy(true);
const r = await window.HemsEnt.openPortal();
if (!r.ok) setErr(r.error);
setBusy(false);
};
return (
{err &&
{err}
}
);
}
/* Gråat innehåll bakom panelen: konfigurationen syns kvar så kunden ser vad
hen går miste om (André 15/9), men inget går att röra. */
function EntLock({ text, children }){
return (
{children}
{text}
);
}
function EntLockEmpty({ text }){
return (
);
}
/* Gemensamt skal: märke, rubrik, brödtext, innehåll, Logga ut. */
function EntLogout(){
['hems_session_token', 'hems_user_email', 'hems_user_name', 'hems_user_id']
.forEach(k => { try { localStorage.removeItem(k); } catch(_){} });
window.location.href = 'login.html';
}
/* Din supportkod på spärrsidan (backend 23/9, S2): ur samma /me. Inget när fältet saknas. */
function EntSupportCode(){
const E = window.HemsEnt;
const [code, setCode] = React.useState(() => { const m = E && E.me(); return (m && m.support_code) || null; });
React.useEffect(() => { const on = () => { const m = E && E.me(); if (m && m.support_code) setCode(m.support_code); }; window.addEventListener('hems:entitlements', on); return () => window.removeEventListener('hems:entitlements', on); }, []);
if (!code) return null;
return (
Din supportkod: {code}– uppge den när du kontaktar oss.
);
}
/* Kryssruta med länkade dokument (K3/K5). Länkarna öppnas i ny flik så
rutan inte tappas när kunden läst klart. URL:erna ur /me när de finns. */
function EntCheck({ checked, onChange, children, id }){
return (
);
}
function EntTermsLabel(){
const E = window.HemsEnt;
const a = 'underline decoration-slate-600 underline-offset-2 hover:text-slate-50';
return (
<>Jag har läst och godkänner användarvillkoren och har tagit del av integritetspolicyn>
);
}
/* Plankort. onPick(plan, interval). Samma kort för subscription_required
(checkout) och subscription_ended (reactivate); texten "Första månaden
gratis" hör inte hit — Checkout debiterar direkt. */
function EntPlanCard({ plan, onPick, busy, err, disabled }){
const [ival, setIval] = React.useState('month');
const yearly = plan.year && ival === 'year';
return (
);
}
/* Styrningsbeskedet (T3) + nedräkningarna (T4). Sidans viktigaste mening:
ligger FÖRE plankorten. Lugn rad, ingen varningspanel. */
function EntControlNotice({ ent }){
const E = window.HemsEnt;
const c = E.daysPhrase(ent.days_until_control_stops, 'for');
const d = E.daysPhrase(ent.days_until_data_deleted, 'in');
let main = null, sub = null;
if (ent.access === 'dash_locked'){
main = c ? `HEMS styr din anläggning som vanligt ${c}.` : 'HEMS styr din anläggning som vanligt.';
if (ent.days_until_control_stops === 0) main = 'HEMS styr din anläggning som vanligt i dag. Sedan upphör styrningen.';
sub = 'Batteriet, bilen och lasterna följer planen även om den här sidan är stängd.';
} else if (ent.access === 'stopped'){
main = 'HEMS har slutat styra din anläggning.';
/* D3: data sparas tolv månader efter avtalets slut. Nedräkningen visas
bara när backend skickat ett tal; null ⇒ ingen raderingstext. */
const n = ent.days_until_data_deleted;
sub = n == null ? 'Dina data och inställningar finns kvar i tolv månader, så du kan fortsätta där du var om du kommer tillbaka.'
: n === 0 ? 'Dina data tas bort i dag.' : n === 1 ? 'Dina data finns kvar till i morgon.' : `Dina data finns kvar i ${n} dagar.`;
} else if (ent.access === 'purged'){
main = 'HEMS har slutat styra din anläggning.';
sub = 'Dina data har tagits bort.';
}
if (!main) return null;
return (
{main}
{sub &&
{sub}
}
{ent.access === 'dash_locked' && d && (
Dina data raderas {d} om du inte förnyar. Fram till dess finns allt kvar.
)}
);
}
/* Spärrsidan — ett läge per reason (2.2). */
function LockedPage({ ent }){
const E = window.HemsEnt;
const e = ent || E.get();
const [busy, setBusy] = React.useState(null);
const [err, setErr] = React.useState(null);
const [termsOk, setTermsOk] = React.useState(false);
const [deliverOk, setDeliverOk] = React.useState(false);
const reason = e.reason;
const pickReactivate = async (plan, interval) => {
setErr(null); setBusy(plan);
const r = await E.reactivate(plan, interval);
if (!r.ok) setErr({ plan, msg:r.error });
setBusy(null);
};
const pickCheckout = async (plan, interval) => {
setErr(null); setBusy(plan);
const r = await E.checkout(plan, interval, { terms_accepted:termsOk, delivery_start_now:deliverOk });
if (!r.ok) setErr({ plan, msg:r.error });
setBusy(null);
};
/* payment_failed hit FÖRST efter varningsperioden (rev 2 D1): access är då
stopped och styrningen har upphört. Under perioden är dashboarden öppen
med PaymentBanner. */
if (reason === 'payment_failed'){
return (
);
}
/* Godkännandesidan (K3). Helsida före all annan rendering. En kryssruta,
knappen inaktiv tills den är kryssad. Ingen väg förbi utom Logga ut. */
function TermsPage(){
const E = window.HemsEnt;
const t = E.terms();
const [ok, setOk] = React.useState(false);
const [busy, setBusy] = React.useState(false);
const [err, setErr] = React.useState(null);
const first = !t.accepted_version;
const go = async () => {
setErr(null); setBusy(true);
const r = await E.acceptTerms();
if (!r.ok) { setErr(r.error); setBusy(false); }
};
return (
Användarvillkor för HEMS och Integritetspolicy · PowerGravio AB · version {t.current_version}
{err &&
{err}
}
);
}
/* Banner vid utebliven betalning (rev 2 D1): dashboarden är öppen, styrningen
pågår, kunden ska se problemet och kunna laga det. Nedräkning + datum ur
backend (days_until_control_stops, control_stops_date), aldrig uträknat här.
Lugn ton; närmare slutet (≤ 3 dagar) tydligare kant, men ingen röd panel. */
function PaymentBanner({ ent }){
const E = window.HemsEnt;
const w = E && E.paymentWarning(ent);
const [busy, setBusy] = React.useState(false);
const [err, setErr] = React.useState(null);
if (!w) return null;
const go = async () => {
setErr(null); setBusy(true);
const r = await E.openPortal();
if (!r.ok) setErr(r.error);
setBusy(false);
};
return (
{w.text}
Fram till dess styr HEMS som vanligt. Dashboarden och inställningarna är öppna.{err ? ` ${err}` : ''}
);
}
/* Grinden i rot (K3 + T2): villkor först, sedan spärren, sedan appen.
Lyssnar på hems:entitlements så 402/403 mitt i sessionen byter vy — och
App avmonteras, så pollningen stannar. */
function EntGate({ children }){
const E = window.HemsEnt;
const [, force] = React.useState(0);
React.useEffect(() => {
if (!E) return;
const on = () => force(n => n + 1);
window.addEventListener('hems:entitlements', on);
return () => window.removeEventListener('hems:entitlements', on);
}, []);
if (E && E.termsRequired()) return ;
if (E && E.locked()) return ;
return children;
}
const BlockedPage = LockedPage; // bakåtkompatibelt namn
Object.assign(window, { EntLock, EntLockEmpty, EntUpgradeBtn, EntPlanCard, EntCheck, EntTermsLabel, EntControlNotice, LockedPage, TermsPage, EntGate, BlockedPage, PaymentBanner });