Skip to content

.dub.link changes#3871

Merged
steven-tey merged 4 commits into
mainfrom
dub-link-revisions
May 8, 2026
Merged

.dub.link changes#3871
steven-tey merged 4 commits into
mainfrom
dub-link-revisions

Conversation

@marcusljf
Copy link
Copy Markdown
Collaborator

@marcusljf marcusljf commented May 7, 2026

CleanShot 2026-05-07 at 11 04 35@2x CleanShot 2026-05-07 at 11 20 17@2x CleanShot 2026-05-07 at 11 24 35@2x

Summary by CodeRabbit

  • New Features

    • Redesigned domain selection cards with optional feature lists, banners, and improved spacing/layout.
    • Modal flow to claim .dub.link subdomains with slug availability checks and guided submission.
    • Automatic verification for claimed .dub.link subdomains during creation.
  • User Experience

    • Conditional onboarding copy and presentation based on product context.
    • Inline monospace domain chips for consistent domain/example styling.

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
dub Ready Ready Preview May 8, 2026 6:23am

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 7, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

The PR enhances domain and subdomain onboarding flows: DomainOption cards gain banners and feature lists; the Domain page becomes an async server component that conditionally renders partner vs non-partner copy; ProgramLinkConfiguration adds a trial-only .dub.link subdomain modal with debounced availability checks and claim submission; backend auto-verifies .dub.link claims.

Changes

Domain & Subdomain Onboarding Enhancement

Layer / File(s) Summary
DomainOption Component Contract
apps/web/app/app.dub.co/(onboarding)/onboarding/(steps)/domain/default-domain-selector.tsx
DomainOption props redefined to accept features, bannerVariant, optional bannerIcon/bannerText/description; icon and utility imports updated.
Domain Option Configurations
apps/web/app/app.dub.co/(onboarding)/onboarding/(steps)/domain/default-domain-selector.tsx
Product-specific option sets: partners use recommended/setup banner and feature rows; links use paid-plan banner and DomainChip examples.
DomainOption Rendering & Helper
apps/web/app/app.dub.co/(onboarding)/onboarding/(steps)/domain/default-domain-selector.tsx
Card layout refactor, banner variant styling, conditional feature list, resized CTA layout, and new DomainChip helper for inline monospace domain examples.
Domain Page Async Routing
apps/web/app/app.dub.co/(onboarding)/onboarding/(steps)/domain/page.tsx
Page converted to async server component; awaits searchParams, derives isPartners, and conditionally renders partner vs default description with appropriate links; adds first-child max-width selector.
Partner Program Subdomain Claim
apps/web/ui/partners/program-link-configuration.tsx
ProgramLinkConfiguration adds trial-conditional "Use .dub.link subdomain" option, modal renders DubLinkSubdomainForm with slug sanitization, debounced availability validation (abortable), claim POST to /api/domains, and SWR revalidation; idle-option icon now accepts string or React node.
Backend Auto-Verification
apps/web/app/api/domains/route.ts
POST /api/domains transaction now conditionally sets verified: true when the submitted slug ends with .dub.link.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Modal as Subdomain Modal
  participant Form as SubdomainForm
  participant ValidAPI as Validation API
  participant CreateAPI as Domains API
  participant Cache as SWR Cache
  participant Toast as Toast Notification
  
  User->>Modal: Click Use dub.link subdomain
  Modal->>Form: Render with initial state
  User->>Form: Type slug (sanitized, debounced)
  Form->>ValidAPI: GET /api/domains/:domain/validate
  ValidAPI-->>Form: Available / Taken
  Form-->>User: Display status
  User->>Form: Click Claim Domain
  Form->>CreateAPI: POST /api/domains?workspaceId=...
  CreateAPI-->>Form: Auto-verify .dub.link slugs / Respond
  Form->>Cache: Revalidate /api/domains
  Form->>Cache: Revalidate /api/links
  Form->>Toast: Show success
  Form->>Modal: Close via callback
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • dubinc/dub#3772: Modifies the onboarding domain selector and surrounding onboarding UI layout.
  • dubinc/dub#3780: Updates data models and API paths for .dub.link domain handling and verification.
  • dubinc/dub#3809: Touches the .dub.link domain flow with client UI and server domain creation/validation logic.

