From b5ea0ace714ed30436381275b5bff035d680c8eb Mon Sep 17 00:00:00 2001 From: Pavel Korchak Date: Sun, 22 Oct 2023 19:56:12 +0400 Subject: [PATCH] feat: Add form save button --- package-lock.json | 2 +- package.json | 2 +- proxy.config.json | 4 +- src/app/app-routing.module.ts | 16 ++++-- src/app/model/dto/rq/form-create-rq-dto.ts | 7 +++ src/app/model/dto/rs/form-create-rs-dto.ts | 4 ++ src/app/services/api/forms-http.service.ts | 13 +++++ src/app/services/api/http.service.ts | 20 ++++--- src/app/widgets/auth/auth.component.less | 6 +- src/app/widgets/auth/auth.component.ts | 55 ++++++++++++------- .../form-editor/form-editor.component.html | 11 +++- .../form-editor/form-editor.component.less | 5 ++ .../form-editor/form-editor.component.ts | 38 ++++++++++++- src/assets/outline/save.svg | 1 + 14 files changed, 144 insertions(+), 40 deletions(-) create mode 100644 src/app/model/dto/rq/form-create-rq-dto.ts create mode 100644 src/app/model/dto/rs/form-create-rs-dto.ts create mode 100644 src/app/services/api/forms-http.service.ts create mode 100644 src/assets/outline/save.svg diff --git a/package-lock.json b/package-lock.json index 901088d..dd5a13c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,7 +42,7 @@ "@types/node": "^13.11.1", "@typescript-eslint/eslint-plugin": "^5.59.2", "@typescript-eslint/parser": "^5.59.2", - "cypress": "*", + "cypress": "latest", "eslint": "^8.39.0", "jasmine-core": "~3.6.0", "karma": "~6.4.2", diff --git a/package.json b/package.json index 29ac1b9..a39685d 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "scripts": { "ng": "ng", - "start": "set NODE_OPTIONS=--openssl-legacy-provider && ng serve --proxy-config=proxy.config.json", + "start": "ng serve --proxy-config=proxy.config.json", "build": "ng build --configuration production", "lint": "ng lint", "test": "ng test", diff --git a/proxy.config.json b/proxy.config.json index 407a0b3..0979cc4 100644 --- a/proxy.config.json +++ b/proxy.config.json @@ -1,7 +1,7 @@ { "/api/*": { - "target": " https://03z6w06wwd.execute-api.us-east-1.amazonaws.com/Stage", - "secure": false, + "target": "https://aq4isfk378.execute-api.us-east-1.amazonaws.com/Prod", + "secure": true, "changeOrigin": true, "pathRewrite": { "^/api": "" diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 32dc8ba..1f425f4 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,10 +1,18 @@ import { NgModule } from '@angular/core'; -import { Routes, RouterModule } from '@angular/router'; +import { RouterModule, Routes } from '@angular/router'; +import { AuthComponent } from '@widgets/auth/auth.component'; const routes: Routes = [ - {path: '', pathMatch: 'full', redirectTo: '/create'}, - {path: 'pre-built', loadChildren: () => import('./pages/pre-built-forms/pre-built-forms.module').then(m => m.PreBuiltFormsModule)}, - {path: 'create', loadChildren: () => import('./pages/create-form/create-form.module').then(m => m.CreateFormModule)} + { path: '', pathMatch: 'full', redirectTo: '/create' }, + { path: 'login', component: AuthComponent }, + { + path: 'pre-built', + loadChildren: () => import('./pages/pre-built-forms/pre-built-forms.module').then(m => m.PreBuiltFormsModule) + }, + { + path: 'create', + loadChildren: () => import('./pages/create-form/create-form.module').then(m => m.CreateFormModule) + } ]; @NgModule({ diff --git a/src/app/model/dto/rq/form-create-rq-dto.ts b/src/app/model/dto/rq/form-create-rq-dto.ts new file mode 100644 index 0000000..6eb2ba0 --- /dev/null +++ b/src/app/model/dto/rq/form-create-rq-dto.ts @@ -0,0 +1,7 @@ +import { FormElement } from '@model/form-elements/abstract-form-element'; + +export interface FormCreateRqDto { + name: string; + columnsNum: number; + elements: FormElement[]; +} diff --git a/src/app/model/dto/rs/form-create-rs-dto.ts b/src/app/model/dto/rs/form-create-rs-dto.ts new file mode 100644 index 0000000..ef8389d --- /dev/null +++ b/src/app/model/dto/rs/form-create-rs-dto.ts @@ -0,0 +1,4 @@ +export interface FormCreateRsDto { + uuid?: string; + message: string; +} diff --git a/src/app/services/api/forms-http.service.ts b/src/app/services/api/forms-http.service.ts new file mode 100644 index 0000000..2be4b90 --- /dev/null +++ b/src/app/services/api/forms-http.service.ts @@ -0,0 +1,13 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { HttpService } from './http.service'; +import { FormCreateRqDto } from '@model/dto/rq/form-create-rq-dto'; +import { FormCreateRsDto } from '@model/dto/rs/form-create-rs-dto'; + +@Injectable({ providedIn: 'root' }) +export class FormsHttpService extends HttpService { + + public create(user: FormCreateRqDto): Observable { + return this.post('/api/forms', user) as Observable; + } +} diff --git a/src/app/services/api/http.service.ts b/src/app/services/api/http.service.ts index ea3d709..75399f3 100644 --- a/src/app/services/api/http.service.ts +++ b/src/app/services/api/http.service.ts @@ -4,25 +4,23 @@ import { Observable } from 'rxjs'; import { Router } from '@angular/router'; import { catchError } from 'rxjs/operators'; -@Injectable({providedIn: 'root'}) +@Injectable({ providedIn: 'root' }) export class HttpService { - private headers: HttpHeaders = new HttpHeaders({'Content-Type': 'application/json'}); - constructor(private http: HttpClient, private router: Router) { } public post(url: string, body: unknown, params?: HttpParams): Observable { - return this.handleResponse(this.http.post(url, body, {headers: this.headers, ...params})); + return this.handleResponse(this.http.post(url, body, { headers: this.buildHeaders(), ...params })); } public get(url: string, params?: HttpParams): Observable { - return this.handleResponse(this.http.get(url, {headers: this.headers, ...params})); + return this.handleResponse(this.http.get(url, { headers: this.buildHeaders(), ...params })); } public patch(url: string, body: unknown, params?: HttpParams): Observable { - return this.handleResponse(this.http.patch(url, body, {headers: this.headers, ...params})); + return this.handleResponse(this.http.patch(url, body, { headers: this.buildHeaders(), ...params })); } private handleResponse(response: Observable): Observable { @@ -34,6 +32,14 @@ export class HttpService { this.router.navigateByUrl('/login'); } // TODO Show notification with error text for the user - throw new Error(`${error.statusText}\n${error.message}`); + throw new Error(`${ error.statusText }\n${ error.message }`); + } + + private buildHeaders() { + const headers = {}; + const token = localStorage.getItem('token'); + if (token) headers['Authorization'] = token; + + return headers; } } diff --git a/src/app/widgets/auth/auth.component.less b/src/app/widgets/auth/auth.component.less index 41914f1..6294dd7 100644 --- a/src/app/widgets/auth/auth.component.less +++ b/src/app/widgets/auth/auth.component.less @@ -1,7 +1,9 @@ @import '~app/styles/global/_colors.less'; :host { - position: absolute; + position: fixed; + top: 0; + left: 0; display: flex; align-items: center; z-index: 100; @@ -15,7 +17,7 @@ height: fit-content; width: 360px; padding: 30px; - border-radius: 6px; + border-radius: 6px; border: 1px solid #e8e8e8; box-shadow: 0 3px 3px 3px rgba(0, 0, 0, 0.05); } diff --git a/src/app/widgets/auth/auth.component.ts b/src/app/widgets/auth/auth.component.ts index 7431dca..0c1ae85 100644 --- a/src/app/widgets/auth/auth.component.ts +++ b/src/app/widgets/auth/auth.component.ts @@ -1,8 +1,10 @@ import { SocialAuthService, SocialUser } from '@abacritt/angularx-social-login'; -import { Component, EventEmitter, OnInit, Output } from '@angular/core'; +import { Component, DestroyRef, EventEmitter, inject, OnInit, Output } from '@angular/core'; import { IdentityProvider, RegisterUserRqDto } from '@model/dto/rq/register-user-rq-dto'; import { RegisterUserRsDto } from '@model/dto/rs/register-user-rs-dto'; import { UsersHttpService } from '@services/api/users-http.service'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { ActivatedRoute, Router } from '@angular/router'; @Component({ selector: 'app-auth', @@ -13,9 +15,13 @@ export class AuthComponent implements OnInit { @Output() signedIn = new EventEmitter(); + private destroyRef = inject(DestroyRef); + constructor( private socialAuthService: SocialAuthService, - private usersHttpService: UsersHttpService) { + private usersHttpService: UsersHttpService, + private route: ActivatedRoute, + private router: Router) { } ngOnInit() { @@ -24,27 +30,34 @@ export class AuthComponent implements OnInit { email: ['', Validators.required], password: ['', Validators.required], });*/ - this.socialAuthService.authState.subscribe((user: SocialUser) => { - if (user && user.idToken != localStorage.getItem('token')) { - localStorage.setItem('token', user.idToken); - - this.usersHttpService.register({ - firstName: user.firstName, - lastName: user.lastName, - email: user.email, - photoUrl: user.photoUrl, - provider: user.provider as IdentityProvider - } as RegisterUserRqDto) - .subscribe((rs: RegisterUserRsDto) => { - localStorage.setItem('userId', rs.id || ''); - this.signedIn.emit(); - }); - } - }); + this.socialAuthService.authState + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((user: SocialUser) => { + if (user && user.idToken != localStorage.getItem('token')) { + localStorage.setItem('token', user.idToken); + + this.usersHttpService.register({ + firstName: user.firstName, + lastName: user.lastName, + email: user.email, + photoUrl: user.photoUrl, + provider: user.provider as IdentityProvider + } as RegisterUserRqDto) + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((rs: RegisterUserRsDto) => { + localStorage.setItem('userId', rs.id || ''); + this.signedIn.emit(); + + if (this.route.snapshot.url.toString().includes('login')) { + this.router.navigateByUrl('/'); + } + }); + } + }); } signOut(): void { - this.socialAuthService.signOut(); - localStorage.removeItem('token'); + this.socialAuthService.signOut() + .then(() => localStorage.removeItem('token')); } } diff --git a/src/app/widgets/form-editor/form-editor.component.html b/src/app/widgets/form-editor/form-editor.component.html index 6d44155..a7df21c 100644 --- a/src/app/widgets/form-editor/form-editor.component.html +++ b/src/app/widgets/form-editor/form-editor.component.html @@ -30,12 +30,21 @@ + + diff --git a/src/app/widgets/form-editor/form-editor.component.less b/src/app/widgets/form-editor/form-editor.component.less index 1ca809b..ccaab33 100644 --- a/src/app/widgets/form-editor/form-editor.component.less +++ b/src/app/widgets/form-editor/form-editor.component.less @@ -18,3 +18,8 @@ font-size: 13px; margin-bottom: 6px; } + +.save-btn { + float: right; + font-size: 1rem; +} diff --git a/src/app/widgets/form-editor/form-editor.component.ts b/src/app/widgets/form-editor/form-editor.component.ts index 3691f35..23660f4 100644 --- a/src/app/widgets/form-editor/form-editor.component.ts +++ b/src/app/widgets/form-editor/form-editor.component.ts @@ -2,7 +2,9 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, + DestroyRef, EventEmitter, + inject, Input, OnChanges, OnInit, @@ -17,6 +19,10 @@ import { EditableFormElementComponent, FormElementControls } from '@widgets/form-editor/element/editable-form-element.component'; +import { FormsHttpService } from '@services/api/forms-http.service'; +import { FormCreateRqDto } from '@model/dto/rq/form-create-rq-dto'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { FormElement } from '@model/form-elements/abstract-form-element'; @Component({ selector: 'app-form-editor', @@ -42,8 +48,11 @@ export class FormEditorComponent implements OnInit, OnChanges { columnWidth: string; formElements = new FormArray>([]); + private destroyRef = inject(DestroyRef); + constructor(private fb: FormBuilder, - private cdr: ChangeDetectorRef) { + private cdr: ChangeDetectorRef, + private formsHttpService: FormsHttpService) { } ngOnInit(): void { @@ -92,6 +101,33 @@ export class FormEditorComponent implements OnInit, OnChanges { deleteElement(index: number): void { this.formElements.removeAt(index); } + + save(): void { + this.formsHttpService.create(this.mapFormCreateRqDto()) + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(() => { + // TODO Show notification with save result + }); + } + + private mapFormCreateRqDto(): FormCreateRqDto { + return { + name: this.formName.value, + columnsNum: this.columnsNum.value, + elements: [ + ...this.formElements.controls.map(element => this.mapFormElement(element)) + ] + } as FormCreateRqDto + } + + private mapFormElement(element: FormGroup): FormElement { + return { + label: element.get('label')?.value, + type: element.get('type')?.value, + required: element.get('required')?.value || undefined, + placeholder: element.get('placeholder')?.value || undefined, + } as FormElement; + } } interface FormEditorControls { diff --git a/src/assets/outline/save.svg b/src/assets/outline/save.svg new file mode 100644 index 0000000..bdbb65e --- /dev/null +++ b/src/assets/outline/save.svg @@ -0,0 +1 @@ +