feat(demo): allow mutations in demo mode via DEMO_READ_ONLY flag (#6224)
This commit is contained in:
1 parent
d067261171
commit
fc2c251557
5 files changed
+26
-10
No files matched your search
+8
-1
@@ -59,4 +59,11 @@ TURBO_TELEMETRY_DISABLED=1
|
|||||||
# ENABLE_KUBERNETES=true
|
# ENABLE_KUBERNETES=true
|
||||||
|
|
||||||
# Enable mock integration
|
# Enable mock integration
|
||||||
UNSAFE_ENABLE_MOCK_INTEGRATION=true
|
UNSAFE_ENABLE_MOCK_INTEGRATION=true
|
||||||
|
|
||||||
|
# Enable demo mode (seeds a demo user "demo"/"demo", a sample board and a mock integration)
|
||||||
|
# DEMO_MODE=true
|
||||||
|
|
||||||
|
# Whether demo mode is read-only. Defaults to true (the public demo blocks all mutations).
|
||||||
|
# Set to false for writable preview deployments so the demo user can modify the dashboard.
|
||||||
|
# DEMO_READ_ONLY=false
|
||||||
@@ -45,7 +45,9 @@ export const BoardContentHeaderActions = () => {
|
|||||||
const [isEditMode] = useEditMode();
|
const [isEditMode] = useEditMode();
|
||||||
const board = useRequiredBoard();
|
const board = useRequiredBoard();
|
||||||
const { hasChangeAccess } = useBoardPermissions(board);
|
const { hasChangeAccess } = useBoardPermissions(board);
|
||||||
const { data: demoMode, isLoading } = clientApi.info.isDemoMode.useQuery();
|
// Fall back to read-only if the query has no data (e.g. errored) so we never expose
|
||||||
|
// edit/save/settings UI that would then fail server-side.
|
||||||
|
const { data: demoReadOnly = true, isLoading } = clientApi.info.isDemoReadOnly.useQuery();
|
||||||
|
|
||||||
if (!hasChangeAccess || isLoading) {
|
if (!hasChangeAccess || isLoading) {
|
||||||
return <SelectBoardsMenu />;
|
return <SelectBoardsMenu />;
|
||||||
@@ -55,9 +57,9 @@ export const BoardContentHeaderActions = () => {
|
|||||||
<>
|
<>
|
||||||
{isEditMode && <AddMenu />}
|
{isEditMode && <AddMenu />}
|
||||||
|
|
||||||
<EditModeMenu demoMode={demoMode ?? false} />
|
<EditModeMenu demoReadOnly={demoReadOnly} />
|
||||||
|
|
||||||
{!demoMode && (
|
{!demoReadOnly && (
|
||||||
<OnboardingTour.Target id="board-settings">
|
<OnboardingTour.Target id="board-settings">
|
||||||
<HeaderButton href={`/boards/${board.name}/settings`}>
|
<HeaderButton href={`/boards/${board.name}/settings`}>
|
||||||
<IconSettings stroke={1.5} />
|
<IconSettings stroke={1.5} />
|
||||||
@@ -158,7 +160,7 @@ const AddMenu = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const EditModeMenu = ({ demoMode }: { demoMode: boolean }) => {
|
const EditModeMenu = ({ demoReadOnly }: { demoReadOnly: boolean }) => {
|
||||||
const [isEditMode, { open, close }] = useEditMode();
|
const [isEditMode, { open, close }] = useEditMode();
|
||||||
const board = useRequiredBoard();
|
const board = useRequiredBoard();
|
||||||
const utils = clientApi.useUtils();
|
const utils = clientApi.useUtils();
|
||||||
@@ -188,11 +190,11 @@ const EditModeMenu = ({ demoMode }: { demoMode: boolean }) => {
|
|||||||
|
|
||||||
const toggle = useCallback(() => {
|
const toggle = useCallback(() => {
|
||||||
if (isEditMode) {
|
if (isEditMode) {
|
||||||
if (demoMode) return discardDemoChanges();
|
if (demoReadOnly) return discardDemoChanges();
|
||||||
return saveBoard(board);
|
return saveBoard(board);
|
||||||
}
|
}
|
||||||
open();
|
open();
|
||||||
}, [board, isEditMode, demoMode, saveBoard, open, discardDemoChanges]);
|
}, [board, isEditMode, demoReadOnly, saveBoard, open, discardDemoChanges]);
|
||||||
|
|
||||||
useHotkeys([[hotkeys.toggleBoardEdit, toggle]]);
|
useHotkeys([[hotkeys.toggleBoardEdit, toggle]]);
|
||||||
usePreventLeaveWithDirty(isEditMode);
|
usePreventLeaveWithDirty(isEditMode);
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ export const env = createEnv({
|
|||||||
server: {
|
server: {
|
||||||
KUBERNETES_SERVICE_ACCOUNT_NAME: z.string().optional(),
|
KUBERNETES_SERVICE_ACCOUNT_NAME: z.string().optional(),
|
||||||
DEMO_MODE: createBooleanSchema(false),
|
DEMO_MODE: createBooleanSchema(false),
|
||||||
|
DEMO_READ_ONLY: createBooleanSchema(true),
|
||||||
},
|
},
|
||||||
runtimeEnv: {
|
runtimeEnv: {
|
||||||
KUBERNETES_SERVICE_ACCOUNT_NAME: process.env.KUBERNETES_SERVICE_ACCOUNT_NAME,
|
KUBERNETES_SERVICE_ACCOUNT_NAME: process.env.KUBERNETES_SERVICE_ACCOUNT_NAME,
|
||||||
DEMO_MODE: process.env.DEMO_MODE,
|
DEMO_MODE: process.env.DEMO_MODE,
|
||||||
|
DEMO_READ_ONLY: process.env.DEMO_READ_ONLY,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import z from "zod/v4";
|
import z from "zod/v4";
|
||||||
|
|
||||||
import packageJson from "../../../../package.json";
|
import packageJson from "../../../../package.json";
|
||||||
import { createTRPCRouter, isDemoMode, publicProcedure, protectedProcedure } from "../trpc";
|
import { createTRPCRouter, isDemoMode, isDemoReadOnly, publicProcedure, protectedProcedure } from "../trpc";
|
||||||
|
|
||||||
export const infoRouter = createTRPCRouter({
|
export const infoRouter = createTRPCRouter({
|
||||||
getInfo: protectedProcedure
|
getInfo: protectedProcedure
|
||||||
@@ -20,4 +20,5 @@ export const infoRouter = createTRPCRouter({
|
|||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
isDemoMode: publicProcedure.query(() => isDemoMode),
|
isDemoMode: publicProcedure.query(() => isDemoMode),
|
||||||
|
isDemoReadOnly: publicProcedure.query(() => isDemoReadOnly),
|
||||||
});
|
});
|
||||||
@@ -95,8 +95,12 @@ export const createTRPCRouter = t.router;
|
|||||||
|
|
||||||
export const isDemoMode = env.DEMO_MODE;
|
export const isDemoMode = env.DEMO_MODE;
|
||||||
|
|
||||||
|
// The public "real demo" (demo + mock integrations) stays read-only, while preview
|
||||||
|
// deployments run with DEMO_MODE + DEMO_READ_ONLY=false to allow mutations.
|
||||||
|
export const isDemoReadOnly = env.DEMO_MODE && env.DEMO_READ_ONLY;
|
||||||
|
|
||||||
const enforceDemoModeReadOnly = t.middleware(({ ctx, next, type }) => {
|
const enforceDemoModeReadOnly = t.middleware(({ ctx, next, type }) => {
|
||||||
if (env.DEMO_MODE && type === "mutation") {
|
if (isDemoReadOnly && type === "mutation") {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "FORBIDDEN",
|
code: "FORBIDDEN",
|
||||||
message: "Mutations are disabled in demo mode",
|
message: "Mutations are disabled in demo mode",
|
||||||
@@ -105,7 +109,7 @@ const enforceDemoModeReadOnly = t.middleware(({ ctx, next, type }) => {
|
|||||||
return next({ ctx });
|
return next({ ctx });
|
||||||
});
|
});
|
||||||
|
|
||||||
const baseProcedure = env.DEMO_MODE ? t.procedure.use(enforceDemoModeReadOnly) : t.procedure;
|
const baseProcedure = isDemoReadOnly ? t.procedure.use(enforceDemoModeReadOnly) : t.procedure;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Public (unauthed) procedure
|
* Public (unauthed) procedure
|
||||||
|
|||||||
Reference in new issue
Block a user