Suggested reviewers

  • pepeladeira
  • steven-tey

Poem

🐰 I hopped through cards and banners bright,
I checked each slug by lantern light,
.dub.link claimed with gentle cheer,
Toasts and modals drawing near,
Onboarding blooms — carrots in sight! 🥕

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title ".dub.link changes" is vague and generic, using non-descriptive language that does not convey meaningful information about the changeset despite the PR involving multiple feature enhancements. Consider a more descriptive title like "Add .dub.link subdomain onboarding flow for partner programs" to better summarize the main changes across domain selector UI, onboarding page, and program configuration.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dub-link-revisions

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
apps/web/app/app.dub.co/(onboarding)/onboarding/(steps)/domain/page.tsx (1)

41-41: 💤 Low value

Verify the arbitrary variant selector.

[&_>div:first-of-type]:max-w-[640px] includes a leading underscore (_) inside the selector before >. Tailwind converts _ to a space in arbitrary variants, so this compiles to & >div:first-of-type (descendant + child combinator), which still matches a direct child but is unusual. The conventional form would be [&>div:first-of-type]:max-w-[640px]. Worth confirming this still resolves to the intended child element under the configured Tailwind version.

♻️ Optional cleanup
-      className="max-w-none [&_>div:first-of-type]:max-w-[640px]"
+      className="max-w-none [&>div:first-of-type]:max-w-[640px]"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/app/app.dub.co/`(onboarding)/onboarding/(steps)/domain/page.tsx at
line 41, The arbitrary variant selector in the className string uses an
underscore (`[&_>div:first-of-type]:max-w-[640px]`) which Tailwind treats as a
space; update the selector to the conventional form
(`[&>div:first-of-type]:max-w-[640px]`) so it clearly targets the direct child
div as intended—locate the className assignment in the onboarding domain page
component (the className containing "max-w-none
[&_>div:first-of-type]:max-w-[640px]") and replace the underscored selector with
the standard one, then run your Tailwind build to verify the rule applies to the
first direct child div.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/ui/partners/program-link-configuration.tsx`:
- Around line 334-358: The availability UI can show results for a different slug
and lose to out-of-order fetches; inside DubLinkSubdomainForm update the
useEffect that validates debouncedDomain to create and use an AbortController
for the fetch, abort it in the effect cleanup to prevent stale responses from
overwriting state, and only set/setAvailable when the response is from the
current request; also change the JSX that renders the "is available" / checking
branches (the parts referencing domain, available, and isChecking) to gate those
branches on slug === debouncedSlug so the UI only displays availability for a
slug that was actually validated.
- Around line 360-369: The claimDomain handler calls fetch with workspace.id
which can be undefined; update claimDomain (and the submit button) to guard
against an unloaded workspace by asserting workspace.id before calling fetch
(return early or show validation) and ensure setIsSubmitting is not set true
unless workspace.id exists; also add disabled={!available || !workspace.id} to
the submit control so the form cannot be submitted until useWorkspace() has a
valid id.
- Around line 371-376: The error handling for the fetch response currently
assumes res.json() returns an object with error.message and only resets
setIsSubmitting(false) on that failure path; update the failure branch in the
submission handler (the block using res, setIsSubmitting, and toast.error in
program-link-configuration.tsx) to parse the JSON defensively, read the message
via optional chaining and type checks (e.g., if error is string or object use
appropriate message), and ensure setIsSubmitting(false) is executed in all exit
paths (move it into a finally block or call it before each return) so the UI is
always reset even with unexpected response shapes.

---

