diff --git a/frontend/vue/components/Account/ApiKeyModal.vue b/frontend/vue/components/Account/ApiKeyModal.vue index 6f339c311..6bd0c0d31 100644 --- a/frontend/vue/components/Account/ApiKeyModal.vue +++ b/frontend/vue/components/Account/ApiKeyModal.vue @@ -89,7 +89,7 @@ const emit = defineEmits(['relist']); const error = ref(null); const newKey = ref(null); -const {form, resetForm, v$} = useVuelidateOnForm( +const {form, resetForm, v$, validate} = useVuelidateOnForm( { comment: {required} }, @@ -115,9 +115,9 @@ const create = () => { const {wrapWithLoading} = useNotify(); const {axios} = useAxios(); -const doSubmit = () => { - v$.value.$touch(); - if (v$.value.$errors.length > 0) { +const doSubmit = async () => { + const isValid = await validate(); + if (!isValid) { return; } diff --git a/frontend/vue/components/Account/ChangePasswordModal.vue b/frontend/vue/components/Account/ChangePasswordModal.vue index ec1b4f0cc..7120d28ce 100644 --- a/frontend/vue/components/Account/ChangePasswordModal.vue +++ b/frontend/vue/components/Account/ChangePasswordModal.vue @@ -74,7 +74,7 @@ const passwordsMatch = (value, siblings) => { const {$gettext} = useTranslate(); -const {form, resetForm, v$} = useVuelidateOnForm( +const {form, resetForm, v$, ifValid} = useVuelidateOnForm( { current_password: {required}, new_password: {required, validatePassword}, @@ -108,16 +108,13 @@ const {wrapWithLoading} = useNotify(); const {axios} = useAxios(); const onSubmit = () => { - v$.value.$touch(); - if (v$.value.$errors.length > 0) { - return; - } - - wrapWithLoading( - axios.put(props.changePasswordUrl, form.value) - ).finally(() => { - $modal.value.hide(); - emit('relist'); + ifValid(() => { + wrapWithLoading( + axios.put(props.changePasswordUrl, form.value) + ).finally(() => { + $modal.value.hide(); + emit('relist'); + }); }); }; diff --git a/frontend/vue/components/Account/EditModal.vue b/frontend/vue/components/Account/EditModal.vue index ce2883f6e..bd1666ea5 100644 --- a/frontend/vue/components/Account/EditModal.vue +++ b/frontend/vue/components/Account/EditModal.vue @@ -41,7 +41,7 @@ const emit = defineEmits(['reload']); const loading = ref(true); const error = ref(null); -const {form, resetForm, v$} = useVuelidateOnForm( +const {form, resetForm, v$, ifValid} = useVuelidateOnForm( { name: {}, email: {required, email}, @@ -89,25 +89,22 @@ const open = () => { }; const doSubmit = () => { - v$.value.$touch(); - if (v$.value.$errors.length > 0) { - return; - } + ifValid(() => { + error.value = null; - error.value = null; - - wrapWithLoading( - axios({ - method: 'PUT', - url: props.userUrl, - data: form.value - }) - ).then(() => { - notifySuccess(); - emit('reload'); - close(); - }).catch((error) => { - error.value = error.response.data.message; + wrapWithLoading( + axios({ + method: 'PUT', + url: props.userUrl, + data: form.value + }) + ).then(() => { + notifySuccess(); + emit('reload'); + close(); + }).catch((error) => { + error.value = error.response.data.message; + }); }); }; diff --git a/frontend/vue/components/Account/TwoFactorModal.vue b/frontend/vue/components/Account/TwoFactorModal.vue index 1f9a61b78..2bc222228 100644 --- a/frontend/vue/components/Account/TwoFactorModal.vue +++ b/frontend/vue/components/Account/TwoFactorModal.vue @@ -97,7 +97,7 @@ const emit = defineEmits(['relist']); const loading = ref(true); const error = ref(null); -const {form, resetForm, v$} = useVuelidateOnForm( +const {form, resetForm, v$, ifValid} = useVuelidateOnForm( { otp: { required, @@ -150,28 +150,25 @@ const open = () => { }; const doSubmit = () => { - v$.value.$touch(); - if (v$.value.$errors.length > 0) { - return; - } + ifValid(() => { + error.value = null; - error.value = null; - - wrapWithLoading( - axios({ - method: 'PUT', - url: props.twoFactorUrl, - data: { - secret: totp.value.secret, - otp: form.value.otp - } - }) - ).then(() => { - notifySuccess(); - emit('relist'); - close(); - }).catch((error) => { - error.value = error.response.data.message; + wrapWithLoading( + axios({ + method: 'PUT', + url: props.twoFactorUrl, + data: { + secret: totp.value.secret, + otp: form.value.otp + } + }) + ).then(() => { + notifySuccess(); + emit('relist'); + close(); + }).catch((error) => { + error.value = error.response.data.message; + }); }); }; diff --git a/frontend/vue/components/Admin/Backups/ConfigureModal.vue b/frontend/vue/components/Admin/Backups/ConfigureModal.vue index 73614b10c..231c21cd8 100644 --- a/frontend/vue/components/Admin/Backups/ConfigureModal.vue +++ b/frontend/vue/components/Admin/Backups/ConfigureModal.vue @@ -151,7 +151,7 @@ const loading = ref(true); const $modal = ref(); // ModalForm -const {form, resetForm, v$} = useVuelidateOnForm( +const {form, resetForm, v$, ifValid} = useVuelidateOnForm( { 'backup_enabled': {}, 'backup_time_code': {}, @@ -215,21 +215,17 @@ const open = () => { const {wrapWithLoading, notifySuccess} = useNotify(); const submit = () => { - v$.value.$touch(); - - if (v$.value.$errors.length > 0) { - return; - } - - wrapWithLoading( - axios({ - method: 'PUT', - url: props.settingsUrl, - data: form.value - }) - ).then(() => { - notifySuccess(); - close(); + ifValid(() => { + wrapWithLoading( + axios({ + method: 'PUT', + url: props.settingsUrl, + data: form.value + }) + ).then(() => { + notifySuccess(); + close(); + }); }); } diff --git a/frontend/vue/components/Admin/Backups/RunBackupModal.vue b/frontend/vue/components/Admin/Backups/RunBackupModal.vue index 1b1ea34d8..386656a61 100644 --- a/frontend/vue/components/Admin/Backups/RunBackupModal.vue +++ b/frontend/vue/components/Admin/Backups/RunBackupModal.vue @@ -152,7 +152,7 @@ const logUrl = ref(null); const error = ref(null); const $modal = ref(); // BModal -const {form, resetForm, v$} = useVuelidateOnForm( +const {form, resetForm, v$, ifValid} = useVuelidateOnForm( { 'storage_location': {}, 'path': {}, @@ -178,22 +178,19 @@ const {wrapWithLoading} = useNotify(); const {axios} = useAxios(); const submit = () => { - v$.value.$touch(); - if (v$.value.$errors.length > 0) { - return; - } - - error.value = null; - wrapWithLoading( - axios({ - method: 'POST', - url: props.runBackupUrl, - data: form.value - }) - ).then((resp) => { - logUrl.value = resp.data.links.log; - }).catch((error) => { - error.value = error.response.data.message; + ifValid(() => { + error.value = null; + wrapWithLoading( + axios({ + method: 'POST', + url: props.runBackupUrl, + data: form.value + }) + ).then((resp) => { + logUrl.value = resp.data.links.log; + }).catch((error) => { + error.value = error.response.data.message; + }); }); }; diff --git a/frontend/vue/components/Admin/Branding/BrandingForm.vue b/frontend/vue/components/Admin/Branding/BrandingForm.vue index 98434535c..d86999e85 100644 --- a/frontend/vue/components/Admin/Branding/BrandingForm.vue +++ b/frontend/vue/components/Admin/Branding/BrandingForm.vue @@ -214,7 +214,7 @@ const props = defineProps({ const loading = ref(true); const error = ref(null); -const {form, resetForm, v$} = useVuelidateOnForm( +const {form, resetForm, v$, ifValid} = useVuelidateOnForm( { 'public_theme': {}, 'hide_album_art': {}, @@ -278,20 +278,17 @@ onMounted(relist); const {wrapWithLoading, notifySuccess} = useNotify(); const submit = () => { - v$.value.$touch(); - if (v$.value.$errors.length > 0) { - return; - } - - wrapWithLoading( - axios({ - method: 'PUT', - url: props.apiUrl, - data: form.value - }) - ).then(() => { - notifySuccess($gettext('Changes saved.')); - relist(); + ifValid(() => { + wrapWithLoading( + axios({ + method: 'PUT', + url: props.apiUrl, + data: form.value + }) + ).then(() => { + notifySuccess($gettext('Changes saved.')); + relist(); + }); }); } diff --git a/frontend/vue/components/Admin/Settings.vue b/frontend/vue/components/Admin/Settings.vue index ddacdb2b9..0854f6fd6 100644 --- a/frontend/vue/components/Admin/Settings.vue +++ b/frontend/vue/components/Admin/Settings.vue @@ -112,7 +112,7 @@ const props = defineProps({ const emit = defineEmits(['saved']); -const {form, v$} = useVuelidateOnForm( +const {form, v$, ifValid} = useVuelidateOnForm( { base_url: {required}, instance_name: {}, @@ -224,22 +224,19 @@ const {wrapWithLoading, notifySuccess} = useNotify(); const {$gettext} = useTranslate(); const submit = () => { - v$.value.$touch(); - if (v$.value.$errors.length > 0) { - return; - } + ifValid(() => { + wrapWithLoading( + axios({ + method: 'PUT', + url: props.apiUrl, + data: form.value + }) + ).then(() => { + emit('saved'); - wrapWithLoading( - axios({ - method: 'PUT', - url: props.apiUrl, - data: form.value - }) - ).then(() => { - emit('saved'); - - notifySuccess($gettext('Changes saved.')); - relist(); + notifySuccess($gettext('Changes saved.')); + relist(); + }); }); } diff --git a/frontend/vue/components/Admin/Settings/TestMessageModal.vue b/frontend/vue/components/Admin/Settings/TestMessageModal.vue index e8648f6f1..27f16d680 100644 --- a/frontend/vue/components/Admin/Settings/TestMessageModal.vue +++ b/frontend/vue/components/Admin/Settings/TestMessageModal.vue @@ -50,7 +50,7 @@ const props = defineProps({ } }); -const {form, v$} = useVuelidateOnForm( +const {form, v$, ifValid} = useVuelidateOnForm( { emailAddress: {required, email} }, @@ -74,20 +74,16 @@ const {axios} = useAxios(); const {$gettext} = useTranslate(); const doSendTest = () => { - v$.value.$touch(); - - if (v$.value.$errors.length > 0) { - return; - } - - wrapWithLoading( - axios.post(props.testMessageUrl, { - 'email': form.value.emailAddress - }) - ).then(() => { - notifySuccess($gettext('Test message sent.')); - }).finally(() => { - close(); + ifValid(() => { + wrapWithLoading( + axios.post(props.testMessageUrl, { + 'email': form.value.emailAddress + }) + ).then(() => { + notifySuccess($gettext('Test message sent.')); + }).finally(() => { + close(); + }); }); }; diff --git a/frontend/vue/components/Admin/Stations/CloneModal.vue b/frontend/vue/components/Admin/Stations/CloneModal.vue index fcd12f8c2..a765728d6 100644 --- a/frontend/vue/components/Admin/Stations/CloneModal.vue +++ b/frontend/vue/components/Admin/Stations/CloneModal.vue @@ -28,7 +28,7 @@ const loading = ref(true); const cloneUrl = ref(null); const error = ref(null); -const {form, resetForm, v$} = useVuelidateOnForm( +const {form, resetForm, v$, ifValid} = useVuelidateOnForm( { name: {required}, description: {}, @@ -71,25 +71,22 @@ const {wrapWithLoading, notifySuccess} = useNotify(); const {axios} = useAxios(); const doSubmit = () => { - v$.value.$touch(); - if (v$.value.$errors.length > 0) { - return; - } + ifValid(() => { + error.value = null; - error.value = null; - - wrapWithLoading( - axios({ - method: 'POST', - url: cloneUrl.value, - data: form.value - }) - ).then(() => { - notifySuccess(); - emit('relist'); - close(); - }).catch((error) => { - error.value = error.response.data.message; + wrapWithLoading( + axios({ + method: 'POST', + url: cloneUrl.value, + data: form.value + }) + ).then(() => { + notifySuccess(); + emit('relist'); + close(); + }).catch((error) => { + error.value = error.response.data.message; + }); }); }; diff --git a/frontend/vue/components/Admin/Stations/StationForm.vue b/frontend/vue/components/Admin/Stations/StationForm.vue index 09453bf1c..2d4b9a1d3 100644 --- a/frontend/vue/components/Admin/Stations/StationForm.vue +++ b/frontend/vue/components/Admin/Stations/StationForm.vue @@ -401,7 +401,7 @@ const buildForm = () => { }; const {blankForm, validations} = buildForm(); -const {form, resetForm, v$} = useVuelidateOnForm(validations, blankForm); +const {form, resetForm, v$, ifValid} = useVuelidateOnForm(validations, blankForm); const isValid = computed(() => { return !v$.value?.$invalid ?? true; @@ -477,27 +477,24 @@ const reset = () => { }; const submit = () => { - v$.value.$touch(); - if (v$.value.$errors.length > 0) { - return; - } - - error.value = null; - wrapWithLoading( - axios({ - method: (props.isEditMode) - ? 'PUT' - : 'POST', - url: (props.isEditMode) - ? props.editUrl - : props.createUrl, - data: form.value - }) - ).then(() => { - notifySuccess(); - emit('submitted'); - }).catch((err) => { - error.value = err.response.data.message; + ifValid(() => { + error.value = null; + wrapWithLoading( + axios({ + method: (props.isEditMode) + ? 'PUT' + : 'POST', + url: (props.isEditMode) + ? props.editUrl + : props.createUrl, + data: form.value + }) + ).then(() => { + notifySuccess(); + emit('submitted'); + }).catch((err) => { + error.value = err.response.data.message; + }); }); }; diff --git a/frontend/vue/components/Stations/LiquidsoapConfig.vue b/frontend/vue/components/Stations/LiquidsoapConfig.vue index 71e4e7b63..823f6287e 100644 --- a/frontend/vue/components/Stations/LiquidsoapConfig.vue +++ b/frontend/vue/components/Stations/LiquidsoapConfig.vue @@ -111,7 +111,7 @@ const buildForm = () => { } const {validations, blankForm} = buildForm(); -const {form, resetForm, v$} = useVuelidateOnForm(validations, blankForm); +const {form, resetForm, v$, ifValid} = useVuelidateOnForm(validations, blankForm); const loading = ref(true); @@ -134,22 +134,19 @@ onMounted(relist); const {wrapWithLoading, notifySuccess} = useNotify(); const submit = () => { - v$.value.$touch(); - if (v$.value.$errors.length > 0) { - return; - } + ifValid(() => { + wrapWithLoading( + axios({ + method: 'PUT', + url: props.settingsUrl, + data: form.value, + }) + ).then(() => { + notifySuccess(); - wrapWithLoading( - axios({ - method: 'PUT', - url: props.settingsUrl, - data: form.value, - }) - ).then(() => { - notifySuccess(); - - mayNeedRestart(); - relist(); + mayNeedRestart(); + relist(); + }); }); } diff --git a/frontend/vue/components/Stations/Media/EditModal.vue b/frontend/vue/components/Stations/Media/EditModal.vue index 0d72d9911..79324b936 100644 --- a/frontend/vue/components/Stations/Media/EditModal.vue +++ b/frontend/vue/components/Stations/Media/EditModal.vue @@ -157,7 +157,7 @@ const buildForm = () => { }; const {blankForm, validations} = buildForm(); -const {form, resetForm: resetBaseForm, v$} = useVuelidateOnForm(validations, blankForm); +const {form, resetForm: resetBaseForm, v$, ifValid} = useVuelidateOnForm(validations, blankForm); const resetForm = () => { resetBaseForm(); @@ -228,19 +228,16 @@ const open = (newRecordUrl, newAlbumArtUrl, newAudioUrl, newWaveformUrl) => { const {notifySuccess} = useNotify(); const doEdit = () => { - v$.value.$touch(); - if (v$.value.$errors.length > 0) { - return; - } + ifValid(() => { + error.value = null; - error.value = null; - - axios.put(recordUrl.value, form.value).then(() => { - notifySuccess(); - emit('relist'); - close(); - }).catch((error) => { - error.value = error.response.data.message; + axios.put(recordUrl.value, form.value).then(() => { + notifySuccess(); + emit('relist'); + close(); + }).catch((error) => { + error.value = error.response.data.message; + }); }); }; diff --git a/frontend/vue/functions/useVuelidateOnForm.js b/frontend/vue/functions/useVuelidateOnForm.js index c27cac9b9..6746f5052 100644 --- a/frontend/vue/functions/useVuelidateOnForm.js +++ b/frontend/vue/functions/useVuelidateOnForm.js @@ -1,5 +1,6 @@ import useVuelidate from "@vuelidate/core"; import {useResettableRef} from "~/functions/useResettableRef"; +import {computed} from "vue"; export function useVuelidateOnForm(validations, blankForm, options = {}) { const {record: form, reset} = useResettableRef(blankForm); @@ -11,9 +12,32 @@ export function useVuelidateOnForm(validations, blankForm, options = {}) { reset(); } + const isValid = computed(() => { + return !v$.value.$invalid ?? true; + }); + + const validate = () => { + v$.value.$touch(); + + return v$.value.$validate(); + } + + const ifValid = (cb) => { + validate().then((isValid) => { + if (!isValid) { + return; + } + + cb(); + }); + } + return { form, resetForm, - v$ + v$, + isValid, + validate, + ifValid }; }