Skip to content

Commit 24cc938

Browse files
committed
program details improvements
1 parent 040b7c1 commit 24cc938

10 files changed

Lines changed: 344 additions & 426 deletions

apps/web/app/app.dub.co/marketplace/[[...slug]]/page.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { getNetworkProgram } from "@/lib/fetchers/get-network-program";
12
import { PROGRAM_CATEGORIES_MAP } from "@/lib/network/program-categories";
23
import { MarketplaceExternalRouter } from "@/ui/partners/program-marketplace/external/marketplace-external-router";
34
import {
@@ -37,8 +38,21 @@ export async function generateMetadata(props: {
3738
if (segments.length === 1 && segments[0] === "all") {
3839
title = "All Programs";
3940
description = "Browse all partner programs on Dub.";
40-
} else if (segments.length === 1) {
41-
title = "Program Details";
41+
} else if (
42+
segments.length === 1 &&
43+
segments[0] !== "all" &&
44+
segments[0] !== "popular"
45+
) {
46+
const program = await getNetworkProgram({ slug: segments[0] });
47+
48+
if (program) {
49+
title = program.name;
50+
description =
51+
program.description ||
52+
`Join the ${program.name} affiliate program on Dub's Partner Network.`;
53+
} else {
54+
title = "Program Details";
55+
}
4256
} else if (segments.length === 2 && segments[0] === "c") {
4357
const category = Object.values(Category).find(
4458
(value) => categoryToSlug(value) === segments[1],

apps/web/ui/partners/program-marketplace/external/marketplace-external-header.tsx

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,29 @@
1+
"use client";
2+
13
import { Button, Wordmark } from "@dub/ui";
24
import { APP_DOMAIN, PARTNERS_DOMAIN, cn } from "@dub/utils";
35
import Link from "next/link";
6+
import { usePathname } from "next/navigation";
7+
import { MARKETPLACE_RESERVED_SLUGS } from "../utils/category-slug";
8+
9+
function getPartnersLoginHref(pathname: string) {
10+
const segments = pathname.split("/").filter(Boolean);
11+
12+
if (
13+
segments[0] === "marketplace" &&
14+
segments.length === 2 &&
15+
!MARKETPLACE_RESERVED_SLUGS.has(segments[1])
16+
) {
17+
return `${PARTNERS_DOMAIN}/login?next=${encodeURIComponent(`/marketplace/${segments[1]}`)}`;
18+
}
19+
20+
return `${PARTNERS_DOMAIN}/login`;
21+
}
422

523
export function MarketplaceExternalHeader() {
24+
const pathname = usePathname();
25+
const loginHref = getPartnersLoginHref(pathname);
26+
627
return (
728
<header className="border-border-subtle border-b bg-white">
829
<div className="mx-auto flex h-16 w-full max-w-screen-xl items-center justify-between px-4 sm:px-6 lg:px-8">
@@ -19,7 +40,7 @@ export function MarketplaceExternalHeader() {
1940

2041
<div className="flex items-center gap-2">
2142
<Link
22-
href={`${PARTNERS_DOMAIN}/login`}
43+
href={loginHref}
2344
className="text-content-default hover:text-content-emphasis px-3 py-2 text-sm font-medium transition-colors"
2445
>
2546
Log in

apps/web/ui/partners/program-marketplace/external/marketplace-external-program-page.tsx

Lines changed: 25 additions & 223 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,14 @@
11
import { getNetworkProgram } from "@/lib/fetchers/get-network-program";
22
import { ApplicationAnalytics } from "@/ui/application-analytics";
3-
import { BLOCK_COMPONENTS } from "@/ui/partners/lander/blocks";
4-
import { LanderHero } from "@/ui/partners/lander/lander-hero";
5-
import { LanderRewards } from "@/ui/partners/lander/lander-rewards";
6-
import { ProgramEligibilityCard } from "@/ui/partners/program-eligibility-card";
73
import {
84
getMarketplaceHref,
9-
getMarketplaceProgramHref,
5+
getMarketplacePublicApplyHref,
106
} from "@/ui/partners/program-marketplace/get-marketplace-href";
11-
import { ProgramCategory } from "@/ui/partners/program-marketplace/program-category";
12-
import { ProgramRewardsDisplay } from "@/ui/partners/program-marketplace/program-rewards-display";
13-
import { Button, ChevronRight, Shop, Tooltip } from "@dub/ui";
14-
import { Globe } from "@dub/ui/icons";
15-
import {
16-
OG_AVATAR_URL,
17-
PARTNERS_DOMAIN,
18-
cn,
19-
getDomainWithoutWWW,
20-
} from "@dub/utils";
7+
import { MarketplaceProgramBackLink } from "@/ui/partners/program-marketplace/marketplace-program-back-link";
8+
import { MarketplaceProgramDetailBody } from "@/ui/partners/program-marketplace/marketplace-program-detail-body";
9+
import { MarketplaceProgramDetailsLayout } from "@/ui/partners/program-marketplace/marketplace-program-details-layout";
10+
import { MarketplaceProgramHero } from "@/ui/partners/program-marketplace/marketplace-program-hero";
11+
import { Button } from "@dub/ui";
2112
import Link from "next/link";
2213
import { redirect } from "next/navigation";
2314
import { MarketplaceExternalShell } from "./marketplace-external-shell";
@@ -35,220 +26,31 @@ export async function MarketplaceExternalProgramPage({
3526
redirect(getMarketplaceHref());
3627
}
3728

38-
const isDarkImage = program.marketplaceHeaderImage?.includes("dark");
39-
const applyHref = `${PARTNERS_DOMAIN}${getMarketplaceProgramHref(program.slug)}`;
40-
4129
return (
4230
<>
4331
<ApplicationAnalytics />
4432
<MarketplaceExternalShell variant="none">
45-
<div className="mx-auto max-w-screen-md">
46-
<div className="mb-6 flex items-center gap-1.5">
47-
<Link
48-
href={getMarketplaceHref()}
49-
className="bg-bg-subtle hover:bg-bg-emphasis flex size-8 shrink-0 items-center justify-center rounded-lg transition-[transform,background-color] duration-150 active:scale-95"
50-
>
51-
<Shop className="text-content-default size-4" />
52-
</Link>
53-
<ChevronRight className="text-content-subtle size-2.5 shrink-0 [&_*]:stroke-2" />
54-
<span className="text-content-emphasis text-lg font-semibold">
55-
{program.name}
56-
</span>
57-
</div>
58-
59-
<div className="mb-6 flex justify-end">
60-
<Link href={applyHref}>
61-
<Button text="Apply on Dub Partners" className="h-9 rounded-lg" />
62-
</Link>
63-
</div>
64-
65-
<div
66-
className={cn(
67-
"relative",
68-
program.featuredOnMarketplaceAt &&
69-
"border-border-subtle overflow-hidden rounded-xl border",
70-
)}
71-
>
72-
{program.featuredOnMarketplaceAt &&
73-
program.marketplaceHeaderImage && (
74-
<>
75-
<img
76-
src={program.marketplaceHeaderImage}
77-
alt={program.name}
78-
className="absolute inset-0 size-full object-cover"
79-
/>
80-
{!isDarkImage && (
81-
<div className="absolute inset-0 size-full bg-gradient-to-t from-white via-white/75 to-transparent" />
82-
)}
83-
</>
84-
)}
85-
<div className="relative px-4 py-8 sm:px-0">
86-
<img
87-
src={program.logo || `${OG_AVATAR_URL}${program.name}`}
88-
alt={program.name}
89-
className="size-16 rounded-full border border-white/20"
90-
/>
91-
92-
<div className="mt-6 flex flex-col">
93-
<span
94-
className={cn(
95-
"text-3xl font-semibold",
96-
isDarkImage && "text-content-inverted",
97-
)}
98-
>
99-
{program.name}
100-
</span>
101-
102-
<div
103-
className={cn(
104-
"mt-2 flex max-w-md items-center gap-1",
105-
isDarkImage && "text-content-inverted/90",
106-
)}
33+
<MarketplaceProgramDetailsLayout
34+
header={<MarketplaceProgramBackLink />}
35+
hero={
36+
<MarketplaceProgramHero
37+
program={program}
38+
applySlot={
39+
<Link
40+
href={getMarketplacePublicApplyHref(program.slug)}
41+
className="inline-block w-fit"
10742
>
108-
{program.description ||
109-
`${program.name} is a program in the Dub Partner Network. Join the network to start partnering with them.`}
110-
</div>
111-
</div>
112-
113-
<div className="mt-6 flex gap-8">
114-
{Boolean(program.rewards?.length || program.discount) && (
115-
<div>
116-
<span
117-
className={cn(
118-
"block text-xs font-medium",
119-
isDarkImage
120-
? "text-content-inverted"
121-
: "text-neutral-400",
122-
)}
123-
>
124-
Rewards
125-
</span>
126-
<ProgramRewardsDisplay
127-
rewards={program.rewards}
128-
discount={program.discount}
129-
isDarkImage={isDarkImage}
130-
className="mt-1"
131-
descriptionClassName="max-w-[240px]"
132-
/>
133-
</div>
134-
)}
135-
{Boolean(program.categories?.length) && (
136-
<div className="min-w-0">
137-
<span
138-
className={cn(
139-
"block text-xs font-medium",
140-
isDarkImage
141-
? "text-content-inverted"
142-
: "text-neutral-400",
143-
)}
144-
>
145-
Category
146-
</span>
147-
<div className="mt-1 flex items-center gap-1.5">
148-
{program.categories
149-
.slice(0, 1)
150-
?.map((category) => (
151-
<ProgramCategory
152-
key={category}
153-
category={category}
154-
className={cn(
155-
isDarkImage && "text-content-inverted",
156-
)}
157-
/>
158-
))}
159-
{program.categories.length > 1 && (
160-
<Tooltip
161-
content={
162-
<div className="flex flex-col gap-0.5 p-2">
163-
{program.categories.slice(1).map((category) => (
164-
<ProgramCategory
165-
key={category}
166-
category={category}
167-
className={cn(
168-
isDarkImage && "text-content-inverted",
169-
)}
170-
/>
171-
))}
172-
</div>
173-
}
174-
>
175-
<div
176-
className={cn(
177-
"-ml-1.5 flex size-6 items-center justify-center rounded-md text-xs font-medium",
178-
isDarkImage && "text-content-inverted/70",
179-
)}
180-
>
181-
+{program.categories.length - 1}
182-
</div>
183-
</Tooltip>
184-
)}
185-
</div>
186-
</div>
187-
)}
188-
{program.url && (
189-
<div className="min-w-0">
190-
<span className="block text-xs font-medium text-neutral-400">
191-
Website
192-
</span>
193-
<Link
194-
href={program.url}
195-
target="_blank"
196-
rel="noopener noreferrer"
197-
className={cn(
198-
"mt-1 flex max-w-[220px] items-center gap-1.5 text-sm font-medium",
199-
isDarkImage
200-
? "text-content-inverted/90 hover:text-content-inverted"
201-
: "text-content-default hover:text-content-emphasis",
202-
)}
203-
>
204-
<Globe className="size-4 shrink-0" />
205-
<span className="truncate">
206-
{getDomainWithoutWWW(program.url)}
207-
</span>
208-
</Link>
209-
</div>
210-
)}
211-
</div>
212-
</div>
213-
</div>
214-
215-
<LanderHero
216-
program={program}
217-
landerData={program.landerData || {}}
218-
showLabel={false}
219-
className="mt-8 sm:mt-8"
220-
heading="h2"
221-
titleClassName="text-2xl"
222-
/>
223-
224-
<LanderRewards
225-
className="mt-4"
226-
rewards={program.rewards || []}
227-
discount={program.discount || null}
228-
bounties={program.bounties}
229-
/>
230-
231-
{program.applicationRequirements?.length ? (
232-
<ProgramEligibilityCard
233-
requirements={program.applicationRequirements}
234-
/>
235-
) : null}
236-
237-
{program.landerData && (
238-
<div className="mt-16 grid grid-cols-1 gap-10">
239-
{program.landerData.blocks.map((block, idx) => {
240-
const Component = BLOCK_COMPONENTS[block.type];
241-
return Component ? (
242-
<Component
243-
key={idx}
244-
block={block}
245-
group={{ logo: program.logo }}
43+
<Button
44+
text="Apply"
45+
className="h-10 w-fit rounded-lg px-6 text-sm font-medium"
24646
/>
247-
) : null;
248-
})}
249-
</div>
250-
)}
251-
</div>
47+
</Link>
48+
}
49+
/>
50+
}
51+
>
52+
<MarketplaceProgramDetailBody program={program} />
53+
</MarketplaceProgramDetailsLayout>
25254
</MarketplaceExternalShell>
25355
</>
25456
);

apps/web/ui/partners/program-marketplace/get-marketplace-href.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,7 @@ export function getMarketplaceCanonicalUrl(pathname: string) {
119119
export function getMarketplacePartnersProgramUrl(programSlug: string) {
120120
return `https://partners.dub.co${getMarketplaceProgramHref(programSlug)}`;
121121
}
122+
123+
export function getMarketplacePublicApplyHref(programSlug: string) {
124+
return getMarketplacePartnersProgramUrl(programSlug);
125+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { getMarketplaceAllHref } from "@/ui/partners/program-marketplace/get-marketplace-href";
2+
import { ChevronLeft } from "@dub/ui";
3+
import Link from "next/link";
4+
5+
export function MarketplaceProgramBackLink() {
6+
return (
7+
<Link
8+
href={getMarketplaceAllHref()}
9+
className="group inline-flex items-center gap-1 text-xs font-medium text-neutral-800 transition-colors hover:text-neutral-500"
10+
>
11+
<ChevronLeft className="size-[10px] text-neutral-500 transition-transform group-hover:-translate-x-0.5" />
12+
All Programs
13+
</Link>
14+
);
15+
}

0 commit comments

Comments
 (0)