diff --git a/resources/js/forms/classic/interactions/ButtonAction.vue b/resources/js/forms/classic/interactions/ButtonAction.vue index bf0257cc..738f9589 100644 --- a/resources/js/forms/classic/interactions/ButtonAction.vue +++ b/resources/js/forms/classic/interactions/ButtonAction.vue @@ -29,9 +29,7 @@ @keydown.enter="stopEditing($event, true)" type="text" :placeholder=" - isChecked && !otherValue - ? t('Type your answer') - : action.label ?? t('Other') + isChecked && !otherValue ? t('type') : action.label ?? t('other') " v-model="otherValue" class="block w-full border-0 focus:ring-0" @@ -54,7 +52,9 @@ e - Enter + {{ + t("enter") + }} diff --git a/resources/js/forms/classic/interactions/FileAction.vue b/resources/js/forms/classic/interactions/FileAction.vue index e4fe485f..f69a56a9 100644 --- a/resources/js/forms/classic/interactions/FileAction.vue +++ b/resources/js/forms/classic/interactions/FileAction.vue @@ -14,9 +14,11 @@ type="button" class="underline text-primary px-5 py-1 rounded" > - Choose Files + {{ t("files_choose") }} - or drop here + {{ + t("files_drop") + }}
@@ -45,8 +47,10 @@ import { useConversation } from "@/stores/conversation"; import { useFileDialog, useDropZone } from "@vueuse/core"; import UploadFileItem from "@/forms/classic/components/UploadFileItem.vue"; import { computed, ref } from "vue"; +import { useI18n } from "vue-i18n"; const store = useConversation(); +const { t } = useI18n(); const props = defineProps<{ index: number; @@ -98,6 +102,7 @@ const setFiles = (files: File[] | FileList | null) => { ) { currentFiles.push(file); } else { + // if validation fails, log an error and show it to the user console.error("File type not allowed"); } } @@ -142,7 +147,10 @@ const allowedFileTypes = computed(() => { const hasMaxFiles = computed(() => { if (props.action?.options?.allowedFiles) { - return currentFiles.value.length >= props.action.options.allowedFiles; + return ( + currentFiles.value && + currentFiles.value.length >= props.action.options.allowedFiles + ); } return false; diff --git a/resources/js/forms/classic/interactions/useButtonAction.ts b/resources/js/forms/classic/interactions/useButtonAction.ts index d2e4018d..ef6e5263 100644 --- a/resources/js/forms/classic/interactions/useButtonAction.ts +++ b/resources/js/forms/classic/interactions/useButtonAction.ts @@ -1,10 +1,12 @@ import ButtonAction from "@/forms/classic/interactions/ButtonAction.vue"; +import { useI18n } from "vue-i18n"; export function useButtonAction(block: PublicFormBlockModel) { + const { t } = useI18n(); const useThis = ["radio", "checkbox"].includes(block.type); const validator = (input: any) => { - const validationMessage = "Please select an option."; + const validationMessage = t("validation.option_required"); if (block.is_required) { if (!input) { diff --git a/resources/js/forms/classic/interactions/useDateAction.ts b/resources/js/forms/classic/interactions/useDateAction.ts index fe318993..9f2e1ff1 100644 --- a/resources/js/forms/classic/interactions/useDateAction.ts +++ b/resources/js/forms/classic/interactions/useDateAction.ts @@ -1,12 +1,14 @@ import DateAction from "@/forms/classic/interactions/DateAction.vue"; +import { useI18n } from "vue-i18n"; export function useDateAction(block: PublicFormBlockModel) { + const { t } = useI18n(); const useThis = ["date"].includes(block.type); const validator = (input: any) => { return { valid: block.is_required ? input?.payload.length > 0 : true, - message: "This field is required", + message: t("validation.field_required"), }; }; diff --git a/resources/js/forms/classic/interactions/useFileAction.ts b/resources/js/forms/classic/interactions/useFileAction.ts index 94d8163d..178519f4 100644 --- a/resources/js/forms/classic/interactions/useFileAction.ts +++ b/resources/js/forms/classic/interactions/useFileAction.ts @@ -1,13 +1,57 @@ import FileAction from "@/forms/classic/interactions/FileAction.vue"; +import { useI18n } from "vue-i18n"; export function useFileAction(block: PublicFormBlockModel) { + const { t } = useI18n(); const useThis = [ "input-file", ].includes(block.type); const validator = (input: any) => { - console.log("input file validation", input) + // check if the block is required + if (block.is_required && (!input?.payload || input?.payload.length === 0)) { + return { + valid: false, + message: t("validation.field_required"), + } + } + + // get constraints from block file interaction + const interaction = block.interactions.find((interaction) => { + return interaction.type === "file"; + }) + + if (!interaction) { + return { + valid: false, + message: t("No file interaction found"), + } + } + + const maxFiles = interaction.options?.allowedFiles; + const maxFileSize = interaction.options?.allowedFileSize ? interaction.options?.allowedFileSize * Math.pow(10,6) : 0; + + // check for max files constrains + if (maxFiles && input?.payload.length > maxFiles) { + return { + valid: false, + message: t(`Too many files uploaded`), + } + } + + // check for file size constraints + if (input?.payload && input.payload.length > 0) { + for (const file of input.payload) { + // check for max file size + if (maxFileSize && file.size > maxFileSize) { + return { + valid: false, + message: t(`File size of ${file.name} too large`), + } + } + } + } return { valid: true, diff --git a/resources/js/forms/classic/interactions/useInputAction.ts b/resources/js/forms/classic/interactions/useInputAction.ts index 30251e11..fe44fceb 100644 --- a/resources/js/forms/classic/interactions/useInputAction.ts +++ b/resources/js/forms/classic/interactions/useInputAction.ts @@ -1,7 +1,9 @@ import InputAction from "@/forms/classic/interactions/InputAction.vue"; import { string, number } from "yup"; +import { useI18n } from "vue-i18n"; export function useInputAction(block: PublicFormBlockModel) { + const { t } = useI18n(); const useThis = [ "input-short", "input-email", @@ -28,35 +30,35 @@ export function useInputAction(block: PublicFormBlockModel) { valid: (!block.is_required && !input?.payload) || emailValidator.isValidSync(input?.payload), - message: "Please enter a valid email.", + message: t("validation.valid_email"), }; case "input-number": return { valid: (!block.is_required && !input?.payload) || numberValidator.isValidSync(input?.payload), - message: "Please enter a valid number.", + message: t("validation.valid_number"), }; case "input-link": return { valid: (!block.is_required && !input?.payload) || linkValidator.isValidSync(input?.payload), - message: "Please enter a valid link.", + message: t("validation.valid_link"), }; case "input-phone": return { valid: (!block.is_required && !input?.payload) || phoneValidator.isValidSync(input?.payload), - message: "Please enter a valid phone number.", + message: t("validation.valid_phone") }; default: return { valid: (!block.is_required && !input?.payload) || defaultValidator.isValidSync(input?.payload), - message: "Please enter a valid short text.", + message: t("Please enter a valid short text."), }; } }; diff --git a/resources/js/forms/classic/interactions/useRangeAction.ts b/resources/js/forms/classic/interactions/useRangeAction.ts index b47e1c98..4a0e390a 100644 --- a/resources/js/forms/classic/interactions/useRangeAction.ts +++ b/resources/js/forms/classic/interactions/useRangeAction.ts @@ -1,10 +1,11 @@ import RangeAction from "@/forms/classic/interactions/RangeAction.vue"; - +import { useI18n } from "vue-i18n"; export function useRangeAction(block: PublicFormBlockModel) { + const { t } = useI18n(); const useThis = ["rating", "scale"].includes(block.type); const validator = (input: any) => { - const validationMessage = "Please choose a rating."; + const validationMessage = t("Please choose a rating."); if (block.is_required) { if (!input) { diff --git a/resources/js/forms/classic/interactions/useTextareaAction.ts b/resources/js/forms/classic/interactions/useTextareaAction.ts index b816bbe4..cede6432 100644 --- a/resources/js/forms/classic/interactions/useTextareaAction.ts +++ b/resources/js/forms/classic/interactions/useTextareaAction.ts @@ -1,6 +1,8 @@ import TextareaAction from "@/forms/classic/interactions/TextareaAction.vue"; +import { useI18n } from "vue-i18n"; export function useTextareaAction(block: PublicFormBlockModel) { + const { t } = useI18n(); const useThis = ["input-long"].includes(block.type); const validator = (input: any) => { @@ -8,7 +10,7 @@ export function useTextareaAction(block: PublicFormBlockModel) { block.is_required && (!input || input?.payload?.trim().length === 0) ) { - return { valid: false, message: "This field is required" }; + return { valid: false, message: t("validation.field_required") }; } const action = block.interactions[0]; @@ -22,7 +24,7 @@ export function useTextareaAction(block: PublicFormBlockModel) { return { valid: false, message: - "You have exceeded the maximum number of characters allowed.", + t("validation.max_characters"), }; } } diff --git a/resources/locales/en.json b/resources/locales/en.json index b1a77b2a..0baa3e0c 100644 --- a/resources/locales/en.json +++ b/resources/locales/en.json @@ -32,5 +32,7 @@ "form_submitted": "Form Submitted", "find_us": "Find us here", "enter": "Enter", - "continue": "Continue" + "continue": "Continue", + "files_choose": "Choose files", + "files_drop": "or drop here" }