refactor(forms): use arktype schemas for form validation
Replaces imperative validate() blocks in TransactionCreate, signup, and AccountNew with schema-driven validation. Drops the dead "(!currency && !date && payFor.length === 0)" branch in TransactionCreate that was hiding behind the generic error.
This commit is contained in:
@@ -14,11 +14,12 @@ import TransactionType from '@/enums/TransactionType'
|
|||||||
import TransactionTag, { TransactionTagLabel } from '@/enums/TransactionTag'
|
import TransactionTag, { TransactionTagLabel } from '@/enums/TransactionTag'
|
||||||
import exchangeService from '@/services/ExchangeService'
|
import exchangeService from '@/services/ExchangeService'
|
||||||
import transactionService from '@/services/TransactionService'
|
import transactionService from '@/services/TransactionService'
|
||||||
import queueNotifService from '@/services/QueueNotifService'
|
|
||||||
import mapService from '@/services/MapService'
|
import mapService from '@/services/MapService'
|
||||||
import { money } from '@/utils/format'
|
import { money } from '@/utils/format'
|
||||||
import { findContrastColor } from '@/utils'
|
import { findContrastColor } from '@/utils'
|
||||||
import notif from '@/utils/notif'
|
import notif from '@/utils/notif'
|
||||||
|
import { validateAndNotify } from '@/utils/validate'
|
||||||
|
import { transactionFormSchema, MAX_TRANSACTION_AMOUNT } from '@/schemas/transaction'
|
||||||
import FabButton from '@/components/FabButton.vue'
|
import FabButton from '@/components/FabButton.vue'
|
||||||
import TransactionSplit from '@/components/TransactionSplit.vue'
|
import TransactionSplit from '@/components/TransactionSplit.vue'
|
||||||
import TransactionTagUpdate from '@/components/TransactionTagUpdate.vue'
|
import TransactionTagUpdate from '@/components/TransactionTagUpdate.vue'
|
||||||
@@ -46,7 +47,7 @@ const date = ref(today)
|
|||||||
const currency = ref<ICurrency | null>(null)
|
const currency = ref<ICurrency | null>(null)
|
||||||
const payBy = ref('')
|
const payBy = ref('')
|
||||||
const payFor = reactive<ISplit[]>([])
|
const payFor = reactive<ISplit[]>([])
|
||||||
const maxAmount = 1_000_000
|
const maxAmount = MAX_TRANSACTION_AMOUNT
|
||||||
const displayLocationModal = ref(false)
|
const displayLocationModal = ref(false)
|
||||||
const isSetLocation = ref(false)
|
const isSetLocation = ref(false)
|
||||||
const location = ref<ILocation | null>(null)
|
const location = ref<ILocation | null>(null)
|
||||||
@@ -120,23 +121,12 @@ const validLocation = async () => {
|
|||||||
const validate = (): boolean => {
|
const validate = (): boolean => {
|
||||||
const el = document.querySelector(':focus') as HTMLInputElement | null
|
const el = document.querySelector(':focus') as HTMLInputElement | null
|
||||||
el?.blur()
|
el?.blur()
|
||||||
if (!name.value || !amount.value || !payBy.value || (!currency.value && !date.value && payFor.length === 0)) {
|
return validateAndNotify(
|
||||||
if (amount.value === 0) {
|
transactionFormSchema,
|
||||||
queueNotifService.error('Le montant doit être supérieur à 0.')
|
{ name: name.value, amount: amount.value, payBy: payBy.value },
|
||||||
} else {
|
t,
|
||||||
queueNotifService.error('Tous les champs sont requis.')
|
{ amount: { max: money(maxAmount, currency.value) } }
|
||||||
}
|
)
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (isNaN(amount.value) || amount.value > maxAmount) {
|
|
||||||
queueNotifService.error(`Veuillez saisir un montant numérique inférieur à ${money(maxAmount, currency.value)}.`)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (amount.value < 0) {
|
|
||||||
queueNotifService.error('Le montant doit être supérieur à 0.')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const submitTransaction = async (finish: () => void) => {
|
const submitTransaction = async (finish: () => void) => {
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import type IUser from '@/models/IUser'
|
|||||||
import userService from '@/services/UserService'
|
import userService from '@/services/UserService'
|
||||||
import queueNotifService from '@/services/QueueNotifService'
|
import queueNotifService from '@/services/QueueNotifService'
|
||||||
import { slug } from '@/utils'
|
import { slug } from '@/utils'
|
||||||
|
import { validateAndNotify } from '@/utils/validate'
|
||||||
|
import { signupFormSchema } from '@/schemas/user'
|
||||||
import LangChanger from '@/components/LangChanger.vue'
|
import LangChanger from '@/components/LangChanger.vue'
|
||||||
import AccountList from '@/components/AccountList.vue'
|
import AccountList from '@/components/AccountList.vue'
|
||||||
import ConfirmButton from '@/components/ConfirmButton.vue'
|
import ConfirmButton from '@/components/ConfirmButton.vue'
|
||||||
@@ -51,17 +53,16 @@ const signin = async () => {
|
|||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const validateRegistration = (): boolean => {
|
const validateRegistration = (): boolean =>
|
||||||
if (!userId.value) {
|
validateAndNotify(
|
||||||
queueNotifService.error('Un pseudo est obligatoire.')
|
signupFormSchema,
|
||||||
return false
|
{
|
||||||
}
|
userId: userId.value,
|
||||||
if (password.value !== confirmPassword.value) {
|
password: password.value,
|
||||||
queueNotifService.error('Les mots de passe doivent être identique.')
|
confirmPassword: confirmPassword.value
|
||||||
return false
|
},
|
||||||
}
|
t
|
||||||
return true
|
)
|
||||||
}
|
|
||||||
|
|
||||||
const register = async (confirmed: boolean) => {
|
const register = async (confirmed: boolean) => {
|
||||||
if (!validateRegistration()) return
|
if (!validateRegistration()) return
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import type IUser from '@/models/IUser'
|
|||||||
import accountService from '@/services/AccountService'
|
import accountService from '@/services/AccountService'
|
||||||
import queueNotifService from '@/services/QueueNotifService'
|
import queueNotifService from '@/services/QueueNotifService'
|
||||||
import { slug } from '@/utils'
|
import { slug } from '@/utils'
|
||||||
|
import { validateAndNotify } from '@/utils/validate'
|
||||||
|
import { accountFormSchema } from '@/schemas/account'
|
||||||
import ColorPicker from '@/components/ColorPicker.vue'
|
import ColorPicker from '@/components/ColorPicker.vue'
|
||||||
import AccountUserNew from '@/components/AccountUserNew.vue'
|
import AccountUserNew from '@/components/AccountUserNew.vue'
|
||||||
|
|
||||||
@@ -55,27 +57,13 @@ watch(
|
|||||||
)
|
)
|
||||||
|
|
||||||
const validate = (allUsers: IUser[]): boolean => {
|
const validate = (allUsers: IUser[]): boolean => {
|
||||||
if (!name.value) {
|
|
||||||
queueNotifService.error('Le nom doit être rempli.')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
const aliases = allUsers.map((u) => (u.alias ? u.alias.toLowerCase() : ''))
|
const aliases = allUsers.map((u) => (u.alias ? u.alias.toLowerCase() : ''))
|
||||||
if (aliases.length === 0) {
|
const userIds = user.value ? allUsers.map((u) => u.userId) : undefined
|
||||||
queueNotifService.error('Au moins une personne doit être ajoutée au compte.')
|
return validateAndNotify(
|
||||||
return false
|
accountFormSchema,
|
||||||
}
|
{ name: name.value, aliases, userIds },
|
||||||
if (aliases.length !== new Set(aliases).size) {
|
t
|
||||||
queueNotifService.error('Les alias doivent être uniques.')
|
)
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (user.value) {
|
|
||||||
const userIds = allUsers.map((u) => u.userId)
|
|
||||||
if (userIds.length !== new Set(userIds).size) {
|
|
||||||
queueNotifService.error('Les identifiants doivent être uniques.')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const submitAccount = async () => {
|
const submitAccount = async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user