Merge branch 'several-steps-creation' into 'master'

5 steps to create a poll

See merge request framasoft/framadate/funky-framadate-front!49
This commit is contained in:
ty kayn 2021-11-16 18:46:40 +00:00
commit c1ec249e02
84 changed files with 2162 additions and 961 deletions

View File

@ -40,7 +40,7 @@ build:
stage: build
script:
- yarn install --pure-lockfile
- npx ng build --prod
- yarn run build:prod
cache:
policy: pull

View File

@ -45,10 +45,12 @@
"@fullcalendar/core": "^4.4.0",
"@ngx-translate/core": "^12.1.2",
"@ngx-translate/http-loader": "^5.0.0",
"angular-date-value-accessor": "^1.0.2",
"axios": "^0.19.2",
"bulma": "^0.9.0",
"bulma-switch": "^2.0.0",
"chart.js": "^2.9.3",
"crypto": "^1.0.1",
"crypto-js": "^4.0.0",
"fork-awesome": "^1.1.7",
"ng-keyboard-shortcuts": "^10.1.17",
@ -57,6 +59,7 @@
"ngx-markdown": "^9.0.0",
"ngx-webstorage": "^5.0.0",
"node-forge": "^0.10.0",
"primeng": "^11.0.0",
"quill": "^1.3.7",
"rxjs": "^6.5.5",
"rxjs-compat": "^6.5.5",
@ -75,9 +78,9 @@
"@babel/preset-env": "^7.9.5",
"@babel/preset-typescript": "^7.9.0",
"@compodoc/compodoc": "^1.1.11",
"@types/crypto-js": "^4.0.0",
"@types/jest": "^26.0.0",
"@types/node": "^14.0.1",
"@types/crypto-js": "^4.0.0",
"@typescript-eslint/eslint-plugin": "^3.0.0",
"@typescript-eslint/parser": "^3.0.0",
"babel-jest": "^26.0.0",

View File

