create services, add user settings button & modal, and other things See merge request framasoft/framadate/funky-framadate-front!37archived-develop
commit
789a1e7535
@ -1,19 +1,20 @@
|
||||
module.exports = {
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018,
|
||||
sourceType: 'module',
|
||||
project: './tsconfig.json',
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
plugins: ['@typescript-eslint'],
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/eslint-recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:@typescript-eslint/recommended-requiring-type-checking',
|
||||
'prettier/@typescript-eslint',
|
||||
'plugin:prettier/recommended',
|
||||
],
|
||||
rules: {},
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['@typescript-eslint'],
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/eslint-recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
'prettier/@typescript-eslint',
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018,
|
||||
sourceType: 'module',
|
||||
project: './tsconfig.json',
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/unbound-method': ['error', { ignoreStatic: true }],
|
||||
},
|
||||
};
|
||||
|
@ -0,0 +1,8 @@
|
||||
{
|
||||
"/api/v1/*": "/$1",
|
||||
"/owners/:email/": "/owners?email=:email",
|
||||
"/choices": "/choices?_embed=answers",
|
||||
"/polls/:slug": "/polls?slug=:slug&_expand=owner&_embed=choices&_embed=comments",
|
||||
"/polls/:slug/choices": "/choices?pollSlug=:slug&_embed=answers",
|
||||
"/polls/:slug/comments": "/comments?pollSlug=:slug"
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
{
|
||||
"/api/v1/*": {
|
||||
"target": "http://localhost:8000",
|
||||
"secure": false,
|
||||
"pathRewrite": {
|
||||
"^/api/v1": ""
|
||||
},
|
||||
"changeOrigin": false,
|
||||
"logLevel": "debug"
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
<p>login works!</p>
|
@ -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 {}
|
||||
}
|
@ -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>
|
@ -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;
|
||||
}
|
||||
}
|
@ -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>
|
@ -1,18 +1,12 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
import { MockingService } from '../../../services/mocking.service';
|
||||
import { Poll } from '../../../models/poll.model';
|
||||
|
||||
@Component({
|
||||
selector: 'app-navigation',
|
||||
templateUrl: './navigation.component.html',
|
||||
styleUrls: ['./navigation.component.scss'],
|
||||
})
|
||||
export class NavigationComponent implements OnInit {
|
||||
public pollsDatabase: Poll[] = [];
|
||||
constructor(private mockingService: MockingService) {}
|
||||
constructor() {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.pollsDatabase = this.mockingService.pollsDatabase;
|
||||
}
|
||||
ngOnInit(): void {}
|
||||
}
|
||||
|
@ -1,4 +0,0 @@
|
||||
export enum AnswerGranularity {
|
||||
BASIC = 'BASIC',
|
||||
COMPLEX = 'COMPLEX',
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
export enum AnswerType {
|
||||
export enum ResponseType {
|
||||
YES = 'YES',
|
||||
NO = 'NO',
|
||||
MAYBE = 'MAYBE',
|
@ -1,5 +1,5 @@
|
||||
export enum UserRole {
|
||||
ANONYMOUS = 'ANONYMOUS',
|
||||
REGISTERED = 'ADMIN',
|
||||
REGISTERED = 'REGISTERED',
|
||||
ADMIN = 'ADMIN',
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
export enum WorkflowStep {
|
||||
DESCRIPTION = 'DESCRIPTION',
|
||||
OPTIONS = 'OPTIONS',
|
||||
CHOICES = 'CHOICES',
|
||||
CONFIGURATION = 'CONFIGURATION',
|
||||
}
|
||||
|
@ -1,5 +0,0 @@
|
||||
export interface DateOption {
|
||||
timeList: any;
|
||||
literal: string;
|
||||
date_object?: object;
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
import { AnswerType } from '../enums/answer-type.enum';
|
||||
import { PollOption } from './poll-options.model';
|
||||
|
||||
export class Answer {
|
||||
constructor(public pollOption: PollOption, public type: AnswerType, public userPseudo: string) {}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
import { ResponseType } from '../enums/response-type.enum';
|
||||
|
||||
export class Choice {
|
||||
constructor(
|
||||
public label: string,
|
||||
public imageUrl?: string,
|
||||
public participants: Map<ResponseType, Set<string>> = new Map<ResponseType, Set<string>>([
|
||||
[ResponseType.YES, new Set<string>()],
|
||||
[ResponseType.NO, new Set<string>()],
|
||||
[ResponseType.MAYBE, new Set<string>()],
|
||||
]),
|
||||
public counts: Map<ResponseType, number> = new Map<ResponseType, number>([
|
||||
[ResponseType.YES, 0],
|
||||
[ResponseType.NO, 0],
|
||||
[ResponseType.MAYBE, 0],
|
||||
])
|
||||
) {}
|
||||
|
||||
public updateParticipation(pseudo: string, responseType: ResponseType): void {
|
||||
this.removeParticipant(pseudo);
|
||||
this.participants.get(responseType).add(pseudo);
|
||||
this.updateCounts();
|
||||
}
|
||||
|
||||
public removeParticipant(pseudo: string): void {
|
||||
for (const responseType of Object.values(ResponseType)) {
|
||||
if (this.participants.get(responseType).has(pseudo)) {
|
||||
this.participants.get(responseType).delete(pseudo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public updateCounts(): void {
|
||||
for (const responseType of Object.values(ResponseType)) {
|
||||
this.counts.set(responseType, this.participants.get(responseType).size);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
export class Comment {
|
||||
constructor(public author: string, public content: string, public dateCreated: Date) {}
|
||||
|
||||
public static sortChronologically(a: Comment, b: Comment): number {
|
||||
if (a.dateCreated < b.dateCreated) {
|
||||
return -1;
|
||||
}
|
||||
if (a.dateCreated > b.dateCreated) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
import { environment } from '../../../environments/environment';
|
||||
import { DateUtilsService } from '../utils/date-utils.service';
|
||||
|
||||
export class Configuration {
|
||||
constructor(
|
||||
public isAboutDate: boolean = false,
|
||||
public isProtectedByPassword: boolean = false,
|
||||
public isOwnerNotifiedByEmail: { onNewVote: boolean; onNewComment: boolean } = {
|
||||
onNewVote: false,
|
||||
onNewComment: false,
|
||||
},
|
||||
public isMaybeAnswerAvailable: boolean = false,
|
||||
public areResultsPublic: boolean = false,
|
||||
public dateCreated: Date = new Date(Date.now()),
|
||||
public expires: Date = DateUtilsService.addDaysToDate(
|
||||
environment.poll.defaultConfig.expiracyInDays,
|
||||
new Date(Date.now())
|
||||
)
|
||||
) {}
|
||||
|
||||
public static isArchived(configuration: Configuration): boolean {
|
||||
return DateUtilsService.isDateInPast(configuration.expires);
|
||||
}
|
||||
}
|
@ -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
|
||||
) {}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
import { ResponseType } from '../enums/response-type.enum';
|
||||
import { Choice } from './choice.model';
|
||||
|
||||
export class PollUserAnswers {
|
||||
constructor(public pseudo: string, public token: string, public responsesByChoices: Map<Choice, ResponseType>) {}
|
||||
}
|
@ -1,23 +1,89 @@
|
||||
import { Answer } from './answer.model';
|
||||
import { PollConfig } from './poll-config.model';
|
||||
import { PollOption } from './poll-options.model';
|
||||
import { User } from './user.model';
|
||||
import { environment } from 'src/environments/environment';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { ResponseType } from '../enums/response-type.enum';
|
||||
import { PollUserAnswers } from './poll-user-answers.model';
|
||||
import { Choice } from './choice.model';
|
||||
import { Comment } from './comment.model';
|
||||
import { Configuration } from './configuration.model';
|
||||
import { User } from './user.model';
|
||||
|
||||
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: string,
|
||||
public description?: string,
|
||||
public slug: string = uuidv4(),
|
||||
public configuration: Configuration = new Configuration(),
|
||||
public comments: Comment[] = [],
|
||||
public choices: Choice[] = [],
|
||||
public answersByChoiceByParticipant: Map<string, Map<string, ResponseType>> = new Map<
|
||||
string,
|
||||
Map<string, ResponseType>
|
||||
>(),
|
||||
public answers: PollUserAnswers[] = []
|
||||
) {}
|
||||
|
||||
public getUrl(): string {
|
||||
return `${environment.api.baseHref}/${this.slug}`;
|
||||
public getAdministrationUrl(): string {
|
||||
return `${environment.api.baseHref}/administration/polls/${this.slug}`;
|
||||
}
|
||||
|
||||
public getParticipationUrl(): string {
|
||||
return `${environment.api.baseHref}/participation/polls/${this.slug}`;
|
||||
}
|
||||
|
||||
public static adaptFromLocalJsonServer(
|
||||
item: Pick<
|
||||
Poll,
|
||||
| 'owner'
|
||||
| 'question'
|
||||
| 'description'
|
||||
| 'slug'
|
||||
| 'configuration'
|
||||
| 'comments'
|
||||
| 'choices'
|
||||
| 'answersByChoiceByParticipant'
|
||||
| 'answers'
|
||||
>
|
||||
): Poll {
|
||||
const poll = new Poll(
|
||||
new User(item.owner.pseudo, item.owner.email, undefined),
|
||||
item.question,
|
||||
item.description,
|
||||
item.slug,
|
||||
item.configuration,
|
||||
item.comments
|
||||
.map(
|
||||
(c: Pick<Comment, 'author' | 'content' | 'dateCreated'>) =>
|
||||
new Comment(c.author, c.content, new Date(c.dateCreated))
|
||||
)
|
||||
.sort(Comment.sortChronologically),
|
||||
item.choices.map((c: Pick<Choice, 'label' | 'imageUrl' | 'participants' | 'imageUrl'>) => {
|
||||
const choice = new Choice(c.label, c.imageUrl, new Map(c.participants));
|
||||
choice.participants.forEach((value, key) => {
|
||||
choice.participants.set(key, new Set(value));
|
||||
});
|
||||
choice.updateCounts();
|
||||
return choice;
|
||||
})
|
||||
);
|
||||
|
||||
// handle answersByChoiceByParticipant
|
||||
for (const [pseudo, answersByChoice] of Object.entries(item.answersByChoiceByParticipant)) {
|
||||
if (!poll.answersByChoiceByParticipant.has(pseudo)) {
|
||||
poll.answersByChoiceByParticipant.set(pseudo, new Map<string, ResponseType>());
|
||||
}
|
||||
for (const [choiceLabel, answer] of Object.entries(answersByChoice)) {
|
||||
poll.answersByChoiceByParticipant.get(pseudo).set(choiceLabel, answer as ResponseType);
|
||||
}
|
||||
}
|
||||
|
||||
// handle answers
|
||||
poll.answers = item.answers.map(
|
||||
(pollUserAnswers: Pick<PollUserAnswers, 'pseudo' | 'token' | 'responsesByChoices'>) =>
|
||||
new PollUserAnswers(pollUserAnswers.pseudo, pollUserAnswers.token, pollUserAnswers.responsesByChoices)
|
||||
);
|
||||
|
||||
return poll;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,3 @@
|
||||
export class Question {
|
||||
constructor(public label: string, public description: string) {}
|
||||
}
|
@ -1,12 +1,6 @@
|
||||
import { Poll } from './poll.model';
|
||||
import { UserRole } from '../enums/user-role.enum';
|
||||
import { Poll } from './poll.model';
|
||||
|
||||
export class User {
|
||||
constructor(
|
||||
public role: UserRole = UserRole.ANONYMOUS,
|
||||
public isOwner: boolean = false,
|
||||
public pseudo?: string,
|
||||
public email?: string,
|
||||
public polls?: Poll[]
|
||||
) {}
|
||||
constructor(public pseudo: string, public email: string, public role?: UserRole, public polls?: Poll[]) {}
|
||||
}
|
||||
|