From 511d7e61d6e843c856a907e2cf13f323bfc4f7d1 Mon Sep 17 00:00:00 2001 From: Steven Tey Date: Fri, 10 Apr 2026 17:34:58 -0700 Subject: [PATCH 1/6] revert to v1/instagram/post, fallback to view_count if play_count undefined --- .../web/lib/api/scrape-creators/get-social-content.ts | 11 ++++++++--- apps/web/lib/api/scrape-creators/schema.ts | 4 ++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/apps/web/lib/api/scrape-creators/get-social-content.ts b/apps/web/lib/api/scrape-creators/get-social-content.ts index 8edff4b90d..d457141ebd 100644 --- a/apps/web/lib/api/scrape-creators/get-social-content.ts +++ b/apps/web/lib/api/scrape-creators/get-social-content.ts @@ -60,7 +60,7 @@ export async function getSocialContent({ } const contentType = PLATFORM_CONTENT_TYPE[platform]; - const version = ["tiktok", "instagram"].includes(platform) ? "v2" : "v1"; + const version = platform === "tiktok" ? "v2" : "v1"; const { data, error } = await scrapeCreatorsFetch( "/:version/:platform/:contentType", @@ -91,6 +91,8 @@ export async function getSocialContent({ return EMPTY_SOCIAL_CONTENT; } + console.log(`Response from ScrapeCreators: ${JSON.stringify(data, null, 2)}`); + let result: SocialContent; switch (data.platform) { @@ -134,7 +136,10 @@ export async function getSocialContent({ mediaType = "carousel"; } else if (data.__typename === "GraphImage") { mediaType = "image"; - } else if (thumbnailUrls === undefined && data.video_play_count > 0) { + } else if ( + thumbnailUrls === undefined && + (data.video_play_count || data.video_view_count) > 0 + ) { mediaType = "video"; } @@ -142,7 +147,7 @@ export async function getSocialContent({ publishedAt: new Date(data.taken_at_timestamp * 1000), handle: data.owner.username, platformId: null, - views: data.video_play_count, + views: data.video_play_count ?? data.video_view_count, likes: data.edge_media_preview_like.count, title: null, description: data.edge_media_to_caption?.edges?.[0]?.node?.text ?? null, diff --git a/apps/web/lib/api/scrape-creators/schema.ts b/apps/web/lib/api/scrape-creators/schema.ts index 6bfe441fd7..8125409d02 100644 --- a/apps/web/lib/api/scrape-creators/schema.ts +++ b/apps/web/lib/api/scrape-creators/schema.ts @@ -234,6 +234,10 @@ export const socialContentSchema = z.preprocess( .number() .nullish() .transform((val) => val ?? 0), + video_view_count: z + .number() + .nullish() + .transform((val) => val ?? 0), edge_media_preview_like: z.object({ count: z .number() From 1442bb97e32a56228c2dad77dd20f0433737c53e Mon Sep 17 00:00:00 2001 From: Pedro Ladeira <57876830+pepeladeira@users.noreply.github.com> Date: Fri, 10 Apr 2026 21:48:03 -0300 Subject: [PATCH 2/6] Block saving additional links when enabled without a link format (#3733) --- .../links/group-additional-links.tsx | 51 +++++++++++-------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/groups/[groupSlug]/links/group-additional-links.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/groups/[groupSlug]/links/group-additional-links.tsx index 9973938d90..0976bfcfba 100644 --- a/apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/groups/[groupSlug]/links/group-additional-links.tsx +++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/groups/[groupSlug]/links/group-additional-links.tsx @@ -88,6 +88,10 @@ export function GroupAdditionalLinksForm({ group }: { group: GroupProps }) { const onSubmit = async (data: FormData) => { if (!group) return; + if (enableAdditionalLinks && (data.additionalLinks?.length ?? 0) === 0) { + return; + } + await updateGroup(`/api/groups/${group.id}`, { method: "PATCH", body: data, @@ -102,6 +106,8 @@ export function GroupAdditionalLinksForm({ group }: { group: GroupProps }) { const additionalLinks = watch("additionalLinks") || []; const maxPartnerLinks = watch("maxPartnerLinks") || 0; + const hasLinkFormat = additionalLinks.length > 0; + const cannotSaveWithoutLinkFormat = enableAdditionalLinks && !hasLinkFormat; const { addDestinationUrlModal, setIsOpen } = useAddDestinationUrlModal({ additionalLinks, @@ -120,25 +126,6 @@ export function GroupAdditionalLinksForm({ group }: { group: GroupProps }) { > {enableAdditionalLinks && ( <> - - - setValue("maxPartnerLinks", v, { - shouldDirty: true, - shouldValidate: true, - }) - } - min={0} - max={MAX_ADDITIONAL_PARTNER_LINKS} - step={1} - className="w-full" - /> - - + + + + setValue("maxPartnerLinks", v, { + shouldDirty: true, + shouldValidate: true, + }) + } + min={0} + max={MAX_ADDITIONAL_PARTNER_LINKS} + step={1} + className="w-full" + /> + )} @@ -218,7 +224,12 @@ export function GroupAdditionalLinksForm({ group }: { group: GroupProps }) { text="Save changes" className="h-8" loading={isSubmitting} - disabled={!isValid || !isDirty} + disabled={!isValid || !isDirty || cannotSaveWithoutLinkFormat} + disabledTooltip={ + cannotSaveWithoutLinkFormat + ? "Add at least one link format before saving." + : undefined + } /> From daec20a6fa35b046f010e6dc42f25441dc2ceec0 Mon Sep 17 00:00:00 2001 From: Steven Tey Date: Fri, 10 Apr 2026 17:52:03 -0700 Subject: [PATCH 3/6] update EMAIL_TEMPLATES_MAP --- apps/web/lib/email/email-templates-map.ts | 3 ++- .../ui/partners/program-marketplace/programs-promo-banner.tsx | 2 +- .../ui/partners/program-marketplace/programs-promo-card.tsx | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/web/lib/email/email-templates-map.ts b/apps/web/lib/email/email-templates-map.ts index d8abaec80f..8e8bee323e 100644 --- a/apps/web/lib/email/email-templates-map.ts +++ b/apps/web/lib/email/email-templates-map.ts @@ -1,5 +1,6 @@ import BountyApproved from "@dub/email/templates/bounty-approved"; import IdentityVerificationAnnouncement from "@dub/email/templates/broadcasts/identity-verification-announcement"; +import StablecoinPayoutsAnnouncement from "@dub/email/templates/broadcasts/stablecoin-payouts-announcement"; import ConnectPayoutReminder from "@dub/email/templates/connect-payout-reminder"; import ConnectPlatformsReminder from "@dub/email/templates/connect-platforms-reminder"; import PartnerBanned from "@dub/email/templates/partner-banned"; @@ -28,5 +29,5 @@ export const EMAIL_TEMPLATES_MAP = { IdentityVerificationAnnouncement, // PayoutAutoWithdrawals, // ProgramMarketplaceAnnouncement, - // StablecoinPayoutsAnnouncement, + StablecoinPayoutsAnnouncement, } as const; diff --git a/apps/web/ui/partners/program-marketplace/programs-promo-banner.tsx b/apps/web/ui/partners/program-marketplace/programs-promo-banner.tsx index 2965b68405..dad7425c6f 100644 --- a/apps/web/ui/partners/program-marketplace/programs-promo-banner.tsx +++ b/apps/web/ui/partners/program-marketplace/programs-promo-banner.tsx @@ -21,7 +21,7 @@ export function ProgramsPromoBanner() { if ( !partner.identityVerifiedAt && - partner.country !== "IN" && + !["IN", "GE"].includes(partner.country ?? "") && (payoutsCount[0]?.amount ?? 0) > 10000 ) { return ; diff --git a/apps/web/ui/partners/program-marketplace/programs-promo-card.tsx b/apps/web/ui/partners/program-marketplace/programs-promo-card.tsx index 7b3a2e4e4e..86949fa39c 100644 --- a/apps/web/ui/partners/program-marketplace/programs-promo-card.tsx +++ b/apps/web/ui/partners/program-marketplace/programs-promo-card.tsx @@ -21,7 +21,7 @@ export function ProgramsPromoCard() { if ( !partner.identityVerifiedAt && - partner.country !== "IN" && + !["IN", "GE"].includes(partner.country ?? "") && (payoutsCount[0]?.amount ?? 0) > 10000 ) { return ; From 02db651c5357d7dafb9c269d4be0a8887fff7201 Mon Sep 17 00:00:00 2001 From: Steven Tey Date: Fri, 10 Apr 2026 18:23:42 -0700 Subject: [PATCH 4/6] improve PartnerInfoCards + PartnerStats --- .../partners/[partnerId]/partner-stats.tsx | 43 ++++++++-- apps/web/ui/partners/partner-info-cards.tsx | 78 +++++++++---------- 2 files changed, 73 insertions(+), 48 deletions(-) diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/partner-stats.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/partner-stats.tsx index 518b76c54c..dc9a2564d6 100644 --- a/apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/partner-stats.tsx +++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/partner-stats.tsx @@ -1,6 +1,6 @@ -import { EnrolledPartnerProps } from "@/lib/types"; -import { ArrowUpRight2 } from "@dub/ui"; -import { cn, currencyFormatter, nFormatter } from "@dub/utils"; +import { EnrolledPartnerExtendedProps } from "@/lib/types"; +import { ArrowUpRight2, TimestampTooltip } from "@dub/ui"; +import { cn, currencyFormatter, nFormatter, timeAgo } from "@dub/utils"; import Link from "next/link"; import { useParams } from "next/navigation"; @@ -8,10 +8,29 @@ export function PartnerStats({ partner, error, }: { - partner?: EnrolledPartnerProps; + partner?: EnrolledPartnerExtendedProps; error?: boolean; }) { const { slug } = useParams() as { slug: string }; + + const lastLeadDate = partner?.lastLeadAt + ? new Date(partner.lastLeadAt) + : null; + const lastConversionDate = partner?.lastConversionAt + ? new Date(partner.lastConversionAt) + : null; + const approved = partner?.status === "approved"; + const leadsLastAt = + approved && lastLeadDate && !Number.isNaN(lastLeadDate.getTime()) + ? lastLeadDate + : undefined; + const conversionsLastAt = + approved && + lastConversionDate && + !Number.isNaN(lastConversionDate.getTime()) + ? lastConversionDate + : undefined; + return (
{ + ].map(({ label, value, href, lastAt }) => { const As = href ? Link : "div"; return ( )} + {lastAt ? ( + + + Last {timeAgo(lastAt, { withAgo: true })} + + + ) : null} ); })} diff --git a/apps/web/ui/partners/partner-info-cards.tsx b/apps/web/ui/partners/partner-info-cards.tsx index c136004678..5821800b2d 100644 --- a/apps/web/ui/partners/partner-info-cards.tsx +++ b/apps/web/ui/partners/partner-info-cards.tsx @@ -13,7 +13,6 @@ import { usePartnerGroupHistorySheet } from "@/ui/activity-logs/partner-group-hi import { Button, CalendarIcon, - ChartActivity2, CopyButton, Globe, Heart, @@ -21,14 +20,14 @@ import { TimestampTooltip, Trophy, } from "@dub/ui"; -import { VerifiedBadge } from "@dub/ui/icons"; +import { TriangleWarning, Users, VerifiedBadge } from "@dub/ui/icons"; import { COUNTRIES, fetcher, formatDate, formatDateTimeSmart, - timeAgo, } from "@dub/utils"; +import { CircleMinus } from "lucide-react"; import Link from "next/link"; import { Fragment, ReactNode, createElement } from "react"; import useSWR from "swr"; @@ -93,10 +92,7 @@ export function PartnerInfoCards({ const isNetwork = type === "network"; const showPayoutMethodField = - isEnrolled && - program?.payoutMode !== "external" && - partner?.payoutsEnabledAt != null && - partner?.defaultPayoutMethod != null; + isEnrolled && program?.payoutMode !== "external"; const { partnerGroupHistorySheet, @@ -143,56 +139,52 @@ export function PartnerInfoCards({ }, ]; - if (isEnrolled) { + if (isEnrolled && partner) { basicFields = basicFields.concat([ - ...(partner?.status === "approved" - ? [ - { - id: "lastLeadAt", - icon: , - text: partner.lastLeadAt - ? `Last lead event ${timeAgo(new Date(partner.lastLeadAt), { withAgo: true })}` - : null, - timestamp: partner.lastLeadAt ?? undefined, - }, - { - id: "lastConversionAt", - icon: , - text: partner.lastConversionAt - ? `Last conversion event ${timeAgo(new Date(partner.lastConversionAt), { withAgo: true })}` - : null, - timestamp: partner.lastConversionAt ?? undefined, - }, - ] - : []), { id: "createdAt", - icon: , - text: partner - ? `${partner.status === "approved" ? "Partner since" : "Applied"} ${formatDate(partner.createdAt)}` - : undefined, - timestamp: partner?.createdAt, + icon: , + text: `${partner.status === "approved" ? "Partner since" : "Applied"} ${formatDate(partner.createdAt)}`, + timestamp: partner.createdAt, }, - ...(showPayoutMethodField && partner + ...(showPayoutMethodField ? [ { id: "payoutMethod" as const, - icon: createElement( - getPayoutMethodIconConfig(partner.defaultPayoutMethod!).Icon, - { className: "size-3.5 shrink-0" }, + icon: partner.defaultPayoutMethod ? ( + createElement( + getPayoutMethodIconConfig(partner.defaultPayoutMethod).Icon, + { className: "size-3.5 shrink-0" }, + ) + ) : ( + ), - text: `${getPayoutMethodLabel(partner.defaultPayoutMethod!)} connected ${formatDateTimeSmart(partner.payoutsEnabledAt!)}`, - timestamp: partner.payoutsEnabledAt!, + text: + partner.defaultPayoutMethod && partner.payoutsEnabledAt + ? `${getPayoutMethodLabel(partner.defaultPayoutMethod)} connected ${formatDateTimeSmart(partner.payoutsEnabledAt)}` + : "No payout method connected", + ...(partner.payoutsEnabledAt + ? { timestamp: partner.payoutsEnabledAt } + : {}), }, ] : []), - ...(partner?.identityVerifiedAt + // TODO: once more partners verify their identity, we can show this by default + ...(partner.identityVerifiedAt ? [ { id: "identityVerifiedAt", - icon: , - text: `Identity verified ${formatDate(partner.identityVerifiedAt, { month: "short" })}`, - timestamp: partner.identityVerifiedAt, + icon: partner.identityVerifiedAt ? ( + + ) : ( + + ), + text: partner.identityVerifiedAt + ? `Identity verified ${formatDate(partner.identityVerifiedAt, { month: "short" })}` + : "Identity not verified", + ...(partner.identityVerifiedAt + ? { timestamp: partner.identityVerifiedAt } + : {}), }, ] : []), From 9040360441e8b37e4faa15018f85735ef23ea2be Mon Sep 17 00:00:00 2001 From: Steven Tey Date: Fri, 10 Apr 2026 18:24:31 -0700 Subject: [PATCH 5/6] Update partner-stats.tsx --- .../[slug]/(ee)/program/partners/[partnerId]/partner-stats.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/partner-stats.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/partner-stats.tsx index dc9a2564d6..0fccfdcb96 100644 --- a/apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/partner-stats.tsx +++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/[partnerId]/partner-stats.tsx @@ -147,7 +147,7 @@ export function PartnerStats({ side="top" delayDuration={250} > - + Last {timeAgo(lastAt, { withAgo: true })} From 7aa9f0db04611fd771d2ab06f59f636653789d2a Mon Sep 17 00:00:00 2001 From: Marcus Farrell Date: Fri, 10 Apr 2026 19:17:11 -0700 Subject: [PATCH 6/6] Bounty email updates (#3735) Co-authored-by: Pedro Ladeira <57876830+pepeladeira@users.noreply.github.com> --- .../cron/bounties/notify-partners/route.ts | 1 + .../src/templates/new-bounty-available.tsx | 64 +++++++++++++++++-- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/apps/web/app/(ee)/api/cron/bounties/notify-partners/route.ts b/apps/web/app/(ee)/api/cron/bounties/notify-partners/route.ts index 6224f2e9c2..4b376c4cda 100644 --- a/apps/web/app/(ee)/api/cron/bounties/notify-partners/route.ts +++ b/apps/web/app/(ee)/api/cron/bounties/notify-partners/route.ts @@ -154,6 +154,7 @@ export async function POST(req: Request) { name: bounty.name, type: bounty.type, endsAt: bounty.endsAt, + rewardAmount: bounty.rewardAmount, description: bounty.description, }, program: { diff --git a/packages/email/src/templates/new-bounty-available.tsx b/packages/email/src/templates/new-bounty-available.tsx index 3b40d5dd99..afa583ec44 100644 --- a/packages/email/src/templates/new-bounty-available.tsx +++ b/packages/email/src/templates/new-bounty-available.tsx @@ -1,6 +1,7 @@ -import { DUB_WORDMARK, formatDate } from "@dub/utils"; +import { currencyFormatter, DUB_WORDMARK, formatDate } from "@dub/utils"; import { Body, + Column, Container, Head, Heading, @@ -9,6 +10,7 @@ import { Link, Markdown, Preview, + Row, Section, Tailwind, Text, @@ -16,12 +18,20 @@ import { import { BountyThumbnailImage } from "../components/bounty-thumbnail"; import { Footer } from "../components/footer"; +const ICONS = { + calendar: "https://assets.dub.co/cms/icon-calendar-bounty.png", + gift: "https://assets.dub.co/cms/icon-gift-bounty.png", +} as const; + +type Icon = keyof typeof ICONS; + export default function NewBountyAvailable({ bounty = { id: "bty_xxx", name: "Promote Acme at your campus and earn $500", type: "performance", endsAt: new Date(), + rewardAmount: 10000, description: "How **does** it work?\n\nGet a group _together_ of at least 15 other people interested in trying out [Acme](https://dub.co). Then, during the event, take a photo of the group using Acme. When submitting, provide any links to the event or photos. Once confirmed, we'll create a one-time commission for you.", }, @@ -36,6 +46,7 @@ export default function NewBountyAvailable({ name: string; type: "performance" | "submission"; endsAt: Date | null; + rewardAmount: number | null; description: string | null; }; program: { @@ -44,6 +55,18 @@ export default function NewBountyAvailable({ }; email: string; }) { + const formattedRewardAmount = + bounty.rewardAmount != null + ? currencyFormatter(bounty.rewardAmount, { + trailingZeroDisplay: "stripIfInteger", + }) + : null; + + const iconSizeClassByIcon: Record = { + calendar: "h-4.5 w-4.5", + gift: "h-4.5 w-4.5", + }; + return ( @@ -68,10 +91,41 @@ export default function NewBountyAvailable({ {bounty.name} - {bounty.endsAt && ( - - Ends {formatDate(bounty.endsAt)} - + {(bounty.endsAt || formattedRewardAmount) && ( +
+ {bounty.endsAt && ( + + + + + + + Ends {formatDate(bounty.endsAt)} + + + + )} + {formattedRewardAmount && ( + + + + + + + Earn {formattedRewardAmount} + + + + )} +
)}