Browse Source

add user settings button & modal

develop
seraph 2 years ago
parent
commit
355fed53f3
  1. 1
      angular.json
  2. 5
      package.json
  3. 2
      src/app/app-routing.module.ts
  4. 7
      src/app/app.component.ts
  5. 30
      src/app/core/components/header/header.component.html
  6. 15
      src/app/core/components/header/header.component.ts
  7. 1
      src/app/core/components/login/login.component.html
  8. 12
      src/app/core/components/login/login.component.ts
  9. 10
      src/app/core/components/selectors/language-selector/language-selector.component.html
  10. 38
      src/app/core/components/selectors/language-selector/language-selector.component.ts
  11. 24
      src/app/core/components/selectors/theme-selector/theme-selector.component.html
  12. 16
      src/app/core/core.module.ts
  13. 4
      src/app/core/enums/answer-granularity.enum.ts
  14. 2
      src/app/core/enums/response.enum.ts
  15. 2
      src/app/core/enums/user-role.enum.ts
  16. 2
      src/app/core/enums/workflow-step.enum.ts
  17. 5
      src/app/core/interfaces/date-options.interface.ts
  18. 6
      src/app/core/models/answer.model.ts
  19. 5
      src/app/core/models/choice.model.ts
  20. 3
      src/app/core/models/comment.model.ts
  21. 26
      src/app/core/models/configuration.model.ts
  22. 13
      src/app/core/models/poll-config.model.ts
  23. 9
      src/app/core/models/poll-options.model.ts
  24. 26
      src/app/core/models/poll.model.ts
  25. 3
      src/app/core/models/question.model.ts
  26. 6
      src/app/core/models/user.model.ts
  27. 31
      src/app/core/services/api.service.ts
  28. 16
      src/app/core/services/language.service.spec.ts
  29. 66
      src/app/core/services/language.service.ts
  30. 16
      src/app/core/services/loader.service.spec.ts
  31. 14
      src/app/core/services/loader.service.ts
  32. 31
      src/app/core/services/mocking.service.ts
  33. 16
      src/app/core/services/modal.service.spec.ts
  34. 15
      src/app/core/services/modal.service.ts
  35. 5
      src/app/core/services/poll.service.ts
  36. 3
      src/app/core/services/storage.service.ts
  37. 16
      src/app/core/services/url.service.spec.ts
  38. 23
      src/app/core/services/url.service.ts
  39. 11
      src/app/core/services/user.service.ts
  40. 2
      src/app/core/services/workflow.service.ts
  41. 31
      src/app/core/utils/date-utils.service.ts
  42. 8
      src/app/features/administration/edit-description/edit-description.component.ts
  43. 8
      src/app/features/administration/poll-edit/poll-edit.component.ts
  44. 8
      src/app/features/administration/profile/profile.component.html
  45. 7
      src/app/features/administration/profile/profile.component.ts
  46. 2
      src/app/features/administration/stepper/stepper.component.ts
  47. 4
      src/app/features/old-stuff/old-stuff-routing.module.ts
  48. 4
      src/app/features/old-stuff/pages/create-or-retrieve/create-or-retrieve.component.html
  49. 2
      src/app/features/old-stuff/pages/voting/voting-navigation/voting-navigation.component.html
  50. 4
      src/app/features/old-stuff/pages/voting/voting-summary/voting-summary.component.html
  51. 10
      src/app/features/participation/participation-routing.module.ts
  52. 33
      src/app/features/participation/participation.component.html
  53. 26
      src/app/features/participation/participation.component.ts
  54. 3
      src/app/features/participation/participation.module.ts
  55. 1
      src/app/features/participation/poll-comment/poll-comment.component.html
  56. 0
      src/app/features/participation/poll-comment/poll-comment.component.scss
  57. 24
      src/app/features/participation/poll-comment/poll-comment.component.spec.ts
  58. 12
      src/app/features/participation/poll-comment/poll-comment.component.ts
  59. 16
      src/app/features/participation/poll/poll.component.html
  60. 22
      src/app/features/participation/poll/poll.component.ts
  61. 14
      src/app/shared/components/selectors/language-selector/language-selector.component.html
  62. 0
      src/app/shared/components/selectors/language-selector/language-selector.component.scss
  63. 0
      src/app/shared/components/selectors/language-selector/language-selector.component.spec.ts
  64. 27
      src/app/shared/components/selectors/language-selector/language-selector.component.ts
  65. 30
      src/app/shared/components/selectors/theme-selector/theme-selector.component.html
  66. 0
      src/app/shared/components/selectors/theme-selector/theme-selector.component.scss
  67. 0
      src/app/shared/components/selectors/theme-selector/theme-selector.component.spec.ts
  68. 10
      src/app/shared/components/selectors/theme-selector/theme-selector.component.ts
  69. 33
      src/app/shared/components/settings/settings.component.html
  70. 0
      src/app/shared/components/settings/settings.component.scss
  71. 12
      src/app/shared/components/settings/settings.component.spec.ts
  72. 41
      src/app/shared/components/settings/settings.component.ts
  73. 76
      src/app/shared/shared.module.ts
  74. 34
      src/environments/environment.prod.ts
  75. 19
      src/environments/environment.ts
  76. 1263
      yarn.lock

1
angular.json

