diff --git a/app/adapters/application.js b/app/adapters/application.js index eab4de54..56139f60 100644 --- a/app/adapters/application.js +++ b/app/adapters/application.js @@ -1,9 +1,11 @@ /* eslint-disable ember/classic-decorator-no-classic-methods, ember/no-computed-properties-in-native-classes, ember/no-get */ +import classic from 'ember-classic-decorator'; import JSONAPIAdapter from '@ember-data/adapter/json-api'; import config from 'prison-rideshare-ui/config/environment'; import { computed } from '@ember/object'; import { inject as service } from '@ember/service'; +@classic export default class ApplicationAdapter extends JSONAPIAdapter { @service session; diff --git a/app/app.js b/app/app.js index 9c7ab0bb..6a2dc056 100644 --- a/app/app.js +++ b/app/app.js @@ -1,3 +1,5 @@ +import 'decorator-transforms/globals'; + import Application from '@ember/application'; import Resolver from 'ember-resolver'; import loadInitializers from 'ember-load-initializers'; diff --git a/app/components/alert.gjs b/app/components/alert.gjs new file mode 100644 index 00000000..8223436e --- /dev/null +++ b/app/components/alert.gjs @@ -0,0 +1,30 @@ +import Component from '@glimmer/component'; +import { HdsAlert } from '@hashicorp/design-system-components/components'; + +export default class Alert extends Component { + get color() { + return this.args.color ?? 'critical'; + } + + get type() { + return this.args.type ?? 'inline'; + } + + +} diff --git a/app/components/app-frame.gjs b/app/components/app-frame.gjs new file mode 100644 index 00000000..f59bae14 --- /dev/null +++ b/app/components/app-frame.gjs @@ -0,0 +1,11 @@ +import { hash } from '@ember/helper'; +import Header from 'prison-rideshare-ui/components/app-frame/header'; +import Sidebar from 'prison-rideshare-ui/components/app-frame/sidebar'; +import Main from 'prison-rideshare-ui/components/app-frame/main'; + + diff --git a/app/components/app-frame/header.gjs b/app/components/app-frame/header.gjs new file mode 100644 index 00000000..c395ecbe --- /dev/null +++ b/app/components/app-frame/header.gjs @@ -0,0 +1,6 @@ + diff --git a/app/components/app-frame/main.gjs b/app/components/app-frame/main.gjs new file mode 100644 index 00000000..52e4c37f --- /dev/null +++ b/app/components/app-frame/main.gjs @@ -0,0 +1,6 @@ + diff --git a/app/components/app-frame/sidebar.gjs b/app/components/app-frame/sidebar.gjs new file mode 100644 index 00000000..da493c69 --- /dev/null +++ b/app/components/app-frame/sidebar.gjs @@ -0,0 +1,6 @@ + diff --git a/app/components/app-side-nav.gjs b/app/components/app-side-nav.gjs new file mode 100644 index 00000000..85296d40 --- /dev/null +++ b/app/components/app-side-nav.gjs @@ -0,0 +1,73 @@ +import { action } from '@ember/object'; +import Component from '@glimmer/component'; +import { on } from '@ember/modifier'; +import { hash } from '@ember/helper'; +import List from 'prison-rideshare-ui/components/app-side-nav/list'; + +// See docs/helios-overrides.md +export default class AppSideNavComponent extends Component { + constructor(owner, args) { + super(owner, args); + + this.args?.onRegister?.(this); + } + + willDestroy() { + super.willDestroy(...arguments); + + this.args?.onRegister?.(null); + } + + get isMinimized() { + return Boolean(this.args.isMinimized); + } + + get isResponsive() { + return Boolean(this.args.isResponsive); + } + + get state() { + return this.isMinimized ? 'closed' : 'open'; + } + + get navClasses() { + return `app-side-nav app-side-nav--${this.state}`; + } + + get showScrim() { + return this.isResponsive && !this.isMinimized; + } + + @action + toggleMinimizedStatus() { + const nextState = !this.isMinimized; + + this.args?.onToggleMinimizedStatus?.(nextState); + } + + +} diff --git a/app/components/app-side-nav/item.gjs b/app/components/app-side-nav/item.gjs new file mode 100644 index 00000000..705f98cc --- /dev/null +++ b/app/components/app-side-nav/item.gjs @@ -0,0 +1,6 @@ + diff --git a/app/components/app-side-nav/link.gjs b/app/components/app-side-nav/link.gjs new file mode 100644 index 00000000..91cdb672 --- /dev/null +++ b/app/components/app-side-nav/link.gjs @@ -0,0 +1,40 @@ +import Component from '@glimmer/component'; +import LinkTo from '@ember/routing/link-component'; + +// See docs/helios-overrides.md +export default class AppSideNavLinkComponent extends Component { + get hasModel() { + return this.args.model !== undefined && this.args.model !== null; + } + + +} diff --git a/app/components/app-side-nav/list.gjs b/app/components/app-side-nav/list.gjs new file mode 100644 index 00000000..7a841e65 --- /dev/null +++ b/app/components/app-side-nav/list.gjs @@ -0,0 +1,43 @@ +import Component from '@glimmer/component'; +import { hash } from '@ember/helper'; +import Link from 'prison-rideshare-ui/components/app-side-nav/link'; +import Item from 'prison-rideshare-ui/components/app-side-nav/item'; +import { action } from '@ember/object'; +import { inject as service } from '@ember/service'; +import { on } from '@ember/modifier'; + +// See docs/helios-overrides.md +export default class AppSideNavListComponent extends Component { + @service sidebar; + + @action handleNavClick(event) { + if (typeof window === 'undefined') { + return; + } + + const isDesktop = window.matchMedia?.('(min-width: 1088px)')?.matches; + + if (isDesktop) { + return; + } + + if (event.target.closest('.app-side-nav__link')) { + this.sidebar.open = false; + this.sidebar.setNavMinimizedState(true); + } + } + + +} diff --git a/app/components/calendar-day.gjs b/app/components/calendar-day.gjs index 40fdad73..811901d3 100644 --- a/app/components/calendar-day.gjs +++ b/app/components/calendar-day.gjs @@ -17,6 +17,7 @@ export default class CalendarDay extends Component { @person={{this.person}} @count={{this.count}} @setViewingSlot={{this.setViewingSlot}} + @setError={{this.setError}} /> {{/each}} diff --git a/app/components/calendar-slot.gjs b/app/components/calendar-slot.gjs index fcb91acb..09a17dcd 100644 --- a/app/components/calendar-slot.gjs +++ b/app/components/calendar-slot.gjs @@ -1,24 +1,19 @@ -/* eslint-disable ember/no-classic-classes, ember/no-classic-components, ember/no-get, ember/require-tagless-components */ -import classic from 'ember-classic-decorator'; import { inject as service } from '@ember/service'; -import { reads } from '@ember/object/computed'; -import Component from '@ember/component'; -import { get, computed } from '@ember/object'; +import Component from '@glimmer/component'; import formatBriefTimespan from 'prison-rideshare-ui/utils/format-brief-timespan'; -import moment from 'moment'; +import moment from 'moment-timezone'; import { task } from 'ember-concurrency'; import gt from 'ember-truth-helpers/helpers/gt'; -import PaperCheckbox from 'ember-paper/components/paper-checkbox'; import perform from 'ember-concurrency/helpers/perform'; import { fn } from '@ember/helper'; import { on } from '@ember/modifier'; +import { HdsFormCheckboxField } from '@hashicorp/design-system-components/components'; -@classic export default class CalendarSlot extends Component { @@ -52,38 +52,51 @@ export default class CalendarSlot extends Component { @service store; - @reads('commitment') - isCommittedTo; + get slot() { + return this.args.slot; + } + + get person() { + return this.args.person; + } + + get count() { + return this.args.count; + } + + get setViewingSlot() { + return this.args.setViewingSlot; + } + + get isCommittedTo() { + return Boolean(this.commitment); + } - @computed('person.id', 'slot.commitments.@each.person') get commitment() { - const personId = this.get('person.id'); + const personId = this.person?.id; - return this.get('slot.commitments').find( - (slot) => slot.belongsTo('person').id() == personId, + return this.slot?.commitments?.find( + (commitment) => commitment.belongsTo('person').id() == personId, ); } - @computed('slot.{start,end}') get timespan() { return formatBriefTimespan( this.moment, - this.get('slot.start'), - this.get('slot.end'), + this.slot?.start, + this.slot?.end, false, ); } - @computed('slot.isNotFull', 'isCommittedTo') get hidden() { - return !this.get('slot.isNotFull') && !this.isCommittedTo; + return !this.slot?.isNotFull && !this.isCommittedTo; } - @computed('isCommittedTo', 'slot.{isNotFull,start}', 'toggle.isRunning') get disabled() { - const isNotFull = this.get('slot.isNotFull'); - const start = this.get('slot.start'); - const toggleIsRunning = this.get('toggle.isRunning'); + const isNotFull = this.slot?.isNotFull; + const start = this.slot?.start; + const toggleIsRunning = this.toggle.isRunning; if (toggleIsRunning) { return true; @@ -96,31 +109,40 @@ export default class CalendarSlot extends Component { } } - @computed('slot.{count,commitments.length}') get capacity() { - const dividend = this.get('slot.commitments.length'); + const dividend = this.slot?.commitments?.length ?? 0; - const count = this.get('slot.count'); + const count = this.slot?.count; const divisor = count === 0 ? '∞' : count; return `${dividend}/${divisor}`; } - @(task(function* () { + @(task(function* (event) { + event.preventDefault(); + + // This is a hack to restore the correct checked status if saving fails. + const checkbox = event.target; + if (this.isCommittedTo) { try { yield this.commitment.destroyRecord(); + this.args.setError(undefined); this.toasts.show( `Cancelled your agreement to drive on ${moment( - this.get('slot.start'), + this.slot?.start, ).format('MMMM D')}`, ); } catch (error) { - const errorDetail = get(error, 'errors.firstObject.detail'); - this.toasts.show(errorDetail || 'Couldn’t save your change'); + if (checkbox) { + checkbox.checked = true; + } + + const errorDetail = error?.errors?.[0]?.detail; + this.args.setError(errorDetail || 'Couldn’t save your change'); } - } else if (this.get('slot.isNotFull')) { + } else if (this.slot?.isNotFull) { const newRecord = this.store.createRecord('commitment', { slot: this.slot, person: this.person, @@ -129,15 +151,20 @@ export default class CalendarSlot extends Component { try { yield newRecord.save(); + this.args.setError(undefined); this.toasts.show( - `Thanks for agreeing to drive on ${moment( - this.get('slot.start'), - ).format('MMMM D')}!`, + `Thanks for agreeing to drive on ${moment(this.slot?.start).format( + 'MMMM D', + )}!`, ); } catch (error) { - const errorDetail = get(error, 'errors.firstObject.detail'); - this.toasts.show(errorDetail || 'Couldn’t save your change'); + const errorDetail = error?.errors?.[0]?.detail; + this.args.setError(errorDetail || 'Couldn’t save your change'); newRecord.destroyRecord(); + + if (checkbox) { + checkbox.checked = false; + } } } }).drop()) diff --git a/app/components/calendar/about.gjs b/app/components/calendar/about.gjs new file mode 100644 index 00000000..8861097e --- /dev/null +++ b/app/components/calendar/about.gjs @@ -0,0 +1,31 @@ +import { HdsCardContainer } from '@hashicorp/design-system-components/components'; + +const CalendarAboutComponent = ; + +export default CalendarAboutComponent; diff --git a/app/components/calendar/edit-person.gjs b/app/components/calendar/edit-person.gjs new file mode 100644 index 00000000..c65b4c9b --- /dev/null +++ b/app/components/calendar/edit-person.gjs @@ -0,0 +1,218 @@ +import Component from '@glimmer/component'; +import { + HdsFormRadioField, + HdsFormTextInputField, + HdsFormTextareaField, + HdsFormToggleField, +} from '@hashicorp/design-system-components/components'; +import { on } from '@ember/modifier'; +import { fn } from '@ember/helper'; +import eq from 'ember-truth-helpers/helpers/eq'; +import gt from 'ember-truth-helpers/helpers/gt'; +import { action } from '@ember/object'; + +export default class CalendarEditPersonComponent extends Component { + @action updatePersonAttribute(attribute, event) { + const target = event?.target; + + if (!target) { + return; + } + + const value = target.type === 'checkbox' ? target.checked : target.value; + this.args.person.set(attribute, value); + } + + +} diff --git a/app/components/cancellation-form.gjs b/app/components/cancellation-form.gjs index df3b0371..0ee0f4a4 100644 --- a/app/components/cancellation-form.gjs +++ b/app/components/cancellation-form.gjs @@ -3,16 +3,19 @@ import classic from 'ember-classic-decorator'; import { action } from '@ember/object'; import Component from '@ember/component'; import reasonToIcon from 'prison-rideshare-ui/utils/reason-to-icon'; -import PaperDialog from 'ember-paper/components/paper-dialog'; -import PaperDialogContent from 'ember-paper/components/paper-dialog-content'; -import PaperCard from 'ember-paper/components/paper-card'; -import PaperForm from 'ember-paper/components/paper-form'; -import PaperButton from 'ember-paper/components/paper-button'; -import paperIcon from 'ember-paper/components/paper-icon'; -import PaperCheckbox from 'ember-paper/components/paper-checkbox'; -import PaperSelect from 'ember-paper/components/paper-select/component'; -import PaperDialogActions from 'ember-paper/components/paper-dialog-actions'; +import { + HdsModal, + HdsButton, + HdsButtonSet, + HdsFormCheckboxField, + HdsFormTextInputField, + HdsFormSelectField, + HdsIcon, +} from '@hashicorp/design-system-components/components'; +import { on } from '@ember/modifier'; import { fn } from '@ember/helper'; +import eq from 'ember-truth-helpers/helpers/eq'; +import Alert from 'prison-rideshare-ui/components/alert'; const reasons = Object.keys(reasonToIcon).sort(); const shortcuts = ['driver not found', 'visitor']; @@ -27,100 +30,139 @@ const shortcutReasonToIcon = shortcuts.reduce( @classic export default class CancellationForm extends Component { + reasons = reasons; + shortcutReasonToIcon = shortcutReasonToIcon; + - reasons = reasons; - shortcutReasonToIcon = shortcutReasonToIcon; @action - cancelledChanged(cancelled) { - if (!cancelled) { + handleSubmit(event) { + event?.preventDefault?.(); + this.save?.(); + } + + @action + handleCancel(event) { + event?.preventDefault?.(); + this.cancel?.(); + } + + @action + toggleCheckbox(property, event) { + const checked = event?.target?.checked ?? false; + this.set(`ride.${property}`, checked); + if (!checked) { this.set('ride.cancellationReason', null); } - - this.set('ride.cancelled', cancelled); } @action cancelViaShortcut(reason) { this.set('ride.cancelled', true); this.set('ride.cancellationReason', reason); - this.save(); + this.save?.(); } - @action updateCancellationReason(reason) { - this.set('ride.cancellationReason', reason); + @action + updateCancellationReason(event) { + this.set('ride.cancellationReason', event?.target?.value); } } diff --git a/app/components/donation-icon.gjs b/app/components/donation-icon.gjs deleted file mode 100644 index 716a9dcb..00000000 --- a/app/components/donation-icon.gjs +++ /dev/null @@ -1,11 +0,0 @@ -/* eslint-disable ember/no-classic-classes, ember/no-classic-components */ -import classic from 'ember-classic-decorator'; -import { tagName } from '@ember-decorators/component'; -import Component from '@ember/component'; -import paperIcon from 'ember-paper/components/paper-icon'; - -@classic -@tagName('') -export default class DonationIcon extends Component { - -} diff --git a/app/components/linked-contact.gjs b/app/components/linked-contact.gjs index aa519d6b..eed4cb99 100644 --- a/app/components/linked-contact.gjs +++ b/app/components/linked-contact.gjs @@ -1,24 +1,18 @@ -/* eslint-disable ember/no-classic-classes, ember/no-classic-components, ember/require-tagless-components */ -import classic from 'ember-classic-decorator'; -import { tagName } from '@ember-decorators/component'; -import { computed } from '@ember/object'; import Ember from 'ember'; -import Component from '@ember/component'; +import Component from '@glimmer/component'; import { htmlSafe } from '@ember/string'; -@classic -@tagName('span') export default class LinkedContact extends Component { - @computed('contact') + get link() { const phonePattern = /(\([0-9]{3}\)\s?|[0-9]{3}-?\s?)[0-9]{3}-?\s?[0-9]{4}/g; - const contact = this.contact; + const contact = this.args.contact; if (contact) { return htmlSafe( diff --git a/app/components/person-badge.gjs b/app/components/person-badge.gjs index df376dc8..d9c120c8 100644 --- a/app/components/person-badge.gjs +++ b/app/components/person-badge.gjs @@ -1,77 +1,113 @@ -/* eslint-disable ember/no-classic-classes, ember/no-classic-components, ember/require-tagless-components */ -import classic from 'ember-classic-decorator'; import { action } from '@ember/object'; -import { classNames } from '@ember-decorators/component'; -import Component from '@ember/component'; +import Component from '@glimmer/component'; import eq from 'ember-truth-helpers/helpers/eq'; -import paperIcon from 'ember-paper/components/paper-icon'; +import { + HdsButton, + HdsIcon, +} from '@hashicorp/design-system-components/components'; +import { on } from '@ember/modifier'; +import { tracked } from '@glimmer/tracking'; -@classic -@classNames('person-badge') export default class PersonBadge extends Component { + @tracked showContact = false; + + @action + toggleContact() { + if (!this.isDestroying && !this.isDestroyed) { + this.showContact = !this.showContact; + } + } + - showContact = false; - - @action - toggleContact() { - if (!this.isDestroying && !this.isDestroyed) { - this.toggleProperty('showContact'); - } - } } diff --git a/app/components/person-row.gjs b/app/components/person-row.gjs deleted file mode 100644 index 80ceaede..00000000 --- a/app/components/person-row.gjs +++ /dev/null @@ -1,118 +0,0 @@ -/* eslint-disable ember/no-classic-classes, ember/no-classic-components, ember/no-get */ -import classic from 'ember-classic-decorator'; -import { tagName } from '@ember-decorators/component'; -import { action, computed } from '@ember/object'; -import { inject as service } from '@ember/service'; -import Component from '@ember/component'; -import PaperSwitch from 'ember-paper/components/paper-switch'; -import CopyButton from 'ember-cli-clipboard/components/copy-button'; -import paperIcon from 'ember-paper/components/paper-icon'; -import momentFormat from 'ember-moment/helpers/moment-format'; -import PaperButton from 'ember-paper/components/paper-button'; - -@classic -@tagName('') -export default class PersonRow extends Component { - - @service - toasts; - - @computed('person.medium') - get emailClass() { - return `email ${ - this.get('person.medium') === 'email' ? 'is-preferred' : '' - }`; - } - - @computed('person.medium') - get mobileClass() { - return `mobile ${ - this.get('person.medium') === 'mobile' ? 'is-preferred' : '' - }`; - } - - @computed('person.medium') - get landlineClass() { - return `landline ${ - this.get('person.medium') === 'landline' ? 'is-preferred' : '' - }`; - } - - @action edit() { - this.editPerson(this.person); - } - - @action - copied() { - this.toasts.show('Copied address'); - } - - @action - toggleActiveness(active) { - this.set('person.active', active); - this.person.save().catch(() => { - this.toasts.show( - `There was an error saving the active status of ${this.get( - 'person.name', - )}`, - ); - }); - } -} diff --git a/app/components/reimbursement-form.gjs b/app/components/reimbursement-form.gjs index d22652c2..0702d7df 100644 --- a/app/components/reimbursement-form.gjs +++ b/app/components/reimbursement-form.gjs @@ -1,47 +1,107 @@ -import PaperDialog from 'ember-paper/components/paper-dialog'; -import PaperDialogContent from 'ember-paper/components/paper-dialog-content'; -import PaperForm from 'ember-paper/components/paper-form'; -import PaperCheckbox from 'ember-paper/components/paper-checkbox'; -import PaperDialogActions from 'ember-paper/components/paper-dialog-actions'; -import PaperButton from 'ember-paper/components/paper-button'; -import { fn } from '@ember/helper'; - +} + +export default ReimbursementForm; diff --git a/app/components/request-time-chart.gjs b/app/components/request-time-chart.gjs index 5ed909ca..979f9a24 100644 --- a/app/components/request-time-chart.gjs +++ b/app/components/request-time-chart.gjs @@ -3,7 +3,7 @@ import classic from 'ember-classic-decorator'; import { classNames } from '@ember-decorators/component'; import { computed } from '@ember/object'; import Component from '@ember/component'; -import moment from 'moment'; +import moment from 'moment-timezone'; import HighCharts from 'ember-highcharts/components/high-charts'; @classic diff --git a/app/components/requests-and-reimbursements-chart.gjs b/app/components/requests-and-reimbursements-chart.gjs index 8f424d58..d302735c 100644 --- a/app/components/requests-and-reimbursements-chart.gjs +++ b/app/components/requests-and-reimbursements-chart.gjs @@ -3,11 +3,8 @@ import classic from 'ember-classic-decorator'; import { action, computed } from '@ember/object'; import { equal } from '@ember/object/computed'; import Component from '@ember/component'; -import moment from 'moment'; +import moment from 'moment-timezone'; import HighCharts from 'ember-highcharts/components/high-charts'; -// import EmberWormhole from 'ember-wormhole/components/ember-wormhole'; -// import PaperButton from 'ember-paper/components/paper-button'; -// import eq from 'ember-truth-helpers/helpers/eq'; function countRidesOrVisitors(rides, grouping) { if (grouping === 'rides') { @@ -28,17 +25,8 @@ export default class RequestsAndReimbursementsChart extends Component { @theme={{this.theme}} @callback={{this.afterRenderCallback}} /> - - {{#if this.rendered}} - {{!-- - - - - - --}} - {{/if}} - // timeGrouping = 'months'; + rendered = false; @equal('timeGrouping', 'weeks') isWeeks; @@ -77,7 +65,6 @@ export default class RequestsAndReimbursementsChart extends Component { @computed('grouping', 'timeGroupKeys', 'timeGrouping', 'timeGroups') get data() { const timeGroups = this.timeGroups; - // const timeGrouping = this.timeGrouping; const grouping = this.grouping; return [ @@ -148,7 +135,6 @@ export default class RequestsAndReimbursementsChart extends Component { get options() { return { title: { - // text: `Ride distances and expenses, grouped into or (broken time axis)`, text: `Ride distances and expenses, grouped into months`, useHTML: true, }, diff --git a/app/components/ride-form.gjs b/app/components/ride-form.gjs index 605de7ec..3cc965e4 100644 --- a/app/components/ride-form.gjs +++ b/app/components/ride-form.gjs @@ -4,263 +4,404 @@ import { action, computed } from '@ember/object'; import { inject as service } from '@ember/service'; import { alias } from '@ember/object/computed'; import Component from '@ember/component'; -import moment from 'moment'; +import moment from 'moment-timezone'; import formatTimespan from 'prison-rideshare-ui/utils/format-timespan'; import parseTimespan from 'prison-rideshare-ui/utils/parse-timespan'; import deduplicateVisitorSuggestions from 'prison-rideshare-ui/utils/deduplicate-visitor-suggestions'; -import PaperDialog from 'ember-paper/components/paper-dialog'; -import PaperDialogContent from 'ember-paper/components/paper-dialog-content'; -import PaperCard from 'ember-paper/components/paper-card'; -import PaperForm from 'ember-paper/components/paper-form'; -import paperIcon from 'ember-paper/components/paper-icon'; -import PaperButton from 'ember-paper/components/paper-button'; +import { + HdsModal, + HdsButton, + HdsButtonSet, + HdsForm, + HdsFormSectionMultiFieldGroup, + HdsFormSuperSelectSingleField, + HdsFormTextInputField, + HdsFormTextareaField, + HdsFormCheckboxField, + HdsFormRadioGroup, + HdsFormField, + HdsIcon, + HdsSegmentedGroup, +} from '@hashicorp/design-system-components/components'; import { on } from '@ember/modifier'; import { fn } from '@ember/helper'; -import PaperSelect from 'ember-paper/components/paper-select/component'; -import PaperCheckbox from 'ember-paper/components/paper-checkbox'; -import PaperRadioGroup from 'ember-paper/components/paper-radio-group'; -import PaperAutocompleteHighlight from 'ember-paper/components/paper-autocomplete/highlight/component'; -import PaperDialogActions from 'ember-paper/components/paper-dialog-actions'; +import eq from 'ember-truth-helpers/helpers/eq'; +import gt from 'ember-truth-helpers/helpers/gt'; +import { tracked } from '@glimmer/tracking'; +import { scheduleTask } from 'ember-lifeline'; +import Alert from 'prison-rideshare-ui/components/alert'; const DATETIME_LOCAL_FORMAT = 'YYYY-MM-DDTHH:mm'; +const SelectedRideVisitor = ; + @classic export default class RideForm extends Component { + lastSearchTerm = null; + lastSearchPromise = null; + overrideTimespan = false; + + @tracked pendingUnmatchedVisitorName = ''; + @tracked visitorSelection = null; + + @computed('ride.name', 'visitorSelection') + get nameOrVisitorSelection() { + if (this.visitorSelection) { + return this.visitorSelection; + } + + const name = this.get('ride.name'); + + return name ? { name } : null; + } + + @service('institutions') institutionsService; @@ -329,8 +481,6 @@ export default class RideForm extends Component { @service('store') store; - overrideTimespan = false; - @computed('ride.{cancellationReason,complete}') get editingWarning() { const reason = this.get('ride.cancellationReason'); @@ -353,6 +503,27 @@ export default class RideForm extends Component { return start && end && start < new Date(); } + @computed('ride.validationErrors.{start.[],end.[]}') + get timespanValidationErrors() { + const validationErrors = this.get('ride.validationErrors') || {}; + const startErrors = validationErrors.start || []; + const endErrors = validationErrors.end || []; + + return [...startErrors, ...endErrors]; + } + + @computed('overrideTimespan', 'timespanValidationErrors.length') + get shouldShowTimespanValidationErrors() { + return !this.overrideTimespan && this.timespanValidationErrors.length > 0; + } + + @computed('timespanWarning', 'shouldShowTimespanValidationErrors') + get timespanFieldIsInvalid() { + return Boolean( + this.timespanWarning || this.shouldShowTimespanValidationErrors, + ); + } + @computed('ride.{start,end}') get rideTimes() { if (this.get('ride.start') && this.get('ride.end')) { @@ -367,16 +538,27 @@ export default class RideForm extends Component { @computed('ride.start') get startTimeString() { - return moment(this.get('ride.start')).format(DATETIME_LOCAL_FORMAT); + const start = this.get('ride.start'); + if (!start) { + return ''; + } + + return moment(start).format(DATETIME_LOCAL_FORMAT); } @computed('ride.end') get endTimeString() { - return moment(this.get('ride.end')).format(DATETIME_LOCAL_FORMAT); + const end = this.get('ride.end'); + if (!end) { + return ''; + } + + return moment(end).format(DATETIME_LOCAL_FORMAT); } @action - timespanUpdated(value) { + timespanUpdated(event) { + const value = event?.target?.value ?? ''; this.set('ride.timespan', value); const parsed = parseTimespan(value); @@ -391,24 +573,105 @@ export default class RideForm extends Component { } } - @action - searchRides(name) { - return this.store.query('ride', { 'filter[name]': name }).then((rides) => { - return deduplicateVisitorSuggestions(rides); - }); + @action searchRides(term) { + const trimmedTerm = term?.trim() ?? ''; + + if (!trimmedTerm) { + this.lastSearchTerm = null; + this.lastSearchPromise = null; + return []; + } + + if (trimmedTerm === this.lastSearchTerm && this.lastSearchPromise) { + return this.lastSearchPromise; + } + + const promise = this.store + .query('ride', { 'filter[name]': term }) + .then((rides) => { + const suggestions = deduplicateVisitorSuggestions(rides); + return this.buildVisitorOptions(trimmedTerm, suggestions); + }); + + this.lastSearchTerm = trimmedTerm; + this.lastSearchPromise = promise; + + return promise; } @action - autocompleteSelectionChanged(ride) { + visitorSelected(ride) { + if (ride?.customVisitor) { + this.visitorSelection = null; + this.set('ride.name', ride.name); + this.pendingUnmatchedVisitorName = ''; + return; + } + + this.visitorSelection = ride; + this.pendingUnmatchedVisitorName = ''; + if (ride) { this.set('ride.name', ride.get('name')); this.set('ride.address', ride.get('address')); this.set('ride.contact', ride.get('contact')); + } else { + this.set('ride.name', ''); + this.set('ride.address', ''); + this.set('ride.contact', ''); + } + } + + @action storeVisitorName(enteredText) { + this.pendingUnmatchedVisitorName = enteredText?.trim?.() ?? ''; + } + + @action maybeStoreUnmatchedVisitorName() { + scheduleTask(this, 'actions', () => { + if (this.visitorSelection) { + this.pendingUnmatchedVisitorName = ''; + return; + } + + let existingName = this.get('ride.name'); + + if ( + this.pendingUnmatchedVisitorName && + existingName !== this.pendingUnmatchedVisitorName + ) { + this.set('ride.name', this.pendingUnmatchedVisitorName); + } + this.pendingUnmatchedVisitorName = ''; + }); + } + + buildVisitorOptions(term, suggestions) { + const manualOption = this.createManualVisitorOption(term); + if (manualOption) { + return [...suggestions, manualOption]; + } + return suggestions; + } + + createManualVisitorOption(name) { + const value = name?.trim?.() ?? ''; + if (!value) { + return null; } + return { + id: `manual-visitor-${value.toLowerCase()}`, + customVisitor: true, + name: value, + }; } @action - updateStartTime(value) { + updateStartTime(event) { + const value = event?.target?.value; + if (!value) { + return; + } + this.set( 'ride.start', new Date(moment(value, DATETIME_LOCAL_FORMAT).valueOf()), @@ -416,14 +679,43 @@ export default class RideForm extends Component { } @action - updateEndTime(value) { + updateEndTime(event) { + const value = event?.target?.value; + if (!value) { + return; + } + this.set( 'ride.end', new Date(moment(value, DATETIME_LOCAL_FORMAT).valueOf()), ); } - @action updateRideProperty(property, value) { + @action + updateRideProperty(property, event) { + this.set(`ride.${property}`, event.target.value); + } + + @action + updateRidePropertyWithValue(property, value) { this.set(`ride.${property}`, value); } + + @action + toggleCheckbox(property, event) { + const checked = event?.target?.checked ?? false; + this.set(`ride.${property}`, checked); + } + + @action + handleSubmit(event) { + event?.preventDefault?.(); + this.save?.(); + } + + @action + handleCancel(event) { + event?.preventDefault?.(); + this.cancel?.(); + } } diff --git a/app/components/ride-person.gjs b/app/components/ride-person.gjs index eecafdf6..72dc9815 100644 --- a/app/components/ride-person.gjs +++ b/app/components/ride-person.gjs @@ -1,55 +1,54 @@ -/* eslint-disable ember/no-classic-components */ -import classic from 'ember-classic-decorator'; -import { tagName } from '@ember-decorators/component'; -import { action, computed } from '@ember/object'; +import { action } from '@ember/object'; import { inject as service } from '@ember/service'; -import { alias } from '@ember/object/computed'; -import Component from '@ember/component'; +import Component from '@glimmer/component'; import PersonBadge from 'prison-rideshare-ui/components/person-badge'; -import PaperSelect from 'ember-paper/components/paper-select/component'; +import { HdsFormSuperSelectSingleField } from '@hashicorp/design-system-components/components'; -@classic -@tagName('') export default class RidePerson extends Component { + @service('people') peopleService; + + get person() { + return this.args.ride.get(this.args.property); + } + + get placeholder() { + return this.args.property === 'driver' ? 'Driver' : 'Car Owner'; + } + + @action + clear() { + const ride = this.args.ride; + ride.set(this.args.property, null); + return ride.save(); + } + - @service('people') - peopleService; - - @alias('peopleService.active') - people; - - @computed('ride', 'property', 'ride.{carOwner.id,driver.id}') - get person() { - return this.ride.get(this.property); - } - - showContact = false; - - @action - clear() { - const ride = this.ride; - ride.set(this.property, null); - return ride.save(); - } } diff --git a/app/components/ride-row.gjs b/app/components/ride-row.gjs index 7c5894fb..c4bb71f3 100644 --- a/app/components/ride-row.gjs +++ b/app/components/ride-row.gjs @@ -1,283 +1,298 @@ /* eslint-disable ember/no-classic-components, ember/no-get */ import classic from 'ember-classic-decorator'; -import { tagName } from '@ember-decorators/component'; import { action, computed } from '@ember/object'; import { inject as service } from '@ember/service'; import Component from '@ember/component'; +import { tagName } from '@ember-decorators/component'; import reasonToIcon from 'prison-rideshare-ui/utils/reason-to-icon'; import fetch from 'fetch'; import ScrollTo from 'prison-rideshare-ui/components/scroll-to'; -import paperIcon from 'ember-paper/components/paper-icon'; import LinkedContact from 'prison-rideshare-ui/components/linked-contact'; -import PaperButton from 'ember-paper/components/paper-button'; import RidePerson from 'prison-rideshare-ui/components/ride-person'; +import ReimbursementUnit from 'prison-rideshare-ui/components/reimbursement-unit'; +import { + HdsButton, + HdsIcon, +} from '@hashicorp/design-system-components/components'; +import { on } from '@ember/modifier'; +import { fn } from '@ember/helper'; import or from 'ember-truth-helpers/helpers/or'; import not from 'ember-truth-helpers/helpers/not'; -import eq from 'ember-truth-helpers/helpers/eq'; import and from 'ember-truth-helpers/helpers/and'; -import ReimbursementUnit from 'prison-rideshare-ui/components/reimbursement-unit'; -import { fn } from '@ember/helper'; - -const mediumIcon = { - txt: 'textsms', - email: 'email', - phone: 'phone', -}; +import eq from 'ember-truth-helpers/helpers/eq'; @classic @tagName('') export default class RideRow extends Component { - @computed( - 'uncombinable', - 'ride.{isCombined,isDivider,enabled,requiresConfirmation}', - 'commitments.[]', - ) - get classAttribute() { - return `ride ${this.get('ride.enabled') ? 'enabled' : ''} ${ - this.uncombinable ? 'uncombinable' : '' - } ${this.get('ride.isCombined') ? 'combined' : ''} ${ - this.get('ride.isDivider') ? 'divider' : '' - } ${ - this.get('ride.requiresConfirmation') || this.get('commitments.length') - ? 'highlighted' - : '' - }`; - } @service moment; @@ -291,27 +306,44 @@ export default class RideRow extends Component { @service store; - // TODO this is unfortunate but without it ignoring doesn’t make the overlap immediately disappear + clearing = false; + + @computed( + 'uncombinable', + 'ride.{isCombined,isDivider,enabled,requiresConfirmation}', + 'commitments.length', + ) + get rowClass() { + return `ride ${this.ride.enabled ? 'enabled' : ''} ${ + this.uncombinable ? 'uncombinable' : '' + } ${this.ride.isCombined ? 'combined' : ''} ${ + this.ride.isDivider ? 'divider' : '' + } ${ + this.ride.requiresConfirmation || this.commitments.length + ? 'highlighted' + : '' + }`; + } + @computed('overlaps.overlaps.data.@each.id', 'ride') get commitments() { return this.overlaps.commitmentsForRide(this.ride); } - clearing = false; - @computed('ride.insertedAt') get creation() { - const insertedAt = this.get('ride.insertedAt'); - + const insertedAt = this.ride.insertedAt; return this.moment.moment(insertedAt).format('ddd MMM D YYYY h:mma'); } - @computed('ride.cancellationReason') + @computed('ride.{enabled,cancellationReason}') get cancellationIcon() { - const reason = this.get('ride.cancellationReason'); - const icon = reasonToIcon[reason]; + if (this.ride.enabled) { + return 'slash'; + } - return icon || 'help'; + const reason = this.ride.cancellationReason; + return reasonToIcon[reason] ?? 'alert-circle'; } @computed('ride.{enabled,cancellationReason}') @@ -323,40 +355,49 @@ export default class RideRow extends Component { } } + @computed('ride.{enabled,cancellationReason}') + get cancellationState() { + return this.ride.enabled ? 'not-cancelled' : this.ride.cancellationReason; + } + @computed('ride.id', 'rideToCombine.id') - get combineButtonLabel() { - if (this.get('ride.id') == this.get('rideToCombine.id')) { + get combineButtonTitle() { + if (this.ride.id === this.rideToCombine?.id) { return 'Cancel combining'; - } else { - return 'Combine with another ride'; } + + return 'Combine with another ride'; } @computed('rideToCombine.{id,start}', 'ride.start') get uncombinable() { const sixHours = 1000 * 60 * 60 * 6; - const rideToCombineStart = this.get('rideToCombine.start'); + const rideToCombineStart = this.rideToCombine?.start; if (!rideToCombineStart) { return false; - } else { - return ( - Math.abs( - new Date(rideToCombineStart).getTime() - - new Date(this.get('ride.start')).getTime(), - ) > sixHours - ); } - } - @computed('ride.medium') - get mediumIcon() { - return mediumIcon[this.get('ride.medium')]; + return ( + Math.abs( + new Date(rideToCombineStart).getTime() - + new Date(this.ride.start).getTime(), + ) > sixHours + ); } @computed('ride.medium') - get mediumIconTitle() { - return `ride was requested via ${this.get('ride.medium')}`; + get mediumIcon() { + switch (this.ride.medium) { + case 'txt': + return 'message-circle'; + case 'email': + return 'mail'; + case 'phone': + return 'phone'; + default: + return null; + } } @action @@ -392,7 +433,7 @@ export default class RideRow extends Component { commitmentJson.relationships.person.data.id, ); - this.send('setDriver', person); + this.setDriver(person); } @action @@ -401,16 +442,14 @@ export default class RideRow extends Component { let url = `${ride.store .adapterFor('ride') .buildURL('ride', ride.id)}/ignore/${commitmentJson.id}`; - let token = this.get('session.data.authenticated.access_token'); + let token = this.session.data?.authenticated?.access_token; fetch(url, { method: 'POST', headers: { Authorization: `Bearer ${token}`, }, - }).then(() => { - return this.overlaps.fetch(); - }); + }).then(() => this.overlaps.fetch()); } @action @@ -420,16 +459,6 @@ export default class RideRow extends Component { return ride.save(); } - @action - match(option, searchTerm) { - const name = option.name; - const result = (name || '') - .toLowerCase() - .startsWith(searchTerm.toLowerCase()); - - return result ? 1 : -1; - } - @action toggleCreation() { this.toggleProperty('showCreation'); @@ -440,7 +469,8 @@ export default class RideRow extends Component { this.set('clearing', true); } - @action unproposeClear() { + @action + unproposeClear() { this.set('clearing', false); } diff --git a/app/components/toolbar-header.gjs b/app/components/toolbar-header.gjs index ef5446a6..f57732c4 100644 --- a/app/components/toolbar-header.gjs +++ b/app/components/toolbar-header.gjs @@ -1,87 +1,99 @@ -/* eslint-disable ember/no-classic-components, ember/require-tagless-components */ -import classic from 'ember-classic-decorator'; -import { action, computed } from '@ember/object'; +import { action } from '@ember/object'; import { inject as service } from '@ember/service'; -import { alias } from '@ember/object/computed'; -import Component from '@ember/component'; -import PaperToolbar from 'ember-paper/components/paper-toolbar'; -import PaperButton from 'ember-paper/components/paper-button'; -import paperIcon from 'ember-paper/components/paper-icon'; -import pluralize from 'ember-inflector/lib/helpers/pluralize'; -import PaperChips from 'ember-paper/components/paper-chips/component'; +import Component from '@glimmer/component'; +import { on } from '@ember/modifier'; import { pageTitle } from 'ember-page-title'; +import { + HdsBadgeCount, + HdsButton, + HdsTag, +} from '@hashicorp/design-system-components/components'; +import { inject as controller } from '@ember/controller'; +import { runTask } from 'ember-lifeline'; -@classic export default class ToolbarHeader extends Component { - - @service - session; + @controller application; + @service sidebar; - @service - sidebar; - - @alias('sidebar.open') - sidebarOpen; + get isSandbox() { + return window.location.hostname.indexOf('sandbox') > -1; + } - @computed - get chips() { - const hostname = window.location.hostname; + get toggleLabel() { + return this.sidebar.open ? 'Close navigation menu' : 'Open navigation menu'; + } - if (hostname.indexOf('sandbox') > -1) { - return [ - { - label: 'Sandbox', - title: - 'All data on this instance is erased daily. If some type of example data would be useful for you, let Buck know.', - }, - ]; - } else { - return []; - } + get showToggleBadge() { + return !this.sidebar.open && Boolean(this.sidebar.notificationCount); } @action toggleSidebar() { - this.toggleProperty('sidebarOpen'); + const navExpanded = + Boolean(this.sidebar.navComponent) && !this.sidebar.navIsMinimized; + + if (navExpanded) { + const collapsed = this.sidebar.collapseNavIfNeeded(); + + this.sidebar.open = false; + + if (!collapsed) { + this.sidebar.setNavMinimizedState(true); + } + } else { + this.sidebar.open = true; + runTask(this.sidebar, this.sidebar.expandNavIfNeeded); + } } + + } diff --git a/app/controllers/admin-calendar.js b/app/controllers/admin-calendar.js index 12eafdc0..f9e21531 100644 --- a/app/controllers/admin-calendar.js +++ b/app/controllers/admin-calendar.js @@ -5,7 +5,7 @@ import CalendarController from './calendar'; import { alias, mapBy, setDiff, sum } from '@ember/object/computed'; import { A } from '@ember/array'; import fetch from 'fetch'; -import moment from 'moment'; +import moment from 'moment-timezone'; import { get, action, computed } from '@ember/object'; import RSVP from 'rsvp'; @@ -40,6 +40,8 @@ export default class AdminCalendarController extends CalendarController { @service toasts; + errorMessage = undefined; + @computed('month') get previousMonth() { return moment(this.month).add(-1, 'M').format(format); @@ -113,6 +115,7 @@ export default class AdminCalendarController extends CalendarController { commitment .save() .then(() => { + this.set('errorMessage', undefined); this.toasts.show( `Committed ${person.get('name')} to drive on ${moment( slot.get('start'), @@ -121,7 +124,7 @@ export default class AdminCalendarController extends CalendarController { }) .catch((error) => { const errorDetail = get(error, 'errors.firstObject.detail'); - this.toasts.show(errorDetail || 'Couldn’t save your change'); + this.set('errorMessage', errorDetail || 'Couldn’t save your change'); }); } @@ -133,11 +136,12 @@ export default class AdminCalendarController extends CalendarController { commitment .destroyRecord() .then(() => { + this.set('errorMessage', undefined); this.toasts.show(`Deleted ${name}’s commitment on ${date}`); }) .catch((error) => { const errorDetail = get(error, 'errors.firstObject.detail'); - this.toasts.show(errorDetail || 'Couldn’t save your change'); + this.set('errorMessage', errorDetail || 'Couldn’t save your change'); }); } diff --git a/app/controllers/application.js b/app/controllers/application.js index dfb5157a..91ce4466 100644 --- a/app/controllers/application.js +++ b/app/controllers/application.js @@ -3,6 +3,7 @@ import classic from 'ember-classic-decorator'; import { action, computed } from '@ember/object'; import { inject as service } from '@ember/service'; import Controller, { inject as controller } from '@ember/controller'; +import { tracked } from '@glimmer/tracking'; @classic export default class ApplicationController extends Controller { @@ -24,6 +25,8 @@ export default class ApplicationController extends Controller { @controller rides; + @tracked headerElement; + @computed('overlaps.count', 'rides.model.@each.requiresConfirmation') get ridesBadgeCount() { let rides = this.get('rides.model') || []; diff --git a/app/controllers/calendar.js b/app/controllers/calendar.js index af62bea1..d0fa0d31 100644 --- a/app/controllers/calendar.js +++ b/app/controllers/calendar.js @@ -5,11 +5,13 @@ import { inject as service } from '@ember/service'; import { alias } from '@ember/object/computed'; import Controller from '@ember/controller'; -import moment from 'moment'; +import moment from 'moment-timezone'; import { task } from 'ember-concurrency'; @classic export default class CalendarController extends Controller { + error = undefined; + @service toasts; @@ -53,8 +55,9 @@ export default class CalendarController extends Controller { this.toasts.show('Saved your details'); this.set('showPerson', false); + this.set('error', undefined); } catch (e) { - this.toasts.show('Couldn’t save your details'); + this.set('error', 'Couldn’t save your details'); } }).drop()) savePerson; @@ -64,4 +67,8 @@ export default class CalendarController extends Controller { this.set('showPerson', false); this.person.rollbackAttributes(); } + + @action setError(error) { + this.set('error', error); + } } diff --git a/app/controllers/debts.js b/app/controllers/debts.js index 6f432cdd..02ff4162 100644 --- a/app/controllers/debts.js +++ b/app/controllers/debts.js @@ -9,4 +9,78 @@ export default class DebtsController extends Controller { reimburse(debt) { return debt.destroyRecord(); } + + get tableColumns() { + return [ + { key: 'person', label: 'Person', isExpandable: true }, + { key: 'food', label: 'Food' }, + { key: 'car', label: 'Car' }, + { key: 'total', label: 'Total' }, + { key: 'actions', label: '' }, + ]; + } + + get tableRows() { + const debts = this.model ?? []; + + return debts.map((debt) => { + const person = debt.get('person'); + + const rideRows = (debt.descendingRides ?? []).map((ride) => { + const matchingReimbursements = (ride.reimbursements ?? []).filter( + (reimbursement) => { + return reimbursement.get('person.id') === debt.get('person.id'); + }, + ); + + const sumAmounts = (items, property) => { + return items + .map((item) => Number(item.get(property) ?? 0)) + .reduce((total, value) => total + value, 0); + }; + + const foodReimbursedValue = sumAmounts( + matchingReimbursements, + 'foodExpensesDollars', + ); + const carReimbursedValue = sumAmounts( + matchingReimbursements, + 'carExpensesDollars', + ); + + const food = + ride.get('driver.id') === debt.get('person.id') + ? ride.foodExpensesDollars + : ''; + const car = + ride.get('carOwner.id') === debt.get('person.id') + ? ride.carExpensesDollars + : ''; + + return { + type: 'ride', + debt, + ride, + person, + food, + car, + donation: + ride.donation && ride.get('carOwner.id') === debt.get('person.id'), + carReimbursed: carReimbursedValue > 0 ? carReimbursedValue : null, + foodReimbursed: foodReimbursedValue > 0 ? foodReimbursedValue : null, + }; + }); + + return { + type: 'person', + debt, + person, + food: debt.foodExpensesDollars, + car: debt.carExpensesDollars, + total: debt.totalExpensesDollars, + isOpen: true, + children: rideRows, + }; + }); + } } diff --git a/app/controllers/drivers.js b/app/controllers/drivers.js index fd880f15..b9660e41 100644 --- a/app/controllers/drivers.js +++ b/app/controllers/drivers.js @@ -1,6 +1,6 @@ /* eslint-disable ember/no-classic-classes, ember/no-get */ import classic from 'ember-classic-decorator'; -import { action } from '@ember/object'; +import { action, computed } from '@ember/object'; import { inject as service } from '@ember/service'; import Controller from '@ember/controller'; import BufferedProxy from 'ember-buffered-proxy/proxy'; @@ -10,7 +10,72 @@ export default class DriversController extends Controller { @service store; + @service + toasts; + showInactive = false; + sortProp = 'name'; + sortDir = 'asc'; + errorMessage = undefined; + + @computed('model.@each.{name,lastRide}', 'sortProp', 'sortDir') + get sortedPeople() { + const people = this.model ? this.model.toArray() : []; + const sorted = people.slice().sort((a, b) => { + let comparison; + + switch (this.sortProp) { + case 'lastRide': + comparison = this.compareByLastRide(a, b); + break; + case 'name': + default: + comparison = this.compareByName(a, b); + break; + } + + if (comparison !== 0) { + return comparison; + } + + return this.compareByName(a, b); + }); + + if (this.sortDir === 'desc') { + sorted.reverse(); + } + + return sorted; + } + + compareByName(a, b) { + return (a.name || '') + .toLowerCase() + .localeCompare((b.name || '').toLowerCase()); + } + + compareByLastRide(a, b) { + const aStart = a.lastRide?.start + ? new Date(a.lastRide.start).getTime() + : null; + const bStart = b.lastRide?.start + ? new Date(b.lastRide.start).getTime() + : null; + + if (aStart === bStart) { + return 0; + } + + if (aStart === null) { + return 1; + } + + if (bStart === null) { + return -1; + } + + return aStart - bStart; + } @action newPerson() { @@ -30,15 +95,23 @@ export default class DriversController extends Controller { } @action - savePerson() { + savePerson(event) { + event?.preventDefault(); + const proxy = this.editingPerson; proxy.applyBufferedChanges(); return proxy .get('content') .save() - .then(() => this.set('editingPerson', undefined)) + .then(() => { + this.set('editingPerson', undefined); + this.set('errorMessage', undefined); + }) .catch(() => { - // FIXME this is handled for ride-saving failures, how to generalise? + this.set( + 'errorMessage', + 'There was an error saving this driver. Please try again.', + ); }); } @@ -52,4 +125,55 @@ export default class DriversController extends Controller { this.set('editingPerson', undefined); } + + @action + toggleShowInactive(event) { + const checked = event?.target?.checked ?? false; + this.set('showInactive', checked); + } + + @action + updatePersonActiveness(person, event) { + const checked = event?.target?.checked ?? false; + + person.set('active', checked); + person + .save() + .then(() => { + this.set('errorMessage', undefined); + }) + .catch(() => { + this.set( + 'errorMessage', + `There was an error saving the active status of ${ + person.name ?? 'this driver' + }.`, + ); + }); + } + + @action + updateEditingPerson(field, event) { + const value = event?.target?.value ?? ''; + + const editingPerson = this.editingPerson; + if (editingPerson) { + editingPerson.set(field, value); + } + } + + @action + copyAddressSuccess() { + this.toasts.show('Copied address'); + } + + @action + sort(property) { + if (this.sortProp === property) { + this.set('sortDir', this.sortDir === 'asc' ? 'desc' : 'asc'); + } else { + this.set('sortProp', property); + this.set('sortDir', 'asc'); + } + } } diff --git a/app/controllers/forgot.js b/app/controllers/forgot.js index 2457b455..a13bdbef 100644 --- a/app/controllers/forgot.js +++ b/app/controllers/forgot.js @@ -8,6 +8,7 @@ import fetch from 'fetch'; @classic export default class ForgotController extends Controller { email = undefined; + error = undefined; @service store; @@ -15,7 +16,10 @@ export default class ForgotController extends Controller { @service toasts; - @action editEmail(value) { + @action + updateEmail(event) { + const value = event?.target?.value ?? ''; + this.set('email', value); } @@ -32,8 +36,25 @@ export default class ForgotController extends Controller { method: 'POST', }); - query.then(() => { - this.toasts.show('Check your email'); - }); + this.set('error', undefined); + + query + .then((response) => { + if (response.ok) { + this.toasts.show('Check your email'); + return; + } + + return response.json().then((json) => { + const message = json?.errors?.[0]?.detail; + this.set( + 'error', + message || 'There was an error sending the reset email.', + ); + }); + }) + .catch(() => { + this.set('error', 'There was an error sending the reset email.'); + }); } } diff --git a/app/controllers/institutions.js b/app/controllers/institutions.js index 3bfe0c38..efefb526 100644 --- a/app/controllers/institutions.js +++ b/app/controllers/institutions.js @@ -10,6 +10,40 @@ export default class InstitutionsController extends Controller { @service store; + get tableColumns() { + return [ + { key: 'name', label: 'Name' }, + { key: 'far', label: 'Far' }, + { key: 'actions', label: '' }, + ]; + } + + get tableRows() { + const institutions = this.model; + + return institutions + .filter((institution) => !institution.isNew) + .sort((a, b) => { + const aName = (a?.name ?? '').toLowerCase(); + const bName = (b?.name ?? '').toLowerCase(); + + if (aName < bName) { + return -1; + } + + if (aName > bName) { + return 1; + } + + return 0; + }) + .map((institution) => { + return { + institution, + }; + }); + } + @action newInstitution() { this.set( @@ -28,7 +62,9 @@ export default class InstitutionsController extends Controller { } @action - saveInstitution() { + saveInstitution(event) { + event?.preventDefault?.(); + const proxy = this.editingInstitution; proxy.applyBufferedChanges(); return proxy @@ -39,7 +75,9 @@ export default class InstitutionsController extends Controller { } @action - cancelInstitution() { + cancelInstitution(event) { + event?.preventDefault?.(); + const model = this.get('editingInstitution.content'); if (model.get('isNew')) { diff --git a/app/controllers/log.js b/app/controllers/log.js index 5c1d3d6d..2e7f73cf 100644 --- a/app/controllers/log.js +++ b/app/controllers/log.js @@ -30,12 +30,15 @@ export default class LogController extends Controller { this.set('editingPost', proxy); } - @action updatePostBody(body) { + @action + updatePostBody(body) { this.editingPost.set('body', JSON.stringify(body)); } @action - savePost() { + savePost(event) { + event?.preventDefault?.(); + const proxy = this.editingPost; proxy.applyBufferedChanges(); return proxy @@ -46,7 +49,9 @@ export default class LogController extends Controller { } @action - cancelPost() { + cancelPost(event) { + event?.preventDefault?.(); + const model = this.get('editingPost.content'); if (model.get('isNew')) { diff --git a/app/controllers/login.js b/app/controllers/login.js index 27842ad3..2bb33070 100644 --- a/app/controllers/login.js +++ b/app/controllers/login.js @@ -1,21 +1,23 @@ -import classic from 'ember-classic-decorator'; import { action } from '@ember/object'; import { inject as service } from '@ember/service'; import Controller from '@ember/controller'; +import { tracked } from '@glimmer/tracking'; -@classic export default class LoginController extends Controller { - @service - session; + @service session; + + @tracked error; @action - updateEmail(value) { - this.model.set('email', value); + updateEmail(event) { + const value = event.target.value; + this.model.email = value; } @action - updatePassword(value) { - this.model.set('password', value); + updatePassword(event) { + const value = event.target.value; + this.model.password = value; } @action @@ -24,6 +26,8 @@ export default class LoginController extends Controller { const user = this.model; + this.error = undefined; + this.session .authenticate( 'authenticator:application', @@ -31,7 +35,8 @@ export default class LoginController extends Controller { user.get('password'), ) .catch((error) => { - this.set('error', error); + this.error = + error?.errors?.[0]?.detail ?? 'There was an error logging you in.'; }); } } diff --git a/app/controllers/register.js b/app/controllers/register.js index bde5870f..8950f1e8 100644 --- a/app/controllers/register.js +++ b/app/controllers/register.js @@ -1,13 +1,31 @@ /* eslint-disable ember/no-get */ -import classic from 'ember-classic-decorator'; import { inject as service } from '@ember/service'; import Controller from '@ember/controller'; import { get, action } from '@ember/object'; +import { tracked } from '@glimmer/tracking'; -@classic export default class RegisterController extends Controller { - @service - session; + @service session; + + @tracked error; + + @action + updateEmail(event) { + const value = event?.target?.value; + this.model.email = value; + } + + @action + updatePassword(event) { + const value = event?.target?.value; + this.model.password = value; + } + + @action + updatePasswordConfirmation(event) { + const value = event?.target?.value; + this.model.passwordConfirmation = value; + } @action register(event) { @@ -28,7 +46,7 @@ export default class RegisterController extends Controller { const errorText = get(error, 'errors.firstObject.detail') ?? 'There was an error registering you'; - this.set('error', errorText); + this.error = errorText; }); } } diff --git a/app/controllers/reimbursements.js b/app/controllers/reimbursements.js index 9f852fbc..42652d4d 100644 --- a/app/controllers/reimbursements.js +++ b/app/controllers/reimbursements.js @@ -6,7 +6,7 @@ import Controller from '@ember/controller'; import ReimbursementCollection from 'prison-rideshare-ui/utils/reimbursement-collection'; // import BufferedProxy from 'ember-buffered-proxy/proxy'; -import moment from 'moment'; +import moment from 'moment-timezone'; @classic export default class ReimbursementsController extends Controller { @@ -132,6 +132,89 @@ export default class ReimbursementsController extends Controller { return monthReimbursementCollections.sortBy('monthNumberString'); } + @computed('monthReimbursementCollections') + get monthTableRows() { + const collections = this.monthReimbursementCollections ?? []; + + return collections.reduce((rows, monthCollection) => { + const monthRow = { + type: 'month', + id: `month-${monthCollection.monthNumberString}`, + monthName: monthCollection.monthName, + clipboardText: monthCollection.clipboardText, + copyIconTitle: monthCollection.copyIconTitle, + }; + + rows.push(monthRow); + + monthCollection.reimbursementCollections.forEach( + (reimbursementCollection, index) => { + if ( + !reimbursementCollection || + reimbursementCollection.reimbursements.length === 0 + ) { + return; + } + + rows.push({ + type: 'person', + id: `month-${monthCollection.monthNumberString}-collection-${index}-${ + reimbursementCollection.get('person.id') ?? 'unknown' + }`, + name: reimbursementCollection.showName + ? (reimbursementCollection.get('person.name') ?? '') + : '', + foodExpenses: reimbursementCollection.donations + ? '' + : reimbursementCollection.foodExpensesDollars, + carExpenses: reimbursementCollection.carExpensesDollars, + totalExpenses: reimbursementCollection.totalExpensesDollars, + isDonation: Boolean(reimbursementCollection.donations), + clipboardText: reimbursementCollection.clipboardText, + copyIconTitle: reimbursementCollection.copyIconTitle, + reimbursementCollection, + }); + }, + ); + + return rows; + }, []); + } + + get monthTableColumns() { + return [ + { key: 'name', label: 'Person' }, + { key: 'food', label: 'Food' }, + { key: 'car', label: 'Car' }, + { key: 'total', label: 'Total' }, + { key: 'actions', label: '' }, + ]; + } + + @computed('processedReimbursements.@each.{insertedAt,donation}') + get processedTableRows() { + const reimbursements = this.processedReimbursements ?? []; + + return reimbursements.map((reimbursement) => { + return { + id: reimbursement.id, + date: reimbursement.insertedAt, + name: reimbursement.get('person.name') ?? '', + ride: reimbursement.ride, + foodExpensesDollars: reimbursement.foodExpensesDollars, + carExpensesDollars: reimbursement.carExpensesDollars, + donation: reimbursement.donation, + }; + }); + } + + @action + toggleShowProcessed(event) { + const checked = event?.target?.checked ?? false; + + this.set('showProcessed', checked); + } + @action processReimbursements(personAndReimbursements, donation) { personAndReimbursements.get('reimbursements').forEach((reimbursement) => { diff --git a/app/controllers/reports/new.js b/app/controllers/reports/new.js index 4e883b0b..dcbc1088 100644 --- a/app/controllers/reports/new.js +++ b/app/controllers/reports/new.js @@ -17,6 +17,20 @@ export default class NewController extends Controller { toasts; editingRide; + errorMessage = undefined; + + _setNumberProperty(property, event) { + const ride = this.editingRide; + + if (!ride) { + return; + } + + const rawValue = event?.target?.value ?? ''; + const parsedValue = rawValue === '' ? null : Number(rawValue); + + ride.set(property, Number.isNaN(parsedValue) ? null : parsedValue); + } @action setRide(ride) { @@ -28,12 +42,56 @@ export default class NewController extends Controller { } @action - submitReport() { + updateDistance(event) { + this._setNumberProperty('distance', event); + } + + @action + updateDonation(event) { + const ride = this.editingRide; + + if (!ride) { + return; + } + + const checked = event?.target?.checked ?? false; + + ride.set('donation', checked); + } + + @action + updateFoodExpenses(event) { + this._setNumberProperty('foodExpensesDollars', event); + } + + @action + updateCarExpenses(event) { + this._setNumberProperty('carExpensesDollars', event); + } + + @action + updateReportNotes(event) { + const ride = this.editingRide; + + if (!ride) { + return; + } + + const value = event?.target?.value ?? ''; + + ride.set('reportNotes', value); + } + + @action + submitReport(event) { + event?.preventDefault?.(); + let editingRide = this.editingRide; if (editingRide) { return editingRide.save().then( () => { + this.set('errorMessage', undefined); this.toasts.show('Your report was saved'); // Remove the ride from the store before reloading from the server @@ -44,11 +102,11 @@ export default class NewController extends Controller { window.scrollTo(0, 0); }, () => { - this.toasts.show('There was an error saving your report!'); + this.set('errorMessage', 'There was an error saving your report!'); }, ); } else { - this.toasts.show('Please choose a ride'); + this.set('errorMessage', 'Please choose a ride'); } } } diff --git a/app/controllers/reset.js b/app/controllers/reset.js index a262c1bd..aeb8b5de 100644 --- a/app/controllers/reset.js +++ b/app/controllers/reset.js @@ -8,6 +8,11 @@ import fetch from 'fetch'; @classic export default class ResetController extends Controller { + password = ''; + + passwordConfirmation = ''; + error = undefined; + @service session; @@ -17,6 +22,20 @@ export default class ResetController extends Controller { @service toasts; + @action + updatePassword(event) { + const value = event?.target?.value ?? ''; + + this.set('password', value); + } + + @action + updatePasswordConfirmation(event) { + const value = event?.target?.value ?? ''; + + this.set('passwordConfirmation', value); + } + @action submitReset(event) { event.preventDefault(); @@ -40,26 +59,32 @@ export default class ResetController extends Controller { }, }); - query.then((response) => { - if (response.ok) { - this.toasts.show('Changed your password, will now log you in'); - - response.json().then((json) => { - let email = get(json, 'data.attributes.email'); - - this.session.authenticate( - 'authenticator:application', - email, - this.password, - ); - }); - } else { - response.json().then((json) => { - let message = get(json, 'errors.firstObject.detail'); - - this.toasts.show(message || 'An unknown error occurred'); - }); - } - }); + this.set('error', undefined); + + query + .then((response) => { + if (response.ok) { + this.toasts.show('Changed your password, will now log you in'); + + response.json().then((json) => { + let email = get(json, 'data.attributes.email'); + + this.session.authenticate( + 'authenticator:application', + email, + this.password, + ); + }); + } else { + response.json().then((json) => { + let message = get(json, 'errors.firstObject.detail'); + + this.set('error', message || 'An unknown error occurred'); + }); + } + }) + .catch(() => { + this.set('error', 'There was an error updating your password.'); + }); } } diff --git a/app/controllers/rides.js b/app/controllers/rides.js index 35428532..3c7ca8db 100644 --- a/app/controllers/rides.js +++ b/app/controllers/rides.js @@ -1,24 +1,19 @@ -/* eslint-disable ember/no-get */ -import classic from 'ember-classic-decorator'; -import { action, computed } from '@ember/object'; +import { action } from '@ember/object'; import { inject as service } from '@ember/service'; -import { alias } from '@ember/object/computed'; import Controller from '@ember/controller'; import BufferedProxy from 'ember-buffered-proxy/proxy'; +import { tracked } from '@glimmer/tracking'; -@classic export default class RidesController extends Controller { - queryParams = { - showCompleted: 'completed', - showCancelled: 'cancelled', - - sortProp: 'sort', - sortDir: 'dir', - - search: { - replace: true, + queryParams = [ + { + showCompleted: { as: 'completed' }, + showCancelled: { as: 'cancelled' }, + sortProp: { as: 'sort' }, + sortDir: { as: 'dir' }, + search: { replace: true }, }, - }; + ]; @service('overlaps') overlapsService; @@ -32,30 +27,28 @@ export default class RidesController extends Controller { @service('people') peopleService; - @alias('peopleService.all') - people; - - editingRide; - editingCancellation; - showCompleted = false; - showCancelled = false; - sortProp = 'start'; - sortDir = 'asc'; - showCreation = false; - - @computed( - 'showCompleted', - 'showCancelled', - 'model.@each.{complete,enabled,isCombined}', - 'search', - 'sortDir', - ) + @tracked editingRide; + @tracked editingCancellation; + @tracked search = undefined; + @tracked showCompleted = false; + @tracked showCancelled = false; + @tracked sortProp = 'start'; + @tracked sortDir = 'asc'; + @tracked showCreation = false; + @tracked rideErrorMessage = undefined; + @tracked cancellationErrorMessage = undefined; + @tracked rideToCombine; + + get people() { + return this.peopleService.all; + } + get filteredRides() { const showCompleted = this.showCompleted, showCancelled = this.showCancelled; const search = this.search; - let rides = this.model.rejectBy('isCombined'); + let rides = this.model.rejectBy('isCombined').rejectBy('isNew'); if (!showCompleted) { rides = rides.filterBy('complete', false); @@ -71,46 +64,43 @@ export default class RidesController extends Controller { rides.setEach('isDivider', false); - const sorted = rides.sortBy('start'); + let sorted = rides.sortBy('start'); const sortDir = this.sortDir; const now = new Date(); + if (sortDir === 'desc') { + sorted = sorted.slice().reverse(); + } + if (sortDir === 'asc') { - const firstAfterNow = sorted.find((ride) => ride.get('start') > now); + const firstAfterNow = sorted.find((ride) => ride.start > now); if (firstAfterNow) { firstAfterNow.set('isDivider', true); } } else { - const reversed = sorted.reverse(); - const firstBeforeNow = reversed.find((ride) => ride.get('start') < now); + const firstBeforeNow = sorted.find((ride) => ride.start < now); if (firstBeforeNow) { firstBeforeNow.set('isDivider', true); } } - return rides; + return sorted; } @action newRide() { - this.set( - 'editingRide', - BufferedProxy.create({ - content: this.store.createRecord('ride'), - }), - ); + this.editingRide = BufferedProxy.create({ + content: this.store.createRecord('ride'), + }); } @action editRide(model) { - this.set( - 'editingRide', - BufferedProxy.create({ - content: model, - }), - ); + this.editingRide = BufferedProxy.create({ + content: model, + }); } @action @@ -118,42 +108,46 @@ export default class RidesController extends Controller { let buffer = proxy.buffer; proxy.applyBufferedChanges(); - return proxy - .get('content') + return proxy.content .save() - .then(() => this.set('editingRide', undefined)) + .then(() => { + this.editingRide = undefined; + this.rideErrorMessage = undefined; + return this.overlapsService.fetch(); + }) .catch(() => { - this.toasts.show('There was an error saving this ride'); + this.rideErrorMessage = 'There was an error saving this ride'; proxy.setProperties(buffer); - }) - .then(() => this.overlapsService.fetch()); + }); } @action cancel() { - const model = this.get('editingRide.content'); + const model = this.editingRide?.content; + + if (!model) { + return; + } - if (model.get('isNew')) { + if (model.isNew) { model.destroyRecord(); } else { model.rollbackAttributes(); } - this.editingRide.discardBufferedChanges(); - this.set('editingRide', undefined); + const editingRide = this.editingRide; + editingRide?.discardBufferedChanges(); + this.editingRide = undefined; } @action editCancellation(ride) { - this.set( - 'editingCancellation', - BufferedProxy.create({ - content: ride, - }), - ); - - if (ride.get('enabled')) { - this.set('editingCancellation.cancelled', true); + this.editingCancellation = BufferedProxy.create({ + content: ride, + }); + + if (ride.enabled) { + this.editingCancellation.set('cancelled', true); } } @@ -162,12 +156,15 @@ export default class RidesController extends Controller { let buffer = proxy.buffer; proxy.applyBufferedChanges(); - return proxy - .get('content') + return proxy.content .save() - .then(() => this.set('editingCancellation'), undefined) + .then(() => { + this.editingCancellation = undefined; + this.cancellationErrorMessage = undefined; + }) .catch(() => { - this.toasts.show('There was an error cancelling this ride'); + this.cancellationErrorMessage = + 'There was an error cancelling this ride'; proxy.content.rollbackAttributes(); proxy.setProperties(buffer); }); @@ -175,8 +172,9 @@ export default class RidesController extends Controller { @action cancelCancellation() { - this.editingCancellation.discardBufferedChanges(); - this.set('editingCancellation', undefined); + const editingCancellation = this.editingCancellation; + editingCancellation?.discardBufferedChanges(); + this.editingCancellation = undefined; } @action @@ -185,14 +183,16 @@ export default class RidesController extends Controller { const rideToCombine = this.rideToCombine; if (rideToCombine.id == ride.id) { - this.set('rideToCombine', undefined); + this.rideToCombine = undefined; } else { rideToCombine.set('combinedWith', ride); - rideToCombine.save().then(() => this.set('rideToCombine', undefined)); + rideToCombine.save().then(() => { + this.rideToCombine = undefined; + }); } } else { - this.set('rideToCombine', ride); + this.rideToCombine = ride; } } @@ -204,15 +204,31 @@ export default class RidesController extends Controller { @action updateSearch(value) { - this.set('search', value); + this.search = value; } @action clearSearch() { - this.set('search', undefined); + this.search = undefined; + } + + @action + updateSearchInput(event) { + const value = event?.target?.value ?? ''; + this.updateSearch(value); + } + + @action + sort(property) { + if (this.sortProp === property) { + this.sortDir = this.sortDir === 'asc' ? 'desc' : 'asc'; + } else { + this.sortProp = property; + this.sortDir = 'asc'; + } } @action toggle(propertyName) { - this.toggleProperty(propertyName); + this[propertyName] = !this[propertyName]; } } diff --git a/app/controllers/statistics.js b/app/controllers/statistics.js index 5f77953e..db0ef3f3 100644 --- a/app/controllers/statistics.js +++ b/app/controllers/statistics.js @@ -93,4 +93,23 @@ export default class StatisticsController extends Controller { this.set('start', moment().startOf('year').format('YYYY-MM-DD')); this.set('end', moment().format('YYYY-MM-DD')); } + + @action + updateStart(event) { + const value = event?.target?.value ?? ''; + + this.set('start', value); + } + + @action + updateEnd(event) { + const value = event?.target?.value ?? ''; + + this.set('end', value); + } + + @action + setGrouping(grouping) { + this.set('grouping', grouping); + } } diff --git a/app/controllers/users.js b/app/controllers/users.js index 75ce5407..64c4256a 100644 --- a/app/controllers/users.js +++ b/app/controllers/users.js @@ -1,15 +1,15 @@ -import classic from 'ember-classic-decorator'; import { action } from '@ember/object'; import { inject as service } from '@ember/service'; import Controller from '@ember/controller'; -@classic export default class UsersController extends Controller { @service session; @action - updateUserAdmin(user, admin) { + updateUserAdmin(user, event) { + const admin = event?.target?.checked; + user.set('admin', admin); user.save(); } diff --git a/app/index.html b/app/index.html index 461f08cc..154cb409 100644 --- a/app/index.html +++ b/app/index.html @@ -15,12 +15,7 @@
-
-
-
-
-
-
+
{{content-for "body"}} diff --git a/app/instance-initializers/custom-flight-icons.js b/app/instance-initializers/custom-flight-icons.js new file mode 100644 index 00000000..5dd0141a --- /dev/null +++ b/app/instance-initializers/custom-flight-icons.js @@ -0,0 +1,34 @@ +import config from 'ember-get-config'; +import customFlightIconSprite from 'prison-rideshare-ui/utils/custom-flight-icon-sprite'; + +const SPRITE_SELECTOR = '#custom-flight-icons-sprite'; + +function injectSprite(container, position) { + if (!container || container.querySelector(SPRITE_SELECTOR)) { + return; + } + + container.insertAdjacentHTML(position, customFlightIconSprite); +} + +export function initialize() { + if (typeof window === 'undefined') { + return; + } + + const document = window.document; + if (!document) { + return; + } + + if (config.environment === 'test') { + const testingContainer = document.getElementById('ember-testing'); + injectSprite(testingContainer, 'afterbegin'); + } else { + injectSprite(document.body, 'beforeend'); + } +} + +export default { + initialize, +}; diff --git a/app/instance-initializers/register-hds-intl.js b/app/instance-initializers/register-hds-intl.js new file mode 100644 index 00000000..580f0924 --- /dev/null +++ b/app/instance-initializers/register-hds-intl.js @@ -0,0 +1,9 @@ +import HdsIntl from 'prison-rideshare-ui/services/hdsintl'; + +export function initialize(appInstance) { + appInstance.register('service:hdsIntl', HdsIntl); +} + +export default { + initialize, +}; diff --git a/app/routes/admin-calendar.js b/app/routes/admin-calendar.js index 3ae3c6b6..ceeb2d5d 100644 --- a/app/routes/admin-calendar.js +++ b/app/routes/admin-calendar.js @@ -3,7 +3,7 @@ import classic from 'ember-classic-decorator'; import { inject as service } from '@ember/service'; import Route from '@ember/routing/route'; import RSVP from 'rsvp'; -import moment from 'moment'; +import moment from 'moment-timezone'; import AuthenticatedRoute from 'prison-rideshare-ui/mixins/authenticated-route'; @classic diff --git a/app/services/hdsintl.js b/app/services/hdsintl.js new file mode 100644 index 00000000..c0529361 --- /dev/null +++ b/app/services/hdsintl.js @@ -0,0 +1,9 @@ +import Service from '@ember/service'; + +// This is a stub to provide the translation helper, things break without it despite this not needing intl +export default class HdsIntl extends Service { + t(key, options) { + const { default: defaultString } = options; + return defaultString || key; + } +} diff --git a/app/services/sidebar.js b/app/services/sidebar.js index c473c6fe..416d8628 100644 --- a/app/services/sidebar.js +++ b/app/services/sidebar.js @@ -1,7 +1,9 @@ /* eslint-disable ember/no-classic-classes, ember/no-get */ import classic from 'ember-classic-decorator'; import { computed } from '@ember/object'; +import { tracked } from '@glimmer/tracking'; import Service, { inject as service } from '@ember/service'; +import { runTask } from 'ember-lifeline'; import ObjectProxy from '@ember/object/proxy'; import PromiseProxyMixin from '@ember/object/promise-proxy-mixin'; @@ -11,16 +13,17 @@ class ObjectPromiseProxy extends ObjectProxy.extend(PromiseProxyMixin) {} @classic export default class SidebarService extends Service { - @service - overlaps; - - @service - store; + @service overlaps; + @service session; + @service store; @service userSocket; - open = false; + @tracked navIsMinimized = true; + @tracked open = false; + + navComponent = null; @computed('userSocket.present.length') get userCount() { @@ -44,6 +47,17 @@ export default class SidebarService extends Service { }); } + @computed + get ridesRequest() { + return ObjectPromiseProxy.create({ + promise: this.store.findAll('ride').then((rides) => { + return { + rides, + }; + }), + }); + } + @computed('postsRequest.posts.@each.unread') get unreadCount() { let posts = this.get('postsRequest.posts'); @@ -55,9 +69,77 @@ export default class SidebarService extends Service { } } - @computed('userCount', 'unreadCount', 'overlaps.count') + @computed('ridesRequest.rides.@each.requiresConfirmation') + get requiresConfirmationCount() { + let rides = this.get('ridesRequest.rides'); + + if (rides) { + return rides.filterBy('requiresConfirmation').length; + } else { + return 0; + } + } + + @computed( + 'session.currentUser.admin', + 'userCount', + 'unreadCount', + 'overlaps.count', + 'requiresConfirmationCount', + ) get notificationCount() { - // TODO this is untested - return this.userCount + this.unreadCount + this.get('overlaps.count'); + if (this.session.get('currentUser.admin')) { + return ( + this.userCount + + this.unreadCount + + this.get('overlaps.count') + + this.requiresConfirmationCount + ); + } + + return 0; + } + + registerNavComponent(component) { + this.navComponent = component; + + // Defer tracked updates to avoid mutating after consumption during render + if (component) { + const nextState = Boolean(component.args?.isMinimized); + + if (this.navIsMinimized !== nextState) { + runTask(this, () => (this.navIsMinimized = nextState)); + } + + runTask(this, this.expandNavIfNeeded); + } else if (!this.navIsMinimized) { + runTask(this, () => (this.navIsMinimized = true)); + } + } + + setNavMinimizedState(isMinimized) { + this.navIsMinimized = isMinimized; + } + + expandNavIfNeeded() { + if (!this.open) { + return; + } + + if (!this.navComponent || !this.navIsMinimized) { + return; + } + + this.navComponent.toggleMinimizedStatus(); + } + + collapseNavIfNeeded() { + if (!this.navComponent || this.navIsMinimized) { + return false; + } + + this.navComponent.toggleMinimizedStatus(); + + return true; } } diff --git a/app/services/toasts.js b/app/services/toasts.js index 45e6b0a2..592e33e2 100644 --- a/app/services/toasts.js +++ b/app/services/toasts.js @@ -1,17 +1,64 @@ -/* eslint-disable ember/no-classic-classes */ -import classic from 'ember-classic-decorator'; -import Service, { inject as service } from '@ember/service'; +import Service from '@ember/service'; +import { tracked } from '@glimmer/tracking'; +import { action } from '@ember/object'; +import { cancelTask, runTask } from 'ember-lifeline'; import config from 'prison-rideshare-ui/config/environment'; -@classic +const DEFAULT_DURATION = 3000; + export default class ToastsService extends Service { - @service - paperToaster; - - show(message) { - this.paperToaster.show(message, { - duration: config.toastDuration, - position: 'top right', - }); + @tracked activeToast = null; + + #dismissTimer = null; + + show(message, options = {}) { + this.dismiss(); + + const toast = { + message, + }; + + this.activeToast = toast; + + const duration = this.resolveDuration(options.duration); + + if (duration !== false && duration !== null && duration !== undefined) { + this.#dismissTimer = runTask( + this, + () => { + if (this.activeToast === toast) { + this.dismiss(toast); + } + }, + duration, + ); + } + + return toast; + } + + @action + dismiss(toast = this.activeToast) { + if (!toast || this.activeToast !== toast) { + return; + } + + this.clearScheduledDismiss(); + this.activeToast = null; + } + + resolveDuration(duration) { + if (duration === undefined) { + return config.toastDuration ?? DEFAULT_DURATION; + } + + return duration; + } + + clearScheduledDismiss() { + if (this.#dismissTimer) { + cancelTask(this, this.#dismissTimer); + this.#dismissTimer = null; + } } } diff --git a/app/styles/app.scss b/app/styles/app.scss index 61fb55e3..9d4ab5ca 100644 --- a/app/styles/app.scss +++ b/app/styles/app.scss @@ -1,9 +1,6 @@ -@import 'color-palette'; +@import '@hashicorp/design-system-components'; +@import "ember-power-select"; -$primary: 'indigo'; - -@import 'ember-paper'; -@import 'paper-data-table'; @import 'ember-power-calendar'; @import 'loading'; @@ -12,10 +9,19 @@ $primary: 'indigo'; @import 'statistics'; @import 'log'; +@import 'toast'; body { + --token-app-header-height: 20px; + max-width: 100%; max-height: 100%; + + font-family: var(--token-typography-body-200-font-family); + font-size: var(--token-typography-body-200-font-size); + line-height: var(--token-typography-body-200-line-height); + + margin: 0; } body.ember-application { @@ -24,113 +30,359 @@ body.ember-application { display: flex; } -body > div.ember-view { - flex-direction: row; - flex: 1; +:root { + --app-header-height: 64px; + --app-sidebar-width: 13rem; + --app-header-bg: var(--token-color-palette-blue-500); +} + +html { + /* Reports and notes looked huge on iOS */ + -webkit-text-size-adjust: 100%; +} + +.app-frame--sidebar-closed { + --app-sidebar-width: 0px; +} + +.app-frame { + display: grid; + grid-template-columns: var(--app-sidebar-width) 1fr; + grid-template-rows: var(--app-header-height) 1fr; + grid-template-areas: + 'header header' + 'sidebar main'; + min-height: 100vh; + width: 100%; + background: #f8f9fb; +} + +.app-frame__header { + grid-area: header; + position: sticky; + top: 0; + z-index: 10; + max-width: 100vw; + overflow: hidden; +} + +.app-frame__sidebar { + grid-area: sidebar; + position: relative; + z-index: 5; +} + +.app-frame__main { + grid-area: main; + padding: 1rem; +} + +.session-button { + margin: 0.75rem; +} + +@media (max-width: 1079px) { + .app-frame { + grid-template-columns: 0 1fr; + } +} + +.app-header { + display: grid; + grid-template-columns: auto 1fr auto; + align-items: center; + gap: 1rem; + height: var(--app-header-height); + padding: 0 1rem; + background: var(--app-header-bg); + color: white; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12); +} + +.app-header__heading { + color: inherit; + font-size: 1.1rem; + font-weight: 600; + margin: 0; +} + +.app-header__title { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.app-header__actions { display: flex; + align-items: center; + gap: 0.75rem; + color: inherit; } -main { +@media (max-width: 640px) { + .app-header { + gap: 0.5rem; + padding: 0 0.75rem; + } + + .app-header__heading { + font-size: 1rem; + } + + .app-header__actions { + gap: 0.5rem; + flex-wrap: nowrap; + } +} + +.app-side-nav { + // Sidebar deviates from Helios: custom z-index, scrim inset, fixed positioning. + position: sticky; + top: var(--app-header-height); + height: calc(100vh - var(--app-header-height)); + pointer-events: none; +} + +.app-side-nav__panel { + background: white; + border-right: 1px solid #dfe3eb; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.06); + height: 100%; + width: var(--app-sidebar-width); position: relative; + z-index: 1; + transform: translateX(-100%); + transition: transform 180ms ease, opacity 180ms ease; +} + +.app-side-nav__scroll { + height: 100%; + overflow-y: auto; +} + +.app-side-nav__nav { + height: 100%; +} + +.app-side-nav__list { + list-style: none; + margin: 0; + padding: 0.5rem 0; display: flex; flex-direction: column; - flex-grow: 1; - overflow-x: scroll; + gap: 0.25rem; +} - md-content.no-overflow-scroll { - overflow-y: hidden; - } +.app-side-nav__item { + display: flex; + flex-direction: column; } -.site-nav-container { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; +.app-side-nav__link { + align-items: center; + border-left: 4px solid transparent; + color: #0f172a; + display: flex; + font-weight: 600; + gap: 0.5rem; + justify-content: space-between; + padding: 0.75rem 1.25rem; + text-decoration: none; } -md-toolbar { - md-chips { - margin-left: 1rem; +.app-side-nav__link:hover { + background: #f0f4ff; +} - .md-chips.md-chips { - box-shadow: none; - } +.app-side-nav__link--active { + background: #eef2fb; + border-left-color: #00194d; + color: #00194d; +} + +.app-side-nav__scrim { + background: rgba(0, 0, 0, 0.32); + border: none; + cursor: pointer; + display: none; + inset: var(--app-header-height) 0 0 0; + padding: 0; + position: fixed; + z-index: 0; +} + +.app-side-nav--open { + pointer-events: auto; +} + +.app-side-nav--open .app-side-nav__panel { + opacity: 1; + transform: translateX(0); + pointer-events: auto; +} + +.app-side-nav--closed .app-side-nav__panel { + opacity: 0; +} + +@media (min-width: 1080px) { + .app-side-nav { + pointer-events: auto; } - .count { - border-radius: 10px; - background: white; - color: paper-color($color-indigo, '600'); - font-size: 50%; - text-align: center; - width: 12px; - height: 12px; - position: absolute; - top: 10px; - left: 32px; + .app-side-nav__panel { + position: sticky; + top: 0; + transform: translateX(0); + opacity: 1; + } + + .app-side-nav--closed .app-side-nav__panel { + display: none; } } -md-sidenav { - width: 150px; - background-color: paper-color($color-indigo, '600'); +@media (max-width: 1079px) { + .app-side-nav { + height: auto; + inset: 0 0 0 0; + position: fixed; + top: var(--app-header-height); + z-index: 9; + } - md-content.md-default-theme, md-list { - background: transparent; + .app-side-nav__panel { + width: min(320px, 80vw); + max-width: min(320px, 80vw); + height: calc(100vh - var(--app-header-height)); + } - padding: 0; + .app-side-nav--open .app-side-nav__scrim { + display: block; + inset: var(--app-header-height) 0 0 min(320px, 80vw); } +} - .md-list-item-inner { - padding: 0; +.header-toggle { + position: relative; + display: inline-flex; + align-items: center; +} + +.header-toggle-button { + background-color: transparent; +} + +.header-toggle-badge { + position: absolute; + top: -1em; + right: -1rem; +} + +/* TODO why? */ +.hds-advanced-table__tbody { + padding-top: 41px; +} + +.rides-filters { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.75rem; + padding-bottom: 0.75rem; + margin: 0; + row-gap: 0.5rem; +} + +.rides-filters.hds-form__section, +.rides-filters.hds-form__section-multi-field-group { + gap: 0.5rem; + margin-bottom: 0; + row-gap: 0.5rem; +} + +.rides-filters .hds-form__section-multi-field-group-item:first-of-type { + flex: 1 1 16rem; + min-width: 12rem; + max-width: 30rem; +} + +.rides-filters .hds-form__section-multi-field-group-item { + flex: 0 1 auto; +} + +@media (max-width: 767px) { + .rides-filters { + flex-direction: column; align-items: stretch; + gap: 0.5rem; + flex-wrap: nowrap; + padding: 0 0.75rem 0.5rem; + row-gap: 0.5rem; } - md-list-item a, md-list-item.session .md-button { - display: flex; - align-items: center; - padding-left: 16px; + .rides-filters.hds-form__section, + .rides-filters.hds-form__section-multi-field-group { + gap: 0.5rem; + } + + .rides-filters .hds-form__section-multi-field-group-item { width: 100%; + flex: 0 0 auto; + } - text-decoration: none; - color: white; - white-space: nowrap; + .rides-filters .hds-form__section-multi-field-group-item:first-of-type { + max-width: none; + flex: 0 0 auto; + } +} - &:hover { - background-color: paper-color($color-indigo, '200'); - } +#ride-form-timespan { + resize: none; +} - &.active { - background-color: paper-color($color-indigo, '400'); - } +.rides-table { + width: 100%; + overflow-x: auto; + margin-top: 0; +} - .md-list-item-inner { - align-items: center; - } +@media (max-width: 640px) { + .hds-modal { + width: 100vw; + max-width: 100vw; + max-height: min(83vh, 90svh); + margin: 0; + border-radius: 0; + display: flex; + flex-direction: column; } - .rides, .users, .log { - display: flex; - width: 100%; - padding-right: 16px; - justify-content: space-between; - align-items: center; - .count { - border-radius: 10px; - background: white; - color: paper-color($color-indigo, '600'); - font-size: 50%; - text-align: center; - width: 10px; - height: 10px; - } + .hds-modal .hds-dialog-primitive__wrapper-body { + padding: 16px; + overflow: auto; + } + + .hds-modal .hds-dialog-primitive__wrapper-footer { + position: sticky; + bottom: 0; + background: white; } } -.rides table .ride { - opacity: 0.5; +.site-nav-container { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; +} + +table.rides tr { + td, th { + border-left-style: none; + border-right-style: none; + } .date-cell { .paper-icon { @@ -143,8 +395,16 @@ md-sidenav { } } - &+.notes { - opacity: 0.5; + &:has(+ .no-top-border) { + td { + border-bottom-style: none; + } + } + + &.no-top-border { + td { + border-top-style: none; + } } &.enabled { @@ -160,11 +420,17 @@ md-sidenav { } &.overlaps { - background-color: paper-color($color-pink, '100'); + background-color: pink; } - md-input-container { - margin: 18px 0 0; + .assignments { + display: flex; + align-items: flex-start; + gap: 0.5rem; + + .ember-power-select-trigger { + min-height: auto; + } } &.combined { @@ -182,8 +448,8 @@ md-sidenav { } } -tr.highlighted { - background-color: paper-color($color-pink, '100'); +.hds-table__tbody .hds-table__tr.highlighted { + background-color: pink; } .search { @@ -199,7 +465,7 @@ tr.highlighted { margin-bottom: 0; } -.rides table { +table.rides { .notes, .report, .overlap { td { border-top: 0; @@ -209,45 +475,26 @@ tr.highlighted { .report .distance::after { content: ' km'; } -} -.medium-row md-radio-group { - display: flex; - justify-content: space-between; - width: 100%; + .report td[colspan='6'], .overlap td[colspan='3'] { + .hds-icon { + transform: translateY(3px); + } + } } -md-table-container.debts .person .name { - font-weight: bold; -} +.report-form { + padding: 1rem; -.form-container { - padding: 16px; + [name='ride-selection'] label { + font-weight: 400; + } } .switch-container { padding: 16px; } -.hint { - position: absolute; - left: 2px; - right: auto; - bottom: 7px; - font-size: 12px; - line-height: 14px; - transition: all 0.3s cubic-bezier(0.55, 0, 0.55, 0.2); - color: grey; -} - -.visitor-autocomplete-option { - display: flex; - - > * { - padding-right: 1em; - } -} - .ride-person { display: inline-block; background-color: #e0e0e0; @@ -255,11 +502,6 @@ md-table-container.debts .person .name { border-radius: 16px; padding: 5px; - md-icon { - transform: translate(2px, -2px); - padding-right: 2px; - } - .remove-container { button { text-align: center; @@ -282,6 +524,23 @@ md-table-container.debts .person .name { } .person-badge { + cursor: pointer; + font-size: 13px; + + padding-right: 1.5rem; + + position: relative; + display: flex; + flex-direction: column; + + .clear { + position: absolute; + top: 0.15rem; + right: -0.25rem; + padding: 0; + min-height: auto; + } + a { color: inherit; text-decoration: none; @@ -291,11 +550,15 @@ md-table-container.debts .person .name { text-decoration: underline; } } -} -.commitments .md-chip-content .contact-container { - background: white; - color: black; + .row { + display: flex; + gap: 0.125rem; + svg { + transform: scale(0.75); + margin-top: 1px; + } + } } .person { @@ -311,24 +574,25 @@ md-table-container.debts .person .name { } .text-radio { - align-items: center; -} + display: grid; + grid-template-columns: 1fr auto; + align-items: end; + gap: 0.5rem 0.75rem; -md-table-container.reimbursements { - .month { - font-weight: bold; + .hds-form-radio-field { + margin: 0; + padding-bottom: 6px; /* align with input baseline */ } } -md-table-container { - .copy-btn { - border: 0; - background: transparent; - } +.person-form .layout-row { + gap: 1rem; + margin-bottom: 1rem; } -md-toast { - position: fixed; +.person-form .layout-row.text-radio .hds-form-field--layout-flag { + margin-left: 1rem; + margin-bottom: 0.5rem; } .unit { @@ -337,20 +601,10 @@ md-toast { font-size: 60%; } -md-option[aria-current] { - font-weight: bold; -} - -/* ng-enter is mysteriously present with pnpm migration, but is its original opacity: 0 ever needed? */ -md-backdrop.md-opaque.ng-enter { - opacity: 0.48; -} - -/* The dropdowns are mysteriously tiny…? And all bold. But probably abandoning Ember Paper */ -#ember-basic-dropdown-wormhole .ember-basic-dropdown-content { - transform: none !important; +.hds-modal input, #ride-search-input { + box-sizing: border-box; } -md-option[aria-current] { - font-weight: inherit; +.hds-modal.not-dismissible .hds-modal__dismiss { + display: none; } diff --git a/app/styles/calendar.scss b/app/styles/calendar.scss index 93309cb4..5bc5da67 100644 --- a/app/styles/calendar.scss +++ b/app/styles/calendar.scss @@ -9,19 +9,23 @@ padding: 3px; border-radius: 5rem; - background-color: $dark-contrast-color; + background-color: var(--token-color-palette-blue-500); color: white; cursor: pointer; &.committed-to { - background-color: paper-color($primary); + background-color: var(--token-color-palette-blue-200); } } .slot { transform: scale(0.7); + .hours { + font-weight: 400; + } + &.hidden { display: none; } diff --git a/app/styles/loading.scss b/app/styles/loading.scss index 74b58aed..417cf2b7 100644 --- a/app/styles/loading.scss +++ b/app/styles/loading.scss @@ -3,252 +3,41 @@ width: 100%; height: 100%; display: flex; + justify-content: center; + align-items: center; } -// Adapted from Michael Hobizal’s work here: https://codepen.io/mikehobizal/pen/RNVQbO +// Adapted from Temani Afif’s work here: https://css-loaders.com/hypnotic/ -@-webkit-keyframes clockwise { - 0% { - -webkit-transform: rotate(-45deg); - } - 100% { - -webkit-transform: rotate(315deg); - } -} -@-moz-keyframes clockwise { - 0% { - -moz-transform: rotate(-45deg); - } - 100% { - -moz-transform: rotate(315deg); - } -} -@keyframes clockwise { - 0% { - -webkit-transform: rotate(-45deg); - -moz-transform: rotate(-45deg); - -ms-transform: rotate(-45deg); - -o-transform: rotate(-45deg); - transform: rotate(-45deg); - } - 100% { - -webkit-transform: rotate(315deg); - -moz-transform: rotate(315deg); - -ms-transform: rotate(315deg); - -o-transform: rotate(315deg); - transform: rotate(315deg); - } -} -@-webkit-keyframes not-clockwise { - 0% { - -webkit-transform: rotate(45deg); - } - 100% { - -webkit-transform: rotate(-315deg); - } -} -@-moz-keyframes not-clockwise { - 0% { - -moz-transform: rotate(45deg); - } - 100% { - -moz-transform: rotate(-315deg); - } -} -@keyframes not-clockwise { - 0% { - -webkit-transform: rotate(45deg); - -moz-transform: rotate(45deg); - -ms-transform: rotate(45deg); - -o-transform: rotate(45deg); - transform: rotate(45deg); - } - 100% { - -webkit-transform: rotate(-315deg); - -moz-transform: rotate(-315deg); - -ms-transform: rotate(-315deg); - -o-transform: rotate(-315deg); - transform: rotate(-315deg); - } -} -@-webkit-keyframes lt { - 0% { - opacity: 1; - } - 25% { - opacity: 1; - } - 26% { - opacity: 0; - } - 75% { - opacity: 0; - } - 76% { - opacity: 1; - } - 100% { - opacity: 1; - } -} -@-moz-keyframes lt { - 0% { - opacity: 1; - } - 25% { - opacity: 1; - } - 26% { - opacity: 0; - } - 75% { - opacity: 0; - } - 76% { - opacity: 1; - } - 100% { - opacity: 1; - } -} -@keyframes lt { - 0% { - opacity: 1; - } - 25% { - opacity: 1; - } - 26% { - opacity: 0; - } - 75% { - opacity: 0; - } - 76% { - opacity: 1; - } - 100% { - opacity: 1; - } -} -.load-container { - width: 115px; - margin: auto; - font-size: 0; - position: relative; - -webkit-transform-origin: 50% 50%; - -moz-transform-origin: 50% 50%; - -ms-transform-origin: 50% 50%; - -o-transform-origin: 50% 50%; - transform-origin: 50% 50%; -} -.load-container:before { - position: absolute; - content: ''; - top: 0; - left: 0; - width: 60px; - height: 60px; - border: 5px solid #e5e5e5; - border-radius: 100%; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} -.load-container:after { - position: absolute; - content: ''; - z-index: -1; - top: 0; - right: 0; - width: 60px; - height: 60px; - border: 5px solid #e5e5e5; - border-radius: 100%; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; +.loader { + width: 50px; + height: 50px; + aspect-ratio: 1; + border: 2px solid var(--token-color-palette-blue-500); box-sizing: border-box; + border-radius: 50%; + display: grid; + animation: l11 2.5s infinite linear; + transform-origin: 50% 80%; } -.box, .lt, .rt, .lb, .rb { - position: relative; - display: inline-block; - overflow: hidden; - width: 60px; - height: 30px; - opacity: 1; -} -.box:before, .lt:before, .rt:before, .lb:before, .rb:before { - position: absolute; - content: ''; - width: 60px; - height: 60px; - border-top: 5px solid #3f51b5; - border-right: 5px solid transparent; - border-bottom: 5px solid transparent; - border-left: 5px solid transparent; - border-radius: 100%; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; +.loader:before, +.loader:after { + content: ""; + grid-area: 1/1; + border: inherit; + border-radius: 50%; + animation: inherit; + animation-duration: 1.5s; + transform-origin: inherit; + border-color: var(--token-color-palette-blue-400); } -.lt { - margin-right: -5px; - -webkit-animation: lt 2s linear -2000ms infinite; - -moz-animation: lt 2s linear -2000ms infinite; - animation: lt 2s linear -2000ms infinite; -} -.lt:before { - top: 0; - left: 0; - -webkit-animation: not-clockwise 1s linear infinite; - -moz-animation: not-clockwise 1s linear infinite; - animation: not-clockwise 1s linear infinite; -} - -.rt { - -webkit-animation: lt 2s linear -1000ms infinite; - -moz-animation: lt 2s linear -1000ms infinite; - animation: lt 2s linear -1000ms infinite; -} -.rt:before { - top: 0; - right: 0; - -webkit-animation: clockwise 1s linear infinite; - -moz-animation: clockwise 1s linear infinite; - animation: clockwise 1s linear infinite; -} - -.lb { - margin-right: -5px; - -webkit-animation: lt 2s linear -1500ms infinite; - -moz-animation: lt 2s linear -1500ms infinite; - animation: lt 2s linear -1500ms infinite; -} -.lb:before { - bottom: 0; - left: 0; - -webkit-animation: not-clockwise 1s linear infinite; - -moz-animation: not-clockwise 1s linear infinite; - animation: not-clockwise 1s linear infinite; -} - -.rb { - -webkit-animation: lt 2s linear -500ms infinite; - -moz-animation: lt 2s linear -500ms infinite; - animation: lt 2s linear -500ms infinite; -} -.rb:before { - bottom: 0; - right: 0; - -webkit-animation: clockwise 1s linear infinite; - -moz-animation: clockwise 1s linear infinite; - animation: clockwise 1s linear infinite; +.loader:after { + --s:-1; + border-color: var(--token-color-palette-blue-300); } -.load-container { - -webkit-animation: clockwise 6s linear infinite; - -moz-animation: clockwise 6s linear infinite; - animation: clockwise 6s linear infinite; +@keyframes l11 { + 100% {transform:rotate(calc(var(--s,1)*1turn))} } diff --git a/app/styles/log.scss b/app/styles/log.scss index 52ff8e35..c3eccca9 100644 --- a/app/styles/log.scss +++ b/app/styles/log.scss @@ -1,13 +1,3 @@ -md-table-container.posts { - table.md-table.md-row-select tbody { - td.md-cell { - vertical-align: top; - padding-top: 1em; - padding-bottom: 1em; - } - } -} - .mobiledoc-toolbar { padding: 0; @@ -21,3 +11,9 @@ md-table-container.posts { } } } + +.log-content { + *:first-child { + margin-top: 0; + } +} diff --git a/app/styles/statistics.scss b/app/styles/statistics.scss index c7abb007..b7a3241e 100644 --- a/app/styles/statistics.scss +++ b/app/styles/statistics.scss @@ -1,23 +1,59 @@ .statistics-card { display: block; + padding: 1rem 1.5rem; +} - md-card-content { - display: flex; - justify-content: space-between; - } +.statistics-controls { + display: grid; + grid-template-columns: minmax(160px, 1fr) minmax(160px, 1fr) auto auto; + align-items: center; + gap: 0.75rem 1rem; +} + +.statistics-card .control { + min-width: 220px; +} + +.statistics-card .buttons { + display: flex; + gap: 0.5rem; + flex-wrap: nowrap; + justify-content: flex-start; + align-items: center; + white-space: nowrap; + flex-direction: row; +} - .inputs { - .md-errors-spacer { - display: none; - } +.statistics-card .buttons .hds-button { + text-transform: none; + font-weight: 600; +} + +.statistics-card .radios { + display: flex; + align-items: center; + gap: 0.75rem; + justify-content: flex-end; +} + +.statistics-card .radios-label { + font-weight: 600; +} + +@media (max-width: 900px) { + .statistics-controls { + align-items: flex-start; + grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); + } - md-input-container { - margin-bottom: 0; - } + .statistics-card .radios { + width: 100%; + justify-content: flex-start; } - .buttons { - display: flex; + .statistics-card .buttons { + justify-content: flex-start; + flex-wrap: wrap; } } diff --git a/app/styles/toast.scss b/app/styles/toast.scss new file mode 100644 index 00000000..57db374a --- /dev/null +++ b/app/styles/toast.scss @@ -0,0 +1,6 @@ +.toast { + position: absolute; + bottom: 32px; + right: 24px; + z-index: 50; +} diff --git a/app/templates/admin-calendar.gjs b/app/templates/admin-calendar.gjs index 71c30756..8d8113b4 100644 --- a/app/templates/admin-calendar.gjs +++ b/app/templates/admin-calendar.gjs @@ -5,24 +5,123 @@ import { LinkTo } from '@ember/routing'; import momentFormat from 'ember-moment/helpers/moment-format'; import pluralize from 'ember-inflector/lib/helpers/pluralize'; import CalendarDay from 'prison-rideshare-ui/components/calendar-day'; -import PaperChips from 'ember-paper/components/paper-chips/component'; -import PersonBadge from 'prison-rideshare-ui/components/person-badge'; -import PaperButton from 'ember-paper/components/paper-button'; import gt from 'ember-truth-helpers/helpers/gt'; +import eq from 'ember-truth-helpers/helpers/eq'; import { action } from '@ember/object'; import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; +import { on } from '@ember/modifier'; +import { fn } from '@ember/helper'; +import { + HdsButton, + HdsCardContainer, + HdsFormTextInputField, + HdsIcon, + HdsTag, +} from '@hashicorp/design-system-components/components'; +import Alert from 'prison-rideshare-ui/components/alert'; class AdminCalendarComponent extends Component { + @tracked openCommitmentId = null; + @tracked assignmentSearchTerm = ''; + @tracked emailSearchTerm = ''; + @action setViewingSlot(slot) { this.args.controller.set('viewingSlot', slot); + this.openCommitmentId = null; + this.assignmentSearchTerm = ''; } @action changeMonth(value) { this.args.controller.set('month', value.date); } + + get uncommittedPeople() { + return this.args.controller.uncommittedPeople ?? []; + } + + get filteredUncommittedPeople() { + const query = this.assignmentSearchTerm?.trim().toLowerCase(); + + if (!query) { + return this.uncommittedPeople; + } + + return this.uncommittedPeople.filter((person) => { + return (person.name ?? '').toLowerCase().includes(query); + }); + } + + get shouldShowAssignmentOptions() { + return Boolean(this.assignmentSearchTerm?.trim().length); + } + + get remainingEmailPeople() { + return this.args.controller.remainingPeople ?? []; + } + + get filteredEmailOptions() { + const query = this.emailSearchTerm?.trim().toLowerCase(); + + if (!query) { + return this.remainingEmailPeople; + } + + return this.remainingEmailPeople.filter((person) => { + return (person.name ?? '').toLowerCase().includes(query); + }); + } + + get shouldShowEmailOptions() { + return Boolean(this.emailSearchTerm?.trim().length); + } + + get emailButtonText() { + const month = this.args.controller?.monthString ?? ''; + return `Email ${month} calendar link`; + } + + @action updateAssignmentSearch(event) { + this.assignmentSearchTerm = event.target.value; + } + + @action selectAssignmentPerson(person) { + this.args.controller.createCommitment(person); + this.assignmentSearchTerm = ''; + } + + @action toggleCommitmentDetails(commitment) { + if (this.openCommitmentId === commitment.id) { + this.openCommitmentId = null; + } else { + this.openCommitmentId = commitment.id; + } + } + + @action removeCommitment(commitment) { + this.args.controller.deleteCommitment(commitment); + } + + @action updateEmailSearch(event) { + this.emailSearchTerm = event.target.value; + } + + @action selectEmailPerson(person) { + this.args.controller.addPerson(person); + this.emailSearchTerm = ''; + } + + @action removeEmailPerson(person) { + this.args.controller.removePerson(person); + } + diff --git a/app/templates/application.gjs b/app/templates/application.gjs index 281f4875..7d1cdd81 100644 --- a/app/templates/application.gjs +++ b/app/templates/application.gjs @@ -1,177 +1,181 @@ import RouteTemplate from 'ember-route-template'; import HeadLayout from 'ember-cli-head/components/head-layout'; import EmberLoadRemover from 'ember-load/components/ember-load-remover'; -import PaperToaster from 'ember-paper/components/paper-toaster'; -import PaperSidenavContainer from 'ember-paper/components/paper-sidenav-container'; -import PaperSidenav from 'ember-paper/components/paper-sidenav'; -import PaperContent from 'ember-paper/components/paper-content/component'; -import PaperList from 'ember-paper/components/paper-list'; -import PaperItem from 'ember-paper/components/paper-item'; -import { LinkTo } from '@ember/routing'; -import momentFormat from 'ember-moment/helpers/moment-format'; -import now from 'ember-moment/helpers/now'; -import PaperDivider from 'ember-paper/components/paper-divider/component'; import { action } from '@ember/object'; +import { concat, fn } from '@ember/helper'; +import { on } from '@ember/modifier'; import Component from '@glimmer/component'; +import { inject as service } from '@ember/service'; +import { + HdsBadgeCount, + HdsButton, + HdsSeparator, + HdsToast, +} from '@hashicorp/design-system-components/components'; +import AppFrame from 'prison-rideshare-ui/components/app-frame'; +import AppSideNav from 'prison-rideshare-ui/components/app-side-nav'; +import AppSideNavList from 'prison-rideshare-ui/components/app-side-nav/list'; import { pageTitle } from 'ember-page-title'; +import momentFormat from 'ember-moment/helpers/moment-format'; +import now from 'ember-moment/helpers/now'; +import BasicDropdownWormhole from 'ember-basic-dropdown/components/basic-dropdown-wormhole'; +import { modifier } from 'ember-modifier'; class ApplicationComponent extends Component { - @action toggleSidebar() { - this.args.controller.sidebar.open = !this.args.controller.sidebar.open; + @service toasts; + @service sidebar; + + constructor(owner, args) { + super(owner, args); + + if (typeof window !== 'undefined') { + const isDesktop = window.matchMedia('(min-width: 1088px)').matches; + this.sidebar.open = isDesktop; + this.sidebar.setNavMinimizedState(!isDesktop); + } + } + + @action storeHeaderElement(headerElement) { + this.args.controller.headerElement = headerElement; + } + + get isSidebarMinimized() { + return this.sidebar.navIsMinimized; + } + + get activeToast() { + return this.toasts.activeToast; } + + @action registerSidebarComponent(component) { + this.sidebar.registerNavComponent(component); + } + + @action handleSidebarToggle(isMinimized) { + this.sidebar.open = !isMinimized; + this.sidebar.setNavMinimizedState(isMinimized); + } + } +const storeHeaderElement = modifier((element, [storeHeaderElement]) => { + storeHeaderElement(element); +}); + export default RouteTemplate(ApplicationComponent); diff --git a/app/templates/calendar.gjs b/app/templates/calendar.gjs index 1f43bc23..5d47dbb9 100644 --- a/app/templates/calendar.gjs +++ b/app/templates/calendar.gjs @@ -1,223 +1,102 @@ import RouteTemplate from 'ember-route-template'; +import ToolbarHeader from 'prison-rideshare-ui/components/toolbar-header'; import PowerCalendar from 'ember-power-calendar/components/power-calendar'; -import PaperCard from 'ember-paper/components/paper-card'; -import PaperButton from 'ember-paper/components/paper-button'; import perform from 'ember-concurrency/helpers/perform'; -import not from 'ember-truth-helpers/helpers/not'; -import PaperForm from 'ember-paper/components/paper-form'; -import PaperRadioGroup from 'ember-paper/components/paper-radio-group'; -import PaperSwitch from 'ember-paper/components/paper-switch'; -import PaperTooltip from 'ember-paper/components/paper-tooltip'; -import CalendarDay from 'prison-rideshare-ui/components/calendar-day'; +import { on } from '@ember/modifier'; import { action } from '@ember/object'; import Component from '@glimmer/component'; -import { fn } from '@ember/helper'; +import CalendarDay from 'prison-rideshare-ui/components/calendar-day'; +import { + HdsButton, + HdsCardContainer, +} from '@hashicorp/design-system-components/components'; + +import About from 'prison-rideshare-ui/components/calendar/about'; +import EditPerson from 'prison-rideshare-ui/components/calendar/edit-person'; +import Alert from 'prison-rideshare-ui/components/alert'; class CalendarComponent extends Component { @action toggleShowPerson() { this.args.controller.set('showPerson', !this.args.controller.showPerson); } - @action updatePersonAttribute(attribute, value) { - this.args.controller.person.set(attribute, value); + @action submitPersonForm(event) { + event.preventDefault(); + this.args.controller.savePerson.perform(); } , ); diff --git a/app/templates/institutions.gjs b/app/templates/institutions.gjs index d9ebb858..50ce9543 100644 --- a/app/templates/institutions.gjs +++ b/app/templates/institutions.gjs @@ -1,130 +1,154 @@ import RouteTemplate from 'ember-route-template'; import ToolbarHeader from 'prison-rideshare-ui/components/toolbar-header'; -import PaperButton from 'ember-paper/components/paper-button'; -import paperIcon from 'ember-paper/components/paper-icon'; -import PaperDataTable from 'paper-data-table/components/paper-data-table'; -import sortBy from 'ember-composable-helpers/helpers/sort-by'; -import PaperDialog from 'ember-paper/components/paper-dialog'; -import PaperDialogContent from 'ember-paper/components/paper-dialog-content'; -import PaperForm from 'ember-paper/components/paper-form'; -import PaperCheckbox from 'ember-paper/components/paper-checkbox'; -import PaperDialogActions from 'ember-paper/components/paper-dialog-actions'; import { action } from '@ember/object'; import Component from '@glimmer/component'; -import { fn } from '@ember/helper'; +import { fn, hash } from '@ember/helper'; +import { on } from '@ember/modifier'; +import { + HdsAdvancedTable, + HdsButton, + HdsButtonSet, + HdsForm, + HdsFormCheckboxField, + HdsFormTextInputField, + HdsIcon, + HdsModal, +} from '@hashicorp/design-system-components/components'; class InstitutionsComponent extends Component { - @action updateName(value) { + @action + updateName(event) { + const value = event?.target?.value ?? ''; + this.args.controller.editingInstitution.set('name', value); } - @action updateFar(value) { - this.args.controller.editingInstitution.set('far', value); + @action + updateFar(event) { + const checked = event?.target?.checked ?? false; + + this.args.controller.editingInstitution.set('far', checked); } } diff --git a/app/templates/log.gjs b/app/templates/log.gjs index 29410c6e..b902e2f4 100644 --- a/app/templates/log.gjs +++ b/app/templates/log.gjs @@ -1,203 +1,221 @@ import RouteTemplate from 'ember-route-template'; import ToolbarHeader from 'prison-rideshare-ui/components/toolbar-header'; -import PaperButton from 'ember-paper/components/paper-button'; -import paperIcon from 'ember-paper/components/paper-icon'; -import PaperContent from 'ember-paper/components/paper-content/component'; -import PaperDataTable from 'paper-data-table/components/paper-data-table'; import filterBy from 'ember-composable-helpers/helpers/filter-by'; import sortBy from 'ember-composable-helpers/helpers/sort-by'; import momentFormat from 'ember-moment/helpers/moment-format'; import eq from 'ember-truth-helpers/helpers/eq'; -import PaperDialog from 'ember-paper/components/paper-dialog'; -import PaperForm from 'ember-paper/components/paper-form'; -import PaperDialogContent from 'ember-paper/components/paper-dialog-content'; import MobiledocEditor from 'ember-mobiledoc-editor/components/mobiledoc-editor/component'; import MobiledocToolbar from 'ember-mobiledoc-editor/components/mobiledoc-toolbar/component'; -import PaperDialogActions from 'ember-paper/components/paper-dialog-actions'; import { fn } from '@ember/helper'; import DOMRenderer from 'ember-mobiledoc-dom-renderer'; import { modifier } from 'ember-modifier'; +import { on } from '@ember/modifier'; +import { + HdsButton, + HdsButtonSet, + HdsForm, + HdsModal, + HdsTable, +} from '@hashicorp/design-system-components/components'; export default RouteTemplate( , ); -const MobiledocRenderer = ; - const renderMobiledoc = modifier((element, [mobiledoc]) => { if (!mobiledoc) { element.innerHTML = ''; diff --git a/app/templates/login.gjs b/app/templates/login.gjs index f35b611b..4b23b11c 100644 --- a/app/templates/login.gjs +++ b/app/templates/login.gjs @@ -1,73 +1,91 @@ import RouteTemplate from 'ember-route-template'; -import PaperDialog from 'ember-paper/components/paper-dialog'; -import PaperToolbar from 'ember-paper/components/paper-toolbar'; -import PaperToolbarTools from 'ember-paper/components/paper-toolbar-tools'; -import PaperForm from 'ember-paper/components/paper-form'; -import PaperDialogContent from 'ember-paper/components/paper-dialog-content'; +import { on } from '@ember/modifier'; import { LinkTo } from '@ember/routing'; -import PaperDialogActions from 'ember-paper/components/paper-dialog-actions'; -import PaperButton from 'ember-paper/components/paper-button'; +import { + HdsButton, + HdsButtonSet, + HdsForm, + HdsFormTextInputField, + HdsModal, +} from '@hashicorp/design-system-components/components'; +import Alert from 'prison-rideshare-ui/components/alert'; export default RouteTemplate( , ); diff --git a/app/templates/register.gjs b/app/templates/register.gjs index 2b0437a5..f6375a0d 100644 --- a/app/templates/register.gjs +++ b/app/templates/register.gjs @@ -1,78 +1,91 @@ import RouteTemplate from 'ember-route-template'; -import PaperDialog from 'ember-paper/components/paper-dialog'; -import PaperToolbar from 'ember-paper/components/paper-toolbar'; -import PaperToolbarTools from 'ember-paper/components/paper-toolbar-tools'; -import PaperForm from 'ember-paper/components/paper-form'; -import PaperDialogContent from 'ember-paper/components/paper-dialog-content'; -import PaperCard from 'ember-paper/components/paper-card'; -import PaperDialogActions from 'ember-paper/components/paper-dialog-actions'; -import PaperButton from 'ember-paper/components/paper-button'; -import { fn } from '@ember/helper'; +import { on } from '@ember/modifier'; +import { + HdsButton, + HdsButtonSet, + HdsForm, + HdsFormTextInputField, + HdsModal, +} from '@hashicorp/design-system-components/components'; +import Alert from 'prison-rideshare-ui/components/alert'; + export default RouteTemplate( , ); diff --git a/app/templates/reimbursements.gjs b/app/templates/reimbursements.gjs index a925a829..3365f440 100644 --- a/app/templates/reimbursements.gjs +++ b/app/templates/reimbursements.gjs @@ -1,211 +1,234 @@ import RouteTemplate from 'ember-route-template'; import ToolbarHeader from 'prison-rideshare-ui/components/toolbar-header'; -import PaperContent from 'ember-paper/components/paper-content/component'; -import PaperDataTable from 'paper-data-table/components/paper-data-table'; -import CopyButton from 'ember-cli-clipboard/components/copy-button'; -import paperIcon from 'ember-paper/components/paper-icon'; -import DonationIcon from 'prison-rideshare-ui/components/donation-icon'; -import PaperButton from 'ember-paper/components/paper-button'; -import not from 'ember-truth-helpers/helpers/not'; -import PaperSwitch from 'ember-paper/components/paper-switch'; import momentFormat from 'ember-moment/helpers/moment-format'; import and from 'ember-truth-helpers/helpers/and'; import ReimbursementForm from 'prison-rideshare-ui/components/reimbursement-form'; -import { fn } from '@ember/helper'; +import { fn, hash } from '@ember/helper'; +import { on } from '@ember/modifier'; +import eq from 'ember-truth-helpers/helpers/eq'; +import { + HdsAdvancedTable, + HdsBadge, + HdsButton, + HdsCopyButton, + HdsFormToggleField, + HdsIcon, + HdsTable, +} from '@hashicorp/design-system-components/components'; + export default RouteTemplate( , ); diff --git a/app/templates/reports/new.gjs b/app/templates/reports/new.gjs index a202c8c1..62ac0392 100644 --- a/app/templates/reports/new.gjs +++ b/app/templates/reports/new.gjs @@ -1,59 +1,59 @@ import RouteTemplate from 'ember-route-template'; import ToolbarHeader from 'prison-rideshare-ui/components/toolbar-header'; -import PaperContent from 'ember-paper/components/paper-content/component'; -import PaperCard from 'ember-paper/components/paper-card'; -import PaperForm from 'ember-paper/components/paper-form'; -import PaperRadioGroup from 'ember-paper/components/paper-radio-group'; import sortBy from 'ember-composable-helpers/helpers/sort-by'; import momentFormat from 'ember-moment/helpers/moment-format'; import ReimbursementUnit from 'prison-rideshare-ui/components/reimbursement-unit'; -import PaperCheckbox from 'ember-paper/components/paper-checkbox'; -import PaperButton from 'ember-paper/components/paper-button'; -import { action } from '@ember/object'; -import Component from '@glimmer/component'; +import { fn } from '@ember/helper'; +import { on } from '@ember/modifier'; +import { + HdsButton, + HdsCardContainer, + HdsForm, + HdsFormCheckboxField, + HdsFormRadioGroup, + HdsFormTextareaField, + HdsFormTextInputField, +} from '@hashicorp/design-system-components/components'; +import eq from 'ember-truth-helpers/helpers/eq'; +import gt from 'ember-truth-helpers/helpers/gt'; +import Alert from 'prison-rideshare-ui/components/alert'; -class NewReportComponent extends Component { - @action updateDistance(distance) { - this.args.controller.editingRide.distance = distance; - } - - @action updatedonation(value) { - this.args.controller.editingRide.donation = value; - } - @action updatefoodExpensesDollars(value) { - this.args.controller.editingRide.foodExpensesDollars = value; - } - @action updatecarExpensesDollars(value) { - this.args.controller.editingRide.carExpensesDollars = value; - } - @action updatereportNotes(value) { - this.args.controller.editingRide.reportNotes = value; - } +export default RouteTemplate( -} -export default RouteTemplate(NewReportComponent); + + + Notes + + Anything unusual, like paying the driver for gas instead of + the car owner. + + + + + + + + + + {{/if}} + + + {{else}} + + There are no rides to report on! Thanks for your diligence, drivers. + Email us if you expected to see a report here. + + {{/if}} + {{/if}} + , +); diff --git a/app/templates/reset.gjs b/app/templates/reset.gjs index 2c664c00..504035ad 100644 --- a/app/templates/reset.gjs +++ b/app/templates/reset.gjs @@ -1,64 +1,77 @@ import RouteTemplate from 'ember-route-template'; -import PaperDialog from 'ember-paper/components/paper-dialog'; -import PaperToolbar from 'ember-paper/components/paper-toolbar'; -import PaperToolbarTools from 'ember-paper/components/paper-toolbar-tools'; -import PaperForm from 'ember-paper/components/paper-form'; -import PaperDialogContent from 'ember-paper/components/paper-dialog-content'; -import PaperDialogActions from 'ember-paper/components/paper-dialog-actions'; -import { fn } from '@ember/helper'; +import { on } from '@ember/modifier'; +import { + HdsButton, + HdsButtonSet, + HdsForm, + HdsFormTextInputField, + HdsModal, +} from '@hashicorp/design-system-components/components'; import { pageTitle } from 'ember-page-title'; +import Alert from 'prison-rideshare-ui/components/alert'; + export default RouteTemplate( , ); diff --git a/app/templates/rides.gjs b/app/templates/rides.gjs index dbc08bce..19313549 100644 --- a/app/templates/rides.gjs +++ b/app/templates/rides.gjs @@ -1,105 +1,101 @@ import RouteTemplate from 'ember-route-template'; import ToolbarHeader from 'prison-rideshare-ui/components/toolbar-header'; -import PaperButton from 'ember-paper/components/paper-button'; -import paperIcon from 'ember-paper/components/paper-icon'; -import PaperInput from 'ember-paper/components/paper-input'; -import PaperSwitch from 'ember-paper/components/paper-switch'; -import PaperContent from 'ember-paper/components/paper-content/component'; -import PaperDataTable from 'paper-data-table/components/paper-data-table'; -import filterBy from 'ember-composable-helpers/helpers/filter-by'; -import sortBy from 'ember-composable-helpers/helpers/sort-by'; import RideRow from 'prison-rideshare-ui/components/ride-row'; import RideForm from 'prison-rideshare-ui/components/ride-form'; import CancellationForm from 'prison-rideshare-ui/components/cancellation-form'; +import { + HdsButton, + HdsFormSectionMultiFieldGroup, + HdsFormTextInputBase, + HdsFormToggleField, + HdsTable, +} from '@hashicorp/design-system-components/components'; +import { on } from '@ember/modifier'; import { fn } from '@ember/helper'; +import eq from 'ember-truth-helpers/helpers/eq'; + export default RouteTemplate( , ); diff --git a/app/templates/statistics.gjs b/app/templates/statistics.gjs index 836a92bc..0959cbd7 100644 --- a/app/templates/statistics.gjs +++ b/app/templates/statistics.gjs @@ -1,80 +1,107 @@ import RouteTemplate from 'ember-route-template'; -import PaperContent from 'ember-paper/components/paper-content/component'; -import PaperCard from 'ember-paper/components/paper-card'; -import PaperInput from 'ember-paper/components/paper-input'; -import PaperButton from 'ember-paper/components/paper-button'; -import PaperRadioGroup from 'ember-paper/components/paper-radio-group'; +import ToolbarHeader from 'prison-rideshare-ui/components/toolbar-header'; import RequestsAndReimbursementsChart from 'prison-rideshare-ui/components/requests-and-reimbursements-chart'; import RequestTimeChart from 'prison-rideshare-ui/components/request-time-chart'; import CancellationChart from 'prison-rideshare-ui/components/cancellation-chart'; -import CopyButton from 'ember-cli-clipboard/components/copy-button'; -import paperIcon from 'ember-paper/components/paper-icon'; import { fn } from '@ember/helper'; -import { pageTitle } from 'ember-page-title'; +import { on } from '@ember/modifier'; +import eq from 'ember-truth-helpers/helpers/eq'; +import { + HdsButton, + HdsCardContainer, + HdsCopyButton, + HdsForm, + HdsFormRadioGroup, + HdsFormTextInputField, +} from '@hashicorp/design-system-components/components'; export default RouteTemplate( , ); diff --git a/app/templates/users.gjs b/app/templates/users.gjs index 24f76024..5ecac1ed 100644 --- a/app/templates/users.gjs +++ b/app/templates/users.gjs @@ -1,54 +1,70 @@ import RouteTemplate from 'ember-route-template'; import ToolbarHeader from 'prison-rideshare-ui/components/toolbar-header'; -import PaperDataTable from 'paper-data-table/components/paper-data-table'; import momentFormat from 'ember-moment/helpers/moment-format'; -import paperIcon from 'ember-paper/components/paper-icon'; -import PaperCheckbox from 'ember-paper/components/paper-checkbox'; import eq from 'ember-truth-helpers/helpers/eq'; import { fn } from '@ember/helper'; +import { on } from '@ember/modifier'; +import { + HdsBadgeCount, + HdsIcon, + HdsTable, + HdsFormCheckboxBase, +} from '@hashicorp/design-system-components/components'; export default RouteTemplate( , ); diff --git a/app/utils/custom-flight-icon-sprite.js b/app/utils/custom-flight-icon-sprite.js new file mode 100644 index 00000000..525ac76a --- /dev/null +++ b/app/utils/custom-flight-icon-sprite.js @@ -0,0 +1,199 @@ +const CUSTOM_FLIGHT_ICON_SPRITE = ` + +`; + +export default CUSTOM_FLIGHT_ICON_SPRITE; diff --git a/app/utils/reason-to-icon.js b/app/utils/reason-to-icon.js index 17243267..29ed5d73 100644 --- a/app/utils/reason-to-icon.js +++ b/app/utils/reason-to-icon.js @@ -1,17 +1,17 @@ const reasonToIcon = { lockdown: 'lock', - visitor: 'perm identity', - 'no car': 'directions car', - 'driver not found': 'assignment ind', - 'at capacity': 'assignment ind', + visitor: 'user', + 'no car': 'car', + 'driver not found': 'user-round-search', + 'at capacity': 'users', weather: 'cloud', transfer: 'shuffle', - error: 'error', - 'visitor missing': 'perm identity', - 'driver missing': 'directions car', - 'driver cancelled': 'hot tub', - released: 'lock open', - 'jail cancelled': 'alarm off', + error: 'alert-octagon', + 'visitor missing': 'user-x', + 'driver missing': 'truck', + 'driver cancelled': 'skip', + released: 'unlock', + 'jail cancelled': 'bell-off', }; export default reasonToIcon; diff --git a/app/utils/reimbursement-collection.js b/app/utils/reimbursement-collection.js index 4fc14cf6..e108b986 100644 --- a/app/utils/reimbursement-collection.js +++ b/app/utils/reimbursement-collection.js @@ -5,7 +5,7 @@ import EmberObject, { computed } from '@ember/object'; import dollars from 'prison-rideshare-ui/utils/dollars'; -import moment from 'moment'; +import moment from 'moment-timezone'; @classic export default class ReimbursementCollection extends EmberObject { diff --git a/docs/helios-overrides.md b/docs/helios-overrides.md new file mode 100644 index 00000000..d86d7813 --- /dev/null +++ b/docs/helios-overrides.md @@ -0,0 +1,12 @@ +# Helios component overrides + +This app wraps and extends Helios primitives to tweak layout and behavior. Key deviations: + +- **AppFrame (`app/components/app-frame*`)**: Minimal wrappers that mirror Helios layout slots but keep the DOM/CSS under our control so we can grid the header/sidebar/main without Helios’ internal container. +- **AppSideNav (`app/components/app-side-nav*`)**: Custom side nav that mirrors Helios’ API but: + - Supports external `isMinimized`/`onRegister` plumbing (instead of Helios’ internal state). + - Hides/dims only main content on mobile; scrim is inset to avoid blocking the nav; panel uses explicit z-index. + - Template-only links/items instead of the Helios toggle, with optional `@model` on links. +- **Styles (`app/styles/app.scss`)**: Sidebar block diverges from Helios—fixed/inset positioning, scrim inset, and z-index adjustments for the panel. + +When touching these components, keep in mind they intentionally replace Helios behavior; prefer updating this doc when behavior changes. diff --git a/ember-cli-build.js b/ember-cli-build.js index 41aa6377..a24bc2ad 100644 --- a/ember-cli-build.js +++ b/ember-cli-build.js @@ -20,10 +20,15 @@ module.exports = function (defaults) { autoImport, babel: { plugins: [ + // eslint-disable-next-line node/no-missing-require + require.resolve('decorator-transforms'), require.resolve('ember-concurrency/async-arrow-task-transform'), ...require('ember-cli-code-coverage').buildBabelPlugin(), ], }, + 'ember-cli-babel': { + disableDecoratorTransforms: true, + }, fingerprint, minifyCSS: { enabled: false, @@ -35,6 +40,13 @@ module.exports = function (defaults) { includeHighCharts: true, includeModules: ['heatmap'], }, + sassOptions: { + precision: 4, + includePaths: [ + './node_modules/@hashicorp/design-system-tokens/dist/products/css', + './node_modules/@hashicorp/design-system-components/dist/styles', + ], + }, }); // Use `app.import` to add additional libraries to the generated diff --git a/mirage/config.js b/mirage/config.js index 4e41324a..995cce5c 100644 --- a/mirage/config.js +++ b/mirage/config.js @@ -15,7 +15,7 @@ export default function (config) { this.get('/rides', function ({ rides }, { queryParams, requestHeaders }) { if (queryParams['filter[name]']) { // FIXME this is a mess, no better way??? - const nameFilter = queryParams['filter[name]']; + const nameFilter = queryParams['filter[name]'].toLowerCase(); const matchingRides = rides .all() .models.filter((ride) => diff --git a/package.json b/package.json index a4cf5c6e..58f9778b 100644 --- a/package.json +++ b/package.json @@ -30,18 +30,21 @@ "@faker-js/faker": "^8.1.0", "@glimmer/component": "^1.0.4", "@glimmer/tracking": "^1.0.4", + "@hashicorp/design-system-components": "^4.24.0", + "@hashicorp/design-system-tokens": "^3.0.0", "babel-eslint": "^8.2.6", "bluebird": "^3.7.2", "broccoli-asset-rev": "^3.0.0", "chrono-node": "^1.3.11", "coveralls": "^3.1.1", - "ember-auto-import": "^2.7.4", + "decorator-transforms": "^2.3.0", + "ember-auto-import": "^2.11.0", + "ember-basic-dropdown": "^8.7.0", "ember-buffered-proxy": "^2.1.1", "ember-classic-decorator": "^3.0.1", "ember-cli": "~3.28.6", "ember-cli-app-version": "^5.0.0", "ember-cli-babel": "^8.2.0", - "ember-cli-clipboard": "^1.1.0", "ember-cli-code-coverage": "^2.0.0", "ember-cli-dependency-checker": "^3.2.0", "ember-cli-deploy": "^2.0.0", @@ -72,11 +75,11 @@ "ember-mobiledoc-editor": "^0.8.1", "ember-moment": "^10.0.0", "ember-page-title": "^8.2.4", - "ember-paper": "1.0.0-beta.36", "ember-percy": "^1.6.0", "ember-phoenix": "^1.0.3", "ember-power-calendar": "^1.1.0", "ember-power-calendar-moment": "^1.0.2", + "ember-power-select": "^8.11.0", "ember-qunit": "^5.1.5", "ember-qunit-nice-errors": "^1.2.1", "ember-resolver": "^8.0.3", @@ -104,7 +107,6 @@ "moment": "^2.29.4", "moment-timezone": "^0.6.0", "npm-run-all": "^4.1.5", - "paper-data-table": "^0.1.5", "prettier": "^3.0.0", "prettier-plugin-ember-template-tag": "^2.1.0", "qunit": "^2.17.2", @@ -123,5 +125,15 @@ "volta": { "node": "18.20.8", "pnpm": "10.17.1" + }, + "pnpm": { + "overrides": { + "ember-focus-trap": "0.8.0" + }, + "patchedDependencies": { + "@hashicorp/design-system-components": "patches/@hashicorp__design-system-components.patch", + "decorator-transforms": "patches/decorator-transforms.patch", + "@hashicorp/flight-icons": "patches/@hashicorp__flight-icons.patch" + } } } diff --git a/patches/@hashicorp__design-system-components.patch b/patches/@hashicorp__design-system-components.patch new file mode 100644 index 00000000..7221dfca --- /dev/null +++ b/patches/@hashicorp__design-system-components.patch @@ -0,0 +1,35 @@ +diff --git a/dist/components/hds/dismiss-button/index.js b/dist/components/hds/dismiss-button/index.js +index 4fa6474b19d4ee1aa853c3ff357571fc993ac1dd..c17fd9cc90e4b4ab2d7578cbaba44a78a638e875 100644 +--- a/dist/components/hds/dismiss-button/index.js ++++ b/dist/components/hds/dismiss-button/index.js +@@ -1,5 +1,5 @@ + import Component from '@glimmer/component'; +-import { service } from '@ember/service'; ++import { inject as service } from '@ember/service'; + import { precompileTemplate } from '@ember/template-compilation'; + import { g, i } from 'decorator-transforms/runtime'; + import { setComponentTemplate } from '@ember/component'; +diff --git a/dist/components/hds/table/th-button-sort.js b/dist/components/hds/table/th-button-sort.js +index a61c6e93ce8eb0468b9683786b84b939a344f383..99795f47b3ec54afe9b9b81d42333e87b2cec4b9 100644 +--- a/dist/components/hds/table/th-button-sort.js ++++ b/dist/components/hds/table/th-button-sort.js +@@ -1,5 +1,5 @@ + import Component from '@glimmer/component'; +-import { service } from '@ember/service'; ++import { inject as service } from '@ember/service'; + import { guidFor } from '@ember/object/internals'; + import { HdsTableThSortOrderIconValues, HdsTableThSortOrderValues } from './types.js'; + import { precompileTemplate } from '@ember/template-compilation'; +diff --git a/dist/helpers/hds-t.js b/dist/helpers/hds-t.js +index d624cbfd07e27eb5427040cba5ddd2533364e78a..0463fe999cc80626e85abe465922e4b0679955d5 100644 +--- a/dist/helpers/hds-t.js ++++ b/dist/helpers/hds-t.js +@@ -17,7 +17,7 @@ class HdsTHelper extends Helper { + compute(positional, named) { + const key = positional[0]; + assert('Hds::T helper requires a key as the first positional argument', typeof key === 'string' && isPresent(key)); +- return this.hdsIntl.t(key, named); ++ return named.default; + } + } + diff --git a/patches/@hashicorp__flight-icons.patch b/patches/@hashicorp__flight-icons.patch new file mode 100644 index 00000000..79a16e89 --- /dev/null +++ b/patches/@hashicorp__flight-icons.patch @@ -0,0 +1,7 @@ +diff --git a/svg/index.js b/svg/index.js +index a88a0f0c0c9e78724495e9db4a82b4f83d08bb91..9bae213730f689889a66fe0795a9fb50c791e7c6 100644 +--- a/svg/index.js ++++ b/svg/index.js +@@ -6 +6 @@ export const iconNames = [ 'loading', 'loading-static', 'running', 'running-static', 'apple', 'apple-color', 'alibaba', 'alibaba-color', 'amazon-ecs', 'amazon-ecs-color', 'amazon-eks', 'amazon-eks-color', 'auth0', 'auth0-color', 'aws', 'aws-color', 'aws-cdk', 'aws-cdk-color', 'aws-cloudwatch', 'aws-cloudwatch-color', 'aws-ec2', 'aws-ec2-color', 'aws-lambda', 'aws-lambda-color', 'aws-s3', 'aws-s3-color', 'azure', 'azure-color', 'azure-aks', 'azure-aks-color', 'azure-blob-storage', 'azure-blob-storage-color', 'azure-devops', 'azure-devops-color', 'azure-vms', 'azure-vms-color', 'bitbucket', 'bitbucket-color', 'bridgecrew', 'bridgecrew-color', 'cisco', 'cisco-color', 'codepen', 'codepen-color', 'confluence', 'confluence-color', 'confluent', 'confluent-color', 'datadog', 'datadog-color', 'digital-ocean', 'digital-ocean-color', 'docker', 'docker-color', 'duo', 'duo-color', 'elastic-observability', 'elastic-observability-color', 'f5', 'f5-color', 'facebook', 'facebook-color', 'figma', 'figma-color', 'gcp', 'gcp-color', 'git', 'git-color', 'gitlab', 'gitlab-color', 'github', 'github-color', 'google', 'google-color', 'google-docs', 'google-docs-color', 'google-drive', 'google-drive-color', 'google-forms', 'google-forms-color', 'google-sheets', 'google-sheets-color', 'google-slides', 'google-slides-color', 'grafana', 'grafana-color', 'helm', 'helm-color', 'infracost', 'infracost-color', 'jfrog', 'jfrog-color', 'jira', 'jira-color', 'jwt', 'jwt-color', 'kubernetes', 'kubernetes-color', 'lightlytics', 'lightlytics-color', 'linkedin', 'linkedin-color', 'linode', 'linode-color', 'linux', 'linux-color', 'loom', 'loom-color', 'meetup', 'meetup-color', 'microsoft', 'microsoft-color', 'microsoft-teams', 'microsoft-teams-color', 'minio', 'minio-color', 'mongodb', 'mongodb-color', 'new-relic', 'new-relic-color', 'okta', 'okta-color', 'oracle', 'oracle-color', 'opa', 'opa-color', 'openid', 'openid-color', 'pack', 'pack-color', 'pager-duty', 'pager-duty-color', 'ping-identity ', 'ping-identity-color', 'postgres', 'postgres-color', 'rabbitmq', 'rabbitmq-color', 'saml', 'saml-color', 'service-now', 'service-now-color', 'slack', 'slack-color', 'snyk', 'snyk-color', 'splunk', 'splunk-color', 'twilio', 'twilio-color', 'twitch', 'twitch-color', 'twitter', 'twitter-color', 'twitter-x', 'twitter-x-color', 'vantage', 'vantage-color', 'venafi', 'venafi-color', 'vercel', 'vercel-color', 'vmware', 'vmware-color', 'youtube', 'youtube-color', 'boundary', 'boundary-color', 'boundary-fill', 'boundary-fill-color', 'boundary-square', 'boundary-square-color', 'consul', 'consul-color', 'consul-fill', 'consul-fill-color', 'consul-square', 'consul-square-color', 'nomad', 'nomad-color', 'nomad-fill', 'nomad-fill-color', 'nomad-square', 'nomad-square-color', 'packer', 'packer-color', 'packer-fill', 'packer-fill-color', 'packer-square', 'packer-square-color', 'terraform', 'terraform-color', 'terraform-fill', 'terraform-fill-color', 'terraform-square', 'terraform-square-color', 'vagrant', 'vagrant-color', 'vagrant-fill', 'vagrant-fill-color', 'vagrant-square', 'vagrant-square-color', 'vault', 'vault-color', 'vault-fill', 'vault-fill-color', 'vault-square', 'vault-square-color', 'vault-radar', 'vault-radar-color', 'vault-radar-fill', 'vault-radar-fill-color', 'vault-radar-square', 'vault-radar-square-color', 'vault-secrets', 'vault-secrets-color', 'vault-secrets-fill', 'vault-secrets-fill-color', 'vault-secrets-square', 'vault-secrets-square-color', 'waypoint', 'waypoint-color', 'waypoint-fill', 'waypoint-fill-color', 'waypoint-square', 'waypoint-square-color', 'hashicorp', 'hashicorp-color', 'hashicorp-fill', 'hashicorp-fill-color', 'hashicorp-square', 'hashicorp-square-color', 'hcp', 'hcp-color', 'hcp-fill', 'hcp-fill-color', 'hcp-square', 'hcp-square-color', 'accessibility', 'folder-users', 'frown', 'identity-service', 'identity-user', 'meh', 'robot', 'smile', 'user', 'user-check', 'user-circle', 'user-circle-fill', 'user-minus', 'user-plus', 'user-x', 'users', 'ampersand', 'beaker', 'bucket', 'bulb', 'circle', 'circle-dot', 'circle-fill', 'circle-half', 'diamond', 'diamond-fill', 'disc', 'dot', 'dot-half', 'droplet', 'flag', 'gift', 'government', 'handshake', 'hash', 'hexagon', 'hexagon-fill', 'labyrinth', 'layers', 'moon', 'octagon', 'outline', 'random', 'rocket', 'sparkle', 'square', 'square-fill', 'sun', 'triangle', 'triangle-fill', 'truck', 'wand', 'zap', 'zap-off', 'docs', 'docs-download', 'docs-link', 'guide', 'guide-link', 'help', 'info', 'info-fill', 'learn', 'learn-link', 'support', 'alert-circle', 'alert-circle-fill', 'alert-diamond', 'alert-diamond-fill', 'alert-octagon', 'alert-octagon-fill', 'alert-triangle', 'alert-triangle-fill', 'check', 'check-circle', 'check-circle-fill', 'check-diamond', 'check-diamond-fill', 'check-hexagon', 'check-hexagon-fill', 'check-square', 'check-square-fill', 'skip', 'x', 'x-circle', 'x-circle-fill', 'x-diamond', 'x-diamond-fill', 'x-hexagon', 'x-hexagon-fill', 'x-square', 'x-square-fill', 'bug', 'certificate', 'eye', 'eye-off', 'fingerprint', 'key', 'keychain', 'lock', 'lock-fill', 'lock-off', 'shield', 'shield-alert', 'shield-check', 'shield-off', 'shield-x', 'token', 'unlock', 'verified', 'wall', 'minus', 'minus-circle', 'minus-circle-fill', 'minus-plus', 'minus-plus-circle', 'minus-plus-square', 'minus-square', 'minus-square-fill', 'plus', 'plus-circle', 'plus-circle-fill', 'plus-square', 'camera', 'camera-off', 'cast', 'closed-caption', 'fast-forward', 'film', 'headphones', 'image', 'music', 'pause', 'pause-circle', 'play', 'play-circle', 'radio', 'rewind', 'rss', 'skip-back', 'skip-forward', 'speaker', 'stop-circle', 'volume', 'volume-down', 'volume-2', 'volume-x', 'wifi', 'wifi-off', 'compass', 'crosshair', 'map', 'map-pin', 'navigation', 'navigation-alt', 'redirect', 'target', 'align-center', 'align-justify', 'align-left', 'align-right', 'battery', 'battery-charging', 'bookmark', 'bookmark-add', 'bookmark-add-fill', 'bookmark-fill', 'bookmark-remove', 'bookmark-remove-fill', 'bottom', 'top', 'start', 'end', 'command', 'crop', 'dashboard', 'delete', 'download', 'edit', 'entry-point', 'exit-point', 'external-link', 'filter', 'filter-circle', 'filter-fill', 'grid', 'grid-alt', 'home', 'jump-link', 'layout', 'link', 'list', 'maximize', 'maximize-alt', 'menu', 'minimize', 'minimize-alt', 'more-horizontal', 'more-vertical', 'mouse-pointer', 'move-horizontal', 'paperclip', 'pen-tool', 'pencil-tool', 'pin', 'pin-off', 'power', 'printer', 'reload', 'repeat', 'resize-column', 'rotate-cw', 'rotate-ccw', 'search', 'share', 'sidebar', 'sidebar-hide', 'sidebar-show', 'sign-in', 'sign-out', 'slash', 'slash-square', 'sort-asc', 'sort-desc', 'switcher', 'sync', 'sync-alert', 'sync-reverse', 'tag', 'toggle-left', 'toggle-right', 'trash', 'type', 'text-wrap', 'unfold-close', 'unfold-open', 'upload', 'zoom-in', 'zoom-out', 'archive', 'clipboard', 'clipboard-checked', 'clipboard-copy', 'clipboard-x', 'file', 'file-change', 'file-check', 'file-diff', 'file-minus', 'file-plus', 'file-source', 'file-text', 'file-x', 'files', 'folder', 'folder-fill', 'folder-minus', 'folder-minus-fill', 'folder-plus', 'folder-plus-fill', 'folder-star', 'inbox', 'api', 'auto-apply', 'build', 'change', 'change-circle', 'change-square', 'channel', 'cloud', 'cloud-check', 'cloud-download', 'cloud-lightning', 'cloud-lock', 'cloud-off', 'cloud-upload', 'cloud-x', 'code', 'connection', 'connection-gateway', 'cpu', 'duplicate', 'gateway', 'git-branch', 'git-commit', 'git-merge', 'git-pull-request', 'git-repo', 'hammer', 'key-values', 'mainframe', 'mesh', 'module', 'monitor', 'network', 'network-alt', 'node', 'path', 'pipeline', 'plug', 'replication-direct', 'replication-perf', 'scissors', 'server', 'server-cluster', 'serverless', 'service', 'settings', 'sliders', 'smartphone', 'socket', 'step', 'tablet', 'terminal', 'terminal-screen', 'test', 'tools', 'transform-data', 'tv', 'webhook', 'wrench', 'calendar', 'clock', 'clock-filled', 'delay', 'event', 'history', 'hourglass', 'watch', 'bar-chart', 'bar-chart-alt', 'box', 'collections', 'database', 'hard-drive', 'line-chart', 'line-chart-up', 'logs', 'package', 'pie-chart', 'queue', 'save', 'trend-down', 'trend-up', 'activity', 'at-sign', 'award', 'bell', 'bell-active', 'bell-active-fill', 'bell-off', 'discussion-circle', 'discussion-square', 'heart', 'heart-fill', 'heart-off', 'mail', 'mail-open', 'message-circle', 'message-circle-fill', 'message-square', 'message-square-fill', 'mic', 'mic-off', 'newspaper', 'phone', 'phone-call', 'phone-off', 'send', 'star', 'star-circle', 'star-fill', 'star-off', 'thumbs-down', 'thumbs-up', 'video', 'video-off', 'bank-vault', 'briefcase', 'credit-card', 'dollar-sign', 'enterprise', 'globe', 'globe-private', 'org', 'provider', 'shopping-bag', 'shopping-cart', 'arrow-down', 'arrow-down-circle', 'arrow-down-left', 'arrow-down-right', 'arrow-left', 'arrow-left-circle', 'arrow-right', 'arrow-right-circle', 'arrow-up', 'arrow-up-circle', 'arrow-up-left', 'arrow-up-right', 'caret', 'chevron-down', 'chevron-left', 'chevron-right', 'chevron-up', 'chevrons-down', 'chevrons-left', 'chevrons-right', 'chevrons-up', 'corner-down-left', 'corner-down-right', 'corner-left-down', 'corner-left-up', 'corner-right-down', 'corner-right-up', 'corner-up-left', 'corner-up-right', 'load-balancer', 'migrate', 'move', 'shuffle', 'swap-horizontal', 'swap-vertical' ]; +-export const iconNames = [ 'loading', 'loading-static', 'running', 'running-static', 'apple', 'apple-color', 'alibaba', 'alibaba-color', 'amazon-ecs', 'amazon-ecs-color', 'amazon-eks', 'amazon-eks-color', 'auth0', 'auth0-color', 'aws', 'aws-color', 'aws-cdk', 'aws-cdk-color', 'aws-cloudwatch', 'aws-cloudwatch-color', 'aws-ec2', 'aws-ec2-color', 'aws-lambda', 'aws-lambda-color', 'aws-s3', 'aws-s3-color', 'azure', 'azure-color', 'azure-aks', 'azure-aks-color', 'azure-blob-storage', 'azure-blob-storage-color', 'azure-devops', 'azure-devops-color', 'azure-vms', 'azure-vms-color', 'bitbucket', 'bitbucket-color', 'bridgecrew', 'bridgecrew-color', 'cisco', 'cisco-color', 'codepen', 'codepen-color', 'confluence', 'confluence-color', 'confluent', 'confluent-color', 'datadog', 'datadog-color', 'digital-ocean', 'digital-ocean-color', 'docker', 'docker-color', 'duo', 'duo-color', 'elastic-observability', 'elastic-observability-color', 'f5', 'f5-color', 'facebook', 'facebook-color', 'figma', 'figma-color', 'gcp', 'gcp-color', 'git', 'git-color', 'gitlab', 'gitlab-color', 'github', 'github-color', 'google', 'google-color', 'google-docs', 'google-docs-color', 'google-drive', 'google-drive-color', 'google-forms', 'google-forms-color', 'google-sheets', 'google-sheets-color', 'google-slides', 'google-slides-color', 'grafana', 'grafana-color', 'helm', 'helm-color', 'infracost', 'infracost-color', 'jfrog', 'jfrog-color', 'jira', 'jira-color', 'jwt', 'jwt-color', 'kubernetes', 'kubernetes-color', 'lightlytics', 'lightlytics-color', 'linkedin', 'linkedin-color', 'linode', 'linode-color', 'linux', 'linux-color', 'loom', 'loom-color', 'meetup', 'meetup-color', 'microsoft', 'microsoft-color', 'microsoft-teams', 'microsoft-teams-color', 'minio', 'minio-color', 'mongodb', 'mongodb-color', 'new-relic', 'new-relic-color', 'okta', 'okta-color', 'oracle', 'oracle-color', 'opa', 'opa-color', 'openid', 'openid-color', 'pack', 'pack-color', 'pager-duty', 'pager-duty-color', 'ping-identity ', 'ping-identity-color', 'postgres', 'postgres-color', 'rabbitmq', 'rabbitmq-color', 'saml', 'saml-color', 'service-now', 'service-now-color', 'slack', 'slack-color', 'snyk', 'snyk-color', 'splunk', 'splunk-color', 'twilio', 'twilio-color', 'twitch', 'twitch-color', 'twitter', 'twitter-color', 'twitter-x', 'twitter-x-color', 'vantage', 'vantage-color', 'venafi', 'venafi-color', 'vercel', 'vercel-color', 'vmware', 'vmware-color', 'youtube', 'youtube-color', 'boundary', 'boundary-color', 'boundary-fill', 'boundary-fill-color', 'boundary-square', 'boundary-square-color', 'consul', 'consul-color', 'consul-fill', 'consul-fill-color', 'consul-square', 'consul-square-color', 'nomad', 'nomad-color', 'nomad-fill', 'nomad-fill-color', 'nomad-square', 'nomad-square-color', 'packer', 'packer-color', 'packer-fill', 'packer-fill-color', 'packer-square', 'packer-square-color', 'terraform', 'terraform-color', 'terraform-fill', 'terraform-fill-color', 'terraform-square', 'terraform-square-color', 'vagrant', 'vagrant-color', 'vagrant-fill', 'vagrant-fill-color', 'vagrant-square', 'vagrant-square-color', 'vault', 'vault-color', 'vault-fill', 'vault-fill-color', 'vault-square', 'vault-square-color', 'vault-radar', 'vault-radar-color', 'vault-radar-fill', 'vault-radar-fill-color', 'vault-radar-square', 'vault-radar-square-color', 'vault-secrets', 'vault-secrets-color', 'vault-secrets-fill', 'vault-secrets-fill-color', 'vault-secrets-square', 'vault-secrets-square-color', 'waypoint', 'waypoint-color', 'waypoint-fill', 'waypoint-fill-color', 'waypoint-square', 'waypoint-square-color', 'hashicorp', 'hashicorp-color', 'hashicorp-fill', 'hashicorp-fill-color', 'hashicorp-square', 'hashicorp-square-color', 'hcp', 'hcp-color', 'hcp-fill', 'hcp-fill-color', 'hcp-square', 'hcp-square-color', 'accessibility', 'folder-users', 'frown', 'identity-service', 'identity-user', 'meh', 'robot', 'smile', 'user', 'user-check', 'user-circle', 'user-circle-fill', 'user-minus', 'user-plus', 'user-x', 'users', 'ampersand', 'beaker', 'bucket', 'bulb', 'circle', 'circle-dot', 'circle-fill', 'circle-half', 'diamond', 'diamond-fill', 'disc', 'dot', 'dot-half', 'droplet', 'flag', 'gift', 'government', 'handshake', 'hash', 'hexagon', 'hexagon-fill', 'labyrinth', 'layers', 'moon', 'octagon', 'outline', 'random', 'rocket', 'sparkle', 'square', 'square-fill', 'sun', 'triangle', 'triangle-fill', 'truck', 'wand', 'zap', 'zap-off', 'docs', 'docs-download', 'docs-link', 'guide', 'guide-link', 'help', 'info', 'info-fill', 'learn', 'learn-link', 'support', 'alert-circle', 'alert-circle-fill', 'alert-diamond', 'alert-diamond-fill', 'alert-octagon', 'alert-octagon-fill', 'alert-triangle', 'alert-triangle-fill', 'check', 'check-circle', 'check-circle-fill', 'check-diamond', 'check-diamond-fill', 'check-hexagon', 'check-hexagon-fill', 'check-square', 'check-square-fill', 'skip', 'x', 'x-circle', 'x-circle-fill', 'x-diamond', 'x-diamond-fill', 'x-hexagon', 'x-hexagon-fill', 'x-square', 'x-square-fill', 'bug', 'certificate', 'eye', 'eye-off', 'fingerprint', 'key', 'keychain', 'lock', 'lock-fill', 'lock-off', 'shield', 'shield-alert', 'shield-check', 'shield-off', 'shield-x', 'token', 'unlock', 'verified', 'wall', 'minus', 'minus-circle', 'minus-circle-fill', 'minus-plus', 'minus-plus-circle', 'minus-plus-square', 'minus-square', 'minus-square-fill', 'plus', 'plus-circle', 'plus-circle-fill', 'plus-square', 'camera', 'camera-off', 'cast', 'closed-caption', 'fast-forward', 'film', 'headphones', 'image', 'music', 'pause', 'pause-circle', 'play', 'play-circle', 'radio', 'rewind', 'rss', 'skip-back', 'skip-forward', 'speaker', 'stop-circle', 'volume', 'volume-down', 'volume-2', 'volume-x', 'wifi', 'wifi-off', 'compass', 'crosshair', 'map', 'map-pin', 'navigation', 'navigation-alt', 'redirect', 'target', 'align-center', 'align-justify', 'align-left', 'align-right', 'battery', 'battery-charging', 'bookmark', 'bookmark-add', 'bookmark-add-fill', 'bookmark-fill', 'bookmark-remove', 'bookmark-remove-fill', 'bottom', 'top', 'start', 'end', 'command', 'crop', 'dashboard', 'delete', 'download', 'edit', 'entry-point', 'exit-point', 'external-link', 'filter', 'filter-circle', 'filter-fill', 'grid', 'grid-alt', 'home', 'jump-link', 'layout', 'link', 'list', 'maximize', 'maximize-alt', 'menu', 'minimize', 'minimize-alt', 'more-horizontal', 'more-vertical', 'mouse-pointer', 'move-horizontal', 'paperclip', 'pen-tool', 'pencil-tool', 'pin', 'pin-off', 'power', 'printer', 'reload', 'repeat', 'resize-column', 'rotate-cw', 'rotate-ccw', 'search', 'share', 'sidebar', 'sidebar-hide', 'sidebar-show', 'sign-in', 'sign-out', 'slash', 'slash-square', 'sort-asc', 'sort-desc', 'switcher', 'sync', 'sync-alert', 'sync-reverse', 'tag', 'toggle-left', 'toggle-right', 'trash', 'type', 'text-wrap', 'unfold-close', 'unfold-open', 'upload', 'zoom-in', 'zoom-out', 'archive', 'clipboard', 'clipboard-checked', 'clipboard-copy', 'clipboard-x', 'file', 'file-change', 'file-check', 'file-diff', 'file-minus', 'file-plus', 'file-source', 'file-text', 'file-x', 'files', 'folder', 'folder-fill', 'folder-minus', 'folder-minus-fill', 'folder-plus', 'folder-plus-fill', 'folder-star', 'inbox', 'api', 'auto-apply', 'build', 'change', 'change-circle', 'change-square', 'channel', 'cloud', 'cloud-check', 'cloud-download', 'cloud-lightning', 'cloud-lock', 'cloud-off', 'cloud-upload', 'cloud-x', 'code', 'connection', 'connection-gateway', 'cpu', 'duplicate', 'gateway', 'git-branch', 'git-commit', 'git-merge', 'git-pull-request', 'git-repo', 'hammer', 'key-values', 'mainframe', 'mesh', 'module', 'monitor', 'network', 'network-alt', 'node', 'path', 'pipeline', 'plug', 'replication-direct', 'replication-perf', 'scissors', 'server', 'server-cluster', 'serverless', 'service', 'settings', 'sliders', 'smartphone', 'socket', 'step', 'tablet', 'terminal', 'terminal-screen', 'test', 'tools', 'transform-data', 'tv', 'webhook', 'wrench', 'calendar', 'clock', 'clock-filled', 'delay', 'event', 'history', 'hourglass', 'watch', 'bar-chart', 'bar-chart-alt', 'box', 'collections', 'database', 'hard-drive', 'line-chart', 'line-chart-up', 'logs', 'package', 'pie-chart', 'queue', 'save', 'trend-down', 'trend-up', 'activity', 'at-sign', 'award', 'bell', 'bell-active', 'bell-active-fill', 'bell-off', 'discussion-circle', 'discussion-square', 'heart', 'heart-fill', 'heart-off', 'mail', 'mail-open', 'message-circle', 'message-circle-fill', 'message-square', 'message-square-fill', 'mic', 'mic-off', 'newspaper', 'phone', 'phone-call', 'phone-off', 'send', 'star', 'star-circle', 'star-fill', 'star-off', 'thumbs-down', 'thumbs-up', 'video', 'video-off', 'bank-vault', 'briefcase', 'credit-card', 'dollar-sign', 'enterprise', 'globe', 'globe-private', 'org', 'provider', 'shopping-bag', 'shopping-cart', 'arrow-down', 'arrow-down-circle', 'arrow-down-left', 'arrow-down-right', 'arrow-left', 'arrow-left-circle', 'arrow-right', 'arrow-right-circle', 'arrow-up', 'arrow-up-circle', 'arrow-up-left', 'arrow-up-right', 'caret', 'chevron-down', 'chevron-left', 'chevron-right', 'chevron-up', 'chevrons-down', 'chevrons-left', 'chevrons-right', 'chevrons-up', 'corner-down-left', 'corner-down-right', 'corner-left-down', 'corner-left-up', 'corner-right-down', 'corner-right-up', 'corner-up-left', 'corner-up-right', 'load-balancer', 'migrate', 'move', 'shuffle', 'swap-horizontal', 'swap-vertical' ]; ++export const iconNames = [ 'loading', 'loading-static', 'running', 'running-static', 'apple', 'apple-color', 'alibaba', 'alibaba-color', 'amazon-ecs', 'amazon-ecs-color', 'amazon-eks', 'amazon-eks-color', 'auth0', 'auth0-color', 'aws', 'aws-color', 'aws-cdk', 'aws-cdk-color', 'aws-cloudwatch', 'aws-cloudwatch-color', 'aws-ec2', 'aws-ec2-color', 'aws-lambda', 'aws-lambda-color', 'aws-s3', 'aws-s3-color', 'azure', 'azure-color', 'azure-aks', 'azure-aks-color', 'azure-blob-storage', 'azure-blob-storage-color', 'azure-devops', 'azure-devops-color', 'azure-vms', 'azure-vms-color', 'bitbucket', 'bitbucket-color', 'bridgecrew', 'bridgecrew-color', 'cisco', 'cisco-color', 'codepen', 'codepen-color', 'confluence', 'confluence-color', 'confluent', 'confluent-color', 'datadog', 'datadog-color', 'digital-ocean', 'digital-ocean-color', 'docker', 'docker-color', 'duo', 'duo-color', 'elastic-observability', 'elastic-observability-color', 'f5', 'f5-color', 'facebook', 'facebook-color', 'figma', 'figma-color', 'gcp', 'gcp-color', 'git', 'git-color', 'gitlab', 'gitlab-color', 'github', 'github-color', 'google', 'google-color', 'google-docs', 'google-docs-color', 'google-drive', 'google-drive-color', 'google-forms', 'google-forms-color', 'google-sheets', 'google-sheets-color', 'google-slides', 'google-slides-color', 'grafana', 'grafana-color', 'helm', 'helm-color', 'infracost', 'infracost-color', 'jfrog', 'jfrog-color', 'jira', 'jira-color', 'jwt', 'jwt-color', 'kubernetes', 'kubernetes-color', 'lightlytics', 'lightlytics-color', 'linkedin', 'linkedin-color', 'linode', 'linode-color', 'linux', 'linux-color', 'loom', 'loom-color', 'meetup', 'meetup-color', 'microsoft', 'microsoft-color', 'microsoft-teams', 'microsoft-teams-color', 'minio', 'minio-color', 'mongodb', 'mongodb-color', 'new-relic', 'new-relic-color', 'okta', 'okta-color', 'oracle', 'oracle-color', 'opa', 'opa-color', 'openid', 'openid-color', 'pack', 'pack-color', 'pager-duty', 'pager-duty-color', 'ping-identity ', 'ping-identity-color', 'postgres', 'postgres-color', 'rabbitmq', 'rabbitmq-color', 'saml', 'saml-color', 'service-now', 'service-now-color', 'slack', 'slack-color', 'snyk', 'snyk-color', 'splunk', 'splunk-color', 'twilio', 'twilio-color', 'twitch', 'twitch-color', 'twitter', 'twitter-color', 'twitter-x', 'twitter-x-color', 'vantage', 'vantage-color', 'venafi', 'venafi-color', 'vercel', 'vercel-color', 'vmware', 'vmware-color', 'youtube', 'youtube-color', 'boundary', 'boundary-color', 'boundary-fill', 'boundary-fill-color', 'boundary-square', 'boundary-square-color', 'consul', 'consul-color', 'consul-fill', 'consul-fill-color', 'consul-square', 'consul-square-color', 'nomad', 'nomad-color', 'nomad-fill', 'nomad-fill-color', 'nomad-square', 'nomad-square-color', 'packer', 'packer-color', 'packer-fill', 'packer-fill-color', 'packer-square', 'packer-square-color', 'terraform', 'terraform-color', 'terraform-fill', 'terraform-fill-color', 'terraform-square', 'terraform-square-color', 'vagrant', 'vagrant-color', 'vagrant-fill', 'vagrant-fill-color', 'vagrant-square', 'vagrant-square-color', 'vault', 'vault-color', 'vault-fill', 'vault-fill-color', 'vault-square', 'vault-square-color', 'vault-radar', 'vault-radar-color', 'vault-radar-fill', 'vault-radar-fill-color', 'vault-radar-square', 'vault-radar-square-color', 'vault-secrets', 'vault-secrets-color', 'vault-secrets-fill', 'vault-secrets-fill-color', 'vault-secrets-square', 'vault-secrets-square-color', 'waypoint', 'waypoint-color', 'waypoint-fill', 'waypoint-fill-color', 'waypoint-square', 'waypoint-square-color', 'hashicorp', 'hashicorp-color', 'hashicorp-fill', 'hashicorp-fill-color', 'hashicorp-square', 'hashicorp-square-color', 'hcp', 'hcp-color', 'hcp-fill', 'hcp-fill-color', 'hcp-square', 'hcp-square-color', 'accessibility', 'folder-users', 'frown', 'fuel', 'identity-service', 'identity-user', 'meh', 'robot', 'smile', 'user', 'user-check', 'user-circle', 'user-circle-fill', 'user-minus', 'user-plus', 'user-x', 'users', 'ampersand', 'beaker', 'bucket', 'bulb', 'circle', 'circle-dot', 'circle-fill', 'circle-half', 'diamond', 'diamond-fill', 'disc', 'dot', 'dot-half', 'droplet', 'flag', 'gift', 'government', 'handshake', 'hash', 'hexagon', 'hexagon-fill', 'labyrinth', 'layers', 'moon', 'octagon', 'outline', 'random', 'rocket', 'sparkle', 'square', 'square-fill', 'sun', 'triangle', 'triangle-fill', 'truck', 'wand', 'zap', 'zap-off', 'docs', 'docs-download', 'docs-link', 'guide', 'guide-link', 'help', 'info', 'info-fill', 'learn', 'learn-link', 'support', 'alert-circle', 'alert-circle-fill', 'alert-diamond', 'alert-diamond-fill', 'alert-octagon', 'alert-octagon-fill', 'alert-triangle', 'alert-triangle-fill', 'check', 'check-circle', 'check-circle-fill', 'check-diamond', 'check-diamond-fill', 'check-hexagon', 'check-hexagon-fill', 'check-square', 'check-square-fill', 'skip', 'x', 'x-circle', 'x-circle-fill', 'x-diamond', 'x-diamond-fill', 'x-hexagon', 'x-hexagon-fill', 'x-square', 'x-square-fill', 'bug', 'certificate', 'eye', 'eye-off', 'fingerprint', 'key', 'keychain', 'lock', 'lock-fill', 'lock-off', 'shield', 'shield-alert', 'shield-check', 'shield-off', 'shield-x', 'token', 'unlock', 'verified', 'wall', 'minus', 'minus-circle', 'minus-circle-fill', 'minus-plus', 'minus-plus-circle', 'minus-plus-square', 'minus-square', 'minus-square-fill', 'plus', 'plus-circle', 'plus-circle-fill', 'plus-square', 'camera', 'camera-off', 'cast', 'closed-caption', 'fast-forward', 'film', 'headphones', 'image', 'music', 'pause', 'pause-circle', 'play', 'play-circle', 'radio', 'rewind', 'rss', 'skip-back', 'skip-forward', 'speaker', 'stop-circle', 'volume', 'volume-down', 'volume-2', 'volume-x', 'wifi', 'wifi-off', 'compass', 'crosshair', 'map', 'map-pin', 'navigation', 'navigation-alt', 'redirect', 'target', 'align-center', 'align-justify', 'align-left', 'align-right', 'battery', 'battery-charging', 'bookmark', 'bookmark-add', 'bookmark-add-fill', 'bookmark-fill', 'bookmark-remove', 'bookmark-remove-fill', 'bottom', 'top', 'start', 'end', 'command', 'crop', 'dashboard', 'delete', 'download', 'edit', 'entry-point', 'exit-point', 'external-link', 'filter', 'filter-circle', 'filter-fill', 'grid', 'grid-alt', 'home', 'jump-link', 'layout', 'link', 'list', 'maximize', 'maximize-alt', 'menu', 'minimize', 'minimize-alt', 'more-horizontal', 'more-vertical', 'mouse-pointer', 'move-horizontal', 'paperclip', 'pen-tool', 'pencil-tool', 'pin', 'pin-off', 'power', 'printer', 'reload', 'repeat', 'resize-column', 'rotate-cw', 'rotate-ccw', 'search', 'share', 'sidebar', 'sidebar-hide', 'sidebar-show', 'sign-in', 'sign-out', 'slash', 'slash-square', 'sort-asc', 'sort-desc', 'switcher', 'sync', 'sync-alert', 'sync-reverse', 'tag', 'toggle-left', 'toggle-right', 'trash', 'type', 'text-wrap', 'unfold-close', 'unfold-open', 'upload', 'zoom-in', 'zoom-out', 'archive', 'clipboard', 'clipboard-checked', 'clipboard-copy', 'clipboard-x', 'file', 'file-change', 'file-check', 'file-diff', 'file-minus', 'file-plus', 'file-source', 'file-text', 'file-x', 'files', 'folder', 'folder-fill', 'folder-minus', 'folder-minus-fill', 'folder-plus', 'folder-plus-fill', 'folder-star', 'inbox', 'api', 'auto-apply', 'build', 'change', 'change-circle', 'change-square', 'channel', 'cloud', 'cloud-check', 'cloud-download', 'cloud-lightning', 'cloud-lock', 'cloud-off', 'cloud-upload', 'cloud-x', 'code', 'coffee', 'connection', 'connection-gateway', 'cpu', 'duplicate', 'gateway', 'git-branch', 'git-commit', 'git-merge', 'git-pull-request', 'git-repo', 'hammer', 'key-values', 'mainframe', 'mesh', 'module', 'monitor', 'network', 'network-alt', 'node', 'path', 'pipeline', 'plug', 'replication-direct', 'replication-perf', 'scissors', 'server', 'server-cluster', 'serverless', 'service', 'settings', 'sliders', 'smartphone', 'socket', 'step', 'tablet', 'terminal', 'terminal-screen', 'test', 'tools', 'transform-data', 'tv', 'webhook', 'wrench', 'calendar', 'clock', 'clock-filled', 'delay', 'event', 'history', 'hourglass', 'watch', 'bar-chart', 'bar-chart-alt', 'box', 'collections', 'database', 'hard-drive', 'line-chart', 'line-chart-up', 'logs', 'package', 'pie-chart', 'queue', 'save', 'trend-down', 'trend-up', 'activity', 'at-sign', 'award', 'bell', 'bell-active', 'bell-active-fill', 'bell-off', 'discussion-circle', 'discussion-square', 'heart', 'heart-fill', 'heart-off', 'mail', 'mail-open', 'message-circle', 'message-circle-fill', 'message-square', 'message-square-fill', 'mic', 'mic-off', 'newspaper', 'phone', 'phone-call', 'phone-off', 'send', 'star', 'star-circle', 'star-fill', 'star-off', 'thumbs-down', 'thumbs-up', 'video', 'video-off', 'bank-vault', 'briefcase', 'credit-card', 'dollar-sign', 'enterprise', 'globe', 'globe-private', 'org', 'provider', 'shopping-bag', 'shopping-cart', 'arrow-down', 'arrow-down-circle', 'arrow-down-left', 'arrow-down-right', 'arrow-left', 'arrow-left-circle', 'arrow-right', 'arrow-right-circle', 'arrow-up', 'arrow-up-circle', 'arrow-up-left', 'arrow-up-right', 'caret', 'chevron-down', 'chevron-left', 'chevron-right', 'chevron-up', 'chevrons-down', 'chevrons-left', 'chevrons-right', 'chevrons-up', 'corner-down-left', 'corner-down-right', 'corner-left-down', 'corner-left-up', 'corner-right-down', 'corner-right-up', 'corner-up-left', 'corner-up-right', 'load-balancer', 'migrate', 'move', 'shuffle', 'swap-horizontal', 'swap-vertical', 'car', 'coffee', 'fuel', 'user-round-search', 'merge', 'split' ]; diff --git a/patches/decorator-transforms.patch b/patches/decorator-transforms.patch new file mode 100644 index 00000000..a3f3fbe8 --- /dev/null +++ b/patches/decorator-transforms.patch @@ -0,0 +1,14 @@ +diff --git a/dist/runtime-BPCpkOf1.js b/dist/runtime-BPCpkOf1.js +index f3b582cd3f9ab548117cc7b1948652aa10f25ac9..af1166f3add5f71de6525ba511eed40a89cde562 100644 +--- a/dist/runtime-BPCpkOf1.js ++++ b/dist/runtime-BPCpkOf1.js +@@ -32,7 +32,9 @@ function decorateFieldV2(prototype, prop, decorators, initializer) { + desc.initializer = initializer; + } + for (let decorator of decorators) { ++ if (decorator) { + desc = decorator(prototype, prop, desc) || desc; ++ } + } + if (desc.initializer === void 0) { + Object.defineProperty(prototype, prop, desc); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 22a8b631..2920a962 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,20 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + ember-focus-trap: 0.8.0 + +patchedDependencies: + '@hashicorp/design-system-components': + hash: fdd37c1f2b693ce834016a5e4d64d8935e1dc495259de08ee2a3da251e56aa34 + path: patches/@hashicorp__design-system-components.patch + '@hashicorp/flight-icons': + hash: a4015afaecef4b5506fd8d6276edc252725a2a582b566ec810996b493e06c511 + path: patches/@hashicorp__flight-icons.patch + decorator-transforms: + hash: daaf70dcf904cf2d805d085a6528bf51c0554bb9a602cfdd5025c76d86e07eb9 + path: patches/decorator-transforms.patch + importers: .: @@ -19,7 +33,7 @@ importers: version: 2.2.0 '@ember/test-helpers': specifier: ^2.6.0 - version: 2.9.6(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4)) + version: 2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)) '@faker-js/faker': specifier: ^8.1.0 version: 8.4.1 @@ -29,6 +43,12 @@ importers: '@glimmer/tracking': specifier: ^1.0.4 version: 1.1.2 + '@hashicorp/design-system-components': + specifier: ^4.24.0 + version: 4.24.0(patch_hash=fdd37c1f2b693ce834016a5e4d64d8935e1dc495259de08ee2a3da251e56aa34)(@babel/core@7.28.4)(@ember/string@3.1.1)(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)))(@glimmer/component@1.1.2(@babel/core@7.28.4))(@glimmer/tracking@1.1.2)(@glint/template@1.6.1)(ember-basic-dropdown@8.7.0(@babel/core@7.28.4)(@ember/string@3.1.1)(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)))(@glimmer/component@1.1.2(@babel/core@7.28.4))(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)))(ember-source@3.28.12(@babel/core@7.28.4))(webpack@5.101.3) + '@hashicorp/design-system-tokens': + specifier: ^3.0.0 + version: 3.0.0 babel-eslint: specifier: ^8.2.6 version: 8.2.6 @@ -44,15 +64,21 @@ importers: coveralls: specifier: ^3.1.1 version: 3.1.1 + decorator-transforms: + specifier: ^2.3.0 + version: 2.3.0(patch_hash=daaf70dcf904cf2d805d085a6528bf51c0554bb9a602cfdd5025c76d86e07eb9)(@babel/core@7.28.4) ember-auto-import: - specifier: ^2.7.4 - version: 2.10.1(webpack@5.101.3) + specifier: ^2.11.0 + version: 2.11.0(@glint/template@1.6.1)(webpack@5.101.3) + ember-basic-dropdown: + specifier: ^8.7.0 + version: 8.7.0(@babel/core@7.28.4)(@ember/string@3.1.1)(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)))(@glimmer/component@1.1.2(@babel/core@7.28.4))(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)) ember-buffered-proxy: specifier: ^2.1.1 version: 2.1.1(@babel/core@7.28.4) ember-classic-decorator: specifier: ^3.0.1 - version: 3.0.1 + version: 3.0.1(@glint/template@1.6.1) ember-cli: specifier: ~3.28.6 version: 3.28.6(babel-core@6.26.3)(handlebars@4.7.8)(underscore@1.13.7) @@ -62,9 +88,6 @@ importers: ember-cli-babel: specifier: ^8.2.0 version: 8.2.0(@babel/core@7.28.4) - ember-cli-clipboard: - specifier: ^1.1.0 - version: 1.3.0(@babel/core@7.28.4)(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4)))(webpack@5.101.3) ember-cli-code-coverage: specifier: ^2.0.0 version: 2.1.2 @@ -91,10 +114,10 @@ importers: version: 2.1.0 ember-cli-mirage: specifier: ^3.0.4 - version: 3.0.4(@ember-data/model@3.28.13(@babel/core@7.28.4))(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4)))(ember-data@3.28.13(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4)))(ember-qunit@5.1.5(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4)))(qunit@2.24.1))(ember-source@3.28.12(@babel/core@7.28.4))(miragejs@0.1.48)(webpack@5.101.3) + version: 3.0.4(@ember-data/model@3.28.13(@babel/core@7.28.4))(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)))(@glint/template@1.6.1)(ember-data@3.28.13(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4)))(ember-qunit@5.1.5(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)))(qunit@2.24.1))(ember-source@3.28.12(@babel/core@7.28.4))(miragejs@0.1.48)(webpack@5.101.3) ember-cli-page-object: specifier: ^2.1.1 - version: 2.3.2(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4))) + version: 2.3.2(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4))) ember-cli-sass: specifier: ^11.0.0 version: 11.0.1 @@ -112,7 +135,7 @@ importers: version: 5.0.0 ember-concurrency: specifier: ^4.0.2 - version: 4.0.6(@babel/core@7.28.4) + version: 4.0.6(@babel/core@7.28.4)(@glint/template@1.6.1) ember-custom-actions: specifier: ^3.3.0 version: 3.3.0(@babel/core@7.28.4) @@ -133,10 +156,10 @@ importers: version: 8.1.2 ember-highcharts: specifier: ^3.2.1 - version: 3.2.2(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4))(highcharts@12.4.0) + version: 3.2.2(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4))(highcharts@12.4.0) ember-lifeline: specifier: ^7.0.0 - version: 7.0.0(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4))) + version: 7.0.0(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4))) ember-load: specifier: 0.0.17 version: 0.0.17 @@ -148,16 +171,13 @@ importers: version: 0.8.1 ember-mobiledoc-editor: specifier: ^0.8.1 - version: 0.8.1(webpack@5.101.3) + version: 0.8.1(@glint/template@1.6.1)(webpack@5.101.3) ember-moment: specifier: ^10.0.0 version: 10.0.2(moment-timezone@0.6.0)(moment@2.30.1) ember-page-title: specifier: ^8.2.4 version: 8.2.4(ember-source@3.28.12(@babel/core@7.28.4)) - ember-paper: - specifier: 1.0.0-beta.36 - version: 1.0.0-beta.36(@babel/core@7.28.4)(@egjs/hammerjs@2.0.17)(ember-source@3.28.12(@babel/core@7.28.4)) ember-percy: specifier: ^1.6.0 version: 1.6.0 @@ -166,13 +186,16 @@ importers: version: 1.0.3(@babel/core@7.28.4) ember-power-calendar: specifier: ^1.1.0 - version: 1.8.0(@babel/core@7.28.4)(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4)))(@glimmer/component@1.1.2(@babel/core@7.28.4))(ember-concurrency@4.0.6(@babel/core@7.28.4))(ember-source@3.28.12(@babel/core@7.28.4)) + version: 1.8.0(@babel/core@7.28.4)(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)))(@glimmer/component@1.1.2(@babel/core@7.28.4))(@glint/template@1.6.1)(ember-concurrency@4.0.6(@babel/core@7.28.4)(@glint/template@1.6.1))(ember-source@3.28.12(@babel/core@7.28.4)) ember-power-calendar-moment: specifier: ^1.0.2 - version: 1.0.4(ember-power-calendar@1.8.0(@babel/core@7.28.4)(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4)))(@glimmer/component@1.1.2(@babel/core@7.28.4))(ember-concurrency@4.0.6(@babel/core@7.28.4))(ember-source@3.28.12(@babel/core@7.28.4)))(moment-timezone@0.6.0)(moment@2.30.1) + version: 1.0.4(@glint/template@1.6.1)(ember-power-calendar@1.8.0(@babel/core@7.28.4)(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)))(@glimmer/component@1.1.2(@babel/core@7.28.4))(@glint/template@1.6.1)(ember-concurrency@4.0.6(@babel/core@7.28.4)(@glint/template@1.6.1))(ember-source@3.28.12(@babel/core@7.28.4)))(moment-timezone@0.6.0)(moment@2.30.1) + ember-power-select: + specifier: ^8.11.0 + version: 8.11.0(@babel/core@7.28.4)(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)))(@glimmer/component@1.1.2(@babel/core@7.28.4))(@glint/template@1.6.1)(ember-basic-dropdown@8.7.0(@babel/core@7.28.4)(@ember/string@3.1.1)(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)))(@glimmer/component@1.1.2(@babel/core@7.28.4))(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)))(ember-concurrency@4.0.6(@babel/core@7.28.4)(@glint/template@1.6.1))(ember-source@3.28.12(@babel/core@7.28.4)) ember-qunit: specifier: ^5.1.5 - version: 5.1.5(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4)))(qunit@2.24.1) + version: 5.1.5(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)))(qunit@2.24.1) ember-qunit-nice-errors: specifier: ^1.2.1 version: 1.2.1 @@ -184,7 +207,7 @@ importers: version: 1.0.3 ember-simple-auth: specifier: ^6.0.0 - version: 6.1.0(@babel/core@7.28.4)(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4)))(ember-source@3.28.12(@babel/core@7.28.4))(eslint@8.57.1) + version: 6.1.0(@babel/core@7.28.4)(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)))(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4))(eslint@8.57.1) ember-source: specifier: ~3.28.8 version: 3.28.12(@babel/core@7.28.4) @@ -251,9 +274,6 @@ importers: npm-run-all: specifier: ^4.1.5 version: 4.1.5 - paper-data-table: - specifier: ^0.1.5 - version: 0.1.5(@babel/core@7.28.4) prettier: specifier: ^3.0.0 version: 3.6.2 @@ -454,20 +474,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-proposal-nullish-coalescing-operator@7.18.6': - resolution: {integrity: sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==} - engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead. - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-proposal-optional-chaining@7.21.0': - resolution: {integrity: sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==} - engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead. - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-proposal-private-methods@7.18.6': resolution: {integrity: sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==} engines: {node: '>=6.9.0'} @@ -506,16 +512,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': - resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-optional-chaining@7.8.3': - resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-private-property-in-object@7.14.5': resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} engines: {node: '>=6.9.0'} @@ -844,11 +840,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-typescript@7.8.7': - resolution: {integrity: sha512-7O0UsPQVNKqpHeHLpfvOG4uXmlw+MOxYvUv6Otc9uH5SYMIxvF6eBdjkWvC3f9G+VXe0RsNExyAQBeTRug/wqQ==} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-unicode-escapes@7.27.1': resolution: {integrity: sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==} engines: {node: '>=6.9.0'} @@ -921,14 +912,55 @@ packages: engines: {node: '>=0.1.95'} hasBin: true + '@codemirror/autocomplete@6.19.0': + resolution: {integrity: sha512-61Hfv3cF07XvUxNeC3E7jhG8XNi1Yom1G0lRC936oLnlF+jrbrv8rc/J98XlYzcsAoTVupfsf5fLej1aI8kyIg==} + + '@codemirror/commands@6.9.0': + resolution: {integrity: sha512-454TVgjhO6cMufsyyGN70rGIfJxJEjcqjBG2x2Y03Y/+Fm99d3O/Kv1QDYWuG6hvxsgmjXmBuATikIIYvERX+w==} + + '@codemirror/lang-css@6.3.1': + resolution: {integrity: sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==} + + '@codemirror/lang-go@6.0.1': + resolution: {integrity: sha512-7fNvbyNylvqCphW9HD6WFnRpcDjr+KXX/FgqXy5H5ZS0eC5edDljukm/yNgYkwTsgp2busdod50AOTIy6Jikfg==} + + '@codemirror/lang-html@6.4.11': + resolution: {integrity: sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==} + + '@codemirror/lang-javascript@6.2.4': + resolution: {integrity: sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==} + + '@codemirror/lang-json@6.0.2': + resolution: {integrity: sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==} + + '@codemirror/lang-markdown@6.4.0': + resolution: {integrity: sha512-ZeArR54seh4laFbUTVy0ZmQgO+C/cxxlW4jEoQMhL3HALScBpZBeZcLzrQmJsTEx4is9GzOe0bFAke2B1KZqeA==} + + '@codemirror/lang-sql@6.10.0': + resolution: {integrity: sha512-6ayPkEd/yRw0XKBx5uAiToSgGECo/GY2NoJIHXIIQh1EVwLuKoU8BP/qK0qH5NLXAbtJRLuT73hx7P9X34iO4w==} + + '@codemirror/lang-yaml@6.1.2': + resolution: {integrity: sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==} + + '@codemirror/language@6.11.3': + resolution: {integrity: sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==} + + '@codemirror/legacy-modes@6.5.2': + resolution: {integrity: sha512-/jJbwSTazlQEDOQw2FJ8LEEKVS72pU0lx6oM54kGpL8t/NJ2Jda3CZ4pcltiKTdqYSRk3ug1B3pil1gsjA6+8Q==} + + '@codemirror/lint@6.9.0': + resolution: {integrity: sha512-wZxW+9XDytH3SKvS8cQzMyQCaaazH8XL1EMHleHe00wVzsv7NBQKVW2yzEHrRhmM7ZOhVdItPbvlRBvMp9ej7A==} + + '@codemirror/state@6.5.2': + resolution: {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==} + + '@codemirror/view@6.38.4': + resolution: {integrity: sha512-hduz0suCcUSC/kM8Fq3A9iLwInJDl8fD1xLpTIk+5xkNm8z/FT7UsIa9sOXrkpChh+XXc18RzswE8QqELsVl+g==} + '@colors/colors@1.5.0': resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} - '@egjs/hammerjs@2.0.17': - resolution: {integrity: sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==} - engines: {node: '>=0.8.0'} - '@ember-data/adapter@3.28.13': resolution: {integrity: sha512-AwLJTs+GvxX72vfP3edV0hoMLD9oPWJNbnqxakXVN9xGTuk6/TeGQLMrVU3222GCoMMNrJ357Nip7kZeFo4IdA==} engines: {node: 12.* || >= 14.*} @@ -1015,6 +1047,10 @@ packages: resolution: {integrity: sha512-gcJuHiXgnrzaU8NyU+2bMbtS6PNOr5v5B8OXBqaBvTCsMpXLvKo8OBOQFCoUN0rPX2J6VaFqrbi/371sMvzZug==} engines: {node: 12.* || 14.* || >= 16} + '@embroider/macros@0.47.2': + resolution: {integrity: sha512-ViNWluJCeM5OPlM3rs8kdOz3RV5rpfXX5D2rDnc/q86xRS0xf4NFEjYRV7W6fBcD0b3v5jSHDTwrjq9Kee4rHg==} + engines: {node: 12.* || 14.* || >= 16} + '@embroider/macros@1.18.2': resolution: {integrity: sha512-mkgk0yjcYgujZQv9IGLD3yPb4a+d6EDKm22GK6TUyBCF8veTeg6HBXwHfu6K2DCnG0LEwnb2MJ0WCFGmTiatPw==} engines: {node: 12.* || 14.* || >= 16} @@ -1024,6 +1060,10 @@ packages: '@glint/template': optional: true + '@embroider/shared-internals@0.47.2': + resolution: {integrity: sha512-SxdZYjAE0fiM5zGDz+12euWIsQZ1tsfR1k+NKmiWMyLhA5T3pNgbR2/Djvx/cVIxOtEavGGSllYbzRKBtV4xMg==} + engines: {node: 12.* || 14.* || >= 16} + '@embroider/shared-internals@1.8.3': resolution: {integrity: sha512-N5Gho6Qk8z5u+mxLCcMYAoQMbN4MmH+z2jXwQHVs859bxuZTxwF6kKtsybDAASCtd2YGxEmzcc1Ja/wM28824w==} engines: {node: 12.* || 14.* || >= 16} @@ -1071,6 +1111,15 @@ packages: resolution: {integrity: sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0, npm: '>=6.14.13'} + '@floating-ui/core@1.7.3': + resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} + + '@floating-ui/dom@1.7.4': + resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==} + + '@floating-ui/utils@0.2.10': + resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + '@glimmer/component@1.1.2': resolution: {integrity: sha512-XyAsEEa4kWOPy+gIdMjJ8XlzA3qrGH55ZDv6nA16ibalCR17k74BI0CztxuRds+Rm6CtbUVgheCVlcCULuqD7A==} engines: {node: 6.* || 8.* || >= 10.*} @@ -1123,6 +1172,9 @@ packages: '@glimmer/wire-format@0.94.8': resolution: {integrity: sha512-A+Cp5m6vZMAEu0Kg/YwU2dJZXyYxVJs2zI57d3CP6NctmX7FsT8WjViiRUmt5abVmMmRH5b8BUovqY6GSMAdrw==} + '@glint/template@1.6.1': + resolution: {integrity: sha512-/VuVbS+p2ON/qYVlTljIBnNMR3eUiu202uXdYJHTQ7bfFWmLija+ZfdgQCjdm83uZUMXQqtWvjOwwFXe5cvPtg==} + '@handlebars/parser@1.1.0': resolution: {integrity: sha512-rR7tJoSwJ2eooOpYGxGGW95sLq6GXUaS1UtWvN7pei6n2/okYvCGld9vsUTvkl2migxbkszsycwtMf/GEc1k1A==} @@ -1130,9 +1182,24 @@ packages: resolution: {integrity: sha512-D76vKOZFEGA9v6g0rZTYTQDUXNopCblW1Zeas3EEVrbdeh8gWrCEO9/goocKmcgtqAwv1Md76p58UQp7HeFTEw==} engines: {node: ^18 || ^20 || ^22 || >=24} - '@html-next/vertical-collection@2.1.0': - resolution: {integrity: sha512-0H1X0vLR9SuK7CX8euwRgAWtX73N26oUGDmZdkeAgmZ1JkU/xR24ae94PpzkqBDzlk0sMv7NPrdDGWnxnXcUvw==} - engines: {node: '>= 10.*'} + '@hashicorp/design-system-components@4.24.0': + resolution: {integrity: sha512-gvy8/kEt0i27l8sV9UXfmwVo8ao99X6+x6oV3jF4SeK10/5oWjdEhv35IyDcArLyEVio5pTw+r0CHjuomCyhaA==} + engines: {node: '>= 18'} + peerDependencies: + '@ember/string': ^3.1.1 || ^4.0.0 + ember-engines: '>= 0.11.0' + ember-intl: ^7.3.0 + peerDependenciesMeta: + ember-engines: + optional: true + ember-intl: + optional: true + + '@hashicorp/design-system-tokens@3.0.0': + resolution: {integrity: sha512-ODOtuwDvOdAHQNnhVgqi43enCVWjXTnweYrxHDi3QkVUY1ysLkGrfAySnr4hVwd3UHmzT5HkQQbjN5zgr8l94w==} + + '@hashicorp/flight-icons@3.13.0': + resolution: {integrity: sha512-eUPjix112KlkS+LVzqHsvxxAaPNpjG1PmQYzTtUdmLNJkG1PsN1Mcaz3gU7HGNpUSVvOqlNbyCSxgeITBJ+7cw==} '@humanwhocodes/config-array@0.13.0': resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} @@ -1187,6 +1254,39 @@ packages: '@kwsites/promise-deferred@1.1.1': resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==} + '@lezer/common@1.2.3': + resolution: {integrity: sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==} + + '@lezer/css@1.3.0': + resolution: {integrity: sha512-pBL7hup88KbI7hXnZV3PQsn43DHy6TWyzuyk2AO9UyoXcDltvIdqWKE1dLL/45JVZ+YZkHe1WVHqO6wugZZWcw==} + + '@lezer/go@1.0.1': + resolution: {integrity: sha512-xToRsYxwsgJNHTgNdStpcvmbVuKxTapV0dM0wey1geMMRc9aggoVyKgzYp41D2/vVOx+Ii4hmE206kvxIXBVXQ==} + + '@lezer/highlight@1.2.1': + resolution: {integrity: sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==} + + '@lezer/html@1.3.12': + resolution: {integrity: sha512-RJ7eRWdaJe3bsiiLLHjCFT1JMk8m1YP9kaUbvu2rMLEoOnke9mcTVDyfOslsln0LtujdWespjJ39w6zo+RsQYw==} + + '@lezer/javascript@1.5.4': + resolution: {integrity: sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==} + + '@lezer/json@1.0.3': + resolution: {integrity: sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==} + + '@lezer/lr@1.4.2': + resolution: {integrity: sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==} + + '@lezer/markdown@1.4.3': + resolution: {integrity: sha512-kfw+2uMrQ/wy/+ONfrH83OkdFNM0ye5Xq96cLlaCy7h5UT9FO54DU4oRoIc0CSBh5NWmWuiIJA7NGLMJbQ+Oxg==} + + '@lezer/yaml@1.0.3': + resolution: {integrity: sha512-GuBLekbw9jDBDhGur82nuwkxKQ+a3W5H0GfaAthDXcAu+XdpS43VlnxA9E9hllkpSP5ellRDKjLLj7Lu9Wr6xA==} + + '@marijn/find-cluster-break@1.0.2': + resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==} + '@miragejs/pretender-node-polyfill@0.1.2': resolution: {integrity: sha512-M/BexG/p05C5lFfMunxo/QcgIJnMT2vDVCd00wNqK2ImZONIlEETZwWJu1QtLxtmYlSHlCFl3JNzp0tLe7OJ5g==} @@ -1205,6 +1305,9 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@nullvoxpopuli/ember-composable-helpers@5.3.0': + resolution: {integrity: sha512-pjuYVAxJJETaFFmDME9sPH++kSNcTJjxHqHUSJOwoYvxSRBHIysJbCFD/CHQjJtbI5D4pVouYU80ugmyGrZoFA==} + '@parcel/watcher-android-arm64@2.5.1': resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} engines: {node: '>= 10.0.0'} @@ -1295,6 +1398,9 @@ packages: resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@popperjs/core@2.11.8': + resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + '@ro0gr/ceibo@2.2.0': resolution: {integrity: sha512-4gSXPwwr99zUWxnTllN5L4QlfgFDloYKOsenoPvx46LE75x3wvLgGUhxUxhIMxJbqOZ0w9pzrugjQR7St0/PQg==} @@ -1320,9 +1426,6 @@ packages: '@types/body-parser@1.19.6': resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} - '@types/broccoli-plugin@1.3.0': - resolution: {integrity: sha512-SLk4/hFc2kGvgwNFrpn2O1juxFOllcHAywvlo7VwxfExLzoz1GGJ0oIZCwj5fwSpvHw4AWpZjJ1fUvb62PDayQ==} - '@types/broccoli-plugin@3.0.4': resolution: {integrity: sha512-VfG0WydDHFr6MGj75U16bKxOnrl8uP9bXvq7VD+NuvnAq5/22cQDrf8o7BnzBJQt+Xm9jkPt1hh2EHVWluGYIA==} deprecated: This is a stub types definition. broccoli-plugin provides its own type definitions, so you do not need this installed. @@ -1370,9 +1473,6 @@ packages: resolution: {integrity: sha512-00UxlRaIUvYm4R4W9WYkN8/J+kV8fmOQ7okeH6YFtGWFMt3odD45tpG5yA5wnL7HE6lLgjaTW5n14ju2hl2NNA==} deprecated: This is a stub types definition. glob provides its own type definitions, so you do not need this installed. - '@types/hammerjs@2.0.46': - resolution: {integrity: sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==} - '@types/http-errors@2.0.5': resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} @@ -1629,9 +1729,6 @@ packages: resolution: {integrity: sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==} engines: {node: '>=0.4.2'} - angular-material-styles@1.1.21: - resolution: {integrity: sha512-xa/j8sBrfqgSUQVkyDOwbDr+239z5NB9uBc2455qO6RXuyUU8tQRpkWIl2hyeeZRdNnf2dV6wQXh4gb0wSv4+g==} - ansi-escapes@3.2.0: resolution: {integrity: sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==} engines: {node: '>=4'} @@ -1887,6 +1984,10 @@ packages: babel-helpers@6.24.1: resolution: {integrity: sha512-n7pFrqQm44TCYvrCDb0MqabAF+JUBq+ijBvNMUxpkLjJaAu32faIexewMumrH5KLLJ1HDyT0PTEqRyAe/GwwuQ==} + babel-import-util@0.2.0: + resolution: {integrity: sha512-CtWYYHU/MgK88rxMrLfkD356dApswtR/kWZ/c6JifG1m10e7tBBrs/366dFzWMAoqYmG5/JSh+94tUSpIwh+ag==} + engines: {node: '>= 12.*'} + babel-import-util@1.4.1: resolution: {integrity: sha512-TNdiTQdPhXlx02pzG//UyVPSKE7SNWjY0n4So/ZnjQpWwaM5LvWBLkWa1JKll5u06HNscHD91XZPuwrMg1kadQ==} engines: {node: '>= 12.*'} @@ -1944,10 +2045,6 @@ packages: resolution: {integrity: sha512-jDLlxI8QnfKd7PtieH6pl4tZJzymzfCDCPGdTq/grgbiYAikwDPp/oL0IlFJn0HQjLpcLkyYhPKkUVneRESw5w==} engines: {node: '>=8'} - babel-plugin-htmlbars-inline-precompile@3.2.0: - resolution: {integrity: sha512-IUeZmgs9tMUGXYu1vfke5I18yYJFldFGdNFQOWslXTnDWXzpwPih7QFduUqvT+awDpDuNtXpdt5JAf43Q1Hhzg==} - engines: {node: 8.* || 10.* || >= 12.*} - babel-plugin-htmlbars-inline-precompile@5.3.1: resolution: {integrity: sha512-QWjjFgSKtSRIcsBhJmEwS2laIdrA6na8HAlc/pEAhjHgQsah/gMiBFRZvbQTy//hWxR4BMwV7/Mya7q5H8uHeA==} engines: {node: 10.* || >= 12.*} @@ -2258,9 +2355,6 @@ packages: broccoli-debug@0.6.5: resolution: {integrity: sha512-RIVjHvNar9EMCLDW/FggxFRXqpjhncM/3qq87bn/y+/zR9tqEkHvTqbyOc4QnB97NO2m6342w4wGkemkaeOuWg==} - broccoli-file-creator@1.2.0: - resolution: {integrity: sha512-l9zthHg6bAtnOfRr/ieZ1srRQEsufMZID7xGYRW3aBDv3u/3Eux+Iawl10tAGYE5pL9YB4n5X4vxkp6iNOoZ9g==} - broccoli-file-creator@2.1.1: resolution: {integrity: sha512-YpjOExWr92C5vhnK0kmD81kM7U09kdIRZk9w4ZDCDHuHXW+VE/x6AGEOQQW3loBQQ6Jk+k+TSm8dESy4uZsnjw==} engines: {node: ^4.5 || 6.* || >= 7.*} @@ -2315,9 +2409,6 @@ packages: resolution: {integrity: sha512-VabSGRpKIzpmC+r+tJueCE5h8k6vON7EIMMWu6d/FyPdtijwLQ7QvzShEw+m3mHoDzUaj/kiZsDYrS8X2adsBg==} engines: {node: 8.* || >= 10.*} - broccoli-output-wrapper@2.0.0: - resolution: {integrity: sha512-V/ozejo+snzNf75i/a6iTmp71k+rlvqjE3+jYfimuMwR1tjNNRdtfno+NGNQB2An9bIAeqZnKhMDurAznHAdtA==} - broccoli-output-wrapper@3.2.5: resolution: {integrity: sha512-bQAtwjSrF4Nu0CK0JOy5OZqw9t5U0zzv2555EA/cF8/a8SLDTIetk9UgrtMVw7qKLKdSpOZ2liZNeZZDaKgayw==} engines: {node: 10.* || >= 12.*} @@ -2343,10 +2434,6 @@ packages: resolution: {integrity: sha512-ElE4caljW4slapyEhSD9jU9Uayc8SoSABWdmY9SqbV8DHNxU6xg1jJsPcMm+cXOvggR3+G+OXAYQeFjWVnznaw==} engines: {node: 6.* || 8.* || >= 10.*} - broccoli-plugin@3.1.0: - resolution: {integrity: sha512-7w7FP8WJYjLvb0eaw27LO678TGGaom++49O1VYIuzjhXjK5kn2+AMlDm7CaUFw4F7CLGoVQeZ84d8gICMJa4lA==} - engines: {node: 8.* || 10.* || >= 12.*} - broccoli-plugin@4.0.7: resolution: {integrity: sha512-a4zUsWtA1uns1K7p9rExYVYG99rdKeGRymW0qOCNkvDPHQxVi3yVyJHhQbM3EZwdt2E0mnhr5e0c/bPpJ7p3Wg==} engines: {node: 10.* || >= 12.*} @@ -2355,10 +2442,6 @@ packages: resolution: {integrity: sha512-aky/Ovg5DbsrsJEx2QCXxHLA6ZR+9u1TNVTf85soP4gL8CjGGKQ/JU8R3BZ2ntkWzo6/83RCKzX6O+nlNKR5MQ==} engines: {node: '>=4.0'} - broccoli-rollup@4.1.1: - resolution: {integrity: sha512-hkp0dB5chiemi32t6hLe5bJvxuTOm1TU+SryFlZIs95KT9+94uj0C8w6k6CsZ2HuIdIZg6D252t4gwOlcTXrpA==} - engines: {node: '>=8.0'} - broccoli-rollup@5.0.0: resolution: {integrity: sha512-QdMuXHwsdz/LOS8zu4HP91Sfi4ofimrOXoYP/lrPdRh7lJYD87Lfq4WzzUhGHsxMfzANIEvl/7qVHKD3cFJ4tA==} engines: {node: '>=12.0'} @@ -2384,10 +2467,6 @@ packages: broccoli-sri-hash@2.1.2: resolution: {integrity: sha512-toLD/v7ut2ajcH8JsdCMG2Bpq2qkwTcKM6CMzVMSAJjaz/KpK69fR+gSqe1dsjh+QTdxG0yVvkq3Sij/XMzV6A==} - broccoli-stew@1.6.0: - resolution: {integrity: sha512-sUwCJNnYH4Na690By5xcEMAZqKgquUQnMAEuIiL3Z2k63mSw9Xg+7Ew4wCrFrMmXMcLpWjZDOm6Yqnq268N+ZQ==} - engines: {node: ^4.5 || 6.* || >= 7.*} - broccoli-stew@3.0.0: resolution: {integrity: sha512-NXfi+Vas24n3Ivo21GvENTI55qxKu7OwKRnCLWXld8MiLiQKQlWIq28eoARaFj0lTUFwUa4jKZeA7fW9PiWQeg==} engines: {node: 8.* || >= 10.*} @@ -2574,9 +2653,6 @@ packages: resolution: {integrity: sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==} engines: {node: '>=0.10.0'} - classlist-polyfill@1.2.0: - resolution: {integrity: sha512-GzIjNdcEtH4ieA2S8NmrSxv7DfEV5fmixQeyTmqmRmRJPGpRBaSnA2a0VrCjyT8iW8JjEdMbKzDotAJf+ajgaQ==} - clean-base-url@1.0.0: resolution: {integrity: sha512-9q6ZvUAhbKOSRFY7A/irCQ/rF0KIpa3uXpx6izm8+fp7b2H4hLeUJ+F1YYk9+gDQ/X8Q0MEyYs+tG3cht//HTg==} @@ -2622,8 +2698,8 @@ packages: resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} engines: {node: '>= 10'} - clipboard@2.0.11: - resolution: {integrity: sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==} + clipboard-polyfill@4.1.1: + resolution: {integrity: sha512-nbvNLrcX0zviek5QHLFRAaLrx8y/s8+RF2stH43tuS+kP5XlHMrcD0UGBWq43Hwp6WuuK7KefRMP56S45ibZkA==} cliui@7.0.4: resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} @@ -2640,6 +2716,9 @@ packages: resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} engines: {node: '>=0.10.0'} + codemirror-lang-hcl@0.0.0-beta.2: + resolution: {integrity: sha512-R3ew7Z2EYTdHTMXsWKBW9zxnLoLPYO+CrAa3dPZjXLrIR96Q3GR4cwJKF7zkSsujsnWgwRQZonyWpXYXfhQYuQ==} + collection-visit@1.0.0: resolution: {integrity: sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==} engines: {node: '>=0.10.0'} @@ -3000,6 +3079,9 @@ packages: create-hmac@1.1.7: resolution: {integrity: sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==} + crelt@1.0.6: + resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + cross-spawn@6.0.6: resolution: {integrity: sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==} engines: {node: '>=4.8'} @@ -3031,6 +3113,9 @@ packages: engines: {node: '>=4'} hasBin: true + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + cyclist@1.0.2: resolution: {integrity: sha512-0sVXIohTfLqVIW3kb/0n6IiWF3Ifj5nm2XaSrLq2DI6fKIGa2fYAZdk917rUneaeLVpYfFcyXE2ft0fe3remsA==} @@ -3150,9 +3235,6 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} - delegate@3.2.0: - resolution: {integrity: sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==} - delegates@1.0.0: resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} @@ -3256,35 +3338,34 @@ packages: electron-to-chromium@1.5.224: resolution: {integrity: sha512-kWAoUu/bwzvnhpdZSIc6KUyvkI1rbRXMT0Eq8pKReyOyaPZcctMli+EgvcN1PAvwVc7Tdo4Fxi2PsLNDU05mdg==} - element-closest@3.0.2: - resolution: {integrity: sha512-JxKQiJKX0Zr5Q2/bCaTx8P+UbfyMET1OQd61qu5xQFeWr1km3fGaxelSJtnfT27XQ5Uoztn2yIyeamAc/VX13g==} - engines: {node: '>=0.12.0'} - elliptic@6.6.1: resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==} - ember-arg-types@1.1.0: - resolution: {integrity: sha512-hWpUz0eiNkWzi3FgHW5QU6LyCDyUlTWwuIROHluEKZoa9m6LJVXbb/EVFgIG3FkAib6a5Ie00WvkXEZFXxh3+A==} - engines: {node: 14.* || >= 16} - - ember-assign-helper@0.4.0: - resolution: {integrity: sha512-GKHhT4HD2fhtDnuBk6eCdCA8XGew9hY7TVs8zjrykegiI7weC0CGtpJscmIG3O0gEEb0d07UTkF2pjfNGLx4Nw==} - engines: {node: '>= 12'} + ember-a11y-refocus@4.1.4: + resolution: {integrity: sha512-51tGk30bskObL1LsGZRxzqIxgZhIE8ZvvDYcT1OWphxZlq00+Arz57aMLS4Vz4qhSE40BfeN2qFYP/gXtp9qDA==} + engines: {node: 16.* || >= 18.*} ember-assign-helper@0.5.1: resolution: {integrity: sha512-dXHbwlBTJWVjG7k4dhVrT3Gh4nQt6rC2LjyltuPztIhQ+YcPYHMqAPJRJYLGZu16aPSJbaGF8K+u51i7CLzqlQ==} + ember-async-data@1.0.3: + resolution: {integrity: sha512-54OtoQwNi+/ZvPOVuT4t8fcHR9xL8N7kBydzcZSo6BIEsLYeXPi3+jUR8niWjfjXXhKlJ8EWXR0lTeHleTrxbw==} + peerDependencies: + ember-source: '>=4.8.4' + ember-auto-import@1.12.2: resolution: {integrity: sha512-gLqML2k77AuUiXxWNon1FSzuG1DV7PEPpCLCU5aJvf6fdL6rmFfElsZRh+8ELEB/qP9dT+LHjNEunVzd2dYc8A==} engines: {node: '>= 10.*'} - ember-auto-import@2.10.1: - resolution: {integrity: sha512-5K4lYSEBch3DKQn1VElFHDHcHTvhTJnB6aebf24VyIobLL+OMWORcCK1fiwyfPiVABztyuLXH2GiXDweNUtntA==} + ember-auto-import@2.11.0: + resolution: {integrity: sha512-6uM3v+pxvifR0wEMibF/F6/AjDK262JIEWPRvndaC1kaFERH29eXpeOVRVqowMhN9f4Spj3mKHpQjLLRM1macw==} engines: {node: 12.* || 14.* || >= 16} - ember-basic-dropdown@4.0.5: - resolution: {integrity: sha512-cD0cnL4gjoNNGJ+kMNtGlsikan5v5aVWp5Mkv8fgnLS96EGhmAMlTiRBexmyzK+4Zbe+xOlZKNQva4bZ7FrQGQ==} - engines: {node: 12.* || 14.* || >= 16} + ember-basic-dropdown@8.7.0: + resolution: {integrity: sha512-M++bfghpFXVRs3A3VdEtHcrv41STf2Far8JWJf3Sukh41oWVm76P0R6ZXBPJ0MyCJwR2qlHgCn1QIesP3fSOpA==} + peerDependencies: + '@ember/test-helpers': ^2.9.4 || ^3.2.1 || ^4.0.2 || ^5.0.0 + '@glimmer/component': ^1.1.2 || ^2.0.0 ember-buffered-proxy@2.1.1: resolution: {integrity: sha512-eF9m36dm+rYegmND3lTSg4HJVH67oQXmcHLZmWUIxZC9zKK+/tO8QEgXjbquixBCNyUFw+fTNeyCG2PGApjXDA==} @@ -3332,12 +3413,6 @@ packages: resolution: {integrity: sha512-AY1Sn3FJcYumpi2ZOrTqYsMPMGmURB6unqvpSx0gQvhOxBxYQyKCA4CKePvJ7YrDzmrUlPMV3dP1qTp2Quei5w==} engines: {node: ^4.5 || 6.* || >= 7.*} - ember-cli-clipboard@1.3.0: - resolution: {integrity: sha512-GTX+zzfxhfGyDgk00PcFIEAT063QrpeB3F2UYrKQYZmJiIFFlyriSRw9LrVcXjhEROCVjrHoOVrhcqCATEDAKw==} - engines: {node: 14.* || >= 16} - peerDependencies: - '@ember/test-helpers': '>= 2.9.3' - ember-cli-code-coverage@2.1.2: resolution: {integrity: sha512-4DpMv6SiGbAHNcfvSk0k/ybogd0iiUEgMdVxnb7t0+ITpLA7G6+bjGpd8W8ImoINcKdhSbP6Zl78WlWo/8XcDg==} engines: {node: 14.* || 16.* || >= 18} @@ -3411,18 +3486,10 @@ packages: resolution: {integrity: sha512-i9qwljBlpzU/ei0xN+FiCHUvU1ZdjVXk0OzRKoeMZJK3m4p29CvB095klT0q+PigvYFYHIyTaeSWmbgjP8CZiw==} engines: {node: 12.* || >= 14} - ember-cli-htmlbars@2.0.5: - resolution: {integrity: sha512-3f3PAxdnQ/fhQa8XP/3z4RLRgLHxV8j4Ln75aHbRdemOCjBa048KxL9l+acRLhCulbGQCMnLiIUIC89PAzLrcA==} - engines: {node: '>= 4.0.0'} - ember-cli-htmlbars@3.1.0: resolution: {integrity: sha512-cgvRJM73IT0aePUG7oQ/afB7vSRBV3N0wu9BrWhHX2zkR7A7cUBI7KC9VPk6tbctCXoM7BRGsCC4aIjF7yrfXA==} engines: {node: 6.* || 8.* || >= 10.*} - ember-cli-htmlbars@4.5.0: - resolution: {integrity: sha512-bYJpK1pqFu9AadDAGTw05g2LMNzY8xTCIqQm7dMJmKEoUpLRFbPf4SfHXrktzDh7Q5iggl6Skzf1M0bPlIxARw==} - engines: {node: 8.* || 10.* || >= 12.*} - ember-cli-htmlbars@5.7.2: resolution: {integrity: sha512-Uj6R+3TtBV5RZoJY14oZn/sNPnc+UgmC8nb5rI4P3fR/gYoyTFIZSXiIM7zl++IpMoIrocxOrgt+mhonKphgGg==} engines: {node: 10.* || >= 12.*} @@ -3478,17 +3545,9 @@ packages: ember-cli-path-utils@1.0.0: resolution: {integrity: sha512-Qq0vvquzf4cFHoDZavzkOy3Izc893r/5spspWgyzLCPTaG78fM3HsrjZm7UWEltbXUqwHHYrqZd/R0jS08NqSA==} - ember-cli-polyfill-importer@0.0.4: - resolution: {integrity: sha512-wzaA7/o2v1nJ6LZP8EWrHOQErfRsr1N7y1kpyKivske8GKH0XQTp3tlmVuVa+8lHSdv76PbXYuZm9lgDm/6cSA==} - engines: {node: 6.* || 8.* || >= 10.*} - ember-cli-preprocess-registry@3.3.0: resolution: {integrity: sha512-60GYpw7VPeB7TvzTLZTuLTlHdOXvayxjAQ+IxM2T04Xkfyu75O2ItbWlftQW7NZVGkaCsXSRAmn22PG03VpLMA==} - ember-cli-sass@10.0.1: - resolution: {integrity: sha512-dWVoX03O2Mot1dEB1AN3ofC8DDZb6iU4Kfkbr3WYi9S9bGVHrpR/ngsR7tuVBuTugTyG53FPtLLqYdqx7XjXdA==} - engines: {node: 6.* || 8.* || >= 10.*} - ember-cli-sass@11.0.1: resolution: {integrity: sha512-RMlFPMK4kaB+67seF/IIoY3EC4rRd+L58q+lyElrxB3FcQTgph/qmGwtqf9Up7m3SDbPiA7cccCOSmgReMgCXA==} engines: {node: '>= 10.*'} @@ -3523,10 +3582,6 @@ packages: resolution: {integrity: sha512-lo5YArbJzJi5ssvaGqTt6+FnhTALnSvYVuxM7lfyL1UCMudyNJ94ovH5C7n5il7ATd6WsNiAPRUO/v+s5Jq/aA==} engines: {node: 8.* || >= 10.*} - ember-cli-typescript@3.1.4: - resolution: {integrity: sha512-HJ73kL45OGRmIkPhBNFt31I1SGUvdZND+LCH21+qpq3pPlFpJG8GORyXpP+2ze8PbnITNLzwe5AwUrpyuRswdQ==} - engines: {node: 8.* || >= 10.*} - ember-cli-typescript@4.2.1: resolution: {integrity: sha512-0iKTZ+/wH6UB/VTWKvGuXlmwiE8HSIGcxHamwNhEC5x1mN3z8RfvsFZdQWYUzIWFN2Tek0gmepGRPTwWdBYl/A==} engines: {node: 10.* || >= 12.*} @@ -3560,22 +3615,10 @@ packages: resolution: {integrity: sha512-BtkjulweiXo9c3yVWrtexw2dTmBrvavD/xixNC6TKOBdrixUwU+6nuOO9dufDWsMxoid7MvtmDpzc9+mE8PdaA==} engines: {node: 10.* || >= 12.*} - ember-composability-tools@0.0.12: - resolution: {integrity: sha512-twb7I60OSSzHyJnCcl9OFw8OwfHvscHSQrgTFa4u6YNftKgNqXqc/Ejez1HqbG6I/CwGAcCrXquvTNDVSZdxvA==} - engines: {node: 8.* || >= 10.*} - ember-composable-helpers@5.0.0: resolution: {integrity: sha512-gyUrjiSju4QwNrsCLbBpP0FL6VDFZaELNW7Kbcp60xXhjvNjncYgzm4zzYXhT+i1lLA6WEgRZ3lOGgyBORYD0w==} engines: {node: 12.* || 14.* || >= 16} - ember-concurrency-decorators@2.0.3: - resolution: {integrity: sha512-r6O34YKI/slyYapVsuOPnmaKC4AsmBSwvgcadbdy+jHNj+mnryXPkm+3hhhRnFdlsKUKdEuXvl43lhjhYRLhhA==} - engines: {node: 10.* || >= 12} - - ember-concurrency@2.3.7: - resolution: {integrity: sha512-sz6sTIXN/CuLb5wdpauFa+rWXuvXXSnSHS4kuNzU5GSMDX1pLBWSuovoUk61FUe6CYRqBmT1/UushObwBGickQ==} - engines: {node: 10.* || 12.* || 14.* || >= 16} - ember-concurrency@4.0.6: resolution: {integrity: sha512-Ikwl2YwXVe8aBwrT1deWTcUVxVu6KxS1qeU1ks3EML1Q/nxwKgxCkGqTJavxczawO8H/SIW45dV4r7z5Yqd2Xg==} engines: {node: 16.* || >= 18} @@ -3595,10 +3638,6 @@ packages: resolution: {integrity: sha512-N/XFvZszrzyyX4IcNoeK4mJvIItNuONumhPLqi64T8NDjJkxBj4Pq61rvMkJx/9eZ8alzE4I8vYKOLxT0FvRuQ==} engines: {node: 10.* || >= 12} - ember-css-transitions@2.1.1: - resolution: {integrity: sha512-Kue3tMUHlmeEQvnV1YXoJSjk/wIKiywAT72ny89Yl7rRzEjgjOMcUD69HSg3ShsQNOpyzU0eOCANVtk00FjJig==} - engines: {node: 10.* || >= 12} - ember-custom-actions@3.3.0: resolution: {integrity: sha512-Elh0EBMu9NxmqBkB0jS6yziaHPZ68s6+8IXTzNJJTipquiCS8uBJz3TtYyF1eUapgH56uBJAzkRvDCe9/evP+w==} engines: {node: 6.* || 8.* || >= 10.*} @@ -3615,12 +3654,6 @@ packages: resolution: {integrity: sha512-TovtNqCumzyAiW0/OisSkkVK93xnVF4NRU6+FN0ubpfwEOpRrmM2RqDwXI6YAChCgSHON1cz0DfQStpA1Gjuuw==} engines: {node: 10.* || >= 12} - ember-element-helper@0.6.1: - resolution: {integrity: sha512-YiOdAMlzYul4ulkIoNp8z7iHDfbT1fbut/9xGFRfxDwU/FmF8HtAUB2f1veu/w50HTeZNopa1OV2PCloZ76XlQ==} - engines: {node: 12.* || 14.* || >= 16} - peerDependencies: - ember-source: ^3.8 || 4 - ember-element-helper@0.8.8: resolution: {integrity: sha512-3slTltQV5ke53t3YVP2GYoswsQ6y+lhuVzKmt09tbEx91DapG8I/xa8W5OA0StvcQlavL3/vHrz/vCQEFs8bBA==} engines: {node: 14.* || 16.* || >= 18} @@ -3643,20 +3676,16 @@ packages: resolution: {integrity: sha512-TVx24/jrvDIuPL296DV0hBwp7BWLcSMf0I8464KGz01sPytAB+ZAePbc9ooBTJDkKZEGFgatJa4nj3yF1S9Bpw==} engines: {node: '>= 10'} + ember-focus-trap@0.8.0: + resolution: {integrity: sha512-Iaqi8lmsVXEtW5gQIWa8x3jXGhdpLrq4HRdZl1GHzHL4ys3b7oaPsJl/CS/jjeJPGSQ+tv5IT3Rn36BUZe5Pig==} + engines: {node: '>= 10.*'} + ember-functions-as-helper-polyfill@2.1.3: resolution: {integrity: sha512-Hte8jfOmSNzrz/vOchf68CGaBWXN2/5qKgFaylqr9omW2i4Wt9JmaBWRkeR0AJ53N57q3DX2TOb166Taq6QjiA==} engines: {node: '>= 14.0.0'} peerDependencies: ember-source: ^3.25.0 || >=4.0.0 - ember-get-config@0.5.0: - resolution: {integrity: sha512-y1osD6g8wV/BlDjuaN6OG5MT0iHY2X/yE38gUj/05uUIMIRfpcwOdWnFQHBiXIhDojvAJQTEF1VOYFIETQMkeQ==} - engines: {node: 12.* || 14.* || >= 16} - - ember-get-config@1.1.0: - resolution: {integrity: sha512-diD+HwwY8QqpEk5DnDYfH7onYwl6NOgr1qv1ENbXih+/iiWYUVS/e0S/PlM7A4gdorD9spn1bnisnTLTf49Wpw==} - engines: {node: 12.* || 14.* || >= 16} - ember-get-config@2.1.1: resolution: {integrity: sha512-uNmv1cPG/4qsac8oIf5txJ2FZ8p88LEpG4P3dNcjsJS98Y8hd0GPMFwVqpnzI78Lz7VYRGQWY4jnE4qm5R3j4g==} engines: {node: 12.* || 14.* || >= 16} @@ -3671,10 +3700,6 @@ packages: resolution: {integrity: sha512-JwlBWz4rNNLs+LBbh/ZCq9rQpsFbuG4Tzmx/n887LMDcuIAMNGORcEiPtqZvX/MC2MLKfjGCirtVa5vCTyxxcA==} engines: {node: 8.* || 10.* || >= 12.*} - ember-in-element-polyfill@0.2.2: - resolution: {integrity: sha512-aWFWTpDU+6mHqQd3Gr0UyrwMXGiGuLBJXYczExNGJsc2zUv/9rDot9HIpMr0sgXtWddzM0Z9Ly1H0sbYZ1ExCA==} - engines: {node: 8.* || >= 10.*} - ember-in-element-polyfill@1.0.1: resolution: {integrity: sha512-eHs+7D7PuQr8a1DPqsJTsEyo3FZ1XuH6WEZaEBPDa9s0xLlwByCNKl8hi1EbXOgvgEZNHHi9Rh0vjxyfakrlgg==} engines: {node: 10.* || >= 12} @@ -3702,10 +3727,6 @@ packages: resolution: {integrity: sha512-gyJrGwUHOri9En8QG/v2NIwxHFC5DFifar+1TRLwsNSeyJaUSwoFExLCVN8xYoDrI5CX1lmRoOUcfcrEApKwfg==} engines: {node: 6.* || 8.* || >= 10.*} - ember-maybe-in-element@2.1.0: - resolution: {integrity: sha512-6WAzPbf4BNQIQzkur2+zRJJJ/PKQoujIYgFjrpj3fOPy8iRlxVUm0/B41qbFyg1LE6bVbg0cWbuESWEvJ9Rswg==} - engines: {node: 10.* || >= 12} - ember-mobiledoc-dom-renderer@0.8.1: resolution: {integrity: sha512-4hROYsaLslqdsnnSud5Q2Lq5LbVFFLkiSkNdauCFMW2wXxhHCPlcO64mjCJyK7crvKFP2a2TflWlkb+kFPeShQ==} engines: {node: 12.* || 14.* || >= 16} @@ -3718,14 +3739,6 @@ packages: resolution: {integrity: sha512-bnaKF1LLKMkBNeDoetvIJ4vhwRPKIIumWr6dbVuW6W6p4QV8ZiO+GdF8J7mxDNlog9CeL9Z/7wam4YS86G8BYA==} engines: {node: 6.* || 8.* || >= 10.*} - ember-modifier@2.1.2: - resolution: {integrity: sha512-3Lsu1fV1sIGa66HOW07RZc6EHISwKt5VA5AUnFss2HX6OTfpxTJ2qvPctt2Yt0XPQXJ4G6BQasr/F35CX7UGJA==} - engines: {node: 10.* || >= 12} - - ember-modifier@3.2.7: - resolution: {integrity: sha512-ezcPQhH8jUfcJQbbHji4/ZG/h0yyj1jRDknfYue/ypQS8fM8LrGcCMo0rjDZLzL1Vd11InjNs3BD7BdxFlzGoA==} - engines: {node: 12.* || >= 14} - ember-modifier@4.2.2: resolution: {integrity: sha512-pPYBAGyczX0hedGWQFQOEiL9s45KS9efKxJxUQkMLjQyh+1Uef1mcmAGsdw2KmvNupITkE/nXxmVO1kZ9tt3ag==} @@ -3746,10 +3759,6 @@ packages: peerDependencies: ember-source: '>= 3.28.0' - ember-paper@1.0.0-beta.36: - resolution: {integrity: sha512-wYVrKgsh3qXf4+6yDkCb0RSksaCe1SGaMcq75Ss0cZ37+xcEGW3kxpN56DQ51wT2U/ueCnuSQNM9wLfhcShS3w==} - engines: {node: 12.* || 14.* || >= 16} - ember-percy@1.6.0: resolution: {integrity: sha512-E/g1gF55OtEemjWeffHAOujwKLHhiwWMy2EFZ7w9aST8viVP7eI3LKEe/1ZeMBxw46BiE7EJeoUesn9zcnlurQ==} engines: {node: 8.* || >= 10.*} @@ -3777,9 +3786,13 @@ packages: '@glimmer/component': ^1.1.2 || ^2.0.0 ember-concurrency: ^4.0.4 || ^5.1.0 - ember-power-select@5.0.4: - resolution: {integrity: sha512-FLVShZr3cYdLzymP+/0rXkfH5tD/FD958kynY86MWvn7ZyL2tXowlgVKY08n+PJHdo/vCrzqubRYckvXRSH6Bg==} - engines: {node: '>= 12'} + ember-power-select@8.11.0: + resolution: {integrity: sha512-Czl8kiPZGr/TFXd3J68ZSjXyeB9NBBQ5SDYl9ym8CmP5AKrN6TL15kU1phc/7jSBFuiHMiWS/xPcAQZr8qc30w==} + peerDependencies: + '@ember/test-helpers': ^2.9.4 || ^3.2.1 || ^4.0.2 || ^5.0.0 + '@glimmer/component': ^1.1.2 || ^2.0.0 + ember-basic-dropdown: ^8.7.0 + ember-concurrency: ^4.0.4 || ^5.1.0 ember-qunit-nice-errors@1.2.1: resolution: {integrity: sha512-KlANdv/vthqX9ZcDZSCm+vcHlCAVczR6Zr+yZpwD2s473ePCp0hZM20mcZ7PUVhDVMk66SGWuoHfgsOToh+SvQ==} @@ -3792,14 +3805,27 @@ packages: '@ember/test-helpers': ^2.4.0 qunit: ^2.13.0 - ember-raf-scheduler@0.2.0: - resolution: {integrity: sha512-z34reOLFORfigukrba3d5I333rixfcPjjfKPOKeaPAHf3I5wXfenoAze0Ijc7r4l/lO/4ocLw9RJbtNL2/oNpw==} - engines: {node: '>= 10.*'} - ember-resolver@8.1.0: resolution: {integrity: sha512-MGD7X2ztZVswGqs1mLgzhZJRhG7XiF6Mg4DgC7xJFWRYQQUHyGJpGdNWY9nXyrYnRIsCrQoL1do41zpxbrB/cg==} engines: {node: '>= 10.*'} + ember-resources@6.5.2: + resolution: {integrity: sha512-8JQ9ebTcKjsmhR5AJ7JNiXziuOiILjrEbGRqcFKkTvodK4QdvvOspDz8yejsf/J/1YUMFe4fjJnjqc2wpORX2Q==} + peerDependencies: + '@ember/test-waiters': '>= 3.0.0' + '@glimmer/component': '>= 1.1.2' + '@glimmer/tracking': '>= 1.1.2' + '@glint/template': ^1.0.0-beta.3 || >= 1.0.0 + ember-concurrency: ^2.0.0 || >= 3.0.0 + ember-source: ^3.28.0 || ^4.0.0 || >= 5.0.0 + peerDependenciesMeta: + '@ember/test-waiters': + optional: true + '@glimmer/component': + optional: true + ember-concurrency: + optional: true + ember-rfc176-data@0.3.18: resolution: {integrity: sha512-JtuLoYGSjay1W3MQAxt3eINWXNYYQliK90tLwtb8aeCuQK8zKGCRbBodVIrkcTqshULMnRuTOS6t1P7oQk3g6Q==} @@ -3827,9 +3853,14 @@ packages: resolution: {integrity: sha512-HGrBpY6TN+MAi7F6BS8XYtNFG6vtbKE9ttPcyj0Ps+76kP7isCHyN0hk8ecKciLq7JYDqiPDNWjdIXAn2JfhZA==} engines: {node: 10.* || >= 12.*} - ember-style-modifier@0.7.0: - resolution: {integrity: sha512-iDzffiwJcb9j6gu3g8CxzZOTvRZ0BmLMEFl+uyqjiaj72VVND9+HbLyQRw1/ewPAtinhSktxxTTdwU/JO+stLw==} - engines: {node: 12.* || 14.* || >= 16} + ember-stargate@0.5.0: + resolution: {integrity: sha512-HYUww+s1M5X4nmErc3VxsCmGAelBrp8AecObadEvO3u6c9cF8RpsMciWpjfvcD94gy0sneIg61S91S4XJaormQ==} + + ember-style-modifier@4.5.1: + resolution: {integrity: sha512-ReVGW9fZmDIsCWsuJGH4joiiHOv9aF9Yv4lUZUjXjQyR9SEAae7RWjZcjPgmEJwpN7yDSyy4PIwdJa0smT2A3g==} + engines: {node: 18.* || >= 20, pnpm: '>= 10.*'} + peerDependencies: + '@ember/string': ^3.1.1 || ^4.0.0 ember-template-imports@4.3.0: resolution: {integrity: sha512-jZ5D6KLKU8up/AynZltmKh4lkXBPgTGSPgomprI/55XvIVqn42UNUpEz7ra/mO3QiGODDZOUesbggPe49i38sQ==} @@ -3849,13 +3880,9 @@ packages: resolution: {integrity: sha512-PgYcI9PeNvtKaF0QncxfbS68olMYM1idwuI8v/WxsjOGqUx5bmsu6V17vy/d9hX4mwmjgsBhEghrVasGSuaIgw==} engines: {node: 12.* || 14.* || >= 16.*} - ember-text-measurer@0.6.0: - resolution: {integrity: sha512-/aZs2x2i6kT4a5tAW+zenH2wg8AbRK9jKxLkbVsKl/1ublNl27idVRdov1gJ+zgWu3DNK7whcfVycXtlaybYQw==} - engines: {node: 10.* || >= 12} - - ember-truth-helpers@3.1.1: - resolution: {integrity: sha512-FHwJAx77aA5q27EhdaaiBFuy9No+8yaWNT5A7zs0sIFCmf14GbcLn69vJEp6mW7vkITezizGAWhw7gL0Wbk7DA==} - engines: {node: 10.* || >= 12} + ember-tracked-storage-polyfill@1.0.0: + resolution: {integrity: sha512-eL7lZat68E6P/D7b9UoTB5bB5Oh/0aju0Z7PCMi3aTwhaydRaxloE7TGrTRYU+NdJuyNVZXeGyxFxn2frvd3TA==} + engines: {node: 12.* || >= 14} ember-truth-helpers@4.0.3: resolution: {integrity: sha512-T6Ogd3pk9FxYiZfSxdjgn3Hb3Ksqgw7CD23V9qfig9jktNdkNEHo4+3PA3cSD/+3a2kdH3KmNvKyarVuzdtEkA==} @@ -4143,10 +4170,6 @@ packages: resolution: {integrity: sha512-Y/URAVapfbYy2Xp/gb6A0E7iR8xeqOCXsuuaoMn7A5PzrXUK84E1gyiEfq0wQd/GHA6GsoHWwhNq8anb0mleIw==} engines: {node: ^8.12.0 || >=9.7.0} - execa@3.4.0: - resolution: {integrity: sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g==} - engines: {node: ^8.12.0 || >=9.7.0} - execa@4.1.0: resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==} engines: {node: '>=10'} @@ -4227,9 +4250,6 @@ packages: fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} - fastboot-transform@0.1.3: - resolution: {integrity: sha512-6otygPIJw1ARp1jJb+6KVO56iKBjhO+5x59RSC9qiZTbZRrv+HZAuP00KD3s+nWMvcFDemtdkugki9DNFTTwCQ==} - fastq@1.19.1: resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} @@ -4348,6 +4368,9 @@ packages: flush-write-stream@1.1.1: resolution: {integrity: sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==} + focus-trap@6.9.4: + resolution: {integrity: sha512-v2NTsZe2FF59Y+sDykKY+XjqZ0cPfhq/hikWVL88BqLivnNiEffAsac6rP6H45ff9wG9LL5ToiDqrLEP9GX9mw==} + follow-redirects@1.15.11: resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} engines: {node: '>=4.0'} @@ -4607,9 +4630,6 @@ packages: globrex@0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} - good-listener@1.2.2: - resolution: {integrity: sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==} - gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -4626,10 +4646,6 @@ packages: growly@1.3.0: resolution: {integrity: sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw==} - hammerjs@2.0.8: - resolution: {integrity: sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==} - engines: {node: '>=0.8.0'} - handlebars@4.7.8: resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} engines: {node: '>=0.4.7'} @@ -5581,9 +5597,6 @@ packages: resolution: {integrity: sha512-daE62nS2ZQsDg9raM0IlZzLmI2u+7ZapXBwdoeBUKAYERPDDIc0qNqA8E0Rp2D+gspKR7BgIFP52GeujaGXWeQ==} engines: {node: 6.* || 8.* || >= 10.*} - matchmedia-polyfill@0.3.2: - resolution: {integrity: sha512-B2zRzjqxZFUusBZrZux59XFFLoTN99SbGranxIHfjZVLGZuy8Iaf/s5iNR3qJwRQZBjBKsU6qBSUCltLV82gdw==} - math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -6074,10 +6087,6 @@ packages: pako@1.0.11: resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} - paper-data-table@0.1.5: - resolution: {integrity: sha512-lcmsxoxgs3oap2bsMrX9nXPbB3aWt51UxUV0bXyGdKxkZaG0Da1jd9XSfasa+uRQFY6FQupk7+AqwF+VDh4zPQ==} - engines: {node: 6.* || 8.* || >= 10.*} - parallel-transform@1.2.0: resolution: {integrity: sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==} @@ -6234,9 +6243,6 @@ packages: resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} engines: {node: '>=8'} - polyfill-nodelist-foreach@1.1.2: - resolution: {integrity: sha512-/P41VySdSHNazme8TxlRJSzNWlvYBFzldUHST2EoBCpB5yu1Nu9TcatU4rwzEKaW5rl6ZgDMnoIon5dhf4ttSw==} - portfinder@1.0.38: resolution: {integrity: sha512-rEwq/ZHlJIKw++XtLAO8PPuOQA/zaPJOZJ37BVuN97nLpMJeuDVLVGRwbFoBgLudgdTMP2hdRJP++H+8QOA3vg==} engines: {node: '>= 10.12'} @@ -6314,6 +6320,10 @@ packages: resolution: {integrity: sha512-is0ctgGdPJ5951KulgfzvHGwJtZ5ck8l042vRkV6jrkpBzTmb/lueTqguWHy2JfVA+RY6gFVlaZgUS0j7S/dsw==} engines: {node: '>= 0.9.0'} + prismjs@1.30.0: + resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} + engines: {node: '>=6'} + private@0.1.8: resolution: {integrity: sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==} engines: {node: '>= 0.6'} @@ -6347,14 +6357,6 @@ packages: resolution: {integrity: sha512-KYcnXctWUWyVD3W3Ye0ZDuA1N8Szrh85cVCxpG6xYrOk/0CttRtYCmU30nWsUch0NuExQQ63QXvzRE6FLimZmg==} engines: {node: 10.* || >= 12.*} - prop-types@15.8.1: - resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} - - propagating-hammerjs@2.0.1: - resolution: {integrity: sha512-PH3zG5whbSxMocphXJzVtvKr+vWAgfkqVvtuwjSJ/apmEACUoiw6auBAT5HYXpZOR0eGcTAfYG5Yl8h91O5Elg==} - peerDependencies: - '@egjs/hammerjs': ^2.0.17 - proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -6452,9 +6454,6 @@ packages: resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} engines: {node: '>= 0.8'} - react-is@16.13.1: - resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - read-pkg@3.0.0: resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==} engines: {node: '>=4'} @@ -6803,9 +6802,6 @@ packages: resolution: {integrity: sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==} engines: {node: '>= 10.13.0'} - select@1.1.2: - resolution: {integrity: sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==} - semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true @@ -7185,6 +7181,9 @@ packages: peerDependencies: webpack: ^4.0.0 || ^5.0.0 + style-mod@4.1.2: + resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==} + styled_string@0.0.1: resolution: {integrity: sha512-DU2KZiB6VbPkO2tGSqQ9n96ZstUPjW7X4sGO6V2m1myIQluX0p1Ol8BrA/l6/EesqhMqXOIXs3cJNOy1UuU2BA==} @@ -7228,6 +7227,12 @@ packages: resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==} engines: {node: ^14.18.0 || >=16.0.0} + tabbable@5.3.3: + resolution: {integrity: sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA==} + + tabbable@6.2.0: + resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} + tap-parser@7.0.0: resolution: {integrity: sha512-05G8/LrzqOOFvZhhAk32wsGiPZ1lfUrl+iV7+OkKgfofZxiceZWMHkKmow71YsyVQ8IvGBP2EjcIjE5gL4l5lA==} hasBin: true @@ -7318,17 +7323,14 @@ packages: resolution: {integrity: sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==} engines: {node: '>=0.6.0'} - tiny-emitter@2.1.0: - resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==} - tiny-glob@0.2.9: resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==} tiny-lr@2.0.0: resolution: {integrity: sha512-f6nh0VMRvhGx4KCeK1lQ/jaL0Zdb5WdR+Jk8q9OSUQnaSDxAEGH1fgqLZ+cMl5EW3F2MGnCsalBO1IsnnogW1Q==} - tinycolor2@1.6.0: - resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} + tippy.js@6.3.7: + resolution: {integrity: sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==} tmp@0.0.28: resolution: {integrity: sha512-c2mmfiBmND6SOVxzogm1oda0OJ1HZVIk/5n26N59dDTh80MUeavpiCls4PGAdkX1PFkKokLpcf7prSjCeXLsJg==} @@ -7391,6 +7393,13 @@ packages: tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + tracked-built-ins@4.0.0: + resolution: {integrity: sha512-0Jl43A1SDZd+yYCJvXfgDSn4Wk/zcawkyFTBPqOETU5UJRngnVEnQ8oOjawqPRg6qja3sKjIQ8z6X9xJzcUTUA==} + + tracked-maps-and-sets@3.0.2: + resolution: {integrity: sha512-UIRcWsX1kDOcC/Q2R58weYWlw01EnmWWBwUv3okWS+zMBvsgIfYoO6veHhuNE3hgzWCEImNp46QS5CyKnw5QUA==} + engines: {node: 12.* || >= 14} + tree-sync@1.4.0: resolution: {integrity: sha512-YvYllqh3qrR5TAYZZTXdspnIhlKAYezPYw11ntmweoceu4VK+keN356phHRIIo1d+RDmLpHZrUlmxga2gc9kSQ==} @@ -7637,6 +7646,9 @@ packages: vm-browserify@1.1.2: resolution: {integrity: sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==} + w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + walk-sync@0.2.7: resolution: {integrity: sha512-OH8GdRMowEFr0XSHQeX5fGweO6zSVHo7bG/0yJQx6LAj9Oukz0C8heI3/FYectT66gY0IPGe89kOvU410/UNpg==} @@ -8098,21 +8110,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.28.4)': - dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.4) - - '@babel/plugin-proposal-optional-chaining@7.21.0(@babel/core@7.28.4)': - dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.4) - transitivePeerDependencies: - - supports-color - '@babel/plugin-proposal-private-methods@7.18.6(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 @@ -8150,16 +8147,6 @@ snapshots: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.4)': - dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.4)': - dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 @@ -8529,15 +8516,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-typescript@7.8.7(@babel/core@7.28.4)': - dependencies: - '@babel/core': 7.28.4 - '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.4) - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.4) - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 @@ -8711,12 +8689,124 @@ snapshots: exec-sh: 0.3.6 minimist: 1.2.8 - '@colors/colors@1.5.0': - optional: true + '@codemirror/autocomplete@6.19.0': + dependencies: + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.4 + '@lezer/common': 1.2.3 + + '@codemirror/commands@6.9.0': + dependencies: + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.4 + '@lezer/common': 1.2.3 + + '@codemirror/lang-css@6.3.1': + dependencies: + '@codemirror/autocomplete': 6.19.0 + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@lezer/common': 1.2.3 + '@lezer/css': 1.3.0 + + '@codemirror/lang-go@6.0.1': + dependencies: + '@codemirror/autocomplete': 6.19.0 + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@lezer/common': 1.2.3 + '@lezer/go': 1.0.1 + + '@codemirror/lang-html@6.4.11': + dependencies: + '@codemirror/autocomplete': 6.19.0 + '@codemirror/lang-css': 6.3.1 + '@codemirror/lang-javascript': 6.2.4 + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.4 + '@lezer/common': 1.2.3 + '@lezer/css': 1.3.0 + '@lezer/html': 1.3.12 + + '@codemirror/lang-javascript@6.2.4': + dependencies: + '@codemirror/autocomplete': 6.19.0 + '@codemirror/language': 6.11.3 + '@codemirror/lint': 6.9.0 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.4 + '@lezer/common': 1.2.3 + '@lezer/javascript': 1.5.4 + + '@codemirror/lang-json@6.0.2': + dependencies: + '@codemirror/language': 6.11.3 + '@lezer/json': 1.0.3 + + '@codemirror/lang-markdown@6.4.0': + dependencies: + '@codemirror/autocomplete': 6.19.0 + '@codemirror/lang-html': 6.4.11 + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.4 + '@lezer/common': 1.2.3 + '@lezer/markdown': 1.4.3 + + '@codemirror/lang-sql@6.10.0': + dependencies: + '@codemirror/autocomplete': 6.19.0 + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@codemirror/lang-yaml@6.1.2': + dependencies: + '@codemirror/autocomplete': 6.19.0 + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + '@lezer/yaml': 1.0.3 + + '@codemirror/language@6.11.3': + dependencies: + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.4 + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + style-mod: 4.1.2 + + '@codemirror/legacy-modes@6.5.2': + dependencies: + '@codemirror/language': 6.11.3 + + '@codemirror/lint@6.9.0': + dependencies: + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.4 + crelt: 1.0.6 + + '@codemirror/state@6.5.2': + dependencies: + '@marijn/find-cluster-break': 1.0.2 - '@egjs/hammerjs@2.0.17': + '@codemirror/view@6.38.4': dependencies: - '@types/hammerjs': 2.0.46 + '@codemirror/state': 6.5.2 + crelt: 1.0.6 + style-mod: 4.1.2 + w3c-keyname: 2.2.8 + + '@colors/colors@1.5.0': + optional: true '@ember-data/adapter@3.28.13(@babel/core@7.28.4)': dependencies: @@ -8880,12 +8970,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@ember/render-modifiers@2.1.0(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4))': + '@ember/render-modifiers@2.1.0(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4))': dependencies: - '@embroider/macros': 1.18.2 + '@embroider/macros': 1.18.2(@glint/template@1.6.1) ember-cli-babel: 7.26.11 ember-modifier-manager-polyfill: 1.2.0(@babel/core@7.28.4) ember-source: 3.28.12(@babel/core@7.28.4) + optionalDependencies: + '@glint/template': 1.6.1 transitivePeerDependencies: - '@babel/core' - supports-color @@ -8896,11 +8988,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@ember/test-helpers@2.9.6(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4))': + '@ember/test-helpers@2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4))': dependencies: '@ember/test-waiters': 3.1.0 - '@embroider/macros': 1.18.2 - '@embroider/util': 1.13.4(ember-source@3.28.12(@babel/core@7.28.4)) + '@embroider/macros': 1.18.2(@glint/template@1.6.1) + '@embroider/util': 1.13.4(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)) broccoli-debug: 0.6.5 broccoli-funnel: 3.0.8 ember-cli-babel: 7.26.11 @@ -8931,7 +9023,19 @@ snapshots: transitivePeerDependencies: - supports-color - '@embroider/macros@1.18.2': + '@embroider/macros@0.47.2': + dependencies: + '@embroider/shared-internals': 0.47.2 + assert-never: 1.4.0 + ember-cli-babel: 7.26.11 + find-up: 5.0.0 + lodash: 4.17.21 + resolve: 1.22.10 + semver: 7.7.2 + transitivePeerDependencies: + - supports-color + + '@embroider/macros@1.18.2(@glint/template@1.6.1)': dependencies: '@embroider/shared-internals': 3.0.1 assert-never: 1.4.0 @@ -8941,15 +9045,27 @@ snapshots: lodash: 4.17.21 resolve: 1.22.10 semver: 7.7.2 + optionalDependencies: + '@glint/template': 1.6.1 transitivePeerDependencies: - supports-color - '@embroider/shared-internals@1.8.3': + '@embroider/shared-internals@0.47.2': dependencies: - babel-import-util: 1.4.1 + babel-import-util: 0.2.0 ember-rfc176-data: 0.3.18 fs-extra: 9.1.0 - js-string-escape: 1.0.1 + lodash: 4.17.21 + resolve-package-path: 4.0.3 + semver: 7.7.2 + typescript-memoize: 1.1.1 + + '@embroider/shared-internals@1.8.3': + dependencies: + babel-import-util: 1.4.1 + ember-rfc176-data: 0.3.18 + fs-extra: 9.1.0 + js-string-escape: 1.0.1 lodash: 4.17.21 resolve-package-path: 4.0.3 semver: 7.7.2 @@ -8990,12 +9106,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@embroider/util@1.13.4(ember-source@3.28.12(@babel/core@7.28.4))': + '@embroider/util@1.13.4(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4))': dependencies: - '@embroider/macros': 1.18.2 + '@embroider/macros': 1.18.2(@glint/template@1.6.1) broccoli-funnel: 3.0.8 ember-cli-babel: 7.26.11 ember-source: 3.28.12(@babel/core@7.28.4) + optionalDependencies: + '@glint/template': 1.6.1 transitivePeerDependencies: - supports-color @@ -9024,6 +9142,17 @@ snapshots: '@faker-js/faker@8.4.1': {} + '@floating-ui/core@1.7.3': + dependencies: + '@floating-ui/utils': 0.2.10 + + '@floating-ui/dom@1.7.4': + dependencies: + '@floating-ui/core': 1.7.3 + '@floating-ui/utils': 0.2.10 + + '@floating-ui/utils@0.2.10': {} + '@glimmer/component@1.1.2(@babel/core@7.28.4)': dependencies: '@glimmer/di': 0.1.11 @@ -9118,24 +9247,72 @@ snapshots: dependencies: '@glimmer/interfaces': 0.94.6 + '@glint/template@1.6.1': {} + '@handlebars/parser@1.1.0': {} '@handlebars/parser@2.2.1': {} - '@html-next/vertical-collection@2.1.0(@babel/core@7.28.4)': - dependencies: - babel6-plugin-strip-class-callcheck: 6.0.0 - broccoli-funnel: 2.0.2 - broccoli-merge-trees: 3.0.2 - broccoli-rollup: 4.1.1 - ember-cli-babel: 7.26.11 - ember-cli-htmlbars: 3.1.0 - ember-cli-version-checker: 3.1.3 - ember-compatibility-helpers: 1.2.7(@babel/core@7.28.4) - ember-raf-scheduler: 0.2.0 + '@hashicorp/design-system-components@4.24.0(patch_hash=fdd37c1f2b693ce834016a5e4d64d8935e1dc495259de08ee2a3da251e56aa34)(@babel/core@7.28.4)(@ember/string@3.1.1)(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)))(@glimmer/component@1.1.2(@babel/core@7.28.4))(@glimmer/tracking@1.1.2)(@glint/template@1.6.1)(ember-basic-dropdown@8.7.0(@babel/core@7.28.4)(@ember/string@3.1.1)(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)))(@glimmer/component@1.1.2(@babel/core@7.28.4))(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)))(ember-source@3.28.12(@babel/core@7.28.4))(webpack@5.101.3)': + dependencies: + '@codemirror/commands': 6.9.0 + '@codemirror/lang-go': 6.0.1 + '@codemirror/lang-javascript': 6.2.4 + '@codemirror/lang-json': 6.0.2 + '@codemirror/lang-markdown': 6.4.0 + '@codemirror/lang-sql': 6.10.0 + '@codemirror/lang-yaml': 6.1.2 + '@codemirror/language': 6.11.3 + '@codemirror/legacy-modes': 6.5.2 + '@codemirror/lint': 6.9.0 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.4 + '@ember/render-modifiers': 2.1.0(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)) + '@ember/string': 3.1.1 + '@ember/test-waiters': 3.1.0 + '@embroider/addon-shim': 1.10.0 + '@embroider/macros': 1.18.2(@glint/template@1.6.1) + '@embroider/util': 1.13.4(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)) + '@floating-ui/dom': 1.7.4 + '@hashicorp/design-system-tokens': 3.0.0 + '@hashicorp/flight-icons': 3.13.0(patch_hash=a4015afaecef4b5506fd8d6276edc252725a2a582b566ec810996b493e06c511) + '@lezer/highlight': 1.2.1 + '@nullvoxpopuli/ember-composable-helpers': 5.3.0(@babel/core@7.28.4) + clipboard-polyfill: 4.1.1 + codemirror-lang-hcl: 0.0.0-beta.2 + decorator-transforms: 2.3.0(patch_hash=daaf70dcf904cf2d805d085a6528bf51c0554bb9a602cfdd5025c76d86e07eb9)(@babel/core@7.28.4) + ember-a11y-refocus: 4.1.4 + ember-cli-sass: 11.0.1 + ember-concurrency: 4.0.6(@babel/core@7.28.4)(@glint/template@1.6.1) + ember-element-helper: 0.8.8 + ember-focus-trap: 0.8.0(@babel/core@7.28.4)(@glint/template@1.6.1)(webpack@5.101.3) + ember-get-config: 2.1.1(@glint/template@1.6.1) + ember-modifier: 4.2.2(@babel/core@7.28.4) + ember-power-select: 8.11.0(@babel/core@7.28.4)(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)))(@glimmer/component@1.1.2(@babel/core@7.28.4))(@glint/template@1.6.1)(ember-basic-dropdown@8.7.0(@babel/core@7.28.4)(@ember/string@3.1.1)(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)))(@glimmer/component@1.1.2(@babel/core@7.28.4))(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)))(ember-concurrency@4.0.6(@babel/core@7.28.4)(@glint/template@1.6.1))(ember-source@3.28.12(@babel/core@7.28.4)) + ember-stargate: 0.5.0(@babel/core@7.28.4)(@ember/test-waiters@3.1.0)(@glimmer/tracking@1.1.2)(@glint/template@1.6.1)(ember-concurrency@4.0.6(@babel/core@7.28.4)(@glint/template@1.6.1))(ember-source@3.28.12(@babel/core@7.28.4)) + ember-style-modifier: 4.5.1(@babel/core@7.28.4)(@ember/string@3.1.1) + ember-truth-helpers: 4.0.3(ember-source@3.28.12(@babel/core@7.28.4)) + luxon: 3.7.2 + prismjs: 1.30.0 + sass: 1.93.2 + tabbable: 6.2.0 + tippy.js: 6.3.7 + tracked-built-ins: 4.0.0(@babel/core@7.28.4) transitivePeerDependencies: - '@babel/core' + - '@ember/test-helpers' + - '@glimmer/component' + - '@glimmer/tracking' + - '@glint/environment-ember-loose' + - '@glint/template' + - ember-basic-dropdown + - ember-source - supports-color + - webpack + + '@hashicorp/design-system-tokens@3.0.0': {} + + '@hashicorp/flight-icons@3.13.0(patch_hash=a4015afaecef4b5506fd8d6276edc252725a2a582b566ec810996b493e06c511)': {} '@humanwhocodes/config-array@0.13.0': dependencies: @@ -9202,6 +9379,59 @@ snapshots: '@kwsites/promise-deferred@1.1.1': {} + '@lezer/common@1.2.3': {} + + '@lezer/css@1.3.0': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/go@1.0.1': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/highlight@1.2.1': + dependencies: + '@lezer/common': 1.2.3 + + '@lezer/html@1.3.12': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/javascript@1.5.4': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/json@1.0.3': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/lr@1.4.2': + dependencies: + '@lezer/common': 1.2.3 + + '@lezer/markdown@1.4.3': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + + '@lezer/yaml@1.0.3': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@marijn/find-cluster-break@1.0.2': {} + '@miragejs/pretender-node-polyfill@0.1.2': {} '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': @@ -9220,6 +9450,14 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 + '@nullvoxpopuli/ember-composable-helpers@5.3.0(@babel/core@7.28.4)': + dependencies: + '@embroider/addon-shim': 1.10.0 + decorator-transforms: 2.3.0(patch_hash=daaf70dcf904cf2d805d085a6528bf51c0554bb9a602cfdd5025c76d86e07eb9)(@babel/core@7.28.4) + transitivePeerDependencies: + - '@babel/core' + - supports-color + '@parcel/watcher-android-arm64@2.5.1': optional: true @@ -9286,6 +9524,8 @@ snapshots: '@pkgr/core@0.2.9': {} + '@popperjs/core@2.11.8': {} + '@ro0gr/ceibo@2.2.0': {} '@simple-dom/document@1.4.0': @@ -9309,8 +9549,6 @@ snapshots: '@types/connect': 3.4.38 '@types/node': 24.5.2 - '@types/broccoli-plugin@1.3.0': {} - '@types/broccoli-plugin@3.0.4': dependencies: broccoli-plugin: 4.0.7 @@ -9379,8 +9617,6 @@ snapshots: dependencies: glob: 7.2.3 - '@types/hammerjs@2.0.46': {} - '@types/http-errors@2.0.5': {} '@types/jquery@3.5.33': @@ -9683,8 +9919,6 @@ snapshots: amdefine@1.0.1: {} - angular-material-styles@1.1.21: {} - ansi-escapes@3.2.0: {} ansi-escapes@4.3.2: @@ -10029,6 +10263,8 @@ snapshots: transitivePeerDependencies: - supports-color + babel-import-util@0.2.0: {} + babel-import-util@1.4.1: {} babel-import-util@2.1.1: {} @@ -10093,8 +10329,6 @@ snapshots: '@babel/types': 7.28.4 lodash: 4.17.21 - babel-plugin-htmlbars-inline-precompile@3.2.0: {} - babel-plugin-htmlbars-inline-precompile@5.3.1: dependencies: babel-plugin-ember-modules-api-polyfill: 3.5.0 @@ -10718,11 +10952,6 @@ snapshots: transitivePeerDependencies: - supports-color - broccoli-file-creator@1.2.0: - dependencies: - broccoli-plugin: 1.3.1 - mkdirp: 0.5.6 - broccoli-file-creator@2.1.1: dependencies: broccoli-plugin: 1.3.1 @@ -10836,12 +11065,6 @@ snapshots: broccoli-node-info@2.2.0: {} - broccoli-output-wrapper@2.0.0: - dependencies: - heimdalljs-logger: 0.1.10 - transitivePeerDependencies: - - supports-color - broccoli-output-wrapper@3.2.5: dependencies: fs-extra: 8.1.0 @@ -10924,18 +11147,6 @@ snapshots: rimraf: 2.7.1 symlink-or-copy: 1.3.1 - broccoli-plugin@3.1.0: - dependencies: - broccoli-node-api: 1.7.0 - broccoli-output-wrapper: 2.0.0 - fs-merger: 3.2.1 - promise-map-series: 0.2.3 - quick-temp: 0.1.8 - rimraf: 2.7.1 - symlink-or-copy: 1.3.1 - transitivePeerDependencies: - - supports-color - broccoli-plugin@4.0.7: dependencies: broccoli-node-api: 1.7.0 @@ -10964,20 +11175,6 @@ snapshots: transitivePeerDependencies: - supports-color - broccoli-rollup@4.1.1: - dependencies: - '@types/broccoli-plugin': 1.3.0 - broccoli-plugin: 2.1.0 - fs-tree-diff: 2.0.1 - heimdalljs: 0.2.6 - node-modules-path: 1.0.2 - rollup: 1.32.1 - rollup-pluginutils: 2.8.2 - symlink-or-copy: 1.3.1 - walk-sync: 1.1.4 - transitivePeerDependencies: - - supports-color - broccoli-rollup@5.0.0: dependencies: '@types/broccoli-plugin': 3.0.4 @@ -11022,25 +11219,6 @@ snapshots: transitivePeerDependencies: - supports-color - broccoli-stew@1.6.0: - dependencies: - broccoli-debug: 0.6.5 - broccoli-funnel: 2.0.2 - broccoli-merge-trees: 2.0.1 - broccoli-persistent-filter: 1.4.6 - broccoli-plugin: 1.3.1 - chalk: 2.4.2 - debug: 3.2.7 - ensure-posix-path: 1.1.1 - fs-extra: 5.0.0 - minimatch: 3.1.2 - resolve: 1.22.10 - rsvp: 4.8.5 - symlink-or-copy: 1.3.1 - walk-sync: 0.3.4 - transitivePeerDependencies: - - supports-color - broccoli-stew@3.0.0: dependencies: broccoli-debug: 0.6.5 @@ -11367,8 +11545,6 @@ snapshots: isobject: 3.0.1 static-extend: 0.1.2 - classlist-polyfill@1.2.0: {} - clean-base-url@1.0.0: {} clean-css-promise@0.1.1: @@ -11410,11 +11586,7 @@ snapshots: cli-width@3.0.0: {} - clipboard@2.0.11: - dependencies: - good-listener: 1.2.2 - select: 1.1.2 - tiny-emitter: 2.1.0 + clipboard-polyfill@4.1.1: {} cliui@7.0.4: dependencies: @@ -11428,6 +11600,12 @@ snapshots: cluster-key-slot@1.1.2: {} + codemirror-lang-hcl@0.0.0-beta.2: + dependencies: + '@codemirror/language': 6.11.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + collection-visit@1.0.0: dependencies: map-visit: 1.0.0 @@ -11638,6 +11816,8 @@ snapshots: safe-buffer: 5.2.1 sha.js: 2.4.12 + crelt@1.0.6: {} + cross-spawn@6.0.6: dependencies: nice-try: 1.0.5 @@ -11690,6 +11870,8 @@ snapshots: cssesc@3.0.0: {} + csstype@3.1.3: {} + cyclist@1.0.2: {} dag-map@2.0.2: {} @@ -11746,14 +11928,14 @@ snapshots: decode-uri-component@0.2.2: {} - decorator-transforms@1.2.1(@babel/core@7.28.4): + decorator-transforms@1.2.1(patch_hash=daaf70dcf904cf2d805d085a6528bf51c0554bb9a602cfdd5025c76d86e07eb9)(@babel/core@7.28.4): dependencies: '@babel/plugin-syntax-decorators': 7.27.1(@babel/core@7.28.4) babel-import-util: 2.1.1 transitivePeerDependencies: - '@babel/core' - decorator-transforms@2.3.0(@babel/core@7.28.4): + decorator-transforms@2.3.0(patch_hash=daaf70dcf904cf2d805d085a6528bf51c0554bb9a602cfdd5025c76d86e07eb9)(@babel/core@7.28.4): dependencies: '@babel/plugin-syntax-decorators': 7.27.1(@babel/core@7.28.4) babel-import-util: 3.0.1 @@ -11801,8 +11983,6 @@ snapshots: delayed-stream@1.0.0: {} - delegate@3.2.0: {} - delegates@1.0.0: {} denque@1.5.1: {} @@ -11893,8 +12073,6 @@ snapshots: electron-to-chromium@1.5.224: {} - element-closest@3.0.2: {} - elliptic@6.6.1: dependencies: bn.js: 4.12.2 @@ -11905,29 +12083,24 @@ snapshots: minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 - ember-arg-types@1.1.0(webpack@5.101.3): + ember-a11y-refocus@4.1.4: dependencies: - '@embroider/macros': 1.18.2 - ember-auto-import: 2.10.1(webpack@5.101.3) ember-cli-babel: 7.26.11 - ember-cli-typescript: 5.3.0 - ember-get-config: 2.1.1 - prop-types: 15.8.1 + ember-cli-htmlbars: 6.3.0 transitivePeerDependencies: - - '@glint/template' - supports-color - - webpack - ember-assign-helper@0.4.0: + ember-assign-helper@0.5.1: dependencies: - ember-cli-babel: 7.26.11 - ember-cli-htmlbars: 6.3.0 + '@embroider/addon-shim': 1.10.0 transitivePeerDependencies: - supports-color - ember-assign-helper@0.5.1: + ember-async-data@1.0.3(ember-source@3.28.12(@babel/core@7.28.4)): dependencies: + '@ember/test-waiters': 3.1.0 '@embroider/addon-shim': 1.10.0 + ember-source: 3.28.12(@babel/core@7.28.4) transitivePeerDependencies: - supports-color @@ -11967,7 +12140,7 @@ snapshots: - webpack-cli - webpack-command - ember-auto-import@2.10.1(webpack@5.101.3): + ember-auto-import@2.11.0(@glint/template@1.6.1)(webpack@5.101.3): dependencies: '@babel/core': 7.28.4 '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.28.4) @@ -11975,7 +12148,7 @@ snapshots: '@babel/plugin-proposal-private-methods': 7.18.6(@babel/core@7.28.4) '@babel/plugin-transform-class-static-block': 7.28.3(@babel/core@7.28.4) '@babel/preset-env': 7.28.3(@babel/core@7.28.4) - '@embroider/macros': 1.18.2 + '@embroider/macros': 1.18.2(@glint/template@1.6.1) '@embroider/shared-internals': 2.9.1 babel-loader: 8.4.1(@babel/core@7.28.4)(webpack@5.101.3) babel-plugin-ember-modules-api-polyfill: 3.5.0 @@ -12010,23 +12183,21 @@ snapshots: - supports-color - webpack - ember-basic-dropdown@4.0.5(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4)): + ember-basic-dropdown@8.7.0(@babel/core@7.28.4)(@ember/string@3.1.1)(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)))(@glimmer/component@1.1.2(@babel/core@7.28.4))(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)): dependencies: - '@ember/render-modifiers': 2.1.0(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4)) - '@embroider/macros': 1.18.2 - '@embroider/util': 1.13.4(ember-source@3.28.12(@babel/core@7.28.4)) + '@ember/test-helpers': 2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)) + '@embroider/addon-shim': 1.10.0 + '@embroider/macros': 1.18.2(@glint/template@1.6.1) + '@embroider/util': 1.13.4(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)) '@glimmer/component': 1.1.2(@babel/core@7.28.4) - '@glimmer/tracking': 1.1.2 - ember-cli-babel: 7.26.11 - ember-cli-htmlbars: 6.3.0 - ember-cli-typescript: 4.2.1 - ember-element-helper: 0.6.1(ember-source@3.28.12(@babel/core@7.28.4)) - ember-get-config: 1.1.0 - ember-maybe-in-element: 2.1.0 - ember-style-modifier: 0.7.0(@babel/core@7.28.4) - ember-truth-helpers: 3.1.1 + decorator-transforms: 2.3.0(patch_hash=daaf70dcf904cf2d805d085a6528bf51c0554bb9a602cfdd5025c76d86e07eb9)(@babel/core@7.28.4) + ember-element-helper: 0.8.8 + ember-modifier: 4.2.2(@babel/core@7.28.4) + ember-style-modifier: 4.5.1(@babel/core@7.28.4)(@ember/string@3.1.1) + ember-truth-helpers: 4.0.3(ember-source@3.28.12(@babel/core@7.28.4)) transitivePeerDependencies: - '@babel/core' + - '@ember/string' - '@glint/environment-ember-loose' - '@glint/template' - ember-source @@ -12060,9 +12231,9 @@ snapshots: - '@babel/core' - supports-color - ember-classic-decorator@3.0.1: + ember-classic-decorator@3.0.1(@glint/template@1.6.1): dependencies: - '@embroider/macros': 1.18.2 + '@embroider/macros': 1.18.2(@glint/template@1.6.1) babel-plugin-filter-imports: 4.0.0 ember-cli-babel: 7.26.11 ember-cli-htmlbars: 6.3.0 @@ -12202,23 +12373,6 @@ snapshots: transitivePeerDependencies: - supports-color - ember-cli-clipboard@1.3.0(@babel/core@7.28.4)(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4)))(webpack@5.101.3): - dependencies: - '@ember/test-helpers': 2.9.6(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4)) - '@embroider/macros': 1.18.2 - clipboard: 2.0.11 - ember-arg-types: 1.1.0(webpack@5.101.3) - ember-auto-import: 2.10.1(webpack@5.101.3) - ember-cli-babel: 7.26.11 - ember-cli-htmlbars: 6.3.0 - ember-modifier: 4.2.2(@babel/core@7.28.4) - prop-types: 15.8.1 - transitivePeerDependencies: - - '@babel/core' - - '@glint/template' - - supports-color - - webpack - ember-cli-code-coverage@2.1.2: dependencies: babel-plugin-istanbul: 6.1.1 @@ -12374,15 +12528,6 @@ snapshots: transitivePeerDependencies: - supports-color - ember-cli-htmlbars@2.0.5: - dependencies: - broccoli-persistent-filter: 1.4.6 - hash-for-dep: 1.5.1 - json-stable-stringify: 1.3.0 - strip-bom: 3.0.0 - transitivePeerDependencies: - - supports-color - ember-cli-htmlbars@3.1.0: dependencies: broccoli-persistent-filter: 2.3.1 @@ -12392,25 +12537,6 @@ snapshots: transitivePeerDependencies: - supports-color - ember-cli-htmlbars@4.5.0: - dependencies: - '@ember/edition-utils': 1.2.0 - babel-plugin-htmlbars-inline-precompile: 3.2.0 - broccoli-debug: 0.6.5 - broccoli-persistent-filter: 2.3.1 - broccoli-plugin: 3.1.0 - common-tags: 1.8.2 - ember-cli-babel-plugin-helpers: 1.1.1 - fs-tree-diff: 2.0.1 - hash-for-dep: 1.5.1 - heimdalljs-logger: 0.1.10 - json-stable-stringify: 1.3.0 - semver: 6.3.1 - strip-bom: 4.0.0 - walk-sync: 2.2.0 - transitivePeerDependencies: - - supports-color - ember-cli-htmlbars@5.7.2: dependencies: '@ember/edition-utils': 1.2.0 @@ -12460,24 +12586,24 @@ snapshots: ember-cli-lodash-subset@2.0.1: {} - ember-cli-mirage@3.0.4(@ember-data/model@3.28.13(@babel/core@7.28.4))(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4)))(ember-data@3.28.13(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4)))(ember-qunit@5.1.5(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4)))(qunit@2.24.1))(ember-source@3.28.12(@babel/core@7.28.4))(miragejs@0.1.48)(webpack@5.101.3): + ember-cli-mirage@3.0.4(@ember-data/model@3.28.13(@babel/core@7.28.4))(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)))(@glint/template@1.6.1)(ember-data@3.28.13(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4)))(ember-qunit@5.1.5(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)))(qunit@2.24.1))(ember-source@3.28.12(@babel/core@7.28.4))(miragejs@0.1.48)(webpack@5.101.3): dependencies: '@babel/core': 7.28.4 - '@embroider/macros': 1.18.2 + '@embroider/macros': 1.18.2(@glint/template@1.6.1) broccoli-file-creator: 2.1.1 broccoli-funnel: 3.0.8 broccoli-merge-trees: 4.2.0 - ember-auto-import: 2.10.1(webpack@5.101.3) + ember-auto-import: 2.11.0(@glint/template@1.6.1)(webpack@5.101.3) ember-cli-babel: 8.2.0(@babel/core@7.28.4) - ember-get-config: 2.1.1 + ember-get-config: 2.1.1(@glint/template@1.6.1) ember-inflector: 4.0.3(ember-source@3.28.12(@babel/core@7.28.4)) ember-source: 3.28.12(@babel/core@7.28.4) miragejs: 0.1.48 optionalDependencies: '@ember-data/model': 3.28.13(@babel/core@7.28.4) - '@ember/test-helpers': 2.9.6(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4)) + '@ember/test-helpers': 2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)) ember-data: 3.28.13(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4)) - ember-qunit: 5.1.5(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4)))(qunit@2.24.1) + ember-qunit: 5.1.5(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)))(qunit@2.24.1) transitivePeerDependencies: - '@glint/template' - supports-color @@ -12489,9 +12615,9 @@ snapshots: transitivePeerDependencies: - supports-color - ember-cli-page-object@2.3.2(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4))): + ember-cli-page-object@2.3.2(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4))): dependencies: - '@ember/test-helpers': 2.9.6(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4)) + '@ember/test-helpers': 2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)) '@embroider/addon-shim': 1.10.0 '@ro0gr/ceibo': 2.2.0 '@types/jquery': 3.5.33 @@ -12501,12 +12627,6 @@ snapshots: ember-cli-path-utils@1.0.0: {} - ember-cli-polyfill-importer@0.0.4: - dependencies: - browserslist: 4.26.2 - caniuse-api: 3.0.0 - resolve: 1.22.10 - ember-cli-preprocess-registry@3.3.0: dependencies: broccoli-clean-css: 1.1.0 @@ -12516,15 +12636,6 @@ snapshots: transitivePeerDependencies: - supports-color - ember-cli-sass@10.0.1: - dependencies: - broccoli-funnel: 2.0.2 - broccoli-merge-trees: 3.0.2 - broccoli-sass-source-maps: 4.3.0 - ember-cli-version-checker: 2.2.0 - transitivePeerDependencies: - - supports-color - ember-cli-sass@11.0.1: dependencies: broccoli-funnel: 2.0.2 @@ -12603,26 +12714,6 @@ snapshots: - '@babel/core' - supports-color - ember-cli-typescript@3.1.4(@babel/core@7.28.4): - dependencies: - '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.28.4) - '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.28.4) - '@babel/plugin-transform-typescript': 7.8.7(@babel/core@7.28.4) - ansi-to-html: 0.6.15 - broccoli-stew: 3.0.0 - debug: 4.4.3 - ember-cli-babel-plugin-helpers: 1.1.1 - execa: 3.4.0 - fs-extra: 8.1.0 - resolve: 1.22.10 - rsvp: 4.8.5 - semver: 6.3.1 - stagehand: 1.0.1 - walk-sync: 2.2.0 - transitivePeerDependencies: - - '@babel/core' - - supports-color - ember-cli-typescript@4.2.1: dependencies: ansi-to-html: 0.6.15 @@ -12841,14 +12932,6 @@ snapshots: - '@babel/core' - supports-color - ember-composability-tools@0.0.12: - dependencies: - ember-cli-babel: 7.26.11 - ember-cli-htmlbars: 3.1.0 - ember-in-element-polyfill: 0.2.2 - transitivePeerDependencies: - - supports-color - ember-composable-helpers@5.0.0: dependencies: '@babel/core': 7.28.4 @@ -12858,37 +12941,15 @@ snapshots: transitivePeerDependencies: - supports-color - ember-concurrency-decorators@2.0.3(@babel/core@7.28.4): - dependencies: - '@ember-decorators/utils': 6.1.1 - ember-cli-babel: 7.26.11 - ember-cli-htmlbars: 4.5.0 - ember-cli-typescript: 3.1.4(@babel/core@7.28.4) - transitivePeerDependencies: - - '@babel/core' - - supports-color - - ember-concurrency@2.3.7(@babel/core@7.28.4): - dependencies: - '@babel/helper-plugin-utils': 7.27.1 - '@babel/types': 7.28.4 - '@glimmer/tracking': 1.1.2 - ember-cli-babel: 7.26.11 - ember-cli-babel-plugin-helpers: 1.1.1 - ember-cli-htmlbars: 5.7.2 - ember-compatibility-helpers: 1.2.7(@babel/core@7.28.4) - ember-destroyable-polyfill: 2.0.3(@babel/core@7.28.4) - transitivePeerDependencies: - - '@babel/core' - - supports-color - - ember-concurrency@4.0.6(@babel/core@7.28.4): + ember-concurrency@4.0.6(@babel/core@7.28.4)(@glint/template@1.6.1): dependencies: '@babel/helper-module-imports': 7.27.1 '@babel/helper-plugin-utils': 7.27.1 '@babel/types': 7.28.4 '@embroider/addon-shim': 1.10.0 - decorator-transforms: 1.2.1(@babel/core@7.28.4) + decorator-transforms: 1.2.1(patch_hash=daaf70dcf904cf2d805d085a6528bf51c0554bb9a602cfdd5025c76d86e07eb9)(@babel/core@7.28.4) + optionalDependencies: + '@glint/template': 1.6.1 transitivePeerDependencies: - '@babel/core' - supports-color @@ -12906,15 +12967,6 @@ snapshots: transitivePeerDependencies: - supports-color - ember-css-transitions@2.1.1(@babel/core@7.28.4): - dependencies: - ember-cli-babel: 7.26.11 - ember-cli-htmlbars: 5.7.2 - ember-modifier: 2.1.2(@babel/core@7.28.4) - transitivePeerDependencies: - - '@babel/core' - - supports-color - ember-custom-actions@3.3.0(@babel/core@7.28.4): dependencies: ember-cli-babel: 6.16.0(@babel/core@7.28.4) @@ -12963,17 +13015,6 @@ snapshots: - '@babel/core' - supports-color - ember-element-helper@0.6.1(ember-source@3.28.12(@babel/core@7.28.4)): - dependencies: - '@embroider/util': 1.13.4(ember-source@3.28.12(@babel/core@7.28.4)) - ember-cli-babel: 7.26.11 - ember-cli-htmlbars: 6.3.0 - ember-source: 3.28.12(@babel/core@7.28.4) - transitivePeerDependencies: - - '@glint/environment-ember-loose' - - '@glint/template' - - supports-color - ember-element-helper@0.8.8: dependencies: '@embroider/addon-shim': 1.10.0 @@ -13017,46 +13058,43 @@ snapshots: - encoding - supports-color - ember-functions-as-helper-polyfill@2.1.3(ember-source@3.28.12(@babel/core@7.28.4)): + ember-focus-trap@0.8.0(@babel/core@7.28.4)(@glint/template@1.6.1)(webpack@5.101.3): dependencies: + '@embroider/macros': 0.47.2 + ember-auto-import: 2.11.0(@glint/template@1.6.1)(webpack@5.101.3) ember-cli-babel: 7.26.11 - ember-cli-typescript: 5.3.0 - ember-cli-version-checker: 5.1.2 - ember-source: 3.28.12(@babel/core@7.28.4) - transitivePeerDependencies: - - supports-color - - ember-get-config@0.5.0: - dependencies: - broccoli-file-creator: 1.2.0 - ember-cli-babel: 7.26.11 - ember-cli-htmlbars: 5.7.2 + ember-modifier-manager-polyfill: 1.2.0(@babel/core@7.28.4) + focus-trap: 6.9.4 transitivePeerDependencies: + - '@babel/core' + - '@glint/template' - supports-color + - webpack - ember-get-config@1.1.0: + ember-functions-as-helper-polyfill@2.1.3(ember-source@3.28.12(@babel/core@7.28.4)): dependencies: - '@embroider/macros': 1.18.2 ember-cli-babel: 7.26.11 + ember-cli-typescript: 5.3.0 + ember-cli-version-checker: 5.1.2 + ember-source: 3.28.12(@babel/core@7.28.4) transitivePeerDependencies: - - '@glint/template' - supports-color - ember-get-config@2.1.1: + ember-get-config@2.1.1(@glint/template@1.6.1): dependencies: - '@embroider/macros': 1.18.2 + '@embroider/macros': 1.18.2(@glint/template@1.6.1) ember-cli-babel: 7.26.11 transitivePeerDependencies: - '@glint/template' - supports-color - ember-highcharts@3.2.2(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4))(highcharts@12.4.0): + ember-highcharts@3.2.2(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4))(highcharts@12.4.0): dependencies: - '@ember/render-modifiers': 2.1.0(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4)) + '@ember/render-modifiers': 2.1.0(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)) broccoli-funnel: 3.0.8 broccoli-merge-trees: 4.2.0 deepmerge: 4.3.1 - ember-auto-import: 2.10.1(webpack@5.101.3) + ember-auto-import: 2.11.0(@glint/template@1.6.1)(webpack@5.101.3) ember-cli-babel: 7.26.11 ember-cli-htmlbars: 6.3.0 ember-copy: 2.0.1 @@ -13082,15 +13120,6 @@ snapshots: transitivePeerDependencies: - supports-color - ember-in-element-polyfill@0.2.2: - dependencies: - debug: 3.2.7 - ember-cli-babel: 7.26.11 - ember-cli-htmlbars: 4.5.0 - ember-cli-version-checker: 2.2.0 - transitivePeerDependencies: - - supports-color - ember-in-element-polyfill@1.0.1: dependencies: debug: 4.4.3 @@ -13107,11 +13136,11 @@ snapshots: transitivePeerDependencies: - supports-color - ember-lifeline@7.0.0(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4))): + ember-lifeline@7.0.0(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4))): dependencies: '@embroider/addon-shim': 1.10.0 optionalDependencies: - '@ember/test-helpers': 2.9.6(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4)) + '@ember/test-helpers': 2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)) transitivePeerDependencies: - supports-color @@ -13131,14 +13160,6 @@ snapshots: transitivePeerDependencies: - supports-color - ember-maybe-in-element@2.1.0: - dependencies: - ember-cli-babel: 7.26.11 - ember-cli-htmlbars: 6.3.0 - ember-cli-version-checker: 5.1.2 - transitivePeerDependencies: - - supports-color - ember-mobiledoc-dom-renderer@0.8.1: dependencies: broccoli-funnel: 3.0.8 @@ -13150,11 +13171,11 @@ snapshots: transitivePeerDependencies: - supports-color - ember-mobiledoc-editor@0.8.1(webpack@5.101.3): + ember-mobiledoc-editor@0.8.1(@glint/template@1.6.1)(webpack@5.101.3): dependencies: broccoli-funnel: 3.0.8 broccoli-merge-trees: 4.2.0 - ember-auto-import: 2.10.1(webpack@5.101.3) + ember-auto-import: 2.11.0(@glint/template@1.6.1)(webpack@5.101.3) ember-cli-babel: 7.26.11 ember-cli-htmlbars: 5.7.2 ember-copy: 2.0.1 @@ -13175,34 +13196,10 @@ snapshots: - '@babel/core' - supports-color - ember-modifier@2.1.2(@babel/core@7.28.4): - dependencies: - ember-cli-babel: 7.26.11 - ember-cli-normalize-entity-name: 1.0.0 - ember-cli-string-utils: 1.1.0 - ember-cli-typescript: 3.1.4(@babel/core@7.28.4) - ember-compatibility-helpers: 1.2.7(@babel/core@7.28.4) - ember-destroyable-polyfill: 2.0.3(@babel/core@7.28.4) - ember-modifier-manager-polyfill: 1.2.0(@babel/core@7.28.4) - transitivePeerDependencies: - - '@babel/core' - - supports-color - - ember-modifier@3.2.7(@babel/core@7.28.4): - dependencies: - ember-cli-babel: 7.26.11 - ember-cli-normalize-entity-name: 1.0.0 - ember-cli-string-utils: 1.1.0 - ember-cli-typescript: 5.3.0 - ember-compatibility-helpers: 1.2.7(@babel/core@7.28.4) - transitivePeerDependencies: - - '@babel/core' - - supports-color - ember-modifier@4.2.2(@babel/core@7.28.4): dependencies: '@embroider/addon-shim': 1.10.0 - decorator-transforms: 2.3.0(@babel/core@7.28.4) + decorator-transforms: 2.3.0(patch_hash=daaf70dcf904cf2d805d085a6528bf51c0554bb9a602cfdd5025c76d86e07eb9)(@babel/core@7.28.4) ember-cli-normalize-entity-name: 1.0.0 ember-cli-string-utils: 1.1.0 transitivePeerDependencies: @@ -13226,46 +13223,6 @@ snapshots: transitivePeerDependencies: - supports-color - ember-paper@1.0.0-beta.36(@babel/core@7.28.4)(@egjs/hammerjs@2.0.17)(ember-source@3.28.12(@babel/core@7.28.4)): - dependencies: - '@html-next/vertical-collection': 2.1.0(@babel/core@7.28.4) - angular-material-styles: 1.1.21 - broccoli-file-creator: 2.1.1 - broccoli-funnel: 3.0.8 - broccoli-merge-trees: 4.2.0 - broccoli-persistent-filter: 3.1.3 - classlist-polyfill: 1.2.0 - element-closest: 3.0.2 - ember-auto-import: 1.12.2 - ember-basic-dropdown: 4.0.5(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4)) - ember-cli-babel: 7.26.11 - ember-cli-htmlbars: 5.7.2 - ember-cli-polyfill-importer: 0.0.4 - ember-cli-sass: 10.0.1 - ember-composability-tools: 0.0.12 - ember-css-transitions: 2.1.1(@babel/core@7.28.4) - ember-decorators: 6.1.1 - ember-get-config: 0.5.0 - ember-maybe-in-element: 2.1.0 - ember-power-select: 5.0.4(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4)) - fastboot-transform: 0.1.3 - hammerjs: 2.0.8 - matchmedia-polyfill: 0.3.2 - polyfill-nodelist-foreach: 1.1.2 - propagating-hammerjs: 2.0.1(@egjs/hammerjs@2.0.17) - resolve: 1.22.10 - sass: 1.93.2 - tinycolor2: 1.6.0 - transitivePeerDependencies: - - '@babel/core' - - '@egjs/hammerjs' - - '@glint/environment-ember-loose' - - '@glint/template' - - ember-source - - supports-color - - webpack-cli - - webpack-command - ember-percy@1.6.0: dependencies: body-parser: 1.20.3 @@ -13286,11 +13243,11 @@ snapshots: - '@babel/core' - supports-color - ember-power-calendar-moment@1.0.4(ember-power-calendar@1.8.0(@babel/core@7.28.4)(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4)))(@glimmer/component@1.1.2(@babel/core@7.28.4))(ember-concurrency@4.0.6(@babel/core@7.28.4))(ember-source@3.28.12(@babel/core@7.28.4)))(moment-timezone@0.6.0)(moment@2.30.1): + ember-power-calendar-moment@1.0.4(@glint/template@1.6.1)(ember-power-calendar@1.8.0(@babel/core@7.28.4)(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)))(@glimmer/component@1.1.2(@babel/core@7.28.4))(@glint/template@1.6.1)(ember-concurrency@4.0.6(@babel/core@7.28.4)(@glint/template@1.6.1))(ember-source@3.28.12(@babel/core@7.28.4)))(moment-timezone@0.6.0)(moment@2.30.1): dependencies: '@embroider/addon-shim': 1.10.0 - '@embroider/macros': 1.18.2 - ember-power-calendar: 1.8.0(@babel/core@7.28.4)(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4)))(@glimmer/component@1.1.2(@babel/core@7.28.4))(ember-concurrency@4.0.6(@babel/core@7.28.4))(ember-source@3.28.12(@babel/core@7.28.4)) + '@embroider/macros': 1.18.2(@glint/template@1.6.1) + ember-power-calendar: 1.8.0(@babel/core@7.28.4)(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)))(@glimmer/component@1.1.2(@babel/core@7.28.4))(@glint/template@1.6.1)(ember-concurrency@4.0.6(@babel/core@7.28.4)(@glint/template@1.6.1))(ember-source@3.28.12(@babel/core@7.28.4)) optionalDependencies: moment: 2.30.1 moment-timezone: 0.6.0 @@ -13298,16 +13255,16 @@ snapshots: - '@glint/template' - supports-color - ember-power-calendar@1.8.0(@babel/core@7.28.4)(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4)))(@glimmer/component@1.1.2(@babel/core@7.28.4))(ember-concurrency@4.0.6(@babel/core@7.28.4))(ember-source@3.28.12(@babel/core@7.28.4)): + ember-power-calendar@1.8.0(@babel/core@7.28.4)(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)))(@glimmer/component@1.1.2(@babel/core@7.28.4))(@glint/template@1.6.1)(ember-concurrency@4.0.6(@babel/core@7.28.4)(@glint/template@1.6.1))(ember-source@3.28.12(@babel/core@7.28.4)): dependencies: - '@ember/test-helpers': 2.9.6(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4)) + '@ember/test-helpers': 2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)) '@embroider/addon-shim': 1.10.0 - '@embroider/macros': 1.18.2 - '@embroider/util': 1.13.4(ember-source@3.28.12(@babel/core@7.28.4)) + '@embroider/macros': 1.18.2(@glint/template@1.6.1) + '@embroider/util': 1.13.4(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)) '@glimmer/component': 1.1.2(@babel/core@7.28.4) - decorator-transforms: 2.3.0(@babel/core@7.28.4) + decorator-transforms: 2.3.0(patch_hash=daaf70dcf904cf2d805d085a6528bf51c0554bb9a602cfdd5025c76d86e07eb9)(@babel/core@7.28.4) ember-assign-helper: 0.5.1 - ember-concurrency: 4.0.6(@babel/core@7.28.4) + ember-concurrency: 4.0.6(@babel/core@7.28.4)(@glint/template@1.6.1) ember-element-helper: 0.8.8 ember-modifier: 4.2.2(@babel/core@7.28.4) ember-truth-helpers: 4.0.3(ember-source@3.28.12(@babel/core@7.28.4)) @@ -13318,20 +13275,18 @@ snapshots: - ember-source - supports-color - ember-power-select@5.0.4(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4)): + ember-power-select@8.11.0(@babel/core@7.28.4)(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)))(@glimmer/component@1.1.2(@babel/core@7.28.4))(@glint/template@1.6.1)(ember-basic-dropdown@8.7.0(@babel/core@7.28.4)(@ember/string@3.1.1)(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)))(@glimmer/component@1.1.2(@babel/core@7.28.4))(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)))(ember-concurrency@4.0.6(@babel/core@7.28.4)(@glint/template@1.6.1))(ember-source@3.28.12(@babel/core@7.28.4)): dependencies: - '@embroider/util': 1.13.4(ember-source@3.28.12(@babel/core@7.28.4)) + '@ember/test-helpers': 2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)) + '@embroider/addon-shim': 1.10.0 + '@embroider/util': 1.13.4(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)) '@glimmer/component': 1.1.2(@babel/core@7.28.4) - '@glimmer/tracking': 1.1.2 - ember-assign-helper: 0.4.0 - ember-basic-dropdown: 4.0.5(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4)) - ember-cli-babel: 7.26.11 - ember-cli-htmlbars: 6.3.0 - ember-cli-typescript: 4.2.1 - ember-concurrency: 2.3.7(@babel/core@7.28.4) - ember-concurrency-decorators: 2.0.3(@babel/core@7.28.4) - ember-text-measurer: 0.6.0 - ember-truth-helpers: 3.1.1 + decorator-transforms: 2.3.0(patch_hash=daaf70dcf904cf2d805d085a6528bf51c0554bb9a602cfdd5025c76d86e07eb9)(@babel/core@7.28.4) + ember-assign-helper: 0.5.1 + ember-basic-dropdown: 8.7.0(@babel/core@7.28.4)(@ember/string@3.1.1)(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)))(@glimmer/component@1.1.2(@babel/core@7.28.4))(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)) + ember-concurrency: 4.0.6(@babel/core@7.28.4)(@glint/template@1.6.1) + ember-modifier: 4.2.2(@babel/core@7.28.4) + ember-truth-helpers: 4.0.3(ember-source@3.28.12(@babel/core@7.28.4)) transitivePeerDependencies: - '@babel/core' - '@glint/environment-ember-loose' @@ -13349,9 +13304,9 @@ snapshots: transitivePeerDependencies: - supports-color - ember-qunit@5.1.5(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4)))(qunit@2.24.1): + ember-qunit@5.1.5(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)))(qunit@2.24.1): dependencies: - '@ember/test-helpers': 2.9.6(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4)) + '@ember/test-helpers': 2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)) broccoli-funnel: 3.0.8 broccoli-merge-trees: 3.0.2 common-tags: 1.8.2 @@ -13367,12 +13322,6 @@ snapshots: - webpack-cli - webpack-command - ember-raf-scheduler@0.2.0: - dependencies: - ember-cli-babel: 7.26.11 - transitivePeerDependencies: - - supports-color - ember-resolver@8.1.0(@babel/core@7.28.4): dependencies: babel-plugin-debug-macros: 0.3.4(@babel/core@7.28.4) @@ -13385,6 +13334,22 @@ snapshots: - '@babel/core' - supports-color + ember-resources@6.5.2(@ember/test-waiters@3.1.0)(@glimmer/component@1.1.2(@babel/core@7.28.4))(@glimmer/tracking@1.1.2)(@glint/template@1.6.1)(ember-concurrency@4.0.6(@babel/core@7.28.4)(@glint/template@1.6.1))(ember-source@3.28.12(@babel/core@7.28.4)): + dependencies: + '@babel/runtime': 7.28.4 + '@embroider/addon-shim': 1.10.0 + '@embroider/macros': 1.18.2(@glint/template@1.6.1) + '@glimmer/tracking': 1.1.2 + '@glint/template': 1.6.1 + ember-async-data: 1.0.3(ember-source@3.28.12(@babel/core@7.28.4)) + ember-source: 3.28.12(@babel/core@7.28.4) + optionalDependencies: + '@ember/test-waiters': 3.1.0 + '@glimmer/component': 1.1.2(@babel/core@7.28.4) + ember-concurrency: 4.0.6(@babel/core@7.28.4)(@glint/template@1.6.1) + transitivePeerDependencies: + - supports-color + ember-rfc176-data@0.3.18: {} ember-route-template@1.0.3: @@ -13401,17 +13366,17 @@ snapshots: transitivePeerDependencies: - supports-color - ember-simple-auth@6.1.0(@babel/core@7.28.4)(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4)))(ember-source@3.28.12(@babel/core@7.28.4))(eslint@8.57.1): + ember-simple-auth@6.1.0(@babel/core@7.28.4)(@ember/test-helpers@2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)))(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4))(eslint@8.57.1): dependencies: '@babel/eslint-parser': 7.28.4(@babel/core@7.28.4)(eslint@8.57.1) '@ember/test-waiters': 3.1.0 '@embroider/addon-shim': 1.10.0 - '@embroider/macros': 1.18.2 + '@embroider/macros': 1.18.2(@glint/template@1.6.1) ember-cli-is-package-missing: 1.0.0 ember-cookies: 1.3.0(ember-source@3.28.12(@babel/core@7.28.4)) silent-error: 1.1.1 optionalDependencies: - '@ember/test-helpers': 2.9.6(@babel/core@7.28.4)(ember-source@3.28.12(@babel/core@7.28.4)) + '@ember/test-helpers': 2.9.6(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)) transitivePeerDependencies: - '@babel/core' - '@glint/template' @@ -13457,10 +13422,29 @@ snapshots: - '@babel/core' - supports-color - ember-style-modifier@0.7.0(@babel/core@7.28.4): + ember-stargate@0.5.0(@babel/core@7.28.4)(@ember/test-waiters@3.1.0)(@glimmer/tracking@1.1.2)(@glint/template@1.6.1)(ember-concurrency@4.0.6(@babel/core@7.28.4)(@glint/template@1.6.1))(ember-source@3.28.12(@babel/core@7.28.4)): dependencies: - ember-cli-babel: 7.26.11 - ember-modifier: 3.2.7(@babel/core@7.28.4) + '@ember/render-modifiers': 2.1.0(@babel/core@7.28.4)(@glint/template@1.6.1)(ember-source@3.28.12(@babel/core@7.28.4)) + '@embroider/addon-shim': 1.10.0 + '@glimmer/component': 1.1.2(@babel/core@7.28.4) + ember-resources: 6.5.2(@ember/test-waiters@3.1.0)(@glimmer/component@1.1.2(@babel/core@7.28.4))(@glimmer/tracking@1.1.2)(@glint/template@1.6.1)(ember-concurrency@4.0.6(@babel/core@7.28.4)(@glint/template@1.6.1))(ember-source@3.28.12(@babel/core@7.28.4)) + tracked-maps-and-sets: 3.0.2 + transitivePeerDependencies: + - '@babel/core' + - '@ember/test-waiters' + - '@glimmer/tracking' + - '@glint/template' + - ember-concurrency + - ember-source + - supports-color + + ember-style-modifier@4.5.1(@babel/core@7.28.4)(@ember/string@3.1.1): + dependencies: + '@ember/string': 3.1.1 + '@embroider/addon-shim': 1.10.0 + csstype: 3.1.3 + decorator-transforms: 2.3.0(patch_hash=daaf70dcf904cf2d805d085a6528bf51c0554bb9a602cfdd5025c76d86e07eb9)(@babel/core@7.28.4) + ember-modifier: 4.2.2(@babel/core@7.28.4) transitivePeerDependencies: - '@babel/core' - supports-color @@ -13517,16 +13501,10 @@ snapshots: transitivePeerDependencies: - supports-color - ember-text-measurer@0.6.0: - dependencies: - ember-cli-babel: 7.26.11 - ember-cli-htmlbars: 4.5.0 - transitivePeerDependencies: - - supports-color - - ember-truth-helpers@3.1.1: + ember-tracked-storage-polyfill@1.0.0: dependencies: ember-cli-babel: 7.26.11 + ember-cli-htmlbars: 5.7.2 transitivePeerDependencies: - supports-color @@ -13907,19 +13885,6 @@ snapshots: signal-exit: 3.0.7 strip-final-newline: 2.0.0 - execa@3.4.0: - dependencies: - cross-spawn: 7.0.6 - get-stream: 5.2.0 - human-signals: 1.1.1 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - p-finally: 2.0.1 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 - execa@4.1.0: dependencies: cross-spawn: 7.0.6 @@ -14068,13 +14033,6 @@ snapshots: fast-uri@3.1.0: {} - fastboot-transform@0.1.3: - dependencies: - broccoli-stew: 1.6.0 - convert-source-map: 1.9.0 - transitivePeerDependencies: - - supports-color - fastq@1.19.1: dependencies: reusify: 1.1.0 @@ -14242,6 +14200,10 @@ snapshots: inherits: 2.0.4 readable-stream: 2.3.8 + focus-trap@6.9.4: + dependencies: + tabbable: 5.3.3 + follow-redirects@1.15.11: {} for-each@0.3.5: @@ -14582,10 +14544,6 @@ snapshots: globrex@0.1.2: {} - good-listener@1.2.2: - dependencies: - delegate: 3.2.0 - gopd@1.2.0: {} graceful-fs@4.2.11: {} @@ -14596,8 +14554,6 @@ snapshots: growly@1.3.0: {} - hammerjs@2.0.8: {} - handlebars@4.7.8: dependencies: minimist: 1.2.8 @@ -15597,8 +15553,6 @@ snapshots: '@types/minimatch': 3.0.5 minimatch: 3.1.2 - matchmedia-polyfill@0.3.2: {} - math-intrinsics@1.1.0: {} mathml-tag-names@2.1.3: {} @@ -16140,14 +16094,6 @@ snapshots: pako@1.0.11: {} - paper-data-table@0.1.5(@babel/core@7.28.4): - dependencies: - ember-cli-babel: 6.18.0(@babel/core@7.28.4) - ember-cli-htmlbars: 2.0.5 - transitivePeerDependencies: - - '@babel/core' - - supports-color - parallel-transform@1.2.0: dependencies: cyclist: 1.0.2 @@ -16282,8 +16228,6 @@ snapshots: dependencies: find-up: 3.0.0 - polyfill-nodelist-foreach@1.1.2: {} - portfinder@1.0.38: dependencies: async: 3.2.6 @@ -16356,6 +16300,8 @@ snapshots: printf@0.6.1: {} + prismjs@1.30.0: {} + private@0.1.8: {} process-nextick-args@2.0.1: {} @@ -16378,16 +16324,6 @@ snapshots: promise.hash.helper@1.0.8: {} - prop-types@15.8.1: - dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - react-is: 16.13.1 - - propagating-hammerjs@2.0.1(@egjs/hammerjs@2.0.17): - dependencies: - '@egjs/hammerjs': 2.0.17 - proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -16507,8 +16443,6 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 - react-is@16.13.1: {} - read-pkg@3.0.0: dependencies: load-json-file: 4.0.0 @@ -16929,8 +16863,6 @@ snapshots: ajv-formats: 2.1.1(ajv@8.17.1) ajv-keywords: 5.1.0(ajv@8.17.1) - select@1.1.2: {} - semver@5.7.2: {} semver@6.3.1: {} @@ -17401,6 +17333,8 @@ snapshots: schema-utils: 3.3.0 webpack: 5.101.3 + style-mod@4.1.2: {} + styled_string@0.0.1: {} sum-up@1.0.3: @@ -17451,6 +17385,10 @@ snapshots: dependencies: '@pkgr/core': 0.2.9 + tabbable@5.3.3: {} + + tabbable@6.2.0: {} + tap-parser@7.0.0: dependencies: events-to-array: 1.1.2 @@ -17622,8 +17560,6 @@ snapshots: dependencies: setimmediate: 1.0.5 - tiny-emitter@2.1.0: {} - tiny-glob@0.2.9: dependencies: globalyzer: 0.1.0 @@ -17640,7 +17576,9 @@ snapshots: transitivePeerDependencies: - supports-color - tinycolor2@1.6.0: {} + tippy.js@6.3.7: + dependencies: + '@popperjs/core': 2.11.8 tmp@0.0.28: dependencies: @@ -17699,6 +17637,24 @@ snapshots: tr46@0.0.3: {} + tracked-built-ins@4.0.0(@babel/core@7.28.4): + dependencies: + '@embroider/addon-shim': 1.10.0 + decorator-transforms: 2.3.0(patch_hash=daaf70dcf904cf2d805d085a6528bf51c0554bb9a602cfdd5025c76d86e07eb9)(@babel/core@7.28.4) + ember-tracked-storage-polyfill: 1.0.0 + transitivePeerDependencies: + - '@babel/core' + - supports-color + + tracked-maps-and-sets@3.0.2: + dependencies: + '@glimmer/tracking': 1.1.2 + ember-cli-babel: 7.26.11 + ember-cli-typescript: 4.2.1 + ember-tracked-storage-polyfill: 1.0.0 + transitivePeerDependencies: + - supports-color + tree-sync@1.4.0: dependencies: debug: 2.6.9 @@ -17952,6 +17908,8 @@ snapshots: vm-browserify@1.1.2: {} + w3c-keyname@2.2.8: {} + walk-sync@0.2.7: dependencies: ensure-posix-path: 1.1.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 00000000..5a47fd94 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +patchedDependencies: + '@hashicorp/design-system-components': patches/@hashicorp__design-system-components.patch + decorator-transforms: patches/decorator-transforms.patch diff --git a/scripts/add-custom-flight-icon.mjs b/scripts/add-custom-flight-icon.mjs new file mode 100644 index 00000000..2f2e31ad --- /dev/null +++ b/scripts/add-custom-flight-icon.mjs @@ -0,0 +1,281 @@ +#!/usr/bin/env node + +import { readFile, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import process from 'node:process'; +import { fileURLToPath } from 'node:url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const projectRoot = path.resolve(__dirname, '..'); + +const LUCIDE_BASE_URL = + 'https://raw.githubusercontent.com/lucide-icons/lucide/main/icons'; +const SPRITE_PATH = path.join( + projectRoot, + 'app/utils/custom-flight-icon-sprite.js', +); +const PATCH_PATH = path.join( + projectRoot, + 'patches/@hashicorp__flight-icons.patch', +); + +const DEFAULT_ATTRIBUTES = { + fill: 'none', + stroke: 'currentColor', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', +}; + +const requestedIcons = process.argv + .slice(2) + .map((name) => + name + .replace(/\.svg$/i, '') + .trim() + .toLowerCase(), + ) + .filter(Boolean); + +if (requestedIcons.length === 0) { + console.error( + 'Usage: pnpm node scripts/add-custom-flight-icon.mjs [...]', + ); + process.exitCode = 1; + process.exit(); +} + +function formatInnerContent(content, indent) { + return content + .trim() + .split('\n') + .map((line) => line.trim()) + .filter((line) => line.length > 0) + .map((line) => `${indent}${line}`) + .join('\n'); +} + +function renderSymbol(icon, size, strokeWidth, viewBox, attrs, innerContent) { + const indent = ' '; + const lines = [ + `${indent}`, + innerContent, + `${indent}`, + ]; + + return lines.join('\n'); +} + +function renderBlock({ icon, viewBox, attrs, innerContent, sourceUrl }) { + const indent = ' '; + const innerIndent = ' '; + const formattedInner = formatInnerContent(innerContent, innerIndent); + + return [ + `${indent}`, + renderSymbol(icon, '16', '2', viewBox, attrs, formattedInner), + '', + renderSymbol(icon, '24', '2', viewBox, attrs, formattedInner), + ].join('\n'); +} + +function matchAttribute(block, attribute) { + const regex = new RegExp(`${attribute}="([^"]+)"`); + const match = block.match(regex); + return match ? match[1] : undefined; +} + +function parseExistingBlocks(defsContent) { + const blockRegex = + /\s*\s*/g; + + const blocks = new Map(); + + for (const match of defsContent.matchAll(blockRegex)) { + const [block, icon] = match; + const urlMatch = block.match(/https:\/\/[^\s"]+/); + const sourceUrl = + urlMatch?.[0] ?? + `https://github.com/lucide-icons/lucide/blob/main/icons/${icon}.svg`; + + const symbolMatch = block.match(//); + if (!symbolMatch) { + continue; + } + + const rawSymbol = symbolMatch[0]; + const viewBox = matchAttribute(rawSymbol, 'viewBox') ?? '0 0 24 24'; + const attrs = { + fill: matchAttribute(rawSymbol, 'fill') ?? DEFAULT_ATTRIBUTES.fill, + stroke: matchAttribute(rawSymbol, 'stroke') ?? DEFAULT_ATTRIBUTES.stroke, + 'stroke-linecap': + matchAttribute(rawSymbol, 'stroke-linecap') ?? + DEFAULT_ATTRIBUTES['stroke-linecap'], + 'stroke-linejoin': + matchAttribute(rawSymbol, 'stroke-linejoin') ?? + DEFAULT_ATTRIBUTES['stroke-linejoin'], + }; + + const innerMatch = rawSymbol.match(/>\s*([\s\S]*?)\s*<\/symbol>/); + const innerContent = innerMatch ? innerMatch[1] : ''; + + blocks.set(icon, { + icon, + viewBox, + attrs, + innerContent, + sourceUrl, + }); + } + + return blocks; +} + +async function fetchLucideIcon(icon) { + const url = `${LUCIDE_BASE_URL}/${icon}.svg`; + const response = await fetch(url); + + if (!response.ok) { + throw new Error( + `Failed to fetch ${icon}: ${response.status} ${response.statusText}`, + ); + } + + const svg = await response.text(); + const viewBoxMatch = svg.match(/viewBox="([^"]+)"/); + const viewBox = viewBoxMatch ? viewBoxMatch[1] : '0 0 24 24'; + + const attrs = { ...DEFAULT_ATTRIBUTES }; + for (const attribute of Object.keys(attrs)) { + const value = matchAttribute(svg, attribute); + if (value) { + attrs[attribute] = value; + } + } + + const innerMatch = svg.match(/]*>([\s\S]*?)<\/svg>/i); + if (!innerMatch) { + throw new Error(`Unable to parse SVG content for ${icon}`); + } + + const innerContent = innerMatch[1]; + + return { + icon, + viewBox, + attrs, + innerContent, + sourceUrl: `https://github.com/lucide-icons/lucide/blob/main/icons/${icon}.svg`, + }; +} + +function updatePatchFile(patchContents, iconsToEnsure) { + const plusLineMatch = patchContents.match( + /^\+export const iconNames = \[ (.*) \];$/m, + ); + + if (!plusLineMatch) { + throw new Error( + 'Unable to locate export line in @hashicorp__flight-icons patch', + ); + } + + const listString = plusLineMatch[1]; + const existingIcons = listString + .split(', ') + .map((entry) => entry.slice(1, -1)); // remove surrounding quotes + + let modified = false; + + for (const icon of iconsToEnsure) { + if (existingIcons.includes(icon)) { + continue; + } + existingIcons.push(icon); + modified = true; + } + + if (!modified) { + return patchContents; + } + + const updatedLine = `+export const iconNames = [ ${existingIcons + .map((entry) => `'${entry}'`) + .join(', ')} ];`; + + return patchContents.replace(plusLineMatch[0], updatedLine); +} + +async function main() { + const spriteContents = await readFile(SPRITE_PATH, 'utf8'); + const defsStart = spriteContents.indexOf(' \n'); + const defsEnd = spriteContents.indexOf(' '); + + if (defsStart === -1 || defsEnd === -1) { + throw new Error('Could not locate section in custom sprite file'); + } + + const header = spriteContents.slice(0, defsStart + ' \n'.length); + const existingDefsBody = spriteContents.slice( + defsStart + ' \n'.length, + defsEnd, + ); + const footer = spriteContents.slice(defsEnd); + + const blocks = parseExistingBlocks(existingDefsBody); + + const iconsNeedingSprites = requestedIcons.filter( + (icon) => !blocks.has(icon), + ); + + for (const icon of iconsNeedingSprites) { + const iconData = await fetchLucideIcon(icon); + blocks.set(icon, iconData); + console.log(`• Added ${icon}`); + } + + if (iconsNeedingSprites.length === 0 && requestedIcons.length > 0) { + requestedIcons.forEach((icon) => + console.log(`• ${icon} already present, skipping`), + ); + } + + const sortedBlocks = Array.from(blocks.values()).sort((a, b) => + a.icon.localeCompare(b.icon), + ); + + const spriteChanged = iconsNeedingSprites.length > 0; + + if (spriteChanged) { + const nextSpriteContents = `${header}${sortedBlocks + .map((block) => renderBlock(block)) + .join('\n\n')}\n${footer}`; + await writeFile(SPRITE_PATH, nextSpriteContents, 'utf8'); + } + + const desiredIcons = sortedBlocks.map((block) => block.icon); + const patchContents = await readFile(PATCH_PATH, 'utf8'); + const updatedPatch = updatePatchFile(patchContents, desiredIcons); + + if (updatedPatch !== patchContents) { + await writeFile(PATCH_PATH, updatedPatch, 'utf8'); + console.log('• Updated patch with custom icon names'); + } + + if (!spriteChanged && updatedPatch === patchContents) { + console.log('No new icons were added.'); + } +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/tests/acceptance/calendar-test.js b/tests/acceptance/calendar-test.js index 0ec231f3..692c540c 100644 --- a/tests/acceptance/calendar-test.js +++ b/tests/acceptance/calendar-test.js @@ -3,6 +3,7 @@ import { currentURL, waitUntil } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupApplicationTest } from '../helpers/application-tests'; import { Response } from 'miragejs'; +import { overrideRoute } from '../helpers/override-route'; import { authenticateSession } from 'ember-simple-auth/test-support'; import { percySnapshot } from 'ember-percy'; import { pollTaskFor } from 'ember-lifeline/test-support'; @@ -229,7 +230,7 @@ module('Acceptance | calendar', function (hooks) { 'expected the past committed slot to be disabled', ); - await page.days[9].slots[0].click(); + assert.rejects(page.days[9].slots[0].click()); assert.notOk( page.days[9].slots[0].isCommittedTo, @@ -238,26 +239,31 @@ module('Acceptance | calendar', function (hooks) { }); test('a failure to delete a commitment keeps it displayed and shows an error', async function (assert) { - this.server.delete('/commitments/:id', function () { - return new Response( - 401, - {}, - { - errors: [ - { - status: 401, - title: 'Unauthorized', - }, - ], - }, - ); - }); + const restoreCommitmentDelete = overrideRoute( + this.server, + 'delete', + '/commitments/:id', + function () { + return new Response( + 401, + {}, + { + errors: [ + { + status: 401, + title: 'Unauthorized', + }, + ], + }, + ); + }, + ); await page.visit({ month: '2117-12', token: 'MAGIC??TOKEN' }); await page.days[3].slots[0].click(); - assert.equal(shared.toast.text, 'Couldn’t save your change'); + assert.equal(shared.inlineAlert.text, 'Couldn’t save your change'); assert.ok( page.days[3].slots[0].isCommittedTo, 'expected the slot to still be committed-to', @@ -267,29 +273,40 @@ module('Acceptance | calendar', function (hooks) { 4, 'expected the commitment to still be on the server', ); + + restoreCommitmentDelete(); + + await page.days[3].slots[0].click(); + + assert.notOk(shared.inlineAlert.isPresent); }); test('a failure to create a commitment makes it not display and shows an error', async function (assert) { - this.server.post('/commitments', function () { - return new Response( - 401, - {}, - { - errors: [ - { - status: 401, - title: 'Unauthorized', - }, - ], - }, - ); - }); + const restoreCommitmentCreate = overrideRoute( + this.server, + 'post', + '/commitments', + function () { + return new Response( + 401, + {}, + { + errors: [ + { + status: 401, + title: 'Unauthorized', + }, + ], + }, + ); + }, + ); await page.visit({ month: '2117-12', token: 'MAGIC??TOKEN' }); await page.days[9].slots[1].click(); - assert.equal(shared.toast.text, 'Couldn’t save your change'); + assert.equal(shared.inlineAlert.text, 'Couldn’t save your change'); assert.notOk( page.days[9].slots[1].isCommittedTo, 'expected the slot to not be committed-to', @@ -299,10 +316,15 @@ module('Acceptance | calendar', function (hooks) { 4, 'expected the commitments to be unchanged on the server', ); + + restoreCommitmentCreate(); + + await page.days[9].slots[1].click(); + assert.notOk(shared.inlineAlert.isPresent); }); test('a failure to create a commitment with a particular error shows the error', async function (assert) { - this.server.post('/commitments', function () { + overrideRoute(this.server, 'post', '/commitments', function () { return new Response( 422, {}, @@ -321,8 +343,7 @@ module('Acceptance | calendar', function (hooks) { await page.visit({ month: '2117-12', token: 'MAGIC??TOKEN' }); await page.days[9].slots[1].click(); - - assert.equal(shared.toast.text, 'Fail!'); + assert.equal(shared.inlineAlert.text, 'Fail!'); }); test('visiting with an unknown magic token shows an error', async function (assert) { @@ -408,12 +429,12 @@ module('Acceptance | calendar', function (hooks) { assert.equal(page.person.email.field.value, 'jorts@jants.ca'); assert.ok( - page.person.mobile.desiredMedium, + page.person.mobile.desiredMedium.isChecked, 'expected mobile to be the desired medium', ); - assert.equal(page.person.selfNotes.field.value, 'My self notes'); - assert.equal(page.person.address.field.value, '91 Albert'); + assert.equal(page.person.selfNotes.value, 'My self notes'); + assert.equal(page.person.address.value, '91 Albert'); assert.notOk( page.person.submitButton.isHighlighted, @@ -440,8 +461,8 @@ module('Acceptance | calendar', function (hooks) { await page.person.activeSwitch.click(); await page.person.mobile.field.fillIn('1234'); await page.person.email.desiredMedium.click(); - await page.person.selfNotes.field.fillIn('Updated self notes'); - await page.person.address.field.fillIn('A new address'); + await page.person.selfNotes.fillIn('Updated self notes'); + await page.person.address.fillIn('A new address'); assert.ok( page.person.submitButton.isHighlighted, @@ -512,42 +533,63 @@ module('Acceptance | calendar', function (hooks) { await page.person.name.field.fillIn(''); await page.person.submitButton.click(); - // FIXME validation-specific error text? - assert.equal(shared.toast.text, 'Couldn’t save your details'); + assert.equal(shared.inlineAlert.text, 'Couldn’t save your details'); assert.equal(page.person.name.error.text, "Name can't be blank"); assert.ok( - page.person.name.isError, + page.person.name.field.isError, 'expected the name field to show as being invalid', ); + + await page.person.name.field.fillIn('Jortleby'); + await page.person.submitButton.click(); + + assert.notOk( + shared.inlineAlert.isPresent, + 'expected the inline alert to clear after fixing validation issues', + ); }); test('handles an error saving details', async function (assert) { await page.visit({ month: '2117-12', token: 'MAGIC??TOKEN' }); - this.server.patch('/people/me', function () { - return new Response( - 401, - {}, - { - errors: [ - { - status: 401, - title: 'Unauthorized', - }, - ], - }, - ); - }); + const restorePeopleMe = overrideRoute( + this.server, + 'patch', + '/people/me', + function () { + return new Response( + 401, + {}, + { + errors: [ + { + status: 401, + title: 'Unauthorized', + }, + ], + }, + ); + }, + ); await page.person.toggle.click(); await page.person.name.field.fillIn('Jartleby'); await page.person.submitButton.click(); - assert.equal(shared.toast.text, 'Couldn’t save your details'); + assert.equal(shared.inlineAlert.text, 'Couldn’t save your details'); assert.ok( page.person.name.isVisible, 'expected the form to still be visible', ); + + restorePeopleMe(); + + await page.person.submitButton.click(); + + assert.notOk( + shared.inlineAlert.isPresent, + 'expected the inline alert to clear after retrying the save', + ); }); test('the path controls the month', async function (assert) { @@ -641,22 +683,20 @@ module('Acceptance | calendar', function (hooks) { await page.days[9].slots[1].count.click(); await page.peopleSearch.fillIn('commit'); - await waitUntil(() => page.peopleSearch.options.length === 3); assert.equal( page.peopleSearch.options.length, 3, 'expected three people to show for possible commitments', ); - assert.equal(page.peopleSearch.options[0].name, 'Also non-committal'); + assert.equal(page.peopleSearch.options[0].text, 'Also non-committal'); assert.equal( - page.peopleSearch.options[1].name, + page.peopleSearch.options[1].text, 'Fully Committed Slot Person', ); - assert.equal(page.peopleSearch.options[2].name, 'Non-committal'); + assert.equal(page.peopleSearch.options[2].text, 'Non-committal'); await page.peopleSearch.fillIn('also'); - await waitUntil(() => page.peopleSearch.options.length === 1); assert.equal( page.peopleSearch.options.length, @@ -693,21 +733,26 @@ module('Acceptance | calendar', function (hooks) { }); test('an error when an admin tries to create a commitment is displayed', async function (assert) { - this.server.post('/commitments', function () { - return new Response( - 422, - {}, - { - errors: [ - { - status: 422, - title: 'Unauthorized', - detail: 'Fail!', - }, - ], - }, - ); - }); + const restoreAdminCommitmentCreate = overrideRoute( + this.server, + 'post', + '/commitments', + function () { + return new Response( + 422, + {}, + { + errors: [ + { + status: 422, + title: 'Unauthorized', + detail: 'Fail!', + }, + ], + }, + ); + }, + ); this.server.create('user', { admin: true }); await authenticateSession({ access_token: 'abcdef' }); @@ -718,12 +763,24 @@ module('Acceptance | calendar', function (hooks) { await waitUntil(() => page.peopleSearch.options.length); await page.peopleSearch.options[0].click(); - assert.equal(shared.toast.text, 'Fail!'); + assert.equal(shared.inlineAlert.text, 'Fail!'); assert.equal( this.server.db.commitments.length, 4, 'expected no change on the server', ); + + restoreAdminCommitmentCreate(); + + await page.days[9].slots[0].count.click(); + await page.peopleSearch.fillIn('also'); + await waitUntil(() => page.peopleSearch.options.length); + await page.peopleSearch.options[0].click(); + + assert.notOk( + shared.inlineAlert.isPresent, + 'expected the inline alert to clear after the admin commitment succeeds', + ); }); test('an admin can delete commitments', async function (assert) { @@ -746,21 +803,26 @@ module('Acceptance | calendar', function (hooks) { }); test('an error when an admin tries to delete a commitment is displayed', async function (assert) { - this.server.delete('/commitments/:id', function () { - return new Response( - 422, - {}, - { - errors: [ - { - status: 422, - title: 'Unauthorized', - detail: 'Fail!', - }, - ], - }, - ); - }); + const restoreAdminCommitmentDelete = overrideRoute( + this.server, + 'delete', + '/commitments/:id', + function () { + return new Response( + 422, + {}, + { + errors: [ + { + status: 422, + title: 'Unauthorized', + detail: 'Fail!', + }, + ], + }, + ); + }, + ); this.server.create('user', { admin: true }); await authenticateSession({ access_token: 'abcdef' }); @@ -769,11 +831,16 @@ module('Acceptance | calendar', function (hooks) { await page.days[3].slots[0].count.click(); await page.people[0].remove(); - assert.equal(shared.toast.text, 'Fail!'); + assert.equal(shared.inlineAlert.text, 'Fail!'); assert.equal( this.server.db.commitments.length, 4, 'expected no change on the server', ); + + restoreAdminCommitmentDelete(); + + await page.people[1].remove(); + assert.notOk(shared.inlineAlert.isPresent); }); }); diff --git a/tests/acceptance/debts-test.js b/tests/acceptance/debts-test.js index acf5eeba..de6f1c33 100644 --- a/tests/acceptance/debts-test.js +++ b/tests/acceptance/debts-test.js @@ -11,12 +11,14 @@ import { getPageTitle } from 'ember-page-title/test-support'; module('Acceptance | debts', function (hooks) { setupApplicationTest(hooks); + let sun, firstSunRide, secondSunRide, will, willRide; + hooks.beforeEach(async function () { - const sun = this.server.create('person', { name: 'Sun' }); + sun = this.server.create('person', { name: 'Sun' }); const kala = this.server.create('person', { name: 'Kala' }); - const will = this.server.create('person', { name: 'Will' }); + will = this.server.create('person', { name: 'Will' }); - const sunRide = this.server.create('ride', { + firstSunRide = this.server.create('ride', { driver: sun, foodExpenses: 15400, @@ -27,7 +29,7 @@ module('Acceptance | debts', function (hooks) { end: new Date(2016, 11, 25, 12, 0), }); - const secondSunRide = this.server.create('ride', { + secondSunRide = this.server.create('ride', { driver: sun, foodExpenses: 1000, @@ -38,7 +40,7 @@ module('Acceptance | debts', function (hooks) { end: new Date(2016, 11, 26, 12, 0), }); - const willRide = this.server.create('ride', { + willRide = this.server.create('ride', { driver: will, foodExpenses: 1919, @@ -49,13 +51,13 @@ module('Acceptance | debts', function (hooks) { this.server.create('reimbursement', { person: sun, - ride: sunRide, + ride: firstSunRide, foodExpenses: 4400, }); this.server.create('reimbursement', { person: kala, - ride: sunRide, + ride: firstSunRide, carExpenses: 4400, }); @@ -64,7 +66,7 @@ module('Acceptance | debts', function (hooks) { person: sun, }); // FIXME is this a Mirage bug? This was formerly within the creation but the mock server was returning *both* rides. - firstDebt.rides = [sunRide, secondSunRide]; + firstDebt.rides = [firstSunRide, secondSunRide]; firstDebt.save(); const secondDebt = this.server.create('debt', { @@ -93,34 +95,50 @@ module('Acceptance | debts', function (hooks) { assert.equal(sun.carExpenses, '0'); assert.equal(sun.totalExpenses, '120'); - assert.equal(sun.rides.length, '2'); - - const recentSunRide = sun.rides[0]; - assert.equal(recentSunRide.date, 'Mon Dec 26 2016 10:15a — 12p'); - assert.equal(recentSunRide.foodExpenses, '10'); - - const sunRide = sun.rides[1]; - assert.equal(sunRide.date, 'Sun Dec 25 2016 10:15a — 12p'); - assert.equal(sunRide.foodExpenses, '154'); - assert.equal(sunRide.carExpenses, ''); - - assert.equal( - sun.reimbursements.length, - '1', - 'expected the Kala reimbursement to be hidden', - ); - assert.equal(sun.reimbursements[0].foodExpenses, '-44'); - assert.equal(sun.reimbursements[0].carExpenses, ''); + assert + .dom(`[data-test-debt-ride-row][data-test-driver-id="${sun.id}"]`) + .exists({ count: 2 }); + + let recentSunRideSelector = `[data-test-debt-ride-row][data-test-ride-id="${secondSunRide.id}"]`; + + assert + .dom(`${recentSunRideSelector} [data-test-debt-ride-date]`) + .hasText('Mon Dec 26 2016 10:15a — 12p'); + assert + .dom(`${recentSunRideSelector} [data-test-debt-ride-food]`) + .hasText('10'); + assert.dom(`${recentSunRideSelector} [data-test-debt-ride-car]`); + + let olderSunRideSelector = `[data-test-debt-ride-row][data-test-ride-id="${firstSunRide.id}"]`; + + assert + .dom(`${olderSunRideSelector} [data-test-debt-ride-date]`) + .hasText('Sun Dec 25 2016 10:15a — 12p'); + assert + .dom(`${olderSunRideSelector} [data-test-debt-ride-food]`) + .hasText('154'); + assert.dom(`${olderSunRideSelector} [data-test-debt-ride-car]`).hasText(''); + assert + .dom(`${olderSunRideSelector} [data-test-debt-ride-food-reimbursed]`) + .hasText('-44'); + assert + .dom(`${olderSunRideSelector} [data-test-debt-ride-food-reimbursed]`) + .hasAttribute('title', '44 has already been reimbursed'); const will = page.people[1]; assert.equal(will.foodExpenses, '19.19'); assert.equal(will.carExpenses, '19.19'); assert.equal(will.totalExpenses, '38.38'); - assert.equal(will.rides.length, '1'); - assert.ok( - will.rides[0].carExpenseIsDonation, - 'expected the ride’s car expenses to be marked a donation', - ); + + assert + .dom(`[data-test-debt-ride-row][data-test-driver-id="${will.id}"]`) + .exists({ count: 1 }); + + assert + .dom( + `[data-test-debt-ride-row][data-test-driver-id="${will.id}"] [data-test-debt-ride-donation]`, + ) + .exists(); }); test('a debt can be reimbursed', async function (assert) { diff --git a/tests/acceptance/forgot-test.js b/tests/acceptance/forgot-test.js index 57d516e5..7ba64a9c 100644 --- a/tests/acceptance/forgot-test.js +++ b/tests/acceptance/forgot-test.js @@ -2,6 +2,7 @@ import { module, test } from 'qunit'; import { setupApplicationTest } from '../helpers/application-tests'; import { percySnapshot } from 'ember-percy'; +import { Response } from 'miragejs'; import { getPageTitle } from 'ember-page-title/test-support'; import forgotPage from 'prison-rideshare-ui/tests/pages/forgot'; @@ -16,7 +17,7 @@ module('Acceptance | forgot', function (hooks) { this.server.post( '/users/reset', function (schema, { queryParams: { email } }) { - assert.equal(email, 'hello'); + assert.equal(email, 'hello@example.com'); return done(); }, @@ -26,10 +27,43 @@ module('Acceptance | forgot', function (hooks) { percySnapshot(assert); - await forgotPage.fillEmail('hello'); + await forgotPage.fillEmail('hello@example.com'); await forgotPage.submit(); assert.equal(getPageTitle(), 'Forgot password · Prison Rideshare'); assert.equal(shared.toast.text, 'Check your email'); }); + + test('a reset email failure shows an alert until it succeeds', async function (assert) { + let shouldFail = true; + + this.server.post('/users/reset', () => { + if (shouldFail) { + return new Response( + 500, + {}, + { + errors: [ + { + detail: 'nope', + }, + ], + }, + ); + } + + return {}; + }); + + await forgotPage.visit(); + await forgotPage.fillEmail('hello@example.com'); + await forgotPage.submit(); + + assert.equal(shared.inlineAlert.text, 'nope'); + + shouldFail = false; + await forgotPage.submit(); + + assert.notOk(shared.inlineAlert.isPresent); + }); }); diff --git a/tests/acceptance/gas-prices-test.js b/tests/acceptance/gas-prices-test.js index 8a435424..b82254d0 100644 --- a/tests/acceptance/gas-prices-test.js +++ b/tests/acceptance/gas-prices-test.js @@ -6,7 +6,7 @@ import { percySnapshot } from 'ember-percy'; import page from 'prison-rideshare-ui/tests/pages/gas-prices'; import { getPageTitle } from 'ember-page-title/test-support'; -module('Acceptance | reports', function (hooks) { +module('Acceptance | gas prices', function (hooks) { setupApplicationTest(hooks); hooks.beforeEach(function () { diff --git a/tests/acceptance/log-test.js b/tests/acceptance/log-test.js index 98e8ef1e..3d6f8ac5 100644 --- a/tests/acceptance/log-test.js +++ b/tests/acceptance/log-test.js @@ -8,6 +8,7 @@ import page from 'prison-rideshare-ui/tests/pages/log'; import loginPage from 'prison-rideshare-ui/tests/pages/login'; import shared from 'prison-rideshare-ui/tests/pages/shared'; import { getPageTitle } from 'ember-page-title/test-support'; +import stringToMobiledoc from 'prison-rideshare-ui/tests/helpers/string-to-mobiledoc'; module('Acceptance | log', function (hooks) { setupApplicationTest(hooks); @@ -171,13 +172,3 @@ module('Acceptance | log', function (hooks) { assert.equal(page.posts.length, 1); }); }); - -function stringToMobiledoc(string) { - return JSON.stringify({ - version: '0.3.2', - atoms: [], - cards: [], - markups: [], - sections: [[1, 'p', [[0, [], 0, string]]]], - }); -} diff --git a/tests/acceptance/login-test.js b/tests/acceptance/login-test.js index aed6397c..ef9233b3 100644 --- a/tests/acceptance/login-test.js +++ b/tests/acceptance/login-test.js @@ -5,6 +5,8 @@ import { setupApplicationTest } from '../helpers/application-tests'; import { percySnapshot } from 'ember-percy'; import { authenticateSession } from 'ember-simple-auth/test-support'; +import { Response } from 'miragejs'; +import { overrideRoute } from '../helpers/override-route'; import page from 'prison-rideshare-ui/tests/pages/login'; import shared from 'prison-rideshare-ui/tests/pages/shared'; @@ -69,29 +71,41 @@ module('Acceptance | login', function (hooks) { ); await page.visit(); - await page.fillEmail('x'); + await page.fillEmail('x@example.com'); await page.fillPassword('x'); await page.submit(); assert.equal(currentURL(), '/reports/new'); }); - test('a failed login shows an error', async function (assert) { - this.server.post( + test('a failed login shows an error that clears on retry', async function (assert) { + const restoreToken = overrideRoute( + this.server, + 'post', '/token', - () => { - return {}; - }, - 401, + () => new Response(401, {}, {}), ); await page.visit(); - await page.fillEmail('x'); + await page.fillEmail('x@example.com'); + await page.fillPassword('wrong-password'); await page.submit(); percySnapshot(assert); assert.equal(currentURL(), '/login'); - assert.equal(page.error, 'There was an error logging you in.'); + assert.equal(shared.inlineAlert.text, 'There was an error logging you in.'); + + restoreToken(); + this.server.create('user', { + email: 'x@example.com', + password: 'correcthorsebatterystaple', + }); + + await page.fillPassword('correcthorsebatterystaple'); + await page.submit(); + + assert.equal(currentURL(), '/reports/new'); + assert.notOk(shared.inlineAlert.isPresent); }); }); diff --git a/tests/acceptance/people-test.js b/tests/acceptance/people-test.js index 53ca5f82..4146394e 100644 --- a/tests/acceptance/people-test.js +++ b/tests/acceptance/people-test.js @@ -2,14 +2,15 @@ import { module, test } from 'qunit'; import { setupApplicationTest } from '../helpers/application-tests'; import { percySnapshot } from 'ember-percy'; +import { Response } from 'miragejs'; import { authenticateSession } from 'ember-simple-auth/test-support'; -import { selectChoose } from 'ember-power-select/test-support/helpers'; import page from 'prison-rideshare-ui/tests/pages/people'; import ridesPage from 'prison-rideshare-ui/tests/pages/rides'; import shared from 'prison-rideshare-ui/tests/pages/shared'; import { getPageTitle } from 'ember-page-title/test-support'; +import { overrideRoute } from '../helpers/override-route'; module('Acceptance | people', function (hooks) { setupApplicationTest(hooks); @@ -139,6 +140,24 @@ module('Acceptance | people', function (hooks) { assert.equal(will.medium, 'mobile'); }); + test('people can be sorted by name and last ride', async function (assert) { + await page.visit(); + + assert.deepEqual(page.peopleNames, ['Kala', 'Sun']); + + await page.head.nameSort.click(); + assert.deepEqual(page.peopleNames, ['Sun', 'Kala']); + + await page.head.nameSort.click(); + assert.deepEqual(page.peopleNames, ['Kala', 'Sun']); + + await page.head.lastRideSort.click(); + assert.deepEqual(page.peopleNames, ['Sun', 'Kala']); + + await page.head.lastRideSort.click(); + assert.deepEqual(page.peopleNames, ['Kala', 'Sun']); + }); + test('a person can be created and chosen for a ride', async function (assert) { await ridesPage.visit(); await ridesPage.rides[0].driver.click(); @@ -162,7 +181,7 @@ module('Acceptance | people', function (hooks) { await ridesPage.visit(); await ridesPage.rides[0].driver.click(); - await selectChoose('.driver md-input-container', 'Capheus'); + await ridesPage.rides[0].driver.choose('Capheus'); assert.equal(ridesPage.rides[0].driver.text, 'Capheus'); }); @@ -190,19 +209,31 @@ module('Acceptance | people', function (hooks) { [sun] = this.server.db.people; assert.ok( sun.active, - 'expected Sun to have been made active on the server', + 'expected Sun to have been made active on the server.', ); + assert.notOk(shared.inlineAlert.isPresent); - this.server.patch('/people/:id', {}, 422); + const restorePeoplePatch = overrideRoute( + this.server, + 'patch', + '/people/:id', + () => new Response(422, {}, {}), + ); // FIXME test that inactive people aren’t shown in the ride-person popup await page.people[1].activeSwitch.click(); assert.equal( - shared.toast.text, - 'There was an error saving the active status of Sun', + shared.inlineAlert.text, + 'There was an error saving the active status of Sun.', ); + + restorePeoplePatch(); + + await page.people[1].activeSwitch.click(); + + assert.notOk(shared.inlineAlert.isPresent); }); test('person validation errors are displayed', async function (assert) { @@ -229,6 +260,7 @@ module('Acceptance | people', function (hooks) { await page.visit(); await page.newPerson(); + await page.form.nameField.fill('William'); await page.form.submit(); assert.equal(page.form.nameError.text, "Name can't be blank"); diff --git a/tests/acceptance/registration-test.js b/tests/acceptance/registration-test.js index faa5c45b..2cb5ba62 100644 --- a/tests/acceptance/registration-test.js +++ b/tests/acceptance/registration-test.js @@ -79,6 +79,9 @@ module('Acceptance | registration', function (hooks) { percySnapshot(assert); assert.equal(currentURL(), '/register'); - assert.equal(page.error, 'Password confirmation did not match'); + assert.equal( + shared.inlineAlert.text, + 'Password confirmation did not match', + ); }); }); diff --git a/tests/acceptance/reimbursements-test.js b/tests/acceptance/reimbursements-test.js index 219251b0..c015b8fd 100644 --- a/tests/acceptance/reimbursements-test.js +++ b/tests/acceptance/reimbursements-test.js @@ -124,12 +124,14 @@ module('Acceptance | reimbursements', function (hooks) { ); assert.equal(kala.carExpenses, '22'); assert.equal(kala.totalExpenses, '22'); - assert.ok( - kala.processButton.isPrimary, + assert.equal( + kala.processButton.variant, + 'primary', 'expected the process button to be default for non-donations', ); - assert.notOk( - kala.donateButton.isPrimary, + assert.equal( + kala.donateButton.variant, + 'secondary', 'expected the donate button to not be default for non-donations', ); @@ -141,12 +143,14 @@ module('Acceptance | reimbursements', function (hooks) { kalaDonation.carExpenseIsDonation, 'expected the donation to be thus marked', ); - assert.notOk( - kalaDonation.processButton.isPrimary, + assert.equal( + kalaDonation.processButton.variant, + 'secondary', 'expected the process button to not be default for donations', ); - assert.ok( - kalaDonation.donateButton.isPrimary, + assert.equal( + kalaDonation.donateButton.variant, + 'primary', 'expected the donate button to be default for donations', ); }); diff --git a/tests/acceptance/reports-test.js b/tests/acceptance/reports-test.js index bd84c1f5..a5e71bdc 100644 --- a/tests/acceptance/reports-test.js +++ b/tests/acceptance/reports-test.js @@ -3,12 +3,14 @@ import { currentURL } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupApplicationTest } from '../helpers/application-tests'; import { percySnapshot } from 'ember-percy'; +import { Response } from 'miragejs'; import { authenticateSession } from 'ember-simple-auth/test-support'; import page from 'prison-rideshare-ui/tests/pages/report'; import shared from 'prison-rideshare-ui/tests/pages/shared'; import { getPageTitle } from 'ember-page-title/test-support'; +import { overrideRoute } from '../helpers/override-route'; module('Acceptance | reports', function (hooks) { setupApplicationTest(hooks); @@ -69,15 +71,15 @@ module('Acceptance | reports', function (hooks) { assert.ok(page.noSession.isHidden); assert.equal( - page.rides[0].label, + page.rides[0].text, 'francine: Sun, Dec 25 at 10:15a to Remand Centre (33 ¢⁄km )', ); assert.equal( - page.rides[1].label, + page.rides[1].text, 'Tue, Dec 27 at 5:00p to Fort Leavenworth', ); - await page.rides[0].choose(); + await page.rides[0].click(); await page.distance.fillIn(75); await page.foodExpenses.fillIn(25.5); await page.carExpenses.fillIn(52.05); @@ -105,7 +107,7 @@ module('Acceptance | reports', function (hooks) { 'expected the reported-on ride to have disappeared', ); assert.equal( - page.rides[0].label, + page.rides[0].text, 'Tue, Dec 27 at 5:00p to Fort Leavenworth', ); }); @@ -131,7 +133,7 @@ module('Acceptance | reports', function (hooks) { test('a ride that is not donatable doesn’t show the donation checkbox, same for overridable and car expenses', async function (assert) { await page.visit(); - await page.rides[1].choose(); + await page.rides[1].click(); assert.ok( page.donation.isHidden, @@ -146,32 +148,34 @@ module('Acceptance | reports', function (hooks) { test('unsaved changes are discarded when the selected ride changes', async function (assert) { await page.visit(); - await page.rides[0].choose(); + await page.rides[0].click(); await page.distance.fillIn(75); - await page.rides[1].choose(); - await page.rides[0].choose(); + await page.rides[1].click(); + await page.rides[0].click(); assert.equal(page.distance.value, ''); }); test('a failure to save keeps the values and displays an error', async function (assert) { - this.server.patch( + const restoreReportSave = overrideRoute( + this.server, + 'patch', '/rides/:id', - () => { - return {}; - }, - 422, + () => new Response(422, {}, {}), ); await page.visit(); - await page.rides[0].choose(); + await page.rides[0].click(); await page.distance.fillIn(75); await page.submitButton.click(); - assert.equal(shared.toast.text, 'There was an error saving your report!'); + assert.equal( + shared.inlineAlert.text, + 'There was an error saving your report!', + ); assert.equal(currentURL(), '/reports/new'); assert.equal( @@ -179,5 +183,15 @@ module('Acceptance | reports', function (hooks) { '75', 'expected the distance field to have the same value', ); + + restoreReportSave(); + + await page.submitButton.click(); + + assert.equal(shared.toast.text, 'Your report was saved'); + assert.notOk( + shared.inlineAlert.isPresent, + 'expected the inline alert to clear after retrying the submission', + ); }); }); diff --git a/tests/acceptance/reset-test.js b/tests/acceptance/reset-test.js index 215de744..689f8ba7 100644 --- a/tests/acceptance/reset-test.js +++ b/tests/acceptance/reset-test.js @@ -4,6 +4,7 @@ import { setupApplicationTest } from '../helpers/application-tests'; import { currentURL } from '@ember/test-helpers'; import { percySnapshot } from 'ember-percy'; import { Response } from 'miragejs'; +import { overrideRoute } from '../helpers/override-route'; import resetPage from 'prison-rideshare-ui/tests/pages/reset'; import shared from 'prison-rideshare-ui/tests/pages/shared'; @@ -65,22 +66,26 @@ module('Acceptance | reset password', function (hooks) { }); test('a validation error is displayed', async function (assert) { - this.server.put('/users/:token', () => { - return new Response( - 422, - {}, - { - errors: [ - { - source: { - pointer: '/data/attributes/password-confirmation', + const restoreResetRoute = overrideRoute( + this.server, + 'put', + '/users/:token', + () => + new Response( + 422, + {}, + { + errors: [ + { + source: { + pointer: '/data/attributes/password-confirmation', + }, + detail: 'Password confirmation did not match', }, - detail: 'Password confirmation did not match', - }, - ], - }, - ); - }); + ], + }, + ), + ); await resetPage.visit({ token: 'hey' }); await resetPage.fillPassword('x'); @@ -88,7 +93,24 @@ module('Acceptance | reset password', function (hooks) { percySnapshot(assert); - assert.equal(shared.toast.text, 'Password confirmation did not match'); + assert.equal( + shared.inlineAlert.text, + 'Password confirmation did not match', + ); + + this.server.create('user', { email: 'validation@test.com' }); + this.server.post('/token', () => { + return { + access_token: 'abcdef', + }; + }); + restoreResetRoute(({ users }) => users.first()); + + await resetPage.fillPassword('hello'); + await resetPage.fillPasswordConfirmation('hello'); + await resetPage.submit(); + + assert.notOk(shared.inlineAlert.isPresent); }); test('an unknown error is handled', async function (assert) { @@ -100,6 +122,6 @@ module('Acceptance | reset password', function (hooks) { await resetPage.fillPassword('x'); await resetPage.submit(); - assert.equal(shared.toast.text, 'An unknown error occurred'); + assert.equal(shared.inlineAlert.text, 'An unknown error occurred'); }); }); diff --git a/tests/acceptance/rides-test.js b/tests/acceptance/rides-test.js index c41a2871..158d8f75 100644 --- a/tests/acceptance/rides-test.js +++ b/tests/acceptance/rides-test.js @@ -1,17 +1,19 @@ /* eslint-disable qunit/assert-args, qunit/require-expect */ -import { find, waitUntil } from '@ember/test-helpers'; +import { click, currentURL, findAll, waitUntil } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupApplicationTest } from '../helpers/application-tests'; import { percySnapshot } from 'ember-percy'; +import { Response } from 'miragejs'; import { authenticateSession } from 'ember-simple-auth/test-support'; -import { selectChoose } from 'ember-power-select/test-support/helpers'; import page from 'prison-rideshare-ui/tests/pages/rides'; import shared from 'prison-rideshare-ui/tests/pages/shared'; import { getPageTitle } from 'ember-page-title/test-support'; -import moment from 'moment'; +import moment from 'moment-timezone'; +import { overrideRoute } from '../helpers/override-route'; +import { selectChoose } from 'ember-power-select/test-support'; module('Acceptance | rides', function (hooks) { setupApplicationTest(hooks); @@ -204,8 +206,6 @@ module('Acceptance | rides', function (hooks) { ); assert.equal(page.cancellationForm.reason.value, 'lockdown'); - // FIXME why did this stop working with Ember Paper beta 2? - // selectChoose('md-input-container.reason', 'visitor'); await page.cancellationForm.other.fillIn('visitor'); await page.cancellationForm.save(); @@ -250,12 +250,89 @@ module('Acceptance | rides', function (hooks) { assert.ok(page.rides[0].cancellation.showsDriverNotFound); - this.server.patch('/rides/:id', {}, 500); + await page.rides[2].cancellation.click(); + await page.cancellationForm.reason.fillIn('lockdown'); + await page.cancellationForm.save(); + + assert.ok(page.rides[2].cancellation.showsLockdown); + + const restoreRidePatch = overrideRoute( + this.server, + 'patch', + '/rides/:id', + () => new Response(500, {}, {}), + ); + + await page.rides[2].cancellation.click(); + await page.cancellationForm.shortcutButtons[0].click(); + + assert.equal( + shared.inlineAlert.text, + 'There was an error cancelling this ride', + ); + restoreRidePatch(); + + await page.cancellationForm.cancel(); await page.rides[2].cancellation.click(); await page.cancellationForm.shortcutButtons[0].click(); - assert.equal(shared.toast.text, 'There was an error cancelling this ride'); + assert.notOk(shared.inlineAlert.isPresent); + + await page.head.search.fillIn('Chelsea'); + await page.head.completedSwitch.click(); + + const url = currentURL(); + + assert.ok( + url.includes('cancelled=true'), + 'expected the cancelled filter to be reflected in the URL', + ); + assert.ok( + url.includes('completed=true'), + 'expected the completed filter to be reflected in the URL', + ); + assert.ok( + url.includes('dir=desc'), + 'expected the sort direction to be reflected in the URL', + ); + assert.ok( + url.includes('search=Chelsea'), + 'expected the search query to be reflected in the URL', + ); + }); + + test('selecting an existing visitor replaces the typed value', async function (assert) { + this.server.create('ride', { + name: 'Octavia Butler', + address: '123 Earthseed Way', + contact: '204-555-1111', + }); + + await page.visit(); + await page.newRide(); + + // Choose by clicking option because text-matching component-rendered option is difficult in Power Select. + await click('[data-test-visitor-select]'); + await page.form.name.searchInput.fillIn('Oct'); + await waitUntil(() => + findAll('.ember-power-select-option').some((option) => + option.textContent.includes('Octavia Butler'), + ), + ); + + const option = findAll('.ember-power-select-option').find((el) => + el.textContent.includes('Octavia Butler'), + ); + await click(option); + + assert.strictEqual(page.form.name.value, 'Octavia Butler'); + assert.strictEqual(page.form.address.value, '123 Earthseed Way'); + assert.strictEqual(page.form.contact.value, '204-555-1111'); + + await page.form.address.click(); // blur the select to mimic mobile behavior + + assert.strictEqual(page.form.name.value, 'Octavia Butler'); }); test('completed rides can be shown and cleared', async function (assert) { @@ -405,13 +482,19 @@ module('Acceptance | rides', function (hooks) { await page.form.firstTime.click(); await page.form.passengers.fillIn(2); + assert.equal( + page.form.name.value, + 'Edward', + 'expected the typed visitor name to remain visible when no suggestion is chosen', + ); + assert.ok( page.form.firstTimePoints.isVisible, 'expected the first time tips to show after the checkbox is set', ); // FIXME not really here, but keyboard input for this is broken, and hovering - await selectChoose('md-input-container.institution', 'Rockwood'); + await page.form.institution.choose('Rockwood'); percySnapshot(assert); @@ -456,7 +539,7 @@ module('Acceptance | rides', function (hooks) { assert.ok(lastRide.overridable); await page.rides[0].driver.click(); - await selectChoose('.driver md-input-container', 'Sun'); + await page.rides[0].driver.choose('Sun'); assert.equal(page.rides[0].driver.text, 'Sun'); assert.equal( @@ -531,15 +614,66 @@ module('Acceptance | rides', function (hooks) { assert.notOk(lastRide.firstTime); assert.equal(lastRide.medium, 'email'); - this.server.patch('/rides/:id', {}, 500); + const restoreRideSave = overrideRoute( + this.server, + 'patch', + '/rides/:id', + () => new Response(500, {}, {}), + ); await page.rides[0].edit(); await page.form.notes.fillIn('Updated request notes?'); await page.form.submit(); - assert.equal(shared.toast.text, 'There was an error saving this ride'); + assert.equal( + shared.inlineAlert.text, + 'There was an error saving this ride', + ); // assert.equal(page.notes[0].text, 'Some request notes?'); FIXME lost due to fix for #123 assert.equal(page.form.notes.value, 'Updated request notes?'); + + restoreRideSave(); + + await page.form.submit(); + assert.notOk(shared.inlineAlert.isPresent); + }); + + test('a visitor name can be entered manually even when matches exist', async function (assert) { + const rockwood = this.server.create('institution', { + name: 'Rockwood', + }); + + this.server.create('ride', { + name: 'Greyson', + address: '91 Albert St.', + contact: 'greyson@example.com', + institution: rockwood, + start: new Date(2016, 11, 20, 9, 0), + end: new Date(2016, 11, 20, 11, 0), + }); + + await page.visit(); + await page.newRide(); + + await page.form.name.fillIn('gre'); + + await waitUntil(() => page.form.name.suggestions.length >= 2); + + assert.strictEqual(page.form.name.suggestions[0].name, 'Greyson'); + assert.strictEqual( + page.form.name.suggestions[1].name, + 'Use “gre” as visitor name', + 'expected the manual option to be listed last', + ); + + await selectChoose( + '[data-test-visitor-select]', + 'Use “gre” as visitor name', + ); + + assert.strictEqual(page.form.name.value, 'gre'); + assert.strictEqual(page.form.address.value, ''); + assert.strictEqual(page.form.contact.value, ''); }); test('ride times can be entered manually', async function (assert) { @@ -555,7 +689,7 @@ module('Acceptance | rides', function (hooks) { await page.form.timespanOverrideButton.click(); - assert.ok(page.form.timespanOverrideButton.isHidden); + assert.ok(page.form.timespanOverrideButton.isDisabled); assert.ok(page.form.timespanStart.isVisible); await page.form.timespan.fillIn('Dec 26 2016 from 9a to 11:30'); @@ -570,7 +704,7 @@ module('Acceptance | rides', function (hooks) { await page.form.contact.fillIn('jants@example.com'); // FIXME not really here, but keyboard input for this is broken, and hovering - await selectChoose('md-input-container.institution', 'Rockwood'); + await page.form.institution.choose('Rockwood'); await page.form.submit(); @@ -598,7 +732,7 @@ module('Acceptance | rides', function (hooks) { await page.form.name.fillIn('fran'); await waitUntil(() => page.form.name.suggestions.length); - assert.equal(page.form.name.suggestions.length, 2); + assert.equal(page.form.name.suggestions.length, 3); await page.form.name.suggestions[0].as((francine) => { assert.equal(francine.name, 'Francine'); @@ -611,15 +745,59 @@ module('Acceptance | rides', function (hooks) { assert.equal(frank.contact, 'frank@jants.ca'); }); + assert.strictEqual( + page.form.name.suggestions[2].name, + 'Use “fran” as visitor name', + ); + await page.form.name.suggestions[1].click(); - // FIXME the page object field value is "" but it works via jQuery? 🤔 - assert.equal(find('md-autocomplete input').value, 'frank'); - // assert.equal(page.form.name.value, 'frank'); + assert.equal(page.form.name.text, 'frank'); assert.equal(page.form.contact.value, 'frank@jants.ca'); assert.equal(page.form.address.value, '91 Albert St.'); }); + test('timespan validation errors are shown when manual overrides are not used', async function (assert) { + this.server.post( + '/rides', + { + errors: [ + { + source: { + pointer: '/data/attributes/start', + }, + detail: 'Start must be present', + }, + { + source: { + pointer: '/data/attributes/end', + }, + detail: 'End must be present', + }, + ], + }, + 422, + ); + + await page.visit(); + await page.newRide(); + + assert.ok( + page.form.timespanStart.isHidden, + 'expected manual start time field to be hidden before override', + ); + assert.ok(page.form.timespanEnd.isHidden); + + await page.form.submit(); + + assert.equal(page.form.timespanErrors.length, 2); + + assert.equal(page.form.timespanErrors[0].text, 'Start must be present'); + assert.equal(page.form.timespanErrors[1].text, 'End must be present'); + + this.server.post('/rides'); + }); + test('ride validation errors are displayed but can be recovered from', async function (assert) { this.server.post( '/rides', @@ -652,6 +830,9 @@ module('Acceptance | rides', function (hooks) { await page.newRide(); await page.form.timespanOverrideButton.click(); + assert.true(page.form.nameError.isHidden); + assert.true(page.form.institutionError.isHidden); + await page.form.submit(); assert.equal(page.form.nameError.text, "Name can't be blank"); @@ -820,10 +1001,6 @@ module('Acceptance | rides', function (hooks) { await page.visit(); assert.equal(page.rides.length, 2, 'expected two rides to show by default'); - assert.ok( - page.head.search.clear.isHidden, - 'expected the empty search field to have no clear button', - ); await page.head.search.fillIn('chel'); @@ -837,27 +1014,14 @@ module('Acceptance | rides', function (hooks) { 'Chelsea', 'expected the ride to be the Chelsea one', ); - assert.ok( - page.head.search.clear.isVisible, - 'expected the clear button to show when the field has content', - ); - await page.head.search.clear.click(); + await page.head.search.fillIn(''); assert.equal( page.rides.length, 2, 'expected the ride list to be returned to its default state', ); - assert.equal( - page.head.search.value, - '', - 'expected the search field to now be empty', - ); - assert.ok( - page.head.search.clear.isHidden, - 'expected the empty search field to have no clear button', - ); await page.head.search.fillIn('HEL'); @@ -936,6 +1100,7 @@ module('Acceptance | rides', function (hooks) { end: nextWeek, medium: 'phone', requestConfirmed: false, + requestNotes: 'Needs confirmation', }); this.server.create('ride', { @@ -952,16 +1117,23 @@ module('Acceptance | rides', function (hooks) { assert.equal(page.confirmationNotifications.length, 2); assert.ok(page.rides[1].isHighlighted); - assert.ok(page.rides[2].isHighlighted); + + // Confusing: the first row with notes is the third ride + const rideWithNotes = page.rides[2]; + const notes = page.notes[0]; + + assert.ok(rideWithNotes.isHighlighted); + assert.ok(notes.isHighlighted); assert.equal(shared.ridesBadge.text, '2'); - await page.rides[2].edit(); + await rideWithNotes.edit(); await page.form.requestConfirmed.click(); await page.form.submit(); assert.equal(page.confirmationNotifications.length, 1); - assert.notOk(page.rides[2].isHighlighted); + assert.notOk(rideWithNotes.isHighlighted); + assert.notOk(notes.isHighlighted); assert.equal(shared.ridesBadge.text, '1'); await page.confirmationNotifications[0].markConfirmed(); diff --git a/tests/acceptance/sidebar-test.js b/tests/acceptance/sidebar-test.js new file mode 100644 index 00000000..c9a06004 --- /dev/null +++ b/tests/acceptance/sidebar-test.js @@ -0,0 +1,197 @@ +import { visit } from '@ember/test-helpers'; +import { module, test } from 'qunit'; +import { setupApplicationTest } from '../helpers/application-tests'; +import stringToMobiledoc from 'prison-rideshare-ui/tests/helpers/string-to-mobiledoc'; +import { authenticateSession } from 'ember-simple-auth/test-support'; + +import rides from 'prison-rideshare-ui/tests/pages/rides'; +import shared from 'prison-rideshare-ui/tests/pages/shared'; + +module('Acceptance | sidebar', function (hooks) { + setupApplicationTest(hooks); + + hooks.beforeEach(async function () { + this.server.create('user', { admin: true }); + await authenticateSession({ access_token: 'abcdef' }); + }); + + test('the header toggle opens and closes the sidebar', async function (assert) { + await rides.visit(); + + const initialState = shared.sidebarState; + + await shared.sidebarToggle.click(); + assert.notStrictEqual(shared.sidebarState, initialState); + + await shared.sidebarToggle.click(); + assert.strictEqual(shared.sidebarState, initialState); + }); + + test('the sidebar is closed on mobile until toggled open', async function (assert) { + assert.expect(11); + + const restoreMatchMedia = stubDesktopMatchMedia(false); + + try { + await rides.visit(); + + assert.true( + shared.sidebar.isPresent, + 'sidebar container renders by default', + ); + assert.strictEqual( + shared.sidebarState, + 'closed', + 'sidebar starts closed on mobile', + ); + + const sidebarService = this.owner.lookup('service:sidebar'); + + assert.false(sidebarService.open, 'sidebar service starts closed'); + assert.true( + sidebarService.navIsMinimized, + 'sidebar service starts minimized', + ); + + await shared.sidebarToggle.click(); + + assert.true( + sidebarService.open, + 'sidebar service reports open after toggle', + ); + assert.true(shared.sidebar.isPresent, 'sidebar renders after toggle'); + assert.strictEqual( + shared.sidebarState, + 'open', + 'sidebar opens when toggled', + ); + assert.true( + shared.sidebarToggle.isPresent, + 'header toggle remains available while sidebar is open', + ); + + await shared.sidebarToggle.click(); + + assert.true( + shared.sidebar.isPresent, + 'sidebar remains rendered after closing', + ); + assert.strictEqual(shared.sidebarState, 'closed', 'sidebar closes again'); + assert.true( + sidebarService.navIsMinimized, + 'sidebar service reports minimized after closing', + ); + } finally { + restoreMatchMedia(); + } + }); + + test('a badge is shown on the toggle when notifications exist and the sidebar is closed', async function (assert) { + this.server.create('post', { + content: stringToMobiledoc('hello'), + poster: this.server.create('user'), + unread: true, + insertedAt: new Date(2018, 6, 6, 14), + }); + + const week = 7 * 24 * 60 * 60 * 1000; + const nowMilliseconds = new Date().getTime(); + const nextWeek = new Date(nowMilliseconds + week); + + this.server.create('ride', { + start: nextWeek, + end: nextWeek, + medium: 'phone', + requestConfirmed: false, + }); + + this.firstRide = this.server.create('ride', { + name: 'Visitor', + contact: '555-1919', + address: '91 alb', + start: new Date(2117, 11, 4, 17, 0), + end: new Date(2117, 11, 4, 30), + }); + + let person = this.server.create('person', { name: 'Octavia Butler' }); + let slot = this.server.create('slot', { + start: new Date(2117, 11, 4, 17, 30), + end: new Date(2117, 11, 4, 20), + }); + + this.firstRide.createCommitment({ slot, person }); + this.firstRide.save(); + + await visit('/rides'); + await shared.sidebarToggle.click(); + + assert.strictEqual( + shared.sidebarState, + 'closed', + 'expected the sidebar to be hidden after toggling', + ); + assert.true( + shared.sidebarToggleBadge.isPresent, + 'expected a badge to be shown on the toggle when notifications exist', + ); + + assert.strictEqual(shared.sidebarToggleBadge.text.trim(), '3'); + }); + + test('the sidebar closes after selecting a link on mobile', async function (assert) { + assert.expect(4); + + const restoreMatchMedia = stubDesktopMatchMedia(false); + + try { + await visit('/rides'); + await shared.sidebarToggle.click(); + assert.strictEqual(shared.sidebarState, 'open'); + + await shared.sidebarNavReportLink.click(); + assert.strictEqual(shared.sidebarState, 'closed'); + + const sidebarService = this.owner.lookup('service:sidebar'); + assert.true(sidebarService.navIsMinimized); + assert.false(sidebarService.open); + } finally { + restoreMatchMedia(); + } + }); +}); + +function stubDesktopMatchMedia(matches) { + const originalMatchMedia = window.matchMedia; + const desktopQuery = /\(min-width:\s*1088px\)/; + + window.matchMedia = (query) => { + if (desktopQuery.test(query)) { + return createMockMediaQueryList(matches, query); + } + + if (typeof originalMatchMedia === 'function') { + return originalMatchMedia(query); + } + + return createMockMediaQueryList(true, query); + }; + + return () => { + window.matchMedia = originalMatchMedia; + }; +} + +function createMockMediaQueryList(matches, media) { + return { + matches, + media, + onchange: null, + addListener() {}, + removeListener() {}, + addEventListener() {}, + removeEventListener() {}, + dispatchEvent() { + return false; + }, + }; +} diff --git a/tests/acceptance/statistics-test.js b/tests/acceptance/statistics-test.js index add0d63b..ebbbb9ae 100644 --- a/tests/acceptance/statistics-test.js +++ b/tests/acceptance/statistics-test.js @@ -7,7 +7,7 @@ import { authenticateSession } from 'ember-simple-auth/test-support'; import page from 'prison-rideshare-ui/tests/pages/statistics'; import { getPageTitle } from 'ember-page-title/test-support'; -import moment from 'moment'; +import moment from 'moment-timezone'; const format = 'YYYY-MM-DD'; diff --git a/tests/helpers/application-tests.js b/tests/helpers/application-tests.js index 19f03632..67305c23 100644 --- a/tests/helpers/application-tests.js +++ b/tests/helpers/application-tests.js @@ -6,8 +6,8 @@ function setupApplicationTest(hooks) { setupMirage(hooks); hooks.afterEach(function () { - let toasts = this.owner.lookup('service:paperToaster'); - toasts.get('queue').forEach((toast) => toasts.cancelToast(toast)); + let toasts = this.owner.lookup('service:toasts'); + toasts.dismiss(); }); } diff --git a/tests/helpers/override-route.js b/tests/helpers/override-route.js new file mode 100644 index 00000000..ef7e903a --- /dev/null +++ b/tests/helpers/override-route.js @@ -0,0 +1,42 @@ +export function overrideRoute(server, method, path, handler, timing) { + const verb = method.toLowerCase(); + const pretender = server.pretender; + const interceptor = server.interceptor; + + if (!pretender || !interceptor) { + throw new Error( + 'overrideRoute requires access to the Mirage pretender and interceptor.', + ); + } + + const patternFullPath = interceptor._getFullPath(path); + const samplePath = path.replace(/:[^/]+/g, 'placeholder'); + const sampleFullPath = interceptor._getFullPath(samplePath); + + let originalMatch; + try { + originalMatch = pretender._handlerFor( + method.toUpperCase(), + sampleFullPath, + { + params: {}, + }, + ); + } catch (_error) { + originalMatch = undefined; + } + + server[verb](path, handler, timing); + + return (fallbackHandler, fallbackTiming) => { + if (originalMatch && originalMatch.handler) { + pretender[verb]( + patternFullPath, + originalMatch.handler, + originalMatch.handler.async, + ); + } else if (fallbackHandler) { + server[verb](path, fallbackHandler, fallbackTiming); + } + }; +} diff --git a/tests/helpers/string-to-mobiledoc.js b/tests/helpers/string-to-mobiledoc.js new file mode 100644 index 00000000..04fa7a97 --- /dev/null +++ b/tests/helpers/string-to-mobiledoc.js @@ -0,0 +1,9 @@ +export default function stringToMobiledoc(string) { + return JSON.stringify({ + version: '0.3.2', + atoms: [], + cards: [], + markups: [], + sections: [[1, 'p', [[0, [], 0, string]]]], + }); +} diff --git a/tests/integration/components/linked-contact-test.gjs b/tests/integration/components/linked-contact-test.gjs index 51065737..d5498b63 100644 --- a/tests/integration/components/linked-contact-test.gjs +++ b/tests/integration/components/linked-contact-test.gjs @@ -8,48 +8,72 @@ module('Integration | Component | linked contact', function (hooks) { test('it just renders the string when nothing is detected', async function (assert) { const value = 'hello'; - await render(); + await render( + , + ); assert.equal(find('span').innerHTML.trim(), 'hello'); }); test('it extracts a phone number', async function (assert) { const value = 'hello 212-986-8227 what'; - await render(); + await render( + , + ); assert.equal( - find('span').innerHTML.trim(), + find('[data-test-container]').innerHTML.trim(), `hello 212-986-8227 what`, ); }); test('it extracts a phone number without dashes', async function (assert) { const value = 'hello 2129868227 what'; - await render(); + await render( + , + ); assert.equal( - find('span').innerHTML.trim(), + find('[data-test-container]').innerHTML.trim(), `hello 2129868227 what`, ); }); test('it extracts a phone number with spaces', async function (assert) { const value = 'hello 212 986 8227 what'; - await render(); + await render( + , + ); assert.equal( - find('span').innerHTML.trim(), + find('[data-test-container]').innerHTML.trim(), `hello 212 986 8227 what`, ); }); test('it extracts a phone number with brackets', async function (assert) { const value = 'hello (212) 986 8227 what'; - await render(); + await render( + , + ); assert.equal( - find('span').innerHTML.trim(), + find('[data-test-container]').innerHTML.trim(), `hello (212) 986 8227 what`, ); }); test('it ignores an undefined const value', async function (assert) { - await render(); - assert.equal(find('span').innerText.trim(), ''); + await render( + , + ); + assert.equal(find('[data-test-container]').innerText.trim(), ''); }); }); diff --git a/tests/pages/calendar.js b/tests/pages/calendar.js index 2b656efb..8fa2a1fb 100644 --- a/tests/pages/calendar.js +++ b/tests/pages/calendar.js @@ -3,9 +3,12 @@ import { clickable, collection, create, + fillable, hasClass, + isVisible, + property, text, - triggerable, + value, visitable, } from 'ember-cli-page-object'; import { getter } from 'ember-cli-page-object/macros'; @@ -14,87 +17,111 @@ export default create({ visit: visitable('/calendar/:month'), adminVisit: visitable('/admin-calendar/:month'), - personSession: text('.person-session'), + personSession: text('[data-test-person-session]'), person: { - scope: '.person-card', + scope: '[data-test-person-card]', toggle: { - scope: '.toggle', + scope: '[data-test-person-toggle]', + click: clickable(), }, name: { - scope: '.name', + scope: '[data-test-person-name-field]', field: { - scope: 'input', + scope: '[data-test-person-name-input]', + validationState: attribute('data-validation-state'), + isError: getter(function () { + return this.validationState === 'invalid'; + }), }, - isError: hasClass('md-input-invalid'), error: { - scope: '.paper-input-error', + scope: '[data-test-person-name-error]', + text: text(), }, }, activeSwitch: { - scope: 'md-switch', - enabled: hasClass('md-checked'), - click: triggerable('keypress', '.md-thumb', { - eventProperties: { keyCode: 13 }, - }), + scope: '[data-test-person-active]', + enabled: property('checked'), + click: clickable(), }, email: { - scope: '.email', + scope: '[data-test-person-email-field]', field: { - scope: 'input', - disabledAttribute: attribute('disabled'), - isDisabled: getter(function () { - return this.disabledAttribute === 'disabled'; - }), - }, - desiredMedium: { - scope: 'md-radio-button', - isChecked: hasClass('md-checked'), + scope: '[data-test-person-email-input]', + isDisabled: property('disabled'), }, error: { - scope: '.paper-input-error', + scope: '[data-test-person-email-error]', + text: text(), + }, + desiredMedium: { + resetScope: true, + scope: '[data-test-person-medium-radio="email"]', + isChecked: property('checked'), }, }, mobile: { - scope: '.mobile', + scope: '[data-test-person-mobile-field]', field: { - scope: 'input', + scope: '[data-test-person-mobile-input]', + value: value(), + fillIn: fillable(), + }, + error: { + scope: '[data-test-person-mobile-error]', + text: text(), }, desiredMedium: { - scope: 'md-radio-button', - isChecked: hasClass('md-checked'), + scope: '[data-test-person-medium-radio="mobile"]', + isChecked: property('checked'), + }, + }, + + landline: { + scope: '[data-test-person-landline-field]', + field: { + scope: '[data-test-person-landline-input]', + value: value(), + fillIn: fillable(), }, error: { - scope: '.paper-input-error', + scope: '[data-test-person-landline-error]', + text: text(), }, }, selfNotes: { - scope: '.self-notes', + scope: '[data-test-person-self-notes-field]', field: { - scope: 'textarea', + scope: '[data-test-person-self-notes-field]', + value: value(), + fillIn: fillable(), }, }, address: { - scope: '.address', + scope: '[data-test-person-address-field]', field: { - scope: 'textarea', + scope: '[data-test-person-address-field]', + value: value(), + fillIn: fillable(), }, }, cancelButton: { - scope: 'button.cancel', + scope: '[data-test-person-cancel]', + click: clickable(), }, submitButton: { - scope: 'button.submit', - isHighlighted: hasClass('md-primary'), + scope: '[data-test-person-save]', + click: clickable(), + isHighlighted: hasClass('hds-button--color-primary'), }, }, @@ -118,47 +145,38 @@ export default create({ }, days: collection('.ember-power-calendar-day', { - slots: collection('.slot', { - click: clickable('md-checkbox'), - checkbox: { scope: 'md-checkbox' }, - hours: text('.hours'), + slots: collection('[data-test-calendar-slot]', { + click: clickable('[data-test-slot-checkbox]'), + checkbox: { scope: '[data-test-slot-checkbox]' }, + hours: text('[data-test-slot-hours]'), count: { - scope: '.count', + scope: '[data-test-slot-count]', + isVisible: isVisible(), isCommittedTo: hasClass('committed-to'), }, - isCommittedTo: hasClass('md-checked', 'md-checkbox'), - disabledAttribute: attribute('disabled', 'md-checkbox'), - isDisabled: getter(function () { - return this.disabledAttribute === 'disabled'; - }), - + isCommittedTo: property('checked', '[data-test-slot-checkbox]'), + isDisabled: property('disabled', '[data-test-slot-checkbox]'), isHidden: hasClass('hidden'), }), }), viewingSlot: text('.viewing-slot .hours'), - people: collection('md-chips.commitments md-chip', { - name: text('.name'), - reveal: clickable('.name-container'), + people: collection('[data-test-commitment]', { + name: text('[data-test-commitment-reveal]'), + reveal: clickable('[data-test-commitment-reveal]'), - email: text('.email'), + email: text('[data-test-commitment-email]'), - remove: clickable('.md-chip-remove'), + remove: clickable('[data-test-commitment-remove]'), }), peopleSearch: { - scope: 'md-chips.commitments input', - - options: collection('.ember-power-select-option', { - testContainer: '.ember-power-select-options', - resetScope: true, + scope: '[data-test-commitment-search-input]', - name: text('.name'), - click: clickable('.name'), - }), + options: collection('[data-test-commitment-option]', { resetScope: true }), }, error: text('.error'), diff --git a/tests/pages/debts.js b/tests/pages/debts.js index 4c22df48..2942e8ae 100644 --- a/tests/pages/debts.js +++ b/tests/pages/debts.js @@ -1,8 +1,8 @@ import { + attribute, clickable, collection, create, - isVisible, text, visitable, } from 'ember-cli-page-object'; @@ -10,25 +10,13 @@ import { export default create({ visit: visitable('/debts'), - people: collection('tbody', { - name: text('.name'), - foodExpenses: text('.person .food-expenses'), - carExpenses: text('.person .car-expenses'), - totalExpenses: text('.person .total-expenses'), + people: collection('[data-test-debt-person-row]', { + id: attribute(['data-test-person-id']), + name: text('[data-test-debt-person-name]'), + foodExpenses: text('[data-test-debt-person-food]'), + carExpenses: text('[data-test-debt-person-car]'), + totalExpenses: text('[data-test-debt-person-total]'), - reimburse: clickable('.person .reimburse'), - - rides: collection('tr.ride', { - date: text('.date'), - foodExpenses: text('.food-expenses'), - carExpenses: text('.car-expenses'), - carExpenseIsDonation: isVisible('md-icon[md-font-icon="card giftcard"]'), - }), - - // TODO this should be nested but table rows 😢 - reimbursements: collection('tr.reimbursement', { - foodExpenses: text('.food-expenses'), - carExpenses: text('.car-expenses'), - }), + reimburse: clickable('[data-test-debt-reimburse]'), }), }); diff --git a/tests/pages/forgot.js b/tests/pages/forgot.js index db41781a..5fd9bb58 100644 --- a/tests/pages/forgot.js +++ b/tests/pages/forgot.js @@ -7,13 +7,11 @@ import { } from 'ember-cli-page-object'; export default create({ - testContainer: 'md-dialog', - visit: visitable('/forgot'), - fillEmail: fillable('.email input'), + fillEmail: fillable('[data-test-forgot-email]'), - error: text('.error'), + error: text('[data-test-forgot-error]'), - submit: clickable('button'), + submit: clickable('[data-test-forgot-submit]'), }); diff --git a/tests/pages/gas-prices.js b/tests/pages/gas-prices.js index 3d393b29..e991e958 100644 --- a/tests/pages/gas-prices.js +++ b/tests/pages/gas-prices.js @@ -3,10 +3,10 @@ import { collection, create, text, visitable } from 'ember-cli-page-object'; export default create({ visit: visitable('/gas-prices'), - gasPrices: collection('.gas-prices tbody tr', { - date: text('.date'), - price: text('.price'), - farRate: text('.far'), - closeRate: text('.close'), + gasPrices: collection('[data-test-gas-prices-row]', { + date: text('[data-test-gas-prices-date]'), + price: text('[data-test-gas-prices-price]'), + farRate: text('[data-test-gas-prices-far-rate]'), + closeRate: text('[data-test-gas-prices-close-rate]'), }), }); diff --git a/tests/pages/institutions.js b/tests/pages/institutions.js index 8a944f03..aea90e28 100644 --- a/tests/pages/institutions.js +++ b/tests/pages/institutions.js @@ -2,7 +2,6 @@ import { clickable, collection, create, - hasClass, isVisible, text, visitable, @@ -10,28 +9,27 @@ import { export default create({ visit: visitable('/institutions'), - newInstitution: clickable('button.new'), + newInstitution: clickable('[data-test-new-institution]'), - institutions: collection('tbody tr.institution', { - name: text('.name'), - isFar: isVisible('.far md-icon'), + institutions: collection('[data-test-institution-row]', { + name: text('[data-test-institution-name]'), + isFar: isVisible('[data-test-institution-far]'), - edit: clickable('button.edit'), + edit: clickable('[data-test-institution-edit]'), }), form: { - testContainer: 'md-dialog', + scope: '[data-test-institution-modal]', nameField: { - scope: '.name input', + scope: '[data-test-institution-name-field]', }, farField: { - scope: 'md-checkbox', - isChecked: hasClass('md-checked'), + scope: '[data-test-institution-far-checkbox]', }, - submit: clickable('button.submit'), - cancel: clickable('button.cancel'), + submit: clickable('[data-test-institution-submit]'), + cancel: clickable('[data-test-institution-cancel]'), }, }); diff --git a/tests/pages/log.js b/tests/pages/log.js index 07ca4373..92acc133 100644 --- a/tests/pages/log.js +++ b/tests/pages/log.js @@ -8,52 +8,52 @@ import { export default create({ visit: visitable('/log'), - newPost: clickable('button.new'), + newPost: clickable('[data-test-log-new-post]'), markAllReadButton: { - scope: 'button.markAllRead', + scope: '[data-test-log-mark-all-read]', }, - posts: collection('.posts tbody tr', { - date: text('.date'), - poster: text('.poster'), - content: text('.content'), + posts: collection('[data-test-log-post-row]', { + date: text('[data-test-log-post-date]'), + poster: text('[data-test-log-post-poster]'), + content: text('[data-test-log-post-content]'), editButton: { - scope: 'button.edit', + scope: '[data-test-log-post-edit]', }, deleteButton: { - scope: 'button.delete', + scope: '[data-test-log-post-delete]', }, deleteConfirm: { - scope: 'button.delete-confirm', + scope: '[data-test-log-post-delete-confirm]', }, markReadButton: { - scope: 'button.markRead', + scope: '[data-test-log-post-mark-read]', }, markUnreadButton: { - scope: 'button.markUnread', + scope: '[data-test-log-post-mark-unread]', }, }), form: { - testContainer: 'md-dialog', + scope: '[data-test-log-modal]', content: { - scope: '.content', + scope: '[data-test-log-form]', field: { scope: '.mobiledoc-editor__editor', }, error: { - scope: '.paper-input-error', + scope: '[data-test-log-form-error]', }, }, - submit: clickable('button.submit'), - cancel: clickable('button.cancel'), + submit: clickable('[data-test-log-form-save]'), + cancel: clickable('[data-test-log-form-cancel]'), }, }); diff --git a/tests/pages/login.js b/tests/pages/login.js index ec7f534f..a879790f 100644 --- a/tests/pages/login.js +++ b/tests/pages/login.js @@ -7,14 +7,12 @@ import { } from 'ember-cli-page-object'; export default create({ - testContainer: 'md-dialog', - visit: visitable('/login'), - fillEmail: fillable('.email input'), - fillPassword: fillable('.password input'), + fillEmail: fillable('[data-test-login-email]'), + fillPassword: fillable('[data-test-login-password]'), - error: text('.error'), + error: text('[data-test-login-error]'), - submit: clickable('button'), + submit: clickable('[data-test-login-submit]'), }); diff --git a/tests/pages/people.js b/tests/pages/people.js index ed0a8815..73f8fd31 100644 --- a/tests/pages/people.js +++ b/tests/pages/people.js @@ -5,124 +5,160 @@ import { create, fillable, hasClass, + isVisible, text, - triggerable, visitable, } from 'ember-cli-page-object'; +import { findOne } from 'ember-cli-page-object/extend'; +import { getter } from 'ember-cli-page-object/macros'; + +function resolveElement(context, selector, pageObjectKey) { + const scopedSelector = selector ?? ''; + return findOne(context, scopedSelector, { pageObjectKey }); +} + +function isChecked(selector) { + return getter(function (pageObjectKey) { + const element = resolveElement(this, selector, pageObjectKey); + return element.checked ?? element.getAttribute('checked') !== null; + }); +} export default create({ visit: visitable('/drivers'), - newPerson: clickable('button.new'), + newPerson: clickable('[data-test-new-driver]'), head: { inactiveSwitch: { - scope: '.paper-switch.inactive', - enabled: hasClass('md-checked'), - click: triggerable('keypress', '.md-container', { - eventProperties: { keyCode: 13 }, - }), + scope: '[data-test-drivers-inactive-toggle]', + enabled: isChecked(), + click: clickable(), + }, + nameSort: { + scope: '[data-test-drivers-head-name]', + click: clickable('button'), + }, + lastRideSort: { + scope: '[data-test-drivers-head-last-ride]', + click: clickable('button'), }, }, - people: collection('tbody tr.person', { + people: collection('[data-test-driver-row]', { activeSwitch: { - scope: '.paper-switch', - enabled: hasClass('md-checked'), - click: triggerable('keypress', '.md-container', { - eventProperties: { keyCode: 13 }, - }), + scope: '[data-test-driver-active-toggle]', + enabled: isChecked(), + click: clickable(), }, - name: text('.name'), + name: text('[data-test-driver-name]'), email: { - scope: '.email', - href: attribute('href', 'a'), + scope: '[data-test-driver-email]', + text: text(), + href: attribute('href', '[data-test-driver-email-link]'), isPreferred: hasClass('is-preferred'), }, landline: { - scope: '.landline', - href: attribute('href', 'a'), + scope: '[data-test-driver-landline]', + text: text(), + href: attribute('href', '[data-test-driver-landline-link]'), isPreferred: hasClass('is-preferred'), }, mobile: { - scope: '.mobile', - href: attribute('href', 'a'), + scope: '[data-test-driver-mobile]', + text: text(), + href: attribute('href', '[data-test-driver-mobile-link]'), isPreferred: hasClass('is-preferred'), }, lastRide: { - scope: '.last-ride', + scope: '[data-test-driver-last-ride]', + text: text(), }, notes: { - scope: '.notes', + scope: '[data-test-driver-notes]', + text: text(), }, copyButton: { - scope: '.copy-btn', - clipboardText: attribute('data-clipboard-text'), + scope: '[data-test-driver-copy-button]', + clipboardText: attribute('data-test-clipboard-text'), + isVisible: isVisible(), }, - edit: clickable('button.edit'), + edit: clickable('[data-test-driver-edit]'), }), form: { - testContainer: 'md-dialog', + scope: '[data-test-driver-modal]', nameField: { - scope: '.name input', - fill: fillable(), + fill: fillable('[data-test-driver-form-name-input]'), + value: attribute('value', '[data-test-driver-form-name-input]'), }, nameError: { - scope: '.name .paper-input-error', + scope: '[data-test-driver-form-name-error]', + text: text(), }, - // TODO rule of three? email: { - scope: '.email', + fill: fillable('[data-test-driver-form-email-input]'), field: { - scope: 'input', + scope: '[data-test-driver-form-email-input]', }, desiredMedium: { - scope: 'md-radio-button', + scope: '[data-test-driver-form-medium-email]', }, error: { - scope: '.paper-input-error', + scope: '[data-test-driver-form-email-error]', + text: text(), }, }, mobile: { - scope: '.mobile', + fill: fillable('[data-test-driver-form-mobile-input]'), field: { - scope: 'input', + scope: '[data-test-driver-form-mobile-input]', }, desiredMedium: { - scope: 'md-radio-button', + scope: '[data-test-driver-form-medium-mobile]', }, - error: { - scope: '.paper-input-error', + }, + + landline: { + fill: fillable('[data-test-driver-form-landline-input]'), + field: { + scope: '[data-test-driver-form-landline-input]', + }, + desiredMedium: { + scope: '[data-test-driver-form-medium-landline]', }, }, address: { - scope: '.address', + fill: fillable('[data-test-driver-form-address-input]'), field: { - scope: 'textarea', + scope: '[data-test-driver-form-address-input]', }, }, notes: { - scope: '.notes', + fill: fillable('[data-test-driver-form-notes-input]'), field: { - scope: 'textarea', + scope: '[data-test-driver-form-notes-input]', }, }, - submit: clickable('button.submit'), - cancel: clickable('button.cancel'), + submit: clickable('[data-test-driver-form-submit]'), + cancel: clickable('[data-test-driver-form-cancel]'), }, + + peopleNames: getter(function () { + return this.people.toArray().map((person) => person.name); + }), }); diff --git a/tests/pages/register.js b/tests/pages/register.js index 0588bd76..50ea87d1 100644 --- a/tests/pages/register.js +++ b/tests/pages/register.js @@ -7,15 +7,17 @@ import { } from 'ember-cli-page-object'; export default create({ - testContainer: 'md-dialog', + testContainer: '[data-test-register-modal]', visit: visitable('/register'), - fillEmail: fillable('.email input'), - fillPassword: fillable('.password input'), - fillPasswordConfirmation: fillable('.password-confirmation input'), + fillEmail: fillable('[data-test-register-email]'), + fillPassword: fillable('[data-test-register-password]'), + fillPasswordConfirmation: fillable( + '[data-test-register-password-confirmation]', + ), - error: text('.error'), + error: text('[data-test-register-error]'), - submit: clickable('button'), + submit: clickable('[data-test-register-submit]'), }); diff --git a/tests/pages/reimbursements.js b/tests/pages/reimbursements.js index fbcafb65..3970feb8 100644 --- a/tests/pages/reimbursements.js +++ b/tests/pages/reimbursements.js @@ -3,83 +3,85 @@ import { clickable, collection, create, - fillable, - hasClass, isVisible, + property, text, - triggerable, - value, visitable, } from 'ember-cli-page-object'; export default create({ visit: visitable('/reimbursements'), - rows: collection('tbody tr', { - month: text('.month'), + rows: collection('[data-test-reimbursement-row]', { + rowType: attribute('data-row-type'), + month: text('[data-test-reimbursement-month]'), - name: text('.name'), - foodExpenses: text('.food-expenses'), - carExpenses: text('.car-expenses'), - carExpenseIsDonation: isVisible('md-icon[md-font-icon="card giftcard"]'), - totalExpenses: text('.total-expenses'), + name: text('[data-test-reimbursement-name]'), + foodExpenses: text('[data-test-reimbursement-food]'), + carExpenses: text('[data-test-reimbursement-car-value]'), + carExpenseIsDonation: isVisible('[data-test-reimbursement-donation]'), + totalExpenses: text('[data-test-reimbursement-total]'), processButton: { - scope: '.process', - isPrimary: hasClass('md-primary'), + scope: '[data-test-reimbursement-process]', + variant: attribute('data-variant'), }, donateButton: { - scope: '.donate', - isPrimary: hasClass('md-primary'), + scope: '[data-test-reimbursement-donate]', + variant: attribute('data-variant'), }, copyButton: { - scope: '.copy-btn', - clipboardText: attribute('data-clipboard-text'), + scope: '[data-test-reimbursement-copy]', + clipboardText: attribute('data-test-clipboard-text'), }, }), - reimbursements: collection('tbody tr.reimbursement', { - date: text('.date'), - name: text('.name'), - ride: text('.ride'), + reimbursements: collection('[data-test-processed-row]', { + date: text('[data-test-processed-date]'), + name: text('[data-test-processed-name]'), + ride: text('[data-test-processed-ride]'), - expenses: text('.expenses span'), - isFoodExpense: isVisible('.paper-icon[md-font-icon="local cafe"]'), - isCarExpense: isVisible('.paper-icon[md-font-icon="local gas station"]'), + expenses: text('[data-test-processed-expense-value]'), + expenseIcon: attribute( + 'data-test-processed-expense-icon', + '[data-test-processed-expense-icon]', + ), - isDonation: isVisible('.donation .paper-icon'), + isFoodExpense() { + return this.expenseIcon === 'food'; + }, + + isCarExpense() { + return this.expenseIcon === 'car'; + }, - edit: clickable('button'), + isDonation: isVisible('[data-test-processed-donation-icon]'), }), processedSwitch: { - scope: '.paper-switch.processed', - enabled: hasClass('md-checked'), - click: triggerable('keypress', '.md-container', { - eventProperties: { keyCode: 13 }, - }), + scope: '[data-test-reimbursements-processed-toggle]', + enabled: property('checked'), }, form: { + scope: '[data-test-reimbursement-modal]', + amountField: { - scope: '.amount input', - value: value(), - fill: fillable(), + scope: '[data-test-reimbursement-amount]', }, donationCheckbox: { - scope: 'md-checkbox', - checked: hasClass('md-checked'), - click: clickable(), + scope: '[data-test-reimbursement-donation]', + checked: property('checked', 'input'), }, - submit: clickable('button.submit'), - cancel: clickable('button.cancel'), + submit: clickable('[data-test-reimbursement-save]'), + cancel: clickable('[data-test-reimbursement-cancel]'), }, noReimbursementsMessage: { - scope: '.no-reimbursements', + scope: '[data-test-no-reimbursements]', }, }); diff --git a/tests/pages/report.js b/tests/pages/report.js index 0497c3f7..6c0c673f 100644 --- a/tests/pages/report.js +++ b/tests/pages/report.js @@ -1,44 +1,28 @@ -import { - clickable, - collection, - create, - text, - visitable, -} from 'ember-cli-page-object'; -import { findOne } from 'ember-cli-page-object/extend'; -import { getter } from 'ember-cli-page-object/macros'; - -function isDisabled(selector) { - return getter(function (pageObjectKey) { - return ( - findOne(this, selector, { pageObjectKey }).getAttribute('disabled') === - 'disabled' - ); - }); -} +import { collection, create, property, visitable } from 'ember-cli-page-object'; export default create({ visit: visitable('/reports/new'), - rides: collection('md-radio-button', { - label: text('.md-label > span'), - choose: clickable(), - }), + rides: collection('[data-test-report-ride-option]'), - noRides: { scope: '.no-rides' }, - noSession: { scope: '.no-session' }, + noRides: { scope: '[data-test-report-no-rides]' }, + noSession: { scope: '[data-test-report-no-session]' }, + + distance: { scope: '[data-test-report-distance]' }, + + carExpenses: { scope: '[data-test-report-car-expenses]' }, + + foodExpenses: { scope: '[data-test-report-food-expenses]' }, + + notes: { scope: '[data-test-report-notes]' }, - distance: { scope: '.distance input' }, - carExpenses: { scope: '.car-expenses input' }, - foodExpenses: { scope: '.food-expenses input' }, - notes: { scope: '.report-notes textarea' }, donation: { - scope: 'md-checkbox', + scope: '[data-test-report-donation]', + isChecked: property('checked', 'input'), }, submitButton: { - scope: 'button.submit', - disabled: isDisabled(), - click: clickable(), + scope: '[data-test-report-submit]', + disabled: property('disabled'), }, }); diff --git a/tests/pages/reset.js b/tests/pages/reset.js index 16e708b2..d6111755 100644 --- a/tests/pages/reset.js +++ b/tests/pages/reset.js @@ -7,14 +7,12 @@ import { } from 'ember-cli-page-object'; export default create({ - testContainer: 'md-dialog', - visit: visitable('/reset/:token'), - fillPassword: fillable('.password input'), - fillPasswordConfirmation: fillable('.password-confirmation input'), + fillPassword: fillable('[data-test-reset-password]'), + fillPasswordConfirmation: fillable('[data-test-reset-password-confirmation]'), - error: text('.error'), + error: text('[data-test-reset-error]'), - submit: clickable('button'), + submit: clickable('[data-test-reset-submit]'), }); diff --git a/tests/pages/rides.js b/tests/pages/rides.js index 6f356984..1fe483c8 100644 --- a/tests/pages/rides.js +++ b/tests/pages/rides.js @@ -3,125 +3,117 @@ import { clickable, collection, create, - fillable, hasClass, isVisible, + property, text, triggerable, value, visitable, } from 'ember-cli-page-object'; - -import reasonToIcon from 'prison-rideshare-ui/utils/reason-to-icon'; +import { getter } from 'ember-cli-page-object/macros'; +import { selectChoose } from 'ember-power-select/test-support'; export default create({ visit: visitable('/rides'), - newRide: clickable('button.new'), + newRide: clickable('[data-test-new-ride]'), head: { cancelledSwitch: { - scope: '.paper-switch.cancelled', - enabled: hasClass('md-checked'), - click: triggerable('keypress', '.md-container', { - eventProperties: { keyCode: 13 }, - }), + scope: '[data-test-show-cancelled]', + enabled: property('checked'), }, completedSwitch: { - scope: '.paper-switch.completed', - enabled: hasClass('md-checked'), - click: triggerable('keypress', '.md-container', { - eventProperties: { keyCode: 13 }, - }), + scope: '[data-test-show-completed]', + enabled: property('checked'), }, search: { - scope: 'md-input-container.search', - - fillIn: fillable('input'), - value: value('input'), - - clear: { - scope: 'button', - click: triggerable('click'), - }, + scope: '[data-test-ride-search]', }, }, ridesHead: { - scope: 'thead', - clickDate: clickable('.date'), + scope: '[data-test-rides-head-date]', + clickDate: clickable('button'), }, rides: collection('tbody tr.ride', { enabled: hasClass('enabled'), isUncombinable: hasClass('uncombinable'), isHighlighted: hasClass('highlighted'), - isDivider: hasClass('divider'), cancellation: { - scope: '.cancellation', - click: clickable('button'), - showsLockdown: isVisible( - `button md-icon[md-font-icon='${reasonToIcon['lockdown']}']`, - ), - showsVisitor: isVisible( - `button md-icon[md-font-icon='${reasonToIcon['visitor']}']`, - ), - showsDriverNotFound: isVisible( - `button md-icon[md-font-icon='${reasonToIcon['driver not found']}']`, - ), - showsNotCancelled: isVisible( - 'button md-icon[md-font-icon="highlight off"]', - ), - showsOther: isVisible('button md-icon[md-font-icon="help"]'), - - title: attribute('title', 'button'), + scope: '[data-test-cancellation-button]', + state: attribute('data-cancellation-state'), + title: attribute('title'), + showsLockdown: getter(function () { + return this.state === 'lockdown'; + }), + showsVisitor: getter(function () { + return this.state === 'visitor'; + }), + showsDriverNotFound: getter(function () { + return this.state === 'driver not found'; + }), + showsNotCancelled: getter(function () { + return this.state === 'not-cancelled'; + }), + showsOther: getter(function () { + return this.state === 'other!'; + }), }, - name: text('.name-and-contact .name'), - isFirstTimer: isVisible( - '.name-and-contact md-icon[md-font-icon=announcement]', - ), - date: text('.date'), - clickDate: clickable('.date-cell'), - institution: text('.institution'), - address: text('.address'), - contact: text('.contact'), - contactPhoneHref: attribute('href', '.contact a'), - passengers: text('.passengers'), + name: text('[data-test-ride-name]'), + isFirstTimer: isVisible('[data-test-ride-first-time]'), + date: text('[data-test-ride-date]'), + clickDate: clickable('[data-test-ride-date-cell]'), + institution: text('[data-test-ride-institution]'), + address: text('[data-test-ride-address]'), + contact: text('[data-test-ride-contact]'), + contactPhoneHref: attribute('href', '[data-test-ride-contact] a'), medium: { - scope: '.medium-and-contact', - isTxt: isVisible('md-icon[md-font-icon=textsms]'), - isEmail: isVisible('md-icon[md-font-icon=email]'), - isPhone: isVisible('md-icon[md-font-icon=phone]'), + scope: '[data-test-ride-medium]', + medium: attribute('data-medium'), + isTxt: getter(function () { + return this.medium === 'txt'; + }), + isEmail: getter(function () { + return this.medium === 'email'; + }), + isPhone: getter(function () { + return this.medium === 'phone'; + }), }, driver: { - scope: '.driver', - text: text('.name'), + scope: '[data-test-driver]', + text: text('[data-test-person-badge-name]'), click: clickable(), - reveal: clickable('.name-container'), - clear: clickable('.remove-container button'), - - email: text('.email'), - landline: text('.landline'), + reveal: clickable('[data-test-person-badge-toggle]'), + clear: clickable('[data-test-person-badge-clear]'), + async choose(option) { + await selectChoose('[data-test-ride-person-select="driver"]', option); + }, - selfNotes: text('.self-notes'), + email: text('[data-test-person-badge-email]'), + landline: text('[data-test-person-badge-landline]'), + selfNotes: text('[data-test-person-badge-self-notes]'), }, carOwner: { - scope: '.car-owner', - text: text('.name'), + scope: '[data-test-car-owner]', + text: text('[data-test-person-badge-name]'), click: clickable(), - clear: clickable('.remove-container button'), + clear: clickable('[data-test-person-badge-clear]'), select: { - scope: 'md-select', + click: clickable(), type: triggerable('keydown'), - enter: triggerable('keydown', '.md-power-select-options', { + enter: triggerable('keydown', '.ember-basic-dropdown-trigger', { testContainer: 'html', resetScope: true, eventProperties: { keyCode: 13 }, @@ -129,116 +121,132 @@ export default create({ }, }, - isOverridable: isVisible('md-icon[md-font-icon=directions_bus]'), + isOverridable: isVisible('[data-test-overridable-indicator]'), combineButton: { - scope: 'button.combine', - isActive: hasClass('md-raised'), + scope: '[data-test-combine-button]', + + activeAttribute: attribute('data-active'), + isActive: getter(function () { + return this.activeAttribute === 'true'; + }), + title: attribute('title'), }, - isCombined: isVisible( - '.driver-and-car-owner md-icon[md-font-icon="call split"]', - ), + isCombined: hasClass('combined'), - edit: clickable('button.edit'), + edit: clickable('[data-test-edit-ride]'), creationDate: { - scope: '.creation', + scope: '[data-test-ride-creation]', }, }), noMatchesRow: { - scope: 'tr.no-matches', + scope: '[data-test-no-matches]', }, - notes: collection('tr.notes', { - text: text('td.notes'), + notes: collection('[data-test-notes-row]', { + text: text('[data-test-notes]'), + isHighlighted: hasClass('highlighted'), }), reports: collection('tr.report', { - distance: text('.distance'), - carExpenses: text('.car-expenses'), - rate: text('.rate'), - foodExpenses: text('.food-expenses'), - notes: text('.notes'), - - clear: clickable('button'), - clearConfirm: { scope: '.clear-confirm' }, - clearCancel: { scope: '.clear-cancel' }, + distance: text('[data-test-report-distance]'), + carExpenses: text('[data-test-report-car-expenses]'), + rate: text('[data-test-report-rate]'), + foodExpenses: text('[data-test-report-food]'), + notes: text('[data-test-report-notes]'), + + clear: clickable('[data-test-report-clear]'), + clearConfirm: { scope: '[data-test-report-clear-confirm]' }, + clearCancel: { scope: '[data-test-report-clear-cancel]' }, }), overlaps: collection('tr.overlap', { - text: text('.text'), - assign: clickable('.assign'), - ignore: clickable('.ignore'), + text: text('[data-test-overlap-text]'), + assign: clickable('[data-test-overlap-assign]'), + ignore: clickable('[data-test-overlap-ignore]'), }), - confirmationNotifications: collection('tr.confirmation-notification', { - text: text('.text'), - medium: { - scope: '.medium', - isTxt: isVisible('md-icon[md-font-icon=textsms]'), - isEmail: isVisible('md-icon[md-font-icon=email]'), - isPhone: isVisible('md-icon[md-font-icon=phone]'), - }, - markConfirmed: clickable('.mark-confirmed'), + confirmationNotifications: collection('[data-test-confirmation-row]', { + text: text('[data-test-confirmation-text]'), + markConfirmed: clickable('[data-test-confirmation-mark]'), }), form: { - testContainer: 'md-dialog', + testContainer: '[data-test-ride-form]', - notice: text('.editing-warning'), + notice: text('[data-test-editing-warning]'), timespan: { - scope: '.timespan textarea', + scope: '[data-test-timespan]', }, + timespanErrors: collection('[data-test-timespan-error]'), + timespanResult: { - scope: '.timespan-result', + scope: '[data-test-timespan-result]', value: value('input'), - hasWarning: isVisible('.timespan-warning'), + hasWarning: isVisible('[data-test-timespan-warning]'), }, timespanOverrideButton: { scope: '[data-test-timespan-override-button]', + isDisabled: property('disabled'), }, timespanStart: { - scope: '[data-test-timespan-start] input', + scope: '[data-test-timespan-start]', }, timespanEnd: { - scope: '[data-test-timespan-end] input', + scope: '[data-test-timespan-end]', }, timespanEndError: { - scope: '[data-test-timespan-end] .paper-input-error', + scope: '[data-test-timespan-end-error]', }, medium: { - scope: '.medium-row', - txt: { scope: '.txt' }, - email: { scope: '.email' }, - phone: { scope: '.phone' }, + txt: { + scope: '[data-test-medium-txt]', + }, + email: { + scope: '[data-test-medium-email]', + }, + phone: { + scope: '[data-test-medium-phone]', + }, }, requestConfirmed: { - scope: 'md-checkbox.request-confirmed', - checked: hasClass('md-checked'), - click: clickable(), + scope: '[data-test-request-confirmed]', + checked: property('checked'), }, overridable: { - scope: 'md-checkbox.overridable', - checked: hasClass('md-checked'), - click: clickable(), + scope: '[data-test-overridable]', + checked: property('checked'), }, name: { - scope: 'md-autocomplete', + scope: '[data-test-visitor-select]', + + value: getter(function () { + return this.text; + }), + + searchInput: { + scope: '.ember-power-select-search-input', + resetScope: true, + }, - fillIn: fillable('input'), + async fillIn(value) { + await this.click(); + await this.searchInput.fillIn(value); + }, suggestions: collection('.ember-power-select-option', { testContainer: '.ember-power-select-options', @@ -251,64 +259,70 @@ export default create({ }, nameError: { - scope: 'md-autocomplete .paper-input-error', + scope: '[data-test-name-error]', }, + institution: { + scope: '[data-test-institution-select]', + async choose(option) { + await selectChoose('[data-test-institution-select]', option); + }, + }, institutionError: { - scope: '.institution .paper-input-error', + scope: '[data-test-institution-error]', }, address: { - scope: '.address input', + scope: '[data-test-address]', }, contact: { - scope: '.contact input', + scope: '[data-test-contact]', }, firstTime: { - scope: 'md-checkbox.first-time', - checked: hasClass('md-checked'), - click: clickable(), + scope: '[data-test-first-time]', + checked: property('checked'), }, firstTimePoints: { - scope: '.first-time-points', + scope: '[data-test-first-time-points]', }, passengers: { - scope: '.passengers input', + scope: '[data-test-passengers]', }, notes: { - scope: '.request-notes textarea', + scope: '[data-test-request-notes]', }, - submit: clickable('button.submit'), - cancel: clickable('button.cancel'), + submit: clickable('[data-test-ride-form-submit]'), + cancel: clickable('[data-test-ride-form-cancel]'), }, cancellationForm: { - testContainer: 'md-dialog', + testContainer: '[data-test-cancellation-form]', - notice: text('md-card-content'), + notice: text('[data-test-cancellation-notice]'), - shortcutButtons: collection('button.shortcut'), + shortcutButtons: collection('[data-test-cancellation-shortcut]'), cancelled: { - scope: 'md-checkbox', - checked: hasClass('md-checked'), + scope: '[data-test-cancellation-cancelled]', + checked: property('checked'), click: clickable(), }, reason: { - value: text('.ember-power-select-selected-item'), + scope: '[data-test-cancellation-reason-select]', }, other: { - scope: '.md-input', + scope: '[data-test-cancellation-other]', }, - save: clickable('button.submit'), + save: clickable('[data-test-cancellation-form-save]'), + cancel: clickable('[data-test-cancellation-form-cancel]'), }, }); diff --git a/tests/pages/shared.js b/tests/pages/shared.js index 56f6a5b7..fd77609e 100644 --- a/tests/pages/shared.js +++ b/tests/pages/shared.js @@ -1,25 +1,46 @@ -import { clickable, create } from 'ember-cli-page-object'; +import { attribute, clickable, create, text } from 'ember-cli-page-object'; export default create({ session: { - scope: '.site-nav-container .session', - click: clickable('button'), + scope: '[data-test-session]', + click: clickable('[data-test-session-button]'), + text: text('[data-test-session-button]'), }, toast: { - scope: '.md-toast-content span', - testContainer: 'md-toast', + scope: '[data-test-toast]', + text: text('[data-test-toast-text]'), }, + inlineAlert: { + scope: '[data-test-inline-alert]', + text: text('[data-test-inline-alert-text]'), + }, + + sidebarToggle: { + scope: '[data-test-sidebar-toggle]', + }, + + sidebarToggleBadge: { + scope: '[data-test-sidebar-toggle-badge]', + }, + + sidebar: { + scope: '[data-test-app-sidenav]', + }, + + sidebarState: attribute('data-state', '[data-test-app-sidenav]'), + sidebarNavReportLink: { scope: '[data-test-nav-report]' }, + userCount: { - scope: '.users .count', + scope: '[data-test-nav-users-count]', }, logCount: { - scope: '.log .count', + scope: '[data-test-nav-log-count]', }, ridesBadge: { - scope: '.rides .count', + scope: '[data-test-nav-rides-count]', }, }); diff --git a/tests/pages/statistics.js b/tests/pages/statistics.js index fb794b03..07c75ec8 100644 --- a/tests/pages/statistics.js +++ b/tests/pages/statistics.js @@ -3,12 +3,13 @@ import { collection, create, visitable } from 'ember-cli-page-object'; export default create({ visit: visitable('/statistics'), - start: { scope: '.start input' }, - end: { scope: '.end input' }, + start: { scope: '[data-test-statistics-start]' }, - pastYear: { scope: '.past-year' }, - pastTwoWeeks: { scope: '.past-two-weeks' }, - thisYear: { scope: '.this-year' }, + end: { scope: '[data-test-statistics-end]' }, + + pastYear: { scope: '[data-test-statistics-past-year]' }, + pastTwoWeeks: { scope: '[data-test-statistics-past-two-weeks]' }, + thisYear: { scope: '[data-test-statistics-this-year]' }, times: { scope: 'table', diff --git a/tests/pages/users.js b/tests/pages/users.js index faa6d3c6..3b40d662 100644 --- a/tests/pages/users.js +++ b/tests/pages/users.js @@ -2,7 +2,6 @@ import { clickable, collection, create, - hasClass, isVisible, text, visitable, @@ -10,27 +9,37 @@ import { import { findOne } from 'ember-cli-page-object/extend'; import { getter } from 'ember-cli-page-object/macros'; +function resolveElement(context, selector, pageObjectKey) { + const scopedSelector = selector ?? ''; + return findOne(context, scopedSelector, { pageObjectKey }); +} + function isDisabled(selector) { return getter(function (pageObjectKey) { - return ( - findOne(this, selector, { pageObjectKey }).getAttribute('disabled') === - 'disabled' - ); + const element = resolveElement(this, selector, pageObjectKey); + return element.disabled ?? element.getAttribute('disabled') !== null; + }); +} + +function isChecked(selector) { + return getter(function (pageObjectKey) { + const element = resolveElement(this, selector, pageObjectKey); + return element.checked ?? element.getAttribute('checked') !== null; }); } export default create({ visit: visitable('/users'), - users: collection('tbody tr.user', { - email: text('.email'), - lastSeenAt: text('.last-seen'), - isPresent: isVisible('.present md-icon'), - presenceCount: text('.present .count'), + users: collection('[data-test-user-row]', { + email: text('[data-test-user-email]'), + lastSeenAt: text('[data-test-user-last-seen]'), + isPresent: isVisible('[data-test-user-present-icon]'), + presenceCount: text('[data-test-user-presence-count]'), adminCheckbox: { - scope: 'md-checkbox', - checked: hasClass('md-checked'), + scope: '[data-test-user-admin-toggle]', + checked: isChecked(), click: clickable(), isDisabled: isDisabled(), }, diff --git a/tests/unit/utils/format-timespan-test.js b/tests/unit/utils/format-timespan-test.js index 1e66f99c..e1f08df6 100644 --- a/tests/unit/utils/format-timespan-test.js +++ b/tests/unit/utils/format-timespan-test.js @@ -1,6 +1,6 @@ import formatTimespan from 'prison-rideshare-ui/utils/format-timespan'; import { module, test } from 'qunit'; -import moment from 'moment'; +import moment from 'moment-timezone'; import momentAddLocaleShortMeridiemFormat from 'prison-rideshare-ui/utils/moment-add-locale-short-meridiem-format'; module('Unit | Utility | format timespan', function (hooks) { diff --git a/tests/unit/utils/parse-timespan-test.js b/tests/unit/utils/parse-timespan-test.js index 64dbcbf5..7bcfc17e 100644 --- a/tests/unit/utils/parse-timespan-test.js +++ b/tests/unit/utils/parse-timespan-test.js @@ -2,7 +2,7 @@ import parseTimespan from 'prison-rideshare-ui/utils/parse-timespan'; import { module, test } from 'qunit'; -import moment from 'moment'; +import moment from 'moment-timezone'; module('Unit | Utility | parse timespan', function (hooks) { hooks.beforeEach(function () {