mirror of
https://github.com/IT4Change/Ocelot-Social.git
synced 2026-04-06 01:25:31 +00:00
refactor(webapp): vue 3 migration - ds-form (#9407)
This commit is contained in:
parent
16c4f03d3f
commit
906ac801be
@ -14,7 +14,8 @@ Phase 3: OsButton ██████████ 100% (133/133 Buttons) ✅
|
|||||||
Phase 4: Tier 1 ██████████ 100% (OsButton, OsIcon, OsSpinner, OsCard) ✅
|
Phase 4: Tier 1 ██████████ 100% (OsButton, OsIcon, OsSpinner, OsCard) ✅
|
||||||
Phase 4: Tier A → HTML ██████████ 100% (10 ds-* Wrapper → Plain HTML) ✅
|
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 ██████████ 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
|
### 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) |
|
| ✅ → OsNumber | Number (5 Nutzungen → OsNumber, CountTo.vue gelöscht, vue-count-to entfernt) |
|
||||||
| ✅ → Plain HTML | Radio (1 Datei → native `<input type="radio">` in ReportModal) |
|
| ✅ → Plain HTML | Radio (1 Datei → native `<input type="radio">` in ReportModal) |
|
||||||
| ⬜ → UI-Library | Modal, Input, Menu, MenuItem, Select (5) — Tier 2-3 |
|
| ⬜ → 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) |
|
| ⬜ Nicht in Webapp | Code, CopyField, FormItem, InputError, InputLabel, Page, PageTitle, Logo, Avatar, TableCol, TableHeadCol (11) |
|
||||||
|
|
||||||
### OsButton Migration (Phase 3) ✅
|
### OsButton Migration (Phase 3) ✅
|
||||||
@ -38,7 +39,7 @@ Phase 4: Tier B+ ████████░░ 75% (ds-table→HTML✅)
|
|||||||
|
|
||||||
**Erkenntnisse aus der Migration:**
|
**Erkenntnisse aus der Migration:**
|
||||||
- `type="submit"` muss explizit gesetzt werden (OsButton Default: `type="button"`)
|
- `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`
|
- CSS `.base-button` Selektoren → `> button` oder `button`
|
||||||
- Filter-Buttons nutzen `:appearance="condition ? 'filled' : 'outline'"` Pattern
|
- Filter-Buttons nutzen `:appearance="condition ? 'filled' : 'outline'"` Pattern
|
||||||
- Circle-Buttons mit Icon: `<template #icon><os-icon :icon="..." /></template>`
|
- Circle-Buttons mit Icon: `<template #icon><os-icon :icon="..." /></template>`
|
||||||
@ -438,19 +439,19 @@ Phase 4: Tier B+ ████████░░ 75% (ds-table→HTML✅)
|
|||||||
14. [x] ds-chip (5 Dateien, 20 Nutzungen) → OsBadge (UI-Library)
|
14. [x] ds-chip (5 Dateien, 20 Nutzungen) → OsBadge (UI-Library)
|
||||||
15. [x] ds-tag (3 Dateien) → OsBadge shape="square" (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)
|
16. [x] ds-grid / ds-grid-item (10 Dateien) → CSS Grid (Plain HTML)
|
||||||
17. [ ] ds-number (5 Dateien) → `<div class="ds-number">`
|
17. [x] ds-number (5 Dateien) → OsNumber (UI-Library) ✅
|
||||||
18. [x] ds-radio (1 Datei) → native `<input type="radio">` + `<fieldset>` (ReportModal) ✅
|
18. [x] ds-radio (1 Datei) → native `<input type="radio">` + `<fieldset>` (ReportModal) ✅
|
||||||
|
|
||||||
### Phase 4: Tier 2-4 — UI-Library
|
### 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. [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. [x] ds-form → formValidation Mixin (async-validator), 18 Dateien migriert, vuelidate entfernt ✅
|
||||||
20. [ ] OsMenu / OsMenuItem (17 Dateien)
|
21. [ ] OsInput (23 Dateien)
|
||||||
21. [ ] OsSelect (3 Dateien), OsTable (7 Dateien)
|
22. [ ] OsMenu / OsMenuItem (17 Dateien)
|
||||||
22. [ ] ds-form → HTML `<form>` oder OsForm (18 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-space | ✅ → div + Margin-Utility-Klassen |
|
||||||
| — | ds-flex, ds-flex-item | ✅ → HTML + CSS @media Queries |
|
| — | ds-flex, ds-flex-item | ✅ → HTML + CSS @media Queries |
|
||||||
|
|
||||||
### Tier B: Einfache ds-* Migration (60%)
|
### Tier B: Einfache ds-* Migration ✅
|
||||||
|
|
||||||
| # | Komponente | Dateien | Ziel | Status |
|
| # | Komponente | Dateien | Ziel | Status |
|
||||||
|---|------------|---------|------|--------|
|
|---|------------|---------|------|--------|
|
||||||
| 5 | **OsBadge** | — | ds-chip (20 Nutzungen, 5 Dateien) + ds-tag (3 Dateien) | ✅ |
|
| 5 | **OsBadge** | — | ds-chip (20 Nutzungen, 5 Dateien) + ds-tag (3 Dateien) | ✅ |
|
||||||
| — | ds-grid / ds-grid-item | 10 | CSS Grid (Plain HTML) | ✅ |
|
| — | ds-grid / ds-grid-item | 10 | CSS Grid (Plain HTML) | ✅ |
|
||||||
| — | ds-number | 5 | `<div class="ds-number">` | ⬜ |
|
| — | ds-number | 5 | OsNumber (UI-Library) | ✅ |
|
||||||
| — | ds-radio | 1 | native `<input type="radio">` | ⬜ |
|
| — | ds-radio | 1 | native `<input type="radio">` | ✅ |
|
||||||
|
|
||||||
### Tier 2: Layout & Feedback
|
### Tier 2: Layout & Feedback
|
||||||
|
|
||||||
@ -964,7 +965,7 @@ interface OsDropdownProps {
|
|||||||
| 5 | **OsModal** | 7 | OsButton | ✅ |
|
| 5 | **OsModal** | 7 | OsButton | ✅ |
|
||||||
| 6 | **OsDropdown** | — | OsButton | ⬜ |
|
| 6 | **OsDropdown** | — | OsButton | ⬜ |
|
||||||
| 7 | **OsAvatar** | — | - | ⬜ |
|
| 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)
|
### Tier 3: Navigation (ausstehend)
|
||||||
|
|
||||||
@ -979,7 +980,7 @@ interface OsDropdownProps {
|
|||||||
|---|------------|---------|
|
|---|------------|---------|
|
||||||
| 11 | OsSelect | 3 |
|
| 11 | OsSelect | 3 |
|
||||||
| 12 | OsTable | 7 |
|
| 12 | OsTable | 7 |
|
||||||
| 13 | ds-form → HTML `<form>` 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).
|
> **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-chip → OsBadge (UI-Library) ✅
|
||||||
ds-tag → OsBadge shape="square" (UI-Library) ✅
|
ds-tag → OsBadge shape="square" (UI-Library) ✅
|
||||||
ds-grid / ds-grid-item → CSS Grid (HTML) ✅
|
ds-grid / ds-grid-item → CSS Grid (HTML) ✅
|
||||||
ds-number → Plain HTML ⬜ (5 Dateien)
|
ds-number → OsNumber (UI-Library) ✅
|
||||||
ds-radio → native <input type="radio"> ⬜ (1 Datei)
|
ds-radio → native <input type="radio"> ✅ (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!
|
6. OsDropdown → Basis: Dropdown (Webapp) — wichtiger als gedacht!
|
||||||
7. OsAvatar → Vereint: DsAvatar + ProfileAvatar
|
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
|
9. OsMenu → Basis: DsMenu/DsMenuItem
|
||||||
10. OsSelect → Basis: DsSelect
|
10. OsSelect → Basis: DsSelect
|
||||||
11. OsTable → Basis: DsTable
|
11. OsTable → Basis: DsTable
|
||||||
@ -1047,8 +1048,8 @@ ds-radio → native <input type="radio"> ⬜ (1 Datei)
|
|||||||
|
|
||||||
### Noch offen:
|
### Noch offen:
|
||||||
1. **Logo** - Existiert doppelt (Webapp + Styleguide)
|
1. **Logo** - Existiert doppelt (Webapp + Styleguide)
|
||||||
2. **Modal** - Existiert doppelt (Webapp Modal.vue ist Modal-Router, DsModal ist UI)
|
2. ~~**Modal** - Existiert doppelt~~ → OsModal migriert ✅
|
||||||
3. **ds-form Kopplung** - ds-input und ds-form sind stark gekoppelt (Schema-Validation)
|
3. ~~**ds-form Kopplung**~~ → aufgelöst via formValidation Mixin (async-validator), vuelidate entfernt ✅
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -289,7 +289,8 @@ ds-chip + ds-tag → OsBadge (UI-Library): ✅
|
|||||||
- [x] 0 Tier-A `ds-*` Komponenten-Tags verbleibend
|
- [x] 0 Tier-A `ds-*` Komponenten-Tags verbleibend
|
||||||
|
|
||||||
**Verbleibende ds-* Komponenten (6 Typen):**
|
**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):**
|
**Zuvor abgeschlossen (Session 26 - CodeRabbit Review Fixes):**
|
||||||
- [x] Cypress: `.os-card .title` → `.os-card > .title` (Kind-Kombinator statt Nachfahren)
|
- [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
|
- [ ] Tier B (Rest): ds-radio → Plain HTML
|
||||||
- [x] OsModal Komponente + DsModal/ConfirmModal/ReportModal → OsModal Webapp-Integration ✅
|
- [x] OsModal Komponente + DsModal/ConfirmModal/ReportModal → OsModal Webapp-Integration ✅
|
||||||
- [ ] Weitere Tier 2 Komponenten (OsDropdown, OsAvatar, OsInput)
|
- [ ] 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-menu / ds-menu-item → OsMenu / OsMenuItem
|
||||||
- [ ] ds-select → OsSelect
|
- [ ] ds-select → OsSelect
|
||||||
- [ ] Browser-Fehler untersuchen: `TypeError: Cannot read properties of undefined (reading 'heartO')` (ocelotIcons undefined im Browser trotz korrekter Webpack-Aliase)
|
- [ ] 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-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-number (5 Dateien) → OsNumber (UI-Library) ✅ + CountTo.vue gelöscht, vue-count-to entfernt
|
||||||
- [x] ds-grid / ds-grid-item (10 Dateien) → CSS Grid ✅
|
- [x] ds-grid / ds-grid-item (10 Dateien) → CSS Grid ✅
|
||||||
- [ ] ds-radio (1 Datei) → native `<input type="radio">`
|
- [x] ds-radio (1 Datei) → native `<input type="radio">` ✅
|
||||||
|
|
||||||
**Tier 2: Layout & Feedback (UI-Library)**
|
**Tier 2: Layout & Feedback (UI-Library)**
|
||||||
- [x] OsModal (Basis: DsModal → h() Render-Function, Vue 2/3 Compat, Focus-Trap, Scroll-Lock, A11y) ✅
|
- [x] OsModal (Basis: DsModal → h() Render-Function, Vue 2/3 Compat, Focus-Trap, Scroll-Lock, A11y) ✅
|
||||||
- [ ] OsDropdown (Basis: Webapp Dropdown)
|
- [ ] OsDropdown (Basis: Webapp Dropdown)
|
||||||
- [ ] OsAvatar (vereint DsAvatar + ProfileAvatar)
|
- [ ] 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)**
|
**Tier 3: Navigation (UI-Library)**
|
||||||
- [ ] OsMenu (Basis: DsMenu, 11 Dateien)
|
- [ ] 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**
|
**Tier 4: Spezial-Komponenten**
|
||||||
- [ ] OsSelect (3 Dateien)
|
- [ ] OsSelect (3 Dateien)
|
||||||
- [x] ds-table (7 Dateien) → Plain HTML `<table>` + CSS-Klassen ✅ (kein OsTable nötig)
|
- [x] ds-table (7 Dateien) → Plain HTML `<table>` + CSS-Klassen ✅ (kein OsTable nötig)
|
||||||
- [ ] ds-form → Plain HTML `<form>` oder OsForm (18 Dateien)
|
- [x] ds-form → formValidation Mixin (async-validator), vuelidate entfernt ✅
|
||||||
|
|
||||||
**Infrastruktur**
|
**Infrastruktur**
|
||||||
- [x] System-Icons einrichten ✅ vite-svg-icon Plugin, 3 System-Icons, Ocelot-Icons Entry-Point
|
- [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 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 | **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 | **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 | **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 | **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 `<os-button>` Tags in 78 Dateien, 0 `<base-button>` in Templates verbleibend |
|
| 2026-02-11 | **Phase 3 abgeschlossen** | 132 `<os-button>` Tags in 78 Dateien, 0 `<base-button>` 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 | **CommentForm.vue Fix** | `type="submit"` fehlte + `!!errors` für disabled-Prop |
|
||||||
| 2026-02-11 | **GroupForm.vue ds-button** | Letzter `<ds-button>` in Webapp → `<os-button>` mit `#icon` Slot migriert |
|
| 2026-02-11 | **GroupForm.vue ds-button** | Letzter `<ds-button>` in Webapp → `<os-button>` 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'`) |
|
| 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 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 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-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 |
|
| Status | Komponenten |
|
||||||
|--------|------------|
|
|--------|------------|
|
||||||
| ✅ UI-Library | OsButton, OsIcon, OsSpinner, OsCard, OsBadge, OsNumber, OsModal (7) |
|
| ✅ 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 |
|
| ✅ → 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 |
|
| ⬜ → UI-Library | Input, Menu, MenuItem, Select (4) — Tier 2-3 |
|
||||||
| ⬜ Nicht genutzt | Code, CopyField, FormItem, InputError, InputLabel, Page, PageTitle, Logo, Avatar, TableCol, TableHeadCol (11) |
|
| ⬜ Nicht genutzt | Code, CopyField, FormItem, InputError, InputLabel, Page, PageTitle, Logo, Avatar, TableCol, TableHeadCol (11) |
|
||||||
| ⬜ Offen | Form (18 Dateien — HTML `<form>` oder OsForm?) |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -1,34 +1,32 @@
|
|||||||
<template>
|
<template>
|
||||||
<ds-form v-model="form" @submit="handleSubmit" class="comment-form">
|
<form @submit.prevent="handleSubmit" class="comment-form" novalidate>
|
||||||
<template #default="{ errors }">
|
<os-card>
|
||||||
<os-card>
|
<hc-editor ref="editor" :users="users" :value="form.content" @input="updateEditorContent" />
|
||||||
<hc-editor ref="editor" :users="users" :value="form.content" @input="updateEditorContent" />
|
<div class="buttons">
|
||||||
<div class="buttons">
|
<os-button
|
||||||
<os-button
|
variant="primary"
|
||||||
variant="primary"
|
appearance="outline"
|
||||||
appearance="outline"
|
:disabled="disabled && !update"
|
||||||
:disabled="disabled && !update"
|
@click="handleCancel"
|
||||||
@click="handleCancel"
|
data-test="cancel-button"
|
||||||
data-test="cancel-button"
|
>
|
||||||
>
|
{{ $t('actions.cancel') }}
|
||||||
{{ $t('actions.cancel') }}
|
</os-button>
|
||||||
</os-button>
|
<os-button
|
||||||
<os-button
|
variant="primary"
|
||||||
variant="primary"
|
appearance="filled"
|
||||||
appearance="filled"
|
type="submit"
|
||||||
type="submit"
|
:loading="loading"
|
||||||
:loading="loading"
|
:disabled="disabled"
|
||||||
:disabled="disabled || !!errors"
|
>
|
||||||
>
|
<template #icon>
|
||||||
<template #icon>
|
<os-icon :icon="icons.comment" />
|
||||||
<os-icon :icon="icons.comment" />
|
</template>
|
||||||
</template>
|
{{ $t('post.comment.submit') }}
|
||||||
{{ $t('post.comment.submit') }}
|
</os-button>
|
||||||
</os-button>
|
</div>
|
||||||
</div>
|
</os-card>
|
||||||
</os-card>
|
</form>
|
||||||
</template>
|
|
||||||
</ds-form>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@ -1,13 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<ds-form
|
<form class="contribution-form" @submit.prevent="onSubmit" novalidate>
|
||||||
class="contribution-form"
|
<template>
|
||||||
ref="contributionForm"
|
|
||||||
v-model="formData"
|
|
||||||
:schema="formSchema"
|
|
||||||
@submit="submit"
|
|
||||||
>
|
|
||||||
<template #default="{ errors }">
|
|
||||||
<os-card>
|
<os-card>
|
||||||
<template #heroImage>
|
<template #heroImage>
|
||||||
<img
|
<img
|
||||||
@ -42,10 +36,10 @@
|
|||||||
<os-badge
|
<os-badge
|
||||||
role="status"
|
role="status"
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
:variant="errors && errors.title ? 'danger' : undefined"
|
:variant="formErrors && formErrors.title ? 'danger' : undefined"
|
||||||
>
|
>
|
||||||
{{ formData.title.length }}/{{ formSchema.title.max }}
|
{{ formData.title.length }}/{{ formSchema.title.max }}
|
||||||
<os-icon v-if="errors && errors.title" :icon="icons.warning" />
|
<os-icon v-if="formErrors && formErrors.title" :icon="icons.warning" />
|
||||||
</os-badge>
|
</os-badge>
|
||||||
<editor
|
<editor
|
||||||
:users="users"
|
:users="users"
|
||||||
@ -56,10 +50,10 @@
|
|||||||
<os-badge
|
<os-badge
|
||||||
role="status"
|
role="status"
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
:variant="errors && errors.content ? 'danger' : undefined"
|
:variant="formErrors && formErrors.content ? 'danger' : undefined"
|
||||||
>
|
>
|
||||||
{{ contentLength }}
|
{{ contentLength }}
|
||||||
<os-icon v-if="errors && errors.content" :icon="icons.warning" />
|
<os-icon v-if="formErrors && formErrors.content" :icon="icons.warning" />
|
||||||
</os-badge>
|
</os-badge>
|
||||||
|
|
||||||
<!-- Eventdata -->
|
<!-- Eventdata -->
|
||||||
@ -86,13 +80,13 @@
|
|||||||
></date-picker>
|
></date-picker>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="errors && errors.eventStart"
|
v-if="formErrors && formErrors.eventStart"
|
||||||
class="chipbox event-grid-item-margin-helper"
|
class="chipbox event-grid-item-margin-helper"
|
||||||
>
|
>
|
||||||
<os-badge
|
<os-badge
|
||||||
role="alert"
|
role="alert"
|
||||||
aria-live="assertive"
|
aria-live="assertive"
|
||||||
:variant="errors && errors.eventStart ? 'danger' : undefined"
|
:variant="formErrors && formErrors.eventStart ? 'danger' : undefined"
|
||||||
>
|
>
|
||||||
<os-icon :icon="icons.warning" />
|
<os-icon :icon="icons.warning" />
|
||||||
</os-badge>
|
</os-badge>
|
||||||
@ -129,10 +123,10 @@
|
|||||||
<os-badge
|
<os-badge
|
||||||
role="status"
|
role="status"
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
:variant="errors && errors.eventVenue ? 'danger' : undefined"
|
:variant="formErrors && formErrors.eventVenue ? 'danger' : undefined"
|
||||||
>
|
>
|
||||||
{{ formData.eventVenue.length }}/{{ formSchema.eventVenue.max }}
|
{{ formData.eventVenue.length }}/{{ formSchema.eventVenue.max }}
|
||||||
<os-icon v-if="errors && errors.eventVenue" :icon="icons.warning" />
|
<os-icon v-if="formErrors && formErrors.eventVenue" :icon="icons.warning" />
|
||||||
</os-badge>
|
</os-badge>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -146,10 +140,13 @@
|
|||||||
<os-badge
|
<os-badge
|
||||||
role="status"
|
role="status"
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
:variant="errors && errors.eventLocationName ? 'danger' : undefined"
|
:variant="formErrors && formErrors.eventLocationName ? 'danger' : undefined"
|
||||||
>
|
>
|
||||||
{{ formData.eventLocationName.length }}/{{ formSchema.eventLocationName.max }}
|
{{ formData.eventLocationName.length }}/{{ formSchema.eventLocationName.max }}
|
||||||
<os-icon v-if="errors && errors.eventLocationName" :icon="icons.warning" />
|
<os-icon
|
||||||
|
v-if="formErrors && formErrors.eventLocationName"
|
||||||
|
:icon="icons.warning"
|
||||||
|
/>
|
||||||
</os-badge>
|
</os-badge>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -177,10 +174,10 @@
|
|||||||
v-if="categoriesActive"
|
v-if="categoriesActive"
|
||||||
role="status"
|
role="status"
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
:variant="errors && errors.categoryIds ? 'danger' : undefined"
|
:variant="formErrors && formErrors.categoryIds ? 'danger' : undefined"
|
||||||
>
|
>
|
||||||
{{ formData.categoryIds.length }} / 3
|
{{ formData.categoryIds.length }} / 3
|
||||||
<os-icon v-if="errors && errors.categoryIds" :icon="icons.warning" />
|
<os-icon v-if="formErrors && formErrors.categoryIds" :icon="icons.warning" />
|
||||||
</os-badge>
|
</os-badge>
|
||||||
<div class="ds-flex ds-flex-gap-xxx-small buttons-footer">
|
<div class="ds-flex ds-flex-gap-xxx-small buttons-footer">
|
||||||
<div style="flex: 3.5 0 0" class="buttons-footer-helper">
|
<div style="flex: 3.5 0 0" class="buttons-footer-helper">
|
||||||
@ -207,7 +204,7 @@
|
|||||||
appearance="filled"
|
appearance="filled"
|
||||||
type="submit"
|
type="submit"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:disabled="!!errors"
|
:disabled="!!formErrors"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<os-icon :icon="icons.check" />
|
<os-icon :icon="icons.check" />
|
||||||
@ -218,7 +215,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</os-card>
|
</os-card>
|
||||||
</template>
|
</template>
|
||||||
</ds-form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
@ -235,9 +232,10 @@ import PageParamsLink from '~/components/_new/features/PageParamsLink/PageParams
|
|||||||
import DatePicker from 'vue2-datepicker'
|
import DatePicker from 'vue2-datepicker'
|
||||||
import 'vue2-datepicker/scss/index.scss'
|
import 'vue2-datepicker/scss/index.scss'
|
||||||
import GetCategories from '~/mixins/getCategoriesMixin.js'
|
import GetCategories from '~/mixins/getCategoriesMixin.js'
|
||||||
|
import formValidation from '~/mixins/formValidation'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [GetCategories],
|
mixins: [GetCategories, formValidation],
|
||||||
components: {
|
components: {
|
||||||
CategoriesSelect,
|
CategoriesSelect,
|
||||||
DatePicker,
|
DatePicker,
|
||||||
@ -424,6 +422,9 @@ export default {
|
|||||||
notBeforeEvent(date) {
|
notBeforeEvent(date) {
|
||||||
return date <= new Date(this.formData.eventStart)
|
return date <= new Date(this.formData.eventStart)
|
||||||
},
|
},
|
||||||
|
onSubmit() {
|
||||||
|
this.formSubmit(this.submit)
|
||||||
|
},
|
||||||
submit() {
|
submit() {
|
||||||
let image = null
|
let image = null
|
||||||
|
|
||||||
@ -470,16 +471,16 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
updateEditorContent(value) {
|
updateEditorContent(value) {
|
||||||
this.$refs.contributionForm.update('content', value)
|
this.updateFormField('content', value)
|
||||||
},
|
},
|
||||||
changeEventIsOnline(event) {
|
changeEventIsOnline(event) {
|
||||||
this.$refs.contributionForm.update('eventIsOnline', this.formData.eventIsOnline)
|
this.updateFormField('eventIsOnline', this.formData.eventIsOnline)
|
||||||
},
|
},
|
||||||
changeEventEnd(event) {
|
changeEventEnd(event) {
|
||||||
this.$refs.contributionForm.update('eventEnd', event)
|
this.updateFormField('eventEnd', event)
|
||||||
},
|
},
|
||||||
changeEventStart(event) {
|
changeEventStart(event) {
|
||||||
this.$refs.contributionForm.update('eventStart', event)
|
this.updateFormField('eventStart', event)
|
||||||
},
|
},
|
||||||
addHeroImage(file) {
|
addHeroImage(file) {
|
||||||
this.formData.image = null
|
this.formData.image = null
|
||||||
|
|||||||
@ -1,12 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<ds-form
|
<form class="enter-nonce" @submit.prevent="onSubmit" novalidate>
|
||||||
class="enter-nonce"
|
|
||||||
v-model="formData"
|
|
||||||
:schema="formSchema"
|
|
||||||
@submit="handleSubmitVerify"
|
|
||||||
@input="handleInput"
|
|
||||||
@input-valid="handleInputValid"
|
|
||||||
>
|
|
||||||
<ds-input
|
<ds-input
|
||||||
:placeholder="$t('components.registration.email-nonce.form.nonce')"
|
:placeholder="$t('components.registration.email-nonce.form.nonce')"
|
||||||
model="nonce"
|
model="nonce"
|
||||||
@ -30,15 +23,17 @@
|
|||||||
{{ $t('components.registration.email-nonce.form.next') }}
|
{{ $t('components.registration.email-nonce.form.next') }}
|
||||||
</os-button>
|
</os-button>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</ds-form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { OsButton } from '@ocelot-social/ui'
|
import { OsButton } from '@ocelot-social/ui'
|
||||||
import registrationConstants from '~/constants/registration'
|
import registrationConstants from '~/constants/registration'
|
||||||
|
import formValidation from '~/mixins/formValidation'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'EnterNonce',
|
name: 'EnterNonce',
|
||||||
|
mixins: [formValidation],
|
||||||
components: { OsButton },
|
components: { OsButton },
|
||||||
props: {
|
props: {
|
||||||
email: { type: String, required: true },
|
email: { type: String, required: true },
|
||||||
@ -69,6 +64,9 @@ export default {
|
|||||||
async handleInputValid() {
|
async handleInputValid() {
|
||||||
this.disabled = false
|
this.disabled = false
|
||||||
},
|
},
|
||||||
|
onSubmit() {
|
||||||
|
this.formSubmit(this.handleSubmitVerify)
|
||||||
|
},
|
||||||
handleSubmitVerify() {
|
handleSubmitVerify() {
|
||||||
const { nonce } = this.formData
|
const { nonce } = this.formData
|
||||||
const email = this.email
|
const email = this.email
|
||||||
|
|||||||
@ -1,14 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<ds-form
|
<form class="group-form" @submit.prevent="onSubmit" novalidate>
|
||||||
class="group-form"
|
<template>
|
||||||
ref="groupForm"
|
|
||||||
v-model="formData"
|
|
||||||
:schema="formSchema"
|
|
||||||
@submit="submit"
|
|
||||||
>
|
|
||||||
<!-- "errors" is only working if you use a submit event on the form -->
|
|
||||||
<template #default="{ errors }">
|
|
||||||
<!-- group Name -->
|
<!-- group Name -->
|
||||||
<ds-input
|
<ds-input
|
||||||
name="name"
|
name="name"
|
||||||
@ -20,10 +13,10 @@
|
|||||||
<os-badge
|
<os-badge
|
||||||
role="status"
|
role="status"
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
:variant="errors && errors.name ? 'danger' : undefined"
|
:variant="formErrors && formErrors.name ? 'danger' : undefined"
|
||||||
>
|
>
|
||||||
{{ `${formData.name.length} / ${formSchema.name.min}–${formSchema.name.max}` }}
|
{{ `${formData.name.length} / ${formSchema.name.min}–${formSchema.name.max}` }}
|
||||||
<os-icon v-if="errors && errors.name" :icon="icons.warning" />
|
<os-icon v-if="formErrors && formErrors.name" :icon="icons.warning" />
|
||||||
</os-badge>
|
</os-badge>
|
||||||
|
|
||||||
<!-- group Slug -->
|
<!-- group Slug -->
|
||||||
@ -56,11 +49,13 @@
|
|||||||
<os-badge
|
<os-badge
|
||||||
role="status"
|
role="status"
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
:variant="errors && errors.groupType && formData.groupType === '' ? 'danger' : undefined"
|
:variant="
|
||||||
|
formErrors && formErrors.groupType && formData.groupType === '' ? 'danger' : undefined
|
||||||
|
"
|
||||||
>
|
>
|
||||||
{{ `${formData.groupType === '' ? 0 : 1} / 1` }}
|
{{ `${formData.groupType === '' ? 0 : 1} / 1` }}
|
||||||
<os-icon
|
<os-icon
|
||||||
v-if="errors && errors.groupType && formData.groupType === ''"
|
v-if="formErrors && formErrors.groupType && formData.groupType === ''"
|
||||||
:icon="icons.warning"
|
:icon="icons.warning"
|
||||||
/>
|
/>
|
||||||
</os-badge>
|
</os-badge>
|
||||||
@ -91,10 +86,10 @@
|
|||||||
<os-badge
|
<os-badge
|
||||||
role="status"
|
role="status"
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
:variant="errors && errors.description ? 'danger' : undefined"
|
:variant="formErrors && formErrors.description ? 'danger' : undefined"
|
||||||
>
|
>
|
||||||
{{ `${descriptionLength} / ${formSchema.description.min}` }}
|
{{ `${descriptionLength} / ${formSchema.description.min}` }}
|
||||||
<os-icon v-if="errors && errors.description" :icon="icons.warning" />
|
<os-icon v-if="formErrors && formErrors.description" :icon="icons.warning" />
|
||||||
</os-badge>
|
</os-badge>
|
||||||
|
|
||||||
<!-- actionRadius -->
|
<!-- actionRadius -->
|
||||||
@ -109,12 +104,14 @@
|
|||||||
role="status"
|
role="status"
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
:variant="
|
:variant="
|
||||||
errors && errors.actionRadius && formData.actionRadius === '' ? 'danger' : undefined
|
formErrors && formErrors.actionRadius && formData.actionRadius === ''
|
||||||
|
? 'danger'
|
||||||
|
: undefined
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
{{ `${formData.actionRadius === '' ? 0 : 1} / 1` }}
|
{{ `${formData.actionRadius === '' ? 0 : 1} / 1` }}
|
||||||
<os-icon
|
<os-icon
|
||||||
v-if="errors && errors.actionRadius && formData.actionRadius === ''"
|
v-if="formErrors && formErrors.actionRadius && formData.actionRadius === ''"
|
||||||
:icon="icons.warning"
|
:icon="icons.warning"
|
||||||
/>
|
/>
|
||||||
</os-badge>
|
</os-badge>
|
||||||
@ -138,10 +135,10 @@
|
|||||||
<os-badge
|
<os-badge
|
||||||
role="status"
|
role="status"
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
:variant="errors && errors.categoryIds ? 'danger' : undefined"
|
:variant="formErrors && formErrors.categoryIds ? 'danger' : undefined"
|
||||||
>
|
>
|
||||||
{{ formData.categoryIds.length }} / 3
|
{{ formData.categoryIds.length }} / 3
|
||||||
<os-icon v-if="errors && errors.categoryIds" :icon="icons.warning" />
|
<os-icon v-if="formErrors && formErrors.categoryIds" :icon="icons.warning" />
|
||||||
</os-badge>
|
</os-badge>
|
||||||
</div>
|
</div>
|
||||||
<!-- submit -->
|
<!-- submit -->
|
||||||
@ -153,14 +150,14 @@
|
|||||||
variant="primary"
|
variant="primary"
|
||||||
appearance="filled"
|
appearance="filled"
|
||||||
type="submit"
|
type="submit"
|
||||||
:disabled="checkFormError(errors)"
|
:disabled="checkFormError(formErrors)"
|
||||||
>
|
>
|
||||||
<template #icon><os-icon :icon="icons.save" /></template>
|
<template #icon><os-icon :icon="icons.save" /></template>
|
||||||
{{ update ? $t('group.update') : $t('group.save') }}
|
{{ update ? $t('group.update') : $t('group.save') }}
|
||||||
</os-button>
|
</os-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</ds-form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -178,10 +175,11 @@ import Editor from '~/components/Editor/Editor'
|
|||||||
import ActionRadiusSelect from '~/components/Select/ActionRadiusSelect'
|
import ActionRadiusSelect from '~/components/Select/ActionRadiusSelect'
|
||||||
import LocationSelect from '~/components/Select/LocationSelect'
|
import LocationSelect from '~/components/Select/LocationSelect'
|
||||||
import GetCategories from '~/mixins/getCategoriesMixin.js'
|
import GetCategories from '~/mixins/getCategoriesMixin.js'
|
||||||
|
import formValidation from '~/mixins/formValidation'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'GroupForm',
|
name: 'GroupForm',
|
||||||
mixins: [GetCategories],
|
mixins: [GetCategories, formValidation],
|
||||||
components: {
|
components: {
|
||||||
CategoriesSelect,
|
CategoriesSelect,
|
||||||
Editor,
|
Editor,
|
||||||
@ -308,16 +306,19 @@ export default {
|
|||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
changeGroupType(event) {
|
changeGroupType(event) {
|
||||||
this.$refs.groupForm.update('groupType', event.target.value)
|
this.updateFormField('groupType', event.target.value)
|
||||||
},
|
},
|
||||||
changeActionRadius(event) {
|
changeActionRadius(event) {
|
||||||
this.$refs.groupForm.update('actionRadius', event.target.value)
|
this.updateFormField('actionRadius', event.target.value)
|
||||||
},
|
},
|
||||||
changeLocation(event) {
|
changeLocation(event) {
|
||||||
this.formData.locationName = event.target.value
|
this.formData.locationName = event.target.value
|
||||||
},
|
},
|
||||||
updateEditorDescription(value) {
|
updateEditorDescription(value) {
|
||||||
this.$refs.groupForm.update('description', value)
|
this.updateFormField('description', value)
|
||||||
|
},
|
||||||
|
onSubmit() {
|
||||||
|
this.formSubmit(this.submit)
|
||||||
},
|
},
|
||||||
submit() {
|
submit() {
|
||||||
const { name, slug, about, description, groupType, actionRadius, categoryIds } = this.formData
|
const { name, slug, about, description, groupType, actionRadius, categoryIds } = this.formData
|
||||||
|
|||||||
@ -65,8 +65,7 @@ describe('ChangePassword.vue', () => {
|
|||||||
it('displays a validation error', async () => {
|
it('displays a validation error', async () => {
|
||||||
await wrapper.find('form').trigger('submit')
|
await wrapper.find('form').trigger('submit')
|
||||||
await wrapper.vm.$nextTick()
|
await wrapper.vm.$nextTick()
|
||||||
const dsForm = wrapper.findComponent({ name: 'DsForm' })
|
expect(wrapper.vm.formErrors).toHaveProperty('passwordConfirmation')
|
||||||
expect(dsForm.vm.errors).toHaveProperty('passwordConfirmation')
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -1,44 +1,42 @@
|
|||||||
<template>
|
<template>
|
||||||
<ds-form v-model="formData" :schema="formSchema" @submit="handleSubmit">
|
<form @submit.prevent="onSubmit" novalidate>
|
||||||
<template #default="{ errors }">
|
<ds-input
|
||||||
<ds-input
|
id="oldPassword"
|
||||||
id="oldPassword"
|
model="oldPassword"
|
||||||
model="oldPassword"
|
type="password"
|
||||||
type="password"
|
autocomplete="off"
|
||||||
autocomplete="off"
|
:label="$t('settings.security.change-password.label-old-password')"
|
||||||
:label="$t('settings.security.change-password.label-old-password')"
|
/>
|
||||||
/>
|
<ds-input
|
||||||
<ds-input
|
id="password"
|
||||||
id="password"
|
model="password"
|
||||||
model="password"
|
type="password"
|
||||||
type="password"
|
autocomplete="off"
|
||||||
autocomplete="off"
|
:label="$t('settings.security.change-password.label-new-password')"
|
||||||
:label="$t('settings.security.change-password.label-new-password')"
|
/>
|
||||||
/>
|
<ds-input
|
||||||
<ds-input
|
id="passwordConfirmation"
|
||||||
id="passwordConfirmation"
|
model="passwordConfirmation"
|
||||||
model="passwordConfirmation"
|
type="password"
|
||||||
type="password"
|
autocomplete="off"
|
||||||
autocomplete="off"
|
:label="$t('settings.security.change-password.label-new-password-confirm')"
|
||||||
:label="$t('settings.security.change-password.label-new-password-confirm')"
|
/>
|
||||||
/>
|
<password-strength :password="formData.password" />
|
||||||
<password-strength :password="formData.password" />
|
<div class="ds-mt-base ds-mb-large">
|
||||||
<div class="ds-mt-base ds-mb-large">
|
<os-button
|
||||||
<os-button
|
variant="primary"
|
||||||
variant="primary"
|
appearance="filled"
|
||||||
appearance="filled"
|
:loading="loading"
|
||||||
:loading="loading"
|
:disabled="!!formErrors"
|
||||||
:disabled="!!errors"
|
type="submit"
|
||||||
type="submit"
|
>
|
||||||
>
|
<template #icon>
|
||||||
<template #icon>
|
<os-icon :icon="icons.lock" />
|
||||||
<os-icon :icon="icons.lock" />
|
</template>
|
||||||
</template>
|
{{ $t('settings.security.change-password.button') }}
|
||||||
{{ $t('settings.security.change-password.button') }}
|
</os-button>
|
||||||
</os-button>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
</template>
|
|
||||||
</ds-form>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@ -47,9 +45,11 @@ import { iconRegistry } from '~/utils/iconRegistry'
|
|||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
import PasswordStrength from './Strength'
|
import PasswordStrength from './Strength'
|
||||||
import PasswordForm from '~/components/utils/PasswordFormHelper'
|
import PasswordForm from '~/components/utils/PasswordFormHelper'
|
||||||
|
import formValidation from '~/mixins/formValidation'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ChangePassword',
|
name: 'ChangePassword',
|
||||||
|
mixins: [formValidation],
|
||||||
components: {
|
components: {
|
||||||
OsButton,
|
OsButton,
|
||||||
OsIcon,
|
OsIcon,
|
||||||
@ -77,6 +77,9 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
onSubmit() {
|
||||||
|
this.formSubmit(this.handleSubmit)
|
||||||
|
},
|
||||||
async handleSubmit(data) {
|
async handleSubmit(data) {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
const mutation = gql`
|
const mutation = gql`
|
||||||
|
|||||||
@ -1,44 +1,41 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="ds-mt-base ds-mb-xxx-small">
|
<div class="ds-mt-base ds-mb-xxx-small">
|
||||||
<ds-form
|
<form
|
||||||
v-if="!changePasswordResult"
|
v-if="!changePasswordResult"
|
||||||
v-model="formData"
|
@submit.prevent="onSubmit"
|
||||||
:schema="formSchema"
|
|
||||||
@submit="handleSubmitPassword"
|
|
||||||
class="change-password"
|
class="change-password"
|
||||||
|
novalidate
|
||||||
>
|
>
|
||||||
<template #default="{ errors }">
|
<ds-input
|
||||||
<ds-input
|
id="password"
|
||||||
id="password"
|
model="password"
|
||||||
model="password"
|
type="password"
|
||||||
type="password"
|
autocomplete="off"
|
||||||
autocomplete="off"
|
:label="$t('settings.security.change-password.label-new-password')"
|
||||||
:label="$t('settings.security.change-password.label-new-password')"
|
/>
|
||||||
/>
|
<ds-input
|
||||||
<ds-input
|
id="passwordConfirmation"
|
||||||
id="passwordConfirmation"
|
model="passwordConfirmation"
|
||||||
model="passwordConfirmation"
|
type="password"
|
||||||
type="password"
|
autocomplete="off"
|
||||||
autocomplete="off"
|
:label="$t('settings.security.change-password.label-new-password-confirm')"
|
||||||
:label="$t('settings.security.change-password.label-new-password-confirm')"
|
/>
|
||||||
/>
|
<password-strength :password="formData.password" />
|
||||||
<password-strength :password="formData.password" />
|
<div class="ds-mt-base ds-mb-xxx-small">
|
||||||
<div class="ds-mt-base ds-mb-xxx-small">
|
<os-button
|
||||||
<os-button
|
variant="primary"
|
||||||
variant="primary"
|
appearance="filled"
|
||||||
appearance="filled"
|
:loading="$apollo.loading"
|
||||||
:loading="$apollo.loading"
|
:disabled="!!formErrors"
|
||||||
:disabled="!!errors"
|
type="submit"
|
||||||
type="submit"
|
>
|
||||||
>
|
<template #icon>
|
||||||
<template #icon>
|
<os-icon :icon="icons.lock" />
|
||||||
<os-icon :icon="icons.lock" />
|
</template>
|
||||||
</template>
|
{{ $t('settings.security.change-password.button') }}
|
||||||
{{ $t('settings.security.change-password.button') }}
|
</os-button>
|
||||||
</os-button>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
</template>
|
|
||||||
</ds-form>
|
|
||||||
<div v-else class="ds-mb-large">
|
<div v-else class="ds-mb-large">
|
||||||
<template v-if="changePasswordResult === 'success'">
|
<template v-if="changePasswordResult === 'success'">
|
||||||
<transition name="ds-transition-fade">
|
<transition name="ds-transition-fade">
|
||||||
@ -75,8 +72,10 @@ import PasswordStrength from '../Password/Strength'
|
|||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
import { SweetalertIcon } from 'vue-sweetalert-icons'
|
import { SweetalertIcon } from 'vue-sweetalert-icons'
|
||||||
import PasswordForm from '~/components/utils/PasswordFormHelper'
|
import PasswordForm from '~/components/utils/PasswordFormHelper'
|
||||||
|
import formValidation from '~/mixins/formValidation'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
mixins: [formValidation],
|
||||||
components: {
|
components: {
|
||||||
OsButton,
|
OsButton,
|
||||||
OsIcon,
|
OsIcon,
|
||||||
@ -105,6 +104,9 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
onSubmit() {
|
||||||
|
this.formSubmit(this.handleSubmitPassword)
|
||||||
|
},
|
||||||
async handleSubmitPassword() {
|
async handleSubmitPassword() {
|
||||||
const mutation = gql`
|
const mutation = gql`
|
||||||
mutation ($nonce: String!, $email: String!, $password: String!) {
|
mutation ($nonce: String!, $email: String!, $password: String!) {
|
||||||
|
|||||||
@ -1,12 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<ds-form
|
<form v-if="!submitted" @submit.prevent="onSubmit" novalidate>
|
||||||
v-if="!submitted"
|
|
||||||
@input="handleInput"
|
|
||||||
@input-valid="handleInputValid"
|
|
||||||
v-model="formData"
|
|
||||||
:schema="formSchema"
|
|
||||||
@submit="handleSubmit"
|
|
||||||
>
|
|
||||||
<div class="ds-my-small">
|
<div class="ds-my-small">
|
||||||
<ds-input
|
<ds-input
|
||||||
:placeholder="$t('login.email')"
|
:placeholder="$t('login.email')"
|
||||||
@ -32,7 +25,7 @@
|
|||||||
{{ $t('components.password-reset.request.form.submit') }}
|
{{ $t('components.password-reset.request.form.submit') }}
|
||||||
</os-button>
|
</os-button>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</ds-form>
|
</form>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<transition name="ds-transition-fade">
|
<transition name="ds-transition-fade">
|
||||||
<div class="ds-flex ds-flex-centered">
|
<div class="ds-flex ds-flex-centered">
|
||||||
@ -47,8 +40,10 @@
|
|||||||
import { OsButton } from '@ocelot-social/ui'
|
import { OsButton } from '@ocelot-social/ui'
|
||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
import { SweetalertIcon } from 'vue-sweetalert-icons'
|
import { SweetalertIcon } from 'vue-sweetalert-icons'
|
||||||
|
import formValidation from '~/mixins/formValidation'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
mixins: [formValidation],
|
||||||
components: {
|
components: {
|
||||||
OsButton,
|
OsButton,
|
||||||
SweetalertIcon,
|
SweetalertIcon,
|
||||||
@ -82,6 +77,9 @@ export default {
|
|||||||
handleInputValid() {
|
handleInputValid() {
|
||||||
this.disabled = false
|
this.disabled = false
|
||||||
},
|
},
|
||||||
|
onSubmit() {
|
||||||
|
this.formSubmit(this.handleSubmit)
|
||||||
|
},
|
||||||
async handleSubmit() {
|
async handleSubmit() {
|
||||||
const mutation = gql`
|
const mutation = gql`
|
||||||
mutation ($email: String!, $locale: String!) {
|
mutation ($email: String!, $locale: String!) {
|
||||||
|
|||||||
@ -25,15 +25,7 @@
|
|||||||
<div class="ds-my-xxx-small"></div>
|
<div class="ds-my-xxx-small"></div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="create-account-card">
|
<div v-else class="create-account-card">
|
||||||
<ds-form
|
<form class="create-user-account" novalidate>
|
||||||
class="create-user-account"
|
|
||||||
v-model="formData"
|
|
||||||
:schema="formSchema"
|
|
||||||
@input="handleInput"
|
|
||||||
@input-valid="handleInputValid"
|
|
||||||
>
|
|
||||||
<!-- leave this here in case the scoped variable is needed in the future nobody would remember this -->
|
|
||||||
<!-- <template v-slot="{ errors }"> -->
|
|
||||||
<template>
|
<template>
|
||||||
<email-display-and-verify :email="sliderData.collectedInputData.email" />
|
<email-display-and-verify :email="sliderData.collectedInputData.email" />
|
||||||
|
|
||||||
@ -151,7 +143,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="ds-my-xxx-small"></div>
|
<div class="ds-my-xxx-small"></div>
|
||||||
</ds-form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -168,11 +160,13 @@ import PasswordForm from '~/components/utils/PasswordFormHelper'
|
|||||||
import { iconRegistry } from '~/utils/iconRegistry'
|
import { iconRegistry } from '~/utils/iconRegistry'
|
||||||
import ShowPassword from '../ShowPassword/ShowPassword.vue'
|
import ShowPassword from '../ShowPassword/ShowPassword.vue'
|
||||||
import LocationSelect from '~/components/Select/LocationSelect'
|
import LocationSelect from '~/components/Select/LocationSelect'
|
||||||
|
import formValidation from '~/mixins/formValidation'
|
||||||
|
|
||||||
const threePerEmSpace = ' ' // unicode u+2004;
|
const threePerEmSpace = ' ' // unicode u+2004;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'RegistrationSlideCreate',
|
name: 'RegistrationSlideCreate',
|
||||||
|
mixins: [formValidation],
|
||||||
components: {
|
components: {
|
||||||
EmailDisplayAndVerify,
|
EmailDisplayAndVerify,
|
||||||
PageParamsLink,
|
PageParamsLink,
|
||||||
|
|||||||
@ -1,11 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<ds-form
|
<form class="enter-email" @submit.prevent novalidate>
|
||||||
class="enter-email"
|
|
||||||
v-model="formData"
|
|
||||||
:schema="formSchema"
|
|
||||||
@input="handleInput"
|
|
||||||
@input-valid="handleInputValid"
|
|
||||||
>
|
|
||||||
<p class="ds-text">
|
<p class="ds-text">
|
||||||
{{ $t('components.registration.signup.form.description') }}
|
{{ $t('components.registration.signup.form.description') }}
|
||||||
</p>
|
</p>
|
||||||
@ -23,7 +17,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</p>
|
</p>
|
||||||
<div class="ds-my-xxx-small"></div>
|
<div class="ds-my-xxx-small"></div>
|
||||||
</ds-form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@ -31,6 +25,7 @@ import gql from 'graphql-tag'
|
|||||||
import metadata from '~/constants/metadata'
|
import metadata from '~/constants/metadata'
|
||||||
import { isEmail } from 'validator'
|
import { isEmail } from 'validator'
|
||||||
import translateErrorMessage from '~/components/utils/TranslateErrorMessage'
|
import translateErrorMessage from '~/components/utils/TranslateErrorMessage'
|
||||||
|
import formValidation from '~/mixins/formValidation'
|
||||||
|
|
||||||
export const SignupMutation = gql`
|
export const SignupMutation = gql`
|
||||||
mutation ($email: String!, $locale: String!, $inviteCode: String) {
|
mutation ($email: String!, $locale: String!, $inviteCode: String) {
|
||||||
@ -41,6 +36,7 @@ export const SignupMutation = gql`
|
|||||||
`
|
`
|
||||||
export default {
|
export default {
|
||||||
name: 'RegistrationSlideEmail',
|
name: 'RegistrationSlideEmail',
|
||||||
|
mixins: [formValidation],
|
||||||
props: {
|
props: {
|
||||||
sliderData: { type: Object, required: true },
|
sliderData: { type: Object, required: true },
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,11 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<ds-form
|
<form class="enter-invite" @submit.prevent novalidate>
|
||||||
class="enter-invite"
|
|
||||||
v-model="formData"
|
|
||||||
:schema="formSchema"
|
|
||||||
@input="handleInput"
|
|
||||||
@input-valid="handleInputValid"
|
|
||||||
>
|
|
||||||
<ds-input
|
<ds-input
|
||||||
:placeholder="formSchema.inviteCode.placeholder"
|
:placeholder="formSchema.inviteCode.placeholder"
|
||||||
:minlength="formSchema.inviteCode.minLength"
|
:minlength="formSchema.inviteCode.minLength"
|
||||||
@ -40,16 +34,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
<div class="ds-my-xxx-small"></div>
|
<div class="ds-my-xxx-small"></div>
|
||||||
</ds-form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import registrationConstants from '~/constants/registrationBranded.js'
|
import registrationConstants from '~/constants/registrationBranded.js'
|
||||||
import { validateInviteCode } from '~/graphql/InviteCode'
|
import { validateInviteCode } from '~/graphql/InviteCode'
|
||||||
import ProfileAvatar from '~/components/_new/generic/ProfileAvatar/ProfileAvatar'
|
import ProfileAvatar from '~/components/_new/generic/ProfileAvatar/ProfileAvatar'
|
||||||
|
import formValidation from '~/mixins/formValidation'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'RegistrationSlideInvite',
|
name: 'RegistrationSlideInvite',
|
||||||
|
mixins: [formValidation],
|
||||||
props: {
|
props: {
|
||||||
sliderData: { type: Object, required: true },
|
sliderData: { type: Object, required: true },
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,12 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<ds-form
|
<form class="enter-nonce" @submit.prevent="onSubmit" novalidate>
|
||||||
class="enter-nonce"
|
|
||||||
v-model="formData"
|
|
||||||
:schema="formSchema"
|
|
||||||
@submit="handleSubmitVerify"
|
|
||||||
@input="handleInput"
|
|
||||||
@input-valid="handleInputValid"
|
|
||||||
>
|
|
||||||
<email-display-and-verify :email="sliderData.collectedInputData.email" />
|
<email-display-and-verify :email="sliderData.collectedInputData.email" />
|
||||||
<ds-input
|
<ds-input
|
||||||
:placeholder="$t('components.registration.email-nonce.form.nonce')"
|
:placeholder="$t('components.registration.email-nonce.form.nonce')"
|
||||||
@ -19,13 +12,14 @@
|
|||||||
</p>
|
</p>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
<div class="ds-my-xxx-small"></div>
|
<div class="ds-my-xxx-small"></div>
|
||||||
</ds-form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
import { isEmail } from 'validator'
|
import { isEmail } from 'validator'
|
||||||
import registrationConstants from '~/constants/registration'
|
import registrationConstants from '~/constants/registration'
|
||||||
|
import formValidation from '~/mixins/formValidation'
|
||||||
|
|
||||||
import EmailDisplayAndVerify from './EmailDisplayAndVerify'
|
import EmailDisplayAndVerify from './EmailDisplayAndVerify'
|
||||||
|
|
||||||
@ -36,6 +30,7 @@ export const verifyNonceQuery = gql`
|
|||||||
`
|
`
|
||||||
export default {
|
export default {
|
||||||
name: 'RegistrationSlideNonce',
|
name: 'RegistrationSlideNonce',
|
||||||
|
mixins: [formValidation],
|
||||||
components: {
|
components: {
|
||||||
EmailDisplayAndVerify,
|
EmailDisplayAndVerify,
|
||||||
},
|
},
|
||||||
@ -101,6 +96,9 @@ export default {
|
|||||||
async handleInputValid() {
|
async handleInputValid() {
|
||||||
this.sendValidation()
|
this.sendValidation()
|
||||||
},
|
},
|
||||||
|
onSubmit() {
|
||||||
|
this.formSubmit(this.handleSubmitVerify)
|
||||||
|
},
|
||||||
async handleSubmitVerify() {
|
async handleSubmitVerify() {
|
||||||
const { email, nonce } = this.sliderData.collectedInputData
|
const { email, nonce } = this.sliderData.collectedInputData
|
||||||
const variables = { email, nonce }
|
const variables = { email, nonce }
|
||||||
|
|||||||
@ -1,12 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="!data && !error" class="ds-my-large">
|
<div v-if="!data && !error" class="ds-my-large">
|
||||||
<ds-form
|
<form @submit.prevent="onSubmit" novalidate>
|
||||||
@input="handleInput"
|
|
||||||
@input-valid="handleInputValid"
|
|
||||||
v-model="formData"
|
|
||||||
:schema="formSchema"
|
|
||||||
@submit="handleSubmit"
|
|
||||||
>
|
|
||||||
<h1>
|
<h1>
|
||||||
{{
|
{{
|
||||||
invitation
|
invitation
|
||||||
@ -40,7 +34,7 @@
|
|||||||
{{ $t('components.registration.signup.form.submit') }}
|
{{ $t('components.registration.signup.form.submit') }}
|
||||||
</os-button>
|
</os-button>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</ds-form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="ds-my-large">
|
<div v-else class="ds-my-large">
|
||||||
<template v-if="!error">
|
<template v-if="!error">
|
||||||
@ -67,6 +61,7 @@ import gql from 'graphql-tag'
|
|||||||
import metadata from '~/constants/metadata'
|
import metadata from '~/constants/metadata'
|
||||||
import { SweetalertIcon } from 'vue-sweetalert-icons'
|
import { SweetalertIcon } from 'vue-sweetalert-icons'
|
||||||
import translateErrorMessage from '~/components/utils/TranslateErrorMessage'
|
import translateErrorMessage from '~/components/utils/TranslateErrorMessage'
|
||||||
|
import formValidation from '~/mixins/formValidation'
|
||||||
|
|
||||||
export const SignupMutation = gql`
|
export const SignupMutation = gql`
|
||||||
mutation ($email: String!, $locale: String!, $inviteCode: String) {
|
mutation ($email: String!, $locale: String!, $inviteCode: String) {
|
||||||
@ -77,6 +72,7 @@ export const SignupMutation = gql`
|
|||||||
`
|
`
|
||||||
export default {
|
export default {
|
||||||
name: 'Signup',
|
name: 'Signup',
|
||||||
|
mixins: [formValidation],
|
||||||
components: {
|
components: {
|
||||||
OsButton,
|
OsButton,
|
||||||
SweetalertIcon,
|
SweetalertIcon,
|
||||||
@ -115,6 +111,9 @@ export default {
|
|||||||
handleInputValid() {
|
handleInputValid() {
|
||||||
this.disabled = false
|
this.disabled = false
|
||||||
},
|
},
|
||||||
|
onSubmit() {
|
||||||
|
this.formSubmit(this.handleSubmit)
|
||||||
|
},
|
||||||
async handleSubmit() {
|
async handleSubmit() {
|
||||||
const { email } = this.formData
|
const { email } = this.formData
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<ds-form
|
<form @submit.prevent="onSubmit" novalidate>
|
||||||
v-model="formData"
|
|
||||||
:schema="formSchema"
|
|
||||||
@input="handleInput"
|
|
||||||
@input-valid="handleInputValid"
|
|
||||||
@submit="handleSubmitItem"
|
|
||||||
>
|
|
||||||
<div v-if="isEditing">
|
<div v-if="isEditing">
|
||||||
<div class="ds-my-base">
|
<div class="ds-my-base">
|
||||||
<h5 class="ds-heading ds-heading-h5">
|
<h5 class="ds-heading ds-heading-h5">
|
||||||
@ -77,16 +71,18 @@
|
|||||||
</os-button>
|
</os-button>
|
||||||
</div>
|
</div>
|
||||||
<confirm-modal v-if="showConfirmModal" :modalData="currentModalData" @close="closeModal" />
|
<confirm-modal v-if="showConfirmModal" :modalData="currentModalData" @close="closeModal" />
|
||||||
</ds-form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { OsButton, OsIcon } from '@ocelot-social/ui'
|
import { OsButton, OsIcon } from '@ocelot-social/ui'
|
||||||
import { iconRegistry } from '~/utils/iconRegistry'
|
import { iconRegistry } from '~/utils/iconRegistry'
|
||||||
import ConfirmModal from '~/components/Modal/ConfirmModal'
|
import ConfirmModal from '~/components/Modal/ConfirmModal'
|
||||||
|
import formValidation from '~/mixins/formValidation'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'MySomethingList',
|
name: 'MySomethingList',
|
||||||
|
mixins: [formValidation],
|
||||||
components: { ConfirmModal, OsButton, OsIcon },
|
components: { ConfirmModal, OsButton, OsIcon },
|
||||||
props: {
|
props: {
|
||||||
useFormData: { type: Object, default: () => ({}) },
|
useFormData: { type: Object, default: () => ({}) },
|
||||||
@ -139,6 +135,9 @@ export default {
|
|||||||
this.icons = iconRegistry
|
this.icons = iconRegistry
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
onSubmit() {
|
||||||
|
this.formSubmit(this.handleSubmitItem)
|
||||||
|
},
|
||||||
handleInput(data) {
|
handleInput(data) {
|
||||||
this.callbacks.handleInput(this, data)
|
this.callbacks.handleInput(this, data)
|
||||||
this.disabled = true
|
this.disabled = true
|
||||||
|
|||||||
@ -2,6 +2,29 @@ import { debounce } from 'lodash'
|
|||||||
import { checkSlugAvailableQuery } from '~/graphql/User.js'
|
import { checkSlugAvailableQuery } from '~/graphql/User.js'
|
||||||
|
|
||||||
export default function UniqueSlugForm({ translate, apollo, currentUser }) {
|
export default function UniqueSlugForm({ translate, apollo, currentUser }) {
|
||||||
|
let pendingCallback = null
|
||||||
|
|
||||||
|
const debouncedSlugCheck = debounce((value, callback) => {
|
||||||
|
const variables = { slug: value }
|
||||||
|
apollo
|
||||||
|
.query({ query: checkSlugAvailableQuery, variables })
|
||||||
|
.then((response) => {
|
||||||
|
const {
|
||||||
|
data: { User },
|
||||||
|
} = response
|
||||||
|
const existingSlug = User && User[0] && User[0].slug
|
||||||
|
const available = !existingSlug || existingSlug === currentUser.slug
|
||||||
|
if (!available) {
|
||||||
|
callback(new Error(translate('settings.validation.slug.alreadyTaken')))
|
||||||
|
} else {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
callback()
|
||||||
|
})
|
||||||
|
}, 500)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
formSchema: {
|
formSchema: {
|
||||||
slug: [
|
slug: [
|
||||||
@ -13,21 +36,20 @@ export default function UniqueSlugForm({ translate, apollo, currentUser }) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
asyncValidator(rule, value, callback) {
|
asyncValidator(rule, value, callback) {
|
||||||
debounce(() => {
|
// Resolve any pending callback from a previous debounced call
|
||||||
const variables = { slug: value }
|
// that was cancelled, so async-validator doesn't hang
|
||||||
apollo.query({ query: checkSlugAvailableQuery, variables }).then((response) => {
|
if (pendingCallback) {
|
||||||
const {
|
pendingCallback()
|
||||||
data: { User },
|
}
|
||||||
} = response
|
pendingCallback = callback
|
||||||
const existingSlug = User && User[0] && User[0].slug
|
debouncedSlugCheck(value, (error) => {
|
||||||
const available = !existingSlug || existingSlug === currentUser.slug
|
pendingCallback = null
|
||||||
if (!available) {
|
if (error) {
|
||||||
callback(new Error(translate('settings.validation.slug.alreadyTaken')))
|
callback(error)
|
||||||
} else {
|
} else {
|
||||||
callback()
|
callback()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, 500)()
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
102
webapp/mixins/formValidation.js
Normal file
102
webapp/mixins/formValidation.js
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import Schema from 'async-validator'
|
||||||
|
|
||||||
|
Schema.warning = function () {}
|
||||||
|
|
||||||
|
function cloneDeep(obj) {
|
||||||
|
return JSON.parse(JSON.stringify(obj))
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
provide() {
|
||||||
|
return {
|
||||||
|
$parentForm: this.$formProxy,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
formErrors: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeCreate() {
|
||||||
|
const vm = this
|
||||||
|
const subscribers = []
|
||||||
|
this.$formProxy = {
|
||||||
|
subscribe(cb) {
|
||||||
|
if (cb && typeof cb === 'function') {
|
||||||
|
cb(cloneDeep(vm.formData))
|
||||||
|
subscribers.push(cb)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
unsubscribe(cb) {
|
||||||
|
const index = subscribers.indexOf(cb)
|
||||||
|
if (index > -1) {
|
||||||
|
subscribers.splice(index, 1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
update(model, value) {
|
||||||
|
vm.updateFormField(model, value)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
this.$formSubscribers = subscribers
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
formData: {
|
||||||
|
handler(value) {
|
||||||
|
this.$notifyFormSubscribers(value, this.formErrors)
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateFormField(model, value) {
|
||||||
|
this.$set(this.formData, model, value)
|
||||||
|
if (typeof this.handleInput === 'function') {
|
||||||
|
this.handleInput(cloneDeep(this.formData))
|
||||||
|
}
|
||||||
|
this.$validateForm(() => {
|
||||||
|
if (typeof this.handleInputValid === 'function') {
|
||||||
|
this.handleInputValid(cloneDeep(this.formData))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
formSubmit(callback) {
|
||||||
|
this.$validateForm(() => {
|
||||||
|
if (callback && typeof callback === 'function') {
|
||||||
|
callback(cloneDeep(this.formData))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
$validateForm(cb) {
|
||||||
|
const schema = this.formSchema
|
||||||
|
if (!schema || Object.keys(schema).length === 0) {
|
||||||
|
this.formErrors = null
|
||||||
|
this.$notifyFormSubscribers(this.formData, null)
|
||||||
|
if (cb && typeof cb === 'function') {
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const validator = new Schema(schema)
|
||||||
|
validator.validate(this.formData, (errors) => {
|
||||||
|
if (errors) {
|
||||||
|
this.formErrors = errors.reduce((errorObj, error) => {
|
||||||
|
const result = { ...errorObj }
|
||||||
|
result[error.field] = error.message
|
||||||
|
return result
|
||||||
|
}, {})
|
||||||
|
} else {
|
||||||
|
this.formErrors = null
|
||||||
|
}
|
||||||
|
this.$notifyFormSubscribers(this.formData, this.formErrors)
|
||||||
|
if (!errors && cb && typeof cb === 'function') {
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
$notifyFormSubscribers(data, errors) {
|
||||||
|
this.$formSubscribers.forEach((cb) => {
|
||||||
|
cb(cloneDeep(data), errors)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<os-card>
|
<os-card>
|
||||||
<h2 class="title">{{ $t('admin.donations.name') }}</h2>
|
<h2 class="title">{{ $t('admin.donations.name') }}</h2>
|
||||||
<ds-form v-model="formData" @submit="submit">
|
<form @submit.prevent="submit" novalidate>
|
||||||
<p class="ds-text show-donations-checkbox">
|
<p class="ds-text show-donations-checkbox">
|
||||||
<input id="showDonations" type="checkbox" v-model="showDonations" />
|
<input id="showDonations" type="checkbox" v-model="showDonations" />
|
||||||
<label for="showDonations">
|
<label for="showDonations">
|
||||||
@ -31,15 +31,17 @@
|
|||||||
<os-button class="donations-info-button" variant="primary" appearance="filled" type="submit">
|
<os-button class="donations-info-button" variant="primary" appearance="filled" type="submit">
|
||||||
{{ $t('actions.save') }}
|
{{ $t('actions.save') }}
|
||||||
</os-button>
|
</os-button>
|
||||||
</ds-form>
|
</form>
|
||||||
</os-card>
|
</os-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { OsButton, OsCard } from '@ocelot-social/ui'
|
import { OsButton, OsCard } from '@ocelot-social/ui'
|
||||||
import { DonationsQuery, UpdateDonations } from '~/graphql/Donations'
|
import { DonationsQuery, UpdateDonations } from '~/graphql/Donations'
|
||||||
|
import formValidation from '~/mixins/formValidation'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
mixins: [formValidation],
|
||||||
components: { OsButton, OsCard },
|
components: { OsButton, OsCard },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -14,8 +14,6 @@ exports[`Users given badges are disabled renders 1`] = `
|
|||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<form
|
<form
|
||||||
autocomplete="off"
|
|
||||||
class="ds-form"
|
|
||||||
novalidate="novalidate"
|
novalidate="novalidate"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@ -465,8 +463,6 @@ exports[`Users given badges are enabled renders 1`] = `
|
|||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<form
|
<form
|
||||||
autocomplete="off"
|
|
||||||
class="ds-form"
|
|
||||||
novalidate="novalidate"
|
novalidate="novalidate"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
<div class="admin-users">
|
<div class="admin-users">
|
||||||
<os-card>
|
<os-card>
|
||||||
<h2 class="title">{{ $t('admin.users.name') }}</h2>
|
<h2 class="title">{{ $t('admin.users.name') }}</h2>
|
||||||
<ds-form v-model="form" @submit="submit">
|
<form @submit.prevent="onSubmit" novalidate>
|
||||||
<div class="ds-flex ds-flex-gap-small">
|
<div class="ds-flex ds-flex-gap-small">
|
||||||
<div style="flex: 0 0 90%; width: 90%">
|
<div style="flex: 0 0 90%; width: 90%">
|
||||||
<ds-input
|
<ds-input
|
||||||
@ -24,7 +24,7 @@
|
|||||||
</os-button>
|
</os-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ds-form>
|
</form>
|
||||||
</os-card>
|
</os-card>
|
||||||
<os-card v-if="User && User.length">
|
<os-card v-if="User && User.length">
|
||||||
<div class="ds-table-wrap">
|
<div class="ds-table-wrap">
|
||||||
@ -162,8 +162,10 @@ import { isEmail } from 'validator'
|
|||||||
import PaginationButtons from '~/components/_new/generic/PaginationButtons/PaginationButtons'
|
import PaginationButtons from '~/components/_new/generic/PaginationButtons/PaginationButtons'
|
||||||
import { adminUserQuery } from '~/graphql/User'
|
import { adminUserQuery } from '~/graphql/User'
|
||||||
import { FetchAllRoles, updateUserRole } from '~/graphql/admin/Roles'
|
import { FetchAllRoles, updateUserRole } from '~/graphql/admin/Roles'
|
||||||
|
import formValidation from '~/mixins/formValidation'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
mixins: [formValidation],
|
||||||
components: {
|
components: {
|
||||||
OsButton,
|
OsButton,
|
||||||
OsCard,
|
OsCard,
|
||||||
@ -184,10 +186,8 @@ export default {
|
|||||||
email: null,
|
email: null,
|
||||||
filter: null,
|
filter: null,
|
||||||
userRoles: [],
|
userRoles: [],
|
||||||
form: {
|
formData: {
|
||||||
formData: {
|
query: '',
|
||||||
query: '',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -234,9 +234,9 @@ export default {
|
|||||||
next() {
|
next() {
|
||||||
this.offset += this.pageSize
|
this.offset += this.pageSize
|
||||||
},
|
},
|
||||||
submit(formData) {
|
onSubmit() {
|
||||||
this.offset = 0
|
this.offset = 0
|
||||||
const { query } = formData
|
const { query } = this.formData
|
||||||
if (isEmail(query)) {
|
if (isEmail(query)) {
|
||||||
this.email = query
|
this.email = query
|
||||||
this.filter = null
|
this.filter = null
|
||||||
|
|||||||
@ -29,7 +29,7 @@ describe('change-password', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('renders', () => {
|
it('renders', () => {
|
||||||
expect(wrapper.findAll('.ds-form')).toHaveLength(1)
|
expect(wrapper.findAll('form')).toHaveLength(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -30,7 +30,7 @@ describe('enter-nonce.vue', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('renders', () => {
|
it('renders', () => {
|
||||||
expect(wrapper.findAll('.ds-form')).toHaveLength(1)
|
expect(wrapper.findAll('form')).toHaveLength(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -33,7 +33,7 @@ describe('request.vue', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('renders', () => {
|
it('renders', () => {
|
||||||
expect(wrapper.findAll('.ds-form')).toHaveLength(1)
|
expect(wrapper.findAll('form')).toHaveLength(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('navigates to enter-nonce on handlePasswordResetRequested', () => {
|
it('navigates to enter-nonce on handlePasswordResetRequested', () => {
|
||||||
|
|||||||
@ -114,7 +114,7 @@ describe('index.vue', () => {
|
|||||||
const wrapper = Wrapper()
|
const wrapper = Wrapper()
|
||||||
|
|
||||||
wrapper.find('#name').setValue('Peter')
|
wrapper.find('#name').setValue('Peter')
|
||||||
wrapper.find('.ds-form').trigger('submit')
|
wrapper.find('form').trigger('submit')
|
||||||
|
|
||||||
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
@ -130,7 +130,7 @@ describe('index.vue', () => {
|
|||||||
const wrapper = Wrapper()
|
const wrapper = Wrapper()
|
||||||
|
|
||||||
wrapper.find('#name').setValue('Peter')
|
wrapper.find('#name').setValue('Peter')
|
||||||
wrapper.find('.ds-form').trigger('submit')
|
wrapper.find('form').trigger('submit')
|
||||||
|
|
||||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(
|
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
@ -147,7 +147,7 @@ describe('index.vue', () => {
|
|||||||
const wrapper = Wrapper()
|
const wrapper = Wrapper()
|
||||||
|
|
||||||
wrapper.find('#slug').setValue('peter-der-lustige')
|
wrapper.find('#slug').setValue('peter-der-lustige')
|
||||||
wrapper.find('.ds-form').trigger('submit')
|
wrapper.find('form').trigger('submit')
|
||||||
|
|
||||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(
|
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
@ -163,7 +163,7 @@ describe('index.vue', () => {
|
|||||||
it('calls updateUser mutation', async () => {
|
it('calls updateUser mutation', async () => {
|
||||||
const wrapper = Wrapper()
|
const wrapper = Wrapper()
|
||||||
wrapper.findComponent(LocationSelect).vm.$emit('input', 'Berlin, Germany')
|
wrapper.findComponent(LocationSelect).vm.$emit('input', 'Berlin, Germany')
|
||||||
wrapper.find('.ds-form').trigger('submit')
|
wrapper.find('form').trigger('submit')
|
||||||
|
|
||||||
await expect(mocks.$apollo.mutate).toHaveBeenCalledWith(
|
await expect(mocks.$apollo.mutate).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
@ -180,7 +180,7 @@ describe('index.vue', () => {
|
|||||||
const wrapper = Wrapper()
|
const wrapper = Wrapper()
|
||||||
|
|
||||||
wrapper.find('#about').setValue('I am Peter!111elf')
|
wrapper.find('#about').setValue('I am Peter!111elf')
|
||||||
wrapper.find('.ds-form').trigger('submit')
|
wrapper.find('form').trigger('submit')
|
||||||
|
|
||||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(
|
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
@ -199,7 +199,7 @@ describe('index.vue', () => {
|
|||||||
wrapper.find('#slug').setValue('peter-der-lustige')
|
wrapper.find('#slug').setValue('peter-der-lustige')
|
||||||
await wrapper.findComponent(LocationSelect).vm.$emit('input', 'Hamburg, Germany')
|
await wrapper.findComponent(LocationSelect).vm.$emit('input', 'Hamburg, Germany')
|
||||||
wrapper.find('#about').setValue('I am Peter!111elf')
|
wrapper.find('#about').setValue('I am Peter!111elf')
|
||||||
wrapper.find('.ds-form').trigger('submit')
|
wrapper.find('form').trigger('submit')
|
||||||
|
|
||||||
await expect(mocks.$apollo.mutate).toHaveBeenCalledWith(
|
await expect(mocks.$apollo.mutate).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
|
|||||||
@ -1,47 +1,45 @@
|
|||||||
<template>
|
<template>
|
||||||
<ds-form class="settings-form" v-model="formData" :schema="formSchema" @submit="submit">
|
<form class="settings-form" @submit.prevent="onSubmit" novalidate>
|
||||||
<template #default="{ errors }">
|
<os-card>
|
||||||
<os-card>
|
<h2 class="title">{{ $t('settings.data.name') }}</h2>
|
||||||
<h2 class="title">{{ $t('settings.data.name') }}</h2>
|
<ds-input
|
||||||
<ds-input
|
id="name"
|
||||||
id="name"
|
model="name"
|
||||||
model="name"
|
icon="user"
|
||||||
icon="user"
|
:label="
|
||||||
:label="
|
$env.ASK_FOR_REAL_NAME
|
||||||
$env.ASK_FOR_REAL_NAME
|
? $t('settings.data.realNamePlease')
|
||||||
? $t('settings.data.realNamePlease')
|
: $t('settings.data.labelName')
|
||||||
: $t('settings.data.labelName')
|
"
|
||||||
"
|
:placeholder="$t('settings.data.namePlaceholder')"
|
||||||
:placeholder="$t('settings.data.namePlaceholder')"
|
/>
|
||||||
/>
|
<ds-input id="slug" model="slug" icon="at" :label="$t('settings.data.labelSlug')" />
|
||||||
<ds-input id="slug" model="slug" icon="at" :label="$t('settings.data.labelSlug')" />
|
<location-select
|
||||||
<location-select
|
class="location-selet"
|
||||||
class="location-selet"
|
v-model="formData.locationName"
|
||||||
v-model="formData.locationName"
|
:canBeCleared="!$env.REQUIRE_LOCATION"
|
||||||
:canBeCleared="!$env.REQUIRE_LOCATION"
|
/>
|
||||||
/>
|
<!-- eslint-enable vue/use-v-on-exact -->
|
||||||
<!-- eslint-enable vue/use-v-on-exact -->
|
<ds-input
|
||||||
<ds-input
|
id="about"
|
||||||
id="about"
|
model="about"
|
||||||
model="about"
|
type="textarea"
|
||||||
type="textarea"
|
rows="3"
|
||||||
rows="3"
|
:label="$t('settings.data.labelBio')"
|
||||||
:label="$t('settings.data.labelBio')"
|
:placeholder="$t('settings.data.labelBio')"
|
||||||
:placeholder="$t('settings.data.labelBio')"
|
/>
|
||||||
/>
|
<os-button
|
||||||
<os-button
|
variant="primary"
|
||||||
variant="primary"
|
appearance="filled"
|
||||||
appearance="filled"
|
type="submit"
|
||||||
type="submit"
|
:disabled="!!formErrors"
|
||||||
:disabled="!!errors"
|
:loading="loadingData"
|
||||||
:loading="loadingData"
|
>
|
||||||
>
|
<template #icon><os-icon :icon="icons.check" /></template>
|
||||||
<template #icon><os-icon :icon="icons.check" /></template>
|
{{ $t('actions.save') }}
|
||||||
{{ $t('actions.save') }}
|
</os-button>
|
||||||
</os-button>
|
</os-card>
|
||||||
</os-card>
|
</form>
|
||||||
</template>
|
|
||||||
</ds-form>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@ -52,9 +50,10 @@ import UniqueSlugForm from '~/components/utils/UniqueSlugForm'
|
|||||||
import LocationSelect from '~/components/Select/LocationSelect'
|
import LocationSelect from '~/components/Select/LocationSelect'
|
||||||
import { updateUserMutation } from '~/graphql/User'
|
import { updateUserMutation } from '~/graphql/User'
|
||||||
import scrollToContent from './scroll-to-content.js'
|
import scrollToContent from './scroll-to-content.js'
|
||||||
|
import formValidation from '~/mixins/formValidation'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [scrollToContent],
|
mixins: [scrollToContent, formValidation],
|
||||||
name: 'Settings',
|
name: 'Settings',
|
||||||
components: {
|
components: {
|
||||||
OsButton,
|
OsButton,
|
||||||
@ -105,6 +104,9 @@ export default {
|
|||||||
...mapMutations({
|
...mapMutations({
|
||||||
setCurrentUser: 'auth/SET_USER',
|
setCurrentUser: 'auth/SET_USER',
|
||||||
}),
|
}),
|
||||||
|
onSubmit() {
|
||||||
|
this.formSubmit(this.submit)
|
||||||
|
},
|
||||||
async submit() {
|
async submit() {
|
||||||
this.loadingData = true
|
this.loadingData = true
|
||||||
const { name, slug, about } = this.formData
|
const { name, slug, about } = this.formData
|
||||||
|
|||||||
@ -1,58 +1,59 @@
|
|||||||
<template>
|
<template>
|
||||||
<ds-form v-model="form" :schema="formSchema" @submit="submit">
|
<form @submit.prevent="onSubmit" novalidate>
|
||||||
<template #default="{ errors }">
|
<os-card>
|
||||||
<os-card>
|
<h2 class="title">{{ $t('settings.email.name') }}</h2>
|
||||||
<h2 class="title">{{ $t('settings.email.name') }}</h2>
|
<ds-input
|
||||||
<ds-input
|
id="email"
|
||||||
id="email"
|
model="email"
|
||||||
model="email"
|
icon="envelope"
|
||||||
icon="envelope"
|
disabled
|
||||||
disabled
|
:label="$t('settings.email.labelNewEmail')"
|
||||||
:label="$t('settings.email.labelNewEmail')"
|
/>
|
||||||
/>
|
<ds-input
|
||||||
<ds-input
|
id="nonce"
|
||||||
id="nonce"
|
model="nonce"
|
||||||
model="nonce"
|
icon="question-circle"
|
||||||
icon="question-circle"
|
:label="$t('settings.email.labelNonce')"
|
||||||
:label="$t('settings.email.labelNonce')"
|
/>
|
||||||
/>
|
<os-button variant="primary" appearance="filled" type="submit" :disabled="!!formErrors">
|
||||||
<os-button variant="primary" appearance="filled" type="submit" :disabled="!!errors">
|
<template #icon><os-icon :icon="icons.check" /></template>
|
||||||
<template #icon><os-icon :icon="icons.check" /></template>
|
{{ $t('actions.save') }}
|
||||||
{{ $t('actions.save') }}
|
</os-button>
|
||||||
</os-button>
|
</os-card>
|
||||||
</os-card>
|
</form>
|
||||||
</template>
|
|
||||||
</ds-form>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { OsButton, OsCard, OsIcon } from '@ocelot-social/ui'
|
import { OsButton, OsCard, OsIcon } from '@ocelot-social/ui'
|
||||||
import { iconRegistry } from '~/utils/iconRegistry'
|
import { iconRegistry } from '~/utils/iconRegistry'
|
||||||
|
import formValidation from '~/mixins/formValidation'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
mixins: [formValidation],
|
||||||
components: { OsButton, OsCard, OsIcon },
|
components: { OsButton, OsCard, OsIcon },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
formData: {
|
||||||
|
email: '',
|
||||||
|
nonce: '',
|
||||||
|
},
|
||||||
formSchema: {
|
formSchema: {
|
||||||
nonce: { type: 'string', required: true },
|
nonce: { type: 'string', required: true },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
mounted() {
|
||||||
form: {
|
const { email = '', nonce = '' } = this.$route.query
|
||||||
get: function () {
|
this.formData.email = email
|
||||||
const { email = '', nonce = '' } = this.$route.query
|
this.formData.nonce = nonce
|
||||||
return { email, nonce }
|
|
||||||
},
|
|
||||||
set: function (formData) {
|
|
||||||
this.formData = formData
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.icons = iconRegistry
|
this.icons = iconRegistry
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
onSubmit() {
|
||||||
|
this.formSubmit(this.submit)
|
||||||
|
},
|
||||||
async submit() {
|
async submit() {
|
||||||
const { email, nonce } = this.formData
|
const { email, nonce } = this.formData
|
||||||
this.$router.replace({
|
this.$router.replace({
|
||||||
|
|||||||
@ -5,34 +5,27 @@
|
|||||||
</transition>
|
</transition>
|
||||||
<p class="ds-text" v-html="submitMessage" />
|
<p class="ds-text" v-html="submitMessage" />
|
||||||
</os-card>
|
</os-card>
|
||||||
<ds-form v-else v-model="form" :schema="formSchema" @submit="submit">
|
<form v-else @submit.prevent="onSubmit" novalidate>
|
||||||
<template #default="{ errors }">
|
<os-card>
|
||||||
<os-card>
|
<h2 class="title">{{ $t('settings.email.name') }}</h2>
|
||||||
<h2 class="title">{{ $t('settings.email.name') }}</h2>
|
<ds-input id="email" model="email" icon="envelope" :label="$t('settings.email.labelEmail')" />
|
||||||
<ds-input
|
<div class="ds-mb-large backendErrors" v-if="backendErrors">
|
||||||
id="email"
|
<p class="ds-text ds-text-center ds-text-bold ds-text-danger">
|
||||||
model="email"
|
{{ backendErrors.message }}
|
||||||
icon="envelope"
|
</p>
|
||||||
:label="$t('settings.email.labelEmail')"
|
</div>
|
||||||
/>
|
<os-button
|
||||||
<div class="ds-mb-large backendErrors" v-if="backendErrors">
|
:disabled="!!formErrors"
|
||||||
<p class="ds-text ds-text-center ds-text-bold ds-text-danger">
|
:loading="loadingData"
|
||||||
{{ backendErrors.message }}
|
type="submit"
|
||||||
</p>
|
variant="primary"
|
||||||
</div>
|
appearance="filled"
|
||||||
<os-button
|
>
|
||||||
:disabled="!!errors"
|
<template #icon><os-icon :icon="icons.check" /></template>
|
||||||
:loading="loadingData"
|
{{ $t('actions.save') }}
|
||||||
type="submit"
|
</os-button>
|
||||||
variant="primary"
|
</os-card>
|
||||||
appearance="filled"
|
</form>
|
||||||
>
|
|
||||||
<template #icon><os-icon :icon="icons.check" /></template>
|
|
||||||
{{ $t('actions.save') }}
|
|
||||||
</os-button>
|
|
||||||
</os-card>
|
|
||||||
</template>
|
|
||||||
</ds-form>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@ -42,9 +35,10 @@ import { iconRegistry } from '~/utils/iconRegistry'
|
|||||||
import { AddEmailAddressMutation } from '~/graphql/EmailAddress.js'
|
import { AddEmailAddressMutation } from '~/graphql/EmailAddress.js'
|
||||||
import { SweetalertIcon } from 'vue-sweetalert-icons'
|
import { SweetalertIcon } from 'vue-sweetalert-icons'
|
||||||
import scrollToContent from '../scroll-to-content.js'
|
import scrollToContent from '../scroll-to-content.js'
|
||||||
|
import formValidation from '~/mixins/formValidation'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [scrollToContent],
|
mixins: [scrollToContent, formValidation],
|
||||||
components: {
|
components: {
|
||||||
OsButton,
|
OsButton,
|
||||||
OsCard,
|
OsCard,
|
||||||
@ -59,8 +53,14 @@ export default {
|
|||||||
backendErrors: null,
|
backendErrors: null,
|
||||||
data: null,
|
data: null,
|
||||||
loadingData: false,
|
loadingData: false,
|
||||||
|
formData: {
|
||||||
|
email: '',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.formData.email = this.currentUser.email || ''
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
submitMessage() {
|
submitMessage() {
|
||||||
const { email } = this.data.AddEmailAddress
|
const { email } = this.data.AddEmailAddress
|
||||||
@ -69,15 +69,6 @@ export default {
|
|||||||
...mapGetters({
|
...mapGetters({
|
||||||
currentUser: 'auth/user',
|
currentUser: 'auth/user',
|
||||||
}),
|
}),
|
||||||
form: {
|
|
||||||
get: function () {
|
|
||||||
const { email } = this.currentUser
|
|
||||||
return { email }
|
|
||||||
},
|
|
||||||
set: function (formData) {
|
|
||||||
this.formData = formData
|
|
||||||
},
|
|
||||||
},
|
|
||||||
formSchema() {
|
formSchema() {
|
||||||
const currentEmail = this.currentUser.email
|
const currentEmail = this.currentUser.email
|
||||||
const sameEmailValidationError = this.$t('settings.email.validation.same-email')
|
const sameEmailValidationError = this.$t('settings.email.validation.same-email')
|
||||||
@ -98,6 +89,9 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
onSubmit() {
|
||||||
|
this.formSubmit(this.submit)
|
||||||
|
},
|
||||||
async submit() {
|
async submit() {
|
||||||
this.loadingData = true
|
this.loadingData = true
|
||||||
const { email } = this.formData
|
const { email } = this.formData
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user