feat: WIP element Chat intergration

This commit is contained in:
Nathnael
2026-07-31 06:36:03 +00:00
parent a2c30a3c96
commit 00bd1250ee
31 changed files with 1128 additions and 2 deletions

View File

@@ -115,6 +115,7 @@ import FaydaCallbackPage from "./pages/FaydaCallbackPage";
import { UserManagementRoutes } from "./user-management/route";
import SetPassword from "./shared/components/SetPassword";
import SupportInboxPage from "./pages/support/SupportInboxPage";
import ChatLaunchPage from "./pages/chat/ChatLaunchPage";
import {
APP_TITLE,
buildSidebarSections,
@@ -298,6 +299,14 @@ const App = () => {
</RequirePermission>
}
/>
<Route
path="chat"
element={
<RequirePermission permission={FREIGHT_PERMS.chat.view}>
<ChatLaunchPage />
</RequirePermission>
}
/>
<Route
path="customers"
element={

View File

@@ -71,6 +71,13 @@ const ROUTE_META: Array<{ prefix: string; meta: PageMeta }> = [
subtitle: "Manage your account and signature",
},
},
{
prefix: "/dashboard/chat",
meta: {
title: "Chat",
subtitle: "Internal messaging for EDR staff",
},
},
{
// Invoices, Payments, and USD Payments are tabs on one page now
// (FinanceHubPage); the header title itself is set per-tab there.

View File

@@ -32,6 +32,7 @@ import {
Users,
Wallet,
LifeBuoy,
MessageSquare,
TrainFront,
XCircle,
} from "lucide-react";
@@ -124,6 +125,12 @@ export const buildSidebarSections = (
icon: <LifeBuoy />,
permission: FREIGHT_PERMS.support.agentView,
},
{
label: "Chat",
href: "/dashboard/chat",
icon: <MessageSquare />,
permission: FREIGHT_PERMS.chat.view,
},
...demoItems,
],
},

View File

@@ -0,0 +1,13 @@
import { api } from "@/auth/http";
/**
* Internal chat (Matrix/Element) REST calls. Just the one endpoint — Chat
* itself is a separate app (chat.edr.et); this backoffice only ever asks for
* a fresh sign-in link into it.
*/
export const chatApi = {
getSsoUrl: async (): Promise<string> => {
const { data } = await api.get<{ url: string }>("/chat/sso");
return data.url;
},
};

View File

@@ -0,0 +1,19 @@
import { useQuery } from "@tanstack/react-query";
import { chatApi } from "./chatApi";
export const CHAT_SSO_KEY = ["chat", "sso"] as const;
/**
* The login_token this resolves to is single-use and expires in 5 minutes
* (Synapse default) — the global `staleTime: 0` (queryClient.ts) already
* means every fresh mount of the launch page refetches rather than reusing
* a possibly-spent link.
*/
export function useChatSso() {
return useQuery({
queryKey: CHAT_SSO_KEY,
queryFn: chatApi.getSsoUrl,
retry: false,
});
}

View File

@@ -33,6 +33,10 @@ export const FREIGHT_PERMS = {
staffUsers: {
view: "edr_freight_app:staff:users:view",
},
chat: {
view: "edr_freight_app:chat:view",
sync: "edr_freight_app:chat:sync",
},
bookings: {
view: "edr_freight_app:bookings:view",
create: "edr_freight_app:bookings:create",

View File

@@ -0,0 +1,62 @@
import { Alert, Button, Card, Center, Loader, Stack, Text } from "@mantine/core";
import { MessageSquare, TriangleAlert } from "lucide-react";
import { PageContainer, PageHeader } from "@/components/page";
import { useChatSso } from "@/features/chat/useChatSso";
/**
* Chat itself lives at chat.edr.et (Element), not in this app — this page's
* only job is a fresh one-click sign-in link into it. A real `<a>` (not
* `window.open()` in a click handler) so the browser never treats it as a
* blocked popup, and no iframe: Element's own CSP refuses to be framed.
*/
export default function ChatLaunchPage() {
const { data: url, isLoading, isError, refetch } = useChatSso();
return (
<PageContainer>
<PageHeader title="Chat" subtitle="Internal messaging for EDR staff" />
<Card withBorder radius="md" p="xl">
<Center>
<Stack align="center" gap="md" py="xl">
{isLoading && <Loader />}
{isError && (
<Stack align="center" gap="sm">
<Alert
icon={<TriangleAlert size={18} />}
color="red"
title="Couldn't get a sign-in link"
variant="light"
>
Something went wrong reaching chat. Try again.
</Alert>
<Button variant="light" onClick={() => refetch()}>
Retry
</Button>
</Stack>
)}
{url && (
<Stack align="center" gap="sm">
<MessageSquare size={40} strokeWidth={1.5} />
<Text c="dimmed" ta="center" maw={360}>
Opens EDR Chat in a new tab, already signed in as you.
</Text>
<Button
component="a"
href={url}
target="_blank"
rel="noopener noreferrer"
leftSection={<MessageSquare size={16} />}
>
Open EDR Chat
</Button>
</Stack>
)}
</Stack>
</Center>
</Card>
</PageContainer>
);
}