Skip to content

feat: add tool claim UI to the tool page - #6501

Open
rebelchris wants to merge 1 commit into
mainfrom
feat/tool-claim
Open

feat: add tool claim UI to the tool page#6501
rebelchris wants to merge 1 commit into
mainfrom
feat/tool-claim

Conversation

@rebelchris

@rebelchris rebelchris commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Shows a "Verified by {company}" badge in the /tools/[slug] hero chip row when claimedBy is set, using the same visual pattern as the official-source chip (ProfilePicture for the company image, not a raw img).
  • Adds a subtle "Work at {host}? Claim this page" affordance below the chip row for logged-in viewers where viewerCanClaim is true and the tool is unclaimed. Clicking runs the claimTool mutation; success toasts Page claimed for {company} and updates the badge in place (no reload); server errors surface via toast with the server's message.
  • Adds LogEvent.ClickClaimTool / LogEvent.ClaimTool (Origin.ToolPage, target_type Tool, target_id = tool slug).

Data fetching

  • claimedBy is fetched via a new, separate ToolClaimedBy query in getStaticProps with .catch(() => null), mirroring the existing officialSource deploy-window pattern — it is never added to DATASET_TOOL_QUERY.
  • viewerCanClaim is viewer-scoped, so it's fetched client-side only (enabled: !!user), tolerant via .catch(() => false) so a not-yet-deployed API can't break the page.

Pairs with the daily-api feat/tool-claim PR (adds claimedBy/viewerCanClaim on DatasetTool and the claimTool mutation). The API PR deploys first; this page tolerates the window where the fields don't exist yet (verified by building against production api.daily.dev, which currently lacks these fields).

Test plan

  • node ./scripts/typecheck-strict-changed.js passes
  • eslint --max-warnings=0 clean on all three changed files
  • NODE_ENV=test pnpm --filter shared test — only the pre-existing numberFormat.spec.ts locale failures
  • NODE_ENV=test pnpm --filter webapp test — only the pre-existing WorldGuideSheet.spec.tsx failure
  • NEXT_PUBLIC_API_URL=https://api.daily.dev pnpm --filter webapp build succeeds against the current production API (missing the new fields)
  • Screenshots (left to Chris)

🤖 Generated with Claude Code

Preview domain

https://feat-tool-claim.preview.app.daily.dev

Adds a "Verified by {company}" badge in the hero chip row when a tool
is claimed, and a subtle "Work at {host}? Claim this page" affordance
for eligible logged-in viewers, backed by the paired daily-api
feat/tool-claim mutation (claimTool) and DatasetTool fields
(claimedBy, viewerCanClaim).

claimedBy is fetched via a separate SSG query with .catch(() => null),
matching the officialSource deploy-window pattern. viewerCanClaim is
viewer-scoped so it's fetched client-side only, tolerant of the
not-yet-deployed API. Claim success/failure surface via toast, and the
badge updates in place without a reload.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 20, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
daily-webapp Ready Ready Preview Aug 20, 2026 12:24pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
storybook Ignored Ignored Aug 20, 2026 12:24pm

Request Review

@rebelchris rebelchris left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Reviewed alongside the paired daily-api PR. Nothing blocking on this side — the deploy-window handling (separate ToolClaimedBy query, client-only viewerCanClaim, tolerant .catch) is the right shape, { image, id: company.name } for ProfilePicture matches the existing company-avatar pattern (UserMetadata, CompanyTopList), and CI is green. Findings are inline.

One cross-repo note: I flagged on the API PR that viewerCanClaim ignores claimedByCompanyId, so it returns true for already-claimed tools. This page's only protection against that is claimedByState, which comes from getStaticProps behind revalidate: 300 — so for up to five minutes after someone claims a tool, an eligible viewer from another company still sees the button and gets an error toast. queryClient.setQueryData(viewerCanClaimKey, result.viewerCanClaim) stores true for the same reason; harmless while claimedByState gates the UI, but it becomes correct only once the API side is fixed.

Reviewed by AI.

onClick={handleClaimClick}
className="self-start"
>
{websiteHost ? `Work at ${websiteHost}? ` : ''}Claim this page

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Non-blocking: this is a single click, no confirmation, and the result is irreversible — the API has no unclaimTool mutation and no admin route, so a mis-click permanently binds this tool page to the clicker's company (I raised the missing revocation path on the API PR). Given the affordance sits inline in the hero and the label ends in a fairly casual "Claim this page", an accidental tap on mobile is plausible. usePrompt/showPrompt is already used for comparable irreversible actions (e.g. settings/organization/[orgId]/members.tsx); a short confirm naming the company would be cheap insurance here.

Reviewed by AI.

id: claimedByState.name,
}}
/>
Verified by {claimedByState.name}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Non-blocking (copy): the badge says "Verified by {company}" but its own tooltip says "Officially claimed by {company}", and the API field/mutation are named claimedBy/claimTool. There is no human review in the flow — it is a self-serve work-email domain match — so "Verified by" reads as a stronger, daily.dev-issued endorsement than what actually happened, on a page every visitor sees. Suggest aligning both strings (and the badge) on "Claimed by" unless the stronger wording is a deliberate product decision.

Reviewed by AI.

'tool-viewer-can-claim',
tool.id,
);
const { data: viewerCanClaim } = useQuery({

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Follow-up: the rollout-window shape is right, but please make sure it gets unwound once the API is deployed, otherwise it becomes permanent cost plus a permanent blind spot.

  1. viewerCanClaim can fold into the existing ToolVoteState query — same datasetTool(slug) selection, also client-side, also viewer-scoped — removing one extra GraphQL round trip per logged-in tool-page view.
  2. .catch(() => false) here and .catch(() => null) on getToolClaimedBy in getStaticProps swallow every error, not just the unknown-field error they exist for. After the API ships, a real regression (claim badge silently disappearing for all tools) fails completely silently.

A short comment or ticket referencing the API PR would be enough to make sure this gets cleaned up.

Reviewed by AI.

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.

1 participant