Merge pull request '155-Prevent-multi-submit' (#156) from 155-Prevent-multi-submit into main
All checks were successful
Continuous Integration / Update Development (push) Successful in 2m59s

Reviewed-on: #156
This commit was merged in pull request #156.
This commit is contained in:
2026-01-19 18:03:51 -06:00
3 changed files with 35 additions and 5 deletions

View File

@@ -66,14 +66,19 @@ import { loaSchema } from '@shared/schemas/loaSchema'
import { toTypedSchema } from "@vee-validate/zod"; import { toTypedSchema } from "@vee-validate/zod";
import Calendar from "../ui/calendar/Calendar.vue"; import Calendar from "../ui/calendar/Calendar.vue";
import { useUserStore } from "@/stores/user"; import { useUserStore } from "@/stores/user";
import Spinner from "../ui/spinner/Spinner.vue";
const { handleSubmit, values, resetForm } = useForm({ const { handleSubmit, values, resetForm } = useForm({
validationSchema: toTypedSchema(loaSchema), validationSchema: toTypedSchema(loaSchema),
}) })
const formSubmitted = ref(false); const formSubmitted = ref(false);
const submitting = ref(false);
const onSubmit = handleSubmit(async (values) => { const onSubmit = handleSubmit(async (values) => {
//catch double submit
if (submitting.value) return;
submitting.value = true;
const out: LOARequest = { const out: LOARequest = {
member_id: values.member_id, member_id: values.member_id,
start_date: values.start_date, start_date: values.start_date,
@@ -88,6 +93,7 @@ const onSubmit = handleSubmit(async (values) => {
userStore.loadUser(); userStore.loadUser();
} }
formSubmitted.value = true; formSubmitted.value = true;
submitting.value = false;
}) })
onMounted(async () => { onMounted(async () => {
@@ -325,7 +331,12 @@ const filteredMembers = computed(() => {
</VeeField> </VeeField>
</div> </div>
<div class="flex justify-end"> <div class="flex justify-end">
<Button type="submit">Submit</Button> <Button type="submit" :disabled="submitting" class="w-35">
<span class="flex items-center gap-2" v-if="submitting">
<Spinner></Spinner> Submitting…
</span>
<span v-else>Submit</span>
</Button>
</div> </div>
</form> </form>
<div v-else class="flex flex-col gap-4 py-8 text-left"> <div v-else class="flex flex-col gap-4 py-8 text-left">

View File

@@ -31,8 +31,12 @@ const { handleSubmit, errors, values, resetForm, setFieldValue, submitCount } =
validateOnMount: false, validateOnMount: false,
}) })
const submitting = ref(false);
const submitForm = handleSubmit( const submitForm = handleSubmit(
async (vals) => { async (vals) => {
if (submitting.value) return;
submitting.value = true;
try { try {
let output = vals; let output = vals;
output.promotions.map(p => p.start_date = new Date(p.start_date).toISOString()) output.promotions.map(p => p.start_date = new Date(p.start_date).toISOString())
@@ -42,6 +46,8 @@ const submitForm = handleSubmit(
} catch (error) { } catch (error) {
submitError.value = error; submitError.value = error;
console.error(error); console.error(error);
} finally {
submitting.value = false;
} }
} }
); );
@@ -281,7 +287,12 @@ function setAllToday() {
</VeeField> </VeeField>
<div class="flex flex-col items-end gap-2"> <div class="flex flex-col items-end gap-2">
<div class="h-6" /> <div class="h-6" />
<Button type="submit" class="w-min">Submit</Button> <Button type="submit" form="trainingForm" :disabled="submitting" class="w-35">
<span class="flex items-center gap-2" v-if="submitting">
<Spinner></Spinner> Submitting…
</span>
<span v-else>Submit</span>
</Button>
<p v-if="submitError" class="text-destructive">{{ submitError }}</p> <p v-if="submitError" class="text-destructive">{{ submitError }}</p>
<div v-else class="h-6 flex justify-end"> <div v-else class="h-6 flex justify-end">
<p v-if="submitCount > 0 && errors.promotions && typeof errors.promotions === 'string'" <p v-if="submitCount > 0 && errors.promotions && typeof errors.promotions === 'string'"

View File

@@ -13,6 +13,7 @@ import Button from '../ui/button/Button.vue';
import InputGroup from '../ui/input-group/InputGroup.vue'; import InputGroup from '../ui/input-group/InputGroup.vue';
import InputGroupAddon from '../ui/input-group/InputGroupAddon.vue'; import InputGroupAddon from '../ui/input-group/InputGroupAddon.vue';
import { SearchIcon } from 'lucide-vue-next'; import { SearchIcon } from 'lucide-vue-next';
import Spinner from '../ui/spinner/Spinner.vue';
const props = defineProps<{ const props = defineProps<{
allMembers: MemberLight[], allMembers: MemberLight[],
@@ -43,8 +44,11 @@ function openDialog() {
showAddMemberDialog.value = true; showAddMemberDialog.value = true;
} }
const submitting = ref(false);
async function handleAddMember() { async function handleAddMember() {
//catch double submit
if (submitting.value) return;
submitting.value = true;
//guard //guard
if (memberToAdd.value == null) if (memberToAdd.value == null)
return; return;
@@ -52,6 +56,7 @@ async function handleAddMember() {
await addMemberToRole(memberToAdd.value.id, props.role.id); await addMemberToRole(memberToAdd.value.id, props.role.id);
emit('submit'); emit('submit');
showAddMemberDialog.value = false; showAddMemberDialog.value = false;
submitting.value = false;
} }
</script> </script>
@@ -94,8 +99,11 @@ async function handleAddMember() {
<Button variant="secondary" @click="showAddMemberDialog = false"> <Button variant="secondary" @click="showAddMemberDialog = false">
Cancel Cancel
</Button> </Button>
<Button :disabled="!memberToAdd" @click="handleAddMember"> <Button :disabled="!memberToAdd || submitting" @click="handleAddMember">
Add <span class="flex items-center gap-2" v-if="submitting">
<Spinner></Spinner> Add
</span>
<span v-else>Add</span>
</Button> </Button>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>