feat(rename): complete searchSpace to workspace transition across frontend and backend

- Introduced a comprehensive specification for renaming `searchSpace` to `workspace` in `surfsense_web` and `surfsense_desktop`, ensuring all TypeScript identifiers, React props, and local data structures are updated.
- Implemented migration shims for persisted local state to prevent data loss during the transition.
- Updated observability metrics and IPC channels to reflect the new naming convention.
- Removed legacy `active-search-space` module and replaced it with `active-workspace` to maintain consistency.
- Ensured no behavioral changes or data loss for users during the renaming process.
This commit is contained in:
Anish Sarkar 2026-07-06 15:12:40 +05:30
parent 2a020629c5
commit a8c1fb660d
259 changed files with 5480 additions and 2285 deletions

View file

@ -45,7 +45,7 @@ export function AutomationDetailContent({
<ShieldAlert className="mx-auto h-10 w-10 text-muted-foreground" aria-hidden />
<h2 className="mt-3 text-base font-semibold text-foreground">Access denied</h2>
<p className="mt-1 text-sm text-muted-foreground max-w-md mx-auto">
You don't have permission to view automations in this search space.
You don't have permission to view automations in this workspace.
</p>
</div>
);

View file

@ -32,7 +32,7 @@ export function AutomationEditContent({ workspaceId, automationId }: AutomationE
<ShieldAlert className="mx-auto h-10 w-10 text-muted-foreground" aria-hidden />
<h2 className="mt-3 text-base font-semibold text-foreground">Access denied</h2>
<p className="mt-1 text-sm text-muted-foreground max-w-md mx-auto">
You don't have permission to edit automations in this search space.
You don't have permission to edit automations in this workspace.
</p>
</div>
);

View file

@ -13,7 +13,7 @@ interface AutomationsContentProps {
/**
* Client orchestrator for the automations list page. Pulls the active
* search space's first page (via ``useAutomations`` ``automationsListAtom``)
* workspace's first page (via ``useAutomations`` ``automationsListAtom``)
* and the user's permissions, then decides between empty / loading / table.
*
* Read access is mandatory; anything else is hidden behind RBAC. The
@ -46,7 +46,7 @@ export function AutomationsContent({ workspaceId }: AutomationsContentProps) {
<ShieldAlert className="mx-auto h-10 w-10 text-muted-foreground" aria-hidden />
<h2 className="mt-3 text-base font-semibold text-foreground">Access denied</h2>
<p className="mt-1 text-sm text-muted-foreground max-w-md mx-auto">
You don't have permission to view automations in this search space.
You don't have permission to view automations in this workspace.
</p>
</div>
);

View file

@ -40,7 +40,7 @@ export function AutomationsEmptyState({ workspaceId, canCreate }: AutomationsEmp
</div>
) : (
<p className="mt-6 text-xs text-muted-foreground">
You don't have permission to create automations in this search space.
You don't have permission to create automations in this workspace.
</p>
)}
</div>

View file

@ -120,7 +120,7 @@ export function AutomationBuilderForm({
const [submitting, setSubmitting] = useState(false);
// Eligible models + the search-space-seeded defaults. Models are chosen per
// Eligible models + the workspace-seeded defaults. Models are chosen per
// automation on create; in edit mode the backend preserves the captured
// snapshot, so the picker is create-only.
const eligibleModels = useAutomationEligibleModels();

View file

@ -235,7 +235,7 @@ export function MentionTaskInput({
<ComposerSuggestionPopoverContent side="bottom">
<DocumentMentionPicker
ref={pickerRef}
searchSpaceId={workspaceId}
workspaceId={workspaceId}
onSelectionChange={handleSelection}
onDone={closePopover}
initialSelectedDocuments={mentions}

View file

@ -47,7 +47,7 @@ export function DeleteAutomationDialog({
async function handleConfirm() {
setSubmitting(true);
try {
await deleteAutomation({ automationId, searchSpaceId: workspaceId });
await deleteAutomation({ automationId, workspaceId: workspaceId });
onDeleted?.();
onOpenChange(false);
} finally {

View file

@ -31,7 +31,7 @@ export function AutomationNewContent({ workspaceId }: AutomationNewContentProps)
<ShieldAlert className="mx-auto h-10 w-10 text-muted-foreground" aria-hidden />
<h2 className="mt-3 text-base font-semibold text-foreground">Access denied</h2>
<p className="mt-1 text-sm text-muted-foreground max-w-md mx-auto">
You don't have permission to create automations in this search space.
You don't have permission to create automations in this workspace.
</p>
</div>
);

View file

@ -5,7 +5,7 @@ export default async function ChatsPage({ params }: { params: Promise<{ workspac
return (
<div className="w-full select-none">
<AllChatsWorkspaceContent searchSpaceId={workspace_id} />
<AllChatsWorkspaceContent workspaceId={workspace_id} />
</div>
);
}

View file

@ -52,12 +52,12 @@ export function DashboardClientLayout({
const isOnboardingPage = pathname?.includes("/onboard");
const isOwner = access?.is_owner ?? false;
const isSearchSpaceReady = activeWorkspaceId === workspaceId;
const isWorkspaceReady = activeWorkspaceId === workspaceId;
useEffect(() => {
if (isSearchSpaceReady) return;
if (isWorkspaceReady) return;
setHasCheckedOnboarding(false);
}, [isSearchSpaceReady]);
}, [isWorkspaceReady]);
useEffect(() => {
if (isOnboardingPage) {
@ -66,7 +66,7 @@ export function DashboardClientLayout({
}
if (
isSearchSpaceReady &&
isWorkspaceReady &&
!loading &&
!accessLoading &&
!globalConfigsLoading &&
@ -75,7 +75,7 @@ export function DashboardClientLayout({
!hasCheckedOnboarding
) {
// Onboarding is only relevant when no operator-provided
// global_llm_config.yaml exists. When it does, search spaces inherit
// global_llm_config.yaml exists. When it does, workspaces inherit
// the global config and should never be forced into onboarding.
if (globalConfigStatus?.exists) {
setHasCheckedOnboarding(true);
@ -102,7 +102,7 @@ export function DashboardClientLayout({
setHasCheckedOnboarding(true);
}
}, [
isSearchSpaceReady,
isWorkspaceReady,
loading,
accessLoading,
globalConfigsLoading,
@ -153,13 +153,13 @@ export function DashboardClientLayout({
setActiveWorkspaceIdState(activeSeacrhSpaceId);
// Sync to Electron store if stored value is null (first navigation)
if (electronAPI?.getActiveSearchSpace && electronAPI.setActiveSearchSpace) {
const setActiveSearchSpace = electronAPI.setActiveSearchSpace;
if (electronAPI?.getActiveWorkspace && electronAPI.setActiveWorkspace) {
const setActiveWorkspace = electronAPI.setActiveWorkspace;
electronAPI
.getActiveSearchSpace()
.getActiveWorkspace()
.then((stored: string | null) => {
if (!stored) {
setActiveSearchSpace(activeSeacrhSpaceId);
setActiveWorkspace(activeSeacrhSpaceId);
}
})
.catch(() => {});
@ -169,7 +169,7 @@ export function DashboardClientLayout({
// Determine if we should show loading
const shouldShowLoading =
!hasCheckedOnboarding &&
(!isSearchSpaceReady ||
(!isWorkspaceReady ||
loading ||
accessLoading ||
globalConfigsLoading ||
@ -214,7 +214,7 @@ export function DashboardClientLayout({
return (
<DocumentUploadDialogProvider>
<OnboardingTour />
<LayoutDataProvider searchSpaceId={workspaceId}>{children}</LayoutDataProvider>
<LayoutDataProvider workspaceId={workspaceId}>{children}</LayoutDataProvider>
</DocumentUploadDialogProvider>
);
}

View file

@ -579,7 +579,7 @@ export default function NewChatPage() {
error,
flow,
context: {
searchSpaceId: workspaceId,
workspaceId: workspaceId,
threadId,
},
});
@ -717,7 +717,7 @@ export default function NewChatPage() {
data: typeof threadMessagesQuery.data;
}>({ threadId: null, data: undefined });
// Reset thread-local runtime state on route/search-space changes. Data fetching
// Reset thread-local runtime state on route/workspace changes. Data fetching
// is handled by React Query below so the chat shell can render immediately.
useEffect(() => {
const nextThreadId = urlChatId > 0 ? urlChatId : null;
@ -755,7 +755,7 @@ export default function NewChatPage() {
chatId: thread.id,
title: thread.title,
chatUrl: `/dashboard/${thread.workspace_id ?? workspaceId}/new-chat/${thread.id}`,
searchSpaceId: thread.workspace_id ?? workspaceId,
workspaceId: thread.workspace_id ?? workspaceId,
visibility: thread.visibility,
hasComments: thread.has_comments ?? false,
});
@ -892,7 +892,7 @@ export default function NewChatPage() {
}
setCurrentThreadMetadata({
id: null,
searchSpaceId: null,
workspaceId: null,
visibility: null,
hasComments: false,
});
@ -906,7 +906,7 @@ export default function NewChatPage() {
setCurrentThreadMetadata({
id: currentThread.id,
searchSpaceId: currentThread.workspace_id ?? workspaceId,
workspaceId: currentThread.workspace_id ?? workspaceId,
visibility,
hasComments: currentThread.has_comments ?? false,
});

View file

@ -74,7 +74,7 @@ export default function OnboardPage() {
</p>
</div>
<ModelProviderConnectionsPanel
searchSpaceId={workspaceId}
workspaceId={workspaceId}
connections={connections}
className="flex flex-col gap-6 text-left"
footerAction={

View file

@ -1,6 +1,6 @@
import { redirect } from "next/navigation";
export default async function SearchSpaceDashboardPage({
export default async function WorkspaceDashboardPage({
params,
}: {
params: Promise<{ workspace_id: string }>;

View file

@ -96,7 +96,7 @@ import type { Role } from "@/contracts/types/roles.types";
import { invitesApiService } from "@/lib/apis/invites-api.service";
import { rolesApiService } from "@/lib/apis/roles-api.service";
import { formatRelativeDate } from "@/lib/format-date";
import { trackSearchSpaceInviteSent, trackSearchSpaceUsersViewed } from "@/lib/posthog/events";
import { trackWorkspaceInviteSent, trackWorkspaceUsersViewed } from "@/lib/posthog/events";
import { cacheKeys } from "@/lib/query-client/cache-keys";
import { cn } from "@/lib/utils";
@ -229,7 +229,7 @@ export function TeamContent({ workspaceId }: TeamContentProps) {
useEffect(() => {
if (members.length > 0 && !membersLoading) {
const ownerCount = members.filter((m) => m.is_owner).length;
trackSearchSpaceUsersViewed(workspaceId, members.length, ownerCount);
trackWorkspaceUsersViewed(workspaceId, members.length, ownerCount);
}
}, [members, membersLoading, workspaceId]);
@ -654,7 +654,7 @@ function CreateInviteDialog({
setCreatedInvite(invite);
const roleName = roleId ? roles.find((r) => r.id.toString() === roleId)?.name : undefined;
trackSearchSpaceInviteSent(workspaceId, {
trackWorkspaceInviteSent(workspaceId, {
roleName,
hasExpiry: !!expiresAt,
hasMaxUses: !!maxUses,

View file

@ -226,9 +226,7 @@ export function AgentPermissionsContent() {
}
if (!workspaceId) {
return (
<p className="text-sm text-muted-foreground">Open a search space to manage agent rules.</p>
);
return <p className="text-sm text-muted-foreground">Open a workspace to manage agent rules.</p>;
}
return (

View file

@ -13,15 +13,15 @@ import {
import { Separator } from "@/components/ui/separator";
import { Skeleton } from "@/components/ui/skeleton";
import { Switch } from "@/components/ui/switch";
import type { SearchSpace } from "@/contracts/types/workspace.types";
import type { Workspace } from "@/contracts/types/workspace.types";
import { useElectronAPI } from "@/hooks/use-platform";
import { searchSpacesApiService } from "@/lib/apis/workspaces-api.service";
import { workspacesApiService } from "@/lib/apis/workspaces-api.service";
export function DesktopContent() {
const api = useElectronAPI();
const [loading, setLoading] = useState(true);
const [searchSpaces, setSearchSpaces] = useState<SearchSpace[]>([]);
const [workspaces, setWorkspaces] = useState<Workspace[]>([]);
const [activeSpaceId, setActiveSpaceId] = useState<string | null>(null);
const [autoLaunchEnabled, setAutoLaunchEnabled] = useState(false);
@ -40,14 +40,14 @@ export function DesktopContent() {
setAutoLaunchSupported(hasAutoLaunchApi);
Promise.all([
api.getActiveSearchSpace?.() ?? Promise.resolve(null),
searchSpacesApiService.getSearchSpaces(),
api.getActiveWorkspace?.() ?? Promise.resolve(null),
workspacesApiService.getWorkspaces(),
hasAutoLaunchApi ? api.getAutoLaunch() : Promise.resolve(null),
])
.then(([spaceId, spaces, autoLaunch]) => {
if (!mounted) return;
setActiveSpaceId(spaceId);
if (spaces) setSearchSpaces(spaces);
if (spaces) setWorkspaces(spaces);
if (autoLaunch) {
setAutoLaunchEnabled(autoLaunch.enabled);
setAutoLaunchHidden(autoLaunch.openAsHidden);
@ -136,30 +136,30 @@ export function DesktopContent() {
}
};
const handleSearchSpaceChange = (value: string) => {
const handleWorkspaceChange = (value: string) => {
setActiveSpaceId(value);
api.setActiveSearchSpace?.(value);
toast.success("Default search space updated");
api.setActiveWorkspace?.(value);
toast.success("Default workspace updated");
};
return (
<div className="flex flex-col gap-4 md:gap-6">
<section>
<div className="pb-2 md:pb-3">
<h2 className="text-base md:text-lg font-semibold">Default Search Space</h2>
<h2 className="text-base md:text-lg font-semibold">Default Workspace</h2>
<p className="text-xs md:text-sm text-muted-foreground">
Choose which search space General Assist, Screenshot Assist, and Quick Assist use by
Choose which workspace General Assist, Screenshot Assist, and Quick Assist use by
default.
</p>
</div>
<div>
{searchSpaces.length > 0 ? (
<Select value={activeSpaceId ?? undefined} onValueChange={handleSearchSpaceChange}>
{workspaces.length > 0 ? (
<Select value={activeSpaceId ?? undefined} onValueChange={handleWorkspaceChange}>
<SelectTrigger className="w-full">
<SelectValue placeholder="Select a search space" />
<SelectValue placeholder="Select a workspace" />
</SelectTrigger>
<SelectContent>
{searchSpaces.map((space) => (
{workspaces.map((space) => (
<SelectItem key={space.id} value={String(space.id)}>
{space.name}
</SelectItem>
@ -167,9 +167,7 @@ export function DesktopContent() {
</SelectContent>
</Select>
) : (
<p className="text-sm text-muted-foreground">
No search spaces found. Create one first.
</p>
<p className="text-sm text-muted-foreground">No workspaces found. Create one first.</p>
)}
</div>
</section>

View file

@ -17,8 +17,8 @@ import {
} from "@/components/ui/select";
import { Separator } from "@/components/ui/separator";
import { Skeleton } from "@/components/ui/skeleton";
import type { SearchSpace } from "@/contracts/types/workspace.types";
import { searchSpacesApiService } from "@/lib/apis/workspaces-api.service";
import type { Workspace } from "@/contracts/types/workspace.types";
import { workspacesApiService } from "@/lib/apis/workspaces-api.service";
import { authenticatedFetch } from "@/lib/auth-fetch";
import { buildBackendUrl } from "@/lib/env-config";
import { cn } from "@/lib/utils";
@ -79,7 +79,7 @@ export function MessagingChannelsContent() {
const workspaceId = Number(params.workspace_id);
const [gatewayConfig, setGatewayConfig] = useState<GatewayConfigState>(null);
const [connections, setConnections] = useState<GatewayConnection[]>([]);
const [searchSpaces, setSearchSpaces] = useState<SearchSpace[]>([]);
const [workspaces, setWorkspaces] = useState<Workspace[]>([]);
const [pairing, setPairing] = useState<Pairing | null>(null);
const [pairingPlatform, setPairingPlatform] = useState<PairingPlatform | null>(null);
const [baileysHealth, setBaileysHealth] = useState<BaileysHealth | null>(null);
@ -114,11 +114,11 @@ export function MessagingChannelsContent() {
const refresh = useCallback(async () => {
const [nextConnections, spaces, nextGatewayConfig] = await Promise.all([
fetchConnections(),
searchSpacesApiService.getSearchSpaces(),
workspacesApiService.getWorkspaces(),
fetchGatewayConfig(),
]);
setConnections(nextConnections);
setSearchSpaces(spaces);
setWorkspaces(spaces);
setGatewayConfig(nextGatewayConfig);
}, [fetchConnections, fetchGatewayConfig]);
@ -210,10 +210,7 @@ export function MessagingChannelsContent() {
await refreshPlatform(connection.platform as GatewayPlatform);
}
async function updateConnectionSearchSpace(
connection: GatewayConnection,
nextWorkspaceId: string
) {
async function updateConnectionWorkspace(connection: GatewayConnection, nextWorkspaceId: string) {
const previousConnections = connections;
const parsedWorkspaceId = Number(nextWorkspaceId);
const targetKey = connectionKey(connection);
@ -324,14 +321,14 @@ export function MessagingChannelsContent() {
<div className="flex flex-wrap items-center gap-2">
<Select
value={String(connection.workspace_id)}
onValueChange={(value) => updateConnectionSearchSpace(connection, value)}
disabled={searchSpaces.length === 0}
onValueChange={(value) => updateConnectionWorkspace(connection, value)}
disabled={workspaces.length === 0}
>
<SelectTrigger className="h-8 min-w-[180px] flex-1 text-xs">
<SelectValue placeholder="Select search space" />
<SelectValue placeholder="Select workspace" />
</SelectTrigger>
<SelectContent>
{searchSpaces.map((space) => (
{workspaces.map((space) => (
<SelectItem key={space.id} value={String(space.id)}>
{space.name}
</SelectItem>
@ -409,7 +406,7 @@ export function MessagingChannelsContent() {
<AlertDescription>
<p>
Soon you'll be able to connect WhatsApp, Telegram, Slack, and Discord to your
SurfSense agent so you can ask questions, route messages to search spaces, and get
SurfSense agent so you can ask questions, route messages to workspaces, and get
answers from your knowledge base without leaving your chat app.
</p>
</AlertDescription>

View file

@ -9,25 +9,20 @@ import { useCallback, useMemo, useState } from "react";
import { Separator } from "@/components/ui/separator";
import { cn } from "@/lib/utils";
export type SearchSpaceSettingsTab =
| "general"
| "models"
| "team-roles"
| "prompts"
| "public-links";
export type WorkspaceSettingsTab = "general" | "models" | "team-roles" | "prompts" | "public-links";
const DEFAULT_TAB: SearchSpaceSettingsTab = "general";
const DEFAULT_TAB: WorkspaceSettingsTab = "general";
interface SearchSpaceSettingsLayoutShellProps {
interface WorkspaceSettingsLayoutShellProps {
workspaceId: string;
children: React.ReactNode;
}
export function SearchSpaceSettingsLayoutShell({
export function WorkspaceSettingsLayoutShell({
workspaceId,
children,
}: SearchSpaceSettingsLayoutShellProps) {
const t = useTranslations("searchSpaceSettings");
}: WorkspaceSettingsLayoutShellProps) {
const t = useTranslations("workspaceSettings");
const segment = useSelectedLayoutSegment();
const [tabScrollPos, setTabScrollPos] = useState<"start" | "middle" | "end">("start");
@ -69,13 +64,13 @@ export function SearchSpaceSettingsLayoutShell({
[t]
);
const activeTab: SearchSpaceSettingsTab =
const activeTab: WorkspaceSettingsTab =
segment && navItems.some((item) => item.value === segment)
? (segment as SearchSpaceSettingsTab)
? (segment as WorkspaceSettingsTab)
: DEFAULT_TAB;
const selectedLabel = navItems.find((item) => item.value === activeTab)?.label ?? t("title");
const hrefFor = (tab: SearchSpaceSettingsTab) =>
const hrefFor = (tab: WorkspaceSettingsTab) =>
`/dashboard/${workspaceId}/workspace-settings/${tab}`;
return (

View file

@ -1,8 +1,8 @@
import type React from "react";
import { use } from "react";
import { SearchSpaceSettingsLayoutShell } from "./layout-shell";
import { WorkspaceSettingsLayoutShell } from "./layout-shell";
export default function SearchSpaceSettingsLayout({
export default function WorkspaceSettingsLayout({
params,
children,
}: {
@ -12,8 +12,8 @@ export default function SearchSpaceSettingsLayout({
const { workspace_id } = use(params);
return (
<SearchSpaceSettingsLayoutShell workspaceId={workspace_id}>
<WorkspaceSettingsLayoutShell workspaceId={workspace_id}>
{children}
</SearchSpaceSettingsLayoutShell>
</WorkspaceSettingsLayoutShell>
);
}

View file

@ -1,6 +1,6 @@
import { redirect } from "next/navigation";
export default async function SearchSpaceSettingsPage({
export default async function WorkspaceSettingsPage({
params,
}: {
params: Promise<{ workspace_id: string }>;

View file

@ -6,8 +6,8 @@ import { motion } from "motion/react";
import { useRouter } from "next/navigation";
import { useTranslations } from "next-intl";
import { useEffect, useState } from "react";
import { searchSpacesAtom } from "@/atoms/workspaces/workspace-query.atoms";
import { CreateSearchSpaceDialog } from "@/components/layout";
import { workspacesAtom } from "@/atoms/workspaces/workspace-query.atoms";
import { CreateWorkspaceDialog } from "@/components/layout";
import { Button } from "@/components/ui/button";
import { Card, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import { useGlobalLoadingEffect } from "@/hooks/use-global-loading";
@ -42,7 +42,7 @@ function ErrorScreen({ message }: { message: string }) {
}
function EmptyState({ onCreateClick }: { onCreateClick: () => void }) {
const t = useTranslations("searchSpace");
const t = useTranslations("workspace");
return (
<div className="flex min-h-screen flex-col items-center justify-center gap-4 p-4">
@ -74,26 +74,26 @@ export default function DashboardPage() {
const router = useRouter();
const [showCreateDialog, setShowCreateDialog] = useState(false);
const { data: searchSpaces = [], isLoading, error } = useAtomValue(searchSpacesAtom);
const { data: workspaces = [], isLoading, error } = useAtomValue(workspacesAtom);
useEffect(() => {
if (isLoading) return;
if (searchSpaces.length > 0) {
if (workspaces.length > 0) {
// Read the query string at the time of redirect — no subscription needed.
// (Vercel Best Practice: rerender-defer-reads 5.2)
const query = window.location.search;
router.replace(`/dashboard/${searchSpaces[0].id}/new-chat${query}`);
router.replace(`/dashboard/${workspaces[0].id}/new-chat${query}`);
}
}, [isLoading, searchSpaces, router]);
}, [isLoading, workspaces, router]);
// Show loading while fetching or while we have spaces and are about to redirect
const shouldShowLoading = isLoading || searchSpaces.length > 0;
const shouldShowLoading = isLoading || workspaces.length > 0;
// Use global loading screen - spinner animation won't reset
useGlobalLoadingEffect(shouldShowLoading);
if (error) return <ErrorScreen message={error?.message || "Failed to load search spaces"} />;
if (error) return <ErrorScreen message={error?.message || "Failed to load workspaces"} />;
if (shouldShowLoading) {
return null;
@ -102,7 +102,7 @@ export default function DashboardPage() {
return (
<>
<EmptyState onCreateClick={() => setShowCreateDialog(true)} />
<CreateSearchSpaceDialog open={showCreateDialog} onOpenChange={setShowCreateDialog} />
<CreateWorkspaceDialog open={showCreateDialog} onOpenChange={setShowCreateDialog} />
</>
);
}

View file

@ -14,7 +14,7 @@ import { Separator } from "@/components/ui/separator";
import { ShortcutKbd } from "@/components/ui/shortcut-kbd";
import { Spinner } from "@/components/ui/spinner";
import { useElectronAPI } from "@/hooks/use-platform";
import { searchSpacesApiService } from "@/lib/apis/workspaces-api.service";
import { workspacesApiService } from "@/lib/apis/workspaces-api.service";
import { getPostLoginRedirectPath } from "@/lib/auth-utils";
type ShortcutKey = "generalAssist" | "quickAsk" | "screenshotAssist";
@ -239,7 +239,7 @@ export default function DesktopLoginPage() {
setIsGoogleRedirecting(true);
try {
await api?.startGoogleOAuth?.();
await autoSetSearchSpace();
await autoSetWorkspace();
router.push(getPostLoginRedirectPath());
} catch (error) {
setIsGoogleRedirecting(false);
@ -247,13 +247,13 @@ export default function DesktopLoginPage() {
}
};
const autoSetSearchSpace = async () => {
const autoSetWorkspace = async () => {
try {
const stored = await api?.getActiveSearchSpace?.();
const stored = await api?.getActiveWorkspace?.();
if (stored) return;
const spaces = await searchSpacesApiService.getSearchSpaces();
const spaces = await workspacesApiService.getWorkspaces();
if (spaces?.length) {
await api?.setActiveSearchSpace?.(String(spaces[0].id));
await api?.setActiveWorkspace?.(String(spaces[0].id));
}
} catch {
// non-critical — dashboard-sync will catch it later
@ -272,7 +272,7 @@ export default function DesktopLoginPage() {
}
await api.loginPassword(email, password);
await autoSetSearchSpace();
await autoSetWorkspace();
setTimeout(() => {
router.push(getPostLoginRedirectPath());

View file

@ -34,9 +34,9 @@ import { useSession } from "@/hooks/use-session";
import { invitesApiService } from "@/lib/apis/invites-api.service";
import { setRedirectPath } from "@/lib/auth-utils";
import {
trackSearchSpaceInviteAccepted,
trackSearchSpaceInviteDeclined,
trackSearchSpaceUserAdded,
trackWorkspaceInviteAccepted,
trackWorkspaceInviteDeclined,
trackWorkspaceUserAdded,
} from "@/lib/posthog/events";
import { cacheKeys } from "@/lib/query-client/cache-keys";
@ -97,12 +97,8 @@ export default function InviteAcceptPage() {
setAcceptedData(result);
// Track invite accepted and user added events
trackSearchSpaceInviteAccepted(
result.workspace_id,
result.workspace_name,
result.role_name
);
trackSearchSpaceUserAdded(result.workspace_id, result.workspace_name, result.role_name);
trackWorkspaceInviteAccepted(result.workspace_id, result.workspace_name, result.role_name);
trackWorkspaceUserAdded(result.workspace_id, result.workspace_name, result.role_name);
}
} catch (err: any) {
setError(err.message || "Failed to accept invite");
@ -113,7 +109,7 @@ export default function InviteAcceptPage() {
const handleDecline = () => {
// Track invite declined event
trackSearchSpaceInviteDeclined(inviteInfo?.workspace_name);
trackWorkspaceInviteDeclined(inviteInfo?.workspace_name);
router.push("/dashboard");
};
@ -187,7 +183,7 @@ export default function InviteAcceptPage() {
</div>
<div>
<p className="font-medium">{acceptedData.workspace_name}</p>
<p className="text-sm text-muted-foreground">Search Space</p>
<p className="text-sm text-muted-foreground">Workspace</p>
</div>
</div>
<div className="flex items-center gap-3">
@ -206,7 +202,7 @@ export default function InviteAcceptPage() {
className="w-full gap-2"
onClick={() => router.push(`/dashboard/${acceptedData.workspace_id}`)}
>
Go to Search Space
Go to Workspace
<ArrowRight className="h-4 w-4" />
</Button>
</CardFooter>
@ -256,7 +252,7 @@ export default function InviteAcceptPage() {
</motion.div>
<CardTitle className="text-2xl">You're Invited!</CardTitle>
<CardDescription>
Sign in to join {inviteInfo?.workspace_name || "this search space"}
Sign in to join {inviteInfo?.workspace_name || "this workspace"}
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
@ -267,7 +263,7 @@ export default function InviteAcceptPage() {
</div>
<div>
<p className="font-medium">{inviteInfo?.workspace_name}</p>
<p className="text-sm text-muted-foreground">Search Space</p>
<p className="text-sm text-muted-foreground">Workspace</p>
</div>
</div>
{inviteInfo?.role_name && (
@ -303,7 +299,7 @@ export default function InviteAcceptPage() {
</motion.div>
<CardTitle className="text-2xl">You're Invited!</CardTitle>
<CardDescription>
Accept this invite to join {inviteInfo?.workspace_name || "this search space"}
Accept this invite to join {inviteInfo?.workspace_name || "this workspace"}
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
@ -314,7 +310,7 @@ export default function InviteAcceptPage() {
</div>
<div>
<p className="font-medium">{inviteInfo?.workspace_name}</p>
<p className="text-sm text-muted-foreground">Search Space</p>
<p className="text-sm text-muted-foreground">Workspace</p>
</div>
</div>
{inviteInfo?.role_name && (