diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 609be0bf..824d529c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -40,7 +40,7 @@ build: stage: build script: - yarn install --pure-lockfile - - npx ng build --prod + - yarn run build:prod cache: policy: pull diff --git a/package.json b/package.json index 324a7cdd..d5c23125 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 6a9826c2..dde91e65 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -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; } diff --git a/src/app/core/components/footer/footer.component.html b/src/app/core/components/footer/footer.component.html index f99f13ca..f14363f7 100644 --- a/src/app/core/components/footer/footer.component.html +++ b/src/app/core/components/footer/footer.component.html @@ -26,5 +26,33 @@ canal Matrix

+
+
+
+

Framasoft

+ L’association,Notre charte,Contacter Framasoft,Statistiques,État des services +
+
+

Communauté

+ Framacolibri,Participer,Bénévolat valorisé,Partenaires,Charte de modération +
+
+

Framadate

+ Entraide,Guides et astuces,Mentions légales,CGU,Crédits +
+
+
+ + + +
+
+
+
diff --git a/src/app/core/components/footer/footer.component.ts b/src/app/core/components/footer/footer.component.ts index 363b479c..ff7db39f 100644 --- a/src/app/core/components/footer/footer.component.ts +++ b/src/app/core/components/footer/footer.component.ts @@ -11,4 +11,8 @@ export class FooterComponent implements OnInit { constructor() {} ngOnInit(): void {} + + subscribeToNewsletter() { + alert('TODO'); + } } diff --git a/src/app/core/components/header/header.component.html b/src/app/core/components/header/header.component.html index deeeee0f..4bc99437 100644 --- a/src/app/core/components/header/header.component.html +++ b/src/app/core/components/header/header.component.html @@ -123,4 +123,10 @@ + + test admin link to edit poll + diff --git a/src/app/core/components/home/home.component.html b/src/app/core/components/home/home.component.html index 7b1abfd9..58d0a2fc 100644 --- a/src/app/core/components/home/home.component.html +++ b/src/app/core/components/home/home.component.html @@ -1,5 +1,20 @@
+
+
+

+ Organisez des évènements ou récoltez l’opinion de vos proches, simplement, librement. +

+

+ Grâce à Framadate planifiez, organisez et prenez des décisions rapidement, simplement et sans + inscription. +

+
+
+ calendrier icone framadate +
+
+
@@ -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: +

+ Ce que l'on peut faire sur cette démo: +

  • ☑️ Créer un nouveau sondage @@ -39,7 +56,9 @@ >
- Ce qu'on ne peut pas encore faire: +

