remove: icon-x button in a read-only view.

This commit is contained in:
Darshan
2024-12-02 14:56:43 +05:30
parent 762f19e170
commit 869813ed3e
8 changed files with 497 additions and 416 deletions
+5 -2
View File
@@ -7,12 +7,15 @@
export let noContent = false;
export let isInfo = false;
export let gap = 16;
export let style = null;
export let wrapperStyle = null;
</script>
<li class="collapsible-item" class:is-info={isInfo}>
{#if noContent}
<div class="collapsible-wrapper">
<div class={`collapsible-button u-gap-${gap}`}>
<div class="collapsible-wrapper" style={wrapperStyle}>
<div class={`collapsible-button u-gap-${gap}`} {style}>
<slot />
</div>
</div>
@@ -130,9 +130,7 @@
<BillingAddress billingAddress={data?.billingAddress} />
<TaxId />
<BudgetCap />
{#if $organization?.billingPlan !== BillingPlan.FREE && !!$organization?.billingBudget}
<BudgetAlert />
{/if}
<BudgetAlert />
<AvailableCredit />
</Container>
@@ -90,7 +90,9 @@
</script>
<CardGrid hideFooter={$organization?.billingPlan !== BillingPlan.FREE}>
<Heading tag="h2" size="6">Available credit</Heading>
<Heading tag="h2" size="6">
{$organization?.billingPlan === BillingPlan.FREE ? 'Credit' : 'Available credit'}
</Heading>
<p class="text">Appwrite credit will automatically be applied to your next invoice.</p>
<svelte:fragment slot="aside">
@@ -1,8 +1,9 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { CardGrid, Heading } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Alert, CardGrid, Heading } from '$lib/components';
import { BillingPlan, Dependencies } from '$lib/constants';
import { upgradeURL } from '$lib/stores/billing';
import { Button, Form, FormList, InputSelectSearch } from '$lib/elements/forms';
import {
Table,
@@ -85,58 +86,87 @@
specified budget cap. You can set a maximum of 3 alerts.
</p>
<svelte:fragment slot="aside">
<FormList>
<div class="u-flex u-gap-16">
<InputSelectSearch
label="Percentage (%) of budget cap"
placeholder="Select a percentage"
id="alerts"
{options}
bind:search
bind:value={selectedAlert}
on:select={() => (search = selectedAlert.toString())} />
<div style="align-self: flex-end">
<Button
secondary
disabled={alerts.length > 2 || (!search && !selectedAlert)}
on:click={addAlert}>
Add alert
</Button>
{#if $organization?.billingPlan === BillingPlan.FREE}
<Alert type="info">
<svelte:fragment slot="title"
>Budget alerts are a Pro plan feature
</svelte:fragment>
Upgrade to a Pro plan to set a budget alerts for your organization. For more information
on what you can do with a Pro plan,
<a
class="link"
href="https://appwrite.io/pricing"
target="_blank"
rel="noopener noreferrer">view our pricing guide.</a>
</Alert>
{:else}
<FormList>
<div class="u-flex u-gap-16">
<InputSelectSearch
label="Percentage (%) of budget cap"
placeholder="Select a percentage"
id="alerts"
{options}
bind:search
bind:value={selectedAlert}
on:select={() => (search = selectedAlert.toString())} />
<div style="align-self: flex-end">
<Button
secondary
disabled={alerts.length > 2 || (!search && !selectedAlert)}
on:click={addAlert}>
Add alert
</Button>
</div>
</div>
</div>
</FormList>
</FormList>
{#if alerts.length}
<Table noMargin noStyles transparent>
<TableHeader>
<TableCellHead>Alert at budget cap %</TableCellHead>
<TableCellHead width={30} />
</TableHeader>
<TableBody>
{#each alerts.sort() as alert}
<TableRow>
<TableCellText title="Percentage">
{alert}%
</TableCellText>
<TableCell>
<Button
text
round
ariaLabel="remove alert"
on:click={() =>
(alerts = alerts.filter((a) => a !== alert))}>
<span class="icon-x" aria-hidden="true" />
</Button>
</TableCell>
</TableRow>
{/each}
</TableBody>
</Table>
{#if alerts.length}
<Table noMargin noStyles transparent>
<TableHeader>
<TableCellHead>Alert at budget cap %</TableCellHead>
<TableCellHead width={30} />
</TableHeader>
<TableBody>
{#each alerts.sort() as alert}
<TableRow>
<TableCellText title="Percentage">
{alert}%
</TableCellText>
<TableCell>
<Button
text
round
ariaLabel="remove alert"
on:click={() =>
(alerts = alerts.filter((a) => a !== alert))}>
<span class="icon-x" aria-hidden="true" />
</Button>
</TableCell>
</TableRow>
{/each}
</TableBody>
</Table>
{/if}
{/if}
</svelte:fragment>
<svelte:fragment slot="actions">
<Button disabled={isButtonDisabled} submit>Update</Button>
{#if $organization?.billingPlan === BillingPlan.FREE}
<Button
secondary
href={$upgradeURL}
on:click={() => {
trackEvent('click_organization_upgrade', {
from: 'button',
source: 'billing_alerts_card'
});
}}
>Upgrade to Pro
</Button>
{:else}
<Button disabled={isButtonDisabled} submit>Update</Button>
{/if}
</svelte:fragment>
</CardGrid>
</Form>
@@ -53,10 +53,10 @@
<Heading tag="h2" size="6">Budget cap</Heading>
<p class="text">
Restrict your resource usage by setting a budget cap. <button
on:click={() => ($showUsageRatesModal = true)}
type="button"
class="link">Learn more about usage rates.</button>
Restrict your resource usage by setting a budget cap. Cap usage is reset at the
beginning of each billing cycle.
<button class="link" type="button" on:click={() => ($showUsageRatesModal = true)}
>Learn more about usage rates.</button>
</p>
<svelte:fragment slot="aside">
{#if $organization?.billingPlan === BillingPlan.FREE}
@@ -101,7 +101,9 @@
from: 'button',
source: 'billing_budget_cap'
});
}}>Upgrade to Pro</Button>
}}
>Upgrade to Pro
</Button>
{:else}
<Button disabled={$organization?.billingBudget === budget} submit>Update</Button>
{/if}
@@ -29,6 +29,7 @@
import { trackEvent } from '$lib/actions/analytics';
import { selectedInvoice, showRetryModal } from './store';
import { organization } from '$lib/stores/organization';
import { BillingPlan } from '$lib/constants';
let showDropdown = [];
let showFailedError = false;
@@ -64,135 +65,144 @@
}
</script>
<CardGrid>
<Heading tag="h2" size="6">Payment history</Heading>
{#if $organization?.billingPlan === BillingPlan.FREE && invoiceList.total > 0}
<CardGrid>
<Heading tag="h2" size="6">Payment history</Heading>
<p class="text">
Transaction history for this organization. Download invoices for more details about your
payments.
</p>
<svelte:fragment slot="aside">
{#if invoiceList.total > 0}
<TableScroll noMargin transparent noStyles>
<TableHeader>
<TableCellHead width={100}>Due Date</TableCellHead>
<TableCellHead width={80}>Status</TableCellHead>
<TableCellHead width={100}>Amount Due</TableCellHead>
<TableCellHead width={40} />
</TableHeader>
<TableBody>
{#each invoiceList?.invoices as invoice, i}
{@const status = invoice.status}
<TableRow>
<TableCellText title="date">
{toLocaleDate(invoice.dueAt)}
</TableCellText>
<TableCell title="status">
{#if invoice?.lastError}
<DropList bind:show={showFailedError}>
<p class="text">
Transaction history for this organization. Download invoices for more details about your
payments.
</p>
<svelte:fragment slot="aside">
{#if invoiceList.total > 0}
<TableScroll noMargin transparent noStyles>
<TableHeader>
<TableCellHead width={100}>Due Date</TableCellHead>
<TableCellHead width={80}>Status</TableCellHead>
<TableCellHead width={100}>Amount Due</TableCellHead>
<TableCellHead width={40} />
</TableHeader>
<TableBody>
{#each invoiceList?.invoices as invoice, i}
{@const status = invoice.status}
<TableRow>
<TableCellText title="date">
{toLocaleDate(invoice.dueAt)}
</TableCellText>
<TableCell title="status">
{#if invoice?.lastError}
<DropList bind:show={showFailedError}>
<Pill
danger={status === 'overdue' ||
status === 'failed' ||
status === 'requires_authentication'}
success={status === 'paid' ||
status === 'succeeded'}
warning={status === 'pending'}
on:click={() => (showFailedError = true)}
button>
{status === 'requires_authentication'
? 'failed'
: status}
</Pill>
<svelte:fragment slot="list">
<li>
The scheduled payment has failed.
<Button
link
on:click={() => {
retryPayment(invoice);
showFailedError = false;
}}
>Try again
</Button>
.
</li>
</svelte:fragment>
</DropList>
{:else}
<Pill
danger={status === 'overdue' ||
status === 'failed' ||
status === 'requires_authentication'}
success={status === 'paid' || status === 'succeeded'}
warning={status === 'pending'}
on:click={() => (showFailedError = true)}
button>
warning={status === 'pending'}>
{status === 'requires_authentication'
? 'failed'
: status}
</Pill>
<svelte:fragment slot="list">
<li>
The scheduled payment has failed. <Button
link
on:click={() => {
retryPayment(invoice);
showFailedError = false;
}}>Try again</Button
>.
</li>
</svelte:fragment>
</DropList>
{:else}
<Pill
danger={status === 'overdue' ||
status === 'failed' ||
status === 'requires_authentication'}
success={status === 'paid' || status === 'succeeded'}
warning={status === 'pending'}>
{status === 'requires_authentication' ? 'failed' : status}
</Pill>
{/if}
</TableCell>
<TableCellText title="due">
{formatCurrency(invoice.grossAmount)}
</TableCellText>
<TableCell showOverflow right>
<DropList
bind:show={showDropdown[i]}
placement="bottom-start"
noArrow>
<Button
round
text
noMargin
ariaLabel="More options"
on:click={() => {
showDropdown[i] = !showDropdown[i];
}}>
<span class="icon-dots-horizontal" aria-hidden="true" />
</Button>
<svelte:fragment slot="list">
<DropListLink
icon="external-link"
external
href={`${endpoint}/organizations/${$page.params.organization}/invoices/${invoice.$id}/view`}
on:click={() => (showDropdown[i] = !showDropdown[i])}
event="view_invoice">
View invoice
</DropListLink>
<DropListLink
icon="download"
href={`${endpoint}/organizations/${$page.params.organization}/invoices/${invoice.$id}/download`}
{/if}
</TableCell>
<TableCellText title="due">
{formatCurrency(invoice.grossAmount)}
</TableCellText>
<TableCell showOverflow right>
<DropList
bind:show={showDropdown[i]}
placement="bottom-start"
noArrow>
<Button
round
text
noMargin
ariaLabel="More options"
on:click={() => {
showDropdown[i] = !showDropdown[i];
}}
event="download_invoice">
Download PDF
</DropListLink>
{#if status === 'overdue' || status === 'failed'}
<DropListItem
icon="refresh"
}}>
<span class="icon-dots-horizontal" aria-hidden="true" />
</Button>
<svelte:fragment slot="list">
<DropListLink
icon="external-link"
external
href={`${endpoint}/organizations/${$page.params.organization}/invoices/${invoice.$id}/view`}
on:click={() =>
(showDropdown[i] = !showDropdown[i])}
event="view_invoice">
View invoice
</DropListLink>
<DropListLink
icon="download"
href={`${endpoint}/organizations/${$page.params.organization}/invoices/${invoice.$id}/download`}
on:click={() => {
retryPayment(invoice);
showDropdown[i] = !showDropdown[i];
trackEvent(`click_retry_payment`, {
from: 'button',
source: 'billing_invoice_menu'
});
}}>
Retry payment
</DropListItem>
{/if}
</svelte:fragment>
</DropList>
</TableCell>
</TableRow>
{/each}
</TableBody>
</TableScroll>
<div class="u-flex u-main-space-between">
<p class="text">Total results: {invoiceList?.total ?? 0}</p>
<PaginationInline {limit} bind:offset sum={invoiceList?.total ?? 0} hidePages />
</div>
{:else}
<EmptySearch hidePagination>
<p class="text u-text-center">
You have no payment history. After you receive your first invoice, you'll see it
here.
</p>
</EmptySearch>
{/if}
</svelte:fragment>
</CardGrid>
}}
event="download_invoice">
Download PDF
</DropListLink>
{#if status === 'overdue' || status === 'failed'}
<DropListItem
icon="refresh"
on:click={() => {
retryPayment(invoice);
showDropdown[i] = !showDropdown[i];
trackEvent(`click_retry_payment`, {
from: 'button',
source: 'billing_invoice_menu'
});
}}>
Retry payment
</DropListItem>
{/if}
</svelte:fragment>
</DropList>
</TableCell>
</TableRow>
{/each}
</TableBody>
</TableScroll>
<div class="u-flex u-main-space-between">
<p class="text">Total results: {invoiceList?.total ?? 0}</p>
<PaginationInline {limit} bind:offset sum={invoiceList?.total ?? 0} hidePages />
</div>
{:else}
<EmptySearch hidePagination>
<p class="text u-text-center">
You have no payment history. After you receive your first invoice, you'll
see it here.
</p>
</EmptySearch>
{/if}
</svelte:fragment>
</CardGrid>
{/if}
@@ -99,7 +99,6 @@
<p class="text">View or update your organization payment methods here.</p>
<svelte:fragment slot="aside">
<div class="u-flex u-flex-vertical u-gap-8">
<h4 class="u-bold body-text-2">Default</h4>
{#if $organization?.paymentMethodId}
<CreditCardInfo isBox paymentMethod={defaultPaymentMethod}>
<DropList bind:show={showDropdown} placement="bottom-start" noArrow>
@@ -196,112 +195,115 @@
</article>
{/if}
</div>
<div class="u-flex u-flex-vertical u-gap-8">
<h4 class="u-bold body-text-2">Backup</h4>
{#if $organization?.backupPaymentMethodId}
<CreditCardInfo isBox paymentMethod={backupPaymentMethod}>
<DropList bind:show={showDropdownBackup} placement="bottom-start" noArrow>
<Button
round
text
ariaLabel="More options"
on:click={() => {
showDropdownBackup = !showDropdownBackup;
}}>
<span class="icon-dots-horizontal" aria-hidden="true" />
</Button>
<svelte:fragment slot="list">
{#if backupPaymentMethod.userId === $user.$id}
{#if $organization?.billingPlan !== BillingPlan.FREE}
<!-- TODO: Add a simple button if a backup payment doesn't exist. -->
<div class="u-flex u-flex-vertical u-gap-8">
<h4 class="u-bold body-text-2">Backup</h4>
{#if $organization?.backupPaymentMethodId}
<CreditCardInfo isBox paymentMethod={backupPaymentMethod}>
<DropList bind:show={showDropdownBackup} placement="bottom-start" noArrow>
<Button
round
text
ariaLabel="More options"
on:click={() => {
showDropdownBackup = !showDropdownBackup;
}}>
<span class="icon-dots-horizontal" aria-hidden="true" />
</Button>
<svelte:fragment slot="list">
{#if backupPaymentMethod.userId === $user.$id}
<DropListItem
icon="pencil"
on:click={() => {
showEdit = true;
isSelectedBackup = true;
showDropdownBackup = false;
}}>
Edit
</DropListItem>
{/if}
<DropListItem
icon="pencil"
icon="switch-horizontal"
on:click={() => {
showEdit = true;
showReplace = true;
isSelectedBackup = true;
showDropdownBackup = false;
}}>
Edit
Replace
</DropListItem>
{/if}
<DropListItem
icon="switch-horizontal"
on:click={() => {
showReplace = true;
isSelectedBackup = true;
showDropdownBackup = false;
}}>
Replace
</DropListItem>
<DropListItem
icon="trash"
on:click={() => {
showDelete = true;
isSelectedBackup = true;
showDropdownBackup = false;
}}>
Delete
</DropListItem>
</svelte:fragment>
</DropList>
</CreditCardInfo>
{:else}
{@const filteredPaymentMethods = $paymentMethods.paymentMethods.filter(
(o) => !!o.last4 && o.$id !== $organization?.paymentMethodId
)}
<article class="card u-grid u-cross-center u-width-full-line dashed">
<div class="u-flex u-cross-center u-flex-vertical u-main-center u-flex">
<div class="common-section">
<DropList bind:show={showDropdownBackup} placement="bottom-start">
<Button
secondary
round
<DropListItem
icon="trash"
on:click={() => {
if (filteredPaymentMethods.length) {
showDropdownBackup = !showDropdownBackup;
} else {
isSelectedBackup = true;
showPayment = true;
}
showDelete = true;
isSelectedBackup = true;
showDropdownBackup = false;
}}>
<i class="icon-plus" />
</Button>
<svelte:fragment slot="list">
{#if $paymentMethods.total}
{#each filteredPaymentMethods as paymentMethod}
<DropListItem
on:click={() => {
showDropdownBackup = true;
addBackupPaymentMethod(paymentMethod?.$id);
}}>
<span class="u-flex u-cross-center u-gap-8">
<p class="text">
Card ending in {paymentMethod.last4}
</p>
<CreditCardBrandImage
brand={paymentMethod?.brand} />
</span>
</DropListItem>
{/each}
{/if}
<DropListItem on:click={() => (showPayment = true)}>
Add new payment method
</DropListItem>
</svelte:fragment>
</DropList>
Delete
</DropListItem>
</svelte:fragment>
</DropList>
</CreditCardInfo>
{:else}
{@const filteredPaymentMethods = $paymentMethods.paymentMethods.filter(
(o) => !!o.last4 && o.$id !== $organization?.paymentMethodId
)}
<article class="card u-grid u-cross-center u-width-full-line dashed">
<div class="u-flex u-cross-center u-flex-vertical u-main-center u-flex">
<div class="common-section">
<DropList bind:show={showDropdownBackup} placement="bottom-start">
<Button
secondary
round
on:click={() => {
if (filteredPaymentMethods.length) {
showDropdownBackup = !showDropdownBackup;
} else {
isSelectedBackup = true;
showPayment = true;
}
}}>
<i class="icon-plus" />
</Button>
<svelte:fragment slot="list">
{#if $paymentMethods.total}
{#each filteredPaymentMethods as paymentMethod}
<DropListItem
on:click={() => {
showDropdownBackup = true;
addBackupPaymentMethod(paymentMethod?.$id);
}}>
<span class="u-flex u-cross-center u-gap-8">
<p class="text">
Card ending in {paymentMethod.last4}
</p>
<CreditCardBrandImage
brand={paymentMethod?.brand} />
</span>
</DropListItem>
{/each}
{/if}
<DropListItem on:click={() => (showPayment = true)}>
Add new payment method
</DropListItem>
</svelte:fragment>
</DropList>
</div>
<div class="common-section u-flex u-cross-center u-gap-4">
<span class="text"> Add a backup payment method </span>
<span
class="icon-info"
style="font-size: var(--icon-size-small)"
use:tooltip={{
content:
'When your default payment method fails, a backup method will be used to pay your invoice automatically'
}} />
</div>
</div>
<div class="common-section u-flex u-cross-center u-gap-4">
<span class="text"> Add a backup payment method </span>
<span
class="icon-info"
style="font-size: var(--icon-size-small)"
use:tooltip={{
content:
'When your default payment method fails, a backup method will be used to pay your invoice automatically'
}} />
</div>
</div>
</article>
{/if}
</div>
</article>
{/if}
</div>
{/if}
</svelte:fragment>
</CardGrid>
@@ -1,6 +1,6 @@
<script lang="ts">
import { base } from '$app/paths';
import { CardGrid, Collapsible, CollapsibleItem, Heading } from '$lib/components';
import { Card, CardGrid, Collapsible, CollapsibleItem, Heading } from '$lib/components';
import { Button } from '$lib/elements/forms';
import { toLocaleDate } from '$lib/helpers/date';
import { plansInfo, tierToPlan, upgradeURL } from '$lib/stores/billing';
@@ -42,148 +42,157 @@
$organization?.billingNextInvoiceDate
)}
</p>
<Collapsible>
<CollapsibleItem noContent>
<span class="body-text-2">
{tierToPlan($organization?.billingPlan)?.name} plan</span>
<div class="body-text-2 u-margin-inline-start-auto">
{isTrial || $organization?.billingPlan === BillingPlan.GITHUB_EDUCATION
? formatCurrency(0)
: currentPlan
? formatCurrency(currentPlan?.price)
: ''}
</div>
</CollapsibleItem>
{#if $organization?.billingPlan !== BillingPlan.FREE && $organization?.billingPlan !== BillingPlan.GITHUB_EDUCATION && extraUsage > 0}
<CollapsibleItem isInfo gap={8}>
<svelte:fragment slot="beforetitle">
<span class="body-text-2"><b>Add-ons</b></span><span class="inline-tag"
>{extraMembers ? extraAddons + 1 : extraAddons}</span>
<div class="icon">
<span class="icon-cheveron-down" aria-hidden="true"></span>
</div>
</svelte:fragment>
<svelte:fragment slot="end">
<div class="body-text-2 u-margin-inline-start-auto">
<b>
{formatCurrency(extraUsage >= 0 ? extraUsage : 0)}
</b>
</div>
</svelte:fragment>
<ul>
{#if extraMembers}
<li class="u-flex-vertical u-gap-4 u-padding-block-8">
<div class="u-flex u-gap-4">
<h5 class="body-text-2 u-stretch">Additional members</h5>
<div>
{formatCurrency(
extraMembers *
(currentPlan?.addons?.member?.price ?? 0)
)}
</div>
</div>
<div class="u-flex u-gap-4">
<h5 class="body-text-2 u-stretch u-color-text-offline">
{extraMembers}
</h5>
</div>
</li>
{/if}
{#if currentInvoice?.usage}
{#each currentInvoice.usage as excess, i}
<li
class="u-flex-vertical u-gap-4 {extraMembers
? 'u-padding-block-8'
: 'u-padding-block-start-8'}"
class:u-sep-block-start={i > 0 || extraMembers}>
{#if ['storage', 'bandwidth'].includes(excess.name)}
{@const excessValue = humanFileSize(excess.value)}
<div class="u-flex u-main-space-between">
<h5 class="body-text-2 u-stretch u-capitalize">
{excess.name}
</h5>
{formatCurrency(excess.amount)}
</div>
<h5
class="body-text-2 u-stretch u-color-text-offline"
title={formatNumberWithCommas(excess.value ?? 0) +
'bytes'}>
{excessValue.value ?? 0}{excessValue.unit}
</h5>
{/if}
{#if ['users', 'executions'].includes(excess.name)}
<div class="u-flex u-main-space-between">
<h5 class="body-text-2 u-stretch u-capitalize">
{excess.name}
</h5>
{formatCurrency(excess.amount)}
</div>
<h5
class="body-text-2 u-stretch u-color-text-offline"
title={formatNumberWithCommas(excess.value)}>
{abbreviateNumber(excess.value)}
</h5>
{/if}
</li>
{/each}
{/if}
</ul>
</CollapsibleItem>
{/if}
{#if $organization?.billingPlan !== BillingPlan.FREE && availableCredit > 0}
<CollapsibleItem noContent gap={4}>
<span class="body-text-2 u-flex u-cross-center u-gap-2"
><svg
width="16"
height="17"
viewBox="0 0 16 17"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M14.166 7.93434C14.4784 8.24676 14.4784 8.75329 14.166 9.06571L8.56603 14.6657C8.25361 14.9781 7.74708 14.9781 7.43466 14.6657L1.83466 9.06571C1.67842 8.90947 1.60032 8.70469 1.60034 8.49992V4.50002C1.60034 3.17454 2.67486 2.10002 4.00034 2.10002H8.00056C8.20523 2.10008 8.40987 2.17818 8.56603 2.33434L14.166 7.93434ZM4.00034 5.30002C4.44217 5.30002 4.80034 4.94185 4.80034 4.50002C4.80034 4.05819 4.44217 3.70002 4.00034 3.70002C3.55851 3.70002 3.20034 4.05819 3.20034 4.50002C3.20034 4.94185 3.55851 5.30002 4.00034 5.30002Z"
fill="#00BC5D" />
</svg>
Credits to be applied</span>
<div
class="body-text-2 u-margin-inline-start-auto"
style="color: var(--web-green-500, #10B981)">
-{formatCurrency(
Math.min(availableCredit, currentInvoice?.amount ?? 0)
)}
<Card isTile style="--p-card-padding: 1.5rem;" class="card-only-on-desktop">
<Collapsible>
<div class="u-flex">
<span class="body-text-2">
{tierToPlan($organization?.billingPlan)?.name} plan</span>
<div class="body-text-2 u-margin-inline-start-auto">
{isTrial || $organization?.billingPlan === BillingPlan.GITHUB_EDUCATION
? formatCurrency(0)
: currentPlan
? formatCurrency(currentPlan?.price)
: ''}
</div>
</CollapsibleItem>
{/if}
<CollapsibleItem noContent gap={4}>
<span class="body-text-2">Current total (USD)</span>
<span class="tooltip u-cross-center" aria-label="total info">
<span
class="icon-info"
aria-hidden="true"
use:tooltip={{
content:
'Totals displayed are estimates updated every 24 hours and may not accurately reflect your invoice.'
}}></span>
</span>
<div class="body-text-2 u-margin-inline-start-auto">
{$organization?.billingPlan === BillingPlan.FREE ||
$organization?.billingPlan === BillingPlan.GITHUB_EDUCATION
? formatCurrency(0)
: formatCurrency(
Math.max(
(currentInvoice?.amount ?? 0) -
Math.min(availableCredit, currentInvoice?.amount ?? 0),
0
)
)}
</div>
</CollapsibleItem>
</Collapsible>
{#if $organization?.billingPlan !== BillingPlan.FREE && $organization?.billingPlan !== BillingPlan.GITHUB_EDUCATION && extraUsage > 0}
<CollapsibleItem gap={8}>
<svelte:fragment slot="beforetitle">
<span class="body-text-2"><b>Add-ons</b></span><span
class="inline-tag"
>{extraMembers ? extraAddons + 1 : extraAddons}</span>
<div class="icon">
<span class="icon-cheveron-down" aria-hidden="true"></span>
</div>
</svelte:fragment>
<svelte:fragment slot="end">
<div class="body-text-2 u-margin-inline-start-auto">
<b>
{formatCurrency(extraUsage >= 0 ? extraUsage : 0)}
</b>
</div>
</svelte:fragment>
<ul>
{#if extraMembers}
<li class="u-flex-vertical u-gap-4 u-padding-block-8">
<div class="u-flex u-gap-4">
<h5 class="body-text-2 u-stretch">
Additional members
</h5>
<div>
{formatCurrency(
extraMembers *
(currentPlan?.addons?.member?.price ?? 0)
)}
</div>
</div>
<div class="u-flex u-gap-4">
<h5 class="body-text-2 u-stretch u-color-text-offline">
{extraMembers}
</h5>
</div>
</li>
{/if}
{#if currentInvoice?.usage}
{#each currentInvoice.usage as excess, i}
<li
class="u-flex-vertical u-gap-4 {extraMembers
? 'u-padding-block-8'
: 'u-padding-block-start-8'}"
class:u-sep-block-start={i > 0 || extraMembers}>
{#if ['storage', 'bandwidth'].includes(excess.name)}
{@const excessValue = humanFileSize(excess.value)}
<div class="u-flex u-main-space-between">
<h5 class="body-text-2 u-stretch u-capitalize">
{excess.name}
</h5>
{formatCurrency(excess.amount)}
</div>
<h5
class="body-text-2 u-stretch u-color-text-offline"
title={formatNumberWithCommas(
excess.value ?? 0
) + 'bytes'}>
{excessValue.value ?? 0}{excessValue.unit}
</h5>
{/if}
{#if ['users', 'executions'].includes(excess.name)}
<div class="u-flex u-main-space-between">
<h5 class="body-text-2 u-stretch u-capitalize">
{excess.name}
</h5>
{formatCurrency(excess.amount)}
</div>
<h5
class="body-text-2 u-stretch u-color-text-offline"
title={formatNumberWithCommas(excess.value)}>
{abbreviateNumber(excess.value)}
</h5>
{/if}
</li>
{/each}
{/if}
</ul>
</CollapsibleItem>
{/if}
{#if $organization?.billingPlan !== BillingPlan.FREE && availableCredit > 0}
<CollapsibleItem noContent gap={4}>
<span class="body-text-2 u-flex u-cross-center u-gap-2"
><svg
width="16"
height="17"
viewBox="0 0 16 17"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M14.166 7.93434C14.4784 8.24676 14.4784 8.75329 14.166 9.06571L8.56603 14.6657C8.25361 14.9781 7.74708 14.9781 7.43466 14.6657L1.83466 9.06571C1.67842 8.90947 1.60032 8.70469 1.60034 8.49992V4.50002C1.60034 3.17454 2.67486 2.10002 4.00034 2.10002H8.00056C8.20523 2.10008 8.40987 2.17818 8.56603 2.33434L14.166 7.93434ZM4.00034 5.30002C4.44217 5.30002 4.80034 4.94185 4.80034 4.50002C4.80034 4.05819 4.44217 3.70002 4.00034 3.70002C3.55851 3.70002 3.20034 4.05819 3.20034 4.50002C3.20034 4.94185 3.55851 5.30002 4.00034 5.30002Z"
fill="#00BC5D" />
</svg>
Credits to be applied</span>
<div
class="body-text-2 u-margin-inline-start-auto"
style="color: var(--web-green-500, #10B981)">
-{formatCurrency(
Math.min(availableCredit, currentInvoice?.amount ?? 0)
)}
</div>
</CollapsibleItem>
{/if}
{#if $organization?.billingPlan !== BillingPlan.FREE && $organization?.billingPlan !== BillingPlan.GITHUB_EDUCATION}
<CollapsibleItem
noContent
gap={4}
style="padding-block-end: unset;"
wrapperStyle="padding-block-end: unset;">
<span class="body-text-2">Current total (USD)</span>
<span class="tooltip u-cross-center" aria-label="total info">
<span
class="icon-info"
aria-hidden="true"
use:tooltip={{
content:
'Totals displayed are estimates updated every 24 hours and may not accurately reflect your invoice.'
}}></span>
</span>
<div class="body-text-2 u-margin-inline-start-auto">
{formatCurrency(
Math.max(
(currentInvoice?.amount ?? 0) -
Math.min(availableCredit, currentInvoice?.amount ?? 0),
0
)
)}
</div>
</CollapsibleItem>
{/if}
</Collapsible>
</Card>
</svelte:fragment>
<svelte:fragment slot="actions">
{#if $organization?.billingPlan === BillingPlan.FREE}
@@ -223,3 +232,28 @@
</svelte:fragment>
</CardGrid>
{/if}
<style>
:global(.card-only-on-desktop) {
background: hsl(var(--color-neutral-5));
border: 1px solid hsl(var(--color-neutral-10));
border-radius: var(--corner-radius-medium, 8px);
}
:global(.theme-dark .card-only-on-desktop) {
background: #2c2c2f;
border: 1px solid #424248;
}
@media (max-width: 768px) {
:global(.card-only-on-desktop) {
padding: unset;
box-shadow: unset;
border-radius: unset;
/* yes, these `!important`s are needed*/
border: unset !important;
background: unset !important;
}
}
</style>