@greatapps/common 1.1.673 → 1.1.674
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/account/constants.mjs +4 -4
- package/dist/components/account/constants.mjs.map +1 -1
- package/dist/components/ui/feedback/CircularProgress.mjs.map +1 -1
- package/dist/i18n/messages/en-us.mjs +43 -1
- package/dist/i18n/messages/en-us.mjs.map +1 -1
- package/dist/i18n/messages/es-es.mjs +43 -1
- package/dist/i18n/messages/es-es.mjs.map +1 -1
- package/dist/i18n/messages/pt-br.mjs +43 -1
- package/dist/i18n/messages/pt-br.mjs.map +1 -1
- package/dist/modules/images/utils/validate-image.mjs +8 -8
- package/dist/modules/images/utils/validate-image.mjs.map +1 -1
- package/dist/modules/plans/utils/map-api-plan-to-ui.mjs +45 -30
- package/dist/modules/plans/utils/map-api-plan-to-ui.mjs.map +1 -1
- package/dist/modules/subscriptions/utils/periodicity.mjs +20 -14
- package/dist/modules/subscriptions/utils/periodicity.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/account/constants.ts +8 -5
- package/src/components/ui/feedback/CircularProgress.tsx +4 -0
- package/src/i18n/messages/en-us.ts +44 -0
- package/src/i18n/messages/es-es.ts +44 -0
- package/src/i18n/messages/pt-br.ts +44 -0
- package/src/modules/images/utils/validate-image.ts +23 -8
- package/src/modules/plans/utils/map-api-plan-to-ui.ts +80 -46
- package/src/modules/subscriptions/utils/periodicity.ts +29 -12
|
@@ -64,6 +64,12 @@ const messages = {
|
|
|
64
64
|
|
|
65
65
|
// Conta — modais e seções. `<b>` = trecho em negrito (rich text); `{count}` = interpolação.
|
|
66
66
|
account: {
|
|
67
|
+
notificationTypes: {
|
|
68
|
+
projectUpdates: 'Atualizações de projetos',
|
|
69
|
+
securityAlerts: 'Alertas de segurança',
|
|
70
|
+
planBilling: 'Mudanças no plano ou faturamento',
|
|
71
|
+
scheduledMaintenance: 'Manutenções programadas',
|
|
72
|
+
},
|
|
67
73
|
deleteAccount: {
|
|
68
74
|
title: 'Excluir conta',
|
|
69
75
|
description:
|
|
@@ -384,6 +390,41 @@ const messages = {
|
|
|
384
390
|
},
|
|
385
391
|
|
|
386
392
|
// Nomes dos idiomas exibidos no seletor.
|
|
393
|
+
// Planos: features e main features (mapApiPlanToUiPlan).
|
|
394
|
+
plans: {
|
|
395
|
+
features: {
|
|
396
|
+
unlimitedVisits: 'Visitas ilimitadas',
|
|
397
|
+
unlimitedLeads: 'Leads ilimitadas',
|
|
398
|
+
sharePages: 'Compartilhar páginas',
|
|
399
|
+
hosting: 'Hospedagem inclusa',
|
|
400
|
+
freeTemplates: 'Templates gratuitos',
|
|
401
|
+
onboarding: 'Reunião de Onboarding',
|
|
402
|
+
projectManagement: 'Gestão de projetos',
|
|
403
|
+
},
|
|
404
|
+
mainFeatures: {
|
|
405
|
+
pagesUnlimited: 'Páginas ilimitadas',
|
|
406
|
+
pageSingular: '1 página',
|
|
407
|
+
pagesPlural: '{count} páginas',
|
|
408
|
+
domainsUnlimited: 'Domínios ilimitados',
|
|
409
|
+
domainSingular: '1 domínio externo',
|
|
410
|
+
domainsPlural: '{count} domínios externos',
|
|
411
|
+
usersUnlimited: 'Usuários ilimitados',
|
|
412
|
+
},
|
|
413
|
+
},
|
|
414
|
+
|
|
415
|
+
// Periodicidade de cobrança.
|
|
416
|
+
periodicity: {
|
|
417
|
+
monthly: 'Mensal',
|
|
418
|
+
semiannual: 'Semestral',
|
|
419
|
+
annual: 'Anual',
|
|
420
|
+
suffixMonthly: '/mês',
|
|
421
|
+
suffixSemiannual: '/semestre',
|
|
422
|
+
suffixAnnual: '/ano',
|
|
423
|
+
labelMonthly: 'mensal',
|
|
424
|
+
labelSemiannual: 'semestral',
|
|
425
|
+
labelAnnual: 'anual',
|
|
426
|
+
},
|
|
427
|
+
|
|
387
428
|
languages: {
|
|
388
429
|
'pt-br': 'Português (Brasil)',
|
|
389
430
|
'en-us': 'Inglês (EUA)',
|
|
@@ -434,6 +475,9 @@ const messages = {
|
|
|
434
475
|
tooSmallBodyNoFileWithDimension: 'A imagem selecionada é menor que {dimensionText}. Faça envio de uma imagem com pelo menos {dimensionText} para continuar.',
|
|
435
476
|
tooSmallBodyNoFileGeneric: 'A imagem selecionada é menor que o tamanho mínimo exigido. Faça envio de uma imagem maior para continuar.',
|
|
436
477
|
sendAnother: 'Enviar outra',
|
|
478
|
+
errorSvg: 'Formato SVG não é suportado. Use PNG, JPEG, WebP ou GIF.',
|
|
479
|
+
errorFormat: 'Formato não suportado. Use PNG, JPEG, WebP ou GIF.',
|
|
480
|
+
errorTooLarge: 'Arquivo muito grande. Máximo {maxSize}MB.',
|
|
437
481
|
},
|
|
438
482
|
|
|
439
483
|
// Componentes: NotificationPageContent (Notifications.tsx), NotificationsPopover.tsx.
|
|
@@ -3,17 +3,24 @@ import {
|
|
|
3
3
|
MAX_FILE_SIZE_MB,
|
|
4
4
|
} from '../constants/image.constants'
|
|
5
5
|
|
|
6
|
+
type TranslateFn = (key: any, values?: Record<string, string | number>) => string;
|
|
7
|
+
|
|
6
8
|
export interface ImageValidationResult {
|
|
7
9
|
valid: boolean
|
|
8
10
|
error?: string
|
|
9
11
|
}
|
|
10
12
|
|
|
11
|
-
export function validateImageFormat(
|
|
13
|
+
export function validateImageFormat(
|
|
14
|
+
file: File,
|
|
15
|
+
translate?: TranslateFn
|
|
16
|
+
): ImageValidationResult {
|
|
12
17
|
// Bloqueia SVG explicitamente (segurança + não funciona com crop)
|
|
13
18
|
if (file.type === 'image/svg+xml' || file.name.toLowerCase().endsWith('.svg')) {
|
|
14
19
|
return {
|
|
15
20
|
valid: false,
|
|
16
|
-
error:
|
|
21
|
+
error: translate
|
|
22
|
+
? translate('common.imageUpload.errorSvg')
|
|
23
|
+
: 'Formato SVG não é suportado. Use PNG, JPEG, WebP ou GIF.',
|
|
17
24
|
}
|
|
18
25
|
}
|
|
19
26
|
|
|
@@ -24,7 +31,9 @@ export function validateImageFormat(file: File): ImageValidationResult {
|
|
|
24
31
|
) {
|
|
25
32
|
return {
|
|
26
33
|
valid: false,
|
|
27
|
-
error:
|
|
34
|
+
error: translate
|
|
35
|
+
? translate('common.imageUpload.errorFormat')
|
|
36
|
+
: 'Formato não suportado. Use PNG, JPEG, WebP ou GIF.',
|
|
28
37
|
}
|
|
29
38
|
}
|
|
30
39
|
return { valid: true }
|
|
@@ -32,22 +41,28 @@ export function validateImageFormat(file: File): ImageValidationResult {
|
|
|
32
41
|
|
|
33
42
|
export function validateImageSize(
|
|
34
43
|
file: File,
|
|
35
|
-
maxSizeMB: number = MAX_FILE_SIZE_MB
|
|
44
|
+
maxSizeMB: number = MAX_FILE_SIZE_MB,
|
|
45
|
+
translate?: TranslateFn
|
|
36
46
|
): ImageValidationResult {
|
|
37
47
|
if (file.size > maxSizeMB * 1024 * 1024) {
|
|
38
48
|
return {
|
|
39
49
|
valid: false,
|
|
40
|
-
error:
|
|
50
|
+
error: translate
|
|
51
|
+
? translate('common.imageUpload.errorTooLarge', { maxSize: maxSizeMB })
|
|
52
|
+
: `Arquivo muito grande. Máximo ${maxSizeMB}MB.`,
|
|
41
53
|
}
|
|
42
54
|
}
|
|
43
55
|
return { valid: true }
|
|
44
56
|
}
|
|
45
57
|
|
|
46
|
-
export function validateImage(
|
|
47
|
-
|
|
58
|
+
export function validateImage(
|
|
59
|
+
file: File,
|
|
60
|
+
translate?: TranslateFn
|
|
61
|
+
): ImageValidationResult {
|
|
62
|
+
const formatValidation = validateImageFormat(file, translate)
|
|
48
63
|
if (!formatValidation.valid) return formatValidation
|
|
49
64
|
|
|
50
|
-
const sizeValidation = validateImageSize(file)
|
|
65
|
+
const sizeValidation = validateImageSize(file, MAX_FILE_SIZE_MB, translate)
|
|
51
66
|
if (!sizeValidation.valid) return sizeValidation
|
|
52
67
|
|
|
53
68
|
return { valid: true }
|
|
@@ -13,37 +13,72 @@ import {
|
|
|
13
13
|
|
|
14
14
|
export type PlanBillingPeriod = "monthly" | "semiannual" | "annual";
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
{ icon: "check",
|
|
20
|
-
{ icon: "check",
|
|
21
|
-
{ icon: "check",
|
|
22
|
-
{ icon: "check",
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
16
|
+
type TranslateFn = (key: any, values?: Record<string, string | number>) => string;
|
|
17
|
+
|
|
18
|
+
const BASE_FEATURES_KEYS = [
|
|
19
|
+
{ icon: "check", key: "plans.features.unlimitedVisits", fallback: "Visitas ilimitadas" },
|
|
20
|
+
{ icon: "check", key: "plans.features.unlimitedLeads", fallback: "Leads ilimitadas" },
|
|
21
|
+
{ icon: "check", key: "plans.features.sharePages", fallback: "Compartilhar páginas", tooltip: "sharePages" },
|
|
22
|
+
{ icon: "check", key: "plans.features.hosting", fallback: "Hospedagem inclusa" },
|
|
23
|
+
// SSL (HTTPS) + CDN — brand/acronym: never translated
|
|
24
|
+
{ icon: "check", key: null, fallback: "SSL (HTTPS) + CDN" },
|
|
25
|
+
{ icon: "check", key: "plans.features.freeTemplates", fallback: "Templates gratuitos" },
|
|
26
|
+
] as const;
|
|
27
|
+
|
|
28
|
+
function buildBaseFeatures(translate?: TranslateFn): PlanFeature[] {
|
|
29
|
+
return BASE_FEATURES_KEYS.map(({ icon, key, fallback, ...rest }) => {
|
|
30
|
+
const text = key && translate ? translate(`common.${key}`) : fallback;
|
|
31
|
+
const feature: PlanFeature = { icon, text };
|
|
32
|
+
if ("tooltip" in rest && rest.tooltip) feature.tooltip = rest.tooltip;
|
|
33
|
+
return feature;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function buildMainFeaturesFromItems(
|
|
38
|
+
items: Plan["items"],
|
|
39
|
+
translate?: TranslateFn
|
|
40
|
+
): PlanMainFeatures {
|
|
26
41
|
const pagesItem = items.find((i) => i.id_addon === ADDON_IDS.PAGES);
|
|
27
42
|
const domainsItem = items.find((i) => i.id_addon === ADDON_IDS.DOMAINS);
|
|
28
43
|
|
|
29
44
|
const pagesQty = pagesItem?.quantity ?? 0;
|
|
30
45
|
const domainsQty = domainsItem?.quantity ?? 1;
|
|
31
46
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
+
let pages: string;
|
|
48
|
+
if (pagesQty === 0) {
|
|
49
|
+
pages = translate
|
|
50
|
+
? translate("common.plans.mainFeatures.pagesUnlimited")
|
|
51
|
+
: "Páginas ilimitadas";
|
|
52
|
+
} else if (pagesQty === 1) {
|
|
53
|
+
pages = translate
|
|
54
|
+
? translate("common.plans.mainFeatures.pageSingular")
|
|
55
|
+
: "1 página";
|
|
56
|
+
} else {
|
|
57
|
+
pages = translate
|
|
58
|
+
? translate("common.plans.mainFeatures.pagesPlural", { count: pagesQty })
|
|
59
|
+
: `${pagesQty} páginas`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let domains: string;
|
|
63
|
+
if (domainsQty >= 999) {
|
|
64
|
+
domains = translate
|
|
65
|
+
? translate("common.plans.mainFeatures.domainsUnlimited")
|
|
66
|
+
: "Domínios ilimitados";
|
|
67
|
+
} else if (domainsQty === 1) {
|
|
68
|
+
domains = translate
|
|
69
|
+
? translate("common.plans.mainFeatures.domainSingular")
|
|
70
|
+
: "1 domínio externo";
|
|
71
|
+
} else {
|
|
72
|
+
domains = translate
|
|
73
|
+
? translate("common.plans.mainFeatures.domainsPlural", { count: domainsQty })
|
|
74
|
+
: `${domainsQty} domínios externos`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const users = translate
|
|
78
|
+
? translate("common.plans.mainFeatures.usersUnlimited")
|
|
79
|
+
: "Usuários ilimitados";
|
|
80
|
+
|
|
81
|
+
return { pages, domains, users };
|
|
47
82
|
}
|
|
48
83
|
|
|
49
84
|
function computePricingByPeriod(apiPlan: Plan, currency: CurrencyCode): PlanPricingByPeriod {
|
|
@@ -58,7 +93,7 @@ function computePricingByPeriod(apiPlan: Plan, currency: CurrencyCode): PlanPric
|
|
|
58
93
|
};
|
|
59
94
|
}
|
|
60
95
|
|
|
61
|
-
function buildUiPlan(plan: Plan, currency: CurrencyCode): UiPlan {
|
|
96
|
+
function buildUiPlan(plan: Plan, currency: CurrencyCode, translate?: TranslateFn): UiPlan {
|
|
62
97
|
const popular = plan.id === 2 || plan.id_plan === 2;
|
|
63
98
|
const buttonVariant = popular ? "brand" : "default";
|
|
64
99
|
|
|
@@ -69,23 +104,21 @@ function buildUiPlan(plan: Plan, currency: CurrencyCode): UiPlan {
|
|
|
69
104
|
const hasProjectAndOnboarding =
|
|
70
105
|
lowerName.includes("agência") || lowerName.includes("agencia");
|
|
71
106
|
|
|
107
|
+
const onboardingText = translate
|
|
108
|
+
? translate("common.plans.features.onboarding")
|
|
109
|
+
: "Reunião de Onboarding";
|
|
110
|
+
const projectMgmtText = translate
|
|
111
|
+
? translate("common.plans.features.projectManagement")
|
|
112
|
+
: "Gestão de projetos";
|
|
113
|
+
|
|
72
114
|
const features: PlanFeature[] = [
|
|
73
|
-
...
|
|
115
|
+
...buildBaseFeatures(translate),
|
|
74
116
|
hasProjectAndOnboarding
|
|
75
|
-
? { icon: "check", text:
|
|
76
|
-
: { icon: "x", text:
|
|
117
|
+
? { icon: "check", text: onboardingText }
|
|
118
|
+
: { icon: "x", text: onboardingText, disabled: true },
|
|
77
119
|
hasProjectAndOnboarding
|
|
78
|
-
? {
|
|
79
|
-
|
|
80
|
-
text: "Gestão de projetos",
|
|
81
|
-
tooltip: "projectManagement",
|
|
82
|
-
}
|
|
83
|
-
: {
|
|
84
|
-
icon: "x",
|
|
85
|
-
text: "Gestão de projetos",
|
|
86
|
-
disabled: true,
|
|
87
|
-
tooltip: "projectManagement",
|
|
88
|
-
},
|
|
120
|
+
? { icon: "check", text: projectMgmtText, tooltip: "projectManagement" }
|
|
121
|
+
: { icon: "x", text: projectMgmtText, disabled: true, tooltip: "projectManagement" },
|
|
89
122
|
];
|
|
90
123
|
|
|
91
124
|
return {
|
|
@@ -93,7 +126,7 @@ function buildUiPlan(plan: Plan, currency: CurrencyCode): UiPlan {
|
|
|
93
126
|
name: plan.name,
|
|
94
127
|
isPopular: popular,
|
|
95
128
|
buttonVariant,
|
|
96
|
-
mainFeatures: buildMainFeaturesFromItems(plan.items),
|
|
129
|
+
mainFeatures: buildMainFeaturesFromItems(plan.items, translate),
|
|
97
130
|
features,
|
|
98
131
|
originalPrice: formatCurrencyNumber(monthlyFinal, 2, currency),
|
|
99
132
|
price: formatCurrencyNumber(monthlyFinal, 2, currency),
|
|
@@ -105,17 +138,18 @@ function buildUiPlan(plan: Plan, currency: CurrencyCode): UiPlan {
|
|
|
105
138
|
/**
|
|
106
139
|
* Mapeia um plano da API para UiPlan em BRL.
|
|
107
140
|
* Assinatura de 1 argumento — seguro em `plans.map(mapApiPlanToUiPlan)`.
|
|
141
|
+
* Passa `translate` opcional para i18n; sem ele cai nos literais pt-br (back-compat).
|
|
108
142
|
*/
|
|
109
|
-
export function mapApiPlanToUiPlan(plan: Plan): UiPlan {
|
|
110
|
-
return buildUiPlan(plan, "brl");
|
|
143
|
+
export function mapApiPlanToUiPlan(plan: Plan, translate?: TranslateFn): UiPlan {
|
|
144
|
+
return buildUiPlan(plan, "brl", translate);
|
|
111
145
|
}
|
|
112
146
|
|
|
113
147
|
/**
|
|
114
148
|
* Variante currency-aware: a moeda vem do gateway da conta (getCurrencyForGateway).
|
|
115
|
-
* Fábrica para uso ergonômico em `.map`: `plans.map(mapApiPlanToUiPlanForCurrency(currency))`.
|
|
149
|
+
* Fábrica para uso ergonômico em `.map`: `plans.map(mapApiPlanToUiPlanForCurrency(currency, translate))`.
|
|
116
150
|
* Mantida separada de mapApiPlanToUiPlan para não quebrar callers que passam a fn direto pro `.map`.
|
|
117
151
|
*/
|
|
118
152
|
export const mapApiPlanToUiPlanForCurrency =
|
|
119
|
-
(currency: CurrencyCode) =>
|
|
153
|
+
(currency: CurrencyCode, translate?: TranslateFn) =>
|
|
120
154
|
(plan: Plan): UiPlan =>
|
|
121
|
-
buildUiPlan(plan, currency);
|
|
155
|
+
buildUiPlan(plan, currency, translate);
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { BillingPeriod } from '../types/billing-period.type';
|
|
2
2
|
import type { CalculateSubscriptionResponse } from '../types/calculate-subscription.type';
|
|
3
3
|
|
|
4
|
+
type TranslateFn = (key: any, values?: Record<string, string | number>) => string;
|
|
5
|
+
|
|
4
6
|
export function periodicityToBillingPeriod(
|
|
5
7
|
periodicity: number | string | null | undefined
|
|
6
8
|
): BillingPeriod | null {
|
|
@@ -16,23 +18,38 @@ export function billingPeriodToNumeric(billingPeriod: BillingPeriod): 30 | 180 |
|
|
|
16
18
|
return 30;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
export function getPeriodicityLabel(
|
|
21
|
+
export function getPeriodicityLabel(
|
|
22
|
+
periodicity: number | string | null | undefined,
|
|
23
|
+
translate?: TranslateFn
|
|
24
|
+
): string {
|
|
20
25
|
const period = periodicityToBillingPeriod(periodicity);
|
|
21
|
-
if (period === 'semiannual')
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
if (period === 'semiannual')
|
|
27
|
+
return translate ? translate('common.periodicity.semiannual') : 'Semestral';
|
|
28
|
+
if (period === 'annual')
|
|
29
|
+
return translate ? translate('common.periodicity.annual') : 'Anual';
|
|
30
|
+
return translate ? translate('common.periodicity.monthly') : 'Mensal';
|
|
24
31
|
}
|
|
25
32
|
|
|
26
|
-
export function getBillingPeriodSuffix(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
export function getBillingPeriodSuffix(
|
|
34
|
+
billingPeriod: BillingPeriod,
|
|
35
|
+
translate?: TranslateFn
|
|
36
|
+
): string {
|
|
37
|
+
if (billingPeriod === 'semiannual')
|
|
38
|
+
return translate ? translate('common.periodicity.suffixSemiannual') : '/semestre';
|
|
39
|
+
if (billingPeriod === 'annual')
|
|
40
|
+
return translate ? translate('common.periodicity.suffixAnnual') : '/ano';
|
|
41
|
+
return translate ? translate('common.periodicity.suffixMonthly') : '/mês';
|
|
30
42
|
}
|
|
31
43
|
|
|
32
|
-
export function getBillingPeriodLabel(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
44
|
+
export function getBillingPeriodLabel(
|
|
45
|
+
billingPeriod: BillingPeriod,
|
|
46
|
+
translate?: TranslateFn
|
|
47
|
+
): string {
|
|
48
|
+
if (billingPeriod === 'semiannual')
|
|
49
|
+
return translate ? translate('common.periodicity.labelSemiannual') : 'semestral';
|
|
50
|
+
if (billingPeriod === 'annual')
|
|
51
|
+
return translate ? translate('common.periodicity.labelAnnual') : 'anual';
|
|
52
|
+
return translate ? translate('common.periodicity.labelMonthly') : 'mensal';
|
|
36
53
|
}
|
|
37
54
|
|
|
38
55
|
export function getBillingPeriodMonths(billingPeriod: BillingPeriod): number {
|