add user settings button & modal

This commit is contained in:
seraph 2020-05-05 18:17:12 +02:00
parent 4f0a29e806
commit 355fed53f3
76 changed files with 1308 additions and 1006 deletions

View File

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

View File

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

View File

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

View File

@ -5,6 +5,8 @@ import { Subscription } from 'rxjs';
import { environment } from '../environments/environment'; import { environment } from '../environments/environment';
import { Theme } from './core/enums/theme.enum'; import { Theme } from './core/enums/theme.enum';
import { UserRole } from './core/enums/user-role.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 { MockingService } from './core/services/mocking.service';
import { ThemeService } from './core/services/theme.service'; import { ThemeService } from './core/services/theme.service';
@ -22,15 +24,18 @@ export class AppComponent implements OnInit, OnDestroy {
constructor( constructor(
private titleService: Title, private titleService: Title,
private themeService: ThemeService, private themeService: ThemeService,
private languageService: LanguageService,
private mockingService: MockingService private mockingService: MockingService
) {} ) {}
ngOnInit(): void { ngOnInit(): void {
if (!environment.production) { if (!environment.production) {
this.appTitle += ' [DEV]'; 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.titleService.setTitle(this.appTitle);
this.languageService.configureAndInitTranslations();
this.themeSubscription = this.themeService.theme.subscribe((theme: Theme) => { this.themeSubscription = this.themeService.theme.subscribe((theme: Theme) => {
switch (theme) { switch (theme) {
case Theme.DARK: case Theme.DARK:

View File

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

View File

@ -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({ @Component({
selector: 'app-header', selector: 'app-header',
@ -9,6 +14,14 @@ export class HeaderComponent {
@Input() isSidebarOpened: boolean; @Input() isSidebarOpened: boolean;
@Output() toggleSidebarEE = new EventEmitter<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 { public toggleSidebarOpening(): void {
this.isSidebarOpened = !this.isSidebarOpened; this.isSidebarOpened = !this.isSidebarOpened;
this.toggleSidebarEE.emit(this.isSidebarOpened); this.toggleSidebarEE.emit(this.isSidebarOpened);

View File

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

View File

@ -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 {}
}

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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}`;
}
}

View File

@ -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
) {}
}

View File

@ -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();
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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();
});
});

View File

@ -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);
}
}
}

View File

@ -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();
});
});

View File

@ -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);
}
}

View File

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

View File

@ -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();
});
});

View File

@ -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 });
}
}

View File

@ -37,7 +37,10 @@ export class PollService {
// GET // GET
public async getPollByIdentifier(slug: string): Promise<void> { 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 // DELETE

View File

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

View File

@ -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();
});
});

View File

@ -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);
}
}

View File

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

View File

@ -7,7 +7,7 @@ import { WorkflowStep } from '../enums/workflow-step.enum';
providedIn: 'root', providedIn: 'root',
}) })
export class WorkflowService { 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]); private _currentStep: BehaviorSubject<WorkflowStep> = new BehaviorSubject<WorkflowStep>(WorkflowStep[0]);
public readonly currentStep: Observable<WorkflowStep> = this._currentStep.asObservable(); public readonly currentStep: Observable<WorkflowStep> = this._currentStep.asObservable();

View File

@ -16,35 +16,4 @@ export class DateUtilsService {
public static formatDate(date): string { public static formatDate(date): string {
return moment(date).format('yyyy-MM-dd'); 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;
}
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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"> <div *ngIf="_isLoading | async" class="p-grid p-justify-center">
Participer au sondage « Gros slug » <div class="p-col has-text-centered">
</a> <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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,12 +1,6 @@
<p-progressSpinner *ngIf="isLoading" class="has-text-centered"></p-progressSpinner> <ng-container *ngIf="_poll | async as poll">
<h1>Titre:{{ poll.question.label }}</h1>
<ng-container *ngIf="!isLoading"> <p>description: {{ poll.question.description }}</p>
<ng-container *ngIf="poll | async"> <p>isDateType:{{ poll.configuration.isAboutDate }}</p>
<!-- <app-poll-presentation></app-poll-presentation> --> <p>slug:{{ poll.configuration.slug }}</p>
<h1>Sondage : {{ poll.title | async }}</h1>
</ng-container>
<ng-container *ngIf="!(poll | async)">
<app-page-not-found [message]="'PAGE_NOT_FOUND.POLL'"></app-page-not-found>
</ng-container>
</ng-container> </ng-container>

View File

@ -1,5 +1,4 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { Poll } from '../../../core/models/poll.model'; import { Poll } from '../../../core/models/poll.model';
@ -11,24 +10,9 @@ import { PollService } from '../../../core/services/poll.service';
styleUrls: ['./poll.component.scss'], styleUrls: ['./poll.component.scss'],
}) })
export class PollComponent implements OnInit { export class PollComponent implements OnInit {
public isLoading = false; public _poll: Observable<Poll> = this.pollService.poll;
public wantedSlug: string;
public poll: Observable<Poll>;
public slugFromUrl: string;
private readonly urlMatcher: RegExp = new RegExp(/participation\/poll\/(\w+)/);
constructor(private router: Router, private pollService: PollService) {} constructor(private pollService: PollService) {}
ngOnInit(): void { ngOnInit(): void {}
this.wantedSlug = this.urlMatcher.exec(this.router.url)[1];
this.poll = this.pollService.poll;
this.loadPollFromUrl();
}
private async loadPollFromUrl(): Promise<void> {
this.isLoading = true;
await this.pollService.getPollByIdentifier(this.wantedSlug);
this.isLoading = false;
}
} }

View File

@ -0,0 +1,14 @@
<div class="p-grid p-align-center p-justify-between">
<div class="p-col">
<p>Langue</p>
</div>
<div class="p-col">
<div class="buttons has-addons">
<select class="select" [(ngModel)]="currentLang">
<option *ngFor="let language of availableLanguages" value="{{ language }}">
{{ 'LANGUAGES.' + language | translate }}
</option>
</select>
</div>
</div>
</div>

View File

@ -0,0 +1,27 @@
import { Component, DoCheck, OnInit } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { Language } from '../../../../core/enums/language.enum';
import { StorageService } from '../../../../core/services/storage.service';
import { LanguageService } from '../../../../core/services/language.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 availableLanguages: string[] = [];
constructor(private languageService: LanguageService) {}
ngOnInit(): void {
this.availableLanguages = this.languageService.getAvailableLanguages();
this.currentLang = this.languageService.getLangage();
}
ngDoCheck(): void {
this.languageService.setLanguage(this.currentLang);
}
}

View File

@ -0,0 +1,30 @@
<div class="p-grid p-align-center p-justify-between">
<div class="p-col">
<p>Theme</p>
</div>
<div class="p-col">
<div class="buttons has-addons">
<button
class="button"
[ngClass]="{ 'is-active': (currentTheme | async) === themeEnum.LIGHT }"
(click)="selectTheme('LIGHT')"
>
<i class="fa fa-sun-o"></i>
</button>
<button
class="button"
[ngClass]="{ 'is-active': (currentTheme | async) === themeEnum.DARK }"
(click)="selectTheme('DARK')"
>
<i class="fa fa-moon"></i>
</button>
<button
class="button"
[ngClass]="{ 'is-active': (currentTheme | async) === themeEnum.RED }"
(click)="selectTheme('RED')"
>
<i class="fa fa-adjust"></i>
</button>
</div>
</div>
</div>

View File

@ -1,8 +1,8 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { Theme } from '../../../enums/theme.enum'; import { Theme } from '../../../../core/enums/theme.enum';
import { ThemeService } from '../../../services/theme.service'; import { ThemeService } from '../../../../core/services/theme.service';
@Component({ @Component({
selector: 'app-theme-selector', selector: 'app-theme-selector',
@ -11,13 +11,11 @@ import { ThemeService } from '../../../services/theme.service';
}) })
export class ThemeSelectorComponent implements OnInit { export class ThemeSelectorComponent implements OnInit {
public themeEnum = Theme; public themeEnum = Theme;
public currentTheme: Observable<Theme>; public currentTheme: Observable<Theme> = this.themeService.theme;
constructor(private themeService: ThemeService) {} constructor(private themeService: ThemeService) {}
ngOnInit(): void { ngOnInit(): void {}
this.currentTheme = this.themeService.theme;
}
public selectTheme(theme: string): void { public selectTheme(theme: string): void {
this.themeService.selectTheme(theme as Theme); this.themeService.selectTheme(theme as Theme);

View File

@ -0,0 +1,33 @@
<p-fieldset legend="Mes données personnelles">
<app-language-selector></app-language-selector>
<app-theme-selector></app-theme-selector>
<div class="p-grid p-align-center p-justify-between">
<div class="p-col">
<p>Pseudo, nom ou prénom :</p>
</div>
<div class="p-col" pFocusTrap>
<input
pInputText
type="text"
[(ngModel)]="user.pseudo"
(keydown.enter)="saveChanges()"
autofocus
required
/>
</div>
</div>
<div class="p-grid p-align-center">
<div class="p-col">
<p>Email :</p>
</div>
<div class="p-col">
<input pInputText type="text" [(ngModel)]="user.email" (keydown.enter)="saveChanges()" required />
</div>
</div>
<br />
<div class="p-grid">
<button class="p-col" pButton (click)="saveChanges()">Enregistrer</button>
</div>
</p-fieldset>

View File

@ -1,19 +1,19 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { LoginComponent } from './login.component'; import { SettingsComponent } from './settings.component';
describe('LoginComponent', () => { describe('SettingsComponent', () => {
let component: LoginComponent; let component: SettingsComponent;
let fixture: ComponentFixture<LoginComponent>; let fixture: ComponentFixture<SettingsComponent>;
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [LoginComponent], declarations: [SettingsComponent],
}).compileComponents(); }).compileComponents();
})); }));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(LoginComponent); fixture = TestBed.createComponent(SettingsComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@ -0,0 +1,41 @@
import { Component, OnInit } from '@angular/core';
import { DynamicDialogConfig, DynamicDialogRef } from 'primeng';
import { Subscription } from 'rxjs';
import { User } from '../../../core/models/user.model';
import { UserService } from '../../../core/services/user.service';
@Component({
selector: 'app-settings',
templateUrl: './settings.component.html',
styleUrls: ['./settings.component.scss'],
})
export class SettingsComponent implements OnInit {
public user: User;
private userSubscription: Subscription;
constructor(public ref: DynamicDialogRef, public config: DynamicDialogConfig, private userService: UserService) {}
ngOnInit(): void {
this.userSubscription = this.userService.user.subscribe((user: User) => {
this.user = user;
});
}
ngOnDestroy(): void {
if (this.userSubscription) {
this.userSubscription.unsubscribe();
}
}
public saveChanges(): void {
if (this.user?.pseudo?.length > 0) {
this.userService.updateUser(this.user);
}
this.closeDialog();
}
public closeDialog(): void {
this.ref.close();
}
}

View File

@ -1,55 +1,67 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { ChartsModule } from 'ng2-charts'; import { ChartsModule } from 'ng2-charts';
import { import {
ButtonModule,
ConfirmDialogModule, ConfirmDialogModule,
DialogModule, DialogModule,
DialogService,
DynamicDialogModule,
FieldsetModule,
FocusTrapModule,
InputSwitchModule, InputSwitchModule,
InputTextareaModule, InputTextareaModule,
MessageModule, MessageModule,
PanelModule,
ProgressSpinnerModule, ProgressSpinnerModule,
SidebarModule, SidebarModule,
StepsModule, StepsModule,
ToastModule, ToastModule,
TooltipModule,
} from 'primeng'; } from 'primeng';
import { ConfirmationService, MessageService } from 'primeng/api'; import { ConfirmationService, MessageService } from 'primeng/api';
import { FeedbackComponent } from './components/feedback/feedback.component';
import { PageNotFoundComponent } from './components/page-not-found/page-not-found.component'; import { PageNotFoundComponent } from './components/page-not-found/page-not-found.component';
import { PollPageComponent } from './components/poll-page/poll-page.component'; import { PollPageComponent } from './components/poll-page/poll-page.component';
import { FeedbackComponent } from './components/feedback/feedback.component'; import { LanguageSelectorComponent } from './components/selectors/language-selector/language-selector.component';
import { ThemeSelectorComponent } from './components/selectors/theme-selector/theme-selector.component';
import { SettingsComponent } from './components/settings/settings.component';
const MODULE_LIST = [CommonModule, ChartsModule, FormsModule, TranslateModule];
const PRIMENG_MODULE_LIST = [
ButtonModule,
ConfirmDialogModule,
DialogModule,
DynamicDialogModule,
FieldsetModule,
FocusTrapModule,
InputSwitchModule,
InputTextareaModule,
MessageModule,
PanelModule,
ProgressSpinnerModule,
SidebarModule,
StepsModule,
ToastModule,
TooltipModule,
];
const COMPONENT_LIST = [
ThemeSelectorComponent,
LanguageSelectorComponent,
PageNotFoundComponent,
PollPageComponent,
SettingsComponent,
FeedbackComponent,
];
@NgModule({ @NgModule({
declarations: [PageNotFoundComponent, PollPageComponent, FeedbackComponent], declarations: COMPONENT_LIST,
imports: [ imports: [...MODULE_LIST, ...PRIMENG_MODULE_LIST],
CommonModule, exports: [...MODULE_LIST, ...PRIMENG_MODULE_LIST, ...COMPONENT_LIST],
ChartsModule, providers: [ConfirmationService, MessageService, DialogService],
ConfirmDialogModule, entryComponents: [SettingsComponent],
DialogModule,
InputSwitchModule,
InputTextareaModule,
MessageModule,
ProgressSpinnerModule,
SidebarModule,
StepsModule,
ToastModule,
],
exports: [
TranslateModule,
ChartsModule,
ConfirmDialogModule,
DialogModule,
FeedbackComponent,
InputSwitchModule,
InputTextareaModule,
MessageModule,
ProgressSpinnerModule,
SidebarModule,
StepsModule,
ToastModule,
PageNotFoundComponent,
PollPageComponent,
],
providers: [ConfirmationService, MessageService],
}) })
export class SharedModule {} export class SharedModule {}

View File

@ -1,5 +1,5 @@
const backendApiUrlsInDev = { const backendApiUrlsInDev = {
local: 'http://localhost:8000/api', local: 'http://localhost:8000',
remote: 'https://framadate-api.cipherbliss.com/api/v1', remote: 'https://framadate-api.cipherbliss.com/api/v1',
}; };
@ -9,31 +9,37 @@ export const environment = {
api: { api: {
baseHref: backendApiUrlsInDev.remote, baseHref: backendApiUrlsInDev.remote,
endpoints: { endpoints: {
poll: { polls: {
name: '/poll', name: '/polls',
vote: { votes: {
name: '/vote', name: '/votes',
}, },
comment: { comments: {
name: '/comment', name: '/comments',
}, },
slug: { slugs: {
name: '/slug', name: '/slugs',
}, },
}, },
user: { voteStack: {
name: '/user', name: '/votes-stacks',
},
users: {
name: '/users',
polls: { polls: {
name: '/polls', name: '/polls',
sendEmail: { sendEmail: {
name: '/polls/send-email', name: '/send-email',
}, },
}, },
}, },
admin: { admin: {
name: '/admin', name: '/admin',
cleanPolls: { polls: {
name: '/admin/clean-polls', name: '/polls',
clean: {
name: '/clean',
},
}, },
}, },
}, },

View File

@ -3,7 +3,7 @@
// The list of file replacements can be found in `angular.json`. // The list of file replacements can be found in `angular.json`.
const backendApiUrlsInDev = { const backendApiUrlsInDev = {
local: 'http://localhost:8000', local: 'http://localhost:8000/api/v1',
remote: 'https://framadate-api.cipherbliss.com/api/v1', remote: 'https://framadate-api.cipherbliss.com/api/v1',
}; };
@ -11,13 +11,16 @@ export const environment = {
production: false, production: false,
appTitle: 'FramaSondage', appTitle: 'FramaSondage',
api: { api: {
baseHref: backendApiUrlsInDev.remote, baseHref: backendApiUrlsInDev.local,
endpoints: { endpoints: {
polls: { polls: {
name: '/polls', name: '/polls',
votes: { votes: {
name: '/votes', name: '/votes',
}, },
votesStacks: {
name: '/votes-stacks',
},
comments: { comments: {
name: '/comments', name: '/comments',
}, },
@ -25,9 +28,6 @@ export const environment = {
name: '/slugs', name: '/slugs',
}, },
}, },
voteStack: {
name: '/votes-stacks',
},
users: { users: {
name: '/users', name: '/users',
polls: { polls: {
@ -37,15 +37,6 @@ export const environment = {
}, },
}, },
}, },
admin: {
name: '/admin',
polls: {
name: '/polls',
clean: {
name: '/clean',
},
},
},
}, },
}, },
poll: { poll: {

1255
yarn.lock

File diff suppressed because it is too large Load Diff