mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
remove: icon-x button in a read-only view.
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user