@greatapps/common 1.1.782 → 1.1.785

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 (38) hide show
  1. package/dist/components/layouts/AppMobileNavBar.mjs +5 -64
  2. package/dist/components/layouts/AppMobileNavBar.mjs.map +1 -1
  3. package/dist/components/layouts/AppNavBar.mjs +0 -2
  4. package/dist/components/layouts/AppNavBar.mjs.map +1 -1
  5. package/dist/components/layouts/NavBar.mjs +52 -109
  6. package/dist/components/layouts/NavBar.mjs.map +1 -1
  7. package/dist/components/modals/cards/AddCardModal.mjs +3 -2
  8. package/dist/components/modals/cards/AddCardModal.mjs.map +1 -1
  9. package/dist/i18n/messages/en-us.mjs +1 -3
  10. package/dist/i18n/messages/en-us.mjs.map +1 -1
  11. package/dist/i18n/messages/es-es.mjs +1 -3
  12. package/dist/i18n/messages/es-es.mjs.map +1 -1
  13. package/dist/i18n/messages/pt-br.mjs +1 -3
  14. package/dist/i18n/messages/pt-br.mjs.map +1 -1
  15. package/dist/index.mjs.map +1 -1
  16. package/dist/modules/subscriptions/actions/update-subscription-payment.action.mjs.map +1 -1
  17. package/dist/modules/subscriptions/hooks/update-subscription-payment.hook.mjs +10 -2
  18. package/dist/modules/subscriptions/hooks/update-subscription-payment.hook.mjs.map +1 -1
  19. package/dist/modules/subscriptions/services/subscriptions.service.mjs +12 -1
  20. package/dist/modules/subscriptions/services/subscriptions.service.mjs.map +1 -1
  21. package/dist/modules/subscriptions/types/subscription.type.mjs.map +1 -1
  22. package/package.json +1 -1
  23. package/src/components/layouts/AppMobileNavBar.tsx +6 -90
  24. package/src/components/layouts/AppNavBar.tsx +1 -5
  25. package/src/components/layouts/NavBar.tsx +66 -158
  26. package/src/components/modals/cards/AddCardModal.tsx +15 -2
  27. package/src/i18n/messages/en-us.ts +0 -2
  28. package/src/i18n/messages/es-es.ts +0 -2
  29. package/src/i18n/messages/pt-br.ts +0 -2
  30. package/src/index.ts +1 -0
  31. package/src/modules/plans/hooks/use-plan-by-id.hook.ts +1 -1
  32. package/src/modules/subscriptions/actions/update-subscription-payment.action.ts +6 -1
  33. package/src/modules/subscriptions/hooks/update-subscription-payment.hook.ts +21 -3
  34. package/src/modules/subscriptions/services/subscriptions.service.ts +46 -4
  35. package/src/modules/subscriptions/types/subscription.type.ts +27 -0
  36. package/dist/components/layouts/GreatDomainIcon.mjs +0 -40
  37. package/dist/components/layouts/GreatDomainIcon.mjs.map +0 -1
  38. package/src/components/layouts/GreatDomainIcon.tsx +0 -43
@@ -15,6 +15,7 @@ import {
15
15
  Subscription,
16
16
  SubscriptionSchema,
17
17
  FindSubscriptionsParams,
18
+ type ChargeNowResult,
18
19
  } from "../types/subscription.type";
