diff --git a/apps/builder/app/builder/features/project-settings/project-settings.tsx b/apps/builder/app/builder/features/project-settings/project-settings.tsx index 833fd2869ad1..3651a26715c1 100644 --- a/apps/builder/app/builder/features/project-settings/project-settings.tsx +++ b/apps/builder/app/builder/features/project-settings/project-settings.tsx @@ -1,3 +1,4 @@ +import type { FunctionComponent } from "react"; import { useStore } from "@nanostores/react"; import { Dialog, @@ -11,22 +12,24 @@ import { ListItem, Text, } from "@webstudio-is/design-system"; -import { $openProjectSettings } from "~/shared/nano-states/project-settings"; +import { + $openProjectSettings, + type SectionName, +} from "~/shared/nano-states/project-settings"; +import { $isDesignMode } from "~/shared/nano-states"; +import { leftPanelWidth, rightPanelWidth } from "./utils"; import { SectionGeneral } from "./section-general"; import { SectionRedirects } from "./section-redirects"; import { SectionPublish } from "./section-publish"; import { SectionMarketplace } from "./section-marketplace"; -import { leftPanelWidth, rightPanelWidth } from "./utils"; -import type { FunctionComponent } from "react"; -import { $isDesignMode } from "~/shared/nano-states"; - -type SectionName = "general" | "redirects" | "publish" | "marketplace"; +import { SectionBackups } from "./section-backups"; const sections = new Map([ ["general", SectionGeneral], ["redirects", SectionRedirects], ["publish", SectionPublish], ["marketplace", SectionMarketplace], + ["backups", SectionBackups], ] as const); export const ProjectSettingsView = ({ @@ -39,6 +42,9 @@ export const ProjectSettingsView = ({ onOpenChange?: (isOpen: boolean) => void; }) => { const isDesignMode = useStore($isDesignMode); + const SectionComponent = currentSection + ? sections.get(currentSection) + : undefined; return ( - + - {currentSection === "general" && } - {currentSection === "redirects" && } - {currentSection === "publish" && } - {currentSection === "marketplace" && } + {SectionComponent && }
diff --git a/apps/builder/app/builder/features/project-settings/section-backups.tsx b/apps/builder/app/builder/features/project-settings/section-backups.tsx new file mode 100644 index 000000000000..6986bde16e7b --- /dev/null +++ b/apps/builder/app/builder/features/project-settings/section-backups.tsx @@ -0,0 +1,146 @@ +import { useStore } from "@nanostores/react"; +import { useEffect, useState } from "react"; +import { + Grid, + Text, + Button, + Select, + Dialog, + DialogTitle, + DialogTrigger, + DialogContent, + DialogClose, + Flex, + theme, + toast, + PanelBanner, + Link, + rawTheme, +} from "@webstudio-is/design-system"; +import { UpgradeIcon } from "@webstudio-is/icons"; +import { nativeClient, trpcClient } from "~/shared/trpc/trpc-client"; +import { $project, $userPlanFeatures } from "~/shared/nano-states"; +import { sectionSpacing } from "./utils"; +import cmsUpgradeBanner from "../settings-panel/cms-upgrade-banner.svg?url"; + +const formatPublishDate = (date: string) => { + try { + const formatter = new Intl.DateTimeFormat("en", { + dateStyle: "long", + timeStyle: "short", + }); + return formatter.format(new Date(date)); + } catch { + return date; + } +}; + +export const SectionBackups = () => { + const { hasProPlan } = useStore($userPlanFeatures); + const { data, load } = trpcClient.project.publishedBuilds.useQuery(); + const projectId = $project.get()?.id ?? ""; + useEffect(() => { + load({ projectId }); + }, [load, projectId]); + const options = data?.success ? data.data : []; + const [backupBuild = options.at(0), setBackupBuild] = useState< + undefined | (typeof options)[number] + >(); + const restore = async () => { + if (!backupBuild?.buildId) { + return; + } + const result = await nativeClient.project.restoreDevelopmentBuild.mutate({ + projectId, + fromBuildId: backupBuild.buildId, + }); + if (result.success) { + location.reload(); + return; + } + toast.error(result.error); + }; + + return ( + + Backups +