update: sensible action button confirm

This commit is contained in:
Théo LAGACHE
2026-03-02 11:21:20 +01:00
parent f1edffed17
commit e644cde514
5 changed files with 500 additions and 418 deletions
@@ -1,21 +1,25 @@
import {PageParams} from "@/types/next"; import { PageParams } from "@/types/next";
import {Page, PageActions, PageContent, PageHeader, PageTitle} from "@/features/layout/page"; import {
import {currentUser} from "@/lib/auth/current-user"; Page,
import {getActiveMember, getOrganization} from "@/lib/auth/auth"; PageActions,
import {notFound} from "next/navigation"; PageContent,
import {Metadata} from "next"; PageHeader,
import {OrganizationTabs} from "@/components/wrappers/dashboard/organization/tabs/organization-tabs"; PageTitle,
import {getOrganizationChannels} from "@/db/services/notification-channel"; } from "@/features/layout/page";
import {computeOrganizationPermissions} from "@/lib/acl/organization-acl"; import { currentUser } from "@/lib/auth/current-user";
import {getOrganizationStorageChannels} from "@/db/services/storage-channel"; import { getActiveMember, getOrganization } from "@/lib/auth/auth";
import {DeleteOrganizationButton} from "@/components/wrappers/dashboard/organization/delete-organization-button"; import { notFound } from "next/navigation";
import {EditOrganizationDialog} from "@/features/organization/components/edit-organization.dialog"; import { Metadata } from "next";
import {Button} from "@/components/ui/button"; import { OrganizationTabs } from "@/components/wrappers/dashboard/organization/tabs/organization-tabs";
import {GearIcon} from "@radix-ui/react-icons"; import { getOrganizationChannels } from "@/db/services/notification-channel";
import {db} from "@/db"; import { computeOrganizationPermissions } from "@/lib/acl/organization-acl";
import {isNull} from "drizzle-orm"; import { getOrganizationStorageChannels } from "@/db/services/storage-channel";
import { DeleteOrganizationButton } from "@/components/wrappers/dashboard/organization/delete-organization-button";
import { EditOrganizationDialog } from "@/features/organization/components/edit-organization.dialog";
import { db } from "@/db";
import { isNull } from "drizzle-orm";
import * as drizzleDb from "@/db"; import * as drizzleDb from "@/db";
import {eq} from "drizzle-orm"; import { eq } from "drizzle-orm";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Settings", title: "Settings",
@@ -24,18 +28,18 @@ export const metadata: Metadata = {
export default async function RoutePage(props: PageParams<{ slug: string }>) { export default async function RoutePage(props: PageParams<{ slug: string }>) {
const organization = await getOrganization({}); const organization = await getOrganization({});
const user = await currentUser(); const user = await currentUser();
const activeMember = await getActiveMember() const activeMember = await getActiveMember();
if (!organization || !activeMember || !user) { if (!organization || !activeMember || !user) {
notFound(); notFound();
} }
const notificationChannels = await getOrganizationChannels(organization.id) const notificationChannels = await getOrganizationChannels(organization.id);
const storageChannels = await getOrganizationStorageChannels(organization.id) const storageChannels = await getOrganizationStorageChannels(organization.id);
const permissions = computeOrganizationPermissions(activeMember); const permissions = computeOrganizationPermissions(activeMember);
const users = await db.query.user.findMany({ const users = await db.query.user.findMany({
where: (fields) => isNull(fields.deletedAt) where: (fields) => isNull(fields.deletedAt),
}); });
const organizationWithMembers = await db.query.organization.findFirst({ const organizationWithMembers = await db.query.organization.findFirst({
@@ -43,10 +47,10 @@ export default async function RoutePage(props: PageParams<{ slug: string }>) {
with: { with: {
members: { members: {
with: { with: {
user: true user: true,
} },
} },
} },
}); });
if (!organizationWithMembers) notFound(); if (!organizationWithMembers) notFound();
@@ -55,12 +59,11 @@ export default async function RoutePage(props: PageParams<{ slug: string }>) {
<Page> <Page>
<PageHeader> <PageHeader>
<PageTitle className="flex flex-col md:flex-row items-center justify-between w-full "> <PageTitle className="flex flex-col md:flex-row items-center justify-between w-full ">
<div className="min-w-full md:min-w-fit "> <div className="min-w-full md:min-w-fit ">Organization settings</div>
Organization settings
</div>
<div className="flex items-center gap-2 md:justify-between w-full "> <div className="flex items-center gap-2 md:justify-between w-full ">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{permissions.canManageSettings && organization.slug !== "default" && ( {permissions.canManageSettings &&
organization.slug !== "default" && (
<EditOrganizationDialog <EditOrganizationDialog
organization={organizationWithMembers} organization={organizationWithMembers}
users={users} users={users}
@@ -69,8 +72,11 @@ export default async function RoutePage(props: PageParams<{ slug: string }>) {
)} )}
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{permissions.canManageDangerZone && organization.slug !== "default" && ( {permissions.canManageDangerZone &&
<DeleteOrganizationButton organizationSlug={organization.slug}/> organization.slug !== "default" && (
<DeleteOrganizationButton
organizationSlug={organization.slug}
/>
)} )}
</div> </div>
</div> </div>
@@ -85,5 +91,5 @@ export default async function RoutePage(props: PageParams<{ slug: string }>) {
/> />
</PageContent> </PageContent>
</Page> </Page>
) );
} }
@@ -1,38 +1,43 @@
"use client" "use client";
import {NotificationChannelWith} from "@/db/schema/09_notification-channel"; import { NotificationChannelWith } from "@/db/schema/09_notification-channel";
import {CardsWithPagination} from "@/components/wrappers/common/cards-with-pagination"; import { CardsWithPagination } from "@/components/wrappers/common/cards-with-pagination";
import {useState} from "react"; import { useState } from "react";
import {EmptyStatePlaceholder} from "@/components/wrappers/common/empty-state-placeholder"; import { EmptyStatePlaceholder } from "@/components/wrappers/common/empty-state-placeholder";
import {OrganizationWithMembers} from "@/db/schema/03_organization"; import { OrganizationWithMembers } from "@/db/schema/03_organization";
import {StorageChannelWith} from "@/db/schema/12_storage-channel"; import { StorageChannelWith } from "@/db/schema/12_storage-channel";
import {ChannelCard} from "@/components/wrappers/dashboard/admin/channels/channel/channel-card/channel-card"; import { ChannelCard } from "@/components/wrappers/dashboard/admin/channels/channel/channel-card/channel-card";
import {ChannelAddEditModal} from "@/components/wrappers/dashboard/admin/channels/channel/channel-add-edit-modal"; import { ChannelAddEditModal } from "@/components/wrappers/dashboard/admin/channels/channel/channel-add-edit-modal";
import {ChannelKind, getChannelTextBasedOnKind} from "@/components/wrappers/dashboard/admin/channels/helpers/common"; import {
ChannelKind,
getChannelTextBasedOnKind,
} from "@/components/wrappers/dashboard/admin/channels/helpers/common";
type ChannelsSectionProps = { type ChannelsSectionProps = {
channels: NotificationChannelWith[] | StorageChannelWith[], channels: NotificationChannelWith[] | StorageChannelWith[];
organizations: OrganizationWithMembers[], organizations: OrganizationWithMembers[];
kind: ChannelKind, kind: ChannelKind;
defaultStorageChannelId?: string | null | undefined defaultStorageChannelId?: string | null | undefined;
} };
export const ChannelsSection = ({ export const ChannelsSection = ({
organizations, organizations,
channels, channels,
kind, kind,
defaultStorageChannelId defaultStorageChannelId,
}: ChannelsSectionProps) => { }: ChannelsSectionProps) => {
const [isAddModalOpen, setIsAddModalOpen] = useState(false); const [isAddModalOpen, setIsAddModalOpen] = useState(false);
const channelText = getChannelTextBasedOnKind(kind) const channelText = getChannelTextBasedOnKind(kind);
const hasChannels = channels.length > 0 const hasChannels = channels.length > 0;
return ( return (
<div className="h-full"> <div className="h-full">
<ChannelAddEditModal kind={kind} open={isAddModalOpen} onOpenChangeAction={setIsAddModalOpen} <ChannelAddEditModal
kind={kind}
open={isAddModalOpen}
onOpenChangeAction={setIsAddModalOpen}
adminView={false} adminView={false}
trigger={false}/> trigger={false}
/>
{hasChannels ? ( {hasChannels ? (
<div className="h-full"> <div className="h-full">
<CardsWithPagination <CardsWithPagination
@@ -50,11 +55,11 @@ export const ChannelsSection = ({
<EmptyStatePlaceholder <EmptyStatePlaceholder
text={`No ${channelText} channels configured yet`} text={`No ${channelText} channels configured yet`}
onClick={() => { onClick={() => {
setIsAddModalOpen(true) setIsAddModalOpen(true);
}} }}
className="h-full" className="h-full"
/> />
)} )}
</div> </div>
); );
} };
@@ -1,13 +1,13 @@
"use client"; "use client";
import {useMutation, useQueryClient} from "@tanstack/react-query"; import { useMutation, useQueryClient } from "@tanstack/react-query";
import {useRouter} from "next/navigation"; import { useRouter } from "next/navigation";
import {toast} from "sonner"; import { toast } from "sonner";
import {backupButtonAction} from "@/components/wrappers/dashboard/backup/backup-button/backup-button.action"; import { backupButtonAction } from "@/components/wrappers/dashboard/backup/backup-button/backup-button.action";
import {ButtonWithLoading} from "@/components/wrappers/common/button/button-with-loading"; import { Check, DatabaseZap, X } from "lucide-react";
import {Database, DatabaseZap} from "lucide-react"; import { useIsMobile } from "@/hooks/use-mobile";
import {useIsMobile} from "@/hooks/use-mobile"; import { ButtonWithConfirm } from "@/components/wrappers/common/button/button-with-confirm";
export type BackupButtonProps = { export type BackupButtonProps = {
databaseId: string; databaseId: string;
@@ -17,14 +17,18 @@ export type BackupButtonProps = {
export const BackupButton = (props: BackupButtonProps) => { export const BackupButton = (props: BackupButtonProps) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const router = useRouter(); const router = useRouter();
const isMobile = useIsMobile() const isMobile = useIsMobile();
const mutation = useMutation({ const mutation = useMutation({
mutationFn: async (databaseId: string) => { mutationFn: async (databaseId: string) => {
const backup = await backupButtonAction(databaseId); const backup = await backupButtonAction(databaseId);
if (backup?.data?.success) { if (backup?.data?.success) {
toast.success(backup.data.actionSuccess?.message || "Backup created successfully!"); toast.success(
queryClient.invalidateQueries({queryKey: ["database-data", props.databaseId]}); backup.data.actionSuccess?.message || "Backup created successfully!",
);
queryClient.invalidateQueries({
queryKey: ["database-data", props.databaseId],
});
router.refresh(); router.refresh();
} else { } else {
toast.error(backup?.serverError || "Failed to create backup."); toast.error(backup?.serverError || "Failed to create backup.");
@@ -36,14 +40,32 @@ export const BackupButton = (props: BackupButtonProps) => {
}; };
return ( return (
<ButtonWithLoading <ButtonWithConfirm
icon={<DatabaseZap/>} title="Create Backup"
disabled={props.disable} description={"Are you sure you want to create a backup?"}
isPending={mutation.isPending} button={{
size={"default"} main: {
onClick={async () => { text: isMobile ? "" : "Backup",
variant: "default",
icon: <DatabaseZap />,
},
confirm: {
className: "w-full",
text: "Yes, create backup",
icon: <Check />,
variant: "default",
onClick: async () => {
await HandleAction(); await HandleAction();
},
},
cancel: {
className: "w-full",
text: "No, cancel",
icon: <X />,
variant: "outline",
},
}} }}
>{isMobile ? "" : "Backup"}</ButtonWithLoading> isPending={mutation.isPending}
/>
); );
}; };
@@ -1,47 +1,50 @@
"use client" "use client";
import {DatabaseBackupActionsModal} from "@/components/wrappers/dashboard/database/backup/actions/backup-actions-modal"; import { DatabaseBackupActionsModal } from "@/components/wrappers/dashboard/database/backup/actions/backup-actions-modal";
import {DatabaseTabs} from "@/components/wrappers/dashboard/projects/database/database-tabs"; import { DatabaseTabs } from "@/components/wrappers/dashboard/projects/database/database-tabs";
import {Setting} from "@/db/schema/01_setting"; import { Setting } from "@/db/schema/01_setting";
import {BackupWith, DatabaseWith, Restoration} from "@/db/schema/07_database"; import { BackupWith, DatabaseWith, Restoration } from "@/db/schema/07_database";
import {MemberWithUser} from "@/db/schema/03_organization"; import { MemberWithUser } from "@/db/schema/03_organization";
import {useBackupModal} from "@/components/wrappers/dashboard/database/backup/backup-modal-context"; import { useBackupModal } from "@/components/wrappers/dashboard/database/backup/backup-modal-context";
import {DatabaseKpi} from "@/components/wrappers/dashboard/projects/database/database-kpi"; import { DatabaseKpi } from "@/components/wrappers/dashboard/projects/database/database-kpi";
import {useQuery, useQueryClient} from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import {getDatabaseDataAction} from "@/components/wrappers/dashboard/database/backup/actions/get-data.action"; import { getDatabaseDataAction } from "@/components/wrappers/dashboard/database/backup/actions/get-data.action";
import {PageContent, PageDescription, PageTitle} from "@/features/layout/page"; import {
import {capitalizeFirstLetter} from "@/utils/text"; PageContent,
import {RetentionPolicySheet} from "@/components/wrappers/dashboard/database/retention-policy/retention-policy-sheet"; PageDescription,
import {CronButton} from "@/components/wrappers/dashboard/database/cron-button/cron-button"; PageTitle,
import {ChannelPoliciesModal} from "@/components/wrappers/dashboard/database/channels-policy/policy-modal"; } from "@/features/layout/page";
import {HardDrive, Megaphone} from "lucide-react"; import { capitalizeFirstLetter } from "@/utils/text";
import {ImportModal} from "@/components/wrappers/dashboard/database/import/import-modal"; import { RetentionPolicySheet } from "@/components/wrappers/dashboard/database/retention-policy/retention-policy-sheet";
import {BackupButton} from "@/components/wrappers/dashboard/backup/backup-button/backup-button"; import { CronButton } from "@/components/wrappers/dashboard/database/cron-button/cron-button";
import { ChannelPoliciesModal } from "@/components/wrappers/dashboard/database/channels-policy/policy-modal";
import { HardDrive, Megaphone } from "lucide-react";
import { ImportModal } from "@/components/wrappers/dashboard/database/import/import-modal";
import { BackupButton } from "@/components/wrappers/dashboard/backup/backup-button/backup-button";
export type DatabaseContentProps = { export type DatabaseContentProps = {
settings: Setting, settings: Setting;
backups: BackupWith[], backups: BackupWith[];
restorations: Restoration[], restorations: Restoration[];
isAlreadyRestore: boolean, isAlreadyRestore: boolean;
database: DatabaseWith, database: DatabaseWith;
activeMember: MemberWithUser, activeMember: MemberWithUser;
totalBackups: number, totalBackups: number;
availableBackups: number, availableBackups: number;
successRate: number | null, successRate: number | null;
organizationId: string, organizationId: string;
activeOrganizationChannels: any[], activeOrganizationChannels: any[];
activeOrganizationStorageChannels: any[] activeOrganizationStorageChannels: any[];
} };
export const DatabaseContent = (props: DatabaseContentProps) => { export const DatabaseContent = (props: DatabaseContentProps) => {
const {} = useBackupModal(); const {} = useBackupModal();
const { data } = useQuery({
const {data} = useQuery({
queryKey: ["database-data", props.database.id], queryKey: ["database-data", props.database.id],
queryFn: async () => { queryFn: async () => {
const result = await getDatabaseDataAction({databaseId: props.database.id}); const result = await getDatabaseDataAction({
databaseId: props.database.id,
});
return result?.data; return result?.data;
}, },
initialData: { initialData: {
@@ -54,12 +57,13 @@ export const DatabaseContent = (props: DatabaseContentProps) => {
backups: props.backups, backups: props.backups,
restorations: props.restorations, restorations: props.restorations,
activeOrganizationChannels: props.activeOrganizationChannels, activeOrganizationChannels: props.activeOrganizationChannels,
activeOrganizationStorageChannels: props.activeOrganizationStorageChannels, activeOrganizationStorageChannels:
props.activeOrganizationStorageChannels,
stats: { stats: {
totalBackups: props.totalBackups, totalBackups: props.totalBackups,
availableBackups: props.availableBackups, availableBackups: props.availableBackups,
successRate: props.successRate successRate: props.successRate,
} },
}, },
staleTime: 0, staleTime: 0,
gcTime: 0, gcTime: 0,
@@ -69,16 +73,21 @@ export const DatabaseContent = (props: DatabaseContentProps) => {
const database = data?.database ?? props.database; const database = data?.database ?? props.database;
const backups = data?.backups ?? props.backups; const backups = data?.backups ?? props.backups;
const restorations = data?.restorations ?? props.restorations; const restorations = data?.restorations ?? props.restorations;
const activeOrganizationChannels = data?.activeOrganizationChannels ?? props.activeOrganizationChannels; const activeOrganizationChannels =
const activeOrganizationStorageChannels = data?.activeOrganizationStorageChannels ?? props.activeOrganizationStorageChannels; data?.activeOrganizationChannels ?? props.activeOrganizationChannels;
const activeOrganizationStorageChannels =
data?.activeOrganizationStorageChannels ??
props.activeOrganizationStorageChannels;
const stats = data?.stats ?? { const stats = data?.stats ?? {
totalBackups: props.totalBackups, totalBackups: props.totalBackups,
availableBackups: props.availableBackups, availableBackups: props.availableBackups,
successRate: props.successRate successRate: props.successRate,
}; };
const isAlreadyRestore = restorations.some((r) => r.status === "waiting"); const isAlreadyRestore = restorations.some((r) => r.status === "waiting");
const isAlreadyBackup = backups.some((b) => b.status === "waiting" || b.status === "ongoing"); const isAlreadyBackup = backups.some(
(b) => b.status === "waiting" || b.status === "ongoing",
);
const isMember = props.activeMember.role === "member"; const isMember = props.activeMember.role === "member";
@@ -92,26 +101,29 @@ export const DatabaseContent = (props: DatabaseContentProps) => {
{!isMember && ( {!isMember && (
<div className="flex items-center gap-2 md:justify-between w-full "> <div className="flex items-center gap-2 md:justify-between w-full ">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<RetentionPolicySheet database={database}/> <RetentionPolicySheet database={database} />
<CronButton database={database}/> <CronButton database={database} />
<ChannelPoliciesModal <ChannelPoliciesModal
database={database} database={database}
kind={"notification"} kind={"notification"}
icon={<Megaphone/>} icon={<Megaphone />}
channels={activeOrganizationChannels} channels={activeOrganizationChannels}
organizationId={props.organizationId} organizationId={props.organizationId}
/> />
<ChannelPoliciesModal <ChannelPoliciesModal
database={database} database={database}
icon={<HardDrive/>} icon={<HardDrive />}
kind={"storage"} kind={"storage"}
channels={activeOrganizationStorageChannels} channels={activeOrganizationStorageChannels}
organizationId={props.organizationId} organizationId={props.organizationId}
/> />
<ImportModal database={database}/> <ImportModal database={database} />
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<BackupButton disable={isAlreadyBackup} databaseId={database.id}/> <BackupButton
disable={isAlreadyBackup}
databaseId={database.id}
/>
</div> </div>
</div> </div>
)} )}
@@ -119,7 +131,9 @@ export const DatabaseContent = (props: DatabaseContentProps) => {
</div> </div>
{database.description && ( {database.description && (
<PageDescription className="mt-5 sm:mt-0">{database.description}</PageDescription> <PageDescription className="mt-5 sm:mt-0">
{database.description}
</PageDescription>
)} )}
<PageContent className="flex flex-col w-full h-full"> <PageContent className="flex flex-col w-full h-full">
@@ -129,7 +143,7 @@ export const DatabaseContent = (props: DatabaseContentProps) => {
availableBackups={stats.availableBackups} availableBackups={stats.availableBackups}
totalBackups={stats.totalBackups} totalBackups={stats.totalBackups}
/> />
<DatabaseBackupActionsModal/> <DatabaseBackupActionsModal />
<DatabaseTabs <DatabaseTabs
activeMember={props.activeMember} activeMember={props.activeMember}
settings={props.settings} settings={props.settings}
@@ -140,5 +154,5 @@ export const DatabaseContent = (props: DatabaseContentProps) => {
/> />
</PageContent> </PageContent>
</> </>
) );
} };
+71 -36
View File
@@ -7,29 +7,29 @@ import {
DropdownMenuItem, DropdownMenuItem,
DropdownMenuLabel, DropdownMenuLabel,
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuTrigger DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import {MoreHorizontal, Trash2} from "lucide-react"; import { Check, MoreHorizontal, Trash2, X } from "lucide-react";
import { ReloadIcon } from "@radix-ui/react-icons"; import { ReloadIcon } from "@radix-ui/react-icons";
import { StatusBadge } from "@/components/wrappers/common/status-badge"; import { StatusBadge } from "@/components/wrappers/common/status-badge";
import {Restoration} from "@/db/schema/07_database"; import { Restoration } from "@/db/schema/07_database";
import {formatLocalizedDate} from "@/utils/date-formatting"; import { formatLocalizedDate } from "@/utils/date-formatting";
import {useMutation, useQueryClient} from "@tanstack/react-query"; import { useMutation, useQueryClient } from "@tanstack/react-query";
import { import {
deleteRestoreAction, deleteRestoreAction,
rerunRestorationAction rerunRestorationAction,
} from "@/features/dashboard/restore/restore.action"; } from "@/features/dashboard/restore/restore.action";
import {toast} from "sonner"; import { toast } from "sonner";
import {TooltipCustom} from "@/components/wrappers/common/tooltip-custom"; import { TooltipCustom } from "@/components/wrappers/common/tooltip-custom";
import {MemberWithUser} from "@/db/schema/03_organization"; import { MemberWithUser } from "@/db/schema/03_organization";
import { ButtonWithConfirm } from "@/components/wrappers/common/button/button-with-confirm";
export function restoreColumns( export function restoreColumns(
isAlreadyRestore: boolean, isAlreadyRestore: boolean,
activeMember: MemberWithUser activeMember: MemberWithUser,
): ColumnDef<Restoration>[] { ): ColumnDef<Restoration>[] {
return[ return [
{ {
accessorKey: "id", accessorKey: "id",
header: "Reference", header: "Reference",
@@ -38,7 +38,7 @@ export function restoreColumns(
accessorKey: "createdAt", accessorKey: "createdAt",
header: "Created At", header: "Created At",
cell: ({ row }) => { cell: ({ row }) => {
return formatLocalizedDate(row.getValue("createdAt")) return formatLocalizedDate(row.getValue("createdAt"));
}, },
}, },
{ {
@@ -56,7 +56,6 @@ export function restoreColumns(
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const rowData: Restoration = row.original; const rowData: Restoration = row.original;
const mutationDeleteRestore = useMutation({ const mutationDeleteRestore = useMutation({
mutationFn: async () => { mutationFn: async () => {
const restoration = await deleteRestoreAction({ const restoration = await deleteRestoreAction({
@@ -66,7 +65,9 @@ export function restoreColumns(
if (restoration.data.success) { if (restoration.data.success) {
// @ts-ignore // @ts-ignore
toast.success(restoration.data.actionSuccess.message); toast.success(restoration.data.actionSuccess.message);
queryClient.invalidateQueries({queryKey: ["database-data", rowData.databaseId]}); queryClient.invalidateQueries({
queryKey: ["database-data", rowData.databaseId],
});
} else { } else {
// @ts-ignore // @ts-ignore
toast.error(restoration.data.actionError.message); toast.error(restoration.data.actionError.message);
@@ -83,7 +84,9 @@ export function restoreColumns(
if (restoration.data.success) { if (restoration.data.success) {
// @ts-ignore // @ts-ignore
toast.success(restoration.data.actionSuccess.message); toast.success(restoration.data.actionSuccess.message);
queryClient.invalidateQueries({queryKey: ["database-data", rowData.databaseId]}); queryClient.invalidateQueries({
queryKey: ["database-data", rowData.databaseId],
});
} else { } else {
// @ts-ignore // @ts-ignore
toast.error(restoration.data.actionError.message); toast.error(restoration.data.actionError.message);
@@ -91,51 +94,84 @@ export function restoreColumns(
}, },
}); });
const handleDelete = async () => { const handleDelete = async () => {
await mutationDeleteRestore.mutateAsync(); await mutationDeleteRestore.mutateAsync();
}; };
const handleRerunRestore = async () => { const handleRerunRestore = async () => {
await mutationRerunRestore.mutateAsync(); await mutationRerunRestore.mutateAsync();
};
}
return ( return (
<> <>
{activeMember.role != "member" && ( {activeMember.role !== "member" && (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0" type="button" onClick={(e) => e.stopPropagation()}> <Button
variant="ghost"
className="h-8 w-8 p-0"
type="button"
onClick={(e) => e.stopPropagation()}
>
<span className="sr-only">Open menu</span> <span className="sr-only">Open menu</span>
<MoreHorizontal className="h-4 w-4"/> <MoreHorizontal className="h-4 w-4" />
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuLabel>Actions</DropdownMenuLabel> <DropdownMenuLabel>Actions</DropdownMenuLabel>
{rowData.backupStorageId && ( {rowData.backupStorageId && (
<TooltipCustom disabled={isAlreadyRestore} text="Already a restoration waiting"> <TooltipCustom
disabled={isAlreadyRestore}
text="Already a restoration waiting"
>
<DropdownMenuItem <DropdownMenuItem
disabled={mutationRerunRestore.isPending || isAlreadyRestore} disabled={
mutationRerunRestore.isPending || isAlreadyRestore
}
onClick={async () => { onClick={async () => {
await handleRerunRestore(); await handleRerunRestore();
}} }}
> >
<ReloadIcon/> Rerun <ReloadIcon /> Rerun
</DropdownMenuItem> </DropdownMenuItem>
</TooltipCustom> </TooltipCustom>
)} )}
<DropdownMenuSeparator/> <DropdownMenuSeparator />
<DropdownMenuItem <DropdownMenuItem>
disabled={status == "waiting"} <ButtonWithConfirm
className="text-red-600" title="Delete Restoration"
onClick={async () => { description={
"Are you sure you want to delete this restoration?"
}
button={{
main: {
text: "Delete",
variant: "ghost",
icon: (
<Trash2 className="text-red-500 size-4 mr-px" />
),
disabled: status === "waiting",
className:
"text-red-500 hover:text-red-500 p-0 h-auto has-[>svg]:px-0",
},
confirm: {
className: "w-full",
text: "Yes, delete",
icon: <Check />,
variant: "destructive",
onClick: async () => {
await handleDelete(); await handleDelete();
},
},
cancel: {
className: "w-full",
text: "No, cancel",
icon: <X />,
variant: "outline",
},
}} }}
> isPending={mutationDeleteRestore.isPending}
<Trash2/> Delete />
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
@@ -144,6 +180,5 @@ export function restoreColumns(
); );
}, },
}, },
]; ];
} }