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("Welcome to our documentation")}

{isMobile ? t("Guides, references and answers across all our spaces.") : t( "Guides, references and answers across all our published spaces.", )}

event.preventDefault()} > setQuery(event.target.value)} placeholder={searchLabel} aria-label={searchLabel} />

{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 && ( )}
); }