diff --git a/config/config-dev.json b/config/config-dev.json index 9308ae9e42a106a4b7a21fa2ffb36f679f8f4ea6..e154cc463bc6600168812652f7d706e0b587c0fb 100644 --- a/config/config-dev.json +++ b/config/config-dev.json @@ -4,5 +4,8 @@ }, "resources": { "url": "https://kong-dev.alpha.grandlyon.com/resources/" + }, + "mediaLibrary": { + "url": "https://kong-dev.alpha.grandlyon.com/media-library/" } -} \ No newline at end of file +} diff --git a/config/config-rec.json b/config/config-rec.json index a7de2abe2bba321edc2b665835b59d7146cd7e41..99666bacf0611bdb0dc575d1877ca8fd1161b41f 100644 --- a/config/config-rec.json +++ b/config/config-rec.json @@ -4,5 +4,8 @@ }, "resources": { "url": "https://kong-rec.alpha.grandlyon.com/resources/" + }, + "mediaLibrary": { + "url": "https://kong-rec.alpha.grandlyon.com/media-library/" } -} \ No newline at end of file +} diff --git a/src/app/app.component.html b/src/app/app.component.html index f752e2e749bef88d778a4a25316c122aa1536965..3043831d86a8dae809137666c003e22eeb3ad9cc 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,23 +1,23 @@ <div class="grid"> - <header class="main-header"> - <div class="hamburger" - (click)="sidebarOpened = !sidebarOpened" - [ngClass]="{'expanded': sidebarOpened}"> + <header class="main-header"> + <div class="hamburger" (click)="sidebarOpened = !sidebarOpened" [ngClass]="{'expanded': sidebarOpened}"> - </div> - <h4>Plateforme Data Admin</h4> - <div class="logo"> - <img src="assets/img/logo.svg" alt="Le Grand Lyon, la métropole"> + </div> + <h4>Plateforme Data Admin</h4> + <div class="logo"> + <img src="assets/img/logo.svg" alt="Le Grand Lyon, la métropole"> - </div> - </header> - <nav class="main-nav" [ngClass]="{'is-active': sidebarOpened}"> - <app-menu [expanded]="sidebarOpened"></app-menu> - </nav> - <div class="main-content" [ngClass]="{'wide': !sidebarOpened}"> - <router-outlet></router-outlet> + </div> + </header> + <nav class="main-nav" [ngClass]="{'is-active': sidebarOpened}"> + <app-menu [expanded]="sidebarOpened"></app-menu> + </nav> + <div class="main-content" [ngClass]="{'wide': !sidebarOpened}"> + <router-outlet></router-outlet> - </div> + </div> </div> + +<app-notifications></app-notifications> \ No newline at end of file diff --git a/src/app/components/image-upload/image-upload.component.html b/src/app/components/image-upload/image-upload.component.html new file mode 100644 index 0000000000000000000000000000000000000000..72e3400a5b9ffda47784a569bdcaca09f18d99ff --- /dev/null +++ b/src/app/components/image-upload/image-upload.component.html @@ -0,0 +1,11 @@ +<div class="field"> + <label class="label" [for]="fieldParams.inputName">{{ fieldParams.label }}</label> + <div class="image-preview-container" *ngIf="fieldParams.existingImageUrl && !removeImageBtnClicked"> + <button class="button" (click)="removeImage()"><i class="far fa-times-circle"></i></button> + <img [src]="fieldParams.existingImageUrl" alt="" class="image-preview"> + </div> + <div class="control"> + <input *ngIf="!fieldParams.existingImageUrl || removeImageBtnClicked" class="input" type="file" + [name]="fieldParams.inputName" [id]="fieldParams.inputName" (change)="setFile($event)"> + </div> +</div> \ No newline at end of file diff --git a/src/app/components/image-upload/image-upload.component.scss b/src/app/components/image-upload/image-upload.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..d4a9d5613206de3aa75a786ce5adcc2bb26cfa48 --- /dev/null +++ b/src/app/components/image-upload/image-upload.component.scss @@ -0,0 +1,17 @@ +.image-preview-container { + display: inline-block; + position: relative; + max-height: 5rem; +} + +button { + position: absolute; + top: 0; + right: 0; + border: none; + background: transparent; +} + +.field { + margin-bottom: 0.75rem; +} diff --git a/src/app/components/image-upload/image-upload.component.ts b/src/app/components/image-upload/image-upload.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..476ad0fffdc9e7b2634d8def54e4c4d9e4826780 --- /dev/null +++ b/src/app/components/image-upload/image-upload.component.ts @@ -0,0 +1,35 @@ +import { Component, OnInit, Input, EventEmitter, Output } from '@angular/core'; +import { IImageUploadFieldParams } from 'src/app/models/image-upload.model'; + +@Component({ + selector: 'app-image-upload', + templateUrl: './image-upload.component.html', + styleUrls: ['./image-upload.component.scss'] +}) +export class ImageUploadComponent implements OnInit { + + removeImageBtnClicked: boolean = false; + + @Input() fieldParams: IImageUploadFieldParams; + @Output() fileChanged = new EventEmitter<FileList>(); + @Output() imageRemoved = new EventEmitter<boolean>(); + + constructor() { } + + ngOnInit() { + } + + setFile(event) { + const files = event.srcElement.files; + if (!files) { + return; + } + + this.fileChanged.emit(files); + } + + removeImage() { + this.removeImageBtnClicked = true; + this.imageRemoved.emit(true); + } +} diff --git a/src/app/components/index.ts b/src/app/components/index.ts index 89665a7ff83dd6f5194d383a3563407781116bbd..5b89ff87ce224523dc22b61a682d4166c34024d6 100644 --- a/src/app/components/index.ts +++ b/src/app/components/index.ts @@ -12,6 +12,8 @@ import { CrudButtonsComponent } from './crud-buttons/crud-buttons.component'; import { FormatsComponent } from './formats/list/formats.component'; import { FormatDetailComponent } from './formats/detail/format-detail.component'; import { FormatFormComponent } from './formats/edit/format-form.component'; +import { ImageUploadComponent } from './image-upload/image-upload.component'; +import { NotificationsComponent } from './notifications/notifications.component'; export { MenuComponent, @@ -28,6 +30,8 @@ export { FormatsComponent, FormatDetailComponent, FormatFormComponent, + ImageUploadComponent, + NotificationsComponent, }; // tslint:disable-next-line:variable-name @@ -46,4 +50,6 @@ export const AppComponents = [ FormatsComponent, FormatDetailComponent, FormatFormComponent, + ImageUploadComponent, + NotificationsComponent, ]; diff --git a/src/app/components/notifications/notifications.component.html b/src/app/components/notifications/notifications.component.html new file mode 100644 index 0000000000000000000000000000000000000000..8c0b6dbd5b8e3ca86bb68010e18fbe0c8b448a92 --- /dev/null +++ b/src/app/components/notifications/notifications.component.html @@ -0,0 +1,21 @@ +<div *ngIf="notifications.length > 0" class="app-notifications"> + <div *ngFor="let notification of notifications; index as i" class="app-notification" [class.success]="notification.type === 'success'" + [class.warning]="notification.type === 'warning'" [class.error]="notification.type === 'error'"> + <ng-container [ngSwitch]="notification.type"> + <div class="app-notification-type" *ngSwitchCase="'success'"> + <img src="./assets/img/notif_ok.svg" alt="Ok icon"> + <span>Ok!</span> + </div> + <div class="app-notification-type" *ngSwitchCase="'warning'"> + <img src="./assets/img/notif_warning.svg" alt="Warning icon"> + <span i18n="@@notification.oops">Oops!</span> + </div> + <div class="app-notification-type" *ngSwitchCase="'error'"> + <img src="./assets/img/notif_error.svg" alt="Error icon"> + <span i18n="@@notification.ouch">Ouch!</span> + </div> + </ng-container> + <p class="app-notification-content">{{ notification.message }}</p> + <button class="close-button" (click)="hide(i)"><i class="fas fa-times"></i></button> + </div> +</div> diff --git a/src/app/components/notifications/notifications.component.scss b/src/app/components/notifications/notifications.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..2c212c343bd7e3ad437add33026feaba335856b9 --- /dev/null +++ b/src/app/components/notifications/notifications.component.scss @@ -0,0 +1,118 @@ +@import '../../../../node_modules/bulma/sass/utilities/_all.sass'; + +$app-notification-width: 29rem; + +.app-notifications { + position: fixed; + z-index: 10; + bottom: 0.5rem; + right: 0.5rem; + + @media screen and (max-width: $tablet) { + bottom: 0; + right: 0; + width: 100%; + } +} + +.app-notification { + position: relative; + min-width: $app-notification-width; + max-width: $app-notification-width; + padding: 1rem; + margin-top: 0.5rem; + border: 2px solid white; + border-radius: 4px; + + @media screen and (max-width: $tablet) { + min-width: 100%; + max-width: 100%; + } + + // Adding slideFromRight animation on notification classe + // The div come from the right and go back after a while + // The animation lasts 5s and is runned only one time + animation: slideFromRight 8s 1; + + @media screen and (max-width: $tablet) { + animation: slideFromRightMobile 8s 1; + } + + span, + p, + button { + color: white; + } + + div.app-notification-type { + display: flex; + margin-bottom: 0.75rem; + + img { + width: 1.125rem; + height: 1.125rem; + margin-right: 0.5rem; + } + + span { + font-size: 1rem; + font-weight: bold; + } + } + + p.app-notification-content { + font-size: 0.875rem; + } +} + +.success { + background-color: rgba(84, 180, 51, 0.9); +} + +.warning { + background-color: rgba(239, 114, 23, 0.9); +} + +.error { + background-color: rgba(217, 3, 3, 0.9); +} + +.close-button { + position: absolute; + top: 0; + right: 0; + border: none; + background-color: transparent; + cursor: pointer; +} + +// Animation +@keyframes slideFromRight { + 0% { + transform: translateX($app-notification-width); + } + + 15%, + 85% { + transform: translateX(0); + } + + 100% { + transform: translateX($app-notification-width); + } +} + +@keyframes slideFromRightMobile { + 0% { + transform: translateX(100%); + } + + 15%, + 85% { + transform: translateX(0); + } + + 100% { + transform: translateX(100%); + } +} diff --git a/src/app/components/notifications/notifications.component.ts b/src/app/components/notifications/notifications.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..cc9bf93d5aa81ec68fc9e804b58fb67ce506e34c --- /dev/null +++ b/src/app/components/notifications/notifications.component.ts @@ -0,0 +1,34 @@ +import { Component, OnInit } from '@angular/core'; +import { NotificationService } from '../../services'; +import { Notification } from '../../models/notification.model'; + +@Component({ + selector: 'app-notifications', + templateUrl: './notifications.component.html', + styleUrls: ['./notifications.component.scss'], +}) +export class NotificationsComponent implements OnInit { + notifications: Notification[] = []; + + constructor( + private _notificationService: NotificationService, + ) { } + + ngOnInit() { + this._notificationService.notification$.subscribe( + (notification) => { + if (notification) { + this.notifications.push(notification); + // Remove notification from array after 5 seconds + // IMPORTANT (if you change this value also change the value of the css animation) + setTimeout(() => this.notifications.shift(), 8000); + } + }, + ); + } + + hide(index) { + this.notifications.splice(index, 1); + } + +} diff --git a/src/app/components/organizations/detail/organization-detail.component.html b/src/app/components/organizations/detail/organization-detail.component.html index 75135cd0dd854c228f45256ec5028023803795e1..3d17686ee4cdaecaee0b39828686063c00b39507 100644 --- a/src/app/components/organizations/detail/organization-detail.component.html +++ b/src/app/components/organizations/detail/organization-detail.component.html @@ -13,7 +13,7 @@ </header> <div class="card-image"> <figure class="image"> - <img src="{{organization.logo}}" alt="Logo de l'organization"> + <img [src]="organization.logo" alt="Logo de l'organization"> </figure> </div> <div class="card-content"> @@ -49,4 +49,4 @@ </section> -</ng-container> +</ng-container> \ No newline at end of file diff --git a/src/app/components/organizations/edit/organization-form.component.html b/src/app/components/organizations/edit/organization-form.component.html index 6eebb451d147460c0d3a59c5d5dd77d835cbe82f..d00a3415c96010844093813d85249076880e9878 100644 --- a/src/app/components/organizations/edit/organization-form.component.html +++ b/src/app/components/organizations/edit/organization-form.component.html @@ -20,12 +20,9 @@ </div> </div> - <div class="field"> - <label class="label" for="logo">Logo</label> - <div class="control"> - <input class="input" type="text" [value]="organization.logo" formControlName="logo" id="logo"> - </div> - </div> + <app-image-upload [fieldParams]="logoFieldParams" (fileChanged)="logoChanged($event)" + (imageRemoved)="removeLogo()"> + </app-image-upload> <div class="field"> <label class="label required" for="description">Description</label> @@ -90,6 +87,4 @@ </div> </div> </form> - - -</ng-container> +</ng-container> \ No newline at end of file diff --git a/src/app/components/organizations/edit/organization-form.component.ts b/src/app/components/organizations/edit/organization-form.component.ts index 7c8e5e506f175a320dfb65e0d0e3c9011827a992..6952b5acf2765a670c81d8a822bc13353d8917a8 100644 --- a/src/app/components/organizations/edit/organization-form.component.ts +++ b/src/app/components/organizations/edit/organization-form.component.ts @@ -3,7 +3,9 @@ import { ActivatedRoute, ParamMap, Router } from '@angular/router'; import { Organization } from 'src/app/models/organization.model'; import { OrganizationService } from 'src/app/services/organization.service'; import { FormBuilder, FormGroup, Validators, FormArray } from '@angular/forms'; -import { tap, filter, switchMap } from 'rxjs/operators'; +import { filter, switchMap, mergeMap } from 'rxjs/operators'; +import { IImageUploadFieldParams } from 'src/app/models/image-upload.model'; +import { NotificationService } from 'src/app/services'; @Component({ selector: 'app-organization-form', @@ -14,23 +16,31 @@ export class OrganizationFormComponent implements OnInit { organization: Organization; form: FormGroup; + logoFile: File; + logoFieldParams: IImageUploadFieldParams = { + inputName: 'logo', + label: 'Logo', + existingImageUrl: null, + }; + logo: File; title: string; constructor( - private organizationService: OrganizationService, - private route: ActivatedRoute, - private router: Router, + private _organizationService: OrganizationService, + private _notificationService: NotificationService, + private _route: ActivatedRoute, + private _router: Router, private _fb: FormBuilder, ) { } ngOnInit() { - this.title = this.route.snapshot.data.title; + this.title = this._route.snapshot.data.title; this.initForm(); - this.route.paramMap.pipe( + this._route.paramMap.pipe( filter((paramMap: ParamMap) => (paramMap.get('id') !== null)), - switchMap((paramMap: ParamMap) => this.organizationService.findById(paramMap.get('id')))) + switchMap((paramMap: ParamMap) => this._organizationService.findById(paramMap.get('id')))) .subscribe((organization: Organization) => { this.organization = organization; @@ -43,16 +53,17 @@ export class OrganizationFormComponent implements OnInit { })); }); + this.logoFieldParams.existingImageUrl = organization.logo; + this.form = this._fb.group( { id: [this.organization.id], name: [organization.name, Validators.required], description: [organization.description, Validators.required], - logo: [organization.logo], elasticSearchName: [organization.elasticSearchName], + logo: [organization.logo], links: arr, }); - }); } @@ -60,13 +71,14 @@ export class OrganizationFormComponent implements OnInit { initForm() { this.organization = new Organization(); const arr = new FormArray([]); + this.logoFieldParams.existingImageUrl = null; this.form = this._fb.group( { id: [this.organization.id], name: [this.organization.name, Validators.required], description: [this.organization.description, Validators.required], - logo: [this.organization.logo], elasticSearchName: [this.organization.elasticSearchName], + logo: [this.organization.logo], links: arr, }); } @@ -102,17 +114,29 @@ export class OrganizationFormComponent implements OnInit { onSubmit() { if (!this.formInvalid) { this.organization = new Organization(this.form.value); - this.organizationService.replaceOrCreate(this.organization) - .subscribe( - (organizationCreated) => { - this.router.navigate(['/organizations', organizationCreated.id]) - .then(() => { - }); - }, - (err) => { - alert(err.message); - }, - ); + let action; + + if (this.logoFile) { + action = this._organizationService.uploadLogoAndSaveOrganization(this.logoFile, this.organization); + } else { + action = this._organizationService.replaceOrCreate(this.organization); + } + + action.subscribe( + (organizationCreated) => { + this._notificationService.notify({ + message: 'L\'organisation a été créée avec succès.', + type: 'success', + }); + this._router.navigate(['/organizations', organizationCreated.id]); + }, + () => { + this._notificationService.notify({ + message: 'Une erreur est survenue lors de la création de l\'organisation.', + type: 'error', + }); + }, + ); } } @@ -126,6 +150,17 @@ export class OrganizationFormComponent implements OnInit { } get formInvalid() { - return this.form.invalid; + return this.form.invalid || + (!this.organization.logo && !this.logoFile); + } + + logoChanged(fileList: FileList) { + if (fileList && fileList.length > 0) { + this.logoFile = fileList[0]; + } + } + + removeLogo() { + this.form.get('logo').setValue(null); } } diff --git a/src/app/models/image-upload.model.ts b/src/app/models/image-upload.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..0c4a768dc92888a6ef1f8ee5f8f0ceba92f32b4e --- /dev/null +++ b/src/app/models/image-upload.model.ts @@ -0,0 +1,5 @@ +export interface IImageUploadFieldParams { + label: string; + inputName: string; + existingImageUrl?: string; +} diff --git a/src/app/models/notification.model.ts b/src/app/models/notification.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..8fdf5fcff77594e5963c191719c2bc0990403e76 --- /dev/null +++ b/src/app/models/notification.model.ts @@ -0,0 +1,16 @@ +type errorType = 'error' | 'warning' | 'success'; + +export interface INotification { + message: string; + type: errorType; +} + +export class Notification { + message: string; + type: errorType; + + constructor(notification: INotification) { + this.message = notification.message; + this.type = notification.type; + } +} diff --git a/src/app/models/organization.model.ts b/src/app/models/organization.model.ts index 53adbd907ffd027f9924c0d4b9376bcae8364a02..54074cb77548abbd77d3452fe686f213512f5f73 100644 --- a/src/app/models/organization.model.ts +++ b/src/app/models/organization.model.ts @@ -8,17 +8,25 @@ export class Organization { constructor(organization?: IOrganization) { if (organization) { - this.id = organization.id; + if (organization.id) { + this.id = organization.id; + } this.name = organization.name; this.description = organization.description; this.elasticSearchName = organization.elasticSearchName; this.links = organization.links; + + this.links.forEach((link) => { + if (!link.id) { + delete link.id; + } + }); this.logo = organization.logo; } else { - this.name = ''; - this.description = ''; - this.logo = ''; - this.elasticSearchName = ''; + this.name = null; + this.description = null; + this.logo = null; + this.elasticSearchName = null; this.links = []; } diff --git a/src/app/services/app-config.service.ts b/src/app/services/app-config.service.ts index edabf90b5de6509faaa179008aaa0cac92a70606..66e9fd31fad98efbde56154a7c9f346e8cf1dcb4 100644 --- a/src/app/services/app-config.service.ts +++ b/src/app/services/app-config.service.ts @@ -7,6 +7,9 @@ export class AppConfig { resources: { url: string; }; + mediaLibrary: { + url: string; + }; } export let APP_CONFIG: AppConfig; diff --git a/src/app/services/index.ts b/src/app/services/index.ts index b72aa77e756069957b67a0b00e1b20cd1a4a1f36..34d4ee52b65425a4c8e071ecb732d74c314446ec 100644 --- a/src/app/services/index.ts +++ b/src/app/services/index.ts @@ -2,12 +2,14 @@ import { AppConfigService } from './app-config.service'; import { OrganizationService } from './organization.service'; import { ResourceService } from './resource.service'; import { FormatService } from './format.service'; +import { NotificationService } from './notification.service'; export { AppConfigService, OrganizationService, ResourceService, FormatService, + NotificationService, }; // tslint:disable-next-line:variable-name @@ -16,4 +18,5 @@ export const AppServices = [ OrganizationService, ResourceService, FormatService, + NotificationService, ]; diff --git a/src/app/services/notification.service.ts b/src/app/services/notification.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..7a1a532e14cd3da0e832564d46e40aa03f3f447b --- /dev/null +++ b/src/app/services/notification.service.ts @@ -0,0 +1,21 @@ + +import { Injectable } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; +import { Notification } from '../models/notification.model'; + +@Injectable() +export class NotificationService { + + private _notification: BehaviorSubject<Notification> = new BehaviorSubject(null); + + constructor() { } + + notify(notification: Notification) { + this._notification.next(notification); + } + + get notification$() { + return this._notification; + } + +} diff --git a/src/app/services/organization.service.ts b/src/app/services/organization.service.ts index 138d0bc91698fc2cfb13a3577609f7871dc6bb8c..d5a82d7ce9d00a5b3a3aeebd765a1a93fca73923 100644 --- a/src/app/services/organization.service.ts +++ b/src/app/services/organization.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { Observable, Subject } from 'rxjs'; -import { map } from 'rxjs/operators'; +import { map, mergeMap } from 'rxjs/operators'; import { HttpClient } from '@angular/common/http'; import { Organization, IOrganization, OrganizationRO } from '../models/organization.model'; import { APP_CONFIG } from './app-config.service'; @@ -53,6 +53,26 @@ export class OrganizationService { return this._httpClient.delete(APP_CONFIG.organizations.url + id); } + uploadLogoAndSaveOrganization(logoFile, organization) { + return this.uploadLogo(logoFile).pipe( + mergeMap((response: any) => { + organization.logo = response.mediaUrl; + return this.replaceOrCreate(organization); + }), + ) + } + + uploadLogo(logoFile): Observable<string> { + const formData = new FormData(); + formData.append('file', logoFile); + + return this._httpClient.post<string>(`${APP_CONFIG.mediaLibrary.url}media`, formData).pipe( + map((response) => { + return response; + }), + ); + } + replaceOrCreate(data: Organization): Observable<Organization> { if (data.id) { return this._httpClient.put<IOrganization>(APP_CONFIG.organizations.url + data.id, data).pipe( @@ -61,10 +81,7 @@ export class OrganizationService { }), ); } - delete data.id; - data.links.forEach((link) => { - delete link.id; - }); + return this._httpClient.post<IOrganization>(APP_CONFIG.organizations.url, data).pipe( map((response) => { return new Organization(response); diff --git a/src/assets/config/config.json b/src/assets/config/config.json index 9308ae9e42a106a4b7a21fa2ffb36f679f8f4ea6..43cecd099741db2447135e4a96bc1d43339ac48c 100644 --- a/src/assets/config/config.json +++ b/src/assets/config/config.json @@ -1,8 +1,11 @@ { "organizations": { - "url": "https://kong-dev.alpha.grandlyon.com/organizations/" + "url": "http://localhost:3000/organizations/" }, "resources": { - "url": "https://kong-dev.alpha.grandlyon.com/resources/" + "url": "http://localhost:3003/" + }, + "mediaLibrary": { + "url": "http://localhost:3006/" } -} \ No newline at end of file +} diff --git a/src/assets/img/notif_error.svg b/src/assets/img/notif_error.svg new file mode 100644 index 0000000000000000000000000000000000000000..7a66bc0ae16910cb1cb4a33a133b6837e2d6651d --- /dev/null +++ b/src/assets/img/notif_error.svg @@ -0,0 +1,11 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"> + <style> + .white { + fill: white; + } + </style> + <g id="picto_x5F_warning"> + <path d="M18.8 15.7c.2.3.2.7.2 1-.1.4-.2.6-.5.9-.3.2-.6.4-1 .4H2.6c-.4 0-.7-.1-1-.4-.3-.2-.4-.5-.5-.9-.1-.3 0-.7.2-1L8.8 2.8c.2-.3.4-.5.7-.7.4-.1.7-.1 1 0 .4.1.6.3.8.7l7.5 12.9zm-8.7-3c-.4 0-.7.1-1 .4-.3.3-.4.6-.4 1s.1.7.4 1c.3.3.6.4 1 .4s.7-.1 1-.4c.3-.3.4-.6.4-1s-.1-.7-.4-1c-.3-.2-.6-.4-1-.4zM8.8 8.1L9 12c0 .1 0 .2.1.2.1.1.2.1.3.1h1.4c.1 0 .2 0 .2-.1.1-.1.1-.2.1-.2l.2-3.9c0-.1 0-.2-.1-.3-.1-.1-.1-.1-.2-.1H9.2c-.1 0-.2 0-.3.1-.1.1-.1.2-.1.3z" class="white"/> + <path d="M18.8 15.7c.2.3.2.7.2 1-.1.4-.2.6-.5.9-.3.2-.6.4-1 .4H2.6c-.4 0-.7-.1-1-.4-.3-.2-.4-.5-.5-.9-.1-.3 0-.7.2-1L8.8 2.8c.2-.3.4-.5.7-.7.4-.1.7-.1 1 0 .4.1.6.3.8.7l7.5 12.9zm-8.7-3c-.4 0-.7.1-1 .4-.3.3-.4.6-.4 1s.1.7.4 1c.3.3.6.4 1 .4s.7-.1 1-.4c.3-.3.4-.6.4-1s-.1-.7-.4-1c-.3-.2-.6-.4-1-.4zM8.8 8.1L9 12c0 .1 0 .2.1.2.1.1.2.1.3.1h1.4c.1 0 .2 0 .2-.1.1-.1.1-.2.1-.2l.2-3.9c0-.1 0-.2-.1-.3-.1-.1-.1-.1-.2-.1H9.2c-.1 0-.2 0-.3.1-.1.1-.1.2-.1.3z" class="white"/> + </g> +</svg> diff --git a/src/assets/img/notif_ok.svg b/src/assets/img/notif_ok.svg new file mode 100644 index 0000000000000000000000000000000000000000..d7d07dc1eb579c1765df18d638422344bb73e8b9 --- /dev/null +++ b/src/assets/img/notif_ok.svg @@ -0,0 +1,11 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"> + <style> + .white { + fill: white; + } + </style> + <g id="picto_x5F_ok"> + <path d="M18.8 10.1c0 1.6-.4 3.1-1.2 4.4-.8 1.4-1.9 2.4-3.2 3.2-1.4.9-2.9 1.3-4.5 1.3s-3.1-.4-4.4-1.2c-1.4-.8-2.4-1.9-3.3-3.2-.8-1.4-1.2-2.8-1.2-4.4 0-1.6.4-3.1 1.2-4.4.8-1.4 1.9-2.4 3.3-3.2 1.4-.8 2.8-1.2 4.4-1.2 1.6 0 3.1.4 4.4 1.2 1.4.8 2.4 1.9 3.2 3.2.9 1.2 1.3 2.7 1.3 4.3zm-9.9 4.7l6.6-6.6c.1-.1.2-.2.2-.4s-.1-.3-.2-.4l-.8-.8c-.1-.1-.3-.2-.4-.2-.1 0-.3.1-.4.2L8.5 12 6 9.5c-.1-.1-.3-.2-.4-.2-.1 0-.3.1-.4.2l-.8.8c-.1.1-.2.2-.2.4s.1.3.2.4l3.7 3.7c.1.1.2.2.4.2s.3-.1.4-.2z" class="white"/> + <path d="M18.8 10.1c0 1.6-.4 3.1-1.2 4.4-.8 1.4-1.9 2.4-3.2 3.2-1.4.9-2.9 1.3-4.5 1.3s-3.1-.4-4.4-1.2c-1.4-.8-2.4-1.9-3.3-3.2-.8-1.4-1.2-2.8-1.2-4.4 0-1.6.4-3.1 1.2-4.4.8-1.4 1.9-2.4 3.3-3.2 1.4-.8 2.8-1.2 4.4-1.2 1.6 0 3.1.4 4.4 1.2 1.4.8 2.4 1.9 3.2 3.2.9 1.2 1.3 2.7 1.3 4.3zm-9.9 4.7l6.6-6.6c.1-.1.2-.2.2-.4s-.1-.3-.2-.4l-.8-.8c-.1-.1-.3-.2-.4-.2-.1 0-.3.1-.4.2L8.5 12 6 9.5c-.1-.1-.3-.2-.4-.2-.1 0-.3.1-.4.2l-.8.8c-.1.1-.2.2-.2.4s.1.3.2.4l3.7 3.7c.1.1.2.2.4.2s.3-.1.4-.2z" class="white"/> + </g> +</svg> diff --git a/src/assets/img/notif_warning.svg b/src/assets/img/notif_warning.svg new file mode 100644 index 0000000000000000000000000000000000000000..f544dba648d1f2479de8a24a70cfa85f3ba2f283 --- /dev/null +++ b/src/assets/img/notif_warning.svg @@ -0,0 +1,11 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"> + <style> + .white { + fill: white; + } + </style> + <g id="picto_x5F_oops"> + <path d="M17 11.3l1.6.9c.1.1.2.1.2.2s.1.2 0 .3c-.4 1.3-1.1 2.5-2.1 3.5-.1.1-.2.1-.3.2h-.3l-1.6-.9c-.7.6-1.4 1-2.3 1.3v1.8c0 .1 0 .2-.1.3 0 .1-.1.2-.2.2-1.4.3-2.7.3-4.1 0-.1 0-.2-.1-.3-.2 0-.1-.1-.2-.1-.3v-1.8c-.8-.3-1.6-.7-2.3-1.3l-1.6.9c-.1.1-.2.1-.3 0-.1 0-.2-.1-.3-.2-.9-1-1.6-2.2-2.1-3.5v-.3c0-.1.1-.2.2-.2l1.6-.9c-.1-.9-.1-1.8 0-2.6l-1.4-1-.2-.2v-.3c.4-1.3 1.1-2.5 2.1-3.5.1-.1.2-.1.3-.2h.3l1.6.9c.7-.6 1.4-1 2.3-1.3V1.3c0-.1 0-.2.1-.3.1-.1.2-.2.3-.2 1.4-.3 2.7-.3 4.1 0 .1 0 .2.1.2.2.1.1.1.2.1.3v1.8c.8.3 1.6.7 2.3 1.3l1.6-.9c.1-.1.2-.1.3 0 .1 0 .2.1.3.2.9 1 1.6 2.2 2.1 3.5v.3c0 .1-.1.2-.2.2l-1.8.9c.1.9.1 1.8 0 2.7zM13.1 10c0-.9-.3-1.6-1-2.3-.6-.6-1.4-1-2.3-1-.8 0-1.6.3-2.3 1-.6.6-.9 1.4-.9 2.3 0 .9.3 1.7.9 2.3.6.6 1.4.9 2.3.9.9 0 1.6-.3 2.3-.9.7-.7 1-1.5 1-2.3z" class="white"/> + <path d="M17 11.3l1.6.9c.1.1.2.1.2.2s.1.2 0 .3c-.4 1.3-1.1 2.5-2.1 3.5-.1.1-.2.1-.3.2h-.3l-1.6-.9c-.7.6-1.4 1-2.3 1.3v1.8c0 .1 0 .2-.1.3 0 .1-.1.2-.2.2-1.4.3-2.7.3-4.1 0-.1 0-.2-.1-.3-.2 0-.1-.1-.2-.1-.3v-1.8c-.8-.3-1.6-.7-2.3-1.3l-1.6.9c-.1.1-.2.1-.3 0-.1 0-.2-.1-.3-.2-.9-1-1.6-2.2-2.1-3.5v-.3c0-.1.1-.2.2-.2l1.6-.9c-.1-.9-.1-1.8 0-2.6l-1.4-1-.2-.2v-.3c.4-1.3 1.1-2.5 2.1-3.5.1-.1.2-.1.3-.2h.3l1.6.9c.7-.6 1.4-1 2.3-1.3V1.3c0-.1 0-.2.1-.3.1-.1.2-.2.3-.2 1.4-.3 2.7-.3 4.1 0 .1 0 .2.1.2.2.1.1.1.2.1.3v1.8c.8.3 1.6.7 2.3 1.3l1.6-.9c.1-.1.2-.1.3 0 .1 0 .2.1.3.2.9 1 1.6 2.2 2.1 3.5v.3c0 .1-.1.2-.2.2l-1.8.9c.1.9.1 1.8 0 2.7zM13.1 10c0-.9-.3-1.6-1-2.3-.6-.6-1.4-1-2.3-1-.8 0-1.6.3-2.3 1-.6.6-.9 1.4-.9 2.3 0 .9.3 1.7.9 2.3.6.6 1.4.9 2.3.9.9 0 1.6-.3 2.3-.9.7-.7 1-1.5 1-2.3z" class="white"/> + </g> +</svg>