@greatapps/common 1.1.71 → 1.1.75

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 (56) hide show
  1. package/dist/components/layouts/AppNavBar.mjs +0 -2
  2. package/dist/components/layouts/AppNavBar.mjs.map +1 -1
  3. package/dist/components/layouts/NotificationsPopover.mjs +24 -14
  4. package/dist/components/layouts/NotificationsPopover.mjs.map +1 -1
  5. package/dist/components/pages/notifications/Notifications.mjs +106 -0
  6. package/dist/components/pages/notifications/Notifications.mjs.map +1 -0
  7. package/dist/components/widgets/notifications/NotificationCard.mjs +53 -0
  8. package/dist/components/widgets/notifications/NotificationCard.mjs.map +1 -0
  9. package/dist/index.mjs +177 -47
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/modules/auth/actions/login.action.mjs +1 -1
  12. package/dist/modules/auth/actions/login.action.mjs.map +1 -1
  13. package/dist/modules/auth/hooks/login.hook.mjs +5 -1
  14. package/dist/modules/auth/hooks/login.hook.mjs.map +1 -1
  15. package/dist/modules/auth/hooks/useUserQuery.mjs +2 -1
  16. package/dist/modules/auth/hooks/useUserQuery.mjs.map +1 -1
  17. package/dist/modules/auth/services/auth.service.mjs +2 -2
  18. package/dist/modules/auth/services/auth.service.mjs.map +1 -1
  19. package/dist/modules/users/action/list-messages.action.mjs +9 -0
  20. package/dist/modules/users/action/list-messages.action.mjs.map +1 -0
  21. package/dist/modules/users/action/mark-all-messages-as-read.action.mjs +9 -0
  22. package/dist/modules/users/action/mark-all-messages-as-read.action.mjs.map +1 -0
  23. package/dist/modules/users/action/mark-message-as-read.action.mjs +9 -0
  24. package/dist/modules/users/action/mark-message-as-read.action.mjs.map +1 -0
  25. package/dist/modules/users/hooks/messages.hook.mjs +38 -0
  26. package/dist/modules/users/hooks/messages.hook.mjs.map +1 -0
  27. package/dist/modules/users/schema/messages.schema.mjs +21 -0
  28. package/dist/modules/users/schema/messages.schema.mjs.map +1 -0
  29. package/dist/modules/users/services/messages.service.mjs +35 -0
  30. package/dist/modules/users/services/messages.service.mjs.map +1 -0
  31. package/dist/providers/auth.provider.mjs +3 -5
  32. package/dist/providers/auth.provider.mjs.map +1 -1
  33. package/dist/utils/safeServerAction.mjs +22 -0
  34. package/dist/utils/safeServerAction.mjs.map +1 -0
  35. package/package.json +1 -1
  36. package/src/components/layouts/AppNavBar.tsx +0 -4
  37. package/src/components/layouts/NotificationsPopover.tsx +27 -24
  38. package/src/components/pages/notifications/Notifications.tsx +106 -0
  39. package/src/components/widgets/notifications/NotificationCard.tsx +66 -0
  40. package/src/index.ts +267 -117
  41. package/src/modules/auth/actions/login.action.ts +1 -1
  42. package/src/modules/auth/hooks/login.hook.tsx +6 -1
  43. package/src/modules/auth/hooks/useUserQuery.ts +2 -1
  44. package/src/modules/auth/schema.ts +6 -4
  45. package/src/modules/auth/services/auth.service.ts +2 -2
  46. package/src/modules/users/action/list-messages.action.ts +10 -0
  47. package/src/modules/users/action/mark-all-messages-as-read.action.ts +8 -0
  48. package/src/modules/users/action/mark-message-as-read.action.ts +8 -0
  49. package/src/modules/users/hooks/messages.hook.ts +39 -0
  50. package/src/modules/users/schema/messages.schema.ts +47 -0
  51. package/src/modules/users/services/messages.service.ts +48 -0
  52. package/src/providers/auth.provider.tsx +6 -9
  53. package/src/utils/safeServerAction.ts +27 -0
  54. package/dist/components/layouts/NotificationItem.mjs +0 -14
  55. package/dist/components/layouts/NotificationItem.mjs.map +0 -1
  56. package/src/components/layouts/NotificationItem.tsx +0 -26
