From aa00e65b259e6ee58cfac60a6016b779340c877d Mon Sep 17 00:00:00 2001 From: Tim Wilson Date: Fri, 31 Jan 2025 15:37:09 -0500 Subject: [PATCH 1/7] Update domain landing hero --- apps/web/app/[domain]/browser-graphic.tsx | 31 +++++ apps/web/app/[domain]/bubble-icon.tsx | 77 +++++++++++ apps/web/app/[domain]/layout.tsx | 1 - apps/web/app/[domain]/placeholder.tsx | 148 +++++++++++----------- 4 files changed, 183 insertions(+), 74 deletions(-) create mode 100644 apps/web/app/[domain]/browser-graphic.tsx create mode 100644 apps/web/app/[domain]/bubble-icon.tsx diff --git a/apps/web/app/[domain]/browser-graphic.tsx b/apps/web/app/[domain]/browser-graphic.tsx new file mode 100644 index 0000000000..37a155841a --- /dev/null +++ b/apps/web/app/[domain]/browser-graphic.tsx @@ -0,0 +1,31 @@ +"use client"; + +import { cn } from "@dub/utils"; + +export function BrowserGraphic({ domain }: { domain: string }) { + return ( +
+
+
+
+ {["bg-red-400", "bg-yellow-400", "bg-green-400"].map((c) => ( +
+ ))} +
+
+
+ {domain} +
+
+
+
+
+
+ ); +} diff --git a/apps/web/app/[domain]/bubble-icon.tsx b/apps/web/app/[domain]/bubble-icon.tsx new file mode 100644 index 0000000000..63f158d766 --- /dev/null +++ b/apps/web/app/[domain]/bubble-icon.tsx @@ -0,0 +1,77 @@ +"use client"; + +import { Icon } from "@dub/ui"; +import { CSSProperties, RefObject, useEffect, useRef, useState } from "react"; + +export function BubbleIcon({ icon: Icon }: { icon: Icon }) { + const ref = useRef(null); + const position = useRelativeMousePosition(ref); + + return ( +
+
+
+ +
+ +
+ +
+
+
+ ); +} + +function useRelativeMousePosition(ref: RefObject) { + const [position, setPosition] = useState<{ + x: number | null; + y: number | null; + }>({ x: null, y: null }); + + useEffect(() => { + const handler = (e: MouseEvent) => { + if (!ref.current) return; + + const boundingRect = ref.current.getBoundingClientRect(); + setPosition({ + x: (e.clientX - boundingRect.left) / boundingRect.width, + y: (e.clientY - boundingRect.top) / boundingRect.height, + }); + }; + + window.addEventListener("mousemove", handler); + return () => window.removeEventListener("mousemove", handler); + }, []); + + return position; +} diff --git a/apps/web/app/[domain]/layout.tsx b/apps/web/app/[domain]/layout.tsx index ea2a062c9e..c67a8ca8b7 100644 --- a/apps/web/app/[domain]/layout.tsx +++ b/apps/web/app/[domain]/layout.tsx @@ -12,7 +12,6 @@ export default function CustomDomainLayout({
); } diff --git a/apps/web/app/[domain]/placeholder.tsx b/apps/web/app/[domain]/placeholder.tsx index 69678df760..c33d9b59cf 100644 --- a/apps/web/app/[domain]/placeholder.tsx +++ b/apps/web/app/[domain]/placeholder.tsx @@ -1,90 +1,92 @@ "use client"; -import { InlineSnippet } from "@dub/ui"; -import { createHref, STAGGER_CHILD_VARIANTS } from "@dub/utils"; -import Spline from "@splinetool/react-spline"; -import { motion } from "framer-motion"; +import { buttonVariants, Grid, Logo } from "@dub/ui"; +import { cn, createHref } from "@dub/utils"; +import Link from "next/link"; import { useParams } from "next/navigation"; -import { useState } from "react"; -import { useDebounce } from "use-debounce"; +import { BrowserGraphic } from "./browser-graphic"; +import { BubbleIcon } from "./bubble-icon"; + +const HERO_GRADIENT = `radial-gradient(77% 116% at 37% 67%, #EEA5BA, rgba(238, 165, 186, 0) 50%), + radial-gradient(56% 84% at 34% 56%, #3A8BFD, rgba(58, 139, 253, 0) 50%), + radial-gradient(85% 127% at 100% 100%, #E4C795, rgba(228, 199, 149, 0) 50%), + radial-gradient(82% 122% at 3% 29%, #855AFC, rgba(133, 90, 252, 0) 50%), + radial-gradient(90% 136% at 52% 100%, #FD3A4E, rgba(253, 58, 78, 0) 50%), + radial-gradient(102% 143% at 92% 7%, #72FE7D, rgba(114, 254, 125, 0) 50%)`; export default function PlaceholderContent() { const { domain } = useParams() as { domain: string }; - const [loading, setLoading] = useState(true); - const onLoad = () => { - setLoading(false); - }; - // workaround to avoid the blinking effect when Spline loads - const [opacity] = useDebounce(loading ? 0 : 1, 200); - - const [showText] = useDebounce(loading ? false : true, 800); return ( - -
- +
+ -
- - - Welcome to {process.env.NEXT_PUBLIC_APP_NAME} - - +
+
+
+ +
+ +
+

+ Welcome to Dub +

+

+ This custom domain is powered by Dub.co – the link management + platform designed for modern marketing teams. +

+
+ +
- {domain} is a custom domain on{" "} - + Try Dub today + + - {process.env.NEXT_PUBLIC_APP_NAME} - {" "} - - the link management platform for modern marketing teams. - - - Create Your Free Branded Link - - - + Learn more + +
+
+
+
); } From 1691bd2a5602fae539c80545f78cbe88b38bb60f Mon Sep 17 00:00:00 2001 From: Tim Wilson Date: Fri, 31 Jan 2025 15:55:41 -0500 Subject: [PATCH 2/7] Performance improvement, tweaks, some content --- apps/web/app/[domain]/bubble-icon.tsx | 59 ++++++++++++--------------- apps/web/app/[domain]/placeholder.tsx | 20 +++++++-- 2 files changed, 42 insertions(+), 37 deletions(-) diff --git a/apps/web/app/[domain]/bubble-icon.tsx b/apps/web/app/[domain]/bubble-icon.tsx index 63f158d766..df5562a6f5 100644 --- a/apps/web/app/[domain]/bubble-icon.tsx +++ b/apps/web/app/[domain]/bubble-icon.tsx @@ -1,34 +1,49 @@ "use client"; import { Icon } from "@dub/ui"; -import { CSSProperties, RefObject, useEffect, useRef, useState } from "react"; +import { useEffect, useRef } from "react"; export function BubbleIcon({ icon: Icon }: { icon: Icon }) { const ref = useRef(null); - const position = useRelativeMousePosition(ref); + + // Pass relative mouse position to the element + useEffect(() => { + const handler = (e: MouseEvent) => { + if (!ref.current) return; + + const boundingRect = ref.current.getBoundingClientRect(); + ref.current.style.setProperty( + "--mx", + ((e.clientX - boundingRect.left) / boundingRect.width).toString(), + ); + ref.current.style.setProperty( + "--my", + ((e.clientY - boundingRect.top) / boundingRect.height).toString(), + ); + }; + + window.addEventListener("mousemove", handler); + return () => window.removeEventListener("mousemove", handler); + }, [ref]); return (
@@ -51,27 +66,3 @@ export function BubbleIcon({ icon: Icon }: { icon: Icon }) {
); } - -function useRelativeMousePosition(ref: RefObject) { - const [position, setPosition] = useState<{ - x: number | null; - y: number | null; - }>({ x: null, y: null }); - - useEffect(() => { - const handler = (e: MouseEvent) => { - if (!ref.current) return; - - const boundingRect = ref.current.getBoundingClientRect(); - setPosition({ - x: (e.clientX - boundingRect.left) / boundingRect.width, - y: (e.clientY - boundingRect.top) / boundingRect.height, - }); - }; - - window.addEventListener("mousemove", handler); - return () => window.removeEventListener("mousemove", handler); - }, []); - - return position; -} diff --git a/apps/web/app/[domain]/placeholder.tsx b/apps/web/app/[domain]/placeholder.tsx index c33d9b59cf..6ddc4cc7b7 100644 --- a/apps/web/app/[domain]/placeholder.tsx +++ b/apps/web/app/[domain]/placeholder.tsx @@ -22,10 +22,10 @@ export default function PlaceholderContent() {
-
+
-
+
+
+
+ What is Dub? +
+

+ Powerful features for modern marketing teams +

+

+ Dub is more than just a link shortener. We've built a suite of + powerful features that gives you marketing superpowers. +

+
+
+
); } From b1e3b9e8a2af71883a5bd17c53d3c9c1602e8e0a Mon Sep 17 00:00:00 2001 From: Tim Wilson Date: Fri, 31 Jan 2025 16:15:25 -0500 Subject: [PATCH 3/7] Add domain landing placeholder content --- apps/web/app/[domain]/layout.tsx | 5 +- apps/web/app/[domain]/placeholder.tsx | 29 ++- apps/web/ui/placeholders/cta.tsx | 137 +++++++++++++ .../feature-graphics/analytics.tsx | 20 ++ .../feature-graphics/collaboration.tsx | 51 +++++ .../placeholders/feature-graphics/domains.tsx | 67 +++++++ .../feature-graphics/personalization.tsx | 82 ++++++++ .../ui/placeholders/feature-graphics/qr.tsx | 105 ++++++++++ apps/web/ui/placeholders/features-section.tsx | 189 ++++++++++++++++++ apps/web/ui/placeholders/logos.tsx | 98 +++++++++ 10 files changed, 764 insertions(+), 19 deletions(-) create mode 100644 apps/web/ui/placeholders/cta.tsx create mode 100644 apps/web/ui/placeholders/feature-graphics/analytics.tsx create mode 100644 apps/web/ui/placeholders/feature-graphics/collaboration.tsx create mode 100644 apps/web/ui/placeholders/feature-graphics/domains.tsx create mode 100644 apps/web/ui/placeholders/feature-graphics/personalization.tsx create mode 100644 apps/web/ui/placeholders/feature-graphics/qr.tsx create mode 100644 apps/web/ui/placeholders/features-section.tsx create mode 100644 apps/web/ui/placeholders/logos.tsx diff --git a/apps/web/app/[domain]/layout.tsx b/apps/web/app/[domain]/layout.tsx index c67a8ca8b7..bacda575e9 100644 --- a/apps/web/app/[domain]/layout.tsx +++ b/apps/web/app/[domain]/layout.tsx @@ -1,4 +1,3 @@ -import { NewBackground } from "@/ui/shared/new-background"; import { Footer, Nav, NavMobile } from "@dub/ui"; export default function CustomDomainLayout({ @@ -9,9 +8,9 @@ export default function CustomDomainLayout({ return (
-
); } diff --git a/apps/web/app/[domain]/placeholder.tsx b/apps/web/app/[domain]/placeholder.tsx index 6ddc4cc7b7..eebe724964 100644 --- a/apps/web/app/[domain]/placeholder.tsx +++ b/apps/web/app/[domain]/placeholder.tsx @@ -1,5 +1,7 @@ "use client"; +import { CTA } from "@/ui/placeholders/cta"; +import { FeaturesSection } from "@/ui/placeholders/features-section"; import { buttonVariants, Grid, Logo } from "@dub/ui"; import { cn, createHref } from "@dub/utils"; import Link from "next/link"; @@ -14,6 +16,11 @@ const HERO_GRADIENT = `radial-gradient(77% 116% at 37% 67%, #EEA5BA, rgba(238, 1 radial-gradient(90% 136% at 52% 100%, #FD3A4E, rgba(253, 58, 78, 0) 50%), radial-gradient(102% 143% at 92% 7%, #72FE7D, rgba(114, 254, 125, 0) 50%)`; +const UTM_PARAMS = { + utm_source: "Custom Domain", + utm_medium: "Welcome Page", +}; + export default function PlaceholderContent() { const { domain } = useParams() as { domain: string }; @@ -57,7 +64,7 @@ export default function PlaceholderContent() {
@@ -72,8 +79,7 @@ export default function PlaceholderContent() {
-
-
- What is Dub? -
-

- Powerful features for modern marketing teams -

-

- Dub is more than just a link shortener. We've built a suite of - powerful features that gives you marketing superpowers. -

-
+ +
+
+
-
); } diff --git a/apps/web/ui/placeholders/cta.tsx b/apps/web/ui/placeholders/cta.tsx new file mode 100644 index 0000000000..3513b90e32 --- /dev/null +++ b/apps/web/ui/placeholders/cta.tsx @@ -0,0 +1,137 @@ +import { buttonVariants, Grid } from "@dub/ui"; +import { cn, createHref, UTMTags } from "@dub/utils"; +import { Star, StarHalf } from "lucide-react"; +import Link from "next/link"; +import { ReactNode } from "react"; +import Logos from "./logos"; + +const RATINGS = [ + { + name: "G2", + logo: "https://assets.dub.co/companies/g2.svg", + stars: 5, + href: "https://www.g2.com/products/dub/reviews", + }, + { + name: "Product Hunt", + logo: "https://assets.dub.co/companies/product-hunt-logo.svg", + stars: 5, + href: "https://www.producthunt.com/products/dub", + }, + { + name: "Trustpilot", + logo: "https://assets.dub.co/companies/trustpilot.svg", + stars: 4.5, + href: "https://www.trustpilot.com/review/dub.co", + }, +]; + +export function CTA({ + domain, + utmParams, + title = "Supercharge your marketing efforts", + subtitle = "See why Dub is the link management platform of choice for modern marketing teams.", + className, +}: { + domain: string; + utmParams?: Partial>; + title?: ReactNode; + subtitle?: ReactNode; + className?: string; +}) { + return ( +
+ +
+
+
+ + + +
+

+ {title} +

+

+ {subtitle} +

+
+ +
+ + Start for free + + + Get a demo + +
+ +
+ +
+
+ ); +} diff --git a/apps/web/ui/placeholders/feature-graphics/analytics.tsx b/apps/web/ui/placeholders/feature-graphics/analytics.tsx new file mode 100644 index 0000000000..b9e23e7f15 --- /dev/null +++ b/apps/web/ui/placeholders/feature-graphics/analytics.tsx @@ -0,0 +1,20 @@ +import Image from "next/image"; + +export function Analytics() { + return ( +
+
+ Analytics +
+
+ ); +} diff --git a/apps/web/ui/placeholders/feature-graphics/collaboration.tsx b/apps/web/ui/placeholders/feature-graphics/collaboration.tsx new file mode 100644 index 0000000000..04239902f1 --- /dev/null +++ b/apps/web/ui/placeholders/feature-graphics/collaboration.tsx @@ -0,0 +1,51 @@ +import { cn } from "@dub/utils"; + +export function Collaboration() { + return ( +
+
+
+ +
+ SAML SSO +
+ +
+
+ {Array.from({ length: 36 }).map((_, idx) => ( +
+ ))} +
+
+
+ ); +} + +function BadgeCap({ className }: { className?: string }) { + return ( + + + + ); +} diff --git a/apps/web/ui/placeholders/feature-graphics/domains.tsx b/apps/web/ui/placeholders/feature-graphics/domains.tsx new file mode 100644 index 0000000000..62173b95ab --- /dev/null +++ b/apps/web/ui/placeholders/feature-graphics/domains.tsx @@ -0,0 +1,67 @@ +import { CursorRays, FlagWavy, LinkLogo } from "@dub/ui"; +import { cn } from "@dub/utils"; +import { CSSProperties } from "react"; + +const DOMAINS = [ + { + domain: "acme.co", + clicks: "15.6K", + primary: true, + }, + { + domain: "acme.li", + clicks: "3.7K", + }, + { + domain: "acme.me", + clicks: "2.4K", + }, +]; + +export function Domains() { + return ( +
+
+ {DOMAINS.map(({ domain, clicks, primary }, idx) => ( +
+
+
+ +
+ + + {domain} + + +
+ +
+ {clicks} + clicks +
+
+ + {primary && ( +
+ +
+ Primary +
+
+ )} +
+
+ ))} +
+
+ ); +} diff --git a/apps/web/ui/placeholders/feature-graphics/personalization.tsx b/apps/web/ui/placeholders/feature-graphics/personalization.tsx new file mode 100644 index 0000000000..3cf05481ef --- /dev/null +++ b/apps/web/ui/placeholders/feature-graphics/personalization.tsx @@ -0,0 +1,82 @@ +"use client"; + +import { + Cards, + CircleHalfDottedClock, + Crosshairs3, + DiamondTurnRight, + InputPassword, + Switch, +} from "@dub/ui"; +import { useState } from "react"; + +const OPTIONS = [ + { + label: "Link Preview", + icon: Cards, + checked: true, + }, + { + label: "UTM", + icon: DiamondTurnRight, + checked: true, + }, + { + label: "Expiration", + icon: CircleHalfDottedClock, + checked: false, + }, + { + label: "Targeting", + icon: Crosshairs3, + checked: true, + }, + { + label: "Password", + icon: InputPassword, + checked: true, + }, +]; + +export function Personalization() { + return ( +
+
+

Link customization

+ +
+ {OPTIONS.map(({ label, icon: Icon, checked }) => ( +
+
+ + {label} +
+
+ +
+
+ ))} +
+
+
+ ); +} + +function DummySwitch({ checked }: { checked: boolean }) { + const [isChecked, setIsChecked] = useState(checked); + + return ( + + ); +} diff --git a/apps/web/ui/placeholders/feature-graphics/qr.tsx b/apps/web/ui/placeholders/feature-graphics/qr.tsx new file mode 100644 index 0000000000..68ef93c4b7 --- /dev/null +++ b/apps/web/ui/placeholders/feature-graphics/qr.tsx @@ -0,0 +1,105 @@ +"use client"; + +import { + ClientOnly, + Copy, + Download, + ShimmerDots, + Switch, + useMediaQuery, +} from "@dub/ui"; +import { DUB_QR_LOGO, cn } from "@dub/utils"; +import { HelpCircle } from "lucide-react"; +import { useState } from "react"; + +export function QR() { + const { isMobile } = useMediaQuery(); + + const [hideLogo, setHideLogo] = useState(false); + + return ( +
+
+
+

QR Code Design

+
+ + Q + +
+
+ +
+
+
+ + QR Code Preview + + +
+
+ + +
+
+
+ + {!isMobile && ( + + )} + +
+ +
+
+
+ + {/* Logo toggle */} +
+
+ Logo + +
+ { + setHideLogo(!checked); + }} + /> +
+
+
+ ); +} + +function QRCode({ hideLogo }: { hideLogo: boolean }) { + return ( + + + + + + + ); +} diff --git a/apps/web/ui/placeholders/features-section.tsx b/apps/web/ui/placeholders/features-section.tsx new file mode 100644 index 0000000000..1c446154e2 --- /dev/null +++ b/apps/web/ui/placeholders/features-section.tsx @@ -0,0 +1,189 @@ +import { ExpandingArrow } from "@dub/ui"; +import { cn, createHref, UTMTags } from "@dub/utils"; +import Link from "next/link"; +import { PropsWithChildren } from "react"; +import Markdown from "react-markdown"; +import { Analytics } from "./feature-graphics/analytics"; +import { Collaboration } from "./feature-graphics/collaboration"; +import { Domains } from "./feature-graphics/domains"; +import { Personalization } from "./feature-graphics/personalization"; +import { QR } from "./feature-graphics/qr"; + +export function FeaturesSection({ + domain, + utmParams, +}: { + domain: string; + utmParams: Partial>; +}) { + return ( +
+
+
+ What is Dub? +
+

+ Powerful features for modern marketing teams +

+

+ Dub is more than just a link shortener. We've built a suite of + powerful features that gives you marketing superpowers. +

+
+
+
+ + + + + + +
+ + + +
+ +
+
+ + View live demo + +
+
+
+ +
+ + + + + + +
+
+
+ ); +} + +function FeatureCard({ + title, + description, + linkText, + href, + children, + className, + graphicClassName, +}: PropsWithChildren<{ + title: string; + description: string; + linkText: string; + href: string; + className?: string; + graphicClassName?: string; +}>) { + return ( +
+
+
+ {children} +
+
+

{title}

+ { + if (!href) return null; + return ( + + {children} + + ); + }, + }} + > + {description} + + + {linkText} + +
+
+ ); +} diff --git a/apps/web/ui/placeholders/logos.tsx b/apps/web/ui/placeholders/logos.tsx new file mode 100644 index 0000000000..4a4c4d29c9 --- /dev/null +++ b/apps/web/ui/placeholders/logos.tsx @@ -0,0 +1,98 @@ +import { ExpandingArrow } from "@dub/ui"; +import { cn, createHref, UTMTags } from "@dub/utils"; +import Link from "next/link"; + +const logos = [ + "cal", + "framer", + "twilio", + "hubermanlab", + "vercel", + "perplexity", + "raycast", + "clerk", + "whop", + "viator", + "sketch", + "supabase", + "hashnode", +]; + +export default function Logos({ + domain, + utmParams, + variant = "default", + copy = "Giving marketing superpowers to world-class companies", + className, +}: { + domain: string; + utmParams?: Partial>; + variant?: "default" | "inline"; + copy?: string | null; + className?: string; +}) { + return ( + + {copy !== null && ( +

+ {copy} +

+ )} +
+ {[...Array(2)].map((_, idx) => ( +
+ {logos.map((logo) => ( + {logo.toUpperCase()} + ))} +
+ ))} +
+
+ + See more of our fantastic customers{" "} + + +
+ + ); +} From a499fbf2b60368ec38b4f3c1fa209d4d3051470b Mon Sep 17 00:00:00 2001 From: Tim Wilson Date: Fri, 31 Jan 2025 16:44:03 -0500 Subject: [PATCH 4/7] Update expired & banned link pages --- apps/web/app/[domain]/placeholder.tsx | 54 +++-------- apps/web/app/banned/page.tsx | 91 ++++++++++++++----- apps/web/app/expired/[domain]/page.tsx | 91 ++++++++++++++----- .../placeholders}/bubble-icon.tsx | 9 +- apps/web/ui/placeholders/button-link.tsx | 23 +++++ apps/web/ui/placeholders/cta.tsx | 23 ++--- apps/web/ui/placeholders/hero.tsx | 28 ++++++ packages/ui/src/icons/nucleo/index.ts | 1 + packages/ui/src/icons/nucleo/shield-slash.tsx | 43 +++++++++ 9 files changed, 259 insertions(+), 104 deletions(-) rename apps/web/{app/[domain] => ui/placeholders}/bubble-icon.tsx (87%) create mode 100644 apps/web/ui/placeholders/button-link.tsx create mode 100644 apps/web/ui/placeholders/hero.tsx create mode 100644 packages/ui/src/icons/nucleo/shield-slash.tsx diff --git a/apps/web/app/[domain]/placeholder.tsx b/apps/web/app/[domain]/placeholder.tsx index eebe724964..7e23c70ef9 100644 --- a/apps/web/app/[domain]/placeholder.tsx +++ b/apps/web/app/[domain]/placeholder.tsx @@ -1,20 +1,14 @@ "use client"; +import { ButtonLink } from "@/ui/placeholders/button-link"; import { CTA } from "@/ui/placeholders/cta"; import { FeaturesSection } from "@/ui/placeholders/features-section"; -import { buttonVariants, Grid, Logo } from "@dub/ui"; +import { Hero } from "@/ui/placeholders/hero"; +import { Logo } from "@dub/ui"; import { cn, createHref } from "@dub/utils"; -import Link from "next/link"; import { useParams } from "next/navigation"; +import { BubbleIcon } from "../../ui/placeholders/bubble-icon"; import { BrowserGraphic } from "./browser-graphic"; -import { BubbleIcon } from "./bubble-icon"; - -const HERO_GRADIENT = `radial-gradient(77% 116% at 37% 67%, #EEA5BA, rgba(238, 165, 186, 0) 50%), - radial-gradient(56% 84% at 34% 56%, #3A8BFD, rgba(58, 139, 253, 0) 50%), - radial-gradient(85% 127% at 100% 100%, #E4C795, rgba(228, 199, 149, 0) 50%), - radial-gradient(82% 122% at 3% 29%, #855AFC, rgba(133, 90, 252, 0) 50%), - radial-gradient(90% 136% at 52% 100%, #FD3A4E, rgba(253, 58, 78, 0) 50%), - radial-gradient(102% 143% at 92% 7%, #72FE7D, rgba(114, 254, 125, 0) 50%)`; const UTM_PARAMS = { utm_source: "Custom Domain", @@ -26,20 +20,11 @@ export default function PlaceholderContent() { return (
-
- -
-
-
+
- + + +
@@ -64,34 +49,25 @@ export default function PlaceholderContent() {
- + Try Dub today - - + Learn more - +
-
+
diff --git a/apps/web/app/banned/page.tsx b/apps/web/app/banned/page.tsx index f0fa97f167..6e464fc4df 100644 --- a/apps/web/app/banned/page.tsx +++ b/apps/web/app/banned/page.tsx @@ -1,6 +1,10 @@ -import { Background, Footer, Nav, NavMobile } from "@dub/ui"; -import { constructMetadata } from "@dub/utils"; -import { ShieldBan } from "lucide-react"; +import { BubbleIcon } from "@/ui/placeholders/bubble-icon"; +import { ButtonLink } from "@/ui/placeholders/button-link"; +import { CTA } from "@/ui/placeholders/cta"; +import { FeaturesSection } from "@/ui/placeholders/features-section"; +import { Hero } from "@/ui/placeholders/hero"; +import { Footer, Nav, NavMobile, ShieldSlash } from "@dub/ui"; +import { cn, constructMetadata, createHref } from "@dub/utils"; export const runtime = "edge"; @@ -10,28 +14,73 @@ export const metadata = constructMetadata({ noIndex: true, }); -export default async function BannedPage() { +const UTM_PARAMS = { + utm_source: "Expired Link", + utm_medium: "Expired Link Page", +}; + +export default async function BannedPage({ + params, +}: { + params: { domain: string }; +}) { return (
- -
); } diff --git a/apps/web/app/expired/[domain]/page.tsx b/apps/web/app/expired/[domain]/page.tsx index 5bf780d31e..4f9db0282d 100644 --- a/apps/web/app/expired/[domain]/page.tsx +++ b/apps/web/app/expired/[domain]/page.tsx @@ -1,7 +1,11 @@ import { getDomainViaEdge } from "@/lib/planetscale/get-domain-via-edge"; -import { Background, Footer, Nav, NavMobile } from "@dub/ui"; -import { CircleHalfDottedClock } from "@dub/ui/icons"; -import { constructMetadata } from "@dub/utils"; +import { BubbleIcon } from "@/ui/placeholders/bubble-icon"; +import { ButtonLink } from "@/ui/placeholders/button-link"; +import { CTA } from "@/ui/placeholders/cta"; +import { FeaturesSection } from "@/ui/placeholders/features-section"; +import { Hero } from "@/ui/placeholders/hero"; +import { CircleHalfDottedClock, Footer, Nav, NavMobile } from "@dub/ui"; +import { cn, constructMetadata, createHref } from "@dub/utils"; import { redirect } from "next/navigation"; export const runtime = "edge"; @@ -13,39 +17,80 @@ export const metadata = constructMetadata({ noIndex: true, }); +const UTM_PARAMS = { + utm_source: "Expired Link", + utm_medium: "Expired Link Page", +}; + export default async function ExpiredLinkPage({ params, }: { params: { domain: string }; }) { - const domain = await getDomainViaEdge(params.domain); + const domainEdge = await getDomainViaEdge(params.domain); - if (domain?.expiredUrl) { - redirect(domain.expiredUrl); + if (domainEdge?.expiredUrl) { + redirect(domainEdge.expiredUrl); } return (
-
); } diff --git a/apps/web/app/[domain]/bubble-icon.tsx b/apps/web/ui/placeholders/bubble-icon.tsx similarity index 87% rename from apps/web/app/[domain]/bubble-icon.tsx rename to apps/web/ui/placeholders/bubble-icon.tsx index df5562a6f5..a946e24ee7 100644 --- a/apps/web/app/[domain]/bubble-icon.tsx +++ b/apps/web/ui/placeholders/bubble-icon.tsx @@ -1,9 +1,8 @@ "use client"; -import { Icon } from "@dub/ui"; -import { useEffect, useRef } from "react"; +import { PropsWithChildren, useEffect, useRef } from "react"; -export function BubbleIcon({ icon: Icon }: { icon: Icon }) { +export function BubbleIcon({ children }: PropsWithChildren) { const ref = useRef(null); // Pass relative mouse position to the element @@ -46,8 +45,8 @@ export function BubbleIcon({ icon: Icon }: { icon: Icon }) { transform: `rotateY(clamp(-20deg, calc(var(--mx, 0.5) * 4deg), 20deg)) rotateX(clamp(-20deg, calc(var(--my, 0.5) * -4deg), 20deg))`, }} > -
- +
+ {children}
diff --git a/apps/web/ui/placeholders/button-link.tsx b/apps/web/ui/placeholders/button-link.tsx new file mode 100644 index 0000000000..e19d2a78db --- /dev/null +++ b/apps/web/ui/placeholders/button-link.tsx @@ -0,0 +1,23 @@ +"use client"; + +import { ButtonProps, buttonVariants } from "@dub/ui"; +import { cn } from "@dub/utils"; +import Link from "next/link"; +import { ComponentProps } from "react"; + +export function ButtonLink({ + variant, + className, + ...rest +}: Pick & ComponentProps) { + return ( + + ); +} diff --git a/apps/web/ui/placeholders/cta.tsx b/apps/web/ui/placeholders/cta.tsx index 3513b90e32..d5a0389bce 100644 --- a/apps/web/ui/placeholders/cta.tsx +++ b/apps/web/ui/placeholders/cta.tsx @@ -1,8 +1,8 @@ -import { buttonVariants, Grid } from "@dub/ui"; +import { Grid } from "@dub/ui"; import { cn, createHref, UTMTags } from "@dub/utils"; import { Star, StarHalf } from "lucide-react"; -import Link from "next/link"; import { ReactNode } from "react"; +import { ButtonLink } from "./button-link"; import Logos from "./logos"; const RATINGS = [ @@ -100,29 +100,20 @@ export function CTA({
- + Start for free - - + Get a demo - +
diff --git a/apps/web/ui/placeholders/hero.tsx b/apps/web/ui/placeholders/hero.tsx new file mode 100644 index 0000000000..fea63d278c --- /dev/null +++ b/apps/web/ui/placeholders/hero.tsx @@ -0,0 +1,28 @@ +import { Grid } from "@dub/ui"; +import { PropsWithChildren } from "react"; + +const HERO_GRADIENT = `radial-gradient(77% 116% at 37% 67%, #EEA5BA, rgba(238, 165, 186, 0) 50%), + radial-gradient(56% 84% at 34% 56%, #3A8BFD, rgba(58, 139, 253, 0) 50%), + radial-gradient(85% 127% at 100% 100%, #E4C795, rgba(228, 199, 149, 0) 50%), + radial-gradient(82% 122% at 3% 29%, #855AFC, rgba(133, 90, 252, 0) 50%), + radial-gradient(90% 136% at 52% 100%, #FD3A4E, rgba(253, 58, 78, 0) 50%), + radial-gradient(102% 143% at 92% 7%, #72FE7D, rgba(114, 254, 125, 0) 50%)`; + +export function Hero({ children }: PropsWithChildren) { + return ( +
+ +
+
+
+ {children} +
+ ); +} diff --git a/packages/ui/src/icons/nucleo/index.ts b/packages/ui/src/icons/nucleo/index.ts index b6c29e6941..67a08aa061 100644 --- a/packages/ui/src/icons/nucleo/index.ts +++ b/packages/ui/src/icons/nucleo/index.ts @@ -142,6 +142,7 @@ export * from "./scribble"; export * from "./shield-alert"; export * from "./shield-check"; export * from "./shield-keyhole"; +export * from "./shield-slash"; export * from "./shuffle"; export * from "./sliders"; export * from "./sparkle3"; diff --git a/packages/ui/src/icons/nucleo/shield-slash.tsx b/packages/ui/src/icons/nucleo/shield-slash.tsx new file mode 100644 index 0000000000..96cbec5733 --- /dev/null +++ b/packages/ui/src/icons/nucleo/shield-slash.tsx @@ -0,0 +1,43 @@ +import { SVGProps } from "react"; + +export function ShieldSlash(props: SVGProps) { + return ( + + + + + + + + ); +} From 8364a79a0e081115a93d588c6c68df1833710481 Mon Sep 17 00:00:00 2001 From: Tim Wilson Date: Fri, 31 Jan 2025 17:04:55 -0500 Subject: [PATCH 5/7] Update link password page --- apps/web/app/password/[linkId]/form.tsx | 10 ++-- apps/web/app/password/[linkId]/page.tsx | 68 +++++++++++++++---------- apps/web/ui/shared/new-background.tsx | 2 +- 3 files changed, 48 insertions(+), 32 deletions(-) diff --git a/apps/web/app/password/[linkId]/form.tsx b/apps/web/app/password/[linkId]/form.tsx index 7e2763d829..b937fc77bc 100644 --- a/apps/web/app/password/[linkId]/form.tsx +++ b/apps/web/app/password/[linkId]/form.tsx @@ -22,11 +22,11 @@ export default function PasswordForm() {
-