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">
|
<CreateSettings @poll={{@model}} />
|
||||||
<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>
|
|
Loading…
Reference in a new issue