Skip to content

Feature/profile page - #49

Merged
dcl10 merged 10 commits into
mainfrom
feature/profile-page
May 20, 2026
Merged

Feature/profile page#49
dcl10 merged 10 commits into
mainfrom
feature/profile-page

Conversation

@dcl10

@dcl10 dcl10 commented May 20, 2026

Copy link
Copy Markdown
Contributor

Description

Adds user profile pages and skill management.

Each user has a profile at /profile/[userId]. Viewing your own profile shows an editable skills list; viewing another user's shows a read-only view.

Backend

  • New GET /api/users/{userId:guid} endpoint — returns UserProfileDto including skills, accessible to any authenticated user (no admin role required)
  • Fixed SkillService using EF.Functions.ILike throughout to replace untranslatable StringComparison LINQ overloads

Frontend

  • Profile page (/profile/[userId]) with avatar, display name, email, and role
  • Own profile: add skills by name (creates catalogue entry if new), change proficiency level, remove skills
  • Other users' profiles: read-only tag list
  • Authenticated layout wrapping all /profile/* pages with TopBar and LeftRail
  • Log out button added to TopBar (icon + label on desktop, icon-only on mobile)
  • Dashboard links to the current user's profile via "My profile"

Linked issues

(Optional) Screenshots

Screenshot 2026-05-20 at 18 05 35

dcl10 and others added 10 commits May 20, 2026 17:14
- Add GET /api/users/{userId:guid} so any authenticated user can fetch another
  user's full profile (including skills) without requiring admin role
- Set MapInboundClaims = false on JWT Bearer so Keycloak claim names (sub,
  name, email) are preserved as-is instead of being remapped to long-form .NET
  URI types; without this User.FindFirst("sub") silently returns null
- Update AppUserService.SyncFromClaims to use raw JWT claim names ("name",
  "email") consistent with MapInboundClaims = false
- Align integration test claims to raw JWT names to match production behaviour

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add /profile/[userId] page visible to any authenticated user:
- Own profile shows editable skills list with add (typeahead search),
  level change, and remove controls
- Other users' profiles show a read-only skills view
- Server actions (addSkill, updateSkillLevel, removeSkill) call the
  existing skills CRUD endpoints with server-side token handling
- API helpers and shared types added under lib/api/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Call POST /api/users/login on dashboard load to upsert the local user
record from Keycloak claims on first login, then link to the profile page.
Without the sync, GET /api/users/me returns 404 for new users.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace StringComparison overloads in SkillService with EF.Functions.ILike,
which maps to PostgreSQL's native ILIKE operator and is fully translatable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a shared layout for all /profile/* pages that handles auth redirect,
syncs the Keycloak user to the local DB, and wraps content in AppShell
(TopBar + collapsible LeftRail). Moves auth/redirect responsibility out of
the profile page into the layout.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rewrites the add-skill form to accept free text rather than requiring a
selection from the pre-populated catalogue. The addSkillByName action
searches for an exact case-insensitive match first and reuses it; only
creates a new catalogue entry if no match exists.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds an onSignOut prop to TopBar, rendered as an icon + "Log out" label
on desktop and icon-only on mobile. AppShell wires it to the
signOutFromKeycloak server action.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…im names

SkillService: replace EF.Functions.ILike (Npgsql-only) with ToLower().Contains/==
so queries work against both PostgreSQL and the in-memory test database.

HealthController: use raw JWT claim name "email" instead of ClaimTypes.Email
(the long .NET URI form) to match the MapInboundClaims = false configuration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@dcl10
dcl10 merged commit ab46c44 into main May 20, 2026
2 checks passed
@dcl10
dcl10 deleted the feature/profile-page branch May 20, 2026 17:17
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.

1 participant