Let the event form submit and say what is wrong #177
1 changed files with 221 additions and 153 deletions
|
|
@ -428,13 +428,34 @@ watch(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const { resetForm, meta } = form;
|
const { resetForm } = form;
|
||||||
const isFormValid = computed(
|
|
||||||
() =>
|
/** Field name -> what the organiser calls it, for the invalid-submit toast. */
|
||||||
meta.value.valid &&
|
const FIELD_LABELS: Record<string, string> = {
|
||||||
!hasPromoErrors(promoErrors.value) &&
|
name: "Title",
|
||||||
!hasWaveErrors.value,
|
info: "Description",
|
||||||
);
|
event_start_date: "Start date",
|
||||||
|
event_start_time: "Start time",
|
||||||
|
event_end_date: "End date",
|
||||||
|
event_end_time: "End time",
|
||||||
|
location: "Location",
|
||||||
|
currency: "Price currency",
|
||||||
|
payment_methods: "Payment methods",
|
||||||
|
fiat_currency: "Fiat currency",
|
||||||
|
amount_tickets: "Tickets",
|
||||||
|
price_per_ticket: "Price",
|
||||||
|
notification_subject: "Subject",
|
||||||
|
notification_body: "Body",
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open whichever collapsible section holds a problem, so the reason is on
|
||||||
|
* screen rather than hidden behind a closed panel.
|
||||||
|
*/
|
||||||
|
function revealSectionFor(field: string) {
|
||||||
|
if (field === "promo_codes") promoOpen.value = true;
|
||||||
|
if (field === "ticket_waves") wavesOpen.value = true;
|
||||||
|
}
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
|
|
||||||
const today = computed(() => format(new Date(), "yyyy-MM-dd"));
|
const today = computed(() => format(new Date(), "yyyy-MM-dd"));
|
||||||
|
|
@ -448,8 +469,22 @@ function toggleCategory(cat: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSubmit = form.handleSubmit(async (formValues) => {
|
const onSubmit = form.handleSubmit(
|
||||||
if (!isFormValid.value) return;
|
async (formValues) => {
|
||||||
|
// Promo codes and ticket waves are not part of the Zod schema, so
|
||||||
|
// vee-validate cannot block on them. Check here and OPEN the section
|
||||||
|
// that holds the problem — both editors live inside collapsibles, and
|
||||||
|
// an error rendered inside a closed one is invisible.
|
||||||
|
if (hasWaveErrors.value) {
|
||||||
|
revealSectionFor("ticket_waves");
|
||||||
|
toastService.error("Check the ticket waves below");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (hasPromoErrors(promoErrors.value)) {
|
||||||
|
revealSectionFor("promo_codes");
|
||||||
|
toastService.error("Check the promo codes below");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!paymentService) {
|
if (!paymentService) {
|
||||||
toastService.error("Payment service not available");
|
toastService.error("Payment service not available");
|
||||||
|
|
@ -468,7 +503,10 @@ const onSubmit = form.handleSubmit(async (formValues) => {
|
||||||
const eventData: CreateEventRequest = {
|
const eventData: CreateEventRequest = {
|
||||||
name: formValues.name,
|
name: formValues.name,
|
||||||
event_start_date: withLocalTzOffset(
|
event_start_date: withLocalTzOffset(
|
||||||
foldDateTime(formValues.event_start_date, formValues.event_start_time),
|
foldDateTime(
|
||||||
|
formValues.event_start_date,
|
||||||
|
formValues.event_start_time,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
if (!isEditMode.value) {
|
if (!isEditMode.value) {
|
||||||
|
|
@ -486,7 +524,9 @@ const onSubmit = form.handleSubmit(async (formValues) => {
|
||||||
}
|
}
|
||||||
if (formValues.location) eventData.location = formValues.location;
|
if (formValues.location) eventData.location = formValues.location;
|
||||||
if (bannerImages.value.length > 0) {
|
if (bannerImages.value.length > 0) {
|
||||||
eventData.banner = imageService.getImageUrl(bannerImages.value[0].alias);
|
eventData.banner = imageService.getImageUrl(
|
||||||
|
bannerImages.value[0].alias,
|
||||||
|
);
|
||||||
} else if (isEditMode.value) {
|
} else if (isEditMode.value) {
|
||||||
// User cleared the banner during edit — propagate the null so the
|
// User cleared the banner during edit — propagate the null so the
|
||||||
// backend wipes the field instead of keeping the old image.
|
// backend wipes the field instead of keeping the old image.
|
||||||
|
|
@ -601,7 +641,28 @@ const onSubmit = form.handleSubmit(async (formValues) => {
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
// vee-validate blocks the submit when the schema fails. Say WHY: a field
|
||||||
|
// whose FormMessage sits inside a closed section — or is not rendered at
|
||||||
|
// all — otherwise leaves a dead button and no explanation, which is the
|
||||||
|
// dead end disabling the button used to create.
|
||||||
|
({ errors }) => {
|
||||||
|
const fields = Object.keys(errors);
|
||||||
|
fields.forEach(revealSectionFor);
|
||||||
|
const named = fields.map((f) => FIELD_LABELS[f] ?? f);
|
||||||
|
toastService.error(
|
||||||
|
named.length
|
||||||
|
? `Check ${named.join(", ")}`
|
||||||
|
: "Some details still need attention",
|
||||||
|
);
|
||||||
|
const first = fields[0];
|
||||||
|
if (first) {
|
||||||
|
document
|
||||||
|
.querySelector(`[name="${first}"], #${CSS.escape(first)}`)
|
||||||
|
?.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const handleOpenChange = (open: boolean) => {
|
const handleOpenChange = (open: boolean) => {
|
||||||
if (!open && !isLoading.value) {
|
if (!open && !isLoading.value) {
|
||||||
|
|
@ -1176,10 +1237,17 @@ const handleOpenChange = (open: boolean) => {
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
|
<!-- Disabled only while a submit is genuinely in flight, NOT on
|
||||||
|
validity. vee-validate's `meta.valid` is false before the form
|
||||||
|
has ever validated, counts disabled and conditionally rendered
|
||||||
|
fields against you, and is documented to go stale
|
||||||
|
(logaretm/vee-validate#4630). Gating on it produced a dead
|
||||||
|
button with no visible reason; let the click through and the
|
||||||
|
invalid handler says what is wrong. -->
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="w-full sm:w-auto"
|
class="w-full sm:w-auto"
|
||||||
:disabled="isLoading || !isFormValid"
|
:disabled="isLoading"
|
||||||
>
|
>
|
||||||
<Loader2 v-if="isLoading" class="w-4 h-4 mr-2 animate-spin" />
|
<Loader2 v-if="isLoading" class="w-4 h-4 mr-2 animate-spin" />
|
||||||
{{
|
{{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue