@greatapps/common 1.1.742 → 1.1.744

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.
@@ -1,30 +1,31 @@
1
- import { isUsdAccount } from "../../../utils/format/gateway";
2
- const INTERNATIONAL_BILLING_FIELDS = [
1
+ const REQUIRED_BILLING_FIELDS = [
2
+ "financial_document",
3
3
  "financial_name",
4
4
  "zipcode",
5
5
  "address",
6
+ "address_number",
7
+ "neighborhood",
6
8
  "city",
7
9
  "state",
8
10
  "country"
9
11
  ];
10
- const BR_BILLING_FIELDS = [
11
- ...INTERNATIONAL_BILLING_FIELDS,
12
- "financial_document",
13
- "address_number",
14
- "neighborhood"
15
- ];
12
+ function isBrazilianBillingAccount(account) {
13
+ if (!account) return false;
14
+ const country = account.country?.trim().toUpperCase();
15
+ return !country || country === "BR";
16
+ }
16
17
  function isFilled(value) {
17
18
  return typeof value === "string" && value.trim().length > 0;
18
19
  }
19
20
  function hasCompleteBillingData(account) {
20
21
  if (!account) return false;
21
- const fields = isUsdAccount(account) ? INTERNATIONAL_BILLING_FIELDS : BR_BILLING_FIELDS;
22
- for (const field of fields) {
22
+ for (const field of REQUIRED_BILLING_FIELDS) {
23
23
  if (!isFilled(account[field])) return false;
24
24
  }
25
25
  return true;
26
26
  }
27
27
  export {
28
- hasCompleteBillingData
28
+ hasCompleteBillingData,
29
+ isBrazilianBillingAccount
29
30
  };
30
31
  //# sourceMappingURL=billing-data.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/modules/accounts/utils/billing-data.ts"],"sourcesContent":["import type { Account } from '../types';\nimport { isUsdAccount } from '../../../utils/format/gateway';\n\nexport type BillingDataSnapshot = Pick<\n Account,\n | 'gateway'\n | 'financial_document'\n | 'financial_name'\n | 'financial_email'\n | 'zipcode'\n | 'address'\n | 'address_number'\n | 'neighborhood'\n | 'city'\n | 'state'\n | 'country'\n>;\n\ntype BillingField = keyof Omit<BillingDataSnapshot, 'gateway'>;\n\n/**\n * Campos exigidos em qualquer país — sem eles a nota fiscal não sai.\n *\n * `financial_email` NÃO entra: o backend cai no e-mail do usuário quando ele está\n * vazio (`financial_email || userDocument.email`, accounts/services.js), então a\n * nota sai do mesmo jeito. Exigi-lo aqui bloquearia 4.7k contas pagantes da wl 1\n * em vez das ~460 que de fato estão sem cadastro fiscal. O campo continua no\n * formulário, pré-preenchido — só não é critério de bloqueio.\n */\nconst INTERNATIONAL_BILLING_FIELDS: readonly BillingField[] = [\n 'financial_name',\n 'zipcode',\n 'address',\n 'city',\n 'state',\n 'country',\n];\n\n/** No Brasil a NF-e também exige documento fiscal, número e bairro. */\nconst BR_BILLING_FIELDS: readonly BillingField[] = [\n ...INTERNATIONAL_BILLING_FIELDS,\n 'financial_document',\n 'address_number',\n 'neighborhood',\n];\n\nfunction isFilled(value: string | null | undefined): boolean {\n return typeof value === 'string' && value.trim().length > 0;\n}\n\n/**\n * A conta tem todos os dados de cobrança/fiscais necessários pra emissão de nota.\n * Retorna `false` quando a conta ainda não carregou — quem decide bloquear é o\n * chamador, que checa o loading antes (ver `useRequiredBillingData`).\n */\nexport function hasCompleteBillingData(\n account: BillingDataSnapshot | null | undefined,\n): boolean {\n if (!account) return false;\n\n const fields = isUsdAccount(account)\n ? INTERNATIONAL_BILLING_FIELDS\n : BR_BILLING_FIELDS;\n\n for (const field of fields) {\n if (!isFilled(account[field])) return false;\n }\n\n return true;\n}\n"],"mappings":"AACA,SAAS,oBAAoB;AA4B7B,MAAM,+BAAwD;AAAA,EAC5D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGA,MAAM,oBAA6C;AAAA,EACjD,GAAG;AAAA,EACH;AAAA,EACA;AAAA,EACA;AACF;AAEA,SAAS,SAAS,OAA2C;AAC3D,SAAO,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS;AAC5D;AAOO,SAAS,uBACd,SACS;AACT,MAAI,CAAC,QAAS,QAAO;AAErB,QAAM,SAAS,aAAa,OAAO,IAC/B,+BACA;AAEJ,aAAW,SAAS,QAAQ;AAC1B,QAAI,CAAC,SAAS,QAAQ,KAAK,CAAC,EAAG,QAAO;AAAA,EACxC;AAEA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../../../../src/modules/accounts/utils/billing-data.ts"],"sourcesContent":["import type { Account } from '../types';\n\nexport type BillingDataSnapshot = Pick<\n Account,\n | 'financial_document'\n | 'financial_name'\n | 'financial_email'\n | 'zipcode'\n | 'address'\n | 'address_number'\n | 'neighborhood'\n | 'city'\n | 'state'\n | 'country'\n>;\n\ntype BillingField = keyof BillingDataSnapshot;\n\n/**\n * Campos exigidos pra emitir a NF-e — sem qualquer um deles a nota não sai.\n *\n * `financial_email` NÃO entra: o backend cai no e-mail do usuário quando ele está\n * vazio (`financial_email || userDocument.email`, accounts/services.js), então a\n * nota sai do mesmo jeito. Exigi-lo aqui bloquearia 4.7k contas pagantes da wl 1\n * em vez das ~460 que de fato estão sem cadastro fiscal. O campo continua no\n * formulário, pré-preenchido — só não é critério de bloqueio.\n *\n * `address_complement` é opcional de verdade.\n */\nconst REQUIRED_BILLING_FIELDS: readonly BillingField[] = [\n 'financial_document',\n 'financial_name',\n 'zipcode',\n 'address',\n 'address_number',\n 'neighborhood',\n 'city',\n 'state',\n 'country',\n];\n\n/**\n * Conta sujeita à NF-e brasileira. ela entra no bloqueio: o formulário pede\n * CPF/CNPJ e CEP de 8 dígitos, que um cliente de fora não tem como fornecer —\n * e o modal é bloqueante, então seria tranca sem saída.\n *\n * País vazio conta como Brasil: é o default do cadastro e a origem da esmagadora\n * maioria da base (155 contas pagas da wl 1 estão assim).\n */\nexport function isBrazilianBillingAccount(\n account: Pick<BillingDataSnapshot, 'country'> | null | undefined,\n): boolean {\n if (!account) return false;\n const country = account.country?.trim().toUpperCase();\n return !country || country === 'BR';\n}\n\nfunction isFilled(value: string | null | undefined): boolean {\n return typeof value === 'string' && value.trim().length > 0;\n}\n\n/**\n * A conta tem todos os dados de cobrança/fiscais necessários pra emissão de nota.\n * Retorna `false` quando a conta ainda não carregou — quem decide bloquear é o\n * chamador, que checa o loading antes (ver `useRequiredBillingData`).\n */\nexport function hasCompleteBillingData(\n account: BillingDataSnapshot | null | undefined,\n): boolean {\n if (!account) return false;\n\n for (const field of REQUIRED_BILLING_FIELDS) {\n if (!isFilled(account[field])) return false;\n }\n\n return true;\n}\n"],"mappings":"AA6BA,MAAM,0BAAmD;AAAA,EACvD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAUO,SAAS,0BACd,SACS;AACT,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,UAAU,QAAQ,SAAS,KAAK,EAAE,YAAY;AACpD,SAAO,CAAC,WAAW,YAAY;AACjC;AAEA,SAAS,SAAS,OAA2C;AAC3D,SAAO,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS;AAC5D;AAOO,SAAS,uBACd,SACS;AACT,MAAI,CAAC,QAAS,QAAO;AAErB,aAAW,SAAS,yBAAyB;AAC3C,QAAI,CAAC,SAAS,QAAQ,KAAK,CAAC,EAAG,QAAO;AAAA,EACxC;AAEA,SAAO;AACT;","names":[]}
@@ -3,6 +3,18 @@ import {
3
3
  formatCurrencyNumber
4
4
  } from "../../../utils/format/currency";
5
5
  const STARTER_PLAN_ID = 415;
6
+ const AGENCY_PLAN_ID = 3;
7
+ const planIdOf = (plan) => plan.id_plan ?? plan.id;
8
+ function resolvePlanName(plan, translate) {
9
+ if (!translate) return plan.name;
10
+ const key = `common.plans.names.${planIdOf(plan)}`;
11
+ try {
12
+ const translated = translate(key);
13
+ return translated && translated !== key ? translated : plan.name;
14
+ } catch {
15
+ return plan.name;
16
+ }
17
+ }
6
18
  const BASE_FEATURES_KEYS = [
7
19
  { icon: "check", key: "plans.features.unlimitedVisits", fallback: "Visitas ilimitadas" },
8
20
  { icon: "check", key: "plans.features.unlimitedLeads", fallback: "Leads ilimitadas" },
@@ -70,7 +82,7 @@ function buildUiPlan(plan, currency, translate) {
70
82
  const pricingByPeriod = computePricingByPeriod(plan);
71
83
  const monthlyFinal = pricingByPeriod.monthly;
72
84
  const isStarterPlan = plan.id === STARTER_PLAN_ID || plan.id_plan === STARTER_PLAN_ID;
73
- const hasProjectAndOnboarding = plan.name.toLowerCase().includes("ag\xEAncia") || plan.name.toLowerCase().includes("agencia");
85
+ const hasProjectAndOnboarding = planIdOf(plan) === AGENCY_PLAN_ID;
74
86
  const onboardingText = translate ? translate("common.plans.features.onboarding") : "Reuni\xE3o de Onboarding";
75
87
  const projectMgmtText = translate ? translate("common.plans.features.projectManagement") : "Gest\xE3o de projetos";
76
88
  const sharePagesText = translate ? translate("common.plans.features.sharePages") : "Compartilhar p\xE1ginas";
@@ -89,7 +101,7 @@ function buildUiPlan(plan, currency, translate) {
89
101
  const unavailableLabel = translate ? translate("common.plans.usdNotConfigured") : "USD n\xE3o configurado";
90
102
  return {
91
103
  planId: plan.id,
92
- name: plan.name,
104
+ name: resolvePlanName(plan, translate),
93
105
  isPopular: popular,
94
106
  buttonVariant,
95
107
  mainFeatures: buildMainFeaturesFromItems(plan.items, translate),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/modules/plans/utils/map-api-plan-to-ui.ts"],"sourcesContent":["import type {\n Plan,\n PlanFeature,\n PlanMainFeatures,\n PlanPricingByPeriod,\n UiPlan,\n} from \"../types/plan.type\";\nimport { ADDON_IDS } from \"../../subscriptions/constants/addons.constants\";\nimport {\n formatCurrencyNumber,\n type CurrencyCode,\n} from \"../../../utils/format/currency\";\n\nexport type PlanBillingPeriod = \"monthly\" | \"semiannual\" | \"annual\";\n\nconst STARTER_PLAN_ID = 415;\n\ntype TranslateFn = (key: any, values?: Record<string, string | number>) => string;\n\nconst BASE_FEATURES_KEYS = [\n { icon: \"check\", key: \"plans.features.unlimitedVisits\", fallback: \"Visitas ilimitadas\" },\n { icon: \"check\", key: \"plans.features.unlimitedLeads\", fallback: \"Leads ilimitadas\" },\n { icon: \"check\", key: \"plans.features.sharePages\", fallback: \"Compartilhar páginas\", tooltip: \"sharePages\" },\n { icon: \"check\", key: \"plans.features.hosting\", fallback: \"Hospedagem inclusa\" },\n // SSL (HTTPS) + CDN — brand/acronym: never translated\n { icon: \"check\", key: null, fallback: \"SSL (HTTPS) + CDN\" },\n { icon: \"check\", key: \"plans.features.freeTemplates\", fallback: \"Templates gratuitos\" },\n] as const;\n\nfunction buildBaseFeatures(translate?: TranslateFn): PlanFeature[] {\n return BASE_FEATURES_KEYS.map(({ icon, key, fallback, ...rest }) => {\n const text = key && translate ? translate(`common.${key}`) : fallback;\n const feature: PlanFeature = { icon, text };\n if (\"tooltip\" in rest && rest.tooltip) feature.tooltip = rest.tooltip;\n return feature;\n });\n}\n\nfunction buildMainFeaturesFromItems(\n items: Plan[\"items\"],\n translate?: TranslateFn\n): PlanMainFeatures {\n const pagesItem = items.find((i) => i.id_addon === ADDON_IDS.PAGES);\n const domainsItem = items.find((i) => i.id_addon === ADDON_IDS.DOMAINS);\n\n const pagesQty = pagesItem?.quantity ?? 0;\n const domainsQty = domainsItem?.quantity ?? 1;\n\n let pages: string;\n if (pagesQty === 0) {\n pages = translate\n ? translate(\"common.plans.mainFeatures.pagesUnlimited\")\n : \"Páginas ilimitadas\";\n } else if (pagesQty === 1) {\n pages = translate\n ? translate(\"common.plans.mainFeatures.pageSingular\")\n : \"1 página\";\n } else {\n pages = translate\n ? translate(\"common.plans.mainFeatures.pagesPlural\", { count: pagesQty })\n : `${pagesQty} páginas`;\n }\n\n let domains: string;\n if (domainsQty >= 999) {\n domains = translate\n ? translate(\"common.plans.mainFeatures.domainsUnlimited\")\n : \"Domínios ilimitados\";\n } else if (domainsQty === 1) {\n domains = translate\n ? translate(\"common.plans.mainFeatures.domainSingular\")\n : \"1 domínio externo\";\n } else {\n domains = translate\n ? translate(\"common.plans.mainFeatures.domainsPlural\", { count: domainsQty })\n : `${domainsQty} domínios externos`;\n }\n\n const users = translate\n ? translate(\"common.plans.mainFeatures.usersUnlimited\")\n : \"Usuários ilimitados\";\n\n return { pages, domains, users };\n}\n\nfunction computePricingByPeriod(apiPlan: Plan): PlanPricingByPeriod {\n const monthly = apiPlan.value;\n if (monthly == null) {\n return { monthly: 0, semiannual: 0, annual: 0 };\n }\n const round2 = (n: number) => Math.round(n * 100) / 100;\n\n const pricing = apiPlan.pricing;\n if (pricing) {\n return {\n monthly,\n semiannual: round2(pricing.semester.charged / 6),\n annual: round2(pricing.annual.charged / 12),\n };\n }\n\n return {\n monthly,\n semiannual: round2(monthly * (1 - apiPlan.discount_semester / 100)),\n annual: round2(monthly * (1 - apiPlan.discount_annual / 100)),\n };\n}\n\nfunction buildUiPlan(plan: Plan, currency: CurrencyCode, translate?: TranslateFn): UiPlan {\n const popular = plan.id === 2 || plan.id_plan === 2;\n const buttonVariant = popular ? \"brand\" : \"default\";\n\n const pricingByPeriod = computePricingByPeriod(plan);\n const monthlyFinal = pricingByPeriod.monthly;\n\n const isStarterPlan = plan.id === STARTER_PLAN_ID || plan.id_plan === STARTER_PLAN_ID;\n const hasProjectAndOnboarding =\n plan.name.toLowerCase().includes(\"agência\") || plan.name.toLowerCase().includes(\"agencia\");\n\n const onboardingText = translate\n ? translate(\"common.plans.features.onboarding\")\n : \"Reunião de Onboarding\";\n const projectMgmtText = translate\n ? translate(\"common.plans.features.projectManagement\")\n : \"Gestão de projetos\";\n const sharePagesText = translate\n ? translate(\"common.plans.features.sharePages\")\n : \"Compartilhar páginas\";\n\n const features: PlanFeature[] = [\n ...buildBaseFeatures(translate).map((feature) =>\n feature.text === sharePagesText && isStarterPlan\n ? { ...feature, icon: \"x\" as const, disabled: true }\n : feature\n ),\n hasProjectAndOnboarding\n ? { icon: \"check\", text: onboardingText }\n : { icon: \"x\", text: onboardingText, disabled: true },\n hasProjectAndOnboarding\n ? { icon: \"check\", text: projectMgmtText, tooltip: \"projectManagement\" }\n : { icon: \"x\", text: projectMgmtText, disabled: true, tooltip: \"projectManagement\" },\n ];\n\n const currencyAwareItems = plan.items.map((item) => ({\n ...item,\n value: item.value ?? 0,\n }));\n\n const priceUnavailable = plan.value == null;\n const unavailableLabel = translate ? translate(\"common.plans.usdNotConfigured\") : \"USD não configurado\";\n\n return {\n planId: plan.id,\n name: plan.name,\n isPopular: popular,\n buttonVariant,\n mainFeatures: buildMainFeaturesFromItems(plan.items, translate),\n features,\n originalPrice: priceUnavailable ? unavailableLabel : formatCurrencyNumber(monthlyFinal, 2, currency),\n price: priceUnavailable ? unavailableLabel : formatCurrencyNumber(monthlyFinal, 2, currency),\n pricingByPeriod,\n priceUnavailable,\n items: currencyAwareItems,\n };\n}\n\n/**\n * Mapeia um plano da API para UiPlan em BRL.\n * Assinatura de 1 argumento — seguro em `plans.map(mapApiPlanToUiPlan)`.\n * Passa `translate` opcional para i18n; sem ele cai nos literais pt-br (back-compat).\n */\nexport function mapApiPlanToUiPlan(plan: Plan, translate?: TranslateFn): UiPlan {\n return buildUiPlan(plan, \"brl\", translate);\n}\n\n/**\n * Variante currency-aware: a moeda vem do gateway da conta (getCurrencyForGateway).\n * Fábrica para uso ergonômico em `.map`: `plans.map(mapApiPlanToUiPlanForCurrency(currency, translate))`.\n * Mantida separada de mapApiPlanToUiPlan para não quebrar callers que passam a fn direto pro `.map`.\n */\nexport const mapApiPlanToUiPlanForCurrency =\n (currency: CurrencyCode, translate?: TranslateFn) =>\n (plan: Plan): UiPlan =>\n buildUiPlan(plan, currency, translate);\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../src/modules/plans/utils/map-api-plan-to-ui.ts"],"sourcesContent":["import type {\n Plan,\n PlanFeature,\n PlanMainFeatures,\n PlanPricingByPeriod,\n UiPlan,\n} from \"../types/plan.type\";\nimport { ADDON_IDS } from \"../../subscriptions/constants/addons.constants\";\nimport {\n formatCurrencyNumber,\n type CurrencyCode,\n} from \"../../../utils/format/currency\";\n\nexport type PlanBillingPeriod = \"monthly\" | \"semiannual\" | \"annual\";\n\nconst STARTER_PLAN_ID = 415;\nconst AGENCY_PLAN_ID = 3;\n\ntype TranslateFn = (key: any, values?: Record<string, string | number>) => string;\n\nconst planIdOf = (plan: Plan): number => plan.id_plan ?? plan.id;\n\nfunction resolvePlanName(plan: Plan, translate?: TranslateFn): string {\n if (!translate) return plan.name;\n const key = `common.plans.names.${planIdOf(plan)}`;\n try {\n const translated = translate(key);\n return translated && translated !== key ? translated : plan.name;\n } catch {\n return plan.name;\n }\n}\n\nconst BASE_FEATURES_KEYS = [\n { icon: \"check\", key: \"plans.features.unlimitedVisits\", fallback: \"Visitas ilimitadas\" },\n { icon: \"check\", key: \"plans.features.unlimitedLeads\", fallback: \"Leads ilimitadas\" },\n { icon: \"check\", key: \"plans.features.sharePages\", fallback: \"Compartilhar páginas\", tooltip: \"sharePages\" },\n { icon: \"check\", key: \"plans.features.hosting\", fallback: \"Hospedagem inclusa\" },\n // SSL (HTTPS) + CDN — brand/acronym: never translated\n { icon: \"check\", key: null, fallback: \"SSL (HTTPS) + CDN\" },\n { icon: \"check\", key: \"plans.features.freeTemplates\", fallback: \"Templates gratuitos\" },\n] as const;\n\nfunction buildBaseFeatures(translate?: TranslateFn): PlanFeature[] {\n return BASE_FEATURES_KEYS.map(({ icon, key, fallback, ...rest }) => {\n const text = key && translate ? translate(`common.${key}`) : fallback;\n const feature: PlanFeature = { icon, text };\n if (\"tooltip\" in rest && rest.tooltip) feature.tooltip = rest.tooltip;\n return feature;\n });\n}\n\nfunction buildMainFeaturesFromItems(\n items: Plan[\"items\"],\n translate?: TranslateFn\n): PlanMainFeatures {\n const pagesItem = items.find((i) => i.id_addon === ADDON_IDS.PAGES);\n const domainsItem = items.find((i) => i.id_addon === ADDON_IDS.DOMAINS);\n\n const pagesQty = pagesItem?.quantity ?? 0;\n const domainsQty = domainsItem?.quantity ?? 1;\n\n let pages: string;\n if (pagesQty === 0) {\n pages = translate\n ? translate(\"common.plans.mainFeatures.pagesUnlimited\")\n : \"Páginas ilimitadas\";\n } else if (pagesQty === 1) {\n pages = translate\n ? translate(\"common.plans.mainFeatures.pageSingular\")\n : \"1 página\";\n } else {\n pages = translate\n ? translate(\"common.plans.mainFeatures.pagesPlural\", { count: pagesQty })\n : `${pagesQty} páginas`;\n }\n\n let domains: string;\n if (domainsQty >= 999) {\n domains = translate\n ? translate(\"common.plans.mainFeatures.domainsUnlimited\")\n : \"Domínios ilimitados\";\n } else if (domainsQty === 1) {\n domains = translate\n ? translate(\"common.plans.mainFeatures.domainSingular\")\n : \"1 domínio externo\";\n } else {\n domains = translate\n ? translate(\"common.plans.mainFeatures.domainsPlural\", { count: domainsQty })\n : `${domainsQty} domínios externos`;\n }\n\n const users = translate\n ? translate(\"common.plans.mainFeatures.usersUnlimited\")\n : \"Usuários ilimitados\";\n\n return { pages, domains, users };\n}\n\nfunction computePricingByPeriod(apiPlan: Plan): PlanPricingByPeriod {\n const monthly = apiPlan.value;\n if (monthly == null) {\n return { monthly: 0, semiannual: 0, annual: 0 };\n }\n const round2 = (n: number) => Math.round(n * 100) / 100;\n\n const pricing = apiPlan.pricing;\n if (pricing) {\n return {\n monthly,\n semiannual: round2(pricing.semester.charged / 6),\n annual: round2(pricing.annual.charged / 12),\n };\n }\n\n return {\n monthly,\n semiannual: round2(monthly * (1 - apiPlan.discount_semester / 100)),\n annual: round2(monthly * (1 - apiPlan.discount_annual / 100)),\n };\n}\n\nfunction buildUiPlan(plan: Plan, currency: CurrencyCode, translate?: TranslateFn): UiPlan {\n const popular = plan.id === 2 || plan.id_plan === 2;\n const buttonVariant = popular ? \"brand\" : \"default\";\n\n const pricingByPeriod = computePricingByPeriod(plan);\n const monthlyFinal = pricingByPeriod.monthly;\n\n const isStarterPlan = plan.id === STARTER_PLAN_ID || plan.id_plan === STARTER_PLAN_ID;\n const hasProjectAndOnboarding = planIdOf(plan) === AGENCY_PLAN_ID;\n\n const onboardingText = translate\n ? translate(\"common.plans.features.onboarding\")\n : \"Reunião de Onboarding\";\n const projectMgmtText = translate\n ? translate(\"common.plans.features.projectManagement\")\n : \"Gestão de projetos\";\n const sharePagesText = translate\n ? translate(\"common.plans.features.sharePages\")\n : \"Compartilhar páginas\";\n\n const features: PlanFeature[] = [\n ...buildBaseFeatures(translate).map((feature) =>\n feature.text === sharePagesText && isStarterPlan\n ? { ...feature, icon: \"x\" as const, disabled: true }\n : feature\n ),\n hasProjectAndOnboarding\n ? { icon: \"check\", text: onboardingText }\n : { icon: \"x\", text: onboardingText, disabled: true },\n hasProjectAndOnboarding\n ? { icon: \"check\", text: projectMgmtText, tooltip: \"projectManagement\" }\n : { icon: \"x\", text: projectMgmtText, disabled: true, tooltip: \"projectManagement\" },\n ];\n\n const currencyAwareItems = plan.items.map((item) => ({\n ...item,\n value: item.value ?? 0,\n }));\n\n const priceUnavailable = plan.value == null;\n const unavailableLabel = translate ? translate(\"common.plans.usdNotConfigured\") : \"USD não configurado\";\n\n return {\n planId: plan.id,\n name: resolvePlanName(plan, translate),\n isPopular: popular,\n buttonVariant,\n mainFeatures: buildMainFeaturesFromItems(plan.items, translate),\n features,\n originalPrice: priceUnavailable ? unavailableLabel : formatCurrencyNumber(monthlyFinal, 2, currency),\n price: priceUnavailable ? unavailableLabel : formatCurrencyNumber(monthlyFinal, 2, currency),\n pricingByPeriod,\n priceUnavailable,\n items: currencyAwareItems,\n };\n}\n\n/**\n * Mapeia um plano da API para UiPlan em BRL.\n * Assinatura de 1 argumento — seguro em `plans.map(mapApiPlanToUiPlan)`.\n * Passa `translate` opcional para i18n; sem ele cai nos literais pt-br (back-compat).\n */\nexport function mapApiPlanToUiPlan(plan: Plan, translate?: TranslateFn): UiPlan {\n return buildUiPlan(plan, \"brl\", translate);\n}\n\n/**\n * Variante currency-aware: a moeda vem do gateway da conta (getCurrencyForGateway).\n * Fábrica para uso ergonômico em `.map`: `plans.map(mapApiPlanToUiPlanForCurrency(currency, translate))`.\n * Mantida separada de mapApiPlanToUiPlan para não quebrar callers que passam a fn direto pro `.map`.\n */\nexport const mapApiPlanToUiPlanForCurrency =\n (currency: CurrencyCode, translate?: TranslateFn) =>\n (plan: Plan): UiPlan =>\n buildUiPlan(plan, currency, translate);\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@greatapps/common",
3
- "version": "1.1.742",
3
+ "version": "1.1.744",
4
4
  "description": "Shared library for GreatApps frontend applications",
5
5
  "main": "./dist/index.mjs",
6
6
  "types": "./src/index.ts",
@@ -48,7 +48,7 @@ const COUNTRY_OPTIONS = COUNTRIES.map((country: { iso: string; name: string }) =
48
48
  label: country.name,
49
49
  }));
50
50
 
51
- function buildBillingDataSchema(isInternational: boolean, translate: TranslateFn) {
51
+ function buildBillingDataSchema(translate: TranslateFn) {
52
52
  const requiredText = z
53
53
  .string()
54
54
  .trim()
@@ -73,11 +73,6 @@ function buildBillingDataSchema(isInternational: boolean, translate: TranslateFn
73
73
  country: requiredText,
74
74
  });
75
75
 
76
- /* Conta internacional não tem CPF/CNPJ, número nem bairro — mesmo recorte do
77
- * AddCardModal. Exigir esses campos aqui trancaria o cliente fora do app, já que
78
- * o modal é bloqueante. */
79
- if (isInternational) return base;
80
-
81
76
  return base.superRefine((data, ctx) => {
82
77
  if (!data.streetNumber) {
83
78
  ctx.addIssue({
@@ -140,7 +135,7 @@ function formatDocument(document: string | null | undefined, isCompany: boolean)
140
135
  function RequiredBillingDataModalContent() {
141
136
  const translate = useTranslations();
142
137
  const { user } = useAuth();
143
- const { isRequired, account, isInternational } = useRequiredBillingData();
138
+ const { isRequired, account } = useRequiredBillingData();
144
139
  const { mutateAsync: updateBillingData } = useUpdateBillingData();
145
140
  const { mutateAsync: lookupCep } = useViaCep();
146
141
 
@@ -150,8 +145,8 @@ function RequiredBillingDataModalContent() {
150
145
  const isPrefilled = useRef(false);
151
146
 
152
147
  const schema = useMemo(
153
- () => buildBillingDataSchema(isInternational, translate),
154
- [isInternational, translate],
148
+ () => buildBillingDataSchema(translate),
149
+ [translate],
155
150
  );
156
151
 
157
152
  const form = useForm<BillingDataFormValues>({
@@ -203,9 +198,9 @@ function RequiredBillingDataModalContent() {
203
198
  neighborhood: account.neighborhood ?? '',
204
199
  city: account.city ?? '',
205
200
  state: account.state ?? '',
206
- country: account.country || (isInternational ? 'US' : 'BR'),
201
+ country: account.country || 'BR',
207
202
  });
208
- }, [account, isInternational, reset, user?.email]);
203
+ }, [account, reset, user?.email]);
209
204
 
210
205
  const handleDocumentChange = (rawValue: string) => {
211
206
  setValue('document', isCompany ? formatCNPJ(rawValue) : formatCPF(rawValue), {
@@ -222,11 +217,6 @@ function RequiredBillingDataModalContent() {
222
217
  };
223
218
 
224
219
  const handleCepChange = (rawValue: string) => {
225
- if (isInternational) {
226
- setValue('cep', rawValue, { shouldValidate: true });
227
- return;
228
- }
229
-
230
220
  setValue('cep', formatPostalCode(rawValue), { shouldValidate: true });
231
221
 
232
222
  const digits = rawValue.replace(/\D/g, '');
@@ -246,12 +236,8 @@ function RequiredBillingDataModalContent() {
246
236
 
247
237
  async function onSubmit(data: BillingDataFormValues) {
248
238
  const payload: UpdateBillingDataRequest = {
249
- ...(isInternational
250
- ? {}
251
- : {
252
- financial_document_type: data.personType === 'pf' ? 1 : 2,
253
- financial_document: data.document,
254
- }),
239
+ financial_document_type: data.personType === 'pf' ? 1 : 2,
240
+ financial_document: data.document,
255
241
  financial_name: data.financialName,
256
242
  financial_email: data.financialEmail,
257
243
  zipcode: data.cep,
@@ -322,8 +308,7 @@ function RequiredBillingDataModalContent() {
322
308
  >
323
309
  <div className="flex flex-col gap-6 p-4 lg:p-5 flex-1 overflow-y-auto">
324
310
  <div className="flex flex-col gap-5">
325
- {!isInternational && (
326
- <div className="flex gap-4">
311
+ <div className="flex gap-4">
327
312
  <Controller
328
313
  name="personType"
329
314
  control={control}
@@ -362,8 +347,7 @@ function RequiredBillingDataModalContent() {
362
347
  })}
363
348
  />
364
349
  </div>
365
- </div>
366
- )}
350
+ </div>
367
351
 
368
352
  <FormField
369
353
  label={
@@ -395,12 +379,8 @@ function RequiredBillingDataModalContent() {
395
379
  </span>
396
380
 
397
381
  <FormField
398
- label={
399
- isInternational
400
- ? translate('common.billing.requiredData.postalCode')
401
- : translate('common.billing.requiredData.cep')
402
- }
403
- placeholder={isInternational ? '' : '_____-___'}
382
+ label={translate('common.billing.requiredData.cep')}
383
+ placeholder="_____-___"
404
384
  error={!!errors.cep}
405
385
  errorMessage={errors.cep?.message}
406
386
  {...register('cep', {
@@ -418,31 +398,27 @@ function RequiredBillingDataModalContent() {
418
398
  {...register('street')}
419
399
  />
420
400
  </div>
421
- {!isInternational && (
422
- <div className="flex-1">
423
- <FormField
424
- label={translate('common.billing.requiredData.streetNumber')}
425
- placeholder="000"
426
- error={!!errors.streetNumber}
427
- errorMessage={errors.streetNumber?.message}
428
- {...register('streetNumber')}
429
- />
430
- </div>
431
- )}
401
+ <div className="flex-1">
402
+ <FormField
403
+ label={translate('common.billing.requiredData.streetNumber')}
404
+ placeholder="000"
405
+ error={!!errors.streetNumber}
406
+ errorMessage={errors.streetNumber?.message}
407
+ {...register('streetNumber')}
408
+ />
409
+ </div>
432
410
  </div>
433
411
 
434
412
  <div className="flex gap-4">
435
- {!isInternational && (
436
- <div className="flex-1">
437
- <FormField
438
- label={translate('common.billing.requiredData.neighborhood')}
439
- placeholder={translate('common.billing.requiredData.typeHere')}
440
- error={!!errors.neighborhood}
441
- errorMessage={errors.neighborhood?.message}
442
- {...register('neighborhood')}
443
- />
444
- </div>
445
- )}
413
+ <div className="flex-1">
414
+ <FormField
415
+ label={translate('common.billing.requiredData.neighborhood')}
416
+ placeholder={translate('common.billing.requiredData.typeHere')}
417
+ error={!!errors.neighborhood}
418
+ errorMessage={errors.neighborhood?.message}
419
+ {...register('neighborhood')}
420
+ />
421
+ </div>
446
422
  <div className="flex-1">
447
423
  <FormField
448
424
  label={translate('common.billing.requiredData.complement')}
@@ -464,16 +440,7 @@ function RequiredBillingDataModalContent() {
464
440
  />
465
441
  </div>
466
442
  <div className="flex-1">
467
- {isInternational ? (
468
- <FormField
469
- label={translate('common.billing.requiredData.stateProvince')}
470
- placeholder={translate('common.billing.requiredData.typeHere')}
471
- error={!!errors.state}
472
- errorMessage={errors.state?.message}
473
- {...register('state')}
474
- />
475
- ) : (
476
- <Controller
443
+ <Controller
477
444
  name="state"
478
445
  control={control}
479
446
  render={({ field }) => (
@@ -489,8 +456,7 @@ function RequiredBillingDataModalContent() {
489
456
  errorMessage={errors.state?.message}
490
457
  />
491
458
  )}
492
- />
493
- )}
459
+ />
494
460
  </div>
495
461
  </div>
496
462
 
@@ -535,6 +535,12 @@ const messages = {
535
535
  onboarding: 'Onboarding meeting',
536
536
  projectManagement: 'Project management',
537
537
  },
538
+ names: {
539
+ '415': 'Starter',
540
+ '1': 'Essential',
541
+ '2': 'Growth',
542
+ '3': 'Agency',
543
+ },
538
544
  mainFeatures: {
539
545
  pagesUnlimited: 'Unlimited pages',
540
546
  pageSingular: '1 page',
@@ -536,6 +536,12 @@ const messages = {
536
536
  onboarding: 'Reunión de Onboarding',
537
537
  projectManagement: 'Gestión de proyectos',
538
538
  },
539
+ names: {
540
+ '415': 'Starter',
541
+ '1': 'Essential',
542
+ '2': 'Growth',
543
+ '3': 'Agency',
544
+ },
539
545
  mainFeatures: {
540
546
  pagesUnlimited: 'Páginas ilimitadas',
541
547
  pageSingular: '1 página',
@@ -460,6 +460,12 @@ const messages = {
460
460
  onboarding: 'Reunião de Onboarding',
461
461
  projectManagement: 'Gestão de projetos',
462
462
  },
463
+ names: {
464
+ '415': 'Iniciante',
465
+ '1': 'Essencial',
466
+ '2': 'Negócio',
467
+ '3': 'Agência',
468
+ },
463
469
  mainFeatures: {
464
470
  pagesUnlimited: 'Páginas ilimitadas',
465
471
  pageSingular: '1 página',
package/src/index.ts CHANGED
@@ -539,7 +539,10 @@ export {
539
539
  export { useViaCep } from "./modules/accounts/hooks/useViaCep";
540
540
  export { useAccountToken } from "./modules/accounts/hooks/use-account-token.hook";
541
541
  export { useRequiredBillingData } from "./modules/accounts/hooks/use-required-billing-data.hook";
542
- export { hasCompleteBillingData } from "./modules/accounts/utils/billing-data";
542
+ export {
543
+ hasCompleteBillingData,
544
+ isBrazilianBillingAccount,
545
+ } from "./modules/accounts/utils/billing-data";
543
546
  export type { BillingDataSnapshot } from "./modules/accounts/utils/billing-data";
544
547
 
545
548
  // Account Types
@@ -4,15 +4,14 @@ import { useCurrentAccount } from './current-account.hook';
4
4
  import { useActiveSubscription } from '../../subscriptions/hooks/find-active-subscription.hook';
5
5
  import { hasPaidSubscription } from '../../subscriptions/utils/has-paid-subscription';
6
6
  import { useManagementPermissions } from '../../auth/hooks/useManagementPermissions';
7
- import { hasCompleteBillingData } from '../utils/billing-data';
8
- import { isUsdAccount } from '../../../utils/format/gateway';
7
+ import { hasCompleteBillingData, isBrazilianBillingAccount } from '../utils/billing-data';
9
8
 
10
9
  /**
11
10
  * Contas que já pagaram mas nunca preencheram o cadastro de cobrança ficam sem
12
11
  * nota fiscal (chamado "Nota fiscal não emitida em pagamentos confirmados").
13
12
  * Este hook diz quando o preenchimento vira obrigatório — o modal que ele
14
13
  * alimenta é bloqueante, então só liga com os dados já carregados e só para
15
- * quem consegue resolver: owner/admin.
14
+ * quem consegue resolver: owner/admin de conta brasileira.
16
15
  */
17
16
  export function useRequiredBillingData() {
18
17
  const { isOwnerOrAdmin } = useManagementPermissions();
@@ -27,6 +26,7 @@ export function useRequiredBillingData() {
27
26
  const isRequired =
28
27
  !isLoading &&
29
28
  isOwnerOrAdmin &&
29
+ isBrazilianBillingAccount(account) &&
30
30
  hasPaidSubscription(subscription) &&
31
31
  !hasCompleteBillingData(account);
32
32
 
@@ -34,6 +34,5 @@ export function useRequiredBillingData() {
34
34
  isRequired,
35
35
  isLoading,
36
36
  account,
37
- isInternational: isUsdAccount(account),
38
37
  };
39
38
  }
@@ -1,9 +1,7 @@
1
1
  import type { Account } from '../types';
2
- import { isUsdAccount } from '../../../utils/format/gateway';
3
2
 
4
3
  export type BillingDataSnapshot = Pick<
5
4
  Account,
6
- | 'gateway'
7
5
  | 'financial_document'
8
6
  | 'financial_name'
9
7
  | 'financial_email'
@@ -16,33 +14,46 @@ export type BillingDataSnapshot = Pick<
16
14
  | 'country'
17
15
  >;
18
16
 
19
- type BillingField = keyof Omit<BillingDataSnapshot, 'gateway'>;
17
+ type BillingField = keyof BillingDataSnapshot;
20
18
 
21
19
  /**
22
- * Campos exigidos em qualquer país — sem eles a nota fiscal não sai.
20
+ * Campos exigidos pra emitir a NF-e — sem qualquer um deles a nota não sai.
23
21
  *
24
22
  * `financial_email` NÃO entra: o backend cai no e-mail do usuário quando ele está
25
23
  * vazio (`financial_email || userDocument.email`, accounts/services.js), então a
26
24
  * nota sai do mesmo jeito. Exigi-lo aqui bloquearia 4.7k contas pagantes da wl 1
27
25
  * em vez das ~460 que de fato estão sem cadastro fiscal. O campo continua no
28
26
  * formulário, pré-preenchido — só não é critério de bloqueio.
27
+ *
28
+ * `address_complement` é opcional de verdade.
29
29
  */
30
- const INTERNATIONAL_BILLING_FIELDS: readonly BillingField[] = [
30
+ const REQUIRED_BILLING_FIELDS: readonly BillingField[] = [
31
+ 'financial_document',
31
32
  'financial_name',
32
33
  'zipcode',
33
34
  'address',
35
+ 'address_number',
36
+ 'neighborhood',
34
37
  'city',
35
38
  'state',
36
39
  'country',
37
40
  ];
38
41
 
39
- /** No Brasil a NF-e também exige documento fiscal, número e bairro. */
40
- const BR_BILLING_FIELDS: readonly BillingField[] = [
41
- ...INTERNATIONAL_BILLING_FIELDS,
42
- 'financial_document',
43
- 'address_number',
44
- 'neighborhood',
45
- ];
42
+ /**
43
+ * Conta sujeita à NF-e brasileira. Só ela entra no bloqueio: o formulário pede
44
+ * CPF/CNPJ e CEP de 8 dígitos, que um cliente de fora não tem como fornecer —
45
+ * e o modal é bloqueante, então seria tranca sem saída.
46
+ *
47
+ * País vazio conta como Brasil: é o default do cadastro e a origem da esmagadora
48
+ * maioria da base (155 contas pagas da wl 1 estão assim).
49
+ */
50
+ export function isBrazilianBillingAccount(
51
+ account: Pick<BillingDataSnapshot, 'country'> | null | undefined,
52
+ ): boolean {
53
+ if (!account) return false;
54
+ const country = account.country?.trim().toUpperCase();
55
+ return !country || country === 'BR';
56
+ }
46
57
 
47
58
  function isFilled(value: string | null | undefined): boolean {
48
59
  return typeof value === 'string' && value.trim().length > 0;
@@ -58,11 +69,7 @@ export function hasCompleteBillingData(
58
69
  ): boolean {
59
70
  if (!account) return false;
60
71
 
61
- const fields = isUsdAccount(account)
62
- ? INTERNATIONAL_BILLING_FIELDS
63
- : BR_BILLING_FIELDS;
64
-
65
- for (const field of fields) {
72
+ for (const field of REQUIRED_BILLING_FIELDS) {
66
73
  if (!isFilled(account[field])) return false;
67
74
  }
68
75
 
@@ -14,9 +14,23 @@ import {
14
14
  export type PlanBillingPeriod = "monthly" | "semiannual" | "annual";
15
15
 
16
16
  const STARTER_PLAN_ID = 415;
17
+ const AGENCY_PLAN_ID = 3;
17
18
 
18
19
  type TranslateFn = (key: any, values?: Record<string, string | number>) => string;
19
20
 
21
+ const planIdOf = (plan: Plan): number => plan.id_plan ?? plan.id;
22
+
23
+ function resolvePlanName(plan: Plan, translate?: TranslateFn): string {
24
+ if (!translate) return plan.name;
25
+ const key = `common.plans.names.${planIdOf(plan)}`;
26
+ try {
27
+ const translated = translate(key);
28
+ return translated && translated !== key ? translated : plan.name;
29
+ } catch {
30
+ return plan.name;
31
+ }
32
+ }
33
+
20
34
  const BASE_FEATURES_KEYS = [
21
35
  { icon: "check", key: "plans.features.unlimitedVisits", fallback: "Visitas ilimitadas" },
22
36
  { icon: "check", key: "plans.features.unlimitedLeads", fallback: "Leads ilimitadas" },
@@ -114,8 +128,7 @@ function buildUiPlan(plan: Plan, currency: CurrencyCode, translate?: TranslateFn
114
128
  const monthlyFinal = pricingByPeriod.monthly;
115
129
 
116
130
  const isStarterPlan = plan.id === STARTER_PLAN_ID || plan.id_plan === STARTER_PLAN_ID;
117
- const hasProjectAndOnboarding =
118
- plan.name.toLowerCase().includes("agência") || plan.name.toLowerCase().includes("agencia");
131
+ const hasProjectAndOnboarding = planIdOf(plan) === AGENCY_PLAN_ID;
119
132
 
120
133
  const onboardingText = translate
121
134
  ? translate("common.plans.features.onboarding")
@@ -151,7 +164,7 @@ function buildUiPlan(plan: Plan, currency: CurrencyCode, translate?: TranslateFn
151
164
 
152
165
  return {
153
166
  planId: plan.id,
154
- name: plan.name,
167
+ name: resolvePlanName(plan, translate),
155
168
  isPopular: popular,
156
169
  buttonVariant,
157
170
  mainFeatures: buildMainFeaturesFromItems(plan.items, translate),