Add "cancellation scheduled" state to superadmin org list (#2594)
Fixes https://github.com/webrecorder/browsertrix/issues/2595 ## Changes Adds "Subscription Cancellation Scheduled" state/icon/tooltip to superadmin org list, with future cancellation duration/date. Adds more subscription-related info and features to the action menu in the same org list - "Open in Stripe" action is visible if subscription id is a Stripe object id - "Plan ID" and "Action on Cancel" correspond to `planId` and `readOnlyOnCancel` properties on `subscription` object - There's also some additional highlighting for possible errors (hopefully only visible on dev) — see the last screenshot for an example Adds first pass at filters for superadmin org list - The filters' counts update when searching - I took an initial pass at figuring out which filters would be most useful — we can always go back and tweak them later
This commit is contained in:
parent
3e169ebc15
commit
5915c24c18
@ -5,19 +5,44 @@ import type {
|
|||||||
SlCheckbox,
|
SlCheckbox,
|
||||||
SlInput,
|
SlInput,
|
||||||
SlMenuItem,
|
SlMenuItem,
|
||||||
|
SlRadioGroup,
|
||||||
} from "@shoelace-style/shoelace";
|
} from "@shoelace-style/shoelace";
|
||||||
import { serialize } from "@shoelace-style/shoelace/dist/utilities/form.js";
|
import { serialize } from "@shoelace-style/shoelace/dist/utilities/form.js";
|
||||||
import Fuse from "fuse.js";
|
import Fuse from "fuse.js";
|
||||||
import { css, html, nothing, type PropertyValues } from "lit";
|
import {
|
||||||
|
css,
|
||||||
|
html,
|
||||||
|
nothing,
|
||||||
|
type PropertyValues,
|
||||||
|
type TemplateResult,
|
||||||
|
} from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators.js";
|
import { customElement, property, query, state } from "lit/decorators.js";
|
||||||
|
import { repeat } from "lit/directives/repeat.js";
|
||||||
import { when } from "lit/directives/when.js";
|
import { when } from "lit/directives/when.js";
|
||||||
|
|
||||||
import { BtrixElement } from "@/classes/BtrixElement";
|
import { BtrixElement } from "@/classes/BtrixElement";
|
||||||
import type { Dialog } from "@/components/ui/dialog";
|
import type { Dialog } from "@/components/ui/dialog";
|
||||||
|
import { ClipboardController } from "@/controllers/clipboard";
|
||||||
import { SubscriptionStatus } from "@/types/billing";
|
import { SubscriptionStatus } from "@/types/billing";
|
||||||
import type { ProxiesAPIResponse, Proxy } from "@/types/crawler";
|
import type { ProxiesAPIResponse, Proxy } from "@/types/crawler";
|
||||||
import type { OrgData } from "@/utils/orgs";
|
import type { OrgData } from "@/utils/orgs";
|
||||||
|
|
||||||
|
enum OrgFilter {
|
||||||
|
All = "all",
|
||||||
|
Active = "active",
|
||||||
|
Inactive = "inactive",
|
||||||
|
Trialing = "trialing",
|
||||||
|
ScheduledCancel = "scheduled-cancel",
|
||||||
|
}
|
||||||
|
|
||||||
|
const none = html`
|
||||||
|
<sl-icon
|
||||||
|
name="slash"
|
||||||
|
class="text-base text-neutral-400"
|
||||||
|
label=${msg("None")}
|
||||||
|
></sl-icon>
|
||||||
|
`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @fires update-quotas
|
* @fires update-quotas
|
||||||
* @fires update-proxies
|
* @fires update-proxies
|
||||||
@ -76,6 +101,9 @@ export class OrgsList extends BtrixElement {
|
|||||||
@state()
|
@state()
|
||||||
private search = "";
|
private search = "";
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private orgFilter: OrgFilter = OrgFilter.All;
|
||||||
|
|
||||||
protected willUpdate(changedProperties: PropertyValues<this>) {
|
protected willUpdate(changedProperties: PropertyValues<this>) {
|
||||||
if (changedProperties.has("orgList")) {
|
if (changedProperties.has("orgList")) {
|
||||||
this.fuse.setCollection(this.orgList ?? []);
|
this.fuse.setCollection(this.orgList ?? []);
|
||||||
@ -91,16 +119,20 @@ export class OrgsList extends BtrixElement {
|
|||||||
return this.renderSkeleton();
|
return this.renderSkeleton();
|
||||||
}
|
}
|
||||||
|
|
||||||
const orgs = this.search
|
const searchResults = this.search
|
||||||
? this.fuse.search(this.search).map(({ item }) => item)
|
? this.fuse.search(this.search).map(({ item }) => item)
|
||||||
: this.orgList;
|
: this.orgList;
|
||||||
|
|
||||||
|
const orgs = searchResults?.filter((org) =>
|
||||||
|
this.filterOrg(org, this.orgFilter),
|
||||||
|
);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<sl-input
|
<sl-input
|
||||||
value=${this.search}
|
value=${this.search}
|
||||||
clearable
|
clearable
|
||||||
size="small"
|
size="small"
|
||||||
class="mb-6"
|
class="mb-4"
|
||||||
placeholder=${msg(
|
placeholder=${msg(
|
||||||
"Search all orgs by name, id, slug, users, and subscriptions",
|
"Search all orgs by name, id, slug, users, and subscriptions",
|
||||||
)}
|
)}
|
||||||
@ -115,6 +147,42 @@ export class OrgsList extends BtrixElement {
|
|||||||
library="default"
|
library="default"
|
||||||
></sl-icon
|
></sl-icon
|
||||||
></sl-input>
|
></sl-input>
|
||||||
|
<btrix-overflow-scroll
|
||||||
|
class="-mx-3 [--btrix-overflow-scroll-scrim-color:theme(colors.neutral.50)] part-[content]:px-3"
|
||||||
|
>
|
||||||
|
<sl-radio-group
|
||||||
|
size="small"
|
||||||
|
value=${this.orgFilter}
|
||||||
|
class="mb-6 flex min-w-min justify-end"
|
||||||
|
@sl-change=${(e: SlChangeEvent) => {
|
||||||
|
this.orgFilter = (e.target as SlRadioGroup).value as OrgFilter;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
${[
|
||||||
|
{ label: msg("All"), icon: "asterisk", filter: OrgFilter.All },
|
||||||
|
{
|
||||||
|
label: msg("Active"),
|
||||||
|
icon: "credit-card",
|
||||||
|
filter: OrgFilter.Active,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: msg("Inactive"),
|
||||||
|
icon: "x-square",
|
||||||
|
filter: OrgFilter.Inactive,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: msg("Trials"),
|
||||||
|
icon: "basket",
|
||||||
|
filter: OrgFilter.Trialing,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: msg("Cancellation Scheduled"),
|
||||||
|
icon: "calendar2-x",
|
||||||
|
filter: OrgFilter.ScheduledCancel,
|
||||||
|
},
|
||||||
|
].map((options) => this.renderFilterButton(searchResults, options))}
|
||||||
|
</sl-radio-group>
|
||||||
|
</btrix-overflow-scroll>
|
||||||
<btrix-overflow-scroll
|
<btrix-overflow-scroll
|
||||||
class="-mx-3 [--btrix-overflow-scroll-scrim-color:theme(colors.neutral.50)] part-[content]:px-3"
|
class="-mx-3 [--btrix-overflow-scroll-scrim-color:theme(colors.neutral.50)] part-[content]:px-3"
|
||||||
>
|
>
|
||||||
@ -143,7 +211,7 @@ export class OrgsList extends BtrixElement {
|
|||||||
</btrix-table-header-cell>
|
</btrix-table-header-cell>
|
||||||
</btrix-table-head>
|
</btrix-table-head>
|
||||||
<btrix-table-body class="rounded border">
|
<btrix-table-body class="rounded border">
|
||||||
${orgs?.map(this.renderOrg)}
|
${repeat(orgs || [], (org) => org.id, this.renderOrg)}
|
||||||
</btrix-table-body>
|
</btrix-table-body>
|
||||||
</btrix-table>
|
</btrix-table>
|
||||||
</btrix-overflow-scroll>
|
</btrix-overflow-scroll>
|
||||||
@ -153,6 +221,61 @@ export class OrgsList extends BtrixElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private renderFilterButton(
|
||||||
|
orgs: OrgData[] | undefined,
|
||||||
|
options: { label: string; icon: string; filter: OrgFilter },
|
||||||
|
) {
|
||||||
|
const { label, icon, filter } = options;
|
||||||
|
return this.orgList?.some((org) => this.filterOrg(org, filter))
|
||||||
|
? html`
|
||||||
|
<sl-radio-button
|
||||||
|
pill
|
||||||
|
value=${filter}
|
||||||
|
class="part-[label]:items-baseline"
|
||||||
|
>
|
||||||
|
<sl-icon name=${icon} slot="prefix"></sl-icon>
|
||||||
|
${label}
|
||||||
|
<span class="ml-2 text-xs font-normal tabular-nums"
|
||||||
|
>${this.localize.number(
|
||||||
|
orgs?.filter((org) => this.filterOrg(org, filter)).length ?? 0,
|
||||||
|
)}</span
|
||||||
|
>
|
||||||
|
</sl-radio-button>
|
||||||
|
`
|
||||||
|
: nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
private filterOrg(org: OrgData, filter: OrgFilter): boolean {
|
||||||
|
switch (filter) {
|
||||||
|
case OrgFilter.Active:
|
||||||
|
return (
|
||||||
|
!!org.subscription &&
|
||||||
|
org.subscription.status === SubscriptionStatus.Active
|
||||||
|
);
|
||||||
|
case OrgFilter.Inactive:
|
||||||
|
return (
|
||||||
|
!!org.subscription &&
|
||||||
|
!(
|
||||||
|
org.subscription.status === SubscriptionStatus.Active ||
|
||||||
|
org.subscription.status === SubscriptionStatus.Trialing
|
||||||
|
)
|
||||||
|
);
|
||||||
|
case OrgFilter.Trialing:
|
||||||
|
return (
|
||||||
|
!!org.subscription &&
|
||||||
|
org.subscription.status === SubscriptionStatus.Trialing
|
||||||
|
);
|
||||||
|
case OrgFilter.ScheduledCancel:
|
||||||
|
return (
|
||||||
|
!!org.subscription &&
|
||||||
|
org.subscription.status === SubscriptionStatus.Active &&
|
||||||
|
!!org.subscription.futureCancelDate
|
||||||
|
);
|
||||||
|
case OrgFilter.All:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private renderOrgQuotas() {
|
private renderOrgQuotas() {
|
||||||
return html`
|
return html`
|
||||||
<btrix-dialog
|
<btrix-dialog
|
||||||
@ -605,14 +728,6 @@ export class OrgsList extends BtrixElement {
|
|||||||
|
|
||||||
const memberCount = Object.keys(org.users || {}).length;
|
const memberCount = Object.keys(org.users || {}).length;
|
||||||
|
|
||||||
const none = html`
|
|
||||||
<sl-icon
|
|
||||||
name="slash"
|
|
||||||
class="text-base text-neutral-400"
|
|
||||||
label=${msg("None")}
|
|
||||||
></sl-icon>
|
|
||||||
`;
|
|
||||||
|
|
||||||
let status = {
|
let status = {
|
||||||
icon: html`<sl-icon
|
icon: html`<sl-icon
|
||||||
class="text-base text-success"
|
class="text-base text-success"
|
||||||
@ -650,7 +765,10 @@ export class OrgsList extends BtrixElement {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let subscription = {
|
let subscription: {
|
||||||
|
icon: TemplateResult<1>;
|
||||||
|
description: string | TemplateResult<1>;
|
||||||
|
} = {
|
||||||
icon: none,
|
icon: none,
|
||||||
description: msg("No Subscription"),
|
description: msg("No Subscription"),
|
||||||
};
|
};
|
||||||
@ -658,14 +776,72 @@ export class OrgsList extends BtrixElement {
|
|||||||
if (org.subscription) {
|
if (org.subscription) {
|
||||||
switch (org.subscription.status) {
|
switch (org.subscription.status) {
|
||||||
case SubscriptionStatus.Active:
|
case SubscriptionStatus.Active:
|
||||||
subscription = {
|
if (
|
||||||
icon: html`<sl-icon
|
org.subscription.futureCancelDate &&
|
||||||
class="text-base text-success"
|
new Date(org.subscription.futureCancelDate).getTime() -
|
||||||
name="credit-card-fill"
|
new Date().getTime() >=
|
||||||
label=${msg("Active Subscription")}
|
0
|
||||||
></sl-icon>`,
|
) {
|
||||||
description: msg("Active Subscription"),
|
subscription = {
|
||||||
};
|
icon: html`<sl-icon
|
||||||
|
class="text-base text-warning"
|
||||||
|
name="calendar2-x"
|
||||||
|
label=${msg("Subscription Cancellation Scheduled")}
|
||||||
|
></sl-icon>`,
|
||||||
|
description: html`${msg("Subscription Cancellation Scheduled")}
|
||||||
|
<div class="mt-2 text-xs">
|
||||||
|
${msg("Subscription will be cancelled in")}
|
||||||
|
${this.localize.humanizeDuration(
|
||||||
|
new Date(org.subscription.futureCancelDate).getTime() -
|
||||||
|
new Date().getTime(),
|
||||||
|
)}
|
||||||
|
(${this.localize.date(org.subscription.futureCancelDate, {
|
||||||
|
timeStyle: "medium",
|
||||||
|
dateStyle: "medium",
|
||||||
|
})})
|
||||||
|
</div>`,
|
||||||
|
};
|
||||||
|
} else if (
|
||||||
|
org.subscription.futureCancelDate &&
|
||||||
|
new Date(org.subscription.futureCancelDate).getTime() -
|
||||||
|
new Date().getTime() <
|
||||||
|
0
|
||||||
|
) {
|
||||||
|
subscription = {
|
||||||
|
icon: html`<sl-icon
|
||||||
|
class="text-base text-warning"
|
||||||
|
name="calendar2-x"
|
||||||
|
label=${msg("Subscription Cancellation Scheduled")}
|
||||||
|
></sl-icon>
|
||||||
|
<sl-icon
|
||||||
|
class="text-base text-danger"
|
||||||
|
name="x-octagon-fill"
|
||||||
|
label=${msg("Subscription Cancellation Scheduled")}
|
||||||
|
></sl-icon>`,
|
||||||
|
description: html`${msg(
|
||||||
|
"Subscription Cancellation Scheduled in the Past",
|
||||||
|
)}
|
||||||
|
<div class="mt-2 text-xs">
|
||||||
|
${msg("Subscription was scheduled for cancellation at")}
|
||||||
|
${this.localize.date(org.subscription.futureCancelDate, {
|
||||||
|
timeStyle: "medium",
|
||||||
|
dateStyle: "medium",
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div class="my-2 font-bold text-danger-300">
|
||||||
|
${msg("This indicates something has gone wrong.")}
|
||||||
|
</div>`,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
subscription = {
|
||||||
|
icon: html`<sl-icon
|
||||||
|
class="text-base text-success"
|
||||||
|
name="credit-card-fill"
|
||||||
|
label=${msg("Active Subscription")}
|
||||||
|
></sl-icon>`,
|
||||||
|
description: msg("Active Subscription"),
|
||||||
|
};
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case SubscriptionStatus.Trialing:
|
case SubscriptionStatus.Trialing:
|
||||||
subscription = {
|
subscription = {
|
||||||
@ -731,10 +907,11 @@ export class OrgsList extends BtrixElement {
|
|||||||
: "opacity-50"} cursor-pointer select-none border-b bg-neutral-0 transition-colors first-of-type:rounded-t last-of-type:rounded-b last-of-type:border-none focus-within:bg-neutral-50 hover:bg-neutral-50"
|
: "opacity-50"} cursor-pointer select-none border-b bg-neutral-0 transition-colors first-of-type:rounded-t last-of-type:rounded-b last-of-type:border-none focus-within:bg-neutral-50 hover:bg-neutral-50"
|
||||||
>
|
>
|
||||||
<btrix-table-cell class="min-w-6 gap-1 pl-2">
|
<btrix-table-cell class="min-w-6 gap-1 pl-2">
|
||||||
<sl-tooltip content=${status.description}>
|
<sl-tooltip content=${status.description} hoist>
|
||||||
${status.icon}
|
${status.icon}
|
||||||
</sl-tooltip>
|
</sl-tooltip>
|
||||||
<sl-tooltip content=${subscription.description}>
|
<sl-tooltip hoist>
|
||||||
|
<span slot="content">${subscription.description}</span>
|
||||||
${subscription.icon}
|
${subscription.icon}
|
||||||
</sl-tooltip>
|
</sl-tooltip>
|
||||||
</btrix-table-cell>
|
</btrix-table-cell>
|
||||||
@ -786,6 +963,74 @@ export class OrgsList extends BtrixElement {
|
|||||||
@click=${(e: MouseEvent) => e.stopPropagation()}
|
@click=${(e: MouseEvent) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<sl-menu>
|
<sl-menu>
|
||||||
|
<sl-menu-label
|
||||||
|
>${msg("Subscription")}
|
||||||
|
${when(
|
||||||
|
org.subscription,
|
||||||
|
(sub) => html`
|
||||||
|
<table class="w-full mt-1 text-xs whitespace-nowrap font-normal">
|
||||||
|
<tr>
|
||||||
|
<th scope="row" class="font-normal text-left">${msg("Plan ID")}</th>
|
||||||
|
<td class="text-right font-monospace text-neutral-900">
|
||||||
|
${sub.planId}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row" class="font-normal text-left">${msg("Action on Cancel")}</td>
|
||||||
|
<td class="text-right font-bold text-neutral-900">
|
||||||
|
${
|
||||||
|
sub.readOnlyOnCancel
|
||||||
|
? msg("Read-Only")
|
||||||
|
: msg("Delete")
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
`,
|
||||||
|
)}
|
||||||
|
</sl-menu-label>
|
||||||
|
${org.subscription
|
||||||
|
? org.subscription.subId.startsWith("stripe:")
|
||||||
|
? html`<sl-menu-item
|
||||||
|
@click=${() => {
|
||||||
|
window.open(
|
||||||
|
`https://dashboard.stripe.com/subscriptions/${org.subscription!.subId.slice(7)}`,
|
||||||
|
"_blank",
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<sl-icon slot="prefix" name="stripe"></sl-icon>
|
||||||
|
${msg("Open in Stripe")}
|
||||||
|
<sl-icon
|
||||||
|
slot="suffix"
|
||||||
|
name="box-arrow-up-right"
|
||||||
|
></sl-icon>
|
||||||
|
</sl-menu-item>`
|
||||||
|
: html`<sl-menu-item
|
||||||
|
@click=${() => {
|
||||||
|
ClipboardController.copyToClipboard(
|
||||||
|
org.subscription!.subId,
|
||||||
|
);
|
||||||
|
this.notify.toast({
|
||||||
|
message: msg("Subscription ID Copied"),
|
||||||
|
duration: 1000,
|
||||||
|
variant: "success",
|
||||||
|
id: "item-copied",
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
${msg("Copy Subscription ID")}
|
||||||
|
</sl-menu-item>`
|
||||||
|
: html`<sl-menu-item disabled>
|
||||||
|
<sl-icon
|
||||||
|
name="slash"
|
||||||
|
class="text-base text-neutral-400"
|
||||||
|
slot="prefix"
|
||||||
|
></sl-icon>
|
||||||
|
${msg("No Subscription")}</sl-menu-item
|
||||||
|
>`}
|
||||||
|
<sl-divider></sl-divider>
|
||||||
|
<sl-menu-label>${msg("Manage Org")}</sl-menu-label>
|
||||||
<sl-menu-item
|
<sl-menu-item
|
||||||
@click=${() => {
|
@click=${() => {
|
||||||
this.currOrg = org;
|
this.currOrg = org;
|
||||||
|
@ -50,7 +50,7 @@ export class OverflowScroll extends LitElement {
|
|||||||
content: "";
|
content: "";
|
||||||
width: var(--btrix-overflow-scrim-width, 3rem);
|
width: var(--btrix-overflow-scrim-width, 3rem);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 1;
|
z-index: 10;
|
||||||
top: 0;
|
top: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
@ -823,7 +823,7 @@ export class App extends BtrixElement {
|
|||||||
case "admin":
|
case "admin":
|
||||||
return this.renderAdminPage(
|
return this.renderAdminPage(
|
||||||
() => html`
|
() => html`
|
||||||
<btrix-admin class="w-full md:bg-neutral-50"></btrix-admin>
|
<btrix-admin class="w-full bg-neutral-50"></btrix-admin>
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -101,30 +101,8 @@ export class Admin extends BtrixElement {
|
|||||||
|
|
||||||
private renderAdminOrgs() {
|
private renderAdminOrgs() {
|
||||||
return html`
|
return html`
|
||||||
<div class="grid grid-cols-3 gap-6">
|
<div class="grid gap-6 lg:grid-cols-[1fr,minmax(320px,20%)]">
|
||||||
<div class="col-span-3 md:col-span-2">
|
<div class="flex flex-wrap gap-4 *:flex-1 lg:order-1 lg:block">
|
||||||
<section>
|
|
||||||
<header
|
|
||||||
class="mb-3 flex items-center justify-between border-b pb-3"
|
|
||||||
>
|
|
||||||
<h2 class="text-lg font-medium">${msg("All Organizations")}</h2>
|
|
||||||
<sl-button
|
|
||||||
variant="primary"
|
|
||||||
size="small"
|
|
||||||
@click=${() => (this.isAddingOrg = true)}
|
|
||||||
>
|
|
||||||
<sl-icon slot="prefix" name="plus-lg"></sl-icon>
|
|
||||||
${msg("New Organization")}
|
|
||||||
</sl-button>
|
|
||||||
</header>
|
|
||||||
<btrix-orgs-list
|
|
||||||
.orgList=${this.orgList}
|
|
||||||
@update-quotas=${this.onUpdateOrgQuotas}
|
|
||||||
@update-proxies=${this.onUpdateOrgProxies}
|
|
||||||
></btrix-orgs-list>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
<div class="col-span-3 md:col-span-1">
|
|
||||||
<btrix-instance-stats
|
<btrix-instance-stats
|
||||||
.orgList=${this.orgList ?? []}
|
.orgList=${this.orgList ?? []}
|
||||||
></btrix-instance-stats>
|
></btrix-instance-stats>
|
||||||
@ -135,6 +113,24 @@ export class Admin extends BtrixElement {
|
|||||||
${this.renderInvite()}
|
${this.renderInvite()}
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
<section class="min-w-0">
|
||||||
|
<header class="mb-3 flex items-center justify-between border-b pb-3">
|
||||||
|
<h2 class="text-lg font-medium">${msg("All Organizations")}</h2>
|
||||||
|
<sl-button
|
||||||
|
variant="primary"
|
||||||
|
size="small"
|
||||||
|
@click=${() => (this.isAddingOrg = true)}
|
||||||
|
>
|
||||||
|
<sl-icon slot="prefix" name="plus-lg"></sl-icon>
|
||||||
|
${msg("New Organization")}
|
||||||
|
</sl-button>
|
||||||
|
</header>
|
||||||
|
<btrix-orgs-list
|
||||||
|
.orgList=${this.orgList}
|
||||||
|
@update-quotas=${this.onUpdateOrgQuotas}
|
||||||
|
@update-proxies=${this.onUpdateOrgProxies}
|
||||||
|
></btrix-orgs-list>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -170,8 +170,13 @@
|
|||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Align left edge with prefix icon */
|
||||||
|
sl-menu sl-menu-label::part(base) {
|
||||||
|
padding-left: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Align left edge with selected item */
|
/* Align left edge with selected item */
|
||||||
sl-menu-label::part(base) {
|
sl-select sl-menu-label::part(base) {
|
||||||
padding-left: var(--sl-spacing-medium);
|
padding-left: var(--sl-spacing-medium);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,9 @@ export const subscriptionStatusSchema = z.nativeEnum(SubscriptionStatus);
|
|||||||
export const subscriptionSchema = z.object({
|
export const subscriptionSchema = z.object({
|
||||||
status: subscriptionStatusSchema,
|
status: subscriptionStatusSchema,
|
||||||
planId: z.string(),
|
planId: z.string(),
|
||||||
|
readOnlyOnCancel: z.boolean(),
|
||||||
futureCancelDate: apiDateSchema.nullable(),
|
futureCancelDate: apiDateSchema.nullable(),
|
||||||
|
subId: z.string(),
|
||||||
});
|
});
|
||||||
export type Subscription = z.infer<typeof subscriptionSchema>;
|
export type Subscription = z.infer<typeof subscriptionSchema>;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user