From 1a410f120bc051e4b086ca47e6a4351f55272dd4 Mon Sep 17 00:00:00 2001 From: Tykayn Date: Sun, 7 Nov 2021 14:52:49 +0100 Subject: [PATCH 01/36] add libs and steps components --- package.json | 5 +- src/app/core/services/poll.service.ts | 13 +- .../administration-routing.module.ts | 22 ++- .../administration/administration.module.ts | 13 ++ .../administration/form/form.component.html | 111 ++------------ .../steps/step-five/step-five.component.html | 92 ++++++++++++ .../steps/step-five/step-five.component.scss | 0 .../step-five/step-five.component.spec.ts | 24 ++++ .../steps/step-five/step-five.component.ts | 22 +++ .../steps/step-four/step-four.component.html | 127 ++++++++++++++++ .../steps/step-four/step-four.component.scss | 0 .../step-four/step-four.component.spec.ts | 24 ++++ .../steps/step-four/step-four.component.ts | 19 +++ .../steps/step-one/step-one.component.html | 91 ++++++++++++ .../steps/step-one/step-one.component.scss | 0 .../steps/step-one/step-one.component.spec.ts | 24 ++++ .../form/steps/step-one/step-one.component.ts | 19 +++ .../step-three/step-three.component.html | 98 +++++++++++++ .../step-three/step-three.component.scss | 9 ++ .../step-three/step-three.component.spec.ts | 24 ++++ .../steps/step-three/step-three.component.ts | 23 +++ .../steps/step-two/step-two.component.html | 43 ++++++ .../steps/step-two/step-two.component.scss | 3 + .../steps/step-two/step-two.component.spec.ts | 24 ++++ .../form/steps/step-two/step-two.component.ts | 61 ++++++++ .../stepper/stepper.component.html | 125 ++-------------- .../stepper/stepper.component.scss | 19 +++ .../stepper/stepper.component.ts | 50 +------ src/styles/variables.scss | 136 ++++++------------ yarn.lock | 19 +++ 30 files changed, 886 insertions(+), 354 deletions(-) create mode 100644 src/app/features/administration/form/steps/step-five/step-five.component.html create mode 100644 src/app/features/administration/form/steps/step-five/step-five.component.scss create mode 100644 src/app/features/administration/form/steps/step-five/step-five.component.spec.ts create mode 100644 src/app/features/administration/form/steps/step-five/step-five.component.ts create mode 100644 src/app/features/administration/form/steps/step-four/step-four.component.html create mode 100644 src/app/features/administration/form/steps/step-four/step-four.component.scss create mode 100644 src/app/features/administration/form/steps/step-four/step-four.component.spec.ts create mode 100644 src/app/features/administration/form/steps/step-four/step-four.component.ts create mode 100644 src/app/features/administration/form/steps/step-one/step-one.component.html create mode 100644 src/app/features/administration/form/steps/step-one/step-one.component.scss create mode 100644 src/app/features/administration/form/steps/step-one/step-one.component.spec.ts create mode 100644 src/app/features/administration/form/steps/step-one/step-one.component.ts create mode 100644 src/app/features/administration/form/steps/step-three/step-three.component.html create mode 100644 src/app/features/administration/form/steps/step-three/step-three.component.scss create mode 100644 src/app/features/administration/form/steps/step-three/step-three.component.spec.ts create mode 100644 src/app/features/administration/form/steps/step-three/step-three.component.ts create mode 100644 src/app/features/administration/form/steps/step-two/step-two.component.html create mode 100644 src/app/features/administration/form/steps/step-two/step-two.component.scss create mode 100644 src/app/features/administration/form/steps/step-two/step-two.component.spec.ts create mode 100644 src/app/features/administration/form/steps/step-two/step-two.component.ts 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/core/services/poll.service.ts b/src/app/core/services/poll.service.ts index 5114b7c8..97e8abb6 100644 --- a/src/app/core/services/poll.service.ts +++ b/src/app/core/services/poll.service.ts @@ -17,6 +17,7 @@ import { Title } from '@angular/platform-browser'; import { DateUtilitiesService } from './date.utilities.service'; import { Stack } from '../models/stack.model'; import { Vote } from '../models/vote.model'; +import { FormGroup } from '@angular/forms'; @Injectable({ providedIn: 'root', @@ -25,6 +26,16 @@ export class PollService implements Resolve { _poll: BehaviorSubject = new BehaviorSubject(undefined); public readonly poll: Observable = this._poll.asObservable(); public pass_hash: string; + public calendar: Date[] = []; + public form: FormGroup; + public startDateInterval: string; + public endDateInterval: string; + public intervalDays: any; + public intervalDaysDefault = 7; + public previousRouteName: string = '/administration'; + public nextRouteName: string = '/administration/step/2'; + public step_current: number = 1; + public step_max: number = 5; constructor( private http: HttpClient, @@ -262,7 +273,7 @@ export class PollService implements Resolve { if (this._poll && this._poll.getValue) { const polltemp = this._poll.getValue(); if (polltemp) { - url = `${environment.frontDomain}#/poll/${polltemp.custom_url}/consultation`; + url = `${environment.api.baseHref}#/poll/${polltemp.custom_url}/consultation`; } } // TODO handle pass access diff --git a/src/app/features/administration/administration-routing.module.ts b/src/app/features/administration/administration-routing.module.ts index 9fad9e8b..05b6a355 100644 --- a/src/app/features/administration/administration-routing.module.ts +++ b/src/app/features/administration/administration-routing.module.ts @@ -2,11 +2,27 @@ 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'; const routes: Routes = [ - { path: '', component: AdministrationComponent, data: { animation: 'AdminPage' } }, - { path: 'naming', component: NamingComponent }, + { + path: '', + component: AdministrationComponent, + }, + { + path: 'step', + children: [ + { path: '1', component: StepOneComponent }, + { path: '2', component: StepTwoComponent }, + { path: '3', component: StepThreeComponent }, + { path: '4', component: StepFourComponent }, + { path: '5', component: StepFiveComponent }, + ], + }, ]; @NgModule({ diff --git a/src/app/features/administration/administration.module.ts b/src/app/features/administration/administration.module.ts index 8d906d69..b0faa2a9 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'; @@ -34,6 +40,12 @@ import { TimeListComponent } from './form/date/list/time/time-list.component'; KindSelectComponent, BaseConfigComponent, AdvancedConfigComponent, + StepOneComponent, + StepTwoComponent, + StepThreeComponent, + StepFourComponent, + StepFiveComponent, + SuccessComponent, IntervalComponent, DayListComponent, PickerComponent, @@ -42,6 +54,7 @@ import { TimeListComponent } from './form/date/list/time/time-list.component'; imports: [ AdministrationRoutingModule, CommonModule, + CalendarModule, ReactiveFormsModule, SharedModule, FormsModule, diff --git a/src/app/features/administration/form/form.component.html b/src/app/features/administration/form/form.component.html index a9f584b7..820437ed 100644 --- a/src/app/features/administration/form/form.component.html +++ b/src/app/features/administration/form/form.component.html @@ -1,101 +1,14 @@ -
-
-
-
-
-

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

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

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

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

Debug data

-
-				form values :
-					{{ form.value | json }}
-				
-
-				poll initial values :
-					{{ poll | json }}
-				
-
-
-
-
+ +
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..daa5d9bd --- /dev/null +++ b/src/app/features/administration/form/steps/step-five/step-five.component.html @@ -0,0 +1,92 @@ + +
+

+ Félicitations, votre sondage " + {{ pollService.form.value.title }} " est créé +

+ Un récapitulatif par email vous a été envoyé. Partagez-le au monde avec ce lien: Administrez-le avec cet autre lien: + +
+ +

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

+
+

{{ 'resume.admins' | translate }}

+

+ Votre sondage « + + {{ pollService.form.value.title }} + + » a bien été créé ! +

+

+ 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.form.value.urlAdmin }} +

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

+ Note : Le sondage sera supprimé {{ pollService.form.value.deletionDateAfterLastModification }} jours après + la date de sa dernière modification. +

+
+
+

{{ 'resume.users' | translate }}

+

+ Pour accéder au sondage : + {{ pollService.form.value.urlPublic }} +

+ + + Voir le sondage + +
+
+

{{ 'resume.links_mail' | translate }}

+ + Voir le sondage + +
+
+
+
+
+ +
+
+ +
+ image WIP + + + + +
+ {{ poll.custom_url }} +
+
+ le formulaire est invalide +
  {{ 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..cf46cab1 --- /dev/null +++ b/src/app/features/administration/form/steps/step-five/step-five.component.ts @@ -0,0 +1,22 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { PollService } from '../../../../../core/services/poll.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) {} + ngOnInit(): void {} + + askInitFormDefault() {} + + createPoll() {} + + 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..849c5996 --- /dev/null +++ b/src/app/features/administration/form/steps/step-four/step-four.component.html @@ -0,0 +1,127 @@ +
+
+
+ + +
+ + + + +
+ +
+ +
+

{{ 'creation.advanced' | translate }}

+ +
+ + +
+ {{ urlPrefix }} {{ pollService.form.controls.custom_url.value }} + + + + + +
+
+ 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 + +
+
+
+
+
+
+ +
+
+ + +
+
+
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..e69de29b 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..423e67b4 --- /dev/null +++ b/src/app/features/administration/form/steps/step-four/step-four.component.ts @@ -0,0 +1,19 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { PollService } from '../../../../../core/services/poll.service'; + +@Component({ + selector: 'app-step-four', + templateUrl: './step-four.component.html', + styleUrls: ['./step-four.component.scss'], +}) +export class StepFourComponent implements OnInit { + urlPrefix: any; + advancedDisplayEnabled: any; + @Input() + step_max: any; + @Input() + form: any; + constructor(public pollService: PollService) {} + + ngOnInit(): void {} +} 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..c88d7ea2 --- /dev/null +++ b/src/app/features/administration/form/steps/step-one/step-one.component.html @@ -0,0 +1,91 @@ +
+
+ +
+

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

+
+
+ + +
+ + +
+ +
+
+
+
+
+
+ +
+ + + +
+
+ richTextMode activé +
+ +
+ 300 caractères maximum +
+
+
+ +
+
+
+ slug: {{ pollService.form.value.custom_url }} +
+
+
+
+ + +
+
+
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..f013b61b --- /dev/null +++ b/src/app/features/administration/form/steps/step-one/step-one.component.ts @@ -0,0 +1,19 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { PollService } from '../../../../../core/services/poll.service'; + +@Component({ + selector: 'app-step-one', + templateUrl: './step-one.component.html', + styleUrls: ['./step-one.component.scss'], +}) +export class StepOneComponent implements OnInit { + constructor(public pollService: PollService) {} + + @Input() + step_max: any; + @Input() + form: FormGroup; + + ngOnInit(): void {} +} 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..2dfaa6c4 --- /dev/null +++ b/src/app/features/administration/form/steps/step-three/step-three.component.html @@ -0,0 +1,98 @@ +
+ + + +
+
+ + {{ pollService.timeList.length }} + + + {{ 'dates.count_time' | translate }} + (pour chaque jour) + +
+
+ + + +
+ +
+
+
+ + + +
+
+
+
+ + {{ pollService.calendar.length }} + + + {{ 'dates.count_dates' | translate }} + +
+ +
+ + + +
+
+
+
+ +
+
+ + +
+
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..621b2737 --- /dev/null +++ b/src/app/features/administration/form/steps/step-three/step-three.component.ts @@ -0,0 +1,23 @@ +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; + + constructor(public pollService: PollService) {} + + 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..4ffd4b05 --- /dev/null +++ b/src/app/features/administration/form/steps/step-two/step-two.component.html @@ -0,0 +1,43 @@ +
+
+ +

+ {{ '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..182a819e --- /dev/null +++ b/src/app/features/administration/form/steps/step-two/step-two.component.scss @@ -0,0 +1,3 @@ +.fa { + margin-right: 1em; +} 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..2a030ff0 --- /dev/null +++ b/src/app/features/administration/form/steps/step-two/step-two.component.ts @@ -0,0 +1,61 @@ +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; + } + + 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..6d196e3e 100644 --- a/src/app/features/administration/stepper/stepper.component.html +++ b/src/app/features/administration/stepper/stepper.component.html @@ -1,114 +1,11 @@ - - -
- 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.

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

+ Étape {{ step_current }} / + {{ step_max }} +

+
+
+
+
+
diff --git a/src/app/features/administration/stepper/stepper.component.scss b/src/app/features/administration/stepper/stepper.component.scss index e69de29b..186e7dd5 100644 --- a/src/app/features/administration/stepper/stepper.component.scss +++ b/src/app/features/administration/stepper/stepper.component.scss @@ -0,0 +1,19 @@ +@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; +} diff --git a/src/app/features/administration/stepper/stepper.component.ts b/src/app/features/administration/stepper/stepper.component.ts index 4df9c485..cf7163c8 100644 --- a/src/app/features/administration/stepper/stepper.component.ts +++ b/src/app/features/administration/stepper/stepper.component.ts @@ -1,55 +1,13 @@ 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'; @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; } diff --git a/src/styles/variables.scss b/src/styles/variables.scss index 410d2a66..0b0e3f4b 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; // bleu 30 +$d-neutral: #767486; // bleu 30 +$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" From d726eb36684ae7b34b0875dc2747b9fcc8d0c013 Mon Sep 17 00:00:00 2001 From: Tykayn Date: Sun, 7 Nov 2021 15:21:27 +0100 Subject: [PATCH 02/36] fill back poll service --- src/app/app.component.ts | 7 +- src/app/core/services/poll.service.ts | 626 ++++++++++++------ .../form/base-config/base-config.component.ts | 2 +- .../form/date/interval/interval.component.ts | 4 +- .../administration/form/form.component.ts | 217 +----- .../consultation/consultation.component.ts | 12 +- .../poll-results-detailed.component.ts | 6 +- 7 files changed, 464 insertions(+), 410 deletions(-) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 6a9826c2..7ad5fadf 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -11,6 +11,7 @@ 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'; @Component({ selector: 'app-root', @@ -64,7 +65,10 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewInit { 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(); @@ -84,6 +88,7 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewInit { } }); } + ngAfterViewInit(): void { console.log('this.shortcuts', this.shortcuts); this.shortcuts.push( diff --git a/src/app/core/services/poll.service.ts b/src/app/core/services/poll.service.ts index 97e8abb6..44cf869c 100644 --- a/src/app/core/services/poll.service.ts +++ b/src/app/core/services/poll.service.ts @@ -1,54 +1,145 @@ -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'; import { UuidService } from './uuid.service'; 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'; -import { FormGroup } from '@angular/forms'; @Injectable({ providedIn: 'root', }) export class PollService implements Resolve { - _poll: BehaviorSubject = new BehaviorSubject(undefined); + private _poll: BehaviorSubject = new BehaviorSubject(undefined); public readonly poll: Observable = this._poll.asObservable(); - public pass_hash: string; - public calendar: Date[] = []; public form: FormGroup; public startDateInterval: string; public endDateInterval: string; - public intervalDays: any; + 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 urlPrefix: string = window.location.origin + '/participation/'; + public advancedDisplayEnabled = false; + public showDateInterval = false; + public allowSeveralHours = false; + public richTextMode = false; + public calendar: any; constructor( private http: HttpClient, private router: Router, private apiService: ApiService, - private storageService: StorageService, private userService: UserService, private uuidService: UuidService, - private dateUtils: DateUtilitiesService, - private titleService: Title, - private toastService: ToastService + 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(); + if (environment.autofill) { + this.setDemoValues(); + } else { + this.calendar = [new Date()]; + } + } + + /** + * add example values to the form + */ + setDemoValues(): void { + this.addChoice('orange'); + this.addChoice('raisin'); + this.addChoice('abricot'); + + this.calendar = [ + this.DateUtilitiesService.addDaysToDate(1, new Date()), + this.DateUtilitiesService.addDaysToDate(2, new Date()), + this.DateUtilitiesService.addDaysToDate(3, new Date()), + ]; + this.form.patchValue({ + title: 'mon titre', + 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, + isOwnerNotifiedByEmailOnNewVote: false, + isOwnerNotifiedByEmailOnNewComment: false, + isMaybeAnswerAvailable: false, + richTextMode: false, + areResultsPublic: true, + expiracyNumberOfDays: 60, + }); + this.automaticSlug(); + } + + public enrichVoteStackWithCurrentPollChoicesDefaultVotes(vote_stack: Stack) { + console.log('vote_stack', vote_stack); + this.toastService.display('TODO refill vote stack'); + // this.form.patchValue(vote_stack) + } + + /** + * set the poll slug from other data of the poll + */ + automaticSlug() { + this.form.patchValue({ custom_url: this.makeSlug(this.form) }); + } + + public createFormGroup() { + let form = this.fb.group({ + title: ['', [Validators.required, Validators.minLength(12)]], + creatorPseudo: ['', [Validators.required]], + created_at: [new Date(), [Validators.required]], + creatorEmail: ['', [Validators.required]], + custom_url: [this.uuidService.getUUID(), [Validators.required]], + description: ['', [Validators.required]], + choices: new FormArray([]), + whoModifiesAnswers: ['', [Validators.required]], + whoCanChangeAnswers: ['', [Validators.required]], + isAboutDate: [true, [Validators.required]], + startDateInterval: ['', [Validators.required]], + endDateInterval: ['', [Validators.required]], + isProtectedByPassword: [false, [Validators.required]], + isOwnerNotifiedByEmailOnNewVote: [false, [Validators.required]], + isOwnerNotifiedByEmailOnNewComment: [false, [Validators.required]], + isMaybeAnswerAvailable: [false, [Validators.required]], + areResultsPublic: [true, [Validators.required]], + richTextMode: [false, [Validators.required]], + expiracyNumberOfDays: [60, [Validators.required, Validators.min(0)]], + }); + this.form = form; + return form; + } + + public updateSlug(): void { + console.log('this.form.value', this.form.value); + this.form.patchValue({ custom_url: this.makeSlug(this.form) }); } /** @@ -57,127 +148,264 @@ export class PollService implements Resolve { * @param state */ public async resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { - console.log('resolve route,state', route, state); const segments: string[] = state.url.split('/'); - const wantedcustom_url: string = segments.includes('poll') ? segments[segments.indexOf('poll') + 1] : ''; - + const wantedSlug: string = segments.includes('poll') ? segments[segments.indexOf('poll') + 1] : ''; + if (!wantedSlug && state.url.includes('administration')) { + // creation of new poll + const poll = new Poll(this.userService.getCurrentUser(), this.uuidService.getUUID(), ''); + this._poll.next(poll); + this.router.navigate(['poll/' + poll.custom_url + '/administration']); + } if ( !this._poll.getValue() || !this._poll.getValue().custom_url || - this._poll.getValue().custom_url !== wantedcustom_url + this._poll.getValue().custom_url !== wantedSlug ) { - if (this.pass_hash) { - this.storageService.vote_stack.pass_hash = this.pass_hash; - await this.loadPollBycustom_urlWithPasswordHash(wantedcustom_url, this.pass_hash); - } else { - await this.loadPollBycustom_url(wantedcustom_url); - } + await this.loadPollByCustomUrl(wantedSlug); } - const loadedPoll = this._poll.getValue(); - if (loadedPoll) { - this.storageService.vote_stack.poll_custom_url = loadedPoll.custom_url; - return loadedPoll; + if (this._poll.getValue()) { + return this._poll.getValue(); } else { this.router.navigate(['page-not-found']); return; } } - /** - * get all polls - */ - getAllAvailablePolls(): void { + getAllAvailablePolls() { 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 { - if (custom_url) { - const poll: Poll | undefined = await this.apiService.getPollByCustomUrl(custom_url); - - if (poll) { - this.updateCurrentPoll(poll); - this.titleService.setTitle(`☑️ ${poll.title} - ${environment.appTitle}`); - } else { - this.toastService.display(`sondage ${custom_url} non trouvé`); - this.router.navigate(['page-not-found']); - } - } else { - this.toastService.display(`sondage sans custom url : ${custom_url}`); + public async loadPollByCustomUrl(slug: string): Promise { + if (slug) { + const poll: Poll | undefined = await this.apiService.getPollByCustomUrl(slug); + console.log({ loadPollBySlugResponse: poll }); + this.updateCurrentPoll(poll); + } + } + public async loadPollByCustomUrlWithPasswordHash(slug: string, pass_hash: string): Promise { + if (slug) { + const poll: Poll | undefined = await this.apiService.getPollByCustomUrlWithHash(slug, pass_hash); + console.log({ loadPollBySlugResponse: poll }); + this.updateCurrentPoll(poll); } } - public async loadPollBycustom_urlWithPasswordHash(custom_url: string, hash: string): Promise { - if (custom_url) { - const poll: Poll | undefined = await this.apiService.getPollByCustomUrlWithHash(custom_url, hash); - - if (poll) { - this.updateCurrentPoll(poll); - this.titleService.setTitle(`☑️ ${poll.title} - ${environment.appTitle}`); - } else { - this.toastService.display(`sondage ${custom_url} non trouvé`); - this.router.navigate(['page-not-found']); - } - } else { - this.toastService.display(`sondage sans custom url : ${custom_url}`); - } - } - - /** - * update poll and parse its fields - * @param poll - */ public updateCurrentPoll(poll: Poll): void { - 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); - } - - this.toastService.display('sondage bien mis à jour', 'success'); this._poll.next(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 { - let str = ''; - const creation_date = new Date(poll.creation_date); - str = - creation_date.getFullYear() + - '_' + - (creation_date.getMonth() + 1) + - '_' + - creation_date.getDate() + - '_' + - poll.owner.pseudo + - '_' + - poll.title; + addIntervalOfDates(): void { + const newIntervalArray = this.DateUtilitiesService.getDatesInRange( + this.DateUtilitiesService.parseInputDateToDateObject(new Date(this.startDateInterval)), + this.DateUtilitiesService.parseInputDateToDateObject(new Date(this.endDateInterval)), + 1 + ); - return this.convertTextToSlug(str) + '-' + this.uuidService.getUUID(); + 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.`); } /** - * convert a text to a slug - * @param str + * handle keyboard shortcuts + * @param $event + * @param choice_number */ - public convertTextToSlug(str: string): string { - str = str.trim(); + 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(); + } + } + + public createPoll(): void { + console.log('this.form', this.form); + const newpoll = this.newPollFromForm(this.form); + console.log('newpoll', newpoll); + this.apiService.createPoll(newpoll); + } + + /** + * 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(false); + 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.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 = ''; + str = + form.value.created_at.getFullYear() + + '_' + + (form.value.created_at.getMonth() + 1) + + '_' + + form.value.created_at.getDate() + + '_' + + form.value.creatorPseudo + + '_' + + form.value.title; str = str.replace(/^\s+|\s+$/g, ''); // trim str = str.toLowerCase(); @@ -192,10 +420,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.'); } @@ -214,98 +457,95 @@ 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; - - 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 = ['yes']; - - 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; - } - - 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 getParticipationUrl(): string { - let url = ''; - if (this._poll && this._poll.getValue) { - const polltemp = this._poll.getValue(); - if (polltemp) { - url = `${environment.api.baseHref}#/poll/${polltemp.custom_url}/consultation`; - } - } - // TODO handle pass access - return url; - } - - /** - * enrich vote stack with missing default votes - * @param vote_stack - */ - enrichVoteStackWithCurrentPollChoicesDefaultVotes(vote_stack: Stack) { - if (this._poll && this._poll.getValue) { - const polltemp = this._poll.getValue(); - polltemp.choices.map((choice) => { - // for each vote, if it has the choice_id, do nothing, else, add a default vote - if (!this.findExistingVoteFromChoiceId(choice.id, vote_stack.votes)) { - vote_stack.votes.push(new Vote(choice.id)); - } + 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 list = new Map>(); + pseudos.forEach((pseudo: string) => { + list.set( + pseudo, + new Map( + poll.choices.map((choice: Choice) => { + return [choice.name, undefined]; + }) + ) + ); + }); + + poll.choices.forEach((choice: Choice) => { + choice.participants.forEach((users: Set, answer: Answer) => { + users.forEach((user: Owner) => { + list.get(user.pseudo).set(choice.name, answer); + }); + }); + }); + + return list; } - /** - * find an existing vote in vote_stack from its choice_id - * @param choice_id - * @param votes - */ - findExistingVoteFromChoiceId(choice_id: number, votes: Vote[]) { - return votes.find((vote: Vote) => { - if (vote.choice_id === choice_id) { - return vote; - } - }); + newPollFromForm(form: any): any { + const newpoll = new Poll( + this.userService.getCurrentUser(), + this.uuidService.getUUID(), + form.controls.title.value + ); + /** + * convert to API version 1 config poll + */ + const apiV1Poll = { + menuVisible: true, + expiracyDateDefaultInDays: newpoll.default_expiracy_days_from_now, + deletionDateAfterLastModification: newpoll.default_expiracy_days_from_now, + pollType: newpoll.kind ? 'date' : 'classic', // classic or dates + title: newpoll.title, + description: newpoll.description, + myName: newpoll.owner.pseudo, + myComment: '', + isAdmin: true, // when we create a poll, we are admin on it + myVoteStack: {}, + myTempVoteStack: 0, + myEmail: newpoll.owner.email, + myPolls: [], // list of retrieved polls from the backend api + /* + date specific poll, we have the choice to setup different hours (timeList) for all possible dates (dateList), or use the same hours for all dates + */ + allowSeveralHours: 'true', + // access + visibility: newpoll.areResultsPublic, // visible to one with the link: + voteChoices: newpoll.isMaybeAnswerAvailable ? 'yes, maybe, no' : 'yes', // possible answers to a vote choice: only "yes", "yes, maybe, no" + created_at: new Date(), + expirationDate: '', // expiracy date + voteStackId: null, // id of the vote stack to update + pollId: null, // id of the current poll when created. data given by the backend api + pollSlug: null, // id of the current poll when created. data given by the backend api + currentPoll: null, // current poll selected with createPoll or getPoll of ConfigService + passwordAccess: false, + password: newpoll.password, + customUrl: newpoll.custom_url, // custom slug in the url, must be unique + customUrlIsUnique: null, // given by the backend + urlSlugPublic: null, + urlPublic: null, + urlAdmin: null, + adminKey: '', // key to change config of the poll + owner_modifier_token: '', // key to change a vote stack + canModifyAnswers: newpoll.modification_policy, // bool for the frontend selector + whoModifiesAnswers: newpoll.modification_policy, // everybody, self, nobody (: just admin) + whoCanChangeAnswers: newpoll.modification_policy, // everybody, self, nobody (: just admin) + dateList: newpoll.dateChoices, // sets of days as strings, config to set identical time for days in a special days poll + timeList: newpoll.timeSlices, // ranges of time expressed as strings + + answers: newpoll.choices, + // modals + displayConfirmVoteModalAdmin: false, + }; + console.log('apiV1Poll', apiV1Poll); + return apiV1Poll; } } 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.ts b/src/app/features/administration/form/form.component.ts index a1455aed..7bb6a64f 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, 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/consultation/consultation.component.ts b/src/app/features/consultation/consultation.component.ts index a293e01a..e9ce1fb5 100644 --- a/src/app/features/consultation/consultation.component.ts +++ b/src/app/features/consultation/consultation.component.ts @@ -60,18 +60,18 @@ 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) => { + this.pollService.loadPollByCustomUrlWithPasswordHash(this.pollSlug, this.pass_hash).then((resp) => { console.log('resp', resp); this.fetching = false; this.storageService.vote_stack.id = null; - this.storageService.setChoicesForVoteStack(this.pollService._poll.getValue().choices); + this.storageService.setChoicesForVoteStack(this.pollService.form.value.choices); }); } else { - this.pollService.loadPollBycustom_url(this.pollSlug).then((resp) => { + this.pollService.loadPollByCustomUrl(this.pollSlug).then((resp) => { console.log('resp', resp); this.fetching = false; this.storageService.vote_stack.id = null; - this.storageService.setChoicesForVoteStack(this.pollService._poll.getValue().choices); + this.storageService.setChoicesForVoteStack(this.pollService.form.value.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/poll-results-detailed/poll-results-detailed.component.ts b/src/app/features/consultation/poll-results-detailed/poll-results-detailed.component.ts index 6b780f13..b5383186 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 @@ -76,11 +76,11 @@ export class PollResultsDetailedComponent { storeVoteStackAndReloadPoll(voteStack: any) { if (voteStack.status == 200) { this.storageService.mapVotes(voteStack.data); - this.pollService.enrichVoteStackWithCurrentPollChoicesDefaultVotes(this.storageService.vote_stack); + // 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'); From 938a8e72d916b88e6d9780aef72aae8cce5aa183 Mon Sep 17 00:00:00 2001 From: Tykayn Date: Sun, 7 Nov 2021 21:08:38 +0100 Subject: [PATCH 03/36] confirm before reinit --- src/app/core/services/api.service.ts | 4 +- src/app/core/services/poll.service.ts | 5 +- .../administration/form/form.component.html | 2 +- .../administration/form/form.component.scss | 10 ++++ .../steps/step-five/step-five.component.html | 51 ++++++++++++------- .../steps/step-five/step-five.component.ts | 14 +++-- src/styles/partials/global.scss | 4 ++ 7 files changed, 66 insertions(+), 24 deletions(-) diff --git a/src/app/core/services/api.service.ts b/src/app/core/services/api.service.ts index 3f69f88b..05ce4cc8 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', }) @@ -78,7 +80,7 @@ export class ApiService { }; } - public async createPoll(poll: Poll): Promise { + public async createPoll(poll: PollDTO): Promise { // this.loaderService.setStatus(true); console.log('createPoll config', poll); return this.axiosInstance.post( diff --git a/src/app/core/services/poll.service.ts b/src/app/core/services/poll.service.ts index 44cf869c..19acaf5f 100644 --- a/src/app/core/services/poll.service.ts +++ b/src/app/core/services/poll.service.ts @@ -23,7 +23,7 @@ import { Stack } from '../models/stack.model'; providedIn: 'root', }) export class PollService implements Resolve { - private _poll: BehaviorSubject = new BehaviorSubject(undefined); + public _poll: BehaviorSubject = new BehaviorSubject(undefined); public readonly poll: Observable = this._poll.asObservable(); public form: FormGroup; public startDateInterval: string; @@ -38,7 +38,8 @@ export class PollService implements Resolve { public step_max: number = 5; public round: Function; public pass_hash: string; - public urlPrefix: string = window.location.origin + '/participation/'; + public admin_key: string; + public urlPrefix: string = window.location.origin; public advancedDisplayEnabled = false; public showDateInterval = false; public allowSeveralHours = false; diff --git a/src/app/features/administration/form/form.component.html b/src/app/features/administration/form/form.component.html index 820437ed..b63a41e6 100644 --- a/src/app/features/administration/form/form.component.html +++ b/src/app/features/administration/form/form.component.html @@ -4,7 +4,7 @@ {{ 'creation.title' | translate }} -
+
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/steps/step-five/step-five.component.html b/src/app/features/administration/form/steps/step-five/step-five.component.html index daa5d9bd..8d476bc0 100644 --- 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 @@ -1,9 +1,7 @@

- Félicitations, votre sondage " - {{ pollService.form.value.title }} " est créé + Félicitations, votre sondage " {{ pollService.form.value.title }} " est créé

Un récapitulatif par email vous a été envoyé. Partagez-le au monde avec ce lien: Administrez-le avec cet autre lien: @@ -30,8 +28,8 @@ Pour accéder au sondage et à tous ses paramètres : {{ pollService.form.value.urlAdmin }}

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

@@ -43,16 +41,20 @@

{{ 'resume.users' | translate }}

Pour accéder au sondage : - {{ pollService.form.value.urlPublic }} + {{ pollService.urlPrefix + '/#/poll/' + pollService.form.value.custom_url + '/consultation' }} +

- - + + Voir le sondage

{{ 'resume.links_mail' | translate }}

- + Voir le sondage
@@ -60,7 +62,25 @@
- +
+
+ +
+
+ +
+
+ @@ -73,20 +93,17 @@ Tout réinitialiser - +
- {{ poll.custom_url }} + {{ pollService.form.value.custom_url }}
-
+
le formulaire est invalide -
  {{ form.errors | json }}
+
  {{ pollService.form.errors | json }}
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 index cf46cab1..04f429d2 100644 --- 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 @@ -1,6 +1,7 @@ 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', @@ -11,12 +12,19 @@ export class StepFiveComponent implements OnInit { @Input() step_max: any; @Input() public form: FormGroup; poll: any; - constructor(public pollService: PollService) {} + constructor(public pollService: PollService, private apiService: ApiService) {} ngOnInit(): void {} - askInitFormDefault() {} + askInitFormDefault() { + if (window.confirm('réinitialiser le formulaire ?')) { + this.pollService.askInitFormDefault(); + } + } - createPoll() {} + createPoll() { + let apiData = this.pollService.newPollFromForm(this.pollService._poll); + this.apiService.createPoll(apiData); + } automaticSlug() {} } diff --git a/src/styles/partials/global.scss b/src/styles/partials/global.scss index 0c7f03c8..568e45b7 100644 --- a/src/styles/partials/global.scss +++ b/src/styles/partials/global.scss @@ -7,3 +7,7 @@ html { main { min-height: 90vh; } + +.content { + padding: 1em; +} From c67f30918097daa3cf1470263500d7d3c030f118 Mon Sep 17 00:00:00 2001 From: Tykayn Date: Mon, 8 Nov 2021 09:32:50 +0100 Subject: [PATCH 04/36] style for calendar in step 3 --- src/app/app.component.ts | 4 +- src/app/core/models/poll.DTO.model.ts | 46 +++++++++++++++++++ src/app/core/services/poll.service.ts | 4 ++ .../step-three/step-three.component.html | 6 +-- src/styles/partials/_forms.scss | 34 ++++++++++++++ src/styles/themes/_base.scss | 1 - 6 files changed, 88 insertions(+), 7 deletions(-) create mode 100644 src/app/core/models/poll.DTO.model.ts diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 7ad5fadf..d9eb0ee9 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -12,6 +12,7 @@ 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'; @Component({ selector: 'app-root', @@ -42,7 +43,6 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewInit { 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); @@ -53,8 +53,6 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewInit { ngOnInit(): void { this.printpath('', this.router.config); this.router.events.subscribe((evt) => { - console.log('route changed', evt); - if (!(evt instanceof NavigationEnd)) { return; } 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/poll.service.ts b/src/app/core/services/poll.service.ts index 19acaf5f..faa691e0 100644 --- a/src/app/core/services/poll.service.ts +++ b/src/app/core/services/poll.service.ts @@ -491,6 +491,10 @@ export class PollService implements Resolve { return list; } + convertCalendarDatesToChoices(array_dates) { + return array_dates; + } + newPollFromForm(form: any): any { const newpoll = new Poll( this.userService.getCurrentUser(), 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 index 2dfaa6c4..96e390f5 100644 --- 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 @@ -78,9 +78,9 @@ [showWeek]="true" > - - - +
+
{{ pollService.calendar | json }}
+
diff --git a/src/styles/partials/_forms.scss b/src/styles/partials/_forms.scss index 977bd1b5..48f18b5e 100644 --- a/src/styles/partials/_forms.scss +++ b/src/styles/partials/_forms.scss @@ -235,3 +235,37 @@ mat-checkbox { width: 100%; padding: 1em; } + +// calendar primeng +.p-datepicker { + border: solix 1px $logo_color; + margin: 1em auto; + + .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%; + } +} diff --git a/src/styles/themes/_base.scss b/src/styles/themes/_base.scss index 5e2a778e..4d7250bc 100644 --- a/src/styles/themes/_base.scss +++ b/src/styles/themes/_base.scss @@ -2,7 +2,6 @@ background: $primary; main { - padding: 0; margin-bottom: 2em; padding-bottom: 5em; padding-top: 1em; From a438bdff6cf54673adc44015e83e564eeeb2d8c8 Mon Sep 17 00:00:00 2001 From: Tykayn Date: Mon, 8 Nov 2021 10:26:16 +0100 Subject: [PATCH 05/36] translate calendar elements from app init --- src/app/app.component.ts | 7 + src/app/core/services/language.service.ts | 4 + .../step-three/step-three.component.html | 5 + src/assets/i18n/FR.json | 125 ++++++++++++------ src/styles/partials/_forms.scss | 6 + 5 files changed, 109 insertions(+), 38 deletions(-) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index d9eb0ee9..8e3001a4 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -13,6 +13,8 @@ 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'; @Component({ selector: 'app-root', @@ -37,6 +39,7 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewInit { private titleService: Title, private themeService: ThemeService, private pollService: PollService, + private config: PrimeNGConfig, private languageService: LanguageService // private mockingService: MockingService ) {} @@ -51,6 +54,10 @@ 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) => { if (!(evt instanceof NavigationEnd)) { 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/features/administration/form/steps/step-three/step-three.component.html b/src/app/features/administration/form/steps/step-three/step-three.component.html index 96e390f5..317527a2 100644 --- 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 @@ -74,12 +74,17 @@ selectionMode="multiple" inputId="multiple" showButtonBar="true" + [locale]="'calendar_widget' | translate" [inline]="true" [showWeek]="true" >
+
+ +
{{ pollService.calendar | json }}
+
{{ 'calendar_widget' | translate }}
diff --git a/src/assets/i18n/FR.json b/src/assets/i18n/FR.json index 70e1096c..385eef74 100644 --- a/src/assets/i18n/FR.json +++ b/src/assets/i18n/FR.json @@ -144,7 +144,7 @@ "selectors": { "lang": "Sélectionner la langue" }, - "validation" : { + "validation": { "You must enter a value": "You must enter a EEEE" }, "You must enter a value": "You must enter a valueeeeeeee", @@ -576,8 +576,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 +592,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": "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" + } } diff --git a/src/styles/partials/_forms.scss b/src/styles/partials/_forms.scss index 48f18b5e..db1b021a 100644 --- a/src/styles/partials/_forms.scss +++ b/src/styles/partials/_forms.scss @@ -241,6 +241,12 @@ mat-checkbox { border: solix 1px $logo_color; margin: 1em auto; + .pi-chevron-left:after { + content: '<'; + } + .pi-chevron-right:after { + content: '>'; + } .p-datepicker-month { margin-right: 1em; } From 77cc54f2fc969d979a4452555e67c60d164ae82c Mon Sep 17 00:00:00 2001 From: Tykayn Date: Mon, 8 Nov 2021 10:34:59 +0100 Subject: [PATCH 06/36] remove some debug info --- .../steps/step-three/step-three.component.html | 14 +++++++------- src/styles/libraries/_overrides.scss | 2 +- src/styles/partials/_logo.scss | 2 +- src/styles/partials/global.scss | 6 +++++- 4 files changed, 14 insertions(+), 10 deletions(-) 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 index 317527a2..275d060b 100644 --- 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 @@ -79,13 +79,13 @@ [showWeek]="true" > -
- -
-
-
{{ pollService.calendar | json }}
-
{{ 'calendar_widget' | translate }}
-
+ + + + + + +
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/_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/global.scss b/src/styles/partials/global.scss index 568e45b7..e1382968 100644 --- a/src/styles/partials/global.scss +++ b/src/styles/partials/global.scss @@ -7,7 +7,11 @@ html { main { min-height: 90vh; } - +.min-height { + margin-top: 1em; + margin-bottom: 1em; + min-height: 50vh; +} .content { padding: 1em; } From f4be6ed39db709b369a1d8bf01ef668bcf138b27 Mon Sep 17 00:00:00 2001 From: Tykayn Date: Mon, 8 Nov 2021 10:56:03 +0100 Subject: [PATCH 07/36] add text in footer --- .../components/footer/footer.component.html | 28 +++++++++++++++++++ .../components/footer/footer.component.ts | 4 +++ src/app/core/services/poll.service.ts | 11 ++++++++ .../steps/step-four/step-four.component.html | 4 +-- .../steps/step-four/step-four.component.ts | 2 +- 5 files changed, 46 insertions(+), 3 deletions(-) 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/services/poll.service.ts b/src/app/core/services/poll.service.ts index faa691e0..333dcca7 100644 --- a/src/app/core/services/poll.service.ts +++ b/src/app/core/services/poll.service.ts @@ -491,6 +491,17 @@ export class PollService implements Resolve { return list; } + getParticipationUrl() { + let poll = this._poll.getValue(); + + // http://localhost:4200/#/poll/dessin-anime/consultation + + // TODO handle secure access + // http://localhost:4200/#/poll/citron/consultation/secure/1c01ed9c94fc640a1be864f197ff808c + + return window.location.host + '/#/poll/' + poll.custom_url + '/consultation'; + } + convertCalendarDatesToChoices(array_dates) { return array_dates; } 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 index 849c5996..89bd173a 100644 --- 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 @@ -53,13 +53,13 @@ - +
Nombre de jours avant expiration 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 index 423e67b4..0d29cf10 100644 --- 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 @@ -8,7 +8,7 @@ import { PollService } from '../../../../../core/services/poll.service'; }) export class StepFourComponent implements OnInit { urlPrefix: any; - advancedDisplayEnabled: any; + advancedDisplayEnabled: boolean = true; @Input() step_max: any; @Input() From 912e8af9905c3e8688e25f6b014fd2a482540c2d Mon Sep 17 00:00:00 2001 From: tykayn <15d65f2f-0b14-4f70-bf34-e130180ed62b@users.tedomum.net> Date: Mon, 8 Nov 2021 11:20:07 +0100 Subject: [PATCH 08/36] autofocus on first field of creation Signed-off-by: tykayn <15d65f2f-0b14-4f70-bf34-e130180ed62b@users.tedomum.net> --- .../form/steps/step-one/step-one.component.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) 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 index f013b61b..0107efb2 100644 --- 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 @@ -1,6 +1,7 @@ -import { Component, Input, OnInit } from '@angular/core'; +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', @@ -8,12 +9,18 @@ import { PollService } from '../../../../../core/services/poll.service'; styleUrls: ['./step-one.component.scss'], }) export class StepOneComponent implements OnInit { - constructor(public pollService: PollService) {} + constructor(public pollService: PollService, @Inject(DOCUMENT) private document: any) {} @Input() step_max: any; @Input() form: FormGroup; - ngOnInit(): void {} + ngOnInit(): void { + const selector = '#title'; + const firstField = this.document.querySelector(selector); + if (firstField) { + firstField.focus(); + } + } } From e1a28a7c8175153454729f9a8eaa91fda0efeac1 Mon Sep 17 00:00:00 2001 From: Tykayn Date: Mon, 8 Nov 2021 11:27:35 +0100 Subject: [PATCH 09/36] retouch homepage, add title html --- .../core/components/home/home.component.html | 33 +++++++++++++++--- .../core/components/home/home.component.scss | 7 ++++ .../core/components/home/home.component.ts | 6 +++- src/assets/i18n/FR.json | 3 +- src/assets/img/icone_home.png | Bin 0 -> 41873 bytes src/assets/img/where-is-it.jpg | Bin 0 -> 91423 bytes src/styles/partials/_main.scss | 1 - src/styles/themes/_base.scss | 1 - 8 files changed, 42 insertions(+), 9 deletions(-) create mode 100644 src/assets/img/icone_home.png create mode 100644 src/assets/img/where-is-it.jpg 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/assets/i18n/FR.json b/src/assets/i18n/FR.json index 385eef74..77a8fbaf 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" }, diff --git a/src/assets/img/icone_home.png b/src/assets/img/icone_home.png new file mode 100644 index 0000000000000000000000000000000000000000..9447bc94819904cc0d22c9fdb6b6d183d1631b47 GIT binary patch literal 41873 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

    Sh|_s?ROCca9UJG+9h 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%0D20GtHu*KNMHWKkS4XhUd;$4{Jf02M0C>F^k@ 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 A7ytkO literal 0 HcmV?d00001 diff --git a/src/styles/partials/_main.scss b/src/styles/partials/_main.scss index b598ef39..459c2cfd 100644 --- a/src/styles/partials/_main.scss +++ b/src/styles/partials/_main.scss @@ -4,7 +4,6 @@ main { display: block; width: 100%; margin: 0 auto; - padding: 0 2rem; } .main-block { min-height: 20em; diff --git a/src/styles/themes/_base.scss b/src/styles/themes/_base.scss index 4d7250bc..6a9d867e 100644 --- a/src/styles/themes/_base.scss +++ b/src/styles/themes/_base.scss @@ -4,7 +4,6 @@ main { margin-bottom: 2em; padding-bottom: 5em; - padding-top: 1em; background: $white; } .big-header { From afdaf04a8f5e10511dbe92a72cfe9d8b59b11208 Mon Sep 17 00:00:00 2001 From: Tykayn Date: Mon, 8 Nov 2021 12:09:35 +0100 Subject: [PATCH 10/36] take back things for pollService, display success screen and advanced options --- src/app/core/services/poll.service.ts | 270 +++++++++++------- .../advanced-config.component.html | 7 +- .../advanced-config.component.scss | 5 + .../steps/step-five/step-five.component.html | 70 +---- .../steps/step-five/step-five.component.ts | 7 +- .../steps/step-four/step-four.component.html | 87 +----- .../steps/step-four/step-four.component.ts | 8 +- .../success/success.component.html | 14 +- .../success/success.component.ts | 5 +- 9 files changed, 215 insertions(+), 258 deletions(-) diff --git a/src/app/core/services/poll.service.ts b/src/app/core/services/poll.service.ts index 333dcca7..200800ef 100644 --- a/src/app/core/services/poll.service.ts +++ b/src/app/core/services/poll.service.ts @@ -11,6 +11,8 @@ import { UserService } from './user.service'; import { UuidService } from './uuid.service'; 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'; @@ -18,6 +20,7 @@ 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'; @Injectable({ providedIn: 'root', @@ -50,9 +53,11 @@ export class PollService implements Resolve { private http: HttpClient, private router: Router, private apiService: ApiService, + private storageService: StorageService, private userService: UserService, private uuidService: UuidService, private toastService: ToastService, + private titleService: Title, public DateUtilitiesService: DateUtilitiesService, public route: ActivatedRoute, @Inject(DOCUMENT) private document: any, @@ -89,9 +94,6 @@ export class PollService implements Resolve { whoModifiesAnswers: 'everybody', whoCanChangeAnswers: 'everybody', isProtectedByPassword: false, - isOwnerNotifiedByEmailOnNewVote: false, - isOwnerNotifiedByEmailOnNewComment: false, - isMaybeAnswerAvailable: false, richTextMode: false, areResultsPublic: true, expiracyNumberOfDays: 60, @@ -99,12 +101,6 @@ export class PollService implements Resolve { this.automaticSlug(); } - public enrichVoteStackWithCurrentPollChoicesDefaultVotes(vote_stack: Stack) { - console.log('vote_stack', vote_stack); - this.toastService.display('TODO refill vote stack'); - // this.form.patchValue(vote_stack) - } - /** * set the poll slug from other data of the poll */ @@ -120,19 +116,29 @@ export class PollService implements Resolve { creatorEmail: ['', [Validators.required]], custom_url: [this.uuidService.getUUID(), [Validators.required]], description: ['', [Validators.required]], + password: ['', [Validators.required]], choices: new FormArray([]), whoModifiesAnswers: ['', [Validators.required]], whoCanChangeAnswers: ['', [Validators.required]], isAboutDate: [true, [Validators.required]], startDateInterval: ['', [Validators.required]], endDateInterval: ['', [Validators.required]], + expiresDaysDelay: ['', [Validators.required]], + maxCountOfAnswers: ['', [Validators.required]], + isZeroKnoledge: [false, [Validators.required]], isProtectedByPassword: [false, [Validators.required]], - isOwnerNotifiedByEmailOnNewVote: [false, [Validators.required]], - isOwnerNotifiedByEmailOnNewComment: [false, [Validators.required]], - isMaybeAnswerAvailable: [false, [Validators.required]], + isOwnerNotifiedByEmailOnNewVote: [true, [Validators.required]], + isOwnerNotifiedByEmailOnNewComment: [true, [Validators.required]], areResultsPublic: [true, [Validators.required]], richTextMode: [false, [Validators.required]], - expiracyNumberOfDays: [60, [Validators.required, Validators.min(0)]], + 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: ['', [Validators.required]], + allowNewDateTime: [60, [Validators.required, Validators.min(0)]], }); this.form = form; return form; @@ -149,30 +155,36 @@ export class PollService implements Resolve { * @param state */ public async resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { + console.log('resolve route,state', route, state); const segments: string[] = state.url.split('/'); - const wantedSlug: string = segments.includes('poll') ? segments[segments.indexOf('poll') + 1] : ''; - if (!wantedSlug && state.url.includes('administration')) { - // creation of new poll - const poll = new Poll(this.userService.getCurrentUser(), this.uuidService.getUUID(), ''); - this._poll.next(poll); - this.router.navigate(['poll/' + poll.custom_url + '/administration']); - } + const wantedcustom_url: string = segments.includes('poll') ? segments[segments.indexOf('poll') + 1] : ''; + if ( !this._poll.getValue() || !this._poll.getValue().custom_url || - this._poll.getValue().custom_url !== wantedSlug + this._poll.getValue().custom_url !== wantedcustom_url ) { - await this.loadPollByCustomUrl(wantedSlug); + if (this.pass_hash) { + this.storageService.vote_stack.pass_hash = this.pass_hash; + await this.loadPollBycustom_urlWithPasswordHash(wantedcustom_url, this.pass_hash); + } else { + await this.loadPollBycustom_url(wantedcustom_url); + } } - if (this._poll.getValue()) { - return this._poll.getValue(); + const loadedPoll = this._poll.getValue(); + if (loadedPoll) { + this.storageService.vote_stack.poll_custom_url = loadedPoll.custom_url; + return loadedPoll; } else { this.router.navigate(['page-not-found']); return; } } - getAllAvailablePolls() { + /** + * get all polls + */ + getAllAvailablePolls(): void { const baseHref = environment.api.version.apiV1.baseHref; console.log('getAllAvailablePolls baseHref', baseHref); const headers = ApiService.makeHeaders(); @@ -186,22 +198,56 @@ export class PollService implements Resolve { } } - public async loadPollByCustomUrl(slug: string): Promise { - if (slug) { - const poll: Poll | undefined = await this.apiService.getPollByCustomUrl(slug); - console.log({ loadPollBySlugResponse: poll }); - this.updateCurrentPoll(poll); - } - } - public async loadPollByCustomUrlWithPasswordHash(slug: string, pass_hash: string): Promise { - if (slug) { - const poll: Poll | undefined = await this.apiService.getPollByCustomUrlWithHash(slug, pass_hash); - console.log({ loadPollBySlugResponse: poll }); - this.updateCurrentPoll(poll); + public async loadPollBycustom_url(custom_url: string): Promise { + if (custom_url) { + const poll: Poll | undefined = await this.apiService.getPollByCustomUrl(custom_url); + + if (poll) { + this.updateCurrentPoll(poll); + this.titleService.setTitle(`☑️ ${poll.title} - ${environment.appTitle}`); + } else { + this.toastService.display(`sondage ${custom_url} non trouvé`); + this.router.navigate(['page-not-found']); + } + } else { + this.toastService.display(`sondage sans custom url : ${custom_url}`); } } + public async loadPollBycustom_urlWithPasswordHash(custom_url: string, hash: string): Promise { + if (custom_url) { + const poll: Poll | undefined = await this.apiService.getPollByCustomUrlWithHash(custom_url, hash); + + if (poll) { + this.updateCurrentPoll(poll); + this.titleService.setTitle(`☑️ ${poll.title} - ${environment.appTitle}`); + } else { + this.toastService.display(`sondage ${custom_url} non trouvé`); + this.router.navigate(['page-not-found']); + } + } else { + this.toastService.display(`sondage sans custom url : ${custom_url}`); + } + } + + /** + * update poll and parse its fields + * @param poll + */ public updateCurrentPoll(poll: Poll): void { + 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); + } + + this.toastService.display('sondage bien mis à jour', 'success'); this._poll.next(poll); } @@ -491,77 +537,107 @@ export class PollService implements Resolve { return list; } - getParticipationUrl() { - let poll = this._poll.getValue(); - + public getParticipationUrl(): string { // http://localhost:4200/#/poll/dessin-anime/consultation // TODO handle secure access // http://localhost:4200/#/poll/citron/consultation/secure/1c01ed9c94fc640a1be864f197ff808c - return window.location.host + '/#/poll/' + poll.custom_url + '/consultation'; + let url = ''; + if (this._poll && this._poll.getValue) { + const polltemp = this._poll.getValue(); + if (polltemp) { + url = `${environment.frontDomain}#/poll/${polltemp.custom_url}/consultation`; + } + } + // TODO handle pass access + return url; + } + 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; } + /** + * enrich vote stack with missing default votes + * @param vote_stack + */ + enrichVoteStackWithCurrentPollChoicesDefaultVotes(vote_stack: Stack) { + if (this._poll && this._poll.getValue) { + const polltemp = this._poll.getValue(); + polltemp.choices.map((choice) => { + // for each vote, if it has the choice_id, do nothing, else, add a default vote + if (!this.findExistingVoteFromChoiceId(choice.id, vote_stack.votes)) { + vote_stack.votes.push(new Vote(choice.id)); + } + }); + } + } + /** + * find an existing vote in vote_stack from its choice_id + * @param choice_id + * @param votes + */ + findExistingVoteFromChoiceId(choice_id: number, votes: Vote[]) { + return votes.find((vote: Vote) => { + if (vote.choice_id === choice_id) { + return vote; + } + }); + } convertCalendarDatesToChoices(array_dates) { return array_dates; } - newPollFromForm(form: any): any { - const newpoll = new Poll( - this.userService.getCurrentUser(), - this.uuidService.getUUID(), - form.controls.title.value - ); - /** - * convert to API version 1 config poll - */ - const apiV1Poll = { - menuVisible: true, - expiracyDateDefaultInDays: newpoll.default_expiracy_days_from_now, - deletionDateAfterLastModification: newpoll.default_expiracy_days_from_now, - pollType: newpoll.kind ? 'date' : 'classic', // classic or dates - title: newpoll.title, - description: newpoll.description, - myName: newpoll.owner.pseudo, - myComment: '', - isAdmin: true, // when we create a poll, we are admin on it - myVoteStack: {}, - myTempVoteStack: 0, - myEmail: newpoll.owner.email, - myPolls: [], // list of retrieved polls from the backend api - /* - date specific poll, we have the choice to setup different hours (timeList) for all possible dates (dateList), or use the same hours for all dates - */ - allowSeveralHours: 'true', - // access - visibility: newpoll.areResultsPublic, // visible to one with the link: - voteChoices: newpoll.isMaybeAnswerAvailable ? 'yes, maybe, no' : 'yes', // possible answers to a vote choice: only "yes", "yes, maybe, no" - created_at: new Date(), - expirationDate: '', // expiracy date - voteStackId: null, // id of the vote stack to update - pollId: null, // id of the current poll when created. data given by the backend api - pollSlug: null, // id of the current poll when created. data given by the backend api - currentPoll: null, // current poll selected with createPoll or getPoll of ConfigService - passwordAccess: false, - password: newpoll.password, - customUrl: newpoll.custom_url, // custom slug in the url, must be unique - customUrlIsUnique: null, // given by the backend - urlSlugPublic: null, - urlPublic: null, - urlAdmin: null, - adminKey: '', // key to change config of the poll - owner_modifier_token: '', // key to change a vote stack - canModifyAnswers: newpoll.modification_policy, // bool for the frontend selector - whoModifiesAnswers: newpoll.modification_policy, // everybody, self, nobody (: just admin) - whoCanChangeAnswers: newpoll.modification_policy, // everybody, self, nobody (: just admin) - dateList: newpoll.dateChoices, // sets of days as strings, config to set identical time for days in a special days poll - timeList: newpoll.timeSlices, // ranges of time expressed as strings + /** + * @description convert to API version 1 data transition object + * @param form + */ + newPollFromForm(form: any): Poll { + const newOwner = this.storageService.vote_stack.owner; - answers: newpoll.choices, - // modals - displayConfirmVoteModalAdmin: false, - }; - console.log('apiV1Poll', apiV1Poll); - return apiV1Poll; + 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 = []; + + 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.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; } } 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..d00c48af 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 @@ -103,20 +103,19 @@ Réponses proposées - - - - La réponse « oui » sera disponible +
    La réponse « peut-être » sera disponible +
    La réponse « non » sera disponible +

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..eb0e5380 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,8 @@ .title { margin-top: 2em; } +.mat-checkbox { + img { + margin-left: 1em; + } +} 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 index 8d476bc0..d6309d29 100644 --- 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 @@ -1,76 +1,10 @@ -
-

- Félicitations, votre sondage " {{ pollService.form.value.title }} " est créé -

- Un récapitulatif par email vous a été envoyé. Partagez-le au monde avec ce lien: Administrez-le avec cet autre lien: -
+ -

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

-
-

{{ 'resume.admins' | translate }}

-

- Votre sondage « - - {{ pollService.form.value.title }} - - » a bien été créé ! -

-

- 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.form.value.urlAdmin }} -

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

- Note : Le sondage sera supprimé {{ pollService.form.value.deletionDateAfterLastModification }} jours après - la date de sa dernière modification. -

-
-
-

{{ 'resume.users' | translate }}

-

- Pour accéder au sondage : - {{ pollService.urlPrefix + '/#/poll/' + pollService.form.value.custom_url + '/consultation' }} - -

- - - Voir le sondage - -
-
-

{{ 'resume.links_mail' | translate }}

- - Voir le sondage - -
-
-
- -
+
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 index 04f429d2..cfd6c4fb 100644 --- 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 @@ -12,7 +12,7 @@ export class StepFiveComponent implements OnInit { @Input() step_max: any; @Input() public form: FormGroup; poll: any; - constructor(public pollService: PollService, private apiService: ApiService) {} + constructor(public pollService: PollService) {} ngOnInit(): void {} askInitFormDefault() { @@ -21,10 +21,5 @@ export class StepFiveComponent implements OnInit { } } - createPoll() { - let apiData = this.pollService.newPollFromForm(this.pollService._poll); - this.apiService.createPoll(apiData); - } - 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 index 89bd173a..bf501989 100644 --- 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 @@ -1,7 +1,7 @@
- +
-
-

{{ 'creation.advanced' | translate }}

- -
- - -
- {{ urlPrefix }} {{ pollService.form.controls.custom_url.value }} - - - - - -
-
- 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 - +
@@ -119,8 +55,13 @@
-
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 index 0d29cf10..ad497bd0 100644 --- 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 @@ -1,5 +1,6 @@ import { Component, Input, OnInit } from '@angular/core'; import { PollService } from '../../../../../core/services/poll.service'; +import { ApiService } from '../../../../../core/services/api.service'; @Component({ selector: 'app-step-four', @@ -13,7 +14,12 @@ export class StepFourComponent implements OnInit { step_max: any; @Input() form: any; - constructor(public pollService: PollService) {} + constructor(public pollService: PollService, private apiService: ApiService) {} ngOnInit(): void {} + + createPoll() { + let apiData = this.pollService.newPollFromForm(this.pollService._poll); + this.apiService.createPoll(apiData); + } } diff --git a/src/app/features/administration/success/success.component.html b/src/app/features/administration/success/success.component.html index 3b37584b..2876aaee 100644 --- a/src/app/features/administration/success/success.component.html +++ b/src/app/features/administration/success/success.component.html @@ -6,7 +6,7 @@

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

@@ -31,7 +31,7 @@

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

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

@@ -54,7 +54,7 @@

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

@@ -80,7 +80,7 @@

- + Voir le sondage côté public

diff --git a/src/app/features/administration/success/success.component.ts b/src/app/features/administration/success/success.component.ts index 8fc9a4bb..41af1f86 100644 --- a/src/app/features/administration/success/success.component.ts +++ b/src/app/features/administration/success/success.component.ts @@ -16,11 +16,12 @@ export class SuccessComponent { window: any = window; environment = environment; 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); }); } From c2c193bfa92129972335d5ee36507c7b0ed43aca Mon Sep 17 00:00:00 2001 From: Tykayn Date: Mon, 8 Nov 2021 12:36:18 +0100 Subject: [PATCH 11/36] success page displays links from form data --- src/app/core/services/poll.service.ts | 27 ++++++++++++++-- .../steps/step-five/step-five.component.html | 17 ++-------- .../success/success.component.html | 32 ++++--------------- .../success/success.component.scss | 5 ++- 4 files changed, 37 insertions(+), 44 deletions(-) diff --git a/src/app/core/services/poll.service.ts b/src/app/core/services/poll.service.ts index 200800ef..d378cc74 100644 --- a/src/app/core/services/poll.service.ts +++ b/src/app/core/services/poll.service.ts @@ -356,8 +356,11 @@ export class PollService implements Resolve { public createPoll(): void { console.log('this.form', this.form); const newpoll = this.newPollFromForm(this.form); - console.log('newpoll', newpoll); - this.apiService.createPoll(newpoll); + this.apiService.createPoll(newpoll).then((resp) => { + console.log('poll created resp', resp); + console.log('TODO fill admin_key'); + this.admin_key = resp.data.admin_key; + }); } /** @@ -537,6 +540,13 @@ export class PollService implements Resolve { return list; } + public getParticipationUrlFromForm(): string { + return `${environment.frontDomain}#/poll/${this.form.value.custom_url}/consultation`; + } + public getAdministrationUrlFromForm(): string { + // admin_key is filled after creation + return `${environment.frontDomain}#/admin/${this.admin_key}/consultation`; + } public getParticipationUrl(): string { // http://localhost:4200/#/poll/dessin-anime/consultation @@ -549,17 +559,24 @@ export class PollService implements Resolve { 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}#/poll/admin/${polltemp.admin_key}`; + url = `${environment.frontDomain}#/admin/${polltemp.admin_key}`; } + } else { + url = `${environment.frontDomain}#/admin/${this.form.value.admin_key}`; } return url; } @@ -635,9 +652,13 @@ export class PollService implements Resolve { newpoll.kind = form.value.kind; newpoll.allow_comments = form.value.allowComments; // merge choices from storage + if (form.value.kind === 'date') { + // convert calendar picker dates + } 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/features/administration/form/steps/step-five/step-five.component.html b/src/app/features/administration/form/steps/step-five/step-five.component.html index d6309d29..61adef2a 100644 --- 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 @@ -9,30 +9,19 @@
- -
-
-
- image WIP - -
{{ pollService.form.value.custom_url }}
diff --git a/src/app/features/administration/success/success.component.html b/src/app/features/administration/success/success.component.html index 2876aaee..56ccb673 100644 --- a/src/app/features/administration/success/success.component.html +++ b/src/app/features/administration/success/success.component.html @@ -32,12 +32,12 @@ Pour accéder au sondage et à tous ses paramètres :
{{ pollService.getAdministrationUrl() }} + >{{ pollService.getAdministrationUrlFromForm() }} - +


- + Voir le sondage coté administrateur·ice
@@ -54,36 +54,16 @@

Pour voir le sondage :
- {{ pollService.getParticipationUrl() }} + {{ 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..4b6791ed 100644 --- a/src/app/features/administration/success/success.component.scss +++ b/src/app/features/administration/success/success.component.scss @@ -5,7 +5,7 @@ button { margin-right: 1ch; } a { - max-width: 20em; + max-width: 35em; @extend .truncate; } .truncate { @@ -13,3 +13,6 @@ a { overflow: hidden; text-overflow: ellipsis; } +:host { + padding: 2em; +} From 19127328d2b92c7f8a7d032679ba5f07bb3cbcf4 Mon Sep 17 00:00:00 2001 From: Tykayn Date: Mon, 8 Nov 2021 17:24:28 +0100 Subject: [PATCH 12/36] bg success --- .../features/administration/success/success.component.html | 2 +- .../features/administration/success/success.component.scss | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/app/features/administration/success/success.component.html b/src/app/features/administration/success/success.component.html index 56ccb673..a3dfffed 100644 --- a/src/app/features/administration/success/success.component.html +++ b/src/app/features/administration/success/success.component.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/app/features/administration/success/success.component.scss b/src/app/features/administration/success/success.component.scss index 4b6791ed..1c432a74 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 { @@ -16,3 +18,6 @@ a { :host { padding: 2em; } +.has-background-success { + background: $logo_color_2; +} From 2f1309bf6fdce59933a87e61d816ab42e8bb2600 Mon Sep 17 00:00:00 2001 From: Tykayn Date: Mon, 8 Nov 2021 18:25:04 +0100 Subject: [PATCH 13/36] :bug: fix compile with rename pollservice functions --- src/app/core/services/poll.service.ts | 10 +++++----- .../form/steps/step-one/step-one.component.html | 2 +- .../poll-results-detailed.component.ts | 12 ++++++------ 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/app/core/services/poll.service.ts b/src/app/core/services/poll.service.ts index d378cc74..de4173b8 100644 --- a/src/app/core/services/poll.service.ts +++ b/src/app/core/services/poll.service.ts @@ -166,9 +166,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(); @@ -198,7 +198,7 @@ export class PollService implements Resolve { } } - 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); @@ -214,7 +214,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); @@ -359,7 +359,7 @@ export class PollService implements Resolve { this.apiService.createPoll(newpoll).then((resp) => { console.log('poll created resp', resp); console.log('TODO fill admin_key'); - this.admin_key = resp.data.admin_key; + // this.admin_key = resp.data.admin_key; }); } 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 index c88d7ea2..550913b3 100644 --- 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 @@ -7,7 +7,7 @@
- + Date: Fri, 12 Nov 2021 11:09:43 +0100 Subject: [PATCH 14/36] fix demo build --- .../administration/form/form.component.ts | 2 +- .../form/steps/step-four/step-four.component.html | 9 +++------ .../steps/step-three/step-three.component.html | 15 ++++----------- .../form/errors-list/errors-list.component.scss | 3 +++ 4 files changed, 11 insertions(+), 18 deletions(-) diff --git a/src/app/features/administration/form/form.component.ts b/src/app/features/administration/form/form.component.ts index 7bb6a64f..beb2298e 100644 --- a/src/app/features/administration/form/form.component.ts +++ b/src/app/features/administration/form/form.component.ts @@ -23,7 +23,7 @@ export class FormComponent implements OnInit { private cd: ChangeDetectorRef, private uuidService: UuidService, private toastService: ToastService, - private pollService: PollService, + public pollService: PollService, private router: Router, public route: ActivatedRoute, private apiService: ApiService, 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 index bf501989..f6ed9b70 100644 --- 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 @@ -54,12 +54,9 @@
- - 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 index 275d060b..72754252 100644 --- 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 @@ -15,7 +15,7 @@
-
+
- - - - - - -
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; +} From 3dc75557474f614cc09b5bfbe20714fd45d6800c Mon Sep 17 00:00:00 2001 From: Tykayn Date: Fri, 12 Nov 2021 12:09:48 +0100 Subject: [PATCH 15/36] autofill creation var in env --- src/app/core/services/poll.service.ts | 5 +-- src/app/core/services/storage.service.ts | 4 +-- .../steps/step-four/step-four.component.html | 7 +++-- .../steps/step-one/step-one.component.html | 31 ++++++------------- src/environments/environment.prod.ts | 3 +- src/environments/environment.ts | 7 +++-- src/styles/partials/_forms.scss | 14 +++++++++ 7 files changed, 39 insertions(+), 32 deletions(-) diff --git a/src/app/core/services/poll.service.ts b/src/app/core/services/poll.service.ts index de4173b8..e9ab0e1e 100644 --- a/src/app/core/services/poll.service.ts +++ b/src/app/core/services/poll.service.ts @@ -64,8 +64,9 @@ export class PollService implements Resolve { private fb: FormBuilder ) { this.createFormGroup(); - if (environment.autofill) { + if (environment.autofill_creation) { this.setDemoValues(); + this.toastService.display('auto fill de création fait'); } else { this.calendar = [new Date()]; } @@ -381,7 +382,7 @@ export class PollService implements Resolve { } askInitFormDefault(): void { - this.initFormDefault(false); + this.initFormDefault(environment.autofill_creation); this.toastService.display('formulaire réinitialisé'); } diff --git a/src/app/core/services/storage.service.ts b/src/app/core/services/storage.service.ts index c173c8f0..d19c83db 100644 --- a/src/app/core/services/storage.service.ts +++ b/src/app/core/services/storage.service.ts @@ -46,7 +46,7 @@ export class StorageService { public choices: Choice[] = []; constructor(public dateUtilities: DateUtilitiesService, private toastService: ToastService) { - if (environment.autofill) { + if (environment.autofill_participation) { this.toastService.display('autofill des sondages utilisateur'); 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')); @@ -71,7 +71,7 @@ export class StorageService { this.vote_stack = new Stack(); for (const choice of choices_list) { - if (environment.autofill) { + 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' : ''; 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 index f6ed9b70..a6f649f3 100644 --- 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 @@ -54,12 +54,15 @@
- - +
+
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 index 550913b3..a6eb4c74 100644 --- 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 @@ -7,44 +7,32 @@
- +
+ +
- - -
- -
-
- + + - -
+ +
richTextMode activé
@@ -77,7 +65,6 @@
- slug: {{ pollService.form.value.custom_url }}
diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index 3f69bd0f..06cc98cd 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -16,7 +16,8 @@ export const environment = { production: true, display_routes: true, showDemoWarning: true, - autofill: false, + autofill_creation: true, + autofill_participation: true, autoSendNewPoll: false, interval_days_default: 7, expiresDaysDelay: 60, diff --git a/src/environments/environment.ts b/src/environments/environment.ts index a8e3fe2a..c52cbea7 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -10,10 +10,11 @@ endpoints.baseHref = apiV1.baseHref; export const environment = { frontDomain: 'http://127.0.0.1:4200', production: false, - display_routes: true, - autofill: true, + display_routes: false, // demo paths to test polls + autofill_creation: true, + autofill_participation: true, // autofill: false, - showDemoWarning: true, + showDemoWarning: false, autoSendNewPoll: false, interval_days_default: 7, expiresDaysDelay: 60, diff --git a/src/styles/partials/_forms.scss b/src/styles/partials/_forms.scss index db1b021a..291955de 100644 --- a/src/styles/partials/_forms.scss +++ b/src/styles/partials/_forms.scss @@ -1,5 +1,14 @@ @charset "UTF-8"; +app-step-one, +app-step-two, +app-step-three, +app-step-four, +app-step-five { + padding: 2em 1em; + display: block; +} + input, select, textarea { @@ -244,15 +253,19 @@ mat-checkbox { .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; @@ -262,6 +275,7 @@ mat-checkbox { padding: 1em; width: 3.5em; transition: all ease 0.5s; + &:hover { background: mix($white, $legend_color); color: $white; From cb971a5421e34ec98f43e859d1a48434b7fa4099 Mon Sep 17 00:00:00 2001 From: Tykayn Date: Fri, 12 Nov 2021 12:50:21 +0100 Subject: [PATCH 16/36] convert calendar dates to DTO creation --- src/app/core/models/owner.model.ts | 6 +- .../core/services/date.utilities.service.ts | 12 --- src/app/core/services/poll.service.ts | 73 +++++++++++++++---- .../steps/step-four/step-four.component.ts | 3 +- src/environments/environment.prod.ts | 1 + src/environments/environment.ts | 3 +- 6 files changed, 66 insertions(+), 32 deletions(-) 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/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/poll.service.ts b/src/app/core/services/poll.service.ts index e9ab0e1e..71fa9219 100644 --- a/src/app/core/services/poll.service.ts +++ b/src/app/core/services/poll.service.ts @@ -47,7 +47,7 @@ export class PollService implements Resolve { public showDateInterval = false; public allowSeveralHours = false; public richTextMode = false; - public calendar: any; + public calendar: any = [new Date()]; constructor( private http: HttpClient, @@ -64,11 +64,18 @@ export class PollService implements Resolve { private fb: FormBuilder ) { this.createFormGroup(); + + this.calendar = [ + this.DateUtilitiesService.addDaysToDate(1, new Date()), + this.DateUtilitiesService.addDaysToDate(2, new Date()), + this.DateUtilitiesService.addDaysToDate(3, new Date()), + ]; if (environment.autofill_creation) { this.setDemoValues(); this.toastService.display('auto fill de création fait'); - } else { - this.calendar = [new Date()]; + } + if (environment.autoSendNewPoll) { + this.createPoll(); } } @@ -80,11 +87,6 @@ export class PollService implements Resolve { this.addChoice('raisin'); this.addChoice('abricot'); - this.calendar = [ - this.DateUtilitiesService.addDaysToDate(1, new Date()), - this.DateUtilitiesService.addDaysToDate(2, new Date()), - this.DateUtilitiesService.addDaysToDate(3, new Date()), - ]; this.form.patchValue({ title: 'mon titre', description: 'répondez SVP <3 ! *-* ', @@ -145,6 +147,26 @@ export class PollService implements Resolve { return 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, + expiracyNumberOfDays: 60, + maxCountOfAnswers: 300, + voterEmailList: '', + password: '', + }); + } + public updateSlug(): void { console.log('this.form.value', this.form.value); this.form.patchValue({ custom_url: this.makeSlug(this.form) }); @@ -355,8 +377,9 @@ export class PollService implements Resolve { } public createPoll(): void { + this.toastService.display('sending...'); console.log('this.form', this.form); - const newpoll = this.newPollFromForm(this.form); + const newpoll = this.newPollFromForm(); this.apiService.createPoll(newpoll).then((resp) => { console.log('poll created resp', resp); console.log('TODO fill admin_key'); @@ -409,6 +432,7 @@ export class PollService implements Resolve { initFormDefault(showDemoValues = true): void { this.form = this.createFormGroup(); + this.patchFormDefaultValues(); this.setDefaultDatesForInterval(); if (showDemoValues) { @@ -544,10 +568,12 @@ export class PollService implements Resolve { public getParticipationUrlFromForm(): string { return `${environment.frontDomain}#/poll/${this.form.value.custom_url}/consultation`; } + public getAdministrationUrlFromForm(): string { // admin_key is filled after creation return `${environment.frontDomain}#/admin/${this.admin_key}/consultation`; } + public getParticipationUrl(): string { // http://localhost:4200/#/poll/dessin-anime/consultation @@ -567,6 +593,7 @@ export class PollService implements Resolve { // TODO handle pass access return url; } + public getAdministrationUrl(): string { // http://localhost:4200/#/admin/9S75b70ECXI5J5xDc058d3H40H9r2CHfO0Kj8T02EK2U8rY8fYTn-eS659j2Dhp794Oa6R1b9V70e3WGaE30iD9h45zwdm76C85SWB4LcUCrc7e0Ncc0 @@ -597,6 +624,7 @@ export class PollService implements Resolve { }); } } + /** * find an existing vote in vote_stack from its choice_id * @param choice_id @@ -609,6 +637,7 @@ export class PollService implements Resolve { } }); } + convertCalendarDatesToChoices(array_dates) { return array_dates; } @@ -617,7 +646,9 @@ export class PollService implements Resolve { * @description convert to API version 1 data transition object * @param form */ - newPollFromForm(form: any): Poll { + 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); @@ -626,6 +657,7 @@ export class PollService implements Resolve { 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]; @@ -646,18 +678,29 @@ export class PollService implements Resolve { } 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.max_count_of_answers = form.value.maxCountOfAnswers; newpoll.maxCountOfAnswers = form.value.maxCountOfAnswers; newpoll.password = form.value.password; - newpoll.kind = form.value.kind; + newpoll.kind = form.value.isAboutDate ? 'date' : 'classic'; newpoll.allow_comments = form.value.allowComments; // merge choices from storage - if (form.value.kind === 'date') { + 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.dateChoices = Object.assign([], this.storageService.dateChoices); newpoll.timeSlices = Object.assign([], this.storageService.timeSlices); console.log('newpoll', newpoll); return newpoll; 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 index ad497bd0..20d1e7c3 100644 --- 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 @@ -1,6 +1,7 @@ import { Component, Input, OnInit } from '@angular/core'; import { PollService } from '../../../../../core/services/poll.service'; import { ApiService } from '../../../../../core/services/api.service'; +import { environment } from '../../../../../../environments/environment'; @Component({ selector: 'app-step-four', @@ -9,7 +10,7 @@ import { ApiService } from '../../../../../core/services/api.service'; }) export class StepFourComponent implements OnInit { urlPrefix: any; - advancedDisplayEnabled: boolean = true; + advancedDisplayEnabled: boolean = environment.advanced_options_display; @Input() step_max: any; @Input() diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index 06cc98cd..7be35465 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -18,6 +18,7 @@ export const environment = { showDemoWarning: true, autofill_creation: true, autofill_participation: true, + advanced_options_display: false, autoSendNewPoll: false, interval_days_default: 7, expiresDaysDelay: 60, diff --git a/src/environments/environment.ts b/src/environments/environment.ts index c52cbea7..b7b112db 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -12,10 +12,11 @@ export const environment = { production: false, display_routes: false, // demo paths to test polls autofill_creation: true, + advanced_options_display: false, autofill_participation: true, // autofill: false, showDemoWarning: false, - autoSendNewPoll: false, + autoSendNewPoll: true, interval_days_default: 7, expiresDaysDelay: 60, maxCountOfAnswers: 150, From d68ba7ac2a9b628adabaae4cc249a61015da3190 Mon Sep 17 00:00:00 2001 From: Tykayn Date: Fri, 12 Nov 2021 13:00:16 +0100 Subject: [PATCH 17/36] new poll from local form of the pollService --- src/app/core/services/poll.service.ts | 1 - .../administration/form/steps/step-four/step-four.component.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/core/services/poll.service.ts b/src/app/core/services/poll.service.ts index 71fa9219..d3f24333 100644 --- a/src/app/core/services/poll.service.ts +++ b/src/app/core/services/poll.service.ts @@ -644,7 +644,6 @@ export class PollService implements Resolve { /** * @description convert to API version 1 data transition object - * @param form */ newPollFromForm(): Poll { let form = this.form; 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 index 20d1e7c3..a2a23e07 100644 --- 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 @@ -20,7 +20,7 @@ export class StepFourComponent implements OnInit { ngOnInit(): void {} createPoll() { - let apiData = this.pollService.newPollFromForm(this.pollService._poll); + let apiData = this.pollService.newPollFromForm(); this.apiService.createPoll(apiData); } } From 96ef61c54154d241dae28c47ca296490e1526e10 Mon Sep 17 00:00:00 2001 From: Tykayn Date: Fri, 12 Nov 2021 13:27:18 +0100 Subject: [PATCH 18/36] fix default values for creation validation --- src/app/core/services/poll.service.ts | 34 +++++++++++-------- .../steps/step-four/step-four.component.html | 4 --- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/app/core/services/poll.service.ts b/src/app/core/services/poll.service.ts index d3f24333..0a11859a 100644 --- a/src/app/core/services/poll.service.ts +++ b/src/app/core/services/poll.service.ts @@ -112,22 +112,23 @@ export class PollService implements Resolve { } public createFormGroup() { + let minlengthValidation = environment.production ? 12 : 0; let form = this.fb.group({ - title: ['', [Validators.required, Validators.minLength(12)]], + title: ['', [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: ['', [Validators.required]], + password: ['', []], choices: new FormArray([]), whoModifiesAnswers: ['', [Validators.required]], whoCanChangeAnswers: ['', [Validators.required]], isAboutDate: [true, [Validators.required]], - startDateInterval: ['', [Validators.required]], - endDateInterval: ['', [Validators.required]], - expiresDaysDelay: ['', [Validators.required]], - maxCountOfAnswers: ['', [Validators.required]], + // startDateInterval: ['', [Validators.required]], + // endDateInterval: ['', [Validators.required]], + expiresDaysDelay: [60, []], + maxCountOfAnswers: [300, []], isZeroKnoledge: [false, [Validators.required]], isProtectedByPassword: [false, [Validators.required]], isOwnerNotifiedByEmailOnNewVote: [true, [Validators.required]], @@ -140,7 +141,7 @@ export class PollService implements Resolve { allowComments: [true, [Validators.required]], hasMaxCountOfAnswers: [300, [Validators.required]], useVoterUniqueLink: [false, [Validators.required]], - voterEmailList: ['', [Validators.required]], + voterEmailList: ['', []], allowNewDateTime: [60, [Validators.required, Validators.min(0)]], }); this.form = form; @@ -160,11 +161,12 @@ export class PollService implements Resolve { isProtectedByPassword: false, richTextMode: false, areResultsPublic: true, - expiracyNumberOfDays: 60, + expiresDayDelay: 60, maxCountOfAnswers: 300, voterEmailList: '', password: '', }); + this.setDefaultDatesForInterval(); } public updateSlug(): void { @@ -376,15 +378,19 @@ export class PollService implements Resolve { } } - public createPoll(): void { + public createPoll(): Promise { this.toastService.display('sending...'); console.log('this.form', this.form); const newpoll = this.newPollFromForm(); - this.apiService.createPoll(newpoll).then((resp) => { - console.log('poll created resp', resp); - console.log('TODO fill admin_key'); - // this.admin_key = resp.data.admin_key; - }); + return this.apiService.createPoll(newpoll).then( + (resp: any) => { + console.log('poll created resp', resp); + console.log('TODO fill admin_key'); + console.log('resp', resp); + this.admin_key = resp.data.poll.admin_key; + }, + (error) => this.apiService.ousideHandleError(error) + ); } /** 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 index a6f649f3..6fd7d762 100644 --- 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 @@ -54,10 +54,6 @@
-
+ + test admin link to edit poll + diff --git a/src/app/core/services/api.service.ts b/src/app/core/services/api.service.ts index 05ce4cc8..4bc8df9a 100644 --- a/src/app/core/services/api.service.ts +++ b/src/app/core/services/api.service.ts @@ -136,7 +136,9 @@ 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 { @@ -147,10 +149,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) { @@ -162,12 +186,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) { @@ -181,11 +205,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; @@ -201,7 +225,7 @@ export class ApiService { //////////// // UPDATE // - + //////////// public async sendUpdateVoteStack(vote_stack: Stack) { try { return await this.axiosInstance.patch( diff --git a/src/app/core/services/poll.service.ts b/src/app/core/services/poll.service.ts index 0a11859a..da254d98 100644 --- a/src/app/core/services/poll.service.ts +++ b/src/app/core/services/poll.service.ts @@ -65,6 +65,7 @@ export class PollService implements Resolve { ) { 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()), @@ -80,7 +81,7 @@ export class PollService implements Resolve { } /** - * add example values to the form + * add example values to the form for demo env */ setDemoValues(): void { this.addChoice('orange'); @@ -125,8 +126,6 @@ export class PollService implements Resolve { whoModifiesAnswers: ['', [Validators.required]], whoCanChangeAnswers: ['', [Validators.required]], isAboutDate: [true, [Validators.required]], - // startDateInterval: ['', [Validators.required]], - // endDateInterval: ['', [Validators.required]], expiresDaysDelay: [60, []], maxCountOfAnswers: [300, []], isZeroKnoledge: [false, [Validators.required]], @@ -148,6 +147,9 @@ export class PollService implements Resolve { return form; } + /** + * set default configs to the form + */ public patchFormDefaultValues() { this.form.patchValue({ title: 'mon titre de sondage', @@ -169,6 +171,9 @@ export class PollService implements Resolve { 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) }); @@ -176,6 +181,7 @@ export class PollService implements Resolve { /** * auto fetch a poll when route is looking for one in the administration pattern + * DO NOT USE - needs refacto * @param route * @param state */ @@ -259,7 +265,7 @@ 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) { @@ -272,8 +278,9 @@ export class PollService implements Resolve { // this.storageService.setChoicesForVoteStack(poll.choices); } - this.toastService.display('sondage bien mis à jour', 'success'); this._poll.next(poll); + this.toastService.display(`sondage ${poll.title} bien mis à jour`, 'success'); + return poll; } /** @@ -577,7 +584,9 @@ export class PollService implements Resolve { public getAdministrationUrlFromForm(): string { // admin_key is filled after creation - return `${environment.frontDomain}#/admin/${this.admin_key}/consultation`; + // example http://localhost:4200/#/administration/8Ubcg2YI99f69xz946cn4O64bQAeb + + return `${environment.frontDomain}#/administration/${this.admin_key}`; } public getParticipationUrl(): string { @@ -648,6 +657,13 @@ export class PollService implements Resolve { return array_dates; } + patchFormWithPoll(poll: Poll) { + this.form.patchValue({ + ...poll, + isAboutDate: poll.kind == 'date', + }); + } + /** * @description convert to API version 1 data transition object */ diff --git a/src/app/features/administration/administration-routing.module.ts b/src/app/features/administration/administration-routing.module.ts index 05b6a355..4676f048 100644 --- a/src/app/features/administration/administration-routing.module.ts +++ b/src/app/features/administration/administration-routing.module.ts @@ -7,12 +7,15 @@ 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, }, + { path: 'key/:admin_key', component: AdminConsultationComponent }, { path: 'step', children: [ @@ -23,6 +26,10 @@ const routes: Routes = [ { 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 b0faa2a9..70e204ed 100644 --- a/src/app/features/administration/administration.module.ts +++ b/src/app/features/administration/administration.module.ts @@ -27,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: [ @@ -50,6 +51,7 @@ import { TimeListComponent } from './form/date/list/time/time-list.component'; DayListComponent, PickerComponent, TimeListComponent, + AdminConsultationComponent, ], imports: [ AdministrationRoutingModule, 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..cfba138c --- /dev/null +++ b/src/app/features/administration/consultation/consultation.component.ts @@ -0,0 +1,54 @@ +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.patchFormLoadedWithPoll(); + 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'); + } + } + ); + }); + } + + patchFormLoadedWithPoll() { + this.pollService.patchFormWithPoll(this.pollService._poll.getValue()); + } +} 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 index 6fd7d762..0fa9ca8d 100644 --- 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 @@ -1,7 +1,7 @@
- +
- vous êtes admin de ce sondage + vous êtes admin de ce sondage et pouvez le modifier
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/assets/i18n/FR.json b/src/assets/i18n/FR.json index 77a8fbaf..08a03b79 100644 --- a/src/assets/i18n/FR.json +++ b/src/assets/i18n/FR.json @@ -73,7 +73,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" diff --git a/src/environments/environment.ts b/src/environments/environment.ts index b7b112db..95c54cfb 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -16,7 +16,8 @@ export const environment = { autofill_participation: true, // autofill: false, showDemoWarning: false, - autoSendNewPoll: true, + // autoSendNewPoll: true, + autoSendNewPoll: false, interval_days_default: 7, expiresDaysDelay: 60, maxCountOfAnswers: 150, From 7b82b5506d2f79f3e52664c5bf2048f9f57b9b7a Mon Sep 17 00:00:00 2001 From: Tykayn Date: Fri, 12 Nov 2021 17:31:47 +0100 Subject: [PATCH 20/36] regroup error list on top of page --- .../form/steps/step-five/step-five.component.html | 2 +- .../form/steps/step-four/step-four.component.html | 5 ++--- .../form/steps/step-three/step-three.component.html | 2 +- .../form/steps/step-two/step-two.component.html | 1 + 4 files changed, 5 insertions(+), 5 deletions(-) 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 index 61adef2a..8ee38851 100644 --- 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 @@ -1,5 +1,5 @@ - +
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 index 0fa9ca8d..f2b9e02a 100644 --- 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 @@ -2,7 +2,7 @@
- +
-
@@ -60,5 +60,4 @@
-
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 index 72754252..2bd90e8b 100644 --- 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 @@ -1,6 +1,6 @@
- +
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 index 4ffd4b05..102c242a 100644 --- 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 @@ -1,6 +1,7 @@
+

{{ 'creation.want' | translate }}

From 54f9e22070fef6dde6898c39bb4cd3ae2cff3a69 Mon Sep 17 00:00:00 2001 From: Tykayn Date: Fri, 12 Nov 2021 18:17:49 +0100 Subject: [PATCH 21/36] debug error messages validation form --- .../administration/consultation/consultation.component.ts | 4 ++-- .../ui/form/errors-list/errors-list.component.html | 7 +++++++ .../ui/form/errors-list/errors-list.component.ts | 3 +++ src/assets/i18n/FR.json | 1 + 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/app/features/administration/consultation/consultation.component.ts b/src/app/features/administration/consultation/consultation.component.ts index cfba138c..150d5c42 100644 --- a/src/app/features/administration/consultation/consultation.component.ts +++ b/src/app/features/administration/consultation/consultation.component.ts @@ -29,7 +29,7 @@ export class AdminConsultationComponent implements OnInit { this._Activatedroute.paramMap.subscribe((params: ParamMap) => { this.admin_key = params.get('admin_key'); if (!this.admin_key) { - this.router.navigate('page-not-found'); + this.router.navigate(['page-not-found']); } this.apiService.getPollByAdminKey(this.admin_key).then( (res) => { @@ -41,7 +41,7 @@ export class AdminConsultationComponent implements OnInit { }, (err) => { if (!this.admin_key) { - this.router.navigate('page-not-found'); + this.router.navigate(['page-not-found']); } } ); 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.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/assets/i18n/FR.json b/src/assets/i18n/FR.json index 08a03b79..59313715 100644 --- a/src/assets/i18n/FR.json +++ b/src/assets/i18n/FR.json @@ -146,6 +146,7 @@ "lang": "Sélectionner la langue" }, "validation": { + "required": "champ requis", "You must enter a value": "You must enter a EEEE" }, "You must enter a value": "You must enter a valueeeeeeee", From 18a2987c04f61ff44b9e06bec77b9495bdcb76ec Mon Sep 17 00:00:00 2001 From: Tykayn Date: Sun, 14 Nov 2021 14:54:14 +0100 Subject: [PATCH 22/36] fix domain for demo test admin link --- src/app/core/components/header/header.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/components/header/header.component.html b/src/app/core/components/header/header.component.html index b75a47f1..4bc99437 100644 --- a/src/app/core/components/header/header.component.html +++ b/src/app/core/components/header/header.component.html @@ -125,7 +125,7 @@
test admin link to edit poll From c6d5a8fc8cf010ada254c832b06932cd8d667d66 Mon Sep 17 00:00:00 2001 From: Tykayn Date: Sun, 14 Nov 2021 15:26:26 +0100 Subject: [PATCH 23/36] stepper shortcuts option --- .../step-three/step-three.component.html | 149 +++++++++--------- .../stepper/stepper.component.html | 10 ++ .../stepper/stepper.component.scss | 10 ++ .../stepper/stepper.component.ts | 4 + src/environments/environment.prod.ts | 1 + src/environments/environment.ts | 3 +- src/styles/partials/_forms.scss | 12 +- 7 files changed, 109 insertions(+), 80 deletions(-) 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 index 2bd90e8b..46acb886 100644 --- 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 @@ -2,83 +2,86 @@ -
-
- - {{ pollService.timeList.length }} - - - {{ 'dates.count_time' | translate }} - (pour chaque jour) - -
-
-
+
+
+
+
+ + + +
+
+ + {{ pollService.timeList.length }} + + + {{ 'dates.count_time' | translate }} + (pour chaque jour) + +
+
- - {{ 'dates.add_time' | translate }} - - - -
- -
-
-
- - - +
+
+ + + +
-
- - {{ pollService.calendar.length }} - - - {{ 'dates.count_dates' | translate }} - -
- -
diff --git a/src/app/features/administration/stepper/stepper.component.html b/src/app/features/administration/stepper/stepper.component.html index 6d196e3e..9fb6739a 100644 --- a/src/app/features/administration/stepper/stepper.component.html +++ b/src/app/features/administration/stepper/stepper.component.html @@ -3,9 +3,19 @@

Étape {{ step_current }} / {{ step_max }} + + {{ pollService.form.value.title }} +

+
+ 1 + 2 + 3 + 4 + 5 +
diff --git a/src/app/features/administration/stepper/stepper.component.scss b/src/app/features/administration/stepper/stepper.component.scss index 186e7dd5..8f135753 100644 --- a/src/app/features/administration/stepper/stepper.component.scss +++ b/src/app/features/administration/stepper/stepper.component.scss @@ -17,3 +17,13 @@ min-width: 1px; background: $primary_color; } +.shortcut { + background: $secondary_color; + color: white; + padding: 1em; + margin: 1em; + display: inline-block; + border-radius: 100%; + text-align: center; + width: 4em; +} diff --git a/src/app/features/administration/stepper/stepper.component.ts b/src/app/features/administration/stepper/stepper.component.ts index cf7163c8..9f4fc3e7 100644 --- a/src/app/features/administration/stepper/stepper.component.ts +++ b/src/app/features/administration/stepper/stepper.component.ts @@ -1,4 +1,6 @@ import { Component, Input, OnInit } from '@angular/core'; +import { PollService } from '../../../core/services/poll.service'; +import { environment } from '../../../../environments/environment'; @Component({ selector: 'app-stepper', @@ -10,4 +12,6 @@ export class StepperComponent { public step_current: number = 1; @Input() public step_max: number = 5; + public show_shortcuts = environment.showStepperShortcuts; + constructor(public pollService: PollService) {} } diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index 7be35465..646996e4 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -21,6 +21,7 @@ export const environment = { 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 95c54cfb..ee72eb3a 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -18,10 +18,11 @@ export const environment = { 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/partials/_forms.scss b/src/styles/partials/_forms.scss index 291955de..50346db9 100644 --- a/src/styles/partials/_forms.scss +++ b/src/styles/partials/_forms.scss @@ -5,7 +5,7 @@ app-step-two, app-step-three, app-step-four, app-step-five { - padding: 2em 1em; + padding: 2em 2.5em; display: block; } @@ -221,18 +221,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; } } From b3b8d46aa3a96e176cab9c56b2769ac3b746a94f Mon Sep 17 00:00:00 2001 From: Tykayn Date: Sun, 14 Nov 2021 15:48:27 +0100 Subject: [PATCH 24/36] styles for steps --- .../advanced-config.component.html | 1 - .../base-config/base-config.component.scss | 2 +- .../steps/step-five/step-five.component.html | 22 ++--- .../steps/step-four/step-four.component.html | 9 +- .../steps/step-four/step-four.component.scss | 8 ++ .../steps/step-one/step-one.component.html | 21 ++-- .../steps/step-two/step-two.component.scss | 7 +- .../success/success.component.html | 99 ++++++++++--------- .../success/success.component.scss | 3 +- src/styles/partials/_forms.scss | 25 ++++- src/styles/partials/_main.scss | 16 +-- 11 files changed, 112 insertions(+), 101 deletions(-) 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 d00c48af..04610dd2 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 }}


-
- 300 caractères maximum -
- -
- + {{ pollService.form.value.description.length }} / 300 caractères maximum +
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 index 182a819e..dea15f78 100644 --- 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 @@ -1,3 +1,6 @@ -.fa { - margin-right: 1em; +.kind-of-poll { + margin-top: 5em; + .fa { + margin-right: 1em; + } } diff --git a/src/app/features/administration/success/success.component.html b/src/app/features/administration/success/success.component.html index a52a4195..0076de43 100644 --- a/src/app/features/administration/success/success.component.html +++ b/src/app/features/administration/success/success.component.html @@ -15,52 +15,61 @@
-
+
-
-

- {{ '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.getAdministrationUrlFromForm() }} - - -

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

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

-
-
-

{{ 'resume.users' | translate }}

-

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

-
- -
+
+
+
+

+ + {{ '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.default_expiracy_days_from_now }} jours après la date de sa + dernière modification. + + Le {{ pollService.form.value.expiracy_date | date: 'short' }} + +

+
+
+
+
+

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

+

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

+
+ +
+
+
diff --git a/src/app/features/administration/success/success.component.scss b/src/app/features/administration/success/success.component.scss index 80cd3a06..f354ac03 100644 --- a/src/app/features/administration/success/success.component.scss +++ b/src/app/features/administration/success/success.component.scss @@ -7,7 +7,8 @@ button { margin-right: 1ch; } a { - max-width: 35em; + padding: 1em; + max-width: 20em; @extend .truncate; } .truncate { diff --git a/src/styles/partials/_forms.scss b/src/styles/partials/_forms.scss index 50346db9..3bc729e9 100644 --- a/src/styles/partials/_forms.scss +++ b/src/styles/partials/_forms.scss @@ -1,13 +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, -app-step-five { +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, diff --git a/src/styles/partials/_main.scss b/src/styles/partials/_main.scss index 459c2cfd..7c84f5e3 100644 --- a/src/styles/partials/_main.scss +++ b/src/styles/partials/_main.scss @@ -5,22 +5,8 @@ main { width: 100%; margin: 0 auto; } -.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; } From a0576a477da918b69a380c858f649c367b96be6c Mon Sep 17 00:00:00 2001 From: Tykayn Date: Sun, 14 Nov 2021 16:00:48 +0100 Subject: [PATCH 25/36] advanced config, toggle display clear password --- .../advanced-config.component.html | 61 ++++++++----------- .../advanced-config.component.scss | 3 + .../advanced-config.component.ts | 4 +- src/environments/environment.ts | 2 +- 4 files changed, 32 insertions(+), 38 deletions(-) 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 04610dd2..e1a6d1c9 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 @@ -10,32 +10,20 @@ formControlName="description" required > -

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

@@ -172,4 +160,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 eb0e5380..bb60120b 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 @@ -6,3 +6,6 @@ margin-left: 1em; } } +.button .fa { + margin: 1em; +} 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/environments/environment.ts b/src/environments/environment.ts index ee72eb3a..4add204a 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -12,7 +12,7 @@ export const environment = { production: false, display_routes: false, // demo paths to test polls autofill_creation: true, - advanced_options_display: false, + advanced_options_display: true, autofill_participation: true, // autofill: false, showDemoWarning: false, From 6796d6e39fed2ba7955be94bc12a28b2b0e6bbd2 Mon Sep 17 00:00:00 2001 From: Tykayn Date: Sun, 14 Nov 2021 16:15:12 +0100 Subject: [PATCH 26/36] emphasis on advanced config --- .../advanced-config/advanced-config.component.html | 8 +++----- .../advanced-config/advanced-config.component.scss | 8 ++++++++ .../form/steps/step-four/step-four.component.scss | 7 ------- .../poll-results-compact.component.scss | 1 - .../page-not-found/page-not-found.component.html | 4 +--- src/styles/partials/_forms.scss | 12 ++++++++++++ 6 files changed, 24 insertions(+), 16 deletions(-) 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 e1a6d1c9..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 @@ -114,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 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 bb60120b..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,5 @@ +@import '../../../../../styles/variables'; + .title { margin-top: 2em; } @@ -9,3 +11,9 @@ .button .fa { margin: 1em; } +input, +textarea { + margin-top: 0.5em; + margin-bottom: 1.5em; + width: 100%; +} 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 index d18cef98..5ab410dc 100644 --- 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 @@ -1,8 +1 @@ @import '../../../../../../styles/variables'; - -.advanced-config { - fieldset { - background: $grey-lighter; - border-left: 3px solid $primary-color; - } -} 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/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/styles/partials/_forms.scss b/src/styles/partials/_forms.scss index 3bc729e9..2c6a0453 100644 --- a/src/styles/partials/_forms.scss +++ b/src/styles/partials/_forms.scss @@ -308,3 +308,15 @@ mat-checkbox { border-radius: 100%; } } + +.advanced-config { + .box { + background: $light; + border: 3px solid $primary-color; + } + .work-in-progress { + padding: 1em 2em; + background: $border-color; + color: $light; + } +} From fc1859719a0715f6cc502cb922424776af3db452 Mon Sep 17 00:00:00 2001 From: Tykayn Date: Sun, 14 Nov 2021 17:02:20 +0100 Subject: [PATCH 27/36] use pollservice proxy to create poll and store the result in storage service --- src/app/app-routing.module.ts | 2 +- src/app/core/services/api.service.ts | 6 +++++- src/app/core/services/poll.service.ts | 15 +++++++++++---- src/app/core/services/storage.service.ts | 2 +- .../form/steps/step-four/step-four.component.ts | 3 +-- src/environments/environment.ts | 2 +- 6 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 43b0fa8e..4a0e9f19 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -3,7 +3,7 @@ import { RouterModule } from '@angular/router'; import { routes } from './routes-framadate'; @NgModule({ - imports: [RouterModule.forRoot(routes, { useHash: true, enableTracing: true })], + imports: [RouterModule.forRoot(routes, { useHash: true })], exports: [RouterModule], }) export class AppRoutingModule {} diff --git a/src/app/core/services/api.service.ts b/src/app/core/services/api.service.ts index 4bc8df9a..7ee51523 100644 --- a/src/app/core/services/api.service.ts +++ b/src/app/core/services/api.service.ts @@ -80,9 +80,13 @@ export class ApiService { }; } + /** + * + * @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, diff --git a/src/app/core/services/poll.service.ts b/src/app/core/services/poll.service.ts index da254d98..c6cf37cd 100644 --- a/src/app/core/services/poll.service.ts +++ b/src/app/core/services/poll.service.ts @@ -89,7 +89,7 @@ export class PollService implements Resolve { this.addChoice('abricot'); this.form.patchValue({ - title: 'mon titre', + title: 'mon titre de sondage du ' + this.DateUtilitiesService.formateDateToInputStringNg(new Date()), description: 'répondez SVP <3 ! *-* ', custom_url: this.uuidService.getUUID(), creatorPseudo: 'Chuck Norris', @@ -115,7 +115,7 @@ export class PollService implements Resolve { public createFormGroup() { let minlengthValidation = environment.production ? 12 : 0; let form = this.fb.group({ - title: ['', [Validators.required, Validators.minLength(minlengthValidation)]], + title: ['mon titre de sondage', [Validators.required, Validators.minLength(minlengthValidation)]], creatorPseudo: ['', [Validators.required]], created_at: [new Date(), [Validators.required]], creatorEmail: ['', [Validators.required]], @@ -385,9 +385,12 @@ export class PollService implements Resolve { } } + /** + * convert form data to DTO to create a new poll, and store the admin key + */ public createPoll(): Promise { this.toastService.display('sending...'); - console.log('this.form', this.form); + console.log('createPoll this.form', this.form); const newpoll = this.newPollFromForm(); return this.apiService.createPoll(newpoll).then( (resp: any) => { @@ -395,8 +398,12 @@ export class PollService implements Resolve { console.log('TODO fill admin_key'); console.log('resp', resp); this.admin_key = resp.data.poll.admin_key; + this.storageService.userPolls.push(resp.data.poll); }, - (error) => this.apiService.ousideHandleError(error) + (error) => { + this.toastService.display('BOOM, the createPoll went wrong'); + this.apiService.ousideHandleError(error); + } ); } diff --git a/src/app/core/services/storage.service.ts b/src/app/core/services/storage.service.ts index d19c83db..eceb828b 100644 --- a/src/app/core/services/storage.service.ts +++ b/src/app/core/services/storage.service.ts @@ -47,7 +47,7 @@ export class StorageService { constructor(public dateUtilities: DateUtilitiesService, private toastService: ToastService) { if (environment.autofill_participation) { - this.toastService.display('autofill des sondages utilisateur'); + // this.toastService.display('autofill des sondages utilisateur'); 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( 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 index 3017ec41..edca8077 100644 --- 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 @@ -21,8 +21,7 @@ export class StepFourComponent implements OnInit { ngOnInit(): void {} createPoll() { - let apiData = this.pollService.newPollFromForm(); - this.apiService.createPoll(apiData).then((resp) => { + this.pollService.createPoll().then((resp) => { this.router.navigate(['administration/success']); }); } diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 4add204a..8b3a6a3c 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -10,7 +10,7 @@ endpoints.baseHref = apiV1.baseHref; export const environment = { frontDomain: 'http://127.0.0.1:4200', production: false, - display_routes: false, // demo paths to test polls + display_routes: true, // demo paths to test polls autofill_creation: true, advanced_options_display: true, autofill_participation: true, From 989208faa5a2c4ebed2bc1e12babe014fc619899 Mon Sep 17 00:00:00 2001 From: Tykayn Date: Sun, 14 Nov 2021 17:25:57 +0100 Subject: [PATCH 28/36] fix filling of choices in consultation component after refacto of poll's data in pollService --- src/app/core/services/poll.service.ts | 30 +++++++++---------- src/app/core/services/storage.service.ts | 22 ++++++-------- .../consultation/consultation.component.ts | 5 ---- .../consultation/consultation.component.ts | 8 ++--- 4 files changed, 28 insertions(+), 37 deletions(-) diff --git a/src/app/core/services/poll.service.ts b/src/app/core/services/poll.service.ts index c6cf37cd..e5ec3eba 100644 --- a/src/app/core/services/poll.service.ts +++ b/src/app/core/services/poll.service.ts @@ -84,10 +84,6 @@ export class PollService implements Resolve { * add example values to the form for demo env */ setDemoValues(): void { - this.addChoice('orange'); - this.addChoice('raisin'); - this.addChoice('abricot'); - this.form.patchValue({ title: 'mon titre de sondage du ' + this.DateUtilitiesService.formateDateToInputStringNg(new Date()), description: 'répondez SVP <3 ! *-* ', @@ -268,17 +264,21 @@ export class PollService implements Resolve { 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._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; } @@ -591,9 +591,9 @@ export class PollService implements Resolve { public getAdministrationUrlFromForm(): string { // admin_key is filled after creation - // example http://localhost:4200/#/administration/8Ubcg2YI99f69xz946cn4O64bQAeb + // example http://localhost:4200/#/administration/key/8Ubcg2YI99f69xz946cn4O64bQAeb - return `${environment.frontDomain}#/administration/${this.admin_key}`; + return `${environment.frontDomain}#/administration/key/${this.admin_key}`; } public getParticipationUrl(): string { diff --git a/src/app/core/services/storage.service.ts b/src/app/core/services/storage.service.ts index eceb828b..ac2c8148 100644 --- a/src/app/core/services/storage.service.ts +++ b/src/app/core/services/storage.service.ts @@ -47,17 +47,12 @@ export class StorageService { constructor(public dateUtilities: DateUtilitiesService, private toastService: ToastService) { if (environment.autofill_participation) { - // this.toastService.display('autofill des sondages utilisateur'); 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_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)); - } + // 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/consultation/consultation.component.ts b/src/app/features/administration/consultation/consultation.component.ts index 150d5c42..19a4c025 100644 --- a/src/app/features/administration/consultation/consultation.component.ts +++ b/src/app/features/administration/consultation/consultation.component.ts @@ -34,7 +34,6 @@ export class AdminConsultationComponent implements OnInit { this.apiService.getPollByAdminKey(this.admin_key).then( (res) => { this.pollService.updateCurrentPoll(res.poll); - this.patchFormLoadedWithPoll(); this.form = this.pollService.form; this.poll = this.pollService._poll.getValue(); console.log('formulaire patché', this.pollService.form, this.pollService.poll); @@ -47,8 +46,4 @@ export class AdminConsultationComponent implements OnInit { ); }); } - - patchFormLoadedWithPoll() { - this.pollService.patchFormWithPoll(this.pollService._poll.getValue()); - } } diff --git a/src/app/features/consultation/consultation.component.ts b/src/app/features/consultation/consultation.component.ts index e9ce1fb5..63195644 100644 --- a/src/app/features/consultation/consultation.component.ts +++ b/src/app/features/consultation/consultation.component.ts @@ -61,17 +61,17 @@ export class ConsultationComponent implements OnInit, OnDestroy { console.log('this.pass_hash ', this.pass_hash); if (this.pass_hash) { this.pollService.loadPollByCustomUrlWithPasswordHash(this.pollSlug, this.pass_hash).then((resp) => { - console.log('resp', resp); + console.log('loadPollByCustomUrlWithPasswordHash resp', resp); this.fetching = false; this.storageService.vote_stack.id = null; - this.storageService.setChoicesForVoteStack(this.pollService.form.value.choices); + this.storageService.setChoicesForVoteStack(this.pollService._poll.getValue().choices); }); } else { this.pollService.loadPollByCustomUrl(this.pollSlug).then((resp) => { - console.log('resp', resp); + console.log('loadPollByCustomUrl resp', resp); this.fetching = false; this.storageService.vote_stack.id = null; - this.storageService.setChoicesForVoteStack(this.pollService.form.value.choices); + this.storageService.setChoicesForVoteStack(this.pollService._poll.getValue().choices); }); } }); From 2f26e27e31696f0ffd9023271dcdc55d830d93d5 Mon Sep 17 00:00:00 2001 From: Tykayn Date: Sun, 14 Nov 2021 17:43:18 +0100 Subject: [PATCH 29/36] fix default days to expire a poll --- src/app/core/services/poll.service.ts | 10 ++++------ .../form/steps/step-four/step-four.component.ts | 11 ++++++++--- .../success/success.component.html | 16 +++++++++++----- .../administration/success/success.component.ts | 1 + 4 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/app/core/services/poll.service.ts b/src/app/core/services/poll.service.ts index e5ec3eba..3c37f592 100644 --- a/src/app/core/services/poll.service.ts +++ b/src/app/core/services/poll.service.ts @@ -96,7 +96,7 @@ export class PollService implements Resolve { isProtectedByPassword: false, richTextMode: false, areResultsPublic: true, - expiracyNumberOfDays: 60, + expiresDaysDelay: environment.expiresDaysDelay, }); this.automaticSlug(); } @@ -122,7 +122,7 @@ export class PollService implements Resolve { whoModifiesAnswers: ['', [Validators.required]], whoCanChangeAnswers: ['', [Validators.required]], isAboutDate: [true, [Validators.required]], - expiresDaysDelay: [60, []], + expiresDaysDelay: [environment.expiresDaysDelay, []], maxCountOfAnswers: [300, []], isZeroKnoledge: [false, [Validators.required]], isProtectedByPassword: [false, [Validators.required]], @@ -137,7 +137,7 @@ export class PollService implements Resolve { hasMaxCountOfAnswers: [300, [Validators.required]], useVoterUniqueLink: [false, [Validators.required]], voterEmailList: ['', []], - allowNewDateTime: [60, [Validators.required, Validators.min(0)]], + allowNewDateTime: [true, [Validators.required]], }); this.form = form; return form; @@ -159,7 +159,7 @@ export class PollService implements Resolve { isProtectedByPassword: false, richTextMode: false, areResultsPublic: true, - expiresDayDelay: 60, + expiresDaysDelay: environment.expiresDaysDelay, maxCountOfAnswers: 300, voterEmailList: '', password: '', @@ -395,8 +395,6 @@ export class PollService implements Resolve { return this.apiService.createPoll(newpoll).then( (resp: any) => { console.log('poll created resp', resp); - console.log('TODO fill admin_key'); - console.log('resp', resp); this.admin_key = resp.data.poll.admin_key; this.storageService.userPolls.push(resp.data.poll); }, 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 index edca8077..3c589228 100644 --- 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 @@ -21,8 +21,13 @@ export class StepFourComponent implements OnInit { ngOnInit(): void {} createPoll() { - this.pollService.createPoll().then((resp) => { - this.router.navigate(['administration/success']); - }); + this.pollService.createPoll().then( + (resp) => { + this.router.navigate(['administration/success']); + }, + (err) => { + console.error('oops err', err); + } + ); } } diff --git a/src/app/features/administration/success/success.component.html b/src/app/features/administration/success/success.component.html index 0076de43..a7b388f7 100644 --- a/src/app/features/administration/success/success.component.html +++ b/src/app/features/administration/success/success.component.html @@ -42,12 +42,18 @@ Voir le sondage coté administrateur·ice
-

- Note : Le sondage sera supprimé - {{ pollService.form.value.default_expiracy_days_from_now }} jours après la date de sa +

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

diff --git a/src/app/features/administration/success/success.component.ts b/src/app/features/administration/success/success.component.ts index 41af1f86..eb1eb58c 100644 --- a/src/app/features/administration/success/success.component.ts +++ b/src/app/features/administration/success/success.component.ts @@ -15,6 +15,7 @@ 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.pollService.form.value.title From 8f6fe6044a1819aaee5bb542ce58a293db689562 Mon Sep 17 00:00:00 2001 From: Tykayn Date: Tue, 16 Nov 2021 11:03:20 +0100 Subject: [PATCH 30/36] labels for step one --- .../form/steps/step-one/step-one.component.html | 13 +++++++++---- src/assets/i18n/FR.json | 3 ++- src/environments/endpoints.ts | 8 ++++---- 3 files changed, 15 insertions(+), 9 deletions(-) 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 index 9577e4f8..be65462f 100644 --- 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 @@ -8,7 +8,7 @@
- +
@@ -41,7 +42,7 @@ #description matInput id="descr" - class="is-large is-full" + class="is-large is-full input" placeholder="Description" formControlName="description" required @@ -58,11 +59,15 @@
-
+
+ +
diff --git a/src/assets/i18n/FR.json b/src/assets/i18n/FR.json index 59313715..7fa7b650 100644 --- a/src/assets/i18n/FR.json +++ b/src/assets/i18n/FR.json @@ -34,7 +34,8 @@ "classic": "classique", "date": "spécial dates" }, - "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", diff --git a/src/environments/endpoints.ts b/src/environments/endpoints.ts index 862b491d..2a3201c1 100644 --- a/src/environments/endpoints.ts +++ b/src/environments/endpoints.ts @@ -1,10 +1,10 @@ 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: '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', }; export const apiV1 = { // baseHref: 'https://localhost:8000/api/v1', From 475c420d96c9088ef89862953739223a4322e3a1 Mon Sep 17 00:00:00 2001 From: Tykayn Date: Tue, 16 Nov 2021 11:16:01 +0100 Subject: [PATCH 31/36] conditionnal display of admin links if admin_key is present --- src/app/core/services/poll.service.ts | 2 +- .../success/success.component.html | 63 +++++++++++-------- 2 files changed, 37 insertions(+), 28 deletions(-) diff --git a/src/app/core/services/poll.service.ts b/src/app/core/services/poll.service.ts index 3c37f592..3ee901dc 100644 --- a/src/app/core/services/poll.service.ts +++ b/src/app/core/services/poll.service.ts @@ -47,7 +47,7 @@ export class PollService implements Resolve { public showDateInterval = false; public allowSeveralHours = false; public richTextMode = false; - public calendar: any = [new Date()]; + public calendar: Date[] = [new Date()]; constructor( private http: HttpClient, diff --git a/src/app/features/administration/success/success.component.html b/src/app/features/administration/success/success.component.html index a7b388f7..61b70b97 100644 --- a/src/app/features/administration/success/success.component.html +++ b/src/app/features/administration/success/success.component.html @@ -28,34 +28,43 @@ 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() }}
-							
- +
+ Pas de clé d'administration, l'enregistrement du sondage a échoué. vérifiez vos paramètres + réseau. +
+
+
+ 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' + }} + +

-
- - 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' - }} - -

From 4233a8eb7e63676a3cf807905b1d568f2cb286d2 Mon Sep 17 00:00:00 2001 From: Tykayn Date: Tue, 16 Nov 2021 16:16:30 +0100 Subject: [PATCH 32/36] handle no network on creation --- src/app/app.component.ts | 20 ++++---- src/app/core/services/api.service.ts | 33 +++++++------ src/app/core/services/poll.service.ts | 5 +- .../core/services/poll.utilities.service.ts | 16 +++---- .../administration/form/form.component.html | 7 +-- .../steps/step-five/step-five.component.ts | 4 +- .../steps/step-four/step-four.component.ts | 6 ++- .../steps/step-one/step-one.component.html | 2 +- .../form/steps/step-one/step-one.component.ts | 1 + .../step-three/step-three.component.html | 2 +- .../steps/step-three/step-three.component.ts | 4 +- .../steps/step-two/step-two.component.html | 2 +- .../form/steps/step-two/step-two.component.ts | 1 + .../stepper/stepper.component.html | 11 +++-- .../success/success.component.html | 47 ++++++++++++------- .../consultation/consultation.module.ts | 2 + src/environments/endpoints.ts | 8 ++-- src/styles/partials/_forms.scss | 4 ++ 18 files changed, 105 insertions(+), 70 deletions(-) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 8e3001a4..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'; @@ -15,6 +15,8 @@ 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', @@ -39,7 +41,9 @@ 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 ) {} @@ -63,7 +67,10 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewInit { 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) { @@ -92,10 +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: '?', @@ -118,7 +127,6 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewInit { preventDefault: true, } ); - console.log('this.shortcuts', this.shortcuts); } ngOnDestroy(): void { @@ -127,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/services/api.service.ts b/src/app/core/services/api.service.ts index 7ee51523..4b72865d 100644 --- a/src/app/core/services/api.service.ts +++ b/src/app/core/services/api.service.ts @@ -38,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); } @@ -68,16 +69,16 @@ 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; } /** @@ -89,8 +90,8 @@ export class ApiService { console.log('apiservice createPoll config', poll); return this.axiosInstance.post( `${this.baseHref}${currentApiRoutes['api_new_poll']}`, - poll, - ApiService.makeHeaders() + poll + // ApiService.makeHeaders() ); } @@ -146,6 +147,8 @@ export class ApiService { 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) { diff --git a/src/app/core/services/poll.service.ts b/src/app/core/services/poll.service.ts index 3ee901dc..d8ed77f7 100644 --- a/src/app/core/services/poll.service.ts +++ b/src/app/core/services/poll.service.ts @@ -85,7 +85,7 @@ export class PollService implements Resolve { */ setDemoValues(): void { this.form.patchValue({ - title: 'mon titre de sondage du ' + this.DateUtilitiesService.formateDateToInputStringNg(new Date()), + title: 'Mon titre de sondage du ' + this.DateUtilitiesService.formateDateToInputStringNg(new Date()), description: 'répondez SVP <3 ! *-* ', custom_url: this.uuidService.getUUID(), creatorPseudo: 'Chuck Norris', @@ -390,7 +390,6 @@ export class PollService implements Resolve { */ public createPoll(): Promise { this.toastService.display('sending...'); - console.log('createPoll this.form', this.form); const newpoll = this.newPollFromForm(); return this.apiService.createPoll(newpoll).then( (resp: any) => { @@ -689,7 +688,7 @@ export class PollService implements Resolve { const field = form.value[pk]; newpoll[pk] = field; } else { - console.log('manque pollKey', pk); + // console.log('manque pollKey', pk); } } 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/features/administration/form/form.component.html b/src/app/features/administration/form/form.component.html index b63a41e6..9f23a16a 100644 --- a/src/app/features/administration/form/form.component.html +++ b/src/app/features/administration/form/form.component.html @@ -1,10 +1,5 @@
-
-

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

-
-
+
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 index cfd6c4fb..c7e8fd33 100644 --- 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 @@ -12,7 +12,9 @@ export class StepFiveComponent implements OnInit { @Input() step_max: any; @Input() public form: FormGroup; poll: any; - constructor(public pollService: PollService) {} + constructor(public pollService: PollService) { + this.pollService.step_current = 5; + } ngOnInit(): void {} askInitFormDefault() { 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 index 3c589228..127e9a62 100644 --- 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 @@ -1,6 +1,5 @@ import { Component, Input, OnInit } from '@angular/core'; import { PollService } from '../../../../../core/services/poll.service'; -import { ApiService } from '../../../../../core/services/api.service'; import { environment } from '../../../../../../environments/environment'; import { Router } from '@angular/router'; @@ -16,7 +15,10 @@ export class StepFourComponent implements OnInit { step_max: any; @Input() form: any; - constructor(private router: Router, public pollService: PollService, private apiService: ApiService) {} + + constructor(private router: Router, public pollService: PollService) { + this.pollService.step_current = 4; + } ngOnInit(): void {} 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 index be65462f..f133ba0b 100644 --- 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 @@ -1,4 +1,4 @@ -
+
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 index 0107efb2..24414959 100644 --- 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 @@ -17,6 +17,7 @@ export class StepOneComponent implements OnInit { form: FormGroup; ngOnInit(): void { + this.pollService.step_current = 1; const selector = '#title'; const firstField = this.document.querySelector(selector); if (firstField) { 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 index 46acb886..3e3da6d6 100644 --- 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 @@ -1,4 +1,4 @@ -
+
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 index 621b2737..1a6da11b 100644 --- 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 @@ -13,7 +13,9 @@ export class StepThreeComponent implements OnInit { @Input() form: any; - constructor(public pollService: PollService) {} + constructor(public pollService: PollService) { + this.pollService.step_current = 3; + } ngOnInit(): void {} 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 index 102c242a..92d2289d 100644 --- 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 @@ -1,4 +1,4 @@ -
+
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 index 2a030ff0..728c04aa 100644 --- 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 @@ -39,6 +39,7 @@ export class StepTwoComponent implements OnInit { @Inject(DOCUMENT) private document: any ) { this.form = this.pollService.form; + this.pollService.step_current = 2; } addIntervalOfDates() {} diff --git a/src/app/features/administration/stepper/stepper.component.html b/src/app/features/administration/stepper/stepper.component.html index 9fb6739a..499ce974 100644 --- a/src/app/features/administration/stepper/stepper.component.html +++ b/src/app/features/administration/stepper/stepper.component.html @@ -1,12 +1,17 @@
-

- Étape {{ step_current }} / - {{ step_max }} +

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

+

{{ pollService.form.value.title }}

+

+ Étape {{ step_current }} / + {{ step_max }} +

diff --git a/src/app/features/administration/success/success.component.html b/src/app/features/administration/success/success.component.html index 61b70b97..00834474 100644 --- a/src/app/features/administration/success/success.component.html +++ b/src/app/features/administration/success/success.component.html @@ -1,14 +1,30 @@ -
+
-

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

+

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

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

@@ -20,19 +36,20 @@
-

- - {{ '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) -

-
+
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 :
@@ -68,7 +85,7 @@
-
+

{{ 'resume.users' | translate }} @@ -86,8 +103,6 @@

- -
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/environments/endpoints.ts b/src/environments/endpoints.ts index 2a3201c1..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: 'http://localhost:8000/api/v1', // local: 'https://framadate-api.cipherbliss.com/api/v1', - remote: 'https://localhost:8000/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/styles/partials/_forms.scss b/src/styles/partials/_forms.scss index 2c6a0453..d6b2f05c 100644 --- a/src/styles/partials/_forms.scss +++ b/src/styles/partials/_forms.scss @@ -320,3 +320,7 @@ mat-checkbox { color: $light; } } + +.step-container { + @extend .container, .is-widescreen; +} From a3db9b7cd52693d92640f9350c71ea6b2282611e Mon Sep 17 00:00:00 2001 From: Tykayn Date: Tue, 16 Nov 2021 16:23:32 +0100 Subject: [PATCH 33/36] highlight current step in stepper --- .../stepper/stepper.component.html | 42 ++++++++++++++++--- .../stepper/stepper.component.scss | 8 +++- src/styles/variables.scss | 4 +- 3 files changed, 46 insertions(+), 8 deletions(-) diff --git a/src/app/features/administration/stepper/stepper.component.html b/src/app/features/administration/stepper/stepper.component.html index 499ce974..4c9b5a9f 100644 --- a/src/app/features/administration/stepper/stepper.component.html +++ b/src/app/features/administration/stepper/stepper.component.html @@ -17,10 +17,42 @@
- 1 - 2 - 3 - 4 - 5 + 1 + 2 + 3 + 4 + 5 + +
diff --git a/src/app/features/administration/stepper/stepper.component.scss b/src/app/features/administration/stepper/stepper.component.scss index 8f135753..0ced13f4 100644 --- a/src/app/features/administration/stepper/stepper.component.scss +++ b/src/app/features/administration/stepper/stepper.component.scss @@ -18,7 +18,7 @@ background: $primary_color; } .shortcut { - background: $secondary_color; + background: $dark-lavender; color: white; padding: 1em; margin: 1em; @@ -26,4 +26,10 @@ border-radius: 100%; text-align: center; width: 4em; + &.is-active { + background: $font_color; + } +} +.poll-title { + color: $d-neutral; } diff --git a/src/styles/variables.scss b/src/styles/variables.scss index 0b0e3f4b..1be3515e 100644 --- a/src/styles/variables.scss +++ b/src/styles/variables.scss @@ -25,8 +25,8 @@ $beige-lighter: #eff0eb; $d-primary: #3e3882; // bleu 800 $d-primary-intense: #6359cf; // bleu 600 -$d-grey: #f6f5fd; // bleu 30 -$d-neutral: #767486; // bleu 30 +$d-grey: #f6f5fd; +$d-neutral: #767486; $d-alt: #a9607f; $d-info: #ecf4ff; From e99a86bb4b9cab2ca33a5f0b0eab699ece514a18 Mon Sep 17 00:00:00 2001 From: Tykayn Date: Tue, 16 Nov 2021 16:35:27 +0100 Subject: [PATCH 34/36] style for kind of poll button --- .../steps/step-two/step-two.component.html | 42 ++++++++++--------- .../steps/step-two/step-two.component.scss | 10 +++++ .../stepper/stepper.component.html | 31 +++++++------- src/assets/i18n/FR.json | 6 +-- 4 files changed, 49 insertions(+), 40 deletions(-) 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 index 92d2289d..5cfbfba4 100644 --- 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 @@ -5,26 +5,28 @@

{{ '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 index dea15f78..ff4d18c0 100644 --- 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 @@ -1,6 +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/stepper/stepper.component.html b/src/app/features/administration/stepper/stepper.component.html index 4c9b5a9f..697b6b74 100644 --- a/src/app/features/administration/stepper/stepper.component.html +++ b/src/app/features/administration/stepper/stepper.component.html @@ -1,21 +1,4 @@
-
-

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

-

- - {{ pollService.form.value.title }} - -

-

- Étape {{ step_current }} / - {{ step_max }} -

-
-
-
-
6-->
+
+

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

+

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

+

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

+
+
+
+
diff --git a/src/assets/i18n/FR.json b/src/assets/i18n/FR.json index 7fa7b650..76a46dc5 100644 --- a/src/assets/i18n/FR.json +++ b/src/assets/i18n/FR.json @@ -28,11 +28,11 @@ }, "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": "Renseignez un nom pour votre sondage", "choose_title_label": "Nom de votre sondage (obligatoire)", From d538af4ca1ff9be6e6da6a50874cea7587f2e8f1 Mon Sep 17 00:00:00 2001 From: Tykayn Date: Tue, 16 Nov 2021 17:30:31 +0100 Subject: [PATCH 35/36] style calendar step --- src/app/core/services/poll.service.ts | 5 ++ .../step-three/step-three.component.html | 14 ++++-- .../steps/step-three/step-three.component.ts | 1 + src/assets/i18n/FR.json | 48 +++++++++---------- src/styles/partials/_forms.scss | 24 +++++++++- 5 files changed, 64 insertions(+), 28 deletions(-) diff --git a/src/app/core/services/poll.service.ts b/src/app/core/services/poll.service.ts index d8ed77f7..a1e7d8b0 100644 --- a/src/app/core/services/poll.service.ts +++ b/src/app/core/services/poll.service.ts @@ -48,6 +48,7 @@ export class PollService implements Resolve { public allowSeveralHours = false; public richTextMode = false; public calendar: Date[] = [new Date()]; + public disabled_dates: Date[] = []; constructor( private http: HttpClient, @@ -71,6 +72,10 @@ export class PollService implements Resolve { 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'); 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 index 3e3da6d6..fa8f3aec 100644 --- 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 @@ -6,18 +6,23 @@ {{ pollService.calendar.length }} - {{ 'dates.count_dates' | translate }} -
+ +
+
@@ -32,6 +37,9 @@ {{ 'dates.add_time' | translate }} +