Thanks to visit codestin.com
Credit goes to github.com

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 @@ -4,7 +4,7 @@ import { FRAUD_RULES_BY_TYPE } from "@/lib/api/fraud/constants";
import { mutatePrefix } from "@/lib/swr/mutate";
import { useFraudEventGroups } from "@/lib/swr/use-fraud-event-groups";
import { useFraudEventsCount } from "@/lib/swr/use-fraud-events-count";
import { fraudEventGroupProps } from "@/lib/types";
import { FraudEventGroupProps } from "@/lib/types";
import { useBanPartnerModal } from "@/ui/modals/ban-partner-modal";
import { useBulkBanPartnersModal } from "@/ui/modals/bulk-ban-partners-modal";
import { FraudReviewSheet } from "@/ui/partners/fraud-risks/fraud-review-sheet";
Expand Down Expand Up @@ -84,11 +84,11 @@ export function FraudEventGroupsTable() {
});

const [pendingBanPartners, setPendingBanPartners] = useState<
Array<NonNullable<fraudEventGroupProps["partner"]>>
Array<NonNullable<FraudEventGroupProps["partner"]>>
>([]);

const tableRef = useRef<
ReturnType<typeof useTable<fraudEventGroupProps>>["table"] | null
ReturnType<typeof useTable<FraudEventGroupProps>>["table"] | null
>(null);

const { BulkBanPartnersModal, setShowBulkBanPartnersModal } =
Expand All @@ -100,7 +100,7 @@ export function FraudEventGroupsTable() {
},
});

