feat(demo): add on-demand demo data reset endpoint and UI button
This commit is contained in:
1 parent
7a26750e93
commit
dff5a155ef
6 files changed
+108
No files matched your search
@@ -2226,6 +2226,26 @@ app.get("/api/v1/resources/:id/blob", async (c) => {
|
||||
return new Response(object.body, { headers });
|
||||
});
|
||||
|
||||
app.post("/api/v1/demo/reset", async (c) => {
|
||||
if (!isDemoMode(c.env) && !isLocalDemoSeedEnabled(c.env)) {
|
||||
return c.json(
|
||||
{
|
||||
error: {
|
||||
code: "demo_mode_disabled",
|
||||
message: "Demo reset is only available when demo mode or local demo seed is enabled",
|
||||
},
|
||||
},
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
await resetDemoData(c.env, Date.now());
|
||||
return c.json({
|
||||
success: true,
|
||||
message: "Demo seed data successfully restored",
|
||||
});
|
||||
});
|
||||
|
||||
app.patch("/api/v1/memos/:id", zValidator("json", MemoUpdateSchema), async (c) => {
|
||||
const denied = requireScopes(c, "write:memos");
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
CheckCircle2,
|
||||
CircleUserRound,
|
||||
Download,
|
||||
RotateCcw,
|
||||
} from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
@@ -236,6 +237,9 @@ export const NotebookPane = ({
|
||||
authRequired,
|
||||
onLogout,
|
||||
isLoggingOut,
|
||||
demoMode = false,
|
||||
onResetDemo,
|
||||
isResettingDemo = false,
|
||||
}: {
|
||||
user: AuthUser | null;
|
||||
view: string;
|
||||
@@ -264,6 +268,9 @@ export const NotebookPane = ({
|
||||
authRequired: boolean;
|
||||
onLogout: () => void;
|
||||
isLoggingOut: boolean;
|
||||
demoMode?: boolean;
|
||||
onResetDemo?: () => void;
|
||||
isResettingDemo?: boolean;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { isInstallable, install } = usePwaInstall();
|
||||
@@ -481,6 +488,21 @@ export const NotebookPane = ({
|
||||
|
||||
<footer className="border-t border-slate-200 bg-white/80 px-3 pb-[max(0.5rem,env(safe-area-inset-bottom))] pt-2 backdrop-blur-sm">
|
||||
<div className="space-y-1">
|
||||
{demoMode && onResetDemo && (
|
||||
<button
|
||||
onClick={onResetDemo}
|
||||
disabled={isResettingDemo}
|
||||
className="flex h-8 w-full items-center gap-3 rounded-md px-3 text-left text-sm font-semibold leading-none text-amber-700 hover:bg-amber-50 transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-amber-400/70 disabled:opacity-50"
|
||||
type="button"
|
||||
title={t("demo.resetTooltip")}
|
||||
aria-label={t("demo.resetButton")}
|
||||
>
|
||||
<span className="flex h-4 w-4 shrink-0 items-center justify-center">
|
||||
<RotateCcw className={cn("h-4 w-4 text-amber-600", isResettingDemo && "animate-spin")} />
|
||||
</span>
|
||||
<span className="min-w-0 flex-1 truncate">{isResettingDemo ? t("demo.resetting") : t("demo.resetButton")}</span>
|
||||
</button>
|
||||
)}
|
||||
{isInstallable && (
|
||||
<button
|
||||
onClick={install}
|
||||
|
||||
@@ -662,6 +662,32 @@ export const WorkspaceApp = ({
|
||||
const [notebookNameDialog, setNotebookNameDialog] = useState<NotebookNameDialogState | null>(null);
|
||||
const [notebookDeleteConfirmation, setNotebookDeleteConfirmation] = useState<Notebook | null>(null);
|
||||
const [appNoticeDialog, setAppNoticeDialog] = useState<AppNoticeDialogState | null>(null);
|
||||
const [demoResetConfirmationOpen, setDemoResetConfirmationOpen] = useState(false);
|
||||
|
||||
const resetDemoMutation = useMutation({
|
||||
mutationFn: () => api.resetDemo(),
|
||||
onSuccess: () => {
|
||||
setDemoResetConfirmationOpen(false);
|
||||
void Promise.all([
|
||||
queryClient.invalidateQueries({ queryKey: ["memos"] }),
|
||||
queryClient.invalidateQueries({ queryKey: ["memo"] }),
|
||||
queryClient.invalidateQueries({ queryKey: ["notebooks"] }),
|
||||
queryClient.invalidateQueries({ queryKey: ["resources"] }),
|
||||
queryClient.invalidateQueries({ queryKey: ["tags"] }),
|
||||
]);
|
||||
setAppNoticeDialog({
|
||||
title: t("demo.resetSuccess"),
|
||||
description: t("demo.resetSuccess"),
|
||||
});
|
||||
},
|
||||
onError: () => {
|
||||
setDemoResetConfirmationOpen(false);
|
||||
setAppNoticeDialog({
|
||||
title: t("demo.resetFailed"),
|
||||
description: t("demo.resetFailed"),
|
||||
});
|
||||
},
|
||||
});
|
||||
const [multiSelectKeyDown, setMultiSelectKeyDown] = useState(false);
|
||||
const [imageCompressionEnabled, setImageCompressionEnabled] = useState(readImageCompressionPreference);
|
||||
const [desktopFocusMode, setDesktopFocusMode] = useState(readDesktopFocusModePreference);
|
||||
@@ -2424,6 +2450,9 @@ export const WorkspaceApp = ({
|
||||
setActivePane("memos");
|
||||
}}
|
||||
onEmptyTrash={handleEmptyTrash}
|
||||
demoMode={demoMode}
|
||||
onResetDemo={() => setDemoResetConfirmationOpen(true)}
|
||||
isResettingDemo={resetDemoMutation.isPending}
|
||||
/>
|
||||
</Suspense>
|
||||
)}
|
||||
@@ -2720,6 +2749,18 @@ export const WorkspaceApp = ({
|
||||
onConfirm={() => setAppNoticeDialog(null)}
|
||||
/>
|
||||
)}
|
||||
{demoResetConfirmationOpen && (
|
||||
<AppConfirmDialog
|
||||
title={t("demo.resetTitle")}
|
||||
description={t("demo.resetDescription")}
|
||||
confirmLabel={t("demo.resetConfirm")}
|
||||
cancelLabel={t("common.cancel")}
|
||||
isWorking={resetDemoMutation.isPending}
|
||||
tone="primary"
|
||||
onCancel={() => setDemoResetConfirmationOpen(false)}
|
||||
onConfirm={() => resetDemoMutation.mutate()}
|
||||
/>
|
||||
)}
|
||||
{visibleActivePane !== "editor" && !memoSelectionModeActive && (
|
||||
<MobileBottomNav
|
||||
activeItem={mobileBottomNavActive}
|
||||
|
||||
@@ -423,4 +423,9 @@ export const api = {
|
||||
method: "POST",
|
||||
body: JSON.stringify(payload),
|
||||
}),
|
||||
|
||||
resetDemo: () =>
|
||||
request<{ success: true }>("/api/v1/demo/reset", {
|
||||
method: "POST",
|
||||
}),
|
||||
};
|
||||
@@ -757,4 +757,14 @@ export const enUS = {
|
||||
dismiss: "Got it",
|
||||
},
|
||||
},
|
||||
demo: {
|
||||
resetTitle: "Restore Demo Memos",
|
||||
resetDescription: "Restores sample notes and notebooks in the demo environment to their initial state. Any edits or changes made will be reset.",
|
||||
resetConfirm: "Confirm Restore",
|
||||
resetButton: "Restore Demo Notes",
|
||||
resetTooltip: "Reset sample notes and notebooks in the demo environment",
|
||||
resetSuccess: "Sample notes successfully restored!",
|
||||
resetFailed: "Failed to restore sample notes. Please try again later.",
|
||||
resetting: "Restoring...",
|
||||
},
|
||||
} as const;
|
||||
@@ -757,4 +757,14 @@ export const zhCN = {
|
||||
dismiss: "我知道了",
|
||||
},
|
||||
},
|
||||
demo: {
|
||||
resetTitle: "还原示例笔记",
|
||||
resetDescription: "将 Demo 环境的示例笔记和笔记本恢复为初始状态。期间对示例笔记所做的任何编辑或修改都会被清空并还原。",
|
||||
resetConfirm: "确认还原",
|
||||
resetButton: "还原示例笔记",
|
||||
resetTooltip: "重置演示环境中的示例笔记与笔记本",
|
||||
resetSuccess: "示例笔记已成功还原!",
|
||||
resetFailed: "还原示例笔记失败,请稍后重试。",
|
||||
resetting: "正在还原...",
|
||||
},
|
||||
} as const;
|
||||
Reference in new issue
Block a user