diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index a7e9e853344e4e054dd9f7d2468053d6689d5134..743bc68525f77cea5aa77ae8d49e10d61f880842 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -14,6 +14,7 @@ import { LegalNoticeComponent } from './legal-notice/legal-notice.component'; import { LoginComponent } from './login/login.component'; import { NewsletterSubscriptionComponent } from './newsletter-subscription/newsletter-subscription.component'; import { PageComponent } from './page/page.component'; +import { EditComponent } from './profile/edit/edit.component'; import { ResetEmailComponent } from './reset-email/reset-email.component'; import { ResetPasswordComponent } from './reset-password/reset-password.component'; import { StructureResolver } from './resolvers/structure.resolver'; @@ -157,6 +158,11 @@ const routes: Routes = [ canActivate: [AuthGuard], loadChildren: () => import('./profile/profile.module').then((m) => m.ProfileModule), }, + { + path: 'edit', + canActivate: [AuthGuard], + component: EditComponent, + }, footerOutletRoute, { path: '', diff --git a/src/app/profile/edit/edit.component.html b/src/app/profile/edit/edit.component.html new file mode 100644 index 0000000000000000000000000000000000000000..38c78c377e48a75649b8abedb1e4b88a518ce46e --- /dev/null +++ b/src/app/profile/edit/edit.component.html @@ -0,0 +1,448 @@ +<div fxLayout="column" class="content-container full-screen" *ngIf="userProfile"> + <div class="edit-profile"> + <div class="header"> + <div class="title"> + <a routerLink="/profile"> + <svg aria-hidden="true"> + <use [attr.xlink:href]="'assets/ico/sprite.svg#arrowBack'"></use> + </svg> + </a> + <h1>Modifier mon profil</h1> + </div> + <app-button + class="hide-on-mobile deleteAccount" + [style]="buttonTypeEnum.Secondary" + [text]="'Supprimer mon compte'" + [type]="'button'" + [iconType]="'ico'" + [iconBtn]="'removeCross'" + (action)="showDeleteAccountModal()" + ></app-button> + <app-button + class="hide-on-desktop" + [style]="buttonTypeEnum.SecondaryOnlyIcon" + [type]="'button'" + [iconBtn]="'edit'" + [iconType]="'ico'" + [iconBtn]="'deleteAccount'" + (action)="showDeleteAccountModal()" + ></app-button> + </div> + <!-- Navigation --> + <div class="navigation"> + <span [ngClass]="{ tab: true, selected: currentTab === tabsEnum.details }" (click)="navigateTo(tabsEnum.details)" + >Coordonnées</span + > + <span + [ngClass]="{ tab: true, selected: currentTab === tabsEnum.credentials }" + (click)="navigateTo(tabsEnum.credentials)" + >Email et mot de passe</span + > + <span + [ngClass]="{ tab: true, selected: currentTab === tabsEnum.employer }" + (click)="navigateTo(tabsEnum.employer)" + >Employeur et fonction</span + > + <span + [ngClass]="{ tab: true, selected: currentTab === tabsEnum.description }" + (click)="navigateTo(tabsEnum.description)" + >Description</span + > + <!-- <span [ngClass]="{ tab: true, selected: currentTab === tabsEnum.avatar }" (click)="navigateTo(tabsEnum.avatar)" + >Avatar</span + > --> + </div> + <!-- Content of tabs --> + <div class="content"> + <!-- TODO: refacto this with account-info.component --> + <div *ngIf="currentTab === tabsEnum.details"> + <div class="form-group" fxLayout="column"> + <label for="name">Prénom</label> + <div fxLayout="row" fxLayoutGap="13px"> + <input type="text" class="form-input" [(ngModel)]="userProfile.name" /> + <app-svg-icon + *ngIf="nameValid()" + [iconClass]="'icon-26'" + [type]="'form'" + [icon]="'validate'" + ></app-svg-icon> + <app-svg-icon + *ngIf="!nameValid()" + [iconClass]="'icon-26'" + [type]="'form'" + [icon]="'notValidate'" + ></app-svg-icon> + </div> + </div> + <div class="form-group" fxLayout="column"> + <label for="surname">Nom</label> + <div fxLayout="row" fxLayoutGap="13px"> + <input type="text" class="form-input" [(ngModel)]="userProfile.surname" /> + <app-svg-icon + *ngIf="surnameValid()" + [iconClass]="'icon-26'" + [type]="'form'" + [icon]="'validate'" + ></app-svg-icon> + <app-svg-icon + *ngIf="!surnameValid()" + [iconClass]="'icon-26'" + [type]="'form'" + [icon]="'notValidate'" + ></app-svg-icon> + </div> + </div> + <div class="form-group" fxLayout="column"> + <label for="phone">Téléphone</label> + <div fxLayout="row" fxLayoutGap="13px"> + <input type="text" class="form-input phone" [(ngModel)]="userProfile.phone" /> + <app-svg-icon + *ngIf="phoneValid()" + [iconClass]="'icon-26'" + [type]="'form'" + [icon]="'validate'" + ></app-svg-icon> + <app-svg-icon + *ngIf="!phoneValid()" + [iconClass]="'icon-26'" + [type]="'form'" + [icon]="'notValidate'" + ></app-svg-icon> + </div> + </div> + </div> + + <div *ngIf="currentTab === tabsEnum.credentials" class="credentialsTab"> + <p class="subTitle">Email</p> + {{ userProfile.email }} + <div class="buttons"> + <app-button + [text]="'Changer mon email'" + (action)="showEmailModal()" + [iconType]="'ico'" + [iconBtn]="'emailOutline'" + [style]="buttonTypeEnum.Secondary" + > + </app-button> + <app-button + [text]="'Changer mon mot de passe'" + (action)="showPasswordModal()" + [iconType]="'ico'" + [iconBtn]="'passwordOutline'" + [style]="buttonTypeEnum.Secondary" + > + </app-button> + </div> + </div> + + <div *ngIf="currentTab === tabsEnum.employer"> + <div class="search-structure"> + <div fxLayout="column" fxLayoutAlign="space-between" class="form-group search"> + <label for="employer">Employeur</label> + <div fxLayout="row" fxLayoutGap="13px"> + <input + id="search-employer" + type="text" + (input)="onSearchChange($event.target.value)" + class="form-input" + autocomplete="off" + #searchEmployer + /> + </div> + </div> + <div class="structureResults"> + <div class="autocomplete-items" *ngIf="!isAlreadySearching"> + <p *ngFor="let employer of employers" (click)="selectEmployer(employer)" class="autocomplete-item"> + {{ employer.name }} + </p> + </div> + </div> + </div> + <p class="subTitle">Fonction</p> + <div fxLayout="column" fxLayoutGap="32px"> + <div class="btn-grid"> + <span *ngFor="let job of jobs"> + <app-button + [ngClass]="{ selectedChoice: true }" + [extraClass]="isSelectedJob(job) ? 'selected' : ''" + [style]="buttonTypeEnum.CheckButton" + [text]="job.name" + (action)="selectJob(job)" + ></app-button> + </span> + </div> + <div *ngIf="isUnexistingJob()" fxLayout="column" fxLayoutAlign="space-between" class="form-group search"> + <label for="employer">Quelle fonction occupez-vous ?</label> + <input + type="text" + (input)="createNewJob($event.target.value)" + class="form-input" + autocomplete="off" + #newJobInput + /> + </div> + </div> + </div> + + <div *ngIf="currentTab === tabsEnum.description" class="descriptionTab"> + <p class="subTitle">Description</p> + <div class="textareaBlock" fxLayout="column"> + <textarea + rows="8" + placeholder="Exemple : formateur depuis 2 ans, je participe tous les ans à Super Demain." + maxlength="500" + [(ngModel)]="userProfile.description" + ></textarea> + <p class="descriptionLength">{{ userProfile.description?.length }} / 500</p> + </div> + </div> + + <!-- <div *ngIf="currentTab === tabsEnum.avatar">Avater container</div> --> + </div> + + <!-- Footer --> + <div class="footer" *ngIf="currentTab !== tabsEnum.credentials"> + <app-button [text]="'Annuler'" (action)="cancel()"></app-button> + <app-button + [text]="'Valider'" + [disabled]="!isPageValid()" + (action)="confirm()" + [style]="buttonTypeEnum.Primary" + [extraClass]="svgCheck" + > + </app-button> + </div> + </div> + + <!-- Modal: Email change --> + <div class="modalBackground" *ngIf="emailModal"> + <div class="modal"> + <div class="modalHeader"> + <div class="empty"></div> + <h3>Changer mon email</h3> + <svg class="close" aria-hidden="true" (click)="closeModal()"> + <use [attr.xlink:href]="'assets/form/sprite.svg#close'"></use> + </svg> + </div> + + <div class="modalContent"> + <div class="form-group" fxLayout="column"> + <label for="email">Nouvel email</label> + <div fxLayout="row" fxLayoutGap="13px"> + <input id="email" type="text" class="form-input email-placeholder" [(ngModel)]="newEmail" /> + <app-svg-icon + *ngIf="emailValid(newEmail)" + [iconClass]="'icon-26'" + [type]="'form'" + [icon]="'validate'" + ></app-svg-icon> + <app-svg-icon + *ngIf="!emailValid(newEmail)" + [iconClass]="'icon-26'" + [type]="'form'" + [icon]="'notValidate'" + ></app-svg-icon> + </div> + </div> + + <div class="form-group" fxLayout="column"> + <label for="emailConfirm">Confirmer le nouvel email</label> + <div fxLayout="row" fxLayoutGap="13px"> + <input id="emailConfirm" type="text" class="form-input email-placeholder" [(ngModel)]="newEmailConfirm" /> + <app-svg-icon + *ngIf="emailValid(newEmailConfirm)" + [iconClass]="'icon-26'" + [type]="'form'" + [icon]="'validate'" + ></app-svg-icon> + <app-svg-icon + *ngIf="!emailValid(newEmailConfirm)" + [iconClass]="'icon-26'" + [type]="'form'" + [icon]="'notValidate'" + ></app-svg-icon> + </div> + </div> + + <div class="buttons"> + <app-button [text]="'Annuler'" (action)="closeModal()"></app-button> + <app-button + [text]="'Valider'" + [style]="buttonTypeEnum.Primary" + [disabled]="!isPageValid()" + (action)="confirm()" + ></app-button> + </div> + </div> + </div> + </div> + + <!-- Modal: Password change --> + <div class="modalBackground" *ngIf="passwordModal" (clickOutside)="closeModal()"> + <div class="modal"> + <div class="modalHeader"> + <div class="empty"></div> + <h3>Changer mon mot de passe</h3> + <svg class="close" aria-hidden="true" (click)="closeModal()"> + <use [attr.xlink:href]="'assets/form/sprite.svg#close'"></use> + </svg> + </div> + + <div class="modalContent"> + <div class="form-group" fxLayout="column"> + <div class="form-group"> + <label for="oldPassword">Ancien mot de passe</label> + <div fxLayout="row" fxLayoutAlign="none center" fxLayoutGap="13px"> + <input + [type]="isShowPassword.oldPassword ? 'text' : 'password'" + [(ngModel)]="oldPassword" + id="oldPassword" + class="form-input password" + autocomplete="on" + /> + <app-svg-icon + [iconClass]="'icon-26 grey hover'" + [type]="'form'" + [icon]="'eyePassword'" + (click)="showPassword('oldPassword')" + ></app-svg-icon> + <app-svg-icon + *ngIf="passwordValid(oldPassword)" + [iconClass]="'icon-26'" + [type]="'form'" + [icon]="'validate'" + ></app-svg-icon> + <app-svg-icon + *ngIf="!passwordValid(oldPassword)" + [iconClass]="'icon-26'" + [type]="'form'" + [icon]="'notValidate'" + ></app-svg-icon> + </div> + </div> + + <div class="form-group"> + <label for="newPassword">Nouveau mot de passe</label> + <div fxLayout="row" fxLayoutAlign="none center" fxLayoutGap="13px"> + <input + [type]="isShowPassword.newPassword ? 'text' : 'password'" + [(ngModel)]="newPassword" + id="newPassword" + class="form-input password" + autocomplete="on" + /> + <app-svg-icon + [iconClass]="'icon-26 grey hover'" + [type]="'form'" + [icon]="'eyePassword'" + (click)="showPassword('newPassword')" + ></app-svg-icon> + <app-svg-icon + *ngIf="passwordValid(newPassword)" + [iconClass]="'icon-26'" + [type]="'form'" + [icon]="'validate'" + ></app-svg-icon> + <app-svg-icon + *ngIf="!passwordValid(newPassword)" + [iconClass]="'icon-26'" + [type]="'form'" + [icon]="'notValidate'" + ></app-svg-icon> + </div> + </div> + </div> + <p class="form-group passwordInfo" [class.warn]="!passwordValid(newPassword)"> + 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 class="form-group"> + <label for="newPasswordConfirm">Confirmer le nouveau mot de passe</label> + <div fxLayout="row" fxLayoutAlign="none center" fxLayoutGap="13px"> + <input + [type]="isShowPassword.newPasswordConfirm ? 'text' : 'password'" + [(ngModel)]="newPasswordConfirm" + id="newPasswordConfirm" + class="form-input password" + autocomplete="on" + /> + <app-svg-icon + [iconClass]="'icon-26 grey hover'" + [type]="'form'" + [icon]="'eyePassword'" + (click)="showPassword('newPasswordConfirm')" + ></app-svg-icon> + <app-svg-icon + *ngIf="passwordValid(newPasswordConfirm)" + [iconClass]="'icon-26'" + [type]="'form'" + [icon]="'validate'" + ></app-svg-icon> + <app-svg-icon + *ngIf="!passwordValid(newPasswordConfirm)" + [iconClass]="'icon-26'" + [type]="'form'" + [icon]="'notValidate'" + ></app-svg-icon> + </div> + </div> + <div class="buttons"> + <app-button [text]="'Annuler'" (action)="closeModal()"></app-button> + <app-button + [text]="'Valider'" + [style]="buttonTypeEnum.Primary" + [disabled]="!isPageValid()" + (action)="confirm()" + ></app-button> + </div> + </div> + </div> + </div> + + <!-- Modal: Delete account --> + <div class="modalBackground" *ngIf="deleteAccountModal"> + <div class="modal"> + <div class="modalHeader"> + <div class="empty"></div> + <h3>Supprimer mon compte</h3> + <svg class="close" aria-hidden="true" (click)="closeModal()"> + <use [attr.xlink:href]="'assets/form/sprite.svg#close'"></use> + </svg> + </div> + + <div class="modalContent"> + <p class="warnText"> + Cette action est définitive, veuillez entrer votre mot de passe afin de supprimer votre compte + </p> + <div class="form-group"> + <label for="oldPassword">Mot de passe</label> + <div fxLayout="row" fxLayoutAlign="none center" fxLayoutGap="13px"> + <input + [type]="isShowPassword.oldPassword ? 'text' : 'password'" + [(ngModel)]="oldPassword" + id="oldPassword" + class="form-input password" + autocomplete="on" + /> + <app-svg-icon + [iconClass]="'icon-26 grey hover'" + [type]="'form'" + [icon]="'eyePassword'" + (click)="showPassword('oldPassword')" + ></app-svg-icon> + </div> + </div> + + <div class="buttons"> + <app-button [text]="'Annuler'" (action)="closeModal()"></app-button> + <app-button + [text]="'Supprimer'" + [style]="buttonTypeEnum.Primary" + [disabled]="!passwordValid(oldPassword)" + (action)="confirmDeleteAccount()" + ></app-button> + </div> + </div> + </div> + </div> +</div> diff --git a/src/app/profile/edit/edit.component.scss b/src/app/profile/edit/edit.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..7674341394ace169c88d6b1e87826a2a40442ac7 --- /dev/null +++ b/src/app/profile/edit/edit.component.scss @@ -0,0 +1,212 @@ +@import '../../../assets/scss/color'; +@import '../../../assets/scss/typography'; +@import '../../../assets/scss/hyperlink'; +@import '../../../assets/scss/shapes'; +@import '../../../assets/scss/breakpoint'; + +.edit-profile { + display: flex; + flex-direction: column; + flex: 1; + max-width: 980px; + width: 100%; + margin: 16px auto 8px auto; + padding: 40px; + padding-bottom: 0px; + background: $white; + border: 1px solid $grey-6; + border-radius: 8px; + + @media #{$tablet} { + margin: 0px 4px 4px 4px; + padding: 16px; + padding-bottom: 0px; + width: auto; + } + + .header, + .navigation, + .title { + display: flex; + } + + .header { + justify-content: space-between; + padding-bottom: 18px; + align-items: center; + + .title { + align-items: center; + h1 { + color: $grey-1; + font-weight: lighter; + @media #{$tablet} { + @include lato-regular-20; + } + } + svg { + stroke: $black; + height: 40px; + width: 40px; + margin-right: 14px; + cursor: pointer; + } + } + + .deleteAccount { + ::ng-deep { + svg { + height: 22px; + width: 22px; + margin-right: 4px; + } + span { + color: $red; + @include lato-regular-14; + } + } + } + } + + .navigation { + justify-content: flex-start; + overflow-x: auto; + white-space: nowrap; + + border-bottom: 1px solid $grey-4; + + &::-webkit-scrollbar { + height: 8px; + margin-right: 4px; + } + + .tab { + color: $grey-3; + margin-right: 40px; + padding-bottom: 8px; + &:hover { + cursor: pointer; + } + &.selected { + color: $grey-1; + border-bottom: 2px solid $grey-1; + } + } + } + + .content { + padding-top: 24px; + flex: 1; + max-width: 600px; + p.subTitle { + @include lato-regular-16; + text-align: left; + margin-top: 0; + margin-bottom: 4px; + } + + .credentialsTab { + .buttons { + margin-top: 25px; + display: flex; + flex-wrap: wrap; + gap: 18px; + ::ng-deep { + svg { + height: 22px; + width: 22px; + margin-right: 4px; + } + } + } + } + + .descriptionTab { + p.descriptionLength { + text-align: right; + @include lato-regular-14; + color: $grey-3; + font-style: italic; + margin-top: 8px; + } + } + + .structureResults { + position: absolute; + width: 600px; + z-index: 1; + } + } + + .footer { + padding: 16px; + display: flex; + gap: 24px; + justify-content: center; + flex-wrap: wrap; + border-top: 1px solid $grey-4; + //To fit border to parent div + margin: 0 -40px; + @media #{$tablet} { + margin: 0 -16px; + } + ::ng-deep div svg { + height: 22px; + } + } +} + +.modalBackground { + //bck fade + .modal { + max-width: 390px; + background-color: $white; + @media #{$tablet} { + width: 85%; + } + .modalHeader { + display: flex; + justify-content: space-between; + align-items: center; + border: 1px solid $grey-6; + padding: 0 8px; + h3 { + @include lato-bold-18; + } + svg, + .empty { + height: 40px; + width: 40px; + } + svg { + cursor: pointer; + } + } + + .modalContent { + padding: 24px 40px; + + .warnText { + margin: 0 auto 24px auto; + } + } + p { + text-align: center; + margin: 10px 0; + &.passwordInfo { + @include lato-regular-14; + text-align: left; + &.warn { + color: $orange-warning; + } + } + } + .buttons { + display: flex; + justify-content: space-between; + align-items: center; + gap: 24px; + padding-top: 8px; + } + } +} diff --git a/src/app/profile/edit/edit.component.spec.ts b/src/app/profile/edit/edit.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..5a24d45a2bfcaf2aa6c4192b21705f05ee02457f --- /dev/null +++ b/src/app/profile/edit/edit.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { EditComponent } from './edit.component'; + +describe('EditComponent', () => { + let component: EditComponent; + let fixture: ComponentFixture<EditComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ EditComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(EditComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/profile/edit/edit.component.ts b/src/app/profile/edit/edit.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..ca4a6e47513ef01f48846487875952a5697faf60 --- /dev/null +++ b/src/app/profile/edit/edit.component.ts @@ -0,0 +1,329 @@ +import { ChangeDetectorRef, Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core'; +import { HttpErrorResponse } from '@angular/common/http'; +import { forkJoin, of } from 'rxjs'; +import { catchError, first, map } from 'rxjs/operators'; +import { Job } from '../../models/job.model'; +import { User } from '../../models/user.model'; +import { Employer } from '../../models/employer.model'; +import { AuthService } from '../../services/auth.service'; +import { ProfileService } from '../services/profile.service'; +import { NotificationService } from '../../services/notification.service'; +import { ButtonType } from '../../shared/components/button/buttonType.enum'; +import { CustomRegExp } from '../../utils/CustomRegExp'; + +enum tabsEnum { + details, + credentials, + employer, + description, + avatar, +} + +enum showPasswordEnum { + oldPassword, + newPassword, + newPasswordConfirm, +} +@Component({ + selector: 'app-edit', + templateUrl: './edit.component.html', + styleUrls: ['./edit.component.scss'], +}) +export class EditComponent implements OnInit { + public tabsEnum = tabsEnum; + public buttonTypeEnum = ButtonType; + public currentTab: tabsEnum = tabsEnum.details; + + @Input() userProfile: User; + public initialUserProfile: User; + private emailModal = false; + private passwordModal = false; + private deleteAccountModal = false; + private newEmail = ''; + private newEmailConfirm = ''; + private oldPassword = ''; + private newPassword = ''; + private newPasswordConfirm = ''; + private isShowPassword = { + oldPassword: false, + newPassword: false, + newPasswordConfirm: false, + }; + public jobs: Job[]; + private newJob: Job; + private selectedJob: Job; + public employers: Employer[]; + private selectedEmployer: Employer; + private isAlreadySearching = false; + @ViewChild('searchEmployer') searchEmployer: ElementRef; + @ViewChild('newJobInput') newJobInput: ElementRef; + + constructor( + private profileService: ProfileService, + private notificationService: NotificationService, + private cdr: ChangeDetectorRef, + private authService: AuthService + ) {} + + ngOnInit(): void { + if (history.state.data === 'description') { + this.currentTab = tabsEnum.description; + } + + this.profileService.getProfile().then((profile) => { + this.userProfile = profile; + this.initialUserProfile = { ...profile }; + this.selectedEmployer = { ...profile.employer }; + + const otherJob = new Job({ name: 'Autre' }); + this.profileService.getJobs().subscribe((jobs) => { + this.jobs = [...jobs, otherJob]; + + // Select "Autre" job and set the job's name + if (jobs.some((job) => job.name === profile.job?.name)) { + this.selectedJob = { ...profile.job }; + } else { + this.selectedJob = otherJob; + this.newJob = profile.job; + } + }); + }); + } + + public phoneValid(): boolean { + return !!this.userProfile.phone.match(CustomRegExp.PHONE); + } + public nameValid(): boolean { + return this.userProfile.name !== ''; + } + public surnameValid(): boolean { + return this.userProfile.surname !== ''; + } + public descriptionValid(): boolean { + return this.userProfile.description.length <= 500; + } + public emailValid(email: string): boolean { + return !!email.match(CustomRegExp.EMAIL); + } + public passwordValid(password: string): boolean { + return !!( + password.match(CustomRegExp.SPECHAR) && + password.match(CustomRegExp.DIGIT) && + password.match(CustomRegExp.UPPERCASE) && + password.match(CustomRegExp.LOWERCASE) + ); + } + + public detailsChanged(): boolean { + return ( + this.initialUserProfile.name !== this.userProfile.name || + this.initialUserProfile.surname !== this.userProfile.surname || + this.initialUserProfile.phone !== this.userProfile.phone + ); + } + + public navigateTo(tab: tabsEnum): void { + this.currentTab = tab; + if (tab === tabsEnum.employer) { + this.cdr.detectChanges(); + this.selectEmployer(this.userProfile.employer); + if (this.newJob) this.newJobInput.nativeElement.value = this.userProfile.job.name; + } + } + + public cancel(): void { + this.userProfile = { ...this.initialUserProfile }; + } + + public showEmailModal(): void { + this.emailModal = true; + } + public showPasswordModal(): void { + this.passwordModal = true; + } + public showDeleteAccountModal(): void { + this.deleteAccountModal = true; + } + public showPassword(key: showPasswordEnum): void { + this.isShowPassword[key] = !this.isShowPassword[key]; + } + public closeModal(): void { + this.emailModal = false; + this.passwordModal = false; + this.deleteAccountModal = false; + } + + public isPageValid(): boolean { + if (this.currentTab === tabsEnum.details) { + return this.detailsChanged() && this.phoneValid() && this.nameValid() && this.surnameValid(); + } else if (this.currentTab === tabsEnum.credentials) { + if (this.emailModal) { + return this.emailValid(this.newEmail) && this.newEmail === this.newEmailConfirm; + } else if (this.passwordModal) { + return this.passwordValid(this.newPassword) && this.newPassword == this.newPasswordConfirm; + } + } else if (this.currentTab === tabsEnum.employer) { + return !!( + this.selectedEmployer?.name !== this.userProfile.employer?.name || + this.selectedJob?.name !== this.userProfile.job?.name + ); + } else if (this.currentTab === tabsEnum.description) { + return this.descriptionValid() && this.initialUserProfile.description !== this.userProfile.description; + } + } + + public confirm(): void { + if (this.currentTab === tabsEnum.details) { + this.confirmDetails(); + } else if (this.currentTab === tabsEnum.credentials) { + if (this.emailModal) { + this.confirmNewEmail(); + } else if (this.passwordModal) { + this.confirmNewPassword(); + } + } else if (this.currentTab === tabsEnum.employer) { + this.confirmEmployer(); + } else if (this.currentTab === tabsEnum.description) { + this.confirmDescription(); + } + } + + public confirmDetails(): void { + this.profileService + .updateDetails({ + name: this.userProfile.name, + surname: this.userProfile.surname, + phone: this.userProfile.phone, + }) + .subscribe((user: User) => { + this.notificationService.showSuccess('Vos coordonnées ont bien été mises à jour'); + //Update localstorage + const updatedUser = { + ...this.authService.userSubject.value, + name: user.name, + surname: user.surname, + phone: user.phone, + }; + localStorage.setItem('user', JSON.stringify(updatedUser)); + this.authService.userSubject.next(updatedUser); + this.updateInitialProfile(); + }); + } + + public confirmEmployer(): void { + if (this.newJob) this.selectedJob = this.newJob; + forkJoin({ + employer: this.profileService.createEmployer(this.selectedEmployer).pipe( + map((res) => res), + catchError(() => of(this.selectedEmployer)) + ), + job: this.profileService.createJob(this.selectedJob).pipe( + map((res) => res), + catchError(() => of(this.selectJob)) + ), + profile: this.profileService.updateProfile(this.selectedEmployer.name, this.selectedJob.name).pipe( + map((res) => res), + catchError(() => of()) + ), + }).subscribe(() => { + this.notificationService.showSuccess('Vos informations ont bien été enregistrées'); + }); + } + + public confirmDescription(): void { + this.profileService.updateDescription(this.userProfile.description).subscribe(() => { + this.notificationService.showSuccess('Vos description a bien été enregistrée'); + this.updateInitialProfile(); + }); + } + + // Updates initialProfile so the button "Valider" is disabled after the user sends new values + private updateInitialProfile() { + this.initialUserProfile = { ...this.userProfile }; + } + + public confirmNewEmail(): void { + if (this.emailValid(this.newEmail) && this.newEmail === this.newEmailConfirm) { + this.profileService.changeEmail(this.newEmail, this.userProfile.email).subscribe(() => { + this.closeModal(); + this.notificationService.showSuccess('Veuillez confirmer votre nouvelle adresse grâce au mail envoyé', ''); + }); + } + } + + public confirmNewPassword(): void { + if (this.passwordValid(this.newPassword) && this.newPassword == this.newPasswordConfirm) { + this.profileService + .changePassword(this.newPassword, this.oldPassword) + .pipe( + catchError(async (response: HttpErrorResponse) => { + if (response.error.statusCode == 401) { + this.notificationService.showError('Une erreur est survenue', ''); + throw new Error('Une erreur est survenue'); + } + }) + ) + .subscribe(() => { + this.notificationService.showSuccess('Votre mot de passe a bien été modifié', ''); + this.closeModal(); + }); + this.oldPassword = ''; + this.isShowPassword.oldPassword = false; + } + } + + public confirmDeleteAccount(): void { + this.authService + .login(this.userProfile.email, this.oldPassword) + .pipe(first()) + .subscribe( + () => { + this.profileService.deleteProfile().subscribe(() => { + this.notificationService.showSuccess('Votre compte a bien été supprimé', ''); + this.closeModal(); + this.authService.logout(); + }); + }, + () => { + this.notificationService.showError('Une erreur est survenue', ''); + } + ); + } + + //Jobs + public selectJob(job: Job): void { + this.selectedJob = job; + } + public isSelectedJob(job: Job): boolean { + return this.selectedJob && this.selectedJob.name === job.name; + } + public isUnexistingJob(): boolean { + return this.selectedJob && this.selectedJob.name === 'Autre'; + } + public createNewJob(value: string): void { + this.newJob = new Job({ name: value, validated: false, hasPersonalOffer: true }); + } + + //Structures + public onSearchChange(searchString: string): void { + if (searchString.length <= 2) this.getEmployers(); + this.getEmployers(searchString); + this.selectedEmployer = new Employer({ name: searchString, validated: false }); + } + + public selectEmployer(employer: Employer): void { + if (employer) this.searchEmployer.nativeElement.value = employer.name; + this.selectedEmployer = employer; + this.employers = []; + } + + private getEmployers(searchString: string = '') { + if (!this.isAlreadySearching) { + this.isAlreadySearching = true; + this.profileService.getEmployers(searchString).subscribe((employers) => { + this.employers = employers; + this.isAlreadySearching = false; + }); + } + } +} diff --git a/src/app/profile/profile.component.html b/src/app/profile/profile.component.html index 00f5d7373312859181196b7fc2d039734e0ebb8d..1894d69585aae7756afe5cd7a4efad1def52610f 100644 --- a/src/app/profile/profile.component.html +++ b/src/app/profile/profile.component.html @@ -49,6 +49,7 @@ [text]="'Ajouter une description'" [style]="buttonTypeEnum.SecondaryUltraWide" routerLink="/profile/edit" + [state]="{ data: 'description' }" [routerLinkActive]="'active'" ></app-button> </div> diff --git a/src/app/profile/profile.module.ts b/src/app/profile/profile.module.ts index 6a2874aafccbe2e25bc37d129f0f3bbe0812ffe1..845a5303a427d0d48975319d02adafb7722d562a 100644 --- a/src/app/profile/profile.module.ts +++ b/src/app/profile/profile.module.ts @@ -4,9 +4,10 @@ import { SharedModule } from '../shared/shared.module'; import { CommonModule } from '@angular/common'; import { ProfileRoutingModule } from './profile-routing.module'; import { StructureEditionSummaryComponent } from './structure-edition-summary/structure-edition-summary.component'; +import { EditComponent } from './edit/edit.component'; @NgModule({ - declarations: [ProfileComponent, StructureEditionSummaryComponent], + declarations: [ProfileComponent, StructureEditionSummaryComponent, EditComponent], imports: [CommonModule, ProfileRoutingModule, SharedModule], }) export class ProfileModule {} diff --git a/src/app/profile/services/profile.service.ts b/src/app/profile/services/profile.service.ts index ab75df01c6963cf2d5bcbb01824a542c77c35353..50b1dadffdc7acbae1b9589e3e713f6cf95d63ac 100644 --- a/src/app/profile/services/profile.service.ts +++ b/src/app/profile/services/profile.service.ts @@ -8,6 +8,8 @@ import { AuthService } from '../../services/auth.service'; import { Structure } from '../../models/structure.model'; import { Employer } from '../../models/employer.model'; import { Job } from '../../models/job.model'; +import { catchError, map } from 'rxjs/operators'; +import { NotificationService } from '../../services/notification.service'; @Injectable({ providedIn: 'root', @@ -15,7 +17,11 @@ import { Job } from '../../models/job.model'; export class ProfileService { private readonly baseUrl = 'api/users'; private currentProfile: User = null; - constructor(private http: HttpClient, private authService: AuthService) {} + constructor( + private http: HttpClient, + private authService: AuthService, + private notificationService: NotificationService + ) {} public async getProfile(): Promise<User> { if (this.authService.isLoggedIn()) { @@ -69,7 +75,7 @@ export class ProfileService { return false; } - public changePassword(newPassword: string, oldPassword: string): Observable<User> { + public changePassword(newPassword: string, oldPassword: string): Observable<any> { return this.http.post<any>(`${this.baseUrl}/change-password`, { newPassword, oldPassword }); } public verifyAndUpdateEmail(token: string): Observable<User> { @@ -108,4 +114,27 @@ export class ProfileService { public updateProfile(employerName: string, jobName: string): Observable<User> { return this.http.post<User>(`${this.baseUrl}/profile`, { employerName, jobName }); } + + public updateDetails(newDetails: { name: string; surname: string; phone: string }): Observable<User | Error> { + console.log('profile service updates details'); + return this.http.post<User>(`${this.baseUrl}/details`, newDetails).pipe( + map((user) => user), + catchError(() => { + this.notificationService.showError('Une erreur est survenue', ''); + return new Observable<Error>(); + }) + ); + } + + public updateDescription(description: string): Observable<User | Error> { + return this.http + .post<User>(`${this.baseUrl}/description`, { description }) + .pipe( + map((user) => user), + catchError(() => { + this.notificationService.showError('Une erreur est survenue', ''); + return new Observable<Error>(); + }) + ); + } } diff --git a/src/app/services/auth.service.ts b/src/app/services/auth.service.ts index 4726f207033f4dc9406e2146fdf2e784a6a81958..c5b7ec7301e4e7a770262717a5f63d99c7ab138a 100644 --- a/src/app/services/auth.service.ts +++ b/src/app/services/auth.service.ts @@ -1,6 +1,5 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { Router } from '@angular/router'; import { DateTime } from 'luxon'; import { BehaviorSubject, Observable } from 'rxjs'; import { map } from 'rxjs/operators'; @@ -10,10 +9,10 @@ import { User } from '../models/user.model'; providedIn: 'root', }) export class AuthService { - private userSubject: BehaviorSubject<UserAuth>; + public userSubject: BehaviorSubject<UserAuth>; public user: Observable<UserAuth>; - constructor(private http: HttpClient, private router: Router) { + constructor(private http: HttpClient) { this.userSubject = new BehaviorSubject<UserAuth>(JSON.parse(localStorage.getItem('user'))); this.user = this.userSubject.asObservable(); } diff --git a/src/app/services/notification.service.ts b/src/app/services/notification.service.ts index fcc0129dbccc1f3b20d7e7a779bf1dc3c74cbe5c..5b491fd86b82cb6e9a6905a8565f6adf543de8a7 100644 --- a/src/app/services/notification.service.ts +++ b/src/app/services/notification.service.ts @@ -7,14 +7,14 @@ import { ToastrService } from 'ngx-toastr'; export class NotificationService { constructor(private toastr: ToastrService) {} - public showSuccess(message: string, title: string, timespan: number = 10000): void { + public showSuccess(message: string, title: string = '', timespan: number = 10000): void { this.toastr.success(message, title, { timeOut: timespan, }); } // Par defaut, l'erreur reste affichée jusqu'à ce qu'on clique dessus - public showError(message: string, title: string, timespan: number = 0): void { + public showError(message: string, title: string = '', timespan: number = 0): void { this.toastr.error(message, title, { timeOut: timespan, disableTimeOut: timespan ? false : true, diff --git a/src/app/shared/components/button/button.component.scss b/src/app/shared/components/button/button.component.scss index 99a93e5ba4eb8700b4bbce130321fc207c8d8c89..844107305e8a912032e19720dee9ea4c03346258 100644 --- a/src/app/shared/components/button/button.component.scss +++ b/src/app/shared/components/button/button.component.scss @@ -121,6 +121,24 @@ button { color: $grey-1; } } + &.wide { + div:first-child { + min-width: 50px; + width: 184px; + } + } + &.ultrawide { + div:first-child { + min-width: 50px; + width: 400px; + @media #{$tablet} { + width: 200px; + } + span { + padding-inline: 4px; + } + } + } .small-text { height: 22px !important; } @@ -193,6 +211,10 @@ button { background: unset !important; } } + &.center { + padding-left: 6px !important; + padding-right: 6px !important; + } } .text { position: relative; diff --git a/src/assets/ico/sprite.svg b/src/assets/ico/sprite.svg index 73b3acd83be688b6ec0326936def7f30833f2b73..8179a30840fd9a3bbb465be1f3fead30d6f28a64 100644 --- a/src/assets/ico/sprite.svg +++ b/src/assets/ico/sprite.svg @@ -74,6 +74,34 @@ <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="removeCross" width="22" height="23" viewBox="0 0 22 23" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M6.5 7L15.5 16" stroke="#DA3635" stroke-width="1.5" stroke-linecap="round"/> +<path d="M15.5 7L6.5 16" stroke="#DA3635" stroke-width="1.5" stroke-linecap="round"/> +</symbol> + +<symbol id="removeCrossBlack" width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M6.5 6.5L15.5 15.5" stroke="#333333" stroke-width="1.5" stroke-linecap="round"/> +<path d="M15.5 6.5L6.5 15.5" stroke="#333333" stroke-width="1.5" stroke-linecap="round"/> +</symbol> + +<symbol id="deleteAccount" width="22" height="23" viewBox="0 0 22 23" fill="none" stroke="none" xmlns="http://www.w3.org/2000/svg"> +<g clip-path="url(#clip0_8047_46397)"> +<path d="M11 20.2498V18.1687C11 17.572 10.7629 16.9997 10.3409 16.5777C9.91899 16.1558 9.3467 15.9187 8.74997 15.9187H4.24999C3.65325 15.9187 3.08096 16.1558 2.65901 16.5777C2.23705 16.9997 2 17.572 2 18.1687V20.2498" stroke="#DA3635" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M10.0999 11.025C10.0999 13.0133 8.48811 14.625 6.49989 14.625C4.51167 14.625 2.8999 13.0133 2.8999 11.025C2.8999 9.03682 4.51167 7.42505 6.49989 7.42505C8.48811 7.42505 10.0999 9.03682 10.0999 11.025ZM8.74988 11.025C8.74988 12.2677 7.74253 13.275 6.49989 13.275C5.25726 13.275 4.2499 12.2677 4.2499 11.025C4.2499 9.7824 5.25726 8.77505 6.49989 8.77505C7.74253 8.77505 8.74988 9.7824 8.74988 11.025Z" fill="#DA3635"/> +<path d="M11.9746 4.52588L20.0259 12.5771" stroke="#DA3635" stroke-width="1.5" stroke-linecap="round"/> +<path d="M20.0254 4.52588L11.9741 12.5771" stroke="#DA3635" stroke-width="1.5" stroke-linecap="round"/> +</g> +<defs> +<clipPath id="clip0_8047_46397"> +<rect width="22" height="22" fill="white" transform="translate(0 0.5)"/> +</clipPath> +</defs> +</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"> <circle cx="7.75" cy="10.75" r="0.75" fill="white"/> <rect x="10" y="10" width="15" height="1.5" rx="0.75" fill="white"/> @@ -101,15 +129,28 @@ <path fill-rule="evenodd" clip-rule="evenodd" d="M6 2C5.44772 2 5 2.44772 5 3V4H3C2.44771 4 2 4.44772 2 5V19C2 19.5523 2.44771 20 3 20H19C19.5523 20 20 19.5523 20 19V5C20 4.44772 19.5523 4 19 4H17V3C17 2.44772 16.5523 2 16 2C15.4477 2 15 2.44772 15 3V4H7V3C7 2.44772 6.55228 2 6 2ZM4 9V18H18V9H4Z" fill="#333333"/> </symbol> -<symbol id ="email" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg"> +<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" stroke="none"/> </symbol> +<symbol id="emailOutline" width="23" height="23" viewBox="0 0 23 23" fill="none" xmlns="http://www.w3.org/2000/svg"> +<rect x="3.25" y="6.6875" width="16.5" height="11.6875" rx="3" stroke="#333333" stroke-width="1.5"/> +<path d="M5.5 9.5L10.1086 12.4261C11.1326 13.0762 12.4472 13.0458 13.44 12.3492L17.5 9.5" stroke="#333333" stroke-width="1.5" stroke-linecap="round"/> +</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="passwordOutline" width="22" height="23" viewBox="0 0 22 23" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M4.89602 9.5C4.40116 9.5 4 9.90116 4 10.396L4 19.104C4 19.5988 4.40116 20 4.89602 20H17.1043C17.5992 20 18.0003 19.5988 18.0003 19.104V10.396C18.0003 9.90116 17.5992 9.5 17.1043 9.5H4.89602Z" stroke="#333333" stroke-width="1.5"/> +<path d="M12.5382 13.844C12.5382 14.3364 12.2734 14.7669 11.8785 15.001L12.5382 17.4001H9.8501L10.5098 15.001C10.1149 14.7669 9.8501 14.3364 9.8501 13.844C9.8501 13.1017 10.4518 12.5 11.1941 12.5C11.9364 12.5 12.5382 13.1017 12.5382 13.844Z" fill="#333333"/> +<path d="M11 2.5C8.37665 2.5 6.25 4.62665 6.25 7.25V9.25H7.75V7.25C7.75 5.45507 9.20507 4 11 4C12.7949 4 14.25 5.45507 14.25 7.25V9.25H15.75V7.25C15.75 4.62665 13.6234 2.5 11 2.5Z" fill="#333333"/> +</symbol> + + <symbol id="pass" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg"> <path fill-rule="evenodd" clip-rule="evenodd" d="M1.55556 5C1.24873 5 1 5.24873 1 5.55556V14.4444C1 14.7513 1.24873 15 1.55556 15H20.4444C20.7513 15 21 14.7513 21 14.4444V5.55556C21 5.24873 20.7513 5 20.4444 5H1.55556ZM4.77222 8.76388C4.78333 8.77499 4.79722 8.78055 4.81389 8.78055H4.91667C4.93333 8.78055 4.94722 8.77499 4.95833 8.76388C4.96944 8.75277 4.97683 8.73888 4.98055 8.72221L5.03055 8.3861H5.43889L5.38889 8.72221C5.38705 8.73888 5.39167 8.75277 5.40278 8.76388C5.41389 8.77499 5.42778 8.78055 5.44444 8.78055H5.54722C5.56389 8.78055 5.57778 8.77499 5.58889 8.76388C5.6 8.75277 5.60739 8.73888 5.61111 8.72221L5.66111 8.3861H5.99444C6.01294 8.3861 6.02778 8.38055 6.03889 8.36943C6.05183 8.35832 6.05833 8.34349 6.05833 8.32499V8.23055C6.05833 8.21205 6.05183 8.19721 6.03889 8.1861C6.02778 8.17499 6.01294 8.16943 5.99444 8.16943H5.69444L5.76667 7.68055H6.06667C6.08517 7.68055 6.1 7.67499 6.11111 7.66388C6.12406 7.65277 6.13056 7.63793 6.13056 7.61943V7.52499C6.13056 7.50649 6.12406 7.49166 6.11111 7.48055C6.1 7.46943 6.08517 7.46388 6.06667 7.46388H5.79722L5.84722 7.12777C5.84905 7.1111 5.84444 7.09721 5.83333 7.0861C5.82222 7.07499 5.80833 7.06943 5.79167 7.06943H5.68889C5.67222 7.06943 5.65833 7.07499 5.64722 7.0861C5.63794 7.09721 5.6315 7.1111 5.62778 7.12777L5.57778 7.46388H5.16667L5.21667 7.12777C5.2185 7.1111 5.21389 7.09721 5.20278 7.0861C5.19167 7.07499 5.17778 7.06943 5.16111 7.06943H5.05833C5.04167 7.06943 5.02778 7.07499 5.01667 7.0861C5.00739 7.09721 5.00094 7.1111 4.99722 7.12777L4.94722 7.46388H4.60555C4.58705 7.46388 4.57222 7.46943 4.56111 7.48055C4.55 7.49166 4.54444 7.50649 4.54444 7.52499V7.61943C4.54444 7.63793 4.55 7.65277 4.56111 7.66388C4.57222 7.67499 4.58705 7.68055 4.60555 7.68055H4.91389L4.84167 8.16943H4.53333C4.51483 8.16943 4.5 8.17499 4.48889 8.1861C4.47778 8.19721 4.47222 8.21205 4.47222 8.23055V8.32499C4.47222 8.34349 4.47778 8.35832 4.48889 8.36943C4.5 8.38055 4.51483 8.3861 4.53333 8.3861H4.80833L4.75833 8.72221C4.7565 8.73888 4.76111 8.75277 4.77222 8.76388ZM6.34239 8.87221C6.3535 8.88332 6.3665 8.88888 6.38128 8.88888H6.52294C6.5415 8.88888 6.55628 8.88427 6.56739 8.87499C6.58039 8.86388 6.58872 8.85277 6.59239 8.84166L6.74517 8.44721H7.65628L7.80905 8.84166C7.81278 8.85277 7.82017 8.86388 7.83128 8.87499C7.84239 8.88427 7.85817 8.88888 7.8785 8.88888H8.02017C8.035 8.88888 8.04794 8.88332 8.05906 8.87221C8.07017 8.8611 8.07572 8.84816 8.07572 8.83332L8.07017 8.80277L7.38406 7.00555C7.36928 6.96482 7.33961 6.94443 7.29517 6.94443H7.10628C7.06183 6.94443 7.03222 6.96482 7.01739 7.00555L6.3285 8.80277C6.32667 8.80832 6.32572 8.81849 6.32572 8.83332C6.32572 8.84816 6.33128 8.8611 6.34239 8.87221ZM8.38939 8.87221C8.40239 8.88332 8.41811 8.88888 8.43661 8.88888H8.58383C8.60239 8.88888 8.61811 8.88332 8.63106 8.87221C8.64406 8.85927 8.6505 8.84349 8.6505 8.82499V8.13055H9.14217C9.35328 8.13055 9.51811 8.08054 9.63661 7.98054C9.757 7.87871 9.81717 7.73149 9.81717 7.53888C9.81717 7.34627 9.757 7.19904 9.63661 7.09721C9.51628 6.99538 9.35144 6.94443 9.14217 6.94443H8.43661C8.41811 6.94443 8.40239 6.95093 8.38939 6.96388C8.37828 6.97499 8.37272 6.99071 8.37272 7.0111V8.82499C8.37272 8.84349 8.37828 8.85927 8.38939 8.87221ZM10.5966 8.87221C10.6096 8.88332 10.6253 8.88888 10.6438 8.88888H10.7883C10.8068 8.88888 10.8226 8.88332 10.8355 8.87221C10.8485 8.85927 10.8549 8.84349 10.8549 8.82499V7.19166H11.3855C11.4041 7.19166 11.4198 7.1861 11.4327 7.17499C11.4457 7.16205 11.4522 7.14627 11.4522 7.12777V7.0111C11.4522 6.99071 11.4457 6.97499 11.4327 6.96388C11.4216 6.95093 11.4059 6.94443 11.3855 6.94443H10.0466C10.0281 6.94443 10.0124 6.95093 9.99939 6.96388C9.98828 6.97682 9.98272 6.9926 9.98272 7.0111V7.12777C9.98272 7.14627 9.98828 7.16205 9.99939 7.17499C10.0124 7.1861 10.0281 7.19166 10.0466 7.19166H10.5799V8.82499C10.5799 8.84349 10.5855 8.85927 10.5966 8.87221ZM11.7803 8.87221C11.7932 8.88332 11.8089 8.88888 11.8275 8.88888H11.9747C11.9932 8.88888 12.0081 8.88332 12.0192 8.87221C12.0321 8.85927 12.0386 8.84349 12.0386 8.82499V7.00832C12.0386 6.98982 12.0321 6.97499 12.0192 6.96388C12.0081 6.95093 11.9932 6.94443 11.9747 6.94443H11.8275C11.8089 6.94443 11.7932 6.95093 11.7803 6.96388C11.7692 6.97499 11.7636 6.98982 11.7636 7.00832V8.82499C11.7636 8.84349 11.7692 8.85927 11.7803 8.87221ZM12.6564 8.71666C12.7861 8.84999 12.975 8.91666 13.2231 8.91666C13.3879 8.91666 13.5278 8.88793 13.6426 8.83055C13.7574 8.77127 13.8444 8.69443 13.9037 8.59999C13.9629 8.50554 13.9953 8.40371 14.0009 8.29443C14.0028 8.27777 13.9972 8.26482 13.9842 8.25555C13.9731 8.24443 13.9592 8.23888 13.9426 8.23888H13.7898C13.7713 8.23888 13.7564 8.24349 13.7453 8.25277C13.7342 8.26204 13.7259 8.27871 13.7203 8.30277C13.6907 8.44166 13.6342 8.53982 13.5509 8.59721C13.4694 8.65277 13.3602 8.68055 13.2231 8.68055C12.9046 8.68055 12.7398 8.50371 12.7287 8.14999C12.7268 8.09627 12.7259 8.0176 12.7259 7.91388C12.7259 7.81016 12.7268 7.73332 12.7287 7.68332C12.7398 7.3296 12.9046 7.15277 13.2231 7.15277C13.3602 7.15277 13.4694 7.18149 13.5509 7.23888C13.6324 7.29443 13.6889 7.39166 13.7203 7.53055C13.7296 7.57316 13.7528 7.59443 13.7898 7.59443H13.9426C13.9574 7.59443 13.9703 7.58982 13.9814 7.58055C13.9944 7.56943 14.0009 7.55649 14.0009 7.54166V7.5361C13.9953 7.42871 13.9629 7.32777 13.9037 7.23332C13.8444 7.13888 13.7574 7.06293 13.6426 7.00555C13.5278 6.94627 13.3879 6.91666 13.2231 6.91666C12.9768 6.91666 12.7889 6.98427 12.6592 7.11943C12.5296 7.25277 12.4602 7.4361 12.4509 7.66943C12.4491 7.72127 12.4481 7.8046 12.4481 7.91943C12.4481 8.03238 12.4491 8.11388 12.4509 8.16388C12.4602 8.39904 12.5287 8.58332 12.6564 8.71666ZM4.25 11C4.11193 11 4 11.1119 4 11.25C4 11.3881 4.11193 11.5 4.25 11.5H12.7259C12.864 11.5 12.9759 11.3881 12.9759 11.25C12.9759 11.1119 12.864 11 12.7259 11H4.25ZM16.6144 11.9418H16.0068C16.0133 12.1185 16.0594 12.249 16.1451 12.3333C16.2321 12.4177 16.3749 12.4598 16.5735 12.4598C16.702 12.4598 16.8163 12.4398 16.9163 12.3996L17 12.9398C16.8273 12.9799 16.6566 13 16.4878 13C16.1373 13 15.8575 12.9063 15.6485 12.7189C15.4408 12.5315 15.3317 12.2724 15.3213 11.9418H15V11.6205H15.3213V11.3755H15V11.0562H15.3272C15.3518 10.7242 15.4726 10.4652 15.6894 10.2791C15.9062 10.093 16.1925 10 16.5482 10C16.6858 10 16.8364 10.0207 17 10.0622L16.9163 10.6044C16.815 10.5629 16.7066 10.5422 16.591 10.5422C16.4119 10.5422 16.2749 10.5843 16.1801 10.6687C16.0854 10.7517 16.0295 10.8809 16.0127 11.0562H16.6144V11.3755H16.0068V11.6205H16.6144V11.9418ZM9.12828 7.89721C9.2635 7.89721 9.36533 7.86666 9.43383 7.80554C9.50422 7.74443 9.53939 7.65554 9.53939 7.53888C9.53939 7.42221 9.50517 7.33332 9.43661 7.27221C9.36811 7.20927 9.26533 7.17777 9.12828 7.17777H8.64772V7.89721H9.12828ZM7.20072 7.22221L7.57572 8.20832H6.82572L7.20072 7.22221ZM5.54444 7.68055L5.47222 8.16943H5.06389L5.13611 7.68055H5.54444ZM19.75 6C19.6119 6 19.5 6.11193 19.5 6.25V6.75C19.5 6.88807 19.6119 7 19.75 7C19.8881 7 20 6.88807 20 6.75V6.25C20 6.11193 19.8881 6 19.75 6ZM19.5 8.25C19.5 8.11193 19.6119 8 19.75 8C19.8881 8 20 8.11193 20 8.25V10.75C20 10.8881 19.8881 11 19.75 11C19.6119 11 19.5 10.8881 19.5 10.75V8.25ZM19.75 12C19.6119 12 19.5 12.1119 19.5 12.25V13.75C19.5 13.8881 19.6119 14 19.75 14C19.8881 14 20 13.8881 20 13.75V12.25C20 12.1119 19.8881 12 19.75 12Z" fill="#333333"/> </symbol> diff --git a/src/styles.scss b/src/styles.scss index dbb591fe49746ead7841c8682ee72513473f91cf..e3ee3fc94f42cde9396798164eba3c3036bac200 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -93,6 +93,9 @@ a { } &.full-screen { min-height: calc(100vh - #{$header-height} - #{$footer-height}); + @media #{$tablet} { + min-height: calc(100vh - #{$header-height}); + } } } .section-container {