Files
sure/config/locales/views/admin/users/en.yml
Juan José Mata 02af8463f6 Administer invitations in /admin/users (#1185)
* Add invited users with delete button to admin users page

Shows pending invitations per family below active users in /admin/users/.
Each invitation row has a red Delete button aligned with the role column.
Alt/option-clicking any Delete button changes all invitation button labels
to "Delete All" and destroys all pending invitations for that family.

- Add admin routes: DELETE /admin/invitations/:id and DELETE /admin/families/:id/invitations
- Add Admin::InvitationsController with destroy and destroy_all actions
- Load pending invitations grouped by family in users controller index
- Render invitation rows in a dashed-border tbody below active user rows
- Add admin-invitation-delete Stimulus controller for alt-click behavior
- Add i18n strings for invitation UI and flash messages

https://claude.ai/code/session_01F8WaH5TmtdUWwhHnVoQ6Gm

* Fix destroy_all using params[:id] from member route

The member route /admin/families/:id/invitations sets params[:id],
not params[:family_id], so Family.find was always receiving nil.

https://claude.ai/code/session_01F8WaH5TmtdUWwhHnVoQ6Gm

* Fix translation key in destroy_all to match locale

t(".success_all") looked up a nonexistent key; the locale defines
admin.invitations.destroy_all.success, so t(".success") is correct.

https://claude.ai/code/session_01F8WaH5TmtdUWwhHnVoQ6Gm

* Scope bulk delete to pending invitations and allow re-inviting emails

- destroy_all now uses family.invitations.pending.destroy_all so accepted
  and expired invitation history is preserved
- Replace blanket email uniqueness validation with a custom check scoped
  to pending invitations only, so the same email can be invited again
  after an invitation is deleted or expires

https://claude.ai/code/session_01F8WaH5TmtdUWwhHnVoQ6Gm

* Drop unconditional unique DB index on invitations(email, family_id)

The model-level uniqueness check was already scoped to pending
invitations, but the blanket unique index on (email, family_id)
still caused ActiveRecord::RecordNotUnique when re-inviting an
email that had any historical invitation record in the same family
(e.g. after an accepted invite or after an account deletion).

Replace it with no DB-level unique constraint — the
no_duplicate_pending_invitation_in_family model validation is the
sole enforcer and correctly scopes uniqueness to pending rows only.

https://claude.ai/code/session_01F8WaH5TmtdUWwhHnVoQ6Gm

* Replace blanket unique index with partial unique index on pending invitations

Instead of dropping the DB-level uniqueness constraint entirely, replace
the unconditional unique index on (email, family_id) with a partial unique
index scoped to WHERE accepted_at IS NULL. This enforces the invariant at
the DB layer (no two non-accepted invitations for the same email in a
family) while allowing re-invites once a prior invitation has been accepted.

https://claude.ai/code/session_01F8WaH5TmtdUWwhHnVoQ6Gm

* Fix migration version and make remove_index reversible

- Change Migration[8.0] to Migration[7.2] to match the rest of the codebase
- Pass column names to remove_index so Rails can reconstruct the old index on rollback

https://claude.ai/code/session_01F8WaH5TmtdUWwhHnVoQ6Gm

---------

Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-03-14 11:32:33 +01:00

54 lines
2.1 KiB
YAML

---
en:
admin:
users:
index:
title: "User Management"
description: "Manage user roles for your instance. Super admins can access SSO provider settings and user management."
section_title: "Families / Groups"
you: "(You)"
trial_ends_at: "Trial ends"
not_available: "n/a"
no_users: "No users found."
unnamed_family: "Unnamed Family/Group"
no_subscription: "No subscription"
family_summary: "%{members} members · %{accounts} accounts · %{transactions} transactions"
filters:
role: "Role"
role_all: "All roles"
trial_status: "Trial status"
trial_all: "All"
trial_expiring_soon: "Expiring in 7 days"
trial_trialing: "On trial"
submit: "Filter"
summary:
trials_expiring_7_days: "Trials expiring in next 7 days"
table:
user: "User"
trial_ends_at: "Trial ends"
family_accounts: "Family accounts"
family_transactions: "Family transactions"
last_login: "Last login"
session_count: "Session count"
never: "Never"
role: "Role"
role_descriptions_title: "Role Descriptions"
roles:
guest: "Guest"
member: "Member"
admin: "Admin"
super_admin: "Super Admin"
role_descriptions:
guest: "Assistant-first experience with intentionally restricted permissions for intro workflows."
member: "Basic user access. Can manage their own accounts, transactions, and settings."
admin: "Family administrator. Can access advanced settings like API keys, imports, and AI prompts."
super_admin: "Instance administrator. Can manage SSO providers, user roles, and impersonate users for support."
invitations:
pending_label: "Invited (pending)"
expires: "Expires %{date}"
delete: "Delete"
delete_all: "Delete All"
update:
success: "User role updated successfully."
failure: "Failed to update user role."