Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { useMemo, useState } from "react";
import { HiCheck, HiChevronDown, HiOutlineUserCircle } from "react-icons/hi2";

interface CreateTaskAssigneePickerProps {
users: SelectUser[];
users: Pick<SelectUser, "id" | "name" | "email" | "image">[];
value: string | null;
onChange: (value: string | null) => void;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ interface MenuItemProps {
className?: string;
}

type AssigneeOption = Pick<SelectUser, "id" | "name" | "email" | "image">;

interface AssigneeMenuItemsProps {
users: SelectUser[];
users: AssigneeOption[];
currentAssigneeId: string | null;
hasExternalAssignee?: boolean;
onSelect: (userId: string | null) => void;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import {
ACCOUNT_DELETION_GRACE_DAYS,
COMPANY,
} from "@superset/shared/constants";
import { Button } from "@superset/ui/button";
import { useNavigate } from "@tanstack/react-router";
import { useState } from "react";
import { HiOutlineExclamationTriangle } from "react-icons/hi2";
import { useSignOut } from "renderer/hooks/useSignOut";
import { cloudTrpc } from "renderer/lib/cloud-trpc";

const DAY_MS = 24 * 60 * 60 * 1000;

export function PendingDeletionScreen({
deletionRequestedAt,
onReactivated,
}: {
deletionRequestedAt: Date;
onReactivated: () => void;
}) {
const navigate = useNavigate();
const signOut = useSignOut();
const [isSigningOut, setIsSigningOut] = useState(false);

const reactivate = cloudTrpc.user.reactivateAccount.useMutation({
onSuccess: onReactivated,
});

const purgeAt =
new Date(deletionRequestedAt).getTime() +
ACCOUNT_DELETION_GRACE_DAYS * DAY_MS;
const daysRemaining = Math.max(0, Math.ceil((purgeAt - Date.now()) / DAY_MS));
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

return (
<div className="relative flex h-screen w-screen flex-col items-center justify-center gap-4 bg-background">
<div className="drag absolute inset-x-0 top-0 h-12" />
<HiOutlineExclamationTriangle className="size-12 text-destructive" />
<div className="text-center select-text cursor-text">
<h2 className="text-lg font-medium">Account pending deletion</h2>
<p className="text-sm text-muted-foreground max-w-md">
Your account is deactivated and will be permanently deleted
{daysRemaining > 0
? ` in ${daysRemaining} ${daysRemaining === 1 ? "day" : "days"}`
: " soon"}
. Reactivate to restore it exactly as you left it.
</p>
</div>
{reactivate.error && (
<p className="text-sm text-destructive select-text cursor-text">
{reactivate.error.message}
</p>
)}
<div className="flex gap-2">
<Button
size="sm"
disabled={reactivate.isPending}
onClick={() => reactivate.mutate()}
>
{reactivate.isPending ? "Reactivating…" : "Reactivate my account"}
</Button>
<Button variant="outline" size="sm" asChild>
<a href={COMPANY.MAIL_TO}>Contact support</a>
</Button>
<Button
variant="outline"
size="sm"
disabled={isSigningOut}
onClick={async () => {
setIsSigningOut(true);
try {
await signOut();
} finally {
void navigate({ to: "/sign-in", replace: true });
}
}}
>
Sign out
</Button>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { PendingDeletionScreen } from "./PendingDeletionScreen";
10 changes: 10 additions & 0 deletions apps/desktop/src/renderer/routes/_authenticated/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { InitGitDialog } from "renderer/react-query/projects/InitGitDialog";
import { DaemonAutoUpdateFailureDialog } from "renderer/routes/_authenticated/components/DaemonAutoUpdateFailureDialog";
import { DashboardNewWorkspaceModal } from "renderer/routes/_authenticated/components/DashboardNewWorkspaceModal";
import { DiffThemeSync } from "renderer/routes/_authenticated/components/DiffThemeSync";
import { PendingDeletionScreen } from "renderer/routes/_authenticated/components/PendingDeletionScreen";
import {
V1AutoMigration,
V1MigrationContinuity,
Expand Down Expand Up @@ -262,6 +263,15 @@ function AuthenticatedLayout() {
return signInRedirect;
}

if (session?.user?.deletionRequestedAt) {
return (
<PendingDeletionScreen
deletionRequestedAt={session.user.deletionRequestedAt}
onReactivated={() => void refetch()}
/>
);
}

if (!activeOrganizationId) {
return createOrganizationRedirect;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
import { ACCOUNT_DELETION_GRACE_DAYS } from "@superset/shared/constants";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@superset/ui/alert-dialog";
import { Avatar } from "@superset/ui/atoms/Avatar";
import { Button } from "@superset/ui/button";
import { Input } from "@superset/ui/input";
Expand Down Expand Up @@ -33,6 +45,10 @@ export function AccountSettings({ visibleItems }: AccountSettingsProps) {
SETTING_ITEM_ID.ACCOUNT_SIGNOUT,
visibleItems,
);
const showDelete = isItemVisible(
SETTING_ITEM_ID.ACCOUNT_DELETE,
visibleItems,
);

const {
data: session,
Expand All @@ -45,9 +61,24 @@ export function AccountSettings({ visibleItems }: AccountSettingsProps) {
const [avatarPreview, setAvatarPreview] = useState<string | null>(null);

const signOut = useSignOut();
const [isDeleting, setIsDeleting] = useState(false);

const selectImageMutation = electronTrpc.window.selectImageFile.useMutation();

async function handleDeleteAccount() {
setIsDeleting(true);
try {
await apiTrpcClient.user.deleteAccount.mutate();
await signOut();
} catch (error) {
toast.error(
error instanceof Error ? error.message : "Failed to delete account",
);
} finally {
setIsDeleting(false);
}
}

useEffect(() => {
if (!user) return;
setNameValue(user.name ?? "");
Expand Down Expand Up @@ -169,6 +200,39 @@ export function AccountSettings({ visibleItems }: AccountSettingsProps) {
</SettingRow>
</div>
)}

{showDelete && (
<div className="pt-5">
<SettingRow label="Delete account">
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="destructive" disabled={isDeleting}>
{isDeleting ? "Deleting…" : "Delete account"}
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Delete account?</AlertDialogTitle>
<AlertDialogDescription>
All of your data will be permanently deleted after{" "}
{ACCOUNT_DELETION_GRACE_DAYS} days — sign back in before
then to restore your account.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
variant="destructive"
onClick={handleDeleteAccount}
>
Delete account
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</SettingRow>
</div>
)}
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export function MembersSettings({ visibleItems }: MembersSettingsProps) {
);

const { data: membersData, isPending } =
cloudTrpc.organization.listMembers.useQuery(undefined);
cloudTrpc.organization.listMembers.useQuery({ includeDeactivated: true });

const { data: orgData } = cloudTrpc.organization.list.useQuery(undefined);
const organization = orgData?.find((org) => org.id === activeOrganizationId);
Expand All @@ -60,6 +60,7 @@ export function MembersSettings({ visibleItems }: MembersSettingsProps) {
name: m.user.name,
email: m.user.email,
image: m.user.image,
deletionRequestedAt: m.user.deletionRequestedAt,
}))
.sort((a, b) => {
const priorityDiff =
Expand Down Expand Up @@ -159,7 +160,13 @@ export function MembersSettings({ visibleItems }: MembersSettingsProps) {
image={member.image}
/>
<div className="flex items-center gap-2">
<span className="font-medium">
<span
className={
member.deletionRequestedAt
? "font-medium text-muted-foreground"
: "font-medium"
}
>
{member.name || "Unknown"}
</span>
{isCurrentUserRow && (
Expand All @@ -170,6 +177,14 @@ export function MembersSettings({ visibleItems }: MembersSettingsProps) {
You
</Badge>
)}
{member.deletionRequestedAt && (
<Badge
variant="outline"
className="text-xs text-muted-foreground"
>
Deactivated
</Badge>
)}
</div>
</div>
</TableCell>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ export type TeamMember = {
name: string;
email: string;
image: string | null;
deletionRequestedAt: Date | null;
};
Loading
Loading