diff --git a/src/app/admin/admin.module.ts b/src/app/admin/admin.module.ts index 05b87149d0291f9799d6b13cd69d2a8277c301ad..e808d3c4971dad47e41c3a81a0fbc01397ba86c5 100644 --- a/src/app/admin/admin.module.ts +++ b/src/app/admin/admin.module.ts @@ -4,9 +4,11 @@ import { PanelComponent } from './components/panel/panel.component'; import { ClaimStructureComponent } from './components/claim-structure/claim-structure.component'; import { DeleteUserComponent } from './components/delete-user/delete-user.component'; import { SharedModule } from '../shared/shared.module'; +import { NewsComponent } from '../post/news.component'; +import { NewsletterUsersComponent } from './components/newsletter-users/newsletter-users.component'; @NgModule({ - declarations: [PanelComponent, ClaimStructureComponent, DeleteUserComponent], + declarations: [PanelComponent, ClaimStructureComponent, DeleteUserComponent, NewsletterUsersComponent], imports: [CommonModule, SharedModule], }) export class AdminModule {} diff --git a/src/app/admin/components/delete-user/delete-user.component.scss b/src/app/admin/components/delete-user/delete-user.component.scss deleted file mode 100644 index 39b7a07412c002ae97dc36db12c0bbee2304561b..0000000000000000000000000000000000000000 --- a/src/app/admin/components/delete-user/delete-user.component.scss +++ /dev/null @@ -1,8 +0,0 @@ -.userList { - max-width: 50%; -} - -.userBlock { - max-width: 50%; - margin: 0 auto; -} \ No newline at end of file diff --git a/src/app/admin/components/delete-user/delete-user.component.ts b/src/app/admin/components/delete-user/delete-user.component.ts index 00e5c943bc5b000a1ee540b2efa020a7dfa1e174..293324fe75e915db8f30a2c891097a7f6a0508ed 100644 --- a/src/app/admin/components/delete-user/delete-user.component.ts +++ b/src/app/admin/components/delete-user/delete-user.component.ts @@ -6,7 +6,6 @@ import { AdminService } from '../../services/admin.service'; @Component({ selector: 'app-admin-delete-user', templateUrl: './delete-user.component.html', - styleUrls: ['./delete-user.component.scss'], }) export class DeleteUserComponent { public users: User[]; diff --git a/src/app/admin/components/newsletter-users/newsletter-users.component.html b/src/app/admin/components/newsletter-users/newsletter-users.component.html new file mode 100644 index 0000000000000000000000000000000000000000..8a82446d753d0b266676efa1087e45c29e4eb07f --- /dev/null +++ b/src/app/admin/components/newsletter-users/newsletter-users.component.html @@ -0,0 +1,25 @@ +<div fxLayout="column" fxLayoutGap="5px" fxLayoutAlign="center center" class="userBlock"> + <h2>Gestion abonnement newsletter</h2> + <div fxLayout="row"> + <button (click)="copySubscription()">Copier les adresses dans le presse papier</button> + </div> + <div fxLayout="row"> + <input #searchstring (keyup)="(0)" /> + <button (click)="searchSubscribedEmail(searchstring.value)">Rechercher</button> + </div> + <div class="userList"> + <tr *ngFor="let subscription of subscriptions"> + <td>{{ subscription.email }}</td> + <td> + <button (click)="toggleUnsubscribeModal(subscription.email)">Désabonner</button> + </td> + </tr> + <app-modal-confirmation + *ngIf="emailToUnsubscribe" + [openned]="deleteModalOpenned" + [content]="'Voulez-vous vraiment supprimer ' + emailToUnsubscribe + ' de la newsletter ?'" + (closed)="unsubscribeEmail(emailToUnsubscribe, $event)" + ></app-modal-confirmation> + <div *ngIf="subscriptions && subscriptions.length == 0">Aucun résultat</div> + </div> +</div> diff --git a/src/app/admin/components/newsletter-users/newsletter-users.component.spec.ts b/src/app/admin/components/newsletter-users/newsletter-users.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..119aadb28d6b6e3661c3f11edc834287a58f4b04 --- /dev/null +++ b/src/app/admin/components/newsletter-users/newsletter-users.component.spec.ts @@ -0,0 +1,27 @@ +import { HttpClientModule } from '@angular/common/http'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NewsletterUsersComponent } from './newsletter-users.component'; + +describe('DeleteUserComponent', () => { + let component: NewsletterUsersComponent; + let fixture: ComponentFixture<NewsletterUsersComponent>; + let USERS; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [HttpClientModule], + declarations: [NewsletterUsersComponent], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(NewsletterUsersComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/admin/components/newsletter-users/newsletter-users.component.ts b/src/app/admin/components/newsletter-users/newsletter-users.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..66290faaedf3612e9c0447c7badc173be669e5e7 --- /dev/null +++ b/src/app/admin/components/newsletter-users/newsletter-users.component.ts @@ -0,0 +1,48 @@ +import { Component } from '@angular/core'; +import { NewsletterSubscription } from '../../../models/subscription-model'; +import { AdminService } from '../../services/admin.service'; + +@Component({ + selector: 'app-admin-newsletter-users', + templateUrl: './newsletter-users.component.html', +}) +export class NewsletterUsersComponent { + public subscriptions: NewsletterSubscription[]; + public deleteModalOpenned = false; + public emailToUnsubscribe: string = null; + + constructor(private adminService: AdminService) {} + + public toggleUnsubscribeModal(emailToUnsubscribe: string): void { + this.emailToUnsubscribe = emailToUnsubscribe; + this.deleteModalOpenned = !this.deleteModalOpenned; + } + + public searchSubscribedEmail(searchString: string): void { + this.adminService.searchNewsletterSubscriptions(searchString).subscribe((emails) => { + this.subscriptions = emails; + }); + } + + public unsubscribeEmail(email: string, shouldUnsubscribe: boolean): void { + this.toggleUnsubscribeModal(email); + if (shouldUnsubscribe) { + this.adminService.unsubscribeEmail(email).subscribe((data) => { + this.subscriptions = this.subscriptions.filter((obj) => obj.email !== email); + }); + } + } + + public copySubscription(): void { + this.adminService.searchNewsletterSubscriptions('').subscribe((emails) => { + let emailsToBeCopied = emails.map((e) => e.email).join(';'); + let copyElement = document.createElement('textarea'); + copyElement.textContent = decodeURI(emailsToBeCopied); + let body = document.getElementsByTagName('body')[0]; + body.appendChild(copyElement); + copyElement.select(); + document.execCommand('copy'); + body.removeChild(copyElement); + }); + } +} diff --git a/src/app/admin/components/panel/panel.component.html b/src/app/admin/components/panel/panel.component.html index 252986fc67f2330751bd82a7fc4ce17b0587be40..1ee88741e970a24fbb62650211d6764856c4c633 100644 --- a/src/app/admin/components/panel/panel.component.html +++ b/src/app/admin/components/panel/panel.component.html @@ -3,6 +3,7 @@ <div fxLayout="row" fxLayoutGap="20px" fxLayoutAlign="center center"> <button (click)="changeActiveFeature(features.pendingStructures)">Gestion structure</button> <button (click)="changeActiveFeature(features.deleteUsers)">Suppression d'utilisateurs</button> + <button (click)="changeActiveFeature(features.newsletterUsers)">Désabonnement newsletter</button> <a target="_blank" class="custom-link" rel="noopener noreferrer" [href]="ghostLink">Ghost</a> </div> <div *ngIf="selectedFeature === features.deleteUsers"> @@ -11,4 +12,8 @@ <div *ngIf="selectedFeature === features.pendingStructures"> <app-admin-claim-structure></app-admin-claim-structure> </div> + <div *ngIf="selectedFeature === features.newsletterUsers"> + <app-admin-newsletter-users></app-admin-newsletter-users> + </div> + </div> diff --git a/src/app/admin/services/admin.service.ts b/src/app/admin/services/admin.service.ts index adc590b8d4be2bdaf8d796b01bf639d92a2bff6a..d71c89ef915ce6f05befd85c676539249929d86f 100644 --- a/src/app/admin/services/admin.service.ts +++ b/src/app/admin/services/admin.service.ts @@ -1,6 +1,7 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; +import { NewsletterSubscription } from '../../models/subscription-model'; import { User } from '../../models/user.model'; import { DemandAttachment } from '../models/demandAttachment.model'; @@ -28,6 +29,14 @@ export class AdminService { return this.http.delete<User>(`api/admin/user/` + id); } + public searchNewsletterSubscriptions(searchString: string): Observable<NewsletterSubscription[]> { + return this.http.post<NewsletterSubscription[]>(`api/admin/searchNewsletterSubscriptions`, { searchString }); + } + + public unsubscribeEmail(email: string): Observable<string> { + return this.http.delete<string>(`api/admin/newsletterSubscription/` + email); + } + public acceptStructureClaim( userEmail: string, structureId: number, diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index a67351971c8528651b8f25fa4829faeaacc8f4b6..46da7c87ef69b04b71c36246a7a194a920dddc3d 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -16,6 +16,7 @@ import { StructureJoinComponent } from './structure-join/structure-join.componen import { StructureDetailsComponent } from './structure-list/components/structure-details/structure-details.component'; import { StructureListComponent } from './structure-list/structure-list.component'; import { UserVerificationComponent } from './user-verification/user-verification.component'; +import { NewsletterSubscriptionComponent } from './newsletter-subscription/newsletter-subscription.component'; const routes: Routes = [ { path: 'print', outlet: 'print', children: [{ path: 'structure', component: StructureDetailsComponent }] }, @@ -79,6 +80,16 @@ const routes: Routes = [ component: FormComponent, canDeactivate: [DeactivateGuard], }, + { + path: 'newsletter', + component: NewsletterSubscriptionComponent, + }, + + { + path: 'newsletter-unsubscribe', + component: NewsletterSubscriptionComponent, + }, + { path: 'news', loadChildren: () => import('./post/post.module').then((m) => m.PostModule), diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 1aebde8468f4160f61c91cdf7b89c5a23e8bd71b..960ec879326b557d58d9bf86c4cec2792149f238 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -33,6 +33,7 @@ import { FooterFormComponent } from './form/footer-form/footer-form.component'; import { TempUserResolver } from './resolvers/temp-user.resolver'; import { StructureJoinComponent } from './structure-join/structure-join.component'; import { RouterListenerService } from './services/routerListener.service'; +import { NewsletterSubscriptionComponent } from './newsletter-subscription/newsletter-subscription.component'; @NgModule({ declarations: [ @@ -54,6 +55,7 @@ import { RouterListenerService } from './services/routerListener.service'; FormComponent, FooterFormComponent, StructureJoinComponent, + NewsletterSubscriptionComponent, ], imports: [BrowserModule, HttpClientModule, AppRoutingModule, SharedModule, MapModule, ProfileModule, AdminModule], providers: [ diff --git a/src/app/footer/footer.component.html b/src/app/footer/footer.component.html index 6cc2485c7cfcd89480be637c69891033a4d0ec25..b6de162784a7e1fde622c48d205e021c4cb1f83e 100644 --- a/src/app/footer/footer.component.html +++ b/src/app/footer/footer.component.html @@ -1,6 +1,7 @@ <div class="footer" fxLayout="row" fxLayoutAlign="center"> <div fxLayout="row"> <a class="clickable text-align-center" routerLink="/legal-notice" i18n>Mentions légales</a> + <a class="clickable text-align-center" routerLink="/newsletter" i18n>Newsletter</a> <!-- <a class="clickable text-align-center" routerLink="/sitemap" i18n>Plan du site</a> --> <a class="clickable text-align-center" href="mailto:inclusionnumerique@grandlyon.com">Contact</a> </div> diff --git a/src/app/form/form.component.html b/src/app/form/form.component.html index d0d588e89a7dc52ec0d3f87914ee0db1adc6d22e..e464ce3ba2e0f77c933abef7634df0ddb75e9e03 100644 --- a/src/app/form/form.component.html +++ b/src/app/form/form.component.html @@ -1074,6 +1074,15 @@ (checkEvent)="acceptDataBeSaved($event)" > </app-checkbox-form> + <div class="title"> + <h3>Acceptez-vous de recevoir des mails d'informations de la part de Res'in ?</h3> + </div> + <app-checkbox-form + [isChecked]="userAcceptNewsletter" + [text]="'J\'accepte'" + (checkEvent)="acceptReceiveNewsletter($event)" + > + </app-checkbox-form> <p class="informationEndForm"> <span class="asterisk">*</span> Les informations recueillies sont enregistrées dans un fichier par la Métropole de Lyon en vue de l'animation du réseau des acteurs de la médiation numérique. Elles sont conservées pendant 24 diff --git a/src/app/form/form.component.ts b/src/app/form/form.component.ts index 10a41c2d986c5a2136138165722d78117f7c2340..aeb849541730701ff077f2ad72434bdca93dbd4a 100644 --- a/src/app/form/form.component.ts +++ b/src/app/form/form.component.ts @@ -20,6 +20,7 @@ import { PageTypeEnum } from './pageType.enum'; import { CustomRegExp } from '../utils/CustomRegExp'; import { StructureWithOwners } from '../models/structureWithOwners.model'; import { RouterListenerService } from '../services/routerListener.service'; +import { NewsletterService } from '../services/newsletter.service'; const { DateTime } = require('luxon'); @Component({ selector: 'app-structureForm', @@ -66,6 +67,7 @@ export class FormComponent implements OnInit { public isShowConfirmPassword = false; public isShowPassword = false; public userAcceptSavedDate = false; + public userAcceptNewsletter = false; public showMenu = false; public isEditMode = false; public isClaimMode = false; @@ -82,7 +84,8 @@ export class FormComponent implements OnInit { private authService: AuthService, private router: Router, private route: ActivatedRoute, - private routerListener: RouterListenerService + private routerListener: RouterListenerService, + private newsletterService: NewsletterService ) {} async ngOnInit(): Promise<void> { @@ -574,6 +577,9 @@ export class FormComponent implements OnInit { const user = new User(this.accountForm.value); // Create user and claim structure this.authService.register(user).subscribe(() => { + if (this.userAcceptNewsletter) { + this.newsletterService.newsletterSubscribe(user.email).subscribe(() => {}); + } // If joinMode, send join request, if not send claim request; if (this.isJoinMode) { this.structureService.joinStructure(this.claimStructure._id, user.email).subscribe(() => { @@ -824,6 +830,10 @@ export class FormComponent implements OnInit { this.setValidationsForm(); } + public acceptReceiveNewsletter(isAccepted: boolean): void { + this.userAcceptNewsletter = isAccepted; + } + public validateForm(): void { if (this.structureForm.valid && this.hoursForm.valid) { let structure: Structure = this.structureForm.value; @@ -839,6 +849,9 @@ export class FormComponent implements OnInit { user = this.profile; structure.accountVerified = true; this.createStructure(structure, user); + if (this.userAcceptNewsletter) { + this.newsletterService.newsletterSubscribe(user.email).subscribe(() => {}); + } } else { if (this.accountForm.valid) { user = new User(this.accountForm.value); @@ -848,6 +861,9 @@ export class FormComponent implements OnInit { .subscribe(() => { this.createStructure(structure, user); }); + if (this.userAcceptNewsletter) { + this.newsletterService.newsletterSubscribe(user.email).subscribe(() => {}); + } } } } diff --git a/src/app/models/subscription-model.ts b/src/app/models/subscription-model.ts new file mode 100644 index 0000000000000000000000000000000000000000..4657f29d6146e18d2cebfa766cf8cdb79d239d5d --- /dev/null +++ b/src/app/models/subscription-model.ts @@ -0,0 +1,4 @@ +export class NewsletterSubscription { + email: string; + _id: string; +} diff --git a/src/app/newsletter-subscription/newsletter-subscription.component.html b/src/app/newsletter-subscription/newsletter-subscription.component.html new file mode 100644 index 0000000000000000000000000000000000000000..445ca0a46c135213cc43a8b18684bb19590015e1 --- /dev/null +++ b/src/app/newsletter-subscription/newsletter-subscription.component.html @@ -0,0 +1,34 @@ +<div fxLayout="column" class="content-container full-screen"> + <div class="section-container" fxLayout="column" fxLayoutAlign="center center"> + <div class="subscriptionForm"> + <div *ngIf="subscriptionMod"><h1>Inscription à la newsletter</h1></div> + <div *ngIf="!subscriptionMod"><h1>Désinscription de la newsletter</h1></div> + <form [formGroup]="subscriptionForm" (ngSubmit)="onSubmit()"> + <div class="form-group"> + <label for="email">Courriel personnel</label> + <div fxLayout="row" fxLayoutGap="13px"> + <input + type="email" + autocomplete="on" + formControlName="email" + class="form-input" + [ngClass]="{ 'is-invalid': submitted && f.email.errors }" + /> + </div> + <div *ngIf="submitted && f.email.errors" class="invalid-feedback"> + <div *ngIf="f.email.errors.required">L'adresse e-mail est requise</div> + <div *ngIf="f.email.errors.pattern">L'adresse e-mail doit être valide</div> + </div> + </div> + <div class="button" fxLayout="row" fxLayoutAlign="space-around center"> + <a routerLink="../home" class="btn btn-link">Annuler</a> + <button [disabled]="loading" class="btn btn-primary"> + <span *ngIf="loading" class="spinner-border spinner-border-sm mr-1"></span> + <div *ngIf="subscriptionMod">S'inscrire</div> + <div *ngIf="!subscriptionMod">Se désinscrire</div> + </button> + </div> + </form> + </div> + </div> +</div> diff --git a/src/app/newsletter-subscription/newsletter-subscription.component.scss b/src/app/newsletter-subscription/newsletter-subscription.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..01eb52d269d84dfb4fee639684ee68dc31238235 --- /dev/null +++ b/src/app/newsletter-subscription/newsletter-subscription.component.scss @@ -0,0 +1,18 @@ +@import '../../assets/scss/color'; + +.subscriptionPasswordForm { + max-width: 500px; +} + +.button { + margin-top: 20px; +} + +.form-input { + background-color: white; + width: 100%; +} + +.existingEmail { + color: $orange-warning; +} diff --git a/src/app/newsletter-subscription/newsletter-subscription.component.spec.ts b/src/app/newsletter-subscription/newsletter-subscription.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..a39ffc1691cdc2f51f1490ed9e76499ab502e91c --- /dev/null +++ b/src/app/newsletter-subscription/newsletter-subscription.component.spec.ts @@ -0,0 +1,28 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ReactiveFormsModule } from '@angular/forms'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { NewsletterSubscriptionComponent } from './newsletter-subscription.component'; + +describe('ResetPasswordComponent', () => { + let component: NewsletterSubscriptionComponent; + let fixture: ComponentFixture<NewsletterSubscriptionComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [NewsletterSubscriptionComponent], + imports: [ReactiveFormsModule, HttpClientTestingModule, RouterTestingModule], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(NewsletterSubscriptionComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/newsletter-subscription/newsletter-subscription.component.ts b/src/app/newsletter-subscription/newsletter-subscription.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..2239fc70fefcccdc9d08b6c995f926de767a3e5a --- /dev/null +++ b/src/app/newsletter-subscription/newsletter-subscription.component.ts @@ -0,0 +1,67 @@ +import { Component, OnInit } from '@angular/core'; +import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { Router } from '@angular/router'; +import { NewsletterService } from '../services/newsletter.service'; +import { CustomRegExp } from '../utils/CustomRegExp'; + +@Component({ + selector: 'app-newsletter-subscription', + templateUrl: './newsletter-subscription.component.html', + styleUrls: ['./newsletter-subscription.component.scss'], +}) +export class NewsletterSubscriptionComponent implements OnInit { + public subscriptionForm: FormGroup; + public loading = false; + public submitted = false; + public subscriptionFailed = false; + public subscriptionMod: boolean; + + constructor(private formBuilder: FormBuilder, private newsletterService: NewsletterService, private router: Router) {} + + ngOnInit(): void { + this.subscriptionForm = this.formBuilder.group({ + email: ['', [Validators.required, Validators.pattern(CustomRegExp.EMAIL)]], + }); + if (this.router.url === '/newsletter') { + this.subscriptionMod = true; + } + if (this.router.url === '/newsletter-unsubscription') { + this.subscriptionMod = false; + } + } + + get f(): { [key: string]: AbstractControl } { + return this.subscriptionForm.controls; + } + + public onSubmit(): void { + this.submitted = true; + + if (this.subscriptionForm.invalid) { + return; + } + this.loading = true; + if (this.subscriptionMod) { + this.newsletterService.newsletterSubscribe(this.f.email.value).subscribe( + () => { + this.router.navigate(['']); + }, + () => { + this.loading = false; + this.subscriptionFailed = true; + } + ); + } + if (!this.subscriptionMod) { + this.newsletterService.newsletterUnsubscribe(this.f.email.value).subscribe( + () => { + this.router.navigate(['']); + }, + () => { + this.loading = false; + this.subscriptionFailed = true; + } + ); + } + } +} diff --git a/src/app/services/newsletter.service.ts b/src/app/services/newsletter.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..d90bd565d283f5939eb974d53c11e218697066e7 --- /dev/null +++ b/src/app/services/newsletter.service.ts @@ -0,0 +1,18 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable, of } from 'rxjs'; + +@Injectable({ + providedIn: 'root', +}) +export class NewsletterService { + constructor(private http: HttpClient) {} + + public newsletterSubscribe(email: string): Observable<any> { + return this.http.post('/api/newsletter/subscribe', {email}); + } + + public newsletterUnsubscribe(email: string): Observable<any> { + return this.http.post('/api/newsletter/unsubscribe', {email}); + } +} diff --git a/src/app/shared/enum/adminPanel.enum.ts b/src/app/shared/enum/adminPanel.enum.ts index 39dbc605f5c246238e3378dd4d14de15c4d2ae24..293c64c4d6156c8833bd2f77e1df8b3d470a2656 100644 --- a/src/app/shared/enum/adminPanel.enum.ts +++ b/src/app/shared/enum/adminPanel.enum.ts @@ -1,4 +1,5 @@ export enum AdminPannelEnum { deleteUsers, pendingStructures, + newsletterUsers } diff --git a/src/styles.scss b/src/styles.scss index 08c7d2ff6b3547358ec0db4353e58d8d6a20559b..97e279390d3472b96b1b5a997a4c0516261303ac 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -269,3 +269,12 @@ button { .no-margin { margin: 0 !important; } + +.userList { + max-width: 50%; +} + +.userBlock { + max-width: 50%; + margin: 0 auto; +}