Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
d60b0b4
added roids skill
Developing-Gamer Apr 21, 2026
6d87ff6
auth methods page redesigned (1/2)
Developing-Gamer Apr 21, 2026
18f9d24
auth methods page redesigned (2/2)
Developing-Gamer Apr 21, 2026
6bfd71c
Sign-up rules page redesigned (1/2)
Developing-Gamer Apr 21, 2026
8c026a9
updated dialogs
Developing-Gamer Apr 22, 2026
ed24d9f
add reusable DesignDialog base component
Developing-Gamer Apr 22, 2026
cad76f8
export DesignDialog primitives from dashboard ui package
Developing-Gamer Apr 22, 2026
6251bd2
document DesignDialog as default dashboard modal
Developing-Gamer Apr 22, 2026
b8a164a
migrate trigger history modal to DesignDialog shell
Developing-Gamer Apr 22, 2026
8e9c228
add DesignDialog demos to design language catalog
Developing-Gamer Apr 22, 2026
d23fbbd
add interactive DesignDialog controls in playground
Developing-Gamer Apr 22, 2026
6d1dc2f
fix(ui): set pointer-events-auto on Popover content
Developing-Gamer Apr 23, 2026
1e96fb6
fix(ui): align dropdown and select item focus and highlight styles
Developing-Gamer Apr 23, 2026
19a7b73
refactor(dashboard): update RepeatingInput component
Developing-Gamer Apr 23, 2026
da1fc82
refactor(payments): update shared ItemDialog
Developing-Gamer Apr 23, 2026
d344c6e
refactor(payments): update create product line dialog
Developing-Gamer Apr 23, 2026
f61093c
refactor(payments): update included item dialog
Developing-Gamer Apr 23, 2026
5513a7b
refactor(payments): update price edit dialog
Developing-Gamer Apr 23, 2026
072a1c2
refactor(payments): update new product page and pricing section
Developing-Gamer Apr 23, 2026
c9979eb
Restyle payments Stripe connection status with DesignCard
Developing-Gamer Apr 23, 2026
e70a9fd
Restyle payments test mode card with DesignCard and badges
Developing-Gamer Apr 23, 2026
9dd6c99
Restyle payment methods settings with DesignCard and actions
Developing-Gamer Apr 23, 2026
dd7726e
Add checkout controls card for block-new-purchases on payments settings
Developing-Gamer Apr 23, 2026
26b52e8
Merge branch 'dev' into Payments-app-design-fixes
Developing-Gamer Apr 23, 2026
3164522
refactor/fix: remove default prod creation
nams1570 Apr 18, 2026
d3d0a0b
Implement migration to remove legacy `include-by-default` price senti…
mantrakp04 Apr 22, 2026
dca5096
Remove legacy invoice entries from dummy data seeding in `seed-dummy-…
mantrakp04 Apr 22, 2026
a7dab36
fix(payments): improve price validation and handling in product dialogs
mantrakp04 Apr 22, 2026
cb24516
fix(payments): drop dead include-by-default comparison in validate-code
mantrakp04 Apr 22, 2026
360c50e
fix(payments): enhance product price validation and subscription swit…
mantrakp04 Apr 22, 2026
65dd2d1
fix(payments): enhance subscription switching logic for free plans
mantrakp04 Apr 22, 2026
5279a49
chore(payments): drop dead include-by-default branches in dev-merged …
nams1570 May 5, 2026
6cbb3de
test(migrations): cover include-by-default snapshot rewrite
nams1570 May 5, 2026
c88bca3
refactor: error handling on poor product saves
nams1570 May 5, 2026
3d49b92
chore: clear up comments
nams1570 May 5, 2026
5286ce9
refactor: payments dashboard consistency changes
nams1570 May 5, 2026
07f38c0
fix: schema protections against non usd currencies
nams1570 May 5, 2026
51cbe5f
feat(schema-fuzzer): enhance price validation and add test for config…
mantrakp04 May 5, 2026
acdb502
feat(payments-demo): implement payments demo page and related API end…
mantrakp04 May 5, 2026
a256924
Merge branch 'dev' into remove-default-prod-support
mantrakp04 May 6, 2026
b798f43
Merge branch 'dev' into remove-default-prod-support
nams1570 May 12, 2026
ff32fc4
chore: switch schema tests away from default prrice
nams1570 May 12, 2026
9ee7f39
feat: add alert on products page for invalid products
nams1570 May 13, 2026
f4de807
Merge branch 'dev' into Payments-app-design-fixes
mantrakp04 May 13, 2026
7759dc3
Merge branch 'remove-default-prod-support' into Payments-app-design-f…
mantrakp04 May 13, 2026
cdc26f8
Enhance seed process and product switching logic
mantrakp04 May 13, 2026
3928ab3
Enhance PageClient with customizable dialog features
mantrakp04 May 14, 2026
c609dd2
Update app-card component to include PlayCircle icon
mantrakp04 May 14, 2026
0645bb4
Enhance UI components with layout and styling improvements
mantrakp04 May 14, 2026
cc8e319
Merge branch 'dev' into Payments-app-design-fixes
mantrakp04 May 19, 2026
b45aec0
Merge branch 'dev' into Payments-app-design-fixes
mantrakp04 May 21, 2026
b8448f9
Refactor payment switch logic and enhance error handling
mantrakp04 May 21, 2026
43df42a
Refactor payment dialogs to use centralized design tokens
mantrakp04 May 21, 2026
18bdf46
Enhance product line creation dialog with async validation
mantrakp04 May 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion apps/backend/prisma/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,10 @@ export async function seed() {
},
});
if (!existingGrowthSub) {
const firstPriceId = Object.keys(growthProduct.prices)[0] ?? null;
const firstPriceId = Object.keys(growthProduct.prices)[0];
if (!firstPriceId) {
throw new Error("Internal seed invariant violated: the Growth product must have at least one price configured before seeding the internal team subscription.");
}
const now = new Date();
// Clone to ensure the stored JSON snapshot is independent of the config object
// (mirrors the pattern used in seed-dummy-data.ts).
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { SubscriptionStatus } from "@/generated/prisma/client";
import { ensureClientCanAccessCustomer, ensureCustomerExists, getDefaultCardPaymentMethodSummary, getStripeCustomerForCustomerOrNull, isActiveSubscription, isAddOnProduct } from "@/lib/payments";
import { bulldozerWriteSubscription } from "@/lib/payments/bulldozer-dual-write";
import { getOwnedProductsForCustomer, getSubscriptionMapForCustomer } from "@/lib/payments/customer-data";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ import {
type DesignDialogVariant,
DesignInput,
DesignPillToggle,
} from "@stackframe/dashboard-ui-components";
} from "@/components/design-components";
import { useMemo, useRef, useState } from "react";

// ─── Types ───────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -463,6 +463,9 @@ export default function PageClient() {
const [dialogShowFooter, setDialogShowFooter] = useState(true);
const [dialogShowIcon, setDialogShowIcon] = useState(true);
const [dialogHideTopClose, setDialogHideTopClose] = useState(false);
const [dialogUseCustomHeader, setDialogUseCustomHeader] = useState(false);
const [dialogNoBodyPadding, setDialogNoBodyPadding] = useState(false);
const [dialogAccentClassNames, setDialogAccentClassNames] = useState(false);

// Editable Grid
const [gridCols, setGridCols] = useState<1 | 2>(2);
Expand Down Expand Up @@ -1016,6 +1019,15 @@ export default function PageClient() {
)
) : undefined;

const customHeader = dialogUseCustomHeader ? (
<div className="flex items-center gap-3 px-6 py-4 bg-gradient-to-r from-indigo-500/15 via-fuchsia-500/10 to-transparent border-b border-foreground/[0.06]">
<div className="h-8 w-8 rounded-full bg-foreground/10 grid place-items-center text-xs font-semibold">JD</div>
<div className="min-w-0">
<div className="text-sm font-semibold truncate">Custom header block</div>
<div className="text-xs text-muted-foreground truncate">Renders instead of the default title/description region.</div>
</div>
</div>
) : undefined;
return (
<DesignDialog
size={dialogSize}
Expand All @@ -1024,8 +1036,15 @@ export default function PageClient() {
title={dialogTitle || "Dialog"}
description={dialogDescription || undefined}
headerContent={headerContent}
customHeader={customHeader}
footer={footer}
hideTopCloseButton={dialogHideTopClose}
noBodyPadding={dialogNoBodyPadding}
className={dialogAccentClassNames ? "ring-2 ring-indigo-500/40" : undefined}
overlayClassName={dialogAccentClassNames ? "bg-indigo-950/40" : undefined}
headerClassName={dialogAccentClassNames ? "bg-indigo-500/5" : undefined}
bodyClassName={dialogAccentClassNames ? "bg-foreground/[0.02]" : undefined}
footerClassName={dialogAccentClassNames ? "bg-foreground/[0.02]" : undefined}
trigger={<DesignButton size="sm">{dialogTriggerLabel}</DesignButton>}
>
{body}
Expand Down Expand Up @@ -1822,6 +1841,15 @@ export default function PageClient() {
<PropField label="Top-right Close">
<BoolToggle value={!dialogHideTopClose} onChange={(v) => setDialogHideTopClose(!v)} on="Show" off="Hide" />
</PropField>
<PropField label="Custom Header">
<BoolToggle value={dialogUseCustomHeader} onChange={setDialogUseCustomHeader} on="On" off="Off" />
</PropField>
<PropField label="No Body Padding">
<BoolToggle value={dialogNoBodyPadding} onChange={setDialogNoBodyPadding} on="On" off="Off" />
</PropField>
<PropField label="Accent classNames">
<BoolToggle value={dialogAccentClassNames} onChange={setDialogAccentClassNames} on="On" off="Off" />
</PropField>
</div>
);
}
Expand Down Expand Up @@ -2309,9 +2337,16 @@ export default function PageClient() {
: dialogShape === "wide"
? `\n <DesignAlert variant=\"info\" description=\"Wide-form body content\" />\n {/* tester form */}`
: `\n <p className=\"text-sm\">Body content lives here.</p>`;
const customHeaderProp = dialogUseCustomHeader
? `\n customHeader={\n <div className=\"flex items-center gap-3 px-6 py-4 ...\">\n {/* custom header content; replaces title/description region */}\n </div>\n }`
: "";
const noBodyPaddingProp = dialogNoBodyPadding ? `\n noBodyPadding` : "";
const classNameProps = dialogAccentClassNames
? `\n className=\"ring-2 ring-indigo-500/40\"\n overlayClassName=\"bg-indigo-950/40\"\n headerClassName=\"bg-indigo-500/5\"\n bodyClassName=\"bg-foreground/[0.02]\"\n footerClassName=\"bg-foreground/[0.02]\"`
: "";
return `<DesignDialog
size="${dialogSize}"${variantProp}${iconProp}
title="${escapeAttr(dialogTitle || "Dialog")}"${descProp}${headerContentProp}${footerProp}${hideCloseProp}
title="${escapeAttr(dialogTitle || "Dialog")}"${descProp}${headerContentProp}${customHeaderProp}${footerProp}${hideCloseProp}${noBodyPaddingProp}${classNameProps}
trigger={<DesignButton size="sm">${escapeAttr(dialogTriggerLabel)}</DesignButton>}
>${bodySnippet}
</DesignDialog>`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,7 @@ function ProjectsListPage() {

{!isRemoteDevelopmentEnvironment && (
<Button
className="rounded-xl"
onClick={async () => {
if (isLocalEmulator) {
setOpenConfigFileDialog(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -302,23 +302,19 @@ function MethodToggleRow({
const iconSize = density === "card" ? 20 : 18;

return (
<div
role="group"
<Label
htmlFor={id}
className={`flex items-center gap-3 cursor-pointer ${innerRing} ${padding}`}
onClick={(e) => {
if (e.target instanceof HTMLElement && e.target.closest('[role="switch"]')) return;
onCheckedChange(!checked);
}}
>
<div className="p-2 rounded-lg bg-foreground/[0.06] dark:bg-foreground/[0.04] shrink-0">
<Icon size={iconSize} className="text-foreground/70 dark:text-muted-foreground" aria-hidden="true" />
</div>
<div className="flex-1 min-w-0">
<Label htmlFor={id} className="text-sm font-medium text-foreground truncate cursor-pointer">{label}</Label>
<div className="text-sm font-medium text-foreground truncate">{label}</div>
{hint && <div className="text-xs text-muted-foreground mt-0.5">{hint}</div>}
</div>
<Switch id={id} checked={checked} onCheckedChange={onCheckedChange} aria-label={label} />
</div>
</Label>
);
}

Expand Down Expand Up @@ -456,8 +452,7 @@ function useEmailVerificationToggle() {
okButton={{
label: "Apply Change",
onClick: async () => {
if (pendingChange == null) return;
await pendingChange.onConfirm();
await pendingChange?.onConfirm();
},
}}
cancelButton={{ label: "Cancel" }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
DesignDialogClose,
DesignInput,
DesignPillToggle,
} from "@stackframe/dashboard-ui-components";
} from "@/components/design-components";
import {
DesignEditableGrid,
type DesignEditableGridItem,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ function PaymentsLayoutInner({ children }: { children: React.ReactNode }) {

if (!stripeAccountInfo && !project.isDevelopmentEnvironment) {
return (
<div className="mx-auto flex w-full max-w-sm min-h-[calc(100vh-4.5rem)] items-center justify-center px-3 py-8">
<Card className="w-full">
<div className="flex flex-1 w-full items-center justify-center self-stretch px-3 py-8">
<Card className="w-full max-w-sm">
<CardContent className="p-8 text-center">
<div className="mx-auto mb-4 grid h-12 w-12 place-items-center rounded-full bg-primary/10 text-primary">
<WalletIcon className="h-6 w-6" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -723,7 +723,7 @@ function OnboardingSlideshow() {

return (
<PageLayout>
<div className="flex flex-col items-center h-full">
<div className="flex flex-1 min-h-0 flex-col items-center justify-center">
{/* Slide content container - fills available space */}
<div className="relative w-full flex-1 min-h-0">
{slides.map((s, index) => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,13 @@ function EditProductForm({ productId, existingProduct }: { productId: string, ex
variant="form"
onMakeFree={() => {
setPrices(createFreePrice());
if (errors.prices) {
setErrors(prev => {
const newErrors = { ...prev };
delete newErrors.prices;
return newErrors;
});
}
}}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
/>
</section>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
"use client";

import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Input, Label, SimpleTooltip, Typography } from "@/components/ui";
import {
DesignButton,
DesignDialog,
DesignDialogClose,
DesignInput,
} from "@/components/design-components";
import { Label, SimpleTooltip, Typography } from "@/components/ui";
import { FolderOpenIcon } from "@phosphor-icons/react";
import { getUserSpecifiedIdErrorMessage, isValidUserSpecifiedId, sanitizeUserSpecifiedId } from "@stackframe/stack-shared/dist/schema-fields";
import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises";
import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
import { useState } from "react";

// Helper to convert display name to ID format
Expand All @@ -25,7 +32,7 @@ export function CreateProductLineDialog({ open, onOpenChange, onCreate }: Create
const [hasManuallyEditedId, setHasManuallyEditedId] = useState(false);
const [errors, setErrors] = useState<{ id?: string, displayName?: string }>({});

const validateAndCreate = () => {
const validateAndCreate = async () => {
const newErrors: { id?: string, displayName?: string } = {};

// Validate display name
Expand All @@ -45,9 +52,10 @@ export function CreateProductLineDialog({ open, onOpenChange, onCreate }: Create
return;
}

runAsynchronously(onCreate({ id: productLineId.trim(), displayName: displayName.trim() }));
// Await onCreate before resetting/closing so the dialog stays open with
// the user's input intact if creation fails.
await onCreate({ id: productLineId.trim(), displayName: displayName.trim() });

// Reset form
setDisplayName("");
setProductLineId("");
setHasManuallyEditedId(false);
Expand All @@ -64,80 +72,82 @@ export function CreateProductLineDialog({ open, onOpenChange, onCreate }: Create
};

return (
<Dialog open={open} onOpenChange={handleClose}>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Create Product Line</DialogTitle>
<DialogDescription>
Product lines allow you to organize related products. Customers can only have one active product from each product line at a time (except for add-ons).
</DialogDescription>
</DialogHeader>

<div className="grid gap-4 py-4">
<div className="grid gap-2">
<Label htmlFor="display-name">
<SimpleTooltip tooltip="This is how the product line will be displayed to users">
Display Name
</SimpleTooltip>
</Label>
<Input
id="display-name"
value={displayName}
onChange={(e) => {
const value = e.target.value;
setDisplayName(value);
setErrors(prev => ({ ...prev, displayName: undefined }));
// Auto-generate ID from display name if not manually edited
if (!hasManuallyEditedId) {
setProductLineId(toIdFormat(value));
setErrors(prev => ({ ...prev, id: undefined }));
}
}}
placeholder="e.g., Pricing Tiers"
className={errors.displayName ? "border-destructive" : ""}
/>
{errors.displayName && (
<Typography type="label" className="text-destructive">
{errors.displayName}
</Typography>
)}
</div>

<div className="grid gap-2">
<Label htmlFor="product-line-id">
<SimpleTooltip tooltip="This is the unique identifier for your product line, used in code">
Product Line ID
</SimpleTooltip>
</Label>
<Input
id="product-line-id"
value={productLineId}
onChange={(e) => {
const value = sanitizeUserSpecifiedId(e.target.value);
setProductLineId(value);
setHasManuallyEditedId(true);
<DesignDialog
open={open}
onOpenChange={(nextOpen) => {
if (!nextOpen) handleClose();
}}
size="md"
icon={FolderOpenIcon}
title="Create Product Line"
description="Product lines allow you to organize related products. Customers can only have one active product from each product line at a time (except for add-ons)."
footer={(
<>
<DesignDialogClose asChild>
<DesignButton variant="secondary" size="sm" type="button">Cancel</DesignButton>
</DesignDialogClose>
<DesignButton size="sm" type="button" onClick={() => runAsynchronouslyWithAlert(validateAndCreate())}>
Create Product Line
</DesignButton>
</>
)}
>
<div className="grid gap-4">
<div className="grid gap-2">
<Label htmlFor="display-name">
<SimpleTooltip tooltip="This is how the product line will be displayed to users">
Display Name
</SimpleTooltip>
</Label>
<DesignInput
id="display-name"
value={displayName}
onChange={(e) => {
const value = e.target.value;
setDisplayName(value);
setErrors(prev => ({ ...prev, displayName: undefined }));
if (!hasManuallyEditedId) {
setProductLineId(toIdFormat(value));
setErrors(prev => ({ ...prev, id: undefined }));
}}
placeholder="e.g., pricing-tiers"
className={`font-mono text-sm ${errors.id ? "border-destructive" : ""}`}
/>
{errors.id && (
<Typography type="label" className="text-destructive">
{errors.id}
</Typography>
)}
</div>
}
}}
placeholder="e.g., Pricing Tiers"
size="md"
className={errors.displayName ? "border-destructive focus-visible:ring-destructive/30" : ""}
/>
{errors.displayName && (
Comment thread
mantrakp04 marked this conversation as resolved.
<Typography type="label" className="text-destructive text-xs">
{errors.displayName}
</Typography>
)}
</div>

<DialogFooter>
<Button variant="outline" onClick={handleClose}>
Cancel
</Button>
<Button onClick={validateAndCreate}>
Create Product Line
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<div className="grid gap-2">
<Label htmlFor="product-line-id">
<SimpleTooltip tooltip="This is the unique identifier for your product line, used in code">
Product Line ID
</SimpleTooltip>
</Label>
<DesignInput
id="product-line-id"
value={productLineId}
onChange={(e) => {
const value = sanitizeUserSpecifiedId(e.target.value);
setProductLineId(value);
setHasManuallyEditedId(true);
setErrors(prev => ({ ...prev, id: undefined }));
}}
placeholder="e.g., pricing-tiers"
size="md"
className={`font-mono text-sm ${errors.id ? "border-destructive focus-visible:ring-destructive/30" : ""}`}
/>
{errors.id && (
<Typography type="label" className="text-destructive text-xs">
{errors.id}
</Typography>
)}
</div>
</div>
</DesignDialog>
);
}
Loading
Loading