Skip to content

feat: add Design-System ThemeProvider with accessible light/dark mode (Closes #30) - #38

Open
waterWang wants to merge 92 commits into
workman-labs:mainfrom
waterWang:feat/30-theme-provider
Open

feat: add Design-System ThemeProvider with accessible light/dark mode (Closes #30)#38
waterWang wants to merge 92 commits into
workman-labs:mainfrom
waterWang:feat/30-theme-provider

Conversation

@waterWang

Copy link
Copy Markdown

Summary

Implements the Design-System Theme Provider (navy/gold/terracotta) with accessible light and dark modes persisted per user, as described in issue #30.

Changes

  • New ThemeProvider (src/components/ThemeProvider.tsx) — React Context provider that:

    • Exposes theme (light/dark), toggleTheme, and setTheme via the useTheme() hook
    • Reads the user's saved preference from localStorage on mount, falling back to prefers-color-scheme
    • Stamps data-theme on <html> and sets color-scheme for native UI controls
    • Listens to system preference changes (when no explicit choice is stored)
    • Works alongside the existing inline script in layout.tsx (no FOUC — the inline script runs before any React hydration)
  • Refactored ThemeToggle — now uses useTheme() from the provider instead of managing its own state and directly manipulating localStorage/DOM

  • Updated layout.tsx — wraps the app content with <ThemeProvider>

Design decisions

  • Follows the same Context + custom hook pattern as the existing NotificationProvider / useNotifications
  • The provider uses the existing CSS custom properties in globals.css (navy/gold/terracotta identity system) — no new CSS variables needed
  • The inline themeScript in layout.tsx is preserved for the flash-of-wrong-theme prevention; the provider reads the same localStorage key so it stays in sync

Verification

  • npm run typecheck — passes
  • npm run lint — 0 errors, 0 new warnings
  • npm run build — production build succeeds
  • npm test — 35/35 tests pass

meshackyaro and others added 29 commits July 11, 2026 16:11
The booking screen's pay action now calls the real client/bookAppointment
endpoint instead of stubbing the confirmation.

- On pay: read clientId from localStorage ("userId"); if absent, redirect
  to /login. Otherwise POST { scheduleTime, category, clientId } via the
  existing bookingApi, combining the picked date + slot into scheduleTime
  and sending the API category enum (e.g. ELECTRICAL).
- Loading + inline error states on the button; success (response.status)
  shows the escrow-locked confirmation.
- Pass apiCategory from the route; drop an unused useMemo import.

Scope note: this books the appointment only. The Soroban escrow contract
is still not integrated (per guildworkman-api "not yet wired up"), so funds
aren't actually locked on-chain yet — the confirmation copy reflects intent.
Point NEXT_PUBLIC_API_BASE_URL at a running backend to exercise it (the
default onrender URL currently 404s; the real API is the Spring service).
The backend Category enum is BEAUTY_CARE (underscore); we were sending
"BEAUTY CARE" (space), which Jackson can't map to the enum — booking a
Beauty pro would 400. Verified all six apiCategory values against
guildworkman-api's Category enum; only Beauty was wrong.

Found by checking the request against the backend controller/DTO source
(scheduleTime LocalDateTime accepts our "YYYY-MM-DDThh:mm"; ApiResponse
is {data, status} so the res.status success check is correct; clientId
is a Long — the localStorage userId must be numeric, unchanged from the
existing flow).
Completes the identity migration: every remaining page now uses the
real design-system tokens, and the legacy colour aliases are removed
from globals.css.

- Migrate login, signup, dashboard/profile, and the appointment
  screens (appoint/view/cancel/update/appMan) + their components
  (forms, CategoryButton, SkillCard, Input, Select, WalletButton):
  cream→sand, ink-900 bg→navy, ink-500→muted, ink-100→line,
  brand→terra/navy by context, gold-100/600→gold/gold-deep,
  success/error→ok/err. Dark split-panels use navy with white/70 text
  and a terracotta accent (active nav item, links, upload icon).
- Delete orphaned CategorySection and TrustSection (replaced by the new
  landing sections; removed the last chain-* usage).
- Remove all legacy --color-* aliases from globals.css; keep the font
  and shadow tokens.

Verified: build + tsc pass; zero legacy token classes remain; login and
dashboard checked visually.
…n-cleanup

Migrate legacy routes off aliased tokens; remove aliases
The appointment endpoints in api.ts didn't match guildworkman-api. Fixed
them against the backend source, and documented the remaining backend gap.

- cancel/update: POST → PUT with the id in the `appointmentId` query param
  (not a JSON body); update sends `{ status, ... }` as the body.
- delete: POST → DELETE with `?appointmentId=` + `{ appointment_Id }` body.
- viewAllAppointment: typed to the real `ApiResponse { data, status }`
  wrapper; unwrap `data`.
- Generalised the fetch helper to support GET/POST/PUT/DELETE + optional body.
- types.ts: add ApiEnvelope, AppointmentStatus enum union, and the real
  (thin) ViewAllAppointmentsResponse; fix UpdateAppointmentRequest; the
  Appointment interface now holds the richer shape the redesign will use.
- Legacy screens made honest: /view is read-only over the single record the
  API returns; /cancel and /update take a manual appointment-id input (since
  the list returns no ids yet) so the corrected calls are actually usable.

Adds docs/appointments-api-spec.md: viewAllAppointment must return a LIST of
appointments with id/status/scheduleTime/category/amount/worker to unblock the
account/appointments redesign; optional COMPLETED status for escrow release.
Mutation endpoints need no change — the frontend now matches them.

Verified: tsc + next build pass. End-to-end still needs a running backend.
…ment-api

Correct appointment API client to match backend + appointments-API spec
- globals.css: dark token values under `prefers-color-scheme: dark` and
  `:root[data-theme="dark"]`, with an explicit `[data-theme="light"]`
  restore so a user choice wins over the OS in both directions. Flips the
  neutrals (bg/surface/ink/muted/line) plus the brand tokens that need it
  for dark legibility (navy-2 -> light periwinkle, navy-tint, gold-deep,
  ok, err); navy/gold/terra grounds stay constant. Sets color-scheme.
- ThemeToggle: sun/moon control that stamps data-theme on <html> and
  persists to localStorage; defaults to the system preference.
- layout: inline pre-paint script applies the stored/OS theme with no
  flash; suppressHydrationWarning on <html>. Toggle added to the navbar.
- Fix hardcoded `bg-white` on form/neutral surfaces (Input, Select,
  CategoryButton, WalletButton dropdown) -> `bg-surface` so they flip;
  kept intentional light-on-dark-brand surfaces.

Components use tokens, so they adapt automatically. Verified in dark via
headless screenshots: landing, browse, login, dashboard. Build passes.
Add dark theme (system default + persistent toggle)
The backend + Soroban contracts now live in one repo, renamed
guildworkman-api -> guildworkman-core (backend at backend-api/, contracts at
soroban-contracts/); guildworkman-contracts is being archived.

- README: ecosystem is now two repos (guildworkman-web + guildworkman-core);
  repointed backend/contract links and prose.
- appointments-api-spec + api.ts/types.ts comments: reference the backend by
  its new home.

Left alone: config.ts's NEXT_PUBLIC_API_BASE_URL default — that's a host URL,
not a repo name, and unaffected by the rename.
…-refs

docs: repoint references to guildworkman-core
Add the production URL (guildworkman-web.vercel.app) at the top of the README
and in the Deployment section, and note that production builds from dev.

Also flag the known mismatch: the backend's CORS is scoped to
guildworkman.vercel.app, which is not the live frontend origin
(guildworkman-web.vercel.app), so restricted endpoints can be blocked in prod
until the backend allows the correct origin.
…ickers

viewAllAppointment now returns a LIST of appointments, each with an id, status,
amount and worker (guildworkman-core#10). Type and use that shape.

- viewAllAppointmentApi is typed ApiEnvelope<ViewAllAppointmentsResponse[]>, and
  ViewAllAppointmentsResponse gains id, status, amount and a worker summary.
- ViewAllAppointments renders every appointment, soonest first, with a status
  pill, the pro's name and the escrowed amount — instead of the single
  time+category record it could show before.
- CancelAppointmentForm and UpdateAppointmentForm drop the "type the appointment
  id" workaround and pick from the real list. That input only existed because the
  API gave clients no way to learn an appointment's id; it does now.
- BookingScreen sends the escrow total as `amount`. It still can't send
  skilledWorkerId: the browse list is sample data whose ids ("gw-chidi") aren't
  the backend's numeric SkilledWorker ids, so that waits on fetching real workers.
- Shared helpers in lib/appointments.ts: schedule-time formatting (the API sends a
  zoneless LocalDateTime), category formatting (BEAUTY_CARE -> "Beauty Care"),
  status tones, and the signed-in client id.

Verified against a live backend: booked three appointments, and /view, /cancel and
/update all render real data — including the accept button correctly disabled on an
already-accepted job, and "No pro assigned yet" for a booking with no worker.
tsc clean, next build passes.
…s-list

feat: consume the appointments list, and restore the cancel/respond pickers
Export the approved Identity System v1 marks as SVG masters plus 4x PNGs:
the primary/stacked/reversed lockups, the North Star (full-colour, one-colour,
and app-icon tile), and the Adinkra tile and glyph. Wordmarks are set in Inter
800 to match the Logo component, so exported art and rendered UI agree.

public/brand/study/ archives the superseded exploration routes (Forge, Chevron,
Guild Seal) in the old Workman Labs steel/amber palette, for reference only.
The Chevron's canvas is widened to 748 (from 640) because the source viewBox
clipped the wordmark's final letter at x=668.9; no artwork coordinates moved.
The Design system section still described the pre-redesign identity
(terracotta primary, Fraunces headings) and the Web3 section pointed at
TrustSection.tsx, which the legacy-token cleanup deleted. Update both to
match what ships: navy/gold/terracotta on sand, Inter throughout, the
dark-theme token flip, the added Button `gold` variant and Badge `navy`
tone, and the homepage sections that now carry the trust copy.
The workman-chevron lockup is 748x300 (2.5:1), so a square avatar crop
truncates it, and the wordmark is unreadable at the 20-40px sizes GitHub
renders in listings. Recompose the chevron mark alone, centered on a 512x512
ink tile, with the steel stroke lifted for contrast on the dark ground so it
reads on both GitHub themes.
The app still shipped Next's default favicon. Generate the icon set from the
North Star mark and wire it up via App Router file conventions (favicon.ico,
icon.svg, apple-icon.png, manifest.ts) so no manual link tags are needed.

favicon.ico carries 16/32/48. The 16px member drops the verified check, per
the identity system's own rule that the check is illegible under 20px; it
returns at 32px and up. The apple and manifest tiles are full-bleed (the OS
applies its own mask) with the star inside the central 60% so it survives the
maskable safe-zone crop.
Replace the steel/amber chevron avatar with the North Star on a navy tile.
The chevron version used the rejected palette and shares its peak geometry
with the current lockup, so as an org avatar it read as a mis-coloured
GuildWorkman logo rather than a distinct mark. The North Star is the mark the
identity system designates for icon use, and it stays legible at the 20-40px
sizes GitHub renders.

The avatar moves out of study/ (now purely an archive, nothing live in it)
and into brand/ alongside the rest of the current marks.
The project's history begins in 2024; the notice should run from first
publication to the current year, not from when the LICENSE file was added.
* feat(booking): add provider→visitor timezone conversion + client-side slot locking libs

- lib/timezone.ts: converts provider-local (Africa/Lagos) wall-clock
  slot times to the visitor's browser timezone using Intl, handling
  DST-safe offset lookups and cross-midnight day drift.
- lib/slotLock.ts: localStorage + BroadcastChannel backed soft-lock
  with a 5-minute TTL, to stop a visitor double-booking themselves
  across tabs. Documented as a frontend-only mitigation pending a
  backend per-worker availability endpoint (see module doc comment).

* fix(booking): anchor calendar 'today' to Africa/Lagos instead of host clock

buildDates() previously used the server process's own local Date(),
which on a serverless host (typically UTC) can compute the wrong
'today' — and therefore the wrong 7-day window — around midnight WAT.
Now derives it from todayIsoInZone(PROVIDER_TIME_ZONE).

* feat(booking): timezone-aware calendar UI + slot-lock integration

- Show each time slot converted to the visitor's local timezone, with
  a +1/-1 badge when the conversion crosses midnight, plus a 'shown in
  your time / provider is in Lagos' banner when they differ.
- Acquire a soft lock on the selected slot on selection and release it
  on change/unmount; disable + label slots ('Held') that are locked by
  another tab for this worker/date/time; re-check the lock immediately
  before submitting payment.
- Reflect the visitor-local time on the escrow summary and the
  confirmation screen too, not just the picker.

* chore: add npm run typecheck script; document timezone/slot-lock decisions

CI already ran npx tsc --noEmit directly and already had npm dependency
caching configured (actions/setup-node cache: npm), so no CI workflow
changes were needed for those two task items — added the typecheck
script for local parity with what CI runs. README's Known limitations
section now documents the Lagos-anchored timezone approach and the
frontend-only scope of slot locking, per the issue's request to record
new architectural decisions.

* feat(booking): timezone-aware calendar + client-side slot locking

* test(booking): add unit tests for timezone + slot-lock logic; fix day-offset bug the tests caught

---------

Co-authored-by: GuildWorkman Contributor <dev@example.com>
… links (workman-labs#35)

Provide a centralized notification context for transaction toasts with
retry capabilities and deep links to relevant escrow/booking pages.

- Add TransactionNotification type system with factory helpers
  (success, error, pending, info) in lib/notifications.ts
- Implement React Context-based NotificationProvider with auto-dismiss
  timers (5s success/info, 10s error/pending)
- Create floating toast stack (bottom-right) with animated entrance,
  per-type icons, retry button, and deep-link navigation
- Build notification center dropdown (bell icon) with unread count badge,
  scrollable list, relative timestamps, and "Clear all" action
- Integrate provider + toast into root layout and bell icon into navbar
- Wire retry/deep-link actions into both toast and dropdown surfaces

Co-authored-by: Maverick <151352447+meshackyaro@users.noreply.github.com>
Adds a progressive, multi-step identity verification flow at
/verify-identity: personal details -> document type -> document
upload -> selfie -> review & submit. Each step validates before
advancing, later steps disclose progressively (e.g. a "back of
document" upload only appears for document types that have one),
and in-progress answers auto-save to localStorage so the flow can
be resumed after a reload.

Architectural notes (documented in src/lib/identityVerification.ts):
- guildworkman-core has no identity-verification endpoint yet, so
  submitIdentityVerification() simulates the network round trip
  (latency + occasional failure, to exercise the error-recovery UI)
  rather than inventing a REST shape the backend can't answer.
- Uploaded files (ID photos, selfie) are intentionally never written
  to localStorage — only their filenames persist for the resume
  summary. Resuming past the upload/selfie steps asks the user to
  re-attach, trading a little resume friction for not storing
  sensitive images client-side.

No new dependencies; built on the existing react-hook-form +
react-dropzone stack already used by ProfileForm.

Closes workman-labs#33
@vercel

vercel Bot commented Aug 1, 2026

Copy link
Copy Markdown

@water is attempting to deploy a commit to the Meshack Yaro 's projects Team on Vercel.

A member of the Team first needs to authorize it.

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.

6 participants