ae7cd02cd6
Self-review of PR 3's TenantMembers.vue surfaced a handful of real bugs
and a few rough edges. This commit fixes them and adds the self-leave
endpoint that PR 3's design called out as a follow-up.
Frontend bug fixes
- Read err.status (not err.response.status). The axios interceptor in
utils/request.ts already flattens errors into { status, message,
... }, so the original branch-by-status code never matched and every
4xx fell through to the generic toast. Fixes 404/409 surfacing as
"userNotFound" / "alreadyMember" / "lastOwner" instead of "Something
went wrong".
- t-dialog @confirm replaces :on-confirm so the dialog stays open on
validation failure and on API error. Users can fix and retry without
retyping the email.
- Drop the t-select revert hack; with :model-value the rendered value
is already source-of-truth-on-row.role, so no setTimeout dance is
needed.
- Don't toast "no active tenant" on cold mount; watch the activeTenantId
ref and load once the auth store hydrates.
Frontend UX additions
- Empty state via t-empty (also covers the search-no-results case).
- Compact role-permissions matrix above the table, mirroring
OrganizationSettingsModal so Owners can see what each role grants
before promoting/inviting.
- Search box filtering by username/email (client-side, sufficient for
expected per-tenant member counts).
- "Leave Tenant" button — visible to any active member except the
last-Owner case (server still enforces).
Backend
- New POST /tenants/:id/leave route. Calls TenantMemberService.RemoveMember
with the caller's own user id so non-Owners can quit on their own.
Gated Viewer+ in the matrix; the existing last-Owner invariant in the
service still rejects last-Owner self-leave with 409.
- 3 new handler tests cover the happy path, the last-Owner 409 path,
and the missing-caller-id 401 path (defensive — auth middleware
always attaches a user, but the test pins the contract).
i18n
- Add tenantMember.permissions / search / leave / empty strings in all
four locales (en/zh/ko/ru).