feat: add tool claim UI to the tool page - #6501
Conversation
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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
rebelchris
left a comment
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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} |
There was a problem hiding this comment.
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({ |
There was a problem hiding this comment.
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.
viewerCanClaimcan fold into the existingToolVoteStatequery — samedatasetTool(slug)selection, also client-side, also viewer-scoped — removing one extra GraphQL round trip per logged-in tool-page view..catch(() => false)here and.catch(() => null)ongetToolClaimedByingetStaticPropsswallow 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.
Summary
/tools/[slug]hero chip row whenclaimedByis set, using the same visual pattern as the official-source chip (ProfilePicturefor the company image, not a rawimg).viewerCanClaimis true and the tool is unclaimed. Clicking runs theclaimToolmutation; success toastsPage claimed for {company}and updates the badge in place (no reload); server errors surface via toast with the server's message.LogEvent.ClickClaimTool/LogEvent.ClaimTool(Origin.ToolPage, target_type Tool, target_id = tool slug).Data fetching
claimedByis fetched via a new, separateToolClaimedByquery ingetStaticPropswith.catch(() => null), mirroring the existingofficialSourcedeploy-window pattern — it is never added toDATASET_TOOL_QUERY.viewerCanClaimis 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-claimPR (addsclaimedBy/viewerCanClaimonDatasetTooland theclaimToolmutation). The API PR deploys first; this page tolerates the window where the fields don't exist yet (verified by building against productionapi.daily.dev, which currently lacks these fields).Test plan
node ./scripts/typecheck-strict-changed.jspasseseslint --max-warnings=0clean on all three changed filesNODE_ENV=test pnpm --filter shared test— only the pre-existingnumberFormat.spec.tslocale failuresNODE_ENV=test pnpm --filter webapp test— only the pre-existingWorldGuideSheet.spec.tsxfailureNEXT_PUBLIC_API_URL=https://api.daily.dev pnpm --filter webapp buildsucceeds against the current production API (missing the new fields)🤖 Generated with Claude Code
Preview domain
https://feat-tool-claim.preview.app.daily.dev