@greatapps/common 1.1.215 → 1.1.217

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.
Files changed (186) hide show
  1. package/dist/components/account/ConfirmDeleteAccountModal.mjs.map +1 -1
  2. package/dist/components/account/DeleteAccountModal.mjs.map +1 -1
  3. package/dist/components/account/IsntPossibleDeleteAccountModal.mjs.map +1 -1
  4. package/dist/components/account/hooks/useChangePhoneForm.mjs.map +1 -1
  5. package/dist/components/account/hooks/useTwoFactorAuthForm.mjs.map +1 -1
  6. package/dist/components/account/sections/ChangePhoneModal.mjs.map +1 -1
  7. package/dist/components/embeds/CrispEmbed.mjs.map +1 -1
  8. package/dist/components/embeds/FrillEmbed.mjs.map +1 -1
  9. package/dist/components/layouts/AppMobileNavBar.mjs.map +1 -1
  10. package/dist/components/layouts/AppNavBar.mjs.map +1 -1
  11. package/dist/components/layouts/NavBar.mjs.map +1 -1
  12. package/dist/components/layouts/UsersSelectorPopover.mjs.map +1 -1
  13. package/dist/components/navigation/CancelledSubscriptionBanner.mjs.map +1 -1
  14. package/dist/components/navigation/NavItemsContext.mjs.map +1 -1
  15. package/dist/components/navigation/shells/TabsMobileShell.mjs.map +1 -1
  16. package/dist/components/pages/notifications/Notifications.mjs +3 -3
  17. package/dist/components/pages/notifications/Notifications.mjs.map +1 -1
  18. package/dist/components/ui/buttons/Button.mjs.map +1 -1
  19. package/dist/components/ui/buttons/CancelButton.mjs.map +1 -1
  20. package/dist/components/ui/buttons/CopyButton.mjs.map +1 -1
  21. package/dist/components/ui/data-display/Accordion.mjs.map +1 -1
  22. package/dist/components/ui/data-display/Badge.mjs.map +1 -1
  23. package/dist/components/ui/data-display/Card.mjs.map +1 -1
  24. package/dist/components/ui/data-display/Pagination.mjs.map +1 -1
  25. package/dist/components/ui/data-display/ScrollArea.mjs.map +1 -1
  26. package/dist/components/ui/data-display/Separator.mjs.map +1 -1
  27. package/dist/components/ui/data-display/Table.mjs.map +1 -1
  28. package/dist/components/ui/data-display/Tabs.mjs.map +1 -1
  29. package/dist/components/ui/feedback/CircularProgress.mjs.map +1 -1
  30. package/dist/components/ui/feedback/DefaultCircularProgress.mjs.map +1 -1
  31. package/dist/components/ui/feedback/LoadingOverlay.mjs.map +1 -1
  32. package/dist/components/ui/feedback/Progress.mjs.map +1 -1
  33. package/dist/components/ui/feedback/Toast.mjs.map +1 -1
  34. package/dist/components/ui/form/Calendar.mjs.map +1 -1
  35. package/dist/components/ui/form/Checkbox.mjs.map +1 -1
  36. package/dist/components/ui/form/DatePicker.mjs.map +1 -1
  37. package/dist/components/ui/form/DateRangePicker.mjs.map +1 -1
  38. package/dist/components/ui/form/Input.mjs.map +1 -1
  39. package/dist/components/ui/form/InputOtp.mjs.map +1 -1
  40. package/dist/components/ui/form/PhoneInput.mjs.map +1 -1
  41. package/dist/components/ui/form/RadioGroup.mjs.map +1 -1
  42. package/dist/components/ui/form/Select.mjs +1 -1
  43. package/dist/components/ui/form/Select.mjs.map +1 -1
  44. package/dist/components/ui/form/Switch.mjs.map +1 -1
  45. package/dist/components/ui/form/Textarea.mjs.map +1 -1
  46. package/dist/components/ui/overlay/Command.mjs.map +1 -1
  47. package/dist/components/ui/overlay/Dialog.mjs.map +1 -1
  48. package/dist/components/ui/overlay/Popover.mjs.map +1 -1
  49. package/dist/components/ui/overlay/Sheet.mjs.map +1 -1
  50. package/dist/components/ui/overlay/Tooltip.mjs.map +1 -1
  51. package/dist/components/widgets/notifications/NotificationCard.mjs +1 -1
  52. package/dist/components/widgets/notifications/NotificationCard.mjs.map +1 -1
  53. package/dist/hooks/copy-to-clipboard.hook.mjs.map +1 -1
  54. package/dist/index.mjs.map +1 -1
  55. package/dist/middleware.mjs.map +1 -1
  56. package/dist/modules/accounts/actions/account-management.action.mjs.map +1 -1
  57. package/dist/modules/accounts/actions/find-current-account.action.mjs.map +1 -1
  58. package/dist/modules/accounts/actions/list-currencies.action.mjs.map +1 -1
  59. package/dist/modules/accounts/actions/list-timezones.action.mjs.map +1 -1
  60. package/dist/modules/accounts/hooks/current-account.hook.mjs.map +1 -1
  61. package/dist/modules/accounts/hooks/useAccountManagement.mjs.map +1 -1
  62. package/dist/modules/accounts/hooks/useCurrencies.mjs.map +1 -1
  63. package/dist/modules/accounts/hooks/useTimezones.mjs.map +1 -1
  64. package/dist/modules/accounts/services/account.service.mjs.map +1 -1
  65. package/dist/modules/auth/hooks/login.hook.mjs.map +1 -1
  66. package/dist/modules/auth/hooks/logout.hook.mjs.map +1 -1
  67. package/dist/modules/auth/hooks/register.hook.mjs.map +1 -1
  68. package/dist/modules/ia-credits/actions/list-ia-credits.action.mjs.map +1 -1
  69. package/dist/modules/projects/actions/add-project-user.action.mjs.map +1 -1
  70. package/dist/modules/projects/actions/list-all-account-users.action.mjs.map +1 -1
  71. package/dist/modules/projects/actions/list-available-users.action.mjs.map +1 -1
  72. package/dist/modules/projects/actions/list-project-users.action.mjs.map +1 -1
  73. package/dist/modules/projects/actions/remove-project-user.action.mjs.map +1 -1
  74. package/dist/modules/projects/hooks/add-project-user.hook.mjs.map +1 -1
  75. package/dist/modules/projects/hooks/list-all-account-users.hook.mjs.map +1 -1
  76. package/dist/modules/projects/hooks/list-available-users.hook.mjs.map +1 -1
  77. package/dist/modules/projects/hooks/list-project-users.hook.mjs.map +1 -1
  78. package/dist/modules/projects/hooks/remove-project-user.hook.mjs.map +1 -1
  79. package/dist/modules/subscriptions/actions/list-subscriptions.action.mjs.map +1 -1
  80. package/dist/modules/subscriptions/hooks/find-active-subscription.hook.mjs.map +1 -1
  81. package/dist/modules/subscriptions/hooks/list-subscriptions.hook.mjs.map +1 -1
  82. package/dist/modules/users/action/find-user-by-id.action.mjs.map +1 -1
  83. package/dist/modules/users/action/list-messages.action.mjs.map +1 -1
  84. package/dist/modules/users/action/mark-all-messages-as-read.action.mjs.map +1 -1
  85. package/dist/modules/users/action/mark-message-as-read.action.mjs.map +1 -1
  86. package/dist/modules/users/hooks/messages.hook.mjs.map +1 -1
  87. package/dist/modules/whitelabel/actions/find-whitelabel-styles.action.mjs.map +1 -1
  88. package/dist/modules/whitelabel/actions/find-whitelabel.action.mjs.map +1 -1
  89. package/dist/modules/whitelabel/services/whitelabel-styles.service.mjs.map +1 -1
  90. package/dist/providers/whitelabel.provider.mjs.map +1 -1
  91. package/dist/utils/countries.mjs.map +1 -1
  92. package/dist/utils/safeServerAction.mjs.map +1 -1
  93. package/dist/utils/withAction.mjs.map +1 -1
  94. package/package.json +90 -90
  95. package/src/components/account/ConfirmDeleteAccountModal.tsx +55 -55
  96. package/src/components/account/DeleteAccountModal.tsx +130 -130
  97. package/src/components/account/IsntPossibleDeleteAccountModal.tsx +49 -49
  98. package/src/components/account/hooks/useChangePhoneForm.tsx +81 -81
  99. package/src/components/account/hooks/useTwoFactorAuthForm.tsx +101 -101
  100. package/src/components/account/sections/ChangePhoneModal.tsx +231 -231
  101. package/src/components/embeds/CrispEmbed.tsx +101 -101
  102. package/src/components/embeds/FrillEmbed.tsx +119 -119
  103. package/src/components/layouts/AppMobileNavBar.tsx +111 -111
  104. package/src/components/layouts/AppNavBar.tsx +57 -57
  105. package/src/components/layouts/NavBar.tsx +120 -120
  106. package/src/components/layouts/UsersSelectorPopover.tsx +238 -238
  107. package/src/components/navigation/CancelledSubscriptionBanner.tsx +69 -69
  108. package/src/components/navigation/NavItemsContext.ts +20 -20
  109. package/src/components/navigation/shells/TabsMobileShell.tsx +72 -72
  110. package/src/components/pages/notifications/Notifications.tsx +160 -160
  111. package/src/components/ui/buttons/Button.tsx +57 -57
  112. package/src/components/ui/buttons/CancelButton.tsx +14 -14
  113. package/src/components/ui/buttons/CopyButton.tsx +54 -54
  114. package/src/components/ui/data-display/Accordion.tsx +81 -81
  115. package/src/components/ui/data-display/Badge.tsx +47 -47
  116. package/src/components/ui/data-display/Card.tsx +93 -93
  117. package/src/components/ui/data-display/Pagination.tsx +126 -126
  118. package/src/components/ui/data-display/ScrollArea.tsx +59 -59
  119. package/src/components/ui/data-display/Separator.tsx +29 -29
  120. package/src/components/ui/data-display/Table.tsx +117 -117
  121. package/src/components/ui/data-display/Tabs.tsx +67 -67
  122. package/src/components/ui/feedback/CircularProgress.tsx +75 -75
  123. package/src/components/ui/feedback/DefaultCircularProgress.tsx +61 -61
  124. package/src/components/ui/feedback/LoadingOverlay.tsx +31 -31
  125. package/src/components/ui/feedback/Progress.tsx +40 -40
  126. package/src/components/ui/feedback/Toast.tsx +101 -101
  127. package/src/components/ui/form/Calendar.tsx +228 -228
  128. package/src/components/ui/form/Checkbox.tsx +31 -31
  129. package/src/components/ui/form/DatePicker.tsx +97 -97
  130. package/src/components/ui/form/DateRangePicker.tsx +108 -108
  131. package/src/components/ui/form/Input.tsx +20 -20
  132. package/src/components/ui/form/InputOtp.tsx +80 -80
  133. package/src/components/ui/form/PhoneInput.tsx +205 -205
  134. package/src/components/ui/form/RadioGroup.tsx +45 -45
  135. package/src/components/ui/form/Select.tsx +200 -200
  136. package/src/components/ui/form/Switch.tsx +32 -32
  137. package/src/components/ui/form/Textarea.tsx +19 -19
  138. package/src/components/ui/overlay/Command.tsx +185 -185
  139. package/src/components/ui/overlay/Dialog.tsx +151 -151
  140. package/src/components/ui/overlay/Popover.tsx +49 -49
  141. package/src/components/ui/overlay/Sheet.tsx +166 -166
  142. package/src/components/ui/overlay/Tooltip.tsx +62 -62
  143. package/src/components/widgets/notifications/NotificationCard.tsx +1 -1
  144. package/src/hooks/copy-to-clipboard.hook.tsx +67 -67
  145. package/src/index.ts +355 -355
  146. package/src/middleware.ts +4 -4
  147. package/src/modules/accounts/actions/account-management.action.ts +90 -90
  148. package/src/modules/accounts/actions/find-current-account.action.ts +9 -9
  149. package/src/modules/accounts/actions/list-currencies.action.ts +8 -8
  150. package/src/modules/accounts/actions/list-timezones.action.ts +8 -8
  151. package/src/modules/accounts/hooks/current-account.hook.tsx +22 -22
  152. package/src/modules/accounts/hooks/useAccountManagement.ts +85 -85
  153. package/src/modules/accounts/hooks/useCurrencies.ts +15 -15
  154. package/src/modules/accounts/hooks/useTimezones.ts +15 -15
  155. package/src/modules/accounts/services/account.service.ts +236 -236
  156. package/src/modules/accounts/types.ts +214 -214
  157. package/src/modules/auth/hooks/login.hook.tsx +13 -13
  158. package/src/modules/auth/hooks/logout.hook.tsx +11 -11
  159. package/src/modules/auth/hooks/register.hook.tsx +11 -11
  160. package/src/modules/ia-credits/actions/list-ia-credits.action.ts +8 -8
  161. package/src/modules/projects/actions/add-project-user.action.ts +9 -9
  162. package/src/modules/projects/actions/list-all-account-users.action.ts +9 -9
  163. package/src/modules/projects/actions/list-available-users.action.ts +9 -9
  164. package/src/modules/projects/actions/list-project-users.action.ts +9 -9
  165. package/src/modules/projects/actions/remove-project-user.action.ts +9 -9
  166. package/src/modules/projects/hooks/add-project-user.hook.ts +69 -69
  167. package/src/modules/projects/hooks/list-all-account-users.hook.ts +34 -34
  168. package/src/modules/projects/hooks/list-available-users.hook.ts +33 -33
  169. package/src/modules/projects/hooks/list-project-users.hook.ts +33 -33
  170. package/src/modules/projects/hooks/remove-project-user.hook.ts +69 -69
  171. package/src/modules/subscriptions/actions/list-subscriptions.action.ts +9 -9
  172. package/src/modules/subscriptions/hooks/find-active-subscription.hook.ts +19 -19
  173. package/src/modules/subscriptions/hooks/list-subscriptions.hook.ts +17 -17
  174. package/src/modules/users/action/find-user-by-id.action.ts +8 -8
  175. package/src/modules/users/action/list-messages.action.ts +12 -12
  176. package/src/modules/users/action/mark-all-messages-as-read.action.ts +8 -8
  177. package/src/modules/users/action/mark-message-as-read.action.ts +8 -8
  178. package/src/modules/users/hooks/messages.hook.ts +60 -60
  179. package/src/modules/whitelabel/actions/find-whitelabel-styles.action.ts +14 -14
  180. package/src/modules/whitelabel/actions/find-whitelabel.action.ts +23 -23
  181. package/src/modules/whitelabel/services/whitelabel-styles.service.ts +98 -98
  182. package/src/modules/whitelabel/styles-schema.ts +37 -37
  183. package/src/providers/whitelabel.provider.tsx +46 -46
  184. package/src/utils/countries.ts +224 -224
  185. package/src/utils/safeServerAction.ts +21 -21
  186. package/src/utils/withAction.ts +13 -13
