From dac3401de73881d7fd2706953363fdaec5bdb81c Mon Sep 17 00:00:00 2001 From: danyaZh Date: Fri, 15 Sep 2023 17:40:40 +0300 Subject: [PATCH 1/6] feat(contact-form): redesigned get contact modal --- .../valor-software-site/src/app/app.module.ts | 3 + .../src/assets/img/bg-img/get_in_touch.svg | 52 ---- .../src/assets/styles/variables.scss | 7 + libs/common-docs/src/common-docs.module.ts | 6 +- .../app-footer/app-footer.component.ts | 2 +- .../contact-modal.component.html | 242 ++++++++++++++++++ .../contact-modal.component.scss | 106 ++++++++ .../contact-modal/contact-modal.components.ts | 189 ++++++++++++++ .../contact-modal/contactModal.component.html | 89 ------- .../contact-modal/contactModal.components.ts | 128 --------- .../components/top-menu/top-menu.component.ts | 2 +- .../common-docs/src/services/modal.service.ts | 6 +- .../src/services/senEmail.service.ts | 21 -- .../src/services/send-email.service.ts | 49 ++++ 14 files changed, 604 insertions(+), 298 deletions(-) delete mode 100644 apps/valor-software-site/src/assets/img/bg-img/get_in_touch.svg create mode 100644 libs/common-docs/src/components/contact-modal/contact-modal.component.html create mode 100644 libs/common-docs/src/components/contact-modal/contact-modal.component.scss create mode 100644 libs/common-docs/src/components/contact-modal/contact-modal.components.ts delete mode 100644 libs/common-docs/src/components/contact-modal/contactModal.component.html delete mode 100644 libs/common-docs/src/components/contact-modal/contactModal.components.ts delete mode 100644 libs/common-docs/src/services/senEmail.service.ts create mode 100644 libs/common-docs/src/services/send-email.service.ts diff --git a/apps/valor-software-site/src/app/app.module.ts b/apps/valor-software-site/src/app/app.module.ts index e42bbaff3..d0f9b64aa 100644 --- a/apps/valor-software-site/src/app/app.module.ts +++ b/apps/valor-software-site/src/app/app.module.ts @@ -17,6 +17,7 @@ import { articlesList, articlesRefactoringTitlesList } from '../../../../assets/ import { projectsList } from '../assets/portfolio/portfolio.list'; import { linksFromOldSite } from '../../../../assets/articles/brokenRoutes'; import { NotFoundComponent } from './404.component'; +import { RECAPTCHA_V3_SITE_KEY, RecaptchaV3Module } from 'ng-recaptcha'; @NgModule({ declarations: [ @@ -29,6 +30,7 @@ import { NotFoundComponent } from './404.component'; CommonDocsModule, BrowserAnimationsModule, ScullyLibModule, + RecaptchaV3Module, ], providers: [ SeoService, @@ -36,6 +38,7 @@ import { NotFoundComponent } from './404.component'; { provide: ARTICLES_REFACTORED_TITLE_LIST, useValue: articlesRefactoringTitlesList }, { provide: PORTFOLIO_LIST, useValue: projectsList }, { provide: OLD_ROUTES_FROM_OLD_SITE, useValue: linksFromOldSite }, + { provide: RECAPTCHA_V3_SITE_KEY, useValue: '6LeXDiUoAAAAABZ2FU4l2GZTJ0v5otDAQkC3UZxs' } ], bootstrap: [AppComponent], }) diff --git a/apps/valor-software-site/src/assets/img/bg-img/get_in_touch.svg b/apps/valor-software-site/src/assets/img/bg-img/get_in_touch.svg deleted file mode 100644 index aa59ca3b8..000000000 --- a/apps/valor-software-site/src/assets/img/bg-img/get_in_touch.svg +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/apps/valor-software-site/src/assets/styles/variables.scss b/apps/valor-software-site/src/assets/styles/variables.scss index 78f30705b..d171207c4 100644 --- a/apps/valor-software-site/src/assets/styles/variables.scss +++ b/apps/valor-software-site/src/assets/styles/variables.scss @@ -1,6 +1,13 @@ $light_font_col: #E1E1E1; $light_title_col: #E3E3E3; +/* Breakpoints */ +$bp-small: 640px; +$bp-medium: 768px; +$bp-large: 1024px; +$bp-xlarge: 1280px; +$bp-2xlarge: 1536px; + :root { --light_font_col: #E1E1E1; } diff --git a/libs/common-docs/src/common-docs.module.ts b/libs/common-docs/src/common-docs.module.ts index fe8d05df8..2700d2a16 100644 --- a/libs/common-docs/src/common-docs.module.ts +++ b/libs/common-docs/src/common-docs.module.ts @@ -4,12 +4,12 @@ import { PopoverComponent } from './components/popover/popover.component'; import { TopMenuComponent } from './components/top-menu/top-menu.component'; import { RouterModule } from '@angular/router'; import { ImgHoverDirective } from './directives/img-hover.directive'; -import { ContactModalComponent } from './components/contact-modal/contactModal.components'; +import { ContactModalComponent } from './components/contact-modal/contact-modal.components'; import { ModalService } from './services/modal.service'; import { ReactiveFormsModule } from '@angular/forms'; import { HttpClientModule } from '@angular/common/http'; import { FileUploaderModule } from '@valor-software/file-uploader'; -import { SendEmailService } from './services/senEmail.service'; +import { SendEmailService } from './services/send-email.service'; import { ResultModalComponent } from './components/result-modal/result-modal.component'; import { AppFooterComponent } from './components/app-footer/app-footer.component'; import { ServicesBlockComponent } from './components/services_block/services-block.component'; @@ -37,7 +37,7 @@ export { TopMenuComponent } from './components/top-menu/top-menu.component'; export { ImgHoverDirective } from './directives/img-hover.directive'; export { AppFooterComponent } from './components/app-footer/app-footer.component'; export { ServicesBlockComponent } from './components/services_block/services-block.component'; -export { ContactModalComponent } from './components/contact-modal/contactModal.components'; +export { ContactModalComponent } from './components/contact-modal/contact-modal.components'; export { ModalService } from './services/modal.service'; export { BreadCrumbsComponent } from './components/breadCrumbs/breadCrumbs.component'; export { ShowHideDirective } from './directives/showHide.directive'; diff --git a/libs/common-docs/src/components/app-footer/app-footer.component.ts b/libs/common-docs/src/components/app-footer/app-footer.component.ts index 446c1df82..784271839 100644 --- a/libs/common-docs/src/components/app-footer/app-footer.component.ts +++ b/libs/common-docs/src/components/app-footer/app-footer.component.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; import { ModalService } from '../../services/modal.service'; -import { ContactModalComponent } from '../contact-modal/contactModal.components'; +import { ContactModalComponent } from '../contact-modal/contact-modal.components'; import { FormControl, Validators } from '@angular/forms'; @Component({ diff --git a/libs/common-docs/src/components/contact-modal/contact-modal.component.html b/libs/common-docs/src/components/contact-modal/contact-modal.component.html new file mode 100644 index 000000000..acad9e69d --- /dev/null +++ b/libs/common-docs/src/components/contact-modal/contact-modal.component.html @@ -0,0 +1,242 @@ +
+
+ +
+ +
+

