@greatapps/common 1.1.786 → 1.1.787
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/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/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
|
@@ -6,11 +6,13 @@ import Link from "next/link";
|
|
|
6
6
|
import { useTranslations } from "next-intl";
|
|
7
7
|
import { IconChevronRight } from "@tabler/icons-react";
|
|
8
8
|
import { Separator } from "../ui/data-display/Separator";
|
|
9
|
+
import { GreatDomainIcon } from "./GreatDomainIcon";
|
|
9
10
|
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/overlay/Tooltip";
|
|
10
11
|
import {
|
|
11
12
|
useDesktopSidebarStore,
|
|
12
13
|
useDesktopSidebarHydration,
|
|
13
14
|
} from "../../store/useDesktopSidebarStore";
|
|
15
|
+
import { useMdSidebarHydration } from "../../store/useMdSidebarStore";
|
|
14
16
|
import {
|
|
15
17
|
useWhitelabel,
|
|
16
18
|
useIsDefaultWhitelabel,
|
|
@@ -20,7 +22,9 @@ import { cn } from "../../infra/utils/clsx";
|
|
|
20
22
|
type NavBarProps = {
|
|
21
23
|
logoHref: string;
|
|
22
24
|
appIconHref: string;
|
|
23
|
-
|
|
25
|
+
/** Sem href o ícone do GreatDomain não é renderizado: é a flag do app. */
|
|
26
|
+
domainIconHref?: string;
|
|
27
|
+
activeApp?: "gapps" | "gpages" | "gdomain";
|
|
24
28
|
showExpandButton?: boolean;
|
|
25
29
|
onExpandClick?: () => void;
|
|
26
30
|
children?: React.ReactNode;
|
|
@@ -30,6 +34,7 @@ type NavBarProps = {
|
|
|
30
34
|
function NavBar({
|
|
31
35
|
logoHref,
|
|
32
36
|
appIconHref,
|
|
37
|
+
domainIconHref,
|
|
33
38
|
activeApp,
|
|
34
39
|
showExpandButton = false,
|
|
35
40
|
onExpandClick,
|
|
@@ -40,6 +45,7 @@ function NavBar({
|
|
|
40
45
|
const { isExpanded: isDesktopExpanded, toggle: toggleDesktop } =
|
|
41
46
|
useDesktopSidebarStore();
|
|
42
47
|
useDesktopSidebarHydration();
|
|
48
|
+
useMdSidebarHydration();
|
|
43
49
|
const { whitelabel } = useWhitelabel();
|
|
44
50
|
const isDefaultWl = useIsDefaultWhitelabel();
|
|
45
51
|
const primaryColor = whitelabel?.general?.primary_color;
|
|
@@ -130,7 +136,9 @@ function NavBar({
|
|
|
130
136
|
<IconChevronRight size={16} className="text-gray-500" />
|
|
131
137
|
</button>
|
|
132
138
|
</TooltipTrigger>
|
|
133
|
-
<TooltipContent side="right">
|
|
139
|
+
<TooltipContent side="right">
|
|
140
|
+
{translate("common.layout.navbar.expandMenu")}
|
|
141
|
+
</TooltipContent>
|
|
134
142
|
</Tooltip>
|
|
135
143
|
)}
|
|
136
144
|
{/* Desktop expand button */}
|
|
@@ -144,78 +152,164 @@ function NavBar({
|
|
|
144
152
|
<IconChevronRight size={16} className="text-gray-500" />
|
|
145
153
|
</button>
|
|
146
154
|
</TooltipTrigger>
|
|
147
|
-
<TooltipContent side="right">
|
|
155
|
+
<TooltipContent side="right">
|
|
156
|
+
{translate("common.layout.navbar.expandMenu")}
|
|
157
|
+
</TooltipContent>
|
|
148
158
|
</Tooltip>
|
|
149
159
|
)}
|
|
150
160
|
</div>
|
|
151
161
|
|
|
152
|
-
{/*
|
|
153
|
-
<div className="
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
162
|
+
{/* Ícones de app — 8px entre eles (Figma 5929:2676) */}
|
|
163
|
+
<div className="flex flex-col gap-2 items-center">
|
|
164
|
+
{/* GreatPages icon */}
|
|
165
|
+
<div className="relative">
|
|
166
|
+
<Tooltip>
|
|
167
|
+
<TooltipTrigger asChild>
|
|
168
|
+
<Link
|
|
169
|
+
href={appIconHref}
|
|
170
|
+
className={cn(
|
|
171
|
+
"size-10 rounded-lg cursor-pointer flex items-center justify-center border-2 ring-2 transition-colors duration-300",
|
|
172
|
+
activeApp === "gpages"
|
|
173
|
+
? isDefaultWl
|
|
174
|
+
? "bg-cyan-100 border-white ring-cyan-300"
|
|
175
|
+
: "border-white ring-transparent"
|
|
176
|
+
: isDefaultWl
|
|
177
|
+
? "hover:bg-gray-100 border-transparent ring-transparent"
|
|
178
|
+
: "border-transparent ring-transparent",
|
|
179
|
+
)}
|
|
180
|
+
style={
|
|
181
|
+
!isDefaultWl
|
|
182
|
+
? {
|
|
183
|
+
backgroundColor: primaryColor,
|
|
184
|
+
boxShadow:
|
|
185
|
+
activeApp === "gpages"
|
|
186
|
+
? `0 0 0 2px ${primaryColor}40`
|
|
187
|
+
: undefined,
|
|
188
|
+
opacity: activeApp !== "gpages" ? 0.2 : undefined,
|
|
189
|
+
}
|
|
190
|
+
: undefined
|
|
191
|
+
}
|
|
192
|
+
>
|
|
193
|
+
{isDefaultWl ? (
|
|
194
|
+
<Image
|
|
195
|
+
src="/icons/icon-navbar.svg"
|
|
196
|
+
alt="GreatPages"
|
|
197
|
+
width={24}
|
|
198
|
+
height={24}
|
|
199
|
+
/>
|
|
200
|
+
) : (
|
|
201
|
+
<svg
|
|
202
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
203
|
+
width="24"
|
|
204
|
+
height="24"
|
|
205
|
+
viewBox="0 0 24 24"
|
|
206
|
+
fill="none"
|
|
207
|
+
stroke="white"
|
|
208
|
+
strokeWidth="2"
|
|
209
|
+
strokeLinecap="round"
|
|
210
|
+
strokeLinejoin="round"
|
|
211
|
+
>
|
|
212
|
+
<path d="M4 8h16" />
|
|
213
|
+
<path d="M4 6a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2l0 -12" />
|
|
214
|
+
<path d="M8 4v4" />
|
|
215
|
+
</svg>
|
|
216
|
+
)}
|
|
217
|
+
</Link>
|
|
218
|
+
</TooltipTrigger>
|
|
219
|
+
<TooltipContent side="right">
|
|
220
|
+
{isDefaultWl
|
|
221
|
+
? translate("common.layout.navbar.greatPages")
|
|
222
|
+
: translate("common.layout.navbar.pages")}
|
|
223
|
+
</TooltipContent>
|
|
224
|
+
</Tooltip>
|
|
225
|
+
<div
|
|
226
|
+
className={cn(
|
|
227
|
+
"absolute -left-4 top-1/2 -translate-y-1/2 z-10 w-[3px] h-5 rounded-r-xl transition-opacity duration-300",
|
|
228
|
+
isDefaultWl ? "bg-cyan-300" : "",
|
|
229
|
+
activeApp === "gpages" ? "opacity-100" : "opacity-0",
|
|
230
|
+
)}
|
|
231
|
+
style={
|
|
232
|
+
!isDefaultWl ? { backgroundColor: primaryColor } : undefined
|
|
233
|
+
}
|
|
234
|
+
/>
|
|
235
|
+
</div>
|
|
236
|
+
|
|
237
|
+
{/* GreatDomain icon */}
|
|
238
|
+
{domainIconHref ? (
|
|
239
|
+
<div className="relative">
|
|
240
|
+
<Tooltip>
|
|
241
|
+
<TooltipTrigger asChild>
|
|
242
|
+
<Link
|
|
243
|
+
href={domainIconHref}
|
|
244
|
+
aria-label={
|
|
245
|
+
isDefaultWl
|
|
246
|
+
? translate("common.layout.navbar.greatDomain")
|
|
247
|
+
: translate("common.layout.navbar.domains")
|
|
248
|
+
}
|
|
249
|
+
className={cn(
|
|
250
|
+
"size-10 rounded-[10px] cursor-pointer flex items-center justify-center border-2 ring-2 transition-colors duration-300",
|
|
251
|
+
activeApp === "gdomain"
|
|
252
|
+
? isDefaultWl
|
|
253
|
+
? "bg-[#EDE9FE] border-white ring-[#C092FF]"
|
|
254
|
+
: "border-white ring-transparent"
|
|
255
|
+
: isDefaultWl
|
|
256
|
+
? "hover:bg-gray-100 border-transparent ring-transparent"
|
|
257
|
+
: "border-transparent ring-transparent",
|
|
258
|
+
)}
|
|
259
|
+
style={
|
|
260
|
+
!isDefaultWl
|
|
261
|
+
? {
|
|
262
|
+
backgroundColor: primaryColor,
|
|
263
|
+
boxShadow:
|
|
264
|
+
activeApp === "gdomain"
|
|
265
|
+
? `0 0 0 2px ${primaryColor}40`
|
|
266
|
+
: undefined,
|
|
267
|
+
opacity: activeApp !== "gdomain" ? 0.2 : undefined,
|
|
268
|
+
}
|
|
269
|
+
: undefined
|
|
270
|
+
}
|
|
271
|
+
>
|
|
272
|
+
{isDefaultWl ? (
|
|
273
|
+
<GreatDomainIcon active={activeApp === "gdomain"} />
|
|
274
|
+
) : (
|
|
275
|
+
<svg
|
|
276
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
277
|
+
width="24"
|
|
278
|
+
height="24"
|
|
279
|
+
viewBox="0 0 24 24"
|
|
280
|
+
fill="none"
|
|
281
|
+
stroke="white"
|
|
282
|
+
strokeWidth="2"
|
|
283
|
+
strokeLinecap="round"
|
|
284
|
+
strokeLinejoin="round"
|
|
285
|
+
>
|
|
286
|
+
<path d="M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0" />
|
|
287
|
+
<path d="M3.6 9h16.8" />
|
|
288
|
+
<path d="M3.6 15h16.8" />
|
|
289
|
+
<path d="M11.5 3a17 17 0 0 0 0 18" />
|
|
290
|
+
<path d="M12.5 3a17 17 0 0 1 0 18" />
|
|
291
|
+
</svg>
|
|
292
|
+
)}
|
|
293
|
+
</Link>
|
|
294
|
+
</TooltipTrigger>
|
|
295
|
+
<TooltipContent side="right">
|
|
296
|
+
{isDefaultWl
|
|
297
|
+
? translate("common.layout.navbar.greatDomain")
|
|
298
|
+
: translate("common.layout.navbar.domains")}
|
|
299
|
+
</TooltipContent>
|
|
300
|
+
</Tooltip>
|
|
301
|
+
<div
|
|
158
302
|
className={cn(
|
|
159
|
-
"
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
? "bg-cyan-100 border-white ring-cyan-300"
|
|
163
|
-
: "border-white ring-transparent"
|
|
164
|
-
: isDefaultWl
|
|
165
|
-
? "hover:bg-gray-100 border-transparent ring-transparent"
|
|
166
|
-
: "border-transparent ring-transparent",
|
|
303
|
+
"absolute -left-4 top-1/2 -translate-y-1/2 z-10 w-[3px] h-5 rounded-r-xl transition-opacity duration-300",
|
|
304
|
+
isDefaultWl ? "bg-[#984DFF]" : "",
|
|
305
|
+
activeApp === "gdomain" ? "opacity-100" : "opacity-0",
|
|
167
306
|
)}
|
|
168
307
|
style={
|
|
169
|
-
!isDefaultWl
|
|
170
|
-
? {
|
|
171
|
-
backgroundColor: primaryColor,
|
|
172
|
-
boxShadow:
|
|
173
|
-
activeApp === "gpages"
|
|
174
|
-
? `0 0 0 2px ${primaryColor}40`
|
|
175
|
-
: undefined,
|
|
176
|
-
opacity: activeApp !== "gpages" ? 0.2 : undefined,
|
|
177
|
-
}
|
|
178
|
-
: undefined
|
|
308
|
+
!isDefaultWl ? { backgroundColor: primaryColor } : undefined
|
|
179
309
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
src="/icons/icon-navbar.svg"
|
|
184
|
-
alt="GreatPages"
|
|
185
|
-
width={24}
|
|
186
|
-
height={24}
|
|
187
|
-
/>
|
|
188
|
-
) : (
|
|
189
|
-
<svg
|
|
190
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
191
|
-
width="24"
|
|
192
|
-
height="24"
|
|
193
|
-
viewBox="0 0 24 24"
|
|
194
|
-
fill="none"
|
|
195
|
-
stroke="white"
|
|
196
|
-
strokeWidth="2"
|
|
197
|
-
strokeLinecap="round"
|
|
198
|
-
strokeLinejoin="round"
|
|
199
|
-
>
|
|
200
|
-
<path d="M4 8h16" />
|
|
201
|
-
<path d="M4 6a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2l0 -12" />
|
|
202
|
-
<path d="M8 4v4" />
|
|
203
|
-
</svg>
|
|
204
|
-
)}
|
|
205
|
-
</Link>
|
|
206
|
-
</TooltipTrigger>
|
|
207
|
-
<TooltipContent side="right">
|
|
208
|
-
{isDefaultWl ? translate('common.layout.navbar.greatPages') : translate('common.layout.navbar.pages')}
|
|
209
|
-
</TooltipContent>
|
|
210
|
-
</Tooltip>
|
|
211
|
-
<div
|
|
212
|
-
className={cn(
|
|
213
|
-
"absolute -left-4 top-1/2 -translate-y-1/2 z-10 w-[3px] h-5 rounded-r-xl transition-opacity duration-300",
|
|
214
|
-
isDefaultWl ? "bg-cyan-300" : "",
|
|
215
|
-
activeApp === "gpages" ? "opacity-100" : "opacity-0",
|
|
216
|
-
)}
|
|
217
|
-
style={!isDefaultWl ? { backgroundColor: primaryColor } : undefined}
|
|
218
|
-
/>
|
|
310
|
+
/>
|
|
311
|
+
</div>
|
|
312
|
+
) : null}
|
|
219
313
|
</div>
|
|
220
314
|
</div>
|
|
221
315
|
|
|
@@ -45,7 +45,7 @@ export default function PhoneInput({
|
|
|
45
45
|
const translate = useTranslations();
|
|
46
46
|
const parsed = parsePhoneValue(value ?? '');
|
|
47
47
|
const [selectedCountry, setSelectedCountry] = useState<Country>(parsed.country);
|
|
48
|
-
const [phoneNumber, setPhoneNumber] = useState(parsed.number);
|
|
48
|
+
const [phoneNumber, setPhoneNumber] = useState(formatNational(parsed.number, parsed.country.iso));
|
|
49
49
|
const [search, setSearch] = useState('');
|
|
50
50
|
const [open, setOpen] = useState(false);
|
|
51
51
|
const prevValueRef = useRef(value);
|
package/src/handlers.ts
CHANGED
|
@@ -20,6 +20,7 @@ export { createListAvailableUsersHandler } from './modules/projects/handlers/lis
|
|
|
20
20
|
export { createListAllAccountUsersHandler } from './modules/projects/handlers/list-all-account-users.handler';
|
|
21
21
|
|
|
22
22
|
export { createListSubscriptionsHandler } from './modules/subscriptions/handlers/list-subscriptions.handler';
|
|
23
|
+
export { createCurrentSubscriptionHandler } from './modules/subscriptions/handlers/current-subscription.handler';
|
|
23
24
|
export { createFreezeNoticeHandler } from './modules/subscriptions/handlers/freeze-notice.handler';
|
|
24
25
|
|
|
25
26
|
export { createListMessagesHandler } from './modules/users/handlers/list-messages.handler';
|
package/src/index.ts
CHANGED
|
@@ -86,6 +86,8 @@ export {
|
|
|
86
86
|
CHARGES_QUERY_KEY,
|
|
87
87
|
FREEZE_NOTICE_QUERY_KEY,
|
|
88
88
|
} from "./modules/subscriptions/constants/query-keys.constants";
|
|
89
|
+
export { useCurrentSubscription } from "./modules/subscriptions/hooks/use-current-subscription.hook";
|
|
90
|
+
/** @deprecated Use `useCurrentSubscription`. Sai na 1.1.784. */
|
|
89
91
|
export { useActiveSubscription } from "./modules/subscriptions/hooks/find-active-subscription.hook";
|
|
90
92
|
export { useCancelledSubscriptionGuard } from "./modules/subscriptions/hooks/use-cancelled-subscription-guard";
|
|
91
93
|
export { useAccountFreeze } from "./modules/subscriptions/hooks/use-account-freeze.hook";
|
|
@@ -222,7 +224,11 @@ export {
|
|
|
222
224
|
confirmPaymentIfRequired,
|
|
223
225
|
PaymentAuthenticationError,
|
|
224
226
|
} from "./modules/subscriptions/utils/confirm-payment-if-required";
|
|
225
|
-
export {
|
|
227
|
+
export {
|
|
228
|
+
SUBSCRIPTION_GRACE_PERIOD_DAYS,
|
|
229
|
+
FREE_PLAN_ID,
|
|
230
|
+
} from "./modules/subscriptions/constants/subscription.constants";
|
|
231
|
+
export { groupSubscriptionsByProduct } from "./modules/subscriptions/utils/group-subscriptions-by-product";
|
|
226
232
|
export { getSubscriptionCancellationState } from "./modules/subscriptions/utils/get-subscription-cancellation-state";
|
|
227
233
|
export type { SubscriptionCancellationState } from "./modules/subscriptions/utils/get-subscription-cancellation-state";
|
|
228
234
|
export {
|
package/src/infra/api/client.ts
CHANGED
|
@@ -31,6 +31,11 @@ export interface ApiClientOptions {
|
|
|
31
31
|
emitLogs?: boolean;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
interface ApiErrorResponseBody {
|
|
35
|
+
message?: string | { message?: string };
|
|
36
|
+
code?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
34
39
|
export interface RequestConfig extends RequestInit {
|
|
35
40
|
timeout?: number;
|
|
36
41
|
/** Padrão `json`. Use `text` quando a API devolve corpo não-JSON. */
|
|
@@ -169,9 +174,12 @@ class ApiClient {
|
|
|
169
174
|
let message = `HTTP error ${response.status} ${response.statusText} ${errBody}`;
|
|
170
175
|
let code = "HTTP_ERROR";
|
|
171
176
|
try {
|
|
172
|
-
const errorData = JSON.parse(errBody)
|
|
173
|
-
|
|
174
|
-
|
|
177
|
+
const errorData: ApiErrorResponseBody = JSON.parse(errBody);
|
|
178
|
+
if (typeof errorData.message === "string") {
|
|
179
|
+
message = errorData.message;
|
|
180
|
+
} else if (errorData.message?.message) {
|
|
181
|
+
message = errorData.message.message;
|
|
182
|
+
}
|
|
175
183
|
if (typeof errorData.code === "string") code = errorData.code;
|
|
176
184
|
} catch {
|
|
177
185
|
if (errBody.trim()) message = errBody.trim();
|
package/src/infra/api/types.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import z from 'zod';
|
|
2
2
|
|
|
3
|
-
export const PaginationParamsSchema = z.object({
|
|
4
|
-
page: z.number().min(1).optional().default(1),
|
|
5
|
-
limit: z.number().optional().default(20),
|
|
6
|
-
});
|
|
3
|
+
export const PaginationParamsSchema = z.object({
|
|
4
|
+
page: z.number().min(1).optional().default(1),
|
|
5
|
+
limit: z.number().optional().default(20),
|
|
6
|
+
});
|
|
7
7
|
|
|
8
8
|
export interface PaginationParams extends z.infer<typeof PaginationParamsSchema> {}
|
|
9
9
|
|
|
@@ -51,6 +51,8 @@ export interface PaginatedSuccessResult<T> extends Omit<SuccessResult<T>, 'data'
|
|
|
51
51
|
export interface ErrorResult {
|
|
52
52
|
success: false;
|
|
53
53
|
error?: string;
|
|
54
|
+
code?: string;
|
|
55
|
+
statusCode?: number;
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
export type ActionResult<T> = SuccessResult<T> | ErrorResult;
|
|
@@ -60,7 +62,7 @@ export class ApiError extends Error {
|
|
|
60
62
|
public readonly code: string;
|
|
61
63
|
public readonly statusCode: number;
|
|
62
64
|
|
|
63
|
-
constructor(message: string, code: string = 'UNKNOWN_ERROR', statusCode: number = 500) {
|
|
65
|
+
constructor(message: string = 'Erro desconhecido', code: string = 'UNKNOWN_ERROR', statusCode: number = 500) {
|
|
64
66
|
super(message);
|
|
65
67
|
this.name = 'ApiError';
|
|
66
68
|
this.code = code;
|
|
@@ -8,7 +8,10 @@ export async function safeRouteHandler<T>(fn: () => Promise<T>): Promise<Respons
|
|
|
8
8
|
return Response.json({ success: true, data });
|
|
9
9
|
} catch (error) {
|
|
10
10
|
if (error instanceof ApiError) {
|
|
11
|
-
return Response.json(
|
|
11
|
+
return Response.json(
|
|
12
|
+
{ success: false, error: error.message, code: error.code },
|
|
13
|
+
{ status: error.statusCode },
|
|
14
|
+
);
|
|
12
15
|
}
|
|
13
16
|
console.error('[safeRouteHandler] erro inesperado', error);
|
|
14
17
|
return Response.json({ success: false, error: 'Erro interno do servidor' }, { status: 500 });
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import "server-only";
|
|
2
2
|
|
|
3
3
|
import { ApiError } from "../../../infra/api/types";
|
|
4
|
-
import {
|
|
4
|
+
import { readCurrentSubscription } from "../../subscriptions/utils/read-current-subscription";
|
|
5
5
|
import { getAccountFreezeState } from "../../subscriptions/utils/get-account-freeze-state";
|
|
6
|
+
import type { Subscription } from "../../subscriptions/types/subscription.type";
|
|
6
7
|
|
|
7
8
|
export async function assertAccountNotFrozen(): Promise<void> {
|
|
8
|
-
|
|
9
|
+
let subscription: Subscription | null;
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
try {
|
|
12
|
+
subscription = await readCurrentSubscription();
|
|
13
|
+
} catch (error) {
|
|
11
14
|
console.error(
|
|
12
15
|
"[assertAccountNotFrozen] failed to read subscription context:",
|
|
13
|
-
|
|
16
|
+
error,
|
|
14
17
|
);
|
|
15
18
|
return;
|
|
16
19
|
}
|
|
17
20
|
|
|
18
|
-
const subscription = result.data?.data?.[0];
|
|
19
|
-
|
|
20
21
|
if (!subscription) return;
|
|
21
22
|
|
|
22
23
|
if (getAccountFreezeState(subscription) !== "frozen") return;
|
|
@@ -1,32 +1,23 @@
|
|
|
1
1
|
import "server-only";
|
|
2
2
|
|
|
3
|
-
import { cache } from "react";
|
|
4
|
-
|
|
5
3
|
import { ApiError } from "../../../infra/api/types";
|
|
6
|
-
import {
|
|
4
|
+
import { readCurrentSubscription } from "../../subscriptions/utils/read-current-subscription";
|
|
5
|
+
import { getSubscriptionCancellationState } from "../../subscriptions/utils/get-subscription-cancellation-state";
|
|
7
6
|
import { getSubscriptionExpiryDate } from "../../subscriptions/utils/get-subscription-expiry-date";
|
|
8
7
|
|
|
9
8
|
/**
|
|
10
|
-
*
|
|
9
|
+
* A leitura por trás (`readCurrentSubscription`) já é deduplicada por request via
|
|
10
|
+
* React.cache, então não precisa de outra camada aqui: em produção (26/08) o mesmo
|
|
11
|
+
* carregamento disparava até quatro cópias da mesma pergunta em paralelo, com 727 ms,
|
|
12
|
+
* 4,2 s, 5,0 s e 7,0 s, contra um p50 de 528 ms; agora é uma leitura só,
|
|
13
|
+
* compartilhada pelos quatro gates.
|
|
11
14
|
*
|
|
12
|
-
* A
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* p50 de 528 ms para o endpoint. Elas se atrasam entre si: são cópias da mesma pergunta disputando
|
|
16
|
-
* a mesma fila.
|
|
15
|
+
* A checagem de cancelamento é obrigatória além da de vencimento: a corrente pode
|
|
16
|
+
* vir com `active: false` (baixa por falha de pagamento) com `date_due` ainda no
|
|
17
|
+
* futuro, caso que só `getSubscriptionExpiryDate` não cobre.
|
|
17
18
|
*/
|
|
18
|
-
export
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
if (!result.success) {
|
|
22
|
-
throw new ApiError(
|
|
23
|
-
result.error || "Não foi possível validar a assinatura atual",
|
|
24
|
-
"SUBSCRIPTION_CONTEXT_FAILED",
|
|
25
|
-
403,
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const subscription = result.data?.data?.[0];
|
|
19
|
+
export async function assertManagementSubscription(): Promise<void> {
|
|
20
|
+
const subscription = await readCurrentSubscription();
|
|
30
21
|
|
|
31
22
|
if (!subscription) {
|
|
32
23
|
throw new ApiError(
|
|
@@ -36,14 +27,15 @@ export const assertManagementSubscription = cache(async () => {
|
|
|
36
27
|
);
|
|
37
28
|
}
|
|
38
29
|
|
|
30
|
+
const isCancelled = getSubscriptionCancellationState(subscription) === "cancelled";
|
|
39
31
|
const expiryDate = getSubscriptionExpiryDate(subscription);
|
|
40
32
|
const isExpired = !!expiryDate && Date.now() >= expiryDate.getTime();
|
|
41
33
|
|
|
42
|
-
if (isExpired) {
|
|
34
|
+
if (isCancelled || isExpired) {
|
|
43
35
|
throw new ApiError(
|
|
44
36
|
"Sua assinatura está vencida. Assine um novo plano para continuar.",
|
|
45
37
|
"SUBSCRIPTION_EXPIRED",
|
|
46
38
|
403,
|
|
47
39
|
);
|
|
48
40
|
}
|
|
49
|
-
}
|
|
41
|
+
}
|
|
@@ -1,21 +1,13 @@
|
|
|
1
1
|
import "server-only";
|
|
2
2
|
|
|
3
3
|
import { ApiError } from "../../../infra/api/types";
|
|
4
|
-
import {
|
|
4
|
+
import { readCurrentSubscription } from "../../subscriptions/utils/read-current-subscription";
|
|
5
|
+
import { getSubscriptionCancellationState } from "../../subscriptions/utils/get-subscription-cancellation-state";
|
|
5
6
|
import { getSubscriptionExpiryDate } from "../../subscriptions/utils/get-subscription-expiry-date";
|
|
7
|
+
import { FREE_PLAN_ID } from "../../subscriptions/constants/subscription.constants";
|
|
6
8
|
|
|
7
9
|
export async function assertPaidSubscription() {
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
if (!result.success) {
|
|
11
|
-
throw new ApiError(
|
|
12
|
-
result.error || "Não foi possível validar a assinatura atual",
|
|
13
|
-
"SUBSCRIPTION_CONTEXT_FAILED",
|
|
14
|
-
403,
|
|
15
|
-
);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const subscription = result.data?.data?.[0];
|
|
10
|
+
const subscription = await readCurrentSubscription();
|
|
19
11
|
|
|
20
12
|
if (!subscription) {
|
|
21
13
|
throw new ApiError(
|
|
@@ -25,10 +17,11 @@ export async function assertPaidSubscription() {
|
|
|
25
17
|
);
|
|
26
18
|
}
|
|
27
19
|
|
|
20
|
+
const isCancelled = getSubscriptionCancellationState(subscription) === "cancelled";
|
|
28
21
|
const expiryDate = getSubscriptionExpiryDate(subscription);
|
|
29
22
|
const isExpired = !!expiryDate && Date.now() >= expiryDate.getTime();
|
|
30
23
|
|
|
31
|
-
if (isExpired) {
|
|
24
|
+
if (isCancelled || isExpired) {
|
|
32
25
|
throw new ApiError(
|
|
33
26
|
"Sua assinatura está vencida. Assine um novo plano para continuar.",
|
|
34
27
|
"SUBSCRIPTION_EXPIRED",
|
|
@@ -36,8 +29,7 @@ export async function assertPaidSubscription() {
|
|
|
36
29
|
);
|
|
37
30
|
}
|
|
38
31
|
|
|
39
|
-
|
|
40
|
-
if (subscription.type === 'free' || subscription.id_plan === 5) {
|
|
32
|
+
if (subscription.type === 'free' || subscription.id_plan === FREE_PLAN_ID) {
|
|
41
33
|
throw new ApiError(
|
|
42
34
|
"Este recurso está disponível apenas para planos pagos. Faça upgrade do seu plano para continuar.",
|
|
43
35
|
"PAID_SUBSCRIPTION_REQUIRED",
|
|
@@ -1,24 +1,14 @@
|
|
|
1
1
|
import "server-only";
|
|
2
2
|
|
|
3
3
|
import { ApiError } from "../../../infra/api/types";
|
|
4
|
-
import {
|
|
4
|
+
import { readCurrentSubscription } from "../../subscriptions/utils/read-current-subscription";
|
|
5
5
|
import type { AddonKindEnum } from "../../subscriptions/constants/addons.constants";
|
|
6
6
|
|
|
7
7
|
export async function assertSubscriptionAddon(
|
|
8
8
|
addonId: AddonKindEnum,
|
|
9
9
|
addonLabel: string,
|
|
10
10
|
) {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
if (!result.success) {
|
|
14
|
-
throw new ApiError(
|
|
15
|
-
result.error || "Não foi possível validar a assinatura atual",
|
|
16
|
-
"SUBSCRIPTION_CONTEXT_FAILED",
|
|
17
|
-
403,
|
|
18
|
-
);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const subscription = result.data?.data?.[0];
|
|
11
|
+
const subscription = await readCurrentSubscription();
|
|
22
12
|
|
|
23
13
|
if (!subscription) {
|
|
24
14
|
throw new ApiError(
|
|
@@ -11,7 +11,7 @@ export function usePlanById(idPlan?: number | string | null) {
|
|
|
11
11
|
const queryClient = useQueryClient();
|
|
12
12
|
const plansKey = useAuthQueryKey([...PLANS_QUERY_KEY]);
|
|
13
13
|
|
|
14
|
-
return useQuery({
|
|
14
|
+
return useQuery<SuccessResult<Plan>>({
|
|
15
15
|
queryKey: [...plansKey, idPlan],
|
|
16
16
|
queryFn: ({ signal }) =>
|
|
17
17
|
apiFetch<SuccessResult<Plan>>(`/api/plans/${idPlan}`, { signal }),
|
|
@@ -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
|
+
}
|