import "@fontsource-variable/inter";
import "@/styles/public-typography.css";
import { useMemo, useState } from "react";
import { Skeleton } from "@mantine/core";
import { useMediaQuery } from "@mantine/hooks";
import clsx from "clsx";
import { Link } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { IconSearch } from "@tabler/icons-react";
import { usePublicSpaceDirectoryQuery } from "@/features/public-space/queries/public-space-query.ts";
import { useAuthenticatedUser } from "@/features/public-space/hooks/use-authenticated-user.ts";
import { buildPublicSpaceUrl } from "@/features/page/page.utils.ts";
import { getAvatarUrl } from "@/lib/config.ts";
import { Error404 } from "@/components/ui/error-404.tsx";
import { DocumentTitle } from "@/components/ui/document-title.tsx";
import { MAIN_CONTENT_ID, SkipToMain } from "@/components/ui/skip-to-main.tsx";
import { AvatarIconType } from "@/features/attachments/types/attachment.types.ts";
import styles from "@/features/public-space/components/docs/docs-hub.module.css";
const TILE_COLORS = [
"#1f9d55",
"#2b4bd6",
"#7c3aed",
"#0e7490",
"#d9480f",
"#b42318",
"#a16207",
"#475569",
"#be185d",
"#0f766e",
"#4338ca",
"#65a30d",
];
function getInitials(name: string) {
return name
.split(/[\s&]+/)
.filter(Boolean)
.slice(0, 2)
.map((word) => word[0])
.join("")
.toUpperCase();
}
export default function PublicSpaceDirectoryPage() {
const { t } = useTranslation();
const { data, isLoading, isError, error } = usePublicSpaceDirectoryQuery();
const { data: currentUser } = useAuthenticatedUser();
const isMobile = useMediaQuery("(max-width: 48em)");
const [query, setQuery] = useState("");
const spaces = useMemo(() => {
const all = (data?.spaces ?? []).map((space, index) => ({
...space,
color: TILE_COLORS[index % TILE_COLORS.length],
initials: getInitials(space.name),
}));
const needle = query.trim().toLowerCase();
if (!needle) return all;
return all.filter(
(space) =>
space.name.toLowerCase().includes(needle) ||
space.description?.toLowerCase().includes(needle),
);
}, [data?.spaces, query]);
if (isError) {
if ([401, 403, 404].includes(error?.["response"]?.status)) {
return ;
}
return
{t("Error fetching page data.")}
;
}
const total = data?.spaces.length ?? 0;
const title = t("Documentation");
const searchLabel = isMobile ? t("Search...") : t("Search documentation...");
return (
{title.charAt(0).toUpperCase()}
{title}
{currentUser?.user ? (
{t("Open app")}
) : (
{t("Sign in")}
)}
{t("Spaces")}
{!isLoading && (
{total === 1
? t("1 published space")
: t("{{count}} published spaces", { count: total })}
)}
{isLoading && (
)}
{!isLoading && total === 0 && (
{t("No public spaces yet.")}
)}
{!isLoading && total > 0 && spaces.length === 0 && (
{t("No spaces match your search.")}
)}
{spaces.length > 0 && (
{spaces.map((space) => {
const logoUrl = getAvatarUrl(
space.logo,
AvatarIconType.SPACE_ICON,
);
return (
{logoUrl ? (
) : (
space.initials
)}
{space.name}
{space.description && (
{space.description}
)}
);
})}
)}
{data && (
)}
);
}