+ Ce qu'on ne peut pas encore faire: +

  • 🚴‍️ mettre à jour son vote à un sondage @@ -58,6 +77,8 @@

    {{ 'home.search_title' | translate }}

    + + batman veut savoir où sont ses sondages
    • @@ -75,7 +96,7 @@
    -
    - sondage date + sondage date +
    +
    + sondage classique
    diff --git a/src/app/core/components/home/home.component.scss b/src/app/core/components/home/home.component.scss index 05590871..b76a9e58 100644 --- a/src/app/core/components/home/home.component.scss +++ b/src/app/core/components/home/home.component.scss @@ -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; + } } diff --git a/src/app/core/components/home/home.component.ts b/src/app/core/components/home/home.component.ts index d3e1acdc..bda8fab6 100644 --- a/src/app/core/components/home/home.component.ts +++ b/src/app/core/components/home/home.component.ts @@ -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; diff --git a/src/app/core/models/owner.model.ts b/src/app/core/models/owner.model.ts index 693d3398..580afd49 100644 --- a/src/app/core/models/owner.model.ts +++ b/src/app/core/models/owner.model.ts @@ -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() ) {} } diff --git a/src/app/core/models/poll.DTO.model.ts b/src/app/core/models/poll.DTO.model.ts new file mode 100644 index 00000000..a2cffb5e --- /dev/null +++ b/src/app/core/models/poll.DTO.model.ts @@ -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() {} +} diff --git a/src/app/core/services/api.service.ts b/src/app/core/services/api.service.ts index 3f69f88b..4b72865d 100644 --- a/src/app/core/services/api.service.ts +++ b/src/app/core/services/api.service.ts @@ -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 { + /** + * + * @param poll + */ + public async createPoll(poll: PollDTO): Promise { // 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 { // TODO: used for facilities in DEV, should be removed in production try { + this.axiosInstance.options(this.pollsEndpoint); + const response: AxiosResponse = await this.axiosInstance.get(`${this.pollsEndpoint}`); return response?.data; } catch (error) { @@ -145,10 +156,32 @@ export class ApiService { } } - public async getPollByCustomUrl(slug: string): Promise { + /** + * get one poll by its admin key + * @param admin_key + */ + public async getPollByAdminKey(admin_key: string): Promise { try { - console.log('fetch API : asking for poll with custom_url=' + slug); - const response: AxiosResponse = await this.axiosInstance.get(`${this.pollsEndpoint}/${slug}`); + console.log('fetch API : asking for poll with admin_key=' + admin_key); + const response: AxiosResponse = await this.axiosInstance.get( + `${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 { + try { + console.log('fetch API : asking for poll with custom_url=' + custom_url); + const response: AxiosResponse = await this.axiosInstance.get( + `${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 { + public async getPollByCustomUrlWithHash(custom_url: string, hash: string): Promise { try { const response: AxiosResponse = await this.axiosInstance.get( - `${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 { + public async getSlug(custom_url: string): Promise { 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( diff --git a/src/app/core/services/date.utilities.service.ts b/src/app/core/services/date.utilities.service.ts index 0f5a52a2..e6519eb2 100644 --- a/src/app/core/services/date.utilities.service.ts +++ b/src/app/core/services/date.utilities.service.ts @@ -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, - }, ]; } } diff --git a/src/app/core/services/language.service.ts b/src/app/core/services/language.service.ts index ba481e0c..d13f559e 100644 --- a/src/app/core/services/language.service.ts +++ b/src/app/core/services/language.service.ts @@ -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) { diff --git a/src/app/core/services/poll.service.ts b/src/app/core/services/poll.service.ts index 5114b7c8..a1e7d8b0 100644 --- a/src/app/core/services/poll.service.ts +++ b/src/app/core/services/poll.service.ts @@ -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: BehaviorSubject = new BehaviorSubject(undefined); + public _poll: BehaviorSubject = new BehaviorSubject(undefined); public readonly poll: Observable = 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 { 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 { ) { 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 { */ 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) => { 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 { + public async loadPollByCustomUrl(custom_url: string): Promise { if (custom_url) { const poll: Poll | undefined = await this.apiService.getPollByCustomUrl(custom_url); @@ -103,7 +246,7 @@ export class PollService implements Resolve { } } - public async loadPollBycustom_urlWithPasswordHash(custom_url: string, hash: string): Promise { + public async loadPollByCustomUrlWithPasswordHash(custom_url: string, hash: string): Promise { if (custom_url) { const poll: Poll | undefined = await this.apiService.getPollByCustomUrlWithHash(custom_url, hash); @@ -123,50 +266,243 @@ export class PollService implements Resolve { * 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 { + 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 { .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 { + 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 n’a é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 { 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> { + const pseudos: Set = new Set(); + poll.choices.forEach((choice: Choice) => { + choice.participants.forEach((users: Set) => { + users.forEach((user: Owner) => { + pseudos.add(user.pseudo); + }); + }); + }); - const newpoll = new Poll(newOwner, form.value.custom_url, form.value.title); + const list = new Map>(); + pseudos.forEach((pseudo: string) => { + list.set( + pseudo, + new Map( + 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, 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 { } }); } + + 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; + } } diff --git a/src/app/core/services/poll.utilities.service.ts b/src/app/core/services/poll.utilities.service.ts index 0b6471fe..4b669c40 100644 --- a/src/app/core/services/poll.utilities.service.ts +++ b/src/app/core/services/poll.utilities.service.ts @@ -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, }; } diff --git a/src/app/core/services/storage.service.ts b/src/app/core/services/storage.service.ts index c173c8f0..ac2c8148 100644 --- a/src/app/core/services/storage.service.ts +++ b/src/app/core/services/storage.service.ts @@ -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)); + // } } } } diff --git a/src/app/features/administration/administration-routing.module.ts b/src/app/features/administration/administration-routing.module.ts index 9fad9e8b..4676f048 100644 --- a/src/app/features/administration/administration-routing.module.ts +++ b/src/app/features/administration/administration-routing.module.ts @@ -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({ diff --git a/src/app/features/administration/administration.component.ts b/src/app/features/administration/administration.component.ts index 54bdcd71..8d8d2286 100644 --- a/src/app/features/administration/administration.component.ts +++ b/src/app/features/administration/administration.component.ts @@ -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; } diff --git a/src/app/features/administration/administration.module.ts b/src/app/features/administration/administration.module.ts index 8d906d69..70e204ed 100644 --- a/src/app/features/administration/administration.module.ts +++ b/src/app/features/administration/administration.module.ts @@ -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, diff --git a/src/app/features/administration/consultation/consultation.component.html b/src/app/features/administration/consultation/consultation.component.html new file mode 100644 index 00000000..e21f1a2f --- /dev/null +++ b/src/app/features/administration/consultation/consultation.component.html @@ -0,0 +1,16 @@ +
    +

    Consulter le sondage

    + +
    +

    {{ form.value.title }}

    + +
    +

    {{ poll.title }}

    + + Créé le {{ poll.created_at | date }} par {{ poll.owner.pseudo }}, {{ poll.owner.email }} +
    +
    +
    diff --git a/src/app/features/administration/consultation/consultation.component.scss b/src/app/features/administration/consultation/consultation.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/features/administration/consultation/consultation.component.spec.ts b/src/app/features/administration/consultation/consultation.component.spec.ts new file mode 100644 index 00000000..7ee3e6f8 --- /dev/null +++ b/src/app/features/administration/consultation/consultation.component.spec.ts @@ -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; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ConsultationComponent], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ConsultationComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/administration/consultation/consultation.component.ts b/src/app/features/administration/consultation/consultation.component.ts new file mode 100644 index 00000000..19a4c025 --- /dev/null +++ b/src/app/features/administration/consultation/consultation.component.ts @@ -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']); + } + } + ); + }); + } +} diff --git a/src/app/features/administration/form/advanced-config/advanced-config.component.html b/src/app/features/administration/form/advanced-config/advanced-config.component.html index 9dce29a0..dc568a4e 100644 --- a/src/app/features/administration/form/advanced-config/advanced-config.component.html +++ b/src/app/features/administration/form/advanced-config/advanced-config.component.html @@ -1,6 +1,5 @@
    -

    {{ 'creation.advanced' | translate }}


    -

    + (click)="form.patchValue({ custom_url: pollService.makeSlug(form) })" + > + régénérer +
    @@ -52,16 +39,6 @@ formControlName="expiresDaysDelay" required /> -
    Les participants pourront consulter les résultats @@ -75,16 +52,26 @@ Le sondage sera protégé par un mot de passe
    - +
    + + +

    @@ -103,20 +90,19 @@ Réponses proposées

    - - - - La réponse « oui » sera disponible +
    La réponse « peut-être » sera disponible +
    La réponse « non » sera disponible +

@@ -128,7 +114,7 @@
- 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. -
+

Fonctionnalités pas encore disponibles:

- -
- Spécifier un lien unique de vote à des participants définis + Spécifier un lien unique de vote à des participants définis par leur email

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

+ diff --git a/src/app/features/administration/form/advanced-config/advanced-config.component.scss b/src/app/features/administration/form/advanced-config/advanced-config.component.scss index 3d5e4870..76ead211 100644 --- a/src/app/features/administration/form/advanced-config/advanced-config.component.scss +++ b/src/app/features/administration/form/advanced-config/advanced-config.component.scss @@ -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%; +} diff --git a/src/app/features/administration/form/advanced-config/advanced-config.component.ts b/src/app/features/administration/form/advanced-config/advanced-config.component.ts index dcb8a75a..22a3f5c5 100644 --- a/src/app/features/administration/form/advanced-config/advanced-config.component.ts +++ b/src/app/features/administration/form/advanced-config/advanced-config.component.ts @@ -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 {} } diff --git a/src/app/features/administration/form/base-config/base-config.component.scss b/src/app/features/administration/form/base-config/base-config.component.scss index db71fe1e..2d3f25b5 100644 --- a/src/app/features/administration/form/base-config/base-config.component.scss +++ b/src/app/features/administration/form/base-config/base-config.component.scss @@ -1,4 +1,4 @@ #title { display: block; - width: 80%; + width: 100%; } diff --git a/src/app/features/administration/form/base-config/base-config.component.ts b/src/app/features/administration/form/base-config/base-config.component.ts index 9a3ccf66..ac97d437 100644 --- a/src/app/features/administration/form/base-config/base-config.component.ts +++ b/src/app/features/administration/form/base-config/base-config.component.ts @@ -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 }); } diff --git a/src/app/features/administration/form/date/interval/interval.component.ts b/src/app/features/administration/form/date/interval/interval.component.ts index dc5f1db5..3d2da36e 100644 --- a/src/app/features/administration/form/date/interval/interval.component.ts +++ b/src/app/features/administration/form/date/interval/interval.component.ts @@ -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); } diff --git a/src/app/features/administration/form/form.component.html b/src/app/features/administration/form/form.component.html index a9f584b7..9f23a16a 100644 --- a/src/app/features/administration/form/form.component.html +++ b/src/app/features/administration/form/form.component.html @@ -1,101 +1,9 @@ -
-
-
-
-
-

