@greatapps/common 1.1.439 → 1.1.441
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/embeds/CrispEmbed.mjs +51 -21
- package/dist/components/embeds/CrispEmbed.mjs.map +1 -1
- package/dist/components/embeds/FrillEmbed.mjs +7 -1
- package/dist/components/embeds/FrillEmbed.mjs.map +1 -1
- package/dist/components/modals/cards/AddCardModal.mjs +2 -5
- package/dist/components/modals/cards/AddCardModal.mjs.map +1 -1
- package/dist/index.mjs +9 -1
- package/dist/index.mjs.map +1 -1
- package/dist/modules/plans/actions/list-plans.action.mjs +26 -0
- package/dist/modules/plans/actions/list-plans.action.mjs.map +1 -0
- package/dist/modules/plans/hooks/list-plans.hook.mjs +22 -0
- package/dist/modules/plans/hooks/list-plans.hook.mjs.map +1 -0
- package/dist/modules/plans/hooks/use-plan-by-id.hook.mjs +18 -2
- package/dist/modules/plans/hooks/use-plan-by-id.hook.mjs.map +1 -1
- package/dist/modules/plans/services/plans.service.mjs +60 -4
- package/dist/modules/plans/services/plans.service.mjs.map +1 -1
- package/dist/modules/plans/types/plan.type.mjs +1 -0
- package/dist/modules/plans/types/plan.type.mjs.map +1 -1
- package/dist/modules/plans/utils/map-api-plan-to-ui.mjs +58 -0
- package/dist/modules/plans/utils/map-api-plan-to-ui.mjs.map +1 -0
- package/dist/server.mjs +2 -0
- package/dist/server.mjs.map +1 -1
- package/dist/utils/format/currency.mjs +13 -0
- package/dist/utils/format/currency.mjs.map +1 -0
- package/package.json +1 -1
- package/src/components/embeds/CrispEmbed.tsx +73 -29
- package/src/components/embeds/FrillEmbed.tsx +8 -1
- package/src/components/modals/cards/AddCardModal.tsx +1 -2
- package/src/index.ts +6 -2
- package/src/modules/plans/actions/list-plans.action.ts +28 -0
- package/src/modules/plans/hooks/list-plans.hook.ts +21 -0
- package/src/modules/plans/hooks/use-plan-by-id.hook.ts +26 -5
- package/src/modules/plans/services/plans.service.ts +87 -4
- package/src/modules/plans/types/plan.type.ts +46 -0
- package/src/modules/plans/utils/map-api-plan-to-ui.ts +89 -0
- package/src/server.ts +1 -0
- package/src/utils/format/currency.ts +15 -0
|
@@ -1,9 +1,63 @@
|
|
|
1
1
|
import "server-only";
|
|
2
|
-
import { api } from "
|
|
3
|
-
import { ApiError } from "
|
|
4
|
-
import
|
|
2
|
+
import { api, apiClient } from "@greatapps/common/server";
|
|
3
|
+
import { ApiError, buildQueryParams, PlanSchema } from "@greatapps/common";
|
|
4
|
+
import greatCache from "@greatapps/cache";
|
|
5
|
+
const PLANS_CACHE_TTL = 604800;
|
|
5
6
|
class PlansService {
|
|
7
|
+
cache = new greatCache({
|
|
8
|
+
service: "plans-service",
|
|
9
|
+
version: "1.0.1",
|
|
10
|
+
domain: "whitelabel-cache.greatapps.com.br",
|
|
11
|
+
ambient: process.env.NODE_ENV || "development"
|
|
12
|
+
});
|
|
13
|
+
buildCacheKey(key, params) {
|
|
14
|
+
const sort = params?.sort ?? "id:ASC";
|
|
15
|
+
const active = params?.active ?? "";
|
|
16
|
+
const search = params?.search ?? "";
|
|
17
|
+
return `${key}-${sort}-${active}-${search}`;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Lista planos do whitelabel.
|
|
21
|
+
* Exemplo:
|
|
22
|
+
* GET /{id_wl}/plans?active=true&search=client&sort=id:ASC
|
|
23
|
+
*/
|
|
24
|
+
async listPlans(params) {
|
|
25
|
+
const cacheKey = this.buildCacheKey("plans", params);
|
|
26
|
+
const cachedData = await this.cache.select(cacheKey);
|
|
27
|
+
if (cachedData.status == 1 && "data" in cachedData && cachedData.data) {
|
|
28
|
+
const data2 = JSON.parse(cachedData.data);
|
|
29
|
+
return { data: data2, total: data2.length, success: true };
|
|
30
|
+
}
|
|
31
|
+
const query = buildQueryParams({
|
|
32
|
+
sort: params?.sort ?? "id:ASC",
|
|
33
|
+
active: params?.active,
|
|
34
|
+
search: params?.search
|
|
35
|
+
});
|
|
36
|
+
const url = `/plans${query ? `?${query}` : ""}`;
|
|
37
|
+
const response = await apiClient.get(url);
|
|
38
|
+
if (response.status === 0) {
|
|
39
|
+
throw new ApiError(
|
|
40
|
+
response.message || "Erro ao listar planos",
|
|
41
|
+
"LIST_PLANS_FAILED",
|
|
42
|
+
400
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
const rawData = response.data;
|
|
46
|
+
const data = Array.isArray(rawData) ? rawData.map((item) => PlanSchema.parse(item)) : [];
|
|
47
|
+
await this.cache.insert(cacheKey, JSON.stringify(data), PLANS_CACHE_TTL);
|
|
48
|
+
return {
|
|
49
|
+
data,
|
|
50
|
+
total: response.total,
|
|
51
|
+
success: true
|
|
52
|
+
};
|
|
53
|
+
}
|
|
6
54
|
async findById(idPlan) {
|
|
55
|
+
const cacheKey = this.buildCacheKey(`plan-${idPlan}`);
|
|
56
|
+
const cachedData = await this.cache.select(cacheKey);
|
|
57
|
+
if (cachedData.status == 1 && "data" in cachedData && cachedData.data) {
|
|
58
|
+
const data = JSON.parse(cachedData.data);
|
|
59
|
+
return { data, success: true };
|
|
60
|
+
}
|
|
7
61
|
const response = await api.apps.get(
|
|
8
62
|
`/plans/${idPlan}?type=client`
|
|
9
63
|
);
|
|
@@ -21,9 +75,11 @@ class PlansService {
|
|
|
21
75
|
404
|
|
22
76
|
);
|
|
23
77
|
}
|
|
78
|
+
const plan = PlanSchema.parse(response.data[0]);
|
|
79
|
+
await this.cache.insert(cacheKey, JSON.stringify(plan), PLANS_CACHE_TTL);
|
|
24
80
|
return {
|
|
25
81
|
success: true,
|
|
26
|
-
data:
|
|
82
|
+
data: plan
|
|
27
83
|
};
|
|
28
84
|
}
|
|
29
85
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/modules/plans/services/plans.service.ts"],"sourcesContent":["import 'server-only';\n\nimport { api } from '
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/plans/services/plans.service.ts"],"sourcesContent":["import 'server-only';\n\nimport { api, apiClient } from '@greatapps/common/server';\nimport { ApiError, buildQueryParams, PlanSchema } from '@greatapps/common';\nimport type { ApiPaginatedActionResult, PaginatedSuccessResult, Plan, SuccessResult } from '@greatapps/common';\nimport greatCache from '@greatapps/cache';\n\nexport type ListPlansParams = {\n sort?: string;\n active?: boolean;\n search?: string;\n};\n\nconst PLANS_CACHE_TTL = 604800;\n\nclass PlansService {\n private cache = new greatCache({\n service: 'plans-service',\n version: '1.0.1',\n domain: 'whitelabel-cache.greatapps.com.br',\n ambient: process.env.NODE_ENV || 'development',\n });\n\n private buildCacheKey(key: string, params?: ListPlansParams): string {\n const sort = params?.sort ?? 'id:ASC';\n const active = params?.active ?? '';\n const search = params?.search ?? '';\n return `${key}-${sort}-${active}-${search}`;\n }\n\n /**\n * Lista planos do whitelabel.\n * Exemplo:\n * GET /{id_wl}/plans?active=true&search=client&sort=id:ASC\n */\n async listPlans(params?: ListPlansParams): Promise<PaginatedSuccessResult<Plan>> {\n const cacheKey = this.buildCacheKey('plans', params);\n\n const cachedData = await this.cache.select(cacheKey);\n\n if (cachedData.status == 1 && 'data' in cachedData && cachedData.data) {\n const data = JSON.parse(cachedData.data) as Plan[];\n return { data, total: data.length, success: true };\n }\n\n const query = buildQueryParams({\n sort: params?.sort ?? 'id:ASC',\n active: params?.active,\n search: params?.search,\n });\n const url = `/plans${query ? `?${query}` : ''}`;\n\n const response = await apiClient.get<ApiPaginatedActionResult<Plan[]>>(url);\n\n if (response.status === 0) {\n throw new ApiError(\n (response as { message?: string }).message || 'Erro ao listar planos',\n 'LIST_PLANS_FAILED',\n 400\n );\n }\n\n const rawData = (response as { data?: unknown }).data;\n const data = Array.isArray(rawData)\n ? rawData.map((item) => PlanSchema.parse(item))\n : [];\n\n await this.cache.insert(cacheKey, JSON.stringify(data), PLANS_CACHE_TTL);\n\n return {\n data,\n total: response.total,\n success: true,\n } satisfies PaginatedSuccessResult<Plan>;\n }\n\n\n async findById(idPlan: number | string): Promise<SuccessResult<Plan>> {\n const cacheKey = this.buildCacheKey(`plan-${idPlan}`);\n\n const cachedData = await this.cache.select(cacheKey);\n \n if (cachedData.status == 1 && 'data' in cachedData && cachedData.data) {\n const data = JSON.parse(cachedData.data) as Plan;\n return { data, success: true };\n }\n\n const response = await api.apps.get<ApiPaginatedActionResult<Plan>>(\n `/plans/${idPlan}?type=client`\n );\n\n if (response.status === 0) {\n throw new ApiError(\n response.message || 'Erro ao buscar plano',\n 'FIND_PLAN_FAILED',\n 400\n );\n }\n\n if (!response.data?.length) {\n throw new ApiError(\n 'Plano não encontrado',\n 'PLAN_NOT_FOUND',\n 404\n );\n }\n\n const plan = PlanSchema.parse(response.data[0]);\n\n await this.cache.insert(cacheKey, JSON.stringify(plan), PLANS_CACHE_TTL);\n\n return {\n success: true,\n data: plan,\n };\n }\n}\n\nexport const plansService = new PlansService();\n"],"mappings":"AAAA,OAAO;AAEP,SAAS,KAAK,iBAAiB;AAC/B,SAAS,UAAU,kBAAkB,kBAAkB;AAEvD,OAAO,gBAAgB;AAQvB,MAAM,kBAAkB;AAExB,MAAM,aAAa;AAAA,EACT,QAAQ,IAAI,WAAW;AAAA,IAC7B,SAAS;AAAA,IACT,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,SAAS,QAAQ,IAAI,YAAY;AAAA,EACnC,CAAC;AAAA,EAEO,cAAc,KAAa,QAAkC;AACnE,UAAM,OAAO,QAAQ,QAAQ;AAC7B,UAAM,SAAS,QAAQ,UAAU;AACjC,UAAM,SAAS,QAAQ,UAAU;AACjC,WAAO,GAAG,GAAG,IAAI,IAAI,IAAI,MAAM,IAAI,MAAM;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,UAAU,QAAiE;AAC/E,UAAM,WAAW,KAAK,cAAc,SAAS,MAAM;AAEnD,UAAM,aAAa,MAAM,KAAK,MAAM,OAAO,QAAQ;AAEnD,QAAI,WAAW,UAAU,KAAK,UAAU,cAAc,WAAW,MAAM;AACrE,YAAMA,QAAO,KAAK,MAAM,WAAW,IAAI;AACvC,aAAO,EAAE,MAAAA,OAAM,OAAOA,MAAK,QAAQ,SAAS,KAAK;AAAA,IACnD;AAEA,UAAM,QAAQ,iBAAiB;AAAA,MAC7B,MAAM,QAAQ,QAAQ;AAAA,MACtB,QAAQ,QAAQ;AAAA,MAChB,QAAQ,QAAQ;AAAA,IAClB,CAAC;AACD,UAAM,MAAM,SAAS,QAAQ,IAAI,KAAK,KAAK,EAAE;AAE7C,UAAM,WAAW,MAAM,UAAU,IAAsC,GAAG;AAE1E,QAAI,SAAS,WAAW,GAAG;AACzB,YAAM,IAAI;AAAA,QACP,SAAkC,WAAW;AAAA,QAC9C;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,UAAM,UAAW,SAAgC;AACjD,UAAM,OAAO,MAAM,QAAQ,OAAO,IAC9B,QAAQ,IAAI,CAAC,SAAS,WAAW,MAAM,IAAI,CAAC,IAC5C,CAAC;AAEL,UAAM,KAAK,MAAM,OAAO,UAAU,KAAK,UAAU,IAAI,GAAG,eAAe;AAEvE,WAAO;AAAA,MACL;AAAA,MACA,OAAO,SAAS;AAAA,MAChB,SAAS;AAAA,IACX;AAAA,EACF;AAAA,EAGA,MAAM,SAAS,QAAuD;AACpE,UAAM,WAAW,KAAK,cAAc,QAAQ,MAAM,EAAE;AAEpD,UAAM,aAAa,MAAM,KAAK,MAAM,OAAO,QAAQ;AAEnD,QAAI,WAAW,UAAU,KAAK,UAAU,cAAc,WAAW,MAAM;AACrE,YAAM,OAAO,KAAK,MAAM,WAAW,IAAI;AACvC,aAAO,EAAE,MAAM,SAAS,KAAK;AAAA,IAC/B;AAEA,UAAM,WAAW,MAAM,IAAI,KAAK;AAAA,MAC9B,UAAU,MAAM;AAAA,IAClB;AAEA,QAAI,SAAS,WAAW,GAAG;AACzB,YAAM,IAAI;AAAA,QACR,SAAS,WAAW;AAAA,QACpB;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,QAAI,CAAC,SAAS,MAAM,QAAQ;AAC1B,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,UAAM,OAAO,WAAW,MAAM,SAAS,KAAK,CAAC,CAAC;AAE9C,UAAM,KAAK,MAAM,OAAO,UAAU,KAAK,UAAU,IAAI,GAAG,eAAe;AAEvE,WAAO;AAAA,MACL,SAAS;AAAA,MACT,MAAM;AAAA,IACR;AAAA,EACF;AACF;AAEO,MAAM,eAAe,IAAI,aAAa;","names":["data"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/modules/plans/types/plan.type.ts"],"sourcesContent":["import z from 'zod';\n\nexport const PlanItemSchema = z.object({\n id_addon: z.number(),\n quantity: z.number(),\n value: z.number(),\n});\n\nexport type PlanItem = z.infer<typeof PlanItemSchema>;\n\nexport const PlanSchema = z.object({\n id: z.number(),\n name: z.string(),\n value: z.number().default(0),\n type: z.string().optional(),\n discount_semester: z.number().default(0),\n discount_annual: z.number().default(0),\n items: z.array(PlanItemSchema).optional().default([]),\n});\n\nexport type Plan = z.infer<typeof PlanSchema>;\n"],"mappings":"AAAA,OAAO,OAAO;
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/plans/types/plan.type.ts"],"sourcesContent":["import z from 'zod';\nimport type { BillingPeriod } from '../../subscriptions/types/billing-period.type';\n\nexport const PlanItemSchema = z.object({\n id_addon: z.number(),\n quantity: z.number(),\n value: z.number(),\n});\n\nexport type PlanItem = z.infer<typeof PlanItemSchema>;\n\nexport const PlanSchema = z.object({\n id: z.number(),\n id_plan: z.number().optional(),\n name: z.string(),\n value: z.number().default(0),\n type: z.string().optional(),\n discount_semester: z.number().default(0),\n discount_annual: z.number().default(0),\n items: z.array(PlanItemSchema).optional().default([]),\n});\n\nexport type Plan = z.infer<typeof PlanSchema>;\n\n// ── UI Plan types (used by list-plans action and consumers) ──\n\nexport type PlanFeature = {\n icon: 'check' | 'x';\n text: string;\n disabled?: boolean;\n tooltip?: keyof PlanTooltips;\n};\n\nexport type PlanMainFeatures = {\n pages: string;\n domains: string;\n users: string;\n};\n\n/** Preços mensais equivalentes por período de cobrança (já com desconto aplicado). */\nexport type PlanPricingByPeriod = {\n monthly: number;\n semiannual: number;\n annual: number;\n};\n\nexport type UiPlan = {\n idPlan: number;\n name: string;\n isPopular?: boolean;\n isCurrentPlan?: boolean;\n currentPeriodicity?: BillingPeriod | null;\n mainFeatures: PlanMainFeatures;\n features: PlanFeature[];\n originalPrice: string;\n price: string;\n pricingByPeriod?: PlanPricingByPeriod;\n buttonVariant?: 'brand' | 'default';\n items: PlanItem[];\n};\n\nexport type PlanTooltips = {\n domains: string;\n users: string;\n sharePages: string;\n projectManagement: string;\n};\n"],"mappings":"AAAA,OAAO,OAAO;AAGP,MAAM,iBAAiB,EAAE,OAAO;AAAA,EACrC,UAAU,EAAE,OAAO;AAAA,EACnB,UAAU,EAAE,OAAO;AAAA,EACnB,OAAO,EAAE,OAAO;AAClB,CAAC;AAIM,MAAM,aAAa,EAAE,OAAO;AAAA,EACjC,IAAI,EAAE,OAAO;AAAA,EACb,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,MAAM,EAAE,OAAO;AAAA,EACf,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC;AAAA,EAC3B,MAAM,EAAE,OAAO,EAAE,SAAS;AAAA,EAC1B,mBAAmB,EAAE,OAAO,EAAE,QAAQ,CAAC;AAAA,EACvC,iBAAiB,EAAE,OAAO,EAAE,QAAQ,CAAC;AAAA,EACrC,OAAO,EAAE,MAAM,cAAc,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;AACtD,CAAC;","names":[]}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { ADDON_IDS } from "../../subscriptions/constants/addons.constants";
|
|
2
|
+
import { formatCurrencyNumber } from "../../../utils/format/currency";
|
|
3
|
+
const BASE_FEATURES = [
|
|
4
|
+
{ icon: "check", text: "Visitas ilimitadas" },
|
|
5
|
+
{ icon: "check", text: "Leads ilimitadas" },
|
|
6
|
+
{ icon: "check", text: "Compartilhar p\xE1ginas", tooltip: "sharePages" },
|
|
7
|
+
{ icon: "check", text: "Hospedagem inclusa" },
|
|
8
|
+
{ icon: "check", text: "SSL (HTTPS) + CDN" },
|
|
9
|
+
{ icon: "check", text: "Templates Free" }
|
|
10
|
+
];
|
|
11
|
+
function buildMainFeaturesFromItems(items) {
|
|
12
|
+
const pagesItem = items.find((i) => i.id_addon === ADDON_IDS.PAGES);
|
|
13
|
+
const domainsItem = items.find((i) => i.id_addon === ADDON_IDS.DOMAINS);
|
|
14
|
+
const pagesQty = pagesItem?.quantity ?? 0;
|
|
15
|
+
const domainsQty = domainsItem?.quantity ?? 1;
|
|
16
|
+
return {
|
|
17
|
+
pages: pagesQty === 0 ? "P\xE1ginas ilimitadas" : pagesQty === 1 ? "1 p\xE1gina" : `${pagesQty} p\xE1ginas`,
|
|
18
|
+
domains: domainsQty >= 999 ? "Dom\xEDnios ilimitados" : domainsQty === 1 ? "1 dom\xEDnio externo" : `${domainsQty} dom\xEDnios externos`,
|
|
19
|
+
users: "Usu\xE1rios ilimitados"
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function computePricingByPeriod(apiPlan) {
|
|
23
|
+
const monthly = apiPlan.value;
|
|
24
|
+
const round2 = (n) => Math.round(n * 100) / 100;
|
|
25
|
+
return {
|
|
26
|
+
monthly,
|
|
27
|
+
semiannual: round2(monthly * (1 - apiPlan.discount_semester / 100)),
|
|
28
|
+
annual: round2(monthly * (1 - apiPlan.discount_annual / 100))
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function mapApiPlanToUiPlan(apiPlan, options) {
|
|
32
|
+
const { planId, isPopular, buttonVariant } = options;
|
|
33
|
+
const pricingByPeriod = computePricingByPeriod(apiPlan);
|
|
34
|
+
const monthlyFinal = pricingByPeriod.monthly;
|
|
35
|
+
const lowerName = apiPlan.name.toLowerCase();
|
|
36
|
+
const hasProjectAndOnboarding = lowerName.includes("ag\xEAncia") || lowerName.includes("agencia");
|
|
37
|
+
const features = [
|
|
38
|
+
...BASE_FEATURES,
|
|
39
|
+
hasProjectAndOnboarding ? { icon: "check", text: "Reuni\xE3o de Onboarding" } : { icon: "x", text: "Reuni\xE3o de Onboarding", disabled: true },
|
|
40
|
+
hasProjectAndOnboarding ? { icon: "check", text: "Gest\xE3o de projetos", tooltip: "projectManagement" } : { icon: "x", text: "Gest\xE3o de projetos", disabled: true, tooltip: "projectManagement" }
|
|
41
|
+
];
|
|
42
|
+
return {
|
|
43
|
+
idPlan: planId,
|
|
44
|
+
name: apiPlan.name,
|
|
45
|
+
isPopular,
|
|
46
|
+
buttonVariant,
|
|
47
|
+
mainFeatures: buildMainFeaturesFromItems(apiPlan.items),
|
|
48
|
+
features,
|
|
49
|
+
originalPrice: formatCurrencyNumber(monthlyFinal),
|
|
50
|
+
price: formatCurrencyNumber(monthlyFinal),
|
|
51
|
+
pricingByPeriod,
|
|
52
|
+
items: apiPlan.items
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export {
|
|
56
|
+
mapApiPlanToUiPlan
|
|
57
|
+
};
|
|
58
|
+
//# sourceMappingURL=map-api-plan-to-ui.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/plans/utils/map-api-plan-to-ui.ts"],"sourcesContent":["import type { Plan, PlanFeature, PlanMainFeatures, PlanPricingByPeriod, UiPlan } from '../types/plan.type';\nimport { ADDON_IDS } from '../../subscriptions/constants/addons.constants';\nimport { formatCurrencyNumber } from '../../../utils/format/currency';\n\nexport type PlanBillingPeriod = 'monthly' | 'semiannual' | 'annual';\n\nconst BASE_FEATURES: PlanFeature[] = [\n { icon: 'check', text: 'Visitas ilimitadas' },\n { icon: 'check', text: 'Leads ilimitadas' },\n { icon: 'check', text: 'Compartilhar páginas', tooltip: 'sharePages' },\n { icon: 'check', text: 'Hospedagem inclusa' },\n { icon: 'check', text: 'SSL (HTTPS) + CDN' },\n { icon: 'check', text: 'Templates Free' },\n];\n\nfunction buildMainFeaturesFromItems(items: Plan['items']): 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 return {\n pages:\n pagesQty === 0\n ? 'Páginas ilimitadas'\n : pagesQty === 1\n ? '1 página'\n : `${pagesQty} páginas`,\n domains:\n domainsQty >= 999\n ? 'Domínios ilimitados'\n : domainsQty === 1\n ? '1 domínio externo'\n : `${domainsQty} domínios externos`,\n users: 'Usuários ilimitados',\n };\n}\n\nfunction computePricingByPeriod(apiPlan: Plan): PlanPricingByPeriod {\n const monthly = apiPlan.value;\n const round2 = (n: number) => Math.round(n * 100) / 100;\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\nexport function mapApiPlanToUiPlan(\n apiPlan: Plan,\n options: {\n planId: number;\n isPopular?: boolean;\n buttonVariant?: 'brand' | 'default';\n }\n): UiPlan {\n const { planId, isPopular, buttonVariant } = options;\n\n const pricingByPeriod = computePricingByPeriod(apiPlan);\n const monthlyFinal = pricingByPeriod.monthly;\n\n const lowerName = apiPlan.name.toLowerCase();\n const hasProjectAndOnboarding =\n lowerName.includes('agência') || lowerName.includes('agencia');\n\n const features: PlanFeature[] = [\n ...BASE_FEATURES,\n hasProjectAndOnboarding\n ? { icon: 'check', text: 'Reunião de Onboarding' }\n : { icon: 'x', text: 'Reunião de Onboarding', disabled: true },\n hasProjectAndOnboarding\n ? { icon: 'check', text: 'Gestão de projetos', tooltip: 'projectManagement' }\n : { icon: 'x', text: 'Gestão de projetos', disabled: true, tooltip: 'projectManagement' },\n ];\n\n return {\n idPlan: planId,\n name: apiPlan.name,\n isPopular,\n buttonVariant,\n mainFeatures: buildMainFeaturesFromItems(apiPlan.items),\n features,\n originalPrice: formatCurrencyNumber(monthlyFinal),\n price: formatCurrencyNumber(monthlyFinal),\n pricingByPeriod,\n items: apiPlan.items,\n };\n}\n"],"mappings":"AACA,SAAS,iBAAiB;AAC1B,SAAS,4BAA4B;AAIrC,MAAM,gBAA+B;AAAA,EACnC,EAAE,MAAM,SAAS,MAAM,qBAAqB;AAAA,EAC5C,EAAE,MAAM,SAAS,MAAM,mBAAmB;AAAA,EAC1C,EAAE,MAAM,SAAS,MAAM,2BAAwB,SAAS,aAAa;AAAA,EACrE,EAAE,MAAM,SAAS,MAAM,qBAAqB;AAAA,EAC5C,EAAE,MAAM,SAAS,MAAM,oBAAoB;AAAA,EAC3C,EAAE,MAAM,SAAS,MAAM,iBAAiB;AAC1C;AAEA,SAAS,2BAA2B,OAAwC;AAC1E,QAAM,YAAY,MAAM,KAAK,CAAC,MAAM,EAAE,aAAa,UAAU,KAAK;AAClE,QAAM,cAAc,MAAM,KAAK,CAAC,MAAM,EAAE,aAAa,UAAU,OAAO;AAEtE,QAAM,WAAW,WAAW,YAAY;AACxC,QAAM,aAAa,aAAa,YAAY;AAE5C,SAAO;AAAA,IACL,OACE,aAAa,IACT,0BACA,aAAa,IACX,gBACA,GAAG,QAAQ;AAAA,IACnB,SACE,cAAc,MACV,2BACA,eAAe,IACb,yBACA,GAAG,UAAU;AAAA,IACrB,OAAO;AAAA,EACT;AACF;AAEA,SAAS,uBAAuB,SAAoC;AAClE,QAAM,UAAU,QAAQ;AACxB,QAAM,SAAS,CAAC,MAAc,KAAK,MAAM,IAAI,GAAG,IAAI;AACpD,SAAO;AAAA,IACL;AAAA,IACA,YAAY,OAAO,WAAW,IAAI,QAAQ,oBAAoB,IAAI;AAAA,IAClE,QAAQ,OAAO,WAAW,IAAI,QAAQ,kBAAkB,IAAI;AAAA,EAC9D;AACF;AAEO,SAAS,mBACd,SACA,SAKQ;AACR,QAAM,EAAE,QAAQ,WAAW,cAAc,IAAI;AAE7C,QAAM,kBAAkB,uBAAuB,OAAO;AACtD,QAAM,eAAe,gBAAgB;AAErC,QAAM,YAAY,QAAQ,KAAK,YAAY;AAC3C,QAAM,0BACJ,UAAU,SAAS,YAAS,KAAK,UAAU,SAAS,SAAS;AAE/D,QAAM,WAA0B;AAAA,IAC9B,GAAG;AAAA,IACH,0BACI,EAAE,MAAM,SAAS,MAAM,2BAAwB,IAC/C,EAAE,MAAM,KAAK,MAAM,4BAAyB,UAAU,KAAK;AAAA,IAC/D,0BACI,EAAE,MAAM,SAAS,MAAM,yBAAsB,SAAS,oBAAoB,IAC1E,EAAE,MAAM,KAAK,MAAM,yBAAsB,UAAU,MAAM,SAAS,oBAAoB;AAAA,EAC5F;AAEA,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,MAAM,QAAQ;AAAA,IACd;AAAA,IACA;AAAA,IACA,cAAc,2BAA2B,QAAQ,KAAK;AAAA,IACtD;AAAA,IACA,eAAe,qBAAqB,YAAY;AAAA,IAChD,OAAO,qBAAqB,YAAY;AAAA,IACxC;AAAA,IACA,OAAO,QAAQ;AAAA,EACjB;AACF;","names":[]}
|
package/dist/server.mjs
CHANGED
|
@@ -17,6 +17,7 @@ import { findUserById } from "./modules/users/action/find-user-by-id.action";
|
|
|
17
17
|
import { findCurrentAccount } from "./modules/accounts/actions/find-current-account.action";
|
|
18
18
|
import { listSubscriptionsAction } from "./modules/subscriptions/actions/list-subscriptions.action";
|
|
19
19
|
import { findPlanByIdAction } from "./modules/plans/actions/find-plan-by-id.action";
|
|
20
|
+
import { listPlansAction } from "./modules/plans/actions/list-plans.action";
|
|
20
21
|
import { calculateSubscriptionAction } from "./modules/subscriptions/actions/calculate-subscription.action";
|
|
21
22
|
import { updateSubscriptionPlanAction } from "./modules/subscriptions/actions/update-subscription-plan.action";
|
|
22
23
|
import { listCardsAction } from "./modules/cards/actions/list-cards.action";
|
|
@@ -89,6 +90,7 @@ export {
|
|
|
89
90
|
listAvailableUsersAction,
|
|
90
91
|
listCardsAction,
|
|
91
92
|
listIaCreditsAction,
|
|
93
|
+
listPlansAction,
|
|
92
94
|
listProjectUsersAction,
|
|
93
95
|
listSubscriptionsAction,
|
|
94
96
|
plansService,
|
package/dist/server.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts"],"sourcesContent":["import 'server-only';\r\n\r\n// API Client\r\nexport { ApiClient, api, apiClient } from './infra/api/client';\r\n\r\n// Services\r\nexport { authService } from './modules/auth/services/auth.service';\r\nexport { whitelabelService } from './modules/whitelabel/services/whitelabel.service';\r\nexport { revalidateWhitelabelAction } from './modules/whitelabel/actions/revalidate-whitelabel.action';\r\nexport { assertManagementPermission } from './modules/auth/utils/assert-management-permission';\r\nexport { assertManagementSubscription } from './modules/auth/utils/assert-management-subscription';\r\nexport { assertSubscriptionAddon } from './modules/auth/utils/assert-subscription-addon';\r\nexport { assertPaidSubscription } from './modules/auth/utils/assert-paid-subscription';\r\nexport { subscriptionsService } from './modules/subscriptions/services/subscriptions.service';\r\nexport { plansService } from './modules/plans/services/plans.service';\r\nexport { cardsService } from './modules/cards/services/cards.service';\r\nexport { iaCreditsService } from './modules/ia-credits/services/ia-credits.service';\r\n\r\n// Actions\r\nexport { findWhitelabel } from './modules/whitelabel/actions/find-whitelabel.action';\r\nexport { validateSessionAction } from './modules/auth/actions/validate-session.action';\r\nexport { findUserById } from './modules/users/action/find-user-by-id.action';\r\nexport { findCurrentAccount } from './modules/accounts/actions/find-current-account.action';\r\nexport { listSubscriptionsAction } from './modules/subscriptions/actions/list-subscriptions.action';\r\nexport { findPlanByIdAction } from './modules/plans/actions/find-plan-by-id.action';\r\nexport { calculateSubscriptionAction } from './modules/subscriptions/actions/calculate-subscription.action';\r\nexport { updateSubscriptionPlanAction } from './modules/subscriptions/actions/update-subscription-plan.action';\r\nexport { listCardsAction } from './modules/cards/actions/list-cards.action';\r\nexport { createCardAction } from './modules/cards/actions/create-card.action';\r\nexport { resolvePlanExtrasPrices } from './modules/subscriptions/utils/resolve-plan-extras-prices';\r\nexport { hasSubscriptionExpired } from './modules/subscriptions/utils/has-subscription-expired';\r\nexport { listIaCreditsAction } from './modules/ia-credits/actions/list-ia-credits.action';\r\n\r\n// Project Users Services & Actions (server-only)\r\nexport { projectUsersService } from './modules/projects/services/project-users.service';\r\nexport { listProjectUsersAction } from './modules/projects/actions/list-project-users.action';\r\nexport { listAvailableUsersAction } from './modules/projects/actions/list-available-users.action';\r\nexport { addProjectUserAction } from './modules/projects/actions/add-project-user.action';\r\nexport { removeProjectUserAction } from './modules/projects/actions/remove-project-user.action';\r\n\r\n// Account Services & Actions (server-only)\r\nexport { accountService } from './modules/accounts/services/account.service';\r\nexport { twoFactorService } from './modules/accounts/services/two-factor.service';\r\nexport {\r\n listAccountUsersAction,\r\n updateUserAction,\r\n updateAccountAction,\r\n updateAccountUserByIdAction,\r\n deleteAccountUserAction,\r\n deleteAccountAction,\r\n createAccountUserAction,\r\n changePasswordAction,\r\n requestEmailChangeAction,\r\n confirmEmailChangeAction,\r\n requestPhoneChangeAction,\r\n confirmPhoneChangeAction,\r\n generateTwoFactorAction,\r\n confirmTwoFactorAction,\r\n disableTwoFactorAction,\r\n} from './modules/accounts/actions/account-management.action';\r\n\r\n// Server Utils\r\nexport { getClientInfoFromRequest } from './infra/utils/client-info';\r\nexport { getUserContext } from './modules/auth/utils/get-user-context';\r\nexport { buildWlOverride, buildWlOverrideFromJwt } from './modules/auth/utils/build-wl-override';\r\nexport { safeServerAction } from './utils/safeServerAction';\r\n"],"mappings":"AAAA,OAAO;AAGP,SAAS,WAAW,KAAK,iBAAiB;AAG1C,SAAS,mBAAmB;AAC5B,SAAS,yBAAyB;AAClC,SAAS,kCAAkC;AAC3C,SAAS,kCAAkC;AAC3C,SAAS,oCAAoC;AAC7C,SAAS,+BAA+B;AACxC,SAAS,8BAA8B;AACvC,SAAS,4BAA4B;AACrC,SAAS,oBAAoB;AAC7B,SAAS,oBAAoB;AAC7B,SAAS,wBAAwB;AAGjC,SAAS,sBAAsB;AAC/B,SAAS,6BAA6B;AACtC,SAAS,oBAAoB;AAC7B,SAAS,0BAA0B;AACnC,SAAS,+BAA+B;AACxC,SAAS,0BAA0B;AACnC,SAAS,mCAAmC;AAC5C,SAAS,oCAAoC;AAC7C,SAAS,uBAAuB;AAChC,SAAS,wBAAwB;AACjC,SAAS,+BAA+B;AACxC,SAAS,8BAA8B;AACvC,SAAS,2BAA2B;AAGpC,SAAS,2BAA2B;AACpC,SAAS,8BAA8B;AACvC,SAAS,gCAAgC;AACzC,SAAS,4BAA4B;AACrC,SAAS,+BAA+B;AAGxC,SAAS,sBAAsB;AAC/B,SAAS,wBAAwB;AACjC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP,SAAS,gCAAgC;AACzC,SAAS,sBAAsB;AAC/B,SAAS,iBAAiB,8BAA8B;AACxD,SAAS,wBAAwB;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/server.ts"],"sourcesContent":["import 'server-only';\r\n\r\n// API Client\r\nexport { ApiClient, api, apiClient } from './infra/api/client';\r\n\r\n// Services\r\nexport { authService } from './modules/auth/services/auth.service';\r\nexport { whitelabelService } from './modules/whitelabel/services/whitelabel.service';\r\nexport { revalidateWhitelabelAction } from './modules/whitelabel/actions/revalidate-whitelabel.action';\r\nexport { assertManagementPermission } from './modules/auth/utils/assert-management-permission';\r\nexport { assertManagementSubscription } from './modules/auth/utils/assert-management-subscription';\r\nexport { assertSubscriptionAddon } from './modules/auth/utils/assert-subscription-addon';\r\nexport { assertPaidSubscription } from './modules/auth/utils/assert-paid-subscription';\r\nexport { subscriptionsService } from './modules/subscriptions/services/subscriptions.service';\r\nexport { plansService } from './modules/plans/services/plans.service';\r\nexport { cardsService } from './modules/cards/services/cards.service';\r\nexport { iaCreditsService } from './modules/ia-credits/services/ia-credits.service';\r\n\r\n// Actions\r\nexport { findWhitelabel } from './modules/whitelabel/actions/find-whitelabel.action';\r\nexport { validateSessionAction } from './modules/auth/actions/validate-session.action';\r\nexport { findUserById } from './modules/users/action/find-user-by-id.action';\r\nexport { findCurrentAccount } from './modules/accounts/actions/find-current-account.action';\r\nexport { listSubscriptionsAction } from './modules/subscriptions/actions/list-subscriptions.action';\r\nexport { findPlanByIdAction } from './modules/plans/actions/find-plan-by-id.action';\r\nexport { listPlansAction } from './modules/plans/actions/list-plans.action';\r\nexport { calculateSubscriptionAction } from './modules/subscriptions/actions/calculate-subscription.action';\r\nexport { updateSubscriptionPlanAction } from './modules/subscriptions/actions/update-subscription-plan.action';\r\nexport { listCardsAction } from './modules/cards/actions/list-cards.action';\r\nexport { createCardAction } from './modules/cards/actions/create-card.action';\r\nexport { resolvePlanExtrasPrices } from './modules/subscriptions/utils/resolve-plan-extras-prices';\r\nexport { hasSubscriptionExpired } from './modules/subscriptions/utils/has-subscription-expired';\r\nexport { listIaCreditsAction } from './modules/ia-credits/actions/list-ia-credits.action';\r\n\r\n// Project Users Services & Actions (server-only)\r\nexport { projectUsersService } from './modules/projects/services/project-users.service';\r\nexport { listProjectUsersAction } from './modules/projects/actions/list-project-users.action';\r\nexport { listAvailableUsersAction } from './modules/projects/actions/list-available-users.action';\r\nexport { addProjectUserAction } from './modules/projects/actions/add-project-user.action';\r\nexport { removeProjectUserAction } from './modules/projects/actions/remove-project-user.action';\r\n\r\n// Account Services & Actions (server-only)\r\nexport { accountService } from './modules/accounts/services/account.service';\r\nexport { twoFactorService } from './modules/accounts/services/two-factor.service';\r\nexport {\r\n listAccountUsersAction,\r\n updateUserAction,\r\n updateAccountAction,\r\n updateAccountUserByIdAction,\r\n deleteAccountUserAction,\r\n deleteAccountAction,\r\n createAccountUserAction,\r\n changePasswordAction,\r\n requestEmailChangeAction,\r\n confirmEmailChangeAction,\r\n requestPhoneChangeAction,\r\n confirmPhoneChangeAction,\r\n generateTwoFactorAction,\r\n confirmTwoFactorAction,\r\n disableTwoFactorAction,\r\n} from './modules/accounts/actions/account-management.action';\r\n\r\n// Server Utils\r\nexport { getClientInfoFromRequest } from './infra/utils/client-info';\r\nexport { getUserContext } from './modules/auth/utils/get-user-context';\r\nexport { buildWlOverride, buildWlOverrideFromJwt } from './modules/auth/utils/build-wl-override';\r\nexport { safeServerAction } from './utils/safeServerAction';\r\n"],"mappings":"AAAA,OAAO;AAGP,SAAS,WAAW,KAAK,iBAAiB;AAG1C,SAAS,mBAAmB;AAC5B,SAAS,yBAAyB;AAClC,SAAS,kCAAkC;AAC3C,SAAS,kCAAkC;AAC3C,SAAS,oCAAoC;AAC7C,SAAS,+BAA+B;AACxC,SAAS,8BAA8B;AACvC,SAAS,4BAA4B;AACrC,SAAS,oBAAoB;AAC7B,SAAS,oBAAoB;AAC7B,SAAS,wBAAwB;AAGjC,SAAS,sBAAsB;AAC/B,SAAS,6BAA6B;AACtC,SAAS,oBAAoB;AAC7B,SAAS,0BAA0B;AACnC,SAAS,+BAA+B;AACxC,SAAS,0BAA0B;AACnC,SAAS,uBAAuB;AAChC,SAAS,mCAAmC;AAC5C,SAAS,oCAAoC;AAC7C,SAAS,uBAAuB;AAChC,SAAS,wBAAwB;AACjC,SAAS,+BAA+B;AACxC,SAAS,8BAA8B;AACvC,SAAS,2BAA2B;AAGpC,SAAS,2BAA2B;AACpC,SAAS,8BAA8B;AACvC,SAAS,gCAAgC;AACzC,SAAS,4BAA4B;AACrC,SAAS,+BAA+B;AAGxC,SAAS,sBAAsB;AAC/B,SAAS,wBAAwB;AACjC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP,SAAS,gCAAgC;AACzC,SAAS,sBAAsB;AAC/B,SAAS,iBAAiB,8BAA8B;AACxD,SAAS,wBAAwB;","names":[]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const formatCurrencyNumber = (value, decimals = 2) => {
|
|
2
|
+
const safeValue = value ?? 0;
|
|
3
|
+
return safeValue.toLocaleString("pt-BR", {
|
|
4
|
+
style: "currency",
|
|
5
|
+
currency: "BRL",
|
|
6
|
+
minimumFractionDigits: decimals,
|
|
7
|
+
maximumFractionDigits: decimals
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
export {
|
|
11
|
+
formatCurrencyNumber
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=currency.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/utils/format/currency.ts"],"sourcesContent":["/**\n * Formata um número (opcional) para moeda BRL\n * @param value - Número a ser formatado (pode ser undefined ou null)\n * @param decimals - Casas decimais (padrão: 2)\n * @returns String formatada como \"R$ X.XXX,XX\"\n */\nexport const formatCurrencyNumber = (value?: number | null, decimals = 2): string => {\n const safeValue = value ?? 0;\n return safeValue.toLocaleString('pt-BR', {\n style: 'currency',\n currency: 'BRL',\n minimumFractionDigits: decimals,\n maximumFractionDigits: decimals,\n });\n};\n"],"mappings":"AAMO,MAAM,uBAAuB,CAAC,OAAuB,WAAW,MAAc;AACnF,QAAM,YAAY,SAAS;AAC3B,SAAO,UAAU,eAAe,SAAS;AAAA,IACvC,OAAO;AAAA,IACP,UAAU;AAAA,IACV,uBAAuB;AAAA,IACvB,uBAAuB;AAAA,EACzB,CAAC;AACH;","names":[]}
|
package/package.json
CHANGED
|
@@ -7,29 +7,80 @@ import { useActiveSubscription } from '../../modules/subscriptions/hooks/find-ac
|
|
|
7
7
|
|
|
8
8
|
const V4_WHITELABEL_ID = 367568;
|
|
9
9
|
|
|
10
|
+
const PROFILE_LABELS: Record<string, string> = {
|
|
11
|
+
owner: 'Proprietário',
|
|
12
|
+
admin: 'Administrador',
|
|
13
|
+
editor: 'Colaborador',
|
|
14
|
+
viewer: 'Visualizador',
|
|
15
|
+
};
|
|
16
|
+
|
|
10
17
|
const CRISP_WEBSITE_ID = '3339a3ec-b4c6-4aac-8b86-45668fb7655c';
|
|
11
18
|
const CRISP_SCRIPT_URL = 'https://client.crisp.chat/l.js';
|
|
12
19
|
const CRISP_LOAD_DELAY = 800;
|
|
13
20
|
|
|
14
|
-
|
|
21
|
+
// ── Crisp helpers ──────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
function safeCrispPush(args: unknown[]) {
|
|
15
24
|
try {
|
|
16
25
|
const $crisp = (window as any)?.$crisp;
|
|
17
26
|
if ($crisp) $crisp.push(args);
|
|
18
27
|
} catch {}
|
|
19
28
|
}
|
|
20
29
|
|
|
30
|
+
function crispDo(action: string) {
|
|
31
|
+
safeCrispPush(['do', action]);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function crispSegment(segment: string) {
|
|
35
|
+
safeCrispPush(['set', 'session:segments', [[segment]]]);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function crispSessionData(key: string, value: string) {
|
|
39
|
+
safeCrispPush(['set', 'session:data', [[[key, value]]]]);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ── Public API ─────────────────────────────────────────────────
|
|
43
|
+
|
|
21
44
|
export function openCrispHelpdesk() {
|
|
22
|
-
|
|
45
|
+
crispDo('helpdesk:search');
|
|
23
46
|
}
|
|
24
47
|
|
|
25
48
|
export function hideCrisp() {
|
|
26
|
-
|
|
49
|
+
crispDo('chat:hide');
|
|
27
50
|
}
|
|
28
51
|
|
|
29
52
|
export function showCrisp() {
|
|
30
|
-
|
|
53
|
+
crispDo('chat:show');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function updateCrispUser(data: {
|
|
57
|
+
email?: string;
|
|
58
|
+
name?: string;
|
|
59
|
+
lastName?: string;
|
|
60
|
+
company?: string;
|
|
61
|
+
ddi?: string;
|
|
62
|
+
phone?: string;
|
|
63
|
+
photo?: string;
|
|
64
|
+
}) {
|
|
65
|
+
try {
|
|
66
|
+
if (data.email) safeCrispPush(['set', 'user:email', data.email]);
|
|
67
|
+
|
|
68
|
+
const fullName = `${data.name ?? ''} ${data.lastName ?? ''}`.trim();
|
|
69
|
+
if (fullName) safeCrispPush(['set', 'user:nickname', fullName]);
|
|
70
|
+
|
|
71
|
+
if (data.company) safeCrispPush(['set', 'user:company', data.company]);
|
|
72
|
+
|
|
73
|
+
const phone = `${data.ddi ?? ''}${data.phone ?? ''}`.trim();
|
|
74
|
+
if (phone) safeCrispPush(['set', 'user:phone', phone]);
|
|
75
|
+
|
|
76
|
+
if (data.photo?.startsWith('http')) {
|
|
77
|
+
safeCrispPush(['set', 'user:avatar', data.photo]);
|
|
78
|
+
}
|
|
79
|
+
} catch {}
|
|
31
80
|
}
|
|
32
81
|
|
|
82
|
+
// ── Component ──────────────────────────────────────────────────
|
|
83
|
+
|
|
33
84
|
export function CrispEmbed() {
|
|
34
85
|
const { user, account } = useAuth();
|
|
35
86
|
const { whitelabel } = useWhitelabel();
|
|
@@ -46,15 +97,12 @@ export function CrispEmbed() {
|
|
|
46
97
|
try {
|
|
47
98
|
const win = window as any;
|
|
48
99
|
|
|
49
|
-
// Only initialize if Crisp hasn't been loaded yet — avoids
|
|
50
|
-
// wiping an already-active Crisp instance on component remount
|
|
51
100
|
if (!win.$crisp) {
|
|
52
101
|
win.$crisp = [];
|
|
53
102
|
win.CRISP_WEBSITE_ID = CRISP_WEBSITE_ID;
|
|
54
103
|
win.$crisp.push(['config', 'container:index', [100]]);
|
|
55
104
|
}
|
|
56
105
|
|
|
57
|
-
// Script already injected (previous mount) — just mark as loaded
|
|
58
106
|
if (document.querySelector(`script[src="${CRISP_SCRIPT_URL}"]`)) {
|
|
59
107
|
scriptLoadedRef.current = true;
|
|
60
108
|
return;
|
|
@@ -78,32 +126,28 @@ export function CrispEmbed() {
|
|
|
78
126
|
// Identify user
|
|
79
127
|
useEffect(() => {
|
|
80
128
|
try {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (phone) $crisp.push(['set', 'user:phone', phone]);
|
|
93
|
-
|
|
94
|
-
if (user.photo && user.photo.startsWith('http')) {
|
|
95
|
-
$crisp.push(['set', 'user:avatar', user.photo]);
|
|
96
|
-
}
|
|
129
|
+
if (!user || !(window as any)?.$crisp) return;
|
|
130
|
+
|
|
131
|
+
updateCrispUser({
|
|
132
|
+
email: user.email,
|
|
133
|
+
name: user.name,
|
|
134
|
+
lastName: user.last_name,
|
|
135
|
+
company: account?.name,
|
|
136
|
+
ddi: user.ddi,
|
|
137
|
+
phone: user.phone,
|
|
138
|
+
photo: user.photo,
|
|
139
|
+
});
|
|
97
140
|
|
|
98
141
|
if (isV4) {
|
|
99
|
-
|
|
100
|
-
|
|
142
|
+
crispSegment('company_v4');
|
|
143
|
+
crispSessionData('Company', 'V4');
|
|
101
144
|
} else {
|
|
102
145
|
const planName = subscription?.plan_name ?? '';
|
|
103
|
-
|
|
104
|
-
if (
|
|
105
|
-
if (
|
|
106
|
-
if (
|
|
146
|
+
const profileLabel = PROFILE_LABELS[user.profile ?? ''] ?? user.profile ?? '';
|
|
147
|
+
if (planName) crispSegment(`plano_${planName}`);
|
|
148
|
+
if (profileLabel) crispSegment(`usuario_${profileLabel}`);
|
|
149
|
+
if (planName) crispSessionData('Plano', planName);
|
|
150
|
+
if (profileLabel) crispSessionData('Usuario', profileLabel);
|
|
107
151
|
}
|
|
108
152
|
} catch {}
|
|
109
153
|
}, [user, account, isV4, subscription?.plan_name]);
|
|
@@ -7,6 +7,13 @@ import { useActiveSubscription } from '../../modules/subscriptions/hooks/find-ac
|
|
|
7
7
|
|
|
8
8
|
const V4_WHITELABEL_ID = 367568;
|
|
9
9
|
|
|
10
|
+
const PROFILE_LABELS: Record<string, string> = {
|
|
11
|
+
owner: 'Proprietário',
|
|
12
|
+
admin: 'Administrador',
|
|
13
|
+
editor: 'Colaborador',
|
|
14
|
+
viewer: 'Visualizador',
|
|
15
|
+
};
|
|
16
|
+
|
|
10
17
|
const FRILL_SCRIPT_URL = 'https://widget.frill.co/v2/container.js';
|
|
11
18
|
const FRILL_KEY = '2f77f232-a120-438e-8ef6-01912ffd1b99';
|
|
12
19
|
|
|
@@ -91,7 +98,7 @@ export function FrillEmbed() {
|
|
|
91
98
|
}
|
|
92
99
|
userData.attributes = isV4
|
|
93
100
|
? { company: 'V4' }
|
|
94
|
-
: { profile: user.profile ?? '', plan: subscription?.plan_name ?? '' };
|
|
101
|
+
: { profile: PROFILE_LABELS[user.profile ?? ''] ?? user.profile ?? '', plan: subscription?.plan_name ?? '' };
|
|
95
102
|
|
|
96
103
|
const config: Record<string, any> = { key: FRILL_KEY, user: userData };
|
|
97
104
|
if (!isV4) {
|
|
@@ -9,7 +9,7 @@ import { useCreateCard } from '../../../modules/cards/hooks/create-card.hook';
|
|
|
9
9
|
import { useUpdateAccount } from '../../../modules/accounts/hooks/useAccountManagement';
|
|
10
10
|
import { CardFormFields, BillingFormFields, cardFormSchema } from './CardFormFields';
|
|
11
11
|
import type { CardFormData, StripeElementsStatus } from './CardFormFields';
|
|
12
|
-
import {
|
|
12
|
+
import { IconX } from '@tabler/icons-react';
|
|
13
13
|
import { useForm } from 'react-hook-form';
|
|
14
14
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
15
15
|
import { CardNumberElement, useElements, useStripe } from '@stripe/react-stripe-js';
|
|
@@ -248,7 +248,6 @@ export default function AddCardModal() {
|
|
|
248
248
|
Voltar
|
|
249
249
|
</Button>
|
|
250
250
|
<Button type="submit" className="h-10!" disabled={isSubmitting || !isStep2Valid}>
|
|
251
|
-
{isSubmitting && <IconLoader2 className="size-4 animate-spin" />}
|
|
252
251
|
{isSubmitting ? 'Adicionando cartão...' : 'Adicionar cartão'}
|
|
253
252
|
</Button>
|
|
254
253
|
</>
|
package/src/index.ts
CHANGED
|
@@ -64,8 +64,11 @@ export {
|
|
|
64
64
|
} from "./modules/subscriptions/types/subscription.type";
|
|
65
65
|
export { ADDON_IDS, AI_CREDIT_OPTIONS, AI_CREDIT_VALUES } from "./modules/subscriptions/constants/addons.constants";
|
|
66
66
|
export type { AddonKindEnum, AiCreditOption } from "./modules/subscriptions/constants/addons.constants";
|
|
67
|
-
export type { Plan, PlanItem } from "./modules/plans/types/plan.type";
|
|
67
|
+
export type { Plan, PlanItem, UiPlan, PlanFeature, PlanMainFeatures, PlanPricingByPeriod, PlanTooltips } from "./modules/plans/types/plan.type";
|
|
68
68
|
export { PlanSchema, PlanItemSchema } from "./modules/plans/types/plan.type";
|
|
69
|
+
export { mapApiPlanToUiPlan } from "./modules/plans/utils/map-api-plan-to-ui";
|
|
70
|
+
export type { PlanBillingPeriod } from "./modules/plans/utils/map-api-plan-to-ui";
|
|
71
|
+
export { usePlans, PLANS_QUERY_KEY } from "./modules/plans/hooks/list-plans.hook";
|
|
69
72
|
export type { BillingPeriod } from "./modules/subscriptions/types/billing-period.type";
|
|
70
73
|
export type {
|
|
71
74
|
CalculateSubscriptionRequest,
|
|
@@ -292,7 +295,7 @@ export {
|
|
|
292
295
|
|
|
293
296
|
// Embeds
|
|
294
297
|
export { FrillEmbed } from "./components/embeds/FrillEmbed";
|
|
295
|
-
export { CrispEmbed, openCrispHelpdesk, hideCrisp, showCrisp } from "./components/embeds/CrispEmbed";
|
|
298
|
+
export { CrispEmbed, openCrispHelpdesk, hideCrisp, showCrisp, updateCrispUser } from "./components/embeds/CrispEmbed";
|
|
296
299
|
export { EmbedWidgets } from "./components/embeds/EmbedWidgets";
|
|
297
300
|
|
|
298
301
|
// Store
|
|
@@ -321,6 +324,7 @@ export {
|
|
|
321
324
|
formatCNPJ,
|
|
322
325
|
formatCardNumber,
|
|
323
326
|
} from "./utils/format/masks";
|
|
327
|
+
export { formatCurrencyNumber } from "./utils/format/currency";
|
|
324
328
|
export { BR_STATE_OPTIONS } from "./utils/constants/br-states";
|
|
325
329
|
export type { TimezoneOption } from "./modules/accounts/services/timezone.service";
|
|
326
330
|
export {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use server';
|
|
2
|
+
|
|
3
|
+
import { plansService, safeServerAction } from '@greatapps/common/server';
|
|
4
|
+
import type { UiPlan } from '../types/plan.type';
|
|
5
|
+
import { mapApiPlanToUiPlan } from '../utils/map-api-plan-to-ui';
|
|
6
|
+
|
|
7
|
+
export async function listPlansAction() {
|
|
8
|
+
return safeServerAction(async () => {
|
|
9
|
+
const { data: apiPlans } = await plansService.listPlans({
|
|
10
|
+
active: true,
|
|
11
|
+
search: 'client',
|
|
12
|
+
sort: 'id:ASC',
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
if (!apiPlans?.length) return [] as UiPlan[];
|
|
16
|
+
|
|
17
|
+
const sorted = [...apiPlans].sort((a, b) => a.value - b.value);
|
|
18
|
+
|
|
19
|
+
return sorted.map((plan) => {
|
|
20
|
+
const popular = plan.id === 2 || plan.id_plan === 2;
|
|
21
|
+
return mapApiPlanToUiPlan(plan, {
|
|
22
|
+
planId: plan.id_plan ?? plan.id,
|
|
23
|
+
isPopular: popular,
|
|
24
|
+
buttonVariant: popular ? 'brand' : undefined,
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useQuery } from '@tanstack/react-query';
|
|
4
|
+
import { withAction } from '@greatapps/common';
|
|
5
|
+
import { listPlansAction } from '../actions/list-plans.action';
|
|
6
|
+
import type { UiPlan } from '../types/plan.type';
|
|
7
|
+
|
|
8
|
+
export const PLANS_QUERY_KEY = ['plans'];
|
|
9
|
+
|
|
10
|
+
export function usePlans() {
|
|
11
|
+
return useQuery<UiPlan[]>({
|
|
12
|
+
queryKey: [...PLANS_QUERY_KEY],
|
|
13
|
+
queryFn: withAction(listPlansAction),
|
|
14
|
+
staleTime: Infinity,
|
|
15
|
+
gcTime: Infinity,
|
|
16
|
+
refetchOnWindowFocus: false,
|
|
17
|
+
refetchOnReconnect: false,
|
|
18
|
+
refetchOnMount: false,
|
|
19
|
+
retry: 1,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -1,13 +1,34 @@
|
|
|
1
|
-
|
|
1
|
+
"use client";
|
|
2
2
|
|
|
3
|
-
import { useQuery } from
|
|
4
|
-
import { withAction } from
|
|
5
|
-
import { findPlanByIdAction } from
|
|
3
|
+
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
4
|
+
import { withAction } from "../../../utils/withAction";
|
|
5
|
+
import { findPlanByIdAction } from "../actions/find-plan-by-id.action";
|
|
6
|
+
import { Plan } from "../types/plan.type";
|
|
7
|
+
import { PLANS_QUERY_KEY } from "./list-plans.hook";
|
|
8
|
+
import { PaginatedSuccessResult } from "../../../infra/api/types";
|
|
6
9
|
|
|
7
10
|
export function usePlanById(idPlan: number | string | undefined) {
|
|
11
|
+
const queryClient = useQueryClient();
|
|
12
|
+
|
|
8
13
|
return useQuery({
|
|
9
|
-
queryKey: [
|
|
14
|
+
queryKey: [...PLANS_QUERY_KEY, idPlan],
|
|
10
15
|
queryFn: withAction(() => findPlanByIdAction(idPlan!)),
|
|
16
|
+
initialData: () => {
|
|
17
|
+
const queries = queryClient.getQueriesData<PaginatedSuccessResult<Plan>>({
|
|
18
|
+
queryKey: [...PLANS_QUERY_KEY],
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
for (const [, data] of queries) {
|
|
22
|
+
if (!data) continue;
|
|
23
|
+
const plan = (Array.isArray(data) ? data : data.data)?.find((p) => p.idPlan === idPlan);
|
|
24
|
+
if (plan) {
|
|
25
|
+
return plan;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return undefined;
|
|
30
|
+
},
|
|
31
|
+
initialDataUpdatedAt: 0,
|
|
11
32
|
enabled: !!idPlan,
|
|
12
33
|
});
|
|
13
34
|
}
|