import { Link } from "react-router-dom"; import { useQuery } from "@tanstack/react-query"; import { getMe, getServices } from "../api/client"; const cardBase: React.CSSProperties = { backgroundColor: "rgb(var(--color-surface))", border: "1px solid rgb(var(--color-border))", borderRadius: 8, padding: 24, width: 280, display: "flex", flexDirection: "column", gap: 12, }; const clickableCard: React.CSSProperties = { ...cardBase, cursor: "pointer", textDecoration: "none", color: "inherit", transition: "box-shadow 150ms, border-color 150ms", }; const unavailableCard: React.CSSProperties = { ...cardBase, opacity: 0.6, }; export default function AppsPage() { const { data: user } = useQuery({ queryKey: ["me"], queryFn: getMe }); const { data: services = [] } = useQuery({ queryKey: ["services"], queryFn: getServices, refetchInterval: 30_000, refetchIntervalInBackground: true, }); return (

Apps

{services.map((svc) => { const canOpen = svc.healthy && !!svc.app_path; const CardWrapper = canOpen ? Link : "div"; const wrapperProps = canOpen ? { to: svc.app_path, style: clickableCard, onMouseEnter: (e: React.MouseEvent) => { e.currentTarget.style.boxShadow = "0 4px 12px rgb(0 0 0 / 0.12)"; e.currentTarget.style.borderColor = "rgb(var(--color-primary))"; }, onMouseLeave: (e: React.MouseEvent) => { e.currentTarget.style.boxShadow = ""; e.currentTarget.style.borderColor = "rgb(var(--color-border))"; }, } : { style: svc.healthy ? cardBase : unavailableCard }; return (

{svc.name}

{svc.healthy ? ( Available ) : ( Unavailable )}

{svc.description}

{!svc.healthy && (

This service is currently unavailable. Please try again later or contact your administrator.

)}
{user?.is_admin && svc.settings_path && ( e.stopPropagation()} style={{ padding: "6px 14px", border: "1px solid #ccc", borderRadius: 4, textDecoration: "none", fontSize: 14, color: "#333", }} title="Settings" > Settings )}
); })}
); }