- {{ 'creation.title' | translate }} -

-
-
- - - -
-
- - - - - - - - +
+
+ + + - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - -
-

Debug data

-
-				form values :
-					{{ form.value | json }}
-				
-
-				poll initial values :
-					{{ poll | json }}
-				
-
-
-
-
+ +

diff --git a/src/app/features/administration/form/form.component.scss b/src/app/features/administration/form/form.component.scss index e69de29b..7974fca8 100644 --- a/src/app/features/administration/form/form.component.scss +++ b/src/app/features/administration/form/form.component.scss @@ -0,0 +1,10 @@ +@import '../../../../styles/variables'; +.admin-form { + padding: 1em; +} + +textarea { + border: solid 1px $border-color; + width: 100%; + display: block; +} diff --git a/src/app/features/administration/form/form.component.ts b/src/app/features/administration/form/form.component.ts index a1455aed..beb2298e 100644 --- a/src/app/features/administration/form/form.component.ts +++ b/src/app/features/administration/form/form.component.ts @@ -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() {} } diff --git a/src/app/features/administration/form/steps/step-five/step-five.component.html b/src/app/features/administration/form/steps/step-five/step-five.component.html new file mode 100644 index 00000000..c4748c30 --- /dev/null +++ b/src/app/features/administration/form/steps/step-five/step-five.component.html @@ -0,0 +1,26 @@ + + + + +
+
+
+ +
+
+ + +
+ {{ pollService.form.value.custom_url }} +
+
+ le formulaire est invalide +
  {{ pollService.form.errors | json }}
