Skip to content

Commit 5f7d7f4

Browse files
authored
Merge branch 'dev' into fraud-protection
2 parents bbb7983 + 52fc3b1 commit 5f7d7f4

32 files changed

Lines changed: 1573 additions & 1158 deletions

File tree

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ export const PATCH = createSmartRouteHandler({
6868
[`emails.templates.${templateId}.tsxSource`]: body.tsx_source,
6969
};
7070

71-
// Only add themeId if it's explicitly provided
7271
if (body.theme_id !== undefined) {
7372
configOverride[`emails.templates.${templateId}.themeId`] = body.theme_id;
7473
}
@@ -120,6 +119,7 @@ export const DELETE = createSmartRouteHandler({
120119
projectId: tenancy.project.id,
121120
branchId: tenancy.branchId,
122121
environmentConfigOverrideOverride: {
122+
// null means delete this key, but the override map type does not model null-valued deletes.
123123
[`emails.templates.${templateId}`]: null as any,
124124
},
125125
});

apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/playground/page-client.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
DesignSelectorDropdown,
2222
DesignUserList,
2323
} from "@/components/design-components";
24-
import { DataTableColumnHeader, Typography } from "@/components/ui";
24+
import { DataTableColumnHeader, SearchToolbarItem, Typography } from "@/components/ui";
2525
import {
2626
CheckCircle,
2727
Cube,
@@ -246,6 +246,8 @@ export default function PageClient() {
246246
// Data Table
247247
const [tableClickableRows, setTableClickableRows] = useState(false);
248248
const [tableLastRowClick, setTableLastRowClick] = useState("");
249+
const [tableGlass, setTableGlass] = useState<boolean | undefined>(undefined);
250+
const [tableShowToolbar, setTableShowToolbar] = useState(false);
249251

250252
// Editable Grid
251253
const [gridCols, setGridCols] = useState<1 | 2>(2);
@@ -546,6 +548,8 @@ export default function PageClient() {
546548
data={DEMO_PRODUCTS}
547549
columns={tableColumns}
548550
defaultSorting={[{ id: "name", desc: false }]}
551+
glassmorphic={tableGlass}
552+
toolbarRender={tableShowToolbar ? (table) => <SearchToolbarItem table={table} keyName="name" placeholder="Filter by name" /> : undefined}
549553
onRowClick={tableClickableRows ? (row) => setTableLastRowClick(row.name) : undefined}
550554
/>
551555
{tableLastRowClick && (
@@ -1049,6 +1053,12 @@ export default function PageClient() {
10491053
if (selected === "data-table") {
10501054
return (
10511055
<div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-3 sm:gap-4 items-end">
1056+
<PropField label="Glassmorphic">
1057+
<GlassmorphicToggle value={tableGlass} onChange={setTableGlass} />
1058+
</PropField>
1059+
<PropField label="Toolbar">
1060+
<BoolToggle value={tableShowToolbar} onChange={setTableShowToolbar} on="Shown" off="Hidden" />
1061+
</PropField>
10521062
<PropField label="Row Click">
10531063
<BoolToggle value={tableClickableRows} onChange={setTableClickableRows} on="Enabled" off="Disabled" />
10541064
</PropField>
@@ -1475,10 +1485,12 @@ export default function PageClient() {
14751485
/>`;
14761486
}
14771487
if (selected === "data-table") {
1488+
const glassProp = tableGlass === undefined ? "" : `\n glassmorphic={${tableGlass}}`;
1489+
const toolbarProp = tableShowToolbar ? `\n toolbarRender={(table) => <SearchToolbarItem table={table} keyName="name" placeholder="Filter by name" />}` : "";
14781490
return `<DesignDataTable
14791491
data={products}
14801492
columns={columns}
1481-
defaultSorting={[{ id: "name", desc: false }]}
1493+
defaultSorting={[{ id: "name", desc: false }]}${glassProp}${toolbarProp}
14821494
onRowClick={${tableClickableRows ? "(row) => setLastClickedRow(row.name)" : "undefined"}}
14831495
/>`;
14841496
}

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

Lines changed: 56 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import {
44
Typography
55
} from "@/components/ui";
66
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart";
7+
import { DesignCardTint, DesignCategoryTabs, DesignPillToggle } from "@/components/design-components";
78
import { UserAvatar } from '@stackframe/stack';
89
import { fromNow, isWeekend } from '@stackframe/stack-shared/dist/utils/dates';
10+
import { urlString } from "@stackframe/stack-shared/dist/utils/urls";
911
import { useState } from "react";
1012
import { Bar, BarChart, CartesianGrid, Cell, Pie, PieChart, TooltipProps, XAxis, YAxis } from "recharts";
1113

@@ -181,13 +183,13 @@ export function ChartCard({
181183
className?: string,
182184
gradientColor?: GradientColor,
183185
}) {
184-
const hoverTints: Record<GradientColor, string> = {
185-
blue: "group-hover:bg-slate-500/[0.02]",
186-
purple: "group-hover:bg-slate-500/[0.02]",
187-
green: "group-hover:bg-slate-500/[0.02]",
188-
orange: "group-hover:bg-slate-500/[0.02]",
189-
slate: "group-hover:bg-slate-500/[0.02]",
190-
cyan: "group-hover:bg-slate-500/[0.02]",
186+
const designCardGradients: Record<GradientColor, "blue" | "cyan" | "purple" | "green" | "orange" | "default"> = {
187+
blue: "blue",
188+
purple: "purple",
189+
green: "green",
190+
orange: "orange",
191+
slate: "default",
192+
cyan: "cyan",
191193
};
192194

193195
return (
@@ -203,23 +205,17 @@ export function ChartCard({
203205
}
204206
`}
205207
</style>
206-
<div className={cn(
207-
"group relative rounded-2xl bg-white/90 dark:bg-background/60 backdrop-blur-xl transition-all duration-150 hover:transition-none chart-card-tooltip-escape",
208-
"ring-1 ring-black/[0.06] hover:ring-black/[0.1] dark:ring-white/[0.06] dark:hover:ring-white/[0.1]",
209-
"shadow-sm hover:shadow-md hover:z-10",
210-
className
211-
)}>
212-
{/* Subtle glassmorphic background */}
213-
<div className="absolute inset-0 bg-gradient-to-br from-foreground/[0.06] via-foreground/[0.02] dark:from-foreground/[0.03] dark:via-foreground/[0.01] to-transparent pointer-events-none rounded-2xl overflow-hidden" />
214-
{/* Accent hover tint */}
215-
<div className={cn(
216-
"absolute inset-0 transition-colors duration-150 group-hover:transition-none pointer-events-none rounded-2xl overflow-hidden",
217-
hoverTints[gradientColor]
218-
)} />
219-
<div className="relative h-full flex flex-col">
208+
<DesignCardTint
209+
gradient={designCardGradients[gradientColor]}
210+
className={cn(
211+
"chart-card-tooltip-escape h-full overflow-visible hover:z-10 [&>div]:h-full [&>div]:min-h-0 [&>div]:flex [&>div]:flex-col [&>div]:overflow-visible",
212+
className
213+
)}
214+
>
215+
<div className="h-full flex flex-col">
220216
{children}
221217
</div>
222-
</div>
218+
</DesignCardTint>
223219
</>
224220
);
225221
}
@@ -231,30 +227,26 @@ export function TimeRangeToggle({
231227
timeRange: TimeRange,
232228
onTimeRangeChange: (range: TimeRange) => void,
233229
}) {
234-
const options: { value: TimeRange, label: string }[] = [
235-
{ value: '7d', label: '7d' },
236-
{ value: '30d', label: '30d' },
237-
{ value: 'all', label: 'All' },
230+
const options: { id: TimeRange, label: string }[] = [
231+
{ id: '7d', label: '7d' },
232+
{ id: '30d', label: '30d' },
233+
{ id: 'all', label: 'All' },
238234
];
239235

240236
return (
241-
<div className="inline-flex items-center gap-1 rounded-xl bg-foreground/[0.04] p-1 backdrop-blur-sm">
242-
{options.map((option) => (
243-
<button
244-
key={option.value}
245-
type="button"
246-
onClick={() => onTimeRangeChange(option.value)}
247-
className={cn(
248-
"px-3 py-1.5 text-xs font-medium rounded-lg transition-all duration-150 hover:transition-none",
249-
timeRange === option.value
250-
? "bg-background text-foreground shadow-sm ring-1 ring-foreground/[0.06] dark:bg-[hsl(240,71%,70%)]/10 dark:text-[hsl(240,71%,90%)] dark:ring-[hsl(240,71%,70%)]/20"
251-
: "text-muted-foreground hover:text-foreground hover:bg-background/50"
252-
)}
253-
>
254-
{option.label}
255-
</button>
256-
))}
257-
</div>
237+
<DesignPillToggle
238+
options={options}
239+
selected={timeRange}
240+
size="sm"
241+
glassmorphic
242+
onSelect={(selectedId) => {
243+
if (selectedId === '7d' || selectedId === '30d' || selectedId === 'all') {
244+
onTimeRangeChange(selectedId);
245+
return;
246+
}
247+
throw new Error(`Unsupported time range selected: ${selectedId}`);
248+
}}
249+
/>
258250
);
259251
}
260252

@@ -295,15 +287,6 @@ export function TabbedMetricsCard({
295287
// For "all" time range, use totalAllTime if provided (which includes data beyond 30 days)
296288
const displayTotal = timeRange === 'all' && totalAllTime !== undefined ? totalAllTime : total;
297289

298-
const activeTabColors: Record<GradientColor, string> = {
299-
blue: "bg-blue-500 dark:bg-[hsl(240,71%,70%)]",
300-
purple: "bg-purple-500 dark:bg-[hsl(200,91%,70%)]",
301-
green: "bg-emerald-500 dark:bg-[hsl(200,91%,70%)]",
302-
orange: "bg-orange-500 dark:bg-[hsl(240,71%,70%)]",
303-
slate: "bg-slate-500 dark:bg-[hsl(240,71%,70%)]",
304-
cyan: "bg-cyan-500 dark:bg-[hsl(200,91%,70%)]",
305-
};
306-
307290
const hoverAccentColors: Record<GradientColor, string> = {
308291
blue: "hover:bg-blue-500/[0.06]",
309292
purple: "hover:bg-purple-500/[0.06]",
@@ -313,40 +296,31 @@ export function TabbedMetricsCard({
313296
cyan: "hover:bg-cyan-500/[0.06]",
314297
};
315298

316-
const activeColorClass = activeTabColors[gradientColor];
317299
const hoverAccentClass = hoverAccentColors[gradientColor];
300+
const tabsGradient: "blue" | "cyan" | "purple" | "green" | "orange" | "default" = gradientColor === "slate" ? "default" : gradientColor;
318301

319302
return (
320303
<ChartCard className="h-full flex flex-col" gradientColor={gradientColor}>
321304
<div className={cn("flex items-center justify-between border-b border-foreground/[0.05]", compact ? "px-4" : "px-5")}>
322-
<div className="flex items-center gap-1">
323-
<button
324-
type="button"
325-
onClick={() => setView('chart')}
326-
className={cn(
327-
"relative px-3 py-3.5 text-xs font-medium transition-all duration-150 hover:transition-none rounded-t-lg",
328-
view === 'chart' ? "text-foreground" : "text-muted-foreground hover:text-foreground"
329-
)}
330-
>
331-
{config.name}
332-
{view === 'chart' && (
333-
<div className={cn("absolute bottom-0 left-3 right-3 h-0.5 rounded-full", activeColorClass)} />
334-
)}
335-
</button>
336-
<button
337-
type="button"
338-
onClick={() => setView('list')}
339-
className={cn(
340-
"relative px-3 py-3.5 text-xs font-medium transition-all duration-150 hover:transition-none rounded-t-lg",
341-
view === 'list' ? "text-foreground" : "text-muted-foreground hover:text-foreground"
342-
)}
343-
>
344-
{listTitle}
345-
{view === 'list' && (
346-
<div className={cn("absolute bottom-0 left-3 right-3 h-0.5 rounded-full", activeColorClass)} />
347-
)}
348-
</button>
349-
</div>
305+
<DesignCategoryTabs
306+
categories={[
307+
{ id: "chart", label: config.name },
308+
{ id: "list", label: listTitle },
309+
]}
310+
selectedCategory={view}
311+
onSelect={(selectedId) => {
312+
if (selectedId === "chart" || selectedId === "list") {
313+
setView(selectedId);
314+
return;
315+
}
316+
throw new Error(`Unsupported metrics tab selected: ${selectedId}`);
317+
}}
318+
showBadge={false}
319+
size="sm"
320+
glassmorphic={false}
321+
gradient={tabsGradient}
322+
className="flex-1 min-w-0 border-0 [&>button]:rounded-none [&>button]:px-3 [&>button]:py-3.5 [&>button]:text-xs"
323+
/>
350324

351325
{view === 'chart' && showTotal && (
352326
<span className="text-lg font-semibold text-foreground tabular-nums">
@@ -390,7 +364,7 @@ export function TabbedMetricsCard({
390364
{listData.map((user) => (
391365
<button
392366
key={user.id}
393-
onClick={() => router.push(`/projects/${projectId}/users/${user.id}`)}
367+
onClick={() => router.push(urlString`/projects/${projectId}/users/${user.id}`)}
394368
className={cn(
395369
"w-full flex items-center gap-3 p-2.5 rounded-xl transition-all duration-150 hover:transition-none text-left group",
396370
hoverAccentClass

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,7 @@ function MetricsContent({
493493
ref={gridContainerRef}
494494
className={cn(
495495
"grid gap-4 sm:gap-5 min-h-[400px]",
496-
gridHeightFromGlobe ? "" : "h-[calc(100vh-180px)]",
496+
gridHeightFromGlobe ? "" : "min-h-[calc(100vh-180px)]",
497497
showGlobe ? "grid-cols-1 lg:grid-cols-12" : "grid-cols-1"
498498
)}
499499
style={gridHeightFromGlobe ? { height: gridHeightFromGlobe } : undefined}
@@ -527,7 +527,7 @@ function MetricsContent({
527527
{/* Right Column: Stats Grid */}
528528
<div
529529
className={cn(
530-
"flex flex-col gap-12 h-full min-h-0",
530+
"flex flex-col gap-12 h-full",
531531
showGlobe && shouldShowGlobeSection ? "lg:col-span-7" : showGlobe ? "lg:col-span-12" : ""
532532
)}
533533
>
@@ -550,14 +550,14 @@ function MetricsContent({
550550
<div
551551
ref={chartsGridRef}
552552
className={cn(
553-
"flex-1 min-h-0 grid gap-4",
553+
"flex-1 grid gap-4",
554554
chartWidgets.length === 1
555555
? "grid-cols-1"
556556
: shouldUseTwoColumns ? "grid-cols-2" : "grid-cols-1"
557557
)}
558558
>
559559
{chartWidgets.map(widgetId => (
560-
<div key={widgetId} className="min-h-0">
560+
<div key={widgetId} className="min-h-[200px]">
561561
{renderWidget(widgetId)}
562562
</div>
563563
))}

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

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import { CodeBlock } from '@/components/code-block';
44
import { APIEnvKeys, NextJsEnvKeys } from '@/components/env-keys';
55
import { InlineCode } from '@/components/inline-code';
66
import { StyledLink } from '@/components/link';
7-
import { Button, Tabs, TabsContent, TabsList, TabsTrigger, Typography, cn } from "@/components/ui";
7+
import { Tabs, TabsContent, TabsList, TabsTrigger, Typography, cn } from "@/components/ui";
8+
import { DesignButton } from "@/components/design-components";
89
import { useThemeWatcher } from '@/lib/theme';
910
import { BookIcon, XIcon } from "@phosphor-icons/react";
1011
import { use } from "@stackframe/stack-shared/dist/utils/react";
@@ -426,10 +427,10 @@ export default function SetupPage(props: { toMetrics: () => void }) {
426427
return (
427428
<PageLayout width={1000}>
428429
<div className="flex justify-end">
429-
<Button variant='plain' onClick={props.toMetrics}>
430+
<DesignButton variant='plain' onClick={props.toMetrics}>
430431
Close Setup
431432
<XIcon className="w-4 h-4 ml-1 mt-0.5" />
432-
</Button>
433+
</DesignButton>
433434
</div>
434435
<div className="flex gap-4 justify-center items-center border rounded-2xl py-4 px-8 backdrop-blur-md bg-slate-200/20 dark:bg-black/20">
435436
<GlobeIllustration />
@@ -446,7 +447,7 @@ export default function SetupPage(props: { toMetrics: () => void }) {
446447
</div>
447448

448449
<Typography>
449-
<Button
450+
<DesignButton
450451
variant='outline'
451452
size='sm'
452453
onClick={() => {
@@ -455,7 +456,7 @@ export default function SetupPage(props: { toMetrics: () => void }) {
455456
>
456457
<BookIcon className="w-4 h-4 mr-2" />
457458
Full Documentation
458-
</Button>
459+
</DesignButton>
459460
</Typography>
460461
</div>
461462
</div>
@@ -489,7 +490,7 @@ export default function SetupPage(props: { toMetrics: () => void }) {
489490
reverseIfDark: false,
490491
imgSrc: '/python-logo.svg',
491492
}] as const).map(({ name, imgSrc: src, reverseIfDark, id }) => (
492-
<Button
493+
<DesignButton
493494
key={id}
494495
variant={id === selectedFramework ? 'secondary' : 'plain'} className='h-24 w-24 flex flex-col items-center justify-center gap-2 '
495496
onClick={() => setSelectedFramework(id)}
@@ -505,7 +506,7 @@ export default function SetupPage(props: { toMetrics: () => void }) {
505506
style={{ width: '30px', height: 'auto' }}
506507
/>
507508
<Typography type='label'>{name}</Typography>
508-
</Button>
509+
</DesignButton>
509510
))}
510511
</div>
511512
</div>,
@@ -637,9 +638,9 @@ function StackAuthKeys(props: {
637638
</>
638639
) : (
639640
<div className="flex items-center justify-center">
640-
<Button onClick={props.onGenerateKeys}>
641+
<DesignButton onClick={props.onGenerateKeys}>
641642
Generate Keys
642-
</Button>
643+
</DesignButton>
643644
</div>
644645
)}
645646
</div>

0 commit comments

Comments
 (0)