const { table, ...tableProps } = useTable<fraudEventGroupProps>({
const { table, ...tableProps } = useTable<FraudEventGroupProps>({
data: fraudEvents || [],
columns: [
{
Expand Down Expand Up @@ -369,7 +369,7 @@ export function FraudEventGroupsTable() {
);
}

function RowMenuButton({ row }: { row: Row<fraudEventGroupProps> }) {
function RowMenuButton({ row }: { row: Row<FraudEventGroupProps> }) {
const fraudEvent = row.original;

const [isOpen, setIsOpen] = useState(false);
Expand Down Expand Up @@ -465,7 +465,7 @@ function useCurrentFraudEventGroup({
fraudEventGroups,
groupKey,
}: {
fraudEventGroups?: fraudEventGroupProps[];
fraudEventGroups?: FraudEventGroupProps[];
groupKey: string | null;
}) {
let currentFraudEventGroup = groupKey
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { resolveFraudEventsAction } from "@/lib/actions/fraud/resolve-fraud-even
import { parseActionError } from "@/lib/actions/parse-action-errors";
import { mutatePrefix } from "@/lib/swr/mutate";
import useWorkspace from "@/lib/swr/use-workspace";
import { fraudEventGroupProps } from "@/lib/types";
import { FraudEventGroupProps } from "@/lib/types";
import {
MAX_RESOLUTION_REASON_LENGTH,
resolveFraudEventsSchema,
Expand Down Expand Up @@ -34,7 +34,7 @@ function ResolveFraudEventsModal({
}: {
showResolveFraudEventModal: boolean;
setShowResolveFraudEventModal: Dispatch<SetStateAction<boolean>>;
fraudEventGroup: fraudEventGroupProps;
fraudEventGroup: FraudEventGroupProps;
onConfirm?: () => void;
}) {
const { id: workspaceId } = useWorkspace();
Expand Down Expand Up @@ -169,7 +169,7 @@ export function useResolveFraudEventsModal({
fraudEventGroup,
onConfirm,
}: {
fraudEventGroup: fraudEventGroupProps;
fraudEventGroup: FraudEventGroupProps;
onConfirm?: () => void;
}) {
const [showResolveFraudEventModal, setShowResolveFraudEventModal] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { FRAUD_RULES_BY_TYPE } from "@/lib/api/fraud/constants";
import { useFraudEventGroups } from "@/lib/swr/use-fraud-event-groups";
import { useFraudEventsCount } from "@/lib/swr/use-fraud-events-count";
import { fraudEventGroupProps } from "@/lib/types";
import { FraudEventGroupProps } from "@/lib/types";
import { FraudReviewSheet } from "@/ui/partners/fraud-risks/fraud-review-sheet";
import { PartnerRowItem } from "@/ui/partners/partner-row-item";
import { AnimatedEmptyState } from "@/ui/shared/animated-empty-state";
Expand Down Expand Up @@ -83,7 +83,7 @@ export function ResolvedFraudEventGroupsTable() {
header: "Event",
minSize: 100,
maxSize: 400,
cell: ({ row }: { row: Row<fraudEventGroupProps> }) => {
cell: ({ row }: { row: Row<FraudEventGroupProps> }) => {
const reason = FRAUD_RULES_BY_TYPE[row.original.type];
const count = row.original.count ?? 1;

Expand Down Expand Up @@ -121,7 +121,7 @@ export function ResolvedFraudEventGroupsTable() {
{
id: "resolvedAt",
header: "Resolved on",
cell: ({ row }: { row: Row<fraudEventGroupProps> }) => {
cell: ({ row }: { row: Row<FraudEventGroupProps> }) => {
const user = row.original.user;
const resolvedAt = row.original.resolvedAt;

Expand All @@ -135,7 +135,7 @@ export function ResolvedFraudEventGroupsTable() {
{
id: "partner",
header: "Partner",
cell: ({ row }: { row: Row<fraudEventGroupProps> }) => {
cell: ({ row }: { row: Row<FraudEventGroupProps> }) => {
const partner = row.original.partner;
if (!partner) return "-";

Expand All @@ -150,7 +150,7 @@ export function ResolvedFraudEventGroupsTable() {
);
},
meta: {
filterParams: ({ row }: { row: Row<fraudEventGroupProps> }) =>
filterParams: ({ row }: { row: Row<FraudEventGroupProps> }) =>
row.original.partner
? {
partnerId: row.original.partner.id,
Expand Down Expand Up @@ -297,7 +297,7 @@ function useCurrentFraudEventGroup({
fraudEventGroups,
groupKey,
}: {
fraudEventGroups?: fraudEventGroupProps[];
fraudEventGroups?: FraudEventGroupProps[];
groupKey: string | null;
}) {
let currentFraudEventGroup = groupKey
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { formatDate, formatDateTime, OG_AVATAR_URL } from "@dub/utils";
type PayoutPaidCellUser = {
id?: string;
name?: string | null;
email?: string | null;
image?: string | null;
} | null;

Expand Down Expand Up @@ -33,11 +34,13 @@ export function PayoutPaidCell({
{user && (
<div className="flex flex-col gap-2">
<img
src={user.image || `${OG_AVATAR_URL}${user.name}`}
alt={user.name ?? user.id}
src={user.image || `${OG_AVATAR_URL}${user.id}`}
alt={user.name ?? user.email ?? user.id}
className="size-6 shrink-0 rounded-full"
/>
<p className="text-sm font-medium">{user.name}</p>
<p className="text-sm font-medium">
{user.name ?? user.email ?? user.id}
</p>
Comment on lines +37 to +43
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add safeguards for undefined user.id in fallback chain.

The type definition allows id to be optional (id?: string), but the fallback logic assumes at least user.id will always be present. If all three fields (name, email, id) are undefined or null, this will result in:

  • Avatar URL: https://api.dub.co/og/avatar/undefined (broken image)
  • Alt text: undefined (accessibility violation)
  • Display text: undefined (blank/confusing UI)

Consider one of these approaches:

Option 1: Add a guard to ensure at least id exists

-        {user && (
+        {user?.id && (
          <img
            src={user.image || `${OG_AVATAR_URL}${user.id}`}
            alt={user.name ?? user.email ?? user.id}
            className="size-5 shrink-0 rounded-full"
          />
        )}

Option 2: Add a fallback string constant

-            src={user.image || `${OG_AVATAR_URL}${user.id}`}
-            alt={user.name ?? user.email ?? user.id}
+            src={user.image || `${OG_AVATAR_URL}${user.id ?? 'unknown'}`}
+            alt={user.name ?? user.email ?? user.id ?? 'Unknown user'}

Apply similar changes to lines 41-43 for the display text.

Also applies to: 84-85

🤖 Prompt for AI Agents
In
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/payouts/payout-paid-cell.tsx
around lines 37-43 (and also apply to 84-85), the fallback chain assumes user.id
exists which can yield "undefined" in the avatar URL, alt text, and display
text; fix this by adding a safe fallback constant (e.g., const FALLBACK_NAME =
"Unknown user") and use it for alt and display text when name, email, and id are
all falsy, and build the avatar src only when user.image exists or user.id is
defined (otherwise use a default avatar URL without interpolating undefined);
update the three usages (src, alt, and visible text) to use these guarded
fallbacks.

</div>
)}
<div className="flex items-center gap-1.5 text-xs text-neutral-500">
Expand Down Expand Up @@ -78,8 +81,8 @@ export function PayoutPaidCell({
<div className="flex items-center gap-2">
{user && (
<img
src={user.image || `${OG_AVATAR_URL}${user.name}`}
alt={user.name ?? user.id}
src={user.image || `${OG_AVATAR_URL}${user.id}`}
alt={user.name ?? user.email ?? user.id}
className="size-5 shrink-0 rounded-full"
/>
)}
Expand Down
3 changes: 3 additions & 0 deletions apps/web/lib/api/fraud/get-grouped-fraud-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ interface QueryResult {
partnerImage: string | null;
userId: string | null;
userName: string | null;
userEmail: string | null;
userImage: string | null;
}

Expand Down Expand Up @@ -80,6 +81,7 @@ export async function getGroupedFraudEvents({
p.email AS partnerEmail,
p.image AS partnerImage,
u.name AS userName,
u.email AS userEmail,
u.image AS userImage
FROM (
SELECT
Expand Down Expand Up @@ -122,6 +124,7 @@ export async function getGroupedFraudEvents({
? {
id: event.userId,
name: event.userName,
email: event.userEmail,
image: event.userImage,
}
: null,
Expand Down
4 changes: 2 additions & 2 deletions apps/web/lib/swr/use-fraud-event-groups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useRouterStuff } from "@dub/ui";
import { fetcher } from "@dub/utils";
import useSWR from "swr";
import { z } from "zod";
import { fraudEventGroupProps } from "../types";
import { FraudEventGroupProps } from "../types";
import { groupedFraudEventsQuerySchema } from "../zod/schemas/fraud";
import useWorkspace from "./use-workspace";

Expand All @@ -26,7 +26,7 @@ export function useFraudEventGroups({
{ exclude },
);

const { data, error } = useSWR<fraudEventGroupProps[]>(
const { data, error } = useSWR<FraudEventGroupProps[]>(
enabled && defaultProgramId ? `/api/fraud/events${queryString}` : undefined,
fetcher,
{
Expand Down
4 changes: 2 additions & 2 deletions apps/web/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ import { DiscountCodeSchema, DiscountSchema } from "./zod/schemas/discount";
import { EmailDomainSchema } from "./zod/schemas/email-domains";
import { FolderSchema } from "./zod/schemas/folders";
import {
groupedFraudEventSchema,
fraudRuleSchema,
groupedFraudEventSchema,
updateFraudRuleSettingsSchema,
} from "./zod/schemas/fraud";
import { GroupWithProgramSchema } from "./zod/schemas/group-with-program";
Expand Down Expand Up @@ -673,7 +673,7 @@ export type WorkflowAttribute = (typeof WORKFLOW_ATTRIBUTES)[number];

export type EmailDomainProps = z.infer<typeof EmailDomainSchema>;

export type fraudEventGroupProps = z.infer<typeof groupedFraudEventSchema>;
export type FraudEventGroupProps = z.infer<typeof groupedFraudEventSchema>;

export type ExtendedFraudRuleType =
| FraudRuleType
Expand Down
6 changes: 1 addition & 5 deletions apps/web/lib/zod/schemas/fraud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,7 @@ export const groupedFraudEventSchema = z.object({
email: true,
image: true,
}),
user: UserSchema.pick({
id: true,
name: true,
image: true,
}).nullable(),
user: UserSchema.nullable(),
});

export const groupedFraudEventsQuerySchema = z
Expand Down
9 changes: 2 additions & 7 deletions apps/web/lib/zod/schemas/payouts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { z } from "zod";
import { getPaginationQuerySchema } from "./misc";
import { EnrolledPartnerSchema, PartnerSchema } from "./partners";
import { ProgramSchema } from "./programs";
import { UserSchema } from "./users";

export const createManualPayoutSchema = z.object({
workspaceId: z.string(),
Expand Down Expand Up @@ -72,13 +73,7 @@ export const PayoutResponseSchema = PayoutSchema.merge(
tenantId: z.string().nullable(),
}),
),
user: z
.object({
id: z.string(),
name: z.string().nullable(),
image: z.string().nullable(),
})
.nullish(),
user: UserSchema.nullish(),
}),
);

Expand Down
1 change: 1 addition & 0 deletions apps/web/lib/zod/schemas/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ import { z } from "zod";
export const UserSchema = z.object({
id: z.string(),
name: z.string().nullable(),
email: z.string().nullable(),
image: z.string().nullable(),
});
4 changes: 2 additions & 2 deletions apps/web/tests/fraud/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Customer, fraudEventGroupProps, TrackLeadResponse } from "@/lib/types";
import { Customer, FraudEventGroupProps, TrackLeadResponse } from "@/lib/types";
import { FraudRuleType } from "@prisma/client";
import { randomCustomer, retry } from "tests/utils/helpers";
import { HttpClient } from "tests/utils/http";
Expand Down Expand Up @@ -234,7 +234,7 @@ async function waitForFraudEvent({
}) {
return await retry(
async () => {
const { data } = await http.get<fraudEventGroupProps[]>({
const { data } = await http.get<FraudEventGroupProps[]>({
path: "/fraud/events",
query: {
partnerId,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import useWorkspace from "@/lib/swr/use-workspace";
import { CommissionResponse, fraudEventGroupProps } from "@/lib/types";
import { CommissionResponse, FraudEventGroupProps } from "@/lib/types";
import { COMMISSIONS_MAX_PAGE_SIZE } from "@/lib/zod/schemas/commissions";
import { CustomerRowItem } from "@/ui/customers/customer-row-item";
import {
Expand All @@ -23,7 +23,7 @@ import { CommissionTypeBadge } from "../commission-type-badge";
export function CommissionsOnHoldTable({
fraudEventGroup,
}: {
fraudEventGroup: fraudEventGroupProps;
fraudEventGroup: FraudEventGroupProps;
}) {
const workspace = useWorkspace();
const { id: workspaceId, slug } = workspace;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { fraudEventGroupProps } from "@/lib/types";
import { FraudEventGroupProps } from "@/lib/types";
import { FraudRuleType } from "@dub/prisma/client";
import React from "react";
import { FraudCrossProgramBanTable } from "./fraud-cross-program-ban-table";
Expand All @@ -21,7 +21,7 @@ const FRAUD_EVENTS_TABLES: Partial<Record<FraudRuleType, React.ComponentType>> =
export function FraudEventsTableWrapper({
fraudEventGroup,
}: {
fraudEventGroup: fraudEventGroupProps;
fraudEventGroup: FraudEventGroupProps;
}) {
const TableComponent = FRAUD_EVENTS_TABLES[fraudEventGroup.type];

Expand Down
12 changes: 6 additions & 6 deletions apps/web/ui/partners/fraud-risks/fraud-review-sheet.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { FRAUD_RULES_BY_TYPE } from "@/lib/api/fraud/constants";
import { fraudEventGroupProps } from "@/lib/types";
import { FraudEventGroupProps } from "@/lib/types";
import { useBanPartnerModal } from "@/ui/modals/ban-partner-modal";
import { X } from "@/ui/shared/icons";
import {
Expand All @@ -25,7 +25,7 @@ import { CommissionsOnHoldTable } from "./commissions-on-hold-table";
import { FraudEventsTableWrapper } from "./fraud-events-tables";

interface FraudReviewSheetProps {
fraudEventGroup: fraudEventGroupProps;
fraudEventGroup: FraudEventGroupProps;
setIsOpen: Dispatch<SetStateAction<boolean>>;
onNext?: () => void;
onPrevious?: () => void;
Expand Down Expand Up @@ -199,8 +199,8 @@ function FraudReviewSheetContent({
{user && (
<div className="flex flex-col gap-2">
<img
src={user.image || `${OG_AVATAR_URL}${user.name}`}
alt={user.name || user.id}
src={user.image || `${OG_AVATAR_URL}${user.id}`}
alt={user.name ?? user.email ?? user.id}
className="size-6 shrink-0 rounded-full"
/>
<p className="text-sm font-medium">{user.name}</p>
Expand All @@ -220,8 +220,8 @@ function FraudReviewSheetContent({
>
{user && (
<img
src={user.image || `${OG_AVATAR_URL}${user.name}`}
alt={user.name || user.id}
src={user.image || `${OG_AVATAR_URL}${user.id}`}
alt={user.name ?? user.email ?? user.id}
className="size-5 shrink-0 rounded-full"
/>
)}
Expand Down
6 changes: 3 additions & 3 deletions apps/web/ui/users/user-row-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ export function UserRowItem({
date,
label,
}: {
user: Pick<UserProps, "id" | "name" | "image">;
user: UserProps;
date: Date;
label: string;
}) {
const image = user.image || `${OG_AVATAR_URL}${user.name}`;
const name = user.name ?? user.id;
const image = user.image || `${OG_AVATAR_URL}${user.id}`;
const name = user.name ?? user.email ?? user.id;

return (
<Tooltip
Expand Down