+
+
diff --git a/src/app/features/administration/form/steps/step-five/step-five.component.scss b/src/app/features/administration/form/steps/step-five/step-five.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/features/administration/form/steps/step-five/step-five.component.spec.ts b/src/app/features/administration/form/steps/step-five/step-five.component.spec.ts new file mode 100644 index 00000000..19eb8e0c --- /dev/null +++ b/src/app/features/administration/form/steps/step-five/step-five.component.spec.ts @@ -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; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [StepFiveComponent], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(StepFiveComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/administration/form/steps/step-five/step-five.component.ts b/src/app/features/administration/form/steps/step-five/step-five.component.ts new file mode 100644 index 00000000..c7e8fd33 --- /dev/null +++ b/src/app/features/administration/form/steps/step-five/step-five.component.ts @@ -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() {} +} diff --git a/src/app/features/administration/form/steps/step-four/step-four.component.html b/src/app/features/administration/form/steps/step-four/step-four.component.html new file mode 100644 index 00000000..d14f8656 --- /dev/null +++ b/src/app/features/administration/form/steps/step-four/step-four.component.html @@ -0,0 +1,64 @@ +
+
+
+ + +
+ + + + +
+ +
+ +
+ +
+
+
+
+
+
+ +
+
+ +
+
+
diff --git a/src/app/features/administration/form/steps/step-four/step-four.component.scss b/src/app/features/administration/form/steps/step-four/step-four.component.scss new file mode 100644 index 00000000..5ab410dc --- /dev/null +++ b/src/app/features/administration/form/steps/step-four/step-four.component.scss @@ -0,0 +1 @@ +@import '../../../../../../styles/variables'; diff --git a/src/app/features/administration/form/steps/step-four/step-four.component.spec.ts b/src/app/features/administration/form/steps/step-four/step-four.component.spec.ts new file mode 100644 index 00000000..a86bd20d --- /dev/null +++ b/src/app/features/administration/form/steps/step-four/step-four.component.spec.ts @@ -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; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [StepFourComponent], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(StepFourComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/administration/form/steps/step-four/step-four.component.ts b/src/app/features/administration/form/steps/step-four/step-four.component.ts new file mode 100644 index 00000000..127e9a62 --- /dev/null +++ b/src/app/features/administration/form/steps/step-four/step-four.component.ts @@ -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); + } + ); + } +} diff --git a/src/app/features/administration/form/steps/step-one/step-one.component.html b/src/app/features/administration/form/steps/step-one/step-one.component.html new file mode 100644 index 00000000..f133ba0b --- /dev/null +++ b/src/app/features/administration/form/steps/step-one/step-one.component.html @@ -0,0 +1,74 @@ +
+
+ +
+

