stremio/apps/admin-ui/src/app/link/page.tsx
Jos Vooges | STH 06e7640ab5 Rename to VMC and polish TV nav, spotlight, Netflix player.
Home billboard shows latest titles; shelves sort by newest files; Films/Series get shelf subnav with search; player overlay closer to Netflix.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 22:18:37 +02:00

142 lines
4 KiB
TypeScript

"use client";
import { FormEvent, useEffect, useState } from "react";
export default function LinkDevicePage() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [code, setCode] = useState("");
const [busy, setBusy] = useState(false);
const [err, setErr] = useState<string | null>(null);
const [ok, setOk] = useState<string | null>(null);
useEffect(() => {
const q = new URLSearchParams(window.location.search);
const c = q.get("code");
if (c) setCode(c.toUpperCase());
}, []);
async function onSubmit(e: FormEvent) {
e.preventDefault();
setBusy(true);
setErr(null);
setOk(null);
try {
const res = await fetch("/api/v1/link/claim", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password, code }),
});
const data = await res.json().catch(() => ({}));
if (!res.ok) {
setErr(data.error?.message ?? "Koppelen mislukt");
return;
}
setOk(`Apparaat “${data.deviceName ?? "TV"}” is gekoppeld. Je kunt dit venster sluiten.`);
setPassword("");
} finally {
setBusy(false);
}
}
return (
<main className="link-page">
<div className="link-card">
<p className="page-kicker">VMC</p>
<h1>Apparaat koppelen</h1>
<p className="muted">
Log in met je kijker-account en voer de code van je TV of telefoon in.
</p>
<form onSubmit={onSubmit} className="link-form">
<label>
E-mail
<input
type="email"
autoComplete="username"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</label>
<label>
Wachtwoord
<input
type="password"
autoComplete="current-password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</label>
<label>
Code
<input
type="text"
inputMode="text"
autoCapitalize="characters"
autoComplete="one-time-code"
placeholder="ABCD-EFGH"
value={code}
onChange={(e) => setCode(e.target.value.toUpperCase())}
required
/>
</label>
{err && <p className="form-error">{err}</p>}
{ok && <p className="form-ok">{ok}</p>}
<button type="submit" className="btn-primary" disabled={busy}>
{busy ? "Bezig…" : "Koppelen"}
</button>
</form>
</div>
<style jsx>{`
.link-page {
min-height: 100vh;
display: grid;
place-items: center;
padding: 2rem 1rem;
background:
radial-gradient(ellipse 80% 50% at 50% -20%, rgba(56, 120, 255, 0.25), transparent),
#04060a;
}
.link-card {
width: min(420px, 100%);
background: rgba(12, 16, 24, 0.92);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 16px;
padding: 1.75rem;
}
h1 {
margin: 0.25rem 0 0.5rem;
font-size: 1.75rem;
}
.link-form {
display: grid;
gap: 0.9rem;
margin-top: 1.25rem;
}
label {
display: grid;
gap: 0.35rem;
font-size: 0.85rem;
color: rgba(255, 255, 255, 0.7);
}
input {
padding: 0.7rem 0.8rem;
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.12);
background: #0a0e16;
color: #fff;
font-size: 1rem;
}
.form-error {
color: #ff8b8b;
margin: 0;
}
.form-ok {
color: #8dffb0;
margin: 0;
}
`}</style>
</main>
);
}