We'd love to hear from + you!

+ +
+ close icon +
+
+ +
+ +
+ +
+ +
+
+ First Name + + + + + {{ validation.message }} + + +
+ +
+

Last Name

+ + + + + {{ validation.message }} + + +
+ +
+ +
+
+

Work Email

+ + + + + {{ validation.message }} + + +
+ +
+

Company name (optional)

+ +
+
+ +
+
+

Your job role (optional)

+ +
+ +
+

Size of the company (optional)

+ +
+
+ +
+

How can we help you?

+ +
+ + + +
+ +
+ + + +
+ +
+ +
+

Comment (optional)

+ + + + {{ validation.message }} + + +
+ +
+ + + +
+ + +
+ +
+
+ Careers: + + careers@valor-software.com + +
+ +
+ Sales: + + sales@valor-software.com + +
+
+ +
+
+ + + + +
+ + + + + +
+
+
diff --git a/libs/common-docs/src/components/contact-modal/contact-modal.component.scss b/libs/common-docs/src/components/contact-modal/contact-modal.component.scss new file mode 100644 index 000000000..e62a4f88e --- /dev/null +++ b/libs/common-docs/src/components/contact-modal/contact-modal.component.scss @@ -0,0 +1,106 @@ +@import 'apps/valor-software-site/src/assets/styles/styles'; + +:host { + font-family: 'Work Sans', 'Roboto', 'Arial', 'sans-serif'; +} + +.contact-body { + height: 100%; + width: 100%; + overflow-x: hidden; + overflow-y: auto; + top: 0; + + +} + +.row { + display: flex; + justify-content: space-between; + width: 100%; + + @media (max-width: $bp-small) { + flex-direction: column; + justify-content: start; + } +} + +.option-background { + width: 100%; + border-radius: 0.25rem; + border-width: 1px; + border-color: transparent; + background-color: rgba(52, 52, 52, var(--tw-bg-opacity)); + padding: 1rem; + font-size: 1rem; + color: rgba(255, 255, 255, var(--tw-text-opacity)); +} + +.form-control-wrapper { + display: flex; + flex-direction: column; + width: 48%; + + @media (max-width: $bp-small) { + width: 100%; + + &:first-child { + margin-bottom: 16px; + } + } +} + +.form-control { + @extend .text-input; + + padding: 12px; +} + +.radio-input { + width: 48%; + display: flex; + align-items: center; + color: white; + cursor: pointer; + padding: 12px; + + @media (max-width: $bp-small) { + width: 100%; + + &:first-child { + margin-bottom: 16px; + } + } +} + +.checkbox-label { + display: flex; + align-items: center; + cursor: pointer; + width: 55%; + color: white; + + @media (max-width: $bp-medium) { + width: 70%; + } + + @media (max-width: $bp-small) { + width: 100%; + } +} + +.checkbox { + width: 20px; + height: 20px; + background-color: #272727; + border: 1px solid #E24E63; + border-radius: 10%; + + &:focus { + box-shadow: none; + } + + &:checked { + background-color: #E24E63 !important; + } +} diff --git a/libs/common-docs/src/components/contact-modal/contact-modal.components.ts b/libs/common-docs/src/components/contact-modal/contact-modal.components.ts new file mode 100644 index 000000000..ddc08ac83 --- /dev/null +++ b/libs/common-docs/src/components/contact-modal/contact-modal.components.ts @@ -0,0 +1,189 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, HostListener, OnDestroy } from '@angular/core'; +import { ModalService } from '../../services/modal.service'; +import { Subscription } from 'rxjs'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { CompanyServiceName, ContactData, SendEmailService } from '../../services/send-email.service'; +import { ReCaptchaV3Service } from 'ng-recaptcha'; +import { errorVocabulary, IError } from './errors'; +import { Router } from '@angular/router'; +import { switchMap, tap } from 'rxjs/operators'; + +interface ContactModalForm { + firstName: FormControl; + lastName: FormControl; + email: FormControl; + companyName: FormControl; + jobRole: FormControl; + companySize: FormControl; + companyServiceName: FormControl; + comment: FormControl; + hasAcceptedPrivacyPolicy: FormControl; + hasAcceptedContactAgreement: FormControl; + ['g-recaptcha-response']: FormControl; +} + +const emailRegex = /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$/; + +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: 'contact-modal', + templateUrl: './contact-modal.component.html', + styleUrls: ['./contact-modal.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ContactModalComponent implements OnDestroy { + showSuccessModalState = false; + showErrorModalState = false; + submitErrorMessage = ''; + + form = this._createForm(); + readonly validationMessages = { + firstName: [ + { type: 'required', message: 'This field is required field' }, + ], + lastName: [ + { type: 'required', message: 'This field is required field' }, + ], + email: [ + { type: 'required', message: 'This field is required field' }, + { type: 'pattern', message: 'Please enter a valid email' } + ], + message: [ + { type: 'minlength', message: 'Comment must be longer than 5 characters' }, + { type: 'maxlength', message: 'Comment must be less than 1000 characters' } + ], + hasAcceptedPrivacyPolicy: [ + { type: 'required', message: 'This field is required field' }, + ], + hasAcceptedContactAgreement: [ + { type: 'required', message: 'This field is required field' }, + ], + }; + readonly CompanyServiceName = CompanyServiceName; + + private readonly _internalSub = new Subscription(); + + @HostListener('document:keydown.escape', ['$event']) onKeydownHandler() { + const focused: HTMLElement | null = document.querySelector(':focus-visible'); + this.closeModal(); + if (focused) { + focused.blur(); + } + } + + constructor( + readonly modalService: ModalService, + private readonly sendEmailService: SendEmailService, + private readonly recaptchaV3Service: ReCaptchaV3Service, + private readonly router: Router, + private readonly _cdRef: ChangeDetectorRef + ) { + const element = document.body.querySelector('.grecaptcha-badge') as HTMLElement; + if (element) { + element.style.display = 'block'; + } + } + + ngOnDestroy() { + const element = document.body.querySelector('.grecaptcha-badge') as HTMLElement; + if (element) { + element.style.display = 'none'; + } + + this._internalSub.unsubscribe(); + } + + closeModal() { + this.modalService.close(); + } + + onSubmit() { + this._internalSub.add( + this.recaptchaV3Service.execute('importantAction').pipe( + tap((token: string) => this.form.get('g-recaptcha-response')?.setValue(token)), + switchMap((token: string) => + this.form.getRawValue().companyServiceName === CompanyServiceName.Career + ? this.sendEmailService.sendCareersEmail(this.getContactData()) + : this.sendEmailService.sendSalesEmail(this.getContactData()) + ) + ).subscribe(() => { + this.form.reset(); + this.showSuccessModal(); + this.recaptchaV3Service.execute(''); + }, (error: IError) => { + const code = error.error.errors[0].code === 'TYPE_EMAIL' ? 'INVALID_EMAIL' : error.error.errors[0].code; + const errorText = errorVocabulary[code as keyof typeof errorVocabulary] || error.error.errors[0].message; + this.showErrorModal(errorText); + }) + ); + } + + showSuccessModal() { + this.showSuccessModalState = true; + this.showErrorModalState = false; + this.submitErrorMessage = ''; + this._cdRef.detectChanges(); + } + + showErrorModal(error?: string) { + this.showSuccessModalState = false; + this.showErrorModalState = true; + this.submitErrorMessage = error ?? ''; + this._cdRef.detectChanges(); + } + + resetError() { + this.showErrorModalState = false; + this.submitErrorMessage = ''; + this._cdRef.detectChanges(); + } + + hasFieldError(validationType: string, field: string): boolean { + const formField = this.form.get(field); + + return formField + ? formField.hasError(validationType) && (formField.dirty || formField.touched) + : false; + } + + private _createForm() { + return new FormGroup({ + firstName: new FormControl('', [Validators.required]), + lastName: new FormControl('', [Validators.required]), + email: new FormControl('', [Validators.required, Validators.pattern(emailRegex)]), + companyName: new FormControl('',), + jobRole: new FormControl('',), + companySize: new FormControl('',), + companyServiceName: new FormControl(this._getDefaultCompanyServiceName()), + comment: new FormControl('', [Validators.maxLength(1000), Validators.minLength(5)]), + hasAcceptedPrivacyPolicy: new FormControl(false, [Validators.required]), + hasAcceptedContactAgreement: new FormControl(false, [Validators.required]), + 'g-recaptcha-response': new FormControl('') + }); + } + + private _getDefaultCompanyServiceName(): CompanyServiceName { + if (this.router.url.includes('/careers')) { + return CompanyServiceName.Career; + } + + return CompanyServiceName.Service; + } + + private getContactData(): ContactData { + const data = this.form.getRawValue(); + + return { + firstName: data.firstName ?? '', + lastName: data.lastName ?? '', + email: data.email ?? '', + companyServiceName: data.companyServiceName ?? CompanyServiceName.Service, + 'g-recaptcha-response': data['g-recaptcha-response'] ?? '', + // optional fields + ...(data.companyName && { companyName: data.companyName }), + ...(data.jobRole && { jobRole: data.jobRole }), + ...(data.companySize && { companySize: data.companySize }), + ...(data.comment && { comment: data.comment }), + }; + } +} diff --git a/libs/common-docs/src/components/contact-modal/contactModal.component.html b/libs/common-docs/src/components/contact-modal/contactModal.component.html deleted file mode 100644 index c96275b30..000000000 --- a/libs/common-docs/src/components/contact-modal/contactModal.component.html +++ /dev/null @@ -1,89 +0,0 @@ -
-
- -
-
- close icon -
-
-

We'd love to hear
from you!

-
-
-
-

How can we help you?

-
- - - - -
-
-

Email

- - - - {{ validation.message }} - - -
-
-

Message

- - - - {{ validation.message }} - - -
- - -
-
- -
-
-
- - - - -
-
-
diff --git a/libs/common-docs/src/components/contact-modal/contactModal.components.ts b/libs/common-docs/src/components/contact-modal/contactModal.components.ts deleted file mode 100644 index 02a88bcc0..000000000 --- a/libs/common-docs/src/components/contact-modal/contactModal.components.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { Component, HostListener, OnDestroy } from '@angular/core'; -import { ModalService } from '../../services/modal.service'; -import { Subscription } from 'rxjs'; -import { UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms'; -import { SendEmailService } from '../../services/senEmail.service'; -import { ReCaptchaV3Service } from 'ng-recaptcha'; -import { errorVocabulary, IError } from './errors'; -import { Router } from '@angular/router'; - -@Component({ - // eslint-disable-next-line @angular-eslint/component-selector - selector: 'contact-modal', - templateUrl: './contactModal.component.html' -}) -export class ContactModalComponent implements OnDestroy { - _state?: Subscription; - state?: boolean; - form: UntypedFormGroup = new UntypedFormGroup({ - type: new UntypedFormControl('', [Validators.required]), - email: new UntypedFormControl('', [Validators.required, Validators.email]), - message: new UntypedFormControl('', [Validators.required, Validators.maxLength(1000), Validators.minLength(5)]), - 'g-recaptcha-response': new UntypedFormControl('') - }); - data?: FormData; - files?: File[]; - showSuccess = false; - showError = false; - $reCaptchaSub?: Subscription; - errorMessage?: string; - - readonly validationMessages = { - email: [ - { type: 'required', message: 'required field' }, - { type: 'email', message: 'Please enter a valid email' } - ], - message: [ - { type: 'required', message: 'required field' }, - { type: 'minlength', message: 'The message must be longer than 5 characters' }, - { type: 'maxlength', message: 'The message must be less than 1000 characters' } - ], - }; - - @HostListener('document:keydown.escape', ['$event']) onKeydownHandler() { - const focused: HTMLElement | null = document.querySelector(':focus-visible'); - this.closeModal(); - if(focused){ - focused.blur(); - } - } - - constructor( - private modalService: ModalService, - private sendEmailServ: SendEmailService, - private recaptchaV3Service: ReCaptchaV3Service, - private router: Router - ) { - this._state = this.modalService.state.subscribe((res: boolean) => { - setTimeout(() => { - this.state = res; - }); - }); - - const element = document.body.querySelector('.grecaptcha-badge') as HTMLElement; - if (element) { - element.style.display = 'block'; - } - - if (this.router.url.includes('/services') || this.router.url.includes('/clients')) { - this.form.get('type')?.setValue('service'); - } else if (this.router.url.includes('/careers')) { - this.form.get('type')?.setValue('career'); - } - } - - closeModal() { - this.modalService.close(); - } - - onSubmit() { - this.$reCaptchaSub = this.recaptchaV3Service.execute('importantAction') - .subscribe((token: string) => { - this.form.get('g-recaptcha-response')?.setValue(token); - this.sendEmailServ.sendEmail(this.form.value).subscribe(res => { - this.form.reset(); - this.showSuccessModal(); - this.recaptchaV3Service.execute(''); - }, (error: IError) => { - const code = error.error.errors[0].code === 'TYPE_EMAIL' ? 'INVALID_EMAIL' : error.error.errors[0].code; - const errorText = errorVocabulary[code as keyof typeof errorVocabulary] || error.error.errors[0].message; - this.showErrorModal(errorText); - }); - }); - } - - showSuccessModal() { - this.showSuccess = true; - this.showError = false; - this.errorMessage = ''; - } - - showErrorModal(error?: string) { - this.showSuccess = false; - this.showError = true; - this.errorMessage = error; - } - - resetError() { - this.showError = false; - this.errorMessage = ''; - } - - ngOnDestroy() { - const element = document.body.querySelector('.grecaptcha-badge') as HTMLElement; - if (element) { - element.style.display = 'none'; - } - this.$reCaptchaSub?.unsubscribe(); - } - - hasFieldError(validationType: string, field: string): boolean { - const formField = this.form.get(field); - if (formField) { - return formField.hasError(validationType) && (formField.dirty || formField.touched); - } else { - return false; - } - } -} diff --git a/libs/common-docs/src/components/top-menu/top-menu.component.ts b/libs/common-docs/src/components/top-menu/top-menu.component.ts index 13c15d143..0fd54afa3 100644 --- a/libs/common-docs/src/components/top-menu/top-menu.component.ts +++ b/libs/common-docs/src/components/top-menu/top-menu.component.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; import { ModalService } from "../../services/modal.service"; -import { ContactModalComponent } from "../contact-modal/contactModal.components"; +import { ContactModalComponent } from "../contact-modal/contact-modal.components"; @Component({ // eslint-disable-next-line @angular-eslint/component-selector diff --git a/libs/common-docs/src/services/modal.service.ts b/libs/common-docs/src/services/modal.service.ts index 5800e6343..70c565998 100644 --- a/libs/common-docs/src/services/modal.service.ts +++ b/libs/common-docs/src/services/modal.service.ts @@ -13,7 +13,7 @@ import { BehaviorSubject } from "rxjs"; }) export class ModalService { private componentRef: ComponentRef | undefined; - state: BehaviorSubject = new BehaviorSubject(false); + modalDisplayState: BehaviorSubject = new BehaviorSubject(false); constructor( private componentFactoryResolver: ComponentFactoryResolver, @@ -32,14 +32,14 @@ export class ModalService { this.appRef.attachView(this.componentRef.hostView); document.body.appendChild(this.componentRef.location.nativeElement); - this.state.next(true); + this.modalDisplayState.next(true); } close(): void { if (!this.componentRef) { return; } - this.state.next(false); + this.modalDisplayState.next(false); setTimeout(()=> { if(this.componentRef) { this.appRef.detachView(this.componentRef.hostView); diff --git a/libs/common-docs/src/services/senEmail.service.ts b/libs/common-docs/src/services/senEmail.service.ts deleted file mode 100644 index 3879b8696..000000000 --- a/libs/common-docs/src/services/senEmail.service.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Injectable } from '@angular/core'; -import { HttpClient, HttpHeaders } from '@angular/common/http'; -import { Observable } from 'rxjs'; - - -@Injectable({providedIn: 'platform'}) -export class SendEmailService { - - constructor( - private http: HttpClient - ){} - - public sendEmail(body: any): Observable { - return this.http - .post('https://formspree.io/f/xeqnjork', body, { - headers: new HttpHeaders({ - 'Accept': 'application/json' - }) - }); - } -} \ No newline at end of file diff --git a/libs/common-docs/src/services/send-email.service.ts b/libs/common-docs/src/services/send-email.service.ts new file mode 100644 index 000000000..be588f417 --- /dev/null +++ b/libs/common-docs/src/services/send-email.service.ts @@ -0,0 +1,49 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +export interface ContactData { + firstName: string; + lastName: string; + email: string; + companyName?: string; + jobRole?: string; + companySize?: string; + companyServiceName: CompanyServiceName; + comment?: string; + ['g-recaptcha-response']: string; +} + +export enum CompanyServiceName { + Service = 'Service', + Career = 'Career', + Partner = 'Partner', + Other = 'Other', +} + +@Injectable({ providedIn: 'platform' }) +export class SendEmailService { + constructor( + private readonly http: HttpClient + ) { + } + + public sendSalesEmail(body: ContactData): Observable { + return this.http + // https://formspree.io/f/xeqnjork + .post('https://formspree.io/f/mbjvkglj', body, { + headers: new HttpHeaders({ + 'Accept': 'application/json' + }) + }); + } + + public sendCareersEmail(body: ContactData): Observable { + return this.http + .post('https://formspree.io/f/mgejzapp', body, { + headers: new HttpHeaders({ + 'Accept': 'application/json' + }) + }); + } +} \ No newline at end of file From afa916193858d8a5bd68f178f5d852e3a2ffa262 Mon Sep 17 00:00:00 2001 From: danyaZh Date: Thu, 21 Sep 2023 16:47:53 +0300 Subject: [PATCH 2/6] change contact form requests --- libs/common-docs/src/services/send-email.service.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/libs/common-docs/src/services/send-email.service.ts b/libs/common-docs/src/services/send-email.service.ts index be588f417..c58b76e7c 100644 --- a/libs/common-docs/src/services/send-email.service.ts +++ b/libs/common-docs/src/services/send-email.service.ts @@ -30,8 +30,7 @@ export class SendEmailService { public sendSalesEmail(body: ContactData): Observable { return this.http - // https://formspree.io/f/xeqnjork - .post('https://formspree.io/f/mbjvkglj', body, { + .post('https://formspree.io/f/xeqbrkvo', body, { headers: new HttpHeaders({ 'Accept': 'application/json' }) @@ -40,7 +39,7 @@ export class SendEmailService { public sendCareersEmail(body: ContactData): Observable { return this.http - .post('https://formspree.io/f/mgejzapp', body, { + .post('https://formspree.io/f/xgejnyra', body, { headers: new HttpHeaders({ 'Accept': 'application/json' }) From f51300cbb1ce5f2d3797f734735c6c081fbfd9e3 Mon Sep 17 00:00:00 2001 From: danyaZh Date: Thu, 21 Sep 2023 19:39:12 +0300 Subject: [PATCH 3/6] fixed build --- .../contact-modal/contact-modal.component.html | 12 ++++++------ .../contact-modal/contact-modal.component.scss | 12 +++--------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/libs/common-docs/src/components/contact-modal/contact-modal.component.html b/libs/common-docs/src/components/contact-modal/contact-modal.component.html index acad9e69d..5c533d70a 100644 --- a/libs/common-docs/src/components/contact-modal/contact-modal.component.html +++ b/libs/common-docs/src/components/contact-modal/contact-modal.component.html @@ -28,7 +28,7 @@

First Name -

Last Name

-

Work Email

-

Company name (optional)

- @@ -88,7 +88,7 @@

Your job role (optional)

- @@ -96,7 +96,7 @@

Size of the company (optional)

- diff --git a/libs/common-docs/src/components/contact-modal/contact-modal.component.scss b/libs/common-docs/src/components/contact-modal/contact-modal.component.scss index e62a4f88e..83f90379e 100644 --- a/libs/common-docs/src/components/contact-modal/contact-modal.component.scss +++ b/libs/common-docs/src/components/contact-modal/contact-modal.component.scss @@ -1,4 +1,6 @@ -@import 'apps/valor-software-site/src/assets/styles/styles'; +// ToDo: add base styles import +$bp-small: 640px; +$bp-medium: 768px; :host { font-family: 'Work Sans', 'Roboto', 'Arial', 'sans-serif'; @@ -10,8 +12,6 @@ overflow-x: hidden; overflow-y: auto; top: 0; - - } .row { @@ -50,12 +50,6 @@ } } -.form-control { - @extend .text-input; - - padding: 12px; -} - .radio-input { width: 48%; display: flex; From 0fb40fd2bab8ef6a1e476946f728619dc6024a64 Mon Sep 17 00:00:00 2001 From: danyaZh Date: Mon, 25 Sep 2023 16:04:34 +0300 Subject: [PATCH 4/6] fixed comments --- .../contact-modal.component.html | 42 ++++++++-- .../contact-modal/contact-modal.components.ts | 82 +++++++++++++++---- 2 files changed, 98 insertions(+), 26 deletions(-) diff --git a/libs/common-docs/src/components/contact-modal/contact-modal.component.html b/libs/common-docs/src/components/contact-modal/contact-modal.component.html index 5c533d70a..83fc26a31 100644 --- a/libs/common-docs/src/components/contact-modal/contact-modal.component.html +++ b/libs/common-docs/src/components/contact-modal/contact-modal.component.html @@ -82,6 +82,13 @@

+ + + + {{ validation.message }} + +

@@ -92,6 +99,13 @@

+ + + + {{ validation.message }} + +

@@ -100,6 +114,13 @@

+ + + + {{ validation.message }} + +

@@ -126,7 +147,8 @@

-
+

Careers

@@ -139,7 +161,8 @@

-
+

Partnerships & Sales

@@ -150,7 +173,8 @@

-
+

Other

@@ -163,8 +187,8 @@

- - + + {{ validation.message }} @@ -223,8 +247,6 @@

@@ -233,10 +255,12 @@

Done + (click)="closeModal()">Done + + (click)="resetError()">Try Again + diff --git a/libs/common-docs/src/components/contact-modal/contact-modal.components.ts b/libs/common-docs/src/components/contact-modal/contact-modal.components.ts index ddc08ac83..37ae3901d 100644 --- a/libs/common-docs/src/components/contact-modal/contact-modal.components.ts +++ b/libs/common-docs/src/components/contact-modal/contact-modal.components.ts @@ -23,6 +23,7 @@ interface ContactModalForm { } const emailRegex = /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$/; +const textRegex = /^[a-zA-Z0-9\-_ ']+$/; @Component({ // eslint-disable-next-line @angular-eslint/component-selector @@ -40,17 +41,38 @@ export class ContactModalComponent implements OnDestroy { readonly validationMessages = { firstName: [ { type: 'required', message: 'This field is required field' }, + { type: 'minlength', message: 'Field must be longer than 2 characters' }, + { type: 'maxlength', message: 'Field must be less than 30 characters' }, + { type: 'pattern', message: 'Please enter valid data' } ], lastName: [ { type: 'required', message: 'This field is required field' }, + { type: 'minlength', message: 'Field must be longer than 2 characters' }, + { type: 'maxlength', message: 'Field must be less than 30 characters' }, + { type: 'pattern', message: 'Please enter valid data' } + ], + companyName: [ + { type: 'minlength', message: 'Field must be longer than 2 characters' }, + { type: 'maxlength', message: 'Field must be less than 30 characters' }, + { type: 'pattern', message: 'Please enter valid data' } + ], + jobRole: [ + { type: 'minlength', message: 'Field must be longer than 2 characters' }, + { type: 'maxlength', message: 'Field must be less than 30 characters' }, + { type: 'pattern', message: 'Please enter valid data' } + ], + companySize: [ + { type: 'minlength', message: 'Field must be longer than 2 characters' }, + { type: 'maxlength', message: 'Field must be less than 30 characters' }, + { type: 'pattern', message: 'Please enter valid data' } ], email: [ { type: 'required', message: 'This field is required field' }, { type: 'pattern', message: 'Please enter a valid email' } ], - message: [ - { type: 'minlength', message: 'Comment must be longer than 5 characters' }, - { type: 'maxlength', message: 'Comment must be less than 1000 characters' } + comment: [ + { type: 'minlength', message: 'Field must be longer than 5 characters' }, + { type: 'maxlength', message: 'Field must be less than 1000 characters' } ], hasAcceptedPrivacyPolicy: [ { type: 'required', message: 'This field is required field' }, @@ -148,14 +170,40 @@ export class ContactModalComponent implements OnDestroy { private _createForm() { return new FormGroup({ - firstName: new FormControl('', [Validators.required]), - lastName: new FormControl('', [Validators.required]), - email: new FormControl('', [Validators.required, Validators.pattern(emailRegex)]), - companyName: new FormControl('',), - jobRole: new FormControl('',), - companySize: new FormControl('',), + firstName: new FormControl('', [ + Validators.required, + Validators.maxLength(30), + Validators.minLength(2), + Validators.pattern(textRegex) + ]), + lastName: new FormControl('', [ + Validators.required, + Validators.maxLength(30), + Validators.minLength(2), + Validators.pattern(textRegex) + ]), + email: new FormControl('', [ + Validators.required, Validators.pattern(emailRegex) + ]), + companyName: new FormControl('', [ + Validators.maxLength(30), + Validators.minLength(2), + Validators.pattern(textRegex) + ]), + jobRole: new FormControl('', [ + Validators.maxLength(30), + Validators.minLength(2), + Validators.pattern(textRegex) + ]), + companySize: new FormControl('', [ + Validators.maxLength(30), + Validators.minLength(2), + Validators.pattern(textRegex) + ]), companyServiceName: new FormControl(this._getDefaultCompanyServiceName()), - comment: new FormControl('', [Validators.maxLength(1000), Validators.minLength(5)]), + comment: new FormControl('', [ + Validators.maxLength(2000), Validators.minLength(5) + ]), hasAcceptedPrivacyPolicy: new FormControl(false, [Validators.required]), hasAcceptedContactAgreement: new FormControl(false, [Validators.required]), 'g-recaptcha-response': new FormControl('') @@ -174,16 +222,16 @@ export class ContactModalComponent implements OnDestroy { const data = this.form.getRawValue(); return { - firstName: data.firstName ?? '', - lastName: data.lastName ?? '', - email: data.email ?? '', + firstName: data.firstName?.trim() ?? '', + lastName: data.lastName?.trim() ?? '', + email: data.email?.trim() ?? '', companyServiceName: data.companyServiceName ?? CompanyServiceName.Service, 'g-recaptcha-response': data['g-recaptcha-response'] ?? '', // optional fields - ...(data.companyName && { companyName: data.companyName }), - ...(data.jobRole && { jobRole: data.jobRole }), - ...(data.companySize && { companySize: data.companySize }), - ...(data.comment && { comment: data.comment }), + ...(data.companyName && { companyName: data.companyName?.trim() }), + ...(data.jobRole && { jobRole: data.jobRole?.trim() }), + ...(data.companySize && { companySize: data.companySize?.trim() }), + ...(data.comment && { comment: data.comment?.trim() }), }; } } From 187fd2656528d817228b01665533b64c3082defe Mon Sep 17 00:00:00 2001 From: danyaZh Date: Tue, 26 Sep 2023 12:18:14 +0300 Subject: [PATCH 5/6] fixed comments --- .../components/contact-modal/contact-modal.components.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libs/common-docs/src/components/contact-modal/contact-modal.components.ts b/libs/common-docs/src/components/contact-modal/contact-modal.components.ts index 37ae3901d..80f9e19be 100644 --- a/libs/common-docs/src/components/contact-modal/contact-modal.components.ts +++ b/libs/common-docs/src/components/contact-modal/contact-modal.components.ts @@ -228,10 +228,10 @@ export class ContactModalComponent implements OnDestroy { companyServiceName: data.companyServiceName ?? CompanyServiceName.Service, 'g-recaptcha-response': data['g-recaptcha-response'] ?? '', // optional fields - ...(data.companyName && { companyName: data.companyName?.trim() }), - ...(data.jobRole && { jobRole: data.jobRole?.trim() }), - ...(data.companySize && { companySize: data.companySize?.trim() }), - ...(data.comment && { comment: data.comment?.trim() }), + ...(data.companyName?.trim() && { companyName: data.companyName?.trim() }), + ...(data.jobRole?.trim() && { jobRole: data.jobRole?.trim() }), + ...(data.companySize?.trim() && { companySize: data.companySize?.trim() }), + ...(data.comment?.trim() && { comment: data.comment?.trim() }), }; } } From e7f96eb8c1acac8854c76685dc95db229e63e0cb Mon Sep 17 00:00:00 2001 From: danyaZh Date: Tue, 26 Sep 2023 18:08:42 +0300 Subject: [PATCH 6/6] feat: added more validation for text fields --- .../contact-modal/contact-modal.components.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/libs/common-docs/src/components/contact-modal/contact-modal.components.ts b/libs/common-docs/src/components/contact-modal/contact-modal.components.ts index 80f9e19be..7e2209797 100644 --- a/libs/common-docs/src/components/contact-modal/contact-modal.components.ts +++ b/libs/common-docs/src/components/contact-modal/contact-modal.components.ts @@ -23,7 +23,8 @@ interface ContactModalForm { } const emailRegex = /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$/; -const textRegex = /^[a-zA-Z0-9\-_ ']+$/; +const textRegex = /^(?! +$)[a-zA-Z0-9\-_ ']+$/; +const commentRegex = /^(?!\s*$).+/; @Component({ // eslint-disable-next-line @angular-eslint/component-selector @@ -72,7 +73,8 @@ export class ContactModalComponent implements OnDestroy { ], comment: [ { type: 'minlength', message: 'Field must be longer than 5 characters' }, - { type: 'maxlength', message: 'Field must be less than 1000 characters' } + { type: 'maxlength', message: 'Field must be less than 1000 characters' }, + { type: 'pattern', message: 'Please enter valid data' } ], hasAcceptedPrivacyPolicy: [ { type: 'required', message: 'This field is required field' }, @@ -202,7 +204,9 @@ export class ContactModalComponent implements OnDestroy { ]), companyServiceName: new FormControl(this._getDefaultCompanyServiceName()), comment: new FormControl('', [ - Validators.maxLength(2000), Validators.minLength(5) + Validators.maxLength(2000), + Validators.minLength(5), + Validators.pattern(commentRegex) ]), hasAcceptedPrivacyPolicy: new FormControl(false, [Validators.required]), hasAcceptedContactAgreement: new FormControl(false, [Validators.required]),