Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ export function computeRollingAveragePayoutFees(
for (let i = 0; i < timeseries.length; i++) {
const windowStart = subMonths(timeseries[i].date, windowMonths);

while (windowStartIdx < i && timeseries[windowStartIdx].date < windowStart) {
while (
windowStartIdx < i &&
timeseries[windowStartIdx].date < windowStart
) {
windowSum -= timeseries[windowStartIdx].fees;
windowStartIdx++;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import PlanUsage from "./plan-usage";
export default function WorkspaceBilling() {
return (
<PageContent title="Billing">
<PageWidthWrapper className="grid gap-8">
<PageWidthWrapper className="grid gap-8 pb-12">
<PlanUsage />
<PaymentMethods />
</PageWidthWrapper>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export default function PaymentMethods() {
const router = useRouter();
const { paymentMethods, defaultPaymentMethodId } = usePaymentMethods();
const [isLoading, setIsLoading] = useState(false);
const { slug, stripeId, plan, role } = useWorkspace();
const { slug, stripeId, role } = useWorkspace();

const regularPaymentMethods = paymentMethods?.filter(
(pm) => !DIRECT_DEBIT_PAYMENT_METHOD_TYPES.includes(pm.type),
Expand All @@ -55,7 +55,7 @@ export default function PaymentMethods() {
router.push(url);
};

if (plan === "free") {
if (!stripeId) {
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,59 +4,18 @@ import { createId } from "@/lib/api/create-id";
import { getDefaultProgramIdOrThrow } from "@/lib/api/programs/get-default-program-id-or-throw";
import { prisma } from "@/lib/prisma";
import {
addProgramResourceSchema,
programResourceColorSchema,
programResourceFileSchema,
programResourceLinkSchema,
programResourceLinkUrlSchema,
} from "@/lib/zod/schemas/program-resources";
import { R2_URL } from "@dub/utils";
import * as z from "zod/v4";
import { authActionClient } from "../../safe-action";
import { throwIfNoPermission } from "../../throw-if-no-permission";
import { MAX_PROGRAM_RESOURCE_FILE_SIZE_BYTES } from "./constants";

// Base schema for all resource types
const baseResourceSchema = z.object({
workspaceId: z.string(),
name: z.string().min(1, "Name is required"),
});

// Schema for logo resources
const logoResourceSchema = baseResourceSchema.extend({
resourceType: z.literal("logo"),
key: z.string(),
fileSize: z.number().int().positive(),
});

// Schema for file resources
const fileResourceSchema = baseResourceSchema.extend({
resourceType: z.literal("file"),
key: z.string(),
fileSize: z.number().int().positive(),
});

// Schema for color resources
const colorResourceSchema = baseResourceSchema.extend({
resourceType: z.literal("color"),
color: z.string(), // Hex color code
});

// Schema for link resources
const linkResourceSchema = baseResourceSchema.extend({
resourceType: z.literal("link"),
url: programResourceLinkUrlSchema,
});

// Combined schema that can handle any resource type
const addResourceSchema = z.discriminatedUnion("resourceType", [
logoResourceSchema,
fileResourceSchema,
colorResourceSchema,
linkResourceSchema,
]);

export const addProgramResourceAction = authActionClient
.inputSchema(addResourceSchema)
.inputSchema(addProgramResourceSchema)
.action(async ({ ctx, parsedInput }) => {
const { workspace } = ctx;
const { name, resourceType } = parsedInput;
Expand All @@ -68,20 +27,16 @@ export const addProgramResourceAction = authActionClient

const programId = getDefaultProgramIdOrThrow(workspace);

// Verify the program exists and belongs to the workspace
const program = await prisma.program.findUnique({
const program = await prisma.program.findUniqueOrThrow({
where: {
id: programId,
workspaceId: workspace.id,
},
select: {
id: true,
resources: true,
},
});

if (!program) throw new Error("Program not found");

const currentResources = (program.resources as any) || {
logos: [],
colors: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,13 @@ import { getDefaultProgramIdOrThrow } from "@/lib/api/programs/get-default-progr
import { prisma } from "@/lib/prisma";
import { storage } from "@/lib/storage";
import {
PROGRAM_RESOURCE_TYPES,
deleteProgramResourceSchema,
programResourcesSchema,
} from "@/lib/zod/schemas/program-resources";
import { R2_URL } from "@dub/utils";
import * as z from "zod/v4";
import { authActionClient } from "../../safe-action";
import { throwIfNoPermission } from "../../throw-if-no-permission";

// Schema for deleting a program resource
const deleteProgramResourceSchema = z.object({
workspaceId: z.string(),
resourceType: z.enum(PROGRAM_RESOURCE_TYPES),
resourceId: z.string(),
});

export const deleteProgramResourceAction = authActionClient
.inputSchema(deleteProgramResourceSchema)
.action(async ({ ctx, parsedInput }) => {
Expand All @@ -32,20 +24,19 @@ export const deleteProgramResourceAction = authActionClient

const programId = getDefaultProgramIdOrThrow(workspace);

// Verify the program exists and belongs to the workspace
const program = await prisma.program.findUnique({
const program = await prisma.program.findUniqueOrThrow({
where: {
id: programId,
workspaceId: workspace.id,
},
select: {
id: true,
resources: true,
},
});

if (!program) throw new Error("Program not found");
if (!program.resources) throw new Error("Program resources not found");
if (!program.resources) {
throw new Error("Program resources not found");
}

// Create a copy of the current resources to update
const updatedResources = { ...(program.resources as any) };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,61 +7,16 @@ import {
programResourceColorSchema,
programResourceFileSchema,
programResourceLinkSchema,
programResourceLinkUrlSchema,
programResourcesSchema,
updateProgramResourceSchema,
} from "@/lib/zod/schemas/program-resources";
import { R2_URL } from "@dub/utils";
import * as z from "zod/v4";
import { authActionClient } from "../../safe-action";
import { throwIfNoPermission } from "../../throw-if-no-permission";
import { MAX_PROGRAM_RESOURCE_FILE_SIZE_BYTES } from "./constants";

// Base schema for all resource types
const baseUpdateSchema = z.object({
workspaceId: z.string(),
resourceId: z.string(),
});

// Schema for logo resources
const updateLogoSchema = baseUpdateSchema.extend({
resourceType: z.literal("logo"),
name: z.string().min(1).optional(),
key: z.string().optional(),
fileSize: z.number().int().positive().optional(),
});

// Schema for file resources
const updateFileSchema = baseUpdateSchema.extend({
resourceType: z.literal("file"),
name: z.string().min(1).optional(),
key: z.string().optional(),
fileSize: z.number().int().positive().optional(),
});

// Schema for color resources
const updateColorSchema = baseUpdateSchema.extend({
resourceType: z.literal("color"),
name: z.string().min(1).optional(),
color: z.string().optional(),
});

// Schema for link resources
const updateLinkSchema = baseUpdateSchema.extend({
resourceType: z.literal("link"),
name: z.string().min(1).optional(),
url: programResourceLinkUrlSchema.optional(),
});

// Combined schema that can handle any resource type
const updateResourceSchema = z.discriminatedUnion("resourceType", [
updateLogoSchema,
updateFileSchema,
updateColorSchema,
updateLinkSchema,
]);

export const updateProgramResourceAction = authActionClient
.inputSchema(updateResourceSchema)
.inputSchema(updateProgramResourceSchema)
.action(async ({ ctx, parsedInput }) => {
const { workspace } = ctx;
const { resourceId, resourceType } = parsedInput;
Expand All @@ -73,20 +28,19 @@ export const updateProgramResourceAction = authActionClient

const programId = getDefaultProgramIdOrThrow(workspace);

// Verify the program exists and belongs to the workspace
const program = await prisma.program.findUnique({
const program = await prisma.program.findUniqueOrThrow({
where: {
id: programId,
workspaceId: workspace.id,
},
select: {
id: true,
resources: true,
},
});

if (!program) throw new Error("Program not found");
if (!program.resources) throw new Error("Program resources not found");
if (!program.resources) {
throw new Error("Program resources not found.");
}

const currentResources = program.resources as any;
const updatedResources = { ...currentResources };
Expand Down Expand Up @@ -172,7 +126,7 @@ export const updateProgramResourceAction = authActionClient
id: program.id,
},
data: {
resources: programResourcesSchema.parse(updatedResources) as any,
resources: programResourcesSchema.parse(updatedResources),
},
});

Expand All @@ -189,8 +143,4 @@ export const updateProgramResourceAction = authActionClient
);
}
}

return {
success: true,
};
});
6 changes: 3 additions & 3 deletions apps/web/lib/partners/get-partner-bank-account.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { prisma } from "@/lib/prisma";
import { stripe } from "@/lib/stripe";
import { waitUntil } from "@vercel/functions";
import Stripe from "stripe";
import * as z from "zod/v4";

Expand Down Expand Up @@ -41,7 +39,9 @@ export const getPartnerBankAccount = async (stripeAccount: string) => {

if (isApplicationAccessRevoked) {
// TODO: recompute payout state + reset payoutsEnabledAt / payoutMethodHash if needed
console.warn("No account connected – application access may have been revoked.")
console.warn(
"No account connected – application access may have been revoked.",
);
}

return null;
Expand Down
2 changes: 1 addition & 1 deletion apps/web/lib/webhook/failure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import { sendEmail } from "@dub/email";
import WebhookDisabled from "@dub/email/templates/webhook-disabled";
import WebhookFailed from "@dub/email/templates/webhook-failed";
import { Webhook } from "@prisma/client";
import { syncWorkspaceWebhookStatus } from "./click-webhook-workspaces";
import {
WEBHOOK_FAILURE_DISABLE_THRESHOLD,
WEBHOOK_FAILURE_NOTIFY_THRESHOLDS,
} from "./constants";
import { syncWorkspaceWebhookStatus } from "./click-webhook-workspaces";

export const handleWebhookFailure = async (webhookId: string) => {
const webhook = await prisma.webhook.update({
Expand Down
2 changes: 1 addition & 1 deletion apps/web/lib/webhook/sample-events/lead-created.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"partnerId": "pn_cm0lcuvtz000xcutmqw4a7wi3",
"programId": "prog_CYCu7IMAapjkRpTnr8F1azjN",
"archived": false,
"expiresAt": "1970-01-01T00:00:00.000Z",
"expiresAt": null,
"expiredUrl": null,
"disabledAt": null,
"password": null,
Expand Down
2 changes: 1 addition & 1 deletion apps/web/lib/webhook/sample-events/link-clicked.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"partnerId": "pn_cm0lcuvtz000xcutmqw4a7wi3",
"programId": "prog_CYCu7IMAapjkRpTnr8F1azjN",
"archived": false,
"expiresAt": "1970-01-01T00:00:00.000Z",
"expiresAt": null,
"expiredUrl": null,
"disabledAt": null,
"password": null,
Expand Down
2 changes: 1 addition & 1 deletion apps/web/lib/webhook/sample-events/sale-created.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"partnerId": "pn_cm0lcuvtz000xcutmqw4a7wi3",
"programId": "prog_CYCu7IMAapjkRpTnr8F1azjN",
"archived": false,
"expiresAt": "1970-01-01T00:00:00.000Z",
"expiresAt": null,
"expiredUrl": null,
"disabledAt": null,
"password": null,
Expand Down
2 changes: 1 addition & 1 deletion apps/web/lib/webhook/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import { nanoid, toCamelCase } from "@dub/utils";
import { ExpandedLink, transformLink } from "../api/links/utils/transform-link";
import { generateRandomName } from "../names";
import { ClickEventTB } from "../types";
import type { WebhookTrigger } from "./types";
import { clickEventSchema } from "../zod/schemas/clicks";
import { WEBHOOK_EVENT_ID_PREFIX } from "./constants";
import { leadWebhookEventSchema, saleWebhookEventSchema } from "./schemas";
import type { WebhookTrigger } from "./types";

export const transformClickEventData = (
data: ClickEventTB & {
Expand Down
10 changes: 5 additions & 5 deletions apps/web/lib/zod/schemas/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -861,11 +861,11 @@ export const linkEventSchema = LinkSchema.extend({
// coerce date fields
createdAt: z.coerce.date(),
updatedAt: z.coerce.date(),
lastClicked: z.coerce.date(),
expiresAt: z.coerce.date(),
disabledAt: z.coerce.date(),
testCompletedAt: z.coerce.date(),
testStartedAt: z.coerce.date(),
lastClicked: z.coerce.date().nullable(),
expiresAt: z.coerce.date().nullable(),
disabledAt: z.coerce.date().nullable(),
testCompletedAt: z.coerce.date().nullable(),
testStartedAt: z.coerce.date().nullable(),
// userId can be null
userId: z.string().nullable(),
});
Loading
Loading