-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
abd58a0
commit 077e1ca
Showing
12 changed files
with
921 additions
and
0 deletions.
There are no files selected for viewing
132 changes: 132 additions & 0 deletions
132
src/components/common/FooterSubscribeForm/FooterSubscribeForm.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
<script setup lang="ts"> | ||
import { | ||
computed, nextTick, reactive, ref, watch, | ||
} from 'vue'; | ||
import BaseButton from 'UiKit/components/BaseButton/BaseButton.vue'; | ||
import BaseFormInput from 'UiKit/components/BaseFormInput/BaseFormInput.vue'; | ||
import { JSONSchemaType } from 'ajv'; | ||
import { emailRule, errorMessageRule } from 'UiKit/helpers/validation/rules'; | ||
import { PrecompiledValidator } from 'UiKit/helpers/validation/PrecompiledValidator'; | ||
import BaseFormGroup from 'UiKit/components/BaseFormGroup/BaseFormGroup.vue'; | ||
import { isEmpty } from 'UiKit/helpers/general'; | ||
import { scrollToError } from 'UiKit/helpers/validation/general'; | ||
const emit = defineEmits(['submit']); | ||
type FormModelSubscribe = { | ||
email: number; | ||
} | ||
const schemaSubscribe = { | ||
$schema: 'http://json-schema.org/draft-07/schema#', | ||
definitions: { | ||
PatchIndividualProfile: { | ||
properties: { | ||
email: emailRule, | ||
}, | ||
type: 'object', | ||
required: ['email'], | ||
errorMessage: errorMessageRule, | ||
}, | ||
}, | ||
$ref: '#/definitions/PatchIndividualProfile', | ||
} as unknown as JSONSchemaType<FormModelSubscribe>; | ||
const model = reactive({ | ||
} as FormModelSubscribe); | ||
const validator = new PrecompiledValidator<FormModelSubscribe>( | ||
schemaSubscribe, | ||
); | ||
const validation = ref<unknown>(); | ||
const isValid = computed(() => isEmpty(validation.value || {})); | ||
const isDisabledButton = computed(() => (!isValid.value)); | ||
const onValidate = () => { | ||
validation.value = validator.getFormValidationErrors(model); | ||
}; | ||
const onSubmit = () => { | ||
onValidate(); | ||
if (!isValid.value) { | ||
void nextTick(() => scrollToError('FooterSubscribeForm')); | ||
return; | ||
} | ||
emit('submit', model.email); | ||
}; | ||
watch(() => model, () => { | ||
if (!isValid.value) onValidate(); | ||
}, { deep: true }); | ||
</script> | ||
|
||
<template> | ||
<div class="FooterSubscribeForm footer-subscribe-form"> | ||
<div class="footer-subscribe-form__title is--h6__title"> | ||
Receive latest news: | ||
</div> | ||
<form | ||
class="form-submit" | ||
@submit.prevent="onSubmit" | ||
> | ||
<div class="field-group form-control"> | ||
<BaseFormGroup | ||
v-slot="baseFormGroupProps" | ||
:model="model" | ||
:validation="validation" | ||
:schema-front="schemaSubscribe" | ||
path="email" | ||
class="footer-subscribe-form__group" | ||
> | ||
<BaseFormInput | ||
:is-error="baseFormGroupProps.isFieldError" | ||
:model-value="model.email" | ||
placeholder="Email Address" | ||
name="email" | ||
text | ||
type="email" | ||
size="large" | ||
@update:model-value="model.email = $event" | ||
/> | ||
</BaseFormGroup> | ||
<BaseButton | ||
size="large" | ||
:disabled="isDisabledButton" | ||
:uppercase="false" | ||
> | ||
Subscribe | ||
</BaseButton> | ||
<span class="form-control-error" /> | ||
</div> | ||
<div class="general-form-error" /> | ||
</form> | ||
</div> | ||
</template> | ||
|
||
|
||
<style lang="scss"> | ||
.footer-subscribe-form { | ||
$root: &; | ||
&__group { | ||
width: 100%; | ||
} | ||
&__form { | ||
width: 100%; | ||
} | ||
&__title { | ||
margin-bottom: 8px; | ||
} | ||
.field-group { | ||
position: relative; | ||
width: 100%; | ||
display: flex; | ||
align-items: start; | ||
margin-bottom: 0; | ||
gap: 4px; | ||
} | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
@import 'UiKit/styles/_colors.sass' | ||
@import 'UiKit/styles/_global.sass' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
|
||
export function isEmpty(obj: object) { | ||
// eslint-disable-next-line | ||
for (const prop of Object.keys(obj)) { | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
export function formatPhoneNumber(phoneNumber: string | undefined): string | undefined { | ||
if (!phoneNumber) return undefined; | ||
// Remove all non-digit characters from the input string | ||
let cleaned: string = phoneNumber.replace(/\D/g, ''); | ||
// Extract the country code (if present) | ||
let countryCode: string = ''; | ||
if (cleaned.length > 10) { | ||
countryCode = `+${cleaned.slice(0, cleaned.length - 10)} `; | ||
// Remove country code from the cleaned string | ||
cleaned = cleaned.slice(-10); | ||
} | ||
// Extract the area code and the rest of the number | ||
const areaCode: string = cleaned.slice(0, 3); | ||
const middlePart: string = cleaned.slice(3, 6); | ||
const lastPart: string = cleaned.slice(6); | ||
|
||
// Format the phone number parts into the desired format | ||
const formattedPhoneNumber: string = `${countryCode}(${areaCode}) ${middlePart}-${lastPart}`; | ||
|
||
return formattedPhoneNumber; | ||
} | ||
|
||
|
||
export function booleanFormatToString(value: boolean | undefined) { | ||
if (value === undefined) return undefined; | ||
if (value) return 'Yes'; | ||
return 'No'; | ||
} | ||
|
||
export function checkObjectAndDeleteNotRequiredFields( | ||
defaultParameters: string[], | ||
requiredFields: string[], | ||
obj: object, | ||
) { | ||
return Object.keys(obj).reduce((acc, key) => { | ||
if (defaultParameters.includes(key)) { | ||
acc[key] = obj[key]; // Set the new value for 'type' | ||
} else if (requiredFields.includes(obj[key])) { | ||
acc[key] = obj[key]; | ||
} else { | ||
// If property value is not in requiredEmployment.value, delete the property | ||
delete acc[key]; | ||
} | ||
return acc; | ||
}, {}); | ||
} | ||
|
||
export function urlize(input: string): string { | ||
// Convert the input string to lowercase and replace spaces with hyphens | ||
let urlFriendlyString = input.toLowerCase().replace(/\s+/g, '-'); | ||
|
||
// Remove any characters that are not alphanumeric or hyphens | ||
// eslint-disable-next-line | ||
urlFriendlyString = urlFriendlyString.replace(/[^a-z0-9\-]/g, ''); | ||
|
||
// Remove any consecutive hyphens | ||
urlFriendlyString = urlFriendlyString.replace(/-{2,}/g, '-'); | ||
|
||
// Trim leading and trailing hyphens | ||
urlFriendlyString = urlFriendlyString.replace(/^-+|-+$/g, ''); | ||
|
||
return urlFriendlyString; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import cloneDeep from 'lodash/cloneDeep'; | ||
import get from 'lodash/get'; | ||
import set from 'lodash/set'; | ||
|
||
import { ErrorSchema } from './types'; | ||
import { ERRORS_KEY } from './constants'; | ||
|
||
/** The `ErrorSchemaBuilder<T>` is used to build an `ErrorSchema<T>` since the definition of the `ErrorSchema` type is | ||
* designed for reading information rather than writing it. Use this class to add, replace or clear errors in an error | ||
* schema by using either dotted path or an array of path names. Once you are done building the `ErrorSchema`, you can | ||
* get the result and/or reset all the errors back to an initial set and start again. | ||
*/ | ||
export default class ErrorSchemaBuilder<T = any> { | ||
/** The error schema being built | ||
* | ||
* @private | ||
*/ | ||
private errorSchema: ErrorSchema<T> = {}; | ||
|
||
/** Construct an `ErrorSchemaBuilder` with an optional initial set of errors in an `ErrorSchema`. | ||
* | ||
* @param [initialSchema] - The optional set of initial errors, that will be cloned into the class | ||
*/ | ||
constructor(initialSchema?: ErrorSchema<T>) { | ||
this.resetAllErrors(initialSchema); | ||
} | ||
|
||
/** Returns the `ErrorSchema` that has been updated by the methods of the `ErrorSchemaBuilder` | ||
*/ | ||
public get ErrorSchema() { | ||
return this.errorSchema; | ||
} | ||
|
||
/** Will get an existing `ErrorSchema` at the specified `pathOfError` or create and return one. | ||
* | ||
* @param [pathOfError] - The optional path into the `ErrorSchema` at which to add the error(s) | ||
* @returns - The error block for the given `pathOfError` or the root if not provided | ||
* @private | ||
*/ | ||
private getOrCreateErrorBlock(pathOfError?: string | string[]) { | ||
const hasPath = (Array.isArray(pathOfError) && pathOfError.length > 0) || typeof pathOfError === 'string'; | ||
let errorBlock: ErrorSchema = hasPath ? get(this.errorSchema, pathOfError) : this.errorSchema; | ||
if (!errorBlock && pathOfError) { | ||
errorBlock = {}; | ||
set(this.errorSchema, pathOfError, errorBlock); | ||
} | ||
return errorBlock; | ||
} | ||
|
||
/** Resets all errors in the `ErrorSchemaBuilder` back to the `initialSchema` if provided, otherwise an empty set. | ||
* | ||
* @param [initialSchema] - The optional set of initial errors, that will be cloned into the class | ||
* @returns - The `ErrorSchemaBuilder` object for chaining purposes | ||
*/ | ||
public resetAllErrors(initialSchema?: ErrorSchema<T>) { | ||
this.errorSchema = initialSchema ? cloneDeep(initialSchema) : {}; | ||
return this; | ||
} | ||
|
||
/** Adds the `errorOrList` to the list of errors in the `ErrorSchema` at either the root level or the location within | ||
* the schema described by the `pathOfError`. For more information about how to specify the path see the | ||
* [eslint lodash plugin docs](https://github.com/wix/eslint-plugin-lodash/blob/master/docs/rules/path-style.md). | ||
* | ||
* @param errorOrList - The error or list of errors to add into the `ErrorSchema` | ||
* @param [pathOfError] - The optional path into the `ErrorSchema` at which to add the error(s) | ||
* @returns - The `ErrorSchemaBuilder` object for chaining purposes | ||
*/ | ||
public addErrors(errorOrList: string | string[], pathOfError?: string | string[]) { | ||
const errorBlock: ErrorSchema = this.getOrCreateErrorBlock(pathOfError); | ||
let errorsList = get(errorBlock, ERRORS_KEY); | ||
if (!Array.isArray(errorsList)) { | ||
errorsList = []; | ||
errorBlock[ERRORS_KEY] = errorsList; | ||
} | ||
|
||
if (Array.isArray(errorOrList)) { | ||
errorsList.push(...errorOrList); | ||
} else { | ||
errorsList.push(errorOrList); | ||
} | ||
return this; | ||
} | ||
|
||
/** Sets/replaces the `errorOrList` as the error(s) in the `ErrorSchema` at either the root level or the location | ||
* within the schema described by the `pathOfError`. For more information about how to specify the path see the | ||
* [eslint lodash plugin docs](https://github.com/wix/eslint-plugin-lodash/blob/master/docs/rules/path-style.md). | ||
* | ||
* @param errorOrList - The error or list of errors to set into the `ErrorSchema` | ||
* @param [pathOfError] - The optional path into the `ErrorSchema` at which to set the error(s) | ||
* @returns - The `ErrorSchemaBuilder` object for chaining purposes | ||
*/ | ||
public setErrors(errorOrList: string | string[], pathOfError?: string | string[]) { | ||
const errorBlock: ErrorSchema = this.getOrCreateErrorBlock(pathOfError); | ||
// Effectively clone the array being given to prevent accidental outside manipulation of the given list | ||
const listToAdd = Array.isArray(errorOrList) ? [...errorOrList] : [errorOrList]; | ||
set(errorBlock, ERRORS_KEY, listToAdd); | ||
return this; | ||
} | ||
|
||
/** Clears the error(s) in the `ErrorSchema` at either the root level or the location within the schema described by | ||
* the `pathOfError`. For more information about how to specify the path see the | ||
* [eslint lodash plugin docs](https://github.com/wix/eslint-plugin-lodash/blob/master/docs/rules/path-style.md). | ||
* | ||
* @param [pathOfError] - The optional path into the `ErrorSchema` at which to clear the error(s) | ||
* @returns - The `ErrorSchemaBuilder` object for chaining purposes | ||
*/ | ||
public clearErrors(pathOfError?: string | string[]) { | ||
const errorBlock: ErrorSchema = this.getOrCreateErrorBlock(pathOfError); | ||
set(errorBlock, ERRORS_KEY, []); | ||
return this; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
|
||
import { ajv } from './ajv'; | ||
import { JSONSchemaType, ValidateFunction } from 'ajv'; | ||
import { processRawValidationErrors } from './processRawValidationErrors'; | ||
import merge from 'lodash/merge'; | ||
import { undefinedEmptyProp } from './general'; | ||
|
||
export class PrecompiledValidator<T> { | ||
private readonly rootSchema: JSONSchemaType<T>; | ||
|
||
private readonly compiledValidator: ValidateFunction<T>; | ||
|
||
constructor(schema1: JSONSchemaType<T>, schema2?: JSONSchemaType<T>) { | ||
let schema = schema1; | ||
if (schema2) schema = merge(schema1, schema2); | ||
this.rootSchema = schema; | ||
this.compiledValidator = this.getCompiledValidator(schema); | ||
} | ||
|
||
private getRawErrors() { | ||
const { errors } = this.compiledValidator; | ||
this.compiledValidator.errors = null; | ||
|
||
return errors; | ||
} | ||
|
||
// eslint-disable-next-line | ||
private getCompiledValidator(schema: JSONSchemaType<T>) { | ||
return ajv.compile(schema); | ||
} | ||
|
||
private getIsValid(formData: T) { | ||
return this.compiledValidator(formData); | ||
} | ||
|
||
private getRawValidation(formData: T) { | ||
this.getIsValid(formData); | ||
return this.getRawErrors(); | ||
} | ||
|
||
public getFormValidationErrors(formData: T) { | ||
const rawErrors = this.getRawValidation(undefinedEmptyProp(formData)); | ||
// Need empty structure here | ||
// Solve this if no error validation | ||
if (!rawErrors) return {}; | ||
return processRawValidationErrors(rawErrors); | ||
} | ||
} |
Oops, something went wrong.