fix: added cards list
This commit is contained in:
parent
d96b313fba
commit
e1c64ed3f0
4 changed files with 254 additions and 5 deletions
|
|
@ -107,7 +107,7 @@ const removeCard = (card) => {
|
|||
message: 'Are you sure you want to remove this card?',
|
||||
actions: [
|
||||
{
|
||||
label: 'Delete',
|
||||
label: 'Remove',
|
||||
variant: 'solid',
|
||||
theme: 'red',
|
||||
onClick: (close) => {
|
||||
|
|
|
|||
18
billing/src/icons/RefreshIcon.vue
Normal file
18
billing/src/icons/RefreshIcon.vue
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<template>
|
||||
<svg width="16" height="16" fill="none" viewBox="0 0 16 16">
|
||||
<g class="es-line-reload" clip-path="url(#a)">
|
||||
<path
|
||||
fill="currentColor"
|
||||
fill-rule="evenodd"
|
||||
d="M9.743 2.189a6 6 0 0 0-6.558 2.596.5.5 0 0 0 .844.535 5 5 0 0 1 9.12 1.683l-1.187-.686a.5.5 0 0 0-.5.866l2.165 1.25a.5.5 0 0 0 .683-.183l1.25-2.165a.5.5 0 0 0-.866-.5l-.603 1.044a6 6 0 0 0-4.348-4.44ZM3.356 9.024l1.189.687a.5.5 0 0 0 .5-.866L2.88 7.595a.5.5 0 0 0-.683.183L.947 9.943a.5.5 0 1 0 .866.5l.603-1.044a6 6 0 0 0 10.9 1.816.5.5 0 0 0-.844-.536 5 5 0 0 1-9.116-1.655Z"
|
||||
class="Union"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="a" class="a">
|
||||
<path fill="currentColor" d="M.25 0h16v16h-16z" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</template>
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
<ListView
|
||||
:columns="columns"
|
||||
:rows="rows"
|
||||
row-key="name"
|
||||
row-key="id"
|
||||
:options="{
|
||||
selectable: false,
|
||||
}"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,238 @@
|
|||
<template>
|
||||
<div class="flex h-full flex-col py-11 px-[68px] gap-8 overflow-y-auto">
|
||||
<h2 class="flex gap-2 text-xl font-semibold leading-5">
|
||||
{{ 'Cards' }}
|
||||
</h2>
|
||||
<div class="flex justify-between items-center">
|
||||
<h2 class="flex gap-2 text-xl font-semibold leading-5">
|
||||
{{ 'Cards' }}
|
||||
</h2>
|
||||
<div class="flex gap-2">
|
||||
<Button :loading="cards.loading" @click="cards.reload()">
|
||||
<template #icon>
|
||||
<RefreshIcon class="h-4 w-4" />
|
||||
</template>
|
||||
</Button>
|
||||
<Button label="Add Card" variant="solid" @click="showAddCardModal = true">
|
||||
<template #prefix>
|
||||
<FeatherIcon name="plus" class="h-4 w-4" />
|
||||
</template>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<ListView
|
||||
:columns="columns"
|
||||
:rows="rows"
|
||||
row-key="id"
|
||||
:options="{
|
||||
selectable: false,
|
||||
showTooltip: false,
|
||||
}"
|
||||
>
|
||||
<ListHeader />
|
||||
<ListRows>
|
||||
<ListRow
|
||||
v-for="row in rows"
|
||||
:key="row.id"
|
||||
v-slot="{ column, item }"
|
||||
:row="row"
|
||||
>
|
||||
<ListRowItem :item="item" :align="column.align">
|
||||
<div
|
||||
v-if="column.key == 'last_4'"
|
||||
class="flex items-center gap-2 text-base"
|
||||
>
|
||||
<component :is="cardBrandIcon(item.brand)" class="h-6 w-6" />
|
||||
<div>{{ item.last_4 }}</div>
|
||||
<Badge
|
||||
v-if="item.default"
|
||||
label="Default"
|
||||
variant="outline"
|
||||
theme="green"
|
||||
/>
|
||||
</div>
|
||||
<Dropdown
|
||||
v-else-if="column.key == 'actions' && !item.is_default"
|
||||
:options="item.options"
|
||||
>
|
||||
<button
|
||||
class="flex items-center rounded bg-gray-100 px-1 py-0.5 hover:bg-gray-200"
|
||||
>
|
||||
<FeatherIcon name="more-horizontal" class="h-4 w-4" />
|
||||
</button>
|
||||
</Dropdown>
|
||||
</ListRowItem>
|
||||
</ListRow>
|
||||
</ListRows>
|
||||
</ListView>
|
||||
</div>
|
||||
</div>
|
||||
<AddCardModal
|
||||
v-if="showAddCardModal"
|
||||
v-model="showAddCardModal"
|
||||
@success="
|
||||
() => {
|
||||
showAddCardModal = false
|
||||
cards.reload()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
<script setup>
|
||||
import AddCardModal from '@/components/AddCardModal.vue'
|
||||
import RefreshIcon from '@/icons/RefreshIcon.vue'
|
||||
import {
|
||||
ListView,
|
||||
ListHeader,
|
||||
ListRows,
|
||||
ListRow,
|
||||
ListRowItem,
|
||||
Dropdown,
|
||||
Button,
|
||||
Badge,
|
||||
Tooltip,
|
||||
FeatherIcon,
|
||||
createResource,
|
||||
} from 'frappe-ui'
|
||||
import { useTimeAgo } from '@vueuse/core'
|
||||
import { createDialog } from '@/dialogs.js'
|
||||
import { cardBrandIcon } from '@/utils'
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const showAddCardModal = ref(false)
|
||||
|
||||
const cards = createResource({
|
||||
url: 'frappe.integrations.frappe_providers.frappecloud_billing.api',
|
||||
params: { method: 'billing.get_payment_methods' },
|
||||
auto: true,
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{
|
||||
label: 'Name on Card',
|
||||
key: 'name_on_card',
|
||||
},
|
||||
{
|
||||
label: 'Card',
|
||||
key: 'last_4',
|
||||
width: 1.5,
|
||||
},
|
||||
{
|
||||
label: 'Expiry',
|
||||
key: 'expiry_month',
|
||||
width: 0.5,
|
||||
},
|
||||
{
|
||||
label: 'Mandated',
|
||||
key: 'stripe_mandate_id',
|
||||
align: 'center',
|
||||
width: 1,
|
||||
},
|
||||
{
|
||||
label: '',
|
||||
key: 'alert',
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
label: '',
|
||||
key: 'creation',
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
label: '',
|
||||
key: 'actions',
|
||||
align: 'right',
|
||||
width: 0.5,
|
||||
},
|
||||
]
|
||||
|
||||
const rows = computed(() => {
|
||||
if (!cards.data) return []
|
||||
return cards.data.map((card) => {
|
||||
return {
|
||||
id: card.name,
|
||||
name_on_card: card.name_on_card,
|
||||
last_4: {
|
||||
brand: card.brand,
|
||||
last_4: `•••• ${card.last_4}`,
|
||||
default: card.is_default,
|
||||
},
|
||||
expiry_month: `${
|
||||
card.expiry_month < 10 ? `0${card.expiry_month}` : card.expiry_month
|
||||
}/${card.expiry_year}`,
|
||||
stripe_mandate_id: card.stripe_mandate_id
|
||||
? h(FeatherIcon, {
|
||||
name: 'check-circle',
|
||||
class: 'h-4 w-4 text-green-600',
|
||||
})
|
||||
: null,
|
||||
alert:
|
||||
card.is_default && card.stripe_payment_method
|
||||
? h(
|
||||
Tooltip,
|
||||
{
|
||||
text: 'The last payment failed on this card. Please use a different card.',
|
||||
},
|
||||
() =>
|
||||
h(FeatherIcon, {
|
||||
name: 'alert-circle',
|
||||
class: 'h-4 w-4 text-red-600',
|
||||
}),
|
||||
)
|
||||
: null,
|
||||
creation: useTimeAgo(card.creation).value,
|
||||
actions: {
|
||||
is_default: card.is_default,
|
||||
options: [
|
||||
{
|
||||
label: 'Set as default',
|
||||
onClick: () => setAsDefault(card.name),
|
||||
condition: () => !card.is_default,
|
||||
},
|
||||
{
|
||||
label: 'Remove',
|
||||
onClick: () => removeCard(card.name),
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const setAsDefault = (card) => {
|
||||
createResource({
|
||||
url: 'frappe.integrations.frappe_providers.frappecloud_billing.api',
|
||||
params: { method: 'billing.set_as_default', data: { name: card } },
|
||||
auto: true,
|
||||
onSuccess: () => {
|
||||
cards.reload()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const removeCard = (card) => {
|
||||
createDialog({
|
||||
title: 'Remove Card',
|
||||
message: 'Are you sure you want to remove this card?',
|
||||
actions: [
|
||||
{
|
||||
label: 'Remove',
|
||||
variant: 'solid',
|
||||
theme: 'red',
|
||||
onClick: (close) => {
|
||||
createResource({
|
||||
url: 'frappe.integrations.frappe_providers.frappecloud_billing.api',
|
||||
params: {
|
||||
method: 'billing.remove_payment_method',
|
||||
data: { name: card },
|
||||
},
|
||||
auto: true,
|
||||
onSuccess: () => {
|
||||
cards.reload()
|
||||
close()
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue