@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.
Files changed (31) hide show
  1. package/dist/components/layouts/WhitelabelCodes.mjs +1 -1
  2. package/dist/components/layouts/WhitelabelCodes.mjs.map +1 -1
  3. package/dist/components/modals/cards/AddCardModal.mjs +133 -0
  4. package/dist/components/modals/cards/AddCardModal.mjs.map +1 -0
  5. package/dist/components/modals/cards/CardFormFields.mjs +134 -0
  6. package/dist/components/modals/cards/CardFormFields.mjs.map +1 -0
  7. package/dist/index.mjs +39 -31
  8. package/dist/index.mjs.map +1 -1
  9. package/dist/modules/cards/actions/create-card.action.mjs +10 -0
  10. package/dist/modules/cards/actions/create-card.action.mjs.map +1 -0
  11. package/dist/modules/cards/hooks/create-card.hook.mjs +18 -0
  12. package/dist/modules/cards/hooks/create-card.hook.mjs.map +1 -0
  13. package/dist/modules/cards/services/cards.service.mjs +15 -0
  14. package/dist/modules/cards/services/cards.service.mjs.map +1 -1
  15. package/dist/modules/whitelabel/services/whitelabel.service.mjs +3 -2
  16. package/dist/modules/whitelabel/services/whitelabel.service.mjs.map +1 -1
  17. package/dist/modules/whitelabel/utils/normalize-hostname.mjs +33 -0
  18. package/dist/modules/whitelabel/utils/normalize-hostname.mjs.map +1 -0
  19. package/dist/server.mjs +2 -0
  20. package/dist/server.mjs.map +1 -1
  21. package/package.json +1 -1
  22. package/src/components/layouts/WhitelabelCodes.tsx +11 -11
  23. package/src/components/modals/cards/AddCardModal.tsx +148 -0
  24. package/src/components/modals/cards/CardFormFields.tsx +158 -0
  25. package/src/index.ts +6 -2
  26. package/src/modules/cards/actions/create-card.action.ts +9 -0
  27. package/src/modules/cards/hooks/create-card.hook.ts +18 -0
  28. package/src/modules/cards/services/cards.service.ts +21 -2
  29. package/src/modules/whitelabel/services/whitelabel.service.ts +3 -2
  30. package/src/modules/whitelabel/utils/normalize-hostname.ts +26 -0
  31. 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