Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ad4e365
fix(foxy-item-form): fix a typo in the subscription section name
pheekus Jan 19, 2026
0fce07f
feat(foxy-item-form): collapse secondary sections by default
pheekus Jan 19, 2026
6fb11d1
chore: update sdk to latest beta
pheekus Jan 19, 2026
b9c6e1c
feat(foxy-item-form): add downloadable purchase controls
pheekus Jan 19, 2026
2d9aa38
fix: ensure resource lists are updated with newly created items when …
pheekus Jan 23, 2026
70f9fc1
docs: open external links in a new tab
pheekus Jan 23, 2026
4016f97
fix(foxy-gift-card-code-form): fix clearing linked customer
pheekus Jan 26, 2026
725475d
feat(foxy-transaction): display ip and user agent when available
pheekus Jan 26, 2026
95a6d56
feat(foxy-filter-attribute-form): add reset button
pheekus Jan 26, 2026
22e2557
build: add compatibility with dev containers
pheekus Jan 26, 2026
596a6d9
build: forward ssh keys for signing in dev containers
pheekus Jan 26, 2026
c9db1c5
fix(foxy-gift-card-code-form): fix unlinking customer ui
pheekus Jan 27, 2026
08e41e4
fix(foxy-gift-card-code-form): add missing translations for customer …
pheekus Jan 27, 2026
4b203c1
feat(foxy-applied-coupon-code-form): update ui with latest controls
pheekus Jan 27, 2026
a1fb986
feat(foxy-client-form): update ui with latest controls
pheekus Jan 27, 2026
b21592a
feat(foxy-customer-portal-settings-form): update ui with latest controls
pheekus Jan 27, 2026
9f1446e
feat(foxy-generate-codes-form): update ui with latest controls
pheekus Jan 27, 2026
bafe144
feat(foxy-passkey-form): update ui with latest controls
pheekus Jan 27, 2026
3ded23a
feat(foxy-template-set-form): update ui with latest controls
pheekus Jan 27, 2026
7be4142
feat(foxy-user-form): update ui with latest controls
pheekus Jan 27, 2026
a6f143f
build: ignore .vscode
pheekus Jan 27, 2026
9a4950e
refactor(foxy-gift-card-code-form): use explicit type
pheekus Jan 27, 2026
e8b0138
build: update sdk to latest
pheekus Jan 29, 2026
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
14 changes: 14 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "foxy-elements",
"image": "mcr.microsoft.com/devcontainers/javascript-node:1-20-bookworm",
"features": {
"ghcr.io/rocker-org/devcontainer-features/apt-packages:1": {
"packages": "chromium"
}
},
"mounts": [
"source=${localEnv:HOME}/.ssh,target=/home/node/.ssh,type=bind,consistency=cached"
],
"forwardPorts": [8000],
"postCreateCommand": "npm install"
}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## editors
/.idea
/.nova
/.vscode

