- Imports onCleanup, Mic, MicOff from solid-js / lucide-solid
- [isRecording, isTranscribing] signals track state across the flow
- startRecording(): requests getUserMedia, creates MediaRecorder (prefers
audio/webm;codecs=opus, falls back to webm then ogg), starts recording
- stopRecording(): stops MediaRecorder; onstop handler assembles Blob,
POSTs to /api/ai/voice/transcribe as multipart 'audio' field
- transcribeAudio(): sends the Blob, populates input() with the transcript,
then auto-sends after 600ms so the user sees it before it flies
- Mic button: grey at rest → red while recording → spinner while
transcribing; uses onPointerDown/Up/Leave for reliable hold UX
- Red recording hint bar below input with pulsing dot while active
- onCleanup stops any in-progress MediaRecorder on widget unmount
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ChatMessage now carries draftContent?: string (from API draft_content field)
- ChatResponse interface extended with draft_content?: string
- sendMessage populates draftContent on assistant messages
- confirmSaveProfile uses draftContent directly (no more regex parsing)
- 'Save to Profile' button guards on msg.draftContent being truthy
- New blue 'Copy Cover Letter' button for cover_letter_draft messages:
copies msg.draftContent to clipboard, marks message confirmed,
appends a confirmation assistant message
- 'Open Cover Letter Tool →' nav link still shows alongside the Copy button
via the existing navUrl('open_cover_letter') path
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Added confirmSaveProfile() — calls POST /api/ai/chat/confirm with
action='save_profile', extracting the quoted improved text from the
LLM's message automatically
- Green 'Save to Profile' button appears on profile_draft messages
(actionType === 'profile_draft'), distinct from the ticket confirm flow
- actionLabel() extended with 'save_profile' key
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ChatMessage gains actionType, userQuery, confirmed fields.
ChatResponse gains action_type from backend.
Action buttons now render below assistant messages:
- Navigation actions (open_help_search, open_billing, etc.) show a link
chip to the correct dashboard section via navUrl()
- ticket_pending / create_ticket action shows a 'Create Support Ticket'
button that calls POST /api/ai/chat/confirm, marks the message
confirmed (hides the button), and appends the ticket result as a new
assistant message
Helper functions navUrl() and actionLabel() map suggested_action keys
to dashboard URLs and human-readable labels respectively.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous coupon UI's Apply button just flipped a local boolean on
any non-empty text - no call to the backend, so an invalid/expired code
still showed a green "will be applied at checkout" confirmation. The
actual discount only surfaced (or failed generically) at order creation.
Now Apply calls the new POST /api/ai-credits/coupons/validate endpoint,
shows the real discount amount on success or the real error message on
failure, and the Pay button reflects the validated discounted price.
Editing the code after a successful apply clears the validated state so
a stale discount can't be sent. create_order still re-validates
server-side regardless (unchanged, already correct).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds an optional coupon code field (uppercase, with Apply button and
confirmation tick) to the checkout modal, visible only for AI credit
package purchases. The code is passed as coupon_code to POST
/api/ai-credits/order; the backend applies the discount and the
discounted amount flows through to PayU.
CheckoutState gains couponCode + couponApplied fields. All open/close
reset paths clear them. Retry from error preserves the entered code.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
AiChatWidget renders unconditionally in the root layout for every
route - looked out of place on the pre-launch marketing page (a
floating chat bubble implying app functionality that doesn't exist
yet). Hidden via a small route-exclusion set keyed off useLocation(),
rather than threading a prop through every route.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replaces the standalone static-nginx coming-soon deployment
(nxtgauge-gitops/coming-soon/ + apps/nxtgauge-coming-soon/) with a
proper route in this app - same content/design, ported to SolidJS
(signals for the form state instead of vanilla DOM manipulation),
CSS classes prefixed cs- to avoid colliding with the rest of the
app's global styles.
src/middleware.ts (already intercepts every request for the /api/*
proxy workaround) now also redirects '/' to '/coming-soon' when the
request Host is nxtgauge.com or www.nxtgauge.com - test111.nxtgauge.com
still shows the real app. Same deployment, same ingress backend, no
separate infra to build+deploy+eventually tear down when launching -
flipping the switch at launch time is just deleting that redirect
block and the route.
Reuses existing public/nxtgauge-logo.png and public/traceworks-logo-white.svg
(confirmed byte-identical to the old coming-soon/assets/ copies) rather
than duplicating assets.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
card.Icon typed as (props) => JSX.Element instead of unknown to
satisfy SolidJS JSX component constraint.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Steps 7/8 of the live-server runbook (npm run lint / tsc --noEmit)
surfaced two unrelated pre-existing issues:
- node_modules/eslint was 10.1.0 despite package.json declaring
^8.57.1 and package-lock.json correctly recording 8.57.1 - a
legacy .eslintrc.cjs config can't run under ESLint v9+, which
dropped the old config format by default. Root cause: @vitest/browser
and @vitest/coverage-v8 were still pinned to ^3.2.4 while vitest
itself had been bumped to ^4.1.1, an internal peer-dependency
conflict that forced node_modules into an inconsistent state
whenever anyone ran npm install without --legacy-peer-deps. Bumped
both to ^4.1.4 to resolve the conflict at its source, then a clean
npm install correctly restored eslint@8.57.1.
- ExploreServicesPage.tsx's RoleCard.Icon field was typed as
"(props) => unknown", which TypeScript correctly refuses to accept
as a JSX component ('card.Icon' cannot be used as a JSX component).
lucide-solid's actual icon components return solid-js's JSX.Element
(confirmed via node_modules/lucide-solid's own .d.ts) - fixed the
annotation to match reality instead of loosening it.
lint: 0 errors (293 pre-existing warnings elsewhere in src/, untouched
by this session, left as tracked debt per the runbook's own bar).
tsc --noEmit --skipLibCheck: exits 0.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The e2e suite only ever worked against a local docker-compose stack:
- Hardcoded http://localhost:3000 / :9100 everywhere, ignoring
TEST_ENV=production and playwright.config.ts's own baseURL logic.
- /api/auth/login and /api/auth/register now require solving a math
captcha first; none of these tests sent captcha_id/captcha_answer,
so every login/register call 422'd against the live API.
- OTP retrieval shelled out to a local, unauthenticated redis-cli,
which can't reach the real (kubectl-exec + password-protected) Redis.
- Several files launched their own chromium.launch({headless: false}),
which crashes immediately on a server with no X display.
- One file had a hardcoded macOS absolute path for screenshots.
Added tests/e2e/helpers/{env,captcha,otp,auth-flow}.ts as shared,
reusable fixes for all of the above, and updated every affected spec
file to use them. Verified via a full run against test111.nxtgauge.com:
971 schemathesis-adjacent smoke assertions aside, the actual signal
here is 0 of the 130 prior failures came from real product bugs - all
were this environment mismatch. See docs/LIVE_SERVER_RUNBOOK.md step 5.
Also fixes .gitignore: it excluded 'playwright-report' (singular) but
playwright.config.ts's actual outputFolder is 'playwright-reports'
(plural) - generated HTML report artifacts had been getting committed
by accident. Untracked the existing ones; left tests/e2e/visual/*-snapshots/
(newly-generated visual regression baselines from this run) untracked
for now since establishing baselines needs a human look, not a blind commit.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
mergeSidebar already restricts the sidebar to 'Verification', 'Settings',
'Help Center' (and conditionally 'My Profile') when a verification is pending.
But if the admin's runtime config for this role omits 'Verification', the item
was silently absent even after registration — leaving users with no way to
track their submission.
Fix: after filtering to the restricted set, inject 'Verification' when it is
missing and the user has a pending status. This is a safety guarantee:
admin config governs approved-role layouts; pending-verification state always
wins on the Verification item regardless.
Also:
- .eslintrc.cjs: add varsIgnorePattern/destructuredArrayIgnorePattern '^_',
turn off no-explicit-any (complex runtime config shapes), matching admin config
- Prefix unused resolveRuntimeSidebarKeys helper with _ to silence lint
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- After registerRole() succeeds, fetch existing profile for identity doc
pre-fill and immediately launch RoleWizard inline (same pattern as
SwitchServicesPage)
- Wizard header shows role name + back arrow; main card grid hides
- On wizard submission: show approval-pending message and reload roles
- Card status now uses /api/me/roles (returns status field) so Pending
roles show 'Under Review' badge instead of 'Switch' button
- Main roles (Company, Job Seeker, Customer) also go through the wizard
- StatusBadge component uses reactive accessor thunks (fixes
solid/reactivity warning)
- Remove unused BTN_PRIMARY import
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pre-fill is now purely schema-driven: only fields explicitly marked
identity_shared: true by an admin in the Onboarding Schema Editor are
pre-filled when registering a second role. No implicit convention set.
SwitchServicesPage now passes the full existing profile to RoleWizard;
RoleWizard filters to identity_shared fields using the new role's schema.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Show RoleWizard inline in SwitchServicesPage immediately after
role registration — no separate navigate-to-profile step
- Pre-fill shared identity docs (Aadhaar, PAN, selfie, address proof)
from the user's active role profile so they don't re-upload on a
second role registration
- Add identity_shared: boolean to RuntimeOnboardingField type so admins
can mark fields in the Onboarding Schema Editor; CONVENTION_IDENTITY_
FIELD_IDS covers common IDs automatically as a fallback
- Extract roleKeyToPrefix() into src/lib/role-utils.ts shared utility
- FileControl sub-component keeps reactivity clean (no any casts)
- Fix solid/reactivity: snapshot form()/docUrls()/portfolioForm()
synchronously before the first await in handleSubmit/savePortfolio
- Reuse badge (♻ indigo) distinguishes pre-filled docs from new uploads;
Change link clears pre-fill so user can re-upload
- Pending roles now show 'Under Review' badge; wizard exits via ArrowLeft
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend now returns JSON error bodies with a code field for every
create_job failure branch. Special-case QUOTA_EXHAUSTED with an
actionable message since there's no self-serve job-slot purchase flow
yet (support has to grant slots manually).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
job-seeker-custom-data.ts hit /api/gateway/jobseeker/profile/me — a prefix
that never existed in production (see the comment in lib/api.ts, which
every other authenticated call already follows); it 404s against the real
ingress, which routes /api/* straight to the gateway. Its local apiFetch
also never sent an Authorization header, which AuthUser requires (no
cookie fallback), so even the corrected path would 401.
This is why RoleWizard's submit flow broke for JOB_SEEKER specifically:
handleSubmit() calls savePortfolio() first, which calls
updateJobSeekerCustomData() (JOB_SEEKER's portfolioModel is "custom_data"),
which threw on the failed fetch and aborted the whole submission before
the actual profile PATCH or submit-for-verification call ever ran.
Confirmed against production: verifications has 5 rows for COMPANY, 1 for
PHOTOGRAPHER, 0 for JOB_SEEKER ever, despite a job seeker having fully
filled out the wizard (including an uploaded document) days ago.
Same file backs readJobSeekerProfile/updateJobSeekerCustomData used by
PortfolioPage, JobSeekerJobsPage, and JobSeekerSavedJobsPage too — all
four were broken the same way.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- VerificationStatusPage: showInlineEditors now hides for PENDING and
UNDER_REVIEW (only shows when user action is needed: NOT_SUBMITTED,
DOCUMENTS_REQUESTED, REVISION_REQUESTED, REJECTED). Previously the
old ProfilePage form with validation errors appeared after submission.
- VerificationStatusPage: wrap onVerificationStatusChange passed to
ProfilePage so the local status signal also updates when the wizard
calls onSubmitted. Previously the status card kept showing NOT_SUBMITTED
until a page reload.
- VerificationStatusPage progress tracker: inline signal reads directly
in JSX instead of capturing them in local variables. In Solid.js, local
const done = signal() inside a For callback is computed once and goes
stale — the step circles stayed grey even after status changed to PENDING.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ProfileCompletionWidget hardcoded a per-role field list (industry,
description, full_name, bio, ...) that never matched what the onboarding
wizard actually collects or where it's stored — for COMPANY only 2 of 5
checked fields existed at all, producing a stuck 40% regardless of real
completeness. Now derives required field ids from the same
fetchOnboardingSchemaForRole the wizard itself uses, and checks them
against the same /api/profile endpoint the wizard writes to, so it can't
drift again.
RoleWizard now shows "no fields configured yet" instead of rendering a
blank step card when a step has zero fields — visible failure instead of
silent, in case a stale/cached schema ever produces one.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Generalizes the COMPANY-only 3-step wizard into RoleWizard, a component
driven entirely by the role's active onboarding_configs schema (fetched
via fetchOnboardingSchemaForRole) rather than hardcoded field/step
lists. Which roles get the wizard, and when, is now a runtime toggle
(enableWizardFlow) set through the new admin schema editor:
- ProfilePage shows the wizard while unverified/sent-back-for-fixes,
falls back to the existing free-form tabs once approved, and greys
out (both client- and server-side) any field the schema marks
lockAfterApproval once approved — previously isLocked() only covered
the in-review window and unlocked everything again at APPROVED.
- dashboard.tsx hides My Profile/My Portfolio from the sidebar
pre-approval only for roles that have the wizard enabled (roles not
yet rolled out keep direct access, so they aren't left with no way to
complete their profile), and shows a checkmark on the Verification
nav item once approved.
Rolling out to more roles is now purely an admin-panel config change —
no further code needed, since RoleWizard has no role-specific branches.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The disabled-button hint just said "Complete all required fields to
submit" with no indication of which section was incomplete. For job
seekers, portfolio completion (headline, summary, education, work
experience, skills — filled in via My Portfolio, a separate page from
this one) is required alongside Basic Info and Documents, so seeing
both visible tabs fully checked while submit stays disabled looked
like a bug. Now names the actual missing items.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two issues in the job seeker (and shared profile) dashboard:
- The uploaded document's full Backblaze URL was rendered as visible text
and linked directly; view-document now resolves a short-lived signed
URL on demand instead of exposing the permanent storage link.
- "Submit for Verification" always showed a generic "Network error"
message on any failure because request() throws on non-2xx responses,
making the destructured status/data unreachable in the catch-free path
(e.g. a 409 "verification already in progress" looked identical to a
network failure). Now surfaces the real backend error message.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The backend emails a plain 6-digit reset code (crates/email/templates/
password-reset.html just renders {{reset_code}}, no link) and expects
POST /api/auth/reset-password with {code, new_password}. This page was
built for a different, unused link-based flow instead: it only read a
`token` from the URL query string and posted it as `token`, a field
name the backend's ResetPasswordPayload doesn't even have. Users had
no way to type the code in at all, so submitting a request just left
them stuck back on the request screen.
Now: requesting a reset code moves straight to a "set new password"
step with a 6-digit code input, and the code is submitted under the
correct `code` field name.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The captcha on login and all four signup forms was generated and
checked entirely in the browser (answer readable via window global),
so it provided no real bot/brute-force protection. Wire up the new
server-side captcha endpoint instead: fetch a challenge on mount,
submit captcha_id + captcha_answer with login/register, and refresh
the challenge on CAPTCHA_FAILED.
Also bump patchable dependency vulnerabilities via npm audit fix
(all criticals resolved; remainder needs an upstream SolidStart/vinxi
bump not yet available).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The dashboard's Submit for Verification widget checked profile_data for
a documents/documents_data field that GET /api/jobseeker/profile/me never
returns, so the Submit button stayed permanently disabled. Even bypassing
that, it POSTed {document_urls: []}, a field the backend doesn't
recognize, instead of the profile_data shape submit-for-verification
actually expects.
Now fetches the job seeker's real uploaded documents via
GET /api/jobseeker/profile/documents, checks the correct document_type
field, and submits profile_data merged with the uploaded document URLs.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
An entire route/component cluster was built on a legacy sibling of
DashboardShell (DashboardLayout.tsx) and called APIs via the bare
api.get/post/patch/delete helper, which never prefixes /api/ — so
every call 404s against the real ingress (which only routes /api/*
to the backend). Confirmed orphaned: nothing in the live dashboard
shell (DashboardShell.tsx / dashboard.tsx) links to any of it; the
only cross-references are within the cluster itself. Some of it also
targeted the apps/leads backend service removed in the companion
backend commit.
Removed:
- src/routes/dashboard/wallet/ (buy.tsx, payu-return.tsx, invoices/*)
- src/routes/dashboard/requests.tsx
- src/routes/dashboard/leads/accepted/*
- src/routes/dashboard/marketplace/*
- src/components/dashboard/AcceptedLeadsView.tsx
- src/components/DashboardLayout.tsx (only consumer was the above)
- the unused `api` object in src/lib/api.ts (the unprefixed-path
footgun itself — `request()`, which it wrapped, stays; it's used
correctly elsewhere with explicit /api/ paths)
Fixed rather than deleted: src/components/NotificationBell.tsx uses
the same broken convention but IS live (rendered on every dashboard
page via DashboardShell). Switched it to apiFetch with correct
/api/me/notifications/* paths, matching the routes that actually exist
in apps/users/src/handlers/notifications.rs.
`tsc --noEmit` shows no errors under src/ after these changes (pre-
existing node_modules/type-declaration noise unrelated to this change
remains, as it did before).
Replaces raw/truncated UUID labels with the backend's new reference_number
field across verification, application, and lead request views. Verification
status page now renders actual uploaded documents (previously invisible
after submission since the API never returned them).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
All dashboard pages and widgets had one of three bugs:
- const API = "/api" combined with paths already starting /api → double prefix
- const API = '/api/gateway' → nonexistent gateway path prefix
- cleanPath stripping /api off paths when API was set to ""
Fix: set const API = "" uniformly and remove cleanPath rewrite in all 30+
affected files (CompanyJobsPage, CompanyApplicationsPage, CreditsPage,
JobSeekerJobsPage, CustomerRequirementsPage, all widgets, etc.).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
AiChatWidget.tsx's local API constant was still "/api/gateway", a path
that only exists via the dev-only vite proxy and has no equivalent in
production (the gateway's resolve_upstream() has no "/api/gateway"
prefix). The usage-summary fetch had been patched to add auth headers
but this constant was left broken, and the two chat-send calls
(/chat/ask, /chat/message - the widget's core send-message path) were
untouched and doubly broken (wrong prefix plus a duplicated "/api").
Every send would 404 in production. Changed API to "/api" and dropped
the duplicated segment from both chat-send calls.
Also corrected a comment in CompanyJobsPage.tsx that incorrectly
described its own local API constant as "/api/gateway" (it's "/api");
no behavior change there, that fetch was already correct.
help-center.ts's uncommitted change (adding an "/api/gateway" prefix to
already-correct direct "/api/kb" calls) was reverted separately since it
introduced the same class of bug rather than fixing anything - it now
matches HEAD with no diff.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
registry_prune.py sorted candidate tags by the manifest GET response's
Date header - which is just "now", the moment the prune script made
that request - not when the image was actually built. Every tag it
queries in the same prune run lands within the same second, so the
"sort by age" was effectively random. On run #56 this deleted the tag
this same CI run had just pushed (4efe848, digest 60114dea) seconds
after pushing it, and seconds before the gitops step committed a
deployment pointing at that now-deleted digest - the cluster then
sat in ImagePullBackOff since the referenced image no longer existed.
Now reads the real "created" timestamp from the image's config blob
(resolving through a manifest list/index if the tag is multi-platform),
and always protects the current run's own SHA from deletion regardless
of sort order, as defense in depth against any remaining timestamp edge
cases.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The K8s ingress for this domain routes any /api/* path directly to the
Rust gateway service — there is no rewrite/proxy layer in production that
collapses /api/gateway/* down to /api/*. That rewrite only exists as a
dev-only vite proxy, so lib/api.ts's request()/apiFetch() (used by Save
Changes, Submit for Verification, document upload, wallet, jobs,
marketplace, portfolio, and more) were 404ing on every call in production
with "Route not found in gateway". ProfilePage.tsx and
VerificationStatusPage.tsx each had their own local apiFetch with the same
assumption, doubly broken (paths already included /api/, prefixed again).
All three now hit the already-fully-qualified /api/... path directly,
matching how the working signup/login calls have always done it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The register() request body sent both profession and role_key with the
same value. The backend's registration DTO aliases role_key onto the same
field as profession, so serde_json rejected the payload outright with a
"duplicate field" deserialization error — professional signup was
returning 422 for every role in production.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Uploaded document URLs lived only in a client-side signal and were never
included in the Save or Submit-for-Verification payloads (the latter sent
a document_urls field the backend silently ignored), so every submitted
verification case ended up with no attached documents for every role
except COMPANY. Now merges doc URLs into profile_data on save/submit and
repopulates them from the saved profile on reload.
Also adds the missing DOC_FIELDS entries for Developer, Video Editor,
Graphic Designer, Social Media Manager, and UGC Content Creator, and makes
the verification-status Documents tab role-aware instead of showing 3
hardcoded generic labels.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
GITOPS_PAT auth was failing against Forgejo. GitHub is the source of
truth for nxtgauge-gitops; push there and let sync-to-forgejo.yml
relay to Forgejo, which Flux actually watches.
Third bug in the same chain: GITEOPS_REPO/GITEOPS_SSH_KEY were
referenced but never actually configured as repo secrets (only
REGISTRY_*, GITOPS_GITHUB_*, and GITOPS_PAT exist) — the digest
extraction fix got the job to actually reach this step, where it then
failed instantly and silently (a bare `test -n` with no echo). Switch
to an HTTPS clone with GITOPS_PAT, matching nxtgauge-backend-rust's
already-working workflow.
The docker gateway fix got the build to actually run and push
successfully, but the digest-extraction grep required a compact
":" with no space, while buildx writes the metadata file
pretty-printed ("containerimage.digest": "sha256:..."), so the match
always failed, aborting the step post-push and skipping the
GitOps-release step entirely. Match nxtgauge-backend-rust's
whitespace-tolerant pattern.
Every build on this branch has been failing with "Cannot connect to
the Docker daemon at tcp://127.0.0.1:2375" — the job container is
nested one level inside the runner pod's dind sidecar, so its own
loopback isn't the sidecar's. nxtgauge-backend-rust already carries
the fix (read the container's default-route gateway from
/proc/net/route); porting the same step here.
- DashboardLayout sidebar: Leads/Credits/Settings/Logout pointed to
routes with no matching file (404). Leads now routes to the real
accepted-leads page; Credits/Settings/Logout reuse the working
/dashboard?nav= deep-link into the main dashboard's tab switcher
(fixes Logout leaving users authenticated on a 404).
- wallet/buy.tsx, wallet/payu-return.tsx: post-purchase, cancel, and
payment-verification redirects targeted non-existent /dashboard/wallet;
now redirect to the existing credits/wallet tab.
- leads/accepted.tsx already contained a detail view gated on
useParams().id, but was only registered as a flat route with no :id
segment, so the detail view was dead code. Split into
leads/accepted/index.tsx + leads/accepted/[id].tsx backed by a shared
AcceptedLeadsView component.
- Added marketplace/[id].tsx: "View Requirement" buttons navigated to
a route that never existed.
This is a test commit to verify:
1. GitHub Actions syncs to Forgejo
2. Forgejo Actions triggers build
3. Runners execute build
4. Image is pushed and deployed
Test timestamp: 2026-07-08T04:28:54+05:30