From eb6cda543bf6349ebf13de1b5b7e2f174e9358cf Mon Sep 17 00:00:00 2001 From: Dominik Zborowski Date: Thu, 24 Oct 2024 16:28:44 +0200 Subject: [PATCH] feat: improve grid validation (#508) --- components/ModalTemplateAddEditGrid.vue | 15 +- components/ModalTemplateMoveGridWidget.vue | 1 - domains/grid/components/AddWidget.vue | 18 +- domains/grid/components/AddWidgetBasic.vue | 108 +++---- .../components/AddWidgetGenericPlatform.vue | 164 ----------- domains/grid/components/AddWidgetText.vue | 198 +++++-------- domains/grid/components/GridTabs.vue | 12 +- domains/grid/components/GridTabsItem.vue | 13 +- domains/grid/components/GridWidget.vue | 45 ++- .../grid/components/GridWidgetInstagram.vue | 4 +- domains/grid/components/GridWidgetX.vue | 2 +- domains/grid/composables/useForm.ts | 57 ++++ domains/grid/composables/useGrid.ts | 63 ++-- domains/grid/shared/config.ts | 83 ++---- .../grid/utils/__tests__/buildGrid.spec.ts | 202 ++++++++----- .../__tests__/compareGridWidgets.spec.ts | 42 +-- .../grid/utils/__tests__/compareGrids.spec.ts | 34 +-- .../{configToGrid.ts => configToGrid.spec.ts} | 6 +- .../grid/utils/__tests__/createGridId.spec.ts | 16 +- .../__tests__/createWidgetObject.spec.ts | 4 +- .../grid/utils/__tests__/gridParser.spec.ts | 92 +++--- .../grid/utils/__tests__/gridToConfig.spec.ts | 6 +- .../utils/__tests__/isConfigValid.spec.ts | 126 -------- .../utils/__tests__/purifyGridConfig.spec.ts | 272 ++++++++++++++++++ domains/grid/utils/buildGrid.ts | 28 +- domains/grid/utils/compareGridWidgets.ts | 6 +- domains/grid/utils/compareGrids.ts | 9 +- domains/grid/utils/configToGrid.ts | 34 +-- domains/grid/utils/createGridId.ts | 18 +- domains/grid/utils/createWidgetObject.ts | 2 +- domains/grid/utils/getUserGrid.ts | 17 +- domains/grid/utils/gridConfig.ts | 11 +- domains/grid/utils/gridParser.ts | 24 +- domains/grid/utils/gridToConfig.ts | 10 +- domains/grid/utils/isConfigValid.ts | 38 --- domains/grid/utils/placeWidgetInGrid.ts | 6 +- domains/grid/utils/purifyGridConfig.ts | 72 +++++ domains/schema/gridConfigSchema.ts | 17 ++ domains/schema/gridSchema.ts | 46 +++ domains/schema/iframeWidgetSchema.ts | 8 + domains/schema/imageWidgetSchema.ts | 7 + domains/schema/instagramWidgetSchema.ts | 12 + domains/schema/soundCloudWidgetSchema.ts | 13 + domains/schema/spotifyWidgetSchema.ts | 14 + domains/schema/textWidgetSchema.ts | 11 + .../schema/transforms/platformTransform.ts | 32 +++ domains/schema/transforms/urlTransform.ts | 22 ++ .../schema/validators/hexColorValidator.ts | 26 ++ domains/schema/warpcastWidgetSchema.ts | 8 + domains/schema/xWidgetSchema.ts | 12 + domains/schema/youtubeWidgetSchema.ts | 12 + package.json | 5 +- stores/grid.ts | 4 +- translations/en_US.json | 2 +- types/grid.ts | 37 +-- yarn.lock | 18 +- 56 files changed, 1202 insertions(+), 962 deletions(-) delete mode 100644 domains/grid/components/AddWidgetGenericPlatform.vue create mode 100644 domains/grid/composables/useForm.ts rename domains/grid/utils/__tests__/{configToGrid.ts => configToGrid.spec.ts} (94%) delete mode 100644 domains/grid/utils/__tests__/isConfigValid.spec.ts create mode 100644 domains/grid/utils/__tests__/purifyGridConfig.spec.ts delete mode 100644 domains/grid/utils/isConfigValid.ts create mode 100644 domains/grid/utils/purifyGridConfig.ts create mode 100644 domains/schema/gridConfigSchema.ts create mode 100644 domains/schema/gridSchema.ts create mode 100644 domains/schema/iframeWidgetSchema.ts create mode 100644 domains/schema/imageWidgetSchema.ts create mode 100644 domains/schema/instagramWidgetSchema.ts create mode 100644 domains/schema/soundCloudWidgetSchema.ts create mode 100644 domains/schema/spotifyWidgetSchema.ts create mode 100644 domains/schema/textWidgetSchema.ts create mode 100644 domains/schema/transforms/platformTransform.ts create mode 100644 domains/schema/transforms/urlTransform.ts create mode 100644 domains/schema/validators/hexColorValidator.ts create mode 100644 domains/schema/warpcastWidgetSchema.ts create mode 100644 domains/schema/xWidgetSchema.ts create mode 100644 domains/schema/youtubeWidgetSchema.ts diff --git a/components/ModalTemplateAddEditGrid.vue b/components/ModalTemplateAddEditGrid.vue index 562da840..353f9ea2 100644 --- a/components/ModalTemplateAddEditGrid.vue +++ b/components/ModalTemplateAddEditGrid.vue @@ -1,5 +1,4 @@ @@ -131,7 +121,6 @@ onMounted(() => { :options="JSON.stringify(gridColumnsOptions)" :label="formatMessage('add_grid_columns_label')" is-full-width - autofocus @on-select="handleChangeColumnNumber" > diff --git a/components/ModalTemplateMoveGridWidget.vue b/components/ModalTemplateMoveGridWidget.vue index bbe8d4a0..d9e7ac38 100644 --- a/components/ModalTemplateMoveGridWidget.vue +++ b/components/ModalTemplateMoveGridWidget.vue @@ -72,7 +72,6 @@ onMounted(() => { :value="JSON.stringify(selectedOptionValue)" :options="JSON.stringify(options)" is-full-width - autofocus @on-select="handleChange" > diff --git a/domains/grid/components/AddWidget.vue b/domains/grid/components/AddWidget.vue index b2c5b1dd..adc096c1 100644 --- a/domains/grid/components/AddWidget.vue +++ b/domains/grid/components/AddWidget.vue @@ -13,15 +13,15 @@ const props = defineProps() const component = shallowRef() const WIDGET_COMPONENTS: Record = { - [GRID_WIDGET_TYPE.TEXT]: 'Text', - [GRID_WIDGET_TYPE.IMAGE]: 'Basic', - [GRID_WIDGET_TYPE.IFRAME]: 'Basic', - [GRID_WIDGET_TYPE.X]: 'GenericPlatform', - [GRID_WIDGET_TYPE.INSTAGRAM]: 'GenericPlatform', - [GRID_WIDGET_TYPE.SPOTIFY]: 'GenericPlatform', - [GRID_WIDGET_TYPE.SOUNDCLOUD]: 'GenericPlatform', - [GRID_WIDGET_TYPE.WARPCAST]: 'Basic', - [GRID_WIDGET_TYPE.YOUTUBE]: 'GenericPlatform', + [GRID_WIDGET_TYPE.enum.TEXT]: 'Text', + [GRID_WIDGET_TYPE.enum.IMAGE]: 'Basic', + [GRID_WIDGET_TYPE.enum.IFRAME]: 'Basic', + [GRID_WIDGET_TYPE.enum.X]: 'Basic', + [GRID_WIDGET_TYPE.enum.INSTAGRAM]: 'Basic', + [GRID_WIDGET_TYPE.enum.SPOTIFY]: 'Basic', + [GRID_WIDGET_TYPE.enum.SOUNDCLOUD]: 'Basic', + [GRID_WIDGET_TYPE.enum.WARPCAST]: 'Basic', + [GRID_WIDGET_TYPE.enum.YOUTUBE]: 'Basic', } const loadComponent = (type?: string): Component | undefined => { diff --git a/domains/grid/components/AddWidgetBasic.vue b/domains/grid/components/AddWidgetBasic.vue index 4832740a..59c15ae6 100644 --- a/domains/grid/components/AddWidgetBasic.vue +++ b/domains/grid/components/AddWidgetBasic.vue @@ -12,68 +12,54 @@ const { formatMessage } = useIntl() const { closeModal, showModal } = useModal() const { addGridWidget, updateGridWidget, getGridById } = useGrid() const { tempGrid, selectedGridId } = storeToRefs(useGridStore()) - -const TEXTAREA_FOCUS_DELAY = 10 // small delay for focusing textarea after element render -const inputValue = ref('') -const inputError = ref('') - -const canSubmit = computed(() => inputValue.value && !inputError.value) +const schema = WIDGET_SCHEMA_MAP[props.type] const isEdit = computed(() => !!props.id) - -const handleSave = () => { +const { + inputValues, + canSubmit, + getFieldErrorMessage, + handleFieldChange, + handleFormErrors, +} = useForm(schema, (await schema?.safeParseAsync(props.properties))?.data) + +const handleSave = async () => { if (!canSubmit.value) { return } - const properties = { - src: inputValue.value, - } - - if (isEdit.value) { - updateGridWidget(props.id, { - properties, - w: props.width, - h: props.height, - }) - } else { - const newWidget: GridWidgetWithoutCords = createWidgetObject({ - type: props.type, - properties, - w: props.width, - h: props.height, - }) - - addGridWidget(newWidget, getGridById(tempGrid.value, selectedGridId.value)) + try { + const properties = await schema?.parseAsync(inputValues.value) + + if (isEdit.value) { + updateGridWidget(props.id, { + properties, + w: props.width, + h: props.height, + }) + } else { + const newWidget: GridWidgetWithoutCords = createWidgetObject({ + type: props.type, + properties, + w: props.width, + h: props.height, + }) + + addGridWidget( + newWidget, + getGridById(tempGrid.value, selectedGridId.value) + ) + } + + handleCancel() + } catch (error: unknown) { + handleFormErrors(error) } - - handleCancel() } const handleCancel = () => { closeModal() } -const handleInput = async (customEvent: CustomEvent) => { - const event = customEvent.detail.event - const input = event.target as HTMLInputElement - inputError.value = '' - - // if no value is entered we just exit here - if (!input.value) { - return - } - - // validation - try { - new URL(input.value) - inputValue.value = encodeURI(input.value) - } catch (error) { - console.warn(error) - inputError.value = formatMessage('errors_invalid_url') - return - } -} - const handleBackToSelection = () => { showModal({ template: 'AddGridWidget', @@ -83,18 +69,6 @@ const handleBackToSelection = () => { }, }) } - -onMounted(() => { - setTimeout(() => { - const textarea = document?.querySelector( - 'lukso-textarea' - ) as unknown as HTMLElement - - textarea?.shadowRoot?.querySelector('textarea')?.focus() - }, TEXTAREA_FOCUS_DELAY) - - inputValue.value = props.properties?.src || '' -})