Skip to content

Draw the three portfolio links on the profile page - #137

Open
toruiwasa wants to merge 1 commit into
mainfrom
feat/portfolio-links
Open

Draw the three portfolio links on the profile page#137
toruiwasa wants to merge 1 commit into
mainfrom
feat/portfolio-links

Conversation

@toruiwasa

Copy link
Copy Markdown
Collaborator

Closes #84.

Summary

The three portfolio links now render on /p/<handle>. The model, the draft, the validation and the editor form already carried them, so this is the last of the four scope items.

What it looks like

No links, so no section. /p/seed0003

links-0-none

One link, beside a booking button. /p/seed0004

links-1-website

Two links, with the first of the three missing.

links-2-github-linkedin

All three, in model order.

links-3-all

The seed has no approved profile for the last two combinations, so those rows were edited locally for the screenshot and put back. The branch does not touch supabase/seed.sql.

What I decided without asking

Verification

  • pnpm lint, pnpm test (276, four new) and pnpm build are green.
  • The four states above were opened against a local stack.
  • pnpm test:e2e passes 20 of 20 with no Supabase configured. With a local .env.local the an empty directory renders the invitation test fails instead, on main as well as here.

Checklist

  • Opened as a draft pull request
  • pnpm lint passed, where applicable
  • pnpm test:e2e passed, where applicable
  • Commit messages have no Co-Authored-By, Generated with, or tool-attribution trailers
  • Linked the closing issue

`website_url`, `github_url` and `linkedin_url` have been in the model, the draft, the validation and the editor form since the columns landed, and nothing rendered them. This draws them on `/p/<handle>`, which is the last of #84's four scope items — the other three were already in `practitioners.ts:310-313`, `profile-draft.ts:112-114` and `profile-validation.ts:94-96`.

## Where they go

A section of their own, after the focus tags and before the CTA row. The reading order becomes who somebody is, what Bluehex checked, what they work on, where else to find them, and then how to reach them.

Below the credentials rather than beside the name, because the badge is what the page is read for and a row of exits above it invites a visitor to leave before reaching the part that was checked. Outside the credentials block for the opposite reason: nothing here is attested, and proximity alone would claim otherwise. That is not only the ADR's line — `clear_profile_verification()` has exactly three triggers, on `name`, on a claim, and on `contact_email` while unclaimed, and no link is among them.

Separate from the CTA row because #84 calls these `a row of ordinary links rather than a call to action`, which is the distinction it was split from #85 along. So they are plain underlined links matching `See the certificate` in the same file, not the pill `Book a meeting` wears.

An empty field renders nothing, and a profile with no links renders no section at all rather than a rule with nothing under it.

## portfolioLinks

The list is built in `@/lib/practitioners` rather than inline, because nothing else can reach it: the page is a client component with no component test to run against it, and `e2e/routes.spec.ts` visits `/` and `/contact` and never `/p/[handle]`. As JSX the ordering and the drop-if-empty rule would be asserted by nobody. #131 states the same rule for the same file.

It takes a `Pick` rather than a whole `Profile`, following `profilePath`. `bookingUrl` is absent from that type on purpose — leaving it out is cheaper than remembering to filter it at the call site, and it keeps the two rows from collapsing into one later.

Named for the ticket's word rather than for the field docstring's, which calls all four of these columns "published links". Three of them are the portfolio and the fourth is a booking, so a helper called `publishedLinks` that returned three would have invited exactly the question the split exists to answer.

## Comments corrected

Three comments described the links as undrawn, two of them naming #84 and #85 as the reason. All three would have shipped as false with this change: the header of `profile-detail.tsx`, `directory.ts` above `PROFILE_COLUMNS`, and `toProfile` in `directory-mapping.ts`. That leaves `availability` as the one column in the `anon` grant that no public surface draws yet. The new wording says that rather than claiming it is drawn nowhere at all, because the editor does draw it, through `my_profile()` rather than through this list. `services` is undrawn too, and both stay flagged.

## Checks

`pnpm lint`, `pnpm test` (276, four of them new) and `pnpm build` are green. The four link patterns in `supabase/seed.sql` were opened at `/p/seed0001` through `/p/seed0004`, which covers two links, one link, none, and one alongside a booking button. No approved seed profile holds all three, so that combination was produced by editing a local row for the screenshot and putting it back, and it is pinned by a unit test as well.

`pnpm test:e2e` passes 20/20 with no Supabase configured, which is the state the empty-directory test documents. With a local `.env.local` that one test fails on both projects because the seeded roster is not empty; that is true on `main` as well.
@toruiwasa toruiwasa self-assigned this Aug 25, 2026
@toruiwasa
toruiwasa marked this pull request as ready for review August 25, 2026 11:23
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.

Carry the portfolio links into the app: website, GitHub and LinkedIn

1 participant