refactor create/settings controller to component (#820)
This commit is contained in:
parent
f25625f40d
commit
27b36e2005
3 changed files with 302 additions and 113 deletions
113
app/components/create-settings.hbs
Normal file
113
app/components/create-settings.hbs
Normal file
|
@ -0,0 +1,113 @@
|
|||
<div class="cr-form-wrapper box">
|
||||
<BsForm
|
||||
@formLayout="horizontal"
|
||||
@model={{this}}
|
||||
@onInvalid={{(scroll-first-invalid-element-into-view-port)}}
|
||||
@onSubmit={{this.createPoll}}
|
||||
novalidate
|
||||
as |form|
|
||||
>
|
||||
<form.element
|
||||
@label={{t "create.settings.answerType.label"}}
|
||||
@property="answerType"
|
||||
@showValidationOn={{array "change" "focusOut"}}
|
||||
@useIcons={{false}}
|
||||
class="answer-type"
|
||||
as |el|
|
||||
>
|
||||
<select
|
||||
id={{el.id}}
|
||||
class="custom-select"
|
||||
{{on "change" (pick "target.value" el.setValue)}}
|
||||
{{autofocus}}
|
||||
>
|
||||
{{#each this.answerTypes as |answerType|}}
|
||||
<option
|
||||
value={{answerType.id}}
|
||||
selected={{eq el.value answerType.id}}
|
||||
>
|
||||
{{t answerType.labelTranslation}}
|
||||
</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</form.element>
|
||||
<form.element
|
||||
@controlType="select"
|
||||
@label={{t "create.settings.expirationDate.label"}}
|
||||
@property="expirationDuration"
|
||||
@showValidationOn={{array "change" "focusOut"}}
|
||||
@useIcons={{false}}
|
||||
class="expiration-duration"
|
||||
as |el|
|
||||
>
|
||||
<select
|
||||
id={{el.id}}
|
||||
{{on "change" (pick "target.value" el.setValue)}}
|
||||
class="custom-select"
|
||||
>
|
||||
{{#each this.expirationDurations as |duration|}}
|
||||
<option value={{duration.id}} selected={{eq el.value duration.id}}>
|
||||
{{t duration.labelTranslation}}
|
||||
</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</form.element>
|
||||
<form.element
|
||||
@controlType="checkbox"
|
||||
@label={{t "create.settings.anonymousUser.label"}}
|
||||
@showValidationOn="change"
|
||||
@property="anonymousUser"
|
||||
class="anonymous-user"
|
||||
/>
|
||||
<form.element
|
||||
@controlType="checkbox"
|
||||
@label={{t "create.settings.forceAnswer.label"}}
|
||||
@showValidationOn="change"
|
||||
@property="forceAnswer"
|
||||
class="force-answer"
|
||||
/>
|
||||
|
||||
<div class="row cr-steps-bottom-nav">
|
||||
<div class="col-6 col-md-8 order-12">
|
||||
<SaveButton
|
||||
@isPending={{form.isSubmitting}}
|
||||
data-test-button="submit"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-6 col-md-4 order-1 text-right">
|
||||
<BackButton @onClick={{this.previousPage}} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<BsModal
|
||||
@onHidden={{this.resetSavingPollFailedState}}
|
||||
@onSubmit={{form.submit}}
|
||||
@open={{this.savingPollFailed}}
|
||||
data-test-modal="saving-failed"
|
||||
as |modal|
|
||||
>
|
||||
<modal.header
|
||||
@closeButton={{false}}
|
||||
@title={{t "error.poll.savingFailed.title"}}
|
||||
/>
|
||||
<modal.body>
|
||||
<p>
|
||||
{{t "error.poll.savingFailed.description"}}
|
||||
</p>
|
||||
</modal.body>
|
||||
<modal.footer>
|
||||
<BsButton @onClick={{modal.close}} data-test-button="abort">
|
||||
{{t "action.abort"}}
|
||||
</BsButton>
|
||||
<SaveButton
|
||||
@isPending={{form.isSubmitting}}
|
||||
@onClick={{modal.submit}}
|
||||
data-test-button="retry"
|
||||
type="button"
|
||||
>
|
||||
{{t "modal.save-retry.button-retry"}}
|
||||
</SaveButton>
|
||||
</modal.footer>
|
||||
</BsModal>
|
||||
</BsForm>
|
||||
</div>
|
188
app/components/create-settings.ts
Normal file
188
app/components/create-settings.ts
Normal file
|
@ -0,0 +1,188 @@
|
|||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { action } from '@ember/object';
|
||||
import { service } from '@ember/service';
|
||||
import { isPresent } from '@ember/utils';
|
||||
import { DateTime, Duration } from 'luxon';
|
||||
import { generatePassphrase } from '../utils/encryption';
|
||||
import Poll from '../models/poll';
|
||||
import type IntlService from 'ember-intl/services/intl';
|
||||
import type RouterService from '@ember/routing/router-service';
|
||||
import type { CreateSettingsRouteModel } from 'croodle/routes/create/settings';
|
||||
|
||||
export interface CreateSettingsSignature {
|
||||
Args: {
|
||||
poll: CreateSettingsRouteModel;
|
||||
};
|
||||
}
|
||||
|
||||
export default class CreateSettingsComponent extends Component<CreateSettingsSignature> {
|
||||
@service declare intl: IntlService;
|
||||
@service declare router: RouterService;
|
||||
|
||||
@tracked savingPollFailed = false;
|
||||
|
||||
get anonymousUser() {
|
||||
return this.args.poll.anonymousUser;
|
||||
}
|
||||
set anonymousUser(value) {
|
||||
this.args.poll.anonymousUser = value;
|
||||
}
|
||||
|
||||
get answerType() {
|
||||
return this.args.poll.answerType;
|
||||
}
|
||||
set answerType(value) {
|
||||
this.args.poll.answerType = value;
|
||||
}
|
||||
|
||||
get answerTypes() {
|
||||
return [
|
||||
{ id: 'YesNo', labelTranslation: 'answerTypes.yesNo.label' },
|
||||
{ id: 'YesNoMaybe', labelTranslation: 'answerTypes.yesNoMaybe.label' },
|
||||
{ id: 'FreeText', labelTranslation: 'answerTypes.freeText.label' },
|
||||
];
|
||||
}
|
||||
|
||||
get expirationDuration() {
|
||||
// TODO: must be calculated based on model.expirationDate
|
||||
return 'P3M';
|
||||
}
|
||||
set expirationDuration(value) {
|
||||
this.args.poll.expirationDate = isPresent(value)
|
||||
? (DateTime.local().plus(Duration.fromISO(value)).toISO() as string)
|
||||
: '';
|
||||
}
|
||||
|
||||
get expirationDurations() {
|
||||
return [
|
||||
{
|
||||
id: 'P7D',
|
||||
labelTranslation: 'create.settings.expirationDurations.P7D',
|
||||
},
|
||||
{
|
||||
id: 'P1M',
|
||||
labelTranslation: 'create.settings.expirationDurations.P1M',
|
||||
},
|
||||
{
|
||||
id: 'P3M',
|
||||
labelTranslation: 'create.settings.expirationDurations.P3M',
|
||||
},
|
||||
{
|
||||
id: 'P6M',
|
||||
labelTranslation: 'create.settings.expirationDurations.P6M',
|
||||
},
|
||||
{
|
||||
id: 'P1Y',
|
||||
labelTranslation: 'create.settings.expirationDurations.P1Y',
|
||||
},
|
||||
{ id: '', labelTranslation: 'create.settings.expirationDurations.never' },
|
||||
];
|
||||
}
|
||||
|
||||
get forceAnswer() {
|
||||
return this.args.poll.forceAnswer;
|
||||
}
|
||||
set forceAnswer(value) {
|
||||
this.args.poll.forceAnswer = value;
|
||||
}
|
||||
|
||||
@action
|
||||
previousPage() {
|
||||
const { pollType } = this.args.poll;
|
||||
|
||||
if (pollType === 'FindADate') {
|
||||
this.router.transitionTo('create.options-datetime');
|
||||
} else {
|
||||
this.router.transitionTo('create.options');
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
async createPoll() {
|
||||
const { poll } = this.args;
|
||||
const {
|
||||
anonymousUser,
|
||||
answerType,
|
||||
description,
|
||||
expirationDate,
|
||||
forceAnswer,
|
||||
freetextOptions,
|
||||
dateOptions,
|
||||
timesForDateOptions,
|
||||
pollType,
|
||||
title,
|
||||
} = poll;
|
||||
|
||||
// calculate options
|
||||
const options: string[] = [];
|
||||
if (pollType === 'FindADate') {
|
||||
// merge date with times
|
||||
for (const date of dateOptions) {
|
||||
if (timesForDateOptions.has(date)) {
|
||||
for (const time of timesForDateOptions.get(date)!) {
|
||||
const [hour, minute] = time.split(':') as [string, string];
|
||||
options.push(
|
||||
DateTime.fromISO(date)
|
||||
.set({
|
||||
hour: parseInt(hour),
|
||||
minute: parseInt(minute),
|
||||
second: 0,
|
||||
millisecond: 0,
|
||||
})
|
||||
.toISO() as string,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
options.push(date);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
options.push(...freetextOptions);
|
||||
}
|
||||
|
||||
// save poll
|
||||
try {
|
||||
const encryptionKey = generatePassphrase();
|
||||
|
||||
// save poll
|
||||
const poll = await Poll.create(
|
||||
{
|
||||
anonymousUser,
|
||||
answerType,
|
||||
description,
|
||||
expirationDate,
|
||||
forceAnswer,
|
||||
options: options.map((option) => {
|
||||
return { title: option };
|
||||
}),
|
||||
pollType,
|
||||
title,
|
||||
},
|
||||
encryptionKey,
|
||||
);
|
||||
|
||||
// redirect to new poll
|
||||
await this.router.transitionTo('poll.participation', poll.id, {
|
||||
queryParams: {
|
||||
encryptionKey,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
this.savingPollFailed = true;
|
||||
|
||||
reportError(err);
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
resetSavingPollFailedState() {
|
||||
this.savingPollFailed = false;
|
||||
}
|
||||
}
|
||||
|
||||
declare module '@glint/environment-ember-loose/registry' {
|
||||
export default interface Registry {
|
||||
CreateSettings: typeof CreateSettingsComponent;
|
||||
}
|
||||
}
|
|
@ -1,113 +1 @@
|
|||
<div class="cr-form-wrapper box">
|
||||
<BsForm
|
||||
@formLayout="horizontal"
|
||||
@model={{this}}
|
||||
@onInvalid={{(scroll-first-invalid-element-into-view-port)}}
|
||||
@onSubmit={{this.createPoll}}
|
||||
novalidate
|
||||
as |form|
|
||||
>
|
||||
<form.element
|
||||
@label={{t "create.settings.answerType.label"}}
|
||||
@property="answerType"
|
||||
@showValidationOn={{array "change" "focusOut"}}
|
||||
@useIcons={{false}}
|
||||
class="answer-type"
|
||||
as |el|
|
||||
>
|
||||
<select
|
||||
id={{el.id}}
|
||||
class="custom-select"
|
||||
{{on "change" (pick "target.value" el.setValue)}}
|
||||
{{autofocus}}
|
||||
>
|
||||
{{#each this.answerTypes as |answerType|}}
|
||||
<option
|
||||
value={{answerType.id}}
|
||||
selected={{eq el.value answerType.id}}
|
||||
>
|
||||
{{t answerType.labelTranslation}}
|
||||
</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</form.element>
|
||||
<form.element
|
||||
@controlType="select"
|
||||
@label={{t "create.settings.expirationDate.label"}}
|
||||
@property="expirationDuration"
|
||||
@showValidationOn={{array "change" "focusOut"}}
|
||||
@useIcons={{false}}
|
||||
class="expiration-duration"
|
||||
as |el|
|
||||
>
|
||||
<select
|
||||
id={{el.id}}
|
||||
{{on "change" (pick "target.value" el.setValue)}}
|
||||
class="custom-select"
|
||||
>
|
||||
{{#each this.expirationDurations as |duration|}}
|
||||
<option value={{duration.id}} selected={{eq el.value duration.id}}>
|
||||
{{t duration.labelTranslation}}
|
||||
</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</form.element>
|
||||
<form.element
|
||||
@controlType="checkbox"
|
||||
@label={{t "create.settings.anonymousUser.label"}}
|
||||
@showValidationOn="change"
|
||||
@property="anonymousUser"
|
||||
class="anonymous-user"
|
||||
/>
|
||||
<form.element
|
||||
@controlType="checkbox"
|
||||
@label={{t "create.settings.forceAnswer.label"}}
|
||||
@showValidationOn="change"
|
||||
@property="forceAnswer"
|
||||
class="force-answer"
|
||||
/>
|
||||
|
||||
<div class="row cr-steps-bottom-nav">
|
||||
<div class="col-6 col-md-8 order-12">
|
||||
<SaveButton
|
||||
@isPending={{form.isSubmitting}}
|
||||
data-test-button="submit"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-6 col-md-4 order-1 text-right">
|
||||
<BackButton @onClick={{this.previousPage}} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<BsModal
|
||||
@onHidden={{this.resetSavingPollFailedState}}
|
||||
@onSubmit={{form.submit}}
|
||||
@open={{this.savingPollFailed}}
|
||||
data-test-modal="saving-failed"
|
||||
as |modal|
|
||||
>
|
||||
<modal.header
|
||||
@closeButton={{false}}
|
||||
@title={{t "error.poll.savingFailed.title"}}
|
||||
/>
|
||||
<modal.body>
|
||||
<p>
|
||||
{{t "error.poll.savingFailed.description"}}
|
||||
</p>
|
||||
</modal.body>
|
||||
<modal.footer>
|
||||
<BsButton @onClick={{modal.close}} data-test-button="abort">
|
||||
{{t "action.abort"}}
|
||||
</BsButton>
|
||||
<SaveButton
|
||||
@isPending={{form.isSubmitting}}
|
||||
@onClick={{modal.submit}}
|
||||
data-test-button="retry"
|
||||
type="button"
|
||||
>
|
||||
{{t "modal.save-retry.button-retry"}}
|
||||
</SaveButton>
|
||||
</modal.footer>
|
||||
</BsModal>
|
||||
</BsForm>
|
||||
</div>
|
||||
<CreateSettings @poll={{@model}} />
|
Loading…
Reference in a new issue