@greatapps/common 1.1.275 → 1.1.277
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/layouts/WhitelabelCodes.mjs +1 -1
- package/dist/components/layouts/WhitelabelCodes.mjs.map +1 -1
- package/dist/components/modals/cards/AddCardModal.mjs +133 -0
- package/dist/components/modals/cards/AddCardModal.mjs.map +1 -0
- package/dist/components/modals/cards/CardFormFields.mjs +134 -0
- package/dist/components/modals/cards/CardFormFields.mjs.map +1 -0
- package/dist/index.mjs +39 -31
- package/dist/index.mjs.map +1 -1
- package/dist/modules/cards/actions/create-card.action.mjs +10 -0
- package/dist/modules/cards/actions/create-card.action.mjs.map +1 -0
- package/dist/modules/cards/hooks/create-card.hook.mjs +18 -0
- package/dist/modules/cards/hooks/create-card.hook.mjs.map +1 -0
- package/dist/modules/cards/services/cards.service.mjs +15 -0
- package/dist/modules/cards/services/cards.service.mjs.map +1 -1
- package/dist/modules/whitelabel/services/whitelabel.service.mjs +3 -2
- package/dist/modules/whitelabel/services/whitelabel.service.mjs.map +1 -1
- package/dist/modules/whitelabel/utils/normalize-hostname.mjs +33 -0
- package/dist/modules/whitelabel/utils/normalize-hostname.mjs.map +1 -0
- package/dist/server.mjs +2 -0
- package/dist/server.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/layouts/WhitelabelCodes.tsx +11 -11
- package/src/components/modals/cards/AddCardModal.tsx +148 -0
- package/src/components/modals/cards/CardFormFields.tsx +158 -0
- package/src/index.ts +6 -2
- package/src/modules/cards/actions/create-card.action.ts +9 -0
- package/src/modules/cards/hooks/create-card.hook.ts +18 -0
- package/src/modules/cards/services/cards.service.ts +21 -2
- package/src/modules/whitelabel/services/whitelabel.service.ts +3 -2
- package/src/modules/whitelabel/utils/normalize-hostname.ts +26 -0
- package/src/server.ts +1 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { UseFormReturn } from 'react-hook-form';
|
|
4
|
+
import { FormField } from '../../ui/form/FormField';
|
|
5
|
+
import { SelectField } from '../../ui/form/SelectField';
|
|
6
|
+
import { formatCPF, formatPostalCode } from '../../../utils/format/masks';
|
|
7
|
+
import z from 'zod';
|
|
8
|
+
import { CardCvcElement, CardExpiryElement, CardNumberElement } from '@stripe/react-stripe-js';
|
|
9
|
+
|
|
10
|
+
const stripeElementOptions = {
|
|
11
|
+
style: {
|
|
12
|
+
base: {
|
|
13
|
+
fontSize: '16px',
|
|
14
|
+
color: '#030712',
|
|
15
|
+
fontFamily: 'Inter, sans-serif',
|
|
16
|
+
'::placeholder': {
|
|
17
|
+
color: '#9ca3af',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const cardFormSchema = z.object({
|
|
24
|
+
name: z.string().min(1, 'Nome é obrigatório'),
|
|
25
|
+
number: z.string().min(1, 'Número do cartão é obrigatório'),
|
|
26
|
+
expiry: z.string().min(1, 'Data de validade é obrigatória'),
|
|
27
|
+
cvv: z.string().min(1, 'Código de segurança é obrigatório'),
|
|
28
|
+
country: z.string().min(1, 'País é obrigatório'),
|
|
29
|
+
postalCode: z.string().optional(),
|
|
30
|
+
cpf: z.string().min(14, 'CPF é obrigatório'),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export type CardFormData = z.infer<typeof cardFormSchema>;
|
|
34
|
+
|
|
35
|
+
type CardFormFieldsProps = {
|
|
36
|
+
readonly form: UseFormReturn<CardFormData>;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export function CardFormFields({ form }: CardFormFieldsProps) {
|
|
40
|
+
const {
|
|
41
|
+
register,
|
|
42
|
+
watch,
|
|
43
|
+
setValue,
|
|
44
|
+
formState: { errors },
|
|
45
|
+
} = form;
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<div className="flex flex-col gap-5">
|
|
49
|
+
<FormField
|
|
50
|
+
label="Nome impresso no cartão"
|
|
51
|
+
placeholder="Digite aqui"
|
|
52
|
+
{...register('name')}
|
|
53
|
+
error={!!errors.name}
|
|
54
|
+
errorMessage={errors.name?.message}
|
|
55
|
+
/>
|
|
56
|
+
|
|
57
|
+
<div className="flex flex-col gap-1.5">
|
|
58
|
+
<label className="paragraph-xsmall-semibold text-zinc-600">
|
|
59
|
+
Número do cartão
|
|
60
|
+
</label>
|
|
61
|
+
<div className="h-10 px-3 flex items-center w-full border border-zinc-200 rounded-lg bg-white transition-colors focus-within:border-zinc-400 relative">
|
|
62
|
+
<div className="w-full">
|
|
63
|
+
<CardNumberElement
|
|
64
|
+
options={{ ...stripeElementOptions, showIcon: true }}
|
|
65
|
+
onChange={(e) =>
|
|
66
|
+
setValue('number', e.complete ? 'complete' : '', { shouldValidate: true })
|
|
67
|
+
}
|
|
68
|
+
/>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
{errors.number && (
|
|
72
|
+
<p className="paragraph-xsmall-semibold text-red-600">{errors.number.message}</p>
|
|
73
|
+
)}
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<div className="flex gap-4">
|
|
77
|
+
<div className="flex-1 flex flex-col gap-1.5">
|
|
78
|
+
<label className="paragraph-xsmall-semibold text-zinc-600">
|
|
79
|
+
Data de validade
|
|
80
|
+
</label>
|
|
81
|
+
<div className="h-10 px-3 flex items-center w-full border border-zinc-200 rounded-lg bg-white transition-colors focus-within:border-zinc-400">
|
|
82
|
+
<div className="w-full">
|
|
83
|
+
<CardExpiryElement
|
|
84
|
+
options={stripeElementOptions}
|
|
85
|
+
onChange={(e) =>
|
|
86
|
+
setValue('expiry', e.complete ? 'complete' : '', { shouldValidate: true })
|
|
87
|
+
}
|
|
88
|
+
/>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
{errors.expiry && (
|
|
92
|
+
<p className="paragraph-xsmall-semibold text-red-600">{errors.expiry.message}</p>
|
|
93
|
+
)}
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<div className="flex-1 flex flex-col gap-1.5">
|
|
97
|
+
<label className="paragraph-xsmall-semibold text-zinc-600">
|
|
98
|
+
CVC
|
|
99
|
+
</label>
|
|
100
|
+
<div className="h-10 px-3 flex items-center w-full border border-zinc-200 rounded-lg bg-white transition-colors focus-within:border-zinc-400">
|
|
101
|
+
<div className="w-full">
|
|
102
|
+
<CardCvcElement
|
|
103
|
+
options={stripeElementOptions}
|
|
104
|
+
onChange={(e) =>
|
|
105
|
+
setValue('cvv', e.complete ? 'complete' : '', { shouldValidate: true })
|
|
106
|
+
}
|
|
107
|
+
/>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
{errors.cvv && (
|
|
111
|
+
<p className="paragraph-xsmall-semibold text-red-600">{errors.cvv.message}</p>
|
|
112
|
+
)}
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<div className="flex gap-4">
|
|
117
|
+
<SelectField
|
|
118
|
+
label="País"
|
|
119
|
+
placeholder="Selecione"
|
|
120
|
+
value={watch('country')}
|
|
121
|
+
onChange={(value) => setValue('country', value as string)}
|
|
122
|
+
options={[
|
|
123
|
+
{ value: 'BR', label: 'Brasil' },
|
|
124
|
+
{ value: 'ES', label: 'Espanha' },
|
|
125
|
+
{ value: 'US', label: 'Estados Unidos' },
|
|
126
|
+
]}
|
|
127
|
+
containerClassName="flex-1"
|
|
128
|
+
/>
|
|
129
|
+
<div className="flex-1">
|
|
130
|
+
<FormField
|
|
131
|
+
label="Código postal"
|
|
132
|
+
optional
|
|
133
|
+
placeholder=""
|
|
134
|
+
{...register('postalCode', {
|
|
135
|
+
onChange: (e) => {
|
|
136
|
+
setValue('postalCode', formatPostalCode(e.target.value));
|
|
137
|
+
},
|
|
138
|
+
})}
|
|
139
|
+
error={!!errors.postalCode}
|
|
140
|
+
errorMessage={errors.postalCode?.message}
|
|
141
|
+
/>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<FormField
|
|
146
|
+
label="CPF"
|
|
147
|
+
placeholder="___.___.___-__"
|
|
148
|
+
{...register('cpf', {
|
|
149
|
+
onChange: (e) => {
|
|
150
|
+
setValue('cpf', formatCPF(e.target.value));
|
|
151
|
+
},
|
|
152
|
+
})}
|
|
153
|
+
error={!!errors.cpf}
|
|
154
|
+
errorMessage={errors.cpf?.message}
|
|
155
|
+
/>
|
|
156
|
+
</div>
|
|
157
|
+
);
|
|
158
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -44,6 +44,7 @@ export { useHasPlanAddon } from "./modules/plans/hooks/use-has-plan-addon.hook";
|
|
|
44
44
|
export { useCalculateSubscription } from "./modules/subscriptions/hooks/calculate-subscription.hook";
|
|
45
45
|
export { useUpdateSubscriptionPlan } from "./modules/subscriptions/hooks/update-subscription-plan.hook";
|
|
46
46
|
export { useCards, CARDS_QUERY_KEY } from "./modules/cards/hooks/cards.hook";
|
|
47
|
+
export { useCreateCard } from "./modules/cards/hooks/create-card.hook";
|
|
47
48
|
export { useIaCredits } from "./modules/ia-credits/hooks/ia-credits.hook";
|
|
48
49
|
export type {
|
|
49
50
|
IaCreditsSummary,
|
|
@@ -68,12 +69,15 @@ export type {
|
|
|
68
69
|
CalculateSubscriptionResponse,
|
|
69
70
|
UpdateSubscriptionPlanRequest,
|
|
70
71
|
} from "./modules/subscriptions/types/calculate-subscription.type";
|
|
71
|
-
export type { Card as PaymentCard } from "./modules/cards/types";
|
|
72
|
-
export { CardSchema as PaymentCardSchema } from "./modules/cards/types";
|
|
72
|
+
export type { Card as PaymentCard, CreateCardRequest } from "./modules/cards/types";
|
|
73
|
+
export { CardSchema as PaymentCardSchema, CreateCardRequestSchema } from "./modules/cards/types";
|
|
73
74
|
export { buildPlanExtras } from "./modules/subscriptions/utils/build-plan-extras";
|
|
74
75
|
export { getPriceFromCalculatedData, periodicityToBillingPeriod } from "./modules/subscriptions/utils/periodicity";
|
|
75
76
|
export { useBuyCreditsModal } from "./store/useBuyCreditsModal";
|
|
76
77
|
export { default as BuyCreditsModal } from "./components/modals/BuyCreditsModal";
|
|
78
|
+
export { default as AddCardModal } from "./components/modals/cards/AddCardModal";
|
|
79
|
+
export { CardFormFields, cardFormSchema } from "./components/modals/cards/CardFormFields";
|
|
80
|
+
export type { CardFormData } from "./components/modals/cards/CardFormFields";
|
|
77
81
|
export { Skeleton } from "./components/ui/feedback/Skeleton";
|
|
78
82
|
export { PaymentInfoCard } from "./components/ui/data-display/PaymentInfoCard";
|
|
79
83
|
export { CardBrandIcon } from "./components/ui/data-display/CardBrandIcons";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
'use server';
|
|
2
|
+
|
|
3
|
+
import { safeServerAction } from '../../../utils/safeServerAction';
|
|
4
|
+
import { cardsService } from '../services/cards.service';
|
|
5
|
+
import { CreateCardRequest } from '../types';
|
|
6
|
+
|
|
7
|
+
export async function createCardAction(data: CreateCardRequest) {
|
|
8
|
+
return safeServerAction(() => cardsService.createCard(data));
|
|
9
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
4
|
+
import { withAction } from '../../../utils/withAction';
|
|
5
|
+
import { createCardAction } from '../actions/create-card.action';
|
|
6
|
+
import { CreateCardRequest } from '../types';
|
|
7
|
+
import { CARDS_QUERY_KEY } from './cards.hook';
|
|
8
|
+
|
|
9
|
+
export function useCreateCard() {
|
|
10
|
+
const queryClient = useQueryClient();
|
|
11
|
+
|
|
12
|
+
return useMutation({
|
|
13
|
+
mutationFn: (data: CreateCardRequest) => withAction(createCardAction)(data),
|
|
14
|
+
onSuccess: () => {
|
|
15
|
+
queryClient.invalidateQueries({ queryKey: CARDS_QUERY_KEY });
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import 'server-only';
|
|
2
2
|
|
|
3
3
|
import { api } from '../../../infra/api/client';
|
|
4
|
-
import { ApiError, ApiPaginatedActionResult, PaginatedSuccessResult } from '../../../infra/api/types';
|
|
4
|
+
import { ApiError, ApiPaginatedActionResult, PaginatedSuccessResult, SuccessResult } from '../../../infra/api/types';
|
|
5
5
|
import { buildQueryParams } from '../../../infra/utils/params';
|
|
6
6
|
import { getUserContext } from '../../auth/utils/get-user-context';
|
|
7
|
-
import { Card, CardSchema } from '../types';
|
|
7
|
+
import { Card, CardSchema, CreateCardRequest } from '../types';
|
|
8
8
|
|
|
9
9
|
class CardsService {
|
|
10
10
|
async listCards(): Promise<PaginatedSuccessResult<Card>> {
|
|
@@ -27,6 +27,25 @@ class CardsService {
|
|
|
27
27
|
|
|
28
28
|
return { data, total: response.total, success: true };
|
|
29
29
|
}
|
|
30
|
+
async createCard(data: CreateCardRequest): Promise<SuccessResult<Card>> {
|
|
31
|
+
const { id_account } = await getUserContext();
|
|
32
|
+
|
|
33
|
+
const response = await api.apps.post<ApiPaginatedActionResult<Card>>(
|
|
34
|
+
`/accounts/${id_account}/cards`,
|
|
35
|
+
data
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
if (response.status === 0) {
|
|
39
|
+
throw new ApiError(response.message || 'Erro ao criar cartão', 'CREATE_CARD_FAILED', 400);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const cardData = CardSchema.parse(response.data[0]);
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
success: true,
|
|
46
|
+
data: cardData,
|
|
47
|
+
} satisfies SuccessResult<Card>;
|
|
48
|
+
}
|
|
30
49
|
}
|
|
31
50
|
|
|
32
51
|
export const cardsService = new CardsService();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import greatCache from "@greatapps/cache";
|
|
2
2
|
import { ApiError } from "../../../infra/api/types";
|
|
3
3
|
import { WhitelabelTokenApiResponse, WhitelabelTokenData } from "../schema";
|
|
4
|
+
import { normalizeHostname } from "../utils/normalize-hostname";
|
|
4
5
|
|
|
5
6
|
class WhitelabelService {
|
|
6
7
|
private getApiUrl(): string {
|
|
@@ -75,7 +76,7 @@ class WhitelabelService {
|
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
async getTokenByDomain(hostname: string): Promise<WhitelabelTokenData> {
|
|
78
|
-
hostname = process.env.WHITELABEL_DOMAIN || hostname;
|
|
79
|
+
hostname = normalizeHostname(process.env.WHITELABEL_DOMAIN || hostname);
|
|
79
80
|
|
|
80
81
|
const cache = this.createCache();
|
|
81
82
|
const cacheKey = this.getCacheKey(hostname);
|
|
@@ -136,7 +137,7 @@ class WhitelabelService {
|
|
|
136
137
|
}
|
|
137
138
|
|
|
138
139
|
async revalidateByDomain(hostname: string): Promise<WhitelabelTokenData> {
|
|
139
|
-
hostname = process.env.WHITELABEL_DOMAIN || hostname;
|
|
140
|
+
hostname = normalizeHostname(process.env.WHITELABEL_DOMAIN || hostname);
|
|
140
141
|
|
|
141
142
|
const cache = this.createCache();
|
|
142
143
|
const cacheKey = this.getCacheKey(hostname);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const CATEGORY_PREFIXES = new Set([
|
|
2
|
+
"com", "dev", "net", "org", "edu", "gov",
|
|
3
|
+
"mil", "co", "art", "inf", "blog", "wiki", "app",
|
|
4
|
+
]);
|
|
5
|
+
|
|
6
|
+
const SIMPLE_TLDS = new Set(["online"]);
|
|
7
|
+
|
|
8
|
+
export function normalizeHostname(hostname: string): string {
|
|
9
|
+
const parts = hostname.split(".");
|
|
10
|
+
if (parts.length <= 2) return hostname;
|
|
11
|
+
|
|
12
|
+
const last = parts[parts.length - 1];
|
|
13
|
+
const secondLast = parts[parts.length - 2];
|
|
14
|
+
|
|
15
|
+
const isCompoundTld =
|
|
16
|
+
last.length <= 3 && CATEGORY_PREFIXES.has(secondLast);
|
|
17
|
+
|
|
18
|
+
const rootParts = isCompoundTld ? 3 : 2;
|
|
19
|
+
|
|
20
|
+
if (SIMPLE_TLDS.has(last) || !isCompoundTld) {
|
|
21
|
+
return parts.slice(-2).join(".");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (parts.length <= rootParts) return hostname;
|
|
25
|
+
return parts.slice(-rootParts).join(".");
|
|
26
|
+
}
|
package/src/server.ts
CHANGED
|
@@ -22,6 +22,7 @@ export { findPlanByIdAction } from './modules/plans/actions/find-plan-by-id.acti
|
|
|
22
22
|
export { calculateSubscriptionAction } from './modules/subscriptions/actions/calculate-subscription.action';
|
|
23
23
|
export { updateSubscriptionPlanAction } from './modules/subscriptions/actions/update-subscription-plan.action';
|
|
24
24
|
export { listCardsAction } from './modules/cards/actions/list-cards.action';
|
|
25
|
+
export { createCardAction } from './modules/cards/actions/create-card.action';
|
|
25
26
|
export { resolvePlanExtrasPrices } from './modules/subscriptions/utils/resolve-plan-extras-prices';
|
|
26
27
|
export { listIaCreditsAction } from './modules/ia-credits/actions/list-ia-credits.action';
|
|
27
28
|
|