fix: Update org status banner on quota reached (#1956)
Fixes https://github.com/webrecorder/browsertrix/issues/1954 ### Changes - Refactors app state to include org data - Fixes banner not showing if storage or execution minutes is exceeded after page load - Disables closing banners - Refreshes org when tab changes --------- Co-authored-by: emma <hi@emma.cafe>
This commit is contained in:
		
							parent
							
								
									b35669af8d
								
							
						
					
					
						commit
						08147ec77d
					
				@ -1,17 +1,16 @@
 | 
				
			|||||||
import { localized, msg, str } from "@lit/localize";
 | 
					import { localized, msg, str } from "@lit/localize";
 | 
				
			||||||
import { differenceInDays } from "date-fns/fp";
 | 
					import { differenceInDays } from "date-fns/fp";
 | 
				
			||||||
import { html, type PropertyValues, type TemplateResult } from "lit";
 | 
					import { html, type TemplateResult } from "lit";
 | 
				
			||||||
import { customElement, property, state } from "lit/decorators.js";
 | 
					import { customElement } from "lit/decorators.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { TailwindElement } from "@/classes/TailwindElement";
 | 
					import { TailwindElement } from "@/classes/TailwindElement";
 | 
				
			||||||
import { NavigateController } from "@/controllers/navigate";
 | 
					import { NavigateController } from "@/controllers/navigate";
 | 
				
			||||||
import { OrgReadOnlyReason, type OrgData } from "@/types/org";
 | 
					import { OrgReadOnlyReason } from "@/types/org";
 | 
				
			||||||
import { formatISODateString } from "@/utils/localization";
 | 
					import { formatISODateString } from "@/utils/localization";
 | 
				
			||||||
import appState, { use } from "@/utils/state";
 | 
					import appState, { use } from "@/utils/state";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Alert = {
 | 
					type Alert = {
 | 
				
			||||||
  test: () => boolean;
 | 
					  test: () => boolean;
 | 
				
			||||||
  persist?: boolean;
 | 
					 | 
				
			||||||
  content: () => {
 | 
					  content: () => {
 | 
				
			||||||
    title: string | TemplateResult;
 | 
					    title: string | TemplateResult;
 | 
				
			||||||
    detail: string | TemplateResult;
 | 
					    detail: string | TemplateResult;
 | 
				
			||||||
@ -21,64 +20,37 @@ type Alert = {
 | 
				
			|||||||
@localized()
 | 
					@localized()
 | 
				
			||||||
@customElement("btrix-org-status-banner")
 | 
					@customElement("btrix-org-status-banner")
 | 
				
			||||||
export class OrgStatusBanner extends TailwindElement {
 | 
					export class OrgStatusBanner extends TailwindElement {
 | 
				
			||||||
  @property({ type: Object })
 | 
					 | 
				
			||||||
  org?: OrgData;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @use()
 | 
					  @use()
 | 
				
			||||||
  appState = appState;
 | 
					  appState = appState;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @state()
 | 
					 | 
				
			||||||
  isAlertOpen = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private readonly navigate = new NavigateController(this);
 | 
					  private readonly navigate = new NavigateController(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private alert?: Alert;
 | 
					  private get org() {
 | 
				
			||||||
 | 
					    return this.appState.org;
 | 
				
			||||||
  protected willUpdate(_changedProperties: PropertyValues): void {
 | 
					 | 
				
			||||||
    if (_changedProperties.has("org") && this.org) {
 | 
					 | 
				
			||||||
      this.alert = this.alerts.find(({ test }) => test());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (this.alert) {
 | 
					 | 
				
			||||||
        this.isAlertOpen = true;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  render() {
 | 
					  render() {
 | 
				
			||||||
    if (!this.org) return;
 | 
					    if (!this.org) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const alert = this.alerts.find(({ test }) => test());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!alert) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const content = alert.content();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return html`
 | 
					    return html`
 | 
				
			||||||
      <div
 | 
					      <div id="banner" class="border-b bg-slate-100 py-5">
 | 
				
			||||||
        class="${this.isAlertOpen
 | 
					 | 
				
			||||||
          ? "bg-slate-100 border-b py-5"
 | 
					 | 
				
			||||||
          : ""} transition-all"
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <div class="mx-auto box-border w-full max-w-screen-desktop px-3">
 | 
					        <div class="mx-auto box-border w-full max-w-screen-desktop px-3">
 | 
				
			||||||
          <sl-alert
 | 
					          <sl-alert variant="danger" open>
 | 
				
			||||||
            variant="danger"
 | 
					 | 
				
			||||||
            ?closable=${!this.alert?.persist}
 | 
					 | 
				
			||||||
            ?open=${this.isAlertOpen}
 | 
					 | 
				
			||||||
            @sl-after-hide=${() => (this.isAlertOpen = false)}
 | 
					 | 
				
			||||||
          >
 | 
					 | 
				
			||||||
            <sl-icon slot="icon" name="exclamation-triangle-fill"></sl-icon>
 | 
					            <sl-icon slot="icon" name="exclamation-triangle-fill"></sl-icon>
 | 
				
			||||||
            ${this.renderContent()}
 | 
					            <strong class="block font-semibold">${content.title}</strong>
 | 
				
			||||||
 | 
					            ${content.detail}
 | 
				
			||||||
          </sl-alert>
 | 
					          </sl-alert>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    `;
 | 
					    `;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private renderContent() {
 | 
					 | 
				
			||||||
    if (!this.alert || !this.org) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const content = this.alert.content();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return html`
 | 
					 | 
				
			||||||
      <strong class="block font-semibold">${content.title}</strong>
 | 
					 | 
				
			||||||
      ${content.detail}
 | 
					 | 
				
			||||||
    `;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Alerts ordered by priority
 | 
					   * Alerts ordered by priority
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
@ -105,7 +77,7 @@ export class OrgStatusBanner extends TailwindElement {
 | 
				
			|||||||
      {
 | 
					      {
 | 
				
			||||||
        test: () =>
 | 
					        test: () =>
 | 
				
			||||||
          !readOnly && !readOnlyOnCancel && !!subscription?.futureCancelDate,
 | 
					          !readOnly && !readOnlyOnCancel && !!subscription?.futureCancelDate,
 | 
				
			||||||
        persist: true,
 | 
					
 | 
				
			||||||
        content: () => {
 | 
					        content: () => {
 | 
				
			||||||
          const daysDiff = differenceInDays(
 | 
					          const daysDiff = differenceInDays(
 | 
				
			||||||
            new Date(),
 | 
					            new Date(),
 | 
				
			||||||
@ -147,7 +119,7 @@ export class OrgStatusBanner extends TailwindElement {
 | 
				
			|||||||
      {
 | 
					      {
 | 
				
			||||||
        test: () =>
 | 
					        test: () =>
 | 
				
			||||||
          !readOnly && readOnlyOnCancel && !!subscription?.futureCancelDate,
 | 
					          !readOnly && readOnlyOnCancel && !!subscription?.futureCancelDate,
 | 
				
			||||||
        persist: true,
 | 
					
 | 
				
			||||||
        content: () => {
 | 
					        content: () => {
 | 
				
			||||||
          const daysDiff = differenceInDays(
 | 
					          const daysDiff = differenceInDays(
 | 
				
			||||||
            new Date(),
 | 
					            new Date(),
 | 
				
			||||||
@ -187,7 +159,7 @@ export class OrgStatusBanner extends TailwindElement {
 | 
				
			|||||||
      {
 | 
					      {
 | 
				
			||||||
        test: () =>
 | 
					        test: () =>
 | 
				
			||||||
          !!readOnly && readOnlyReason === OrgReadOnlyReason.SubscriptionPaused,
 | 
					          !!readOnly && readOnlyReason === OrgReadOnlyReason.SubscriptionPaused,
 | 
				
			||||||
        persist: true,
 | 
					
 | 
				
			||||||
        content: () => ({
 | 
					        content: () => ({
 | 
				
			||||||
          title: msg(str`Your org has been set to read-only mode`),
 | 
					          title: msg(str`Your org has been set to read-only mode`),
 | 
				
			||||||
          detail: msg(
 | 
					          detail: msg(
 | 
				
			||||||
@ -200,7 +172,7 @@ export class OrgStatusBanner extends TailwindElement {
 | 
				
			|||||||
        test: () =>
 | 
					        test: () =>
 | 
				
			||||||
          !!readOnly &&
 | 
					          !!readOnly &&
 | 
				
			||||||
          readOnlyReason === OrgReadOnlyReason.SubscriptionCancelled,
 | 
					          readOnlyReason === OrgReadOnlyReason.SubscriptionCancelled,
 | 
				
			||||||
        persist: true,
 | 
					
 | 
				
			||||||
        content: () => ({
 | 
					        content: () => ({
 | 
				
			||||||
          title: msg(str`This org has been set to read-only mode`),
 | 
					          title: msg(str`This org has been set to read-only mode`),
 | 
				
			||||||
          detail: msg(
 | 
					          detail: msg(
 | 
				
			||||||
@ -210,7 +182,7 @@ export class OrgStatusBanner extends TailwindElement {
 | 
				
			|||||||
      },
 | 
					      },
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        test: () => !!readOnly,
 | 
					        test: () => !!readOnly,
 | 
				
			||||||
        persist: true,
 | 
					
 | 
				
			||||||
        content: () => ({
 | 
					        content: () => ({
 | 
				
			||||||
          title: msg(str`This org has been set to read-only mode`),
 | 
					          title: msg(str`This org has been set to read-only mode`),
 | 
				
			||||||
          detail: msg(`Please contact Browsertrix support to renew your plan.`),
 | 
					          detail: msg(`Please contact Browsertrix support to renew your plan.`),
 | 
				
			||||||
 | 
				
			|||||||
@ -84,7 +84,7 @@ export class Dashboard extends LiteElement {
 | 
				
			|||||||
          () =>
 | 
					          () =>
 | 
				
			||||||
            html` <sl-icon-button
 | 
					            html` <sl-icon-button
 | 
				
			||||||
              href=${`${this.orgBasePath}/settings`}
 | 
					              href=${`${this.orgBasePath}/settings`}
 | 
				
			||||||
              class="text-lg"
 | 
					              class="size-8 text-lg"
 | 
				
			||||||
              name="gear"
 | 
					              name="gear"
 | 
				
			||||||
              label=${msg("Edit org settings")}
 | 
					              label=${msg("Edit org settings")}
 | 
				
			||||||
              @click=${this.navLink}
 | 
					              @click=${this.navLink}
 | 
				
			||||||
@ -355,24 +355,26 @@ export class Dashboard extends LiteElement {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private renderCrawlingMeter(_metrics: Metrics) {
 | 
					  private renderCrawlingMeter(_metrics: Metrics) {
 | 
				
			||||||
 | 
					    if (!this.org) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let quotaSeconds = 0;
 | 
					    let quotaSeconds = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (this.org!.quotas.maxExecMinutesPerMonth) {
 | 
					    if (this.org.quotas.maxExecMinutesPerMonth) {
 | 
				
			||||||
      quotaSeconds = this.org!.quotas.maxExecMinutesPerMonth * 60;
 | 
					      quotaSeconds = this.org.quotas.maxExecMinutesPerMonth * 60;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let quotaSecondsAllTypes = quotaSeconds;
 | 
					    let quotaSecondsAllTypes = quotaSeconds;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let quotaSecondsExtra = 0;
 | 
					    let quotaSecondsExtra = 0;
 | 
				
			||||||
    if (this.org!.extraExecSecondsAvailable) {
 | 
					    if (this.org.extraExecSecondsAvailable) {
 | 
				
			||||||
      quotaSecondsExtra = this.org!.extraExecSecondsAvailable;
 | 
					      quotaSecondsExtra = this.org.extraExecSecondsAvailable;
 | 
				
			||||||
      quotaSecondsAllTypes += this.org!.extraExecSecondsAvailable;
 | 
					      quotaSecondsAllTypes += this.org.extraExecSecondsAvailable;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let quotaSecondsGifted = 0;
 | 
					    let quotaSecondsGifted = 0;
 | 
				
			||||||
    if (this.org!.giftedExecSecondsAvailable) {
 | 
					    if (this.org.giftedExecSecondsAvailable) {
 | 
				
			||||||
      quotaSecondsGifted = this.org!.giftedExecSecondsAvailable;
 | 
					      quotaSecondsGifted = this.org.giftedExecSecondsAvailable;
 | 
				
			||||||
      quotaSecondsAllTypes += this.org!.giftedExecSecondsAvailable;
 | 
					      quotaSecondsAllTypes += this.org.giftedExecSecondsAvailable;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const now = new Date();
 | 
					    const now = new Date();
 | 
				
			||||||
@ -381,8 +383,8 @@ export class Dashboard extends LiteElement {
 | 
				
			|||||||
    const currentPeriod = `${currentYear}-${currentMonth}` as YearMonth;
 | 
					    const currentPeriod = `${currentYear}-${currentMonth}` as YearMonth;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let usageSeconds = 0;
 | 
					    let usageSeconds = 0;
 | 
				
			||||||
    if (this.org!.monthlyExecSeconds) {
 | 
					    if (this.org.monthlyExecSeconds) {
 | 
				
			||||||
      const actualUsage = this.org!.monthlyExecSeconds[currentPeriod];
 | 
					      const actualUsage = this.org.monthlyExecSeconds[currentPeriod];
 | 
				
			||||||
      if (actualUsage) {
 | 
					      if (actualUsage) {
 | 
				
			||||||
        usageSeconds = actualUsage;
 | 
					        usageSeconds = actualUsage;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@ -393,21 +395,21 @@ export class Dashboard extends LiteElement {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let usageSecondsAllTypes = 0;
 | 
					    let usageSecondsAllTypes = 0;
 | 
				
			||||||
    if (this.org!.crawlExecSeconds) {
 | 
					    if (this.org.crawlExecSeconds) {
 | 
				
			||||||
      const actualUsage = this.org!.crawlExecSeconds[currentPeriod];
 | 
					      const actualUsage = this.org.crawlExecSeconds[currentPeriod];
 | 
				
			||||||
      if (actualUsage) {
 | 
					      if (actualUsage) {
 | 
				
			||||||
        usageSecondsAllTypes = actualUsage;
 | 
					        usageSecondsAllTypes = actualUsage;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let usageSecondsExtra = 0;
 | 
					    let usageSecondsExtra = 0;
 | 
				
			||||||
    if (this.org!.extraExecSeconds) {
 | 
					    if (this.org.extraExecSeconds) {
 | 
				
			||||||
      const actualUsageExtra = this.org!.extraExecSeconds[currentPeriod];
 | 
					      const actualUsageExtra = this.org.extraExecSeconds[currentPeriod];
 | 
				
			||||||
      if (actualUsageExtra) {
 | 
					      if (actualUsageExtra) {
 | 
				
			||||||
        usageSecondsExtra = actualUsageExtra;
 | 
					        usageSecondsExtra = actualUsageExtra;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const maxExecSecsExtra = this.org!.quotas.extraExecMinutes * 60;
 | 
					    const maxExecSecsExtra = this.org.quotas.extraExecMinutes * 60;
 | 
				
			||||||
    // Cap usage at quota for display purposes
 | 
					    // Cap usage at quota for display purposes
 | 
				
			||||||
    if (usageSecondsExtra > maxExecSecsExtra) {
 | 
					    if (usageSecondsExtra > maxExecSecsExtra) {
 | 
				
			||||||
      usageSecondsExtra = maxExecSecsExtra;
 | 
					      usageSecondsExtra = maxExecSecsExtra;
 | 
				
			||||||
@ -419,13 +421,13 @@ export class Dashboard extends LiteElement {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let usageSecondsGifted = 0;
 | 
					    let usageSecondsGifted = 0;
 | 
				
			||||||
    if (this.org!.giftedExecSeconds) {
 | 
					    if (this.org.giftedExecSeconds) {
 | 
				
			||||||
      const actualUsageGifted = this.org!.giftedExecSeconds[currentPeriod];
 | 
					      const actualUsageGifted = this.org.giftedExecSeconds[currentPeriod];
 | 
				
			||||||
      if (actualUsageGifted) {
 | 
					      if (actualUsageGifted) {
 | 
				
			||||||
        usageSecondsGifted = actualUsageGifted;
 | 
					        usageSecondsGifted = actualUsageGifted;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const maxExecSecsGifted = this.org!.quotas.giftedExecMinutes * 60;
 | 
					    const maxExecSecsGifted = this.org.quotas.giftedExecMinutes * 60;
 | 
				
			||||||
    // Cap usage at quota for display purposes
 | 
					    // Cap usage at quota for display purposes
 | 
				
			||||||
    if (usageSecondsGifted > maxExecSecsGifted) {
 | 
					    if (usageSecondsGifted > maxExecSecsGifted) {
 | 
				
			||||||
      usageSecondsGifted = maxExecSecsGifted;
 | 
					      usageSecondsGifted = maxExecSecsGifted;
 | 
				
			||||||
@ -447,9 +449,9 @@ export class Dashboard extends LiteElement {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    const hasExtra =
 | 
					    const hasExtra =
 | 
				
			||||||
      usageSecondsExtra ||
 | 
					      usageSecondsExtra ||
 | 
				
			||||||
      this.org!.extraExecSecondsAvailable ||
 | 
					      this.org.extraExecSecondsAvailable ||
 | 
				
			||||||
      usageSecondsGifted ||
 | 
					      usageSecondsGifted ||
 | 
				
			||||||
      this.org!.giftedExecSecondsAvailable;
 | 
					      this.org.giftedExecSecondsAvailable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const renderBar = (
 | 
					    const renderBar = (
 | 
				
			||||||
      /** Time in Seconds */
 | 
					      /** Time in Seconds */
 | 
				
			||||||
@ -502,14 +504,14 @@ export class Dashboard extends LiteElement {
 | 
				
			|||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          `,
 | 
					          `,
 | 
				
			||||||
          () =>
 | 
					          () =>
 | 
				
			||||||
            hasQuota
 | 
					            hasQuota && this.org
 | 
				
			||||||
              ? html`
 | 
					              ? html`
 | 
				
			||||||
                  <span class="inline-flex items-center">
 | 
					                  <span class="inline-flex items-center">
 | 
				
			||||||
                    ${humanizeExecutionSeconds(
 | 
					                    ${humanizeExecutionSeconds(
 | 
				
			||||||
                      quotaSeconds -
 | 
					                      quotaSeconds -
 | 
				
			||||||
                        usageSeconds +
 | 
					                        usageSeconds +
 | 
				
			||||||
                        this.org!.extraExecSecondsAvailable +
 | 
					                        this.org.extraExecSecondsAvailable +
 | 
				
			||||||
                        this.org!.giftedExecSecondsAvailable,
 | 
					                        this.org.giftedExecSecondsAvailable,
 | 
				
			||||||
                      { style: "short", round: "down" },
 | 
					                      { style: "short", round: "down" },
 | 
				
			||||||
                    )}
 | 
					                    )}
 | 
				
			||||||
                    <span class="ml-1">${msg("remaining")}</span>
 | 
					                    <span class="ml-1">${msg("remaining")}</span>
 | 
				
			||||||
@ -519,12 +521,12 @@ export class Dashboard extends LiteElement {
 | 
				
			|||||||
        )}
 | 
					        )}
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      ${when(
 | 
					      ${when(
 | 
				
			||||||
        hasQuota,
 | 
					        hasQuota && this.org,
 | 
				
			||||||
        () => html`
 | 
					        (org) => html`
 | 
				
			||||||
          <div class="mb-2">
 | 
					          <div class="mb-2">
 | 
				
			||||||
            <btrix-meter
 | 
					            <btrix-meter
 | 
				
			||||||
              value=${this.org!.giftedExecSecondsAvailable ||
 | 
					              value=${org.giftedExecSecondsAvailable ||
 | 
				
			||||||
              this.org!.extraExecSecondsAvailable ||
 | 
					              org.extraExecSecondsAvailable ||
 | 
				
			||||||
              isReached
 | 
					              isReached
 | 
				
			||||||
                ? quotaSecondsAllTypes
 | 
					                ? quotaSecondsAllTypes
 | 
				
			||||||
                : usageSeconds}
 | 
					                : usageSeconds}
 | 
				
			||||||
@ -540,9 +542,7 @@ export class Dashboard extends LiteElement {
 | 
				
			|||||||
                  hasExtra ? true : false,
 | 
					                  hasExtra ? true : false,
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
              )}
 | 
					              )}
 | 
				
			||||||
              ${when(
 | 
					              ${when(usageSecondsGifted || org.giftedExecSecondsAvailable, () =>
 | 
				
			||||||
                usageSecondsGifted || this.org!.giftedExecSecondsAvailable,
 | 
					 | 
				
			||||||
                () =>
 | 
					 | 
				
			||||||
                renderBar(
 | 
					                renderBar(
 | 
				
			||||||
                  usageSecondsGifted > quotaSecondsGifted
 | 
					                  usageSecondsGifted > quotaSecondsGifted
 | 
				
			||||||
                    ? quotaSecondsGifted
 | 
					                    ? quotaSecondsGifted
 | 
				
			||||||
@ -552,9 +552,7 @@ export class Dashboard extends LiteElement {
 | 
				
			|||||||
                  "blue",
 | 
					                  "blue",
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
              )}
 | 
					              )}
 | 
				
			||||||
              ${when(
 | 
					              ${when(usageSecondsExtra || org.extraExecSecondsAvailable, () =>
 | 
				
			||||||
                usageSecondsExtra || this.org!.extraExecSecondsAvailable,
 | 
					 | 
				
			||||||
                () =>
 | 
					 | 
				
			||||||
                renderBar(
 | 
					                renderBar(
 | 
				
			||||||
                  usageSecondsExtra > quotaSecondsExtra
 | 
					                  usageSecondsExtra > quotaSecondsExtra
 | 
				
			||||||
                    ? quotaSecondsExtra
 | 
					                    ? quotaSecondsExtra
 | 
				
			||||||
@ -664,13 +662,15 @@ export class Dashboard extends LiteElement {
 | 
				
			|||||||
  `;
 | 
					  `;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private readonly hasMonthlyTime = () =>
 | 
					  private readonly hasMonthlyTime = () =>
 | 
				
			||||||
    Object.keys(this.org!.monthlyExecSeconds!).length;
 | 
					    this.org?.monthlyExecSeconds &&
 | 
				
			||||||
 | 
					    Object.keys(this.org.monthlyExecSeconds).length;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private readonly hasExtraTime = () =>
 | 
					  private readonly hasExtraTime = () =>
 | 
				
			||||||
    Object.keys(this.org!.extraExecSeconds!).length;
 | 
					    this.org?.extraExecSeconds && Object.keys(this.org.extraExecSeconds).length;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private readonly hasGiftedTime = () =>
 | 
					  private readonly hasGiftedTime = () =>
 | 
				
			||||||
    Object.keys(this.org!.giftedExecSeconds!).length;
 | 
					    this.org?.giftedExecSeconds &&
 | 
				
			||||||
 | 
					    Object.keys(this.org.giftedExecSeconds).length;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private renderUsageHistory() {
 | 
					  private renderUsageHistory() {
 | 
				
			||||||
    if (!this.org) return;
 | 
					    if (!this.org) return;
 | 
				
			||||||
@ -750,34 +750,36 @@ export class Dashboard extends LiteElement {
 | 
				
			|||||||
      // Sort latest
 | 
					      // Sort latest
 | 
				
			||||||
      .reverse()
 | 
					      .reverse()
 | 
				
			||||||
      .map(([mY, crawlTime]) => {
 | 
					      .map(([mY, crawlTime]) => {
 | 
				
			||||||
        let monthlySecondsUsed = this.org!.monthlyExecSeconds?.[mY] || 0;
 | 
					        if (!this.org) return [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let monthlySecondsUsed = this.org.monthlyExecSeconds?.[mY] || 0;
 | 
				
			||||||
        let maxMonthlySeconds = 0;
 | 
					        let maxMonthlySeconds = 0;
 | 
				
			||||||
        if (this.org!.quotas.maxExecMinutesPerMonth) {
 | 
					        if (this.org.quotas.maxExecMinutesPerMonth) {
 | 
				
			||||||
          maxMonthlySeconds = this.org!.quotas.maxExecMinutesPerMonth * 60;
 | 
					          maxMonthlySeconds = this.org.quotas.maxExecMinutesPerMonth * 60;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (monthlySecondsUsed > maxMonthlySeconds) {
 | 
					        if (monthlySecondsUsed > maxMonthlySeconds) {
 | 
				
			||||||
          monthlySecondsUsed = maxMonthlySeconds;
 | 
					          monthlySecondsUsed = maxMonthlySeconds;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let extraSecondsUsed = this.org!.extraExecSeconds?.[mY] || 0;
 | 
					        let extraSecondsUsed = this.org.extraExecSeconds?.[mY] || 0;
 | 
				
			||||||
        let maxExtraSeconds = 0;
 | 
					        let maxExtraSeconds = 0;
 | 
				
			||||||
        if (this.org!.quotas.extraExecMinutes) {
 | 
					        if (this.org.quotas.extraExecMinutes) {
 | 
				
			||||||
          maxExtraSeconds = this.org!.quotas.extraExecMinutes * 60;
 | 
					          maxExtraSeconds = this.org.quotas.extraExecMinutes * 60;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (extraSecondsUsed > maxExtraSeconds) {
 | 
					        if (extraSecondsUsed > maxExtraSeconds) {
 | 
				
			||||||
          extraSecondsUsed = maxExtraSeconds;
 | 
					          extraSecondsUsed = maxExtraSeconds;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let giftedSecondsUsed = this.org!.giftedExecSeconds?.[mY] || 0;
 | 
					        let giftedSecondsUsed = this.org.giftedExecSeconds?.[mY] || 0;
 | 
				
			||||||
        let maxGiftedSeconds = 0;
 | 
					        let maxGiftedSeconds = 0;
 | 
				
			||||||
        if (this.org!.quotas.giftedExecMinutes) {
 | 
					        if (this.org.quotas.giftedExecMinutes) {
 | 
				
			||||||
          maxGiftedSeconds = this.org!.quotas.giftedExecMinutes * 60;
 | 
					          maxGiftedSeconds = this.org.quotas.giftedExecMinutes * 60;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (giftedSecondsUsed > maxGiftedSeconds) {
 | 
					        if (giftedSecondsUsed > maxGiftedSeconds) {
 | 
				
			||||||
          giftedSecondsUsed = maxGiftedSeconds;
 | 
					          giftedSecondsUsed = maxGiftedSeconds;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let totalSecondsUsed = this.org!.crawlExecSeconds?.[mY] || 0;
 | 
					        let totalSecondsUsed = this.org.crawlExecSeconds?.[mY] || 0;
 | 
				
			||||||
        const totalMaxQuota =
 | 
					        const totalMaxQuota =
 | 
				
			||||||
          maxMonthlySeconds + maxExtraSeconds + maxGiftedSeconds;
 | 
					          maxMonthlySeconds + maxExtraSeconds + maxGiftedSeconds;
 | 
				
			||||||
        if (totalSecondsUsed > totalMaxQuota) {
 | 
					        if (totalSecondsUsed > totalMaxQuota) {
 | 
				
			||||||
 | 
				
			|||||||
@ -24,6 +24,7 @@ import type { AuthState } from "@/utils/AuthService";
 | 
				
			|||||||
import { DEFAULT_MAX_SCALE } from "@/utils/crawler";
 | 
					import { DEFAULT_MAX_SCALE } from "@/utils/crawler";
 | 
				
			||||||
import LiteElement, { html } from "@/utils/LiteElement";
 | 
					import LiteElement, { html } from "@/utils/LiteElement";
 | 
				
			||||||
import { isAdmin, isCrawler, type OrgData } from "@/utils/orgs";
 | 
					import { isAdmin, isCrawler, type OrgData } from "@/utils/orgs";
 | 
				
			||||||
 | 
					import { AppStateService } from "@/utils/state";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import "./workflow-detail";
 | 
					import "./workflow-detail";
 | 
				
			||||||
import "./workflows-list";
 | 
					import "./workflows-list";
 | 
				
			||||||
@ -113,30 +114,12 @@ export class Org extends LiteElement {
 | 
				
			|||||||
  @property({ type: Number })
 | 
					  @property({ type: Number })
 | 
				
			||||||
  maxScale: number = DEFAULT_MAX_SCALE;
 | 
					  maxScale: number = DEFAULT_MAX_SCALE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @state()
 | 
					 | 
				
			||||||
  private orgStorageQuotaReached = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @state()
 | 
					 | 
				
			||||||
  private showReadOnlyAlert = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @state()
 | 
					 | 
				
			||||||
  private showStorageQuotaAlert = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @state()
 | 
					 | 
				
			||||||
  private orgExecutionMinutesQuotaReached = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @state()
 | 
					 | 
				
			||||||
  private showExecutionMinutesQuotaAlert = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @state()
 | 
					  @state()
 | 
				
			||||||
  private openDialogName?: ResourceName;
 | 
					  private openDialogName?: ResourceName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @state()
 | 
					  @state()
 | 
				
			||||||
  private isCreateDialogVisible = false;
 | 
					  private isCreateDialogVisible = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @state()
 | 
					 | 
				
			||||||
  private org?: OrgData | null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  get userOrg() {
 | 
					  get userOrg() {
 | 
				
			||||||
    if (!this.userInfo) return null;
 | 
					    if (!this.userInfo) return null;
 | 
				
			||||||
    return this.userInfo.orgs.find(({ slug }) => slug === this.slug)!;
 | 
					    return this.userInfo.orgs.find(({ slug }) => slug === this.slug)!;
 | 
				
			||||||
@ -146,6 +129,10 @@ export class Org extends LiteElement {
 | 
				
			|||||||
    return this.userOrg?.id || "";
 | 
					    return this.userOrg?.id || "";
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get org() {
 | 
				
			||||||
 | 
					    return this.appState.org;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  get isAdmin() {
 | 
					  get isAdmin() {
 | 
				
			||||||
    const userOrg = this.userOrg;
 | 
					    const userOrg = this.userOrg;
 | 
				
			||||||
    if (userOrg) return isAdmin(userOrg.role);
 | 
					    if (userOrg) return isAdmin(userOrg.role);
 | 
				
			||||||
@ -168,7 +155,6 @@ export class Org extends LiteElement {
 | 
				
			|||||||
      "btrix-storage-quota-update",
 | 
					      "btrix-storage-quota-update",
 | 
				
			||||||
      this.onStorageQuotaUpdate,
 | 
					      this.onStorageQuotaUpdate,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    this.addEventListener("", () => {});
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  disconnectedCallback() {
 | 
					  disconnectedCallback() {
 | 
				
			||||||
@ -202,6 +188,9 @@ export class Org extends LiteElement {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					    } else if (changedProperties.has("orgTab") && this.orgId) {
 | 
				
			||||||
 | 
					      // Get most up to date org data
 | 
				
			||||||
 | 
					      void this.updateOrg();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (changedProperties.has("openDialogName")) {
 | 
					    if (changedProperties.has("openDialogName")) {
 | 
				
			||||||
      // Sync URL to create dialog
 | 
					      // Sync URL to create dialog
 | 
				
			||||||
@ -231,15 +220,11 @@ export class Org extends LiteElement {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    if (!this.userInfo || !this.orgId) return;
 | 
					    if (!this.userInfo || !this.orgId) return;
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      this.org = await this.getOrg(this.orgId);
 | 
					      const org = await this.getOrg(this.orgId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.showReadOnlyAlert = Boolean(
 | 
					      AppStateService.updateOrg(org);
 | 
				
			||||||
        this.org?.readOnly || this.org?.subscription?.futureCancelDate,
 | 
					    } catch (e) {
 | 
				
			||||||
      );
 | 
					      console.debug(e);
 | 
				
			||||||
 | 
					 | 
				
			||||||
      this.checkStorageQuota();
 | 
					 | 
				
			||||||
      this.checkExecutionMinutesQuota();
 | 
					 | 
				
			||||||
    } catch {
 | 
					 | 
				
			||||||
      this.notify({
 | 
					      this.notify({
 | 
				
			||||||
        message: msg("Sorry, couldn't retrieve organization at this time."),
 | 
					        message: msg("Sorry, couldn't retrieve organization at this time."),
 | 
				
			||||||
        variant: "danger",
 | 
					        variant: "danger",
 | 
				
			||||||
@ -319,7 +304,7 @@ export class Org extends LiteElement {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return html`
 | 
					    return html`
 | 
				
			||||||
      <div class="flex min-h-full flex-col">
 | 
					      <div class="flex min-h-full flex-col">
 | 
				
			||||||
        <btrix-org-status-banner .org=${this.org}></btrix-org-status-banner>
 | 
					        <btrix-org-status-banner></btrix-org-status-banner>
 | 
				
			||||||
        ${this.renderOrgNavBar()}
 | 
					        ${this.renderOrgNavBar()}
 | 
				
			||||||
        <main
 | 
					        <main
 | 
				
			||||||
          class="${noMaxWidth
 | 
					          class="${noMaxWidth
 | 
				
			||||||
@ -523,7 +508,7 @@ export class Org extends LiteElement {
 | 
				
			|||||||
      .authState=${this.authState!}
 | 
					      .authState=${this.authState!}
 | 
				
			||||||
      userId=${this.userInfo!.id}
 | 
					      userId=${this.userInfo!.id}
 | 
				
			||||||
      orgId=${this.orgId}
 | 
					      orgId=${this.orgId}
 | 
				
			||||||
      ?orgStorageQuotaReached=${this.orgStorageQuotaReached}
 | 
					      ?orgStorageQuotaReached=${this.org?.storageQuotaReached}
 | 
				
			||||||
      ?isCrawler=${this.isCrawler}
 | 
					      ?isCrawler=${this.isCrawler}
 | 
				
			||||||
      itemType=${ifDefined(params.itemType || undefined)}
 | 
					      itemType=${ifDefined(params.itemType || undefined)}
 | 
				
			||||||
      @select-new-dialog=${this.onSelectNewDialog}
 | 
					      @select-new-dialog=${this.onSelectNewDialog}
 | 
				
			||||||
@ -543,9 +528,8 @@ export class Org extends LiteElement {
 | 
				
			|||||||
          class="col-span-5 mt-6"
 | 
					          class="col-span-5 mt-6"
 | 
				
			||||||
          .authState=${this.authState!}
 | 
					          .authState=${this.authState!}
 | 
				
			||||||
          orgId=${this.orgId}
 | 
					          orgId=${this.orgId}
 | 
				
			||||||
          ?orgStorageQuotaReached=${this.orgStorageQuotaReached}
 | 
					          ?orgStorageQuotaReached=${this.org?.storageQuotaReached}
 | 
				
			||||||
          ?orgExecutionMinutesQuotaReached=${this
 | 
					          ?orgExecutionMinutesQuotaReached=${this.org?.execMinutesQuotaReached}
 | 
				
			||||||
            .orgExecutionMinutesQuotaReached}
 | 
					 | 
				
			||||||
          workflowId=${workflowId}
 | 
					          workflowId=${workflowId}
 | 
				
			||||||
          openDialogName=${this.viewStateData?.dialog}
 | 
					          openDialogName=${this.viewStateData?.dialog}
 | 
				
			||||||
          ?isEditing=${isEditing}
 | 
					          ?isEditing=${isEditing}
 | 
				
			||||||
@ -566,8 +550,8 @@ export class Org extends LiteElement {
 | 
				
			|||||||
        .initialWorkflow=${workflow}
 | 
					        .initialWorkflow=${workflow}
 | 
				
			||||||
        .initialSeeds=${seeds}
 | 
					        .initialSeeds=${seeds}
 | 
				
			||||||
        jobType=${ifDefined(params.jobType)}
 | 
					        jobType=${ifDefined(params.jobType)}
 | 
				
			||||||
        ?orgStorageQuotaReached=${this.orgStorageQuotaReached}
 | 
					        ?orgStorageQuotaReached=${this.org?.storageQuotaReached}
 | 
				
			||||||
        ?orgExecutionMinutesQuotaReached=${this.orgExecutionMinutesQuotaReached}
 | 
					        ?orgExecutionMinutesQuotaReached=${this.org?.execMinutesQuotaReached}
 | 
				
			||||||
        @select-new-dialog=${this.onSelectNewDialog}
 | 
					        @select-new-dialog=${this.onSelectNewDialog}
 | 
				
			||||||
      ></btrix-workflows-new>`;
 | 
					      ></btrix-workflows-new>`;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -575,8 +559,8 @@ export class Org extends LiteElement {
 | 
				
			|||||||
    return html`<btrix-workflows-list
 | 
					    return html`<btrix-workflows-list
 | 
				
			||||||
      .authState=${this.authState!}
 | 
					      .authState=${this.authState!}
 | 
				
			||||||
      orgId=${this.orgId}
 | 
					      orgId=${this.orgId}
 | 
				
			||||||
      ?orgStorageQuotaReached=${this.orgStorageQuotaReached}
 | 
					      ?orgStorageQuotaReached=${this.org?.storageQuotaReached}
 | 
				
			||||||
      ?orgExecutionMinutesQuotaReached=${this.orgExecutionMinutesQuotaReached}
 | 
					      ?orgExecutionMinutesQuotaReached=${this.org?.execMinutesQuotaReached}
 | 
				
			||||||
      userId=${this.userInfo!.id}
 | 
					      userId=${this.userInfo!.id}
 | 
				
			||||||
      ?isCrawler=${this.isCrawler}
 | 
					      ?isCrawler=${this.isCrawler}
 | 
				
			||||||
      @select-new-dialog=${this.onSelectNewDialog}
 | 
					      @select-new-dialog=${this.onSelectNewDialog}
 | 
				
			||||||
@ -686,22 +670,26 @@ export class Org extends LiteElement {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  private async onStorageQuotaUpdate(e: CustomEvent<QuotaUpdateDetail>) {
 | 
					  private async onStorageQuotaUpdate(e: CustomEvent<QuotaUpdateDetail>) {
 | 
				
			||||||
    e.stopPropagation();
 | 
					    e.stopPropagation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const { reached } = e.detail;
 | 
					    const { reached } = e.detail;
 | 
				
			||||||
    this.orgStorageQuotaReached = reached;
 | 
					
 | 
				
			||||||
    if (reached) {
 | 
					    AppStateService.partialUpdateOrg({
 | 
				
			||||||
      this.showStorageQuotaAlert = true;
 | 
					      id: this.orgId,
 | 
				
			||||||
    }
 | 
					      storageQuotaReached: reached,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private async onExecutionMinutesQuotaUpdate(
 | 
					  private async onExecutionMinutesQuotaUpdate(
 | 
				
			||||||
    e: CustomEvent<QuotaUpdateDetail>,
 | 
					    e: CustomEvent<QuotaUpdateDetail>,
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    e.stopPropagation();
 | 
					    e.stopPropagation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const { reached } = e.detail;
 | 
					    const { reached } = e.detail;
 | 
				
			||||||
    this.orgExecutionMinutesQuotaReached = reached;
 | 
					
 | 
				
			||||||
    if (reached) {
 | 
					    AppStateService.partialUpdateOrg({
 | 
				
			||||||
      this.showExecutionMinutesQuotaAlert = true;
 | 
					      id: this.orgId,
 | 
				
			||||||
    }
 | 
					      execMinutesQuotaReached: reached,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private async onUserRoleChange(e: UserRoleChangeEvent) {
 | 
					  private async onUserRoleChange(e: UserRoleChangeEvent) {
 | 
				
			||||||
@ -723,7 +711,9 @@ export class Org extends LiteElement {
 | 
				
			|||||||
        variant: "success",
 | 
					        variant: "success",
 | 
				
			||||||
        icon: "check2-circle",
 | 
					        icon: "check2-circle",
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
      this.org = await this.getOrg(this.orgId);
 | 
					      const org = await this.getOrg(this.orgId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      AppStateService.updateOrg(org);
 | 
				
			||||||
    } catch (e) {
 | 
					    } catch (e) {
 | 
				
			||||||
      console.debug(e);
 | 
					      console.debug(e);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -742,14 +732,14 @@ export class Org extends LiteElement {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private async removeMember(member: Member) {
 | 
					  private async removeMember(member: Member) {
 | 
				
			||||||
    if (!this.org) return;
 | 
					    const org = this.org;
 | 
				
			||||||
 | 
					    if (!org) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const isSelf = member.email === this.userInfo!.email;
 | 
					    const isSelf = member.email === this.userInfo!.email;
 | 
				
			||||||
    if (
 | 
					    if (
 | 
				
			||||||
      isSelf &&
 | 
					      isSelf &&
 | 
				
			||||||
      !window.confirm(
 | 
					      !window.confirm(
 | 
				
			||||||
        msg(
 | 
					        msg(str`Are you sure you want to remove yourself from ${org.name}?`),
 | 
				
			||||||
          str`Are you sure you want to remove yourself from ${this.org.name}?`,
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
      )
 | 
					      )
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
@ -766,7 +756,7 @@ export class Org extends LiteElement {
 | 
				
			|||||||
      this.notify({
 | 
					      this.notify({
 | 
				
			||||||
        message: msg(
 | 
					        message: msg(
 | 
				
			||||||
          str`Successfully removed ${member.name || member.email} from ${
 | 
					          str`Successfully removed ${member.name || member.email} from ${
 | 
				
			||||||
            this.org.name
 | 
					            org.name
 | 
				
			||||||
          }.`,
 | 
					          }.`,
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        variant: "success",
 | 
					        variant: "success",
 | 
				
			||||||
@ -776,7 +766,9 @@ export class Org extends LiteElement {
 | 
				
			|||||||
        // FIXME better UX, this is the only page currently that doesn't require org...
 | 
					        // FIXME better UX, this is the only page currently that doesn't require org...
 | 
				
			||||||
        this.navTo("/account/settings");
 | 
					        this.navTo("/account/settings");
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        this.org = await this.getOrg(this.orgId);
 | 
					        const org = await this.getOrg(this.orgId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        AppStateService.updateOrg(org);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    } catch (e) {
 | 
					    } catch (e) {
 | 
				
			||||||
      console.debug(e);
 | 
					      console.debug(e);
 | 
				
			||||||
@ -794,14 +786,4 @@ export class Org extends LiteElement {
 | 
				
			|||||||
      });
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					 | 
				
			||||||
  checkStorageQuota() {
 | 
					 | 
				
			||||||
    this.orgStorageQuotaReached = !!this.org?.storageQuotaReached;
 | 
					 | 
				
			||||||
    this.showStorageQuotaAlert = this.orgStorageQuotaReached;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  checkExecutionMinutesQuota() {
 | 
					 | 
				
			||||||
    this.orgExecutionMinutesQuotaReached = !!this.org?.execMinutesQuotaReached;
 | 
					 | 
				
			||||||
    this.showExecutionMinutesQuotaAlert = this.orgExecutionMinutesQuotaReached;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -6,6 +6,7 @@ import { locked, options, use } from "lit-shared-state";
 | 
				
			|||||||
import { persist } from "./persist";
 | 
					import { persist } from "./persist";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import type { AppSettings } from "@/types/app";
 | 
					import type { AppSettings } from "@/types/app";
 | 
				
			||||||
 | 
					import type { OrgData } from "@/types/org";
 | 
				
			||||||
import type { CurrentUser } from "@/types/user";
 | 
					import type { CurrentUser } from "@/types/user";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export { use };
 | 
					export { use };
 | 
				
			||||||
@ -20,6 +21,7 @@ type SlugLookup = Record<string, string>;
 | 
				
			|||||||
class AppState {
 | 
					class AppState {
 | 
				
			||||||
  settings: AppSettings | null = null;
 | 
					  settings: AppSettings | null = null;
 | 
				
			||||||
  userInfo: CurrentUser | null = null;
 | 
					  userInfo: CurrentUser | null = null;
 | 
				
			||||||
 | 
					  org: OrgData | null | undefined = undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @options(persist(window.localStorage))
 | 
					  @options(persist(window.localStorage))
 | 
				
			||||||
  orgSlug: string | null = null;
 | 
					  orgSlug: string | null = null;
 | 
				
			||||||
@ -57,6 +59,23 @@ export class AppStateService {
 | 
				
			|||||||
      appState.userInfo = userInfo;
 | 
					      appState.userInfo = userInfo;
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					  static updateOrg = (org: AppState["org"]) => {
 | 
				
			||||||
 | 
					    unlock(() => {
 | 
				
			||||||
 | 
					      appState.org = org;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  static partialUpdateOrg = (org: { id: string } & Partial<OrgData>) => {
 | 
				
			||||||
 | 
					    unlock(() => {
 | 
				
			||||||
 | 
					      if (org.id && appState.org?.id === org.id) {
 | 
				
			||||||
 | 
					        appState.org = {
 | 
				
			||||||
 | 
					          ...appState.org,
 | 
				
			||||||
 | 
					          ...org,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        console.warn("no matching org in app state");
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
  static updateOrgSlug = (orgSlug: AppState["orgSlug"]) => {
 | 
					  static updateOrgSlug = (orgSlug: AppState["orgSlug"]) => {
 | 
				
			||||||
    unlock(() => {
 | 
					    unlock(() => {
 | 
				
			||||||
      appState.orgSlug = orgSlug;
 | 
					      appState.orgSlug = orgSlug;
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user