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 @@ -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: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -120,25 +126,6 @@ export function GroupAdditionalLinksForm({ group }: { group: GroupProps }) {
>
{enableAdditionalLinks && (
<>
<SettingsRow
heading="Link limit"
description="Set how many extra links a partner can create"
>
<NumberStepper
value={maxPartnerLinks}
onChange={(v) =>
setValue("maxPartnerLinks", v, {
shouldDirty: true,
shouldValidate: true,
})
}
min={0}
max={MAX_ADDITIONAL_PARTNER_LINKS}
step={1}
className="w-full"
/>
</SettingsRow>

<SettingsRow
heading="Link formats"
description="Specify the domains or URLs partners can create additional links on"
Expand Down Expand Up @@ -185,6 +172,25 @@ export function GroupAdditionalLinksForm({ group }: { group: GroupProps }) {
/>
</div>
</SettingsRow>

<SettingsRow
heading="Link limit"
description="Set how many extra links a partner can create"
>
<NumberStepper
value={maxPartnerLinks}
onChange={(v) =>
setValue("maxPartnerLinks", v, {
shouldDirty: true,
shouldValidate: true,
})
}
min={0}
max={MAX_ADDITIONAL_PARTNER_LINKS}
step={1}
className="w-full"
/>
</SettingsRow>
</>
)}

Expand Down Expand Up @@ -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
}
/>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,36 @@
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";

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 (
<div className="@container/stats">
<div
Expand Down Expand Up @@ -46,6 +65,7 @@ export function PartnerStats({
href: partner?.id
? `/${slug}/events?event=leads&partnerId=${partner.id}&interval=1y`
: undefined,
lastAt: leadsLastAt,
},
{
label: "Conversions",
Expand All @@ -59,6 +79,7 @@ export function PartnerStats({
href: partner?.id
? `/${slug}/events?event=sales&partnerId=${partner.id}&interval=1y&saleType=new`
: undefined,
lastAt: conversionsLastAt,
},
{
label: "Revenue",
Expand Down Expand Up @@ -101,7 +122,7 @@ export function PartnerStats({
? `/${slug}/events?event=sales&partnerId=${partner.id}&interval=1y`
: undefined,
},
].map(({ label, value, href }) => {
].map(({ label, value, href, lastAt }) => {
const As = href ? Link : "div";
return (
<As
Expand All @@ -119,6 +140,18 @@ export function PartnerStats({
{value}
</span>
)}
{lastAt ? (
<TimestampTooltip
timestamp={lastAt}
rows={["local", "utc", "unix"]}
side="top"
delayDuration={250}
>
<span className="text-content-muted absolute bottom-3 right-3 max-w-[calc(100%-1.5rem)] cursor-help text-right text-[0.6875rem] leading-tight underline decoration-neutral-300/70 decoration-dotted underline-offset-2 hover:decoration-neutral-400">
Last {timeAgo(lastAt, { withAgo: true })}
</span>
</TimestampTooltip>
) : null}
</As>
);
})}
Expand Down
11 changes: 8 additions & 3 deletions apps/web/lib/api/scrape-creators/get-social-content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -134,15 +136,18 @@ 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";
}

result = {
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,
Expand Down
4 changes: 4 additions & 0 deletions apps/web/lib/api/scrape-creators/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
3 changes: 2 additions & 1 deletion apps/web/lib/email/email-templates-map.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -28,5 +29,5 @@ export const EMAIL_TEMPLATES_MAP = {
IdentityVerificationAnnouncement,
// PayoutAutoWithdrawals,
// ProgramMarketplaceAnnouncement,
// StablecoinPayoutsAnnouncement,
StablecoinPayoutsAnnouncement,
} as const;
78 changes: 35 additions & 43 deletions apps/web/ui/partners/partner-info-cards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,21 @@ import { usePartnerGroupHistorySheet } from "@/ui/activity-logs/partner-group-hi
import {
Button,
CalendarIcon,
ChartActivity2,
CopyButton,
Globe,
Heart,
OfficeBuilding,
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";
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -143,56 +139,52 @@ export function PartnerInfoCards({
},
];

if (isEnrolled) {
if (isEnrolled && partner) {
basicFields = basicFields.concat([
...(partner?.status === "approved"
? [
{
id: "lastLeadAt",
icon: <ChartActivity2 className="size-3.5" />,
text: partner.lastLeadAt
? `Last lead event ${timeAgo(new Date(partner.lastLeadAt), { withAgo: true })}`
: null,
timestamp: partner.lastLeadAt ?? undefined,
},
{
id: "lastConversionAt",
icon: <ChartActivity2 className="size-3.5" />,
text: partner.lastConversionAt
? `Last conversion event ${timeAgo(new Date(partner.lastConversionAt), { withAgo: true })}`
: null,
timestamp: partner.lastConversionAt ?? undefined,
},
]
: []),
{
id: "createdAt",
icon: <CalendarIcon className="size-3.5" />,
text: partner
? `${partner.status === "approved" ? "Partner since" : "Applied"} ${formatDate(partner.createdAt)}`
: undefined,
timestamp: partner?.createdAt,
icon: <Users className="size-3.5" />,
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" },
)
) : (
<CircleMinus 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: <VerifiedBadge className="size-3.5 shrink-0" />,
text: `Identity verified ${formatDate(partner.identityVerifiedAt, { month: "short" })}`,
timestamp: partner.identityVerifiedAt,
icon: partner.identityVerifiedAt ? (
<VerifiedBadge className="size-3.5 shrink-0" />
) : (
<TriangleWarning className="size-3.5 shrink-0" />
),
text: partner.identityVerifiedAt
? `Identity verified ${formatDate(partner.identityVerifiedAt, { month: "short" })}`
: "Identity not verified",
...(partner.identityVerifiedAt
? { timestamp: partner.identityVerifiedAt }
: {}),
},
]
: []),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function ProgramsPromoBanner() {

if (
!partner.identityVerifiedAt &&
partner.country !== "IN" &&
!["IN", "GE"].includes(partner.country ?? "") &&
(payoutsCount[0]?.amount ?? 0) > 10000
) {
return <IdentityVerificationBanner />;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function ProgramsPromoCard() {

if (
!partner.identityVerifiedAt &&
partner.country !== "IN" &&
!["IN", "GE"].includes(partner.country ?? "") &&
(payoutsCount[0]?.amount ?? 0) > 10000
) {
return <IdentityVerificationCard />;
Expand Down
Loading
Loading