From 906ac801bef5791bbf434e25319c60e2e9c5a283 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Fri, 20 Mar 2026 18:14:59 +0100 Subject: [PATCH] refactor(webapp): vue 3 migration - ds-form (#9407) --- packages/ui/KATALOG.md | 45 ++++---- packages/ui/PROJEKT.md | 24 +++-- webapp/components/CommentForm/CommentForm.vue | 58 +++++----- .../ContributionForm/ContributionForm.vue | 55 +++++----- webapp/components/EnterNonce/EnterNonce.vue | 16 ++- webapp/components/Group/GroupForm.vue | 51 ++++----- webapp/components/Password/Change.spec.js | 3 +- webapp/components/Password/Change.vue | 83 +++++++------- .../PasswordReset/ChangePassword.vue | 74 ++++++------- webapp/components/PasswordReset/Request.vue | 16 ++- .../Registration/RegistrationSlideCreate.vue | 14 +-- .../Registration/RegistrationSlideEmail.vue | 12 +-- .../Registration/RegistrationSlideInvite.vue | 12 +-- .../Registration/RegistrationSlideNonce.vue | 16 ++- webapp/components/Registration/Signup.vue | 15 ++- .../MySomethingList/MySomethingList.vue | 15 ++- webapp/components/utils/UniqueSlugForm.js | 52 ++++++--- webapp/mixins/formValidation.js | 102 ++++++++++++++++++ webapp/pages/admin/donations.vue | 6 +- .../users/__snapshots__/index.spec.js.snap | 4 - webapp/pages/admin/users/index.vue | 16 +-- .../password-reset/change-password.spec.js | 2 +- .../pages/password-reset/enter-nonce.spec.js | 2 +- webapp/pages/password-reset/request.spec.js | 2 +- webapp/pages/settings/index.spec.js | 12 +-- webapp/pages/settings/index.vue | 90 ++++++++-------- .../settings/my-email-address/enter-nonce.vue | 69 ++++++------ .../pages/settings/my-email-address/index.vue | 70 ++++++------ 28 files changed, 521 insertions(+), 415 deletions(-) create mode 100644 webapp/mixins/formValidation.js diff --git a/packages/ui/KATALOG.md b/packages/ui/KATALOG.md index b8c83f7ed..2ad2472c5 100644 --- a/packages/ui/KATALOG.md +++ b/packages/ui/KATALOG.md @@ -14,7 +14,8 @@ Phase 3: OsButton ██████████ 100% (133/133 Buttons) ✅ Phase 4: Tier 1 ██████████ 100% (OsButton, OsIcon, OsSpinner, OsCard) ✅ Phase 4: Tier A → HTML ██████████ 100% (10 ds-* Wrapper → Plain HTML) ✅ Phase 4: Tier B ██████████ 100% (ds-chip→OsBadge✅, ds-tag→OsBadge✅, ds-grid✅, ds-number→OsNumber✅, ds-radio→HTML✅) -Phase 4: Tier B+ ████████░░ 75% (ds-table→HTML✅) | Tier 2 begonnen (OsModal✅) | Rest ausstehend (OsInput, OsMenu, OsSelect) +Phase 4: Tier B ██████████ 100% (Chip→OsBadge, Tag→OsBadge, Grid→HTML, Number→OsNumber, Radio→HTML, Table→HTML) ✅ +Phase 4: Tier 2+ ██████░░░░ 50% (OsModal✅, ds-form entkoppelt✅) | Rest ausstehend (OsInput, OsMenu, OsSelect, OsDropdown, OsAvatar) ``` ### Statistiken @@ -29,7 +30,7 @@ Phase 4: Tier B+ ████████░░ 75% (ds-table→HTML✅) | ✅ → OsNumber | Number (5 Nutzungen → OsNumber, CountTo.vue gelöscht, vue-count-to entfernt) | | ✅ → Plain HTML | Radio (1 Datei → native `` in ReportModal) | | ⬜ → UI-Library | Modal, Input, Menu, MenuItem, Select (5) — Tier 2-3 | -| ⬜ Offen | Form (18 Dateien — HTML oder OsForm?) | +| ✅ ds-form entkoppelt | Form-Validierung → formValidation Mixin (async-validator), ds-input/ds-select bleiben als UI-Komponenten | | ⬜ Nicht in Webapp | Code, CopyField, FormItem, InputError, InputLabel, Page, PageTitle, Logo, Avatar, TableCol, TableHeadCol (11) | ### OsButton Migration (Phase 3) ✅ @@ -38,7 +39,7 @@ Phase 4: Tier B+ ████████░░ 75% (ds-table→HTML✅) **Erkenntnisse aus der Migration:** - `type="submit"` muss explizit gesetzt werden (OsButton Default: `type="button"`) -- DsForm `errors` ist ein Objekt → `!!errors` für Boolean-Cast bei `:disabled` +- `formErrors` ist ein Objekt → `!!formErrors` für Boolean-Cast bei `:disabled` - CSS `.base-button` Selektoren → `> button` oder `button` - Filter-Buttons nutzen `:appearance="condition ? 'filled' : 'outline'"` Pattern - Circle-Buttons mit Icon: `` @@ -438,19 +439,19 @@ Phase 4: Tier B+ ████████░░ 75% (ds-table→HTML✅) 14. [x] ds-chip (5 Dateien, 20 Nutzungen) → OsBadge (UI-Library) 15. [x] ds-tag (3 Dateien) → OsBadge shape="square" (UI-Library) 16. [x] ds-grid / ds-grid-item (10 Dateien) → CSS Grid (Plain HTML) -17. [ ] ds-number (5 Dateien) → `
` +17. [x] ds-number (5 Dateien) → OsNumber (UI-Library) ✅ 18. [x] ds-radio (1 Datei) → native `` + `
` (ReportModal) ✅ ### Phase 4: Tier 2-4 — UI-Library -18. [x] OsModal (h() Render, Focus-Trap, Scroll-Lock, A11y; ConfirmModal + ReportModal nutzen OsModal; DeleteUserModal/DisableModal/ReleaseModal gelöscht) ✅ -19. [ ] OsInput (23 Dateien, gekoppelt mit ds-form) -20. [ ] OsMenu / OsMenuItem (17 Dateien) -21. [ ] OsSelect (3 Dateien), OsTable (7 Dateien) -22. [ ] ds-form → HTML `
` oder OsForm (18 Dateien) +19. [x] OsModal (h() Render, Focus-Trap, Scroll-Lock, A11y; ConfirmModal + ReportModal nutzen OsModal; DeleteUserModal/DisableModal/ReleaseModal gelöscht) ✅ +20. [x] ds-form → formValidation Mixin (async-validator), 18 Dateien migriert, vuelidate entfernt ✅ +21. [ ] OsInput (23 Dateien) +22. [ ] OsMenu / OsMenuItem (17 Dateien) +23. [ ] OsSelect (3 Dateien), OsTable (7 Dateien) --- -**✅ Phase 0-3 abgeschlossen. Phase 4: Tier 1 + Tier A ✅, Tier B 80% (Chip→OsBadge, Tag→OsBadge, Grid→HTML, Number→OsNumber, Table→HTML), Tier 2: OsModal ✅, Rest ausstehend.** +**✅ Phase 0-3 abgeschlossen. Phase 4: Tier 1 + Tier A ✅, Tier B ✅ (Chip→OsBadge, Tag→OsBadge, Grid→HTML, Number→OsNumber, Radio→HTML, Table→HTML), Tier 2: OsModal ✅, ds-form entkoppelt ✅, Rest ausstehend (OsInput, OsMenu, OsSelect).** --- @@ -948,14 +949,14 @@ interface OsDropdownProps { | — | ds-space | ✅ → div + Margin-Utility-Klassen | | — | ds-flex, ds-flex-item | ✅ → HTML + CSS @media Queries | -### Tier B: Einfache ds-* Migration (60%) +### Tier B: Einfache ds-* Migration ✅ | # | Komponente | Dateien | Ziel | Status | |---|------------|---------|------|--------| | 5 | **OsBadge** | — | ds-chip (20 Nutzungen, 5 Dateien) + ds-tag (3 Dateien) | ✅ | | — | ds-grid / ds-grid-item | 10 | CSS Grid (Plain HTML) | ✅ | -| — | ds-number | 5 | `
` | ⬜ | -| — | ds-radio | 1 | native `` | ⬜ | +| — | ds-number | 5 | OsNumber (UI-Library) | ✅ | +| — | ds-radio | 1 | native `` | ✅ | ### Tier 2: Layout & Feedback @@ -964,7 +965,7 @@ interface OsDropdownProps { | 5 | **OsModal** | 7 | OsButton | ✅ | | 6 | **OsDropdown** | — | OsButton | ⬜ | | 7 | **OsAvatar** | — | - | ⬜ | -| 8 | **OsInput** | 23 | gekoppelt mit ds-form (18 Dateien) | ⬜ | +| 8 | **OsInput** | 23 | ds-form Kopplung aufgelöst (formValidation Mixin) | ⬜ | ### Tier 3: Navigation (ausstehend) @@ -979,7 +980,7 @@ interface OsDropdownProps { |---|------------|---------| | 11 | OsSelect | 3 | | 12 | OsTable | 7 | -| 13 | ds-form → HTML `` oder OsForm | 18 | +| 13 | ~~ds-form~~ | — | ✅ entkoppelt via formValidation Mixin (async-validator) | > **Hinweis:** OsHeading, OsText, OsTag sind nicht mehr geplant — wurden zu Plain HTML migriert (Tier A). @@ -1014,17 +1015,17 @@ ds-flex, ds-flex-item ✅ → HTML + CSS @media Q ds-chip → OsBadge (UI-Library) ✅ ds-tag → OsBadge shape="square" (UI-Library) ✅ ds-grid / ds-grid-item → CSS Grid (HTML) ✅ -ds-number → Plain HTML ⬜ (5 Dateien) -ds-radio → native ⬜ (1 Datei) +ds-number → OsNumber (UI-Library) ✅ +ds-radio → native ✅ (1 Datei) ``` -### Tier 2-4: UI-Library (ausstehend) +### Tier 2-4: UI-Library ``` -5. OsModal → Basis: DsModal, Feature-Modals bleiben in Webapp +5. OsModal → Basis: DsModal, Feature-Modals bleiben in Webapp ✅ 6. OsDropdown → Basis: Dropdown (Webapp) — wichtiger als gedacht! 7. OsAvatar → Vereint: DsAvatar + ProfileAvatar -8. OsInput → Basis: DsInput, gekoppelt mit ds-form +8. OsInput → Basis: DsInput (ds-form Kopplung aufgelöst via formValidation Mixin) 9. OsMenu → Basis: DsMenu/DsMenuItem 10. OsSelect → Basis: DsSelect 11. OsTable → Basis: DsTable @@ -1047,8 +1048,8 @@ ds-radio → native ⬜ (1 Datei) ### Noch offen: 1. **Logo** - Existiert doppelt (Webapp + Styleguide) -2. **Modal** - Existiert doppelt (Webapp Modal.vue ist Modal-Router, DsModal ist UI) -3. **ds-form Kopplung** - ds-input und ds-form sind stark gekoppelt (Schema-Validation) +2. ~~**Modal** - Existiert doppelt~~ → OsModal migriert ✅ +3. ~~**ds-form Kopplung**~~ → aufgelöst via formValidation Mixin (async-validator), vuelidate entfernt ✅ --- diff --git a/packages/ui/PROJEKT.md b/packages/ui/PROJEKT.md index 52c41d225..2986b2e1f 100644 --- a/packages/ui/PROJEKT.md +++ b/packages/ui/PROJEKT.md @@ -289,7 +289,8 @@ ds-chip + ds-tag → OsBadge (UI-Library): ✅ - [x] 0 Tier-A `ds-*` Komponenten-Tags verbleibend **Verbleibende ds-* Komponenten (6 Typen):** -- Tier C (→ UI-Library): ds-input (23), ds-form (18), ds-modal (7), ds-menu/ds-menu-item (17), ds-select (3) +- Tier C (→ UI-Library): ds-input (23), ds-modal (7→✅ OsModal), ds-menu/ds-menu-item (17), ds-select (3) +- ✅ ds-form (18 Dateien) → formValidation Mixin (async-validator), vuelidate entfernt **Zuvor abgeschlossen (Session 26 - CodeRabbit Review Fixes):** - [x] Cypress: `.os-card .title` → `.os-card > .title` (Kind-Kombinator statt Nachfahren) @@ -415,7 +416,8 @@ ds-chip + ds-tag → OsBadge (UI-Library): ✅ - [ ] Tier B (Rest): ds-radio → Plain HTML - [x] OsModal Komponente + DsModal/ConfirmModal/ReportModal → OsModal Webapp-Integration ✅ - [ ] Weitere Tier 2 Komponenten (OsDropdown, OsAvatar, OsInput) -- [ ] ds-form + ds-input → OsForm + OsInput (stark gekoppelt, 18+23 Dateien) +- [x] ds-form → formValidation Mixin (async-validator), 18 Dateien migriert, vuelidate entfernt ✅ +- [ ] ds-input → OsInput (23 Dateien, ds-form Kopplung aufgelöst) - [ ] ds-menu / ds-menu-item → OsMenu / OsMenuItem - [ ] ds-select → OsSelect - [ ] Browser-Fehler untersuchen: `TypeError: Cannot read properties of undefined (reading 'heartO')` (ocelotIcons undefined im Browser trotz korrekter Webpack-Aliase) @@ -686,13 +688,13 @@ Jeder migrierte Button muss manuell geprüft werden: Normal, Hover, Focus, Activ - [x] ds-tag (3 Dateien) → OsBadge shape="square" (UI-Library) ✅ - [x] ds-number (5 Dateien) → OsNumber (UI-Library) ✅ + CountTo.vue gelöscht, vue-count-to entfernt - [x] ds-grid / ds-grid-item (10 Dateien) → CSS Grid ✅ -- [ ] ds-radio (1 Datei) → native `` +- [x] ds-radio (1 Datei) → native `` ✅ **Tier 2: Layout & Feedback (UI-Library)** - [x] OsModal (Basis: DsModal → h() Render-Function, Vue 2/3 Compat, Focus-Trap, Scroll-Lock, A11y) ✅ - [ ] OsDropdown (Basis: Webapp Dropdown) - [ ] OsAvatar (vereint DsAvatar + ProfileAvatar) -- [ ] OsInput (Basis: DsInput, 23 Dateien — gekoppelt mit ds-form) +- [ ] OsInput (Basis: DsInput, 23 Dateien — ds-form Kopplung aufgelöst via formValidation Mixin) **Tier 3: Navigation (UI-Library)** - [ ] OsMenu (Basis: DsMenu, 11 Dateien) @@ -701,7 +703,7 @@ Jeder migrierte Button muss manuell geprüft werden: Normal, Hover, Focus, Activ **Tier 4: Spezial-Komponenten** - [ ] OsSelect (3 Dateien) - [x] ds-table (7 Dateien) → Plain HTML `` + CSS-Klassen ✅ (kein OsTable nötig) -- [ ] ds-form → Plain HTML `` oder OsForm (18 Dateien) +- [x] ds-form → formValidation Mixin (async-validator), vuelidate entfernt ✅ **Infrastruktur** - [x] System-Icons einrichten ✅ vite-svg-icon Plugin, 3 System-Icons, Ocelot-Icons Entry-Point @@ -1703,11 +1705,11 @@ Bei der Migration werden: | 2026-02-11 | **Milestone 4b abgeschlossen** | icon ✅, circle ✅, loading ✅ — alle OsButton-Props implementiert | | 2026-02-11 | **Milestone 4c: 59 Buttons** | Chat (2), AddChatRoomByUserSearch (1), CommentCard (1), CommentForm (2), ComponentSlider (2), ContributionForm (1), DeleteData (1), EmbedComponent (1), FilterMenu (1), HeaderButton (2), CategoriesFilter (2), OrderByFilter (2), EventsByFilter (2), FollowingFilter (3), GroupButton (1), ConfirmModal (2), ReportModal (2), Password/Change (1), PasswordReset/Request (1), PasswordReset/ChangePassword (1), Registration/Signup (1), ReleaseModal (1), ImageUploader (2), CreateInvitation (1), Invitation (2), ProfileList (1), ReportRow (1), MySomethingList (3), ActionButton (1), pages/index (2), profile/add-post (1), post/blur-toggle (1), groups/slug (3), settings/index (1), admin/users (2), blocked-users (1), data-download (1), muted-users (1), groups/index (1), enter-nonce (1) | | 2026-02-11 | **type="submit" Pattern** | OsButton hat `type="button"` als Default; alle Form-Submit-Buttons brauchen explizit `type="submit"` | -| 2026-02-11 | **!!errors Pattern** | DsForm `errors` ist ein Objekt, nicht Boolean; OsButton `disabled` Prop erwartet Boolean → `!!errors` nötig | +| 2026-02-11 | **!!errors Pattern** | `formErrors` ist ein Objekt, nicht Boolean; OsButton `disabled` Prop erwartet Boolean → `!!formErrors` nötig | | 2026-02-11 | **CSS-Selector Pattern** | `.base-button` → `> button` oder `button`; Position/Dimensions brauchen `!important` für Tailwind-Override | | 2026-02-11 | **Disabled border-color** | Outline disabled border von `var(--color-disabled)` auf `var(--color-disabled-border,#e5e3e8)` mit Fallback | | 2026-02-11 | **Phase 3 abgeschlossen** | 132 `` Tags in 78 Dateien, 0 `` in Templates verbleibend | -| 2026-02-11 | **Password/Change.vue Fix** | `!!errors` für disabled-Prop (DsForm errors ist Objekt) | +| 2026-02-11 | **Password/Change.vue Fix** | `!!errors` für disabled-Prop (formErrors ist Objekt) | | 2026-02-11 | **CommentForm.vue Fix** | `type="submit"` fehlte + `!!errors` für disabled-Prop | | 2026-02-11 | **GroupForm.vue ds-button** | Letzter `` in Webapp → `` mit `#icon` Slot migriert | | 2026-02-11 | **OsButton.spec.ts TS-Fix** | `size` aus `Object.entries` als Union Type gecastet (`as 'sm' | 'md' | 'lg' | 'xl'`) | @@ -1847,6 +1849,9 @@ Bei der Migration werden: | 2026-03-13 | **Modal Webapp-Integration** | ConfirmModal + ReportModal nutzen OsModal; Vuex Modal Store entfernt; Modals inline gerendert | | 2026-03-13 | **Modal Bugfixes** | z-index Stacking Context Fix (PostTeaser/GroupTeaser), Callback-Promise Propagation (ReportList, MySomethingList), Group Leave Authorization Fix ($nextTick), Cypress .ds-modal → .os-modal | | 2026-03-13 | **Modal A11y** | scrollable-region-focusable Fix (tabindex=0), aria-label Fallback wenn kein Title, body overflow save/restore | +| 2026-03-14 | **ds-form entkoppelt** | Neues `formValidation` Mixin (async-validator): provide/subscribe Pattern, formData/formSchema/formErrors, handleInput/handleInputValid Callbacks; vuelidate komplett entfernt | +| 2026-03-14 | **18 Formulare migriert** | CommentForm, ContributionForm, EnterNonce, GroupForm, Password/Change, PasswordReset (2), Registration (5), Signup, MySomethingList, donations, admin/users, settings (3) | +| 2026-03-20 | **formValidation Fix** | `handleInput()` vor `$validateForm()` aufrufen (Reihenfolge-Bug: handleInput überschrieb handleInputValid bei synchronem async-validator Callback) | --- @@ -1865,12 +1870,11 @@ Bei der Migration werden: | Status | Komponenten | |--------|------------| | ✅ UI-Library | OsButton, OsIcon, OsSpinner, OsCard, OsBadge, OsNumber, OsModal (7) | -| ✅ → Plain HTML | Section, Placeholder, List, ListItem, Container, Heading, Text, Space, Flex, FlexItem, Grid, GridItem, Table (13) — Tier A/B | +| ✅ → Plain HTML | Section, Placeholder, List, ListItem, Container, Heading, Text, Space, Flex, FlexItem, Grid, GridItem, Table, Radio (14) — Tier A/B | | ✅ → UI-Library | Chip, Tag → OsBadge (2), Number → OsNumber (1) — Tier B | -| ⬜ → Plain HTML | Radio (1) — Tier B | +| ✅ ds-form entkoppelt | Form-Validierung → formValidation Mixin (async-validator), vuelidate entfernt, ds-input/ds-select bleiben als UI-Komponenten | | ⬜ → UI-Library | Input, Menu, MenuItem, Select (4) — Tier 2-3 | | ⬜ Nicht genutzt | Code, CopyField, FormItem, InputError, InputLabel, Page, PageTitle, Logo, Avatar, TableCol, TableHeadCol (11) | -| ⬜ Offen | Form (18 Dateien — HTML `` oder OsForm?) | --- diff --git a/webapp/components/CommentForm/CommentForm.vue b/webapp/components/CommentForm/CommentForm.vue index 9308e04bc..3fb3bf881 100644 --- a/webapp/components/CommentForm/CommentForm.vue +++ b/webapp/components/CommentForm/CommentForm.vue @@ -1,34 +1,32 @@