{"id":50759368,"url":"https://github.com/selimdev00/capella","last_synced_at":"2026-06-11T08:30:45.877Z","repository":{"id":359742273,"uuid":"1247335663","full_name":"selimdev00/capella","owner":"selimdev00","description":"Users dashboard on the dummyjson API - Next.js 16, RSC, URL state, CSS charts","archived":false,"fork":false,"pushed_at":"2026-05-23T07:52:53.000Z","size":1924,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-05-23T09:29:27.164Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/selimdev00.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-23T07:17:21.000Z","updated_at":"2026-05-23T07:52:58.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/selimdev00/capella","commit_stats":null,"previous_names":["selimdev00/capella"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/selimdev00/capella","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/selimdev00%2Fcapella","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/selimdev00%2Fcapella/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/selimdev00%2Fcapella/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/selimdev00%2Fcapella/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/selimdev00","download_url":"https://codeload.github.com/selimdev00/capella/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/selimdev00%2Fcapella/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34190582,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-11T02:00:06.485Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-11T08:30:45.249Z","updated_at":"2026-06-11T08:30:45.865Z","avatar_url":"https://github.com/selimdev00.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Capella - Users Dashboard\n\nA users dashboard built on the public [dummyjson](https://dummyjson.com/docs/users)\n`/users` API. Browse, search, filter, sort and paginate 208 users, with an\noverview that moves with your filters and a detail view per user. The whole\nview state lives in the URL, so any slice is shareable.\n\nLive: https://capella.selim.services\n\n![Dashboard, light](docs/screenshots/dashboard-light.png)\n![Dashboard, dark](docs/screenshots/dashboard-dark.png)\n\n## Stack\n\n- **Next.js 16** (App Router, React Server Components) + **TypeScript** (strict)\n- **Tailwind CSS v4** + **shadcn/ui** (Base UI)\n- **TanStack Table** for the data grid (sorting, column visibility)\n- **nuqs** for type-safe URL state\n- **zod** for runtime validation at the API boundary\n- **Vitest** + Testing Library + **Playwright** for tests\n- Deployed to **Cloudflare Workers** via **OpenNext**\n\n## Features\n\n- Server-driven search (every field), role and gender filters, multi-column sort.\n- Three-state column sort: initial -\u003e descending -\u003e ascending -\u003e initial.\n- Overview (stat strip + role/gender charts) that recomputes from the same\n  filtered set as the table.\n- Per-user detail page with address, company, masked bank details, and Posts /\n  Todos sub-resources streamed with Suspense.\n- Every state designed: loading skeletons, error boundary, empty results,\n  404 for unknown users, resilient sub-resource failures.\n- Light / dark theme with a View Transitions crossfade, full keyboard a11y,\n  responsive down to 320px, and CSS-only entrance animations.\n\n## Architecture notes (why it is built this way)\n\n**The URL is the single source of truth.** Server Components read `searchParams`\nand render the result; client controls only push to the URL. That makes every\nview shareable and bookmarkable, removes a client-side data layer, and means the\nback button just works. `nuqs` parsers are defined once and shared by the server\nloader and the client controls.\n\n**Fetch once, compose locally.** dummyjson exposes search, filter and sort as\n*separate single-axis endpoints* that cannot be combined in one request. The\ndataset is also small and bounded (208 users). So I fetch the full list once\nserver-side, cache it (`revalidate`), and compose search + filter + sort +\npagination together in one pure, unit-tested module (`lib/query.ts`). This gives\nfull composability and instant interactions. For a large or unbounded dataset I\nwould push all of this back to the API or a real backend and accept the\nsingle-axis limitation, rather than over-fetch.\n\n**Validated at the boundary.** Every API response is parsed with zod\n(`lib/types.ts`), so the rest of the app works against trusted, typed data\ninstead of `any`. Unknown roles fall back rather than failing the whole parse.\n\n**Charts are pure CSS/SSR.** The role bars and gender donut are plain CSS\n(flex widths + `conic-gradient`), animated with `@property`. No charting library,\nso they paint with the first server response, ship zero client JS, and stay\ncrisp. The count-up numbers are an animated CSS counter, so they start in sync\nwith the mount reveal with no hydration delay or flash.\n\n## Getting started\n\n```bash\nnpm install\nnpm run dev          # http://localhost:3000\n```\n\nOther scripts:\n\n```bash\nnpm run build        # production build\nnpm run lint         # eslint\nnpm run typecheck    # tsc --noEmit\nnpm test             # vitest (unit + component)\nnpm run test:e2e     # playwright\nnpm run preview      # build + run on Cloudflare Workers locally (OpenNext)\nnpm run deploy       # build + deploy to Cloudflare\n```\n\n## Tests\n\n- Unit: the query layer (search / filter / sort / paginate / stats) and the URL\n  param normaliser.\n- Component: zod schema parsing and the stat overview rendering.\n- E2E: search updates the URL and filters, sorting reflects in the URL, and\n  opening a user reaches the detail page.\n\n## Project shape\n\n```\napp/                  routes, layouts, loading/error/not-found\ncomponents/dashboard/  table, filters, charts, stat strip, pagination\ncomponents/users/      role badge, info card, posts/todos\nlib/                   api client + zod, query layer, URL params, formatting\ntests/                 unit / component / e2e\n```\n\n![User detail](docs/screenshots/detail.png)\n![Mobile](docs/screenshots/mobile.png)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fselimdev00%2Fcapella","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fselimdev00%2Fcapella","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fselimdev00%2Fcapella/lists"}