From 8c829ba2b118414c6cb536b6033b6f1faf1a864c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20BRISON?= <ext.sopra.jbrison@grandlyon.com> Date: Mon, 22 Feb 2021 13:52:14 +0100 Subject: [PATCH] Fix: design profile --- src/app/form/form.component.html | 2 + src/app/form/form.component.scss | 5 + src/app/form/form.component.ts | 51 +-- src/app/header/header.component.html | 2 +- src/app/header/header.component.ts | 4 +- src/app/home/home.component.ts | 5 +- src/app/map/components/map.component.scss | 4 +- src/app/models/owner.model.ts | 4 + src/app/models/structureWithOwners.model.ts | 7 + src/app/models/temp-user.model.ts | 2 - src/app/profile/enum/TypeModalProfile.enum.ts | 6 + .../enum/functionTypeModalOptions.enum.ts | 9 + .../modal-options.component.html | 46 +++ .../modal-options.component.scss | 36 ++ .../modal-options.component.spec.ts | 24 ++ .../modal-options/modal-options.component.ts | 21 + src/app/profile/profile.component.html | 363 ++++++++++++++---- src/app/profile/profile.component.scss | 166 ++++++++ src/app/profile/profile.component.ts | 229 +++++++++-- src/app/profile/profile.module.ts | 3 +- src/app/profile/services/profile.service.ts | 5 +- .../reset-password.component.ts | 6 +- src/app/services/structure.service.ts | 13 + .../components/button/button.component.html | 12 +- .../components/button/button.component.scss | 3 +- .../create-account-form.component.spec.ts | 3 +- .../create-account-form.component.ts | 17 +- .../modal-confirmation.component.scss | 2 +- .../signup-modal/signup-modal.component.ts | 12 +- .../structure-type-picker.component.scss | 2 +- .../svg-icon/svg-icon.component.scss | 5 + src/app/shared/enum/regex.enum.ts | 11 - .../modal-filter/modal-filter.component.scss | 2 +- src/app/utils/CustomRegExp.ts | 22 ++ src/assets/form/sprite.svg | 5 + src/assets/ico/more_vert_24px.svg | 3 + src/assets/ico/sprite.svg | 40 +- src/assets/scss/_shapes.scss | 10 +- src/styles.scss | 2 +- 39 files changed, 954 insertions(+), 210 deletions(-) create mode 100644 src/app/models/owner.model.ts create mode 100644 src/app/models/structureWithOwners.model.ts create mode 100644 src/app/profile/enum/TypeModalProfile.enum.ts create mode 100644 src/app/profile/enum/functionTypeModalOptions.enum.ts create mode 100644 src/app/profile/modal-options/modal-options.component.html create mode 100644 src/app/profile/modal-options/modal-options.component.scss create mode 100644 src/app/profile/modal-options/modal-options.component.spec.ts create mode 100644 src/app/profile/modal-options/modal-options.component.ts delete mode 100644 src/app/shared/enum/regex.enum.ts create mode 100644 src/app/utils/CustomRegExp.ts create mode 100644 src/assets/ico/more_vert_24px.svg diff --git a/src/app/form/form.component.html b/src/app/form/form.component.html index c4b68351b..5667595b7 100644 --- a/src/app/form/form.component.html +++ b/src/app/form/form.component.html @@ -164,6 +164,8 @@ (keyup)="verifyUserExist($event.target.value)" formControlName="email" class="form-input" + [readonly]="isAccountMode" + [ngClass]="{ disabled: isAccountMode }" /> <img *ngIf="accountForm.get('email').valid" src="../../assets/form/validate.svg" alt="logo valid" /> <img diff --git a/src/app/form/form.component.scss b/src/app/form/form.component.scss index 398ad100c..fb6d475f8 100644 --- a/src/app/form/form.component.scss +++ b/src/app/form/form.component.scss @@ -5,6 +5,11 @@ @import '../../assets/scss/shapes'; @import '../../assets/scss/z-index'; +.disabled { + opacity: 0.4; + cursor: not-allowed; +} + h3 { margin: 0; } diff --git a/src/app/form/form.component.ts b/src/app/form/form.component.ts index f1dfb68e2..837866f33 100644 --- a/src/app/form/form.component.ts +++ b/src/app/form/form.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { AbstractControl, FormArray, FormControl, FormGroup, Validators } from '@angular/forms'; import { Structure } from '../models/structure.model'; import { Time } from '../models/time.model'; @@ -16,9 +16,9 @@ import { Equipment } from '../structure-list/enum/equipment.enum'; import { ActivatedRoute, Router } from '@angular/router'; import { AuthService } from '../services/auth.service'; import { first } from 'rxjs/operators'; -import { Regex } from '../shared/enum/regex.enum'; import { PageTypeEnum } from './pageType.enum'; import { TempUserService } from '../services/temp-user.service'; +import { CustomRegExp } from '../utils/CustomRegExp'; const { DateTime } = require('luxon'); @Component({ selector: 'app-structureForm', @@ -112,7 +112,7 @@ export class FormComponent implements OnInit { this.route.data.subscribe((data) => { if (data.user) { this.isAccountMode = true; - this.createAccountForm(data.user.email, data.user.name, data.user.surname); + this.createAccountForm(data.user.email); this.linkedStructureId = data.user.pendingStructuresLink; this.setValidationsForm(); this.currentPage = PageTypeEnum.accountInfo; @@ -182,19 +182,16 @@ export class FormComponent implements OnInit { this.setValidationsForm(); } - private createAccountForm(email?: string, name?: string, surname?: string): void { + private createAccountForm(email?: string): void { this.accountForm = new FormGroup( { - email: new FormControl(email ? email : '', [Validators.required, Validators.pattern(Regex.email)]), - name: new FormControl(name ? name : '', [Validators.required, Validators.pattern(Regex.textWithoutNumber)]), - surname: new FormControl(surname ? surname : '', [ - Validators.required, - Validators.pattern(Regex.textWithoutNumber), - ]), - phone: new FormControl('', [Validators.required, Validators.pattern(Regex.phone)]), + email: new FormControl(email ? email : '', [Validators.required, Validators.pattern(CustomRegExp.EMAIL)]), + name: new FormControl('', [Validators.required, Validators.pattern(CustomRegExp.TEXT_WITHOUT_NUMBER)]), + surname: new FormControl('', [Validators.required, Validators.pattern(CustomRegExp.TEXT_WITHOUT_NUMBER)]), + phone: new FormControl('', [Validators.required, Validators.pattern(CustomRegExp.PHONE)]), password: new FormControl('', [ Validators.required, - Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{8,})/), //NOSONAR + Validators.pattern(CustomRegExp.PASSWORD), //NOSONAR ]), confirmPassword: new FormControl(''), }, @@ -215,13 +212,19 @@ export class FormComponent implements OnInit { street: new FormControl(structure.address.street, Validators.required), commune: new FormControl(structure.address.commune, Validators.required), }), - contactMail: new FormControl(structure.contactMail, [Validators.required, Validators.pattern(Regex.email)]), - contactPhone: new FormControl(structure.contactPhone, [Validators.required, Validators.pattern(Regex.phone)]), - website: new FormControl(structure.website, Validators.pattern(Regex.website)), - facebook: new FormControl(structure.facebook, Validators.pattern(Regex.facebook)), - twitter: new FormControl(structure.twitter, Validators.pattern(Regex.twitter)), - instagram: new FormControl(structure.instagram, Validators.pattern(Regex.instagram)), - linkedin: new FormControl(structure.linkedin, Validators.pattern(Regex.linkedIn)), + contactMail: new FormControl(structure.contactMail, [ + Validators.required, + Validators.pattern(CustomRegExp.EMAIL), + ]), + contactPhone: new FormControl(structure.contactPhone, [ + Validators.required, + Validators.pattern(CustomRegExp.PHONE), + ]), + website: new FormControl(structure.website, Validators.pattern(CustomRegExp.WEBSITE)), + facebook: new FormControl(structure.facebook, Validators.pattern(CustomRegExp.FACEBOOK)), + twitter: new FormControl(structure.twitter, Validators.pattern(CustomRegExp.TWITTER)), + instagram: new FormControl(structure.instagram, Validators.pattern(CustomRegExp.INSTAGRAM)), + linkedin: new FormControl(structure.linkedin, Validators.pattern(CustomRegExp.LINKEDIN)), hours: new FormGroup({}), pmrAccess: new FormControl(structure.pmrAccess, Validators.required), exceptionalClosures: new FormControl(structure.exceptionalClosures), @@ -239,23 +242,23 @@ export class FormComponent implements OnInit { digitalCultureSecurity: this.loadArrayForCheckbox(structure.digitalCultureSecurity, false), nbComputers: new FormControl( structure.equipmentsAndServices.includes('ordinateurs') ? structure.nbComputers : 1, - [Validators.required, Validators.pattern(Regex.noNullNumber)] + [Validators.required, Validators.pattern(CustomRegExp.NO_NULL_NUMBER)] ), nbPrinters: new FormControl(structure.equipmentsAndServices.includes('imprimantes') ? structure.nbPrinters : 1, [ Validators.required, - Validators.pattern(Regex.noNullNumber), + Validators.pattern(CustomRegExp.NO_NULL_NUMBER), ]), nbTablets: new FormControl(structure.equipmentsAndServices.includes('tablettes') ? structure.nbTablets : 1, [ Validators.required, - Validators.pattern(Regex.noNullNumber), + Validators.pattern(CustomRegExp.NO_NULL_NUMBER), ]), nbNumericTerminal: new FormControl( structure.equipmentsAndServices.includes('bornesNumeriques') ? structure.nbNumericTerminal : 1, - [Validators.required, Validators.pattern(Regex.noNullNumber)] + [Validators.required, Validators.pattern(CustomRegExp.NO_NULL_NUMBER)] ), nbScanners: new FormControl(structure.equipmentsAndServices.includes('scanners') ? structure.nbScanners : 1, [ Validators.required, - Validators.pattern(Regex.noNullNumber), + Validators.pattern(CustomRegExp.NO_NULL_NUMBER), ]), freeWorkShop: new FormControl(structure.freeWorkShop, Validators.required), }); diff --git a/src/app/header/header.component.html b/src/app/header/header.component.html index 713873855..c26ce85cc 100644 --- a/src/app/header/header.component.html +++ b/src/app/header/header.component.html @@ -32,7 +32,7 @@ </div> <div fxLayout="column" class="right-header" fxLayoutAlign="none baseline" fxLayoutGap="5vw"> <a routerLink="/home" [routerLinkActive]="'active'" (click)="closeMenu()" i18n>Les acteurs</a> - <a *ngIf="isAdmin" routerLink="/admin" [routerLinkActive]="'active'">Administration</a> + <a *ngIf="isAdmin" routerLink="/admin" [routerLinkActive]="'active'" (click)="closeMenu()">Administration</a> </div> </div> <div> diff --git a/src/app/header/header.component.ts b/src/app/header/header.component.ts index ab1858df6..1fff90f72 100644 --- a/src/app/header/header.component.ts +++ b/src/app/header/header.component.ts @@ -45,9 +45,7 @@ export class HeaderComponent implements OnInit { public openMenu(): void { this.showMenu = true; } - - public closeMenu(route: string): void { - this.router.navigateByUrl(route); + public closeMenu(): void { this.showMenu = false; } diff --git a/src/app/home/home.component.ts b/src/app/home/home.component.ts index b2b2b512f..5a803cdb4 100644 --- a/src/app/home/home.component.ts +++ b/src/app/home/home.component.ts @@ -7,7 +7,7 @@ import { StructureService } from '../services/structure.service'; import { Filter } from '../structure-list/models/filter.model'; import { GeoJson } from '../map/models/geojson.model'; import { GeojsonService } from '../services/geojson.service'; -import { ActivatedRoute } from '@angular/router'; +import { CustomRegExp } from '../utils/CustomRegExp'; @Component({ selector: 'app-home', @@ -107,8 +107,7 @@ export class HomeComponent implements OnInit { * @param value string */ private isLocationRequest(value: string): boolean { - const regex = /^\d+\s[A-z]+\s[A-z]+/g; //NOSONAR - if (value.match(regex)) { + if (value.match(CustomRegExp.LOCATION)) { return true; } return false; diff --git a/src/app/map/components/map.component.scss b/src/app/map/components/map.component.scss index de50ff0b1..817150907 100644 --- a/src/app/map/components/map.component.scss +++ b/src/app/map/components/map.component.scss @@ -8,7 +8,7 @@ .map-wrapper { border-radius: 6px; - @include background-hash; + @include background-hash($grey-2); border: 1px solid $grey-4; } @@ -90,7 +90,7 @@ ::ng-deep .leaflet-popup { border-radius: 6px; - @include background-hash; + @include background-hash($grey-2); border: 1px solid $grey-4; padding: 0 0 4px 4px; bottom: -15px !important; diff --git a/src/app/models/owner.model.ts b/src/app/models/owner.model.ts new file mode 100644 index 000000000..fb682b346 --- /dev/null +++ b/src/app/models/owner.model.ts @@ -0,0 +1,4 @@ +export class Owner { + email: string; + id: string; +} diff --git a/src/app/models/structureWithOwners.model.ts b/src/app/models/structureWithOwners.model.ts new file mode 100644 index 000000000..e3d2a6340 --- /dev/null +++ b/src/app/models/structureWithOwners.model.ts @@ -0,0 +1,7 @@ +import { Owner } from './owner.model'; +import { Structure } from './structure.model'; + +export class StructureWithOwners { + structure: Structure; + owners: Owner[]; +} diff --git a/src/app/models/temp-user.model.ts b/src/app/models/temp-user.model.ts index 5a487980f..8540ce74e 100644 --- a/src/app/models/temp-user.model.ts +++ b/src/app/models/temp-user.model.ts @@ -1,7 +1,5 @@ export class TempUser { _id: string; email: string; - name: string; - surname: string; pendingStructuresLink: string[]; } diff --git a/src/app/profile/enum/TypeModalProfile.enum.ts b/src/app/profile/enum/TypeModalProfile.enum.ts new file mode 100644 index 000000000..4608f9bf5 --- /dev/null +++ b/src/app/profile/enum/TypeModalProfile.enum.ts @@ -0,0 +1,6 @@ +export enum TypeModalProfile { + password = 'password', + email = 'email', + deleteAccount = 'deleteAccount', + addAccount = 'addAccount', +} diff --git a/src/app/profile/enum/functionTypeModalOptions.enum.ts b/src/app/profile/enum/functionTypeModalOptions.enum.ts new file mode 100644 index 000000000..2ec4677af --- /dev/null +++ b/src/app/profile/enum/functionTypeModalOptions.enum.ts @@ -0,0 +1,9 @@ +export enum FunctionTypeModalOptions { + changeEmail = 1, + changePassword, + deleteAccount, + addUser, + removeUser, + editStructure, + removeStructure, +} diff --git a/src/app/profile/modal-options/modal-options.component.html b/src/app/profile/modal-options/modal-options.component.html new file mode 100644 index 000000000..6d8a62025 --- /dev/null +++ b/src/app/profile/modal-options/modal-options.component.html @@ -0,0 +1,46 @@ +<div class="modalOptions" (clickOutside)="closeModal(0)"> + <div *ngIf="isModalProfileOpts" class="modalContent" fxLayout="column" fxLayoutGap="10px"> + <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="9px"> + <app-svg-icon [type]="'ico'" [iconColor]="inherit" [icon]="'email'"></app-svg-icon> + <p (click)="closeModal(functionType.changeEmail)">Changer mon courriel</p> + </div> + <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="9px"> + <app-svg-icon [type]="'ico'" [iconColor]="inherit" [icon]="'password'"></app-svg-icon> + <p (click)="closeModal(functionType.changePassword)">Changer mon mot de passe</p> + </div> + <div fxLayout="row" class="deleteItem" fxLayoutAlign="start center" fxLayoutGap="9px"> + <app-svg-icon [type]="'ico'" [iconColor]="inherit" [icon]="'cancel'"></app-svg-icon> + <p (click)="closeModal(functionType.deleteAccount)">Supprimer mon compte</p> + </div> + </div> + <div *ngIf="!isModalProfileOpts" class="modalContent" fxLayout="column" fxLayoutGap="10px"> + <div (click)="closeModal(functionType.addUser)" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="9px"> + <app-svg-icon [type]="'ico'" [iconColor]="'inherit'" [icon]="'add'"></app-svg-icon> + <p>Ajouter un compte</p> + </div> + <div + (click)="hasOwners ? closeModal(functionType.removeUser) : null" + fxLayout="row" + fxLayoutAlign="start center" + fxLayoutGap="9px" + [ngClass]="{ invalid: !hasOwners }" + > + <app-svg-icon [type]="'ico'" [iconColor]="'inherit'" [icon]="'remove'"></app-svg-icon> + <p>Supprimer un compte</p> + </div> + <div (click)="closeModal(functionType.editStructure)" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="9px"> + <app-svg-icon [type]="'ico'" [iconColor]="'inherit'" [icon]="'edit'"></app-svg-icon> + <p>Modifier la structure</p> + </div> + <div + (click)="closeModal(functionType.removeStructure)" + fxLayout="row" + class="deleteItem" + fxLayoutAlign="start center" + fxLayoutGap="9px" + > + <app-svg-icon [type]="'ico'" [iconColor]="'inherit'" [icon]="'cancel'"></app-svg-icon> + <p>Supprimer la structure</p> + </div> + </div> +</div> diff --git a/src/app/profile/modal-options/modal-options.component.scss b/src/app/profile/modal-options/modal-options.component.scss new file mode 100644 index 000000000..907e3f944 --- /dev/null +++ b/src/app/profile/modal-options/modal-options.component.scss @@ -0,0 +1,36 @@ +@import '../../../assets/scss/color'; +@import '../../../assets/scss/typography'; +@import '../../../assets/scss/shapes'; +@import '../../../assets/scss/z-index'; + +.modalOptions { + width: 300px; + background-color: $white; + position: absolute; + z-index: $modal-z-index; + list-style-type: none; + @include background-hash($secondary-color); + border: 1px solid $secondary-color; + border-radius: 6px; + .modalContent { + background: $white; + border-radius: 6px; + padding: 25px 22px 18px 22px; + div { + cursor: pointer; + &.invalid { + opacity: 0.4; + cursor: default; + } + } + p { + @include cn-bold-16; + white-space: nowrap; + margin: 0; + } + .deleteItem { + color: $red-default; + fill: $red-default; + } + } +} diff --git a/src/app/profile/modal-options/modal-options.component.spec.ts b/src/app/profile/modal-options/modal-options.component.spec.ts new file mode 100644 index 000000000..b1b9a866a --- /dev/null +++ b/src/app/profile/modal-options/modal-options.component.spec.ts @@ -0,0 +1,24 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ModalOptionsComponent } from './modal-options.component'; + +describe('ModalConfirmationComponent', () => { + let component: ModalOptionsComponent; + let fixture: ComponentFixture<ModalOptionsComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ModalOptionsComponent], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ModalOptionsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/profile/modal-options/modal-options.component.ts b/src/app/profile/modal-options/modal-options.component.ts new file mode 100644 index 000000000..e9eb32e28 --- /dev/null +++ b/src/app/profile/modal-options/modal-options.component.ts @@ -0,0 +1,21 @@ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { FunctionTypeModalOptions } from '../enum/functionTypeModalOptions.enum'; + +@Component({ + selector: 'app-modal-options', + templateUrl: './modal-options.component.html', + styleUrls: ['./modal-options.component.scss'], +}) +export class ModalOptionsComponent implements OnInit { + functionType = FunctionTypeModalOptions; + + constructor() {} + @Input() isModalProfileOpts = false; + @Input() hasOwners = true; + @Output() closed = new EventEmitter<number>(); + ngOnInit(): void {} + + public closeModal(value: number): void { + this.closed.emit(value); + } +} diff --git a/src/app/profile/profile.component.html b/src/app/profile/profile.component.html index a26b3822a..3ef2c4743 100644 --- a/src/app/profile/profile.component.html +++ b/src/app/profile/profile.component.html @@ -1,100 +1,311 @@ -<div fxLayout="column" class="content-container full-screen"> - <div class="section-container" fxLayout="column" fxLayoutAlign="center center"> - <h1>Profil</h1> +<div fxLayout="column" class="content-container full-screen" *ngIf="userProfile"> + <div class="profileSection"> + <div class="section-container" fxLayout="row"> + <svg class="cameraProfile" aria-hidden="true"> + <use [attr.xlink:href]="'assets/ico/sprite.svg#camera'"></use> + </svg> + <div class="profileInformation" fxLayoutGap="18px" fxLayout="column"> + <div fxLayout="row" fxLayoutAlign="space-between center"> + <p class="profileName">{{ userProfile.surname | titlecase }} {{ userProfile.name | titlecase }}</p> + <button class="btn-primary" (click)="logout()">Déconnexion</button> + </div> + <div class="profileEmail" fxLayout="column"> + <span>Identifiant</span> + <div fxLayout="row" fxLayoutAlign="space-between center"> + <p>{{ userProfile.email }}</p> + <nav aria-label="modalOption"> + <ul> + <li> + <button + [ngClass]="{ active: isModalOptsProfile }" + (click)="openModalOptsProfile()" + class="btn-primary transparent" + > + <app-svg-icon [type]="'ico'" [iconColor]="inherit" [icon]="'moreOpts'"></app-svg-icon> + </button> + <ul *ngIf="isModalOptsProfile" class="dropdown"> + <app-modal-options + [isModalProfileOpts]="true" + [hasOwners]="false" + (closed)="closeModalOpts($event)" + ></app-modal-options> + </ul> + </li> + </ul> + </nav> + </div> + </div> + </div> + </div> + </div> + <div class="structureSection"> + <div class="section-container" fxLayoutGap="18px" fxLayout="column"> + <ng-container *ngIf="userProfile.structuresLink.length > 0 && structures"> + <div class="structureCard" *ngFor="let s of structures; let i = index"> + <div class="structureInfo" fxLayout="column" fxLayoutGap="14px"> + <div fxLayout="row" fxLayoutAlign="space-between start" fxLayoutGap="20px"> + <a class="structureName" routerLink="/home" [state]="{ data: s.structure }">{{ + s.structure.structureName + }}</a> + <nav aria-label="modalOption"> + <ul> + <li> + <button + [ngClass]="{ active: modalOptsStructureIndex == i }" + (click)="openModalOptsStructure(i, s)" + class="btn-primary transparent" + > + <app-svg-icon [type]="'ico'" [iconColor]="inherit" [icon]="'moreOpts'"></app-svg-icon> + </button> + <ul *ngIf="modalOptsStructureIndex == i" class="dropdown"> + <app-modal-options + [isModalProfileOpts]="false" + [hasOwners]="currentStructureOwners.owners.length > 0" + (closed)="closeModalOpts($event)" + ></app-modal-options> + </ul> + </li> + </ul> + </nav> + </div> + <div fxLayout="column" fxLayoutGap="14px"> + <p class="ownerName" *ngFor="let owner of s.owners">{{ owner.email }}</p> + </div> + </div> + </div> + </ng-container> + + <div class="addSection" fxLayout="row" fxLayoutAlign="center center"> + <app-button + class="hide-on-print" + [type]="'button'" + [style]="'buttonWithHash'" + [text]="'Ajouter une structure'" + [iconBtn]="'add'" + (action)="addStructure()" + ></app-button> + </div> + </div> + </div> + + <div> <div *ngIf="userProfile" fxLayout="column" fxLayoutAlign="center" fxLayoutGap="10px"> - <p>Id: {{ userProfile._id }}</p> - <p>Email: {{ userProfile.email }}</p> - <button (click)="logout()">Se déconnecter</button> - <p fxLayout="column" *ngIf="userProfile.structuresLink.length > 0"> - Mes structures : - <span *ngFor="let structureId of userProfile.structuresLink"> - <strong>{{ structureId }}</strong> - </span> - </p> <p fxLayout="column" *ngIf="userProfile.pendingStructuresLink.length > 0"> Mes structures en attente de validation: <span *ngFor="let structureId of userProfile.pendingStructuresLink"> <strong>{{ structureId }}</strong> </span> </p> - <button (click)="toogleAddStructure()">Ajouter une structure</button> - <button (click)="toogleChangeEmail()">Changer d'email</button> - <form - *ngIf="changeEmail" - fxLayout="column" - fxLayoutGap="10px" - [formGroup]="formEmail" - (ngSubmit)="onSubmitEmail()" - > - <div class="form-group"> - <label for="email">Email</label> - <input type="email" autocomplete="on" formControlName="email" class="form-control" /> - <div *ngIf="submitted" class="invalid-feedback"> - <app-validator-form [control]="formEmail.controls.email"></app-validator-form> - </div> - </div> - <div class="form-group"> - <button type="submit" [disabled]="loading" class="btn btn-primary">Appliquer</button> + </div> + </div> +</div> +<div *ngIf="editModal" class="modalBackground"> + <div class="modal" (clickOutside)="closeModalOptsProfile()"> + <form + *ngIf="editModal == typeModalProfile.password" + [formGroup]="formPassword" + class="contentModal" + fxLayout="column" + fxLayoutAlign="center start" + fxLayoutGap="20px" + > + <div fxLayout="row" class="headerModal" fxLayoutAlign="space-between center"> + <h2>Changer de mot de passe</h2> + <div class="ico-close-details" (click)="closeModalOptsProfile()"></div> + </div> + <div class="form-group" fxLayout="column" fxLayoutGap="4px"> + <label for="oldPassword">Ancien mot de passe</label> + <p *ngIf="passwordError" class="special invalid">Votre ancien mot de passe est incorrect.</p> + <div fxLayout="row" fxLayoutGap="13px"> + <input + [type]="isShowOldPassword ? 'text' : 'password'" + formControlName="oldPassword" + class="form-input password" + autocomplete="on" + /> + <app-svg-icon + (click)="showOldPassword()" + [type]="'form'" + [iconClass]="'grey'" + [icon]="'eyePassword'" + ></app-svg-icon> + <app-svg-icon *ngIf="passwordError" [type]="'form'" [icon]="'notValidate'"></app-svg-icon> </div> - </form> - <button (click)="toogleChangePassword()">Changer de mot de passe</button> - - <form *ngIf="changePassword" fxLayout="column" fxLayoutGap="10px" [formGroup]="form" (ngSubmit)="onSubmit()"> - <div class="form-group"> - <label for="oldPassword">Ancien mot de passe</label> + </div> + <div class="form-group" fxLayout="column"> + <label for="password">Nouveau mot de passe</label> + <p class="special" [ngClass]="{ invalid: fpass.password.invalid && fpass.password.value }"> + Le mot de passe doit contenir au minimum : 8 caractères dont un caractère spécial, un caractère en majuscule + et un chiffre. + </p> + <div fxLayout="row" fxLayoutGap="13px"> <input - type="password" + [type]="isShowPassword ? 'text' : 'password'" + formControlName="password" + class="form-input password" autocomplete="on" - formControlName="oldPassword" - class="form-control" - [ngClass]="{ 'is-invalid': submitted && f.oldPassword.errors }" /> - <div *ngIf="submitted && f.oldPassword.errors" class="invalid-feedback"> - <div *ngIf="f.oldPassword.errors.required">L'Ancien mot de passe est obligatoire</div> - </div> + <app-svg-icon + (click)="showPassword()" + [type]="'form'" + [iconClass]="'grey'" + [icon]="'eyePassword'" + ></app-svg-icon> + <app-svg-icon *ngIf="fpass.password.valid" [type]="'form'" [icon]="'validate'"></app-svg-icon> + <app-svg-icon + *ngIf="fpass.password.invalid && fpass.password.value" + [type]="'form'" + [icon]="'notValidate'" + ></app-svg-icon> </div> - <div class="form-group"> - <label for="password">Mot de passe</label> + </div> + <div class="form-group" fxLayout="column"> + <label for="confirmPassword">Confirmation du mot de passe</label> + <div fxLayout="row" fxLayoutGap="13px"> <input - type="password" + [type]="isShowConfirmPassword ? 'text' : 'password'" + formControlName="confirmPassword" + class="form-input password" autocomplete="on" - formControlName="password" - class="form-control" - [ngClass]="{ 'is-invalid': submitted && f.password.errors }" /> - <div *ngIf="submitted && f.password.errors" class="invalid-feedback"> - <div *ngIf="f.password.errors.required">Le mot de passe est obligatoire</div> - <div *ngIf="f.password.errors.pattern"> - Le mot de passe doit avoir au minimun 8 caractères, une majuscule, une minuscule, un chiffre et un - caractère spécial. - </div> - </div> + <app-svg-icon + (click)="showConfirmPassword()" + [type]="'form'" + [iconClass]="'grey'" + [icon]="'eyePassword'" + ></app-svg-icon> + <app-svg-icon + *ngIf="fpass.confirmPassword.valid && fpass.password.value" + [type]="'form'" + [icon]="'validate'" + ></app-svg-icon> + <app-svg-icon + *ngIf="fpass.confirmPassword.invalid && fpass.confirmPassword.value" + [type]="'form'" + [icon]="'notValidate'" + ></app-svg-icon> </div> - <div class="form-group"> - <label for="confirmPassword">Confirmation du mot de passe</label> + </div> + <div class="footerModal" fxLayout="row" fxLayoutAlign="center center"> + <button + type="submit" + [ngClass]="{ invalid: formPassword.invalid }" + class="btn-primary small leave" + (click)="submitPassword()" + > + Valider + </button> + </div> + </form> + <form + *ngIf="editModal == typeModalProfile.email" + [formGroup]="formEmail" + class="contentModal" + fxLayout="column" + fxLayoutAlign="center start" + > + <div fxLayout="row" class="headerModal" fxLayoutAlign="space-between center"> + <h2>Changer de courriel</h2> + <div class="ico-close-details" (click)="closeModalOptsProfile()"></div> + </div> + <div class="form-group" fxLayout="column"> + <label for="email">Nouveau courriel</label> + <p class="special invalid" *ngIf="this.fmail.email.hasError('alreadyExist')">L'email est déja utilisé.</p> + <div fxLayout="row" fxLayoutGap="13px"> <input - type="password" + type="text" + formControlName="email" + class="form-input" autocomplete="on" - formControlName="confirmPassword" - class="form-control" - [ngClass]="{ 'is-invalid': submitted && f.confirmPassword.errors }" + (keyup)="verifyEmailAlreadyUsed($event.target.value, this.fmail.email)" /> - <div *ngIf="submitted && f.confirmPassword.errors" class="invalid-feedback"> - <div *ngIf="f.confirmPassword.errors.required">La confirmation du mot de passe est obligatoire</div> - <div *ngIf="f.confirmPassword.errors.mustMatch">Les mot de passe ne sont pas les mêmes</div> - </div> + <app-svg-icon *ngIf="fmail.email.valid" [type]="'form'" [icon]="'validate'"></app-svg-icon> + <app-svg-icon + *ngIf="fmail.email.invalid && fmail.email.value" + [type]="'form'" + [icon]="'notValidate'" + ></app-svg-icon> </div> - - <div class="form-group"> - <button type="submit" [disabled]="loading" class="btn btn-primary">Appliquer</button> + </div> + <div class="footerModal" fxLayout="row" fxLayoutAlign="center center"> + <button + type="submit" + [ngClass]="{ invalid: formEmail.invalid }" + class="btn-primary small leave" + (click)="submitEmail()" + > + Valider + </button> + </div> + </form> + <div + *ngIf="editModal == typeModalProfile.deleteAccount" + class="contentModal" + fxLayout="column" + fxLayoutAlign="center start" + fxLayoutGap="30px" + > + <div fxLayout="row" class="headerModal" fxLayoutAlign="space-between center"> + <h2>Supprimer un compte</h2> + <div class="ico-close-details" (click)="closeModalOptsProfile()"></div> + </div> + <div fxLayout="column" fxLayoutGap="16px"> + <div class="row removeOwner" *ngFor="let owner of currentStructureOwners.owners" fxLayoutGap="16px"> + <button class="btn-primary small" (click)="removeOwner(owner.id)">X</button> + <span> + {{ owner.email }} + </span> </div> - </form> + </div> + <div class="footerModal" fxLayout="row" fxLayoutAlign="center center"> + <button type="button" class="btn-primary small leave" (click)="closeModalOptsProfile()">Terminer</button> + </div> </div> + <form + *ngIf="editModal == typeModalProfile.addAccount" + [formGroup]="formAddAccount" + class="contentModal" + fxLayout="column" + fxLayoutAlign="center start" + fxLayoutGap="30px" + > + <div fxLayout="row" class="headerModal" fxLayoutAlign="space-between center"> + <h2>Ajouter un compte</h2> + <div class="ico-close-details" (click)="closeModalOptsProfile()"></div> + </div> + <div class="form-group" fxLayout="column"> + <label for="email">Courriel du compte à ajouter</label> + <p *ngIf="ownerAlreadyLinked" class="special invalid">L'email est déjà rattaché à la structure.</p> + <div fxLayout="row" fxLayoutGap="13px"> + <input type="text" formControlName="email" class="form-input" autocomplete="on" /> + <app-svg-icon *ngIf="fAddAccount.email.valid" [type]="'form'" [icon]="'validate'"></app-svg-icon> + <app-svg-icon + *ngIf="fAddAccount.email.invalid && fAddAccount.email.value" + [type]="'form'" + [icon]="'notValidate'" + ></app-svg-icon> + </div> + </div> + <div class="footerModal" fxLayout="row" fxLayoutAlign="center center"> + <button + type="submit" + [ngClass]="{ invalid: formAddAccount.invalid }" + class="btn-primary small leave" + (click)="addOwner()" + > + Envoyer + </button> + </div> + </form> </div> - <app-structureForm - *ngIf="addStructure" - [profile]="userProfile" - (closeEvent)="toogleAddStructure($event)" - (clickOutside)="toogleAddStructure()" - ></app-structureForm> </div> +<app-modal-confirmation + [openned]="deleteModalStructureOpenned" + [content]="'Voulez-vous vraiment supprimer cette structure ?'" + (closed)="deleteStructure($event)" +></app-modal-confirmation> +<app-modal-confirmation + [openned]="deleteModalAccountOpenned" + [content]="'Voulez-vous vraiment supprimer votre compte ?'" + (closed)="deleteAccount($event)" +></app-modal-confirmation> diff --git a/src/app/profile/profile.component.scss b/src/app/profile/profile.component.scss index e69de29bb..c7b151d23 100644 --- a/src/app/profile/profile.component.scss +++ b/src/app/profile/profile.component.scss @@ -0,0 +1,166 @@ +@import '../../assets/scss/color'; +@import '../../assets/scss/typography'; +@import '../../assets/scss/hyperlink'; +@import '../../assets/scss/shapes'; +@import '../../assets/scss/breakpoint'; + +.content-container { + padding-top: 0; +} +.section-container { + width: 50%; + padding: 20px 0; + .profileInformation { + width: 100%; + } + .cameraProfile { + width: 128px; + margin-right: 16px; + height: 113px; + } + @media #{$tablet} { + .cameraProfile { + display: none; + } + width: 70%; + } + @media #{$large-phone} { + width: 90%; + } +} +.profileSection { + background: $white; + border-bottom: 1px solid $grey-4; + .profileName { + @include cn-bold-24; + margin: 0 !important; + } + .profileEmail { + span { + @include cn-regular-16; + color: $grey-2; + } + p { + margin: 0; + @include cn-regular-18; + } + } +} +button { + &.transparent { + background: none; + border: 1px solid $grey-4; + border-radius: 6px; + } +} +.structureSection { + margin-bottom: 108px; + .structureCard { + border: 1px solid $grey-4; + border-radius: 6px; + .structureInfo { + border-radius: 6px; + background: $white; + min-height: 100px; + padding: 33px 55px; + @media #{$large-phone} { + padding: 33px 25px; + } + a { + margin: 0; + &.structureName { + @include cn-bold-24; + color: $secondary-color; + text-decoration: underline; + } + } + .ownerName { + @include cn-regular-18; + color: $grey-2; + } + } + @include background-hash($grey-2); + } +} +.addSection { + button { + background: $red-default; + color: $white; + } +} +.contentModal { + padding: 35px 34px 18px 54px !important; + .headerModal { + width: 100%; + } + p { + &.special { + margin: 8px 0; + @include cn-regular-14; + color: $grey-3; + &.invalid { + color: $orange-warning; + } + } + } + .removeOwner { + button { + width: 40px; + background-color: $red-default; + } + span { + @include cn-bold-18; + } + } + button { + &.invalid { + opacity: 0.4; + } + } + .form-group { + width: 100%; + padding-right: 40px; + input { + width: 100%; + } + } + .ico-close-details { + min-width: 40px; + } +} + +ul { + list-style: none; + margin: 0; + padding-left: 0; +} + +li { + fill: $secondary-color; + color: $black; + display: block; + float: left; + position: relative; + text-decoration: none; + button { + width: 40px; + fill: $secondary-color; + &.active { + background-color: $secondary-color; + fill: $white; + border-color: $secondary-color; + } + &:hover { + background-color: $secondary-color; + fill: $white; + border-color: $secondary-color; + } + } +} + +ul li ul { + position: absolute; + display: block; + margin-left: -268px; + margin-top: 7px; +} diff --git a/src/app/profile/profile.component.ts b/src/app/profile/profile.component.ts index 5f3b49a73..67cb92574 100644 --- a/src/app/profile/profile.component.ts +++ b/src/app/profile/profile.component.ts @@ -1,8 +1,16 @@ import { Component, OnInit } from '@angular/core'; -import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { AbstractControl, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; +import { Router } from '@angular/router'; +import { Structure } from '../models/structure.model'; +import { StructureWithOwners } from '../models/structureWithOwners.model'; +import { TempUser } from '../models/temp-user.model'; import { User } from '../models/user.model'; import { AuthService } from '../services/auth.service'; +import { StructureService } from '../services/structure.service'; import { MustMatch } from '../shared/validator/form'; +import { CustomRegExp } from '../utils/CustomRegExp'; +import { FunctionTypeModalOptions } from './enum/functionTypeModalOptions.enum'; +import { TypeModalProfile } from './enum/TypeModalProfile.enum'; import { ProfileService } from './services/profile.service'; @Component({ @@ -11,73 +19,136 @@ import { ProfileService } from './services/profile.service'; styleUrls: ['./profile.component.scss'], }) export class ProfileComponent implements OnInit { - public form: FormGroup; - public userProfile: User; - public submitted = false; + // Password profile + public formPassword: FormGroup; + public isShowOldPassword = false; + public isShowPassword = false; + public isShowConfirmPassword = false; public changePassword = false; - public loading = false; - public changeEmail = false; + public passwordError = false; + + // Email profile public formEmail: FormGroup; - public addStructure = false; + public changeEmail = false; + + // formAddAccount + public formAddAccount: FormGroup; + public ownerAlreadyLinked = false; + + // Global var + public userProfile: User; + public loading = false; + public structures: StructureWithOwners[] = []; + public editModal: TypeModalProfile; + public typeModalProfile = TypeModalProfile; + + // Modal options + public modalOptsStructureIndex: number; + public isModalOptsProfile = false; + public currentStructureOwners: StructureWithOwners; + public deleteModalStructureOpenned = false; + public deleteModalAccountOpenned = false; constructor( private authService: AuthService, private formBuilder: FormBuilder, - private profileService: ProfileService + private profileService: ProfileService, + private structureService: StructureService, + private router: Router ) {} ngOnInit(): void { this.profileService.getProfile().then((profile) => { this.userProfile = profile; + this.structures = []; + profile.structuresLink.forEach((structureId) => { + this.structureService.getStructureWithOwners(structureId, profile).subscribe((s) => { + this.structures.push(s); + }); + }); }); this.initForm(); } - public initForm(): void { - this.form = this.formBuilder.group( + this.formPassword = this.formBuilder.group( { - oldPassword: [ - '', - [Validators.required, Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{8,})/)], //NOSONAR - ], - password: [ - '', - [Validators.required, Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{8,})/)], //NOSONAR - ], + oldPassword: ['', [Validators.required, Validators.pattern(CustomRegExp.PASSWORD)]], + password: ['', [Validators.required, Validators.pattern(CustomRegExp.PASSWORD)]], confirmPassword: [''], }, { validator: MustMatch('password', 'confirmPassword') } ); + this.formEmail = this.formBuilder.group({ - email: ['', [Validators.required, Validators.pattern('[a-z0-9._%+-]+@[a-z0-9.-]+.[a-z]{2,3}$')]], + email: ['', [Validators.required, Validators.pattern(CustomRegExp.EMAIL)]], }); - } + this.formAddAccount = this.formBuilder.group({ + email: ['', [Validators.required, Validators.pattern(CustomRegExp.EMAIL)]], + }); + } // getter for form fields - get f(): { [key: string]: AbstractControl } { - return this.form.controls; + get fpass(): { [key: string]: AbstractControl } { + return this.formPassword.controls; } - public toogleChangePassword(): void { - this.changePassword = !this.changePassword; + // getter for form fields + get fmail(): { [key: string]: AbstractControl } { + return this.formEmail.controls; } - public toogleAddStructure(): void { - this.addStructure = !this.addStructure; + + get fAddAccount(): { [key: string]: AbstractControl } { + return this.formAddAccount.controls; } - public toogleChangeEmail(): void { - this.changeEmail = !this.changeEmail; + public closeModalOpts(functionType: number): void { + switch (functionType) { + case FunctionTypeModalOptions.changeEmail: + this.editModal = TypeModalProfile.email; + break; + case FunctionTypeModalOptions.changePassword: + this.editModal = TypeModalProfile.password; + break; + case FunctionTypeModalOptions.deleteAccount: + this.toggleDeleteAccountModal(); + break; + case FunctionTypeModalOptions.addUser: + this.editModal = TypeModalProfile.addAccount; + this.ownerAlreadyLinked = false; + break; + case FunctionTypeModalOptions.removeUser: + this.editModal = TypeModalProfile.deleteAccount; + break; + case FunctionTypeModalOptions.editStructure: + this.router.navigateByUrl('/create-structure', { state: { data: this.currentStructureOwners.structure } }); + break; + case FunctionTypeModalOptions.removeStructure: + this.toggleDeleteStructureModal(); + break; + default: + break; + } + this.isModalOptsProfile = false; + this.modalOptsStructureIndex = null; } - public onSubmitEmail(): void { - this.submitted = true; + // Profile Section + public closeModalOptsProfile(): void { + this.editModal = null; + this.formAddAccount.reset(); + this.formEmail.reset(); + this.formPassword.reset(); + } + public submitEmail(): void { + // stop here if form is invalid if (this.formEmail.invalid) { return; } this.loading = true; this.profileService.changeEmail(this.formEmail.value.email, this.userProfile.email).subscribe( () => { - this.toogleChangeEmail(); + this.closeModalOptsProfile(); + this.formEmail.reset(); this.loading = false; }, (err) => { @@ -85,25 +156,109 @@ export class ProfileComponent implements OnInit { } ); } - public onSubmit(): void { - this.submitted = true; + public submitPassword(): void { // stop here if form is invalid - if (this.form.invalid) { + if (this.formPassword.invalid) { return; } this.loading = true; - this.profileService.changePassword(this.form.value.password, this.form.value.oldPassword).subscribe( + this.profileService.changePassword(this.formPassword.value.password, this.formPassword.value.oldPassword).subscribe( () => { - this.toogleChangePassword(); + this.closeModalOptsProfile(); + this.formPassword.reset(); + this.loading = false; + this.passwordError = false; }, (error) => { + this.passwordError = true; this.loading = false; } ); } - + public openModalOptsProfile(): void { + this.isModalOptsProfile = true; + } + public showOldPassword(): void { + this.isShowOldPassword = !this.isShowOldPassword; + } + public showPassword(): void { + this.isShowPassword = !this.isShowPassword; + } + public showConfirmPassword(): void { + this.isShowConfirmPassword = !this.isShowConfirmPassword; + } public logout(): void { this.authService.logout(); } + public deleteAccount(shouldDelete: boolean): void { + this.toggleDeleteAccountModal(); + if (shouldDelete) { + this.profileService.deleteProfile().subscribe(() => { + this.logout(); + }); + } + } + + // Structure section + public openModalOptsStructure(index: number, s: StructureWithOwners): void { + this.modalOptsStructureIndex = index; + this.currentStructureOwners = s; + } + public addStructure(): void { + this.router.navigateByUrl('/create-structure'); + } + private toggleDeleteStructureModal(): void { + this.deleteModalStructureOpenned = !this.deleteModalStructureOpenned; + } + private toggleDeleteAccountModal(): void { + this.deleteModalAccountOpenned = !this.deleteModalAccountOpenned; + } + + public deleteStructure(shouldDelete: boolean): void { + this.toggleDeleteStructureModal(); + if (shouldDelete) { + this.structureService.delete(this.currentStructureOwners.structure._id).subscribe((structure: Structure) => { + this.ngOnInit(); + }); + } + } + + public verifyEmailAlreadyUsed(inputEmail, formControl: FormControl): void { + if (formControl.valid) { + this.profileService.isEmailAlreadyUsed(inputEmail).subscribe((isExist) => { + if (isExist) { + formControl.setErrors({ alreadyExist: true }); + } + }); + } + } + public removeOwner(owner: string): void { + this.structureService.removeOwnerFromStructure(owner, this.currentStructureOwners.structure._id).subscribe(() => { + this.currentStructureOwners.owners = this.currentStructureOwners.owners.filter((o) => o.id !== owner); + if (this.currentStructureOwners.owners.length == 0) { + this.closeModalOptsProfile(); + } + }); + } + public addOwner(): void { + // stop here if form is invalid + if (this.formAddAccount.invalid) { + return; + } + this.loading = true; + const user = new TempUser(); + user.email = this.fAddAccount.email.value; + this.structureService.addOwnerToStructure(user, this.currentStructureOwners.structure._id).subscribe( + () => { + this.closeModalOptsProfile(); + this.formAddAccount.reset(); + this.loading = false; + }, + (err) => { + this.ownerAlreadyLinked = true; + this.loading = false; + } + ); + } } diff --git a/src/app/profile/profile.module.ts b/src/app/profile/profile.module.ts index a9427003f..adf5fb74c 100644 --- a/src/app/profile/profile.module.ts +++ b/src/app/profile/profile.module.ts @@ -3,10 +3,11 @@ import { ProfileComponent } from './profile.component'; import { SharedModule } from '../shared/shared.module'; import { CommonModule } from '@angular/common'; import { BrowserModule } from '@angular/platform-browser'; +import { ModalOptionsComponent } from './modal-options/modal-options.component'; @NgModule({ imports: [CommonModule, BrowserModule, SharedModule], - declarations: [ProfileComponent], + declarations: [ProfileComponent, ModalOptionsComponent], exports: [ProfileComponent], }) export class ProfileModule {} diff --git a/src/app/profile/services/profile.service.ts b/src/app/profile/services/profile.service.ts index 849d148b4..6b7515fd4 100644 --- a/src/app/profile/services/profile.service.ts +++ b/src/app/profile/services/profile.service.ts @@ -15,7 +15,7 @@ export class ProfileService { constructor(private http: HttpClient, private authService: AuthService) {} public async getProfile(): Promise<User> { - if (this.authService.isLoggedIn() && !this.currentProfile) { + if (this.authService.isLoggedIn()) { const profile = await this.http.get<User>(`${this.baseUrl}/profile`).toPromise(); this.currentProfile = profile; } @@ -25,6 +25,9 @@ export class ProfileService { public setProfile(profile: User): void { this.currentProfile = profile; } + public deleteProfile(): Observable<User> { + return this.http.delete<User>(`${this.baseUrl}`); + } public isLinkedToStructure(idStructure: string): boolean { if (!this.currentProfile) { diff --git a/src/app/reset-password/reset-password.component.ts b/src/app/reset-password/reset-password.component.ts index 3f3187b99..153f2dc8c 100644 --- a/src/app/reset-password/reset-password.component.ts +++ b/src/app/reset-password/reset-password.component.ts @@ -3,6 +3,7 @@ import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/fo import { ActivatedRoute, Router } from '@angular/router'; import { AuthService } from '../services/auth.service'; import { MustMatch } from '../shared/validator/form'; +import { CustomRegExp } from '../utils/CustomRegExp'; @Component({ selector: 'app-reset-password', @@ -36,10 +37,7 @@ export class ResetPasswordComponent implements OnInit { private initPasswordForm(): void { this.resetFormChangePassword = this.formBuilder.group( { - password: [ - '', - [Validators.required, Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{8,})/)], //NOSONAR - ], + password: ['', [Validators.required, Validators.pattern(CustomRegExp.PASSWORD)]], confirmPassword: [''], }, { validator: MustMatch('password', 'confirmPassword') } diff --git a/src/app/services/structure.service.ts b/src/app/services/structure.service.ts index 7848ab3c4..f65757bff 100644 --- a/src/app/services/structure.service.ts +++ b/src/app/services/structure.service.ts @@ -12,6 +12,9 @@ import { Weekday } from '../structure-list/enum/weekday.enum'; import { Time } from '../models/time.model'; import { Filter } from '../structure-list/models/filter.model'; import { User } from '../models/user.model'; +import { StructureWithOwners } from '../models/structureWithOwners.model'; +import { Owner } from '../models/owner.model'; +import { TempUser } from '../models/temp-user.model'; @Injectable({ providedIn: 'root', @@ -62,6 +65,13 @@ export class StructureService { return this.http.delete<Structure>(`${this.baseUrl}/${id}`); } + public removeOwnerFromStructure(idOwner: string, idStructure: string): Observable<any> { + return this.http.delete<any>(`${this.baseUrl}/${idStructure}/owner/${idOwner}`); + } + public addOwnerToStructure(user: TempUser, idStructure: string): Observable<any> { + return this.http.post<any>(`${this.baseUrl}/${idStructure}/addOwner`, user); + } + public getStructures(filters: Filter[]): Observable<Structure[]> { if (filters && filters.length > 0) { let requestUrl = `${this.baseUrl}/search`; @@ -181,4 +191,7 @@ export class StructureService { return tabNum[0] + 'h' + tabNum[1]; } } + public getStructureWithOwners(structureId: string, profile: User): Observable<StructureWithOwners> { + return this.http.post<any>(`${this.baseUrl}/${structureId}/withOwners`, { emailUser: profile.email }); + } } diff --git a/src/app/shared/components/button/button.component.html b/src/app/shared/components/button/button.component.html index 099145ec9..dcfb2a0f7 100644 --- a/src/app/shared/components/button/button.component.html +++ b/src/app/shared/components/button/button.component.html @@ -6,16 +6,10 @@ fxLayout="row center" class="searchButton withIcon" fxLayoutAlign="space-between center" - fxLayoutGap="13px" + fxLayoutGap="5px" > - <app-svg-icon - style="height: 100%" - [type]="'ico'" - [iconClass]="'icon-32'" - [icon]="iconBtn" - [iconColor]="'currentColor'" - ></app-svg-icon> - {{ text }} + <app-svg-icon style="height: 100%" [type]="'ico'" [icon]="iconBtn" [iconColor]="'currentColor'"></app-svg-icon> + <span>{{ text }}</span> </div> </button> </ng-container> diff --git a/src/app/shared/components/button/button.component.scss b/src/app/shared/components/button/button.component.scss index 6aecd56b1..55ae3ca67 100644 --- a/src/app/shared/components/button/button.component.scss +++ b/src/app/shared/components/button/button.component.scss @@ -22,7 +22,7 @@ button { border: 1px solid; } .btnSearch { - @include background-hash; + @include background-hash($grey-2); border-color: $grey-4; padding: 0 0 4px 5px; &:hover { @@ -45,6 +45,7 @@ button { padding: 3px 16px 3px 16px; display: table-cell; vertical-align: middle; + border-radius: 4px; @include btn-bold; &.withIcon { color: $black; diff --git a/src/app/shared/components/create-account-form/create-account-form.component.spec.ts b/src/app/shared/components/create-account-form/create-account-form.component.spec.ts index 79e2eb5be..1a3aa14de 100644 --- a/src/app/shared/components/create-account-form/create-account-form.component.spec.ts +++ b/src/app/shared/components/create-account-form/create-account-form.component.spec.ts @@ -1,5 +1,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { CustomRegExp } from '../../../utils/CustomRegExp'; import { MustMatch } from '../../validator/form'; import { CreateAccountFormComponent } from './create-account-form.component'; @@ -12,7 +13,7 @@ describe('CreateAccountFormComponent', () => { email: new FormControl('test@test.fr', Validators.required), password: new FormControl('Testaze123!', [ Validators.required, - Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{8,})/), //NOSONAR + Validators.pattern(CustomRegExp.PASSWORD), //NOSONAR ]), confirmPassword: new FormControl('Testaze123!'), }, diff --git a/src/app/shared/components/create-account-form/create-account-form.component.ts b/src/app/shared/components/create-account-form/create-account-form.component.ts index 702924838..5d3d73098 100644 --- a/src/app/shared/components/create-account-form/create-account-form.component.ts +++ b/src/app/shared/components/create-account-form/create-account-form.component.ts @@ -1,6 +1,6 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { Component, EventEmitter, OnInit, Output } from '@angular/core'; import { AbstractControl, FormControl, FormGroup, Validators } from '@angular/forms'; -import { Regex } from '../../enum/regex.enum'; +import { CustomRegExp } from '../../../utils/CustomRegExp'; import { MustMatch } from '../../validator/form'; @Component({ @@ -19,14 +19,11 @@ export class CreateAccountFormComponent implements OnInit { ngOnInit(): void { this.accountForm = new FormGroup( { - email: new FormControl('', [Validators.required, Validators.pattern(Regex.email)]), - name: new FormControl('', [Validators.required, Validators.pattern(Regex.textWithoutNumber)]), - surname: new FormControl('', [Validators.required, Validators.pattern(Regex.textWithoutNumber)]), - phone: new FormControl('', [Validators.required, Validators.pattern('([0-9]{2} ){4}[0-9]{2}')]), //NOSONAR - password: new FormControl('', [ - Validators.required, - Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{8,})/), //NOSONAR - ]), + email: new FormControl('', [Validators.required, Validators.pattern(CustomRegExp.EMAIL)]), + name: new FormControl('', [Validators.required, Validators.pattern(CustomRegExp.TEXT_WITHOUT_NUMBER)]), + surname: new FormControl('', [Validators.required, Validators.pattern(CustomRegExp.TEXT_WITHOUT_NUMBER)]), + phone: new FormControl('', [Validators.required, Validators.pattern(CustomRegExp.PHONE)]), + password: new FormControl('', [Validators.required, Validators.pattern(CustomRegExp.PASSWORD)]), confirmPassword: new FormControl(''), }, [MustMatch('password', 'confirmPassword')] diff --git a/src/app/shared/components/modal-confirmation/modal-confirmation.component.scss b/src/app/shared/components/modal-confirmation/modal-confirmation.component.scss index 8b50ba8f3..21be72c13 100644 --- a/src/app/shared/components/modal-confirmation/modal-confirmation.component.scss +++ b/src/app/shared/components/modal-confirmation/modal-confirmation.component.scss @@ -39,7 +39,7 @@ width: 350px; margin: auto; border-radius: 6px; - @include background-hash; + @include background-hash($grey-2); border: 1px solid $grey-4; margin-top: 50vh; transform: translateY(-50%); diff --git a/src/app/shared/components/signup-modal/signup-modal.component.ts b/src/app/shared/components/signup-modal/signup-modal.component.ts index eb03ae892..2c13f60c1 100644 --- a/src/app/shared/components/signup-modal/signup-modal.component.ts +++ b/src/app/shared/components/signup-modal/signup-modal.component.ts @@ -3,7 +3,7 @@ import { FormGroup, FormBuilder, Validators, AbstractControl } from '@angular/fo import { ActivatedRoute, Router } from '@angular/router'; import { first } from 'rxjs/operators'; import { AuthService } from '../../../services/auth.service'; -import { Regex } from '../../enum/regex.enum'; +import { CustomRegExp } from '../../../utils/CustomRegExp'; @Component({ selector: 'app-signup-modal', @@ -29,14 +29,8 @@ export class SignUpModalComponent implements OnInit { ngOnInit(): void { this.loginForm = this.formBuilder.group({ - email: ['', [Validators.required, Validators.pattern(Regex.email)]], - password: [ - '', - [ - Validators.required, - Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{8,})/), //NOSONAR - ], - ], + email: ['', [Validators.required, Validators.pattern(CustomRegExp.EMAIL)]], + password: ['', [Validators.required, Validators.pattern(CustomRegExp.PASSWORD)]], }); // get return url from route parameters or default to '/' this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/'; diff --git a/src/app/shared/components/structure-type-picker/structure-type-picker.component.scss b/src/app/shared/components/structure-type-picker/structure-type-picker.component.scss index bac3648ad..9772d6210 100644 --- a/src/app/shared/components/structure-type-picker/structure-type-picker.component.scss +++ b/src/app/shared/components/structure-type-picker/structure-type-picker.component.scss @@ -59,7 +59,7 @@ button { } } .containerBtn { - @include background-hash; + @include background-hash($grey-2); padding: 0 0 4px 5px; border-radius: 4px; cursor: pointer; diff --git a/src/app/shared/components/svg-icon/svg-icon.component.scss b/src/app/shared/components/svg-icon/svg-icon.component.scss index c1377ef46..30dac9094 100644 --- a/src/app/shared/components/svg-icon/svg-icon.component.scss +++ b/src/app/shared/components/svg-icon/svg-icon.component.scss @@ -1,3 +1,4 @@ +@import '../../../../assets/scss/color'; .icon { display: inline-block; height: 2em; @@ -8,6 +9,10 @@ &.icon-75 { width: 4.688em; } + &.grey { + fill: $grey-3; + stroke: $grey-3; + } } svg { diff --git a/src/app/shared/enum/regex.enum.ts b/src/app/shared/enum/regex.enum.ts deleted file mode 100644 index 6011651df..000000000 --- a/src/app/shared/enum/regex.enum.ts +++ /dev/null @@ -1,11 +0,0 @@ -export enum Regex { - email = '[a-z0-9.-]+@[a-z0-9.-]+[.][a-z]{2,3}', - textWithoutNumber = '[A-Za-zÀ-ÖØ-öø-ÿ- ]{1,}', - phone = '([0-9]{2} ){4}[0-9]{2}', - website = '(www[.])?(https://)?(http://)?[a-zA-Z0-9.-]*[.][a-z]{2,3}((/)[a-zA-Z0-9-/]*)?', - linkedIn = '(linkedin.com/in/.{1,})', - facebook = '(facebook.com/.{1,})', - twitter = '(twitter.com/.{1,})', - instagram = '(instagram.com/.{1,})', - noNullNumber = '[1-9]{1}[0-9]*', -} diff --git a/src/app/structure-list/components/modal-filter/modal-filter.component.scss b/src/app/structure-list/components/modal-filter/modal-filter.component.scss index 247ab7361..7c31c30f9 100644 --- a/src/app/structure-list/components/modal-filter/modal-filter.component.scss +++ b/src/app/structure-list/components/modal-filter/modal-filter.component.scss @@ -37,7 +37,7 @@ border: none; padding: 0; } - @include background-hash; + @include background-hash($grey-2); border: 1px solid $grey-4; ::-webkit-scrollbar { width: 16px; diff --git a/src/app/utils/CustomRegExp.ts b/src/app/utils/CustomRegExp.ts new file mode 100644 index 000000000..94ead16e4 --- /dev/null +++ b/src/app/utils/CustomRegExp.ts @@ -0,0 +1,22 @@ +export class CustomRegExp { + /** + * Validate a password (at least 8 characters, 1 uppercase letter, 1 lowercase letter, 1 number, and 1 special character) + */ + public static readonly PASSWORD: RegExp = /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{8,})/; + /** + * Validate an email + */ + public static readonly EMAIL: RegExp = /^[a-z0-9.-]+@[a-z0-9.-]+[.][a-z]{2,3}/; + public static readonly TEXT_WITHOUT_NUMBER: RegExp = /^[A-Za-zÀ-ÖØ-öø-ÿ- ]{1,}/; + public static readonly PHONE: RegExp = /^([0-9]{2} ){4}[0-9]{2}/; + public static readonly WEBSITE: RegExp = /^(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})/; + public static readonly LINKEDIN: string = '(linkedin.com/in/.{1,})'; + public static readonly FACEBOOK: string = '(facebook.com/.{1,})'; + public static readonly TWITTER: string = '(twitter.com/.{1,})'; + public static readonly INSTAGRAM: string = '(instagram.com/.{1,})'; + public static readonly NO_NULL_NUMBER: string = '[1-9]{1}[0-9]'; + /** + * Validate a location request in search bar + */ + public static readonly LOCATION: RegExp = /^\d+\s[A-z]+\s[A-z]+/g; +} diff --git a/src/assets/form/sprite.svg b/src/assets/form/sprite.svg index e81f41f7b..37f14d8e9 100644 --- a/src/assets/form/sprite.svg +++ b/src/assets/form/sprite.svg @@ -336,4 +336,9 @@ <path d="M13.25 20.6066L13.25 20" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/> </symbol> +<symbol id="validate" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg"> +<circle cx="13" cy="13" r="13" fill="#47C562"/> +<path d="M8 13.8182L11.8889 17L18 10" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/> +</symbol> + </svg> \ No newline at end of file diff --git a/src/assets/ico/more_vert_24px.svg b/src/assets/ico/more_vert_24px.svg new file mode 100644 index 000000000..4e88a7515 --- /dev/null +++ b/src/assets/ico/more_vert_24px.svg @@ -0,0 +1,3 @@ +<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M11 7.66675C12.1 7.66675 13 6.76675 13 5.66675C13 4.56675 12.1 3.66675 11 3.66675C9.9 3.66675 9 4.56675 9 5.66675C9 6.76675 9.9 7.66675 11 7.66675ZM11 9.66675C9.9 9.66675 9 10.5667 9 11.6667C9 12.7667 9.9 13.6667 11 13.6667C12.1 13.6667 13 12.7667 13 11.6667C13 10.5667 12.1 9.66675 11 9.66675ZM9 17.6667C9 16.5667 9.9 15.6667 11 15.6667C12.1 15.6667 13 16.5667 13 17.6667C13 18.7667 12.1 19.6667 11 19.6667C9.9 19.6667 9 18.7667 9 17.6667Z" fill="#348899"/> +</svg> diff --git a/src/assets/ico/sprite.svg b/src/assets/ico/sprite.svg index 08676f739..02b135476 100644 --- a/src/assets/ico/sprite.svg +++ b/src/assets/ico/sprite.svg @@ -24,9 +24,9 @@ <path fill-rule="evenodd" clip-rule="evenodd" d="M4 4C4 3.44771 4.44772 3 5 3H14C14.5523 3 15 3.44772 15 4V6H9C8.44772 6 8 6.44772 8 7V18H5C4.44772 18 4 17.5523 4 17V4ZM10 7C9.44772 7 9 7.44772 9 8V20C9 20.5523 9.44771 21 10 21H19C19.5523 21 20 20.5523 20 20V8C20 7.44772 19.5523 7 19 7H10Z" fill="#32383D"/> </symbol> -<symbol id="cancel" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> -<path d="M16.9498 5.36385C17.3403 4.97332 17.9734 4.97332 18.364 5.36385C18.7545 5.75437 18.7545 6.38753 18.364 6.77806L7.05026 18.0918C6.65973 18.4823 6.02657 18.4823 5.63605 18.0918C5.24552 17.7012 5.24552 17.0681 5.63605 16.6776L16.9498 5.36385Z" fill="black"/> -<path d="M18.364 16.6777C18.7545 17.0682 18.7545 17.7013 18.364 18.0919C17.9734 18.4824 17.3403 18.4824 16.9498 18.0919L5.63605 6.77816C5.24552 6.38764 5.24552 5.75447 5.63605 5.36395C6.02657 4.97343 6.65974 4.97343 7.05026 5.36395L18.364 16.6777Z" fill="black"/> +<symbol id="cancel" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> +<path d="M16.9498 5.36385C17.3403 4.97332 17.9734 4.97332 18.364 5.36385C18.7545 5.75437 18.7545 6.38753 18.364 6.77806L7.05026 18.0918C6.65973 18.4823 6.02657 18.4823 5.63605 18.0918C5.24552 17.7012 5.24552 17.0681 5.63605 16.6776L16.9498 5.36385Z" stroke="none"/> +<path d="M18.364 16.6777C18.7545 17.0682 18.7545 17.7013 18.364 18.0919C17.9734 18.4824 17.3403 18.4824 16.9498 18.0919L5.63605 6.77816C5.24552 6.38764 5.24552 5.75447 5.63605 5.36395C6.02657 4.97343 6.65974 4.97343 7.05026 5.36395L18.364 16.6777Z" stroke="none"/> </symbol> <symbol id="nok" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg"> @@ -40,10 +40,17 @@ <path d="M11 16.8182L14.8889 20L21 13" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/> </symbol> -<symbol id="add" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> -<path d="M12 5C11.4477 5 11 5.44772 11 6V11H6C5.44772 11 5 11.4477 5 12C5 12.5523 5.44772 13 6 13H11V18C11 18.5523 11.4477 19 12 19C12.5523 19 13 18.5523 13 18V13H18C18.5523 13 19 12.5523 19 12C19 11.4477 18.5523 11 18 11H13V6C13 5.44772 12.5523 5 12 5Z" fill="#333333"/> +<symbol id="add" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> +<path d="M12 5C11.4477 5 11 5.44772 11 6V11H6C5.44772 11 5 11.4477 5 12C5 12.5523 5.44772 13 6 13H11V18C11 18.5523 11.4477 19 12 19C12.5523 19 13 18.5523 13 18V13H18C18.5523 13 19 12.5523 19 12C19 11.4477 18.5523 11 18 11H13V6C13 5.44772 12.5523 5 12 5Z" stroke="none"/> </symbol> +<symbol id="remove" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> +<path d="M6.97363 12.9062C6.34733 12.9062 5.89876 12.7877 5.62793 12.5507C5.36556 12.3053 5.23438 11.9879 5.23438 11.5986C5.23438 11.2092 5.36556 10.8961 5.62793 10.6591C5.89876 10.4137 6.34733 10.2952 6.97363 10.3036C14.44 10.3036 10.571 10.3036 17.0156 10.3036C17.6419 10.3036 18.0863 10.4221 18.3486 10.6591C18.6195 10.8961 18.7549 11.2092 18.7549 11.5986C18.7549 11.9879 18.6195 12.3053 18.3486 12.5507C18.0863 12.7877 17.6419 12.9062 17.0156 12.9062C9.63742 12.9062 13.3678 12.9062 6.97363 12.9062Z" stroke="none"/> +</symbol> + +<symbol id="edit" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M17.3745 2.62547C17.72 2.28003 18.3461 2.34613 18.7731 2.7731L20.3193 4.3193C20.7463 4.74627 20.8124 5.37243 20.4669 5.71787L18.6115 7.57331L15.5191 4.48091L17.3745 2.62547ZM17.993 8.1918L14.9006 5.0994L5.62344 14.3766L8.71584 17.469L17.993 8.1918ZM8.09736 18.0874L5.00496 14.995L4.74469 15.2553C4.60266 15.3973 4.52461 15.5949 4.52337 15.8155L3.77535 18.7134C3.7736 19.0246 4.0678 19.3188 4.37904 19.3171L7.27695 18.569C7.49751 18.5678 7.69506 18.4897 7.83709 18.3477L8.09736 18.0874Z" stroke="none"/> +</symbol> <symbol id="liste" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"> <rect x="10" y="9" width="16" height="2" rx="1" /> @@ -73,7 +80,12 @@ </symbol> <symbol id ="email" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg"> -<path fill-rule="evenodd" clip-rule="evenodd" d="M3.5 4H19.5C20.3284 4 21 4.67157 21 5.5V16.0714C21 16.8998 20.3284 17.5714 19.5 17.5714H3.5C2.67157 17.5714 2 16.8998 2 16.0714V5.5C2 4.67157 2.67157 4 3.5 4ZM2.91716 6.02444C3.04832 5.78143 3.35163 5.69075 3.59464 5.8219L11.2431 9.94966C11.5474 10.1138 11.9148 10.1093 12.2149 9.93753L19.3945 5.82797C19.6341 5.69079 19.9396 5.77387 20.0768 6.01353C20.214 6.25318 20.1309 6.55867 19.8913 6.69585L12.7116 10.8054C12.1116 11.1489 11.3767 11.1581 10.7682 10.8297L3.11971 6.70192C2.8767 6.57077 2.78602 6.26745 2.91716 6.02444Z" fill="#333333"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M3.5 4H19.5C20.3284 4 21 4.67157 21 5.5V16.0714C21 16.8998 20.3284 17.5714 19.5 17.5714H3.5C2.67157 17.5714 2 16.8998 2 16.0714V5.5C2 4.67157 2.67157 4 3.5 4ZM2.91716 6.02444C3.04832 5.78143 3.35163 5.69075 3.59464 5.8219L11.2431 9.94966C11.5474 10.1138 11.9148 10.1093 12.2149 9.93753L19.3945 5.82797C19.6341 5.69079 19.9396 5.77387 20.0768 6.01353C20.214 6.25318 20.1309 6.55867 19.8913 6.69585L12.7116 10.8054C12.1116 11.1489 11.3767 11.1581 10.7682 10.8297L3.11971 6.70192C2.8767 6.57077 2.78602 6.26745 2.91716 6.02444Z" stroke="none"/> +</symbol> + +<symbol id="password" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M4.89603 10C4.40117 10 4.00001 10.4012 4.00001 10.896L4 18.2042C4 18.699 4.40116 19.1002 4.89602 19.1002H17.1043C17.5992 19.1002 18.0003 18.699 18.0003 18.2042L18.0003 10.896C18.0003 10.4012 17.5992 10 17.1043 10H4.89603ZM12.3442 13.4441C12.3442 13.9365 12.0794 14.367 11.6845 14.6011L12.3442 17.0002H9.65611L10.3158 14.6011C9.92088 14.367 9.65611 13.9365 9.65611 13.4441C9.65611 12.7018 10.2579 12.1 11.0001 12.1C11.7424 12.1 12.3442 12.7018 12.3442 13.4441Z" stroke="none"/> +<path d="M13.8017 10.0002V7.90011C13.8017 6.35368 12.5481 5.10005 11.0017 5.10005C9.45524 5.10005 8.20161 6.35368 8.20161 7.90011V10.0002H6.10156V7.90011C6.10156 5.19386 8.29542 3 11.0017 3C13.7079 3 15.9018 5.19385 15.9018 7.90011V10.0002H13.8017Z" stroke="none"/> </symbol> <symbol id="pass" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg"> @@ -237,4 +249,20 @@ <path d="M15.227 14.2886C14.1412 13.2028 12.6412 12.5312 10.9844 12.5312C9.32752 12.5312 7.82752 13.2028 6.74173 14.2886L8.15595 15.7028C8.8798 14.979 9.8798 14.5312 10.9844 14.5312C12.0889 14.5312 13.0889 14.979 13.8128 15.7028L15.227 14.2886Z" stroke="none"/> <path d="M12.3986 17.117C12.0367 16.7551 11.5367 16.5312 10.9844 16.5312C10.4321 16.5312 9.93209 16.7551 9.57016 17.117L10.9844 18.5312L12.3986 17.117Z" stroke="none"/> </symbol> + +<symbol id="moreOpts" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M11 7.66675C12.1 7.66675 13 6.76675 13 5.66675C13 4.56675 12.1 3.66675 11 3.66675C9.9 3.66675 9 4.56675 9 5.66675C9 6.76675 9.9 7.66675 11 7.66675ZM11 9.66675C9.9 9.66675 9 10.5667 9 11.6667C9 12.7667 9.9 13.6667 11 13.6667C12.1 13.6667 13 12.7667 13 11.6667C13 10.5667 12.1 9.66675 11 9.66675ZM9 17.6667C9 16.5667 9.9 15.6667 11 15.6667C12.1 15.6667 13 16.5667 13 17.6667C13 18.7667 12.1 19.6667 11 19.6667C9.9 19.6667 9 18.7667 9 17.6667Z" stroke="none"/> +</symbol> + +<symbol id="camera" width="100" height="113" viewBox="0 0 100 113" fill="none" xmlns="http://www.w3.org/2000/svg"> +<rect width="100" height="113" rx="12" fill="#F8F8F8"/> +<path d="M27 11H15C13.8954 11 13 11.8954 13 13V25" stroke="#BDBDBD" stroke-width="2" stroke-linecap="round"/> +<path d="M27 100H15C13.8954 100 13 99.1046 13 98V86" stroke="#BDBDBD" stroke-width="2" stroke-linecap="round"/> +<path d="M73 11H85C86.1046 11 87 11.8954 87 13V25" stroke="#BDBDBD" stroke-width="2" stroke-linecap="round"/> +<path d="M73 100H85C86.1046 100 87 99.1046 87 98V86" stroke="#BDBDBD" stroke-width="2" stroke-linecap="round"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M61.1175 39.1429H72.7941C75.6692 39.1429 78 41.5002 78 44.4082V70.7347C78 73.6426 75.6692 76 72.7941 76H24.2059C21.3308 76 19 73.6426 19 70.7347V44.4082C19 41.5002 21.3308 39.1429 24.2059 39.1429H36.7502C37.8558 35.5825 41.1444 33 45.0294 33H52.8382C56.7232 33 60.0119 35.5825 61.1175 39.1429ZM49.5 70C56.9558 70 63 63.9558 63 56.5C63 49.0442 56.9558 43 49.5 43C42.0442 43 36 49.0442 36 56.5C36 63.9558 42.0442 70 49.5 70Z" fill="#C4C4C4"/> +<circle cx="49.5" cy="56.5" r="10.5" fill="#BDBDBD"/> +</symbol> + + </svg> diff --git a/src/assets/scss/_shapes.scss b/src/assets/scss/_shapes.scss index 9eb400742..a5497d14f 100644 --- a/src/assets/scss/_shapes.scss +++ b/src/assets/scss/_shapes.scss @@ -33,17 +33,17 @@ $mat-tab-shadow: 0px 2px 7px rgba(0, 0, 0, 0.25); background-position-y: 12px; } -@mixin background-hash { +@mixin background-hash($color) { background: linear-gradient( -45deg, - $grey-2 2.5%, + $color 2.5%, $white 2.5%, $white 47.5%, - $grey-2 47.5%, - $grey-2 52.5%, + $color 47.5%, + $color 52.5%, $white 52.5%, $white 97.5%, - $grey-2 97.5% + $color 97.5% ); background-size: 5px 5px; background-position: 25px 25px; diff --git a/src/styles.scss b/src/styles.scss index c8e77507a..b49b24295 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -228,7 +228,7 @@ button { max-width: 560px; margin: auto; border-radius: 6px; - @include background-hash; + @include background-hash($grey-2); border: 1px solid $grey-4; position: absolute; top: 50%; -- GitLab