Skip to content

Commit ba54f8d

Browse files
authored
Merge pull request #2706 from appwrite/improve-payment-modal
2 parents 7269eff + 9f05f73 commit ba54f8d

7 files changed

Lines changed: 57 additions & 33 deletions

File tree

src/lib/components/billing/paymentModal.svelte

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,30 @@
11
<script lang="ts">
22
import { FakeModal } from '$lib/components';
33
import { InputText, Button } from '$lib/elements/forms';
4-
import { createEventDispatcher, onMount } from 'svelte';
5-
import { initializeStripe, setPaymentMethod, submitStripeCard } from '$lib/stores/stripe';
4+
import { onMount, onDestroy } from 'svelte';
5+
import {
6+
initializeStripe,
7+
setPaymentMethod,
8+
submitStripeCard,
9+
unmountPaymentElement
10+
} from '$lib/stores/stripe';
611
import { invalidate } from '$app/navigation';
712
import { Dependencies } from '$lib/constants';
813
import { addNotification } from '$lib/stores/notifications';
914
import { page } from '$app/state';
1015
import { Spinner } from '@appwrite.io/pink-svelte';
1116
import type { PaymentMethod } from '@stripe/stripe-js';
1217
import StatePicker from './statePicker.svelte';
18+
import type { PaymentMethodData } from '$lib/sdk/billing';
1319
1420
export let show = false;
21+
export let onCardSubmit: ((card: PaymentMethodData) => void) | null = null;
1522
16-
const dispatch = createEventDispatcher();
17-
18-
let name: string;
19-
let error: string;
2023
let modal: FakeModal;
21-
let showState: boolean = false;
24+
let name: string;
2225
let state: string = '';
26+
let error: string = null;
27+
let showState: boolean = false;
2328
let paymentMethod: PaymentMethod | null = null;
2429
2530
async function handleSubmit() {
@@ -32,7 +37,7 @@
3237
const card = await setPaymentMethod(paymentMethod.id, name, state);
3338
modal.closeModal();
3439
await invalidate(Dependencies.PAYMENT_METHODS);
35-
dispatch('submit', card);
40+
onCardSubmit?.(card);
3641
addNotification({
3742
type: 'success',
3843
message: 'A new payment method has been added to your account'
@@ -50,7 +55,7 @@
5055
}
5156
modal.closeModal();
5257
await invalidate(Dependencies.PAYMENT_METHODS);
53-
dispatch('submit', card);
58+
onCardSubmit?.(card as PaymentMethodData);
5459
addNotification({
5560
type: 'success',
5661
message: 'A new payment method has been added to your account'
@@ -89,6 +94,8 @@
8994
};
9095
});
9196
97+
onDestroy(unmountPaymentElement);
98+
9299
$: if (element) {
93100
observer.observe(element, { childList: true });
94101
}
@@ -99,7 +106,8 @@
99106
bind:show
100107
title="Add payment method"
101108
bind:error
102-
onSubmit={handleSubmit}>
109+
onSubmit={handleSubmit}
110+
skipEnterOnBackdrop={showState}>
103111
<slot />
104112
{#if showState}
105113
<StatePicker card={paymentMethod} bind:state />

src/lib/components/billing/selectPaymentMethod.svelte

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@
1818
let showTaxId = false;
1919
let showPaymentModal = false;
2020
21-
async function cardSaved(event: CustomEvent<PaymentMethodData>) {
22-
value = event.detail.$id;
21+
async function cardSaved(card: PaymentMethodData) {
22+
value = card.$id;
2323
2424
if (value) {
2525
methods = {
2626
...methods,
2727
total: methods.total + 1,
28-
paymentMethods: [...methods.paymentMethods, event.detail]
28+
paymentMethods: [...methods.paymentMethods, card]
2929
};
3030
}
3131
@@ -98,7 +98,7 @@
9898
</Layout.Stack>
9999

100100
{#if showPaymentModal && isCloud && hasStripePublicKey}
101-
<PaymentModal bind:show={showPaymentModal} on:submit={cardSaved}>
101+
<PaymentModal bind:show={showPaymentModal} onCardSubmit={cardSaved}>
102102
<svelte:fragment slot="end">
103103
<Selector.Checkbox
104104
id="taxIdCheck"

src/lib/components/fakeModal.svelte

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
<script lang="ts">
22
import { Alert } from '@appwrite.io/pink-svelte';
3-
import { onMount } from 'svelte';
3+
import { clickOnEnter } from '$lib/helpers/a11y';
44
import Form from '$lib/elements/forms/form.svelte';
55
import { Click, trackEvent } from '$lib/actions/analytics';
6-
import { clickOnEnter } from '$lib/helpers/a11y';
76
7+
export let title = '';
88
export let show = false;
99
export let size: 'small' | 'big' = 'big';
1010
export let icon: string = null;
@@ -15,11 +15,13 @@
1515
export let onSubmit: (e: SubmitEvent) => Promise<void> | void = function () {
1616
return;
1717
};
18-
export let title = '';
1918
20-
let backdrop: HTMLDivElement;
19+
/**
20+
* needed when using `StatePicker`
21+
*/
22+
export let skipEnterOnBackdrop = false;
2123
22-
onMount(async () => {});
24+
let backdrop: HTMLDivElement;
2325
2426
function handleBLur(event: MouseEvent) {
2527
if (event.target === backdrop) {
@@ -60,12 +62,12 @@
6062
<svelte:window on:keydown={handleKeydown} />
6163

6264
{#if show}
63-
<!-- svelte-ignore a11y-no-static-element-interactions -->
65+
<!-- svelte-ignore a11y_no_static_element_interactions -->
6466
<div
67+
bind:this={backdrop}
68+
onclick={handleBLur}
6569
class="payment-modal-backdrop"
66-
on:keyup={clickOnEnter}
67-
on:click={handleBLur}
68-
bind:this={backdrop}>
70+
onkeyup={skipEnterOnBackdrop ? undefined : clickOnEnter}>
6971
<div
7072
class="modal"
7173
class:is-small={size === 'small'}
@@ -99,11 +101,12 @@
99101
style="--button-size:1.5rem;"
100102
aria-label="Close Modal"
101103
title="Close Modal"
102-
on:click={() =>
104+
onclick={() => {
105+
closeModal();
103106
trackEvent(Click.ModalCloseClick, {
104107
from: 'button'
105-
})}
106-
on:click={closeModal}>
108+
});
109+
}}>
107110
<span class="icon-x" aria-hidden="true"></span>
108111
</button>
109112
{/if}

src/lib/stores/stripe.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ export const isStripeInitialized = writable(false);
2626

2727
export async function initializeStripe(node: HTMLElement) {
2828
if (!get(stripe)) return;
29+
30+
// cleanup any existing state
31+
await unmountPaymentElement();
32+
2933
isStripeInitialized.set(true);
3034

3135
const methods = await sdk.forConsole.billing.listPaymentMethods();
@@ -54,10 +58,20 @@ export async function initializeStripe(node: HTMLElement) {
5458

5559
export async function unmountPaymentElement() {
5660
isStripeInitialized.set(false);
57-
paymentElement?.unmount();
61+
62+
if (paymentElement) {
63+
try {
64+
paymentElement.unmount();
65+
paymentElement.destroy();
66+
} catch (e) {
67+
console.debug('Payment element cleanup:', e.message);
68+
}
69+
}
70+
71+
elements = null;
5872
clientSecret = null;
5973
paymentMethod = null;
60-
elements = null;
74+
paymentElement = null;
6175
}
6276

6377
export async function submitStripeCard(name: string, organizationId?: string) {

src/routes/(console)/organization-[organization]/billing/paymentMethods.svelte

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -308,11 +308,11 @@
308308
{#if showPayment && isCloud && hasStripePublicKey}
309309
<PaymentModal
310310
bind:show={showPayment}
311-
on:submit={(e) => {
311+
onCardSubmit={(card) => {
312312
if (isSelectedBackup) {
313-
addBackupPaymentMethod(e.detail.$id);
313+
addBackupPaymentMethod(card.$id);
314314
} else {
315-
addPaymentMethod(e.detail.$id);
315+
addPaymentMethod(card.$id);
316316
}
317317
}} />
318318
{/if}

src/routes/(console)/organization-[organization]/billing/replaceCard.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
export let methods: PaymentList;
2020
2121
let name: string;
22-
let error: string;
22+
let error: string = null;
2323
let selectedPaymentMethodId: string;
2424
let showState: boolean = false;
2525
let state: string = '';

src/routes/(console)/organization-[organization]/billing/retryPaymentModal.svelte

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,6 @@
147147
onSubmit={handleSubmit}
148148
size="big"
149149
title="Retry payment">
150-
<!-- TODO: format currency -->
151150
<p class="text">
152151
Your payment of <span class="inline-tag">{formatCurrency(invoice.grossAmount)}</span> due on {toLocaleDate(
153152
invoice.dueAt

0 commit comments

Comments
 (0)