@@ -1,72 +1,72 @@
1
- 'use client';
2
-
3
- import { ReactNode, useRef, useEffect } from 'react';
4
- import { AppNavigationContext } from '../AppNavigationContext';
5
- import { useNavItemsContext } from '../NavItemsContext';
6
-
7
- function InternalTabsBar({ topOffset = '72px' }: { topOffset?: string }) {
8
- const { navItems } = useNavItemsContext();
9
- const tabsListRef = useRef<HTMLDivElement>(null);
10
- const activeTabRef = useRef<HTMLButtonElement>(null);
11
-
12
- const activeLabel = navItems.find((i) => i.isActive)?.label;
13
-
14
- // Scroll active tab into center when active item changes
15
- useEffect(() => {
16
- if (activeTabRef.current && tabsListRef.current) {
17
- const tabsList = tabsListRef.current;
18
- const activeTabElement = activeTabRef.current;
19
- const tabsListRect = tabsList.getBoundingClientRect();
20
- const activeTabRect = activeTabElement.getBoundingClientRect();
21
- const scrollLeft =
22
- activeTabElement.offsetLeft - tabsListRect.width / 2 + activeTabRect.width / 2;
23
- tabsList.scrollTo({ left: Math.max(0, scrollLeft), behavior: 'smooth' });
24
- }
25
- }, [activeLabel]);
26
-
27
- if (!navItems.length) return null;
28
-
29
- return (
30
- <div
31
- ref={tabsListRef}
32
- className="md:hidden fixed left-0 right-0 z-40 bg-white border-b border-gray-200 flex overflow-x-auto scrollbar-hide"
33
- style={{ top: topOffset }}
34
- >
35
- {navItems.map((item) => (
36
- <button
37
- key={item.label}
38
- ref={item.isActive ? activeTabRef : undefined}
39
- onClick={item.onClick}
40
- className={`flex items-center gap-2 px-4 py-3 shrink-0 border-b-2 transition-colors ${
41
- item.isActive
42
- ? 'border-gray-950 text-gray-950'
43
- : 'border-transparent text-gray-500 hover:text-gray-900'
44
- }`}
45
- >
46
- {item.icon && <item.icon size={16} className="shrink-0" />}
47
- <span
48
- className={
49
- item.isActive ? 'paragraph-small-semibold' : 'paragraph-small-medium'
50
- }
51
- >
52
- {item.label}
53
- </span>
54
- </button>
55
- ))}
56
- </div>
57
- );
58
- }
59
-
60
- export function TabsMobileShell({ children, topOffset }: { children: ReactNode; topOffset?: string }) {
61
- return (
62
- <AppNavigationContext.Provider value={{ breakpoint: 'mobile' }}>
63
- {/*
64
- Children are hidden visually, but NavItems' useEffect still fires inside
65
- display:none — React effects are tied to commit phase, not layout visibility.
66
- This registers navItems in NavItemsContext so InternalTabsBar can read them.
67
- */}
68
- <div className="hidden">{children}</div>
69
- <InternalTabsBar topOffset={topOffset} />
70
- </AppNavigationContext.Provider>
71
- );
72
- }
1
+ 'use client';
2
+
3
+ import { ReactNode, useRef, useEffect } from 'react';
4
+ import { AppNavigationContext } from '../AppNavigationContext';
5
+ import { useNavItemsContext } from '../NavItemsContext';
6
+
7
+ function InternalTabsBar({ topOffset = '72px' }: { topOffset?: string }) {
8
+ const { navItems } = useNavItemsContext();
9
+ const tabsListRef = useRef<HTMLDivElement>(null);
10
+ const activeTabRef = useRef<HTMLButtonElement>(null);
11
+
12
+ const activeLabel = navItems.find((i) => i.isActive)?.label;
13
+
14
+ // Scroll active tab into center when active item changes
15
+ useEffect(() => {
16
+ if (activeTabRef.current && tabsListRef.current) {
17
+ const tabsList = tabsListRef.current;
18
+ const activeTabElement = activeTabRef.current;
19
+ const tabsListRect = tabsList.getBoundingClientRect();
20
+ const activeTabRect = activeTabElement.getBoundingClientRect();
21
+ const scrollLeft =
22
+ activeTabElement.offsetLeft - tabsListRect.width / 2 + activeTabRect.width / 2;
23
+ tabsList.scrollTo({ left: Math.max(0, scrollLeft), behavior: 'smooth' });
24
+ }
25
+ }, [activeLabel]);
26
+
27
+ if (!navItems.length) return null;
28
+
29
+ return (
30
+ <div
31
+ ref={tabsListRef}
32
+ className="md:hidden fixed left-0 right-0 z-40 bg-white border-b border-gray-200 flex overflow-x-auto scrollbar-hide"
33
+ style={{ top: topOffset }}
34
+ >
35
+ {navItems.map((item) => (
36
+ <button
37
+ key={item.label}
38
+ ref={item.isActive ? activeTabRef : undefined}
39
+ onClick={item.onClick}
40
+ className={`flex items-center gap-2 px-4 py-3 shrink-0 border-b-2 transition-colors ${
41
+ item.isActive
42
+ ? 'border-gray-950 text-gray-950'
43
+ : 'border-transparent text-gray-500 hover:text-gray-900'
44
+ }`}
45
+ >
46
+ {item.icon && <item.icon size={16} className="shrink-0" />}
47
+ <span
48
+ className={
49
+ item.isActive ? 'paragraph-small-semibold' : 'paragraph-small-medium'
50
+ }
51
+ >
52
+ {item.label}
53
+ </span>
54
+ </button>
55
+ ))}
56
+ </div>
57
+ );
58
+ }
59
+
60
+ export function TabsMobileShell({ children, topOffset }: { children: ReactNode; topOffset?: string }) {
61
+ return (
62
+ <AppNavigationContext.Provider value={{ breakpoint: 'mobile' }}>
63
+ {/*
64
+ Children are hidden visually, but NavItems' useEffect still fires inside
65
+ display:none — React effects are tied to commit phase, not layout visibility.
66
+ This registers navItems in NavItemsContext so InternalTabsBar can read them.
67
+ */}
68
+ <div className="hidden">{children}</div>
69
+ <InternalTabsBar topOffset={topOffset} />
70
+ </AppNavigationContext.Provider>
71
+ );
72
+ }
@@ -1,160 +1,160 @@
1
- 'use client';
2
-
3
- import { Bell, Check, Loader2 } from "lucide-react";
4
- import { Button } from "../../ui/buttons/Button";
5
- import {
6
- Tabs,
7
- TabsContent,
8
- TabsList,
9
- TabsTrigger,
10
- } from "../../ui/data-display/Tabs";
11
- import { useInfiniteMessages, useMarkAllMessagesAsRead } from "../../../modules/users/hooks/messages.hook";
12
- import NotificationCard from "../../widgets/notifications/NotificationCard";
13
- import { Toast } from "../../ui/feedback/Toast";
14
- import { formatDistanceToNow } from "date-fns";
15
- import { ptBR } from "date-fns/locale";
16
- import { toast } from "sonner";
17
- import React from "react";
18
-
19
- function NotificationsList({
20
- pages,
21
- isFetchingNextPage,
22
- hasNextPage,
23
- fetchNextPage,
24
- }: {
25
- pages: ReturnType<typeof useInfiniteMessages>["data"];
26
- isFetchingNextPage: boolean;
27
- hasNextPage: boolean;
28
- fetchNextPage: () => void;
29
- }) {
30
- const notifications = pages?.pages.flatMap((p) => p.data) ?? [];
31
-
32
- const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
33
- const target = e.currentTarget;
34
- const nearBottom =
35
- target.scrollTop + target.clientHeight >= target.scrollHeight - 50;
36
- if (!nearBottom) return;
37
- if (hasNextPage && !isFetchingNextPage) {
38
- fetchNextPage();
39
- }
40
- };
41
-
42
- return (
43
- <div
44
- className="flex flex-col border border-gray-200 rounded-lg w-full bg-white max-h-[70vh] overflow-y-auto custom-scrollbar"
45
- onScroll={handleScroll}
46
- >
47
- {notifications.map((notification, index) => (
48
- <NotificationCard
49
- key={notification.id}
50
- icon={<Bell size={20} className="text-pages-600" />}
51
- iconBgColor="bg-pages-100"
52
- title={notification.subject}
53
- description={notification.message}
54
- gapPx={16}
55
- time={formatDistanceToNow(new Date(notification.datetime_add), {
56
- addSuffix: true,
57
- locale: ptBR,
58
- })}
59
- isUnread={notification.status === 1}
60
- showBorder={index < notifications.length - 1}
61
- />
62
- ))}
63
- {isFetchingNextPage && (
64
- <div className="flex items-center justify-center py-4">
65
- <Loader2 size={20} className="animate-spin text-gray-400" />
66
- </div>
67
- )}
68
- </div>
69
- );
70
- }
71
-
72
- export function NotificationPageContent() {
73
- const unreadNotifications = useInfiniteMessages({
74
- readed: false,
75
- type: "notification",
76
- order: "desc",
77
- });
78
- const allNotifications = useInfiniteMessages({
79
- type: "notification",
80
- order: "desc",
81
- });
82
-
83
- const totalAll = allNotifications.data?.pages[0]?.total ?? 0;
84
- const totalUnread = unreadNotifications.data?.pages[0]?.total ?? 0;
85
-
86
- const { mutate: markAllAsRead, isPending: isMarkingAll } = useMarkAllMessagesAsRead();
87
-
88
- const handleMarkAllAsRead = () => {
89
- markAllAsRead(undefined, {
90
- onSuccess: () => {
91
- toast.custom((t) => (
92
- <Toast variant="success" message="Todas as notificações foram marcadas como lidas" toastId={t} />
93
- ));
94
- },
95
- onError: () => {
96
- toast.custom((t) => (
97
- <Toast variant="error" message="Erro ao marcar notificações como lidas" toastId={t} />
98
- ));
99
- },
100
- });
101
- };
102
-
103
- return (
104
- <div className="flex flex-col gap-6 lg:gap-10 px-4 py-6 lg:px-20 lg:py-14">
105
- <div className="flex items-center justify-between gap-4">
106
- <div className="flex flex-col gap-2">
107
- <h1 className="paragraph-xlarge-semibold lg:title-large-semibold text-gray-950">
108
- Notificações
109
- </h1>
110
- <p className="paragraph-medium-regular text-gray-600 hidden lg:block">
111
- Tenha controle total sobre páginas, experiências e conversões.
112
- </p>
113
- </div>
114
- <Button
115
- variant={"secondary"}
116
- className="h-10! lg:h-12! shrink-0"
117
- onClick={handleMarkAllAsRead}
118
- disabled={isMarkingAll || totalUnread === 0}
119
- >
120
- {isMarkingAll ? <Loader2 size={20} className="animate-spin" /> : <Check size={20} />}
121
- <span className="lg:block hidden">Marcar todos como lidos</span>
122
- <span className="lg:hidden">Marcar como lidos</span>
123
- </Button>
124
- </div>
125
- <Tabs
126
- defaultValue="all"
127
- className="flex flex-col w-full bg-transparent gap-6 lg:gap-8"
128
- >
129
- <TabsList className="h-auto pb-0 bg-transparent">
130
- <TabsTrigger className="gap-2" value="all">
131
- Todas as notificações{" "}
132
- <span className="text-gray-500">({totalAll})</span>
133
- </TabsTrigger>
134
- <TabsTrigger className="gap-2" value="unreads" disabled={totalUnread === 0}>
135
- Não lidas{" "}
136
- <span className="text-gray-500">({totalUnread})</span>
137
- </TabsTrigger>
138
- </TabsList>
139
-
140
- <TabsContent value="all">
141
- <NotificationsList
142
- pages={allNotifications.data}
143
- isFetchingNextPage={allNotifications.isFetchingNextPage}
144
- hasNextPage={allNotifications.hasNextPage}
145
- fetchNextPage={allNotifications.fetchNextPage}
146
- />
147
- </TabsContent>
148
-
149
- <TabsContent value="unreads">
150
- <NotificationsList
151
- pages={unreadNotifications.data}
152
- isFetchingNextPage={unreadNotifications.isFetchingNextPage}
153
- hasNextPage={unreadNotifications.hasNextPage}
154
- fetchNextPage={unreadNotifications.fetchNextPage}
155
- />
156
- </TabsContent>
157
- </Tabs>
158
- </div>
159
- );
160
- }
1
+ 'use client';
2
+
3
+ import { Bell, Check, Loader2 } from "lucide-react";
4
+ import { Button } from "../../ui/buttons/Button";
5
+ import {
6
+ Tabs,
7
+ TabsContent,
8
+ TabsList,
9
+ TabsTrigger,
10
+ } from "../../ui/data-display/Tabs";
11
+ import { useInfiniteMessages, useMarkAllMessagesAsRead } from "../../../modules/users/hooks/messages.hook";
12
+ import NotificationCard from "../../widgets/notifications/NotificationCard";
13
+ import { Toast } from "../../ui/feedback/Toast";
14
+ import { formatDistanceToNow } from "date-fns";
15
+ import { ptBR } from "date-fns/locale";
16
+ import { toast } from "sonner";
17
+ import React from "react";
18
+
19
+ function NotificationsList({
20
+ pages,
21
+ isFetchingNextPage,
22
+ hasNextPage,
23
+ fetchNextPage,
24
+ }: {
25
+ pages: ReturnType<typeof useInfiniteMessages>["data"];
26
+ isFetchingNextPage: boolean;
27
+ hasNextPage: boolean;
28
+ fetchNextPage: () => void;
29
+ }) {
30
+ const notifications = pages?.pages.flatMap((p) => p.data) ?? [];
31
+
32
+ const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
33
+ const target = e.currentTarget;
34
+ const nearBottom =
35
+ target.scrollTop + target.clientHeight >= target.scrollHeight - 50;
36
+ if (!nearBottom) return;
37
+ if (hasNextPage && !isFetchingNextPage) {
38
+ fetchNextPage();
39
+ }
40
+ };
41
+
42
+ return (
43
+ <div
44
+ className="flex flex-col border border-gray-200 rounded-lg w-full bg-white max-h-[70vh] overflow-y-auto custom-scrollbar"
45
+ onScroll={handleScroll}
46
+ >
47
+ {notifications.map((notification, index) => (
48
+ <NotificationCard
49
+ key={notification.id}
50
+ icon={<Bell size={20} className="text-pages-600" />}
51
+ iconBgColor="bg-pages-100"
52
+ title={notification.subject}
53
+ description={notification.message}
54
+ gapPx={8}
55
+ time={formatDistanceToNow(new Date(notification.datetime_add), {
56
+ addSuffix: true,
57
+ locale: ptBR,
58
+ })}
59
+ isUnread={notification.status === 1}
60
+ showBorder={index < notifications.length - 1}
61
+ />
62
+ ))}
63
+ {isFetchingNextPage && (
64
+ <div className="flex items-center justify-center py-4">
65
+ <Loader2 size={20} className="animate-spin text-gray-400" />
66
+ </div>
67
+ )}
68
+ </div>
69
+ );
70
+ }
71
+
72
+ export function NotificationPageContent() {
73
+ const unreadNotifications = useInfiniteMessages({
74
+ readed: false,
75
+ type: "notification",
76
+ order: "desc",
77
+ });
78
+ const allNotifications = useInfiniteMessages({
79
+ type: "notification",
80
+ order: "desc",
81
+ });
82
+
83
+ const totalAll = allNotifications.data?.pages[0]?.total ?? 0;
84
+ const totalUnread = unreadNotifications.data?.pages[0]?.total ?? 0;
85
+
86
+ const { mutate: markAllAsRead, isPending: isMarkingAll } = useMarkAllMessagesAsRead();
87
+
88
+ const handleMarkAllAsRead = () => {
89
+ markAllAsRead(undefined, {
90
+ onSuccess: () => {
91
+ toast.custom((t) => (
92
+ <Toast variant="success" message="Todas as notificações foram marcadas como lidas" toastId={t} />
93
+ ));
94
+ },
95
+ onError: () => {
96
+ toast.custom((t) => (
97
+ <Toast variant="error" message="Erro ao marcar notificações como lidas" toastId={t} />
98
+ ));
99
+ },
100
+ });
101
+ };
102
+
103
+ return (
104
+ <div className="flex flex-col gap-6 lg:gap-8">
105
+ <div className="flex items-center justify-between gap-4">
106
+ <div className="flex flex-col gap-2">
107
+ <h1 className="paragraph-xlarge-semibold lg:title-large-semibold text-gray-950">
108
+ Notificações
109
+ </h1>
110
+ <p className="paragraph-small-regular text-gray-600 hidden lg:block">
111
+ Tenha controle total sobre páginas, experiências e conversões.
112
+ </p>
113
+ </div>
114
+ <Button
115
+ variant={"secondary"}
116
+ className="h-10! lg:h-12! shrink-0"
117
+ onClick={handleMarkAllAsRead}
118
+ disabled={isMarkingAll || totalUnread === 0}
119
+ >
120
+ {isMarkingAll ? <Loader2 size={20} className="animate-spin" /> : <Check size={20} />}
121
+ <span className="lg:block hidden">Marcar todos como lidos</span>
122
+ <span className="lg:hidden">Marcar como lidos</span>
123
+ </Button>
124
+ </div>
125
+ <Tabs
126
+ defaultValue="all"
127
+ className="flex flex-col w-full bg-transparent gap-6 lg:gap-8"
128
+ >
129
+ <TabsList className="h-auto pb-0 bg-transparent">
130
+ <TabsTrigger className="gap-2" value="all">
131
+ Todas as notificações{" "}
132
+ <span className="text-gray-500">({totalAll})</span>
133
+ </TabsTrigger>
134
+ <TabsTrigger className="gap-2" value="unreads" disabled={totalUnread === 0}>
135
+ Não lidas{" "}
136
+ <span className="text-gray-500">({totalUnread})</span>
137
+ </TabsTrigger>
138
+ </TabsList>
139
+
140
+ <TabsContent value="all">
141
+ <NotificationsList
142
+ pages={allNotifications.data}
143
+ isFetchingNextPage={allNotifications.isFetchingNextPage}
144
+ hasNextPage={allNotifications.hasNextPage}
145
+ fetchNextPage={allNotifications.fetchNextPage}
146
+ />
147
+ </TabsContent>
148
+
149
+ <TabsContent value="unreads">
150
+ <NotificationsList
151
+ pages={unreadNotifications.data}
152
+ isFetchingNextPage={unreadNotifications.isFetchingNextPage}
153
+ hasNextPage={unreadNotifications.hasNextPage}
154
+ fetchNextPage={unreadNotifications.fetchNextPage}
155
+ />
156
+ </TabsContent>
157
+ </Tabs>
158
+ </div>
159
+ );
160
+ }
@@ -1,57 +1,57 @@
1
- 'use client';
2
-
3
- import * as React from "react"
4
- import { Slot } from "@radix-ui/react-slot"
5
- import { cva, type VariantProps } from "class-variance-authority"
6
- import { cn } from "../../../infra/utils/clsx"
7
-
8
- const buttonVariants = cva(
9
- "paragraph-small-semibold cursor-pointer flex items-center justify-center gap-1.5 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
10
- {
11
- variants: {
12
- variant: {
13
- default: " bg-gray-950 text-white hover:bg-gray-950/90",
14
- secondary:
15
- "bg-white text-gray-950 border-gray-200 hover:border-gray-400 border",
16
- ghost:
17
- "text-gray-600 hover:bg-accent hover:text-gray-950 dark:hover:bg-accent/50",
18
- brand: "bg-pages-300 hover:bg-pages-200 text-gray-950",
19
- },
20
- size: {
21
- default: "h-12 px-4 py-2 has-[>svg]:px-3",
22
- sm: "rounded-md gap-1.5 px-2 py-2 has-[>svg]:px-2.5",
23
- lg: "rounded-md px-6 has-[>svg]:px-4",
24
- icon: "size-9",
25
- "icon-sm": "size-8",
26
- "icon-lg": "size-10",
27
- },
28
- },
29
- defaultVariants: {
30
- variant: "default",
31
- size: "default",
32
- },
33
- }
34
- )
35
-
36
- function Button({
37
- className,
38
- variant,
39
- size,
40
- asChild = false,
41
- ...props
42
- }: React.ComponentProps<"button"> &
43
- VariantProps<typeof buttonVariants> & {
44
- asChild?: boolean
45
- }) {
46
- const Comp = asChild ? Slot : "button"
47
-
48
- return (
49
- <Comp
50
- data-slot="button"
51
- className={cn(buttonVariants({ variant, size, className }))}
52
- {...props}
53
- />
54
- )
55
- }
56
-
57
- export { Button, buttonVariants }
1
+ 'use client';
2
+
3
+ import * as React from "react"
4
+ import { Slot } from "@radix-ui/react-slot"
5
+ import { cva, type VariantProps } from "class-variance-authority"
6
+ import { cn } from "../../../infra/utils/clsx"
7
+
8
+ const buttonVariants = cva(
9
+ "paragraph-small-semibold cursor-pointer flex items-center justify-center gap-1.5 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
10
+ {
11
+ variants: {
12
+ variant: {
13
+ default: " bg-gray-950 text-white hover:bg-gray-950/90",
14
+ secondary:
15
+ "bg-white text-gray-950 border-gray-200 hover:border-gray-400 border",
16
+ ghost:
17
+ "text-gray-600 hover:bg-accent hover:text-gray-950 dark:hover:bg-accent/50",
18
+ brand: "bg-pages-300 hover:bg-pages-200 text-gray-950",
19
+ },
20
+ size: {
21
+ default: "h-12 px-4 py-2 has-[>svg]:px-3",
22
+ sm: "rounded-md gap-1.5 px-2 py-2 has-[>svg]:px-2.5",
23
+ lg: "rounded-md px-6 has-[>svg]:px-4",
24
+ icon: "size-9",
25
+ "icon-sm": "size-8",
26
+ "icon-lg": "size-10",
27
+ },
28
+ },
29
+ defaultVariants: {
30
+ variant: "default",
31
+ size: "default",
32
+ },
33
+ }
34
+ )
35
+
36
+ function Button({
37
+ className,
38
+ variant,
39
+ size,
40
+ asChild = false,
41
+ ...props
42
+ }: React.ComponentProps<"button"> &
43
+ VariantProps<typeof buttonVariants> & {
44
+ asChild?: boolean
45
+ }) {
46
+ const Comp = asChild ? Slot : "button"
47
+
48
+ return (
49
+ <Comp
50
+ data-slot="button"
51
+ className={cn(buttonVariants({ variant, size, className }))}
52
+ {...props}
53
+ />
54
+ )
55
+ }
56
+
57
+ export { Button, buttonVariants }
@@ -1,14 +1,14 @@
1
- 'use client';
2
-
3
- import * as React from "react";
4
-
5
- export default function CancelButton({
6
- ...props
7
- }: React.ComponentProps<"button">) {
8
- return (
9
- <button
10
- className="flex h-8 px-2 justify-center items-center rounded-lg bg-red-50 text-red-600 font-semibold text-sm px-3 cursor-pointer hover:bg-red-100"
11
- {...props}
12
- />
13
- );
14
- }
1
+ 'use client';
2
+
3
+ import * as React from "react";
4
+
5
+ export default function CancelButton({
6
+ ...props
7
+ }: React.ComponentProps<"button">) {
8
+ return (
9
+ <button
10
+ className="flex h-8 px-2 justify-center items-center rounded-lg bg-red-50 text-red-600 font-semibold text-sm px-3 cursor-pointer hover:bg-red-100"
11
+ {...props}
12
+ />
13
+ );
14
+ }