Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/elements/internal/InternalCalendar/InternalCalendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { LitElement, PropertyDeclarations, TemplateResult, html } from 'lit-elem
import { ThemeableMixin } from '../../../mixins/themeable';
import { classMap } from '../../../utils/class-map';
import { parseDate } from '../../../utils/parse-date';
import { safeDate } from '../../../utils/safe-date';
import { serializeDate } from '../../../utils/serialize-date';

export class InternalCalendar extends ThemeableMixin(LitElement) {
Expand Down Expand Up @@ -97,7 +98,7 @@ export class InternalCalendar extends ThemeableMixin(LitElement) {

private __renderMonth(month: number, year: number) {
const lang = this.lang || navigator.language;
const date = new Date(year, month, 1, 0, 0, 0, 0);
const date = safeDate(year, month, 1);
const items: TemplateResult[] = [];

for (let i = 0; i < 7; ++i) {
Expand Down Expand Up @@ -145,7 +146,7 @@ export class InternalCalendar extends ThemeableMixin(LitElement) {

private __renderDate(date: number, month: number, year: number, checked = false) {
const disabled =
this.disabled || this.readonly || !this.checkAvailability(new Date(year, month, date));
this.disabled || this.readonly || !this.checkAvailability(safeDate(year, month, date));

return html`
<label
Expand All @@ -166,7 +167,7 @@ export class InternalCalendar extends ThemeableMixin(LitElement) {
class="sr-only"
?disabled=${this.readonly || this.disabled || disabled}
@change=${() => {
this.__valueAsDate = new Date(year, month, date);
this.__valueAsDate = safeDate(year, month, date);
this.dispatchEvent(new CustomEvent('change'));
}}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { html } from 'lit-element';
import { getWeekdayShortNames } from './getWeekdayShortNames';
import { getWeekdayLongNames } from './getWeekdayLongNames';
import { getMonthNames } from './getMonthNames';
import { safeDate } from '../../../utils/safe-date';

/**
* Internal control displaying a basic date picker box.
Expand Down Expand Up @@ -102,7 +103,7 @@ export class InternalDateControl extends InternalEditableControl {
parseDate: null,
formatTitle: (m: string, y: string) => m + ' ' + y,
formatDate: (d: { day: number; month: number; year: number }) => {
return this.t('display_value', { value: new Date(d.year, d.month, d.day) });
return this.t('display_value', { value: safeDate(d.year, d.month, d.day) });
},
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,26 @@ describe('InternalEditableListControl', () => {
expect(value).to.deep.equal([{ value: 'foo,bar', unit: '' }]);
});

it('can add items on blur (non-range only)', async () => {
const layout = html`<foxy-internal-editable-list-control></foxy-internal-editable-list-control>`;
const element = await fixture<Control>(layout);

let value: unknown = [];
element.getValue = () => value;
element.setValue = newValue => (value = newValue);
await element.requestUpdate();

const input = element.renderRoot.querySelector('input') as HTMLInputElement;
const whenChangeEmitted = oneEvent(element, 'change');

input.value = 'foo';
input.dispatchEvent(new InputEvent('input'));
input.dispatchEvent(new FocusEvent('blur'));

expect(await whenChangeEmitted).to.be.instanceOf(CustomEvent);
expect(value).to.deep.equal([{ value: 'foo', unit: '' }]);
});

it('can add items on paste (multiline text - split into multiple entries)', async () => {
const element = await fixture<Control>(html`
<foxy-internal-editable-list-control></foxy-internal-editable-list-control>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ export class InternalEditableListControl extends InternalEditableControl {
}}
@blur=${() => {
this.__isErrorVisible = true;
addItem();
}}
/>
`}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export class InternalPostActionControl extends InternalControl {

<vaadin-button
theme=${ifDefined(this.theme ?? void 0)}
class="w-full"
?disabled=${this.disabled || this.readonly}
@click=${(evt: CustomEvent) => {
const button = evt.currentTarget as ButtonElement;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,37 @@ describe('AdminSubscriptionForm', () => {
expect($('foxy-i18n[infer=""][key="cancel_how_to_reactivate_text"]')).to.exist;
});

it('renders text content for the Cancel state for future subscriptions', async () => {
const $ = (selector: string) => form.renderRoot.querySelector(selector);
const router = createRouter();
const form = await fixture<Form>(html`
<foxy-internal-admin-subscription-form-status-action-form
href="https://demo.api/hapi/subscriptions/0"
@fetch=${(evt: FetchEvent) => router.handleEvent(evt)}
>
</foxy-internal-admin-subscription-form-status-action-form>
`);

await waitUntil(() => !!form.data, undefined, { timeout: 5000 });

form.data = {
...form.data!,
is_active: true,
start_date: serializeDate(new Date(Date.now() + 86400000)),
};

await form.requestUpdate();

expect($('foxy-i18n[infer=""][key="cancel_title"]')).to.exist;
expect($('foxy-i18n[infer=""][key="cancel_subtitle_future"]')).to.exist;
expect($('foxy-i18n[infer=""][key="cancel_why_not_today_title"]')).to.exist;
expect($('foxy-i18n[infer=""][key="cancel_why_not_today_text_future"]')).to.exist;
expect($('foxy-i18n[infer=""][key="cancel_whats_next_title"]')).to.exist;
expect($('foxy-i18n[infer=""][key="cancel_whats_next_text_future"]')).to.exist;
expect($('foxy-i18n[infer=""][key="cancel_how_to_reactivate_title"]')).to.exist;
expect($('foxy-i18n[infer=""][key="cancel_how_to_reactivate_text"]')).to.exist;
});

it('renders text content for the Reactivate state', async () => {
const $ = (selector: string) => form.renderRoot.querySelector(selector);
const router = createRouter();
Expand Down Expand Up @@ -170,6 +201,54 @@ describe('AdminSubscriptionForm', () => {
expect(form.form.end_date).to.equal('');
});

it('renders end date presets in the Cancel state for future subscriptions', async () => {
const router = createRouter();
const form = await fixture<Form>(html`
<foxy-internal-admin-subscription-form-status-action-form
href="https://demo.api/hapi/subscriptions/0"
@fetch=${(evt: FetchEvent) => router.handleEvent(evt)}
>
</foxy-internal-admin-subscription-form-status-action-form>
`);

await waitUntil(() => !!form.data, undefined, { timeout: 5000 });

form.data = {
...form.data!,
is_active: true,
start_date: serializeDate(new Date(Date.now() + 86400000)),
};

await form.requestUpdate();

const select = form.renderRoot.querySelector<InternalSelectControl>(
'foxy-internal-select-control[infer="end-date-preset"]'
);

expect(select).to.exist;
expect(select).to.have.attribute(
'options',
JSON.stringify([
{ value: 'start_date', label: 'option_start_date' },
{ value: 'next_transaction_date', label: 'option_next_transaction_date' },
{ value: 'custom_date', label: 'option_custom_date' },
])
);

expect(select?.getValue()).to.equal('next_transaction_date');
form.edit({ end_date_preset: 'start_date' });
expect(select?.getValue()).to.equal('start_date');

select?.setValue('next_transaction_date');
expect(form.form.end_date).to.equal(form.form.next_transaction_date);

select?.setValue('start_date');
expect(form.form.end_date).to.equal(form.data!.start_date);

select?.setValue('custom_date');
expect(form.form.end_date).to.equal('');
});

it('renders end date field in the Cancel state', async () => {
const router = createRouter();
const form = await fixture<Form>(html`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ export class InternalAdminSubscriptionFormStatusActionForm extends InternalForm<
{ value: 'custom_date', label: 'option_custom_date' },
]);

private readonly __endDatePresetOptionsForFutureSubs = JSON.stringify([
{ value: 'start_date', label: 'option_start_date' },
{ value: 'next_transaction_date', label: 'option_next_transaction_date' },
{ value: 'custom_date', label: 'option_custom_date' },
]);

private readonly __endDatePresetGetValue = () =>
this.form.end_date_preset ?? 'next_transaction_date';

Expand All @@ -40,6 +46,8 @@ export class InternalAdminSubscriptionFormStatusActionForm extends InternalForm<
tomorrowDate.setDate(tomorrowDate.getDate() + 1);
tomorrowDate.setHours(0, 0, 0, 0);
this.edit({ end_date: serializeDate(tomorrowDate) });
} else if (newValue === 'start_date') {
this.edit({ end_date: this.data?.start_date ?? '' });
} else if (newValue === 'next_transaction_date') {
this.edit({ end_date: this.form.next_transaction_date });
} else {
Expand Down Expand Up @@ -83,6 +91,12 @@ export class InternalAdminSubscriptionFormStatusActionForm extends InternalForm<
? svg`<svg class="text-error" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" style="width: 2rem; height: 2rem;"><path stroke-linecap="round" stroke-linejoin="round" d="M5.25 7.5A2.25 2.25 0 0 1 7.5 5.25h9a2.25 2.25 0 0 1 2.25 2.25v9a2.25 2.25 0 0 1-2.25 2.25h-9a2.25 2.25 0 0 1-2.25-2.25v-9Z" /></svg>`
: svg`<svg class="text-success" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" style="width: 2rem; height: 2rem;"><path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" /></svg>`;

const startDate = this.data ? parseDate(this.data.start_date) : null;
const isFutureSubscription = !!startDate && startDate > new Date();
const datePickerMin = serializeDate(
new Date(Math.max(tomorrowDate.getTime(), startDate?.getTime() ?? tomorrowDate.getTime()))
);

return html`
<foxy-internal-summary-control infer="" label="" helper-text="">
<div class="text-center flex flex-col items-center bg-transparent">
Expand All @@ -91,7 +105,8 @@ export class InternalAdminSubscriptionFormStatusActionForm extends InternalForm<
<foxy-i18n infer="" key="${action}_title"></foxy-i18n>
</p>
<p class="text-secondary leading-s">
<foxy-i18n infer="" key="${action}_subtitle"></foxy-i18n>
<foxy-i18n infer="" key="${action}_subtitle${isFutureSubscription ? '_future' : ''}">
</foxy-i18n>
</p>
</div>
</foxy-internal-summary-control>
Expand All @@ -100,7 +115,9 @@ export class InternalAdminSubscriptionFormStatusActionForm extends InternalForm<
${isActive
? html`
<foxy-internal-select-control
options=${this.__endDatePresetOptions}
options=${isFutureSubscription
? this.__endDatePresetOptionsForFutureSubs
: this.__endDatePresetOptions}
layout="summary-item"
infer="end-date-preset"
.getValue=${this.__endDatePresetGetValue}
Expand All @@ -111,7 +128,7 @@ export class InternalAdminSubscriptionFormStatusActionForm extends InternalForm<
<foxy-internal-date-control
layout="summary-item"
infer="end-date"
min=${serializeDate(tomorrowDate)}
min=${datePickerMin}
hide-clear-button
.getValue=${this.__endDateGetValue}
>
Expand All @@ -121,7 +138,7 @@ export class InternalAdminSubscriptionFormStatusActionForm extends InternalForm<
<foxy-internal-date-control
layout="summary-item"
infer="next-transaction-date"
min=${serializeDate(tomorrowDate)}
min=${datePickerMin}
hide-clear-button
.getValue=${this.__nextTransactionDateGetValue}
>
Expand All @@ -134,11 +151,13 @@ export class InternalAdminSubscriptionFormStatusActionForm extends InternalForm<
? [
this.__renderFaq(
'cancel_why_not_today',
svg`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="text-error flex-shrink-0" style="width: 1.25em; height: 1.25em;"><path d="M5.25 12a.75.75 0 0 1 .75-.75h.01a.75.75 0 0 1 .75.75v.01a.75.75 0 0 1-.75.75H6a.75.75 0 0 1-.75-.75V12ZM6 13.25a.75.75 0 0 0-.75.75v.01c0 .414.336.75.75.75h.01a.75.75 0 0 0 .75-.75V14a.75.75 0 0 0-.75-.75H6ZM7.25 12a.75.75 0 0 1 .75-.75h.01a.75.75 0 0 1 .75.75v.01a.75.75 0 0 1-.75.75H8a.75.75 0 0 1-.75-.75V12ZM8 13.25a.75.75 0 0 0-.75.75v.01c0 .414.336.75.75.75h.01a.75.75 0 0 0 .75-.75V14a.75.75 0 0 0-.75-.75H8ZM9.25 10a.75.75 0 0 1 .75-.75h.01a.75.75 0 0 1 .75.75v.01a.75.75 0 0 1-.75.75H10a.75.75 0 0 1-.75-.75V10ZM10 11.25a.75.75 0 0 0-.75.75v.01c0 .414.336.75.75.75h.01a.75.75 0 0 0 .75-.75V12a.75.75 0 0 0-.75-.75H10ZM9.25 14a.75.75 0 0 1 .75-.75h.01a.75.75 0 0 1 .75.75v.01a.75.75 0 0 1-.75.75H10a.75.75 0 0 1-.75-.75V14ZM12 9.25a.75.75 0 0 0-.75.75v.01c0 .414.336.75.75.75h.01a.75.75 0 0 0 .75-.75V10a.75.75 0 0 0-.75-.75H12ZM11.25 12a.75.75 0 0 1 .75-.75h.01a.75.75 0 0 1 .75.75v.01a.75.75 0 0 1-.75.75H12a.75.75 0 0 1-.75-.75V12ZM12 13.25a.75.75 0 0 0-.75.75v.01c0 .414.336.75.75.75h.01a.75.75 0 0 0 .75-.75V14a.75.75 0 0 0-.75-.75H12ZM13.25 10a.75.75 0 0 1 .75-.75h.01a.75.75 0 0 1 .75.75v.01a.75.75 0 0 1-.75.75H14a.75.75 0 0 1-.75-.75V10ZM14 11.25a.75.75 0 0 0-.75.75v.01c0 .414.336.75.75.75h.01a.75.75 0 0 0 .75-.75V12a.75.75 0 0 0-.75-.75H14Z" /><path fill-rule="evenodd" d="M5.75 2a.75.75 0 0 1 .75.75V4h7V2.75a.75.75 0 0 1 1.5 0V4h.25A2.75 2.75 0 0 1 18 6.75v8.5A2.75 2.75 0 0 1 15.25 18H4.75A2.75 2.75 0 0 1 2 15.25v-8.5A2.75 2.75 0 0 1 4.75 4H5V2.75A.75.75 0 0 1 5.75 2Zm-1 5.5c-.69 0-1.25.56-1.25 1.25v6.5c0 .69.56 1.25 1.25 1.25h10.5c.69 0 1.25-.56 1.25-1.25v-6.5c0-.69-.56-1.25-1.25-1.25H4.75Z" clip-rule="evenodd" /></svg>`
svg`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="text-error flex-shrink-0" style="width: 1.25em; height: 1.25em;"><path d="M5.25 12a.75.75 0 0 1 .75-.75h.01a.75.75 0 0 1 .75.75v.01a.75.75 0 0 1-.75.75H6a.75.75 0 0 1-.75-.75V12ZM6 13.25a.75.75 0 0 0-.75.75v.01c0 .414.336.75.75.75h.01a.75.75 0 0 0 .75-.75V14a.75.75 0 0 0-.75-.75H6ZM7.25 12a.75.75 0 0 1 .75-.75h.01a.75.75 0 0 1 .75.75v.01a.75.75 0 0 1-.75.75H8a.75.75 0 0 1-.75-.75V12ZM8 13.25a.75.75 0 0 0-.75.75v.01c0 .414.336.75.75.75h.01a.75.75 0 0 0 .75-.75V14a.75.75 0 0 0-.75-.75H8ZM9.25 10a.75.75 0 0 1 .75-.75h.01a.75.75 0 0 1 .75.75v.01a.75.75 0 0 1-.75.75H10a.75.75 0 0 1-.75-.75V10ZM10 11.25a.75.75 0 0 0-.75.75v.01c0 .414.336.75.75.75h.01a.75.75 0 0 0 .75-.75V12a.75.75 0 0 0-.75-.75H10ZM9.25 14a.75.75 0 0 1 .75-.75h.01a.75.75 0 0 1 .75.75v.01a.75.75 0 0 1-.75.75H10a.75.75 0 0 1-.75-.75V14ZM12 9.25a.75.75 0 0 0-.75.75v.01c0 .414.336.75.75.75h.01a.75.75 0 0 0 .75-.75V10a.75.75 0 0 0-.75-.75H12ZM11.25 12a.75.75 0 0 1 .75-.75h.01a.75.75 0 0 1 .75.75v.01a.75.75 0 0 1-.75.75H12a.75.75 0 0 1-.75-.75V12ZM12 13.25a.75.75 0 0 0-.75.75v.01c0 .414.336.75.75.75h.01a.75.75 0 0 0 .75-.75V14a.75.75 0 0 0-.75-.75H12ZM13.25 10a.75.75 0 0 1 .75-.75h.01a.75.75 0 0 1 .75.75v.01a.75.75 0 0 1-.75.75H14a.75.75 0 0 1-.75-.75V10ZM14 11.25a.75.75 0 0 0-.75.75v.01c0 .414.336.75.75.75h.01a.75.75 0 0 0 .75-.75V12a.75.75 0 0 0-.75-.75H14Z" /><path fill-rule="evenodd" d="M5.75 2a.75.75 0 0 1 .75.75V4h7V2.75a.75.75 0 0 1 1.5 0V4h.25A2.75 2.75 0 0 1 18 6.75v8.5A2.75 2.75 0 0 1 15.25 18H4.75A2.75 2.75 0 0 1 2 15.25v-8.5A2.75 2.75 0 0 1 4.75 4H5V2.75A.75.75 0 0 1 5.75 2Zm-1 5.5c-.69 0-1.25.56-1.25 1.25v6.5c0 .69.56 1.25 1.25 1.25h10.5c.69 0 1.25-.56 1.25-1.25v-6.5c0-.69-.56-1.25-1.25-1.25H4.75Z" clip-rule="evenodd" /></svg>`,
isFutureSubscription ? '_future' : ''
),
this.__renderFaq(
'cancel_whats_next',
svg`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="text-error flex-shrink-0" style="width: 1.25em; height: 1.25em;"><path d="M3.288 4.818A1.5 1.5 0 0 0 1 6.095v7.81a1.5 1.5 0 0 0 2.288 1.276l6.323-3.905c.155-.096.285-.213.389-.344v2.973a1.5 1.5 0 0 0 2.288 1.276l6.323-3.905a1.5 1.5 0 0 0 0-2.552l-6.323-3.906A1.5 1.5 0 0 0 10 6.095v2.972a1.506 1.506 0 0 0-.389-.343L3.288 4.818Z" /></svg>`
svg`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="text-error flex-shrink-0" style="width: 1.25em; height: 1.25em;"><path d="M3.288 4.818A1.5 1.5 0 0 0 1 6.095v7.81a1.5 1.5 0 0 0 2.288 1.276l6.323-3.905c.155-.096.285-.213.389-.344v2.973a1.5 1.5 0 0 0 2.288 1.276l6.323-3.905a1.5 1.5 0 0 0 0-2.552l-6.323-3.906A1.5 1.5 0 0 0 10 6.095v2.972a1.506 1.506 0 0 0-.389-.343L3.288 4.818Z" /></svg>`,
isFutureSubscription ? '_future' : ''
),
this.__renderFaq(
'cancel_how_to_reactivate',
Expand Down Expand Up @@ -189,7 +208,7 @@ export class InternalAdminSubscriptionFormStatusActionForm extends InternalForm<
return super._sendPatch(edits);
}

private __renderFaq(prefix: string, icon: SVGTemplateResult) {
private __renderFaq(prefix: string, icon: SVGTemplateResult, textSuffix = '') {
return html`
<div
class="bg-transparent flex leading-xs"
Expand All @@ -199,7 +218,11 @@ export class InternalAdminSubscriptionFormStatusActionForm extends InternalForm<
<p class="grid">
<foxy-i18n class="leading-m text-s font-medium" infer="" key="${prefix}_title">
</foxy-i18n>
<foxy-i18n class="leading-xs text-secondary text-xs" infer="" key="${prefix}_text">
<foxy-i18n
class="leading-xs text-secondary text-xs"
infer=""
key="${prefix}_text${textSuffix}"
>
</foxy-i18n>
</p>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/elements/public/CouponForm/CouponForm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ describe('CouponForm', () => {
control.setValue([{ value: 'a' }, { unit: 'block', value: 'b' }]);
expect(element).to.have.deep.nested.property('form.product_code_restrictions', 'a,-b');

element.edit({ product_code_restrictions: '-foo,bar' });
element.edit({ product_code_restrictions: ' -foo , bar ' });
expect(control.getValue()).to.deep.equal([
{ label: 'product-code-restrictions.label_block', value: '-foo' },
{ label: 'product-code-restrictions.label_allow', value: 'bar' },
Expand Down
3 changes: 2 additions & 1 deletion src/elements/public/CouponForm/CouponForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,8 @@ export class CouponForm extends Base<Data> {
private readonly __productCodeRestrictionsGetValue = () => {
return this.form.product_code_restrictions
?.split(',')
.filter(v => !!v.trim())
.map(v => v.trim())
.filter(v => v.length > 0)
.map(value => ({
value,
label: value.startsWith('-')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,11 +249,11 @@ export class PaymentsApiPaymentMethodForm extends Base<Data> {
: allMethods;

return filteredMethods
.sort((a, b) => a[0].localeCompare(b[0], 'en'))
.sort((a, b) => a[1].name.localeCompare(b[1].name, 'en'))
.reduce((groups, [type, helper]) => {
if (helper.is_deprecated) return groups;

const firstChar = type.charAt(0).toUpperCase();
const firstChar = helper.name.charAt(0).toUpperCase();
const isSpecialCharacter = !/\w/.test(firstChar);
const name = isSpecialCharacter ? '#' : firstChar;
const group = groups.find(group => group.name === name);
Expand Down
Loading