+ {{ 'creation.choose_title' | translate }} +

+
+
+
+ +
+ +
+
+
+
+
+
+ + + + + + +
+ richTextMode activé +
+ +
+ {{ pollService.form.value.description.length }} / 300 caractères maximum +
+
+
+
+
+
+
+ +
+
+ + +
+
+
diff --git a/src/app/features/administration/form/steps/step-one/step-one.component.scss b/src/app/features/administration/form/steps/step-one/step-one.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/features/administration/form/steps/step-one/step-one.component.spec.ts b/src/app/features/administration/form/steps/step-one/step-one.component.spec.ts new file mode 100644 index 00000000..2443e97c --- /dev/null +++ b/src/app/features/administration/form/steps/step-one/step-one.component.spec.ts @@ -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; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [StepOneComponent], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(StepOneComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/administration/form/steps/step-one/step-one.component.ts b/src/app/features/administration/form/steps/step-one/step-one.component.ts new file mode 100644 index 00000000..24414959 --- /dev/null +++ b/src/app/features/administration/form/steps/step-one/step-one.component.ts @@ -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(); + } + } +} diff --git a/src/app/features/administration/form/steps/step-three/step-three.component.html b/src/app/features/administration/form/steps/step-three/step-three.component.html new file mode 100644 index 00000000..fa8f3aec --- /dev/null +++ b/src/app/features/administration/form/steps/step-three/step-three.component.html @@ -0,0 +1,107 @@ +
+ + + + + {{ pollService.calendar.length }} + + - {{ 'dates.count_dates' | translate }} + +
+ +
+ +
+
+
+
+
+ + + + +
+
+ + {{ pollService.timeList.length }} + + + {{ 'dates.count_time' | translate }} + (pour chaque jour) + +
+
+
+
+ + + +
+
+
+
+
+
+
+
+ +
+
+ + +
+
diff --git a/src/app/features/administration/form/steps/step-three/step-three.component.scss b/src/app/features/administration/form/steps/step-three/step-three.component.scss new file mode 100644 index 00000000..b35d40a1 --- /dev/null +++ b/src/app/features/administration/form/steps/step-three/step-three.component.scss @@ -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; +} diff --git a/src/app/features/administration/form/steps/step-three/step-three.component.spec.ts b/src/app/features/administration/form/steps/step-three/step-three.component.spec.ts new file mode 100644 index 00000000..c7d2c651 --- /dev/null +++ b/src/app/features/administration/form/steps/step-three/step-three.component.spec.ts @@ -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; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [StepThreeComponent], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(StepThreeComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/administration/form/steps/step-three/step-three.component.ts b/src/app/features/administration/form/steps/step-three/step-three.component.ts new file mode 100644 index 00000000..82dacea8 --- /dev/null +++ b/src/app/features/administration/form/steps/step-three/step-three.component.ts @@ -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) { + // moveItemInArray(this.pollService.choices, event.previousIndex, event.currentIndex); + } +} diff --git a/src/app/features/administration/form/steps/step-two/step-two.component.html b/src/app/features/administration/form/steps/step-two/step-two.component.html new file mode 100644 index 00000000..5cfbfba4 --- /dev/null +++ b/src/app/features/administration/form/steps/step-two/step-two.component.html @@ -0,0 +1,46 @@ +
+
+ + +

+ {{ 'creation.want' | translate }} +

+
+
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+ + +
+
+
diff --git a/src/app/features/administration/form/steps/step-two/step-two.component.scss b/src/app/features/administration/form/steps/step-two/step-two.component.scss new file mode 100644 index 00000000..ff4d18c0 --- /dev/null +++ b/src/app/features/administration/form/steps/step-two/step-two.component.scss @@ -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; + } + } +} diff --git a/src/app/features/administration/form/steps/step-two/step-two.component.spec.ts b/src/app/features/administration/form/steps/step-two/step-two.component.spec.ts new file mode 100644 index 00000000..e5e2b6f2 --- /dev/null +++ b/src/app/features/administration/form/steps/step-two/step-two.component.spec.ts @@ -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; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [StepTwoComponent], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(StepTwoComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/administration/form/steps/step-two/step-two.component.ts b/src/app/features/administration/form/steps/step-two/step-two.component.ts new file mode 100644 index 00000000..728c04aa --- /dev/null +++ b/src/app/features/administration/form/steps/step-two/step-two.component.ts @@ -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() {} +} diff --git a/src/app/features/administration/stepper/stepper.component.html b/src/app/features/administration/stepper/stepper.component.html index 00d5d761..697b6b74 100644 --- a/src/app/features/administration/stepper/stepper.component.html +++ b/src/app/features/administration/stepper/stepper.component.html @@ -1,114 +1,55 @@ - - -
- Informations du sondage - - Titre - - - - - Description - - - - - Url pour les participants - {{ urlPrefix }} - - - -
- -
-
-
- - -
- PollConfiguration du sondage - - Nombre de jours avant expiration - - - - - Les participants pourront consulter les résultats - - - Les choix possibles concerneront des dates - - - Le sondage sera protégé par un mot de passe - - - Vous recevrez un mail à chaque nouvelle participation - - - Vous recevrez un mail à chaque nouveau commentaire - - - La réponse « peut-être » sera disponible - - -
- - -
-
-
- - - Done -