@ -1,4 +1,4 @@
import { AfterViewInit, Component, OnDestroy, OnInit } from '@angular/core';
import { AfterViewInit, Component, Inject, OnDestroy, OnInit } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { Subscription } from 'rxjs';
@ -11,6 +11,12 @@ import { slideInAnimation } from './shared/animations/main';
import { FramaKeyboardShortcuts } from './shared/shortcuts/main';
import { ShortcutEventOutput, ShortcutInput } from 'ng-keyboard-shortcuts';
import { PollService } from './core/services/poll.service';
import { Poll } from './core/models/poll.model';
import { PollDTO } from './core/models/poll.DTO.model';
import { PrimeNGConfig } from 'primeng/api';
import { Language } from './core/enums/language.enum';
import { ApiService } from './core/services/api.service';
import { DOCUMENT } from '@angular/common';
@Component({
selector: 'app-root',
@ -35,13 +41,15 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewInit {
private titleService: Title,
private themeService: ThemeService,
private pollService: PollService,
private apiService: ApiService,
private config: PrimeNGConfig,
@Inject(DOCUMENT) private document: any,
private languageService: LanguageService // private mockingService: MockingService
) {}
printpath(parent: string, config: Route[]) {
for (let i = 0; i < config.length; i++) {
const route = config[i];
console.info(parent + '/' + route.path);
if (route.children) {
const currentPath = route.path ? parent + '/' + route.path : parent;
this.printpath(currentPath, route.children);
@ -50,21 +58,29 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewInit {
}
ngOnInit(): void {
this.languageService.getPrimeNgStrings().subscribe((resp) => {
this.config.setTranslation(resp);
});
this.printpath('', this.router.config);
this.router.events.subscribe((evt) => {
console.log('route changed', evt);
if (!(evt instanceof NavigationEnd)) {
return;
}
window.scrollTo(0, 0);
let mainelem = this.document.querySelector('#big_container main');
console.log('mainelem', mainelem);
window.scrollTo(0, mainelem.offsetTop);
});
if (!environment.production) {
this.appTitle += ' [DEV]';
}
const loadedPoll = this.pollService._poll.getValue();
let loadedPoll;
if (this.pollService.poll) {
loadedPoll = this.pollService.poll;
}
this.titleService.setTitle(this.appTitle + ' - ' + loadedPoll.title);
this.languageService.configureAndInitTranslations();
@ -83,9 +99,12 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewInit {
this.themeClass = 'theme-light-watermelon';
}
});
// debug cors
this.apiService.getAllAvailablePolls();
}
ngAfterViewInit(): void {
console.log('this.shortcuts', this.shortcuts);
this.shortcuts.push(
{
key: '?',
@ -108,7 +127,6 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewInit {
preventDefault: true,
}
);
console.log('this.shortcuts', this.shortcuts);
}
ngOnDestroy(): void {
@ -117,10 +135,6 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewInit {
}
}
public toggleSidebar(status: boolean): void {
this.isSidebarOpened = status === true;
}
prepareRoute(outlet: RouterOutlet) {
return outlet && outlet.activatedRouteData && outlet.activatedRouteData.animation;
}

View File

@ -26,5 +26,33 @@
canal Matrix
</a>
</p>
<div>
<div class="columns">
<div class="column">
<h3 class="title is-3">Framasoft</h3>
Lassociation,Notre charte,Contacter Framasoft,Statistiques,État des services
</div>
<div class="column">
<h3 class="title is-3">Communauté</h3>
Framacolibri,Participer,Bénévolat valorisé,Partenaires,Charte de modération
</div>
<div class="column">
<h3 class="title is-3">Framadate</h3>
Entraide,Guides et astuces,Mentions légales,CGU,Crédits
</div>
<div class="column">
<form action="#">
<label for="subscribe_newsletter">
<i class="fa fa-enveloppe"></i>
Inscrivez-vous à la newsletter
</label>
<input type="text" class="input" id="subscribe_newsletter" />
<button class="button is-outlined is-primary" (click)="subscribeToNewsletter()">
S'inscrire
</button>
</form>
</div>
</div>
</div>
</div>
</footer>

View File

@ -11,4 +11,8 @@ export class FooterComponent implements OnInit {
constructor() {}
ngOnInit(): void {}
subscribeToNewsletter() {
alert('TODO');
}
}

View File

@ -123,4 +123,10 @@
</div>
</div>
</div>
<a
class="has-text-black"
href="/#/administration/key/8Ubcg2YI99f69xz946cn4O64bQAebi11012P70trL5372qJ9JOem1Ks2fz7XD0b09p-8Ubcg2YI99f69xz946cn4O64bQAeb"
>
<i class="fa fa-hand-paper-o"></i> test admin link to edit poll
</a>
</header>

View File

@ -1,5 +1,20 @@
<section class="hero">
<div class="hero-body">
<div class="columns presentation">
<div class="column">
<h2 class="title is-2">
Organisez des évènements ou récoltez lopinion de vos proches, simplement, librement.
</h2>
<p>
Grâce à Framadate planifiez, organisez et prenez des décisions rapidement, simplement et sans
inscription.
</p>
</div>
<div class="column">
<img src="assets/img/icone_home.png" alt="calendrier icone framadate" />
</div>
</div>
<div class="container">
<div class="column">
<section class="creation">
@ -22,7 +37,9 @@
*ngIf="environment.showDemoWarning"
class="demo demo-warning well has-background-warning-light padded marged"
>
Ce que l'on peut faire sur cette démo:
<h3 class="title is-3">
Ce que l'on peut faire sur cette démo:
</h3>
<ul>
<li>
☑️ Créer un nouveau sondage
@ -39,7 +56,9 @@
>
</li>
</ul>
Ce qu'on ne peut pas encore faire:
<h3 class="title is-3">
Ce qu'on ne peut pas encore faire:
</h3>
<ul>
<li>
🚴‍️ mettre à jour son vote à un sondage
@ -58,6 +77,8 @@
<h1 class="title is-1 is-centered">
{{ 'home.search_title' | translate }}
</h1>
<img src="assets/img/where-is-it.jpg" alt="batman veut savoir où sont ses sondages" />
<div class="poll-list">
<ul>
<li *ngFor="let p of storageService.userPolls">
@ -75,7 +96,7 @@
<form (ngSubmit)="searchMyPolls()">
<div class="search-others">
<label for="search_email">
Je cherche d'autres sondages, qui correspondent à mon mail :
{{ 'home.search_subtitle' | translate }}
</label>
<input
type="email"
@ -139,9 +160,11 @@
<div class="column"></div>
</div>
<div class="columns">
<div class="column"></div>
<div class="column">
<img src="assets/img/kind/classic.jpeg" alt="sondage date" />
<img src="assets/img/kind/date.jpeg" alt="sondage date" />
</div>
<div class="column">
<img src="assets/img/kind/classic.jpeg" alt="sondage classique" />
</div>
</div>

View File

@ -1,5 +1,9 @@
@import '../../../../styles/variables';
:host {
text-align: center;
margin-left: -2em;
margin-right: -2em;
a .fa {
margin-right: 1ch;
}
@ -9,4 +13,7 @@
.poll-list {
margin: 2em 0;
}
.presentation {
background: $secondary_color;
}
}

View File

@ -4,6 +4,7 @@ import { StorageService } from '../../services/storage.service';
import { ApiService } from '../../services/api.service';
import { ToastService } from '../../services/toast.service';
import { DOCUMENT } from '@angular/common';
import { Title } from '@angular/platform-browser';
@Component({
selector: 'app-home',
@ -18,8 +19,11 @@ export class HomeComponent {
@Inject(DOCUMENT) private document: any,
public storageService: StorageService,
public toastService: ToastService,
public titleService: Title,
private api: ApiService
) {}
) {
this.titleService.setTitle(environment.appTitle + ' - Accueil ');
}
searchMyPolls() {
const email = this.storageService.vote_stack.owner.email;

View File

@ -6,8 +6,8 @@ export class Owner {
public pseudo: string = 'pseudo',
public email: string = '_nonexistent_contact@cipherbliss.com',
public polls: Poll[] = [],
public role?: UserRole,
public modifier_token?: string,
public created_at?: string
public role: UserRole = UserRole.ADMIN,
public modifier_token: string = '',
public created_at: string = new Date().toISOString()
) {}
}

View File

@ -0,0 +1,46 @@
import { Choice, ChoiceGroup } from './choice.model';
import { DateChoice, TimeSlices } from './dateChoice.model';
export class PollDTO {
menuVisible = true;
expiracyDateDefaultInDays;
deletionDateAfterLastModification;
pollType: string = 'date'; // classic or dates
title;
description;
myName;
myComment = '';
isAdmin; // when we create a poll; we are admin on it
myVoteStack;
myTempVoteStack;
myEmail;
myPolls; // list of retrieved polls from the backend api
allowSeveralHours;
visibility; // visible to one with the link:
voteChoices = 'yes; maybe; no'; // possible answers to a vote choice: only "yes"; "yes; maybe; no"
created_at;
expirationDate; // expiracy date
voteStackId; // id of the vote stack to update
pollId; // id of the current poll when created. data given by the backend api
pollSlug; // id of the current poll when created. data given by the backend api
currentPoll; // current poll selected with createPoll or getPoll of ConfigService
passwordAccess;
password;
customUrl; // custom slug in the url; must be unique
customUrlIsUnique; // given by the backend
urlSlugPublic;
urlPublic;
urlAdmin;
adminKey; // key to change config of the poll
owner_modifier_token; // key to change a vote stack
canModifyAnswers; // bool for the frontend selector
whoModifiesAnswers; // everybody; self; nobody (: just admin)
whoCanChangeAnswers; // everybody; self; nobody (: just admin)
dateList; // sets of days as strings; config to set identical time for days in a special days poll
timeList; // ranges of time expressed as strings
answers;
displayConfirmVoteModalAdmin;
constructor() {}
}

View File

@ -16,6 +16,8 @@ const apiBaseHref = environment.api.version[apiVersion].baseHref;
const apiEndpoints = environment.api.endpoints;
class PollDTO {}
@Injectable({
providedIn: 'root',
})
@ -36,16 +38,17 @@ export class ApiService {
this.axiosInstance = axios.create({ baseURL: apiBaseHref });
this.axiosInstance.defaults.timeout = 2500;
this.axiosInstance.defaults.headers.post['Content-Type'] = 'application/json';
this.axiosInstance.defaults.headers.post['Accept'] = 'application/json';
this.axiosInstance.defaults.headers.post['Charset'] = 'UTF-8';
// this.axiosInstance.defaults.headers.post['Content-Type'] = 'application/json';
// this.axiosInstance.defaults.headers.post['Accept'] = 'application/json';
// this.axiosInstance.defaults.headers.post['Charset'] = 'UTF-8';
// this.axiosInstance.defaults.headers.post['Accept-Charset'] = 'UTF-8';
this.axiosInstance.defaults.headers.post['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS';
this.axiosInstance.defaults.headers.post['Referrer-Policy'] = 'origin-when-cross-origin';
this.axiosInstance.defaults.headers.post['Access-Control-Allow-Origin'] = '*';
this.axiosInstance.defaults.headers.post['Allow-Origin'] = '*';
this.axiosInstance.defaults.headers.post['Access-Control-Allow-Headers'] =
'Origin, X-Requested-With, Content-Type, Accept';
// this.axiosInstance.defaults.headers.post['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS';
// this.axiosInstance.defaults.headers.post['Referrer-Policy'] = 'origin-when-cross-origin';
// this.axiosInstance.defaults.headers.post['Access-Control-Allow-Origin'] = '*';
// this.axiosInstance.defaults.headers.post['Control-Allow-Origin'] = '*';
// this.axiosInstance.defaults.headers.post['Allow-Origin'] = '*';
// this.axiosInstance.defaults.headers.post['Access-Control-Allow-Headers'] =
// 'Origin, X-Requested-With, Content-Type, Accept';
console.log('this.axiosInstance.defaults.headers', this.axiosInstance.defaults.headers);
}
@ -66,25 +69,29 @@ export class ApiService {
// Accept: 'application/json',
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json',
mode: 'no-cors',
// mode: 'no-cors',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Accept,Accept-Language,Content-Language,Content-Type',
// 'Access-Control-Allow-Origin': '*',
};
return {
const headersAxios = {
headers: headerDict,
body: bodyContent,
};
console.log('headersAxios', headersAxios);
return headersAxios;
}
public async createPoll(poll: Poll): Promise<Subscription> {
/**
*
* @param poll
*/
public async createPoll(poll: PollDTO): Promise<Subscription> {
// this.loaderService.setStatus(true);
console.log('createPoll config', poll);
console.log('apiservice createPoll config', poll);
return this.axiosInstance.post(
`${this.baseHref}${currentApiRoutes['api_new_poll']}`,
poll,
ApiService.makeHeaders()
poll
// ApiService.makeHeaders()
);
}
@ -134,10 +141,14 @@ export class ApiService {
}
}
//////////
/**
* get all polls published by the API
*/
public async getAllAvailablePolls(): Promise<Poll[]> {
// TODO: used for facilities in DEV, should be removed in production
try {
this.axiosInstance.options(this.pollsEndpoint);
const response: AxiosResponse<Poll[]> = await this.axiosInstance.get<Poll[]>(`${this.pollsEndpoint}`);
return response?.data;
} catch (error) {
@ -145,10 +156,32 @@ export class ApiService {
}
}
public async getPollByCustomUrl(slug: string): Promise<Poll | undefined> {
/**
* get one poll by its admin key
* @param admin_key
*/
public async getPollByAdminKey(admin_key: string): Promise<any | undefined> {
try {
console.log('fetch API : asking for poll with custom_url=' + slug);
const response: AxiosResponse<Poll> = await this.axiosInstance.get<Poll>(`${this.pollsEndpoint}/${slug}`);
console.log('fetch API : asking for poll with admin_key=' + admin_key);
const response: AxiosResponse<Poll> = await this.axiosInstance.get<any>(
`${this.pollsEndpoint}/admin/${admin_key}`
);
return response && response.data && !Array.isArray(response.data) ? response.data : undefined;
} catch (error) {
if (error.response?.status === 404) {
return undefined;
} else {
ApiService.handleError(error);
}
}
}
public async getPollByCustomUrl(custom_url: string): Promise<Poll | undefined> {
try {
console.log('fetch API : asking for poll with custom_url=' + custom_url);
const response: AxiosResponse<Poll> = await this.axiosInstance.get<Poll>(
`${this.pollsEndpoint}/${custom_url}`
);
return response && response.data && !Array.isArray(response.data) ? response.data : undefined;
} catch (error) {
@ -160,12 +193,12 @@ export class ApiService {
}
}
public async getPollByCustomUrlWithHash(slug: string, hash: string): Promise<Poll | undefined> {
public async getPollByCustomUrlWithHash(custom_url: string, hash: string): Promise<Poll | undefined> {
try {
const response: AxiosResponse<Poll> = await this.axiosInstance.get<Poll>(
`${this.pollsEndpoint}/${slug}/pass/${hash}`
`${this.pollsEndpoint}/${custom_url}/pass/${hash}`
);
console.log('fetch API : asking for poll with custom_url=' + slug, { response });
console.log('fetch API : asking for poll with custom_url=' + custom_url, { response });
return response && response.data && !Array.isArray(response.data) ? response.data : undefined;
} catch (error) {
@ -179,11 +212,11 @@ export class ApiService {
}
}
public async getSlug(slug: string): Promise<boolean> {
public async getSlug(custom_url: string): Promise<boolean> {
try {
// TODO: scenario should be : if we can get this custom_url, it exists. if not, it doesn't. It's just a GET.
const response: AxiosResponse = await this.axiosInstance.get(
`${this.pollsEndpoint}${this.slugsEndpoint}/${slug}`
`${this.pollsEndpoint}${this.slugsEndpoint}/${custom_url}`
);
if (response?.status !== 404) {
return false;
@ -199,7 +232,7 @@ export class ApiService {
////////////
// UPDATE //
////////////
public async sendUpdateVoteStack(vote_stack: Stack) {
try {
return await this.axiosInstance.patch(

View File

@ -100,8 +100,6 @@ export class DateUtilitiesService {
const ladate2 = this.addDaysToDate(1, today);
const ladate3 = this.addDaysToDate(2, today);
const ladate4 = this.addDaysToDate(3, today);
const ladate5 = this.addDaysToDate(4, today);
const ladate6 = this.addDaysToDate(5, today);
return [
{
@ -124,16 +122,6 @@ export class DateUtilitiesService {
timeSlices: Object.create(defaultTimeOfDay),
date_object: ladate4,
},
{
literal: this.formateDateToInputStringNg(ladate5),
timeSlices: Object.create(defaultTimeOfDay),
date_object: ladate5,
},
{
literal: this.formateDateToInputStringNg(ladate6),
timeSlices: Object.create(defaultTimeOfDay),
date_object: ladate6,
},
];
}
}

View File

@ -37,6 +37,10 @@ export class LanguageService {
this.setLanguageOnInit();
}
public getPrimeNgStrings() {
return this.translate.get('calendar_widget');
}
private setLanguageOnInit(): void {
// set language from storage
if (!this.translate.currentLang) {

View File

@ -1,11 +1,10 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
import { BehaviorSubject, Observable } from 'rxjs';
import { Inject, Injectable } from '@angular/core';
import { ActivatedRoute, ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
import { Answer } from '../enums/answer.enum';
import { Choice } from '../models/choice.model';
import { Poll } from '../models/poll.model';
import { Owner } from '../models/owner.model';
import { ApiService } from './api.service';
import { ToastService } from './toast.service';
import { UserService } from './user.service';
@ -14,7 +13,12 @@ import { HttpClient } from '@angular/common/http';
import { environment } from '../../../environments/environment';
import { StorageService } from './storage.service';
import { Title } from '@angular/platform-browser';
import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { DOCUMENT } from '@angular/common';
import { DateChoice, TimeSlices } from '../models/dateChoice.model';
import { DateUtilitiesService } from './date.utilities.service';
import { Owner } from '../models/owner.model';
import { Stack } from '../models/stack.model';
import { Vote } from '../models/vote.model';
@ -22,9 +26,29 @@ import { Vote } from '../models/vote.model';
providedIn: 'root',
})
export class PollService implements Resolve<Poll> {
_poll: BehaviorSubject<Poll | undefined> = new BehaviorSubject<Poll | undefined>(undefined);
public _poll: BehaviorSubject<Poll | undefined> = new BehaviorSubject<Poll | undefined>(undefined);
public readonly poll: Observable<Poll | undefined> = this._poll.asObservable();
public form: FormGroup;
public startDateInterval: string;
public endDateInterval: string;
public intervalDays: number = 1;
public intervalDaysDefault = 7;
public dateList: DateChoice[] = []; // sets of days as strings, config to set identical time for days in a special days poll
public timeList: TimeSlices[] = []; // ranges of time expressed as strings
public previousRouteName: string = '/administration';
public nextRouteName: string = '/administration/step/2';
public step_current: number = 1;
public step_max: number = 5;
public round: Function;
public pass_hash: string;
public admin_key: string;
public urlPrefix: string = window.location.origin;
public advancedDisplayEnabled = false;
public showDateInterval = false;
public allowSeveralHours = false;
public richTextMode = false;
public calendar: Date[] = [new Date()];
public disabled_dates: Date[] = [];
constructor(
private http: HttpClient,
@ -33,15 +57,132 @@ export class PollService implements Resolve<Poll> {
private storageService: StorageService,
private userService: UserService,
private uuidService: UuidService,
private dateUtils: DateUtilitiesService,
private toastService: ToastService,
private titleService: Title,
private toastService: ToastService
public DateUtilitiesService: DateUtilitiesService,
public route: ActivatedRoute,
@Inject(DOCUMENT) private document: any,
private fb: FormBuilder
) {
this._poll.next(new Poll(null, 'titre', 'custom-title'));
this.createFormGroup();
// fill in the next 3 days of the calendar date picker
this.calendar = [
this.DateUtilitiesService.addDaysToDate(1, new Date()),
this.DateUtilitiesService.addDaysToDate(2, new Date()),
this.DateUtilitiesService.addDaysToDate(3, new Date()),
];
// disable days before today
for (let i = 1; i < 31; i++) {
this.disabled_dates.push(this.DateUtilitiesService.addDaysToDate(-i, new Date()));
}
if (environment.autofill_creation) {
this.setDemoValues();
this.toastService.display('auto fill de création fait');
}
if (environment.autoSendNewPoll) {
this.createPoll();
}
}
/**
* add example values to the form for demo env
*/
setDemoValues(): void {
this.form.patchValue({
title: 'Mon titre de sondage du ' + this.DateUtilitiesService.formateDateToInputStringNg(new Date()),
description: 'répondez SVP <3 ! *-* ',
custom_url: this.uuidService.getUUID(),
creatorPseudo: 'Chuck Norris',
creatorEmail: 'chucknorris@example.com',
isAboutDate: true,
whoModifiesAnswers: 'everybody',
whoCanChangeAnswers: 'everybody',
isProtectedByPassword: false,
richTextMode: false,
areResultsPublic: true,
expiresDaysDelay: environment.expiresDaysDelay,
});
this.automaticSlug();
}
/**
* set the poll slug from other data of the poll
*/
automaticSlug() {
this.form.patchValue({ custom_url: this.makeSlug(this.form) });
}
public createFormGroup() {
let minlengthValidation = environment.production ? 12 : 0;
let form = this.fb.group({
title: ['mon titre de sondage', [Validators.required, Validators.minLength(minlengthValidation)]],
creatorPseudo: ['', [Validators.required]],
created_at: [new Date(), [Validators.required]],
creatorEmail: ['', [Validators.required]],
custom_url: [this.uuidService.getUUID(), [Validators.required]],
description: ['', [Validators.required]],
password: ['', []],
choices: new FormArray([]),
whoModifiesAnswers: ['', [Validators.required]],
whoCanChangeAnswers: ['', [Validators.required]],
isAboutDate: [true, [Validators.required]],
expiresDaysDelay: [environment.expiresDaysDelay, []],
maxCountOfAnswers: [300, []],
isZeroKnoledge: [false, [Validators.required]],
isProtectedByPassword: [false, [Validators.required]],
isOwnerNotifiedByEmailOnNewVote: [true, [Validators.required]],
isOwnerNotifiedByEmailOnNewComment: [true, [Validators.required]],
areResultsPublic: [true, [Validators.required]],
richTextMode: [false, [Validators.required]],
isYesAnswerAvailable: [true, [Validators.required]],
isMaybeAnswerAvailable: [true, [Validators.required]],
isNoAnswerAvailable: [true, [Validators.required]],
allowComments: [true, [Validators.required]],
hasMaxCountOfAnswers: [300, [Validators.required]],
useVoterUniqueLink: [false, [Validators.required]],
voterEmailList: ['', []],
allowNewDateTime: [true, [Validators.required]],
});
this.form = form;
return form;
}
/**
* set default configs to the form
*/
public patchFormDefaultValues() {
this.form.patchValue({
title: 'mon titre de sondage',
description: '',
custom_url: this.uuidService.getUUID(),
creatorPseudo: '',
creatorEmail: '',
isAboutDate: true,
whoModifiesAnswers: 'everybody',
whoCanChangeAnswers: 'everybody',
isProtectedByPassword: false,
richTextMode: false,
areResultsPublic: true,
expiresDaysDelay: environment.expiresDaysDelay,
maxCountOfAnswers: 300,
voterEmailList: '',
password: '',
});
this.setDefaultDatesForInterval();
}
/**
* get a new slug from form title and creation date
*/
public updateSlug(): void {
console.log('this.form.value', this.form.value);
this.form.patchValue({ custom_url: this.makeSlug(this.form) });
}
/**
* auto fetch a poll when route is looking for one in the administration pattern
* DO NOT USE - needs refacto
* @param route
* @param state
*/
@ -57,9 +198,9 @@ export class PollService implements Resolve<Poll> {
) {
if (this.pass_hash) {
this.storageService.vote_stack.pass_hash = this.pass_hash;
await this.loadPollBycustom_urlWithPasswordHash(wantedcustom_url, this.pass_hash);
await this.loadPollByCustomUrlWithPasswordHash(wantedcustom_url, this.pass_hash);
} else {
await this.loadPollBycustom_url(wantedcustom_url);
await this.loadPollByCustomUrl(wantedcustom_url);
}
}
const loadedPoll = this._poll.getValue();
@ -77,17 +218,19 @@ export class PollService implements Resolve<Poll> {
*/
getAllAvailablePolls(): void {
const baseHref = environment.api.version.apiV1.baseHref;
console.log('getAllAvailablePolls baseHref', baseHref);
const headers = ApiService.makeHeaders();
console.log('getAllAvailablePolls headers', headers);
try {
this.http.get(`${baseHref}/poll`, headers).subscribe((res: Observable<any>) => {
console.log('getAllAvailablePolls res', res);
});
} catch (e) {
console.error('getAllAvailablePolls e', e);
console.log('getAllAvailablePolls e', e);
}
}
public async loadPollBycustom_url(custom_url: string): Promise<void> {
public async loadPollByCustomUrl(custom_url: string): Promise<void> {
if (custom_url) {
const poll: Poll | undefined = await this.apiService.getPollByCustomUrl(custom_url);
@ -103,7 +246,7 @@ export class PollService implements Resolve<Poll> {
}
}
public async loadPollBycustom_urlWithPasswordHash(custom_url: string, hash: string): Promise<void> {
public async loadPollByCustomUrlWithPasswordHash(custom_url: string, hash: string): Promise<void> {
if (custom_url) {
const poll: Poll | undefined = await this.apiService.getPollByCustomUrlWithHash(custom_url, hash);
@ -123,50 +266,243 @@ export class PollService implements Resolve<Poll> {
* update poll and parse its fields
* @param poll
*/
public updateCurrentPoll(poll: Poll): void {
public updateCurrentPoll(poll: Poll): Poll {
console.log('this.storageService.vote_stack.id', this.storageService.vote_stack.id);
if (!this.storageService.vote_stack.id || this.storageService.vote_stack.poll_custom_url !== poll.custom_url) {
console.log('set base choices', poll.choices);
// set the choices only the first time the poll loads, or if we changed the poll
console.log(
'this.storageService.vote_stack.poll_custom_url',
this.storageService.vote_stack.poll_custom_url
);
// this.storageService.setChoicesForVoteStack(poll.choices);
}
// if (!this.storageService.vote_stack.id || this.storageService.vote_stack.poll_custom_url !== poll.custom_url) {
// console.log('set base choices', poll.choices);
// // set the choices only the first time the poll loads, or if we changed the poll
// console.log(
// 'this.storageService.vote_stack.poll_custom_url',
// this.storageService.vote_stack.poll_custom_url
// );
// this.storageService.setChoicesForVoteStack(poll.choices);
// }
this.toastService.display('sondage bien mis à jour', 'success');
this._poll.next(poll);
console.log('next poll', poll);
this.storageService.setChoicesForVoteStack(poll.choices);
this.toastService.display(`sondage ${poll.title} bien mis à jour`, 'success');
return poll;
}
/**
* make a uniq custom_url for the current poll creation
* @param poll
* add all the dates between the start and end dates in the interval section
*/
makecustom_url(poll: Poll): string {
addIntervalOfDates(): void {
const newIntervalArray = this.DateUtilitiesService.getDatesInRange(
this.DateUtilitiesService.parseInputDateToDateObject(new Date(this.startDateInterval)),
this.DateUtilitiesService.parseInputDateToDateObject(new Date(this.endDateInterval)),
1
);
const converted = [];
newIntervalArray.forEach((element) => {
converted.push({
literal: element.literal,
date_object: element.date_object,
timeList: [],
});
});
this.dateList = [...new Set(converted)];
// add only dates that are not already present with a Set of unique items
console.log('this.dateList', this.dateList);
this.showDateInterval = false;
this.form.patchValue({ choices: this.dateList });
this.toastService.display(`les dates ont été ajoutées aux réponses possibles.`);
}
/**
* handle keyboard shortcuts
* @param $event
* @param choice_number
*/
keyOnChoice($event: KeyboardEvent, choice_number: number): void {
$event.preventDefault();
const lastChoice = this.choices.length - 1 === choice_number;
// reset field with Ctrl + D
// add a field with Ctrl + N
// go to previous choice with arrow up
// go to next choice with arrow down
if ($event.key == 'ArrowUp' && choice_number > 0) {
this.focusOnChoice(choice_number - 1);
}
if ($event.key == 'ArrowDown') {
// add a field if we are on the last choice
if (lastChoice) {
this.addChoice();
this.toastService.display('choix ajouté par raccourci "flèche bas"');
} else {
this.focusOnChoice(choice_number + 1);
}
}
if ($event.ctrlKey && $event.key == 'Backspace') {
this.deleteChoiceField(choice_number);
this.toastService.display('choix supprimé par raccourci "Ctrl + retour"');
this.focusOnChoice(Math.min(choice_number - 1, 0));
}
if ($event.ctrlKey && $event.key == 'Enter') {
// go to other fields
const elem = this.document.querySelector('#creatorEmail');
if (elem) {
elem.focus();
}
}
}
/**
* change time spans
*/
addTime() {
this.timeList.push({
literal: '',
});
}
removeAllTimes() {
this.timeList = [];
}
resetTimes() {
this.timeList = [];
}
/**
* add a time period to a specific date choice,
* focus on the new input
* @param config
* @param id
*/
addTimeToDate(config: any, id: number) {
this.timeList.push({
literal: '',
});
const selector = '[ng-reflect-choice_label="dateTime_' + id + '_Choices_' + (this.timeList.length - 1) + '"]';
const elem = this.document.querySelector(selector);
if (elem) {
elem.focus();
}
}
/**
* convert form data to DTO to create a new poll, and store the admin key
*/
public createPoll(): Promise<any> {
this.toastService.display('sending...');
const newpoll = this.newPollFromForm();
return this.apiService.createPoll(newpoll).then(
(resp: any) => {
console.log('poll created resp', resp);
this.admin_key = resp.data.poll.admin_key;
this.storageService.userPolls.push(resp.data.poll);
},
(error) => {
this.toastService.display('BOOM, the createPoll went wrong');
this.apiService.ousideHandleError(error);
}
);
}
/**
* default interval of dates proposed is from today to 7 days more
*/
setDefaultDatesForInterval(): void {
const dateCurrent = new Date();
const dateJson = dateCurrent.toISOString();
this.startDateInterval = dateJson.substring(0, 10);
this.endDateInterval = this.DateUtilitiesService.addDaysToDate(this.intervalDaysDefault, dateCurrent)
.toISOString()
.substring(0, 10);
this.form.patchValue({
startDateInterval: this.startDateInterval,
endDateInterval: this.endDateInterval,
});
this.countDays();
}
askInitFormDefault(): void {
this.initFormDefault(environment.autofill_creation);
this.toastService.display('formulaire réinitialisé');
}
countDays(): void {
this.intervalDays = this.DateUtilitiesService.countDays(
this.DateUtilitiesService.parseInputDateToDateObject(new Date(this.startDateInterval)),
this.DateUtilitiesService.parseInputDateToDateObject(new Date(this.endDateInterval))
);
}
focusOnChoice(index): void {
const selector = '#choice_label_' + index;
const elem = this.document.querySelector(selector);
if (elem) {
elem.focus();
}
}
deleteChoiceField(index: number): void {
if (this.choices.length !== 1) {
this.choices.removeAt(index);
}
}
initFormDefault(showDemoValues = true): void {
this.form = this.createFormGroup();
this.patchFormDefaultValues();
this.setDefaultDatesForInterval();
if (showDemoValues) {
this.setDemoValues();
}
}
get choices(): FormArray {
return this.form.get('choices') as FormArray;
}
reinitChoices(): void {
this.choices.setValue([]);
}
addChoice(optionalLabel = ''): void {
const newControlGroup = this.fb.group({
label: this.fb.control('', [Validators.required]),
imageUrl: ['', [Validators.required]],
});
if (optionalLabel) {
newControlGroup.patchValue({
label: optionalLabel,
imageUrl: 'mon url',
});
}
this.choices.push(newControlGroup);
this.focusOnChoice(this.choices.length - 1);
}
/**
* make a uniq slug for the current poll creation
* @param form
*/
makeSlug(form: FormGroup): string {
let str = '';
const creation_date = new Date(poll.creation_date);
str =
creation_date.getFullYear() +
form.value.created_at.getFullYear() +
'_' +
(creation_date.getMonth() + 1) +
(form.value.created_at.getMonth() + 1) +
'_' +
creation_date.getDate() +
form.value.created_at.getDate() +
'_' +
poll.owner.pseudo +
form.value.creatorPseudo +
'_' +
poll.title;
return this.convertTextToSlug(str) + '-' + this.uuidService.getUUID();
}
/**
* convert a text to a slug
* @param str
*/
public convertTextToSlug(str: string): string {
str = str.trim();
form.value.title;
str = str.replace(/^\s+|\s+$/g, ''); // trim
str = str.toLowerCase();
@ -181,10 +517,25 @@ export class PollService implements Resolve<Poll> {
.replace(/[^a-z0-9 -]/g, '') // remove invalid chars
.replace(/\s+/g, '-') // collapse whitespace and replace by -
.replace(/-+/g, '-'); // collapse dashes
return str;
return str + '-' + this.uuidService.getUUID();
}
public async saveCurrentPoll(): Promise<void> {
const pollUrl: Subscription = await this.apiService.createPoll(this._poll.getValue());
// TODO: Maybe handle the url to update currentPoll according to backend response
if (pollUrl) {
this.toastService.display('Le sondage a été enregistré.');
} else {
this.toastService.display('Le sondage na été correctement enregistré, veuillez ré-essayer.');
}
}
public saveParticipation(choice: Choice, user: Owner, response: Answer): void {
const currentPoll = this._poll.getValue();
currentPoll.choices.find((c) => c.name === choice.name)?.updateParticipation(user, response);
this.updateCurrentPoll(currentPoll);
this.apiService.createParticipation(currentPoll.custom_url, choice.name, user.pseudo, response);
this.toastService.display('Votre participation au sondage a été enregistrée.');
}
@ -203,72 +554,85 @@ export class PollService implements Resolve<Poll> {
this.toastService.display('Les commentaires de ce sondage ont été supprimés.');
}
/**
* @description convert to API version 1 data transition object
* @param form
*/
newPollFromForm(form: any): Poll {
const newOwner = this.storageService.vote_stack.owner;
public buildAnswersByChoiceLabelByPseudo(poll: Poll): Map<string, Map<string, Answer>> {
const pseudos: Set<string> = new Set();
poll.choices.forEach((choice: Choice) => {
choice.participants.forEach((users: Set<Owner>) => {
users.forEach((user: Owner) => {
pseudos.add(user.pseudo);
});
});
});
const newpoll = new Poll(newOwner, form.value.custom_url, form.value.title);
const list = new Map<string, Map<string, Answer>>();
pseudos.forEach((pseudo: string) => {
list.set(
pseudo,
new Map<string, Answer>(
poll.choices.map((choice: Choice) => {
return [choice.name, undefined];
})
)
);
});
const pollKeys = Object.keys(newpoll);
const formFields = Object.keys(form.value);
newpoll.allowed_answers = ['yes'];
poll.choices.forEach((choice: Choice) => {
choice.participants.forEach((users: Set<Owner>, answer: Answer) => {
users.forEach((user: Owner) => {
list.get(user.pseudo).set(choice.name, answer);
});
});
});
for (const pk of pollKeys) {
if (formFields.indexOf(pk) !== -1) {
const field = form.value[pk];
newpoll[pk] = field;
} else {
console.log('manque pollKey', pk);
}
}
if (form.value.isMaybeAnswerAvailable) {
newpoll.allowed_answers.push('maybe');
}
if (form.value.isNoAnswerAvailable) {
newpoll.allowed_answers.push('no');
}
newpoll.description = form.value.description;
newpoll.has_several_hours = form.value.hasSeveralHours;
newpoll.hasSeveralHours = form.value.hasSeveralHours;
newpoll.max_count_of_answers = form.value.allowComments;
newpoll.maxCountOfAnswers = form.value.maxCountOfAnswers;
newpoll.password = form.value.password;
newpoll.kind = form.value.kind;
newpoll.allow_comments = form.value.allowComments;
// merge choices from storage
newpoll.choices = Object.assign([], this.storageService.choices);
newpoll.dateChoices = Object.assign([], this.storageService.dateChoices);
newpoll.timeSlices = Object.assign([], this.storageService.timeSlices);
return newpoll;
return list;
}
public getAdministrationUrl(): string {
let url = '';
if (this._poll && this._poll.getValue) {
const polltemp = this._poll.getValue();
if (polltemp) {
url = `${environment.frontDomain}#/poll/admin/${polltemp.admin_key}`;
}
}
return url;
public getParticipationUrlFromForm(): string {
return `${environment.frontDomain}#/poll/${this.form.value.custom_url}/consultation`;
}
public getAdministrationUrlFromForm(): string {
// admin_key is filled after creation
// example http://localhost:4200/#/administration/key/8Ubcg2YI99f69xz946cn4O64bQAeb
return `${environment.frontDomain}#/administration/key/${this.admin_key}`;
}
public getParticipationUrl(): string {
// http://localhost:4200/#/poll/dessin-anime/consultation
// TODO handle secure access
// http://localhost:4200/#/poll/citron/consultation/secure/1c01ed9c94fc640a1be864f197ff808c
let url = '';
if (this._poll && this._poll.getValue) {
const polltemp = this._poll.getValue();
if (polltemp) {
url = `${environment.frontDomain}#/poll/${polltemp.custom_url}/consultation`;
}
} else {
url = `${environment.frontDomain}#/poll/${this.form.value.custom_url}/consultation`;
}
// TODO handle pass access
return url;
}
public getAdministrationUrl(): string {
// http://localhost:4200/#/admin/9S75b70ECXI5J5xDc058d3H40H9r2CHfO0Kj8T02EK2U8rY8fYTn-eS659j2Dhp794Oa6R1b9V70e3WGaE30iD9h45zwdm76C85SWB4LcUCrc7e0Ncc0
let url = '';
if (this._poll && this._poll.getValue) {
const polltemp = this._poll.getValue();
if (polltemp) {
url = `${environment.frontDomain}#/admin/${polltemp.admin_key}`;
}
} else {
url = `${environment.frontDomain}#/admin/${this.form.value.admin_key}`;
}
return url;
}
/**
* enrich vote stack with missing default votes
* @param vote_stack
@ -297,4 +661,78 @@ export class PollService implements Resolve<Poll> {
}
});
}
convertCalendarDatesToChoices(array_dates) {
return array_dates;
}
patchFormWithPoll(poll: Poll) {
this.form.patchValue({
...poll,
isAboutDate: poll.kind == 'date',
});
}
/**
* @description convert to API version 1 data transition object
*/
newPollFromForm(): Poll {
let form = this.form;
console.log('this.form.value', this.form.value);
const newOwner = this.storageService.vote_stack.owner;
const newpoll = new Poll(newOwner, form.value.custom_url, form.value.title);
const pollKeys = Object.keys(newpoll);
const formFields = Object.keys(form.value);
newpoll.allowed_answers = [];
// comparer les champs de formulaire avec le DTO de création de sondage
for (const pk of pollKeys) {
if (formFields.indexOf(pk) !== -1) {
const field = form.value[pk];
newpoll[pk] = field;
} else {
// console.log('manque pollKey', pk);
}
}
if (form.value.isYesAnswerAvailable) {
newpoll.allowed_answers.push('yes');
}
if (form.value.isMaybeAnswerAvailable) {
newpoll.allowed_answers.push('maybe');
}
if (form.value.isNoAnswerAvailable) {
newpoll.allowed_answers.push('no');
}
newpoll.description = form.value.description;
newpoll.has_several_hours = form.value.hasSeveralHours;
newpoll.max_count_of_answers = form.value.maxCountOfAnswers;
newpoll.maxCountOfAnswers = form.value.maxCountOfAnswers;
newpoll.password = form.value.password;
newpoll.kind = form.value.isAboutDate ? 'date' : 'classic';
newpoll.allow_comments = form.value.allowComments;
// merge choices from storage
if (form.value.isAboutDate) {
// convert calendar picker dates
console.log('this.calendar', this.calendar);
for (let elem of this.calendar) {
console.log('elem', elem);
let converted_day = {
literal: this.DateUtilitiesService.formateDateToInputStringNg(elem),
timeSlices: [],
date_object: elem,
};
newpoll.dateChoices.push(converted_day);
}
console.log('newpoll.dateChoices', newpoll.dateChoices);
}
newpoll.choices = Object.assign([], this.storageService.choices);
// newpoll.dateChoices = Object.assign([], this.storageService.dateChoices);
newpoll.timeSlices = Object.assign([], this.storageService.timeSlices);
console.log('newpoll', newpoll);
return newpoll;
}
}

View File

@ -65,16 +65,16 @@ export class PollUtilitiesService {
* @param bodyContent
*/
makeHeaders(bodyContent?: any) {
const headerDict = {
Charset: 'UTF-8',
'Content-Type': 'application/json',
Accept: 'application/json',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Origin': '*',
};
// const headerDict = {
// Charset: 'UTF-8',
// 'Content-Type': 'application/json',
// Accept: 'application/json',
// 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
// 'Access-Control-Allow-Origin': '*',
// };
return {
headers: headerDict,
headers: [],
body: bodyContent,
};
}

View File

@ -46,18 +46,13 @@ export class StorageService {
public choices: Choice[] = [];
constructor(public dateUtilities: DateUtilitiesService, private toastService: ToastService) {
if (environment.autofill) {
this.toastService.display('autofill des sondages utilisateur');
if (environment.autofill_participation) {
this.userPolls.push(new Poll(new Owner(), 'Démo: Anniversaire de tonton Patrick', 'aujourdhui-ou-demain'));
this.userPolls.push(new Poll(new Owner(), 'Démo: Atelier cuisine du quartier', 'aujourdhui-ou-demain'));
this.userPolls.push(
new Poll(new Owner(), 'Démo: Réunion du département des chatons', 'aujourdhui-ou-demain')
);
}
if (!this.dateChoices.length) {
this.dateChoices = this.dateUtilities.makeDefaultDateChoices();
}
}
/**
@ -70,15 +65,16 @@ export class StorageService {
if (!this.vote_stack.id) {
this.vote_stack = new Stack();
console.log('choices_list', choices_list);
for (const choice of choices_list) {
if (environment.autofill) {
console.log('autofill au hasard des votes à ce sondage');
this.toastService.display('autofill au hasard des votes à ce sondage');
const defaultvalue = Math.random() > 0.75 ? 'yes' : '';
this.vote_stack.votes.push(new Vote(choice.id, defaultvalue));
} else {
this.vote_stack.votes.push(new Vote(choice.id));
}
// if (environment.autofill_participation) {
// console.log('autofill au hasard des votes à ce sondage');
// this.toastService.display('autofill au hasard des votes à ce sondage');
// const defaultvalue = Math.random() > 0.75 ? 'yes' : '';
// this.vote_stack.votes.push(new Vote(choice.id, defaultvalue));
// } else {
this.vote_stack.votes.push(new Vote(choice.id));
// }
}
}
}

View File

@ -2,11 +2,34 @@ import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AdministrationComponent } from './administration.component';
import { NamingComponent } from './naming/naming.component';
import { StepTwoComponent } from './form/steps/step-two/step-two.component';
import { StepThreeComponent } from './form/steps/step-three/step-three.component';
import { StepFourComponent } from './form/steps/step-four/step-four.component';
import { StepFiveComponent } from './form/steps/step-five/step-five.component';
import { StepOneComponent } from './form/steps/step-one/step-one.component';
import { SuccessComponent } from './success/success.component';
import { AdminConsultationComponent } from './consultation/consultation.component';
const routes: Routes = [
{ path: '', component: AdministrationComponent, data: { animation: 'AdminPage' } },
{ path: 'naming', component: NamingComponent },
{
path: '',
component: AdministrationComponent,
},
{ path: 'key/:admin_key', component: AdminConsultationComponent },
{
path: 'step',
children: [
{ path: '1', component: StepOneComponent },
{ path: '2', component: StepTwoComponent },
{ path: '3', component: StepThreeComponent },
{ path: '4', component: StepFourComponent },
{ path: '5', component: StepFiveComponent },
],
},
{
path: 'success',
component: SuccessComponent,
},
];
@NgModule({

View File

@ -20,7 +20,7 @@ export class AdministrationComponent implements OnInit, OnDestroy {
ngOnInit(): void {
this.routeSubscription = this.route.data.subscribe((data: { poll: Poll }) => {
console.log('data', data);
console.log('routeSubscription data', data);
if (data.poll) {
this.poll = data.poll;
}

View File

@ -9,6 +9,12 @@ import { AdministrationComponent } from './administration.component';
import { StepperComponent } from './stepper/stepper.component';
import { NamingComponent } from './naming/naming.component';
import { FormComponent } from './form/form.component';
import { StepOneComponent } from './form/steps/step-one/step-one.component';
import { StepTwoComponent } from './form/steps/step-two/step-two.component';
import { StepThreeComponent } from './form/steps/step-three/step-three.component';
import { StepFourComponent } from './form/steps/step-four/step-four.component';
import { StepFiveComponent } from './form/steps/step-five/step-five.component';
import { CalendarModule } from 'primeng/calendar';
import { SuccessComponent } from './success/success.component';
import { DateSelectComponent } from './form/date-select/date-select.component';
import { TextSelectComponent } from './form/text-select/text-select.component';
@ -21,6 +27,7 @@ import { IntervalComponent } from './form/date/interval/interval.component';
import { DayListComponent } from './form/date/list/day/day-list.component';
import { PickerComponent } from './form/date/picker/picker.component';
import { TimeListComponent } from './form/date/list/time/time-list.component';
import { AdminConsultationComponent } from './consultation/consultation.component';
@NgModule({
declarations: [
@ -34,14 +41,22 @@ import { TimeListComponent } from './form/date/list/time/time-list.component';
KindSelectComponent,
BaseConfigComponent,
AdvancedConfigComponent,
StepOneComponent,
StepTwoComponent,
StepThreeComponent,
StepFourComponent,
StepFiveComponent,
SuccessComponent,
IntervalComponent,
DayListComponent,
PickerComponent,
TimeListComponent,
AdminConsultationComponent,
],
imports: [
AdministrationRoutingModule,
CommonModule,
CalendarModule,
ReactiveFormsModule,
SharedModule,
FormsModule,

View File

@ -0,0 +1,16 @@
<div class="admin-consultation min-height padded">
<h2 class="title is-2">Consulter le sondage</h2>
<button class="btn is-primary" [routerLink]="'/administration'">
<i class="fa fa-pencil"></i>
modifier
</button>
<div>
<h2>{{ form.value.title }}</h2>
<div *ngIf="poll">
<h2>{{ poll.title }}</h2>
Créé le {{ poll.created_at | date }} par {{ poll.owner.pseudo }}, {{ poll.owner.email }}
</div>
</div>
</div>

View File

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

View File

@ -0,0 +1,49 @@
import { Component, OnInit } from '@angular/core';
import { PollService } from '../../../core/services/poll.service';
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
import { ApiService } from '../../../core/services/api.service';
import { FormGroup } from '@angular/forms';
import { Poll } from '../../../core/models/poll.model';
@Component({
selector: 'app-admin-consultation',
templateUrl: './consultation.component.html',
styleUrls: ['./consultation.component.scss'],
})
export class AdminConsultationComponent implements OnInit {
private admin_key: string;
public form: FormGroup;
public poll: any;
constructor(
private pollService: PollService,
private apiService: ApiService,
private _Activatedroute: ActivatedRoute,
private router: Router
) {
this.poll = this.pollService._poll.getValue();
this.form = this.pollService.form;
}
ngOnInit(): void {
this._Activatedroute.paramMap.subscribe((params: ParamMap) => {
this.admin_key = params.get('admin_key');
if (!this.admin_key) {
this.router.navigate(['page-not-found']);
}
this.apiService.getPollByAdminKey(this.admin_key).then(
(res) => {
this.pollService.updateCurrentPoll(res.poll);
this.form = this.pollService.form;
this.poll = this.pollService._poll.getValue();
console.log('formulaire patché', this.pollService.form, this.pollService.poll);
},
(err) => {
if (!this.admin_key) {
this.router.navigate(['page-not-found']);
}
}
);
});
}
}

View File

@ -1,6 +1,5 @@
<form [formGroup]="form" class="box">
<fieldset class="complete well">
<h2>{{ 'creation.advanced' | translate }}</h2>
<label for="descr">Description (optionnel)</label>
<br />
<textarea
@ -11,32 +10,20 @@
formControlName="description"
required
></textarea>
<button
mat-button
*ngIf="description.value"
matSuffix
mat-icon-button
aria-label="Clear"
(click)="description.value = ''"
>
<i class="fa fa-close"></i>
</button>
<br />
<label for="custom_url">
Url personnalisée pour les participants
<i class="fa fa-close"></i>
</label>
<br />
<button
mat-button
*ngIf="form.controls.custom_url.value"
matSuffix
mat-icon-button
aria-label="Clear"
(click)="form.patchValue({ custom_url: '' })"
></button>
(click)="form.patchValue({ custom_url: pollService.makeSlug(form) })"
>
<i class="fa fa-recycle"></i> régénérer
</button>
<input #custom_url matInput id="custom_url" placeholder="Url" formControlName="custom_url" required />
<br />
<div appearance="outline" class="is-not-flex">
@ -52,16 +39,6 @@
formControlName="expiresDaysDelay"
required
/>
<button
mat-button
*ngIf="expiresDaysDelay.value"
matSuffix
mat-icon-button
aria-label="Clear"
(click)="expiresDaysDelay.value = ''"
>
<i class="fa fa-close"></i>
</button>
</div>
<mat-checkbox class="is-not-flex" formControlName="areResultsPublic">
Les participants pourront consulter les résultats
@ -75,16 +52,26 @@
Le sondage sera protégé par un mot de passe
</mat-checkbox>
<br />
<input
*ngIf="form.value.isProtectedByPassword"
#password
id="password"
matInput
type="password"
placeholder="password"
formControlName="password"
required
/>
<div class="password-box" *ngIf="form.value.isProtectedByPassword">
<input
#password
id="password"
matInput
[type]="displayClearPassword ? 'text' : 'password'"
placeholder="password"
formControlName="password"
required
/>
<button
class="button"
[ngClass]="{ 'is-primary': displayClearPassword, 'is-info': !displayClearPassword }"
(click)="displayClearPassword = !displayClearPassword"
>
voir
<i class="fa fa-eye" *ngIf="!displayClearPassword"></i>
<i class="fa fa-eye-slash" *ngIf="displayClearPassword"></i>
</button>
</div>
<h3 class="title is-3">
<i class="fa fa-envelope-open"></i>
@ -103,20 +90,19 @@
Réponses proposées
</h3>
<img class="image is-24x24 pull-right" src="assets/img/icon_voter_YES.svg" />
<img class="image is-24x24 pull-right" src="assets/img/icon_voter_MAYBE.svg" />
<img class="image is-24x24 pull-right" src="assets/img/icon_voter_NO.svg" />
<mat-checkbox class="is-not-flex" formControlName="isYesAnswerAvailable">
La réponse « oui » sera disponible
<img class="image is-24x24 pull-right" src="assets/img/icon_voter_YES.svg" />
</mat-checkbox>
<br />
<mat-checkbox class="is-not-flex" formControlName="isMaybeAnswerAvailable">
La réponse « peut-être » sera disponible
<img class="image is-24x24 pull-right" src="assets/img/icon_voter_MAYBE.svg" />
</mat-checkbox>
<br />
<mat-checkbox class="is-not-flex" formControlName="isNoAnswerAvailable">
La réponse « non » sera disponible
<img class="image is-24x24 pull-right" src="assets/img/icon_voter_NO.svg" />
</mat-checkbox>
</div>
<h3 class="title is-3">
@ -128,7 +114,7 @@
</mat-checkbox>
<br />
<mat-checkbox class="is-not-flex" formControlName="hasMaxCountOfAnswers">
Nombre de réponses limitées à ce nombre
Nombre de réponses limitées à ce nombre. Utile pour réserver des places à un évènement.
</mat-checkbox>
<input
*ngIf="form.value.hasMaxCountOfAnswers"
@ -142,16 +128,14 @@
/>
</fieldset>
<fieldset>
<fieldset class="work-in-progress">
<h2 class="title is-2">
<i class="fa fa-wikidata"></i>
Fonctionnalités pas encore disponibles:
</h2>
<app-wip-todo></app-wip-todo>
<div>
<mat-checkbox class="is-not-flex" formControlName="useVoterUniqueLink">
Spécifier un lien unique de vote à des participants définis
Spécifier un <strong> lien unique de vote</strong> à des participants définis par leur email
</mat-checkbox>
<p>
lister les email des participants et leur fournir un lien unique pour voter à chacun, au lieu d'un lien
@ -174,4 +158,5 @@
Les informations du sondage seront chiffrés en base de données
</mat-checkbox>
</fieldset>
<app-errors-list [form]="form"></app-errors-list>
</form>

View File

@ -1,3 +1,19 @@
@import '../../../../../styles/variables';
.title {
margin-top: 2em;
}
.mat-checkbox {
img {
margin-left: 1em;
}
}
.button .fa {
margin: 1em;
}
input,
textarea {
margin-top: 0.5em;
margin-bottom: 1.5em;
width: 100%;
}

View File

@ -2,6 +2,7 @@ import { Component, Input, OnInit } from '@angular/core';
import { Poll } from '../../../../core/models/poll.model';
import { FormGroup } from '@angular/forms';
import { environment } from 'src/environments/environment';
import { PollService } from '../../../../core/services/poll.service';
@Component({
selector: 'app-advanced-config',
@ -11,11 +12,12 @@ import { environment } from 'src/environments/environment';
export class AdvancedConfigComponent implements OnInit {
public urlPrefix = '/participation/';
public environment = environment;
public displayClearPassword = false;
@Input()
public poll?: Poll;
@Input()
public form: FormGroup;
constructor() {}
constructor(public pollService: PollService) {}
ngOnInit(): void {}
}

View File

@ -1,4 +1,4 @@
#title {
display: block;
width: 80%;
width: 100%;
}

View File

@ -34,7 +34,7 @@ export class BaseConfigComponent {
) {}
public updateSlug(): void {
const newValueFormatted = this.pollService.convertTextToSlug(this.form.value.title);
const newValueFormatted = this.pollService.makeSlug(this.pollService.form);
console.log('newValueFormatted', newValueFormatted);
this.form.patchValue({ custom_url: newValueFormatted });
}

View File

@ -54,8 +54,8 @@ export class IntervalComponent implements OnInit {
this.intervalDays = this.dateUtilities.countDays(
this.startDateInterval,
this.endDateInterval
// this.dateUtilities.parseInputDateToDateObject(this.startDateIntervalString),
// this.dateUtilities.parseInputDateToDateObject(this.endDateIntervalString)
// this.DateUtilitiesService.parseInputDateToDateObject(this.startDateIntervalString),
// this.DateUtilitiesService.parseInputDateToDateObject(this.endDateIntervalString)
);
console.log('this.intervalDays ', this.intervalDays);
}

View File

@ -1,101 +1,9 @@
<div class="admin-form padded">
<div class="container is-max-widescreen">
<form [formGroup]="form">
<header class="columns">
<div class="column">
<h1 class="title is-2">
{{ 'creation.title' | translate }}
</h1>
</div>
</header>
<button class="btn is-success is-fixed-bottom" (click)="createPoll()" *ngIf="!environment.production">
<i class="fa fa-save"></i>
Enregistrer le sondage (sans vérifier)
</button>
<button class="btn btn--warning" (click)="askInitFormDefault()">
<i class="fa fa-refresh"></i>
Tout réinitialiser
</button>
<button class="btn is-default" (click)="automaticSlug()">
<i class="fa fa-refresh"></i>
Slug automatique
</button>
<main class="columns">
<div class="column">
<label class="label is-medium" for="kind">
{{ 'creation.want' | translate }}
</label>
<!-- <div class="step-choices" *ngIf="currentStep === 'base'">-->
<app-kind-select [form]="form"></app-kind-select>
<app-base-config [form]="form"></app-base-config>
<!-- </div>-->
<!-- <div class="step-choices" *ngIf="currentStep === 'choices'"> </div>-->
<app-date-select *ngIf="form.value && form.value.kind == 'date'" [form]="form"></app-date-select>
<app-text-select *ngIf="form.value && form.value.kind == 'text'" [form]="form"></app-text-select>
<div class="admin-form">
<section class="min-height padded">
<router-outlet>
<app-step-one [form]="form"></app-step-one>
</router-outlet>
<button
class="btn"
[class]="{ 'is-primary': advancedDisplayEnabled, 'is-info': !advancedDisplayEnabled }"
(click)="advancedDisplayEnabled = !advancedDisplayEnabled"
>
<i class="fa fa-save"></i>
{{ 'creation.advanced' | translate }}
</button>
<app-advanced-config
[poll]="poll"
[form]="form"
*ngIf="advancedDisplayEnabled"
></app-advanced-config>
<!-- <div class="bar-nav-admin">-->
<!-- <div class="columns">-->
<!-- <div class="column">-->
<!-- <p class="control">-->
<!-- <a class="button is-light" (click)="goPreviousStep()">-->
<!-- Précédent-->
<!-- </a>-->
<!-- </p>-->
<!-- </div>-->
<!-- <div class="column">-->
<!-- <p class="control text-right">-->
<!-- <a class="button is-primary" (click)="goNextStep()">-->
<!-- Suivant-->
<!-- </a>-->
<!-- </p>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
</div>
</main>
<app-picker [form]="form" *ngIf="displayDatePicker"></app-picker>
<app-errors-list [form]="form"></app-errors-list>
<button
[disabled]="!form.valid && form.touched"
class="btn is-success is-fixed-bottom"
(click)="createPoll()"
>
<i class="fa fa-save"></i>
Enregistrer le sondage
</button>
<button class="btn is-success is-fixed-bottom" (click)="createPoll()" *ngIf="!environment.production">
<i class="fa fa-save"></i>
Enregistrer le sondage (sans vérifier)
</button>
<footer class="column" *ngIf="show_debug_data">
<h2>Debug data</h2>
<pre class="debug padded warning">
form values :
{{ form.value | json }}
</pre
>
<pre class="debug padded warning">
poll initial values :
{{ poll | json }}
</pre
>
</footer>
<hr />
</form>
</div>
<app-errors-list [form]="pollService.form"></app-errors-list>
</section>
</div>

View File

@ -0,0 +1,10 @@
@import '../../../../styles/variables';
.admin-form {
padding: 1em;
}
textarea {
border: solid 1px $border-color;
width: 100%;
display: block;
}

View File

@ -1,231 +1,40 @@
import { ChangeDetectorRef, Component, Inject, Input, OnInit, AfterViewInit } from '@angular/core';
import { ChangeDetectorRef, Component, Inject, Input, OnInit } from '@angular/core';
import { Poll } from '../../../core/models/poll.model';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { UuidService } from '../../../core/services/uuid.service';
import { ApiService } from '../../../core/services/api.service';
import { ToastService } from '../../../core/services/toast.service';
import { PollService } from '../../../core/services/poll.service';
import { DOCUMENT } from '@angular/common';
import { Router } from '@angular/router';
import { environment } from '../../../../environments/environment';
import { PollUtilitiesService } from '../../../core/services/poll.utilities.service';
import { StorageService } from '../../../core/services/storage.service';
import { DateUtilitiesService } from '../../../core/services/date.utilities.service';
import { formatDate } from '@angular/common';
import { ActivatedRoute, Router } from '@angular/router';
@Component({
selector: 'app-admin-form',
templateUrl: './form.component.html',
styleUrls: ['./form.component.scss'],
})
export class FormComponent implements OnInit, AfterViewInit {
export class FormComponent implements OnInit {
@Input()
public poll?: Poll;
public form: FormGroup;
public displayDatePicker = false;
public advancedDisplayEnabled = false;
public show_debug_data = false;
public currentStep = 'base';
public steps = ['base', 'choices', 'advanced'];
public environment = environment;
constructor(
private fb: FormBuilder,
private cd: ChangeDetectorRef,
private pollUtilitiesService: PollUtilitiesService,
private uuidService: UuidService,
private toastService: ToastService,
private pollService: PollService,
private storageService: StorageService,
public apiService: ApiService,
public dateUtils: DateUtilitiesService,
public pollService: PollService,
private router: Router,
private utilitiesService: PollUtilitiesService,
public route: ActivatedRoute,
private apiService: ApiService,
@Inject(DOCUMENT) private document: any
) {}
) {
this.form = this.pollService.form;
}
ngOnInit(): void {
this.initFormDefault();
// this.goNextStep();
this.pollService.askInitFormDefault();
}
ngAfterViewInit() {
// focus on first field of the creation form
const firstField = this.document.querySelector('#kind');
if (firstField) {
console.log('focus on ', firstField);
firstField.focus();
} else {
console.log('no first field of form');
}
}
initFormDefault(showDemoValues = environment.autofill): void {
const creationDate = new Date();
// choices of date are managed outside of this form
this.form = this.fb.group({
title: ['', [Validators.required, Validators.minLength(5)]],
creatorPseudo: ['', [Validators.required]],
creatorEmail: ['', [Validators.required, Validators.email]],
custom_url: [this.pollUtilitiesService.makeUuid(), [Validators.required]],
description: ['', [Validators.required]],
kind: ['date', [Validators.required]],
areResultsPublic: [true, [Validators.required]],
whoCanChangeAnswers: ['everybody', [Validators.required]],
isProtectedByPassword: [false, [Validators.required]],
allowNewDateTime: [false, [Validators.required]],
isOwnerNotifiedByEmailOnNewVote: [false, [Validators.required]],
isOwnerNotifiedByEmailOnNewComment: [false, [Validators.required]],
isYesAnswerAvailable: [false, [Validators.required]],
isMaybeAnswerAvailable: [false, [Validators.required]],
isNoAnswerAvailable: [false, [Validators.required]],
isAboutDate: [true, [Validators.required]],
isZeroKnoledge: [false, [Validators.required]],
useVoterUniqueLink: [false, [Validators.required]],
expiresDaysDelay: [60, [Validators.required, Validators.min(1), Validators.max(365)]],
maxCountOfAnswers: [150, [Validators.required, Validators.min(1), Validators.max(5000)]],
allowComments: [true, [Validators.required]],
password: ['', []],
voterEmailList: ['', []],
natural_lang_interval: ['', []],
dateCreated: [creationDate, [Validators.required]],
hasSeveralHours: [false, [Validators.required]],
hasMaxCountOfAnswers: [true, [Validators.required, Validators.min(1)]],
startDateInterval: ['', [Validators.required]],
endDateInterval: ['', [Validators.required]],
});
// take back values from pollservice
// this.form.patchValue(this.pollService.poll);
this.setDefaultFormValues();
if (showDemoValues) {
this.setDemoValues();
this.toastService.display('default values filled for demo');
}
if (environment.autoSendNewPoll) {
this.createPoll();
}
}
setDefaultFormValues(): void {
this.form.patchValue({
creatorPseudo: 'Anne Onyme',
creatorEmail: 'anne_onyme@anonymous_email.com',
description: 'RSVP',
isAboutDate: true,
hasSeveralHours: false,
kind: 'date',
password: '',
whoCanChangeAnswers: 'everybody',
isProtectedByPassword: false,
isOwnerNotifiedByEmailOnNewVote: false,
isOwnerNotifiedByEmailOnNewComment: false,
isYesAnswerAvailable: true,
isMaybeAnswerAvailable: false,
isNoAnswerAvailable: false,
isZeroKnoledge: true,
areResultsPublic: true,
allowComments: true,
expiresDaysDelay: environment.expiresDaysDelay,
maxCountOfAnswers: environment.maxCountOfAnswers,
allowNewDateTime: false,
// startDateInterval: formatDate(new Date(), 'yyyy-MM-dd', 'fr_FR'),
endDateInterval: formatDate(
this.dateUtils.addDaysToDate(environment.interval_days_default, new Date()),
'yyyy-MM-dd',
'fr_FR'
),
});
console.log("this.form.controls['startDateInterval']", this.form.controls['startDateInterval']);
this.form.controls['startDateInterval'].setValue(formatDate(new Date(), 'yyyy-MM-dd', 'fr_FR'));
console.log("this.form.controls['startDateInterval']", this.form.controls['startDateInterval']);
this.automaticSlug();
}
/**
* add example values to the form, overrides defaults of PollConfiguration
*/
setDemoValues(): void {
const title = 'le titre de démo __ ' + new Date().getTime();
this.form.patchValue({ creatorPseudo: 'Chuck Norris', creatorEmail: 'chucknorris@example.com' });
this.form.patchValue({
title: title,
custom_url: this.pollUtilitiesService.makeSlugFromString(title),
description: 'répondez SVP <3 ! *-*',
creatorPseudo: 'Chuck Norris',
creatorEmail: 'chucknorris@example.com',
});
}
askInitFormDefault(): void {
this.toastService.display('formulaire réinitialisé', 'info');
}
/**
* set the poll custom_url from other data of the poll
*/
automaticSlug(): void {
this.form.patchValue({
custom_url:
this.pollService.convertTextToSlug(this.form.value.title) +
'_' +
this.utilitiesService.makeUuid().substr(0, 12),
});
}
goPreviousStep() {
alert('todo');
}
goNextStep() {
let indexCurrentStep = this.steps.indexOf(this.currentStep);
indexCurrentStep += 1;
this.currentStep = this.steps[indexCurrentStep];
window.scrollTo(0, 0);
}
public createPoll(): void {
const newpoll = this.pollService.newPollFromForm(this.form);
console.log('newpoll', newpoll);
const router = this.router;
if (!environment.production) {
this.toastService.display('mode dev : envoi du form sans validation');
this.apiService.createPoll(newpoll).then(
(resp: any) => {
this.pollService.updateCurrentPoll(resp.data.poll);
this.storageService.userPolls.push(resp.data.poll);
this.storageService.vote_stack.owner.polls.push(resp.data.poll);
this.toastService.display('sauvegarde du nouveau sondage réussie');
router.navigate(['success']);
},
(err) => {
this.toastService.display('erreur lors de la sauvegarde ' + err.message);
}
);
} else {
if (this.form.valid) {
this.toastService.display("C'est parti!");
this.apiService.createPoll(newpoll).then(
(resp: any) => {
this.pollService.updateCurrentPoll(resp.data.poll);
this.storageService.userPolls.push(resp.data.poll);
this.storageService.vote_stack.owner.polls.push(resp.data.poll);
this.toastService.display('sauvegarde du nouveau sondage réussie');
router.navigate(['success']);
},
(err) => {
this.toastService.display('erreur lors de la sauvegarde');
}
);
} else {
this.toastService.display('invalid form');
}
}
}
goNextStep() {}
}

View File

@ -0,0 +1,26 @@
<app-stepper [step_current]="5" [step_max]="5"></app-stepper>
<app-errors-list [form]="pollService.form"></app-errors-list>
<app-success [poll]="pollService.form.value"></app-success>
<section class="supplement container">
<div class="columns">
<div class="column">
<button class="button is-primary is-fullwidth" [routerLink]="['/administration/step/1']">
<i class="fa fa-pencil"></i>
modifier le sondage
</button>
</div>
</div>
<button class="btn btn--warning" (click)="askInitFormDefault()">
<i class="fa fa-refresh"></i>
Tout réinitialiser
</button>
<div class="well">
{{ pollService.form.value.custom_url }}
</div>
<div class="has-background-danger" *ngIf="!pollService.form.valid">
le formulaire est invalide
<pre> {{ pollService.form.errors | json }}</pre>
</div>
</section>

View File

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

View File

@ -0,0 +1,27 @@
import { Component, Input, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { PollService } from '../../../../../core/services/poll.service';
import { ApiService } from '../../../../../core/services/api.service';
@Component({
selector: 'app-step-five',
templateUrl: './step-five.component.html',
styleUrls: ['./step-five.component.scss'],
})
export class StepFiveComponent implements OnInit {
@Input() step_max: any;
@Input() public form: FormGroup;
poll: any;
constructor(public pollService: PollService) {
this.pollService.step_current = 5;
}
ngOnInit(): void {}
askInitFormDefault() {
if (window.confirm('réinitialiser le formulaire ?')) {
this.pollService.askInitFormDefault();
}
}
automaticSlug() {}
}

View File

@ -0,0 +1,64 @@
<div class="step">
<div class="min-height">
<form action="#" [formGroup]="pollService.form">
<app-stepper [step_current]="4" [step_max]="pollService.step_max"></app-stepper>
<app-errors-list [form]="pollService.form"></app-errors-list>
<div class="creator-infos">
<label class="" for="creatorEmail">
<span>
{{ 'creation.name' | translate }}
</span>
</label>
<input
#title
matInput
placeholder="pseudo"
formControlName="creatorPseudo"
id="creatorPseudo"
required
/>
<label class="hidden" for="creatorPseudo">
<span>
{{ 'creation.email' | translate }}
</span>
</label>
<input
#title
matInput
placeholder="mon-email@example.com"
formControlName="creatorEmail"
id="creatorEmail"
required
/>
</div>
<fieldset class="advanced-config">
<button
class="button is-unchecked-info"
[ngClass]="{ 'is-info': !advancedDisplayEnabled, 'is-primary': advancedDisplayEnabled }"
(click)="advancedDisplayEnabled = !advancedDisplayEnabled"
>
<i class="fa fa-chevron-circle-down" *ngIf="!advancedDisplayEnabled"></i>
<i class="fa fa-chevron-circle-up" *ngIf="advancedDisplayEnabled"></i>
{{ 'creation.advanced' | translate }}
</button>
<fieldset class="complete well" *ngIf="advancedDisplayEnabled">
<app-advanced-config [form]="pollService.form"></app-advanced-config>
</fieldset>
</fieldset>
</form>
</div>
<div class="columns">
<div class="column">
<button class="button is-secondary is-fullwidth" [routerLink]="['/administration/step/3']">
précédent
</button>
</div>
<div class="column">
<button class="btn is-primary is-fullwidth" (click)="createPoll()" [disabled]="!pollService.form.valid">
<i class="fa fa-save"></i>
Enregistrer le sondage
</button>
</div>
</div>
</div>

View File

@ -0,0 +1 @@
@import '../../../../../../styles/variables';

View File

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

View File

@ -0,0 +1,35 @@
import { Component, Input, OnInit } from '@angular/core';
import { PollService } from '../../../../../core/services/poll.service';
import { environment } from '../../../../../../environments/environment';
import { Router } from '@angular/router';
@Component({
selector: 'app-step-four',
templateUrl: './step-four.component.html',
styleUrls: ['./step-four.component.scss'],
})
export class StepFourComponent implements OnInit {
urlPrefix: any;
advancedDisplayEnabled: boolean = environment.advanced_options_display;
@Input()
step_max: any;
@Input()
form: any;
constructor(private router: Router, public pollService: PollService) {
this.pollService.step_current = 4;
}
ngOnInit(): void {}
createPoll() {
this.pollService.createPoll().then(
(resp) => {
this.router.navigate(['administration/success']);
},
(err) => {
console.error('oops err', err);
}
);
}
}

View File

@ -0,0 +1,74 @@
<div class="step step-container">
<form class="min-height" [formGroup]="pollService.form">
<app-stepper [step_current]="1" [step_max]="5"></app-stepper>
<section class="poll-title">
<h2 class="title is-2">
{{ 'creation.choose_title' | translate }}
</h2>
<div class="columns">
<div class="column">
<div>
<label for="title">{{ 'creation.choose_title_label' | translate }}</label>
</div>
<input
class="input is-fullwidth"
#title
[placeholder]="'creation.choose_title_placeholder' | translate"
formControlName="title"
(keyup)="pollService.updateSlug()"
(blur)="pollService.updateSlug()"
id="title"
maxlength="140"
autofocus="autofocus"
required
/>
</div>
</div>
</section>
<div class="poll-description">
<div class="columns">
<div class="column">
<label for="descr">Description (optionnel)</label>
<!-- <div class="rich-text-toggle">-->
<!-- <label for="richTextMode">mode de saisie avancée</label>-->
<!-- <mat-checkbox formControlName="richTextMode" id="richTextMode"></mat-checkbox>-->
<!-- </div>-->
<div class="rich-toolbar" *ngIf="pollService.form.value.richTextMode">
richTextMode activé
</div>
<textarea
class="ui-inputtextarea is-fullwidth is-block"
#description
matInput
id="descr"
class="is-large is-full input"
placeholder="Description"
formControlName="description"
required
maxlength="300"
></textarea>
<div
class="text-info padded"
[ngClass]="{ 'has-background-warning': pollService.form.value.description.length === 300 }"
>
{{ pollService.form.value.description.length }} / 300 caractères maximum
</div>
</div>
</div>
</div>
</form>
<div class="columns">
<div class="column">
<button class="button is-warning is-fullwidth" [routerLink]="['/']">
Annuler
</button>
</div>
<div class="column">
<!-- [disabled]="form.invalid"-->
<button class="button is-primary is-fullwidth" [routerLink]="['/administration/step/2']">
Suivant
</button>
</div>
</div>
</div>

View File

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

View File

@ -0,0 +1,27 @@
import { Component, Inject, Input, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { PollService } from '../../../../../core/services/poll.service';
import { DOCUMENT } from '@angular/common';
@Component({
selector: 'app-step-one',
templateUrl: './step-one.component.html',
styleUrls: ['./step-one.component.scss'],
})
export class StepOneComponent implements OnInit {
constructor(public pollService: PollService, @Inject(DOCUMENT) private document: any) {}
@Input()
step_max: any;
@Input()
form: FormGroup;
ngOnInit(): void {
this.pollService.step_current = 1;
const selector = '#title';
const firstField = this.document.querySelector(selector);
if (firstField) {
firstField.focus();
}
}
}

View File

@ -0,0 +1,107 @@
<div class="step min-height">
<app-stepper [step_current]="3" [step_max]="5"></app-stepper>
<app-errors-list [form]="pollService.form"></app-errors-list>
<!-- choix spécialement pour les dates-->
<span class="count-dates title">
{{ pollService.calendar.length }}
</span>
<span> - {{ 'dates.count_dates' | translate }} </span>
<div class="calendar" *ngIf="mode_calendar">
<p-calendar
[(ngModel)]="pollService.calendar"
firstDayOfWeek="1"
selectionMode="multiple"
inputId="multiple"
showButtonBar="true"
[locale]="'calendar_widget' | translate"
[disabledDates]="pollService.disabled_dates"
[inline]="true"
[showWeek]="false"
></p-calendar>
</div>
<button class="button" (click)="mode_calendar = !mode_calendar" [ngClass]="{ 'is-primary': !mode_calendar }">
Saisir les dates manuellement
</button>
</div>
<div class="columns">
<div class="column">
<div class="dates-list">
<div class="actions">
<button
(click)="pollService.addTime()"
*ngIf="false == pollService.allowSeveralHours"
class="button is-primary is-block is-fullwidth"
id="add_time_button"
>
<i class="fa fa-plus" aria-hidden="true"></i>
{{ 'dates.add_time' | translate }}
</button>
<button class="button" (click)="pollService.allowSeveralHours = !pollService.allowSeveralHours">
Horaires différentes pour chaque jour
</button>
<button
(click)="pollService.removeAllTimes()"
*ngIf="pollService.timeList.length && false == pollService.allowSeveralHours"
class="btn is-warning marged"
id="remove_time_button"
>
<i class="fa fa-trash" aria-hidden="true"></i>
Aucune plage horaire
</button>
<button
(click)="pollService.resetTimes()"
*ngIf="pollService.timeList.length && false == pollService.allowSeveralHours"
class="btn is-warning marged"
id="reset_time_button"
>
<i class="fa fa-refresh" aria-hidden="true"></i>
réinitialiser
</button>
</div>
<div class="title" *ngIf="pollService.timeList.length">
<span class="count-dates">
{{ pollService.timeList.length }}
</span>
<span class="count-dates-txt">
{{ 'dates.count_time' | translate }}
(pour chaque jour)
</span>
</div>
<div
*ngIf="pollService.timeList.length && false == pollService.allowSeveralHours"
class="marged padded identical-dates"
>
<div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)">
<div *ngFor="let time of pollService.timeList; index as id" class="time-choice" cdkDrag>
<label for="timeChoices_{{ id }}">
<i class="fa fa-clock-o" aria-hidden="true"></i>
</label>
<input
[(ngModel)]="time.literal"
name="timeChoices_{{ id }}"
type="text"
id="timeChoices_{{ id }}"
/>
<button (click)="time.timeList.splice(id, 1)" class="btn btn-warning">
<i class="fa fa-times" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="columns">
<div class="column">
<button class="button is-secondary is-fullwidth" [routerLink]="['/administration/step/2']">
précédent
</button>
</div>
<div class="column">
<!-- [disabled]="form.invalid"-->
<button class="button is-primary is-fullwidth" [routerLink]="['/administration/step/4']">
suivant
</button>
</div>
</div>

View File

@ -0,0 +1,9 @@
@import '../../../../../../styles/variables';
.ui-datepicker table td.ui-datepicker-today > a.ui-state-active,
.ui-datepicker table td.ui-datepicker-today > span.ui-state-active {
background-color: $primary-color !important;
}
.calendar {
margin-top: 1em;
}

View File

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

View File

@ -0,0 +1,26 @@
import { Component, Input, OnInit } from '@angular/core';
import { PollService } from '../../../../../core/services/poll.service';
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
@Component({
selector: 'app-step-three',
templateUrl: './step-three.component.html',
styleUrls: ['./step-three.component.scss'],
})
export class StepThreeComponent implements OnInit {
@Input()
step_max: any;
@Input()
form: any;
public mode_calendar = true;
constructor(public pollService: PollService) {
this.pollService.step_current = 3;
}
ngOnInit(): void {}
drop(event: CdkDragDrop<string[]>) {
// moveItemInArray(this.pollService.choices, event.previousIndex, event.currentIndex);
}
}

View File

@ -0,0 +1,46 @@
<div class="step step-container form-field poll-kind">
<div class="min-height">
<app-stepper [step_current]="2" [step_max]="5"></app-stepper>
<app-errors-list [form]="pollService.form"></app-errors-list>
<h2 class="title is-2">
{{ 'creation.want' | translate }}
</h2>
<div class="container">
<div class="kind-of-poll columns">
<div class="column">
<button
class="button is-fullwidth"
[ngClass]="{ 'is-selected is-primary': pollService.form.controls.isAboutDate.value }"
(click)="pollService.form.controls.isAboutDate.setValue(true)"
>
<i class="fa fa-calendar"></i>
{{ 'creation.kind.date' | translate }}
</button>
</div>
<div class="column">
<button
class="button is-fullwidth"
[ngClass]="{ 'is-selected is-primary': !pollService.form.controls.isAboutDate.value }"
(click)="pollService.form.controls.isAboutDate.setValue(false)"
>
<i class="fa fa-list-ul"></i>
{{ 'creation.kind.classic' | translate }}
</button>
</div>
</div>
</div>
</div>
<div class="columns">
<div class="column">
<button class="button is-secondary is-fullwidth" [routerLink]="['/administration/step/1']">
précédent
</button>
</div>
<div class="column">
<!-- [disabled]="form.invalid"-->
<button class="button is-primary is-fullwidth" [routerLink]="['/administration/step/3']">
suivant
</button>
</div>
</div>
</div>

View File

@ -0,0 +1,16 @@
@import '../../../../../../styles/variables';
.kind-of-poll {
margin-top: 5em;
.fa {
margin-right: 1em;
}
.button {
background: $d-grey;
border: solid white 1px;
&.is-selected {
border: solid $primary-color 1px;
color: $font_color;
}
}
}

View File

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

View File

@ -0,0 +1,62 @@
import { ChangeDetectorRef, Component, Inject, Input, OnInit } from '@angular/core';
import { FormArray, FormBuilder } from '@angular/forms';
import { DOCUMENT } from '@angular/common';
import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { Router } from '@angular/router';
import { UuidService } from '../../../../../core/services/uuid.service';
import { ToastService } from '../../../../../core/services/toast.service';
import { PollService } from '../../../../../core/services/poll.service';
import { DateUtilitiesService } from '../../../../../core/services/date.utilities.service';
import { ApiService } from '../../../../../core/services/api.service';
@Component({
selector: 'app-step-two',
templateUrl: './step-two.component.html',
styleUrls: ['./step-two.component.scss'],
})
export class StepTwoComponent implements OnInit {
ngOnInit(): void {}
@Input()
form: any;
@Input()
step_max: any;
timeList: any;
allowSeveralHours: string;
dateList: any;
showDateInterval: boolean;
intervalDays: any;
constructor(
private fb: FormBuilder,
private cd: ChangeDetectorRef,
private uuidService: UuidService,
private toastService: ToastService,
public pollService: PollService,
private router: Router,
public dateUtilities: DateUtilitiesService,
private apiService: ApiService,
@Inject(DOCUMENT) private document: any
) {
this.form = this.pollService.form;
this.pollService.step_current = 2;
}
addIntervalOfDates() {}
get choices(): FormArray {
return this.form.get('choices') as FormArray;
}
addTime() {}
removeAllTimes() {}
resetTimes() {}
addChoice() {}
addTimeToDate(choice: any, id: number) {}
countDays() {}
}

View File

@ -1,114 +1,55 @@
<mat-vertical-stepper #stepper linear>
<mat-step [stepControl]="pollFormGroup" class="is-expanded">
<form [formGroup]="pollFormGroup">
<ng-template matStepLabel>Informations du sondage</ng-template>
<mat-form-field appearance="outline">
<mat-label>Titre</mat-label>
<input #title matInput placeholder="Question posée, sujet" formControlName="title" required />
</mat-form-field>
<mat-form-field appearance="outline" class="is-flex">
<mat-label>Description</mat-label>
<textarea
#description
matInput
placeholder="Description"
formControlName="description"
required
></textarea>
<button
mat-button
*ngIf="description.value"
matSuffix
mat-icon-button
aria-label="Clear"
(click)="description.value = ''"
>
<i class="fa fa-close"></i>
</button>
</mat-form-field>
<mat-form-field appearance="outline" class="is-flex">
<mat-label>Url pour les participants</mat-label>
<span matPrefix>{{ urlPrefix }}</span>
<input #slug matInput placeholder="Url" formControlName="slug" required />
<button
mat-button
*ngIf="slug.value"
matSuffix
mat-icon-button
aria-label="Clear"
(click)="slug.value = ''"
>
<i class="fa fa-close"></i>
</button>
</mat-form-field>
<div>
<button mat-button matStepperNext>Next</button>
</div>
</form>
</mat-step>
<mat-step [stepControl]="configurationFormGroup">
<form [formGroup]="configurationFormGroup">
<ng-template matStepLabel>PollConfiguration du sondage</ng-template>
<mat-form-field appearance="outline" class="is-flex">
<mat-label>Nombre de jours avant expiration</mat-label>
<input
#expiracy
matInput
type="number"
placeholder="Nombre de jours avant expiration"
formControlName="expiracyNumberOfDays"
required
/>
<button
mat-button
*ngIf="expiracy.value"
matSuffix
mat-icon-button
aria-label="Clear"
(click)="expiracy.value = ''"
>
<i class="fa fa-close"></i>
</button>
</mat-form-field>
<mat-checkbox class="is-flex" formControlName="areResultsPublic">
Les participants pourront consulter les résultats
</mat-checkbox>
<mat-checkbox class="is-flex" formControlName="isAboutDate">
Les choix possibles concerneront des dates
</mat-checkbox>
<mat-checkbox class="is-flex" formControlName="isProtectedByPassword">
Le sondage sera protégé par un mot de passe
</mat-checkbox>
<mat-checkbox class="is-flex" formControlName="isOwnerNotifiedByEmailOnNewVote">
Vous recevrez un mail à chaque nouvelle participation
</mat-checkbox>
<mat-checkbox class="is-flex" formControlName="isOwnerNotifiedByEmailOnNewComment">
Vous recevrez un mail à chaque nouveau commentaire
</mat-checkbox>
<mat-checkbox class="is-flex" formControlName="isMaybeAnswerAvailable">
La réponse « peut-être » sera disponible
</mat-checkbox>
<div>
<button mat-button matStepperPrevious>Back</button>
<button mat-button matStepperNext>Next</button>
</div>
</form>
</mat-step>
<mat-step>
<ng-template matStepLabel>Done</ng-template>
<p>You are now done.</p>
<div>
<button mat-button matStepperPrevious>Back</button>
<button mat-button (click)="stepper.reset()">Reset</button>
</div>
<div>
<button mat-button (click)="savePoll()" [disabled]="!pollFormGroup.valid || !configurationFormGroup.valid">
Enregistrer le sondage
</button>
</div>
</mat-step>
</mat-vertical-stepper>
<section class="creation-stepper">
<div class="shortcuts">
<a
class="shortcut"
href="#"
[routerLink]="['/administration/step/1']"
[ngClass]="{ 'is-active': pollService.step_current == 1 }"
>1</a
>
<a
class="shortcut"
href="#"
[routerLink]="['/administration/step/2']"
[ngClass]="{ 'is-active': pollService.step_current == 2 }"
>2</a
>
<a
class="shortcut"
href="#"
[routerLink]="['/administration/step/3']"
[ngClass]="{ 'is-active': pollService.step_current == 3 }"
>3</a
>
<a
class="shortcut"
href="#"
[routerLink]="['/administration/step/4']"
[ngClass]="{ 'is-active': pollService.step_current == 4 }"
>4</a
>
<a
class="shortcut"
href="#"
[routerLink]="['/administration/step/5']"
[ngClass]="{ 'is-active': pollService.step_current == 5 }"
>5</a
>
<!-- <a class="shortcut" href="#" [routerLink]="['/administration/step/6']" [ngClass]="{'is-active':pollService.step_current == 6}">6</a>-->
<!-- <a class="shortcut" href="#" [routerLink]="['/administration/step/7']" [ngClass]="{'is-active':pollService.step_current == 7}">7</a>-->
</div>
<div class="step-info">
<h2 classs="title is-2" *ngIf="pollService.step_current == 1">
{{ 'creation.title' | translate }}
</h2>
<h2 class="title is-3" *ngIf="pollService.step_current > 1">
<span class="poll-title">
{{ pollService.form.value.title }}
</span>
</h2>
<h3 class="title is-2">Étape {{ step_current }} sur {{ step_max }}</h3>
</div>
<div class="step-bar-container" style="width: 100%;">
<div class="step-bar-progress" [ngStyle]="{ width: (step_current / step_max) * 100 + '%' }"></div>
</div>
</section>

View File

@ -0,0 +1,35 @@
@import '../../../../styles/variables';
.step-bar-container {
margin: 1em 0;
height: 0.5em;
display: inline-block;
min-width: 1px;
background: $border-color !important;
width: 100%;
}
.step-bar-progress {
position: relative;
top: -0.6em;
left: 0;
height: 0.5em;
display: inline-block;
min-width: 1px;
background: $primary_color;
}
.shortcut {
background: $dark-lavender;
color: white;
padding: 1em;
margin: 1em;
display: inline-block;
border-radius: 100%;
text-align: center;
width: 4em;
&.is-active {
background: $font_color;
}
}
.poll-title {
color: $d-neutral;
}

View File

@ -1,55 +1,17 @@
import { Component, Input, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Poll } from '../../../core/models/poll.model';
import { UuidService } from '../../../core/services/uuid.service';
import { DateService } from '../../../core/services/date.service';
import { PollService } from '../../../core/services/poll.service';
import { environment } from '../../../../environments/environment';
@Component({
selector: 'app-stepper',
templateUrl: './stepper.component.html',
styleUrls: ['./stepper.component.scss'],
})
export class StepperComponent implements OnInit {
export class StepperComponent {
@Input()
public poll?: Poll;
public pollFormGroup: FormGroup;
public configurationFormGroup: FormGroup;
public choicesFormGroup: FormGroup;
public urlPrefix = '/participation/';
constructor(private fb: FormBuilder, private uuidService: UuidService) {}
ngOnInit(): void {
this.pollFormGroup = this.fb.group({
question: [this.poll ? this.poll.title : '', [Validators.required]],
slug: [this.poll ? this.poll.custom_url : this.uuidService.getUUID(), [Validators.required]],
description: [this.poll ? this.poll.description : ''],
});
this.configurationFormGroup = this.fb.group({
title: [this.poll ? this.poll : false, [Validators.required]],
isAboutDate: [this.poll ? this.poll.kind === 'date' : false, [Validators.required]],
isProtectedByPassword: [this.poll ? this.poll.password.length : false, [Validators.required]],
isOwnerNotifiedByEmailOnNewVote: [
this.poll ? this.poll.isOwnerNotifiedByEmailOnNewVote : false,
[Validators.required],
],
isOwnerNotifiedByEmailOnNewComment: [
this.poll ? this.poll.isOwnerNotifiedByEmailOnNewComment : false,
[Validators.required],
],
areResultsPublic: [this.poll ? this.poll.areResultsPublic : true, [Validators.required]],
expiracyNumberOfDays: [this.poll ? this.poll.default_expiracy_days_from_now : '60', [Validators.required]],
});
}
public savePoll(): void {
if (this.pollFormGroup.valid && this.configurationFormGroup.valid) {
console.log('Le sondage est correctement rempli, prêt à enregistrer.');
// TODO : save the poll
}
}
public step_current: number = 1;
@Input()
public step_max: number = 5;
public show_shortcuts = environment.showStepperShortcuts;
constructor(public pollService: PollService) {}
}

View File

@ -1,89 +1,108 @@
<section class="hero is-medium is-success">
<section
class="hero is-medium"
[ngClass]="{ 'has-background-success': pollService.admin_key, 'has-background-danger': pollService.admin_key }"
>
<div class="hero-body">
<div class="container has-text-centered">
<div class="main-block">
<h1 class="title is-1">🎉 {{ 'resume.title' | translate }}</h1>
<h1 class="title is-1" *ngIf="pollService.admin_key">🎉 {{ 'resume.title' | translate }}</h1>
<h2 class="subtitle">
Votre sondage «
Votre sondage
<br />
«
<strong class="poll-title">
{{ poll.title }}
{{ pollService.form.value.title }}
</strong>
» a bien été créé !
»
<span *ngIf="!pollService.admin_key">
n'a pas été créé :(
<br />
<button class="button is-primary" [routerLink]="['/administration/step/4']">
Revenir en arrière
</button>
</span>
<span *ngIf="pollService.admin_key">
a bien été créé !
</span>
</h2>
</div>
</div>
</div>
</section>
<div class="container padded">
<div class="has-text-centered">
<div class=" ">
<div class="main-block">
<div class="admin">
<h2 class="title is-2">
{{ 'resume.admins' | translate }}
</h2>
<p>
Voici les liens daccès au sondage, conservez-les soigneusement ! (Si vous les perdez vous pourrez
toujours les recevoir par email)
</p>
<h2 class="title is-2">
Côté admin
</h2>
<p>
Pour accéder au sondage et à tous ses paramètres :
<br />
<a class="button is-info" routerLink="/admin/{{ poll.admin_key }}"
>{{ pollService.getAdministrationUrl() }}
</a>
<app-copy-text [textToCopy]="pollService.getAdministrationUrl()"></app-copy-text>
</p>
<br />
<a class="button is-info" [href]="pollService.getAdministrationUrl()">
Voir le sondage coté administrateur·ice
</a>
<br />
<p class="note">
Note : Le sondage sera supprimé {{ poll.default_expiracy_days_from_now }} jours après la date de sa
dernière modification.
<span class="expiracy-detail" *ngIf="poll.expiracy_date">
Le {{ poll.expiracy_date | date: 'short' }}
</span>
</p>
</div>
<div class="public">
<h2 class="title is-2" i18n>{{ 'resume.users' | translate }}</h2>
<p>
Pour voir le sondage :
<br />
<a class="button is-info" routerLink="/poll/{{ poll.custom_url }}/consultation"
>{{ pollService.getParticipationUrl() }}
</a>
</p>
<br />
<app-copy-text [textToCopy]="pollService.getParticipationUrl()"></app-copy-text>
<br />
</div>
<div class="columns">
<div class="column">
<div class="admin">
<div class="no-admin-key padded has-background-danger" *ngIf="!pollService.admin_key">
Pas de clé d'administration, l'enregistrement du sondage a échoué. vérifiez vos paramètres
réseau.
</div>
<div class="admin-ok" *ngIf="pollService.admin_key">
<h2 class="title is-2">
<i class="fa fa-gears"></i>
{{ 'resume.admins' | translate }}
</h2>
<!-- <img src="assets/img/undraw_group_selfie_ijc6.svg" alt="image succès" />-->
<p>
Voici les liens daccès au sondage, conservez-les soigneusement ! (Si vous les perdez
vous pourrez toujours les recevoir par email)
</p>
<div>
Pour accéder au sondage et à tous ses paramètres :
<br />
<pre class="is-default" routerLink="{{ pollService.getAdministrationUrlFromForm() }}"
>{{ pollService.getAdministrationUrlFromForm() }}
</pre
>
<app-copy-text
[textToCopy]="pollService.getAdministrationUrlFromForm()"
></app-copy-text>
</div>
<br />
<a class="button is-info" [href]="pollService.getAdministrationUrlFromForm()">
Voir le sondage coté administrateur·ice
</a>
<br />
<p class="note has-background-info padded">
<i class="fa fa-info-circle"></i> Note : Le sondage sera supprimé
<strong> {{ pollService.form.value.expiresDaysDelay }} </strong> jours après la date de
sa dernière modification.
<span class="expiracy-detail" *ngIf="pollService.form.value.expiresDaysDelay">
Le
{{
pollService.DateUtilitiesService.addDaysToDate(
pollService.form.value.expiresDaysDelay,
today
) | date: 'short'
}}
</span>
</p>
</div>
</div>
</div>
<div class="column">
<div class="public" *ngIf="pollService.admin_key">
<h2 class="title is-2">
<i class="fa fa-ellipsis-v"></i>
{{ 'resume.users' | translate }}
</h2>
<p>
Pour voir le sondage :
<br />
<a class="button is-info" [href]="pollService.getParticipationUrlFromForm()"
>{{ pollService.getParticipationUrlFromForm() }}
</a>
</p>
<br />
<app-copy-text [textToCopy]="pollService.getParticipationUrlFromForm()"></app-copy-text>
<br />
</div>
</div>
</div>
</div>
<section class="mail">
<h2 i18n>{{ 'resume.links_mail' | translate }}</h2>
<p>
<label for="email">
Pour être sûr de retrouver ces liens, nous pouvons vous les envoyer sur votre mail :
</label>
<br />
<input type="email" id="email" name="email" [(ngModel)]="poll.creatorEmail" placeholder="email" />
<br />
<button class="btn btn--primary" (click)="sendToEmail()">
Envoyer les liens du sondage
<i class="fa fa-paper-plane" aria-hidden="true"></i>
</button>
<br />
<br />
<a class="button is-info" href="{{ poll.custom_url }}">
Voir le sondage côté public
</a>
</p>
</section>
</div>
</div>

View File

@ -1,3 +1,5 @@
@import './src/styles/variables';
.button,
a,
button {
@ -5,6 +7,7 @@ button {
margin-right: 1ch;
}
a {
padding: 1em;
max-width: 20em;
@extend .truncate;
}
@ -13,3 +16,9 @@ a {
overflow: hidden;
text-overflow: ellipsis;
}
:host {
padding: 2em;
}
.has-background-success {
background: $logo_color_2 !important;
}

View File

@ -15,12 +15,14 @@ export class SuccessComponent {
mailToRecieve: string;
window: any = window;
environment = environment;
today: Date = new Date();
constructor(public pollService: PollService, private dateUtils: DateUtilitiesService, private titleService: Title) {
this.titleService.setTitle(environment.appTitle + ' - 🎉 succès de création de sondage -');
this.titleService.setTitle(
environment.appTitle + ' - 🎉 succès de création de sondage - ' + this.pollService.form.value.title
);
this.pollService.poll.subscribe((newpoll: Poll) => {
this.poll = newpoll;
// this.poll.expiracy_date = this.getExpiracyDateFromPoll(this.poll);
});
}

View File

@ -7,6 +7,7 @@ import { PasswordPromptComponent } from './password/password-prompt/password-pro
const routes: Routes = [
{ path: 'secure/:pass_hash', component: ConsultationComponent },
{ path: 'prompt', component: PasswordPromptComponent },
{ path: 'simple', component: WipTodoComponent },
{ path: 'table', component: WipTodoComponent },

View File

@ -8,7 +8,7 @@
</div>
<div class="message is-warning" *ngIf="poll && poll.admin_key">
<div class="message-body">
vous êtes admin de ce sondage
vous êtes admin de ce sondage et pouvez le modifier
</div>
</div>

View File

@ -60,15 +60,15 @@ export class ConsultationComponent implements OnInit, OnDestroy {
console.log('this.pass_hash ', this.pass_hash);
if (this.pass_hash) {
this.pollService.loadPollBycustom_urlWithPasswordHash(this.pollSlug, this.pass_hash).then((resp) => {
console.log('resp', resp);
this.pollService.loadPollByCustomUrlWithPasswordHash(this.pollSlug, this.pass_hash).then((resp) => {
console.log('loadPollByCustomUrlWithPasswordHash resp', resp);
this.fetching = false;
this.storageService.vote_stack.id = null;
this.storageService.setChoicesForVoteStack(this.pollService._poll.getValue().choices);
});
} else {
this.pollService.loadPollBycustom_url(this.pollSlug).then((resp) => {
console.log('resp', resp);
this.pollService.loadPollByCustomUrl(this.pollSlug).then((resp) => {
console.log('loadPollByCustomUrl resp', resp);
this.fetching = false;
this.storageService.vote_stack.id = null;
this.storageService.setChoicesForVoteStack(this.pollService._poll.getValue().choices);
@ -131,9 +131,9 @@ export class ConsultationComponent implements OnInit, OnDestroy {
this.storageService.mapVotes(voteStack.data);
this.pollService.enrichVoteStackWithCurrentPollChoicesDefaultVotes(this.storageService.vote_stack);
if (this.pass_hash) {
this.pollService.loadPollBycustom_urlWithPasswordHash(this.poll.custom_url, this.pass_hash);
this.pollService.loadPollByCustomUrlWithPasswordHash(this.poll.custom_url, this.pass_hash);
} else {
this.pollService.loadPollBycustom_url(this.poll.custom_url);
this.pollService.loadPollByCustomUrl(this.poll.custom_url);
}
} else {
this.toastService.display('erreur à l enregistrement');

View File

@ -9,6 +9,8 @@ import { PollResultsCompactComponent } from './poll-results-compact/poll-results
import { PollResultsDetailedComponent } from './poll-results-detailed/poll-results-detailed.component';
import { ChoiceButtonComponent } from '../../shared/components/choice-item/choice-button.component';
import { PasswordPromptComponent } from './password/password-prompt/password-prompt.component';
import { ChoiceDetailsComponent } from '../../shared/components/choice-details/choice-details.component';
import { CoreModule } from '../../core/core.module';
@NgModule({
declarations: [

View File

@ -1,7 +1,6 @@
@import '../../../../styles/variables';
.box {
border-left: 3px solid white;
cursor: pointer;
* {
cursor: pointer;

View File

@ -77,11 +77,11 @@ export class PollResultsDetailedComponent {
if (voteStack.status == 200) {
this.storageService.mapVotes(voteStack.data);
this.pollService.enrichVoteStackWithCurrentPollChoicesDefaultVotes(this.storageService.vote_stack);
// if (this.pass_hash) {
// this.pollService.loadPollBycustom_urlWithPasswordHash(this.poll.custom_url, this.pass_hash);
// } else {
this.pollService.loadPollBycustom_url(this.poll.custom_url);
// }
if (this.pollService.pass_hash) {
this.pollService.loadPollByCustomUrlWithPasswordHash(this.poll.custom_url, this.pollService.pass_hash);
} else {
this.pollService.loadPollByCustomUrl(this.poll.custom_url);
}
} else {
this.toastService.display('erreur à l enregistrement');
}

View File

@ -1,5 +1,6 @@
<div
class="validation-error-list padded"
*ngIf="(form.valid && !hide_on_valid) || !form.valid"
[ngClass]="{ 'has-background-warning': totalErrors > 0, 'has-background-success': totalErrors === 0 }"
>
<h1 class="title is-1">
@ -24,4 +25,10 @@
{{ m }}
</li>
</ul>
<div class="debug" *ngIf="!environment.production">
<pre>
{{ messages | json }}
</pre
>
</div>
</div>

View File

@ -7,3 +7,6 @@
color: white;
border: solid 2px white;
}
.validation-error-list {
margin: 2em;
}

View File

@ -1,6 +1,7 @@
import { Component, Inject, Input, OnInit } from '@angular/core';
import { FormGroup, ValidationErrors } from '@angular/forms';
import { DOCUMENT } from '@angular/common';
import { environment } from '../../../../../../../environments/environment';
@Component({
selector: 'app-errors-list',
@ -12,6 +13,8 @@ export class ErrorsListComponent implements OnInit {
public totalErrors = 0;
public firstErrorId = '';
public messages = [];
public hide_on_valid = true;
public environment = environment;
constructor(@Inject(DOCUMENT) private document: any) {}

View File

@ -20,13 +20,6 @@ export const routes: Routes = [
data: { animation: 'AdminPage' },
loadChildren: () =>
import('./features/administration/administration.module').then((m) => m.AdministrationModule),
// resolve: { poll: PollService },
},
{
path: 'admin/:admin_key',
loadChildren: () =>
import('./features/administration/administration.module').then((m) => m.AdministrationModule),
// resolve: { poll: PollService },
},
{
path: 'poll/:custom_url/consultation',

View File

@ -1,9 +1,7 @@
<section class="hero is-warning is-medium">
<div class="hero-body">
<div class="container">
<h1 class="title has-text-centered">
{{ message | translate }}
</h1>
<h1 class="title has-text-centered">o_O {{ message | translate }}</h1>
</div>
</div>
</section>

View File

@ -5,7 +5,8 @@
"home": {
"title": "Bienvenue sur",
"subtitle": "Se consulter simplement pour sorganiser collectivement.",
"search_title": "Où sont mes sondages? ",
"search_title": "Vous avez déjà créé un sondage et vous souhaitez y accéder ?",
"search_subtitle": "Saisissez votre adresse e-mail et nous vous enverrons le lien vers votre sondage. ",
"create_button": "Créer un nouveau sondage ",
"search_button": "Rechercher"
},
@ -27,13 +28,14 @@
},
"creation": {
"title": "Créer un sondage",
"want": "Je veux créer un sondage",
"want": "Choisissez le type de sondage",
"advanced": "Options avancées",
"kind": {
"classic": "classique",
"date": "spécial dates"
"classic": "Propositions",
"date": "Date"
},
"choose_title": "Dont le titre sera",
"choose_title": "Renseignez un nom pour votre sondage",
"choose_title_label": "Nom de votre sondage (obligatoire)",
"choose_title_placeholder": "titre",
"choices_hint": "Utilisez les flèches haut ⬆️ et bas ⬇️ pour passer d'un choix à un autre",
"name": "Je peux aussi préciser mon nom si je le souhaite",
@ -72,7 +74,7 @@
"continue": "Voyons ce que ça donne"
},
"resume": {
"title": "Et c'est tout pour nous !",
"title": "Félicitations !",
"admins": "Côté administrateur-ice-eux",
"users": "Côté sondés",
"links_mail": "Recevoir les liens par e-mail"
@ -144,7 +146,8 @@
"selectors": {
"lang": "Sélectionner la langue"
},
"validation" : {
"validation": {
"required": "champ requis",
"You must enter a value": "You must enter a EEEE"
},
"You must enter a value": "You must enter a valueeeeeeee",
@ -576,8 +579,7 @@
"the-administrator-locked-this-poll-votes-and-comments-are-frozen-it-is-no-longer-possible-to-partici": "L'administrateur·rice a verrouillé ce sondage. Les votes et commentaires sont gelés, il n'est plus possible de participer",
"the-poll-has-expired-it-will-soon-be-deleted": "Le sondage a expiré, il sera bientôt supprimé.",
"your-vote-has-been-saved-but-please-note-you-need-to-keep-this-personalised-link-to-be-able-to-edit-": "Votre vote a bien été pris en compte, mais faites attention : ce sondage n'autorise l'édition de votre vote qu'avec le lien personnalisé suivant ; conservez-le précieusement !"
}
,
},
"LANGUAGES": {
"DE": "Allemand",
"FR": "Français",
@ -593,39 +595,89 @@
"OC": "oc",
"SV": "sv"
},
"calendar_widget" : {
"startsWith": "Starts with",
"contains": "Contains",
"notContains": "Not contains",
"endsWith": "Ends with",
"equals": "Equals",
"notEquals": "Not equals",
"noFilter": "No Filter",
"lt": "Less than",
"lte": "Less than or equal to",
"gt": "Greater than",
"gte": "Great then or equals",
"is": "Is",
"isNot": "Is not",
"before": "Before",
"after": "After",
"clear": "Clear",
"apply": "Apply",
"matchAll": "Match All",
"matchAny": "Match Any",
"addRule": "Add Rule",
"removeRule": "Remove Rule",
"accept": "Yes",
"reject": "No",
"choose": "Choose",
"upload": "Upload",
"cancel": "Cancel",
"dayNames": ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
"dayNamesShort": ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
"dayNamesMin": ["Su","Mo","Tu","We","Th","Fr","Sa"],
"monthNames": ["Janvier","Février","Mars","Avril","Mai","Juin","Juillet","Août","Septembre","Octobre","Novembre","Décembre"],
"monthNamesShort": ["Jan", "Feb", "Mar", "Apr", "May", "Jun","Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
"today": "Aujourd'hui",
"weekHeader": "Wk"
}
"calendar_widget": {
"startsWith": "Commence par",
"contains": "Contains",
"notContains": "Not contains",
"endsWith": "Ends with",
"equals": "Equals",
"notEquals": "Not equals",
"noFilter": "No Filter",
"lt": "Less than",
"lte": "Less than or equal to",
"gt": "Greater than",
"gte": "Great then or equals",
"is": "Est",
"isNot": "N'est pas",
"before": "Avant",
"after": "Après",
"clear": "Vider",
"apply": "Appliquer",
"matchAll": "Match All",
"matchAny": "Match Any",
"addRule": "Add Rule",
"removeRule": "Remove Rule",
"accept": "Oui",
"reject": "Non",
"choose": "Choisir",
"upload": "Upload",
"cancel": "Annuler",
"dayNames": [
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday"
],
"dayNamesShort": [
"Dim",
"Lun",
"Mar",
"Mer",
"Jeu",
"Ven",
"Sam"
],
"dayNamesMin": [
"Di",
"Lu",
"Ma",
"Me",
"Je",
"Ve",
"Sa"
],
"monthNames": [
"Janvier",
"Février",
"Mars",
"Avril",
"Mai",
"Juin",
"Juillet",
"Août",
"Septembre",
"Octobre",
"Novembre",
"Décembre"
],
"monthNamesShort": [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec"
],
"today": "Aujourd'hui",
"weekHeader": "Wk"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

View File

@ -1,15 +1,15 @@
export const backendApiUrlsInDev = {
// local: 'http://tktest.lan/api/v1',
// remote: 'http://tktest.lan/api/v1',
// local: 'https://localhost:8000/api/v1',
local: 'https://framadate-api.cipherbliss.com/api/v1',
// remote: 'https://localhost:8000/api/v1',
remote: 'https://framadate-api.cipherbliss.com/api/v1',
local: 'http://localhost:8000/api/v1',
// local: 'https://framadate-api.cipherbliss.com/api/v1',
remote: 'http://localhost:8000/api/v1',
// remote: 'https://framadate-api.cipherbliss.com/api/v1',
};
export const apiV1 = {
// baseHref: 'https://localhost:8000/api/v1',
baseHref: 'http://localhost:8000/api/v1',
// baseHref: 'http://tktest.lan/api/v1',
baseHref: 'https://framadate-api.cipherbliss.com/api/v1',
// baseHref: 'https://framadate-api.cipherbliss.com/api/v1',
api_new_poll: '/poll/',
api_get_poll: '/poll/{id}',
api_new_vote_stack: '/vote-stack',

View File

@ -16,9 +16,12 @@ export const environment = {
production: true,
display_routes: true,
showDemoWarning: true,
autofill: false,
autofill_creation: true,
autofill_participation: true,
advanced_options_display: false,
autoSendNewPoll: false,
interval_days_default: 7,
showStepperShortcuts: true,
expiresDaysDelay: 60,
maxCountOfAnswers: 150,
appTitle: 'FramaDate Funky',

View File

@ -10,15 +10,19 @@ endpoints.baseHref = apiV1.baseHref;
export const environment = {
frontDomain: 'http://127.0.0.1:4200',
production: false,
display_routes: true,
autofill: true,
display_routes: true, // demo paths to test polls
autofill_creation: true,
advanced_options_display: true,
autofill_participation: true,
// autofill: false,
showDemoWarning: true,
showDemoWarning: false,
// autoSendNewPoll: true,
autoSendNewPoll: false,
showStepperShortcuts: true,
interval_days_default: 7,
expiresDaysDelay: 60,
maxCountOfAnswers: 150,
appTitle: 'Framadate Funky',
appTitle: 'Framadate',
appVersion: '0.6.0',
appLogo: 'assets/img/logo.png',
api: endpoints,

View File

@ -1,7 +1,7 @@
// Update Bulma's global variables
$family-sans-serif: 'Nunito', sans-serif;
$primary: $dark-lavender;
$primary: $primary_color;
$link: $wisteria;
$widescreen-enabled: false;
$fullhd-enabled: false;

View File

@ -1,4 +1,32 @@
@charset "UTF-8";
.input:hover,
input:hover,
select:hover,
.textarea:hover,
.select select:hover,
.is-hovered.input,
input.is-hovered,
select.is-hovered,
.is-hovered.textarea,
.select select.is-hovered {
border-color: $border-color !important;
}
app-step-one,
app-step-two,
app-step-three,
app-step-four {
padding: 2em 2.5em;
display: block;
}
app-step-five {
app-stepper {
padding: 2em 2.5em;
display: block;
}
.container {
padding: 2em;
}
}
input,
select,
@ -212,18 +240,18 @@ mat-checkbox {
.ng-pristine,
.ng-dirty {
//border-left: #ccc 3px solid;
padding-left: 1em;
//padding-left: 1em;
}
.ng-touched.ng-invalid {
border-left: $danger 3px solid;
padding-left: 1em;
//border-left: $danger 3px solid;
//padding-left: 1em;
}
.theme-dark-crystal {
.ng-touched.ng-valid {
border-left: $success 3px solid;
padding-left: 1em;
//border-left: $success 3px solid;
//padding-left: 1em;
}
}
@ -235,3 +263,86 @@ mat-checkbox {
width: 100%;
padding: 1em;
}
// calendar primeng
.p-datepicker {
border: solid 1px $logo_color;
padding: 0.5em;
margin: 1em auto;
button {
border: solid 1px $primary_color;
}
.p-datepicker-buttonbar {
margin-top: 0.5em;
}
.pi-chevron-left:after {
content: '<';
}
.pi-chevron-right:after {
content: '>';
}
.p-datepicker-month {
margin-right: 1em;
}
.p-datepicker-weeknumber span {
border-right: 1px solid $legend_color;
}
.p-datepicker-today span {
font-weight: bold;
border: solid 1px $legend_color;
}
.p-datepicker-calendar td span {
padding: 1em;
width: 3.5em;
transition: all ease 0.5s;
&:hover {
background: mix($white, $legend_color);
color: $white;
transition: all ease 0.2s;
}
}
.p-highlight {
background: $legend_color;
color: $white;
border-radius: 100%;
}
.p-disabled {
background: $d-grey;
color: $grey;
}
.p-datepicker-other-month {
color: white;
}
// weekend days
tr > td {
&:nth-of-type(6),
&:nth-of-type(7) {
//border-left: 1px solid $border-color;
background: $grey;
}
}
}
.advanced-config {
.box {
background: $light;
border: 3px solid $primary-color;
}
.work-in-progress {
padding: 1em 2em;
background: $border-color;
color: $light;
}
}
.step-container {
@extend .container, .is-widescreen;
}

View File

@ -53,7 +53,7 @@
.legend {
font-size: 14px;
margin-left: 14px;
color: $dark-lavender;
color: $primary-light;
}
.legend_first {

View File

@ -4,24 +4,9 @@ main {
display: block;
width: 100%;
margin: 0 auto;
padding: 0 2rem;
}
.main-block {
min-height: 20em;
max-width: 40em;
margin-bottom: 10em;
margin-left: auto;
margin-right: auto;
.title {
margin-top: 2em;
margin-bottom: 2em;
}
.button {
border: 0;
}
}
.creation,
.search {
@extend .main-block;
@extend main;
}

View File

@ -7,3 +7,11 @@ html {
main {
min-height: 90vh;
}
.min-height {
margin-top: 1em;
margin-bottom: 1em;
min-height: 50vh;
}
.content {
padding: 1em;
}

View File

@ -2,10 +2,8 @@
background: $primary;
main {
padding: 0;
margin-bottom: 2em;
padding-bottom: 5em;
padding-top: 1em;
background: $white;
}
.big-header {

View File

@ -1,106 +1,62 @@
@charset "UTF-8";
// ****************************** config ******************************
$theme-vars: 'violet'; // violet , blue
// ****************************** colors from styleguide https://app.zeplin.io/project/5d4d83d68866d6522ff2ff10/styleguide/colors?cid=5d502bb032e23e3516af8154
// colors from styleguide https://app.zeplin.io/project/5d4d83d68866d6522ff2ff10/styleguide/colors?cid=5d502bb032e23e3516af8154
$green: #64d16e;
$black: #000000;
$ugly-purple: #b24eb7;
$lavender-pink: #e9bdeb;
$white: #ffffff;
$dark-lavender: #7d6c99;
$dusty-orange: #f18647;
$violet: #bd10e0;
$red: #cd0000;
$cool-grey: #aeafb1;
$warm-grey: #807e7e;
$green: #64d16e;
$dusty-orange: #f18647;
$red: #cd0000;
$pink: #fa7c91;
$purple: #8a4d76;
$ugly-purple: #b24eb7;
$violet: #bd10e0;
$wisteria: #bf83c2;
$pale-purple: #d198d4;
$lavender-pink: #e9bdeb;
$dark-lavender: #7d6c99;
// themes ****************************** blue variation around styleguide
$blueish-green: #64d1a9;
$blueish-dusty-orange: #74a389;
$blueish-red: #9d00cd;
$blueish-pink: #d47cfa;
$blueish-purple: #4d4d8a;
$blueish-ugly-purple: #4d5b8a;
$blueish-violet: #5810bd;
$blueish-wisteria: #8b83bf;
$blueish-pale-purple: #8a9bd1;
$blueish-lavender-pink: #8a97e9;
$blueish-dark-lavender: #7d6c8a;
$blueish-brown: #636c77;
$purple: #8a4d76;
$pink: #fa7c91;
$brown: #757763;
$beige-light: #d0d1cd;
$beige-lighter: #eff0eb;
// ****************************** interpretations in app
// DINUM colors
$primary_color: $ugly-purple;
$primary: $ugly-purple;
$secondary_color: $lavender-pink;
$d-primary: #3e3882; // bleu 800
$d-primary-intense: #6359cf; // bleu 600
$d-grey: #f6f5fd;
$d-neutral: #767486;
$d-alt: #a9607f;
$d-info: #ecf4ff;
$d-info-text: #316ec7;
$d-success: #ecfff5;
$d-success-text: #128149;
$d-warning: #dcd3bb;
$d-warning-text: #86671b;
$d-error: #ffecee;
$d-error-text: #d51b38;
// interpretations in app
$primary_color: $d-primary;
$primary: $d-primary;
$secondary_color: $d-primary-intense;
$font_color: $black;
$logo_color: $dark-lavender;
$logo_color_2: $green;
$legend_color: $dark-lavender;
$legend_color_2: $dusty-orange;
$choice_select_border_color: $cool-grey;
$hover-color: $warm-grey;
$grey-dark: $warm-grey;
$grey-light: $beige-light;
$clicked-color: $wisteria;
$mini-button-color: $pale-purple;
$warning: $dusty-orange;
$danger: $red;
$success: $green;
// ****************************** render ******************************
@if $theme-vars == 'violet' {
$primary_color: $ugly-purple;
$primary: $ugly-purple;
$secondary_color: $lavender-pink;
$font_color: $black;
$logo_color: $dark-lavender;
$logo_color_2: $green;
$legend_color: $dark-lavender;
$legend_color_2: $dusty-orange;
$choice_select_border_color: $cool-grey;
$hover-color: $warm-grey;
$grey-dark: $warm-grey;
$grey-light: $beige-light;
$clicked-color: $wisteria;
$mini-button-color: $pale-purple;
$warning: $dusty-orange;
$danger: $red;
$success: $green;
$logo_color: $d-primary;
$logo_color_2: $d-primary-intense;
$legend_color: $d-info-text;
$legend_color_2: $d-info;
$choice_select_border_color: $d-info;
$hover-color: $d-neutral;
$border-color: $d-neutral;
$grey-dark: $d-primary;
$grey-lighter: $beige-light;
$clicked-color: $d-primary;
$mini-button-color: $d-primary-intense;
$warning: $d-warning;
$danger: $d-error;
$success: $d-success;
// FONT
$default_font: 'pt_sans';
$title_font: 'proza_libre', 'Brie Light', 'Arial', 'DejaVu Sans Mono';
} @else if $theme-vars == 'blue' {
$primary_color: $blueish-ugly-purple;
$primary: $blueish-ugly-purple;
$secondary_color: $blueish-lavender-pink;
$font_color: $black;
$logo_color: $blueish-dark-lavender;
$logo_color_2: $blueish-green;
$legend_color: $blueish-dark-lavender;
$legend_color_2: $blueish-dusty-orange;
$choice_select_border_color: $cool-grey;
$hover-color: $warm-grey;
$grey-dark: $warm-grey;
$grey-light: $beige-light;
$clicked-color: $blueish-wisteria;
$mini-button-color: $blueish-pale-purple;
$warning: $blueish-dusty-orange;
$danger: $blueish-red;
$success: $blueish-green;
// FONT
$default_font: 'pt_sans';
$title_font: 'proza_libre', 'Brie Light', 'Arial', 'DejaVu Sans Mono';
}
$default_font: 'pt_sans';
$title_font: 'proza_libre', 'Brie Light', 'Arial', 'DejaVu Sans Mono';

View File

@ -2150,6 +2150,13 @@ amdefine@>=0.0.4:
resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
integrity sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=
angular-date-value-accessor@^1.0.2:
version "1.2.1"
resolved "https://registry.yarnpkg.com/angular-date-value-accessor/-/angular-date-value-accessor-1.2.1.tgz#a5f07b11fef1c0d1fde5aa851057de177c510137"
integrity sha512-4lhVi5PRpaIKtsCDEHioue324u1j18t46ZrD/jI7+M6DrZeRyxfMeSGsZXWNOC6eaq9x/pzXyaE8slXSj3Qd5A==
dependencies:
tslib "^2.0.0"
ansi-align@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.0.tgz#b536b371cf687caaef236c18d3e21fe3797467cb"
@ -3755,6 +3762,11 @@ crypto-random-string@^2.0.0:
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==
crypto@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/crypto/-/crypto-1.0.1.tgz#2af1b7cad8175d24c8a1b0778255794a21803037"
integrity sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==
css-color-names@0.0.4, css-color-names@^0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0"
@ -9451,6 +9463,13 @@ pretty-format@^26.0.0, pretty-format@^26.1.0:
ansi-styles "^4.0.0"
react-is "^16.12.0"
primeng@^11.0.0:
version "11.4.5"
resolved "https://registry.yarnpkg.com/primeng/-/primeng-11.4.5.tgz#128137d727d555f68c212a1dcb1f2af3b0f4afd4"
integrity sha512-7f5LDHrvFsJA4670Ftmib5ndDxTqcaQiM88XXJrjWYNGjXsXT3Yc5g9fgPvDrg2D38/jjpcSYeW9kalNcvlbrQ==
dependencies:
tslib "^2.0.0"
prismjs@^1.20.0:
version "1.20.0"
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.20.0.tgz#9b685fc480a3514ee7198eac6a3bf5024319ff03"