@@ -0,0 +1,9 @@
1
+ "use server";
2
+ import { messagesService } from "../services/messages.service";
3
+ async function markMessageAsReadAction(messageId) {
4
+ return messagesService.markAsRead(messageId);
5
+ }
6
+ export {
7
+ markMessageAsReadAction
8
+ };
9
+ //# sourceMappingURL=mark-message-as-read.action.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/modules/users/action/mark-message-as-read.action.ts"],"sourcesContent":["'use server';\r\n\r\nimport { messagesService } from '../services/messages.service';\r\nimport type { MarkAsReadApiResponse } from '../schema/messages.schema';\r\n\r\nexport async function markMessageAsReadAction(messageId: string): Promise<MarkAsReadApiResponse> {\r\n return messagesService.markAsRead(messageId);\r\n}\r\n"],"mappings":";AAEA,SAAS,uBAAuB;AAGhC,eAAsB,wBAAwB,WAAmD;AAC/F,SAAO,gBAAgB,WAAW,SAAS;AAC7C;","names":[]}
@@ -0,0 +1,38 @@
1
+ "use client";
2
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
3
+ import { listMessagesAction } from "../action/list-messages.action";
4
+ import { markMessageAsReadAction } from "../action/mark-message-as-read.action";
5
+ import { markAllMessagesAsReadAction } from "../action/mark-all-messages-as-read.action";
6
+ import { safeServerAction } from "../../../utils/safeServerAction";
7
+ const MESSAGES_QUERY_KEY = ["messages"];
8
+ function useMessages(params) {
9
+ return useQuery({
10
+ queryKey: [...MESSAGES_QUERY_KEY, params],
11
+ queryFn: () => safeServerAction(listMessagesAction, params)
12
+ });
13
+ }
14
+ function useMarkMessageAsRead() {
15
+ const queryClient = useQueryClient();
16
+ return useMutation({
17
+ mutationFn: (messageId) => safeServerAction(markMessageAsReadAction, messageId),
18
+ onSuccess: () => {
19
+ queryClient.invalidateQueries({ queryKey: MESSAGES_QUERY_KEY });
20
+ }
21
+ });
22
+ }
23
+ function useMarkAllMessagesAsRead() {
24
+ const queryClient = useQueryClient();
25
+ return useMutation({
26
+ mutationFn: () => safeServerAction(markAllMessagesAsReadAction),
27
+ onSuccess: () => {
28
+ queryClient.invalidateQueries({ queryKey: MESSAGES_QUERY_KEY });
29
+ }
30
+ });
31
+ }
32
+ export {
33
+ MESSAGES_QUERY_KEY,
34
+ useMarkAllMessagesAsRead,
35
+ useMarkMessageAsRead,
36
+ useMessages
37
+ };
38
+ //# sourceMappingURL=messages.hook.mjs.map
@@ -0,0 +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":[]}
@@ -0,0 +1,21 @@
1
+ import { z } from "zod";
2
+ const MessageSchema = z.object({
3
+ id: z.string(),
4
+ deleted: z.number(),
5
+ datetime_alt: z.string().nullable(),
6
+ datetime_del: z.string().nullable(),
7
+ datetime_add: z.string(),
8
+ id_wl: z.number(),
9
+ id_account: z.number(),
10
+ id_user: z.number(),
11
+ status: z.number().nullable(),
12
+ type: z.string(),
13
+ subject: z.string(),
14
+ message: z.string(),
15
+ to: z.string(),
16
+ url: z.string()
17
+ });
18
+ export {
19
+ MessageSchema
20
+ };
21
+ //# sourceMappingURL=messages.schema.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/modules/users/schema/messages.schema.ts"],"sourcesContent":["import { z } from 'zod';\r\n\r\nexport const MessageSchema = z.object({\r\n id: z.string(),\r\n deleted: z.number(),\r\n datetime_alt: z.string().nullable(),\r\n datetime_del: z.string().nullable(),\r\n datetime_add: z.string(),\r\n id_wl: z.number(),\r\n id_account: z.number(),\r\n id_user: z.number(),\r\n status: z.number().nullable(),\r\n type: z.string(),\r\n subject: z.string(),\r\n message: z.string(),\r\n to: z.string(),\r\n url: z.string(),\r\n});\r\n\r\nexport type Message = z.infer<typeof MessageSchema>;\r\n\r\nexport interface FindMessagesParams {\r\n id_user: number;\r\n type: string;\r\n limit: number;\r\n page: number;\r\n status: number;\r\n order: 'asc' | 'desc';\r\n}\r\n\r\nexport interface ListMessagesApiResponse {\r\n status: 0 | 1;\r\n total: number;\r\n data: Message[];\r\n totalUnread: number;\r\n message?: string;\r\n}\r\n\r\nexport interface MarkAsReadApiResponse {\r\n status: 0 | 1;\r\n message?: string;\r\n}\r\n\r\nexport interface MarkAllAsReadApiResponse {\r\n status: 0 | 1;\r\n message?: string;\r\n}\r\n"],"mappings":"AAAA,SAAS,SAAS;AAEX,MAAM,gBAAgB,EAAE,OAAO;AAAA,EACpC,IAAI,EAAE,OAAO;AAAA,EACb,SAAS,EAAE,OAAO;AAAA,EAClB,cAAc,EAAE,OAAO,EAAE,SAAS;AAAA,EAClC,cAAc,EAAE,OAAO,EAAE,SAAS;AAAA,EAClC,cAAc,EAAE,OAAO;AAAA,EACvB,OAAO,EAAE,OAAO;AAAA,EAChB,YAAY,EAAE,OAAO;AAAA,EACrB,SAAS,EAAE,OAAO;AAAA,EAClB,QAAQ,EAAE,OAAO,EAAE,SAAS;AAAA,EAC5B,MAAM,EAAE,OAAO;AAAA,EACf,SAAS,EAAE,OAAO;AAAA,EAClB,SAAS,EAAE,OAAO;AAAA,EAClB,IAAI,EAAE,OAAO;AAAA,EACb,KAAK,EAAE,OAAO;AAChB,CAAC;","names":[]}
@@ -0,0 +1,35 @@
1
+ import "server-only";
2
+ import { api } from "../../../infra/api/client";
3
+ import { buildQueryParams } from "../../../infra/utils/params";
4
+ import { getUserContext } from "../../auth/utils/get-user-context";
5
+ class MessagesService {
6
+ async listMessages(params) {
7
+ const { id_account, id_user } = await getUserContext();
8
+ const query = buildQueryParams({
9
+ id_account,
10
+ id_user,
11
+ type: "notification",
12
+ limit: 20,
13
+ page: 1,
14
+ order: "desc",
15
+ ...params
16
+ });
17
+ const url = `/accounts/${id_account}/messages${query ? `?${query}` : ""}`;
18
+ return api.apps.get(url);
19
+ }
20
+ async markAsRead(messageId) {
21
+ const { id_account } = await getUserContext();
22
+ const url = `/accounts/${id_account}/messages/${messageId}/action/markAsRead`;
23
+ return api.apps.put(url);
24
+ }
25
+ async markAllAsRead() {
26
+ const { id_account, id_user } = await getUserContext();
27
+ const url = `/accounts/${id_account}/messages/action/markAllAsRead`;
28
+ return api.apps.put(url, { id_user });
29
+ }
30
+ }
31
+ const messagesService = new MessagesService();
32
+ export {
33
+ messagesService
34
+ };
35
+ //# sourceMappingURL=messages.service.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/modules/users/services/messages.service.ts"],"sourcesContent":["import 'server-only';\n\nimport { api } from '../../../infra/api/client';\nimport { buildQueryParams } from '../../../infra/utils/params';\nimport { getUserContext } from '../../auth/utils/get-user-context';\nimport {\n FindMessagesParams,\n ListMessagesApiResponse,\n MarkAsReadApiResponse,\n MarkAllAsReadApiResponse,\n} from '../schema/messages.schema';\n\nclass MessagesService {\n async listMessages(\n params?: Partial<FindMessagesParams>\n ): Promise<ListMessagesApiResponse> {\n const { id_account, id_user } = await getUserContext();\n\n const query = buildQueryParams({\n id_account,\n id_user,\n type: 'notification',\n limit: 20,\n page: 1,\n order: 'desc',\n ...params,\n });\n\n const url = `/accounts/${id_account}/messages${query ? `?${query}` : ''}`;\n return api.apps.get<ListMessagesApiResponse>(url);\n }\n\n async markAsRead(messageId: string): Promise<MarkAsReadApiResponse> {\n const { id_account } = await getUserContext();\n\n const url = `/accounts/${id_account}/messages/${messageId}/action/markAsRead`;\n return api.apps.put<MarkAsReadApiResponse>(url);\n }\n\n async markAllAsRead(): Promise<MarkAllAsReadApiResponse> {\n const { id_account, id_user } = await getUserContext();\n\n const url = `/accounts/${id_account}/messages/action/markAllAsRead`;\n return api.apps.put<MarkAllAsReadApiResponse>(url, { id_user });\n }\n}\n\nexport const messagesService = new MessagesService();\n"],"mappings":"AAAA,OAAO;AAEP,SAAS,WAAW;AACpB,SAAS,wBAAwB;AACjC,SAAS,sBAAsB;AAQ/B,MAAM,gBAAgB;AAAA,EACpB,MAAM,aACJ,QACkC;AAClC,UAAM,EAAE,YAAY,QAAQ,IAAI,MAAM,eAAe;AAErD,UAAM,QAAQ,iBAAiB;AAAA,MAC7B;AAAA,MACA;AAAA,MACA,MAAM;AAAA,MACN,OAAO;AAAA,MACP,MAAM;AAAA,MACN,OAAO;AAAA,MACP,GAAG;AAAA,IACL,CAAC;AAED,UAAM,MAAM,aAAa,UAAU,YAAY,QAAQ,IAAI,KAAK,KAAK,EAAE;AACvE,WAAO,IAAI,KAAK,IAA6B,GAAG;AAAA,EAClD;AAAA,EAEA,MAAM,WAAW,WAAmD;AAClE,UAAM,EAAE,WAAW,IAAI,MAAM,eAAe;AAE5C,UAAM,MAAM,aAAa,UAAU,aAAa,SAAS;AACzD,WAAO,IAAI,KAAK,IAA2B,GAAG;AAAA,EAChD;AAAA,EAEA,MAAM,gBAAmD;AACvD,UAAM,EAAE,YAAY,QAAQ,IAAI,MAAM,eAAe;AAErD,UAAM,MAAM,aAAa,UAAU;AACnC,WAAO,IAAI,KAAK,IAA8B,KAAK,EAAE,QAAQ,CAAC;AAAA,EAChE;AACF;AAEO,MAAM,kBAAkB,IAAI,gBAAgB;","names":[]}
@@ -39,15 +39,13 @@ function AuthProvider({ children }) {
39
39
  const login = useCallback(
40
40
  async (credentials) => {
41
41
  const data = await loginMutate(credentials);
42
- if (data.status == 0)
43
- return { status: "error", message: data.message || "Login failed" };
44
- if (data.twoFactorRequired) {
45
- return { status: "two_factor_required", cookie: data.cookie };
42
+ if (data.result === "two_factor_required") {
43
+ return { result: "two_factor_required", cookie: data.cookie };
46
44
  }
47
45
  invalidateUser();
48
46
  invalidateAccount();
49
47
  setUserData(data.user);
50
- return { status: "success" };
48
+ return { result: "success" };
51
49
  },
52
50
  [setUserData, invalidateUser, invalidateAccount]
53
51
  );
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/providers/auth.provider.tsx"],"sourcesContent":["\"use client\";\r\n\r\nimport {\r\n createContext,\r\n ReactNode,\r\n useCallback,\r\n useContext,\r\n useMemo,\r\n} from \"react\";\r\nimport {\r\n useUserQuery,\r\n useSetUserData,\r\n useInvalidateUser,\r\n useUserValidateSession,\r\n} from \"../modules/auth/hooks/useUserQuery\";\r\nimport { useLogin } from \"../modules/auth/hooks/login.hook\";\r\nimport { useRegister } from \"../modules/auth/hooks/register.hook\";\r\nimport { useLogout } from \"../modules/auth/hooks/logout.hook\";\r\nimport {\r\n AuthState,\r\n LoginRequest,\r\n RegisterRequest,\r\n} from \"../modules/auth/schema\";\r\n\r\nexport type LoginResult =\r\n | { status: \"success\" }\r\n | { status: \"two_factor_required\"; cookie: string }\r\n | { status: \"error\"; message: string };\r\nimport {\r\n useCurrentAccount,\r\n useInvalidateAccount,\r\n} from \"../modules/accounts/hooks/current-account.hook\";\r\nimport { useRouter } from \"next/navigation\";\r\n\r\ninterface AuthContextProps extends AuthState {\r\n login: (credentials: LoginRequest) => Promise<LoginResult>;\r\n register: (data: RegisterRequest) => Promise<void>;\r\n logout: () => Promise<void>;\r\n}\r\n\r\nexport const AuthContext = createContext<AuthContextProps | undefined>(\r\n undefined\r\n);\r\n\r\ninterface AuthProviderProps {\r\n children: ReactNode;\r\n}\r\n\r\nexport function AuthProvider({ children }: AuthProviderProps) {\r\n const { data: user, isLoading: isQueryLoading } = useUserQuery();\r\n const { data: account, isLoading: isAccountLoading } = useCurrentAccount();\r\n const { isLoading: isSessionLoading } = useUserValidateSession();\r\n\r\n const setUserData = useSetUserData();\r\n const invalidateUser = useInvalidateUser();\r\n const invalidateAccount = useInvalidateAccount();\r\n\r\n const router = useRouter();\r\n\r\n const { mutateAsync: loginMutate, isPending: isLogging } = useLogin();\r\n const { mutateAsync: registerMutate, isPending: isRegistering } =\r\n useRegister();\r\n const { mutateAsync: logoutMutate, isPending: isLoggingOut } = useLogout();\r\n\r\n const isAuthenticated = !!user;\r\n const isLoading =\r\n isLogging ||\r\n isRegistering ||\r\n isLoggingOut ||\r\n isQueryLoading ||\r\n isAccountLoading ||\r\n isSessionLoading;\r\n\r\n const login = useCallback(\r\n async (credentials: LoginRequest): Promise<LoginResult> => {\r\n const data = await loginMutate(credentials);\r\n\r\n if (data.status == 0)\r\n return { status: \"error\", message: data.message || \"Login failed\" };\r\n\r\n if (data.twoFactorRequired) {\r\n return { status: \"two_factor_required\", cookie: data.cookie };\r\n }\r\n\r\n invalidateUser();\r\n invalidateAccount();\r\n\r\n setUserData(data.user);\r\n\r\n return { status: \"success\" };\r\n },\r\n [setUserData, invalidateUser, invalidateAccount]\r\n );\r\n\r\n const register = useCallback(\r\n async (data: RegisterRequest): Promise<void> => {\r\n await registerMutate(data, {\r\n onSuccess: (data) => {\r\n invalidateUser();\r\n invalidateAccount();\r\n setUserData(data.user);\r\n },\r\n });\r\n },\r\n [setUserData, invalidateUser, invalidateAccount]\r\n );\r\n\r\n const logout = useCallback(async (): Promise<void> => {\r\n await logoutMutate(undefined, {\r\n onSuccess: () => {\r\n invalidateUser();\r\n router.push(\"/login\");\r\n },\r\n });\r\n }, [setUserData]);\r\n\r\n const value: AuthContextProps = useMemo(\r\n () => ({\r\n user: user ?? null,\r\n account: account ?? null,\r\n isAuthenticated,\r\n isLoading,\r\n login,\r\n register,\r\n logout,\r\n }),\r\n [user, isAuthenticated, isLoading, login, register, logout]\r\n );\r\n\r\n return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;\r\n}\r\n\r\nexport function useAuth(): AuthContextProps {\r\n const context = useContext(AuthContext);\r\n\r\n if (context === undefined) {\r\n throw new Error(\"useAuth deve ser usado dentro de um AuthProvider\");\r\n }\r\n\r\n return context;\r\n}\r\n"],"mappings":";AAiIS;AA/HT;AAAA,EACE;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,gBAAgB;AACzB,SAAS,mBAAmB;AAC5B,SAAS,iBAAiB;AAW1B;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,iBAAiB;AAQnB,MAAM,cAAc;AAAA,EACzB;AACF;AAMO,SAAS,aAAa,EAAE,SAAS,GAAsB;AAC5D,QAAM,EAAE,MAAM,MAAM,WAAW,eAAe,IAAI,aAAa;AAC/D,QAAM,EAAE,MAAM,SAAS,WAAW,iBAAiB,IAAI,kBAAkB;AACzE,QAAM,EAAE,WAAW,iBAAiB,IAAI,uBAAuB;AAE/D,QAAM,cAAc,eAAe;AACnC,QAAM,iBAAiB,kBAAkB;AACzC,QAAM,oBAAoB,qBAAqB;AAE/C,QAAM,SAAS,UAAU;AAEzB,QAAM,EAAE,aAAa,aAAa,WAAW,UAAU,IAAI,SAAS;AACpE,QAAM,EAAE,aAAa,gBAAgB,WAAW,cAAc,IAC5D,YAAY;AACd,QAAM,EAAE,aAAa,cAAc,WAAW,aAAa,IAAI,UAAU;AAEzE,QAAM,kBAAkB,CAAC,CAAC;AAC1B,QAAM,YACJ,aACA,iBACA,gBACA,kBACA,oBACA;AAEF,QAAM,QAAQ;AAAA,IACZ,OAAO,gBAAoD;AACzD,YAAM,OAAO,MAAM,YAAY,WAAW;AAE1C,UAAI,KAAK,UAAU;AACjB,eAAO,EAAE,QAAQ,SAAS,SAAS,KAAK,WAAW,eAAe;AAEpE,UAAI,KAAK,mBAAmB;AAC1B,eAAO,EAAE,QAAQ,uBAAuB,QAAQ,KAAK,OAAO;AAAA,MAC9D;AAEA,qBAAe;AACf,wBAAkB;AAElB,kBAAY,KAAK,IAAI;AAErB,aAAO,EAAE,QAAQ,UAAU;AAAA,IAC7B;AAAA,IACA,CAAC,aAAa,gBAAgB,iBAAiB;AAAA,EACjD;AAEA,QAAM,WAAW;AAAA,IACf,OAAO,SAAyC;AAC9C,YAAM,eAAe,MAAM;AAAA,QACzB,WAAW,CAACA,UAAS;AACnB,yBAAe;AACf,4BAAkB;AAClB,sBAAYA,MAAK,IAAI;AAAA,QACvB;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IACA,CAAC,aAAa,gBAAgB,iBAAiB;AAAA,EACjD;AAEA,QAAM,SAAS,YAAY,YAA2B;AACpD,UAAM,aAAa,QAAW;AAAA,MAC5B,WAAW,MAAM;AACf,uBAAe;AACf,eAAO,KAAK,QAAQ;AAAA,MACtB;AAAA,IACF,CAAC;AAAA,EACH,GAAG,CAAC,WAAW,CAAC;AAEhB,QAAM,QAA0B;AAAA,IAC9B,OAAO;AAAA,MACL,MAAM,QAAQ;AAAA,MACd,SAAS,WAAW;AAAA,MACpB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,CAAC,MAAM,iBAAiB,WAAW,OAAO,UAAU,MAAM;AAAA,EAC5D;AAEA,SAAO,oBAAC,YAAY,UAAZ,EAAqB,OAAe,UAAS;AACvD;AAEO,SAAS,UAA4B;AAC1C,QAAM,UAAU,WAAW,WAAW;AAEtC,MAAI,YAAY,QAAW;AACzB,UAAM,IAAI,MAAM,kDAAkD;AAAA,EACpE;AAEA,SAAO;AACT;","names":["data"]}
1
+ {"version":3,"sources":["../../src/providers/auth.provider.tsx"],"sourcesContent":["\"use client\";\r\n\r\nimport {\r\n createContext,\r\n ReactNode,\r\n useCallback,\r\n useContext,\r\n useMemo,\r\n} from \"react\";\r\nimport {\r\n useUserQuery,\r\n useSetUserData,\r\n useInvalidateUser,\r\n useUserValidateSession,\r\n} from \"../modules/auth/hooks/useUserQuery\";\r\nimport { useLogin } from \"../modules/auth/hooks/login.hook\";\r\nimport { useRegister } from \"../modules/auth/hooks/register.hook\";\r\nimport { useLogout } from \"../modules/auth/hooks/logout.hook\";\r\nimport {\r\n AuthState,\r\n LoginRequest,\r\n RegisterRequest,\r\n} from \"../modules/auth/schema\";\r\n\r\nexport type LoginResult =\r\n | { result: \"success\" }\r\n | { result: \"two_factor_required\"; cookie: string }\r\n | { result: \"error\"; message: string };\r\nimport {\r\n useCurrentAccount,\r\n useInvalidateAccount,\r\n} from \"../modules/accounts/hooks/current-account.hook\";\r\nimport { useRouter } from \"next/navigation\";\r\n\r\ninterface AuthContextProps extends AuthState {\r\n login: (credentials: LoginRequest) => Promise<LoginResult>;\r\n register: (data: RegisterRequest) => Promise<void>;\r\n logout: () => Promise<void>;\r\n}\r\n\r\nexport const AuthContext = createContext<AuthContextProps | undefined>(\r\n undefined\r\n);\r\n\r\ninterface AuthProviderProps {\r\n children: ReactNode;\r\n}\r\n\r\nexport function AuthProvider({ children }: AuthProviderProps) {\r\n const { data: user, isLoading: isQueryLoading } = useUserQuery();\r\n const { data: account, isLoading: isAccountLoading } = useCurrentAccount();\r\n const { isLoading: isSessionLoading } = useUserValidateSession();\r\n\r\n const setUserData = useSetUserData();\r\n const invalidateUser = useInvalidateUser();\r\n const invalidateAccount = useInvalidateAccount();\r\n\r\n const router = useRouter();\r\n\r\n const { mutateAsync: loginMutate, isPending: isLogging } = useLogin();\r\n const { mutateAsync: registerMutate, isPending: isRegistering } =\r\n useRegister();\r\n const { mutateAsync: logoutMutate, isPending: isLoggingOut } = useLogout();\r\n\r\n const isAuthenticated = !!user;\r\n const isLoading =\r\n isLogging ||\r\n isRegistering ||\r\n isLoggingOut ||\r\n isQueryLoading ||\r\n isAccountLoading ||\r\n isSessionLoading;\r\n\r\n const login = useCallback(\r\n async (credentials: LoginRequest): Promise<LoginResult> => {\r\n const data = await loginMutate(credentials);\r\n\r\n if (data.result === \"two_factor_required\") {\r\n return { result: \"two_factor_required\", cookie: data.cookie };\r\n }\r\n\r\n invalidateUser();\r\n invalidateAccount();\r\n\r\n setUserData(data.user);\r\n\r\n return { result: \"success\" };\r\n },\r\n [setUserData, invalidateUser, invalidateAccount]\r\n );\r\n\r\n const register = useCallback(\r\n async (data: RegisterRequest): Promise<void> => {\r\n await registerMutate(data, {\r\n onSuccess: (data) => {\r\n invalidateUser();\r\n invalidateAccount();\r\n setUserData(data.user);\r\n },\r\n });\r\n },\r\n [setUserData, invalidateUser, invalidateAccount]\r\n );\r\n\r\n const logout = useCallback(async (): Promise<void> => {\r\n await logoutMutate(undefined, {\r\n onSuccess: () => {\r\n invalidateUser();\r\n router.push(\"/login\");\r\n },\r\n });\r\n }, [setUserData]);\r\n\r\n const value: AuthContextProps = useMemo(\r\n () => ({\r\n user: user ?? null,\r\n account: account ?? null,\r\n isAuthenticated,\r\n isLoading,\r\n login,\r\n register,\r\n logout,\r\n }),\r\n [user, isAuthenticated, isLoading, login, register, logout]\r\n );\r\n\r\n return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;\r\n}\r\n\r\nexport function useAuth(): AuthContextProps {\r\n const context = useContext(AuthContext);\r\n\r\n if (context === undefined) {\r\n throw new Error(\"useAuth deve ser usado dentro de um AuthProvider\");\r\n }\r\n\r\n return context;\r\n}\r\n"],"mappings":";AA8HS;AA5HT;AAAA,EACE;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,gBAAgB;AACzB,SAAS,mBAAmB;AAC5B,SAAS,iBAAiB;AAW1B;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,iBAAiB;AAQnB,MAAM,cAAc;AAAA,EACzB;AACF;AAMO,SAAS,aAAa,EAAE,SAAS,GAAsB;AAC5D,QAAM,EAAE,MAAM,MAAM,WAAW,eAAe,IAAI,aAAa;AAC/D,QAAM,EAAE,MAAM,SAAS,WAAW,iBAAiB,IAAI,kBAAkB;AACzE,QAAM,EAAE,WAAW,iBAAiB,IAAI,uBAAuB;AAE/D,QAAM,cAAc,eAAe;AACnC,QAAM,iBAAiB,kBAAkB;AACzC,QAAM,oBAAoB,qBAAqB;AAE/C,QAAM,SAAS,UAAU;AAEzB,QAAM,EAAE,aAAa,aAAa,WAAW,UAAU,IAAI,SAAS;AACpE,QAAM,EAAE,aAAa,gBAAgB,WAAW,cAAc,IAC5D,YAAY;AACd,QAAM,EAAE,aAAa,cAAc,WAAW,aAAa,IAAI,UAAU;AAEzE,QAAM,kBAAkB,CAAC,CAAC;AAC1B,QAAM,YACJ,aACA,iBACA,gBACA,kBACA,oBACA;AAEF,QAAM,QAAQ;AAAA,IACZ,OAAO,gBAAoD;AACzD,YAAM,OAAO,MAAM,YAAY,WAAW;AAE1C,UAAI,KAAK,WAAW,uBAAuB;AACzC,eAAO,EAAE,QAAQ,uBAAuB,QAAQ,KAAK,OAAO;AAAA,MAC9D;AAEA,qBAAe;AACf,wBAAkB;AAElB,kBAAY,KAAK,IAAI;AAErB,aAAO,EAAE,QAAQ,UAAU;AAAA,IAC7B;AAAA,IACA,CAAC,aAAa,gBAAgB,iBAAiB;AAAA,EACjD;AAEA,QAAM,WAAW;AAAA,IACf,OAAO,SAAyC;AAC9C,YAAM,eAAe,MAAM;AAAA,QACzB,WAAW,CAACA,UAAS;AACnB,yBAAe;AACf,4BAAkB;AAClB,sBAAYA,MAAK,IAAI;AAAA,QACvB;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IACA,CAAC,aAAa,gBAAgB,iBAAiB;AAAA,EACjD;AAEA,QAAM,SAAS,YAAY,YAA2B;AACpD,UAAM,aAAa,QAAW;AAAA,MAC5B,WAAW,MAAM;AACf,uBAAe;AACf,eAAO,KAAK,QAAQ;AAAA,MACtB;AAAA,IACF,CAAC;AAAA,EACH,GAAG,CAAC,WAAW,CAAC;AAEhB,QAAM,QAA0B;AAAA,IAC9B,OAAO;AAAA,MACL,MAAM,QAAQ;AAAA,MACd,SAAS,WAAW;AAAA,MACpB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,CAAC,MAAM,iBAAiB,WAAW,OAAO,UAAU,MAAM;AAAA,EAC5D;AAEA,SAAO,oBAAC,YAAY,UAAZ,EAAqB,OAAe,UAAS;AACvD;AAEO,SAAS,UAA4B;AAC1C,QAAM,UAAU,WAAW,WAAW;AAEtC,MAAI,YAAY,QAAW;AACzB,UAAM,IAAI,MAAM,kDAAkD;AAAA,EACpE;AAEA,SAAO;AACT;","names":["data"]}
@@ -0,0 +1,22 @@
1
+ class ServerActionError extends Error {
2
+ status;
3
+ constructor(message, status) {
4
+ super(message);
5
+ this.name = "ServerActionError";
6
+ this.status = status;
7
+ }
8
+ }
9
+ async function safeServerAction(action, ...args) {
10
+ const result = await action(...args);
11
+ if (result.status === 0 || result.result === "error" || result.error) {
12
+ const msg = result.message || result.error || "Erro na a\xE7\xE3o";
13
+ throw new ServerActionError(msg, result.status || 0);
14
+ }
15
+ const { error, ...successData } = result;
16
+ return successData;
17
+ }
18
+ export {
19
+ ServerActionError,
20
+ safeServerAction
21
+ };
22
+ //# sourceMappingURL=safeServerAction.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/safeServerAction.ts"],"sourcesContent":["export class ServerActionError extends Error {\n status: number;\n\n constructor(message: string, status: number) {\n super(message);\n this.name = 'ServerActionError';\n this.status = status;\n }\n}\n\nexport async function safeServerAction<\n TArgs extends any[],\n TResult extends Record<string, any>,\n>(\n action: (...args: TArgs) => Promise<TResult>,\n ...args: TArgs\n): Promise<Omit<TResult, 'error'>> {\n const result: TResult = await action(...args);\n\n if (result.status === 0 || result.result === 'error' || result.error) {\n const msg = result.message || result.error || 'Erro na ação';\n throw new ServerActionError(msg, result.status || 0);\n }\n\n const { error, ...successData } = result;\n return successData as Omit<TResult, 'error'>;\n}\n"],"mappings":"AAAO,MAAM,0BAA0B,MAAM;AAAA,EAC3C;AAAA,EAEA,YAAY,SAAiB,QAAgB;AAC3C,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AAAA,EAChB;AACF;AAEA,eAAsB,iBAIpB,WACG,MAC8B;AACjC,QAAM,SAAkB,MAAM,OAAO,GAAG,IAAI;AAE5C,MAAI,OAAO,WAAW,KAAK,OAAO,WAAW,WAAW,OAAO,OAAO;AACpE,UAAM,MAAM,OAAO,WAAW,OAAO,SAAS;AAC9C,UAAM,IAAI,kBAAkB,KAAK,OAAO,UAAU,CAAC;AAAA,EACrD;AAEA,QAAM,EAAE,OAAO,GAAG,YAAY,IAAI;AAClC,SAAO;AACT;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@greatapps/common",
3
- "version": "1.1.71",
3
+ "version": "1.1.75",
4
4
  "description": "Shared library for GreatApps frontend applications",