You are now done.

-
- - -
-
- -
-
-
+
+
+ 1 + 2 + 3 + 4 + 5 + + +
+
+

+ {{ 'creation.title' | translate }} +

+

+ + {{ pollService.form.value.title }} + +

+

Étape {{ step_current }} sur {{ step_max }}

+
+
+
+
+
diff --git a/src/app/features/administration/stepper/stepper.component.scss b/src/app/features/administration/stepper/stepper.component.scss index e69de29b..0ced13f4 100644 --- a/src/app/features/administration/stepper/stepper.component.scss +++ b/src/app/features/administration/stepper/stepper.component.scss @@ -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; +} diff --git a/src/app/features/administration/stepper/stepper.component.ts b/src/app/features/administration/stepper/stepper.component.ts index 4df9c485..9f4fc3e7 100644 --- a/src/app/features/administration/stepper/stepper.component.ts +++ b/src/app/features/administration/stepper/stepper.component.ts @@ -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) {} } diff --git a/src/app/features/administration/success/success.component.html b/src/app/features/administration/success/success.component.html index 3b37584b..00834474 100644 --- a/src/app/features/administration/success/success.component.html +++ b/src/app/features/administration/success/success.component.html @@ -1,89 +1,108 @@ -
+
-

🎉 {{ 'resume.title' | translate }}

+

🎉 {{ 'resume.title' | translate }}

- Votre sondage « + Votre sondage +
+ « - {{ poll.title }} + {{ pollService.form.value.title }} - » a bien été créé ! + » + + n'a pas été créé :( + +
+ +
+ + a bien été créé ! +

-
+
-
-

- {{ 'resume.admins' | translate }} -

-

- Voici les liens d’accès au sondage, conservez-les soigneusement ! (Si vous les perdez vous pourrez - toujours les recevoir par email) -

-

- Côté admin -

-

- Pour accéder au sondage et à tous ses paramètres : -
- {{ pollService.getAdministrationUrl() }} - - -

-
- - Voir le sondage coté administrateur·ice - -
-

- Note : Le sondage sera supprimé {{ poll.default_expiracy_days_from_now }} jours après la date de sa - dernière modification. - - Le {{ poll.expiracy_date | date: 'short' }} - -

-
-
-

{{ 'resume.users' | translate }}

-

- Pour voir le sondage : -
- {{ pollService.getParticipationUrl() }} - -

-
- -
-
+
+
+
+
+ Pas de clé d'administration, l'enregistrement du sondage a échoué. vérifiez vos paramètres + réseau. +
+
+

+ + {{ 'resume.admins' | translate }} +

- +

+ Voici les liens d’accès au sondage, conservez-les soigneusement ! (Si vous les perdez + vous pourrez toujours les recevoir par email) +

+
+ Pour accéder au sondage et à tous ses paramètres : +
+
{{ pollService.getAdministrationUrlFromForm() }}
+							
+ +
+
+ + Voir le sondage coté administrateur·ice + + +
+

+ Note : Le sondage sera supprimé + {{ pollService.form.value.expiresDaysDelay }} jours après la date de + sa dernière modification. + + Le + {{ + pollService.DateUtilitiesService.addDaysToDate( + pollService.form.value.expiresDaysDelay, + today + ) | date: 'short' + }} + +