Nitpick comments:
In `@apps/web/app/app.dub.co/`(onboarding)/onboarding/(steps)/domain/page.tsx:
- Line 41: The arbitrary variant selector in the className string uses an
underscore (`[&_>div:first-of-type]:max-w-[640px]`) which Tailwind treats as a
space; update the selector to the conventional form
(`[&>div:first-of-type]:max-w-[640px]`) so it clearly targets the direct child
div as intended—locate the className assignment in the onboarding domain page
component (the className containing "max-w-none
[&_>div:first-of-type]:max-w-[640px]") and replace the underscored selector with
the standard one, then run your Tailwind build to verify the rule applies to the
first direct child div.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1a67afae-1058-43da-ac0b-841d3655566b

📥 Commits

Reviewing files that changed from the base of the PR and between 0f8c4c2 and 0e0c9e9.

📒 Files selected for processing (4)
  • apps/web/app/app.dub.co/(onboarding)/onboarding/(steps)/domain/default-domain-selector.tsx
  • apps/web/app/app.dub.co/(onboarding)/onboarding/(steps)/domain/page.tsx
  • apps/web/app/app.dub.co/(onboarding)/onboarding/(steps)/products/product-selector.tsx
  • apps/web/ui/partners/program-link-configuration.tsx

Comment thread apps/web/ui/partners/program-link-configuration.tsx
Comment thread apps/web/ui/partners/program-link-configuration.tsx Outdated
Comment thread apps/web/ui/partners/program-link-configuration.tsx Outdated
@steven-tey
Copy link
Copy Markdown
Collaborator

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 8, 2026

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/ui/partners/program-link-configuration.tsx`:
- Around line 380-383: The early-return branches that handle empty debouncedSlug
(where debouncedSlug.trim() is checked and setAvailable(null) is called) do not
reset the checking flag, leaving isChecking true; update both places (the
empty-slug branches around the debouncedSlug checks) to call
setIsChecking(false) before returning so the spinner/loading state is cleared
when the input is emptied or a pending request is aborted.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7e8adfd6-ef61-40eb-8035-0b0309b92e4f

📥 Commits

Reviewing files that changed from the base of the PR and between aa42b2f and e7bdc60.

📒 Files selected for processing (2)
  • apps/web/app/api/domains/route.ts
  • apps/web/ui/partners/program-link-configuration.tsx

Comment thread apps/web/ui/partners/program-link-configuration.tsx
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
apps/web/ui/partners/program-link-configuration.tsx (1)

379-383: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

isChecking is not reset when debouncedSlug is empty, leaving the spinner stuck.

When the user clears the input while a fetch is in-flight, the effect cleanup aborts the request. The finally block at line 413 correctly skips setIsChecking(false) for aborted requests. However, when the debounce settles on an empty string, this early-return path runs setAvailable(null) and returns without ever clearing isChecking, so the spinner persists indefinitely.

🛡️ Proposed fix
  useEffect(() => {
    if (!debouncedSlug.trim()) {
+     setIsChecking(false);
      setAvailable(null);
      return;
    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/ui/partners/program-link-configuration.tsx` around lines 379 - 383,
The effect that watches debouncedSlug returns early when debouncedSlug.trim() is
empty but doesn't reset the isChecking state, leaving the spinner active; update
the useEffect block that checks debouncedSlug (the one using
debouncedSlug.trim(), setAvailable, and setIsChecking) so that on the
early-return path you also call setIsChecking(false) (and setAvailable(null) as
already done) to ensure isChecking is cleared when the input becomes empty or
the request is aborted.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@apps/web/ui/partners/program-link-configuration.tsx`:
- Around line 379-383: The effect that watches debouncedSlug returns early when
debouncedSlug.trim() is empty but doesn't reset the isChecking state, leaving
the spinner active; update the useEffect block that checks debouncedSlug (the
one using debouncedSlug.trim(), setAvailable, and setIsChecking) so that on the
early-return path you also call setIsChecking(false) (and setAvailable(null) as
already done) to ensure isChecking is cleared when the input becomes empty or
the request is aborted.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1771b1ab-fe67-48dc-8df9-a019625cd146

📥 Commits

Reviewing files that changed from the base of the PR and between e14eb63 and e7bdc60.

📒 Files selected for processing (4)
  • apps/web/app/api/domains/route.ts
  • apps/web/app/app.dub.co/(onboarding)/onboarding/(steps)/domain/default-domain-selector.tsx
  • apps/web/app/app.dub.co/(onboarding)/onboarding/(steps)/domain/page.tsx
  • apps/web/ui/partners/program-link-configuration.tsx

@steven-tey steven-tey merged commit a5fa025 into main May 8, 2026
11 checks passed
@steven-tey steven-tey deleted the dub-link-revisions branch May 8, 2026 06:32
@coderabbitai coderabbitai Bot mentioned this pull request May 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants