@greatapps/common 1.1.786 → 1.1.788
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/layouts/AppMobileNavBar.mjs +64 -5
- package/dist/components/layouts/AppMobileNavBar.mjs.map +1 -1
- package/dist/components/layouts/AppNavBar.mjs +2 -0
- package/dist/components/layouts/AppNavBar.mjs.map +1 -1
- package/dist/components/layouts/GreatDomainIcon.mjs +40 -0
- package/dist/components/layouts/GreatDomainIcon.mjs.map +1 -0
- package/dist/components/layouts/NavBar.mjs +111 -52
- package/dist/components/layouts/NavBar.mjs.map +1 -1
- package/dist/components/ui/form/PhoneInput.mjs +1 -1
- package/dist/components/ui/form/PhoneInput.mjs.map +1 -1
- package/dist/components/widgets/ImageUpload/ImageCropModal.mjs +6 -6
- package/dist/components/widgets/ImageUpload/ImageCropModal.mjs.map +1 -1
- package/dist/handlers.mjs +2 -0
- package/dist/handlers.mjs.map +1 -1
- package/dist/i18n/messages/en-us.mjs +3 -1
- package/dist/i18n/messages/en-us.mjs.map +1 -1
- package/dist/i18n/messages/es-es.mjs +3 -1
- package/dist/i18n/messages/es-es.mjs.map +1 -1
- package/dist/i18n/messages/pt-br.mjs +3 -1
- package/dist/i18n/messages/pt-br.mjs.map +1 -1
- package/dist/index.mjs +9 -1
- package/dist/index.mjs.map +1 -1
- package/dist/infra/api/client.mjs +5 -1
- package/dist/infra/api/client.mjs.map +1 -1
- package/dist/infra/api/types.mjs +1 -1
- package/dist/infra/api/types.mjs.map +1 -1
- package/dist/infra/route/safe-route-handler.mjs +4 -1
- package/dist/infra/route/safe-route-handler.mjs.map +1 -1
- package/dist/modules/auth/utils/assert-account-not-frozen.mjs +6 -5
- package/dist/modules/auth/utils/assert-account-not-frozen.mjs.map +1 -1
- package/dist/modules/auth/utils/assert-management-subscription.mjs +7 -14
- package/dist/modules/auth/utils/assert-management-subscription.mjs.map +1 -1
- package/dist/modules/auth/utils/assert-paid-subscription.mjs +7 -12
- package/dist/modules/auth/utils/assert-paid-subscription.mjs.map +1 -1
- package/dist/modules/auth/utils/assert-subscription-addon.mjs +2 -10
- package/dist/modules/auth/utils/assert-subscription-addon.mjs.map +1 -1
- package/dist/modules/plans/hooks/use-plan-by-id.hook.mjs.map +1 -1
- package/dist/modules/subscriptions/actions/get-current-subscription.action.mjs +10 -0
- package/dist/modules/subscriptions/actions/get-current-subscription.action.mjs.map +1 -0
- package/dist/modules/subscriptions/constants/subscription.constants.mjs +2 -0
- package/dist/modules/subscriptions/constants/subscription.constants.mjs.map +1 -1
- package/dist/modules/subscriptions/handlers/current-subscription.handler.mjs +17 -0
- package/dist/modules/subscriptions/handlers/current-subscription.handler.mjs.map +1 -0
- package/dist/modules/subscriptions/hooks/find-active-subscription.hook.mjs +2 -13
- package/dist/modules/subscriptions/hooks/find-active-subscription.hook.mjs.map +1 -1
- package/dist/modules/subscriptions/hooks/use-current-subscription.hook.mjs +28 -0
- package/dist/modules/subscriptions/hooks/use-current-subscription.hook.mjs.map +1 -0
- package/dist/modules/subscriptions/services/subscriptions.service.mjs +26 -0
- package/dist/modules/subscriptions/services/subscriptions.service.mjs.map +1 -1
- package/dist/modules/subscriptions/types/subscription.type.mjs.map +1 -1
- package/dist/modules/subscriptions/utils/group-subscriptions-by-product.mjs +16 -0
- package/dist/modules/subscriptions/utils/group-subscriptions-by-product.mjs.map +1 -0
- package/dist/modules/subscriptions/utils/has-active-paid-subscription.mjs +1 -1
- package/dist/modules/subscriptions/utils/has-active-paid-subscription.mjs.map +1 -1
- package/dist/modules/subscriptions/utils/has-paid-subscription.mjs +1 -1
- package/dist/modules/subscriptions/utils/has-paid-subscription.mjs.map +1 -1
- package/dist/modules/subscriptions/utils/read-current-subscription.mjs +21 -0
- package/dist/modules/subscriptions/utils/read-current-subscription.mjs.map +1 -0
- package/dist/server.mjs +4 -0
- package/dist/server.mjs.map +1 -1
- package/dist/store/useMdSidebarStore.mjs +17 -4
- package/dist/store/useMdSidebarStore.mjs.map +1 -1
- package/dist/utils/safeServerAction.mjs +4 -1
- package/dist/utils/safeServerAction.mjs.map +1 -1
- package/dist/utils/withAction.mjs +2 -1
- package/dist/utils/withAction.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/layouts/AppMobileNavBar.tsx +90 -6
- package/src/components/layouts/AppNavBar.tsx +5 -1
- package/src/components/layouts/GreatDomainIcon.tsx +43 -0
- package/src/components/layouts/NavBar.tsx +160 -66
- package/src/components/ui/form/PhoneInput.tsx +1 -1
- package/src/components/widgets/ImageUpload/ImageCropModal.tsx +7 -11
- package/src/handlers.ts +1 -0
- package/src/i18n/messages/en-us.ts +2 -0
- package/src/i18n/messages/es-es.ts +2 -0
- package/src/i18n/messages/pt-br.ts +2 -0
- package/src/index.ts +7 -1
- package/src/infra/api/client.ts +11 -3
- package/src/infra/api/types.ts +7 -5
- package/src/infra/route/safe-route-handler.ts +4 -1
- package/src/modules/auth/utils/assert-account-not-frozen.ts +7 -6
- package/src/modules/auth/utils/assert-management-subscription.ts +15 -23
- package/src/modules/auth/utils/assert-paid-subscription.ts +7 -15
- package/src/modules/auth/utils/assert-subscription-addon.ts +2 -12
- package/src/modules/plans/hooks/use-plan-by-id.hook.ts +1 -1
- package/src/modules/subscriptions/actions/get-current-subscription.action.ts +8 -0
- package/src/modules/subscriptions/constants/subscription.constants.ts +3 -0
- package/src/modules/subscriptions/handlers/current-subscription.handler.ts +16 -0
- package/src/modules/subscriptions/hooks/find-active-subscription.hook.ts +7 -17
- package/src/modules/subscriptions/hooks/use-current-subscription.hook.ts +38 -0
- package/src/modules/subscriptions/services/subscriptions.service.ts +34 -0
- package/src/modules/subscriptions/types/subscription.type.ts +2 -0
- package/src/modules/subscriptions/utils/group-subscriptions-by-product.ts +23 -0
- package/src/modules/subscriptions/utils/has-active-paid-subscription.ts +1 -2
- package/src/modules/subscriptions/utils/has-paid-subscription.ts +1 -2
- package/src/modules/subscriptions/utils/read-current-subscription.ts +34 -0
- package/src/server.ts +2 -0
- package/src/store/useMdSidebarStore.ts +20 -5
- package/src/utils/safeServerAction.ts +4 -6
- package/src/utils/withAction.ts +2 -1
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
'use server';
|
|
2
|
+
|
|
3
|
+
import { safeServerAction } from '../../../utils/safeServerAction';
|
|
4
|
+
import { subscriptionsService } from '../services/subscriptions.service';
|
|
5
|
+
|
|
6
|
+
export async function getCurrentSubscriptionAction(params?: { id_product?: number }) {
|
|
7
|
+
return safeServerAction(() => subscriptionsService.getCurrentSubscription(params));
|
|
8
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import 'server-only';
|
|
2
|
+
|
|
3
|
+
import type { NextRequest } from 'next/server';
|
|
4
|
+
import { safeRouteHandler } from '../../../infra/route/safe-route-handler';
|
|
5
|
+
import { searchParamsToParams } from '../../../infra/route/search-params';
|
|
6
|
+
import { subscriptionsService } from '../services/subscriptions.service';
|
|
7
|
+
|
|
8
|
+
export function createCurrentSubscriptionHandler() {
|
|
9
|
+
return async function GET(req: NextRequest) {
|
|
10
|
+
return safeRouteHandler(() =>
|
|
11
|
+
subscriptionsService.getCurrentSubscription(
|
|
12
|
+
searchParamsToParams<{ id_product?: number }>(req.nextUrl.searchParams),
|
|
13
|
+
),
|
|
14
|
+
);
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -1,22 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import { apiFetch } from "../../../infra/http/api-fetch";
|
|
5
|
-
import type { PaginatedSuccessResult } from "../../../infra/api/types";
|
|
6
|
-
import type { Subscription } from "../types/subscription.type";
|
|
7
|
-
import { SUBSCRIPTIONS_QUERY_KEY } from "../constants/query-keys.constants";
|
|
8
|
-
import { useAuthQueryKey } from "../../../hooks/useAuthQueryKey";
|
|
3
|
+
import { useCurrentSubscription } from "./use-current-subscription.hook";
|
|
9
4
|
|
|
5
|
+
/**
|
|
6
|
+
* @deprecated Use `useCurrentSubscription`, mesma leitura (mesma chave de
|
|
7
|
+
* cache, uma única requisição); só o nome muda para o vocabulário certo
|
|
8
|
+
* ("corrente", não "ativa"). Sai na 1.1.784.
|
|
9
|
+
*/
|
|
10
10
|
export function useActiveSubscription() {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
return useQuery({
|
|
14
|
-
queryKey,
|
|
15
|
-
queryFn: ({ signal }) =>
|
|
16
|
-
apiFetch<PaginatedSuccessResult<Subscription>>('/api/subscriptions', {
|
|
17
|
-
params: { limit: 1 },
|
|
18
|
-
signal,
|
|
19
|
-
}),
|
|
20
|
-
staleTime: 10_000,
|
|
21
|
-
});
|
|
11
|
+
return useCurrentSubscription();
|
|
22
12
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useQuery } from '@tanstack/react-query';
|
|
4
|
+
import { apiFetch } from '../../../infra/http/api-fetch';
|
|
5
|
+
import { useAuthQueryKey } from '../../../hooks/useAuthQueryKey';
|
|
6
|
+
import type { PaginatedSuccessResult, SuccessResult } from '../../../infra/api/types';
|
|
7
|
+
import type { Subscription } from '../types/subscription.type';
|
|
8
|
+
import { SUBSCRIPTIONS_QUERY_KEY } from '../constants/query-keys.constants';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A assinatura corrente do plano da conta, pela regra única do backend
|
|
12
|
+
* (contrato de domínio nunca é corrente; encerrada é decidida por `active`).
|
|
13
|
+
*
|
|
14
|
+
* Devolvida na mesma forma paginada de `useSubscriptions`/`useActiveSubscription`
|
|
15
|
+
* (`{ data: [sub] }`) para não exigir mudança nos call sites que já leem
|
|
16
|
+
* `data?.[0]`: só a URL e a regra por trás mudam.
|
|
17
|
+
*/
|
|
18
|
+
export function useCurrentSubscription() {
|
|
19
|
+
const queryKey = useAuthQueryKey([...SUBSCRIPTIONS_QUERY_KEY, 'current']);
|
|
20
|
+
|
|
21
|
+
return useQuery({
|
|
22
|
+
queryKey,
|
|
23
|
+
queryFn: async ({ signal }): Promise<PaginatedSuccessResult<Subscription>> => {
|
|
24
|
+
const result = await apiFetch<SuccessResult<Subscription | null>>(
|
|
25
|
+
'/api/subscriptions/current',
|
|
26
|
+
{ signal },
|
|
27
|
+
);
|
|
28
|
+
const subscription = result.data ?? null;
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
success: true,
|
|
32
|
+
data: subscription ? [subscription] : [],
|
|
33
|
+
total: subscription ? 1 : 0,
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
staleTime: 10_000,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
@@ -86,6 +86,40 @@ class SubscriptionsService {
|
|
|
86
86
|
} satisfies PaginatedSuccessResult<Subscription>;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
/**
|
|
90
|
+
* A assinatura corrente do plano da conta, pela regra única do backend
|
|
91
|
+
* (`GET .../subscriptions/action/current`): contrato de domínio nunca é
|
|
92
|
+
* corrente, encerrada é decidida por `active`. Sem `id_product`, paridade
|
|
93
|
+
* com hoje (melhor rank entre todos os produtos, nunca item/domínio).
|
|
94
|
+
*/
|
|
95
|
+
async getCurrentSubscription(
|
|
96
|
+
params?: { id_product?: number },
|
|
97
|
+
): Promise<SuccessResult<Subscription | null>> {
|
|
98
|
+
const { id_account } = await getUserContext();
|
|
99
|
+
|
|
100
|
+
const query = buildQueryParams(
|
|
101
|
+
params as Record<string, string | number | undefined>,
|
|
102
|
+
);
|
|
103
|
+
const url = `/accounts/${id_account}/subscriptions/action/current${query ? `?${query}` : ""}`;
|
|
104
|
+
|
|
105
|
+
const response = await api.apps.get<ApiPaginatedActionResult<Subscription>>(url);
|
|
106
|
+
|
|
107
|
+
if (response.status === 0) {
|
|
108
|
+
throw new ApiError(
|
|
109
|
+
response.message || "Erro ao consultar assinatura corrente",
|
|
110
|
+
"GET_CURRENT_SUBSCRIPTION_FAILED",
|
|
111
|
+
400,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const raw = response.data[0];
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
success: true,
|
|
119
|
+
data: raw ? SubscriptionSchema.parse(raw) : null,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
89
123
|
async calculateSubscription(
|
|
90
124
|
data: CalculateSubscriptionRequest,
|
|
91
125
|
): Promise<SuccessResult<CalculateSubscriptionResponse>> {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Subscription } from '../types/subscription.type';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Agrupa assinaturas por `id_product`, preservando a ordem de chegada dentro
|
|
5
|
+
* de cada grupo. Base da seção "Domínios registrados" do painel e da aba
|
|
6
|
+
* Assinatura agrupada por produto no admin.
|
|
7
|
+
*/
|
|
8
|
+
export function groupSubscriptionsByProduct(
|
|
9
|
+
subscriptions: Subscription[],
|
|
10
|
+
): Map<number, Subscription[]> {
|
|
11
|
+
const groups = new Map<number, Subscription[]>();
|
|
12
|
+
|
|
13
|
+
for (const subscription of subscriptions) {
|
|
14
|
+
const group = groups.get(subscription.id_product);
|
|
15
|
+
if (group) {
|
|
16
|
+
group.push(subscription);
|
|
17
|
+
} else {
|
|
18
|
+
groups.set(subscription.id_product, [subscription]);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return groups;
|
|
23
|
+
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { toCalendarDate, daysBetween } from "../../../infra/utils/date";
|
|
2
2
|
import { getSubscriptionCancellationState } from "./get-subscription-cancellation-state";
|
|
3
|
+
import { FREE_PLAN_ID } from "../constants/subscription.constants";
|
|
3
4
|
import type { Subscription } from "../types/subscription.type";
|
|
4
5
|
|
|
5
|
-
const FREE_PLAN_ID = 5;
|
|
6
|
-
|
|
7
6
|
type SubscriptionSnapshot = Pick<
|
|
8
7
|
Subscription,
|
|
9
8
|
| "type"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import "server-only";
|
|
2
|
+
|
|
3
|
+
import { cache } from "react";
|
|
4
|
+
|
|
5
|
+
import { ApiError } from "../../../infra/api/types";
|
|
6
|
+
import { getCurrentSubscriptionAction } from "../actions/get-current-subscription.action";
|
|
7
|
+
import type { Subscription } from "../types/subscription.type";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Leitura única por request de "qual é a assinatura corrente do plano desta
|
|
11
|
+
* conta", deduplicada via React.cache. Os quatro gates de auth
|
|
12
|
+
* (`assertAccountNotFrozen`, `assertManagementSubscription`,
|
|
13
|
+
* `assertPaidSubscription`, `assertSubscriptionAddon`) chamam esta função em
|
|
14
|
+
* vez de repetir a própria leitura cada um: eram até 4 cópias da mesma
|
|
15
|
+
* pergunta disputando a mesma fila (0,7 a 7s cada, medido em produção).
|
|
16
|
+
*
|
|
17
|
+
* Lança em falha de leitura; `assertAccountNotFrozen` é o único chamador que
|
|
18
|
+
* precisa de fail-open, e faz isso capturando o erro no próprio call site.
|
|
19
|
+
*/
|
|
20
|
+
export const readCurrentSubscription = cache(
|
|
21
|
+
async (): Promise<Subscription | null> => {
|
|
22
|
+
const result = await getCurrentSubscriptionAction();
|
|
23
|
+
|
|
24
|
+
if (!result.success) {
|
|
25
|
+
throw new ApiError(
|
|
26
|
+
result.error || "Não foi possível validar a assinatura atual",
|
|
27
|
+
"SUBSCRIPTION_CONTEXT_FAILED",
|
|
28
|
+
403,
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return result.data?.data ?? null;
|
|
33
|
+
},
|
|
34
|
+
);
|
package/src/server.ts
CHANGED
|
@@ -28,6 +28,8 @@ export { findUserById } from './modules/users/action/find-user-by-id.action';
|
|
|
28
28
|
export { findCurrentAccount } from './modules/accounts/actions/find-current-account.action';
|
|
29
29
|
export { getAccountTokenAction } from './modules/accounts/actions/get-account-token.action';
|
|
30
30
|
export { listSubscriptionsAction } from './modules/subscriptions/actions/list-subscriptions.action';
|
|
31
|
+
export { getCurrentSubscriptionAction } from './modules/subscriptions/actions/get-current-subscription.action';
|
|
32
|
+
export { readCurrentSubscription } from './modules/subscriptions/utils/read-current-subscription';
|
|
31
33
|
export { ackFreezeNoticeAction } from './modules/subscriptions/actions/ack-freeze-notice.action';
|
|
32
34
|
export { calculateSubscriptionAction } from './modules/subscriptions/actions/calculate-subscription.action';
|
|
33
35
|
export { updateSubscriptionPlanAction } from './modules/subscriptions/actions/update-subscription-plan.action';
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import { create } from "zustand";
|
|
2
|
+
import { useEffect } from "react";
|
|
2
3
|
import { readStorage, writeStorage } from "../utils/browser/safe-storage";
|
|
3
4
|
|
|
4
5
|
const STORAGE_KEY = "md-sidebar-expanded";
|
|
5
6
|
|
|
6
|
-
function getInitialState(): boolean {
|
|
7
|
-
return readStorage(STORAGE_KEY) === "true";
|
|
8
|
-
}
|
|
9
|
-
|
|
10
7
|
type MdSidebarStore = {
|
|
11
8
|
isExpanded: boolean;
|
|
9
|
+
_hydrated: boolean;
|
|
12
10
|
toggle: () => void;
|
|
13
11
|
expand: () => void;
|
|
14
12
|
collapse: () => void;
|
|
13
|
+
_hydrate: () => void;
|
|
15
14
|
};
|
|
16
15
|
|
|
17
16
|
export const useMdSidebarStore = create<MdSidebarStore>((set) => ({
|
|
18
|
-
isExpanded:
|
|
17
|
+
isExpanded: false,
|
|
18
|
+
_hydrated: false,
|
|
19
19
|
toggle: () => set((state) => {
|
|
20
20
|
const next = !state.isExpanded;
|
|
21
21
|
writeStorage(STORAGE_KEY, String(next));
|
|
@@ -29,4 +29,19 @@ export const useMdSidebarStore = create<MdSidebarStore>((set) => ({
|
|
|
29
29
|
writeStorage(STORAGE_KEY, "false");
|
|
30
30
|
return set({ isExpanded: false });
|
|
31
31
|
},
|
|
32
|
+
_hydrate: () => {
|
|
33
|
+
const value = readStorage(STORAGE_KEY) === "true";
|
|
34
|
+
set({ isExpanded: value, _hydrated: true });
|
|
35
|
+
},
|
|
32
36
|
}));
|
|
37
|
+
|
|
38
|
+
export function useMdSidebarHydration() {
|
|
39
|
+
const hydrate = useMdSidebarStore((s) => s._hydrate);
|
|
40
|
+
const hydrated = useMdSidebarStore((s) => s._hydrated);
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (!hydrated) {
|
|
44
|
+
hydrate();
|
|
45
|
+
}
|
|
46
|
+
}, [hydrate, hydrated]);
|
|
47
|
+
}
|
|
@@ -10,12 +10,10 @@ export async function safeServerAction<T>(
|
|
|
10
10
|
return { success: true, data };
|
|
11
11
|
} catch (error) {
|
|
12
12
|
if (isRedirectError(error)) throw error;
|
|
13
|
-
|
|
14
|
-
error
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
? error.message
|
|
18
|
-
: 'Erro desconhecido';
|
|
13
|
+
if (error instanceof ApiError) {
|
|
14
|
+
return { success: false, error: error.message, code: error.code, statusCode: error.statusCode };
|
|
15
|
+
}
|
|
16
|
+
const message = error instanceof Error ? error.message : 'Erro desconhecido';
|
|
19
17
|
return { success: false, error: message };
|
|
20
18
|
}
|
|
21
19
|
}
|
package/src/utils/withAction.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ApiError } from '../infra/api/types';
|
|
1
2
|
import type { ActionResult } from '../infra/api/types';
|
|
2
3
|
|
|
3
4
|
export function withAction<TArgs extends any[], TData>(
|
|
@@ -6,7 +7,7 @@ export function withAction<TArgs extends any[], TData>(
|
|
|
6
7
|
return async (...args) => {
|
|
7
8
|
const result = await action(...args);
|
|
8
9
|
if (!result.success) {
|
|
9
|
-
throw new
|
|
10
|
+
throw new ApiError(result.error, result.code, result.statusCode);
|
|
10
11
|
}
|
|
11
12
|
return result.data as TData;
|
|
12
13
|
};
|