@ -28,6 +28,7 @@
"node_modules/primeicons/primeicons.css",
"node_modules/primeng/resources/themes/nova-light/theme.css",
"node_modules/primeng/resources/primeng.min.css",
"node_modules/primeflex/primeflex.css",
"src/styles.scss"
],
"scripts": [

5
package.json

@ -43,6 +43,7 @@
"ngx-clipboard": "^13.0.0",
"ngx-markdown": "^9.0.0",
"ngx-webstorage": "^5.0.0",
"primeflex": "^1.0.0",
"primeicons": "^2.0.0",
"primeng": "^9.0.6",
"quill": "^1.3.7",
@ -67,12 +68,12 @@
"@types/uuid": "^7.0.2",
"@typescript-eslint/eslint-plugin": "^2.27.0",
"@typescript-eslint/parser": "^2.27.0",
"babel-jest": "^25.4.0",
"babel-jest": "^26.0.0",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-prettier": "^3.1.3",
"husky": "^4.2.5",
"jest": "^25.5.1",
"jest": "^26.0.0",
"jest-environment-jsdom-sixteen": "^1.0.3",
"jest-preset-angular": "^8.1.3",
"lint-staged": "^10.1.7",

2
src/app/app-routing.module.ts

@ -2,12 +2,10 @@ import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './core/components/home/home.component';
import { LoginComponent } from './core/components/login/login.component';
import { PageNotFoundComponent } from './shared/components/page-not-found/page-not-found.component';
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'login', component: LoginComponent },
{
path: 'administration',
loadChildren: () =>

7
src/app/app.component.ts

@ -5,6 +5,8 @@ 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';
@ -22,15 +24,18 @@ export class AppComponent implements OnInit, OnDestroy {
constructor(
private titleService: Title,
private themeService: ThemeService,
private languageService: LanguageService,
private mockingService: MockingService
) {}
ngOnInit(): void {
if (!environment.production) {
this.appTitle += ' [DEV]';
this.mockingService.loadUser(UserRole.REGISTERED);
// TODO: to be removed
this.mockingService.loadUser(new User('TOTO', 'toto@gafam.com', UserRole.REGISTERED, false));
}
this.titleService.setTitle(this.appTitle);
this.languageService.configureAndInitTranslations();
this.themeSubscription = this.themeService.theme.subscribe((theme: Theme) => {
switch (theme) {
case Theme.DARK:

30
src/app/core/components/header/header.component.html

@ -40,8 +40,12 @@
<a class="navbar-item" routerLink="administration" routerLinkActive="is-active">
Administration
</a>
<a class="navbar-item" routerLink="participation" routerLinkActive="is-active">
Participation
<a
class="navbar-item"
routerLink="participation/poll/citron_ou_orange"
routerLinkActive="is-active"
>
Participation à citron_ou_orange
</a>
<a class="navbar-item" routerLink="oldstuff" routerLinkActive="is-active">
Old stuff
@ -50,22 +54,14 @@
</div>
</div>
<div class="navbar-item">
<app-theme-selector></app-theme-selector>
</div>
<div class="navbar-item">
<app-language-selector></app-language-selector>
</div>
<div class="navbar-end">
<div class="navbar-item">
<div class="buttons">
<a class="button is-primary">
<strong>Sign up</strong>
</a>
<a class="button is-light">
Log in
</a>
<div class="navbar-item" #container>
<div class="buttons has-addons is-centered clickable" (click)="openDialog()">
<button class="button is-static"><i class="fa fa-user-circle"></i></button>
<button class="button is-static" *ngIf="_user | async as user">
{{ user.pseudo || 'anonyme' }}
</button>
<button class="button is-static"><i class="fa fa-cogs"></i></button>
</div>
</div>
</div>

15
src/app/core/components/header/header.component.ts

@ -1,4 +1,9 @@
import { Component, EventEmitter, Output, Input } from '@angular/core';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Observable } from 'rxjs';
import { User } from '../../models/user.model';
import { ModalService } from '../../services/modal.service';
import { UserService } from '../../services/user.service';
@Component({
selector: 'app-header',
@ -9,6 +14,14 @@ export class HeaderComponent {
@Input() isSidebarOpened: boolean;
@Output() toggleSidebarEE = new EventEmitter<boolean>();
public _user: Observable<User> = this.userService.user;
constructor(private userService: UserService, private modalService: ModalService) {}
public openDialog(): void {
this.modalService.openSettingsComponent();
}
public toggleSidebarOpening(): void {
this.isSidebarOpened = !this.isSidebarOpened;
this.toggleSidebarEE.emit(this.isSidebarOpened);

1
src/app/core/components/login/login.component.html

@ -1 +0,0 @@
<p>login works!</p>

12
src/app/core/components/login/login.component.ts

@ -1,12 +0,0 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss'],
})
export class LoginComponent implements OnInit {
constructor() {}
ngOnInit(): void {}
}

10
src/app/core/components/selectors/language-selector/language-selector.component.html

@ -1,10 +0,0 @@
<div class="control has-icons-left">
<select class="select is-small" [(ngModel)]="currentLang">
<ng-container *ngFor="let language of languagesAvailable">
<option value="{{ language }}">{{ 'LANGUAGES.' + language | translate }}</option>
</ng-container>
</select>
<div class="icon is-left">
<i class="fa fa-globe"></i>
</div>
</div>

38
src/app/core/components/selectors/language-selector/language-selector.component.ts

@ -1,38 +0,0 @@
import { Component, DoCheck, OnInit } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { Language } from '../../../enums/language.enum';
import { StorageService } from '../../../services/storage.service';
@Component({
selector: 'app-language-selector',
templateUrl: './language-selector.component.html',
styleUrls: ['./language-selector.component.scss'],
})
export class LanguageSelectorComponent implements OnInit, DoCheck {
public currentLang: Language;
public languagesAvailable: string[] = Object.values(Language);
constructor(private translate: TranslateService, private storageService: StorageService) {}
ngOnInit(): void {
const currentBrowserLanguage: Language = this.translate.getBrowserLang().toUpperCase() as Language;
if (this.storageService.language && Object.keys(Language).includes(this.storageService.language)) {
this.currentLang = this.storageService.language;
} else if (Object.keys(Language).includes(currentBrowserLanguage)) {
this.currentLang = currentBrowserLanguage;
} else {
this.currentLang = Language.EN;
}
this.updateLanguage();
}
ngDoCheck(): void {
this.updateLanguage();
}
public updateLanguage(): void {
this.translate.use(this.currentLang.toString().toUpperCase());
this.storageService.language = this.currentLang;
}
}

24
src/app/core/components/selectors/theme-selector/theme-selector.component.html

@ -1,24 +0,0 @@
<div class="buttons has-addons is-centered">
<button class="button is-small is-static">Theme</button>
<button
class="button is-small"
[ngClass]="{ 'is-active': (currentTheme | async) === themeEnum.LIGHT }"
(click)="selectTheme('LIGHT')"
>
<i class="fa fa-sun-o"></i>
</button>
<button
class="button is-small"
[ngClass]="{ 'is-active': (currentTheme | async) === themeEnum.DARK }"
(click)="selectTheme('DARK')"
>
<i class="fa fa-moon"></i>
</button>
<button
class="button is-small"
[ngClass]="{ 'is-active': (currentTheme | async) === themeEnum.RED }"
(click)="selectTheme('RED')"
>
<i class="fa fa-adjust"></i>
</button>
</div>

16
src/app/core/core.module.ts

@ -7,26 +7,14 @@ import { TranslateModule } from '@ngx-translate/core';
import { FooterComponent } from './components/footer/footer.component';
import { HeaderComponent } from './components/header/header.component';
import { HomeComponent } from './components/home/home.component';
import { LoginComponent } from './components/login/login.component';
import { LogoComponent } from './components/logo/logo.component';
import { LanguageSelectorComponent } from './components/selectors/language-selector/language-selector.component';
import { ThemeSelectorComponent } from './components/selectors/theme-selector/theme-selector.component';
import { NavigationComponent } from './components/sibebar/navigation/navigation.component';
import { throwIfAlreadyLoaded } from './guards/module-import.guard';
@NgModule({
declarations: [
FooterComponent,
HeaderComponent,
HomeComponent,
LanguageSelectorComponent,
LoginComponent,
LogoComponent,
NavigationComponent,
ThemeSelectorComponent,
],
declarations: [FooterComponent, HeaderComponent, HomeComponent, LogoComponent, NavigationComponent],
imports: [CommonModule, FormsModule, RouterModule, TranslateModule],
exports: [HeaderComponent, FooterComponent, NavigationComponent, LoginComponent, LogoComponent],
exports: [HeaderComponent, FooterComponent, NavigationComponent, LogoComponent],
})
export class CoreModule {
constructor(@Optional() @SkipSelf() parentModule: CoreModule) {

4
src/app/core/enums/answer-granularity.enum.ts

@ -1,4 +0,0 @@
export enum AnswerGranularity {
BASIC = 'BASIC',
COMPLEX = 'COMPLEX',
}

2
src/app/core/enums/answer-type.enum.ts → src/app/core/enums/response.enum.ts

@ -1,4 +1,4 @@
export enum AnswerType {
export enum Response {
YES = 'YES',
NO = 'NO',
MAYBE = 'MAYBE',

2
src/app/core/enums/user-role.enum.ts

@ -1,5 +1,5 @@
export enum UserRole {
ANONYMOUS = 'ANONYMOUS',
REGISTERED = 'ADMIN',
REGISTERED = 'REGISTERED',
ADMIN = 'ADMIN',
}

2
src/app/core/enums/workflow-step.enum.ts

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

5
src/app/core/interfaces/date-options.interface.ts

@ -1,5 +0,0 @@
export interface DateOption {
timeList: any;
literal: string;
date_object?: object;
}

6
src/app/core/models/answer.model.ts

@ -1,6 +1,6 @@
import { AnswerType } from '../enums/answer-type.enum';
import { PollOption } from './poll-options.model';
import { Response } from '../enums/response.enum';
import { Choice } from './choice.model';
export class Answer {
constructor(public pollOption: PollOption, public type: AnswerType, public userPseudo: string) {}
constructor(public author: string, public choice: Choice, public response: Response) {}
}

5
src/app/core/models/choice.model.ts

@ -0,0 +1,5 @@
import { Response } from '../enums/response.enum';
export class Choice {
constructor(public label: string, public responses: Response[] = [Response.YES, Response.NO]) {}
}

3
src/app/core/models/comment.model.ts

@ -0,0 +1,3 @@
export class Comment {
constructor(public author: string, public content: string) {}
}

26
src/app/core/models/configuration.model.ts

@ -0,0 +1,26 @@
import { v4 as uuidv4 } from 'uuid';
import { environment } from '../../../environments/environment';
import { DateUtilsService } from '../utils/date-utils.service';
export class Configuration {
constructor(
public isAboutDate: boolean = false,
public slug: string = uuidv4(),
public isProtectedByPassword: boolean = false,
public isMaybeAnswerAvailable: boolean = false,
public creationDate: Date = new Date(Date.now()),
public expirationDate: Date = DateUtilsService.addDaysToDate(
environment.poll.defaultConfig.expiracyInDays,
new Date(Date.now())
)
) {}
public getAdministrationUrl(): string {
return `${environment.api.baseHref}/administration/${environment.api.endpoints.polls}/${this.slug}`;
}
public getParticipationUrl(): string {
return `${environment.api.baseHref}/participation/${environment.api.endpoints.polls}/${this.slug}`;
}
}

13
src/app/core/models/poll-config.model.ts

@ -1,13 +0,0 @@
import { AnswerGranularity } from '../enums/answer-granularity.enum';
export class PollConfig {
constructor(
public allowSeveralHours = true,
public isVisibleToAnyoneWithTheLink: boolean = true,
public answerType: AnswerGranularity = AnswerGranularity.BASIC,
public creationDate: Date = new Date(),
public expirationDate?: Date,
public canVotersModifyTheirAnswers = true,
public isProtectedByPassword: boolean = false
) {}
}

9
src/app/core/models/poll-options.model.ts

@ -1,9 +0,0 @@
import * as moment from 'moment';
export class PollOption {
constructor(public label: string, public url?: string, public subOptions?: PollOption[]) {}
public isDatePoll(): boolean {
return moment(this.label).isValid();
}
}

26
src/app/core/models/poll.model.ts

@ -1,23 +1,17 @@
import { Answer } from './answer.model';
import { PollConfig } from './poll-config.model';
import { PollOption } from './poll-options.model';
import { Choice } from './choice.model';
import { Comment } from './comment.model';
import { Configuration } from './configuration.model';
import { Question } from './question.model';
import { User } from './user.model';
import { environment } from 'src/environments/environment';
export class Poll {
constructor(
public isDateType: boolean,
public title: string,
public description: string,
public slug: string,
public id: string,
public owner?: User,
public config?: PollConfig,
public options: PollOption[] = [],
public answers: Answer[] = []
public owner: User,
public question: Question,
public choices: Choice[],
public configuration: Configuration = new Configuration(),
public answers: Answer[] = [],
public comments: Comment[] = []
) {}
public getUrl(): string {
return `${environment.api.baseHref}/${this.slug}`;
}
}

3
src/app/core/models/question.model.ts

@ -0,0 +1,3 @@
export class Question {
constructor(public label: string, public description: string) {}
}

6
src/app/core/models/user.model.ts

@ -1,12 +1,12 @@
import { Poll } from './poll.model';
import { UserRole } from '../enums/user-role.enum';
import { Poll } from './poll.model';
export class User {
constructor(
public pseudo: string,
public email: string,
public role: UserRole = UserRole.ANONYMOUS,
public isOwner: boolean = false,
public pseudo?: string,
public email?: string,
public polls?: Poll[]
) {}
}

31
src/app/core/services/api.service.ts

@ -14,7 +14,7 @@ export class ApiService {
private readonly commentsEndpoint = environment.api.endpoints.polls.comments.name;
private readonly votesEndpoint = environment.api.endpoints.polls.votes.name;
private readonly slugsEndpoint = environment.api.endpoints.polls.slugs.name;
private readonly votesStacksEndpoint = environment.api.endpoints.voteStack.name;
private readonly votesStacksEndpoint = environment.api.endpoints.polls.votesStacks.name;
private readonly usersEndpoint = environment.api.endpoints.users.name;
private readonly usersPollsEndpoint = environment.api.endpoints.users.polls.name;
private readonly usersPollsSendEmailEndpoint = environment.api.endpoints.users.polls.sendEmail.name;
@ -28,7 +28,7 @@ export class ApiService {
////////////
public async savePoll(poll: Poll): Promise<void> {
try {
await this.axiosInstance.post(`${this.pollsEndpoint}`, { params: { config: poll.config } });
await this.axiosInstance.post(`${this.pollsEndpoint}`, poll);
} catch (error) {
this.handleError(error);
}
@ -37,7 +37,7 @@ export class ApiService {
public async saveVote(poll: Poll): Promise<void> {
try {
// TODO: add the votestack in the params
await this.axiosInstance.post(`${this.pollsEndpoint}/${poll.id}${this.votesEndpoint}`, {
await this.axiosInstance.post(`${this.pollsEndpoint}/${poll.configuration.slug}${this.votesEndpoint}`, {
params: { voteStack: {} },
});
} catch (error) {
@ -48,9 +48,10 @@ export class ApiService {
public async saveComment(poll: Poll, comment: string): Promise<void> {
try {
// TODO: add the comment in the params
await this.axiosInstance.post(`${this.pollsEndpoint}/${poll.id}${this.commentsEndpoint}`, {
params: { comment },
});
await this.axiosInstance.post(
`${this.pollsEndpoint}/${poll.configuration.slug}${this.commentsEndpoint}`,
comment
);
} catch (error) {
this.handleError(error);
}
@ -104,9 +105,17 @@ export class ApiService {
public async getPollByIdentifier(identifier: string): Promise<Poll | undefined> {
// TODO: identifier should be decided according to backend : Id || Slug ?
try {
// TODO: this interceptor should not be existing, backend should return the good object poll
const adapterInterceptor: number = this.axiosInstance.interceptors.response.use(
(response): AxiosResponse => {
response.data = response.data['poll'];
return response;
}
);
const response: AxiosResponse<Poll> = await this.axiosInstance.get<Poll>(
`${this.pollsEndpoint}/${identifier}`
);
axios.interceptors.request.eject(adapterInterceptor);
return response?.data;
} catch (error) {
if (error.response?.status === 404) {
@ -123,7 +132,7 @@ export class ApiService {
public async updatePoll(poll: Poll): Promise<void> {
try {
// TODO: implement the params when entities are finalized.
await this.axiosInstance.put(`${this.pollsEndpoint}/${poll.id}`, {
await this.axiosInstance.put(`${this.pollsEndpoint}/${poll.configuration.slug}`, {
params: { voteStack: {}, token: '' },
});
} catch (error) {
@ -145,10 +154,9 @@ export class ApiService {
////////////
// DELETE //
////////////
public async deletePoll(poll: Poll): Promise<void> {
try {
await this.axiosInstance.delete(`${this.pollsEndpoint}${poll.id}`, {});
await this.axiosInstance.delete(`${this.pollsEndpoint}${poll.configuration.slug}`, {});
} catch (error) {
this.handleError(error);
}
@ -157,7 +165,7 @@ export class ApiService {
public async deletePollVotes(poll: Poll): Promise<void> {
try {
// TODO: update endpoint in Backend
await this.axiosInstance.delete(`${this.pollsEndpoint}${poll.id}${this.votesEndpoint}`);
await this.axiosInstance.delete(`${this.pollsEndpoint}${poll.configuration.slug}${this.votesEndpoint}`);
} catch (error) {
this.handleError(error);
}
@ -166,7 +174,7 @@ export class ApiService {
public async deletePollComments(poll: Poll): Promise<void> {
try {
// TODO: modify endpoint in Backend
await this.axiosInstance.delete(`${this.pollsEndpoint}${poll.id}${this.commentsEndpoint}`);
await this.axiosInstance.delete(`${this.pollsEndpoint}${poll.configuration.slug}${this.commentsEndpoint}`);
} catch (error) {
this.handleError(error);
}
@ -192,5 +200,6 @@ export class ApiService {
console.log('Error', error.message);
}
console.log(error.config);
throw error;
}
}

16
src/app/core/services/language.service.spec.ts

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

66
src/app/core/services/language.service.ts

@ -0,0 +1,66 @@
import { Injectable } from '@angular/core';
import { TranslateService, LangChangeEvent } from '@ngx-translate/core';
import { Language } from '../enums/language.enum';
import { StorageService } from './storage.service';
@Injectable({
providedIn: 'root',
})
export class LanguageService {
constructor(private translate: TranslateService, private storageService: StorageService) {}
public getLangage(): Language {
return this.translate.currentLang as Language;
}
public setLanguage(language: Language): void {
this.translate.use(language.toString().toUpperCase());
}
public getAvailableLanguages(): string[] {
return this.translate.getLangs();
}
public configureAndInitTranslations(): void {
// always save in storage the currentLang used
this.translate.onLangChange.subscribe((event: LangChangeEvent) => {
this.storageService.language = event.lang as Language;
});
// set all languages available
this.translate.addLangs(Object.keys(Language));
// set language
this.setLanguageOnInit();
}
private setLanguageOnInit(): void {
// set language from storage
if (!this.translate.currentLang) {
this.setLanguageFromStorage();
}
// or set language from browser
if (!this.translate.currentLang) {
this.setLanguageFromBrowser();
}
// set default language
if (!this.translate.currentLang) {
this.setLanguage(Language.EN);
}
}
private setLanguageFromStorage(): void {
if (this.storageService.language && this.translate.getLangs().includes(this.storageService.language)) {
this.setLanguage(this.storageService.language);
}
}
private setLanguageFromBrowser(): void {
const currentBrowserLanguage: Language = this.translate.getBrowserLang().toUpperCase() as Language;
if (this.translate.getLangs().includes(currentBrowserLanguage)) {
this.setLanguage(currentBrowserLanguage);
}
}
}

16
src/app/core/services/loader.service.spec.ts

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

14
src/app/core/services/loader.service.ts

@ -0,0 +1,14 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class LoaderService {
private _loadingStatus: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
public readonly isLoading: Observable<boolean> = this._loadingStatus.asObservable();
public setStatus(status: boolean): void {
this._loadingStatus.next(status);
}
}

31
src/app/core/services/mocking.service.ts

@ -1,7 +1,9 @@
import { Injectable } from '@angular/core';
import { UserRole } from '../enums/user-role.enum';
import { Choice } from '../models/choice.model';
import { Poll } from '../models/poll.model';
import { Question } from '../models/question.model';
import { User } from '../models/user.model';
import { PollService } from './poll.service';
import { UserService } from './user.service';
@ -10,28 +12,33 @@ import { UserService } from './user.service';
providedIn: 'root',
})
export class MockingService {
public pollsDatabase: Poll[];
private user: User;
public pollsDatabase: Poll[] = [];
private user: User = new User(UserRole.ANONYMOUS, false, 'toto', 'toto@gafam.com', []);
private poll1: Poll = new Poll(false, 'mon super sondage', 'super description 1', 'super_slug_1', 'id1');
private poll2: Poll = new Poll(false, 'mon autre sondage', 'super description 2', 'super_slug_2', 'id2');
private poll1: Poll = new Poll(this.user, new Question('Quand le picnic ?', 'Pour faire la teuf'), [
new Choice('mardi prochain'),
new Choice('mercredi'),
]);
private poll2: Poll = new Poll(this.user, new Question('On fait quoi à la soirée ?', 'Balancez vos idées'), [
new Choice('jeux'),
new Choice('danser'),
new Choice('discuter en picolant'),
]);
constructor(private userService: UserService, private pollService: PollService) {
this.pollsDatabase = [this.poll1, this.poll2];
this.user.polls = this.pollsDatabase;
}
public loadUser(role: UserRole): void {
this.user.role = role;
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 loadPoll(slug: string): void {
const poll: Poll | undefined = this.pollsDatabase.find((poll: Poll) => poll.slug === slug);
if (poll) {
console.info('MOCKING poll', { poll });
this.pollService.updateCurrentPoll(poll);
}
this.poll1.configuration.slug = slug;
console.info('MOCKING poll', { poll: this.poll1 });
this.pollService.updateCurrentPoll(this.poll1);
}
}

16
src/app/core/services/modal.service.spec.ts

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

15
src/app/core/services/modal.service.ts

@ -0,0 +1,15 @@
import { Injectable } from '@angular/core';
import { DialogService } from 'primeng';
import { SettingsComponent } from '../../shared/components/settings/settings.component';
@Injectable({
providedIn: 'root',
})
export class ModalService {
constructor(public dialogService: DialogService) {}
public openSettingsComponent(): void {
this.dialogService.open(SettingsComponent, { header: 'Paramètres', dismissableMask: true });
}
}

5
src/app/core/services/poll.service.ts

@ -37,7 +37,10 @@ export class PollService {
// GET
public async getPollByIdentifier(slug: string): Promise<void> {
this.updateCurrentPoll(await this.apiService.getPollByIdentifier(slug));
const poll: Poll | undefined = await this.apiService.getPollByIdentifier(slug);
if (poll) {
this.updateCurrentPoll(poll);
}
}
// DELETE

3
src/app/core/services/storage.service.ts

@ -16,4 +16,7 @@ export class StorageService {
@LocalStorage()
public userPollsIds: string[];
@LocalStorage()
public pseudo: string;
}

16
src/app/core/services/url.service.spec.ts

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

23
src/app/core/services/url.service.ts

@ -0,0 +1,23 @@
import { Injectable } from '@angular/core';
import { ActivatedRoute, UrlSegment } from '@angular/router';
import { LoaderService } from './loader.service';
import { PollService } from './poll.service';
@Injectable({
providedIn: 'root',
})
export class UrlService {
constructor(
private route: ActivatedRoute,
private pollService: PollService,
private loaderService: LoaderService
) {}
public async loadPollFromUrl(): Promise<void> {
this.loaderService.setStatus(true);
const wantedSlug: string = this.route.snapshot.firstChild.firstChild.url[1].path;
await this.pollService.getPollByIdentifier(wantedSlug);
this.loaderService.setStatus(false);
}
}

11
src/app/core/services/user.service.ts

@ -1,6 +1,8 @@
import { Injectable } from '@angular/core';
import { DialogService } from 'primeng';
import { BehaviorSubject, Observable } from 'rxjs';
import { UserRole } from '../enums/user-role.enum';
import { User } from '../models/user.model';
import { ApiService } from './api.service';
@ -8,16 +10,21 @@ import { ApiService } from './api.service';
providedIn: 'root',
})
export class UserService {
public anonymous: User = new User();
public anonymous: User = new User('', '', UserRole.ANONYMOUS, false);
private _user: BehaviorSubject<User> = new BehaviorSubject<User>(this.anonymous);
public readonly user: Observable<User> = this._user.asObservable();
constructor(private apiService: ApiService) {}
constructor(private apiService: ApiService, public dialogService: DialogService) {}
public updateUser(user: User): void {
this._user.next(user);
}
public isCurrentUserIdentifiable(): boolean {
return this._user.getValue().pseudo ? true : false;
}
// GET
public async getUserPolls(): Promise<void> {
const currentUser: User = this._user.getValue();

2
src/app/core/services/workflow.service.ts

@ -7,7 +7,7 @@ import { WorkflowStep } from '../enums/workflow-step.enum';
providedIn: 'root',
})
export class WorkflowService {
private steps = [WorkflowStep.DESCRIPTION, WorkflowStep.OPTIONS, WorkflowStep.CONFIGURATION];
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();

31
src/app/core/utils/date-utils.service.ts

@ -16,35 +16,4 @@ export class DateUtilsService {
public static formatDate(date): string {
return moment(date).format('yyyy-MM-dd');
}
public static orderDates(): Date[] {
// TODO: to implement
const datesOrdered: Date[] = [];
return datesOrdered;
}
public static getDatesInRange(d1: Date, d2: Date, interval: number): Date[] {
// TODO: refacto this
d1 = new Date(d1);
d2 = new Date(d2);
const dates = [];
while (+d1 < +d2) {
dates.push({
literal: this.formateDate(d1),
date_object: d1,
});
d1.setDate(d1.getDate() + interval);
}
return [...dates];
}
public static getDoubleDigits(str: string): string {
// TODO: ça sert à quoi ça ?
// Parce que ajouter 2 caractère à une string et ensuite slicer à partir du 2ème caractère, euh…
return ('00' + str).slice(-2);
}
private isInChronologicalOrder(date1: Date, date2: Date): boolean {
return date1 < date2;
}
}

8
src/app/features/administration/edit-description/edit-description.component.ts

@ -18,10 +18,10 @@ export class EditDescriptionComponent implements OnInit {
ngOnInit(): void {
this.pollForm = this.fb.group({
type: [this.poll ? this.poll.isDateType : false, [Validators.required]],
title: [this.poll ? this.poll.title : '', [Validators.required]],
description: [this.poll ? this.poll.description : ''],
slug: [this.poll ? this.poll.slug : this.generateRandomSlug(), [Validators.required]],
type: [this.poll ? this.poll.configuration.isAboutDate : false, [Validators.required]],
title: [this.poll ? this.poll.question.label : '', [Validators.required]],
description: [this.poll ? this.poll.question.description : ''],
slug: [this.poll ? this.poll.configuration.slug : this.generateRandomSlug(), [Validators.required]],
address: this.fb.group({
street: [''],
city: [''],

8
src/app/features/administration/poll-edit/poll-edit.component.ts

@ -18,10 +18,10 @@ export class PollEditComponent implements OnInit {
ngOnInit(): void {
this.pollForm = this.fb.group({
type: [this.poll ? this.poll.isDateType : false, [Validators.required]],
title: [this.poll ? this.poll.title : '', [Validators.required]],
description: [this.poll ? this.poll.description : ''],
slug: [this.poll ? this.poll.slug : this.generateRandomSlug(), [Validators.required]],
type: [this.poll ? this.poll.configuration.isAboutDate : false, [Validators.required]],
title: [this.poll ? this.poll.question.label : '', [Validators.required]],
description: [this.poll ? this.poll.question.description : ''],
slug: [this.poll ? this.poll.configuration.slug : this.generateRandomSlug(), [Validators.required]],
address: this.fb.group({
street: [''],
city: [''],

8
src/app/features/administration/profile/profile.component.html

@ -11,10 +11,10 @@
<thead></thead>
<tbody>
<tr *ngFor="let poll of (_user | async)?.polls">
<th>{{ poll.title }}</th>
<th>{{ poll.question.label }}</th>
<td>
<a routerLink="{{ '/administration/edit/description/' + poll.slug }}">
{{ poll.getUrl() }}
<a routerLink="{{ '/administration/edit/description/' + poll.configuration.slug }}">
{{ poll.configuration.getAdministrationUrl() }}
</a>
</td>
</tr>
@ -26,7 +26,7 @@
<ng-container *ngIf="['ANONYMOUS'].includes((_user | async)?.role)">
<div class="columns">
<div class="column">
<a class="button is-primary" role="button" routerLink="/login">
<a class="button is-primary" role="button" routerLink="/">
J’ai un compte, je me connecte
</a>
</div>

7
src/app/features/administration/profile/profile.component.ts

@ -10,14 +10,13 @@ import { UserService } from '../../../core/services/user.service';
styleUrls: ['./profile.component.scss'],
})
export class ProfileComponent implements OnInit {
public _user: Observable<User>;
public _user: Observable<User> = this.userService.user;
public isModalOpened = false;
constructor(private userService: UserService) {}
ngOnInit(): void {
this._user = this.userService.user;
}
ngOnInit(): void {}
public toggleModal(): void {
this.isModalOpened = !this.isModalOpened;
}

2
src/app/features/administration/stepper/stepper.component.ts

@ -22,7 +22,7 @@ export class StepperComponent implements OnInit {
};
public itemOptions: MenuItem = {
id: '2',
label: WorkflowStep.OPTIONS,
label: WorkflowStep.CHOICES,
title: 'Je renseigne les différentes options sur lesquelles les gens vont donner leur avis',
routerLink: './options',
command: () => {},

4
src/app/features/old-stuff/old-stuff-routing.module.ts

@ -32,8 +32,8 @@ const routes: Routes = [
{ path: 'step/resume', component: ResumeComponent },
{ path: 'step/end', component: EndConfirmationComponent },
{ path: 'graphic/:poll', component: PollGraphicComponent },
{ path: 'vote/poll/id/:poll', component: PollDisplayComponent },
{ path: 'vote/poll/slug/:pollSlug', component: PollDisplayComponent },
{ path: 'vote/poll/id/:id', component: PollDisplayComponent },
{ path: 'vote/poll/slug/:slug', component: PollDisplayComponent },
{ path: 'votingchoice', component: VotingChoiceComponent },
{ path: 'voting', component: VotingComponent },
];

4
src/app/features/old-stuff/pages/create-or-retrieve/create-or-retrieve.component.html

@ -51,9 +51,9 @@
<ul class="poll-list" *ngFor="let poll of config.myPolls; index as i; trackBy: trackFunction">
<li>
<a href="{{ poll.url }}">
{{ poll.title }}
{{ poll.question.label }}
<sub>
{{ poll.description }}
{{ poll.question.description }}
</sub>
</a>
</li>

2
src/app/features/old-stuff/pages/voting/voting-navigation/voting-navigation.component.html

@ -3,7 +3,7 @@
<ul>
<li>
<a href="title">
{{ config.currentPoll.poll.title }}
{{ config.currentPoll.poll.question.label }}
</a>
</li>
<li>

4
src/app/features/old-stuff/pages/voting/voting-summary/voting-summary.component.html

@ -1,8 +1,8 @@
<h2>Résumé</h2>
<div class="heading">
<div class="col-xs-6">
<h1 id="title">{{ config.currentPoll.poll.title }}</h1>
<p>{{ config.currentPoll.poll.description }}</p>
<h1 id="title">{{ config.currentPoll.poll.question.label }}</h1>
<p>{{ config.currentPoll.poll.question.description }}</p>
<span class="creationDate"> Créé le {{ config.currentPoll.poll.creationDate.date }} </span>
<span class="expiracyDate"> Expire le {{ config.currentPoll.poll.expiracyDate.date }} </span>
<div class="votants">

10
src/app/features/participation/participation-routing.module.ts

@ -2,14 +2,12 @@ import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ParticipationComponent } from './participation.component';
import { PollComponent } from './poll/poll.component';
const routes: Routes = [
{
path: '',
component: ParticipationComponent,
children: [{ path: 'poll/:slug', component: PollComponent }],
},
{ path: '', redirectTo: 'poll', pathMatch: 'full' },
{ path: 'poll', component: ParticipationComponent },
{ path: ':slug', redirectTo: 'poll/:slug', pathMatch: 'full' },
{ path: 'poll/:slug', component: ParticipationComponent },
];
@NgModule({

33
src/app/features/participation/participation.component.html

@ -1,7 +1,30 @@
<h1>Participation</h1>
<div class="p-grid">
<div class="p-col has-text-centered">
<h1>Participation</h1>
</div>
</div>
<a role="button" class="button" routerLink="../participation/poll/groSlug">
Participer au sondage « Gros slug »
</a>
<div *ngIf="_isLoading | async" class="p-grid p-justify-center">
<div class="p-col has-text-centered">
<p-progressSpinner></p-progressSpinner>
</div>
</div>
<router-outlet></router-outlet>
<ng-container *ngIf="!(_isLoading | async)">
<ng-container *ngIf="!(_poll | async)">
<app-page-not-found [message]="'PAGE_NOT_FOUND.POLL'"></app-page-not-found>
</ng-container>
<ng-container *ngIf="_poll | async">
<div class="p-grid">
<div class="p-col">
<app-poll></app-poll>
</div>
</div>
<div class="p-grid">
<div class="p-col">
<app-poll-comment></app-poll-comment>
</div>
</div>
</ng-container>
</ng-container>

26
src/app/features/participation/participation.component.ts

@ -1,4 +1,12 @@
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { Poll } from '../../core/models/poll.model';
import { LoaderService } from '../../core/services/loader.service';
import { ModalService } from '../../core/services/modal.service';
import { PollService } from '../../core/services/poll.service';
import { UrlService } from '../../core/services/url.service';
import { UserService } from '../../core/services/user.service';
@Component({
selector: 'app-participation',
@ -6,7 +14,21 @@ import { Component, OnInit } from '@angular/core';
styleUrls: ['./participation.component.scss'],
})
export class ParticipationComponent implements OnInit {
constructor() {}
public _isLoading: Observable<boolean> = this.loaderService.isLoading;
public _poll: Observable<Poll> = this.pollService.poll;
constructor(
private urlService: UrlService,
private loaderService: LoaderService,
private pollService: PollService,
private userService: UserService,
private modalService: ModalService
) {}
ngOnInit(): void {}
ngOnInit(): void {
if (!this.userService.isCurrentUserIdentifiable()) {
this.modalService.openSettingsComponent();
}
this.urlService.loadPollFromUrl();
}
}

3
src/app/features/participation/participation.module.ts

@ -6,9 +6,10 @@ import { SharedModule } from '../../shared/shared.module';
import { ParticipationRoutingModule } from './participation-routing.module';
import { ParticipationComponent } from './participation.component';
import { PollComponent } from './poll/poll.component';
import { PollCommentComponent } from './poll-comment/poll-comment.component';
@NgModule({
declarations: [ParticipationComponent, PollComponent],
declarations: [ParticipationComponent, PollComponent, PollCommentComponent],
imports: [CommonModule, ParticipationRoutingModule, SharedModule, TranslateModule.forChild({ extend: true })],
exports: [],