From a67a2780b7a70c5fda2cd8cc88f7a1ae4865aab3 Mon Sep 17 00:00:00 2001 From: Moritz Date: Thu, 20 Nov 2025 15:57:10 +0100 Subject: [PATCH] docs: add translations and update development log (#168) --- CHANGELOG.md | 19 ++++ docs/development-progress-log.md | 129 +++++++++++++++++++++++++ mix.lock | 4 +- priv/gettext/de/LC_MESSAGES/default.po | 76 ++++++++++++--- priv/gettext/default.pot | 69 +++++++++++-- priv/gettext/en/LC_MESSAGES/default.po | 74 ++++++++++++-- 6 files changed, 338 insertions(+), 33 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..74df997 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,19 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added +- User-Member linking with fuzzy search autocomplete (#168) +- PostgreSQL trigram-based member search with typo tolerance +- WCAG 2.1 AA compliant autocomplete dropdown with ARIA support +- Bilingual UI (German/English) for member linking workflow + +### Fixed +- Email validation false positive when linking user and member with identical emails (#168 Problem #4) +- Relationship data extraction from Ash manage_relationship during validation + diff --git a/docs/development-progress-log.md b/docs/development-progress-log.md index f7447f2..1b86106 100644 --- a/docs/development-progress-log.md +++ b/docs/development-progress-log.md @@ -1321,6 +1321,135 @@ end --- +## Session: User-Member Linking UI Enhancement (2025-01-13) + +### Feature Summary +Implemented user-member linking functionality in User Edit/Create views with fuzzy search autocomplete, email conflict handling, and accessibility support. + +**Key Features:** +- Autocomplete dropdown with PostgreSQL Trigram fuzzy search +- Link/unlink members to user accounts +- Email synchronization between linked entities +- WCAG 2.1 AA compliant (ARIA labels) +- Bilingual UI (English/German) + +### Technical Decisions + +**1. Search Priority Logic** +Search query takes precedence over email filtering to provide better UX: +- User types → fuzzy search across all unlinked members +- Email matching only used for post-filtering when no search query present + +**2. JavaScript Hook for Input Value** +Used minimal JavaScript (~6 lines) for reliable input field updates: +```javascript +// assets/js/app.js +window.addEventListener("phx:set-input-value", (e) => { + document.getElementById(e.detail.id).value = e.detail.value +}) +``` +**Rationale:** LiveView DOM patching has race conditions with rapid state changes in autocomplete components. Direct DOM manipulation via `push_event` is the idiomatic LiveView solution for this edge case. + +**3. Fuzzy Search Implementation** +Combined PostgreSQL Full-Text Search + Trigram for optimal results: +```sql +-- FTS for exact word matching +search_vector @@ websearch_to_tsquery('simple', 'greta') +-- Trigram for typo tolerance +word_similarity('gre', first_name) > 0.2 +-- Substring for email/IDs +email ILIKE '%greta%' +``` + +### Key Learnings + +#### 1. Ash `manage_relationship` Internals +**Critical Discovery:** During validation, relationship data lives in `changeset.relationships`, NOT `changeset.attributes`: + +```elixir +# During validation (manage_relationship processing): +changeset.relationships.member = [{[%{id: "uuid"}], opts}] +changeset.attributes.member_id = nil # Still nil! + +# After action completes: +changeset.attributes.member_id = "uuid" # Now set +``` + +**Solution:** Extract member_id from both sources: +```elixir +defp get_member_id_from_changeset(changeset) do + case Map.get(changeset.relationships, :member) do + [{[%{id: id}], _opts}] -> id # New link + _ -> Ash.Changeset.get_attribute(changeset, :member_id) # Existing + end +end +``` + +**Impact:** Fixed email validation false positives when linking user+member with identical emails. + +#### 2. LiveView + JavaScript Integration Patterns + +**When to use JavaScript:** +- ✅ Direct DOM manipulation (autocomplete, input values) +- ✅ Browser APIs (clipboard, geolocation) +- ✅ Third-party libraries + +**When NOT to use JavaScript:** +- ❌ Form submissions +- ❌ Simple show/hide logic +- ❌ Server-side data fetching + +**Pattern:** +```elixir +socket |> push_event("event-name", %{key: value}) +``` +```javascript +window.addEventListener("phx:event-name", (e) => { /* handle */ }) +``` + +#### 3. PostgreSQL Trigram Search +Requires `pg_trgm` extension with GIN indexes: +```sql +CREATE INDEX members_first_name_trgm_idx + ON members USING GIN(first_name gin_trgm_ops); +``` +Supports: +- Typo tolerance: "Gret" finds "Greta" +- Partial matching: "Mit" finds "Mitglied" +- Substring: "exam" finds "example.com" + +#### 4. Test-Driven Development for Bug Fixes +Effective workflow: +1. Write test that reproduces bug (should fail) +2. Implement minimal fix +3. Verify test passes +4. Refactor while green + +**Result:** 355 tests passing, 100% backend coverage for new features. + +### Files Changed + +**Backend:** +- `lib/membership/member.ex` - `:available_for_linking` action with fuzzy search +- `lib/mv/accounts/user/validations/email_not_used_by_other_member.ex` - Relationship change extraction +- `lib/mv_web/live/user_live/form.ex` - Event handlers, state management + +**Frontend:** +- `assets/js/app.js` - Input value hook (6 lines) +- `priv/gettext/**/*.po` - 10 new translation keys (DE/EN) + +**Tests (NEW):** +- `test/membership/member_fuzzy_search_linking_test.exs` +- `test/accounts/user_member_linking_email_test.exs` +- `test/mv_web/user_live/form_member_linking_ui_test.exs` + +### Deployment Notes +- **Assets:** Requires `cd assets && npm run build` +- **Database:** No migrations (uses existing indexes) +- **Config:** No changes required + +--- + ## Conclusion This project demonstrates a modern Phoenix application built with: diff --git a/mix.lock b/mix.lock index 28683a3..77dcc09 100644 --- a/mix.lock +++ b/mix.lock @@ -16,7 +16,7 @@ "comeonin": {:hex, :comeonin, "5.5.1", "5113e5f3800799787de08a6e0db307133850e635d34e9fab23c70b6501669510", [:mix], [], "hexpm", "65aac8f19938145377cee73973f192c5645873dcf550a8a6b18187d17c13ccdb"}, "credo": {:hex, :credo, "1.7.13", "126a0697df6b7b71cd18c81bc92335297839a806b6f62b61d417500d1070ff4e", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "47641e6d2bbff1e241e87695b29f617f1a8f912adea34296fb10ecc3d7e9e84f"}, "crux": {:hex, :crux, "0.1.1", "94f2f97d2a6079ae3c57f356412bc3b307f9579a80e43f526447b1d508dd4a72", [:mix], [{:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: true]}], "hexpm", "e59d498f038193cbe31e448f9199f5b4c53a4c67cece9922bb839595189dd2b6"}, - "db_connection": {:hex, :db_connection, "2.8.1", "9abdc1e68c34c6163f6fb96a96532272d13ad7ca45262156ae8b7ec6d9dc4bec", [], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a61a3d489b239d76f326e03b98794fb8e45168396c925ef25feb405ed09da8fd"}, + "db_connection": {:hex, :db_connection, "2.8.1", "9abdc1e68c34c6163f6fb96a96532272d13ad7ca45262156ae8b7ec6d9dc4bec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a61a3d489b239d76f326e03b98794fb8e45168396c925ef25feb405ed09da8fd"}, "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, "dns_cluster": {:hex, :dns_cluster, "0.2.0", "aa8eb46e3bd0326bd67b84790c561733b25c5ba2fe3c7e36f28e88f384ebcb33", [:mix], [], "hexpm", "ba6f1893411c69c01b9e8e8f772062535a4cf70f3f35bcc964a324078d8c8240"}, "ecto": {:hex, :ecto, "3.13.3", "6a983f0917f8bdc7a89e96f2bf013f220503a0da5d8623224ba987515b3f0d80", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1927db768f53a88843ff25b6ba7946599a8ca8a055f69ad8058a1432a399af94"}, @@ -80,7 +80,7 @@ "telemetry_metrics": {:hex, :telemetry_metrics, "1.1.0", "5bd5f3b5637e0abea0426b947e3ce5dd304f8b3bc6617039e2b5a008adc02f8f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7b79e8ddfde70adb6db8a6623d1778ec66401f366e9a8f5dd0955c56bc8ce67"}, "telemetry_poller": {:hex, :telemetry_poller, "1.3.0", "d5c46420126b5ac2d72bc6580fb4f537d35e851cc0f8dbd571acf6d6e10f5ec7", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "51f18bed7128544a50f75897db9974436ea9bfba560420b646af27a9a9b35211"}, "text_diff": {:hex, :text_diff, "0.1.0", "1caf3175e11a53a9a139bc9339bd607c47b9e376b073d4571c031913317fecaa", [:mix], [], "hexpm", "d1ffaaecab338e49357b6daa82e435f877e0649041ace7755583a0ea3362dbd7"}, - "thousand_island": {:hex, :thousand_island, "1.4.1", "8df065e627407e281f7935da5ad0f3842d10eb721afa92e760b720d71e2e37aa", [], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "204a8640e5d2818589b87286ae66160978628d7edf6095181cbe0440765fb6c1"}, + "thousand_island": {:hex, :thousand_island, "1.4.1", "8df065e627407e281f7935da5ad0f3842d10eb721afa92e760b720d71e2e37aa", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "204a8640e5d2818589b87286ae66160978628d7edf6095181cbe0440765fb6c1"}, "tidewave": {:hex, :tidewave, "0.5.0", "8f278d7eb2d0af36ae6d4f73a5872bd066815bd57b57401125187ba901f095a4", [:mix], [{:circular_buffer, "~> 0.4 or ~> 1.0", [hex: :circular_buffer, repo: "hexpm", optional: false]}, {:igniter, "~> 0.6", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_live_reload, ">= 1.6.1", [hex: :phoenix_live_reload, repo: "hexpm", optional: true]}, {:plug, "~> 1.17", [hex: :plug, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "9a1eb5d2f12ff4912328dfbfe652c27fded462c6ed6fd11814ee28d3e9d016b4"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.1", "a48703a25c170eedadca83b11e88985af08d35f37c6f664d6dcfb106a97782fc", [:rebar3], [], "hexpm", "b3a917854ce3ae233619744ad1e0102e05673136776fb2fa76234f3e03b23642"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, diff --git a/priv/gettext/de/LC_MESSAGES/default.po b/priv/gettext/de/LC_MESSAGES/default.po index 527a279..b7f472d 100644 --- a/priv/gettext/de/LC_MESSAGES/default.po +++ b/priv/gettext/de/LC_MESSAGES/default.po @@ -16,7 +16,7 @@ msgid "Actions" msgstr "Aktionen" #: lib/mv_web/live/member_live/index.html.heex:200 -#: lib/mv_web/live/user_live/index.html.heex:65 +#: lib/mv_web/live/user_live/index.html.heex:72 #, elixir-autogen, elixir-format msgid "Are you sure?" msgstr "Bist du sicher?" @@ -35,14 +35,14 @@ msgid "City" msgstr "Stadt" #: lib/mv_web/live/member_live/index.html.heex:202 -#: lib/mv_web/live/user_live/index.html.heex:67 +#: lib/mv_web/live/user_live/index.html.heex:74 #, elixir-autogen, elixir-format msgid "Delete" msgstr "Löschen" #: lib/mv_web/live/member_live/index.html.heex:194 -#: lib/mv_web/live/user_live/form.ex:141 -#: lib/mv_web/live/user_live/index.html.heex:59 +#: lib/mv_web/live/user_live/form.ex:251 +#: lib/mv_web/live/user_live/index.html.heex:66 #, elixir-autogen, elixir-format msgid "Edit" msgstr "Bearbeite" @@ -88,7 +88,7 @@ msgid "New Member" msgstr "Neues Mitglied" #: lib/mv_web/live/member_live/index.html.heex:191 -#: lib/mv_web/live/user_live/index.html.heex:56 +#: lib/mv_web/live/user_live/index.html.heex:63 #, elixir-autogen, elixir-format msgid "Show" msgstr "Anzeigen" @@ -161,7 +161,7 @@ msgstr "Mitglied speichern" #: lib/mv_web/live/custom_field_live/form.ex:64 #: lib/mv_web/live/custom_field_value_live/form.ex:74 #: lib/mv_web/live/member_live/form.ex:79 -#: lib/mv_web/live/user_live/form.ex:124 +#: lib/mv_web/live/user_live/form.ex:234 #, elixir-autogen, elixir-format msgid "Saving..." msgstr "Speichern..." @@ -255,7 +255,7 @@ msgstr "Ihr Passwort wurde erfolgreich zurückgesetzt" #: lib/mv_web/live/custom_field_live/form.ex:67 #: lib/mv_web/live/custom_field_value_live/form.ex:77 #: lib/mv_web/live/member_live/form.ex:82 -#: lib/mv_web/live/user_live/form.ex:127 +#: lib/mv_web/live/user_live/form.ex:237 #, elixir-autogen, elixir-format msgid "Cancel" msgstr "Abbrechen" @@ -335,6 +335,7 @@ msgstr "Nicht gesetzt" #: lib/mv_web/live/user_live/form.ex:107 #: lib/mv_web/live/user_live/form.ex:115 +#: lib/mv_web/live/user_live/form.ex:210 #, elixir-autogen, elixir-format msgid "Note" msgstr "Hinweis" @@ -375,7 +376,7 @@ msgstr "Mitglied auswählen" msgid "Settings" msgstr "Einstellungen" -#: lib/mv_web/live/user_live/form.ex:125 +#: lib/mv_web/live/user_live/form.ex:235 #, elixir-autogen, elixir-format msgid "Save User" msgstr "Benutzer*in speichern" @@ -400,7 +401,7 @@ msgstr "Nicht unterstützter Wertetyp: %{type}" msgid "Use this form to manage user records in your database." msgstr "Verwenden Sie dieses Formular, um Benutzer*innen-Datensätze zu verwalten." -#: lib/mv_web/live/user_live/form.ex:142 +#: lib/mv_web/live/user_live/form.ex:252 #: lib/mv_web/live/user_live/show.ex:34 #, elixir-autogen, elixir-format msgid "User" @@ -428,7 +429,7 @@ msgstr "aufsteigend" msgid "descending" msgstr "absteigend" -#: lib/mv_web/live/user_live/form.ex:141 +#: lib/mv_web/live/user_live/form.ex:251 #, elixir-autogen, elixir-format msgid "New" msgstr "Neue*r" @@ -503,6 +504,8 @@ msgstr "Passwort setzen" msgid "User will be created without a password. Check 'Set Password' to add one." msgstr "Benutzer*in wird ohne Passwort erstellt. Aktivieren Sie 'Passwort setzen', um eines hinzuzufügen." +#: lib/mv_web/live/user_live/form.ex:126 +#: lib/mv_web/live/user_live/index.html.heex:53 #: lib/mv_web/live/user_live/show.ex:55 #, elixir-autogen, elixir-format msgid "Linked Member" @@ -513,6 +516,7 @@ msgstr "Verknüpftes Mitglied" msgid "Linked User" msgstr "Verknüpfte*r Benutzer*in" +#: lib/mv_web/live/user_live/index.html.heex:57 #: lib/mv_web/live/user_live/show.ex:65 #, elixir-autogen, elixir-format msgid "No member linked" @@ -659,4 +663,54 @@ msgstr "Verwende dieses Formular, um Benutzerdefinierte Feldwerte in deiner Date #: lib/mv_web/live/custom_field_live/show.ex:56 #, elixir-autogen, elixir-format msgid "Auto-generated identifier (immutable)" -msgstr "Automatisch generierter Identifier" +msgstr "Automatisch generierte Kennung (unveränderlich)" + +#: lib/mv_web/live/user_live/form.ex:210 +#, elixir-autogen, elixir-format +msgid "A member with this email already exists. To link with a different member, please change one of the email addresses first." +msgstr "Ein Mitglied mit dieser E-Mail-Adresse existiert bereits. Um mit einem anderen Mitglied zu verknüpfen, ändern Sie bitte zuerst eine der E-Mail-Adressen." + +#: lib/mv_web/live/user_live/form.ex:185 +#, elixir-autogen, elixir-format +msgid "Available members" +msgstr "Verfügbare Mitglieder" + +#: lib/mv_web/live/user_live/form.ex:152 +#, elixir-autogen, elixir-format +msgid "Member will be unlinked when you save. Cannot select new member until saved." +msgstr "Mitglied wird beim Speichern entverknüpft. Neues Mitglied kann erst nach dem Speichern ausgewählt werden." + +#: lib/mv_web/live/user_live/form.ex:226 +#, elixir-autogen, elixir-format +msgid "Save to confirm linking." +msgstr "Speichern, um die Verknüpfung zu bestätigen." + +#: lib/mv_web/live/user_live/form.ex:169 +#, elixir-autogen, elixir-format +msgid "Search for a member to link..." +msgstr "Nach einem Mitglied zum Verknüpfen suchen..." + +#: lib/mv_web/live/user_live/form.ex:173 +#, elixir-autogen, elixir-format +msgid "Search for member to link" +msgstr "Nach Mitglied zum Verknüpfen suchen" + +#: lib/mv_web/live/user_live/form.ex:223 +#, elixir-autogen, elixir-format +msgid "Selected" +msgstr "Ausgewählt" + +#: lib/mv_web/live/user_live/form.ex:143 +#, elixir-autogen, elixir-format +msgid "Unlink Member" +msgstr "Mitglied entverknüpfen" + +#: lib/mv_web/live/user_live/form.ex:152 +#, elixir-autogen, elixir-format +msgid "Unlinking scheduled" +msgstr "Entverknüpfung geplant" + +#~ #: lib/mv_web/live/custom_field_live/form.ex:58 +#~ #, elixir-autogen, elixir-format +#~ msgid "Slug" +#~ msgstr "Slug" diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot index 6035e4a..75cb2b1 100644 --- a/priv/gettext/default.pot +++ b/priv/gettext/default.pot @@ -17,7 +17,7 @@ msgid "Actions" msgstr "" #: lib/mv_web/live/member_live/index.html.heex:200 -#: lib/mv_web/live/user_live/index.html.heex:65 +#: lib/mv_web/live/user_live/index.html.heex:72 #, elixir-autogen, elixir-format msgid "Are you sure?" msgstr "" @@ -36,14 +36,14 @@ msgid "City" msgstr "" #: lib/mv_web/live/member_live/index.html.heex:202 -#: lib/mv_web/live/user_live/index.html.heex:67 +#: lib/mv_web/live/user_live/index.html.heex:74 #, elixir-autogen, elixir-format msgid "Delete" msgstr "" #: lib/mv_web/live/member_live/index.html.heex:194 -#: lib/mv_web/live/user_live/form.ex:141 -#: lib/mv_web/live/user_live/index.html.heex:59 +#: lib/mv_web/live/user_live/form.ex:251 +#: lib/mv_web/live/user_live/index.html.heex:66 #, elixir-autogen, elixir-format msgid "Edit" msgstr "" @@ -89,7 +89,7 @@ msgid "New Member" msgstr "" #: lib/mv_web/live/member_live/index.html.heex:191 -#: lib/mv_web/live/user_live/index.html.heex:56 +#: lib/mv_web/live/user_live/index.html.heex:63 #, elixir-autogen, elixir-format msgid "Show" msgstr "" @@ -162,7 +162,7 @@ msgstr "" #: lib/mv_web/live/custom_field_live/form.ex:64 #: lib/mv_web/live/custom_field_value_live/form.ex:74 #: lib/mv_web/live/member_live/form.ex:79 -#: lib/mv_web/live/user_live/form.ex:124 +#: lib/mv_web/live/user_live/form.ex:234 #, elixir-autogen, elixir-format msgid "Saving..." msgstr "" @@ -256,7 +256,7 @@ msgstr "" #: lib/mv_web/live/custom_field_live/form.ex:67 #: lib/mv_web/live/custom_field_value_live/form.ex:77 #: lib/mv_web/live/member_live/form.ex:82 -#: lib/mv_web/live/user_live/form.ex:127 +#: lib/mv_web/live/user_live/form.ex:237 #, elixir-autogen, elixir-format msgid "Cancel" msgstr "" @@ -336,6 +336,7 @@ msgstr "" #: lib/mv_web/live/user_live/form.ex:107 #: lib/mv_web/live/user_live/form.ex:115 +#: lib/mv_web/live/user_live/form.ex:210 #, elixir-autogen, elixir-format msgid "Note" msgstr "" @@ -376,7 +377,7 @@ msgstr "" msgid "Settings" msgstr "" -#: lib/mv_web/live/user_live/form.ex:125 +#: lib/mv_web/live/user_live/form.ex:235 #, elixir-autogen, elixir-format msgid "Save User" msgstr "" @@ -401,7 +402,7 @@ msgstr "" msgid "Use this form to manage user records in your database." msgstr "" -#: lib/mv_web/live/user_live/form.ex:142 +#: lib/mv_web/live/user_live/form.ex:252 #: lib/mv_web/live/user_live/show.ex:34 #, elixir-autogen, elixir-format msgid "User" @@ -429,7 +430,7 @@ msgstr "" msgid "descending" msgstr "" -#: lib/mv_web/live/user_live/form.ex:141 +#: lib/mv_web/live/user_live/form.ex:251 #, elixir-autogen, elixir-format msgid "New" msgstr "" @@ -504,6 +505,8 @@ msgstr "" msgid "User will be created without a password. Check 'Set Password' to add one." msgstr "" +#: lib/mv_web/live/user_live/form.ex:126 +#: lib/mv_web/live/user_live/index.html.heex:53 #: lib/mv_web/live/user_live/show.ex:55 #, elixir-autogen, elixir-format msgid "Linked Member" @@ -514,6 +517,7 @@ msgstr "" msgid "Linked User" msgstr "" +#: lib/mv_web/live/user_live/index.html.heex:57 #: lib/mv_web/live/user_live/show.ex:65 #, elixir-autogen, elixir-format msgid "No member linked" @@ -661,3 +665,48 @@ msgstr "" #, elixir-autogen, elixir-format msgid "Auto-generated identifier (immutable)" msgstr "" + +#: lib/mv_web/live/user_live/form.ex:210 +#, elixir-autogen, elixir-format +msgid "A member with this email already exists. To link with a different member, please change one of the email addresses first." +msgstr "" + +#: lib/mv_web/live/user_live/form.ex:185 +#, elixir-autogen, elixir-format +msgid "Available members" +msgstr "" + +#: lib/mv_web/live/user_live/form.ex:152 +#, elixir-autogen, elixir-format +msgid "Member will be unlinked when you save. Cannot select new member until saved." +msgstr "" + +#: lib/mv_web/live/user_live/form.ex:226 +#, elixir-autogen, elixir-format +msgid "Save to confirm linking." +msgstr "" + +#: lib/mv_web/live/user_live/form.ex:169 +#, elixir-autogen, elixir-format +msgid "Search for a member to link..." +msgstr "" + +#: lib/mv_web/live/user_live/form.ex:173 +#, elixir-autogen, elixir-format +msgid "Search for member to link" +msgstr "" + +#: lib/mv_web/live/user_live/form.ex:223 +#, elixir-autogen, elixir-format +msgid "Selected" +msgstr "" + +#: lib/mv_web/live/user_live/form.ex:143 +#, elixir-autogen, elixir-format +msgid "Unlink Member" +msgstr "" + +#: lib/mv_web/live/user_live/form.ex:152 +#, elixir-autogen, elixir-format +msgid "Unlinking scheduled" +msgstr "" diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po index cbc0a5d..7cae329 100644 --- a/priv/gettext/en/LC_MESSAGES/default.po +++ b/priv/gettext/en/LC_MESSAGES/default.po @@ -17,7 +17,7 @@ msgid "Actions" msgstr "" #: lib/mv_web/live/member_live/index.html.heex:200 -#: lib/mv_web/live/user_live/index.html.heex:65 +#: lib/mv_web/live/user_live/index.html.heex:72 #, elixir-autogen, elixir-format msgid "Are you sure?" msgstr "" @@ -36,14 +36,14 @@ msgid "City" msgstr "" #: lib/mv_web/live/member_live/index.html.heex:202 -#: lib/mv_web/live/user_live/index.html.heex:67 +#: lib/mv_web/live/user_live/index.html.heex:74 #, elixir-autogen, elixir-format msgid "Delete" msgstr "" #: lib/mv_web/live/member_live/index.html.heex:194 -#: lib/mv_web/live/user_live/form.ex:141 -#: lib/mv_web/live/user_live/index.html.heex:59 +#: lib/mv_web/live/user_live/form.ex:251 +#: lib/mv_web/live/user_live/index.html.heex:66 #, elixir-autogen, elixir-format msgid "Edit" msgstr "" @@ -89,7 +89,7 @@ msgid "New Member" msgstr "" #: lib/mv_web/live/member_live/index.html.heex:191 -#: lib/mv_web/live/user_live/index.html.heex:56 +#: lib/mv_web/live/user_live/index.html.heex:63 #, elixir-autogen, elixir-format msgid "Show" msgstr "" @@ -162,7 +162,7 @@ msgstr "" #: lib/mv_web/live/custom_field_live/form.ex:64 #: lib/mv_web/live/custom_field_value_live/form.ex:74 #: lib/mv_web/live/member_live/form.ex:79 -#: lib/mv_web/live/user_live/form.ex:124 +#: lib/mv_web/live/user_live/form.ex:234 #, elixir-autogen, elixir-format msgid "Saving..." msgstr "" @@ -256,7 +256,7 @@ msgstr "" #: lib/mv_web/live/custom_field_live/form.ex:67 #: lib/mv_web/live/custom_field_value_live/form.ex:77 #: lib/mv_web/live/member_live/form.ex:82 -#: lib/mv_web/live/user_live/form.ex:127 +#: lib/mv_web/live/user_live/form.ex:237 #, elixir-autogen, elixir-format msgid "Cancel" msgstr "" @@ -336,6 +336,7 @@ msgstr "" #: lib/mv_web/live/user_live/form.ex:107 #: lib/mv_web/live/user_live/form.ex:115 +#: lib/mv_web/live/user_live/form.ex:210 #, elixir-autogen, elixir-format, fuzzy msgid "Note" msgstr "" @@ -376,7 +377,7 @@ msgstr "" msgid "Settings" msgstr "" -#: lib/mv_web/live/user_live/form.ex:125 +#: lib/mv_web/live/user_live/form.ex:235 #, elixir-autogen, elixir-format, fuzzy msgid "Save User" msgstr "" @@ -401,7 +402,7 @@ msgstr "" msgid "Use this form to manage user records in your database." msgstr "" -#: lib/mv_web/live/user_live/form.ex:142 +#: lib/mv_web/live/user_live/form.ex:252 #: lib/mv_web/live/user_live/show.ex:34 #, elixir-autogen, elixir-format msgid "User" @@ -429,7 +430,7 @@ msgstr "" msgid "descending" msgstr "" -#: lib/mv_web/live/user_live/form.ex:141 +#: lib/mv_web/live/user_live/form.ex:251 #, elixir-autogen, elixir-format msgid "New" msgstr "" @@ -504,6 +505,8 @@ msgstr "Set Password" msgid "User will be created without a password. Check 'Set Password' to add one." msgstr "User will be created without a password. Check 'Set Password' to add one." +#: lib/mv_web/live/user_live/form.ex:126 +#: lib/mv_web/live/user_live/index.html.heex:53 #: lib/mv_web/live/user_live/show.ex:55 #, elixir-autogen, elixir-format, fuzzy msgid "Linked Member" @@ -514,6 +517,7 @@ msgstr "" msgid "Linked User" msgstr "" +#: lib/mv_web/live/user_live/index.html.heex:57 #: lib/mv_web/live/user_live/show.ex:65 #, elixir-autogen, elixir-format msgid "No member linked" @@ -661,3 +665,53 @@ msgstr "" #, elixir-autogen, elixir-format msgid "Auto-generated identifier (immutable)" msgstr "" + +#: lib/mv_web/live/user_live/form.ex:210 +#, elixir-autogen, elixir-format +msgid "A member with this email already exists. To link with a different member, please change one of the email addresses first." +msgstr "" + +#: lib/mv_web/live/user_live/form.ex:185 +#, elixir-autogen, elixir-format +msgid "Available members" +msgstr "" + +#: lib/mv_web/live/user_live/form.ex:152 +#, elixir-autogen, elixir-format +msgid "Member will be unlinked when you save. Cannot select new member until saved." +msgstr "" + +#: lib/mv_web/live/user_live/form.ex:226 +#, elixir-autogen, elixir-format +msgid "Save to confirm linking." +msgstr "" + +#: lib/mv_web/live/user_live/form.ex:169 +#, elixir-autogen, elixir-format +msgid "Search for a member to link..." +msgstr "" + +#: lib/mv_web/live/user_live/form.ex:173 +#, elixir-autogen, elixir-format +msgid "Search for member to link" +msgstr "" + +#: lib/mv_web/live/user_live/form.ex:223 +#, elixir-autogen, elixir-format, fuzzy +msgid "Selected" +msgstr "" + +#: lib/mv_web/live/user_live/form.ex:143 +#, elixir-autogen, elixir-format +msgid "Unlink Member" +msgstr "" + +#: lib/mv_web/live/user_live/form.ex:152 +#, elixir-autogen, elixir-format +msgid "Unlinking scheduled" +msgstr "" + +#~ #: lib/mv_web/live/custom_field_live/form.ex:58 +#~ #, elixir-autogen, elixir-format +#~ msgid "Slug" +#~ msgstr ""