feat: add validation and error messages for custom HTML fields in portal settings

This commit is contained in:
gabrieljablonski 2026-03-09 11:48:53 -03:00
parent eaac65c973
commit a4ff73d496
3 changed files with 54 additions and 9 deletions

View File

@ -7,7 +7,13 @@ import { useStore, useStoreGetters } from 'dashboard/composables/store';
import { uploadFile } from 'dashboard/helper/uploadHelper'; import { uploadFile } from 'dashboard/helper/uploadHelper';
import { checkFileSizeLimit } from 'shared/helpers/FileHelper'; import { checkFileSizeLimit } from 'shared/helpers/FileHelper';
import { useVuelidate } from '@vuelidate/core'; import { useVuelidate } from '@vuelidate/core';
import { required, minLength, helpers, url } from '@vuelidate/validators'; import {
required,
minLength,
maxLength,
helpers,
url,
} from '@vuelidate/validators';
import { isValidSlug } from 'shared/helpers/Validators'; import { isValidSlug } from 'shared/helpers/Validators';
import Button from 'dashboard/components-next/button/Button.vue'; import Button from 'dashboard/components-next/button/Button.vue';
@ -35,6 +41,7 @@ const store = useStore();
const getters = useStoreGetters(); const getters = useStoreGetters();
const MAXIMUM_FILE_UPLOAD_SIZE = 4; // in MB const MAXIMUM_FILE_UPLOAD_SIZE = 4; // in MB
const CUSTOM_HTML_MAX_LENGTH = 15_000;
const state = reactive({ const state = reactive({
name: '', name: '',
@ -84,6 +91,8 @@ const rules = {
), ),
}, },
homePageLink: { url }, homePageLink: { url },
customHeadHtml: { maxLength: maxLength(CUSTOM_HTML_MAX_LENGTH) },
customBodyHtml: { maxLength: maxLength(CUSTOM_HTML_MAX_LENGTH) },
}; };
const v$ = useVuelidate(rules, state); const v$ = useVuelidate(rules, state);
@ -102,6 +111,18 @@ const homePageLinkError = computed(() =>
: '' : ''
); );
const customHeadHtmlError = computed(() =>
v$.value.customHeadHtml.$error
? t('HELP_CENTER.PORTAL_SETTINGS.FORM.CUSTOM_HEAD_HTML.ERROR')
: ''
);
const customBodyHtmlError = computed(() =>
v$.value.customBodyHtml.$error
? t('HELP_CENTER.PORTAL_SETTINGS.FORM.CUSTOM_BODY_HTML.ERROR')
: ''
);
const isUpdatingPortal = computed(() => { const isUpdatingPortal = computed(() => {
const slug = props.activePortal?.slug; const slug = props.activePortal?.slug;
if (slug) return getters['portals/uiFlagsIn'].value(slug)?.isUpdating; if (slug) return getters['portals/uiFlagsIn'].value(slug)?.isUpdating;
@ -375,10 +396,20 @@ const handleAvatarDelete = () => {
t('HELP_CENTER.PORTAL_SETTINGS.FORM.CUSTOM_HEAD_HTML.PLACEHOLDER') t('HELP_CENTER.PORTAL_SETTINGS.FORM.CUSTOM_HEAD_HTML.PLACEHOLDER')
" "
rows="4" rows="4"
class="w-full px-3 py-2 text-sm border rounded-lg resize-y border-n-weak bg-transparent dark:bg-transparent text-n-slate-12 placeholder:text-n-slate-9 focus:outline-none focus:ring-1 focus:ring-n-brand font-mono" class="w-full px-3 py-2 text-sm border rounded-lg resize-y bg-transparent dark:bg-transparent text-n-slate-12 placeholder:text-n-slate-9 focus:outline-none focus:ring-1 font-mono"
:class="
customHeadHtmlError
? 'border-n-ruby-9 focus:ring-n-ruby-9'
: 'border-n-weak focus:ring-n-brand'
"
@input="v$.customHeadHtml.$touch()"
/> />
<span class="text-xs text-n-slate-11"> <span
class="text-xs"
:class="customHeadHtmlError ? 'text-n-ruby-9' : 'text-n-slate-11'"
>
{{ {{
customHeadHtmlError ||
t('HELP_CENTER.PORTAL_SETTINGS.FORM.CUSTOM_HEAD_HTML.HELP_TEXT') t('HELP_CENTER.PORTAL_SETTINGS.FORM.CUSTOM_HEAD_HTML.HELP_TEXT')
}} }}
</span> </span>
@ -399,10 +430,20 @@ const handleAvatarDelete = () => {
t('HELP_CENTER.PORTAL_SETTINGS.FORM.CUSTOM_BODY_HTML.PLACEHOLDER') t('HELP_CENTER.PORTAL_SETTINGS.FORM.CUSTOM_BODY_HTML.PLACEHOLDER')
" "
rows="4" rows="4"
class="w-full px-3 py-2 text-sm border rounded-lg resize-y border-n-weak bg-transparent dark:bg-transparent text-n-slate-12 placeholder:text-n-slate-9 focus:outline-none focus:ring-1 focus:ring-n-brand font-mono" class="w-full px-3 py-2 text-sm border rounded-lg resize-y bg-transparent dark:bg-transparent text-n-slate-12 placeholder:text-n-slate-9 focus:outline-none focus:ring-1 font-mono"
:class="
customBodyHtmlError
? 'border-n-ruby-9 focus:ring-n-ruby-9'
: 'border-n-weak focus:ring-n-brand'
"
@input="v$.customBodyHtml.$touch()"
/> />
<span class="text-xs text-n-slate-11"> <span
class="text-xs"
:class="customBodyHtmlError ? 'text-n-ruby-9' : 'text-n-slate-11'"
>
{{ {{
customBodyHtmlError ||
t('HELP_CENTER.PORTAL_SETTINGS.FORM.CUSTOM_BODY_HTML.HELP_TEXT') t('HELP_CENTER.PORTAL_SETTINGS.FORM.CUSTOM_BODY_HTML.HELP_TEXT')
}} }}
</span> </span>

View File

@ -754,12 +754,14 @@
"CUSTOM_HEAD_HTML": { "CUSTOM_HEAD_HTML": {
"LABEL": "Custom head HTML", "LABEL": "Custom head HTML",
"PLACEHOLDER": "Add custom HTML to the <head> section (e.g., meta tags, analytics, custom CSS)", "PLACEHOLDER": "Add custom HTML to the <head> section (e.g., meta tags, analytics, custom CSS)",
"HELP_TEXT": "This code will be injected into the <head> of your portal pages" "HELP_TEXT": "This code will be injected into the <head> of your portal pages",
"ERROR": "Custom head HTML must be less than 15,000 characters"
}, },
"CUSTOM_BODY_HTML": { "CUSTOM_BODY_HTML": {
"LABEL": "Custom body HTML", "LABEL": "Custom body HTML",
"PLACEHOLDER": "Add custom HTML before </body> (e.g., scripts, tracking pixels, widgets)", "PLACEHOLDER": "Add custom HTML before </body> (e.g., scripts, tracking pixels, widgets)",
"HELP_TEXT": "This code will be injected before the closing </body> tag of your portal pages" "HELP_TEXT": "This code will be injected before the closing </body> tag of your portal pages",
"ERROR": "Custom body HTML must be less than 15,000 characters"
}, },
"SAVE_CHANGES": "Save changes" "SAVE_CHANGES": "Save changes"
}, },

View File

@ -754,12 +754,14 @@
"CUSTOM_HEAD_HTML": { "CUSTOM_HEAD_HTML": {
"LABEL": "HTML personalizado no head", "LABEL": "HTML personalizado no head",
"PLACEHOLDER": "Adicione HTML personalizado na seção <head> (ex: meta tags, analytics, CSS personalizado)", "PLACEHOLDER": "Adicione HTML personalizado na seção <head> (ex: meta tags, analytics, CSS personalizado)",
"HELP_TEXT": "Este código será injetado no <head> das páginas do seu portal" "HELP_TEXT": "Este código será injetado no <head> das páginas do seu portal",
"ERROR": "O HTML personalizado do head deve ter menos de 15.000 caracteres"
}, },
"CUSTOM_BODY_HTML": { "CUSTOM_BODY_HTML": {
"LABEL": "HTML personalizado no body", "LABEL": "HTML personalizado no body",
"PLACEHOLDER": "Adicione HTML personalizado antes do </body> (ex: scripts, pixels de rastreamento, widgets)", "PLACEHOLDER": "Adicione HTML personalizado antes do </body> (ex: scripts, pixels de rastreamento, widgets)",
"HELP_TEXT": "Este código será injetado antes da tag de fechamento </body> das páginas do seu portal" "HELP_TEXT": "Este código será injetado antes da tag de fechamento </body> das páginas do seu portal",
"ERROR": "O HTML personalizado do body deve ter menos de 15.000 caracteres"
}, },
"SAVE_CHANGES": "Salvar Alterações" "SAVE_CHANGES": "Salvar Alterações"
}, },