diff --git a/templates/clips/app/components/library/library-layout.tsx b/templates/clips/app/components/library/library-layout.tsx index 1bfe24bc81..1ec0d24b61 100644 --- a/templates/clips/app/components/library/library-layout.tsx +++ b/templates/clips/app/components/library/library-layout.tsx @@ -32,9 +32,11 @@ import { IconShare, IconSettings, IconSearch, + IconDots, + IconEdit, } from "@tabler/icons-react"; import { ReactNode, useEffect, useMemo, useState } from "react"; -import { NavLink, useLocation, useParams } from "react-router"; +import { NavLink, useLocation, useNavigate, useParams } from "react-router"; import { toast } from "sonner"; import { @@ -52,6 +54,12 @@ import { AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; import { Tooltip, TooltipContent, @@ -73,6 +81,7 @@ import { CreateSpaceDialog } from "./create-space-dialog"; import { FolderTree, type FolderNode } from "./folder-tree"; import { PageHeaderSlotProvider } from "./page-header"; import { SearchBar } from "./search-bar"; +import { SpaceDialogs } from "./space-dialogs"; interface LibraryLayoutProps { children: ReactNode; @@ -98,6 +107,7 @@ function ClipsAgentToggleButton() { export function LibraryLayout({ children }: LibraryLayoutProps) { const location = useLocation(); + const navigate = useNavigate(); const t = useT(); // Bind chat to the currently-open recording (`/r/:id`). Library, spaces, // meetings, dictate, and settings stay unscoped — those are list-y views @@ -123,9 +133,12 @@ export function LibraryLayout({ children }: LibraryLayoutProps) { const currentOrganizationId = organizations?.currentId ?? organizations?.organizations?.[0]?.id; - const { data: spaces } = useSpaces(currentOrganizationId, { - enabled: hasActiveOrg && Boolean(currentOrganizationId), - }); + const { data: spaces, refetch: refetchSpaces } = useSpaces( + currentOrganizationId, + { + enabled: hasActiveOrg && Boolean(currentOrganizationId), + }, + ); const { data: libFolders } = useFolders( { organizationId: currentOrganizationId, @@ -240,6 +253,10 @@ export function LibraryLayout({ children }: LibraryLayoutProps) { const [newFolderOpen, setNewFolderOpen] = useState(false); const [newFolderName, setNewFolderName] = useState(""); const [newSpaceOpen, setNewSpaceOpen] = useState(false); + const [deleteSpaceId, setDeleteSpaceId] = useState(null); + const [deleteSpaceName, setDeleteSpaceName] = useState(""); + const [renameSpaceId, setRenameSpaceId] = useState(null); + const [renameSpaceValue, setRenameSpaceValue] = useState(""); const createFolder = useCreateFolder(); const navItems: { @@ -540,26 +557,70 @@ export function LibraryLayout({ children }: LibraryLayoutProps) { const active = spaceId === s.id; return (
  • - -
    - {s.iconEmoji ?? s.name.slice(0, 1).toUpperCase()} -
    - {s.name} -
    +
    + {s.iconEmoji ?? + s.name.slice(0, 1).toUpperCase()} +
    + {s.name} + + {canManageOrg && ( + + + + + + { + setTimeout(() => { + setRenameSpaceValue(s.name); + setRenameSpaceId(s.id); + }, 0); + }} + > + + {t("spaceDialog.renameSpace")} + + { + setTimeout(() => { + setDeleteSpaceId(s.id); + setDeleteSpaceName(s.name); + }, 0); + }} + className="text-destructive" + > + + {t("spaceDialog.deleteSpace")} + + + + )} +
  • ); })} @@ -781,6 +842,23 @@ export function LibraryLayout({ children }: LibraryLayoutProps) { onOpenChange={setNewSpaceOpen} organizationId={currentOrganizationId} /> + + { + if (deletedSpaceId && spaceId === deletedSpaceId) { + navigate("/spaces"); + } + refetchSpaces?.(); + }} + /> ); } diff --git a/templates/clips/app/components/library/space-card.tsx b/templates/clips/app/components/library/space-card.tsx index dbe9916633..20c12a6ee8 100644 --- a/templates/clips/app/components/library/space-card.tsx +++ b/templates/clips/app/components/library/space-card.tsx @@ -1,8 +1,24 @@ -import { IconUsersGroup, IconVideo } from "@tabler/icons-react"; +import { useT } from "@agent-native/core/client/i18n"; +import { useOrgRole } from "@agent-native/core/client/org"; +import { + IconUsersGroup, + IconVideo, + IconTrash, + IconEdit, +} from "@tabler/icons-react"; +import { useState } from "react"; import { Link } from "react-router"; +import { + ContextMenu, + ContextMenuContent, + ContextMenuItem, + ContextMenuTrigger, +} from "@/components/ui/context-menu"; import { cn } from "@/lib/utils"; +import { SpaceDialogs } from "./space-dialogs"; + export interface SpaceCardData { id: string; name: string; @@ -16,80 +32,134 @@ export interface SpaceCardData { interface SpaceCardProps { space: SpaceCardData; className?: string; + onMutationSuccess?: () => void; } -export function SpaceCard({ space, className }: SpaceCardProps) { +export function SpaceCard({ + space, + className, + onMutationSuccess, +}: SpaceCardProps) { + const t = useT(); + const { canManageOrg } = useOrgRole(); const color = space.color || "hsl(var(--primary))"; const members = space.memberEmails ?? []; const initial = (space.name.trim().slice(0, 1) || "S").toUpperCase(); + const [renameSpaceId, setRenameSpaceId] = useState(null); + const [renameValue, setRenameValue] = useState(""); + const [deleteSpaceId, setDeleteSpaceId] = useState(null); return ( - -
    - {space.iconEmoji ? ( - {space.iconEmoji} - ) : ( - {initial} - )} -
    -
    -

    - {space.name} -

    -
    -
    - - - {space.memberCount ?? members.length} member - {(space.memberCount ?? members.length) === 1 ? "" : "s"} - -
    -
    - - - {space.recordingCount ?? 0} recording - {(space.recordingCount ?? 0) === 1 ? "" : "s"} - -
    -
    - - {members.length > 0 && ( -
    - {members.slice(0, 5).map((email) => { - const initials = (email.split("@")[0] || "?") - .slice(0, 2) - .toUpperCase(); - return ( -
    - {initials} + <> + + + +
    + {space.iconEmoji ? ( + {space.iconEmoji} + ) : ( + + {initial} + + )} +
    +
    +

    + {space.name} +

    +
    +
    + + + {space.memberCount ?? members.length} member + {(space.memberCount ?? members.length) === 1 ? "" : "s"} + +
    +
    + + + {space.recordingCount ?? 0} recording + {(space.recordingCount ?? 0) === 1 ? "" : "s"} +
    - ); - })} - {members.length > 5 && ( -
    - +{members.length - 5}
    - )} -
    + + {members.length > 0 && ( +
    + {members.slice(0, 5).map((email) => { + const initials = (email.split("@")[0] || "?") + .slice(0, 2) + .toUpperCase(); + return ( +
    + {initials} +
    + ); + })} + {members.length > 5 && ( +
    + +{members.length - 5} +
    + )} +
    + )} +
    + +
    + {canManageOrg && ( + + { + setTimeout(() => { + setRenameValue(space.name); + setRenameSpaceId(space.id); + }, 0); + }} + > + + {t("spaceDialog.renameSpace")} + + { + setTimeout(() => setDeleteSpaceId(space.id), 0); + }} + className="text-destructive focus:text-destructive" + > + + {t("spaceDialog.deleteSpace")} + + )} -
    - + + + + ); } diff --git a/templates/clips/app/components/library/space-dialogs.tsx b/templates/clips/app/components/library/space-dialogs.tsx new file mode 100644 index 0000000000..643e9689ec --- /dev/null +++ b/templates/clips/app/components/library/space-dialogs.tsx @@ -0,0 +1,144 @@ +import { useActionMutation } from "@agent-native/core/client/hooks"; +import { useT } from "@agent-native/core/client/i18n"; +import { useState } from "react"; +import { toast } from "sonner"; + +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; + +interface SpaceDialogsProps { + renameSpaceId: string | null; + renameSpaceName: string; + setRenameSpaceId: (id: string | null) => void; + renameValue: string; + setRenameValue: (value: string) => void; + deleteSpaceId: string | null; + deleteSpaceName: string; + setDeleteSpaceId: (id: string | null) => void; + onMutationSuccess?: (deletedSpaceId?: string) => void; +} + +export function SpaceDialogs({ + renameSpaceId, + renameSpaceName, + setRenameSpaceId, + renameValue, + setRenameValue, + deleteSpaceId, + deleteSpaceName, + setDeleteSpaceId, + onMutationSuccess, +}: SpaceDialogsProps) { + const t = useT(); + const renameSpace = useActionMutation("rename-space"); + const deleteSpace = useActionMutation("delete-space"); + + const handleRename = async () => { + if (!renameSpaceId) return; + try { + await renameSpace.mutateAsync({ + id: renameSpaceId, + name: renameValue.trim(), + }); + toast.success(t("spaceDialog.renamed")); + setRenameSpaceId(null); + setRenameValue(""); + onMutationSuccess?.(); + } catch (err) { + toast.error( + err instanceof Error ? err.message : t("spaceDialog.renameFailed"), + ); + } + }; + + const handleDelete = async () => { + if (!deleteSpaceId) return; + try { + await deleteSpace.mutateAsync({ id: deleteSpaceId }); + toast.success(t("spaceDialog.deleted", { name: deleteSpaceName })); + setDeleteSpaceId(null); + onMutationSuccess?.(deleteSpaceId); + } catch (err) { + toast.error( + err instanceof Error ? err.message : t("spaceDialog.deleteFailed"), + ); + } + }; + + return ( + <> + {/* Rename space dialog */} + { + if (!open) { + setRenameSpaceId(null); + setRenameValue(""); + } + }} + > + + {t("spaceDialog.renameSpace")} + setRenameValue(e.target.value)} + className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-primary/30" + /> +
    + {t("common.cancel")} + { + event.preventDefault(); + void handleRename(); + }} + disabled={renameSpace.isPending || !renameValue.trim()} + > + {renameSpace.isPending + ? t("spaceDialog.renaming") + : t("spaceDialog.renameSpace")} + +
    +
    +
    + + {/* Delete space dialog */} + { + if (!open) setDeleteSpaceId(null); + }} + > + + + {t("spaceDialog.deleteTitle", { name: deleteSpaceName })} + + + {t("spaceDialog.deleteDescription")} + +
    + {t("common.cancel")} + { + event.preventDefault(); + void handleDelete(); + }} + disabled={deleteSpace.isPending} + className="bg-destructive text-destructive-foreground hover:bg-destructive/90" + > + {deleteSpace.isPending + ? t("spaceDialog.deleting") + : t("spaceDialog.deleteSpace")} + +
    +
    +
    + + ); +} diff --git a/templates/clips/app/hooks/use-library.ts b/templates/clips/app/hooks/use-library.ts index 8356a57e35..91252264ff 100644 --- a/templates/clips/app/hooks/use-library.ts +++ b/templates/clips/app/hooks/use-library.ts @@ -240,11 +240,11 @@ export function useSpaces( organizationId?: string, options: { enabled?: boolean } = {}, ) { - const { data, isLoading } = useOrganizationState(organizationId, { + const { data, isLoading, refetch } = useOrganizationState(organizationId, { enabled: options.enabled ?? Boolean(organizationId), }); const spaces = Array.isArray(data?.spaces) ? (data.spaces as any[]) : []; - return { data: { spaces }, isLoading }; + return { data: { spaces }, isLoading, refetch }; } export function useOrganizations(options: { enabled?: boolean } = {}) { diff --git a/templates/clips/app/i18n/ar-SA.ts b/templates/clips/app/i18n/ar-SA.ts index ddc020ab29..cc570ccdf1 100644 --- a/templates/clips/app/i18n/ar-SA.ts +++ b/templates/clips/app/i18n/ar-SA.ts @@ -1113,6 +1113,19 @@ const messages = { spaceCreated: "تم إنشاء المساحة", createFailed: "تعذر إنشاء المساحة", }, + spaceDialog: { + deleteSpace: "حذف المساحة", + renameSpace: "إعادة تسمية المساحة", + deleteTitle: 'حذف "{{name}}"؟', + deleteDescription: + "سيؤدي ذلك إلى حذف المساحة وإزالتها من جميع التسجيلات. لا يمكن التراجع عن هذا الإجراء.", + renamed: "تمت إعادة تسمية المساحة", + deleted: 'تم حذف "{{name}}"', + renameFailed: "فشلت إعادة تسمية المساحة", + deleteFailed: "فشل حذف المساحة", + renaming: "جارٍ إعادة التسمية...", + deleting: "جارٍ الحذف...", + }, signInPrompt: { title: "سجّل الدخول من أجل {{intent}}", description: diff --git a/templates/clips/app/i18n/de-DE.ts b/templates/clips/app/i18n/de-DE.ts index a31d489ec4..fde25a8e51 100644 --- a/templates/clips/app/i18n/de-DE.ts +++ b/templates/clips/app/i18n/de-DE.ts @@ -1136,6 +1136,19 @@ Alle sichtbaren Änderungen für Clips-Nutzer werden hier dokumentiert. Du kanns spaceCreated: "Bereich erstellt", createFailed: "Bereich konnte nicht erstellt werden", }, + spaceDialog: { + deleteSpace: "Bereich löschen", + renameSpace: "Bereich umbenennen", + deleteTitle: "„{{name}}“ löschen?", + deleteDescription: + "Dadurch wird der Bereich gelöscht und aus allen Aufnahmen entfernt. Diese Aktion kann nicht rückgängig gemacht werden.", + renamed: "Bereich umbenannt", + deleted: "„{{name}}“ gelöscht", + renameFailed: "Bereich konnte nicht umbenannt werden", + deleteFailed: "Bereich konnte nicht gelöscht werden", + renaming: "Wird umbenannt...", + deleting: "Wird gelöscht...", + }, signInPrompt: { title: "Anmelden, um {{intent}}", description: diff --git a/templates/clips/app/i18n/en-US.ts b/templates/clips/app/i18n/en-US.ts index 3b96b947a1..9fef6d45cc 100644 --- a/templates/clips/app/i18n/en-US.ts +++ b/templates/clips/app/i18n/en-US.ts @@ -1101,6 +1101,19 @@ All notable user-facing changes to Clips are documented here. Open it any time f spaceCreated: "Space created", createFailed: "Could not create space", }, + spaceDialog: { + deleteSpace: "Delete space", + renameSpace: "Rename space", + deleteTitle: 'Delete "{{name}}"?', + deleteDescription: + "This will delete the space and remove it from all recordings. This action cannot be undone.", + renamed: "Space renamed", + deleted: 'Deleted "{{name}}"', + renameFailed: "Failed to rename space", + deleteFailed: "Failed to delete space", + renaming: "Renaming...", + deleting: "Deleting...", + }, signInPrompt: { title: "Sign in to {{intent}}", description: diff --git a/templates/clips/app/i18n/es-ES.ts b/templates/clips/app/i18n/es-ES.ts index 78aa6e7e9d..f1045ee727 100644 --- a/templates/clips/app/i18n/es-ES.ts +++ b/templates/clips/app/i18n/es-ES.ts @@ -1129,6 +1129,19 @@ Todos los cambios visibles para los usuarios de Clips se documentan aquí. Puede spaceCreated: "Espacio creado", createFailed: "No se pudo crear el espacio", }, + spaceDialog: { + deleteSpace: "Eliminar espacio", + renameSpace: "Renombrar espacio", + deleteTitle: "¿Eliminar «{{name}}»?", + deleteDescription: + "Esto eliminará el espacio y lo quitará de todas las grabaciones. Esta acción no se puede deshacer.", + renamed: "Espacio renombrado", + deleted: "Se eliminó «{{name}}»", + renameFailed: "No se pudo renombrar el espacio", + deleteFailed: "No se pudo eliminar el espacio", + renaming: "Renombrando...", + deleting: "Eliminando...", + }, signInPrompt: { title: "Inicia sesión para {{intent}}", description: diff --git a/templates/clips/app/i18n/fr-FR.ts b/templates/clips/app/i18n/fr-FR.ts index a9779f3b47..3a172b98a7 100644 --- a/templates/clips/app/i18n/fr-FR.ts +++ b/templates/clips/app/i18n/fr-FR.ts @@ -1131,6 +1131,19 @@ Tous les changements visibles par les utilisateurs de Clips sont documentés ici spaceCreated: "Espace créé", createFailed: "Impossible de créer l’espace", }, + spaceDialog: { + deleteSpace: "Supprimer l’espace", + renameSpace: "Renommer l’espace", + deleteTitle: "Supprimer « {{name}} » ?", + deleteDescription: + "Cette action supprimera l’espace et le retirera de tous les enregistrements. Elle est irréversible.", + renamed: "Espace renommé", + deleted: "« {{name}} » supprimé", + renameFailed: "Échec du renommage de l’espace", + deleteFailed: "Échec de la suppression de l’espace", + renaming: "Renommage...", + deleting: "Suppression...", + }, signInPrompt: { title: "Connectez-vous pour {{intent}}", description: diff --git a/templates/clips/app/i18n/hi-IN.ts b/templates/clips/app/i18n/hi-IN.ts index 78ad5f34c1..54ae57f451 100644 --- a/templates/clips/app/i18n/hi-IN.ts +++ b/templates/clips/app/i18n/hi-IN.ts @@ -1091,6 +1091,19 @@ Clips में उपयोगकर्ताओं को दिखने व spaceCreated: "स्पेस बनाई गई", createFailed: "स्पेस नहीं बनाई जा सकी", }, + spaceDialog: { + deleteSpace: "स्पेस हटाएं", + renameSpace: "स्पेस का नाम बदलें", + deleteTitle: '"{{name}}" हटाएं?', + deleteDescription: + "यह स्पेस को हटा देगा और सभी रिकॉर्डिंग से निकाल देगा। इस कार्रवाई को पूर्ववत नहीं किया जा सकता।", + renamed: "स्पेस का नाम बदल दिया गया", + deleted: '"{{name}}" हटा दिया गया', + renameFailed: "स्पेस का नाम बदलना विफल", + deleteFailed: "स्पेस हटाना विफल", + renaming: "नाम बदला जा रहा है...", + deleting: "हटाया जा रहा है...", + }, signInPrompt: { title: "{{intent}} के लिए साइन इन करें", description: diff --git a/templates/clips/app/i18n/ja-JP.ts b/templates/clips/app/i18n/ja-JP.ts index e715788248..b1bb97738c 100644 --- a/templates/clips/app/i18n/ja-JP.ts +++ b/templates/clips/app/i18n/ja-JP.ts @@ -1114,6 +1114,19 @@ Clips のユーザー向けの主な変更はここに記録されます。コ spaceCreated: "スペースを作成しました", createFailed: "スペースを作成できませんでした", }, + spaceDialog: { + deleteSpace: "スペースを削除", + renameSpace: "スペース名を変更", + deleteTitle: "「{{name}}」を削除しますか?", + deleteDescription: + "スペースを削除し、すべての録画から削除します。この操作は元に戻せません。", + renamed: "スペース名を変更しました", + deleted: "「{{name}}」を削除しました", + renameFailed: "スペース名の変更に失敗しました", + deleteFailed: "スペースの削除に失敗しました", + renaming: "名前を変更中...", + deleting: "削除中...", + }, signInPrompt: { title: "{{intent}}するにはログイン", description: diff --git a/templates/clips/app/i18n/ko-KR.ts b/templates/clips/app/i18n/ko-KR.ts index ca9671de0d..eb1430fa80 100644 --- a/templates/clips/app/i18n/ko-KR.ts +++ b/templates/clips/app/i18n/ko-KR.ts @@ -1102,6 +1102,19 @@ Clips의 모든 사용자 대상 변경 사항은 여기에 기록됩니다. 명 spaceCreated: "공간이 생성되었습니다", createFailed: "공간을 만들 수 없습니다", }, + spaceDialog: { + deleteSpace: "스페이스 삭제", + renameSpace: "스페이스 이름 변경", + deleteTitle: "“{{name}}”을(를) 삭제할까요?", + deleteDescription: + "스페이스가 삭제되고 모든 녹화에서 제거됩니다. 이 작업은 취소할 수 없습니다.", + renamed: "스페이스 이름이 변경됨", + deleted: "“{{name}}” 삭제됨", + renameFailed: "스페이스 이름 변경 실패", + deleteFailed: "스페이스 삭제 실패", + renaming: "이름 변경 중...", + deleting: "삭제 중...", + }, signInPrompt: { title: "{{intent}}하려면 로그인", description: diff --git a/templates/clips/app/i18n/pt-BR.ts b/templates/clips/app/i18n/pt-BR.ts index d08ec9675e..ea2127cdbe 100644 --- a/templates/clips/app/i18n/pt-BR.ts +++ b/templates/clips/app/i18n/pt-BR.ts @@ -1126,6 +1126,19 @@ Todas as mudanças visíveis para usuários do Clips são documentadas aqui. Voc spaceCreated: "Espaço criado", createFailed: "Não foi possível criar o espaço", }, + spaceDialog: { + deleteSpace: "Excluir espaço", + renameSpace: "Renomear espaço", + deleteTitle: 'Excluir "{{name}}"?', + deleteDescription: + "Isso excluirá o espaço e o removerá de todas as gravações. Esta ação não pode ser desfeita.", + renamed: "Espaço renomeado", + deleted: '"{{name}}" excluído', + renameFailed: "Falha ao renomear o espaço", + deleteFailed: "Falha ao excluir o espaço", + renaming: "Renomeando...", + deleting: "Excluindo...", + }, signInPrompt: { title: "Entre para {{intent}}", description: diff --git a/templates/clips/app/i18n/zh-CN.ts b/templates/clips/app/i18n/zh-CN.ts index bd9fff0c34..1f8505ff98 100644 --- a/templates/clips/app/i18n/zh-CN.ts +++ b/templates/clips/app/i18n/zh-CN.ts @@ -1060,6 +1060,18 @@ Clips 中所有面向用户的重要更改都会记录在这里。你可以随 spaceCreated: "空间已创建", createFailed: "无法创建空间", }, + spaceDialog: { + deleteSpace: "删除空间", + renameSpace: "重命名空间", + deleteTitle: "删除“{{name}}”?", + deleteDescription: "这将删除空间,并将其从所有录音中移除。此操作无法撤销。", + renamed: "空间已重命名", + deleted: "已删除“{{name}}”", + renameFailed: "重命名空间失败", + deleteFailed: "删除空间失败", + renaming: "正在重命名...", + deleting: "正在删除...", + }, signInPrompt: { title: "登录以{{intent}}", description: diff --git a/templates/clips/app/i18n/zh-TW.ts b/templates/clips/app/i18n/zh-TW.ts index 8f967dfcd9..eb46d20970 100644 --- a/templates/clips/app/i18n/zh-TW.ts +++ b/templates/clips/app/i18n/zh-TW.ts @@ -1054,6 +1054,18 @@ const messages = { spaceCreated: "空間已建立", createFailed: "無法建立空間", }, + spaceDialog: { + deleteSpace: "刪除空間", + renameSpace: "重新命名空間", + deleteTitle: "要刪除「{{name}}」嗎?", + deleteDescription: "這會刪除空間,並將其從所有錄影中移除。此操作無法復原。", + renamed: "空間已重新命名", + deleted: "已刪除「{{name}}」", + renameFailed: "重新命名空間失敗", + deleteFailed: "刪除空間失敗", + renaming: "正在重新命名...", + deleting: "正在刪除...", + }, signInPrompt: { title: "登入以{{intent}}", description: diff --git a/templates/clips/app/routes/_app.spaces._index.tsx b/templates/clips/app/routes/_app.spaces._index.tsx index 2fe0b88e45..31a04fcd9f 100644 --- a/templates/clips/app/routes/_app.spaces._index.tsx +++ b/templates/clips/app/routes/_app.spaces._index.tsx @@ -34,7 +34,7 @@ export default function SpacesIndexRoute() { const { data: organizations } = useOrganizations(); const currentOrganizationId = organizations?.currentId ?? organizations?.organizations?.[0]?.id; - const { data, isLoading } = useSpaces(currentOrganizationId); + const { data, isLoading, refetch } = useSpaces(currentOrganizationId); const spaces: SpaceCardData[] = (data?.spaces ?? []).map((s: any) => ({ id: s.id, @@ -80,7 +80,11 @@ export default function SpacesIndexRoute() { ) : (
    {spaces.map((s) => ( - + refetch?.()} + /> ))}
    )}