@greatapps/common 1.1.81 → 1.1.83

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.
@@ -1,5 +1,5 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
- import { Bell, Check } from "lucide-react";
2
+ import { Bell, Check, Loader2 } from "lucide-react";
3
3
  import { Button } from "../../ui/buttons/Button";
4
4
  import {
5
5
  Tabs,
@@ -7,35 +7,98 @@ import {
7
7
  TabsList,
8
8
  TabsTrigger
9
9
  } from "../../ui/data-display/Tabs";
10
- import { useMessages } from "../../../modules/users/hooks/messages.hook";
10
+ import { useInfiniteMessages, useMarkAllMessagesAsRead } from "../../../modules/users/hooks/messages.hook";
11
11
  import NotificationCard from "../../widgets/notifications/NotificationCard";
12
+ import { Toast } from "../../ui/feedback/Toast";
12
13
  import { formatDistanceToNow } from "date-fns";
13
14
  import { ptBR } from "date-fns/locale";
15
+ import { toast } from "sonner";
16
+ function NotificationsList({
17
+ pages,
18
+ isFetchingNextPage,
19
+ hasNextPage,
20
+ fetchNextPage
21
+ }) {
22
+ const notifications = pages?.pages.flatMap((p) => p.data) ?? [];
23
+ const handleScroll = (e) => {
24
+ const target = e.currentTarget;
25
+ const nearBottom = target.scrollTop + target.clientHeight >= target.scrollHeight - 50;
26
+ if (!nearBottom) return;
27
+ if (hasNextPage && !isFetchingNextPage) {
28
+ fetchNextPage();
29
+ }
30
+ };
31
+ return /* @__PURE__ */ jsxs(
32
+ "div",
33
+ {
34
+ className: "flex flex-col border border-gray-200 rounded-lg w-full bg-white max-h-[70vh] overflow-y-auto custom-scrollbar",
35
+ onScroll: handleScroll,
36
+ children: [
37
+ notifications.map((notification, index) => /* @__PURE__ */ jsx(
38
+ NotificationCard,
39
+ {
40
+ icon: /* @__PURE__ */ jsx(Bell, { size: 20, className: "text-pages-600" }),
41
+ iconBgColor: "bg-pages-100",
42
+ title: notification.subject,
43
+ description: notification.message,
44
+ gapPx: 16,
45
+ time: formatDistanceToNow(new Date(notification.datetime_add), {
46
+ addSuffix: true,
47
+ locale: ptBR
48
+ }),
49
+ isUnread: notification.status === 1,
50
+ showBorder: index < notifications.length - 1
51
+ },
52
+ notification.id
53
+ )),
54
+ isFetchingNextPage && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-4", children: /* @__PURE__ */ jsx(Loader2, { size: 20, className: "animate-spin text-gray-400" }) })
55
+ ]
56
+ }
57
+ );
58
+ }
14
59
  function NotificationPageContent() {
15
- const { data: unreadedNotifications } = useMessages({
16
- page: 1,
17
- limit: 10,
18
- status: 1,
60
+ const unreadNotifications = useInfiniteMessages({
61
+ unread: true,
19
62
  type: "notification",
20
63
  order: "desc"
21
64
  });
22
- const { data: allNotifications } = useMessages({
23
- page: 1,
24
- limit: 10,
65
+ const allNotifications = useInfiniteMessages({
25
66
  type: "notification",
26
67
  order: "desc"
27
68
  });
69
+ const totalAll = allNotifications.data?.pages[0]?.total ?? 0;
70
+ const totalUnread = unreadNotifications.data?.pages[0]?.total ?? 0;
71
+ const { mutate: markAllAsRead, isPending: isMarkingAll } = useMarkAllMessagesAsRead();
72
+ const handleMarkAllAsRead = () => {
73
+ markAllAsRead(void 0, {
74
+ onSuccess: () => {
75
+ toast.custom((t) => /* @__PURE__ */ jsx(Toast, { variant: "success", message: "Todas as notifica\xE7\xF5es foram marcadas como lidas", toastId: t }));
76
+ },
77
+ onError: () => {
78
+ toast.custom((t) => /* @__PURE__ */ jsx(Toast, { variant: "error", message: "Erro ao marcar notifica\xE7\xF5es como lidas", toastId: t }));
79
+ }
80
+ });
81
+ };
28
82
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-6 lg:gap-10 px-4 py-6 lg:px-20 lg:py-14", children: [
29
83
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4", children: [
30
84
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
31
85
  /* @__PURE__ */ jsx("h1", { className: "paragraph-xlarge-semibold lg:title-large-semibold text-gray-950", children: "Notifica\xE7\xF5es" }),
32
86
  /* @__PURE__ */ jsx("p", { className: "paragraph-medium-regular text-gray-600 hidden lg:block", children: "Tenha controle total sobre p\xE1ginas, experi\xEAncias e convers\xF5es." })
33
87
  ] }),
34
- /* @__PURE__ */ jsxs(Button, { variant: "secondary", className: "h-10! lg:h-12! shrink-0", children: [
35
- /* @__PURE__ */ jsx(Check, { size: 20 }),
36
- /* @__PURE__ */ jsx("span", { className: "lg:block hidden", children: "Marcar todos como lidos" }),
37
- /* @__PURE__ */ jsx("span", { className: "lg:hidden", children: "Marcar como lidos" })
38
- ] })
88
+ /* @__PURE__ */ jsxs(
89
+ Button,
90
+ {
91
+ variant: "secondary",
92
+ className: "h-10! lg:h-12! shrink-0",
93
+ onClick: handleMarkAllAsRead,
94
+ disabled: isMarkingAll || totalUnread === 0,
95
+ children: [
96
+ isMarkingAll ? /* @__PURE__ */ jsx(Loader2, { size: 20, className: "animate-spin" }) : /* @__PURE__ */ jsx(Check, { size: 20 }),
97
+ /* @__PURE__ */ jsx("span", { className: "lg:block hidden", children: "Marcar todos como lidos" }),
98
+ /* @__PURE__ */ jsx("span", { className: "lg:hidden", children: "Marcar como lidos" })
99
+ ]
100
+ }
101
+ )
39
102
  ] }),
