diff --git a/package.json b/package.json index 7c49024..f94e601 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "orval": "orval" }, "dependencies": { + "@headlessui/react": "^2.2.6", "@hookform/resolvers": "^5.1.1", "@radix-ui/react-checkbox": "^1.3.2", "@radix-ui/react-switch": "^1.2.5", @@ -36,6 +37,7 @@ "react-dropzone": "^14.3.8", "react-hook-form": "^7.60.0", "react-i18next": "^15.5.1", + "react-infinite-scroll-component": "^6.1.0", "react-loading-skeleton": "^3.5.0", "react-multi-date-picker": "^4.5.2", "react-quill": "^2.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1a3b7b0..3b096bf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@headlessui/react': + specifier: ^2.2.6 + version: 2.2.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@hookform/resolvers': specifier: ^5.1.1 version: 5.1.1(react-hook-form@7.60.0(react@19.1.0)) @@ -83,6 +86,9 @@ importers: react-i18next: specifier: ^15.5.1 version: 15.5.2(i18next@25.2.1(typescript@5.7.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.7.3) + react-infinite-scroll-component: + specifier: ^6.1.0 + version: 6.1.0(react@19.1.0) react-loading-skeleton: specifier: ^3.5.0 version: 3.5.0(react@19.1.0) @@ -483,9 +489,37 @@ packages: '@exodus/schemasafe@1.3.0': resolution: {integrity: sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==} + '@floating-ui/core@1.7.2': + resolution: {integrity: sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==} + + '@floating-ui/dom@1.7.2': + resolution: {integrity: sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA==} + + '@floating-ui/react-dom@2.1.4': + resolution: {integrity: sha512-JbbpPhp38UmXDDAu60RJmbeme37Jbgsm7NrHGgzYYFKmblzRUh6Pa641dII6LsjwF4XlScDrde2UAzDo/b9KPw==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/react@0.26.28': + resolution: {integrity: sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.10': + resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + '@gerrit0/mini-shiki@3.7.0': resolution: {integrity: sha512-7iY9wg4FWXmeoFJpUL2u+tsmh0d0jcEJHAIzVxl3TG4KL493JNnisdLAILZ77zcD+z3J0keEXZ+lFzUgzQzPDg==} + '@headlessui/react@2.2.6': + resolution: {integrity: sha512-gN5CT8Kf4IWwL04GQOjZ/ZnHMFoeFHZmVSFoDKeTmbtmy9oFqQqJMthdBiO3Pl5LXk2w03fGFLpQV6EW84vjjQ==} + engines: {node: '>=10'} + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + react-dom: ^18 || ^19 || ^19.0.0-rc + '@hookform/resolvers@5.1.1': resolution: {integrity: sha512-J/NVING3LMAEvexJkyTLjruSm7aOFx7QX21pzkiJfMoNG0wl5aFEjLTl7ay7IQb9EWY6AkrBy7tHL2Alijpdcg==} peerDependencies: @@ -739,6 +773,38 @@ packages: '@types/react': optional: true + '@react-aria/focus@3.21.0': + resolution: {integrity: sha512-7NEGtTPsBy52EZ/ToVKCu0HSelE3kq9qeis+2eEq90XSuJOMaDHUQrA7RC2Y89tlEwQB31bud/kKRi9Qme1dkA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + '@react-aria/interactions@3.25.4': + resolution: {integrity: sha512-HBQMxgUPHrW8V63u9uGgBymkMfj6vdWbB0GgUJY49K9mBKMsypcHeWkWM6+bF7kxRO728/IK8bWDV6whDbqjHg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + '@react-aria/ssr@3.9.10': + resolution: {integrity: sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==} + engines: {node: '>= 12'} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + '@react-aria/utils@3.30.0': + resolution: {integrity: sha512-ydA6y5G1+gbem3Va2nczj/0G0W7/jUVo/cbN10WA5IizzWIwMP5qhFr7macgbKfHMkZ+YZC3oXnt2NNre5odKw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + '@react-stately/flags@3.1.2': + resolution: {integrity: sha512-2HjFcZx1MyQXoPqcBGALwWWmgFVUk2TuKVIQxCbRq7fPyWXIl6VHcakCLurdtYC2Iks7zizvz0Idv48MQ38DWg==} + + '@react-stately/utils@3.10.8': + resolution: {integrity: sha512-SN3/h7SzRsusVQjQ4v10LaVsDc81jyyR0DD5HnsQitm/I5WDpaSr2nRHtyloPFU48jlql1XX/S04T2DLQM7Y3g==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + '@react-three/drei@10.1.2': resolution: {integrity: sha512-CCcLAqZEvYiUErOcJgGzovY3RH6KgdrqD4ubeAx1nyGbSPLnKR9a8ynYbPdtZhIyiWqGc07z+RYQzpaOfN4ZIA==} peerDependencies: @@ -775,6 +841,11 @@ packages: react-native: optional: true + '@react-types/shared@3.31.0': + resolution: {integrity: sha512-ua5U6V66gDcbLZe4P2QeyNgPp4YWD1ymGA6j3n+s8CGExtrCPe64v+g4mvpT8Bnb985R96e4zFT61+m0YCwqMg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + '@rolldown/pluginutils@1.0.0-beta.9': resolution: {integrity: sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==} @@ -969,6 +1040,9 @@ packages: resolution: {integrity: sha512-JZlVFE6/dYpP9tQmV0/ADfn32L9uFarHWxfcRhReKUnljz1ZiUM5zpX+PH8h5CJs6lao3TuFqnPm9IJJCEkE2w==} engines: {node: '>=10.8'} + '@swc/helpers@0.5.17': + resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==} + '@tailwindcss/node@4.1.7': resolution: {integrity: sha512-9rsOpdY9idRI2NH6CL4wORFY0+Q6fnx9XP9Ju+iq/0wJwGD5IByIgFmwVbyy4ymuyprj8Qh4ErxMKTUL4uNh3g==} @@ -1067,6 +1141,15 @@ packages: peerDependencies: react: ^18 || ^19 + '@tanstack/react-virtual@3.13.12': + resolution: {integrity: sha512-Gd13QdxPSukP8ZrkbgS2RwoZseTTbQPLnQEn7HY/rqtM+8Zt95f7xKC7N0EsKs7aoz0WzZ+fditZux+F8EzYxA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tanstack/virtual-core@3.13.12': + resolution: {integrity: sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==} + '@tweenjs/tween.js@23.1.3': resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==} @@ -2538,6 +2621,11 @@ packages: typescript: optional: true + react-infinite-scroll-component@6.1.0: + resolution: {integrity: sha512-SQu5nCqy8DxQWpnUVLx7V7b7LcA37aM7tvoWjTLZp1dk6EJibM5/4EJKzOnl07/BsM1Y40sKLuqjCwwH/xV0TQ==} + peerDependencies: + react: '>=16.0.0' + react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -2814,6 +2902,9 @@ packages: resolution: {integrity: sha512-S5FVf6zWynPWooi7pJ7lZhSUe2snTzqLuUzbd5h5PHUOhzgvW0bLKBd2wv0ixn6/5o9vwc/IkQT74CRcLJQzeg==} engines: {node: '>= 4.7.0'} + tabbable@6.2.0: + resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} + tailwind-merge@3.3.0: resolution: {integrity: sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ==} @@ -2844,6 +2935,10 @@ packages: three@0.177.0: resolution: {integrity: sha512-EiXv5/qWAaGI+Vz2A+JfavwYCMdGjxVsrn3oBwllUoqYeaBO75J63ZfyaQKoiLrqNHoTlUc6PFgMXnS0kI45zg==} + throttle-debounce@2.3.0: + resolution: {integrity: sha512-H7oLPV0P7+jgvrk+6mwwwBDmxTaxnu9HMXmloNLXwnNO0ZxZ31Orah2n8lU1eMPvsaowP2CX+USCgyovXfdOFQ==} + engines: {node: '>=8'} + tiny-case@1.0.3: resolution: {integrity: sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==} @@ -3435,6 +3530,31 @@ snapshots: '@exodus/schemasafe@1.3.0': {} + '@floating-ui/core@1.7.2': + dependencies: + '@floating-ui/utils': 0.2.10 + + '@floating-ui/dom@1.7.2': + dependencies: + '@floating-ui/core': 1.7.2 + '@floating-ui/utils': 0.2.10 + + '@floating-ui/react-dom@2.1.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@floating-ui/dom': 1.7.2 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@floating-ui/react@0.26.28(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@floating-ui/react-dom': 2.1.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@floating-ui/utils': 0.2.10 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + tabbable: 6.2.0 + + '@floating-ui/utils@0.2.10': {} + '@gerrit0/mini-shiki@3.7.0': dependencies: '@shikijs/engine-oniguruma': 3.7.0 @@ -3443,6 +3563,16 @@ snapshots: '@shikijs/types': 3.7.0 '@shikijs/vscode-textmate': 10.0.2 + '@headlessui/react@2.2.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@floating-ui/react': 0.26.28(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@react-aria/focus': 3.21.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@react-aria/interactions': 3.25.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@tanstack/react-virtual': 3.13.12(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + use-sync-external-store: 1.5.0(react@19.1.0) + '@hookform/resolvers@5.1.1(react-hook-form@7.60.0(react@19.1.0))': dependencies: '@standard-schema/utils': 0.3.0 @@ -3743,6 +3873,51 @@ snapshots: optionalDependencies: '@types/react': 19.1.6 + '@react-aria/focus@3.21.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@react-aria/interactions': 3.25.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@react-aria/utils': 3.30.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@react-types/shared': 3.31.0(react@19.1.0) + '@swc/helpers': 0.5.17 + clsx: 2.1.1 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@react-aria/interactions@3.25.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@react-aria/ssr': 3.9.10(react@19.1.0) + '@react-aria/utils': 3.30.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@react-stately/flags': 3.1.2 + '@react-types/shared': 3.31.0(react@19.1.0) + '@swc/helpers': 0.5.17 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@react-aria/ssr@3.9.10(react@19.1.0)': + dependencies: + '@swc/helpers': 0.5.17 + react: 19.1.0 + + '@react-aria/utils@3.30.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@react-aria/ssr': 3.9.10(react@19.1.0) + '@react-stately/flags': 3.1.2 + '@react-stately/utils': 3.10.8(react@19.1.0) + '@react-types/shared': 3.31.0(react@19.1.0) + '@swc/helpers': 0.5.17 + clsx: 2.1.1 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@react-stately/flags@3.1.2': + dependencies: + '@swc/helpers': 0.5.17 + + '@react-stately/utils@3.10.8(react@19.1.0)': + dependencies: + '@swc/helpers': 0.5.17 + react: 19.1.0 + '@react-three/drei@10.1.2(@react-three/fiber@9.1.2(@types/react@19.1.6)(immer@9.0.21)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(three@0.177.0))(@types/react@19.1.6)(@types/three@0.177.0)(immer@9.0.21)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(three@0.177.0)': dependencies: '@babel/runtime': 7.27.3 @@ -3798,6 +3973,10 @@ snapshots: - '@types/react' - immer + '@react-types/shared@3.31.0(react@19.1.0)': + dependencies: + react: 19.1.0 + '@rolldown/pluginutils@1.0.0-beta.9': {} '@rollup/rollup-android-arm-eabi@4.41.1': @@ -4045,6 +4224,10 @@ snapshots: '@stoplight/yaml-ast-parser': 0.0.50 tslib: 2.8.1 + '@swc/helpers@0.5.17': + dependencies: + tslib: 2.8.1 + '@tailwindcss/node@4.1.7': dependencies: '@ampproject/remapping': 2.3.0 @@ -4123,6 +4306,14 @@ snapshots: '@tanstack/query-core': 5.81.5 react: 19.1.0 + '@tanstack/react-virtual@3.13.12(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@tanstack/virtual-core': 3.13.12 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@tanstack/virtual-core@3.13.12': {} + '@tweenjs/tween.js@23.1.3': {} '@types/babel__core@7.20.5': @@ -5686,6 +5877,11 @@ snapshots: react-dom: 19.1.0(react@19.1.0) typescript: 5.7.3 + react-infinite-scroll-component@6.1.0(react@19.1.0): + dependencies: + react: 19.1.0 + throttle-debounce: 2.3.0 + react-is@16.13.1: {} react-loading-skeleton@3.5.0(react@19.1.0): @@ -6011,6 +6207,8 @@ snapshots: swiper@11.2.8: {} + tabbable@6.2.0: {} + tailwind-merge@3.3.0: {} tailwindcss@4.1.7: {} @@ -6046,6 +6244,8 @@ snapshots: three@0.177.0: {} + throttle-debounce@2.3.0: {} + tiny-case@1.0.3: {} tinyglobby@0.2.14: diff --git a/src/assets/images/avatar_image.png b/src/assets/images/avatar_image.png new file mode 100644 index 0000000..96000aa Binary files /dev/null and b/src/assets/images/avatar_image.png differ diff --git a/src/components/StatusCircle.tsx b/src/components/StatusCircle.tsx new file mode 100644 index 0000000..97fa12a --- /dev/null +++ b/src/components/StatusCircle.tsx @@ -0,0 +1,15 @@ +import { FC } from 'react' + +type Props = { + color: string +} + +const StatusCircle: FC = (props: Props) => { + return ( +
+ +
+ ) +} + +export default StatusCircle \ No newline at end of file diff --git a/src/hooks/useOutSideClick.tsx b/src/hooks/useOutSideClick.tsx new file mode 100644 index 0000000..0ef2f1b --- /dev/null +++ b/src/hooks/useOutSideClick.tsx @@ -0,0 +1,24 @@ +import { useEffect, useRef } from 'react'; + +export const useOutsideClick = (callback: () => void) => { + const ref = useRef(null); + + useEffect(() => { + const handleClickOutside = (event: MouseEvent | TouchEvent) => { + if (ref.current && !ref.current.contains(event.target as Node)) { + callback(); + } + }; + + document.addEventListener('mouseup', handleClickOutside); + document.addEventListener('touchend', handleClickOutside); + + + return () => { + document.removeEventListener('mouseup', handleClickOutside); + document.removeEventListener('touchend', handleClickOutside); + }; + }, [callback]); + + return ref; +}; \ No newline at end of file diff --git a/src/langs/fa.json b/src/langs/fa.json index 29a38d5..e495f1a 100644 --- a/src/langs/fa.json +++ b/src/langs/fa.json @@ -71,9 +71,13 @@ "unread": "خوانده نشده", "search": "جستجو" }, + "notif": { + "natification": "اعلان ها", + "all_read": "همه خوانده شده" + }, "setting": { "title": "تنظیمات", - "personality": "قالب", + "personality": "قالب ها", "mail_server": "تنظیمات میل سرور", "domain": "تنظیمات دامنه", "address": "نشانی ها", @@ -81,6 +85,7 @@ "background": "رنگ زمینه", "upload_background": "تصویر پس زمینه مورد نظر را آپلود کنید", "size": "سایز", + "position": "موقعیت", "horizontal_position": "موقعیت افقی", "vertical_position": "موقعیت عمودی", "text": "متن", @@ -120,7 +125,7 @@ "email": "ایمیل", "marketing": "بازاریابی", "actions": "عملیات", - "delete": "حذف کردن ایمیل", + "delete_email": "حذف کردن ایمیل", "active": "فعال", "inactive": "غیرفعال", "add_address": "اضافه کردن نشانی", diff --git a/src/pages/notification/Notification.tsx b/src/pages/notification/Notification.tsx new file mode 100644 index 0000000..c2c7909 --- /dev/null +++ b/src/pages/notification/Notification.tsx @@ -0,0 +1,172 @@ +import { CloseCircle, Notification } from 'iconsax-react'; +import { FC, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { clx } from '../../helpers/utils'; +import StatusCircle from '../../components/StatusCircle'; +import { useOutsideClick } from '../../hooks/useOutSideClick'; +import { useGetNotification, useReadAll } from './hooks/useNotificationData'; +import { NotificationItemType, NotificationTypeEnum } from './types/NotificationTypes'; +import { timeAgo } from '../../config/func'; +import InfiniteScroll from 'react-infinite-scroll-component'; +import MoonLoader from "react-spinners/MoonLoader" +import Button from '../../components/Button'; +import { toast } from '../../components/Toast'; +// import { useNavigate } from 'react-router-dom'; +import { ErrorType } from '../../helpers/types'; +const Notifications: FC = () => { + // const navigate = useNavigate() + const { t } = useTranslation(); + const ref = useOutsideClick(() => setShowModal(false)); + const [activeTab, setActiveTab] = useState<'read' | 'unread'>('unread'); + const [showModal, setShowModal] = useState(false); + const { data, fetchNextPage, hasNextPage, refetch } = useGetNotification(activeTab); + const readAll = useReadAll() + const posts = data?.pages.flatMap((page) => page.data?.notifications) || []; + + const handleAllRead = () => { + readAll.mutate(undefined, { + onSuccess: () => { + refetch() + toast(t('success'), 'success') + }, + onError: (error: ErrorType) => { + toast(error.response?.data?.error?.message[0], 'error') + } + }) + } + + const handleRedirect = (type: NotificationTypeEnum) => { + switch (type) { + case NotificationTypeEnum.USER_LOGIN: + break; + // case NotificationTypeEnum.ANNOUNCEMENT: + // navigate(Pages.announcement.list) + // break; + // case NotificationTypeEnum.WALLET_CHARGE: + // navigate(Pages.transactions) + // break; + // case NotificationTypeEnum.WALLET_DEDUCTION: + // navigate(Pages.transactions) + // break; + // case NotificationTypeEnum.BILL_INVOICE_REMINDER: + // navigate(Pages.receipts.index) + // break; + // case NotificationTypeEnum.BILL_INVOICE: + // navigate(Pages.receipts.index) + // break; + // case NotificationTypeEnum.CREATE_INVOICE: + // navigate(Pages.receipts.index) + // break; + // case NotificationTypeEnum.CREATE_SERVICE: + // navigate(Pages.services.other) + // break; + // case NotificationTypeEnum.UNBLOCK_SERVICE: + // navigate(Pages.services.other) + // break; + // case NotificationTypeEnum.BLOCK_SERVICE: + // navigate(Pages.services.other) + // break; + // case NotificationTypeEnum.ANSWER_TICKET: + // navigate(Pages.ticket.list) + // break; + // case NotificationTypeEnum.CREATE_TICKET: + // navigate(Pages.ticket.list) + // break; + default: + break + } + setShowModal(false) + } + + return ( +
+
setShowModal(!showModal)} className='relative cursor-pointer'> + +
+ {data?.pages[0]?.data?.notificationCount} +
+
+ + {showModal && ( +
+
+
{t('notif.natification')}
+
+ setShowModal(false)} /> +
+
+ + + +
+
setActiveTab('unread')} + className={clx( + 'flex-1 border-l cursor-pointer text-white border-white border-opacity-70 bg-transparent flex justify-center items-center', + activeTab === 'unread' ? 'bg-white/70 text-black' : '' + )} + > + خوانده نشده +
+
setActiveTab('read')} + className={clx( + 'flex-1 cursor-pointer text-white border-white bg-transparent flex justify-center items-center', + activeTab === 'read' ? 'bg-white/70 text-black' : '' + )} + > + خوانده شده +
+
+ +
+ +
+ +
+
} + scrollableTarget="notificationsContainer" + > + { + posts.map((item: NotificationItemType) => ( +
handleRedirect(item.type)} className="bg-white cursor-pointer h-fit gap-4 items-start rounded-xl bg-opacity-60 p-3 mb-4 flex" key={item.id}> + { + !item.isRead && +
+ +
+ } +
+
{item.message}
+
+
{timeAgo(item.createdAt)}
+
+ +
{item.title}
+
+
+
+
+ )) + } + +
+
+ )} + + ); +}; + +export default Notifications; \ No newline at end of file diff --git a/src/pages/notification/hooks/useNotificationData.ts b/src/pages/notification/hooks/useNotificationData.ts new file mode 100644 index 0000000..b5cac39 --- /dev/null +++ b/src/pages/notification/hooks/useNotificationData.ts @@ -0,0 +1,22 @@ +import * as api from "../service/NotificationService"; +import { useInfiniteQuery, useMutation } from "@tanstack/react-query"; + +export const useGetNotification = (status: "all" | "read" | "unread") => { + return useInfiniteQuery({ + queryKey: ["notifications", status], + queryFn: ({ pageParam = 1 }) => api.getNotifications(pageParam, status), // فراخوانی API برای گرفتن داده‌ها + initialPageParam: 1, // صفحه اول به طور پیشفرض + getNextPageParam: (lastPage) => { + const currentPage = lastPage.data?.pager.page; + const totalPages = lastPage.data?.pager.totalPages; + + return currentPage < totalPages ? currentPage + 1 : undefined; + }, + }); +}; + +export const useReadAll = () => { + return useMutation({ + mutationFn: (_) => api.readAll(), + }); +}; diff --git a/src/pages/notification/service/NotificationService.ts b/src/pages/notification/service/NotificationService.ts new file mode 100644 index 0000000..c7c3897 --- /dev/null +++ b/src/pages/notification/service/NotificationService.ts @@ -0,0 +1,18 @@ +import axios from "../../../config/axiosDanak"; + +export const getNotifications = async ( + page: number, + status: "all" | "read" | "unread" +) => { + let query = ``; + if (status !== "all") { + query = `&isRead=${status === "read" ? 1 : 0}`; + } + const { data } = await axios.get(`/notifications?page=${page}${query}`); + return data; +}; + +export const readAll = async () => { + const { data } = await axios.patch(`/notifications/read-all`); + return data; +}; diff --git a/src/pages/notification/types/NotificationTypes.ts b/src/pages/notification/types/NotificationTypes.ts new file mode 100644 index 0000000..4985ece --- /dev/null +++ b/src/pages/notification/types/NotificationTypes.ts @@ -0,0 +1,31 @@ +export type NotificationItemType = { + id: string; + title: string; + message: string; + createdAt: string; + isRead: boolean; + type: NotificationTypeEnum; +}; + +export enum NotificationTypeEnum { + USER_LOGIN = "USER_LOGIN", + ANNOUNCEMENT = "ANNOUNCEMENT", + + // Finance category + WALLET_CHARGE = "WALLET_CHARGE", + WALLET_DEDUCTION = "WALLET_DEDUCTION", + + //Invoice + BILL_INVOICE_REMINDER = "BILL_INVOICE_REMINDER", + BILL_INVOICE = "BILL_INVOICE", + CREATE_INVOICE = "CREATE_INVOICE", + + // Service category + CREATE_SERVICE = "CREATE_SERVICE", + UNBLOCK_SERVICE = "UNBLOCK_SERVICE", + BLOCK_SERVICE = "BLOCK_SERVICE", + + // Support category + ANSWER_TICKET = "ANSWER_TICKET", + CREATE_TICKET = "CREATE_TICKET", +} diff --git a/src/pages/profile/hooks/useProfileData.tsx b/src/pages/profile/hooks/useProfileData.tsx new file mode 100644 index 0000000..d93b4c2 --- /dev/null +++ b/src/pages/profile/hooks/useProfileData.tsx @@ -0,0 +1,9 @@ +import { useQuery } from '@tanstack/react-query'; +import * as api from '../service/ProfileService' + +export const useGetProfile = () => { + return useQuery({ + queryKey: ["profile"], + queryFn: () => api.getProfile(), + }); +}; \ No newline at end of file diff --git a/src/pages/profile/service/ProfileService.ts b/src/pages/profile/service/ProfileService.ts new file mode 100644 index 0000000..012fbdf --- /dev/null +++ b/src/pages/profile/service/ProfileService.ts @@ -0,0 +1,6 @@ +import axios from "../../../config/axiosDanak"; + +export const getProfile = async () => { + const { data } = await axios.get(`/users/me`); + return data; +}; diff --git a/src/pages/setting/Setting.tsx b/src/pages/setting/Setting.tsx index b629af6..5781af2 100644 --- a/src/pages/setting/Setting.tsx +++ b/src/pages/setting/Setting.tsx @@ -158,7 +158,7 @@ const Setting: FC = () => { return (
-
+

{t('setting.title')}

{ activeTab === SettingTabEnum.SETTING_PERSONALITY && diff --git a/src/pages/setting/personality/SideBar.tsx b/src/pages/setting/personality/SideBar.tsx index 1640fa6..72ffd67 100644 --- a/src/pages/setting/personality/SideBar.tsx +++ b/src/pages/setting/personality/SideBar.tsx @@ -9,13 +9,13 @@ import ImageSideBar from './components/ImageSideBar' import SocialSidebar from './components/SocialSidebar' import { clx } from '@/helpers/utils' import { usePersonalityStore } from './store/Store' -import { ElementType } from './types/Types' +import { ElementType, SectionName } from './types/Types' const PersonalitySidebar: FC = () => { const [active, setActive] = useState(SideBarTab.SETTING) const sidebarRef = useRef(null) - const { selectedElement } = usePersonalityStore() + const { selectedElement, activeSection } = usePersonalityStore() // Auto-switch to relevant tab when element is selected useEffect(() => { @@ -39,6 +39,11 @@ const PersonalitySidebar: FC = () => { } }, [selectedElement]) + // وقتی بین بخش‌ها (header, content, footer) جابجا می‌شود، تب را به تنظیمات تغییر بده + useEffect(() => { + setActive(SideBarTab.SETTING) + }, [activeSection]) + useEffect(() => { const handleClickOutside = (event: MouseEvent) => { // فقط در موبایل (عرض کمتر از 1280px) کار کند @@ -88,34 +93,38 @@ const PersonalitySidebar: FC = () => { onClick={() => setActive(SideBarTab.SETTING)} className='cursor-pointer' /> - setActive(SideBarTab.TEXT)} - className='cursor-pointer' - /> - setActive(SideBarTab.BUTTON)} - className='cursor-pointer' - /> - setActive(SideBarTab.IMAGE)} - className='cursor-pointer' - /> - setActive(SideBarTab.SOCIAL)} - className='cursor-pointer' - /> + { + activeSection !== SectionName.CONTENT && <> + setActive(SideBarTab.TEXT)} + className='cursor-pointer' + /> + setActive(SideBarTab.BUTTON)} + className='cursor-pointer' + /> + setActive(SideBarTab.IMAGE)} + className='cursor-pointer' + /> + setActive(SideBarTab.SOCIAL)} + className='cursor-pointer' + /> + + }
diff --git a/src/pages/setting/personality/components/ImageSideBar.tsx b/src/pages/setting/personality/components/ImageSideBar.tsx index 3786425..9bccd04 100644 --- a/src/pages/setting/personality/components/ImageSideBar.tsx +++ b/src/pages/setting/personality/components/ImageSideBar.tsx @@ -1,5 +1,6 @@ import Button from '@/components/Button' import Select from '@/components/Select' +import Input from '@/components/Input' import UploadBoxDraggble from '@/components/UploadBoxDraggble' import { Add, AlignBottom, AlignHorizontally, AlignLeft, AlignRight, AlignTop, AlignVertically, Edit, Trash } from 'iconsax-react' import { FC, useState, useEffect } from 'react' @@ -26,6 +27,8 @@ const ImageSideBar: FC = () => { const [imageSize, setImageSize] = useState(ImageSize.COVER) const [horizontalAlignment, setHorizontalAlignment] = useState(HorizontalAlignment.CENTER) const [verticalAlignment, setVerticalAlignment] = useState(VerticalAlignment.MIDDLE) + const [imageWidth, setImageWidth] = useState('100%') + const [imageHeight, setImageHeight] = useState('auto') const [isLoading, setIsLoading] = useState(false) const [isReset, setIsReset] = useState(false) const [isUploading, setIsUploading] = useState(false) @@ -59,6 +62,8 @@ const ImageSideBar: FC = () => { setImageSize(image.size); setHorizontalAlignment(image.alignment || HorizontalAlignment.CENTER); setVerticalAlignment(image.verticalAlignment || VerticalAlignment.MIDDLE); + setImageWidth(image.width || '100%'); + setImageHeight(image.height || 'auto'); } else { console.error('Image element not found'); } @@ -71,6 +76,8 @@ const ImageSideBar: FC = () => { setImageSize(ImageSize.COVER) setHorizontalAlignment(HorizontalAlignment.CENTER) setVerticalAlignment(VerticalAlignment.MIDDLE) + setImageWidth('100%') + setImageHeight('auto') } }, [selectedElement, isEditMode, data]); @@ -138,6 +145,8 @@ const ImageSideBar: FC = () => { size: imageSize, alignment: horizontalAlignment, verticalAlignment, + width: imageWidth, + height: imageHeight, }) // Reset form after adding @@ -145,6 +154,8 @@ const ImageSideBar: FC = () => { setImageSize(ImageSize.COVER) setHorizontalAlignment(HorizontalAlignment.CENTER) setVerticalAlignment(VerticalAlignment.MIDDLE) + setImageWidth('100%') + setImageHeight('auto') setIsReset(true) setTimeout(() => { setIsReset(false) @@ -171,6 +182,8 @@ const ImageSideBar: FC = () => { size: imageSize, alignment: horizontalAlignment, verticalAlignment, + width: imageWidth, + height: imageHeight, } ); @@ -239,6 +252,73 @@ const ImageSideBar: FC = () => { /> +
+
اندازه سریع
+
+ + + + + +
+
+ +
+
+ setImageWidth(e.target.value)} + placeholder='مثال: 100%, 200px' + disabled={isUploading} + /> +
+
+ setImageHeight(e.target.value)} + placeholder='مثال: auto, 150px' + disabled={isUploading} + /> +
+
+
@@ -292,7 +372,7 @@ const ImageSideBar: FC = () => {
+ +
+ + {/* + /> */}
setOpenSidebar(!openSidebar)} className='xl:hidden block'>
{/* */} -
- - - - - - - -
-
- {/* {NumberFormat(getWalletBalance.data?.data?.balance) + ' ' + t('toman')} */} -
-
- -
-
- - {/* */} - {/* { +
+ + + + {/* + + */} + + { data && ( @@ -71,7 +76,7 @@ const Header: FC = () => {
setPopoverKey((prevKey) => prevKey + 1)} size={20} color='black' />
- +
@@ -85,79 +90,19 @@ const Header: FC = () => {
-
-
- } - title={t('header.wallet')} - link={Pages.wallet} - isActive - isWithoutLine - /> -
-
{t('home.balance')}
-
- {NumberFormat(getWalletBalance.data?.data?.balance) + ' ' + t('toman')} -
+
+
handleLogout()} className='flex gap-2 items-center cursor-pointer'> + +
+ {t('sidebar.logout')}
- - } - title={t('sidebar.transactions')} - isActive - link={Pages.transactions} - isWithoutLine - /> - - } - title={t('header.discounts')} - isActive - link={Pages.discounts} - isWithoutLine - /> - - } - title={t('header.financial_info')} - isActive - link={Pages.financial} - isWithoutLine - /> - - } - title={t('header.profile')} - isActive - link={Pages.profile} - isWithoutLine - /> - - } - title={t('header.setting')} - isActive - link={Pages.setting} - isWithoutLine - /> -
- -
- } - title={t('sidebar.logout')} - isActive - link={Pages.setting} - isWithoutLine - isLogout - />
) - } */} + }
) diff --git a/src/shared/store/sharedStore.ts b/src/shared/store/sharedStore.ts index 20c0b1d..7c1142d 100644 --- a/src/shared/store/sharedStore.ts +++ b/src/shared/store/sharedStore.ts @@ -8,4 +8,6 @@ export const useSharedStore = create((set) => ({ setOpenNewMessage: (value) => set({ openNewMessage: value }), openBuySpace: false, setOpenBuySpace: (value) => set({ openBuySpace: value }), + hasSubMenu: false, + setSubtMenu: (value: boolean) => set({ hasSubMenu: value }), })); diff --git a/src/shared/types/SharedTypes.ts b/src/shared/types/SharedTypes.ts index 68dcc37..a005eae 100644 --- a/src/shared/types/SharedTypes.ts +++ b/src/shared/types/SharedTypes.ts @@ -5,4 +5,6 @@ export type SharedStoreType = { setOpenNewMessage: (value: boolean) => void; openBuySpace: boolean; setOpenBuySpace: (value: boolean) => void; + hasSubMenu: boolean; + setSubtMenu: (value: boolean) => void; };