8000 Implement workspace delete by hakanshehu · Pull Request #21 · colanode/colanode · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Implement workspace delete #21

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
10000
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/desktop/src/main/mutations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import { AccountMetadataSaveMutationHandler } from '@/main/mutations/accounts/ac
import { AccountMetadataDeleteMutationHandler } from '@/main/mutations/accounts/account-metadata-delete';
import { EmailPasswordResetInitMutationHandler } from '@/main/mutations/accounts/email-password-reset-init';
import { EmailPasswordResetCompleteMutationHandler } from '@/main/mutations/accounts/email-password-reset-complete';
import { WorkspaceDeleteMutationHandler } from '@/main/mutations/workspaces/workspace-delete';
import { MutationHandler } from '@/main/lib/types';
import { MutationMap } from '@/shared/mutations';

Expand Down Expand Up @@ -141,4 +142,5 @@ export const mutationHandlerMap: MutationHandlerMap = {
email_password_reset_init: new EmailPasswordResetInitMutationHandler(),
email_password_reset_complete:
new EmailPasswordResetCompleteMutationHandler(),
workspace_delete: new WorkspaceDeleteMutationHandler(),
};
50 changes: 50 additions & 0 deletions apps/desktop/src/main/mutations/workspaces/workspace-delete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { WorkspaceOutput } from '@colanode/core';

import { appService } from '@/main/services/app-service';
import { MutationHandler } from '@/main/lib/types';
import { parseApiError } from '@/shared/lib/axios';
import { MutationError, MutationErrorCode } from '@/shared/mutations';
import {
WorkspaceDeleteMutationInput,
WorkspaceDeleteMutationOutput,
} from '@/shared/mutations/workspaces/workspace-delete';

export class WorkspaceDeleteMutationHandler
implements MutationHandler<WorkspaceDeleteMutationInput>
{
async handleMutation(
input: WorkspaceDeleteMutationInput
): Promise<WorkspaceDeleteMutationOutput> {
const accountService = appService.getAccount(input.accountId);

if (!accountService) {
throw new MutationError(
MutationErrorCode.AccountNotFound,
'Account not found or has been logged out.'
);
}

const workspaceService = accountService.getWorkspace(input.workspaceId);
if (!workspaceService) {
throw new MutationError(
MutationErrorCode.WorkspaceNotFound,
'Workspace not found.'
);
}

try {
const { data } = await accountService.client.delete<WorkspaceOutput>(
`/v1/workspaces/${input.workspaceId}`
);

await accountService.deleteWorkspace(data.id);

return {
id: data.id,
};
} catch (error) {
const apiError = parseApiError(error);
throw new MutationError(MutationErrorCode.ApiError, apiError.message);
}
}
}
9 changes: 8 additions & 1 deletion apps/desktop/src/main/queries/interactions/radar-data-get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,14 @@ export class RadarDataGetQueryHandler
_: RadarDataGetQueryInput,
___: RadarDataGetQueryOutput
): Promise<ChangeCheckResult<RadarDataGetQueryInput>> {
if (event.type === 'radar_data_updated') {
const shouldUpdate =
event.type === 'radar_data_updated' ||
event.type === 'workspace_created' ||
event.type === 'workspace_deleted' ||
event.type === 'account_created' ||
event.type === 'account_deleted';

if (shouldUpdate) {
const data = this.getRadarData();
return {
hasChanges: true,
Expand Down
4 changes: 4 additions & 0 deletions apps/desktop/src/main/services/server-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ export class ServerService {
return this.server.domain;
}

public get version() {
return this.server.version;
}

private async sync() {
const config = await ServerService.fetchServerConfig(this.server.domain);
const existingState = this.state;
Expand Down
83 changes: 43 additions & 40 deletions apps/desktop/src/renderer/components/accounts/account.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Account as AccountType } from '@/shared/types/accounts';
import { useQuery } from '@/renderer/hooks/use-query';
import { WorkspaceCreate } from '@/renderer/components/workspaces/workspace-create';
import { Workspace } from '@/renderer/components/workspaces/workspace';
import { ServerProvider } from '@/renderer/components/servers/server-provider';

interface AccountProps {
account: AccountType;
Expand Down Expand Up @@ -56,45 +57,47 @@ export const Account = ({ account }: AccountProps) => {
: undefined;

return (
<AccountContext.Provider
value={{
...account,
openSettings: () => setOpenSettings(true),
openLogout: () => setOpenLogout(true),
openWorkspaceCreate: () => setOpenCreateWorkspace(true),
openWorkspace: (id) => {
setOpenCreateWorkspace(false);
window.colanode.executeMutation({
type: 'account_metadata_save',
accountId: account.id,
key: 'workspace',
value: id,
});
},
}}
>
{!openCreateWorkspace && workspace ? (
<Workspace workspace={workspace} />
) : (
<WorkspaceCreate
>
>
/>
)}
{openSettings && (
<AccountSettingsDialog
open={true}
=> setOpenSettings(false)}
/>
)}
{openLogout && (
<AccountLogout
=> setOpenLogout(false)}
=> {
setOpenLogout(false);
}}
/>
)}
</AccountContext.Provider>
<ServerProvider domain={account.server}>
<AccountContext.Provider
value={{
...account,
openSettings: () => setOpenSettings(true),
openLogout: () => setOpenLogout(true),
openWorkspaceCreate: () => setOpenCreateWorkspace(true),
openWorkspace: (id) => {
setOpenCreateWorkspace(false);
window.colanode.executeMutation({
type: 'account_metadata_save',
accountId: account.id,
key: 'workspace',
value: id,
});
},
}}
>
{!openCreateWorkspace && workspace ? (
<Workspace workspace={workspace} />
) : (
<WorkspaceCreate
F438 >
>
/>
)}
{openSettings && (
<AccountSettingsDialog
open={true}
=> setOpenSettings(false)}
/>
)}
{openLogout && (
<AccountLogout
=> setOpenLogout(false)}
=> {
setOpenLogout(false);
}}
/>
)}
</AccountContext.Provider>
</ServerProvider>
);
};
20 changes: 20 additions & 0 deletions apps/desktop/src/renderer/components/servers/server-not-found.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { BadgeAlert } from 'lucide-react';