+
+
+
+
+
+

+ + {{ 'resume.users' | translate }} +

+

+ Pour voir le sondage : +
+ {{ pollService.getParticipationUrlFromForm() }} + +

+
+ +
+
+
+
-
-

{{ 'resume.links_mail' | translate }}

-

- -
- -
- -
-
- - Voir le sondage côté public - -

-
diff --git a/src/app/features/administration/success/success.component.scss b/src/app/features/administration/success/success.component.scss index 17666849..f354ac03 100644 --- a/src/app/features/administration/success/success.component.scss +++ b/src/app/features/administration/success/success.component.scss @@ -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; +} diff --git a/src/app/features/administration/success/success.component.ts b/src/app/features/administration/success/success.component.ts index 8fc9a4bb..eb1eb58c 100644 --- a/src/app/features/administration/success/success.component.ts +++ b/src/app/features/administration/success/success.component.ts @@ -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); }); } diff --git a/src/app/features/consultation/consultation-routing.module.ts b/src/app/features/consultation/consultation-routing.module.ts index 9432512f..66d7647b 100644 --- a/src/app/features/consultation/consultation-routing.module.ts +++ b/src/app/features/consultation/consultation-routing.module.ts @@ -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 }, diff --git a/src/app/features/consultation/consultation.component.html b/src/app/features/consultation/consultation.component.html index bcc40b2f..6447b16e 100644 --- a/src/app/features/consultation/consultation.component.html +++ b/src/app/features/consultation/consultation.component.html @@ -8,7 +8,7 @@
- vous êtes admin de ce sondage + vous êtes admin de ce sondage et pouvez le modifier
diff --git a/src/app/features/consultation/consultation.component.ts b/src/app/features/consultation/consultation.component.ts index a293e01a..63195644 100644 --- a/src/app/features/consultation/consultation.component.ts +++ b/src/app/features/consultation/consultation.component.ts @@ -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'); diff --git a/src/app/features/consultation/consultation.module.ts b/src/app/features/consultation/consultation.module.ts index 7771ab14..7b5cfcb8 100644 --- a/src/app/features/consultation/consultation.module.ts +++ b/src/app/features/consultation/consultation.module.ts @@ -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: [ diff --git a/src/app/features/consultation/poll-results-compact/poll-results-compact.component.scss b/src/app/features/consultation/poll-results-compact/poll-results-compact.component.scss index f9db3d5e..51e247bd 100644 --- a/src/app/features/consultation/poll-results-compact/poll-results-compact.component.scss +++ b/src/app/features/consultation/poll-results-compact/poll-results-compact.component.scss @@ -1,7 +1,6 @@ @import '../../../../styles/variables'; .box { - border-left: 3px solid white; cursor: pointer; * { cursor: pointer; diff --git a/src/app/features/consultation/poll-results-detailed/poll-results-detailed.component.ts b/src/app/features/consultation/poll-results-detailed/poll-results-detailed.component.ts index 6b780f13..02027b58 100644 --- a/src/app/features/consultation/poll-results-detailed/poll-results-detailed.component.ts +++ b/src/app/features/consultation/poll-results-detailed/poll-results-detailed.component.ts @@ -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'); } diff --git a/src/app/features/shared/components/ui/form/errors-list/errors-list.component.html b/src/app/features/shared/components/ui/form/errors-list/errors-list.component.html index 601c1541..fda6b1d6 100644 --- a/src/app/features/shared/components/ui/form/errors-list/errors-list.component.html +++ b/src/app/features/shared/components/ui/form/errors-list/errors-list.component.html @@ -1,5 +1,6 @@

@@ -24,4 +25,10 @@ {{ m }} +
+
+			{{ messages | json }}
+		
+