## system files
.DS_Store
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"prepack": "npm run lint && rimraf dist && node ./.build/compile-for-npm.js && rollup -c"
},
"dependencies": {
"@foxy.io/sdk": "^1.15.0",
"@foxy.io/sdk": "^1.16.1",
"@open-wc/lit-helpers": "^0.3.12",
"@open-wc/scoped-elements": "^1.2.1",
"@polymer/iron-icons": "^3.0.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,23 @@ import './index';

import { expect, fixture, html, waitUntil } from '@open-wc/testing';
import { AppliedCouponCodeForm as Form } from './AppliedCouponCodeForm';
import { InternalCheckboxGroupControl } from '../../internal/InternalCheckboxGroupControl/InternalCheckboxGroupControl';
import { InternalSummaryControl } from '../../internal/InternalSummaryControl/InternalSummaryControl';
import { InternalSwitchControl } from '../../internal/InternalSwitchControl/InternalSwitchControl';
import { InternalTextControl } from '../../internal/InternalTextControl/InternalTextControl';
import { InternalForm } from '../../internal/InternalForm/InternalForm';
import { createRouter } from '../../../server';
import { getTestData } from '../../../testgen/getTestData';
import { stub } from 'sinon';

describe('AppliedCouponCodeForm', () => {
it('imports and registers foxy-internal-checkbox-group-control element', () => {
const constructor = customElements.get('foxy-internal-checkbox-group-control');
expect(constructor).to.equal(InternalCheckboxGroupControl);
it('imports and registers foxy-internal-summary-control element', () => {
const constructor = customElements.get('foxy-internal-summary-control');
expect(constructor).to.equal(InternalSummaryControl);
});

it('imports and registers foxy-internal-switch-control element', () => {
const constructor = customElements.get('foxy-internal-switch-control');
expect(constructor).to.equal(InternalSwitchControl);
});

it('imports and registers foxy-internal-text-control element', () => {
Expand Down Expand Up @@ -91,26 +97,15 @@ describe('AppliedCouponCodeForm', () => {
expect(control).to.have.property('helperText', 'code.helper_text_existing');
});

it('renders a checkbox group control for "ignore_usage_limits" field in template state', async () => {
it('renders a switch control for "ignore_usage_limits" field in template state', async () => {
const element = await fixture<Form>(
html`<foxy-applied-coupon-code-form></foxy-applied-coupon-code-form>`
);

const control = element.renderRoot.querySelector<InternalCheckboxGroupControl>(
const control = element.renderRoot.querySelector<InternalSwitchControl>(
'[infer="ignore-usage-limits"]'
);

const options = [{ value: 'checked', label: 'option_checked' }];

expect(control).to.be.instanceOf(InternalCheckboxGroupControl);
expect(control).to.have.deep.property('options', options);

control?.setValue(['checked']);
expect(control?.getValue()).to.deep.equal(['checked']);
expect(element).to.have.nested.property('form.ignore_usage_limits', true);

control?.setValue([]);
expect(control?.getValue()).to.deep.equal([]);
expect(element).to.have.nested.property('form.ignore_usage_limits', false);
expect(control).to.be.instanceOf(InternalSwitchControl);
});
});
45 changes: 15 additions & 30 deletions src/elements/public/AppliedCouponCodeForm/AppliedCouponCodeForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,6 @@ export class AppliedCouponCodeForm extends Base<Data> {
return [({ code: v }) => !!v || 'code:v8n_required'];
}

private readonly __ignoreUsageLimitsOptions = [{ value: 'checked', label: 'option_checked' }];

private readonly __getIgnoreUsageLimitsValue = () => {
return this.form.ignore_usage_limits ? ['checked'] : [];
};

private readonly __setIgnoreUsageLimitsValue = (newValue: string[]) => {
this.edit({ ignore_usage_limits: newValue.includes('checked') });
};

get readonlySelector(): BooleanSelector {
return this.data ? new BooleanSelector('not=delete') : super.readonlySelector;
}
Expand All @@ -43,26 +33,21 @@ export class AppliedCouponCodeForm extends Base<Data> {
return html`
${this.renderHeader()}

<foxy-internal-text-control
helper-text=${this.t(this.data ? 'code.helper_text_existing' : 'code.helper_text_new')}
infer="code"
>
</foxy-internal-text-control>

${this.data
? ''
: html`
<foxy-internal-checkbox-group-control
infer="ignore-usage-limits"
class="-my-xs"
.getValue=${this.__getIgnoreUsageLimitsValue}
.setValue=${this.__setIgnoreUsageLimitsValue}
.options=${this.__ignoreUsageLimitsOptions}
>
</foxy-internal-checkbox-group-control>
`}

<!-- -->
<foxy-internal-summary-control infer="" label="" helper-text="">
<foxy-internal-text-control
helper-text=${this.t(this.data ? 'code.helper_text_existing' : 'code.helper_text_new')}
layout="summary-item"
infer="code"
>
</foxy-internal-text-control>

${this.data
? ''
: html`
<foxy-internal-switch-control infer="ignore-usage-limits">
</foxy-internal-switch-control>
`}
</foxy-internal-summary-control>

${super.renderBody()}
`;
Expand Down
3 changes: 2 additions & 1 deletion src/elements/public/AppliedCouponCodeForm/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import '../../internal/InternalCheckboxGroupControl/index';
import '../../internal/InternalSummaryControl/index';
import '../../internal/InternalSwitchControl/index';
import '../../internal/InternalTextControl/index';
import '../../internal/InternalForm/index';

Expand Down
68 changes: 47 additions & 21 deletions src/elements/public/ClientForm/ClientForm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import { stub } from 'sinon';
import { Data } from './types';

describe('ClientForm', () => {
it('imports and defines foxy-internal-text-area-control', () => {
expect(customElements.get('foxy-internal-text-area-control')).to.exist;
it('imports and defines foxy-internal-summary-control', () => {
expect(customElements.get('foxy-internal-summary-control')).to.exist;
});

it('imports and defines foxy-internal-text-control', () => {
Expand Down Expand Up @@ -42,82 +42,108 @@ describe('ClientForm', () => {
expect(renderHeaderMethod).to.have.been.called;
});

it('renders a foxy-internal-summary-control for general section', async () => {
const element = await fixture<Form>(html`<foxy-client-form></foxy-client-form>`);
const control = element.renderRoot.querySelector('[infer="general"]');
expect(control).to.be.instanceOf(customElements.get('foxy-internal-summary-control'));
});

it('renders a foxy-internal-summary-control for project section', async () => {
const element = await fixture<Form>(html`<foxy-client-form></foxy-client-form>`);
const control = element.renderRoot.querySelector('[infer="project"]');
expect(control).to.be.instanceOf(customElements.get('foxy-internal-summary-control'));
});

it('renders a foxy-internal-summary-control for company section', async () => {
const element = await fixture<Form>(html`<foxy-client-form></foxy-client-form>`);
const control = element.renderRoot.querySelector('[infer="company"]');
expect(control).to.be.instanceOf(customElements.get('foxy-internal-summary-control'));
});

it('renders a foxy-internal-summary-control for contact section', async () => {
const element = await fixture<Form>(html`<foxy-client-form></foxy-client-form>`);
const control = element.renderRoot.querySelector('[infer="contact"]');
expect(control).to.be.instanceOf(customElements.get('foxy-internal-summary-control'));
});

it('renders a foxy-internal-text-control for client id', async () => {
const element = await fixture<Form>(html`<foxy-client-form></foxy-client-form>`);
const control = element.renderRoot.querySelector('[infer="client-id"]');
const control = element.renderRoot.querySelector('[infer="general"] [infer="client-id"]');
expect(control).to.be.instanceOf(customElements.get('foxy-internal-text-control'));
});

it('renders a foxy-internal-text-control for client secret', async () => {
const element = await fixture<Form>(html`<foxy-client-form></foxy-client-form>`);
const control = element.renderRoot.querySelector('[infer="client-secret"]');
const control = element.renderRoot.querySelector('[infer="general"] [infer="client-secret"]');
expect(control).to.be.instanceOf(customElements.get('foxy-internal-text-control'));
});

it('renders a foxy-internal-text-control for redirect uri', async () => {
const element = await fixture<Form>(html`<foxy-client-form></foxy-client-form>`);
const control = element.renderRoot.querySelector('[infer="redirect-uri"]');
const control = element.renderRoot.querySelector('[infer="general"] [infer="redirect-uri"]');
expect(control).to.be.instanceOf(customElements.get('foxy-internal-text-control'));
});

it('renders a foxy-internal-text-control for project name', async () => {
const element = await fixture<Form>(html`<foxy-client-form></foxy-client-form>`);
const control = element.renderRoot.querySelector('[infer="project-name"]');
const control = element.renderRoot.querySelector('[infer="project"] [infer="project-name"]');
expect(control).to.be.instanceOf(customElements.get('foxy-internal-text-control'));
});

it('renders a foxy-internal-text-area-control for project description', async () => {
it('renders a foxy-internal-text-control for project description', async () => {
const element = await fixture<Form>(html`<foxy-client-form></foxy-client-form>`);
const control = element.renderRoot.querySelector('[infer="project-description"]');
expect(control).to.be.instanceOf(customElements.get('foxy-internal-text-area-control'));
const control = element.renderRoot.querySelector(
'[infer="project"] [infer="project-description"]'
);
expect(control).to.be.instanceOf(customElements.get('foxy-internal-text-control'));
});

it('renders a foxy-internal-text-control for company name', async () => {
const element = await fixture<Form>(html`<foxy-client-form></foxy-client-form>`);
const control = element.renderRoot.querySelector('[infer="company-name"]');
const control = element.renderRoot.querySelector('[infer="company"] [infer="company-name"]');
expect(control).to.be.instanceOf(customElements.get('foxy-internal-text-control'));
});

it('renders a foxy-internal-text-control for company url', async () => {
const element = await fixture<Form>(html`<foxy-client-form></foxy-client-form>`);
const control = element.renderRoot.querySelector('[infer="company-url"]');
const control = element.renderRoot.querySelector('[infer="company"] [infer="company-url"]');
expect(control).to.be.instanceOf(customElements.get('foxy-internal-text-control'));
});

it('renders a foxy-internal-text-control for company logo', async () => {
const element = await fixture<Form>(html`<foxy-client-form></foxy-client-form>`);
const control = element.renderRoot.querySelector('[infer="company-logo"]');
const control = element.renderRoot.querySelector('[infer="company"] [infer="company-logo"]');
expect(control).to.be.instanceOf(customElements.get('foxy-internal-text-control'));
});

it('renders a foxy-internal-text-control for contact name', async () => {
const element = await fixture<Form>(html`<foxy-client-form></foxy-client-form>`);
const control = element.renderRoot.querySelector('[infer="contact-name"]');
const control = element.renderRoot.querySelector('[infer="contact"] [infer="contact-name"]');
expect(control).to.be.instanceOf(customElements.get('foxy-internal-text-control'));
});

it('renders a foxy-internal-text-control for contact email', async () => {
const element = await fixture<Form>(html`<foxy-client-form></foxy-client-form>`);
const control = element.renderRoot.querySelector('[infer="contact-email"]');
const control = element.renderRoot.querySelector('[infer="contact"] [infer="contact-email"]');
expect(control).to.be.instanceOf(customElements.get('foxy-internal-text-control'));
});

it('renders a foxy-internal-text-control for contact phone', async () => {
const element = await fixture<Form>(html`<foxy-client-form></foxy-client-form>`);
const control = element.renderRoot.querySelector('[infer="contact-phone"]');
const control = element.renderRoot.querySelector('[infer="contact"] [infer="contact-phone"]');
expect(control).to.be.instanceOf(customElements.get('foxy-internal-text-control'));
});

it('always marks client secret control as readonly', async () => {
const element = await fixture<Form>(html`<foxy-client-form></foxy-client-form>`);
expect(element.readonlySelector.matches('client-secret', true)).to.be.true;
expect(element.readonlySelector.matches('general:client-secret', true)).to.be.true;
});

it('marks client id control as readonly when loaded', async () => {
const element = await fixture<Form>(html`<foxy-client-form></foxy-client-form>`);
expect(element.readonlySelector.matches('client-id', true)).to.be.false;
expect(element.readonlySelector.matches('general:client-id', true)).to.be.false;
element.data = await getTestData('./hapi/clients/0');
expect(element.readonlySelector.matches('client-id', true)).to.be.true;
expect(element.readonlySelector.matches('general:client-id', true)).to.be.true;
});

it('marks client id control as readonly when loading', async () => {
Expand All @@ -126,15 +152,15 @@ describe('ClientForm', () => {
<foxy-client-form @fetch=${(evt: FetchEvent) => router.handleEvent(evt)}></foxy-client-form>
`);

expect(element.readonlySelector.matches('client-id', true)).to.be.false;
expect(element.readonlySelector.matches('general:client-id', true)).to.be.false;
element.href = 'https://demo.api/virtual/stall';
await element.requestUpdate();
expect(element.readonlySelector.matches('client-id', true)).to.be.true;
expect(element.readonlySelector.matches('general:client-id', true)).to.be.true;
});

it('hides client secret control when empty', async () => {
const element = await fixture<Form>(html`<foxy-client-form></foxy-client-form>`);
expect(element.hiddenSelector.matches('client-secret', true)).to.be.true;
expect(element.hiddenSelector.matches('general:client-secret', true)).to.be.true;
});

it('uses custom options for header subtitle', async () => {
Expand Down
Loading