40
103
  /* @__PURE__ */ jsxs(
41
104
  Tabs,
@@ -49,7 +112,7 @@ function NotificationPageContent() {
49
112
  " ",
50
113
  /* @__PURE__ */ jsxs("span", { className: "text-gray-500", children: [
51
114
  "(",
52
- allNotifications?.data.length,
115
+ totalAll,
53
116
  ")"
54
117
  ] })
55
118
  ] }),
@@ -58,45 +121,29 @@ function NotificationPageContent() {
58
121
  " ",
59
122
  /* @__PURE__ */ jsxs("span", { className: "text-gray-500", children: [
60
123
  "(",
61
- unreadedNotifications?.data.length,
124
+ totalUnread,
62
125
  ")"
63
126
  ] })
64
127
  ] })
65
128
  ] }),
66
- /* @__PURE__ */ jsx(TabsContent, { value: "all", children: /* @__PURE__ */ jsx("div", { className: "flex flex-col border border-gray-200 rounded-lg w-full bg-white", children: allNotifications?.data.map((notification, index) => /* @__PURE__ */ jsx(
67
- NotificationCard,
129
+ /* @__PURE__ */ jsx(TabsContent, { value: "all", children: /* @__PURE__ */ jsx(
130
+ NotificationsList,
68
131
  {
69
- icon: /* @__PURE__ */ jsx(Bell, { size: 20, className: "text-pages-600" }),
70
- iconBgColor: "bg-pages-100",
71
- title: notification.subject,
72
- description: notification.message,
73
- gapPx: 16,
74
- time: formatDistanceToNow(new Date(notification.datetime_add), {
75
- addSuffix: true,
76
- locale: ptBR
77
- }),
78
- isUnread: notification.status === 1,
79
- showBorder: index < allNotifications.data.length - 1
80
- },
81
- notification.id
82
- )) }) }),
83
- /* @__PURE__ */ jsx(TabsContent, { value: "unreads", children: /* @__PURE__ */ jsx("div", { className: "flex flex-col border border-gray-200 rounded-lg w-full bg-white", children: unreadedNotifications?.data.map((notification, index) => /* @__PURE__ */ jsx(
84
- NotificationCard,
132
+ pages: allNotifications.data,
133
+ isFetchingNextPage: allNotifications.isFetchingNextPage,
134
+ hasNextPage: allNotifications.hasNextPage,
135
+ fetchNextPage: allNotifications.fetchNextPage
136
+ }
137
+ ) }),
138
+ /* @__PURE__ */ jsx(TabsContent, { value: "unreads", children: /* @__PURE__ */ jsx(
139
+ NotificationsList,
85
140
  {
86
- icon: /* @__PURE__ */ jsx(Bell, { size: 20, className: "text-pages-600" }),
87
- iconBgColor: "bg-pages-100",
88
- title: notification.subject,
89
- description: notification.message,
90
- gapPx: 16,
91
- time: formatDistanceToNow(new Date(notification.datetime_add), {
92
- addSuffix: true,
93
- locale: ptBR
94
- }),
95
- isUnread: notification.status === 1,
96
- showBorder: index < unreadedNotifications.data.length - 1
97
- },
98
- notification.id
99
- )) }) })
141
+ pages: unreadNotifications.data,
142
+ isFetchingNextPage: unreadNotifications.isFetchingNextPage,
143
+ hasNextPage: unreadNotifications.hasNextPage,
144
+ fetchNextPage: unreadNotifications.fetchNextPage
145
+ }
146
+ ) })
100
147
  ]
101
148
  }
