Skip to content

Commit 0268140

Browse files
Enhance email template handling and improve UI components
- Added dynamic string handling for email template configuration overrides. - Updated line-chart component to use urlString for routing. - Refactored data vault store configuration to directly set displayName. - Improved error handling in email template deletion process. - Enhanced launch checklist with new asynchronous alert functionality. - Updated Vercel page to ensure proper key assignment with error handling. - Modified dropdown menu state initialization for better default handling. - Improved data table component to prevent multiple calls on table readiness.
1 parent a309168 commit 0268140

8 files changed

Lines changed: 23 additions & 17 deletions

File tree

apps/backend/src/app/api/latest/internal/email-templates/[templateId]/route.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export const PATCH = createSmartRouteHandler({
6464
throw new KnownErrors.EmailRenderingError("NotificationCategory is required, import it from @stackframe/emails");
6565
}
6666

67+
// Path-notation keys are dynamic strings and cannot be represented precisely by the override type.
6768
const configOverride: Record<string, any> = {
6869
[`emails.templates.${templateId}.tsxSource`]: body.tsx_source,
6970
};
@@ -120,6 +121,7 @@ export const DELETE = createSmartRouteHandler({
120121
projectId: tenancy.project.id,
121122
branchId: tenancy.branchId,
122123
environmentConfigOverrideOverride: {
124+
// null means delete this key, but the override map type does not model null-valued deletes.
123125
[`emails.templates.${templateId}`]: null as any,
124126
},
125127
});

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from "
77
import { DesignCardTint, DesignCategoryTabs, DesignPillToggle } from "@/components/design-components";
88
import { UserAvatar } from '@stackframe/stack';
99
import { fromNow, isWeekend } from '@stackframe/stack-shared/dist/utils/dates';
10+
import { urlString } from "@stackframe/stack-shared/dist/utils/urls";
1011
import { useState } from "react";
1112
import { Bar, BarChart, CartesianGrid, Cell, Pie, PieChart, TooltipProps, XAxis, YAxis } from "recharts";
1213

@@ -363,7 +364,7 @@ export function TabbedMetricsCard({
363364
{listData.map((user) => (
364365
<button
365366
key={user.id}
366-
onClick={() => router.push(`/projects/${projectId}/users/${user.id}`)}
367+
onClick={() => router.push(urlString`/projects/${projectId}/users/${user.id}`)}
367368
className={cn(
368369
"w-full flex items-center gap-3 p-2.5 rounded-xl transition-all duration-150 hover:transition-none text-left group",
369370
hoverAccentClass

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/data-vault/stores/[storeId]/page-client.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,7 @@ export default function PageClient({ storeId }: PageClientProps) {
6262
await updateConfig({
6363
adminApp: stackAdminApp,
6464
configUpdate: {
65-
[`dataVault.stores.${storeId}`]: {
66-
...store,
67-
displayName: localDisplayName.trim() || store.displayName,
68-
},
65+
[`dataVault.stores.${storeId}.displayName`]: localDisplayName.trim() || store.displayName,
6966
},
7067
pushable: true,
7168
});

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-templates/page-client.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,12 @@ export default function PageClient() {
2828
await stackAdminApp.deleteEmailTemplate(templateId);
2929
toast({ title: "Template deleted successfully", variant: "success" });
3030
setDeleteDialogOpen(null);
31+
return;
3132
} catch (error) {
3233
console.error("Failed to delete email template:", error);
3334
const errorMessage = error instanceof Error ? error.message : "An unexpected error occurred while deleting the template";
3435
setDeleteError(errorMessage);
36+
return "prevent-close" as const;
3537
}
3638
};
3739

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/launch-checklist/page-client.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
cn
1717
} from "@/components/ui";
1818
import { CaretDownIcon, CaretUpIcon, CheckCircleIcon, CircleIcon, ClockIcon } from "@phosphor-icons/react";
19-
import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises";
19+
import { runAsynchronously, runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
2020
import * as confetti from "canvas-confetti";
2121
import { useEffect, useRef, useState } from "react";
2222
import { AppEnabledGuard } from "../app-enabled-guard";
@@ -690,7 +690,7 @@ export default function PageClient() {
690690
checked={project.isProductionMode}
691691
disabled={!project.isProductionMode && productionModeErrors.length > 0}
692692
onCheckedChange={(checked) => {
693-
runAsynchronously(project.update({ isProductionMode: checked }));
693+
runAsynchronouslyWithAlert(project.update({ isProductionMode: checked }));
694694
}}
695695
/>
696696
),

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/vercel/page-client.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { InlineCode } from "@/components/inline-code";
55
import { StyledLink } from "@/components/link";
66
import { CaretDownIcon, CaretUpIcon, CheckCircleIcon, CircleIcon, ClockIcon } from "@phosphor-icons/react";
77
import { runAsynchronously, runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
8+
import { throwErr } from "@stackframe/stack-shared/dist/utils/errors";
89
import {
910
DesignAlert,
1011
DesignBadge,
@@ -83,8 +84,8 @@ export default function PageClient() {
8384

8485
setKeys({
8586
projectId: adminApp.projectId,
86-
publishableClientKey: newKey.publishableClientKey!,
87-
secretServerKey: newKey.secretServerKey!,
87+
publishableClientKey: newKey.publishableClientKey ?? throwErr("Expected publishableClientKey after creating an API key with hasPublishableClientKey: true"),
88+
secretServerKey: newKey.secretServerKey ?? throwErr("Expected secretServerKey after creating an API key with hasSecretServerKey: true"),
8889
});
8990
} finally {
9091
setIsGenerating(false);
@@ -265,15 +266,15 @@ export default function PageClient() {
265266
if (prevAllCompleted !== undefined && !prevAllCompleted && allCompleted) {
266267
// Create a confetti effect dropping from the top
267268
const duration = 3000;
268-
const animationEnd = Date.now() + duration;
269+
const animationEnd = performance.now() + duration;
269270
const defaults = { startVelocity: 30, spread: 360, ticks: 60, zIndex: 9999 };
270271

271272
function randomInRange(min: number, max: number) {
272273
return Math.random() * (max - min) + min;
273274
}
274275

275276
const interval = setInterval(() => {
276-
const timeLeft = animationEnd - Date.now();
277+
const timeLeft = animationEnd - performance.now();
277278

278279
if (timeLeft <= 0) {
279280
clearInterval(interval);

apps/dashboard/src/components/ui/data-table/data-table.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ function DataTableBase<TData, TValue>({
292292
}: DataTableBaseProps<TData, TValue>) {
293293
const [rowSelection, setRowSelection] = React.useState({});
294294
const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>(defaultVisibility || {});
295+
const calledOnTableReadyRef = React.useRef(false);
295296

296297
const table: TableType<TData> = useReactTable({
297298
data,
@@ -325,7 +326,10 @@ function DataTableBase<TData, TValue>({
325326
});
326327

327328
React.useEffect(() => {
328-
onTableReady?.(table);
329+
if (!calledOnTableReadyRef.current && onTableReady) {
330+
onTableReady(table);
331+
calledOnTableReadyRef.current = true;
332+
}
329333
}, [table, onTableReady]);
330334

331335
return <TableView

apps/dashboard/src/components/ui/dropdown-menu.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const DropdownMenu = forwardRefIfNeeded<
1919
React.ElementRef<typeof DropdownMenuPrimitive.Root>,
2020
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Root>
2121
>(({ ...props }, ref) => {
22-
const [open, setOpen] = React.useState(!!props.open);
22+
const [open, setOpen] = React.useState(() => props.open ?? props.defaultOpen ?? false);
2323

2424
return (
2525
<DropdownMenuContext.Provider value={{
@@ -124,11 +124,11 @@ const DropdownMenuItem = forwardRefIfNeeded<
124124
const handleItemAction = (event: { preventDefault: () => void, stopPropagation: () => void }) => {
125125
event.preventDefault();
126126
event.stopPropagation();
127-
const result = props.onClick?.(event as React.MouseEvent<HTMLDivElement, MouseEvent>);
128-
if (result && typeof (result as Promise<void>).then === "function") {
127+
const result = props.onClick?.(event as unknown as React.MouseEvent<HTMLDivElement, MouseEvent>);
128+
if (result instanceof Promise) {
129129
setIsLoading(true);
130130
runAsynchronouslyWithAlert(
131-
Promise.resolve(result).finally(() => {
131+
result.finally(() => {
132132
setIsLoading(false);
133133
setOpen(false);
134134
})
@@ -150,7 +150,6 @@ const DropdownMenuItem = forwardRefIfNeeded<
150150
{...props}
151151
disabled={isLoading || props.disabled}
152152
onSelect={props.onClick ? handleItemAction : undefined}
153-
onClick={props.onClick ? handleItemAction : undefined}
154153
>
155154
<div style={{ visibility: isLoading ? "visible" : "hidden", position: "absolute", inset: 0, display: "flex", justifyContent: "center", alignItems: "center" }}>
156155
<Spinner />

0 commit comments

Comments
 (0)