feat: billing address, and many fixes

This commit is contained in:
Arman
2023-08-03 19:16:22 +02:00
parent 8335735f45
commit 515d80e40d
14 changed files with 146 additions and 42 deletions
+2
View File
@@ -227,6 +227,8 @@ export enum Submit {
OrganizationBackupPaymentAdded = 'submit_organization_backup_payment_added',
OrganizationBackupPaymentUpdated = 'submit_organization_backup_payment_updated',
OrganizationBackupPaymentRemoved = 'submit_organization_backup_payment_removed',
OrganizationBillingAddressAdded = 'submit_organization_billing_address_added',
OrganizationBillingAddressRemoved = 'submit_organization_billing_address_removed',
PromoCodeRedeemed = 'submit_promo_code_redeemed',
SupportTicket = 'submit_support_ticket'
}
-1
View File
@@ -22,7 +22,6 @@
await goto(href);
await waitUntil(() => {
console.log('tickUntil', el);
return el.classList.contains('is-selected');
}, 1000);
el.focus();
+1
View File
@@ -5,6 +5,7 @@ export const INTERVAL = 5 * 60000; // default interval to check for feedback
export enum Dependencies {
CREDIT = 'dependency:credit',
INVOICES = 'dependency:invoices',
ADDRESS = 'dependency:address',
PAYMENT_METHODS = 'dependency:paymentMethods',
ORGANIZATION = 'dependency:organization',
PROJECT = 'dependency:project',
+9 -5
View File
@@ -1,7 +1,6 @@
import type { Client, Query } from '@appwrite.io/console';
import type { Organization } from '../stores/organization';
import type { PaymentMethod } from '@stripe/stripe-js';
import type BillingAddress from '$routes/console/account/payments/billingAddress.svelte';
export type PaymentMethodData = {
$id: string;
@@ -161,7 +160,7 @@ export type Address = {
};
export type AddressesList = {
addresses: Address[];
billingAddresses: Address[];
total: number;
};
@@ -197,6 +196,11 @@ export type Plan = {
realtimeAddon: AdditionalResource | null;
};
export type PlanList = {
plans: Plan[];
total: number;
};
export class Billing {
client: Client;
@@ -515,7 +519,7 @@ export class Billing {
async setBillingAddress(
organizationId: string,
billingAddressId: string
): Promise<BillingAddress> {
): Promise<Organization> {
const path = `/organizations/${organizationId}/billing-address`;
const params = {
organizationId,
@@ -532,7 +536,7 @@ export class Billing {
);
}
async removeBillingAddress(organizationId: string): Promise<BillingAddress> {
async removeBillingAddress(organizationId: string): Promise<void> {
const path = `/organizations/${organizationId}/billing-address`;
const params = {
organizationId
@@ -787,7 +791,7 @@ export class Billing {
);
}
async getPlans(): Promise<RegionList> {
async getPlanList(): Promise<PlanList> {
const path = `/console/plans`;
const params = {};
const uri = new URL(this.client.config.endpoint + path);
@@ -6,22 +6,32 @@
import { Button, InputNumber, InputSelect, InputText } from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
import { onMount } from 'svelte';
export let show = false;
//TODO: fetch countries from appwrite
const options = [
{
value: 'US',
label: 'United States'
}
];
let country: string;
let address: string;
let address2: string;
let city: string;
let state: string;
let zip: number;
let options = [
{
value: 'US',
label: 'United States'
}
];
onMount(async () => {
const countryList = await sdk.forProject.locale.listCountries();
options = countryList.countries.map((country) => {
return {
value: country.code,
label: country.name
};
});
});
async function handleSubmit() {
try {
@@ -33,19 +43,19 @@
state,
zip?.toString()
);
await invalidate(Dependencies.PAYMENT_METHODS);
await invalidate(Dependencies.ADDRESS);
show = false;
addNotification({
type: 'success',
message: `Address has been deleted`
message: `Address has been added`
});
trackEvent(Submit.BillingAddressDeleted);
trackEvent(Submit.BillingAddressCreated);
} catch (error) {
addNotification({
type: 'error',
message: error.message
});
trackError(error, Submit.BillingAddressDeleted);
trackError(error, Submit.BillingAddressCreated);
}
}
@@ -67,6 +67,6 @@
</FormList>
<svelte:fragment slot="footer">
<Button secondary on:click={() => (show = false)}>Cancel</Button>
<Button submit>Save</Button>
<Button submit>Update</Button>
</svelte:fragment>
</Modal>
@@ -50,6 +50,6 @@
</FormList>
<svelte:fragment slot="footer">
<Button secondary on:click={() => (show = false)}>Cancel</Button>
<Button submit>Save</Button>
<Button submit disabled={!name}>Save</Button>
</svelte:fragment>
</Modal>
@@ -12,8 +12,8 @@
import { failedInvoice, paymentMethods } from '$lib/stores/billing';
import type { PaymentMethodData } from '$lib/sdk/billing';
$: defaultPaymentMethod = $paymentMethods.paymentMethods.find(
(method: PaymentMethodData) => method.$id === $organization.paymentMethodId
$: defaultPaymentMethod = $paymentMethods?.paymentMethods?.find(
(method: PaymentMethodData) => method.$id === $organization?.paymentMethodId
);
</script>
@@ -38,7 +38,7 @@
await sdk.forConsole.billing.addCredit($organization.$id, coupon);
addNotification({
type: 'success',
message: `A new payment method has been added to ${$organization.name}`
message: `Credit has been added to ${$organization.name}`
});
invalidate(Dependencies.ORGANIZATION);
trackEvent(Submit.CouponRedeemed, {
@@ -55,6 +55,7 @@
}
async function request() {
if (!$organization?.$id) return;
creditList = await sdk.forConsole.billing.listCredits($organization.$id, [
Query.limit(limit),
Query.offset(offset)
@@ -1,9 +1,39 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { CardGrid, DropList, Heading } from '$lib/components';
import DropListItem from '$lib/components/dropListItem.svelte';
import { Dependencies } from '$lib/constants';
import { Button } from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { organization } from '$lib/stores/organization';
import { sdk } from '$lib/stores/sdk';
import AddressModal from '$routes/console/account/payments/addressModal.svelte';
import { addressList } from './store';
let showDropdown = false;
let showCreate = false;
async function addAddress(addressId: string) {
try {
await sdk.forConsole.billing.setBillingAddress($organization.$id, addressId);
await invalidate(Dependencies.ADDRESS);
showDropdown = false;
addNotification({
type: 'success',
message: `A new billing address has been added to ${$organization.name}`
});
trackEvent(Submit.OrganizationBillingAddressAdded);
} catch (error) {
addNotification({
type: 'error',
message: error.message
});
trackError(error, Submit.OrganizationBillingAddressAdded);
}
}
$: console.log($addressList);
</script>
<CardGrid>
@@ -21,18 +51,24 @@
<i class="icon-plus" />
</Button>
<svelte:fragment slot="list">
<!-- {#if $addressList.total}
{#each $addressList.addresses as address}
<DropListItem
on:click={() => addAddress(address?.$id)}>
{#if $addressList?.total}
{#each $addressList.billingAddresses as address}
<DropListItem on:click={() => addAddress(address?.$id)}>
<p class="text">
{address.address1}
<span>{address.city}</span>
<span>{address.streetAddress}</span>,
<span>{address.city}</span>,
<span>{address.state}</span>,
<span>{address.postalCode}</span>,
<span>{address.country}</span>
</p>
</DropListItem>
{/each}
{/if} -->
<DropListItem>Add new billing address</DropListItem>
{/if}
<DropListItem
on:click={() => {
showCreate = true;
showDropdown = false;
}}>Add new billing address</DropListItem>
</svelte:fragment>
</DropList>
</div>
@@ -43,3 +79,5 @@
</article>
</svelte:fragment>
</CardGrid>
<AddressModal bind:show={showCreate} />
@@ -13,11 +13,13 @@
import type { PaymentMethodData } from '$lib/sdk/billing';
import DeleteOrgPayment from './deleteOrgPayment.svelte';
import ReplaceCard from './replaceCard.svelte';
import EditPaymentModal from '$routes/console/account/payments/editPaymentModal.svelte';
let showDropdown = false;
let showDropdownBackup = false;
let showPayment = false;
let showEdit = false;
let showDelete = false;
let showReplace = false;
let isSelectedBackup = false;
@@ -65,13 +67,13 @@
}
}
$: if ($organization.backupPaymentMethodId) {
$: if ($organization?.backupPaymentMethodId) {
sdk.forConsole.billing
.getPaymentMethod($organization.backupPaymentMethodId)
.then((res) => (backupPaymentMethod = res));
}
$: if ($organization.paymentMethodId) {
$: if ($organization?.paymentMethodId) {
sdk.forConsole.billing
.getPaymentMethod($organization.paymentMethodId)
.then((res) => (defaultPaymentMethod = res));
@@ -107,7 +109,8 @@
<DropListItem
icon="pencil"
on:click={() => {
console.log('test');
showEdit = true;
showDropdown = false;
}}>
Edit
</DropListItem>
@@ -189,7 +192,9 @@
<DropListItem
icon="pencil"
on:click={() => {
console.log('test');
showEdit = true;
isSelectedBackup = true;
showDropdownBackup = false;
}}>
Edit
</DropListItem>
@@ -247,8 +252,9 @@
</DropListItem>
{/each}
{/if}
<DropListItem on:click={() => (showPayment = true)}
>Add new payment method</DropListItem>
<DropListItem on:click={() => (showPayment = true)}>
Add new payment method
</DropListItem>
</svelte:fragment>
</DropList>
</div>
@@ -264,6 +270,13 @@
{#if showPayment && isCloud && hasStripePublicKey}
<PaymentModal bind:show={showPayment} />
{/if}
{#if showEdit && isCloud && hasStripePublicKey}
<EditPaymentModal
selectedPaymentMethod={isSelectedBackup
? $organization.backupPaymentMethodId
: $organization.paymentMethodId}
bind:show={showEdit} />
{/if}
{#if showReplace && isCloud && hasStripePublicKey}
<ReplaceCard bind:show={showReplace} isBackup={isSelectedBackup} />
{/if}
@@ -1,7 +1,8 @@
import { page } from '$app/stores';
import type { AddressesList } from '$lib/sdk/billing';
import { derived } from 'svelte/store';
export const addressList = derived(page, ($page) => $page.data.addressList as string | null);
export const addressList = derived(page, ($page) => $page.data.addressList as AddressesList);
export const aggregationList = derived(
page,
($page) => $page.data.aggregationList as string | null
@@ -1,6 +1,5 @@
<script lang="ts">
import { Modal } from '$lib/components';
import { usageRates } from '$lib/constants';
import { Button } from '$lib/elements/forms';
import {
Table,
@@ -12,14 +11,50 @@
} from '$lib/elements/table';
import { toLocaleDate } from '$lib/helpers/date';
import { organization } from '$lib/stores/organization';
import { onMount } from 'svelte';
import { createOrganization } from './store';
import { sdk } from '$lib/stores/sdk';
import type { Plan } from '$lib/sdk/billing';
export let show = false;
export let tier: string;
let plan: Plan = null;
onMount(async () => {
const planList = await sdk.forConsole.billing.getPlanList();
plan = planList.plans.find((p) => p.$id === tier);
});
$: nextDate = $createOrganization?.name
? new Date(new Date().getFullYear(), new Date().getMonth() + 1, 1).toString()
: $organization?.billingNextInvoiceDate;
$: console.log(plan);
const planData = [
{
id: 'members',
resource: 'Organization members',
unit: ''
},
{ id: 'bandwith', resource: 'Bandwidth', unit: 'GB' },
{ id: 'storage', resource: 'Storage', unit: 'GB' },
{
id: 'executions',
resource: 'Function executions',
unit: 'executions'
},
{
id: 'users',
resource: 'Active users',
unit: 'AU'
},
{
id: 'realtime',
resource: 'Concurrent connections',
unit: 'connections'
}
];
</script>
<Modal bind:show size="big" headerDivider={false}>
@@ -36,11 +71,11 @@
<TableCellHead>Rate</TableCellHead>
</TableHeader>
<TableBody>
{#each usageRates[tier] as usage}
{#each planData as usage}
<TableRow>
<TableCellText title="resource">{usage.resource}</TableCellText>
<TableCellText title="limit">{usage.amount}{usage?.unit}</TableCellText>
<TableCellText title="rate">{usage.rate}</TableCellText>
<TableCellText title="limit">{plan[usage.id]}{usage?.unit}</TableCellText>
<TableCellText title="rate">{plan[`${usage.id}Addon`]}</TableCellText>
</TableRow>
{/each}
</TableBody>
@@ -96,5 +96,5 @@
{/if}
</ul>
{#if $organization.billingPlan === 'tier-0'}TODO: add transfer modal{/if}
<!-- {#if $organization.billingPlan === 'tier-0'}TODO: add transfer modal{/if} -->
</WizardStep>