19
20
  import {
20
21
  type CalculateSubscriptionRequest,
@@ -161,12 +162,31 @@ class SubscriptionsService {
161
162
 
162
163
  async updateSubscriptionPayment(
163
164
  subscriptionId: number | string,
164
- data: { id_card?: string; payment_method_id?: string; payment_method: number },
165
- ): Promise<SuccessResult<void>> {
165
+ data: {
166
+ id_card?: string;
167
+ payment_method_id?: string;
168
+ payment_method: number;
169
+ charge_now?: boolean;
170
+ },
171
+ ): Promise<
172
+ SuccessResult<void> & {
173
+ applied: boolean;
174
+ client_secret?: string;
175
+ charge_now?: ChargeNowResult;
176
+ }
177
+ > {
166
178
  await assertManagementPermission("manage_subscriptions");
167
179
  const { id_account } = await getUserContext();
168
180
 
169
- const response = await api.apps.put<ApiActionResult<void>>(
181
+ /* O `status: 2` do guard de 3DS pendente não cabe em `ApiActionResult`, que só conhece 0 e 1.
182
+ * Declarar o terceiro estado aqui é o que permite tratá-lo — sem isso o TypeScript apaga o
183
+ * ramo como inalcançável e o caso volta a atravessar como sucesso. */
184
+ const response = await api.apps.put<
185
+ (ApiActionResult<void> | { status: 2; message?: string }) & {
186
+ charge_now?: ChargeNowResult;
187
+ client_secret?: string;
188
+ }
189
+ >(
170
190
  `/accounts/${id_account}/subscriptions/${subscriptionId}/action/payment`,
171
191
  data,
172
192
  { timeout: SUBSCRIPTION_WRITE_TIMEOUT_MS },
@@ -180,7 +200,29 @@ class SubscriptionsService {
180
200
  );
181
201
  }
182
202
 
183
- return { success: true };
203
+ /* `status: 2` NÃO é sucesso: o guard de 3DS pendente do paymentEdit devolve esse status sem
204
+ * aplicar a troca, e como só o `status === 0` era tratado como erro, a chamada caía no
205
+ * `success: true` e a tela dizia "sua assinatura passou a ser cobrada neste cartão" para uma
206
+ * troca que não aconteceu — descartando junto o `client_secret` que resolveria a pendência.
207
+ *
208
+ * Por isso o retorno carrega `applied`: quem chama precisa poder distinguir "trocou" de
209
+ * "não trocou, conclua o pagamento pendente primeiro". */
210
+ if (response.status === 2) {
211
+ return {
212
+ success: true,
213
+ applied: false,
214
+ ...(response.client_secret ? { client_secret: response.client_secret } : {}),
215
+ };
216
+ }
217
+
218
+ /* O desfecho da cobrança imediata precisa atravessar: descartá-lo (como este método fazia com
219
+ * o corpo inteiro) apagaria tanto a recusa quanto o `client_secret` do 3DS, e a tela
220
+ * anunciaria "tudo certo" para uma fatura que continua em aberto. */
221
+ return {
222
+ success: true,
223
+ applied: true,
224
+ ...(response.charge_now ? { charge_now: response.charge_now } : {}),
225
+ };
184
226
  }
185
227
 
186
228
  async getFreezeNotice(): Promise<FreezeNotice> {
@@ -107,6 +107,33 @@ export const SubscriptionSchema = z.object({
107
107
 
108
108
  export type Subscription = z.infer<typeof SubscriptionSchema>;
109
109
 
110
+ /* Desfecho da cobrança imediata pedida com `charge_now` na troca de forma de pagamento
111
+ * (gapps-r3-api, PUT subscriptions/:id/action/payment).
112
+ *
113
+ * Vem separado do `status` da resposta de propósito: quando isto chega, a troca JÁ está firmada
114
+ * no banco e na Stripe. Uma cobrança recusada aqui não desfaz a troca — o dunning da Stripe segue
115
+ * armado no cartão novo —, então a tela precisa contar as duas coisas em separado.
116
+ *
117
+ * `outcome` é a ÚNICA fonte da verdade sobre o que aconteceu, e a tela deve falar só a partir dele.
118
+ * Substituiu um par de booleanos (`attempted`/`paid`) que não distinguia recusa de 3DS pendente: a
119
+ * tela lia `paid: false` depois de um desafio bem-sucedido e anunciava falha para um pagamento que
120
+ * tinha acabado de completar.
121
+ *
122
+ * `amount` vem em centavos, na `currency` da fatura, e é o valor REALMENTE cobrado — não uma
123
+ * previsão feita antes da chamada. */
124
+ export interface ChargeNowResult {
125
+ outcome: 'charged' | 'requires_action' | 'declined' | 'none';
126
+ invoice_id?: string;
127
+ amount?: number | null;
128
+ currency?: string | null;
129
+ client_secret?: string;
130
+ /* Presente em `declined`: a razão que o emissor deu. */
131
+ message?: string;
132
+ /* Presente em `none`: por que não houve o que cobrar (`sem_fatura_de_ciclo_aberta`,
133
+ * `deteccao_falhou`, `sem_contexto_de_cobranca`). */
134
+ reason?: string;
135
+ }
136
+
110
137
  export interface FindSubscriptionsParams {
111
138
  active?: boolean;
112
139
  trial?: boolean;
@@ -1,40 +0,0 @@
1
- "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
3
- import { cn } from "../../infra/utils/clsx";
4
- function GreatDomainIcon({ active, className }) {
5
- return /* @__PURE__ */ jsxs(
6
- "svg",
7
- {
8
- width: "23",
9
- height: "23",
10
- viewBox: "0 0 23 23",
11
- fill: "none",
12
- xmlns: "http://www.w3.org/2000/svg",
13
- className,
14
- "aria-hidden": "true",
15
- children: [
16
- /* @__PURE__ */ jsx(
17
- "path",
18
- {
19
- d: "M11.8444 17.0777C11.8444 20.3487 9.19292 23.0003 5.92218 23.0003C2.65145 23.0003 0 20.3487 0 17.0777C0 13.8068 2.65145 11.1551 5.92218 11.1551C9.19292 11.1551 11.8444 13.8068 11.8444 17.0777Z",
20
- className: cn(
21
- "transition-colors duration-300",
22
- active ? "fill-[#984DFF]" : "fill-[#650CE4]"
23
- )
24
- }
25
- ),
26
- /* @__PURE__ */ jsx(
27
- "path",
28
- {
29
- d: "M11.8436 3.06666C11.8436 1.37299 13.2166 0 14.9103 0H19.9331C21.6267 0 22.9997 1.37299 22.9997 3.06666V19.9333C22.9997 21.6269 21.6267 22.9999 19.9331 22.9999H14.9103C13.2166 22.9999 11.8436 21.6269 11.8436 19.9333V3.06666Z",
30
- fill: "#C9B2FF"
31
- }
32
- )
33
- ]
34
- }
35
- );
36
- }
37
- export {
38
- GreatDomainIcon
39
- };
40
- //# sourceMappingURL=GreatDomainIcon.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../src/components/layouts/GreatDomainIcon.tsx"],"sourcesContent":["\"use client\";\n\nimport { cn } from \"../../infra/utils/clsx\";\n\ntype GreatDomainIconProps = {\n /** Em repouso o círculo é #650CE4; só no estado ativo vira #984DFF. */\n active?: boolean;\n className?: string;\n};\n\n/**\n * Glifo do GreatDomain — geometria exportada do Figma (node 5929:2700), 23×23.\n * O círculo só troca de cor quando ativo. No hover o container ganha fundo cinza\n * (igual ao GreatPages) e o glifo fica como está.\n */\nfunction GreatDomainIcon({ active, className }: GreatDomainIconProps) {\n return (\n <svg\n width=\"23\"\n height=\"23\"\n viewBox=\"0 0 23 23\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n className={className}\n aria-hidden=\"true\"\n >\n <path\n d=\"M11.8444 17.0777C11.8444 20.3487 9.19292 23.0003 5.92218 23.0003C2.65145 23.0003 0 20.3487 0 17.0777C0 13.8068 2.65145 11.1551 5.92218 11.1551C9.19292 11.1551 11.8444 13.8068 11.8444 17.0777Z\"\n className={cn(\n \"transition-colors duration-300\",\n active ? \"fill-[#984DFF]\" : \"fill-[#650CE4]\",\n )}\n />\n <path\n d=\"M11.8436 3.06666C11.8436 1.37299 13.2166 0 14.9103 0H19.9331C21.6267 0 22.9997 1.37299 22.9997 3.06666V19.9333C22.9997 21.6269 21.6267 22.9999 19.9331 22.9999H14.9103C13.2166 22.9999 11.8436 21.6269 11.8436 19.9333V3.06666Z\"\n fill=\"#C9B2FF\"\n />\n </svg>\n );\n}\n\nexport { GreatDomainIcon };\nexport type { GreatDomainIconProps };\n"],"mappings":";AAiBI,SASE,KATF;AAfJ,SAAS,UAAU;AAanB,SAAS,gBAAgB,EAAE,QAAQ,UAAU,GAAyB;AACpE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAM;AAAA,MACN,QAAO;AAAA,MACP,SAAQ;AAAA,MACR,MAAK;AAAA,MACL,OAAM;AAAA,MACN;AAAA,MACA,eAAY;AAAA,MAEZ;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,GAAE;AAAA,YACF,WAAW;AAAA,cACT;AAAA,cACA,SAAS,mBAAmB;AAAA,YAC9B;AAAA;AAAA,QACF;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,GAAE;AAAA,YACF,MAAK;AAAA;AAAA,QACP;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
@@ -1,43 +0,0 @@
1
- "use client";
2
-
3
- import { cn } from "../../infra/utils/clsx";
4
-
5
- type GreatDomainIconProps = {
6
- /** Em repouso o círculo é #650CE4; só no estado ativo vira #984DFF. */
7
- active?: boolean;
8
- className?: string;
9
- };
10
-
11
- /**
12
- * Glifo do GreatDomain — geometria exportada do Figma (node 5929:2700), 23×23.
13
- * O círculo só troca de cor quando ativo. No hover o container ganha fundo cinza
14
- * (igual ao GreatPages) e o glifo fica como está.
15
- */
16
- function GreatDomainIcon({ active, className }: GreatDomainIconProps) {
17
- return (
18
- <svg
19
- width="23"
20
- height="23"
21
- viewBox="0 0 23 23"
22
- fill="none"
23
- xmlns="http://www.w3.org/2000/svg"
24
- className={className}
25
- aria-hidden="true"
26
- >
27
- <path
28
- d="M11.8444 17.0777C11.8444 20.3487 9.19292 23.0003 5.92218 23.0003C2.65145 23.0003 0 20.3487 0 17.0777C0 13.8068 2.65145 11.1551 5.92218 11.1551C9.19292 11.1551 11.8444 13.8068 11.8444 17.0777Z"
29
- className={cn(
30
- "transition-colors duration-300",
31
- active ? "fill-[#984DFF]" : "fill-[#650CE4]",
32
- )}
33
- />
34
- <path
35
- d="M11.8436 3.06666C11.8436 1.37299 13.2166 0 14.9103 0H19.9331C21.6267 0 22.9997 1.37299 22.9997 3.06666V19.9333C22.9997 21.6269 21.6267 22.9999 19.9331 22.9999H14.9103C13.2166 22.9999 11.8436 21.6269 11.8436 19.9333V3.06666Z"
36
- fill="#C9B2FF"
37
- />
38
- </svg>
39
- );
40
- }
41
-
42
- export { GreatDomainIcon };
43
- export type { GreatDomainIconProps };