diff --git a/src/app/features/shared/components/ui/form/errors-list/errors-list.component.scss b/src/app/features/shared/components/ui/form/errors-list/errors-list.component.scss index d2c1ba67..15e9106d 100644 --- a/src/app/features/shared/components/ui/form/errors-list/errors-list.component.scss +++ b/src/app/features/shared/components/ui/form/errors-list/errors-list.component.scss @@ -7,3 +7,6 @@ color: white; border: solid 2px white; } +.validation-error-list { + margin: 2em; +} diff --git a/src/app/features/shared/components/ui/form/errors-list/errors-list.component.ts b/src/app/features/shared/components/ui/form/errors-list/errors-list.component.ts index c8936a8c..7603857d 100644 --- a/src/app/features/shared/components/ui/form/errors-list/errors-list.component.ts +++ b/src/app/features/shared/components/ui/form/errors-list/errors-list.component.ts @@ -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) {} diff --git a/src/app/routes-framadate.ts b/src/app/routes-framadate.ts index 1a9ca878..f2d7d619 100644 --- a/src/app/routes-framadate.ts +++ b/src/app/routes-framadate.ts @@ -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', diff --git a/src/app/shared/components/page-not-found/page-not-found.component.html b/src/app/shared/components/page-not-found/page-not-found.component.html index 0fbaa098..3db2a4e2 100644 --- a/src/app/shared/components/page-not-found/page-not-found.component.html +++ b/src/app/shared/components/page-not-found/page-not-found.component.html @@ -1,9 +1,7 @@
-

- {{ message | translate }} -

+

o_O {{ message | translate }}

diff --git a/src/assets/i18n/FR.json b/src/assets/i18n/FR.json index 70e1096c..95cb5e88 100644 --- a/src/assets/i18n/FR.json +++ b/src/assets/i18n/FR.json @@ -5,7 +5,8 @@ "home": { "title": "Bienvenue sur", "subtitle": "Se consulter simplement pour s’organiser 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" + } } diff --git a/src/assets/img/icone_home.png b/src/assets/img/icone_home.png new file mode 100644 index 00000000..9447bc94 Binary files /dev/null and b/src/assets/img/icone_home.png differ diff --git a/src/assets/img/where-is-it.jpg b/src/assets/img/where-is-it.jpg new file mode 100644 index 00000000..cf097e47 Binary files /dev/null and b/src/assets/img/where-is-it.jpg differ diff --git a/src/environments/endpoints.ts b/src/environments/endpoints.ts index 862b491d..01f50335 100644 --- a/src/environments/endpoints.ts +++ b/src/environments/endpoints.ts @@ -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', diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index 3f69bd0f..646996e4 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -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', diff --git a/src/environments/environment.ts b/src/environments/environment.ts index a8e3fe2a..8b3a6a3c 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -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, diff --git a/src/styles/libraries/_overrides.scss b/src/styles/libraries/_overrides.scss index 3218657a..1f847af9 100644 --- a/src/styles/libraries/_overrides.scss +++ b/src/styles/libraries/_overrides.scss @@ -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; diff --git a/src/styles/partials/_forms.scss b/src/styles/partials/_forms.scss index 977bd1b5..0f25d68f 100644 --- a/src/styles/partials/_forms.scss +++ b/src/styles/partials/_forms.scss @@ -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; +} diff --git a/src/styles/partials/_logo.scss b/src/styles/partials/_logo.scss index 50c83440..80aaa274 100644 --- a/src/styles/partials/_logo.scss +++ b/src/styles/partials/_logo.scss @@ -53,7 +53,7 @@ .legend { font-size: 14px; margin-left: 14px; - color: $dark-lavender; + color: $primary-light; } .legend_first { diff --git a/src/styles/partials/_main.scss b/src/styles/partials/_main.scss index b598ef39..7c84f5e3 100644 --- a/src/styles/partials/_main.scss +++ b/src/styles/partials/_main.scss @@ -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; } diff --git a/src/styles/partials/global.scss b/src/styles/partials/global.scss index 0c7f03c8..e1382968 100644 --- a/src/styles/partials/global.scss +++ b/src/styles/partials/global.scss @@ -7,3 +7,11 @@ html { main { min-height: 90vh; } +.min-height { + margin-top: 1em; + margin-bottom: 1em; + min-height: 50vh; +} +.content { + padding: 1em; +} diff --git a/src/styles/themes/_base.scss b/src/styles/themes/_base.scss index 5e2a778e..6a9d867e 100644 --- a/src/styles/themes/_base.scss +++ b/src/styles/themes/_base.scss @@ -2,10 +2,8 @@ background: $primary; main { - padding: 0; margin-bottom: 2em; padding-bottom: 5em; - padding-top: 1em; background: $white; } .big-header { diff --git a/src/styles/variables.scss b/src/styles/variables.scss index 410d2a66..1be3515e 100644 --- a/src/styles/variables.scss +++ b/src/styles/variables.scss @@ -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'; diff --git a/yarn.lock b/yarn.lock index d24ce927..a1ff46b2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"