interface ServerNotFoundProps {
domain: string;
}

export const ServerNotFound = ({ domain }: ServerNotFoundProps) => {
return (
<div className="flex flex-col items-center justify-center h-full p-6 text-center">
<BadgeAlert className="size-12 mb-4" />
<h1 className="text-2xl font-semibold tracking-tight">
Server not found
</h1>
<p className="mt-2 text-sm font-medium text-muted-foreground">
The server {domain} does not exist. It may have been deleted from your
app or the data has been lost.
</p>
</div>
);
};
38 changes: 38 additions & 0 deletions apps/desktop/src/renderer/components/servers/server-provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { ServerContext } from '@/renderer/contexts/server';
import { useQuery } from '@/renderer/hooks/use-query';
import { ServerNotFound } from '@/renderer/components/servers/server-not-found';
import { isFeatureSupported } from '@/shared/lib/features';

interface ServerProviderProps {
domain: string;
children: React.ReactNode;
}

export const ServerProvider = ({ domain, children }: ServerProviderProps) => {
const { data, isPending } = useQuery({
type: 'server_list',
});

const server = data?.find((server) => server.domain === domain);

if (isPending) {
return null;
}

if (!server) {
return <ServerNotFound domain={domain} />;
}

return (
<ServerContext.Provider
value={{
...server,
supports: (feature) => {
return isFeatureSupported(feature, server.version);
},
}}
>
{children}
</ServerContext.Provider>
);
};
115 changes: 115 additions & 0 deletions apps/desktop/src/renderer/components/workspaces/workspace-delete.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import React from 'react';

import { Button } from '@/renderer/components/ui/button';
import { useWorkspace } from '@/renderer/contexts/workspace';
import { useMutation } from '@/renderer/hooks/use-mutation';
import { toast } from '@/renderer/hooks/use-toast';
import {
AlertDialog,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@/renderer/components/ui/alert-dialog';
import { Spinner } from '@/renderer/components/ui/spinner';
import { useServer } from '@/renderer/contexts/server';

interface WorkspaceDeleteProps {
onDeleted: () => void;
}

export const WorkspaceDelete = ({ onDeleted }: WorkspaceDeleteProps) => {
const server = useServer();
const workspace = useWorkspace();
const { mutate, isPending } = useMutation();

const [showDeleteModal, setShowDeleteModal] = React.useState(false);
const isDeleteSupported = server.supports('workspace-delete');

if (!isDeleteSupported) {
return (
<div className="flex flex-col gap-4">
<h3 className="font-heading mb-px text-2xl font-semibold tracking-tight">
Delete workspace
</h3>
<p>
This feature is not supported on the server this workspace is hosted
on. Please contact your administrator to upgrade the server.
</p>
</div>
);
}

return (
<div className="flex flex-col gap-4">
<h3 className="font-heading mb-px text-2xl font-semibold tracking-tight">
Delete workspace
</h3>
<p>Deleting a workspace is permanent and cannot be undone.</p>
<p>
All data associated with the workspace will be deleted, including users,
chats, messages, pages, channels, databases, records, files and more.
</p>
<div>
<Button
variant="destructive"
=> {
setShowDeleteModal(true);
}}
>
Delete
</Button>
</div>
<AlertDialog open={showDeleteModal} >
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
Are you sure you want delete this workspace?
</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This workspace will no longer be
accessible by you or other users that are part of it.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<Button
variant="destructive"
disabled={isPending}
=> {
mutate({
input: {
type: 'workspace_delete',
accountId: workspace.accountId,
workspaceId: workspace.id,
},
onSuccess() {
setShowDeleteModal(false);
onDeleted();
toast({
title: 'Workspace deleted',
description: 'Workspace was deleted successfully',
variant: 'default',
});
},
onError(error) {
toast({
title: 'Failed to delete workspace',
description: error.message,
variant: 'destructive',
});
},
});
}}
>
{isPending && <Spinner className="mr-1" />}
Delete
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
import { WorkspaceUpdate } from '@/renderer/components/workspaces/workspace-update';
import { WorkspaceUsers } from '@/renderer/components/workspaces/workspace-users';
import { useWorkspace } from '@/renderer/contexts/workspace';
import { WorkspaceDelete } from '@/renderer/components/workspaces/workspace-delete';

interface WorkspaceSettingsDialogProps {
open: boolean;
Expand Down Expand Up @@ -98,6 +99,7 @@ export const WorkspaceSettingsDialog = ({
<SidebarMenuButton
isActive={tab === 'delete'}
=> setTab('delete')}
disabled={workspace.role !== 'owner'}
>
<Trash2 className="mr-2 size-4" />
<span>Delete</span>
Expand All @@ -113,7 +115,9 @@ export const WorkspaceSettingsDialog = ({
{match(tab)
.with('info', () => <WorkspaceUpdate />)
.with('users', () => <WorkspaceUsers />)
.with('delete', () => <p>Coming soon.</p>)
.with('delete', () => (
<WorkspaceDelete => onOpenChange(false)} />
))
.exhaustive()}
</div>
</SidebarProvider>
Expand Down
Loading
0