5
5
  "main": "./dist/index.mjs",
6
6
  "types": "./src/index.ts",
@@ -8,7 +8,6 @@ import { NotificationsPopover } from './NotificationsPopover';
8
8
  import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/overlay/Tooltip';
9
9
  import { useAccountModals } from '../../store/useAccountModals';
10
10
  import type { ProfileMenuItem } from './ProfilePopover';
11
- import type { NotificationData } from './NotificationsPopover';
12
11
 
13
12
  export interface AppNavBarProps {
14
13
  onLogoClick?: () => void;
@@ -21,7 +20,6 @@ export interface AppNavBarProps {
21
20
  onExpandClick?: () => void;
22
21
  menuItems?: ProfileMenuItem[];
23
22
  onCreditsClick?: () => void;
24
- notifications?: NotificationData[];
25
23
  onNotificationsViewAll?: () => void;
26
24
  }
27
25
 
@@ -35,7 +33,6 @@ export function AppNavBar({
35
33
  onExpandClick,
36
34
  menuItems = [],
37
35
  onCreditsClick,
38
- notifications,
39
36
  onNotificationsViewAll,
40
37
  }: AppNavBarProps) {
41
38
  const router = useRouter();
@@ -73,7 +70,6 @@ export function AppNavBar({
73
70
  }
74
71
  >
75
72
  <NotificationsPopover
76
- notifications={notifications}
77
73
  onViewAll={onNotificationsViewAll}
78
74
  />
79
75
  <ProfilePopover
@@ -1,22 +1,17 @@
1
1
  "use client";
2
2
 
3
- import { ReactNode, useState } from "react";
3
+ import { useState } from "react";
4
4
  import { Bell } from "lucide-react";
5
5
  import * as PopoverPrimitive from "@radix-ui/react-popover";
6
6
  import { Popover } from "../ui/overlay/Popover";
7
7
  import { Button } from "../ui/buttons/Button";
8
- import { NotificationItem } from "./NotificationItem";
8
+ import NotificationCard from "../widgets/notifications/NotificationCard";
9
9
  import { cn } from "../../infra/utils/clsx";
10
-
11
- export interface NotificationData {
12
- icon: ReactNode;
13
- iconBgColor: string;
14
- title: string;
15
- time: string;
16
- }
10
+ import { useMessages, useMarkAllMessagesAsRead } from "../../modules/users/hooks/messages.hook";
11
+ import { formatDistanceToNow } from "date-fns";
12
+ import { ptBR } from "date-fns/locale";
17
13
 
18
14
  export interface NotificationsPopoverProps {
19
- notifications?: NotificationData[];
20
15
  onViewAll?: () => void;
21
16
  side?: "top" | "bottom" | "left" | "right";
22
17
  align?: "start" | "center" | "end";
@@ -25,7 +20,6 @@ export interface NotificationsPopoverProps {
25
20
  }
26
21
 
27
22
  function NotificationsPopover({
28
- notifications = [],
29
23
  onViewAll,
30
24
  side,
31
25
  align,
@@ -33,11 +27,17 @@ function NotificationsPopover({
33
27
  badgeClassName = "bg-pages-400",
34
28
  }: NotificationsPopoverProps) {
35
29
  const [open, setOpen] = useState(false);
36
- const [hasNewNotifications, setHasNewNotifications] = useState(true);
30
+ const { data: messagesData } = useMessages({ limit: 5 });
31
+ const { mutate: markAllAsRead } = useMarkAllMessagesAsRead();
32
+
33
+ const messages = messagesData?.data ?? [];
34
+ const totalUnread = messagesData?.totalUnread ?? 0;
37
35
 
38
36
  const handleOpenChange = (isOpen: boolean) => {
39
37
  setOpen(isOpen);
40
- if (isOpen) setHasNewNotifications(false);
38
+ if (isOpen && totalUnread > 0) {
39
+ markAllAsRead();
40
+ }
41
41
  };
42
42
 
43
43
  return (
@@ -47,7 +47,7 @@ function NotificationsPopover({
47
47
  className={`relative rounded-full p-2.5 ${open ? "bg-gray-100" : "hover:bg-gray-100"}`}
48
48
  >
49
49
  <Bell size={20} className="text-gray-500" />
50
- {hasNewNotifications && (
50
+ {totalUnread > 0 && (
51
51
  <span
52
52
  className={cn(
53
53
  "absolute top-2 right-2 size-2.5 rounded-full",
@@ -60,7 +60,7 @@ function NotificationsPopover({
60
60
  <PopoverPrimitive.Portal>
61
61
  <PopoverPrimitive.Content
62
62
  className={cn(
63
- "absolute bottom-2 left-[26px] w-[308px] p-0 pt-4 bg-white rounded-2xl shadow-md border border-gray-200",
63
+ "absolute bottom-2 left-[26px] z-[9999] w-[308px] p-0 pt-4 bg-white rounded-2xl shadow-md border border-gray-200",
64
64
  contentClassName,
65
65
  )}
66
66
  side={side}
@@ -71,15 +71,18 @@ function NotificationsPopover({
71
71
  Últimas notificações
72
72
  </span>
73
73
 
74
- {notifications.length > 0 ? (
75
- notifications.map((notification, index) => (
76
- <NotificationItem
77
- key={index}
78
- icon={notification.icon}
79
- iconBgColor={notification.iconBgColor}
80
- title={notification.title}
81
- time={notification.time}
82
- showBorder={index < notifications.length - 1}
74
+ {messages.length > 0 ? (
75
+ messages.map((message, index) => (
76
+ <NotificationCard
77
+ key={message.id}
78
+ icon={<Bell size={16} />}
79
+ iconBgColor="bg-gray-100"
80
+ title={message.subject}
81
+ time={formatDistanceToNow(new Date(message.datetime_add), {
82
+ addSuffix: true,
83
+ locale: ptBR,
84
+ })}
85
+ showBorder={index < messages.length - 1}
83
86
  />
84
87
  ))
85
88
  ) : (
@@ -0,0 +1,106 @@
1
+ import { Bell, Check } from "lucide-react";
2
+ import { Button } from "../../ui/buttons/Button";
3
+ import {
4
+ Tabs,
5
+ TabsContent,
6
+ TabsList,
7
+ TabsTrigger,
8
+ } from "../../ui/data-display/Tabs";
9
+ import { useMessages } from "../../../modules/users/hooks/messages.hook";
10
+ import NotificationCard from "../../widgets/notifications/NotificationCard";
11
+ import { formatDistanceToNow } from "date-fns";
12
+ import { ptBR } from "date-fns/locale";
13
+
14
+ export function Notifications() {
15
+ const { data: unreadedNotifications } = useMessages({
16
+ page: 1,
17
+ limit: 10,
18
+ status: 1,
19
+ type: "notification",
20
+ order: "desc",
21
+ });
22
+ const { data: allNotifications } = useMessages({
23
+ page: 1,
24
+ limit: 10,
25
+ type: "notification",
26
+ order: "desc",
27
+ });
28
+ return (
29
+ <div className="flex flex-col gap-6 lg:gap-10 px-4 py-6 lg:px-20 lg:py-14">
30
+ <div className="flex items-center justify-between gap-4">
31
+ <div className="flex flex-col gap-2">
32
+ <h1 className="paragraph-xlarge-semibold lg:title-large-semibold text-gray-950">
33
+ Notificações
34
+ </h1>
35
+ <p className="paragraph-medium-regular text-gray-600 hidden lg:block">
36
+ Tenha controle total sobre páginas, experiências e conversões.
37
+ </p>
38
+ </div>
39
+ <Button variant={"secondary"} className="h-10! lg:h-12! shrink-0">
40
+ <Check size={20} />
41
+ <span className="lg:block hidden">Marcar todos como lidos</span>
42
+ <span className="lg:hidden">Marcar como lidos</span>
43
+ </Button>
44
+ </div>
45
+ <Tabs
46
+ defaultValue="all"
47
+ className="flex flex-col w-full bg-transparent gap-6 lg:gap-8"
48
+ >
49
+ <TabsList className="h-auto pb-0 bg-transparent">
50
+ <TabsTrigger className="gap-2" value="all">
51
+ Todas as notificações{" "}
52
+ <span className="text-gray-500">
53
+ ({allNotifications?.data.length})
54
+ </span>
55
+ </TabsTrigger>
56
+ <TabsTrigger className="gap-2" value="unreads">
57
+ Não lidas{" "}
58
+ <span className="text-gray-500">
59
+ ({unreadedNotifications?.data.length})
60
+ </span>
61
+ </TabsTrigger>
62
+ </TabsList>
63
+
64
+ <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
+ time={formatDistanceToNow(new Date(notification.datetime_add), {
74
+ addSuffix: true,
75
+ locale: ptBR,
76
+ })}
77
+ isUnread={notification.status === 1}
78
+ showBorder={index < allNotifications.data.length - 1}
79
+ />
80
+ ))}
81
+ </div>
82
+ </TabsContent>
83
+
84
+ <TabsContent value="unreads">
85
+ <div className="flex flex-col border border-gray-200 rounded-lg w-full bg-white">
86
+ {unreadedNotifications?.data.map((notification, index) => (
87
+ <NotificationCard
88
+ key={notification.id}
89
+ icon={<Bell size={20} className="text-pages-600" />}
90
+ iconBgColor="bg-pages-100"
91
+ title={notification.subject}
92
+ description={notification.message}
93
+ time={formatDistanceToNow(new Date(notification.datetime_add), {
94
+ addSuffix: true,
95
+ locale: ptBR,
96
+ })}
97
+ isUnread={notification.status === 1}
98
+ showBorder={index < unreadedNotifications.data.length - 1}
99
+ />
100
+ ))}
101
+ </div>
102
+ </TabsContent>
103
+ </Tabs>
104
+ </div>
105
+ );
106
+ }
@@ -0,0 +1,66 @@
1
+ import { Clock } from 'lucide-react';
2
+ import { ReactNode } from 'react';
3
+ import { cn } from '../../../infra/utils/clsx';
4
+
5
+ type NotificationCardProps = {
6
+ icon: ReactNode;
7
+ iconBgColor?: string;
8
+ title: string;
9
+ description?: string;
10
+ time: string;
11
+ actions?: ReactNode;
12
+ isUnread?: boolean;
13
+ showBorder?: boolean;
14
+ className?: string;
15
+ };
16
+
17
+ export default function NotificationCard({
18
+ icon,
19
+ iconBgColor,
20
+ title,
21
+ description,
22
+ time,
23
+ actions,
24
+ isUnread = false,
25
+ showBorder = true,
26
+ className,
27
+ }: NotificationCardProps) {
28
+ return (
29
+ <div
30
+ className={cn(
31
+ 'flex flex-col lg:flex-row items-start p-4 lg:p-6 gap-4 lg:gap-5 relative',
32
+ showBorder && 'border-b border-gray-200',
33
+ className
34
+ )}
35
+ >
36
+ <div
37
+ className={cn(
38
+ 'flex items-center justify-center size-10 rounded-full shrink-0',
39
+ iconBgColor
40
+ )}
41
+ >
42
+ {icon}
43
+ </div>
44
+
45
+ <div className="flex flex-col gap-4 flex-1">
46
+ <div className="flex flex-col gap-1">
47
+ <span className="paragraph-small-semibold text-gray-950">{title}</span>
48
+ {description && (
49
+ <span className="paragraph-small-regular text-gray-600">{description}</span>
50
+ )}
51
+ </div>
52
+
53
+ <div className="flex items-center gap-1.5">
54
+ <Clock size={14} className="text-gray-400" />
55
+ <span className="paragraph-xsmall-medium text-gray-600">{time}</span>
56
+ </div>
57
+
58
+ {actions && <div className="flex items-center gap-2">{actions}</div>}
59
+ </div>
60
+
61
+ {isUnread && <div className="size-2 bg-pages-400 rounded-full shrink-0 mt-1" />}
62
+ </div>
63
+ );
64
+ }
65
+
66
+ export type { NotificationCardProps };