@greatapps/common 1.1.565 → 1.1.567
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/modals/cards/CannotDeleteCardModal.mjs +100 -0
- package/dist/components/modals/cards/CannotDeleteCardModal.mjs.map +1 -0
- package/dist/components/modals/cards/DeleteCardModal.mjs +121 -0
- package/dist/components/modals/cards/DeleteCardModal.mjs.map +1 -0
- package/dist/components/ui/data-display/CardItem.mjs +68 -17
- package/dist/components/ui/data-display/CardItem.mjs.map +1 -1
- package/dist/components/ui/data-display/PaymentInfoCard.mjs +22 -26
- package/dist/components/ui/data-display/PaymentInfoCard.mjs.map +1 -1
- package/dist/index.mjs +10 -0
- package/dist/index.mjs.map +1 -1
- package/dist/modules/cards/hooks/delete-confirmation.hook.mjs +31 -0
- package/dist/modules/cards/hooks/delete-confirmation.hook.mjs.map +1 -0
- package/dist/modules/cards/hooks/handle-delete-card.hook.mjs +31 -0
- package/dist/modules/cards/hooks/handle-delete-card.hook.mjs.map +1 -0
- package/dist/modules/subscriptions/actions/update-subscription-payment.action.mjs +12 -0
- package/dist/modules/subscriptions/actions/update-subscription-payment.action.mjs.map +1 -0
- package/dist/modules/subscriptions/hooks/update-subscription-payment.hook.mjs +23 -0
- package/dist/modules/subscriptions/hooks/update-subscription-payment.hook.mjs.map +1 -0
- package/dist/modules/subscriptions/services/subscriptions.service.mjs +16 -0
- package/dist/modules/subscriptions/services/subscriptions.service.mjs.map +1 -1
- package/dist/modules/subscriptions/types/subscription.type.mjs +1 -1
- package/dist/modules/subscriptions/types/subscription.type.mjs.map +1 -1
- package/dist/server.mjs +2 -0
- package/dist/server.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/modals/cards/CannotDeleteCardModal.tsx +123 -0
- package/src/components/modals/cards/DeleteCardModal.tsx +132 -0
- package/src/components/ui/data-display/CardItem.tsx +70 -12
- package/src/components/ui/data-display/PaymentInfoCard.tsx +28 -28
- package/src/index.ts +6 -0
- package/src/modules/cards/hooks/delete-confirmation.hook.ts +42 -0
- package/src/modules/cards/hooks/handle-delete-card.hook.ts +42 -0
- package/src/modules/subscriptions/actions/update-subscription-payment.action.ts +13 -0
- package/src/modules/subscriptions/hooks/update-subscription-payment.hook.ts +25 -0
- package/src/modules/subscriptions/services/subscriptions.service.ts +23 -0
- package/src/modules/subscriptions/types/subscription.type.ts +1 -1
- package/src/server.ts +1 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Dialog,
|
|
5
|
+
DialogContent,
|
|
6
|
+
DialogHeader,
|
|
7
|
+
DialogTitle,
|
|
8
|
+
DialogDescription,
|
|
9
|
+
} from '../../ui/overlay/Dialog';
|
|
10
|
+
import { Button } from '../../ui/buttons/Button';
|
|
11
|
+
import { Separator } from '../../ui/data-display/Separator';
|
|
12
|
+
import { Toast } from '../../ui/feedback/Toast';
|
|
13
|
+
import { FormField } from '../../ui/form/FormField';
|
|
14
|
+
import { IconTrash } from '@tabler/icons-react';
|
|
15
|
+
import { toast } from 'sonner';
|
|
16
|
+
import { useModalManager } from '../../../store/useModalManager';
|
|
17
|
+
import { useDeleteConfirmation } from '../../../modules/cards/hooks/delete-confirmation.hook';
|
|
18
|
+
import { useDeleteCard } from '../../../modules/cards/hooks/delete-card.hook';
|
|
19
|
+
import { useCards } from '../../../modules/cards/hooks/cards.hook';
|
|
20
|
+
|
|
21
|
+
export function DeleteCardModal() {
|
|
22
|
+
const { activeModal, modalData, closeModal } = useModalManager();
|
|
23
|
+
const { form, isValidConfirmation, resetConfirmation } = useDeleteConfirmation();
|
|
24
|
+
const deleteCard = useDeleteCard();
|
|
25
|
+
const { data: cardsData } = useCards();
|
|
26
|
+
|
|
27
|
+
const isOpen = activeModal === 'deleteCardModal';
|
|
28
|
+
const cardId = modalData.cardId as string | undefined;
|
|
29
|
+
const onSuccess = modalData.onSuccess as (() => void) | undefined;
|
|
30
|
+
|
|
31
|
+
const card = cardsData?.data?.find((c) => c.id.toString() === cardId);
|
|
32
|
+
const cardLastDigits = (card?.number ?? '').replaceAll(/\D/g, '').slice(-4);
|
|
33
|
+
|
|
34
|
+
const handleClose = () => {
|
|
35
|
+
resetConfirmation();
|
|
36
|
+
closeModal();
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const onSubmit = async () => {
|
|
40
|
+
if (!cardId) return;
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
await deleteCard.mutateAsync(cardId);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
toast.custom(
|
|
46
|
+
(t) => (
|
|
47
|
+
<Toast
|
|
48
|
+
toastId={t}
|
|
49
|
+
variant="error"
|
|
50
|
+
message={
|
|
51
|
+
error instanceof Error
|
|
52
|
+
? error.message
|
|
53
|
+
: 'Não foi possível excluir o cartão'
|
|
54
|
+
}
|
|
55
|
+
/>
|
|
56
|
+
),
|
|
57
|
+
{ duration: 5000 },
|
|
58
|
+
);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
onSuccess?.();
|
|
63
|
+
resetConfirmation();
|
|
64
|
+
closeModal();
|
|
65
|
+
|
|
66
|
+
toast.custom(
|
|
67
|
+
(t) => (
|
|
68
|
+
<Toast
|
|
69
|
+
variant="success"
|
|
70
|
+
message={`O cartão •••• ${cardLastDigits} foi excluído com sucesso`}
|
|
71
|
+
toastId={t}
|
|
72
|
+
/>
|
|
73
|
+
),
|
|
74
|
+
{ duration: 5000 },
|
|
75
|
+
);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<Dialog open={isOpen} onOpenChange={(open) => !open && handleClose()}>
|
|
80
|
+
<DialogContent className="flex flex-col p-0! gap-0! max-w-[calc(100%-2rem)]! w-full sm:max-w-[400px]! lg:max-w-[410px]! rounded-lg border">
|
|
81
|
+
<form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col">
|
|
82
|
+
<DialogHeader className="gap-4 text-left p-4 lg:p-5 pb-0 lg:pb-0">
|
|
83
|
+
<div className="flex items-center justify-center w-fit bg-red-50 rounded-lg p-2.5">
|
|
84
|
+
<IconTrash size={20} className="text-red-500" />
|
|
85
|
+
</div>
|
|
86
|
+
<div className="flex flex-col gap-2">
|
|
87
|
+
<DialogTitle className="paragraph-medium-semibold text-zinc-950">
|
|
88
|
+
Excluir cartão
|
|
89
|
+
</DialogTitle>
|
|
90
|
+
<DialogDescription className="paragraph-small-regular text-zinc-600">
|
|
91
|
+
Deseja realmente excluir o cartão:{' '}
|
|
92
|
+
<span className="font-semibold text-zinc-950">
|
|
93
|
+
•••• {cardLastDigits}
|
|
94
|
+
</span>
|
|
95
|
+
?
|
|
96
|
+
</DialogDescription>
|
|
97
|
+
</div>
|
|
98
|
+
</DialogHeader>
|
|
99
|
+
|
|
100
|
+
<div className="px-4 lg:px-5 py-5">
|
|
101
|
+
<FormField
|
|
102
|
+
label="Digite EXCLUIR abaixo"
|
|
103
|
+
required
|
|
104
|
+
placeholder="EXCLUIR"
|
|
105
|
+
{...form.register('confirmText')}
|
|
106
|
+
/>
|
|
107
|
+
</div>
|
|
108
|
+
<Separator />
|
|
109
|
+
<div className="flex flex-row items-center gap-2 p-4 lg:p-5">
|
|
110
|
+
<Button
|
|
111
|
+
type="submit"
|
|
112
|
+
disabled={!isValidConfirmation}
|
|
113
|
+
loading={form.formState.isSubmitting}
|
|
114
|
+
className="h-10!"
|
|
115
|
+
>
|
|
116
|
+
{form.formState.isSubmitting ? 'Excluindo...' : 'Excluir'}
|
|
117
|
+
</Button>
|
|
118
|
+
<Button
|
|
119
|
+
type="button"
|
|
120
|
+
variant="secondary"
|
|
121
|
+
className="h-10!"
|
|
122
|
+
onClick={handleClose}
|
|
123
|
+
disabled={form.formState.isSubmitting}
|
|
124
|
+
>
|
|
125
|
+
Cancelar
|
|
126
|
+
</Button>
|
|
127
|
+
</div>
|
|
128
|
+
</form>
|
|
129
|
+
</DialogContent>
|
|
130
|
+
</Dialog>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useState } from 'react';
|
|
4
|
-
import {
|
|
4
|
+
import { toast } from 'sonner';
|
|
5
|
+
import { IconDotsVertical, IconStar, IconTrash } from '@tabler/icons-react';
|
|
5
6
|
import { Popover, PopoverContent, PopoverTrigger } from '../overlay/Popover';
|
|
6
7
|
import { NavBarItem } from '../../layouts/NavBarItem';
|
|
8
|
+
import { Toast } from '../feedback/Toast';
|
|
7
9
|
import { CardBrandIcon } from './CardBrandIcons';
|
|
10
|
+
import { useSetDefaultCard } from '../../../modules/cards/hooks/set-default-card.hook';
|
|
11
|
+
import { useHandleDeleteCard } from '../../../modules/cards/hooks/handle-delete-card.hook';
|
|
8
12
|
import type { Card } from '../../../modules/cards/types';
|
|
9
13
|
|
|
10
14
|
type CardItemProps = {
|
|
@@ -12,24 +16,63 @@ type CardItemProps = {
|
|
|
12
16
|
isSelected?: boolean;
|
|
13
17
|
onSelect?: () => void;
|
|
14
18
|
showMenu?: boolean;
|
|
15
|
-
|
|
16
|
-
|
|
19
|
+
hideDelete?: boolean;
|
|
20
|
+
hideSetDefault?: boolean;
|
|
21
|
+
onAfterDelete?: (card: Card) => void;
|
|
22
|
+
onAfterSetDefault?: (card: Card) => void;
|
|
17
23
|
};
|
|
18
24
|
|
|
19
25
|
export function CardItem({
|
|
20
26
|
card,
|
|
21
27
|
isSelected,
|
|
22
28
|
onSelect,
|
|
23
|
-
showMenu =
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
showMenu = true,
|
|
30
|
+
hideDelete = false,
|
|
31
|
+
hideSetDefault = false,
|
|
32
|
+
onAfterDelete,
|
|
33
|
+
onAfterSetDefault,
|
|
26
34
|
}: CardItemProps) {
|
|
27
35
|
const [popoverOpen, setPopoverOpen] = useState(false);
|
|
28
36
|
|
|
37
|
+
const setDefaultCardMutation = useSetDefaultCard();
|
|
38
|
+
|
|
39
|
+
const handleDeleteCard = useHandleDeleteCard({ onAfterDelete });
|
|
40
|
+
|
|
41
|
+
const handleSetDefault = () => {
|
|
42
|
+
setDefaultCardMutation.mutate(card.id.toString(), {
|
|
43
|
+
onSuccess: () => {
|
|
44
|
+
toast.custom((t) => (
|
|
45
|
+
<Toast
|
|
46
|
+
variant="success"
|
|
47
|
+
message="Cartão definido como padrão"
|
|
48
|
+
toastId={t}
|
|
49
|
+
/>
|
|
50
|
+
));
|
|
51
|
+
onAfterSetDefault?.(card);
|
|
52
|
+
},
|
|
53
|
+
onError: () => {
|
|
54
|
+
toast.custom((t) => (
|
|
55
|
+
<Toast
|
|
56
|
+
variant="error"
|
|
57
|
+
message="Erro ao definir cartão padrão"
|
|
58
|
+
toastId={t}
|
|
59
|
+
/>
|
|
60
|
+
));
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const menuHasSetDefault = !hideSetDefault && !card.is_default;
|
|
66
|
+
const menuHasDelete = !hideDelete;
|
|
67
|
+
const hasMenuItems = menuHasSetDefault || menuHasDelete;
|
|
68
|
+
const renderMenu = showMenu && hasMenuItems;
|
|
69
|
+
|
|
29
70
|
return (
|
|
30
71
|
<div
|
|
31
72
|
className={`flex items-center p-4 cursor-pointer transition-colors gap-3 rounded-lg border ${
|
|
32
|
-
isSelected
|
|
73
|
+
isSelected
|
|
74
|
+
? 'bg-zinc-50 border-zinc-950'
|
|
75
|
+
: 'bg-white border-zinc-200 hover:bg-zinc-50'
|
|
33
76
|
}`}
|
|
34
77
|
onClick={onSelect}
|
|
35
78
|
>
|
|
@@ -41,8 +84,13 @@ export function CardItem({
|
|
|
41
84
|
•••• {(card.number ?? '').replaceAll(/\D/g, '').slice(-4)}
|
|
42
85
|
</span>
|
|
43
86
|
</div>
|
|
87
|
+
{card.is_default && (
|
|
88
|
+
<span className="paragraph-xsmall-semibold text-zinc-600 bg-zinc-100 px-2 py-0.5 rounded-lg whitespace-nowrap">
|
|
89
|
+
Padrão
|
|
90
|
+
</span>
|
|
91
|
+
)}
|
|
44
92
|
<CardBrandIcon brand={card.brand} />
|
|
45
|
-
{
|
|
93
|
+
{renderMenu && (
|
|
46
94
|
<Popover open={popoverOpen} onOpenChange={setPopoverOpen}>
|
|
47
95
|
<PopoverTrigger asChild>
|
|
48
96
|
<button
|
|
@@ -54,16 +102,26 @@ export function CardItem({
|
|
|
54
102
|
</PopoverTrigger>
|
|
55
103
|
<PopoverContent
|
|
56
104
|
align="end"
|
|
57
|
-
className="w-[
|
|
105
|
+
className="w-[180px] p-2 bg-white rounded-2xl border border-zinc-200"
|
|
58
106
|
onClick={(e) => e.stopPropagation()}
|
|
59
107
|
>
|
|
60
|
-
{
|
|
108
|
+
{menuHasSetDefault && (
|
|
109
|
+
<NavBarItem
|
|
110
|
+
icon={<IconStar size={18} className="text-zinc-400" />}
|
|
111
|
+
label="Tornar padrão"
|
|
112
|
+
onClick={() => {
|
|
113
|
+
setPopoverOpen(false);
|
|
114
|
+
handleSetDefault();
|
|
115
|
+
}}
|
|
116
|
+
/>
|
|
117
|
+
)}
|
|
118
|
+
{menuHasDelete && (
|
|
61
119
|
<NavBarItem
|
|
62
|
-
icon={<IconTrash size={
|
|
120
|
+
icon={<IconTrash size={18} className="text-zinc-400" />}
|
|
63
121
|
label="Excluir"
|
|
64
122
|
onClick={() => {
|
|
65
123
|
setPopoverOpen(false);
|
|
66
|
-
|
|
124
|
+
handleDeleteCard(card);
|
|
67
125
|
}}
|
|
68
126
|
/>
|
|
69
127
|
)}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import { useCallback, useState } from 'react';
|
|
3
4
|
import { IconChevronDown, IconPlus } from '@tabler/icons-react';
|
|
4
5
|
import { CardBrandIcon } from './CardBrandIcons';
|
|
5
6
|
import { CardItem } from './CardItem';
|
|
6
7
|
import useIsMobile from '../../../hooks/useIsMobile';
|
|
7
8
|
import { useCards } from '../../../modules/cards/hooks/cards.hook';
|
|
8
|
-
import { useCallback, useState } from 'react';
|
|
9
9
|
import { useModalManager } from '../../../store/useModalManager';
|
|
10
10
|
import { useIsDefaultWhitelabel } from '../../../providers/whitelabel.provider';
|
|
11
11
|
import type { Card } from '../../../modules/cards/types';
|
|
@@ -16,6 +16,8 @@ type PaymentInfoCardProps = {
|
|
|
16
16
|
onCardSelect: (cardId: string) => void;
|
|
17
17
|
onManageCards?: () => void;
|
|
18
18
|
onOpenAddCard?: () => void;
|
|
19
|
+
hideDelete?: boolean;
|
|
20
|
+
hideSetDefault?: boolean;
|
|
19
21
|
};
|
|
20
22
|
|
|
21
23
|
export function PaymentInfoCard({
|
|
@@ -24,6 +26,8 @@ export function PaymentInfoCard({
|
|
|
24
26
|
onCardSelect,
|
|
25
27
|
onManageCards,
|
|
26
28
|
onOpenAddCard,
|
|
29
|
+
hideDelete,
|
|
30
|
+
hideSetDefault,
|
|
27
31
|
}: PaymentInfoCardProps) {
|
|
28
32
|
const isMobile = useIsMobile();
|
|
29
33
|
const { openModal } = useModalManager();
|
|
@@ -34,9 +38,11 @@ export function PaymentInfoCard({
|
|
|
34
38
|
const { data: cardsData } = useCards();
|
|
35
39
|
const cards = cardsData?.data ?? [];
|
|
36
40
|
|
|
37
|
-
const selectedCard = cards.find(
|
|
41
|
+
const selectedCard = cards.find(
|
|
42
|
+
(card) => card.id.toString() === selectedCardId.toString(),
|
|
43
|
+
);
|
|
38
44
|
|
|
39
|
-
const
|
|
45
|
+
const onlyOneCard = cards.length <= 1;
|
|
40
46
|
const hasCards = cards.length > 0;
|
|
41
47
|
|
|
42
48
|
const hasMoreThan3Cards = cards.length > 3;
|
|
@@ -53,32 +59,24 @@ export function PaymentInfoCard({
|
|
|
53
59
|
});
|
|
54
60
|
}, [openModal, onOpenAddCard, onCardSelect]);
|
|
55
61
|
|
|
56
|
-
const handleDeleteCard = useCallback(
|
|
57
|
-
(card: Card) => {
|
|
58
|
-
if (canDeleteCards) {
|
|
59
|
-
openModal('deleteCardModal', {
|
|
60
|
-
cardId: card.id.toString(),
|
|
61
|
-
cardLastDigits: (card.number ?? '').replaceAll(/\D/g, '').slice(-4),
|
|
62
|
-
onSuccess: () => {
|
|
63
|
-
if (card.id === selectedCardId && cards.length > 1) {
|
|
64
|
-
const remainingCards = cards.filter((c) => c.id !== card.id);
|
|
65
|
-
if (remainingCards.length > 0) {
|
|
66
|
-
onCardSelect(remainingCards[0].id.toString());
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
},
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
},
|
|
73
|
-
[canDeleteCards, openModal, selectedCardId, cards, onCardSelect]
|
|
74
|
-
);
|
|
75
|
-
|
|
76
62
|
const handleSelectCard = useCallback(
|
|
77
63
|
(cardId: string) => {
|
|
78
64
|
onCardSelect(cardId);
|
|
79
65
|
setIsExpanded(false);
|
|
80
66
|
},
|
|
81
|
-
[onCardSelect]
|
|
67
|
+
[onCardSelect],
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const handleAfterDelete = useCallback(
|
|
71
|
+
(card: Card) => {
|
|
72
|
+
if (card.id.toString() === selectedCardId.toString()) {
|
|
73
|
+
const remaining = cards.filter((c) => c.id !== card.id);
|
|
74
|
+
if (remaining.length > 0) {
|
|
75
|
+
onCardSelect(remaining[0].id.toString());
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
[cards, selectedCardId, onCardSelect],
|
|
82
80
|
);
|
|
83
81
|
|
|
84
82
|
if (!hasCards) {
|
|
@@ -123,7 +121,9 @@ export function PaymentInfoCard({
|
|
|
123
121
|
<>
|
|
124
122
|
<span className="paragraph-small-medium text-zinc-500 w-[100px]">Pagamento</span>
|
|
125
123
|
<div className="flex flex-col gap-0.5 flex-1 min-w-0">
|
|
126
|
-
<span className="paragraph-small-semibold text-zinc-950 truncate">
|
|
124
|
+
<span className="paragraph-small-semibold text-zinc-950 truncate">
|
|
125
|
+
{selectedCard?.name}
|
|
126
|
+
</span>
|
|
127
127
|
<span className="paragraph-small-medium text-zinc-950">
|
|
128
128
|
•••• {selectedCard?.number?.replaceAll(/\D/g, '')?.slice(-4)}
|
|
129
129
|
</span>
|
|
@@ -156,9 +156,9 @@ export function PaymentInfoCard({
|
|
|
156
156
|
card={card}
|
|
157
157
|
isSelected={card.id.toString() === selectedCardId.toString()}
|
|
158
158
|
onSelect={() => handleSelectCard(card.id.toString())}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
159
|
+
hideDelete={hideDelete || onlyOneCard}
|
|
160
|
+
hideSetDefault={hideSetDefault}
|
|
161
|
+
onAfterDelete={handleAfterDelete}
|
|
162
162
|
/>
|
|
163
163
|
))}
|
|
164
164
|
{shouldShowManageButton && onManageCards && (
|
package/src/index.ts
CHANGED
|
@@ -73,11 +73,15 @@ export { usePlanById } from "./modules/plans/hooks/use-plan-by-id.hook";
|
|
|
73
73
|
export { useHasPlanAddon } from "./modules/plans/hooks/use-has-plan-addon.hook";
|
|
74
74
|
export { useCalculateSubscription } from "./modules/subscriptions/hooks/calculate-subscription.hook";
|
|
75
75
|
export { useUpdateSubscriptionPlan } from "./modules/subscriptions/hooks/update-subscription-plan.hook";
|
|
76
|
+
export { useUpdateSubscriptionPayment } from "./modules/subscriptions/hooks/update-subscription-payment.hook";
|
|
76
77
|
export { useCards, CARDS_QUERY_KEY } from "./modules/cards/hooks/cards.hook";
|
|
77
78
|
export { useCardById } from "./modules/cards/hooks/card-by-id.hook";
|
|
78
79
|
export { useCreateCard } from "./modules/cards/hooks/create-card.hook";
|
|
79
80
|
export { useSetDefaultCard } from "./modules/cards/hooks/set-default-card.hook";
|
|
80
81
|
export { useDeleteCard } from "./modules/cards/hooks/delete-card.hook";
|
|
82
|
+
export { useDeleteConfirmation } from "./modules/cards/hooks/delete-confirmation.hook";
|
|
83
|
+
export type { DeleteConfirmationFormData } from "./modules/cards/hooks/delete-confirmation.hook";
|
|
84
|
+
export { useHandleDeleteCard } from "./modules/cards/hooks/handle-delete-card.hook";
|
|
81
85
|
export { useIaCredits } from "./modules/ia-credits/hooks/ia-credits.hook";
|
|
82
86
|
export type {
|
|
83
87
|
IaCreditsSummary,
|
|
@@ -150,6 +154,8 @@ export { usePaidPlanRequiredModal } from "./store/usePaidPlanRequiredModal";
|
|
|
150
154
|
export { default as BuyCreditsModal } from "./components/modals/BuyCreditsModal";
|
|
151
155
|
export { default as PaidPlanRequiredModal } from "./components/modals/PaidPlanRequiredModal";
|
|
152
156
|
export { default as AddCardModal } from "./components/modals/cards/AddCardModal";
|
|
157
|
+
export { DeleteCardModal } from "./components/modals/cards/DeleteCardModal";
|
|
158
|
+
export { CannotDeleteCardModal } from "./components/modals/cards/CannotDeleteCardModal";
|
|
153
159
|
export {
|
|
154
160
|
CardFormFields,
|
|
155
161
|
cardFormSchema,
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback } from 'react';
|
|
4
|
+
import { useForm } from 'react-hook-form';
|
|
5
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
6
|
+
import z from 'zod';
|
|
7
|
+
|
|
8
|
+
type UseDeleteConfirmationProps = {
|
|
9
|
+
confirmWord?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function createDeleteConfirmationSchema(confirmWord: string) {
|
|
13
|
+
return z.object({
|
|
14
|
+
confirmText: z.string().refine((val) => val === confirmWord, {
|
|
15
|
+
message: `Digite ${confirmWord} para confirmar`,
|
|
16
|
+
}),
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type DeleteConfirmationFormData = z.infer<
|
|
21
|
+
ReturnType<typeof createDeleteConfirmationSchema>
|
|
22
|
+
>;
|
|
23
|
+
|
|
24
|
+
export function useDeleteConfirmation({
|
|
25
|
+
confirmWord = 'EXCLUIR',
|
|
26
|
+
}: UseDeleteConfirmationProps = {}) {
|
|
27
|
+
const schema = createDeleteConfirmationSchema(confirmWord);
|
|
28
|
+
|
|
29
|
+
const form = useForm<DeleteConfirmationFormData>({
|
|
30
|
+
resolver: zodResolver(schema),
|
|
31
|
+
defaultValues: { confirmText: '' },
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const confirmText = form.watch('confirmText');
|
|
35
|
+
const isValidConfirmation = confirmText === confirmWord;
|
|
36
|
+
|
|
37
|
+
const resetConfirmation = useCallback(() => {
|
|
38
|
+
form.reset({ confirmText: '' });
|
|
39
|
+
}, [form]);
|
|
40
|
+
|
|
41
|
+
return { form, confirmText, isValidConfirmation, resetConfirmation };
|
|
42
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback } from 'react';
|
|
4
|
+
import { useModalManager } from '../../../store/useModalManager';
|
|
5
|
+
import { useActiveSubscription } from '../../subscriptions/hooks/find-active-subscription.hook';
|
|
6
|
+
import type { Card } from '../types';
|
|
7
|
+
|
|
8
|
+
type UseHandleDeleteCardOptions = {
|
|
9
|
+
onAfterDelete?: (card: Card) => void;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function useHandleDeleteCard({
|
|
13
|
+
onAfterDelete,
|
|
14
|
+
}: UseHandleDeleteCardOptions = {}) {
|
|
15
|
+
const { openModal } = useModalManager();
|
|
16
|
+
const { data: subscriptionData } = useActiveSubscription();
|
|
17
|
+
const activeSubscription = subscriptionData?.data?.[0] ?? null;
|
|
18
|
+
|
|
19
|
+
return useCallback(
|
|
20
|
+
(card: Card) => {
|
|
21
|
+
const isCardLinkedToSubscription =
|
|
22
|
+
!!activeSubscription &&
|
|
23
|
+
!!activeSubscription.date_cancellation &&
|
|
24
|
+
(!activeSubscription.date_due ||
|
|
25
|
+
activeSubscription.date_due < new Date()) &&
|
|
26
|
+
card.id.toString() === activeSubscription.id_card?.toString();
|
|
27
|
+
|
|
28
|
+
if (isCardLinkedToSubscription) {
|
|
29
|
+
openModal('cannotDeleteCardModal', {
|
|
30
|
+
cardId: card.id.toString(),
|
|
31
|
+
});
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
openModal('deleteCardModal', {
|
|
36
|
+
cardId: card.id.toString(),
|
|
37
|
+
...(onAfterDelete && { onSuccess: () => onAfterDelete(card) }),
|
|
38
|
+
});
|
|
39
|
+
},
|
|
40
|
+
[activeSubscription, openModal, onAfterDelete],
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use server";
|
|
2
|
+
|
|
3
|
+
import { safeMutationAction } from "../../../utils/safeMutationAction";
|
|
4
|
+
import { subscriptionsService } from "../services/subscriptions.service";
|
|
5
|
+
|
|
6
|
+
export async function updateSubscriptionPaymentAction(
|
|
7
|
+
subscriptionId: number | string,
|
|
8
|
+
data: { id_card: string; payment_method: number },
|
|
9
|
+
) {
|
|
10
|
+
return safeMutationAction(async () => {
|
|
11
|
+
return subscriptionsService.updateSubscriptionPayment(subscriptionId, data);
|
|
12
|
+
});
|
|
13
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
4
|
+
import { withAction } from '../../../utils/withAction';
|
|
5
|
+
import { updateSubscriptionPaymentAction } from '../actions/update-subscription-payment.action';
|
|
6
|
+
import { SUBSCRIPTIONS_QUERY_KEY } from '../constants/query-keys.constants';
|
|
7
|
+
import { useAuthQueryKey } from '../../../hooks/useAuthQueryKey';
|
|
8
|
+
|
|
9
|
+
export function useUpdateSubscriptionPayment() {
|
|
10
|
+
const queryClient = useQueryClient();
|
|
11
|
+
const subscriptionsKey = useAuthQueryKey([...SUBSCRIPTIONS_QUERY_KEY]);
|
|
12
|
+
|
|
13
|
+
return useMutation({
|
|
14
|
+
mutationFn: ({
|
|
15
|
+
subscriptionId,
|
|
16
|
+
data,
|
|
17
|
+
}: {
|
|
18
|
+
subscriptionId: number | string;
|
|
19
|
+
data: { id_card: string; payment_method: number };
|
|
20
|
+
}) => withAction(updateSubscriptionPaymentAction)(subscriptionId, data),
|
|
21
|
+
onSuccess: () => {
|
|
22
|
+
queryClient.invalidateQueries({ queryKey: subscriptionsKey });
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -104,6 +104,29 @@ class SubscriptionsService {
|
|
|
104
104
|
|
|
105
105
|
return { success: true };
|
|
106
106
|
}
|
|
107
|
+
|
|
108
|
+
async updateSubscriptionPayment(
|
|
109
|
+
subscriptionId: number | string,
|
|
110
|
+
data: { id_card: string; payment_method: number },
|
|
111
|
+
): Promise<SuccessResult<void>> {
|
|
112
|
+
await assertManagementPermission("manage_subscriptions");
|
|
113
|
+
const { id_account } = await getUserContext();
|
|
114
|
+
|
|
115
|
+
const response = await api.apps.put<ApiActionResult<void>>(
|
|
116
|
+
`/accounts/${id_account}/subscriptions/${subscriptionId}/action/payment`,
|
|
117
|
+
data,
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
if (response.status === 0) {
|
|
121
|
+
throw new ApiError(
|
|
122
|
+
response.message || "Erro ao atualizar método de pagamento",
|
|
123
|
+
"UPDATE_SUBSCRIPTION_PAYMENT_FAILED",
|
|
124
|
+
400,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return { success: true };
|
|
129
|
+
}
|
|
107
130
|
}
|
|
108
131
|
|
|
109
132
|
export const subscriptionsService = new SubscriptionsService();
|
|
@@ -36,7 +36,7 @@ export const SubscriptionSchema = z.object({
|
|
|
36
36
|
periodicity: z.coerce.number().nullable(),
|
|
37
37
|
value: z.number().nullable(),
|
|
38
38
|
id_product: z.number(),
|
|
39
|
-
id_plan: z.number(),
|
|
39
|
+
id_plan: z.number().nullable(),
|
|
40
40
|
plan_name: z.string().nullable().optional(),
|
|
41
41
|
id_gateway: z.union([z.number(), z.string()]).nullable(),
|
|
42
42
|
gateway_account: z.string().nullable(),
|
package/src/server.ts
CHANGED
|
@@ -27,6 +27,7 @@ export { findPlanByIdAction } from './modules/plans/actions/find-plan-by-id.acti
|
|
|
27
27
|
export { listPlansAction } from './modules/plans/actions/list-plans.action';
|
|
28
28
|
export { calculateSubscriptionAction } from './modules/subscriptions/actions/calculate-subscription.action';
|
|
29
29
|
export { updateSubscriptionPlanAction } from './modules/subscriptions/actions/update-subscription-plan.action';
|
|
30
|
+
export { updateSubscriptionPaymentAction } from './modules/subscriptions/actions/update-subscription-payment.action';
|
|
30
31
|
export { listCardsAction } from './modules/cards/actions/list-cards.action';
|
|
31
32
|
export { createCardAction } from './modules/cards/actions/create-card.action';
|
|
32
33
|
export { resolvePlanExtrasPrices } from './modules/subscriptions/utils/resolve-plan-extras-prices';
|