Skip to content

Commit fa70f18

Browse files
authored
Customize deepview page (#3946)
1 parent afc8b88 commit fa70f18

5 files changed

Lines changed: 102 additions & 56 deletions

File tree

apps/web/app/app.dub.co/(deeplink)/deeplink/[domain]/[[...key]]/action-buttons.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@
22

33
import { Link } from "@dub/prisma/client";
44
import { Button, useCopyToClipboard } from "@dub/ui";
5+
import { cn } from "@dub/utils";
56
import { useSearchParams } from "next/navigation";
67
import { getTranslations, Language } from "./translations";
78

89
export function DeepLinkActionButtons({
910
link,
1011
language,
12+
buttonClassnames,
1113
}: {
1214
link: Pick<Link, "shortLink">;
1315
language: Language;
16+
buttonClassnames?: string;
1417
}) {
1518
const t = getTranslations(language);
1619
const searchParams = useSearchParams();
@@ -28,7 +31,7 @@ export function DeepLinkActionButtons({
2831
return (
2932
<Button
3033
text={t.openInApp}
31-
className="h-12 w-full rounded-xl bg-neutral-900 text-white"
34+
className={cn("h-12 w-full font-medium text-white", buttonClassnames)}
3235
onClick={handleClick}
3336
/>
3437
);
Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,26 @@
11
"use client";
22

33
import { Link } from "@dub/prisma/client";
4-
import { useCopyToClipboard } from "@dub/ui";
54
import { getApexDomain, GOOGLE_FAVICON_URL } from "@dub/utils";
65

76
export function BrandLogoBadge({
87
link,
8+
appName,
99
}: {
1010
link: Pick<Link, "shortLink" | "url">;
11+
appName?: string;
1112
}) {
12-
const [_copied, copyToClipboard] = useCopyToClipboard();
13-
1413
return (
15-
<button
16-
onClick={async () => {
17-
await copyToClipboard(link.shortLink);
18-
window.location.href = `${link.shortLink}?skip_deeplink_preview=1`;
19-
}}
20-
className="inline-flex items-center gap-2 rounded-full bg-white px-3 py-1.5 shadow-lg shadow-black/10 ring-1 ring-neutral-200"
21-
>
22-
<img
23-
src={`${GOOGLE_FAVICON_URL}${getApexDomain(link.url)}`}
24-
className="size-8 shrink-0 overflow-visible rounded-full p-px"
25-
/>
14+
<div className="flex flex-col items-center justify-center gap-4">
15+
<div className="rounded-2xl border border-neutral-100 bg-white p-6 shadow-lg">
16+
<img
17+
src={`${GOOGLE_FAVICON_URL}${getApexDomain(link.url)}`}
18+
className="size-8 shrink-0 overflow-visible rounded-full p-px"
19+
/>
20+
</div>
2621
<div className="pr-1.5 text-lg font-semibold text-neutral-900">
27-
{getApexDomain(link.url)}
22+
{appName ?? getApexDomain(link.url)}
2823
</div>
29-
</button>
24+
</div>
3025
);
3126
}

apps/web/app/app.dub.co/(deeplink)/deeplink/[domain]/[[...key]]/page.tsx

Lines changed: 55 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,18 @@ export default async function DeepLinkPreviewPage(props: {
9595
redirect(`https://${domain}`);
9696
}
9797

98+
const {
99+
hidePoweredByBadge = false,
100+
appName,
101+
variant = "default",
102+
buttonClassnames,
103+
} = deepViewData ?? {};
104+
105+
const description = t.description.replace(
106+
"{appName}",
107+
appName ?? t.appNameFallback,
108+
);
109+
98110
return (
99111
<>
100112
{domain === "pliab.ly" && (
@@ -143,43 +155,56 @@ export default async function DeepLinkPreviewPage(props: {
143155
</div>
144156

145157
<div className="relative z-10 flex flex-1 flex-col px-8 py-8">
146-
<div className="flex justify-center">
147-
<Link
148-
href="https://dub.co/docs/concepts/deep-links/quickstart"
149-
target="_blank"
150-
className={cn(
151-
"flex items-center gap-1 whitespace-nowrap text-sm font-medium text-neutral-900",
152-
t["poweredByOrder"] === "inverted" ? "flex-row-reverse" : "",
153-
)}
154-
>
155-
{t.poweredBy} <Wordmark className="text-content-emphasis h-3.5" />
156-
</Link>
157-
</div>
158+
{!hidePoweredByBadge && (
159+
<div className="flex justify-center">
160+
<Link
161+
href="https://dub.co/docs/concepts/deep-links/quickstart"
162+
target="_blank"
163+
className={cn(
164+
"flex items-center gap-1 whitespace-nowrap text-sm font-medium text-neutral-900",
165+
t["poweredByOrder"] === "inverted" ? "flex-row-reverse" : "",
166+
)}
167+
>
168+
{t.poweredBy}{" "}
169+
<Wordmark className="text-content-emphasis h-3.5" />
170+
</Link>
171+
</div>
172+
)}
158173

159174
<div className="flex flex-1 flex-col justify-center gap-12">
160-
<div className="flex flex-col items-center gap-y-6">
161-
<BrandLogoBadge link={link} />
175+
<div className="flex flex-col items-center gap-y-4">
176+
<BrandLogoBadge link={link} appName={appName} />
162177

163-
<div className="flex h-40 w-full max-w-xs flex-col gap-6 rounded-xl border border-neutral-300 px-10 py-8">
164-
<p className="text-center text-sm font-normal leading-5 text-neutral-700">
165-
{t.description}
178+
{variant === "minimal" ? (
179+
<p className="text-pretty text-center leading-5 text-neutral-500">
180+
{description}
166181
</p>
167-
168-
<div className="flex items-center justify-center gap-3">
169-
<Copy className="text-content-default size-6" />
170-
<ArrowRight className="text-content-subtle size-3" />
171-
{platform === "android" ? (
172-
<AndroidLogo className="text-content-default size-6" />
173-
) : (
174-
<IOSAppStore className="text-content-default size-6" />
175-
)}
176-
<ArrowRight className="text-content-subtle size-3" />
177-
<MobilePhone className="text-content-default size-6" />
182+
) : (
183+
<div className="flex h-40 w-full max-w-xs flex-col gap-6 rounded-xl border border-neutral-300 px-10 py-8">
184+
<p className="text-pretty text-center text-sm leading-5 text-neutral-600">
185+
{description}
186+
</p>
187+
188+
<div className="flex items-center justify-center gap-3">
189+
<Copy className="text-content-default size-6" />
190+
<ArrowRight className="text-content-subtle size-3" />
191+
{platform === "android" ? (
192+
<AndroidLogo className="text-content-default size-6" />
193+
) : (
194+
<IOSAppStore className="text-content-default size-6" />
195+
)}
196+
<ArrowRight className="text-content-subtle size-3" />
197+
<MobilePhone className="text-content-default size-6" />
198+
</div>
178199
</div>
179-
</div>
200+
)}
180201
</div>
181202

182-
<DeepLinkActionButtons link={link} language={language} />
203+
<DeepLinkActionButtons
204+
link={link}
205+
language={language}
206+
buttonClassnames={buttonClassnames}
207+
/>
183208
</div>
184209
</div>
185210
</main>

apps/web/app/app.dub.co/(deeplink)/deeplink/[domain]/[[...key]]/translations.ts

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,51 @@
11
export const translations = {
22
en: {
33
poweredBy: "Powered by",
4-
description: "Clicking below will copy this page and open it in the app.",
4+
description: "Click below to open the page in {appName}.",
5+
appNameFallback: "the application",
56
openInApp: "Open in the app",
67
},
78
zh: {
89
poweredBy: "由",
9-
description: "点击下方将复制此页面并在应用中打开。",
10+
description: "点击下方在 {appName} 中打开页面。",
11+
appNameFallback: "应用",
1012
openInApp: "在应用中打开",
1113
},
1214
es: {
1315
poweredBy: "Desarrollado por",
14-
description:
15-
"Al hacer clic a continuación, se copiará esta página y se abrirá en la aplicación.",
16+
description: "Haz clic abajo para abrir la página en {appName}.",
17+
appNameFallback: "la aplicación",
1618
openInApp: "Abrir en la aplicación",
1719
},
1820
fr: {
1921
poweredBy: "Propulsé par",
20-
description:
21-
"En cliquant ci-dessous, cette page sera copiée et ouverte dans l'application.",
22+
description: "Cliquez ci-dessous pour ouvrir la page dans {appName}.",
23+
appNameFallback: "l'application",
2224
openInApp: "Ouvrir dans l'application",
2325
},
26+
it: {
27+
poweredBy: "Offerto da",
28+
description: "Clicca qui sotto per aprire la pagina in {appName}.",
29+
appNameFallback: "l'applicazione",
30+
openInApp: "Apri nell'app",
31+
},
32+
pt: {
33+
poweredBy: "Desenvolvido por",
34+
description: "Clique abaixo para abrir a página no {appName}.",
35+
appNameFallback: "o aplicativo",
36+
openInApp: "Abrir no aplicativo",
37+
},
38+
de: {
39+
poweredBy: "Bereitgestellt von",
40+
description: "Klicke unten, um die Seite in {appName} zu öffnen.",
41+
appNameFallback: "der App",
42+
openInApp: "In der App öffnen",
43+
},
2444
tr: {
2545
poweredBy: "tarafından desteklenmektedir",
2646
poweredByOrder: "inverted",
27-
description: "Linki uygulamada açmak için aşağıdaki butona tıklayın.",
47+
description: "Sayfayı {appName} içinde açmak için aşağıya tıklayın.",
48+
appNameFallback: "uygulama",
2849
openInApp: "Uygulamada aç",
2950
},
3051
} as const;

apps/web/lib/zod/schemas/deep-links.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import * as z from "zod/v4";
22

33
export const deepViewDataSchema = z
44
.object({
5-
ios: z.any(),
6-
android: z.any(),
5+
hidePoweredByBadge: z.boolean().optional(),
6+
appName: z.string().trim().min(1).optional(),
7+
variant: z.enum(["default", "minimal"]).default("default"),
8+
buttonClassnames: z.string().trim().min(1).optional(),
79
})
810
.nullish();

0 commit comments

Comments
 (0)