102
149
  )
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/components/pages/notifications/Notifications.tsx"],"sourcesContent":["import { Bell, Check } from \"lucide-react\";\nimport { Button } from \"../../ui/buttons/Button\";\nimport {\n Tabs,\n TabsContent,\n TabsList,\n TabsTrigger,\n} from \"../../ui/data-display/Tabs\";\nimport { useMessages } from \"../../../modules/users/hooks/messages.hook\";\nimport NotificationCard from \"../../widgets/notifications/NotificationCard\";\nimport { formatDistanceToNow } from \"date-fns\";\nimport { ptBR } from \"date-fns/locale\";\n\nexport function NotificationPageContent() {\n const { data: unreadedNotifications } = useMessages({\n page: 1,\n limit: 10,\n status: 1,\n type: \"notification\",\n order: \"desc\",\n });\n const { data: allNotifications } = useMessages({\n page: 1,\n limit: 10,\n type: \"notification\",\n order: \"desc\",\n });\n return (\n <div className=\"flex flex-col gap-6 lg:gap-10 px-4 py-6 lg:px-20 lg:py-14\">\n <div className=\"flex items-center justify-between gap-4\">\n <div className=\"flex flex-col gap-2\">\n <h1 className=\"paragraph-xlarge-semibold lg:title-large-semibold text-gray-950\">\n Notificações\n </h1>\n <p className=\"paragraph-medium-regular text-gray-600 hidden lg:block\">\n Tenha controle total sobre páginas, experiências e conversões.\n </p>\n </div>\n <Button variant={\"secondary\"} className=\"h-10! lg:h-12! shrink-0\">\n <Check size={20} />\n <span className=\"lg:block hidden\">Marcar todos como lidos</span>\n <span className=\"lg:hidden\">Marcar como lidos</span>\n </Button>\n </div>\n <Tabs\n defaultValue=\"all\"\n className=\"flex flex-col w-full bg-transparent gap-6 lg:gap-8\"\n >\n <TabsList className=\"h-auto pb-0 bg-transparent\">\n <TabsTrigger className=\"gap-2\" value=\"all\">\n Todas as notificações{\" \"}\n <span className=\"text-gray-500\">\n ({allNotifications?.data.length})\n </span>\n </TabsTrigger>\n <TabsTrigger className=\"gap-2\" value=\"unreads\">\n Não lidas{\" \"}\n <span className=\"text-gray-500\">\n ({unreadedNotifications?.data.length})\n </span>\n </TabsTrigger>\n </TabsList>\n\n <TabsContent value=\"all\">\n <div className=\"flex flex-col border border-gray-200 rounded-lg w-full bg-white\">\n {allNotifications?.data.map((notification, index) => (\n <NotificationCard\n key={notification.id}\n icon={<Bell size={20} className=\"text-pages-600\" />}\n iconBgColor=\"bg-pages-100\"\n title={notification.subject}\n description={notification.message}\n gapPx={16}\n time={formatDistanceToNow(new Date(notification.datetime_add), {\n addSuffix: true,\n locale: ptBR,\n })}\n isUnread={notification.status === 1}\n showBorder={index < allNotifications.data.length - 1}\n />\n ))}\n </div>\n </TabsContent>\n\n <TabsContent value=\"unreads\">\n <div className=\"flex flex-col border border-gray-200 rounded-lg w-full bg-white\">\n {unreadedNotifications?.data.map((notification, index) => (\n <NotificationCard\n key={notification.id}\n icon={<Bell size={20} className=\"text-pages-600\" />}\n iconBgColor=\"bg-pages-100\"\n title={notification.subject}\n description={notification.message}\n gapPx={16}\n time={formatDistanceToNow(new Date(notification.datetime_add), {\n addSuffix: true,\n locale: ptBR,\n })}\n isUnread={notification.status === 1}\n showBorder={index < unreadedNotifications.data.length - 1}\n />\n ))}\n </div>\n </TabsContent>\n </Tabs>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../src/components/pages/notifications/Notifications.tsx"],"sourcesContent":["import { Bell, Check, Loader2 } from \"lucide-react\";\nimport { Button } from \"../../ui/buttons/Button\";\nimport {\n Tabs,\n TabsContent,\n TabsList,\n TabsTrigger,\n} from \"../../ui/data-display/Tabs\";\nimport { useInfiniteMessages, useMarkAllMessagesAsRead } from \"../../../modules/users/hooks/messages.hook\";\nimport NotificationCard from \"../../widgets/notifications/NotificationCard\";\nimport { Toast } from \"../../ui/feedback/Toast\";\nimport { formatDistanceToNow } from \"date-fns\";\nimport { ptBR } from \"date-fns/locale\";\nimport { toast } from \"sonner\";\nimport React from \"react\";\n\nfunction NotificationsList({\n pages,\n isFetchingNextPage,\n hasNextPage,\n fetchNextPage,\n}: {\n pages: ReturnType<typeof useInfiniteMessages>[\"data\"];\n isFetchingNextPage: boolean;\n hasNextPage: boolean;\n fetchNextPage: () => void;\n}) {\n const notifications = pages?.pages.flatMap((p) => p.data) ?? [];\n\n const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {\n const target = e.currentTarget;\n const nearBottom =\n target.scrollTop + target.clientHeight >= target.scrollHeight - 50;\n if (!nearBottom) return;\n if (hasNextPage && !isFetchingNextPage) {\n fetchNextPage();\n }\n };\n\n return (\n <div\n className=\"flex flex-col border border-gray-200 rounded-lg w-full bg-white max-h-[70vh] overflow-y-auto custom-scrollbar\"\n onScroll={handleScroll}\n >\n {notifications.map((notification, index) => (\n <NotificationCard\n key={notification.id}\n icon={<Bell size={20} className=\"text-pages-600\" />}\n iconBgColor=\"bg-pages-100\"\n title={notification.subject}\n description={notification.message}\n gapPx={16}\n time={formatDistanceToNow(new Date(notification.datetime_add), {\n addSuffix: true,\n locale: ptBR,\n })}\n isUnread={notification.status === 1}\n showBorder={index < notifications.length - 1}\n />\n ))}\n {isFetchingNextPage && (\n <div className=\"flex items-center justify-center py-4\">\n <Loader2 size={20} className=\"animate-spin text-gray-400\" />\n </div>\n )}\n </div>\n );\n}\n\nexport function NotificationPageContent() {\n const unreadNotifications = useInfiniteMessages({\n unread: true,\n type: \"notification\",\n order: \"desc\",\n });\n const allNotifications = useInfiniteMessages({\n type: \"notification\",\n order: \"desc\",\n });\n\n const totalAll = allNotifications.data?.pages[0]?.total ?? 0;\n const totalUnread = unreadNotifications.data?.pages[0]?.total ?? 0;\n\n const { mutate: markAllAsRead, isPending: isMarkingAll } = useMarkAllMessagesAsRead();\n\n const handleMarkAllAsRead = () => {\n markAllAsRead(undefined, {\n onSuccess: () => {\n toast.custom((t) => (\n <Toast variant=\"success\" message=\"Todas as notificações foram marcadas como lidas\" toastId={t} />\n ));\n },\n onError: () => {\n toast.custom((t) => (\n <Toast variant=\"error\" message=\"Erro ao marcar notificações como lidas\" toastId={t} />\n ));\n },\n });\n };\n\n return (\n <div className=\"flex flex-col gap-6 lg:gap-10 px-4 py-6 lg:px-20 lg:py-14\">\n <div className=\"flex items-center justify-between gap-4\">\n <div className=\"flex flex-col gap-2\">\n <h1 className=\"paragraph-xlarge-semibold lg:title-large-semibold text-gray-950\">\n Notificações\n </h1>\n <p className=\"paragraph-medium-regular text-gray-600 hidden lg:block\">\n Tenha controle total sobre páginas, experiências e conversões.\n </p>\n </div>\n <Button\n variant={\"secondary\"}\n className=\"h-10! lg:h-12! shrink-0\"\n onClick={handleMarkAllAsRead}\n disabled={isMarkingAll || totalUnread === 0}\n >\n {isMarkingAll ? <Loader2 size={20} className=\"animate-spin\" /> : <Check size={20} />}\n <span className=\"lg:block hidden\">Marcar todos como lidos</span>\n <span className=\"lg:hidden\">Marcar como lidos</span>\n </Button>\n </div>\n <Tabs\n defaultValue=\"all\"\n className=\"flex flex-col w-full bg-transparent gap-6 lg:gap-8\"\n >\n <TabsList className=\"h-auto pb-0 bg-transparent\">\n <TabsTrigger className=\"gap-2\" value=\"all\">\n Todas as notificações{\" \"}\n <span className=\"text-gray-500\">({totalAll})</span>\n </TabsTrigger>\n <TabsTrigger className=\"gap-2\" value=\"unreads\">\n Não lidas{\" \"}\n <span className=\"text-gray-500\">({totalUnread})</span>\n </TabsTrigger>\n </TabsList>\n\n <TabsContent value=\"all\">\n <NotificationsList\n pages={allNotifications.data}\n isFetchingNextPage={allNotifications.isFetchingNextPage}\n hasNextPage={allNotifications.hasNextPage}\n fetchNextPage={allNotifications.fetchNextPage}\n />\n </TabsContent>\n\n <TabsContent value=\"unreads\">\n <NotificationsList\n pages={unreadNotifications.data}\n isFetchingNextPage={unreadNotifications.isFetchingNextPage}\n hasNextPage={unreadNotifications.hasNextPage}\n fetchNextPage={unreadNotifications.fetchNextPage}\n />\n </TabsContent>\n </Tabs>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -1,16 +1,32 @@
1
1
  "use client";
2
- import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
2
+ import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
3
3
  import { listMessagesAction } from "../action/list-messages.action";
4
4
  import { markMessageAsReadAction } from "../action/mark-message-as-read.action";
5
5
  import { markAllMessagesAsReadAction } from "../action/mark-all-messages-as-read.action";
6
6
  import { safeServerAction } from "../../../utils/safeServerAction";
7
7
  const MESSAGES_QUERY_KEY = ["messages"];
8
+ const INFINITE_MESSAGES_PAGE_SIZE = 10;
8
9
  function useMessages(params) {
9
10
  return useQuery({
10
11
  queryKey: [...MESSAGES_QUERY_KEY, params],
11
12
  queryFn: () => safeServerAction(listMessagesAction, params)
12
13
  });
13
14
  }
15
+ function useInfiniteMessages(params) {
16
+ return useInfiniteQuery({
17
+ queryKey: [...MESSAGES_QUERY_KEY, "infinite", params],
18
+ queryFn: ({ pageParam }) => safeServerAction(listMessagesAction, {
19
+ ...params,
20
+ page: pageParam,
21
+ limit: INFINITE_MESSAGES_PAGE_SIZE
22
+ }),
23
+ getNextPageParam: (lastPage, allPages) => {
24
+ const loaded = allPages.reduce((sum, p) => sum + p.data.length, 0);
25
+ return loaded < lastPage.total ? allPages.length + 1 : void 0;
26
+ },
27
+ initialPageParam: 1
28
+ });
29
+ }
14
30
  function useMarkMessageAsRead() {
15
31
  const queryClient = useQueryClient();
16
32
  return useMutation({
@@ -31,6 +47,7 @@ function useMarkAllMessagesAsRead() {
31
47
  }
32
48
  export {
33
49
  MESSAGES_QUERY_KEY,
50
+ useInfiniteMessages,
34
51
  useMarkAllMessagesAsRead,
35
52
  useMarkMessageAsRead,
36
53
  useMessages
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/modules/users/hooks/messages.hook.ts"],"sourcesContent":["'use client';\r\n\r\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\r\nimport { listMessagesAction } from '../action/list-messages.action';\r\nimport { markMessageAsReadAction } from '../action/mark-message-as-read.action';\r\nimport { markAllMessagesAsReadAction } from '../action/mark-all-messages-as-read.action';\r\nimport { safeServerAction } from '../../../utils/safeServerAction';\r\nimport type { FindMessagesParams } from '../schema/messages.schema';\r\n\r\nexport const MESSAGES_QUERY_KEY = ['messages'];\r\n\r\nexport function useMessages(params?: Partial<FindMessagesParams>) {\r\n return useQuery({\r\n queryKey: [...MESSAGES_QUERY_KEY, params],\r\n queryFn: () => safeServerAction(listMessagesAction, params),\r\n });\r\n}\r\n\r\nexport function useMarkMessageAsRead() {\r\n const queryClient = useQueryClient();\r\n\r\n return useMutation({\r\n mutationFn: (messageId: string) => safeServerAction(markMessageAsReadAction, messageId),\r\n onSuccess: () => {\r\n queryClient.invalidateQueries({ queryKey: MESSAGES_QUERY_KEY });\r\n },\r\n });\r\n}\r\n\r\nexport function useMarkAllMessagesAsRead() {\r\n const queryClient = useQueryClient();\r\n\r\n return useMutation({\r\n mutationFn: () => safeServerAction(markAllMessagesAsReadAction),\r\n onSuccess: () => {\r\n queryClient.invalidateQueries({ queryKey: MESSAGES_QUERY_KEY });\r\n },\r\n });\r\n}\r\n"],"mappings":";AAEA,SAAS,aAAa,UAAU,sBAAsB;AACtD,SAAS,0BAA0B;AACnC,SAAS,+BAA+B;AACxC,SAAS,mCAAmC;AAC5C,SAAS,wBAAwB;AAG1B,MAAM,qBAAqB,CAAC,UAAU;AAEtC,SAAS,YAAY,QAAsC;AAChE,SAAO,SAAS;AAAA,IACd,UAAU,CAAC,GAAG,oBAAoB,MAAM;AAAA,IACxC,SAAS,MAAM,iBAAiB,oBAAoB,MAAM;AAAA,EAC5D,CAAC;AACH;AAEO,SAAS,uBAAuB;AACrC,QAAM,cAAc,eAAe;AAEnC,SAAO,YAAY;AAAA,IACjB,YAAY,CAAC,cAAsB,iBAAiB,yBAAyB,SAAS;AAAA,IACtF,WAAW,MAAM;AACf,kBAAY,kBAAkB,EAAE,UAAU,mBAAmB,CAAC;AAAA,IAChE;AAAA,EACF,CAAC;AACH;AAEO,SAAS,2BAA2B;AACzC,QAAM,cAAc,eAAe;AAEnC,SAAO,YAAY;AAAA,IACjB,YAAY,MAAM,iBAAiB,2BAA2B;AAAA,IAC9D,WAAW,MAAM;AACf,kBAAY,kBAAkB,EAAE,UAAU,mBAAmB,CAAC;AAAA,IAChE;AAAA,EACF,CAAC;AACH;","names":[]}
1
+ {"version":3,"sources":["../../../../src/modules/users/hooks/messages.hook.ts"],"sourcesContent":["'use client';\r\n\r\nimport { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\r\nimport { listMessagesAction } from '../action/list-messages.action';\r\nimport { markMessageAsReadAction } from '../action/mark-message-as-read.action';\r\nimport { markAllMessagesAsReadAction } from '../action/mark-all-messages-as-read.action';\r\nimport { safeServerAction } from '../../../utils/safeServerAction';\r\nimport type { FindMessagesParams } from '../schema/messages.schema';\r\n\r\nexport const MESSAGES_QUERY_KEY = ['messages'];\r\n\r\nconst INFINITE_MESSAGES_PAGE_SIZE = 10;\r\n\r\nexport function useMessages(params?: Partial<FindMessagesParams>) {\r\n return useQuery({\r\n queryKey: [...MESSAGES_QUERY_KEY, params],\r\n queryFn: () => safeServerAction(listMessagesAction, params),\r\n });\r\n}\r\n\r\nexport function useInfiniteMessages(params?: Partial<Omit<FindMessagesParams, 'page' | 'limit'>>) {\r\n return useInfiniteQuery({\r\n queryKey: [...MESSAGES_QUERY_KEY, 'infinite', params],\r\n queryFn: ({ pageParam }) =>\r\n safeServerAction(listMessagesAction, {\r\n ...params,\r\n page: pageParam as number,\r\n limit: INFINITE_MESSAGES_PAGE_SIZE,\r\n }),\r\n getNextPageParam: (lastPage, allPages) => {\r\n const loaded = allPages.reduce((sum, p) => sum + p.data.length, 0);\r\n return loaded < lastPage.total ? allPages.length + 1 : undefined;\r\n },\r\n initialPageParam: 1,\r\n });\r\n}\r\n\r\nexport function useMarkMessageAsRead() {\r\n const queryClient = useQueryClient();\r\n\r\n return useMutation({\r\n mutationFn: (messageId: string) => safeServerAction(markMessageAsReadAction, messageId),\r\n onSuccess: () => {\r\n queryClient.invalidateQueries({ queryKey: MESSAGES_QUERY_KEY });\r\n },\r\n });\r\n}\r\n\r\nexport function useMarkAllMessagesAsRead() {\r\n const queryClient = useQueryClient();\r\n\r\n return useMutation({\r\n mutationFn: () => safeServerAction(markAllMessagesAsReadAction),\r\n onSuccess: () => {\r\n queryClient.invalidateQueries({ queryKey: MESSAGES_QUERY_KEY });\r\n },\r\n });\r\n}\r\n"],"mappings":";AAEA,SAAS,kBAAkB,aAAa,UAAU,sBAAsB;AACxE,SAAS,0BAA0B;AACnC,SAAS,+BAA+B;AACxC,SAAS,mCAAmC;AAC5C,SAAS,wBAAwB;AAG1B,MAAM,qBAAqB,CAAC,UAAU;AAE7C,MAAM,8BAA8B;AAE7B,SAAS,YAAY,QAAsC;AAChE,SAAO,SAAS;AAAA,IACd,UAAU,CAAC,GAAG,oBAAoB,MAAM;AAAA,IACxC,SAAS,MAAM,iBAAiB,oBAAoB,MAAM;AAAA,EAC5D,CAAC;AACH;AAEO,SAAS,oBAAoB,QAA8D;AAChG,SAAO,iBAAiB;AAAA,IACtB,UAAU,CAAC,GAAG,oBAAoB,YAAY,MAAM;AAAA,IACpD,SAAS,CAAC,EAAE,UAAU,MACpB,iBAAiB,oBAAoB;AAAA,MACnC,GAAG;AAAA,MACH,MAAM;AAAA,MACN,OAAO;AAAA,IACT,CAAC;AAAA,IACH,kBAAkB,CAAC,UAAU,aAAa;AACxC,YAAM,SAAS,SAAS,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,KAAK,QAAQ,CAAC;AACjE,aAAO,SAAS,SAAS,QAAQ,SAAS,SAAS,IAAI;AAAA,IACzD;AAAA,IACA,kBAAkB;AAAA,EACpB,CAAC;AACH;AAEO,SAAS,uBAAuB;AACrC,QAAM,cAAc,eAAe;AAEnC,SAAO,YAAY;AAAA,IACjB,YAAY,CAAC,cAAsB,iBAAiB,yBAAyB,SAAS;AAAA,IACtF,WAAW,MAAM;AACf,kBAAY,kBAAkB,EAAE,UAAU,mBAAmB,CAAC;AAAA,IAChE;AAAA,EACF,CAAC;AACH;AAEO,SAAS,2BAA2B;AACzC,QAAM,cAAc,eAAe;AAEnC,SAAO,YAAY;AAAA,IACjB,YAAY,MAAM,iBAAiB,2BAA2B;AAAA,IAC9D,WAAW,MAAM;AACf,kBAAY,kBAAkB,EAAE,UAAU,mBAAmB,CAAC;AAAA,IAChE;AAAA,EACF,CAAC;AACH;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@greatapps/common",
3
- "version": "1.1.81",
3
+ "version": "1.1.83",
4
4
  "description": "Shared library for GreatApps frontend applications",
5
5
  "main": "./dist/index.mjs",
6
6
  "types": "./src/index.ts",
@@ -1,4 +1,4 @@
1
- import { Bell, Check } from "lucide-react";
1
+ import { Bell, Check, Loader2 } from "lucide-react";
2
2
  import { Button } from "../../ui/buttons/Button";
3
3
  import {
4
4
  Tabs,
@@ -6,25 +6,98 @@ import {
6
6
  TabsList,
7
7
  TabsTrigger,
8
8
  } from "../../ui/data-display/Tabs";
9
- import { useMessages } from "../../../modules/users/hooks/messages.hook";
9
+ import { useInfiniteMessages, useMarkAllMessagesAsRead } from "../../../modules/users/hooks/messages.hook";
10
10
  import NotificationCard from "../../widgets/notifications/NotificationCard";
11
+ import { Toast } from "../../ui/feedback/Toast";
11
12
  import { formatDistanceToNow } from "date-fns";
12
13
  import { ptBR } from "date-fns/locale";
14
+ import { toast } from "sonner";
15
+ import React from "react";
16
+
17
+ function NotificationsList({
18
+ pages,
19
+ isFetchingNextPage,
20
+ hasNextPage,
21
+ fetchNextPage,
22
+ }: {
23
+ pages: ReturnType<typeof useInfiniteMessages>["data"];
24
+ isFetchingNextPage: boolean;
25
+ hasNextPage: boolean;
26
+ fetchNextPage: () => void;
27
+ }) {
28
+ const notifications = pages?.pages.flatMap((p) => p.data) ?? [];
29
+
30
+ const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
31
+ const target = e.currentTarget;
32
+ const nearBottom =
33
+ target.scrollTop + target.clientHeight >= target.scrollHeight - 50;
34
+ if (!nearBottom) return;
35
+ if (hasNextPage && !isFetchingNextPage) {
36
+ fetchNextPage();
37
+ }
38
+ };
39
+
40
+ return (
41
+ <div
42
+ className="flex flex-col border border-gray-200 rounded-lg w-full bg-white max-h-[70vh] overflow-y-auto custom-scrollbar"
43
+ onScroll={handleScroll}
44
+ >
45
+ {notifications.map((notification, index) => (
46
+ <NotificationCard
47
+ key={notification.id}
48
+ icon={<Bell size={20} className="text-pages-600" />}
49
+ iconBgColor="bg-pages-100"
50
+ title={notification.subject}
51
+ description={notification.message}
52
+ gapPx={16}
53
+ time={formatDistanceToNow(new Date(notification.datetime_add), {
54
+ addSuffix: true,
55
+ locale: ptBR,
56
+ })}
57
+ isUnread={notification.status === 1}
58
+ showBorder={index < notifications.length - 1}
59
+ />
60
+ ))}
61
+ {isFetchingNextPage && (
62
+ <div className="flex items-center justify-center py-4">
63
+ <Loader2 size={20} className="animate-spin text-gray-400" />
64
+ </div>
65
+ )}
66
+ </div>
67
+ );
68
+ }
13
69
 
14
70
  export function NotificationPageContent() {
15
- const { data: unreadedNotifications } = useMessages({
16
- page: 1,
17
- limit: 10,
18
- status: 1,
71
+ const unreadNotifications = useInfiniteMessages({
72
+ unread: true,
19
73
  type: "notification",
20
74
  order: "desc",
21
75
  });
22
- const { data: allNotifications } = useMessages({
23
- page: 1,
24
- limit: 10,
76
+ const allNotifications = useInfiniteMessages({
25
77
  type: "notification",
26
78
  order: "desc",
27
79
  });
80
+
81
+ const totalAll = allNotifications.data?.pages[0]?.total ?? 0;
82
+ const totalUnread = unreadNotifications.data?.pages[0]?.total ?? 0;
83
+
84
+ const { mutate: markAllAsRead, isPending: isMarkingAll } = useMarkAllMessagesAsRead();
85
+
86
+ const handleMarkAllAsRead = () => {
87
+ markAllAsRead(undefined, {
88
+ onSuccess: () => {
89
+ toast.custom((t) => (
90
+ <Toast variant="success" message="Todas as notificações foram marcadas como lidas" toastId={t} />
91
+ ));
92
+ },
93
+ onError: () => {
94
+ toast.custom((t) => (
95
+ <Toast variant="error" message="Erro ao marcar notificações como lidas" toastId={t} />
96
+ ));
97
+ },
98
+ });
99
+ };
100
+
28
101
  return (
29
102
  <div className="flex flex-col gap-6 lg:gap-10 px-4 py-6 lg:px-20 lg:py-14">
30
103
  <div className="flex items-center justify-between gap-4">
@@ -36,8 +109,13 @@ export function NotificationPageContent() {
36
109
  Tenha controle total sobre páginas, experiências e conversões.
37
110
  </p>
38
111
  </div>
39
- <Button variant={"secondary"} className="h-10! lg:h-12! shrink-0">
40
- <Check size={20} />
112
+ <Button
113
+ variant={"secondary"}
114
+ className="h-10! lg:h-12! shrink-0"
115
+ onClick={handleMarkAllAsRead}
116
+ disabled={isMarkingAll || totalUnread === 0}
117
+ >
118
+ {isMarkingAll ? <Loader2 size={20} className="animate-spin" /> : <Check size={20} />}
41
119
  <span className="lg:block hidden">Marcar todos como lidos</span>
42
120
  <span className="lg:hidden">Marcar como lidos</span>
43
121
  </Button>
@@ -49,58 +127,30 @@ export function NotificationPageContent() {
49
127
  <TabsList className="h-auto pb-0 bg-transparent">
50
128
  <TabsTrigger className="gap-2" value="all">
51
129
  Todas as notificações{" "}
52
- <span className="text-gray-500">
53
- ({allNotifications?.data.length})
54
- </span>
130
+ <span className="text-gray-500">({totalAll})</span>
55
131
  </TabsTrigger>
56
132
  <TabsTrigger className="gap-2" value="unreads">
57
133
  Não lidas{" "}
58
- <span className="text-gray-500">
59
- ({unreadedNotifications?.data.length})
60
- </span>
134
+ <span className="text-gray-500">({totalUnread})</span>
61
135
  </TabsTrigger>
62
136
  </TabsList>
63
137
 
64
138
  <TabsContent value="all">
65
- <div className="flex flex-col border border-gray-200 rounded-lg w-full bg-white">
66
- {allNotifications?.data.map((notification, index) => (
67
- <NotificationCard
68
- key={notification.id}
69
- icon={<Bell size={20} className="text-pages-600" />}
70
- iconBgColor="bg-pages-100"
71
- title={notification.subject}
72
- description={notification.message}
73
- gapPx={16}
74
- time={formatDistanceToNow(new Date(notification.datetime_add), {
75
- addSuffix: true,
76
- locale: ptBR,
77
- })}
78
- isUnread={notification.status === 1}
79
- showBorder={index < allNotifications.data.length - 1}
80
- />
81
- ))}
82
- </div>
139
+ <NotificationsList
140
+ pages={allNotifications.data}
141
+ isFetchingNextPage={allNotifications.isFetchingNextPage}
142
+ hasNextPage={allNotifications.hasNextPage}
143
+ fetchNextPage={allNotifications.fetchNextPage}
144
+ />
83
145
  </TabsContent>
84
146
 
85
147
  <TabsContent value="unreads">
86
- <div className="flex flex-col border border-gray-200 rounded-lg w-full bg-white">
87
- {unreadedNotifications?.data.map((notification, index) => (
88
- <NotificationCard
89
- key={notification.id}
90
- icon={<Bell size={20} className="text-pages-600" />}
91
- iconBgColor="bg-pages-100"
92
- title={notification.subject}
93
- description={notification.message}
94
- gapPx={16}
95
- time={formatDistanceToNow(new Date(notification.datetime_add), {
96
- addSuffix: true,
97
- locale: ptBR,
98
- })}
99
- isUnread={notification.status === 1}
100
- showBorder={index < unreadedNotifications.data.length - 1}
101
- />
102
- ))}
103
- </div>
148
+ <NotificationsList
149
+ pages={unreadNotifications.data}
150
+ isFetchingNextPage={unreadNotifications.isFetchingNextPage}
151
+ hasNextPage={unreadNotifications.hasNextPage}
152
+ fetchNextPage={unreadNotifications.fetchNextPage}
153
+ />
104
154
  </TabsContent>
105
155
  </Tabs>
106
156
  </div>
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
3
+ import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
4
4
  import { listMessagesAction } from '../action/list-messages.action';
5
5
  import { markMessageAsReadAction } from '../action/mark-message-as-read.action';
6
6
  import { markAllMessagesAsReadAction } from '../action/mark-all-messages-as-read.action';
@@ -9,6 +9,8 @@ import type { FindMessagesParams } from '../schema/messages.schema';
9
9
 
10
10
  export const MESSAGES_QUERY_KEY = ['messages'];
11
11
 
12
+ const INFINITE_MESSAGES_PAGE_SIZE = 10;
13
+
12
14
  export function useMessages(params?: Partial<FindMessagesParams>) {
13
15
  return useQuery({
14
16
  queryKey: [...MESSAGES_QUERY_KEY, params],
@@ -16,6 +18,23 @@ export function useMessages(params?: Partial<FindMessagesParams>) {
16
18
  });
17
19
  }
18
20
 
21
+ export function useInfiniteMessages(params?: Partial<Omit<FindMessagesParams, 'page' | 'limit'>>) {
22
+ return useInfiniteQuery({
23
+ queryKey: [...MESSAGES_QUERY_KEY, 'infinite', params],
24
+ queryFn: ({ pageParam }) =>
25
+ safeServerAction(listMessagesAction, {
26
+ ...params,
27
+ page: pageParam as number,
28
+ limit: INFINITE_MESSAGES_PAGE_SIZE,
29
+ }),
30
+ getNextPageParam: (lastPage, allPages) => {
31
+ const loaded = allPages.reduce((sum, p) => sum + p.data.length, 0);
32
+ return loaded < lastPage.total ? allPages.length + 1 : undefined;
33
+ },
34
+ initialPageParam: 1,
35
+ });
36
+ }
37
+
19
38
  export function useMarkMessageAsRead() {
20
39
  const queryClient = useQueryClient();
21
40