Merge branch 'features/routing_improvments' into 'develop'

refacto routing

See merge request framasoft/framadate/funky-framadate-front!43
This commit is contained in:
seraph_ino 2020-06-26 18:24:04 +02:00
commit 1fb8fa2b44
34 changed files with 746 additions and 952 deletions

View File

@ -1,9 +1,8 @@
{
"owners": [
{ "id": 1, "email": "toto@gafam.com", "pseudo": "TOTO" },
{ "id": 2, "email": "titi@gafam.com", "pseudo": "TITI" }
{ "id": 1, "email": "titi@gafam.com", "pseudo": "TITI", "role": "REGISTERED" },
{ "id": 2, "email": "toto@gafam.com", "pseudo": "TOTO", "role": "REGISTERED" }
],
"voters": [{ "pseudo": "TOTO", "polls": [] }],
"polls": [
{
"id": 1,

View File

@ -1,6 +1,7 @@
{
"/api/v1/*": "/$1",
"/owners/:email/": "/owners?email=:email",
"/polls": "/polls?_expand=owner&_embed=choices&_embed=comments",
"/polls/:slug": "/polls?slug=:slug&_expand=owner&_embed=choices&_embed=comments",
"/polls/:slug/choices": "/choices?pollSlug=:slug",
"/polls/:slug/comments": "/comments?pollSlug=:slug"

View File

@ -7,6 +7,10 @@ import { PageNotFoundComponent } from './shared/components/page-not-found/page-n
const routes: Routes = [
{ path: '', component: HomeComponent },
{
path: 'user',
loadChildren: () => import('./features/user-profile/user-profile.module').then((m) => m.UserProfileModule),
},
{
path: 'administration',
loadChildren: () =>
@ -14,12 +18,18 @@ const routes: Routes = [
resolve: { poll: PollService },
},
{
path: 'consultation',
path: 'poll/:slug/administration',
loadChildren: () =>
import('./features/administration/administration.module').then((m) => m.AdministrationModule),
resolve: { poll: PollService },
},
{
path: 'poll/:slug/consultation',
loadChildren: () => import('./features/consultation/consultation.module').then((m) => m.ConsultationModule),
resolve: { poll: PollService },
},
{
path: 'participation',
path: 'poll/:slug/participation',
loadChildren: () => import('./features/participation/participation.module').then((m) => m.ParticipationModule),
resolve: { poll: PollService },
},
@ -27,8 +37,8 @@ const routes: Routes = [
path: 'oldstuff',
loadChildren: () => import('./features/old-stuff/old-stuff.module').then((m) => m.OldStuffModule),
},
{ path: '**', component: PageNotFoundComponent },
{ path: 'page-not-found', component: PageNotFoundComponent },
{ path: '**', redirectTo: 'page-not-found', pathMatch: 'full' },
];
@NgModule({

View File

@ -4,11 +4,9 @@ import { Subscription } from 'rxjs';
import { environment } from '../environments/environment';
import { Theme } from './core/enums/theme.enum';
import { UserRole } from './core/enums/user-role.enum';
import { User } from './core/models/user.model';
import { LanguageService } from './core/services/language.service';
import { MockingService } from './core/services/mocking.service';
import { ThemeService } from './core/services/theme.service';
import { MockingService } from './core/services/mocking.service';
@Component({
selector: 'app-root',
@ -32,7 +30,7 @@ export class AppComponent implements OnInit, OnDestroy {
if (!environment.production) {
this.appTitle += ' [DEV]';
// TODO: to be removed
this.mockingService.loadUser(new User('TOTO', 'toto@gafam.com', [], UserRole.REGISTERED));
this.mockingService.init();
}
this.titleService.setTitle(this.appTitle);
this.languageService.configureAndInitTranslations();
@ -41,7 +39,7 @@ export class AppComponent implements OnInit, OnDestroy {
case Theme.DARK:
this.themeClass = 'theme-dark-crystal';
break;
case Theme.RED:
case Theme.CONTRAST:
this.themeClass = 'theme-hot-covid';
break;
default:

View File

@ -1,52 +0,0 @@
<nav class="navbar" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<a class="navbar-item" role="button" (click)="toggleSidebarOpening()"> Dev menu </a>
<a
role="button"
class="navbar-burger burger"
aria-label="menu"
aria-expanded="false"
data-target="navbarBasicExample"
>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div id="navbarBasicExample" class="navbar-menu">
<div class="navbar-start">
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link"> Modules </a>
<div class="navbar-dropdown">
<a class="navbar-item" routerLink="oldstuff" routerLinkActive="is-active">
Old stuff
</a>
<a class="navbar-item" routerLink="administration" routerLinkActive="is-active">
Administration
</a>
<a class="navbar-item" routerLink="consultation" routerLinkActive="is-active">
Consultation
</a>
<a class="navbar-item" routerLink="participation" routerLinkActive="is-active">
Participation
</a>
</div>
</div>
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link"> Tous les sondages </a>
<div class="navbar-dropdown">
<a
class="navbar-item"
*ngFor="let slug of slugsAvailables"
routerLink="{{ '/consultation/poll/' + slug }}"
routerLinkActive="is-active"
>
« {{ slug }} »
</a>
</div>
</div>
</div>
</div>
</nav>

View File

@ -1,24 +0,0 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { DevNavbarComponent } from './dev-navbar.component';
describe('DevNavbarComponent', () => {
let component: DevNavbarComponent;
let fixture: ComponentFixture<DevNavbarComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [DevNavbarComponent],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DevNavbarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,35 +0,0 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Observable } from 'rxjs';
import { User } from '../../models/user.model';
import { ApiService } from '../../services/api.service';
import { UserService } from '../../services/user.service';
@Component({
selector: 'app-dev-navbar',
templateUrl: './dev-navbar.component.html',
styleUrls: ['./dev-navbar.component.scss'],
})
export class DevNavbarComponent implements OnInit {
@Input() isSidebarOpened: boolean;
@Output() toggleSidebarEE = new EventEmitter<boolean>();
public _user: Observable<User> = this.userService.user;
public slugsAvailables: string[] = [];
constructor(private apiService: ApiService, private userService: UserService) {}
public ngOnInit(): void {
this.getSlugs();
}
public async getSlugs(): Promise<void> {
this.slugsAvailables = await this.apiService.getAllPollsSlugs();
}
public toggleSidebarOpening(): void {
this.isSidebarOpened = !this.isSidebarOpened;
this.toggleSidebarEE.emit(this.isSidebarOpened);
}
}

View File

@ -21,7 +21,7 @@
<a class="navbar-item" routerLink="administration" routerLinkActive="is-active">
Créer un sondage
</a>
<a class="navbar-item" routerLink="administration/user-polls" routerLinkActive="is-active">
<a class="navbar-item" routerLink="user/polls" routerLinkActive="is-active">
Mes sondages
</a>
</div>

View File

@ -17,7 +17,7 @@
<h2 class="subtitle">
Où sont mes sondages?
</h2>
<a role="button" class="button is-fullwidth is-primary" routerLink="administration/user-polls">
<a role="button" class="button is-fullwidth is-primary" routerLink="user/polls">
Mes sondages
</a>
</div>

View File

@ -2,16 +2,16 @@
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link"> Tous les sondages </a>
<div class="navbar-dropdown">
<a class="navbar-item" routerLink="/consultation/poll/inexistentPoll" routerLinkActive="is-active">
<a class="navbar-item" routerLink="/poll/inexistentPoll/consultation" routerLinkActive="is-active">
« inexistentPoll »
</a>
<a
class="navbar-item"
*ngFor="let slug of slugsAvailables"
routerLink="{{ '/consultation/poll/' + slug }}"
*ngFor="let poll of _pollsAvailables | async"
routerLink="{{ '/poll/' + poll.slug + '/consultation' }}"
routerLinkActive="is-active"
>
« {{ slug }} »
« {{ poll.slug }} »
</a>
</div>
</div>

View File

@ -1,7 +1,8 @@
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { ApiService } from '../../../services/api.service';
import { UserService } from '../../../services/user.service';
import { Poll } from '../../../models/poll.model';
import { MockingService } from '../../../services/mocking.service';
@Component({
selector: 'app-navigation',
@ -9,15 +10,9 @@ import { UserService } from '../../../services/user.service';
styleUrls: ['./navigation.component.scss'],
})
export class NavigationComponent implements OnInit {
public slugsAvailables: string[] = [];
public _pollsAvailables: Observable<Poll[]> = this.mockingService.pollsAvailables;
constructor(private apiService: ApiService, private userService: UserService) {}
constructor(private mockingService: MockingService) {}
public ngOnInit(): void {
this.getSlugs();
}
public async getSlugs(): Promise<void> {
this.slugsAvailables = await this.apiService.getAllPollsSlugs();
}
public ngOnInit(): void {}
}

View File

@ -10,17 +10,9 @@ import { HomeComponent } from './components/home/home.component';
import { LogoComponent } from './components/logo/logo.component';
import { NavigationComponent } from './components/sibebar/navigation/navigation.component';
import { throwIfAlreadyLoaded } from './guards/module-import.guard';
import { DevNavbarComponent } from './components/dev-navbar/dev-navbar.component';
@NgModule({
declarations: [
FooterComponent,
HeaderComponent,
HomeComponent,
LogoComponent,
NavigationComponent,
DevNavbarComponent,
],
declarations: [FooterComponent, HeaderComponent, HomeComponent, LogoComponent, NavigationComponent],
imports: [CommonModule, FormsModule, RouterModule, TranslateModule],
exports: [HeaderComponent, FooterComponent, NavigationComponent, LogoComponent],
})

View File

@ -1,6 +0,0 @@
export enum MessageSeverity {
SUCCESS = 'success',
INFO = 'info',
WARN = 'warn',
ERROR = 'error',
}

View File

@ -1,5 +1,5 @@
export enum Theme {
LIGHT = 'LIGHT',
DARK = 'DARK',
RED = 'RED',
CONTRAST = 'CONTRAST',
}

View File

@ -1,5 +0,0 @@
export enum WorkflowStep {
DESCRIPTION = 'DESCRIPTION',
CHOICES = 'CHOICES',
CONFIGURATION = 'CONFIGURATION',
}

View File

@ -64,11 +64,11 @@ export class ApiService {
//////////
// READ //
//////////
public async getAllPollsSlugs(): Promise<string[]> {
public async getAllAvailablePolls(): Promise<Poll[]> {
// TODO: used for facilities in DEV, should be removed in production
try {
const response: AxiosResponse<Poll[]> = await this.axiosInstance.get<Poll[]>(`${this.pollsEndpoint}`);
return response?.data.map((poll: Poll) => poll.slug);
return response?.data;
} catch (error) {
this.handleError(error);
}
@ -79,7 +79,7 @@ export class ApiService {
try {
// TODO: this interceptor should be avoided if backends returns the good object
const adapterInterceptor: number = this.axiosInstance.interceptors.response.use(
(response): AxiosResponse => {
(response: AxiosResponse): AxiosResponse => {
if (response.data['poll']) {
// response from cipherbliss backend, actually used by oldstuffModule
response.data = response.data['poll'];

View File

@ -1,9 +1,11 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { UserRole } from '../enums/user-role.enum';
import { Choice } from '../models/choice.model';
import { Poll } from '../models/poll.model';
import { User } from '../models/user.model';
import { PollService } from './poll.service';
import { ApiService } from './api.service';
import { UserService } from './user.service';
import { UuidService } from './uuid.service';
@ -11,31 +13,43 @@ import { UuidService } from './uuid.service';
providedIn: 'root',
})
export class MockingService {
private user: User;
public pollsDatabase: Poll[] = [];
private _pollsAvailables: BehaviorSubject<Poll[]> = new BehaviorSubject<Poll[]>([]);
public readonly pollsAvailables: Observable<Poll[]> = this._pollsAvailables.asObservable();
private poll1: Poll = new Poll(this.user, undefined, 'Quand le picnic ?', 'Pour faire la teuf');
private poll2: Poll = new Poll(this.user, undefined, 'On fait quoi à la soirée ?', 'Balancez vos idées');
constructor(private apiService: ApiService, private userService: UserService, private uuidService: UuidService) {}
constructor(private userService: UserService, private pollService: PollService, private uuidService: UuidService) {
this.poll1.slug = this.uuidService.getUUID();
this.poll2.slug = this.uuidService.getUUID();
this.poll1.choices = [new Choice('mardi prochain'), new Choice('mercredi')];
this.poll2.choices = [new Choice('jeux'), new Choice('danser'), new Choice('discuter en picolant')];
public async init(): Promise<void> {
const pollsAvailable = await this.apiService.getAllAvailablePolls();
this._pollsAvailables.next(pollsAvailable);
this.pollsDatabase = [this.poll1, this.poll2];
if (this._pollsAvailables.getValue() && this._pollsAvailables.getValue().length > 0) {
// arbitrary choose first owner available
const currentUser = this._pollsAvailables.getValue()[0].owner;
currentUser.polls = [this._pollsAvailables.getValue()[0]];
this.userService.updateUser(currentUser);
} else {
this.loadMock();
}
}
public loadUser(user: User): void {
this.user = user;
this.user.polls = this.pollsDatabase;
console.info('MOCKING user', { user: this.user });
this.userService.updateUser(this.user);
}
public loadMock(): void {
const owner = new User('TOTO', 'toto@gafam.com', [], UserRole.REGISTERED);
public loadPoll(slug: string): void {
this.poll1.slug = slug;
console.info('MOCKING poll', { poll: this.poll1 });
this.pollService.updateCurrentPoll(this.poll1);
const poll1: Poll = new Poll(owner, this.uuidService.getUUID(), 'Quand le picnic ?', 'Pour faire la teuf');
const poll2: Poll = new Poll(
owner,
this.uuidService.getUUID(),
'On fait quoi à la soirée ?',
'Balancez vos idées'
);
poll1.choices = [new Choice('mardi prochain'), new Choice('mercredi')];
poll2.choices = [new Choice('jeux'), new Choice('danser'), new Choice('discuter en picolant')];
this._pollsAvailables.next([poll1, poll2]);
owner.polls = [poll1, poll2];
this.userService.updateUser(owner);
console.info('MOCKING user', { user: owner });
}
}

View File

@ -3,12 +3,13 @@ import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@a
import { BehaviorSubject, Observable } from 'rxjs';
import { Answer } from '../enums/answer.enum';
import { MessageSeverity } from '../enums/message-severity.enum';
import { Choice } from '../models/choice.model';
import { Poll } from '../models/poll.model';
import { User } from '../models/user.model';
import { ApiService } from './api.service';
import { ToastService } from './toast.service';
import { UserService } from './user.service';
import { UuidService } from './uuid.service';
@Injectable({
providedIn: 'root',
@ -17,13 +18,22 @@ export class PollService implements Resolve<Poll> {
private _poll: BehaviorSubject<Poll | undefined> = new BehaviorSubject<Poll | undefined>(undefined);
public readonly poll: Observable<Poll | undefined> = this._poll.asObservable();
constructor(private router: Router, private apiService: ApiService, private toastService: ToastService) {}
constructor(
private router: Router,
private apiService: ApiService,
private userService: UserService,
private uuidService: UuidService,
private toastService: ToastService
) {}
public async resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<Poll> {
const wantedSlug: string = state.url.split('/poll/')[1];
const segments: string[] = state.url.split('/');
const wantedSlug: string = segments.includes('poll') ? segments[segments.indexOf('poll') + 1] : '';
if (!wantedSlug && state.url.includes('administration')) {
// creation of new poll
return;
const poll = new Poll(this.userService.getCurrentUser(), this.uuidService.getUUID(), '');
this._poll.next(poll);
this.router.navigate(['poll/' + poll.slug + '/administration']);
}
if (!this._poll.getValue() || !this._poll.getValue().slug || this._poll.getValue().slug !== wantedSlug) {
await this.loadPollBySlug(wantedSlug);
@ -38,7 +48,7 @@ export class PollService implements Resolve<Poll> {
public async loadPollBySlug(slug: string): Promise<void> {
const poll: Poll | undefined = await this.apiService.getPollBySlug(slug);
console.log({ fetchedResponse: poll });
console.log({ loadPollBySlugResponse: poll });
this.updateCurrentPoll(poll);
}
@ -61,15 +71,12 @@ export class PollService implements Resolve<Poll> {
currentPoll.choices.find((c) => c.label === choice.label)?.updateParticipation(user, response);
this.updateCurrentPoll(currentPoll);
this.apiService.createParticipation(currentPoll.slug, choice.label, user.pseudo, response);
this.toastService.display(MessageSeverity.SUCCESS, 'Votre participation au sondage a été enregistrée.');
this.toastService.display('Votre participation au sondage a été enregistrée.');
}
public async deleteAllAnswers(): Promise<void> {
await this.apiService.deletePollAnswers(this._poll.getValue().slug);
this.toastService.display(
MessageSeverity.SUCCESS,
'Les participations des votants à ce sondage ont été supprimées.'
);
this.toastService.display('Les participations des votants à ce sondage ont été supprimées.');
}
public async addComment(comment: string): Promise<void> {

View File

@ -20,8 +20,12 @@ export class UserService {
this._user.next(user);
}
public getCurrentUser(): User {
return this._user.getValue();
}
public isCurrentUserIdentifiable(): boolean {
return this._user.getValue().pseudo ? true : false;
return this._user.getValue()?.pseudo ? true : false;
}
public async getUserPolls(): Promise<void> {

View File

@ -1,16 +0,0 @@
import { TestBed } from '@angular/core/testing';
import { WorkflowService } from './workflow.service';
describe('WorkflowService', () => {
let service: WorkflowService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(WorkflowService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -1,30 +0,0 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { WorkflowStep } from '../enums/workflow-step.enum';
@Injectable({
providedIn: 'root',
})
export class WorkflowService {
private steps = [WorkflowStep.DESCRIPTION, WorkflowStep.CHOICES, WorkflowStep.CONFIGURATION];
private _currentStep: BehaviorSubject<WorkflowStep> = new BehaviorSubject<WorkflowStep>(WorkflowStep[0]);
public readonly currentStep: Observable<WorkflowStep> = this._currentStep.asObservable();
constructor() {}
public loadNextStep(): void {
this._currentStep.next(WorkflowStep[this.getNewIndex(1)]);
}
public loadPriorStep(): void {
this._currentStep.next(WorkflowStep[this.getNewIndex(-1)]);
}
private getNewIndex(way: number): number {
const currentIndex: number = Object.keys(WorkflowStep).indexOf(this._currentStep.getValue());
const newIndex: number = currentIndex + way;
return 0 <= newIndex && newIndex <= Object.keys(WorkflowStep).length ? newIndex : currentIndex;
}
}

View File

@ -2,15 +2,8 @@ import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AdministrationComponent } from './administration.component';
import { UserPollsComponent } from './user-polls/user-polls.component';
const routes: Routes = [
{ path: '', redirectTo: 'poll', pathMatch: 'full' },
{ path: ':slug', redirectTo: 'poll/:slug', pathMatch: 'full' },
{ path: 'poll', component: AdministrationComponent },
{ path: 'poll/:slug', component: AdministrationComponent },
{ path: 'user-polls', component: UserPollsComponent, pathMatch: 'full' },
];
const routes: Routes = [{ path: '', component: AdministrationComponent }];
@NgModule({
imports: [RouterModule.forChild(routes)],

View File

@ -7,10 +7,9 @@ import { SharedModule } from '../../shared/shared.module';
import { AdministrationRoutingModule } from './administration-routing.module';
import { AdministrationComponent } from './administration.component';
import { StepperComponent } from './stepper/stepper.component';
import { UserPollsComponent } from './user-polls/user-polls.component';
@NgModule({
declarations: [AdministrationComponent, StepperComponent, UserPollsComponent],
declarations: [AdministrationComponent, StepperComponent],
imports: [
AdministrationRoutingModule,
CommonModule,

View File

@ -4,10 +4,7 @@ import { RouterModule, Routes } from '@angular/router';
import { ConsultationComponent } from './consultation.component';
const routes: Routes = [
{ path: '', redirectTo: 'poll', pathMatch: 'full' },
{ path: ':slug', redirectTo: 'poll/:slug', pathMatch: 'full' },
{ path: 'poll', component: ConsultationComponent },
{ path: 'poll/:slug', component: ConsultationComponent },
{ path: '', component: ConsultationComponent }
];
@NgModule({

View File

@ -27,10 +27,10 @@
</div>
</div>
<footer class="card-footer" *ngIf="!isArchived">
<a routerLink="{{ '../../../participation/poll/' + poll.slug }}" class="card-footer-item">
<a routerLink="{{ '../../../poll/' + poll.slug + '/participation' }}" class="card-footer-item">
Participer
</a>
<a routerLink="{{ '../../../administration/poll/' + poll.slug }}" class="card-footer-item">
<a routerLink="{{ '../../../poll/' + poll.slug + '/administration' }}" class="card-footer-item">
Administrer
</a>
</footer>

View File

@ -4,8 +4,7 @@ import { RouterModule, Routes } from '@angular/router';
import { ParticipationComponent } from './participation.component';
const routes: Routes = [
{ path: ':slug', redirectTo: 'poll/:slug', pathMatch: 'full' },
{ path: 'poll/:slug', component: ParticipationComponent },
{ path: '', component: ParticipationComponent },
];
@NgModule({

View File

@ -13,7 +13,7 @@
<tr *ngFor="let poll of (_user | async)?.polls">
<th>{{ poll.question }}</th>
<td>
<a routerLink="{{ '/administration/edit/description/' + poll.slug }}">
<a routerLink="{{ '../../poll/' + poll.slug + '/consultation' }}">
{{ poll.slug }}
</a>
</td>

View File

@ -0,0 +1,12 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { UserPollsComponent } from './user-polls/user-polls.component';
const routes: Routes = [{ path: 'polls', component: UserPollsComponent }];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class UserProfileRoutingModule {}

View File

@ -0,0 +1,13 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { TranslateModule } from '@ngx-translate/core';
import { SharedModule } from '../../shared/shared.module';
import { UserPollsComponent } from './user-polls/user-polls.component';
import { UserProfileRoutingModule } from './user-profile-routing.module';
@NgModule({
declarations: [UserPollsComponent],
imports: [CommonModule, UserProfileRoutingModule, SharedModule, TranslateModule.forChild({ extend: true })],
})
export class UserProfileModule {}

1313
yarn.lock

File diff suppressed because it is too large Load Diff