latest changes
This commit is contained in:
1 parent
56cd6fd058
commit
1de72863db
40 files changed
+3837
-568
No files matched your search
@@ -1,100 +0,0 @@
|
||||
import Link from "next/link";
|
||||
import { headers } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
import { ContentCard, EmptyState } from "@/components/public/ui";
|
||||
import { issueSsoTicket } from "@/lib/auth/sso-ticket";
|
||||
import { buildNitroClientUrl } from "@/lib/services/clients";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { siteSettings } from "@/lib/services/site-settings";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function FlashClientPage() {
|
||||
const session = await auth();
|
||||
if (!session?.user?.id) redirect("/login");
|
||||
|
||||
const userId = Number(session.user.id);
|
||||
// The Flash client base URL may be stored under either settings key.
|
||||
const [hotelNameRaw, flashUrl, flashUrlAlt] = await Promise.all([
|
||||
siteSettings.get("hotel_name", "Atom").catch(() => "Atom"),
|
||||
siteSettings.get("flash_client_url", "").catch(() => ""),
|
||||
siteSettings.get("client_flash_url", "").catch(() => ""),
|
||||
]);
|
||||
const hotelName = hotelNameRaw ?? "Atom";
|
||||
|
||||
const hdrs = await headers();
|
||||
const ip =
|
||||
hdrs.get("x-forwarded-for")?.split(",")[0]?.trim() ?? hdrs.get("x-real-ip") ?? "0.0.0.0";
|
||||
|
||||
// Faithful to AtomCMS's client controllers: issue (and persist) the SSO ticket
|
||||
// on the launcher render, then hand it to the Flash client.
|
||||
const ticket = await issueSsoTicket(prisma, userId, hotelName, ip);
|
||||
|
||||
// Use a configured Flash base URL (with the SSO ticket appended); otherwise
|
||||
// fall back to the Nitro client so the launcher still works.
|
||||
const flashBase = flashUrl || flashUrlAlt || "";
|
||||
const flashSrc = flashBase
|
||||
? `${flashBase}${flashBase.includes("?") ? "&" : "?"}sso=${encodeURIComponent(ticket)}`
|
||||
: "";
|
||||
const src = flashSrc || (await buildNitroClientUrl(ticket));
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<ContentCard
|
||||
icon="🎮"
|
||||
title={`Launching ${hotelName ?? "Atom"} (Flash)…`}
|
||||
subtitle="Your single sign-on ticket is ready"
|
||||
action={
|
||||
<Link href="/client" className="btn btn-outline">
|
||||
Use Nitro client
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
|
||||
{src ? (
|
||||
<>
|
||||
<ContentCard
|
||||
icon="🚀"
|
||||
title="Open the Flash client"
|
||||
subtitle="Flash works best in its own tab"
|
||||
action={
|
||||
<a
|
||||
href={src}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="btn btn-primary"
|
||||
>
|
||||
Launch in a new tab
|
||||
</a>
|
||||
}
|
||||
/>
|
||||
<ContentCard padded={false}>
|
||||
<iframe
|
||||
title="Flash hotel client"
|
||||
src={src}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "80vh",
|
||||
border: 0,
|
||||
borderRadius: 10,
|
||||
display: "block",
|
||||
}}
|
||||
/>
|
||||
</ContentCard>
|
||||
</>
|
||||
) : (
|
||||
<ContentCard
|
||||
icon="🔌"
|
||||
title="No Flash client configured"
|
||||
subtitle="Set flash_client_url in settings"
|
||||
>
|
||||
<EmptyState icon="🔌">
|
||||
No Flash client URL configured (set <code>flash_client_url</code> in settings). SSO
|
||||
ticket issued for this session: <code>{ticket}</code>
|
||||
</EmptyState>
|
||||
</ContentCard>
|
||||
)}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
+100
-50
@@ -1,61 +1,111 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { headers } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
import { ContentCard, EmptyState } from "@/components/public/ui";
|
||||
import { issueSsoTicket } from "@/lib/auth/sso-ticket";
|
||||
import { buildNitroClientUrl } from "@/lib/services/clients";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { siteSettings } from "@/lib/services/site-settings";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
export default function ClientPage() {
|
||||
const [ticket, setTicket] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [hotelName, setHotelName] = useState("Hubbly");
|
||||
const [launched, setLaunched] = useState(false);
|
||||
|
||||
export default async function ClientPage() {
|
||||
const session = await auth();
|
||||
if (!session?.user?.id) redirect("/login");
|
||||
useEffect(() => {
|
||||
async function fetchTicket() {
|
||||
try {
|
||||
const res = await fetch("/api/client/sso");
|
||||
if (!res.ok) throw new Error("Failed to get SSO ticket");
|
||||
const data = await res.json();
|
||||
setTicket(data.ticket);
|
||||
setHotelName(data.hotelName);
|
||||
} catch (e) {
|
||||
setError("Could not generate login ticket");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
fetchTicket();
|
||||
}, []);
|
||||
|
||||
const userId = Number(session.user.id);
|
||||
const hotelName = (await siteSettings.get("hotel_name", "Atom")) ?? "Atom";
|
||||
useEffect(() => {
|
||||
if (ticket && !launched) {
|
||||
setLaunched(true);
|
||||
const clientUrl = `/nitro-client/?sso=${encodeURIComponent(ticket)}`;
|
||||
const fullUrl = `${window.location.origin}${clientUrl}`;
|
||||
window.open(fullUrl, "_blank", "noopener,noreferrer");
|
||||
}
|
||||
}, [ticket, launched]);
|
||||
|
||||
const hdrs = await headers();
|
||||
const ip =
|
||||
hdrs.get("x-forwarded-for")?.split(",")[0]?.trim() ?? hdrs.get("x-real-ip") ?? "0.0.0.0";
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col" style={{ backgroundColor: "var(--color-background)" }}>
|
||||
<header
|
||||
className="flex h-14 items-center justify-between border-b px-4"
|
||||
style={{ backgroundColor: "var(--color-navbar)", borderColor: "color-mix(in srgb, var(--color-text-muted) 15%, transparent)" }}
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="font-bold text-lg">{hotelName}</span>
|
||||
<span className="text-sm text-gray-500">Loading client…</span>
|
||||
</div>
|
||||
</header>
|
||||
<main className="flex-1 flex items-center justify-center">
|
||||
<ContentCard icon="⏳" title="Loading" subtitle="Generating SSO ticket…" />
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Faithful to AtomCMS's NitroController: issue (and persist) the SSO ticket on
|
||||
// the launcher render, then hand it to the Nitro client at {nitro_path}.
|
||||
const ticket = await issueSsoTicket(prisma, userId, hotelName, ip);
|
||||
const src = await buildNitroClientUrl(ticket);
|
||||
if (error || !ticket) {
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col" style={{ backgroundColor: "var(--color-background)" }}>
|
||||
<header
|
||||
className="flex h-14 items-center justify-between border-b px-4"
|
||||
style={{ backgroundColor: "var(--color-navbar)", borderColor: "color-mix(in srgb, var(--color-text-muted) 15%, transparent)" }}
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="font-bold text-lg">{hotelName}</span>
|
||||
</div>
|
||||
</header>
|
||||
<main className="flex-1 flex items-center justify-center">
|
||||
<ContentCard icon="⚠" title="Error" subtitle={error ?? "No SSO ticket"}>
|
||||
<EmptyState icon="⚠">{error ?? "Could not generate SSO ticket"}</EmptyState>
|
||||
</ContentCard>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<ContentCard
|
||||
icon="🏨"
|
||||
title={`Launching ${hotelName}…`}
|
||||
subtitle="Your single sign-on ticket is ready"
|
||||
action={
|
||||
<Link href="/client/flash" className="btn btn-outline">
|
||||
Use Flash client
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
|
||||
{src ? (
|
||||
<ContentCard padded={false}>
|
||||
<iframe
|
||||
title="Hotel client"
|
||||
src={src}
|
||||
style={{ width: "100%", height: "85vh", border: 0, borderRadius: 10, display: "block" }}
|
||||
/>
|
||||
<div className="flex min-h-screen flex-col" style={{ backgroundColor: "var(--color-background)" }}>
|
||||
<header
|
||||
className="flex h-14 items-center justify-between border-b px-4"
|
||||
style={{
|
||||
backgroundColor: "var(--color-navbar)",
|
||||
borderColor: "color-mix(in srgb, var(--color-text-muted) 15%, transparent)",
|
||||
color: "var(--color-navbar-text)",
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="font-bold text-lg">{hotelName}</span>
|
||||
<span className="text-sm" style={{ color: "var(--color-navbar-text)" }}>Connected</span>
|
||||
</div>
|
||||
</header>
|
||||
<main className="flex-1 flex items-center justify-center" style={{ backgroundColor: "var(--color-background)" }}>
|
||||
<ContentCard>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<h1 style={{ fontSize: "1.75rem", fontWeight: 700, marginBottom: "0.5rem" }}>
|
||||
Welcome to {hotelName}
|
||||
</h1>
|
||||
<p style={{ color: "var(--color-text-muted)", marginBottom: "2rem" }}>
|
||||
Opening the client in a new tab… If it doesn't open,{" "}
|
||||
<a href={`/nitro-client/?sso=${encodeURIComponent(ticket)}`} target="_blank" rel="noopener noreferrer">
|
||||
click here
|
||||
</a>.
|
||||
</p>
|
||||
</div>
|
||||
</ContentCard>
|
||||
) : (
|
||||
<ContentCard icon="🔌" title="No client configured" subtitle="Set nitro_path in settings">
|
||||
<EmptyState icon="🔌">
|
||||
No Nitro client configured — set <code>nitro_path</code> (e.g.{" "}
|
||||
<code>/Nitro-V3/dist</code>) in the admin settings. SSO ticket issued for this session:{" "}
|
||||
<code>{ticket}</code>
|
||||
</EmptyState>
|
||||
</ContentCard>
|
||||
)}
|
||||
</main>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user