feat: implement user profile page, chatbot integration, and navigation components
Build and Release / release (push) Successful in 55s

This commit is contained in:
2026-06-06 23:28:40 +07:00
parent 820e0b5216
commit 6d7d63a891
103 changed files with 251 additions and 5559 deletions
+1
View File
@@ -66,5 +66,6 @@ export const API = {
},
Chatbot:{
CHAT: `${API_URL_ROOT}/chatbot/chat`,
HISTORY: `${API_URL_ROOT}/chatbot/history`,
}
}
+2 -2
View File
@@ -1,11 +1,11 @@
{
"name": "fe_admin_history_web",
"name": "ultra_history_map",
"version": "2.2.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "fe_admin_history_web",
"name": "ultra_history_map",
"version": "2.2.3",
"dependencies": {
"@fullcalendar/core": "^6.1.19",
-6
View File
@@ -1,6 +0,0 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M26.5002 14.9998C27.8808 14.9998 29 13.8806 29 12.5C29 11.1194 27.8807 10.0002 26.5001 10.0002C25.1194 10.0002 24 11.1195 24 12.5002V14.9998H26.5002ZM19.5 14.9998C20.8807 14.9998 22 13.8805 22 12.4998V5.50018C22 4.11947 20.8807 3.00018 19.5 3.00018C18.1193 3.00018 17 4.11947 17 5.50018V12.4998C17 13.8805 18.1193 14.9998 19.5 14.9998Z" fill="#2EB67D"/>
<path d="M5.49979 17.0002C4.11919 17.0002 3 18.1194 3 19.5C3 20.8806 4.1193 21.9998 5.49989 21.9998C6.8806 21.9998 8 20.8805 8 19.4998V17.0002H5.49979ZM12.5 17.0002C11.1193 17.0002 10 18.1195 10 19.5002V26.4998C10 27.8805 11.1193 28.9998 12.5 28.9998C13.8807 28.9998 15 27.8805 15 26.4998V19.5002C15 18.1195 13.8807 17.0002 12.5 17.0002Z" fill="#E01E5A"/>
<path d="M17.0002 26.5002C17.0002 27.8808 18.1194 29 19.5 29C20.8806 29 21.9998 27.8807 21.9998 26.5001C21.9998 25.1194 20.8805 24 19.4998 24L17.0002 24L17.0002 26.5002ZM17.0002 19.5C17.0002 20.8807 18.1195 22 19.5002 22L26.4998 22C27.8805 22 28.9998 20.8807 28.9998 19.5C28.9998 18.1193 27.8805 17 26.4998 17L19.5002 17C18.1195 17 17.0002 18.1193 17.0002 19.5Z" fill="#ECB22E"/>
<path d="M14.9998 5.49979C14.9998 4.11919 13.8806 3 12.5 3C11.1194 3 10.0002 4.1193 10.0002 5.49989C10.0002 6.88061 11.1195 8 12.5002 8L14.9998 8L14.9998 5.49979ZM14.9998 12.5C14.9998 11.1193 13.8805 10 12.4998 10L5.50024 10C4.11953 10 3.00024 11.1193 3.00024 12.5C3.00024 13.8807 4.11953 15 5.50024 15L12.4998 15C13.8805 15 14.9998 13.8807 14.9998 12.5Z" fill="#36C5F0"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

-10
View File
@@ -1,10 +0,0 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="16" cy="16" r="14" fill="url(#paint0_linear_1589_37170)"/>
<path d="M21.2137 20.2816L21.8356 16.3301H17.9452V13.767C17.9452 12.6857 18.4877 11.6311 20.2302 11.6311H22V8.26699C22 8.26699 20.3945 8 18.8603 8C15.6548 8 13.5617 9.89294 13.5617 13.3184V16.3301H10V20.2816H13.5617V29.8345C14.2767 29.944 15.0082 30 15.7534 30C16.4986 30 17.2302 29.944 17.9452 29.8345V20.2816H21.2137Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_1589_37170" x1="16" y1="2" x2="16" y2="29.917" gradientUnits="userSpaceOnUse">
<stop stop-color="#18ACFE"/>
<stop offset="1" stop-color="#0163E0"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 725 B

-5
View File
@@ -1,5 +0,0 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.3851 10.9171C20.4654 9.0306 19.8243 6.61829 17.9532 5.5291C16.0821 4.4399 13.6896 5.08628 12.6093 6.97282L4.53087 21.0806C3.45059 22.9671 4.09168 25.3794 5.96277 26.4686C7.83387 27.5578 10.2264 26.9114 11.3067 25.0249L19.3851 10.9171Z" fill="#F8BB2D"/>
<path d="M11.8263 23.0546C11.8263 25.2336 10.0743 27 7.91313 27C5.75197 27 4 25.2336 4 23.0546C4 20.8756 5.75197 19.1091 7.91313 19.1091C10.0743 19.1091 11.8263 20.8756 11.8263 23.0546Z" fill="#3BA757"/>
<path d="M12.621 10.9171C11.5407 9.0306 12.1818 6.61829 14.0529 5.5291C15.924 4.4399 18.3165 5.08628 19.3968 6.97282L27.4752 21.0806C28.5555 22.9671 27.9144 25.3794 26.0433 26.4686C24.1722 27.5578 21.7797 26.9114 20.6994 25.0249L12.621 10.9171Z" fill="#4689F2"/>
</svg>

Before

Width:  |  Height:  |  Size: 836 B

-26
View File
@@ -1,26 +0,0 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="2" y="2" width="28" height="28" rx="6" fill="url(#paint0_radial_1589_37182)"/>
<rect x="2" y="2" width="28" height="28" rx="6" fill="url(#paint1_radial_1589_37182)"/>
<rect x="2" y="2" width="28" height="28" rx="6" fill="url(#paint2_radial_1589_37182)"/>
<path d="M23 10.5C23 11.3284 22.3284 12 21.5 12C20.6716 12 20 11.3284 20 10.5C20 9.67157 20.6716 9 21.5 9C22.3284 9 23 9.67157 23 10.5Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M16 21C18.7614 21 21 18.7614 21 16C21 13.2386 18.7614 11 16 11C13.2386 11 11 13.2386 11 16C11 18.7614 13.2386 21 16 21ZM16 19C17.6569 19 19 17.6569 19 16C19 14.3431 17.6569 13 16 13C14.3431 13 13 14.3431 13 16C13 17.6569 14.3431 19 16 19Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M6 15.6C6 12.2397 6 10.5595 6.65396 9.27606C7.2292 8.14708 8.14708 7.2292 9.27606 6.65396C10.5595 6 12.2397 6 15.6 6H16.4C19.7603 6 21.4405 6 22.7239 6.65396C23.8529 7.2292 24.7708 8.14708 25.346 9.27606C26 10.5595 26 12.2397 26 15.6V16.4C26 19.7603 26 21.4405 25.346 22.7239C24.7708 23.8529 23.8529 24.7708 22.7239 25.346C21.4405 26 19.7603 26 16.4 26H15.6C12.2397 26 10.5595 26 9.27606 25.346C8.14708 24.7708 7.2292 23.8529 6.65396 22.7239C6 21.4405 6 19.7603 6 16.4V15.6ZM15.6 8H16.4C18.1132 8 19.2777 8.00156 20.1779 8.0751C21.0548 8.14674 21.5032 8.27659 21.816 8.43597C22.5686 8.81947 23.1805 9.43139 23.564 10.184C23.7234 10.4968 23.8533 10.9452 23.9249 11.8221C23.9984 12.7223 24 13.8868 24 15.6V16.4C24 18.1132 23.9984 19.2777 23.9249 20.1779C23.8533 21.0548 23.7234 21.5032 23.564 21.816C23.1805 22.5686 22.5686 23.1805 21.816 23.564C21.5032 23.7234 21.0548 23.8533 20.1779 23.9249C19.2777 23.9984 18.1132 24 16.4 24H15.6C13.8868 24 12.7223 23.9984 11.8221 23.9249C10.9452 23.8533 10.4968 23.7234 10.184 23.564C9.43139 23.1805 8.81947 22.5686 8.43597 21.816C8.27659 21.5032 8.14674 21.0548 8.0751 20.1779C8.00156 19.2777 8 18.1132 8 16.4V15.6C8 13.8868 8.00156 12.7223 8.0751 11.8221C8.14674 10.9452 8.27659 10.4968 8.43597 10.184C8.81947 9.43139 9.43139 8.81947 10.184 8.43597C10.4968 8.27659 10.9452 8.14674 11.8221 8.0751C12.7223 8.00156 13.8868 8 15.6 8Z" fill="white"/>
<defs>
<radialGradient id="paint0_radial_1589_37182" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(12 23) rotate(-55.3758) scale(25.5196)">
<stop stop-color="#B13589"/>
<stop offset="0.79309" stop-color="#C62F94"/>
<stop offset="1" stop-color="#8A3AC8"/>
</radialGradient>
<radialGradient id="paint1_radial_1589_37182" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(11 31) rotate(-65.1363) scale(22.5942)">
<stop stop-color="#E0E8B7"/>
<stop offset="0.444662" stop-color="#FB8A2E"/>
<stop offset="0.71474" stop-color="#E2425C"/>
<stop offset="1" stop-color="#E2425C" stop-opacity="0"/>
</radialGradient>
<radialGradient id="paint2_radial_1589_37182" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(0.500002 3) rotate(-8.1301) scale(38.8909 8.31836)">
<stop offset="0.156701" stop-color="#406ADC"/>
<stop offset="0.467799" stop-color="#6A45BE"/>
<stop offset="1" stop-color="#6A45BE" stop-opacity="0"/>
</radialGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 3.2 KiB

-6
View File
@@ -1,6 +0,0 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M30.0014 16.3109C30.0014 15.1598 29.9061 14.3198 29.6998 13.4487H16.2871V18.6442H24.1601C24.0014 19.9354 23.1442 21.8798 21.2394 23.1864L21.2127 23.3604L25.4536 26.58L25.7474 26.6087C28.4458 24.1665 30.0014 20.5731 30.0014 16.3109Z" fill="#4285F4"/>
<path d="M16.2863 30C20.1434 30 23.3814 28.7555 25.7466 26.6089L21.2386 23.1865C20.0323 24.011 18.4132 24.5866 16.2863 24.5866C12.5086 24.5866 9.30225 22.1444 8.15929 18.7688L7.99176 18.7827L3.58208 22.1272L3.52441 22.2843C5.87359 26.8577 10.699 30 16.2863 30Z" fill="#34A853"/>
<path d="M8.16013 18.7688C7.85855 17.8977 7.68401 16.9643 7.68401 15.9999C7.68401 15.0354 7.85855 14.1021 8.14426 13.231L8.13627 13.0455L3.67132 9.64734L3.52524 9.71544C2.55703 11.6132 2.00146 13.7444 2.00146 15.9999C2.00146 18.2555 2.55703 20.3865 3.52524 22.2843L8.16013 18.7688Z" fill="#FBBC05"/>
<path d="M16.2864 7.4133C18.9689 7.4133 20.7784 8.54885 21.8102 9.4978L25.8419 5.64C23.3658 3.38445 20.1435 2 16.2864 2C10.699 2 5.8736 5.1422 3.52441 9.71549L8.14345 13.2311C9.30229 9.85555 12.5086 7.4133 16.2864 7.4133Z" fill="#EB4335"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

-4
View File
@@ -1,4 +0,0 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.24451 9.94111C2.37304 7.96233 3.96395 6.41157 5.94447 6.31345C8.81239 6.17136 12.9115 6 16 6C19.0885 6 23.1876 6.17136 26.0555 6.31345C28.0361 6.41157 29.627 7.96233 29.7555 9.94111C29.8786 11.8369 30 14.1697 30 16C30 17.8303 29.8786 20.1631 29.7555 22.0589C29.627 24.0377 28.0361 25.5884 26.0555 25.6866C23.1876 25.8286 19.0885 26 16 26C12.9115 26 8.81239 25.8286 5.94447 25.6866C3.96395 25.5884 2.37304 24.0377 2.24451 22.0589C2.12136 20.1631 2 17.8303 2 16C2 14.1697 2.12136 11.8369 2.24451 9.94111Z" fill="#FC0D1B"/>
<path d="M13 12V20L21 16L13 12Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 684 B

-4
View File
@@ -1,4 +0,0 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M37.5 20C37.5 29.66 29.6687 37.5 20 37.5C10.3312 37.5 2.5 29.66 2.5 20C2.5 10.3312 10.3312 2.49999 20 2.49999C29.6687 2.49999 37.5 10.3312 37.5 20Z" fill="#283544"/>
<path d="M28.2026 15.5717C28.1071 15.6274 25.8338 16.8031 25.8338 19.4098C25.941 22.3826 28.7026 23.4252 28.75 23.4252C28.7026 23.4809 28.3331 24.8454 27.2383 26.2757C26.3696 27.5078 25.4053 28.75 23.941 28.75C22.5481 28.75 22.0481 27.9288 20.441 27.9288C18.715 27.9288 18.2267 28.75 16.9052 28.75C15.441 28.75 14.4052 27.4412 13.4891 26.2207C12.2989 24.6232 11.2872 22.1164 11.2515 19.7093C11.2274 18.4338 11.4899 17.18 12.156 16.1151C13.0962 14.6283 14.7748 13.619 16.6079 13.5858C18.0124 13.5416 19.2624 14.4843 20.1195 14.4843C20.941 14.4843 22.4767 13.5858 24.2143 13.5858C24.9643 13.5865 26.9643 13.797 28.2026 15.5717ZM20.0008 13.3311C19.7508 12.1663 20.441 11.0015 21.0838 10.2585C21.9053 9.3599 23.2026 8.75 24.3214 8.75C24.3928 9.91481 23.9402 11.0572 23.1312 11.8892C22.4053 12.7878 21.1553 13.4642 20.0008 13.3311Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

-7
View File
@@ -1,7 +0,0 @@
<svg width="41" height="40" viewBox="0 0 41 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" width="40" height="40" rx="20" fill="#1B4BF1"/>
<path opacity="0.5" d="M28.9266 15.7265C29.1963 13.9679 28.9266 12.7956 27.9826 11.7134C26.9487 10.496 25.0607 10 22.6333 10H15.6657C15.1713 10 14.7667 10.3607 14.6768 10.8567L11.7549 29.3437C11.7099 29.7044 11.9797 30.02 12.3393 30.02H16.6547L16.34 31.9138C16.2951 32.2295 16.5198 32.5 16.8794 32.5H20.5205C20.9701 32.5 21.3297 32.1844 21.3746 31.7786L22.1388 26.999C22.1838 26.5932 22.5883 26.2776 22.9929 26.2776H23.5323C27.0386 26.2776 29.8256 24.8347 30.6348 20.6864C30.9494 18.9729 30.8146 17.485 29.9155 16.493C29.6458 16.1774 29.3311 15.9519 28.9266 15.7265" fill="white"/>
<path d="M28.9266 15.7265C29.1963 13.9679 28.9266 12.7956 27.9826 11.7134C26.9487 10.496 25.0607 10 22.6333 10H15.6657C15.1713 10 14.7667 10.3607 14.6768 10.8567L11.7549 29.3437C11.7099 29.7044 11.9797 30.02 12.3393 30.02H16.6547L17.6886 23.3467C17.7785 22.8507 18.183 22.49 18.6775 22.49H20.7453C24.791 22.49 27.9376 20.8667 28.8367 16.0872C28.8816 15.997 28.8816 15.8617 28.9266 15.7265Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M30.1209 16.096C30.1192 16.105 30.1161 16.121 30.1115 16.1467C30.1057 16.1785 30.0932 16.2474 30.0743 16.3214C30.0669 16.3504 30.0572 16.3854 30.0445 16.425C29.5373 19.0088 28.3926 20.8874 26.6974 22.0981C25.0064 23.3058 22.9195 23.74 20.7452 23.74H18.8924L17.7258 31.27H12.3392C11.1964 31.27 10.3804 30.2642 10.5144 29.1891L10.5169 29.1688L13.4442 10.6476L13.4467 10.6338C13.6357 9.59143 14.519 8.75 15.6656 8.75H22.6332C25.1361 8.75 27.5194 9.24302 28.9298 10.8979C29.516 11.5715 29.9235 12.3133 30.1267 13.1814C30.3266 14.0353 30.3118 14.9394 30.162 15.916L30.146 16.0204L30.1209 16.096ZM27.9825 11.7134C26.9486 10.496 25.0606 10 22.6332 10H15.6656C15.1712 10 14.7666 10.3607 14.6767 10.8567L11.7548 29.3437C11.7099 29.7044 11.9796 30.02 12.3392 30.02H16.6546L17.6885 23.3467C17.7784 22.8507 18.1829 22.49 18.6774 22.49H20.7452C24.7909 22.49 27.9375 20.8667 28.8366 16.0872C28.8609 16.0384 28.8721 15.9763 28.8843 15.9082C28.8947 15.8505 28.9059 15.7885 28.9265 15.7265C29.1962 13.9679 28.9265 12.7956 27.9825 11.7134Z" fill="#1B4BF1"/>
<path d="M18.9023 15.7715C18.9472 15.4559 19.3518 15.0501 19.7564 15.0501H25.2405C25.8698 15.0501 26.4992 15.0952 27.0386 15.1854C27.5331 15.2755 28.4321 15.501 28.8816 15.7715C29.1513 14.013 28.8816 12.8407 27.9376 11.7585C26.9487 10.496 25.0607 10 22.6333 10H15.6657C15.1713 10 14.7667 10.3607 14.6768 10.8567L11.7549 29.3437C11.7099 29.7044 11.9797 30.02 12.3393 30.02H16.6547L18.9023 15.7715V15.7715Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

-5
View File
@@ -1,5 +0,0 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="20" cy="20" r="20" fill="#E31937"/>
<path d="M20.0005 31.4285L22.9444 14.8726C25.7502 14.8726 26.6353 15.1803 26.7632 16.4362C26.7632 16.4362 28.6456 15.7343 29.5948 14.3089C25.8901 12.5923 22.1678 12.5148 22.1678 12.5148L19.9957 15.1604L20.0006 15.16L17.8285 12.5144C17.8285 12.5144 14.106 12.5919 10.4019 14.3086C11.3504 15.734 13.2334 16.4358 13.2334 16.4358C13.362 15.1799 14.2459 14.8722 17.033 14.8702L20.0005 31.4285Z" fill="white"/>
<path d="M19.9996 11.7508C22.9943 11.7279 26.422 12.2141 29.9311 13.7434C30.4001 12.8993 30.5207 12.5262 30.5207 12.5262C26.6847 11.0086 23.0925 10.4893 19.9992 10.4762C16.906 10.4893 13.3139 11.0087 9.47852 12.5262C9.47852 12.5262 9.64962 12.9858 10.0677 13.7434C13.576 12.2141 17.0045 11.7279 19.9993 11.7508H19.9996Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 892 B

-6
View File
@@ -1,6 +0,0 @@
<svg width="41" height="40" viewBox="0 0 41 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.5 2.5C10.8477 2.5 3 10.3477 3 20C3 29.6523 10.8477 37.5 20.5 37.5C30.1523 37.5 38 29.6523 38 20C38 10.3477 30.1523 2.5 20.5 2.5Z" fill="#F9981B"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M26.5124 22.5475C26.461 22.4738 26.4093 22.4016 26.3578 22.3298C25.9392 21.7463 25.5402 21.1902 25.5402 20.0754V15.9195C25.5402 15.775 25.541 15.6315 25.5418 15.4891C25.551 13.8957 25.5593 12.4417 24.4101 11.3294C23.4246 10.3467 21.7876 10 20.5357 10C18.0872 10 15.3558 10.947 14.783 14.0851C14.7223 14.4184 14.9558 14.5929 15.1675 14.642L17.6607 14.9218C17.8941 14.9095 18.0627 14.6715 18.1079 14.4318C18.3217 13.3505 19.195 12.831 20.1762 12.831C20.7054 12.831 21.3063 13.0316 21.6196 13.5238C21.9425 14.0146 21.9374 14.6699 21.9328 15.2558C21.9323 15.3241 21.9317 15.3914 21.9317 15.4574V15.8058C21.7004 15.8326 21.458 15.858 21.2083 15.8842C19.8486 16.0267 18.2732 16.1919 17.0932 16.7283C15.4823 17.4517 14.3511 18.9238 14.3511 21.0876C14.3511 23.8606 16.0344 25.2469 18.2016 25.2469C20.0305 25.2469 21.0314 24.7993 22.4423 23.3077C22.5128 23.4136 22.5762 23.5113 22.6358 23.6033C22.9724 24.1222 23.1921 24.4609 23.9171 25.0864C24.1085 25.1923 24.3618 25.1883 24.5314 25.0289C25.0451 24.5552 25.9779 23.7146 26.5039 23.2576C26.714 23.0786 26.6767 22.7894 26.5124 22.5475ZM21.4505 21.3491C21.0421 22.1015 20.3916 22.5619 19.6689 22.5619C18.6834 22.5619 18.1047 21.7816 18.1047 20.6272C18.1047 18.3547 20.0714 17.9423 21.9312 17.9423C21.9312 18.0782 21.9325 18.2149 21.9337 18.352C21.9428 19.3807 21.9521 20.4299 21.4505 21.3491Z" fill="white"/>
<path d="M29.5873 27.0823C27.1246 28.9867 23.5531 30 20.4773 30C16.1681 30 12.2866 28.3307 9.34948 25.5515C9.11909 25.333 9.32473 25.0346 9.60151 25.2042C12.7708 27.1371 16.6894 28.3006 20.7367 28.3006C23.4672 28.3006 26.4682 27.7073 29.2295 26.479C29.646 26.2944 29.9951 26.7674 29.5873 27.0823Z" fill="white"/>
<path d="M27.7363 25.7559C28.5293 25.6555 30.2974 25.4334 30.6122 25.8556C30.9273 26.2775 30.2654 28.0124 29.967 28.7943L29.9649 28.8C29.8751 29.0358 30.0681 29.1299 30.2715 28.9512C31.5931 27.7933 31.9346 25.366 31.6638 25.0143C31.3951 24.6674 29.0847 24.367 27.6747 25.4049C27.458 25.5659 27.4951 25.7852 27.7363 25.7559Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

-4
View File
@@ -1,4 +0,0 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="20" cy="20" r="17.5" fill="#1ED760"/>
<path d="M27.9554 27.0287C27.6564 27.5031 27.0186 27.6359 26.5203 27.3513C22.5937 25.0742 17.6705 24.5618 11.8503 25.8142C11.2922 25.9281 10.7341 25.6055 10.6145 25.0742C10.4949 24.5428 10.8338 24.0115 11.3919 23.8976C17.7502 22.5124 23.2116 23.1007 27.5966 25.6624C28.095 25.9471 28.2544 26.5543 27.9554 27.0287ZM29.9885 22.7022C29.6098 23.2904 28.8125 23.4612 28.1946 23.1196C23.7099 20.482 16.8732 19.7229 11.5712 21.26C10.8736 21.4497 10.1561 21.0892 9.95674 20.444C9.75742 19.7798 10.1361 19.0967 10.8338 18.9069C16.8931 17.1611 24.4274 17.9961 29.5899 21.0133C30.1679 21.3549 30.3672 22.1139 29.9885 22.7022ZM30.1679 18.1859C24.7862 15.1497 15.9164 14.865 10.774 16.3452C9.95674 16.5919 9.07973 16.1554 8.82061 15.3584C8.5615 14.5804 9.03987 13.7455 9.85708 13.4988C15.757 11.7909 25.5636 12.1325 31.7425 15.6241C32.48 16.0416 32.7192 16.9524 32.2807 17.6545C31.8621 18.3756 30.9054 18.6223 30.1679 18.1859Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

-4
View File
@@ -1,4 +0,0 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20 2.5C10.3477 2.5 2.5 10.3477 2.5 20C2.5 29.6523 10.3477 37.5 20 37.5C29.6523 37.5 37.5 29.6523 37.5 20C37.5 10.3477 29.6523 2.5 20 2.5Z" fill="#FF5A5F"/>
<path d="M20.0002 24.6084C18.8528 23.1652 18.1779 21.9001 17.953 20.8178C17.728 19.9383 17.818 19.2393 18.2004 18.7206C18.6054 18.1118 19.2128 17.8186 20.0002 17.8186C20.7875 17.8186 21.3949 18.1118 21.7999 18.7206C22.1823 19.2393 22.2723 19.9383 22.0473 20.8178C21.7999 21.9227 21.125 23.1855 20.0002 24.6084ZM26.2812 27.9458C24.369 28.7801 22.477 27.4497 20.8573 25.6457C23.5366 22.2835 24.0315 19.6677 22.882 17.9742C22.2071 17.0046 21.2397 16.531 20.0002 16.531C17.503 16.531 16.1285 18.6507 16.6684 21.1109C16.9834 22.4413 17.8158 23.9544 19.143 25.6457C18.1475 26.7515 16.8414 28.0059 15.2736 28.1487C13.0015 28.487 11.222 26.2771 12.0319 23.9973L17.7933 12.0413C18.285 11.1425 18.8919 10.3726 19.9979 10.3726C20.8078 10.3726 21.4377 10.8462 21.7076 11.2295L27.9639 23.9973C28.5769 25.54 27.7996 27.2886 26.2812 27.9458ZM29.1832 23.5463L23.8268 12.3796C22.8145 10.305 22.0946 9.0625 20.0002 9.0625C17.9305 9.0625 17.0509 10.5057 16.151 12.3796L10.8171 23.5463C9.66976 26.7055 12.0319 29.4792 14.8912 29.4792C15.0712 29.4792 15.2499 29.4566 15.4311 29.4566C16.9159 29.2762 18.4479 28.3291 20.0002 26.6356C21.5524 28.3269 23.0844 29.2762 24.5692 29.4566C24.7505 29.4566 24.9291 29.4792 25.1091 29.4792C27.9684 29.4814 30.3306 26.7055 29.1832 23.5463Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

-5
View File
@@ -1,5 +0,0 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20 2.5C10.3477 2.5 2.5 10.3477 2.5 20C2.5 29.6523 10.3477 37.5 20 37.5C29.6523 37.5 37.5 29.6523 37.5 20C37.5 10.3477 29.6523 2.5 20 2.5Z" fill="#87E64B"/>
<path d="M20.4865 30C20.9801 30 21.3803 29.5998 21.3803 29.1062C21.3803 28.6126 20.9801 28.2125 20.4865 28.2125C19.9929 28.2125 19.5928 28.6126 19.5928 29.1062C19.5928 29.5998 19.9929 30 20.4865 30Z" fill="white"/>
<path d="M25.6233 23.0134L20.5833 23.5539C20.4895 23.5633 20.4427 23.4477 20.5177 23.3883L25.4483 19.5482C25.767 19.2857 25.9732 18.8795 25.8857 18.4421C25.7982 17.7734 25.2452 17.336 24.5453 17.4235L19.1866 18.2077C19.0928 18.2202 19.0428 18.1015 19.1178 18.0421L24.4297 13.9864C25.4764 13.1709 25.5639 11.5711 24.6046 10.6399C23.7329 9.76817 22.3331 9.79629 21.4613 10.6681L12.903 19.3763C12.5843 19.7263 12.4374 20.1919 12.5249 20.6855C12.6718 21.4729 13.4561 21.9947 14.2435 21.851L18.8585 20.9105C18.9585 20.8886 19.0116 21.023 18.9272 21.0792L13.8091 24.3569C13.1686 24.7631 12.878 25.4912 13.0811 26.2192C13.2842 27.1785 14.2466 27.7315 15.1777 27.5003L22.8299 25.6162C22.9174 25.5943 22.9798 25.6943 22.9236 25.763L21.73 27.2378C21.4113 27.644 21.9331 28.1971 22.3705 27.8784L26.3013 24.6475C27.0012 24.0664 26.5356 22.929 25.6326 23.0165L25.6233 23.0134Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

-6
View File
@@ -1,6 +0,0 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="20" cy="20" r="17.5" fill="#FF8C00"/>
<path d="M27.4363 25.1256C27.408 24.8784 27.2659 24.3115 26.8965 24.1659H26.897C26.712 24.0931 26.5558 24.151 26.4852 24.2821C26.3713 24.486 26.4565 24.8784 26.6697 25.2278C26.8824 25.5768 27.0814 25.7513 27.2235 25.7513C27.3656 25.7513 27.4932 25.5768 27.4363 25.1256Z" fill="white"/>
<path d="M25.4485 26.1147C25.2213 25.9113 24.9657 25.8096 24.7384 25.8096C24.5398 25.8096 24.3836 25.8819 24.2839 26.013C24.0288 26.3326 24.1422 26.9148 24.5398 27.2638C24.7102 27.4239 24.9657 27.5111 25.2071 27.5111C25.4485 27.5111 25.6613 27.4094 25.7892 27.2494C26.0165 26.9293 25.8885 26.4931 25.4485 26.1147Z" fill="white"/>
<path d="M10 20.0061C10 25.5328 14.3728 30.0125 19.7687 30.0128C21.203 30.0128 22.6227 29.7365 24.7102 29.7216C26.5704 29.7216 28.615 30.3907 30.8583 32.4272C31.0856 32.6306 31.3835 32.3688 31.1849 32.1216C28.984 29.2565 26.9393 28.7185 24.8947 28.2529C22.3959 27.6856 21.1178 26.2603 20.2233 24.6749C20.0529 24.3549 19.9677 24.4132 19.9532 24.8205C19.9371 25.4198 19.98 26.0192 20.0812 26.6097H19.7833C16.2191 26.6097 13.3223 23.6424 13.3223 19.9917C13.3223 16.3409 16.2191 13.3737 19.7828 13.3737C23.347 13.3737 26.2438 16.3409 26.2438 19.9917C26.2438 20.2534 26.2292 20.5152 26.201 20.7624C25.7182 20.6752 24.7954 20.6607 24.1422 20.719C23.9008 20.748 23.9291 20.8641 24.114 20.8935C26.2438 21.3004 27.7063 22.6534 28.047 25.1112C28.0612 25.1695 28.1322 25.1839 28.1609 25.1405C29.0268 23.6424 29.5378 21.8822 29.5378 20.0061C29.5378 14.4798 25.1643 10 19.7687 10C14.3735 10 10 14.4794 10 20.0061Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

-4
View File
@@ -1,4 +0,0 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M16 30C23.732 30 30 23.732 30 16C30 8.26801 23.732 2 16 2C8.26801 2 2 8.26801 2 16C2 23.732 8.26801 30 16 30Z" fill="#2BDE73"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.357 13.4527L17.3808 9.20142C17.9525 8.40047 18.6904 8 19.5953 8C20.3334 8 20.972 8.25407 21.512 8.76237C22.0516 9.27068 22.3213 9.8791 22.3213 10.5876C22.3213 11.1114 22.1788 11.5736 21.8929 11.9741L19.1665 15.821L22.5 19.9222C22.8334 20.3303 23 20.808 23 21.3547C23 22.0788 22.7384 22.7004 22.2144 23.2203C21.6906 23.7403 21.0553 24 20.3096 24C19.492 24 18.8691 23.7423 18.4404 23.2261L14.357 18.2817V21.0082C14.357 21.7862 14.2182 22.3906 13.9403 22.8219C13.4324 23.6072 12.6943 24 11.7262 24C10.8452 24 10.1624 23.7112 9.67848 23.1337C9.22604 22.6022 9 21.8973 9 21.0198V10.9111C9 10.0793 9.22983 9.39412 9.69041 8.85495C10.1745 8.2851 10.841 8 11.6903 8C12.4999 8 13.1744 8.2851 13.7141 8.85495C14.0157 9.17056 14.2061 9.4902 14.2855 9.8137C14.3332 10.0141 14.357 10.3874 14.357 10.9343V13.4527Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 312 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 869 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 453 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 849 KiB

-15
View File
@@ -1,15 +0,0 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1788_4304)">
<path d="M16 32C24.8366 32 32 24.8366 32 16C32 7.16344 24.8366 0 16 0C7.16344 0 0 7.16344 0 16C0 24.8366 7.16344 32 16 32Z" fill="#F0F0F0"/>
<path d="M15.3037 16H31.9993C31.9993 14.5559 31.8068 13.1569 31.4481 11.826H15.3037V16Z" fill="#D80027"/>
<path d="M15.3037 7.65135H29.651C28.6715 6.0531 27.4192 4.64042 25.9591 3.47742H15.3037V7.65135Z" fill="#D80027"/>
<path d="M15.9995 32C19.7651 32 23.2262 30.6985 25.9593 28.5217H6.03979C8.77292 30.6985 12.234 32 15.9995 32Z" fill="#D80027"/>
<path d="M2.34797 24.3465H29.6512C30.4375 23.0635 31.0473 21.661 31.4484 20.1726H0.550781C0.951844 21.661 1.56166 23.0635 2.34797 24.3465Z" fill="#D80027"/>
<path d="M7.4115 2.49863H8.86956L7.51331 3.48394L8.03137 5.07825L6.67519 4.09294L5.319 5.07825L5.7665 3.70094C4.57237 4.69562 3.52575 5.861 2.66325 7.1595H3.13044L2.26712 7.78669C2.13262 8.01106 2.00362 8.239 1.88 8.47031L2.29225 9.73913L1.52313 9.18031C1.33194 9.58537 1.15706 9.99956 0.999875 10.4224L1.45406 11.8204H3.13044L1.77419 12.8057L2.29225 14.4L0.936063 13.4147L0.123687 14.0049C0.042375 14.6586 0 15.3243 0 16H16C16 7.1635 16 6.12175 16 0C12.8393 0 9.89281 0.916875 7.4115 2.49863ZM8.03137 14.4L6.67519 13.4147L5.319 14.4L5.83706 12.8057L4.48081 11.8204H6.15719L6.67519 10.2261L7.19319 11.8204H8.86956L7.51331 12.8057L8.03137 14.4ZM7.51331 8.14481L8.03137 9.73913L6.67519 8.75381L5.319 9.73913L5.83706 8.14481L4.48081 7.1595H6.15719L6.67519 5.56519L7.19319 7.1595H8.86956L7.51331 8.14481ZM13.7705 14.4L12.4143 13.4147L11.0581 14.4L11.5762 12.8057L10.2199 11.8204H11.8963L12.4143 10.2261L12.9323 11.8204H14.6087L13.2524 12.8057L13.7705 14.4ZM13.2524 8.14481L13.7705 9.73913L12.4143 8.75381L11.0581 9.73913L11.5762 8.14481L10.2199 7.1595H11.8963L12.4143 5.56519L12.9323 7.1595H14.6087L13.2524 8.14481ZM13.2524 3.48394L13.7705 5.07825L12.4143 4.09294L11.0581 5.07825L11.5762 3.48394L10.2199 2.49863H11.8963L12.4143 0.904312L12.9323 2.49863H14.6087L13.2524 3.48394Z" fill="#0052B4"/>
</g>
<defs>
<clipPath id="clip0_1788_4304">
<rect width="32" height="32" rx="16" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

-12
View File
@@ -1,12 +0,0 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1788_4322)">
<path d="M16 32C24.8366 32 32 24.8366 32 16C32 7.16344 24.8366 0 16 0C7.16344 0 0 7.16344 0 16C0 24.8366 7.16344 32 16 32Z" fill="#F0F0F0"/>
<path d="M32 15.9999C32 9.12049 27.658 3.2558 21.5652 0.995117V31.0048C27.658 28.7441 32 22.8794 32 15.9999Z" fill="#D80027"/>
<path d="M0.000488281 16.001C0.000488281 22.8805 4.34255 28.7452 10.4353 31.0058V0.996216C4.34255 3.2569 0.000488281 9.12159 0.000488281 16.001Z" fill="#0052B4"/>
</g>
<defs>
<clipPath id="clip0_1788_4322">
<rect width="32" height="32" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 680 B

-17
View File
@@ -1,17 +0,0 @@
<svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1589_35166)">
<path d="M10.875 20.5C16.3978 20.5 20.875 16.0228 20.875 10.5C20.875 4.97715 16.3978 0.5 10.875 0.5C5.35215 0.5 0.875 4.97715 0.875 10.5C0.875 16.0228 5.35215 20.5 10.875 20.5Z" fill="#F0F0F0"/>
<path d="M0.875 10.5C0.875 4.97719 5.35219 0.5 10.875 0.5C16.3978 0.5 20.875 4.97719 20.875 10.5" fill="#D80027"/>
<path d="M6.96163 5.71751C6.96163 4.26056 7.98558 3.04345 9.35292 2.74481C9.14276 2.69896 8.92475 2.67407 8.70073 2.67407C7.01983 2.67407 5.65726 4.03665 5.65726 5.71755C5.65726 7.39845 7.01983 8.76103 8.70073 8.76103C8.92468 8.76103 9.14272 8.73614 9.35292 8.69025C7.98558 8.39161 6.96163 7.1745 6.96163 5.71751Z" fill="#F0F0F0"/>
<path d="M10.8747 2.89172L11.0905 3.55598H11.789L11.2239 3.96657L11.4398 4.63083L10.8747 4.22032L10.3096 4.63083L10.5255 3.96657L9.96039 3.55598H10.6588L10.8747 2.89172Z" fill="#F0F0F0"/>
<path d="M9.18043 4.19556L9.39625 4.85985H10.0947L9.52964 5.2704L9.7455 5.93466L9.18043 5.52415L8.61527 5.93466L8.83117 5.2704L8.26605 4.85985H8.96453L9.18043 4.19556Z" fill="#F0F0F0"/>
<path d="M12.5691 4.19556L12.785 4.85985H13.4835L12.9184 5.2704L13.1343 5.93466L12.5691 5.52415L12.0041 5.93466L12.2199 5.2704L11.6548 4.85985H12.3533L12.5691 4.19556Z" fill="#F0F0F0"/>
<path d="M11.9171 6.15283L12.133 6.81713H12.8314L12.2663 7.22768L12.4822 7.89193L11.9171 7.48143L11.352 7.89193L11.5679 7.22768L11.0028 6.81713H11.7012L11.9171 6.15283Z" fill="#F0F0F0"/>
<path d="M9.83243 6.15283L10.0482 6.81713H10.7468L10.1816 7.22768L10.3975 7.89193L9.83243 7.48143L9.26731 7.89193L9.48317 7.22768L8.91809 6.81713H9.61657L9.83243 6.15283Z" fill="#F0F0F0"/>
</g>
<defs>
<clipPath id="clip0_1589_35166">
<rect x="0.875" y="0.5" width="20" height="20" rx="10" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

-23
View File
@@ -1,23 +0,0 @@
<svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1589_35168)">
<path d="M10.875 20.5C16.3978 20.5 20.875 16.0228 20.875 10.5C20.875 4.97715 16.3978 0.5 10.875 0.5C5.35215 0.5 0.875 4.97715 0.875 10.5C0.875 16.0228 5.35215 20.5 10.875 20.5Z" fill="#F0F0F0"/>
<path d="M2.94224 4.41089C2.15673 5.43288 1.56443 6.61081 1.21954 7.89046H6.42181L2.94224 4.41089Z" fill="#0052B4"/>
<path d="M20.5309 7.89054C20.186 6.61093 19.5936 5.433 18.8082 4.41101L15.3287 7.89054H20.5309Z" fill="#0052B4"/>
<path d="M1.21954 13.1085C1.56447 14.3881 2.15677 15.5661 2.94224 16.588L6.42169 13.1085H1.21954Z" fill="#0052B4"/>
<path d="M16.9629 2.56649C15.9409 1.78098 14.763 1.18867 13.4834 0.84375V6.04598L16.9629 2.56649Z" fill="#0052B4"/>
<path d="M4.78662 18.4314C5.80861 19.2169 6.98655 19.8092 8.26616 20.1541V14.9519L4.78662 18.4314Z" fill="#0052B4"/>
<path d="M8.26611 0.84375C6.9865 1.18867 5.80857 1.78098 4.78662 2.56644L8.26611 6.04593V0.84375Z" fill="#0052B4"/>
<path d="M13.4834 20.1541C14.763 19.8092 15.9409 19.2169 16.9629 18.4314L13.4834 14.9519V20.1541Z" fill="#0052B4"/>
<path d="M15.3287 13.1085L18.8082 16.588C19.5936 15.5661 20.186 14.3881 20.5309 13.1085H15.3287Z" fill="#0052B4"/>
<path d="M20.7904 9.19566H12.1794H12.1794V0.584648C11.7524 0.529063 11.3171 0.5 10.875 0.5C10.4329 0.5 9.99762 0.529063 9.57066 0.584648V9.19559V9.19563H0.959648C0.904063 9.62262 0.875 10.0579 0.875 10.5C0.875 10.9421 0.904063 11.3774 0.959648 11.8043H9.57059H9.57063V20.4154C9.99762 20.4709 10.4329 20.5 10.875 20.5C11.3171 20.5 11.7524 20.471 12.1793 20.4154V11.8044V11.8044H20.7904C20.8459 11.3774 20.875 10.9421 20.875 10.5C20.875 10.0579 20.8459 9.62262 20.7904 9.19566Z" fill="#D80027"/>
<path d="M13.4837 13.1094L17.946 17.5718C18.1513 17.3666 18.3471 17.1521 18.5339 16.9298L14.7135 13.1094H13.4837V13.1094Z" fill="#D80027"/>
<path d="M8.26628 13.1094H8.2662L3.80389 17.5717C4.00905 17.7769 4.22354 17.9727 4.44589 18.1595L8.26628 14.339V13.1094Z" fill="#D80027"/>
<path d="M8.26616 7.89093V7.89085L3.80382 3.42847C3.59858 3.63362 3.4028 3.84812 3.216 4.07046L7.03643 7.89089H8.26616V7.89093Z" fill="#D80027"/>
<path d="M13.4837 7.89187L17.9461 3.42945C17.7409 3.22421 17.5264 3.02843 17.3041 2.84167L13.4837 6.6621V7.89187V7.89187Z" fill="#D80027"/>
</g>
<defs>
<clipPath id="clip0_1589_35168">
<rect x="0.875" y="0.5" width="20" height="20" rx="10" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

-12
View File
@@ -1,12 +0,0 @@
<svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1589_35170)">
<path d="M10.875 20.5C16.3978 20.5 20.875 16.0228 20.875 10.5C20.875 4.97715 16.3978 0.5 10.875 0.5C5.35215 0.5 0.875 4.97715 0.875 10.5C0.875 16.0228 5.35215 20.5 10.875 20.5Z" fill="#F0F0F0"/>
<path d="M10.875 20.4993C15.1746 20.4993 18.84 17.7856 20.253 13.9775H1.49695C2.90988 17.7856 6.57531 20.4993 10.875 20.4993Z" fill="black"/>
<path d="M10.875 0.50061C6.57531 0.50061 2.90988 3.21436 1.49695 7.02237H20.253C18.84 3.21436 15.1746 0.50061 10.875 0.50061Z" fill="#D80027"/>
</g>
<defs>
<clipPath id="clip0_1589_35170">
<rect x="0.875" y="0.5" width="20" height="20" rx="10" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 758 B

-11
View File
@@ -1,11 +0,0 @@
<svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1589_35172)">
<path d="M10.875 20.5C16.3978 20.5 20.875 16.0228 20.875 10.5C20.875 4.97715 16.3978 0.5 10.875 0.5C5.35215 0.5 0.875 4.97715 0.875 10.5C0.875 16.0228 5.35215 20.5 10.875 20.5Z" fill="#F0F0F0"/>
<path d="M20.7905 9.19564H8.70125H8.70122V0.737671C7.77708 0.942593 6.90094 1.27474 6.0925 1.71587V9.19556V9.1956H0.959771C0.904106 9.62259 0.875122 10.0579 0.875122 10.5C0.875122 10.942 0.904106 11.3774 0.959771 11.8043H6.09247H6.0925V19.284C6.90094 19.7251 7.77708 20.0574 8.70122 20.2622V11.8044V11.8044H20.7905C20.8461 11.3774 20.8751 10.942 20.8751 10.5C20.8751 10.0579 20.8461 9.62259 20.7905 9.19564Z" fill="#0052B4"/>
</g>
<defs>
<clipPath id="clip0_1589_35172">
<rect x="0.875" y="0.5" width="20" height="20" rx="10" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 898 B

-12
View File
@@ -1,12 +0,0 @@
<svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1589_35174)">
<path d="M14.353 1.12211C13.2697 0.720161 12.0979 0.500122 10.8747 0.500122C9.65153 0.500122 8.47981 0.720161 7.39649 1.12211L6.52692 10.5001L7.39649 19.8781C8.47981 20.2801 9.65153 20.5001 10.8747 20.5001C12.0979 20.5001 13.2697 20.2801 14.353 19.8781L15.2225 10.5001L14.353 1.12211Z" fill="#FFDA44"/>
<path d="M20.8749 10.4994C20.8749 6.19982 18.1612 2.53435 14.3531 1.12146V19.8775C18.1612 18.4645 20.8749 14.7991 20.8749 10.4994Z" fill="#D80027"/>
<path d="M0.874664 10.5C0.874664 14.7997 3.58841 18.4651 7.39642 19.8781V1.12207C3.58841 2.53496 0.874664 6.20043 0.874664 10.5Z" fill="black"/>
</g>
<defs>
<clipPath id="clip0_1589_35174">
<rect x="0.875" y="0.5" width="20" height="20" rx="10" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 874 B

-11
View File
@@ -1,11 +0,0 @@
<svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1589_35176)">
<path d="M10.875 20.5C16.3978 20.5 20.875 16.0228 20.875 10.5C20.875 4.97715 16.3978 0.5 10.875 0.5C5.35215 0.5 0.875 4.97715 0.875 10.5C0.875 16.0228 5.35215 20.5 10.875 20.5Z" fill="#496E2D"/>
<path d="M8.70105 14.8479C11.1023 14.8479 13.0489 12.9013 13.0489 10.5C13.0489 8.09881 11.1023 6.15222 8.70105 6.15222C6.29982 6.15222 4.35324 8.09881 4.35324 10.5C4.35324 12.9013 6.29982 14.8479 8.70105 14.8479Z" fill="#D80027"/>
</g>
<defs>
<clipPath id="clip0_1589_35176">
<rect x="0.875" y="0.5" width="20" height="20" rx="10" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 703 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

@@ -1,4 +0,0 @@
<svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.61127 24.8528C5.93259 19.9058 9.90987 16.0289 14.8612 15.7836C22.031 15.4284 32.2787 15 40 15C47.7213 15 57.969 15.4284 65.1388 15.7836C70.0901 16.0289 74.0674 19.9058 74.3887 24.8528C74.6966 29.5923 75 35.4241 75 40C75 44.5759 74.6966 50.4077 74.3887 55.1472C74.0674 60.0942 70.0901 63.9711 65.1388 64.2164C57.969 64.5716 47.7213 65 40 65C32.2787 65 22.031 64.5716 14.8612 64.2164C9.90987 63.9711 5.93259 60.0942 5.61127 55.1472C5.30341 50.4077 5 44.5759 5 40C5 35.4241 5.30341 29.5923 5.61127 24.8528Z" fill="#FC0D1B"/>
<path d="M32.5 30V50L52.5 40L32.5 30Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 691 B

@@ -1,86 +0,0 @@
import ComponentCard from "@/components/common/ComponentCard";
import PageBreadcrumb from "@/components/common/PageBreadCrumb";
import Alert from "@/components/ui/alert/Alert";
import { Metadata } from "next";
import React from "react";
export const metadata: Metadata = {
title: "Next.js Alerts | TailAdmin - Next.js Dashboard Template",
description:
"This is Next.js Alerts page for TailAdmin - Next.js Tailwind CSS Admin Dashboard Template",
// other metadata
};
export default function Alerts() {
return (
<div>
<PageBreadcrumb pageTitle="Alerts" />
<div className="space-y-5 sm:space-y-6">
<ComponentCard title="Success Alert">
<Alert
variant="success"
title="Success Message"
message="Be cautious when performing this action."
showLink={true}
linkHref="/"
linkText="Learn more"
/>
<Alert
variant="success"
title="Success Message"
message="Be cautious when performing this action."
showLink={false}
/>
</ComponentCard>
<ComponentCard title="Warning Alert">
<Alert
variant="warning"
title="Warning Message"
message="Be cautious when performing this action."
showLink={true}
linkHref="/"
linkText="Learn more"
/>
<Alert
variant="warning"
title="Warning Message"
message="Be cautious when performing this action."
showLink={false}
/>
</ComponentCard>{" "}
<ComponentCard title="Error Alert">
<Alert
variant="error"
title="Error Message"
message="Be cautious when performing this action."
showLink={true}
linkHref="/"
linkText="Learn more"
/>
<Alert
variant="error"
title="Error Message"
message="Be cautious when performing this action."
showLink={false}
/>
</ComponentCard>{" "}
<ComponentCard title="Info Alert">
<Alert
variant="info"
title="Info Message"
message="Be cautious when performing this action."
showLink={true}
linkHref="/"
linkText="Learn more"
/>
<Alert
variant="info"
title="Info Message"
message="Be cautious when performing this action."
showLink={false}
/>
</ComponentCard>
</div>
</div>
);
}
@@ -1,126 +0,0 @@
import ComponentCard from "@/components/common/ComponentCard";
import PageBreadcrumb from "@/components/common/PageBreadCrumb";
import Avatar from "@/components/ui/avatar/Avatar";
import { Metadata } from "next";
import React from "react";
export const metadata: Metadata = {
title: "Next.js Avatars | TailAdmin - Next.js Dashboard Template",
description:
"This is Next.js Avatars page for TailAdmin - Next.js Tailwind CSS Admin Dashboard Template",
};
export default function AvatarPage() {
return (
<div>
<PageBreadcrumb pageTitle="Avatar" />
<div className="space-y-5 sm:space-y-6">
<ComponentCard title="Default Avatar">
{/* Default Avatar (No Status) */}
<div className="flex flex-col items-center justify-center gap-5 sm:flex-row">
<Avatar src="/images/user/user-01.jpg" size="xsmall" />
<Avatar src="/images/user/user-01.jpg" size="small" />
<Avatar src="/images/user/user-01.jpg" size="medium" />
<Avatar src="/images/user/user-01.jpg" size="large" />
<Avatar src="/images/user/user-01.jpg" size="xlarge" />
<Avatar src="/images/user/user-01.jpg" size="xxlarge" />
</div>
</ComponentCard>
<ComponentCard title="Avatar with online indicator">
<div className="flex flex-col items-center justify-center gap-5 sm:flex-row">
<Avatar
src="/images/user/user-01.jpg"
size="xsmall"
status="online"
/>
<Avatar
src="/images/user/user-01.jpg"
size="small"
status="online"
/>
<Avatar
src="/images/user/user-01.jpg"
size="medium"
status="online"
/>
<Avatar
src="/images/user/user-01.jpg"
size="large"
status="online"
/>
<Avatar
src="/images/user/user-01.jpg"
size="xlarge"
status="online"
/>
<Avatar
src="/images/user/user-01.jpg"
size="xxlarge"
status="online"
/>
</div>
</ComponentCard>
<ComponentCard title="Avatar with Offline indicator">
<div className="flex flex-col items-center justify-center gap-5 sm:flex-row">
<Avatar
src="/images/user/user-01.jpg"
size="xsmall"
status="offline"
/>
<Avatar
src="/images/user/user-01.jpg"
size="small"
status="offline"
/>
<Avatar
src="/images/user/user-01.jpg"
size="medium"
status="offline"
/>
<Avatar
src="/images/user/user-01.jpg"
size="large"
status="offline"
/>
<Avatar
src="/images/user/user-01.jpg"
size="xlarge"
status="offline"
/>
<Avatar
src="/images/user/user-01.jpg"
size="xxlarge"
status="offline"
/>
</div>
</ComponentCard>{" "}
<ComponentCard title="Avatar with busy indicator">
<div className="flex flex-col items-center justify-center gap-5 sm:flex-row">
<Avatar
src="/images/user/user-01.jpg"
size="xsmall"
status="busy"
/>
<Avatar src="/images/user/user-01.jpg" size="small" status="busy" />
<Avatar
src="/images/user/user-01.jpg"
size="medium"
status="busy"
/>
<Avatar src="/images/user/user-01.jpg" size="large" status="busy" />
<Avatar
src="/images/user/user-01.jpg"
size="xlarge"
status="busy"
/>
<Avatar
src="/images/user/user-01.jpg"
size="xxlarge"
status="busy"
/>
</div>
</ComponentCard>
</div>
</div>
);
}
@@ -1,221 +0,0 @@
import PageBreadcrumb from "@/components/common/PageBreadCrumb";
import Badge from "@/components/ui/badge/Badge";
import { PlusIcon } from "@/icons";
import { Metadata } from "next";
import React from "react";
export const metadata: Metadata = {
title: "Next.js Badge | TailAdmin - Next.js Dashboard Template",
description:
"This is Next.js Badge page for TailAdmin - Next.js Tailwind CSS Admin Dashboard Template",
// other metadata
};
export default function BadgePage() {
return (
<div>
<PageBreadcrumb pageTitle="Badges" />
<div className="space-y-5 sm:space-y-6">
<div className="rounded-2xl border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03]">
<div className="px-6 py-5">
<h3 className="text-base font-medium text-gray-800 dark:text-white/90">
With Light Background
</h3>
</div>
<div className="p-6 border-t border-gray-100 dark:border-gray-800 xl:p-10">
<div className="flex flex-wrap gap-4 sm:items-center sm:justify-center">
{/* Light Variant */}
<Badge variant="light" color="primary">
Primary
</Badge>
<Badge variant="light" color="success">
Success
</Badge>{" "}
<Badge variant="light" color="error">
Error
</Badge>{" "}
<Badge variant="light" color="warning">
Warning
</Badge>{" "}
<Badge variant="light" color="info">
Info
</Badge>
<Badge variant="light" color="light">
Light
</Badge>
<Badge variant="light" color="dark">
Dark
</Badge>
</div>
</div>
</div>
<div className="rounded-2xl border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03]">
<div className="px-6 py-5">
<h3 className="text-base font-medium text-gray-800 dark:text-white/90">
With Solid Background
</h3>
</div>
<div className="p-6 border-t border-gray-100 dark:border-gray-800 xl:p-10">
<div className="flex flex-wrap gap-4 sm:items-center sm:justify-center">
{/* Light Variant */}
<Badge variant="solid" color="primary">
Primary
</Badge>
<Badge variant="solid" color="success">
Success
</Badge>{" "}
<Badge variant="solid" color="error">
Error
</Badge>{" "}
<Badge variant="solid" color="warning">
Warning
</Badge>{" "}
<Badge variant="solid" color="info">
Info
</Badge>
<Badge variant="solid" color="light">
Light
</Badge>
<Badge variant="solid" color="dark">
Dark
</Badge>
</div>
</div>
</div>
<div className="rounded-2xl border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03]">
<div className="px-6 py-5">
<h3 className="text-base font-medium text-gray-800 dark:text-white/90">
Light Background with Left Icon
</h3>
</div>
<div className="p-6 border-t border-gray-100 dark:border-gray-800 xl:p-10">
<div className="flex flex-wrap gap-4 sm:items-center sm:justify-center">
<Badge variant="light" color="primary" startIcon={<PlusIcon />}>
Primary
</Badge>
<Badge variant="light" color="success" startIcon={<PlusIcon />}>
Success
</Badge>{" "}
<Badge variant="light" color="error" startIcon={<PlusIcon />}>
Error
</Badge>{" "}
<Badge variant="light" color="warning" startIcon={<PlusIcon />}>
Warning
</Badge>{" "}
<Badge variant="light" color="info" startIcon={<PlusIcon />}>
Info
</Badge>
<Badge variant="light" color="light" startIcon={<PlusIcon />}>
Light
</Badge>
<Badge variant="light" color="dark" startIcon={<PlusIcon />}>
Dark
</Badge>
</div>
</div>
</div>
<div className="rounded-2xl border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03]">
<div className="px-6 py-5">
<h3 className="text-base font-medium text-gray-800 dark:text-white/90">
Solid Background with Left Icon
</h3>
</div>
<div className="p-6 border-t border-gray-100 dark:border-gray-800 xl:p-10">
<div className="flex flex-wrap gap-4 sm:items-center sm:justify-center">
<Badge variant="solid" color="primary" startIcon={<PlusIcon />}>
Primary
</Badge>
<Badge variant="solid" color="success" startIcon={<PlusIcon />}>
Success
</Badge>{" "}
<Badge variant="solid" color="error" startIcon={<PlusIcon />}>
Error
</Badge>{" "}
<Badge variant="solid" color="warning" startIcon={<PlusIcon />}>
Warning
</Badge>{" "}
<Badge variant="solid" color="info" startIcon={<PlusIcon />}>
Info
</Badge>
<Badge variant="solid" color="light" startIcon={<PlusIcon />}>
Light
</Badge>
<Badge variant="solid" color="dark" startIcon={<PlusIcon />}>
Dark
</Badge>
</div>
</div>
</div>
<div className="rounded-2xl border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03]">
<div className="px-6 py-5">
<h3 className="text-base font-medium text-gray-800 dark:text-white/90">
Light Background with Right Icon
</h3>
</div>
<div className="p-6 border-t border-gray-100 dark:border-gray-800 xl:p-10">
<div className="flex flex-wrap gap-4 sm:items-center sm:justify-center">
<Badge variant="light" color="primary" endIcon={<PlusIcon />}>
Primary
</Badge>
<Badge variant="light" color="success" endIcon={<PlusIcon />}>
Success
</Badge>{" "}
<Badge variant="light" color="error" endIcon={<PlusIcon />}>
Error
</Badge>{" "}
<Badge variant="light" color="warning" endIcon={<PlusIcon />}>
Warning
</Badge>{" "}
<Badge variant="light" color="info" endIcon={<PlusIcon />}>
Info
</Badge>
<Badge variant="light" color="light" endIcon={<PlusIcon />}>
Light
</Badge>
<Badge variant="light" color="dark" endIcon={<PlusIcon />}>
Dark
</Badge>
</div>
</div>
</div>
<div className="rounded-2xl border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03]">
<div className="px-6 py-5">
<h3 className="text-base font-medium text-gray-800 dark:text-white/90">
Solid Background with Right Icon
</h3>
</div>
<div className="p-6 border-t border-gray-100 dark:border-gray-800 xl:p-10">
<div className="flex flex-wrap gap-4 sm:items-center sm:justify-center">
<Badge variant="solid" color="primary" endIcon={<PlusIcon />}>
Primary
</Badge>
<Badge variant="solid" color="success" endIcon={<PlusIcon />}>
Success
</Badge>{" "}
<Badge variant="solid" color="error" endIcon={<PlusIcon />}>
Error
</Badge>{" "}
<Badge variant="solid" color="warning" endIcon={<PlusIcon />}>
Warning
</Badge>{" "}
<Badge variant="solid" color="info" endIcon={<PlusIcon />}>
Info
</Badge>
<Badge variant="solid" color="light" endIcon={<PlusIcon />}>
Light
</Badge>
<Badge variant="solid" color="dark" endIcon={<PlusIcon />}>
Dark
</Badge>
</div>
</div>
</div>
</div>
</div>
);
}
@@ -1,89 +0,0 @@
import ComponentCard from "@/components/common/ComponentCard";
import PageBreadcrumb from "@/components/common/PageBreadCrumb";
import Button from "@/components/ui/button/Button";
import { BoxIcon } from "@/icons";
import { Metadata } from "next";
import React from "react";
export const metadata: Metadata = {
title: "Next.js Buttons | TailAdmin - Next.js Dashboard Template",
description:
"This is Next.js Buttons page for TailAdmin - Next.js Tailwind CSS Admin Dashboard Template",
};
export default function Buttons() {
return (
<div>
<PageBreadcrumb pageTitle="Buttons" />
<div className="space-y-5 sm:space-y-6">
{/* Primary Button */}
<ComponentCard title="Primary Button">
<div className="flex items-center gap-5">
<Button size="sm" variant="primary">
Button Text
</Button>
<Button size="md" variant="primary">
Button Text
</Button>
</div>
</ComponentCard>
{/* Primary Button with Start Icon */}
<ComponentCard title="Primary Button with Left Icon">
<div className="flex items-center gap-5">
<Button size="sm" variant="primary" startIcon={<BoxIcon />}>
Button Text
</Button>
<Button size="md" variant="primary" startIcon={<BoxIcon />}>
Button Text
</Button>
</div>
</ComponentCard>{" "}
{/* Primary Button with Start Icon */}
<ComponentCard title="Primary Button with Right Icon">
<div className="flex items-center gap-5">
<Button size="sm" variant="primary" endIcon={<BoxIcon />}>
Button Text
</Button>
<Button size="md" variant="primary" endIcon={<BoxIcon />}>
Button Text
</Button>
</div>
</ComponentCard>
{/* Outline Button */}
<ComponentCard title="Secondary Button">
<div className="flex items-center gap-5">
{/* Outline Button */}
<Button size="sm" variant="outline">
Button Text
</Button>
<Button size="md" variant="outline">
Button Text
</Button>
</div>
</ComponentCard>
{/* Outline Button with Start Icon */}
<ComponentCard title="Outline Button with Left Icon">
<div className="flex items-center gap-5">
<Button size="sm" variant="outline" startIcon={<BoxIcon />}>
Button Text
</Button>
<Button size="md" variant="outline" startIcon={<BoxIcon />}>
Button Text
</Button>
</div>
</ComponentCard>{" "}
{/* Outline Button with Start Icon */}
<ComponentCard title="Outline Button with Right Icon">
<div className="flex items-center gap-5">
<Button size="sm" variant="outline" endIcon={<BoxIcon />}>
Button Text
</Button>
<Button size="md" variant="outline" endIcon={<BoxIcon />}>
Button Text
</Button>
</div>
</ComponentCard>
</div>
</div>
);
}
@@ -1,33 +0,0 @@
import ComponentCard from "@/components/common/ComponentCard";
import PageBreadcrumb from "@/components/common/PageBreadCrumb";
import ResponsiveImage from "@/components/ui/images/ResponsiveImage";
import ThreeColumnImageGrid from "@/components/ui/images/ThreeColumnImageGrid";
import TwoColumnImageGrid from "@/components/ui/images/TwoColumnImageGrid";
import { Metadata } from "next";
import React from "react";
export const metadata: Metadata = {
title: "Next.js Images | TailAdmin - Next.js Dashboard Template",
description:
"This is Next.js Images page for TailAdmin - Next.js Tailwind CSS Admin Dashboard Template",
// other metadata
};
export default function Images() {
return (
<div>
<PageBreadcrumb pageTitle="Images" />
<div className="space-y-5 sm:space-y-6">
<ComponentCard title="Responsive image">
<ResponsiveImage />
</ComponentCard>
<ComponentCard title="Image in 2 Grid">
<TwoColumnImageGrid />
</ComponentCard>
<ComponentCard title="Image in 3 Grid">
<ThreeColumnImageGrid />
</ComponentCard>
</div>
</div>
);
}
@@ -1,30 +0,0 @@
import PageBreadcrumb from "@/components/common/PageBreadCrumb";
import DefaultModal from "@/components/example/ModalExample/DefaultModal";
import FormInModal from "@/components/example/ModalExample/FormInModal";
import FullScreenModal from "@/components/example/ModalExample/FullScreenModal";
import ModalBasedAlerts from "@/components/example/ModalExample/ModalBasedAlerts";
import VerticallyCenteredModal from "@/components/example/ModalExample/VerticallyCenteredModal";
import { Metadata } from "next";
import React from "react";
export const metadata: Metadata = {
title: "Next.js Modals | TailAdmin - Next.js Dashboard Template",
description:
"This is Next.js Modals page for TailAdmin - Next.js Tailwind CSS Admin Dashboard Template",
// other metadata
};
export default function Modals() {
return (
<div>
<PageBreadcrumb pageTitle="Modals" />
<div className="grid grid-cols-1 gap-5 xl:grid-cols-2 xl:gap-6">
<DefaultModal />
<VerticallyCenteredModal />
<FormInModal />
<FullScreenModal />
<ModalBasedAlerts />
</div>
</div>
);
}
@@ -1,20 +0,0 @@
import PageBreadcrumb from "@/components/common/PageBreadCrumb";
import VideosExample from "@/components/ui/video/VideosExample";
import { Metadata } from "next";
import React from "react";
export const metadata: Metadata = {
title: "Next.js Videos | TailAdmin - Next.js Dashboard Template",
description:
"This is Next.js Videos page for TailAdmin - Next.js Tailwind CSS Admin Dashboard Template",
};
export default function VideoPage() {
return (
<div>
<PageBreadcrumb pageTitle="Videos" />
<VideosExample />
</div>
);
}
-24
View File
@@ -1,24 +0,0 @@
import BarChartOne from "@/components/charts/bar/BarChartOne";
import ComponentCard from "@/components/common/ComponentCard";
import PageBreadcrumb from "@/components/common/PageBreadCrumb";
import { Metadata } from "next";
import React from "react";
export const metadata: Metadata = {
title: "Next.js Bar Chart | TailAdmin - Next.js Dashboard Template",
description:
"This is Next.js Bar Chart page for TailAdmin - Next.js Tailwind CSS Admin Dashboard Template",
};
export default function page() {
return (
<div>
<PageBreadcrumb pageTitle="Bar Chart" />
<div className="space-y-6">
<ComponentCard title="Bar Chart 1">
<BarChartOne />
</ComponentCard>
</div>
</div>
);
}
-23
View File
@@ -1,23 +0,0 @@
import LineChartOne from "@/components/charts/line/LineChartOne";
import ComponentCard from "@/components/common/ComponentCard";
import PageBreadcrumb from "@/components/common/PageBreadCrumb";
import { Metadata } from "next";
import React from "react";
export const metadata: Metadata = {
title: "Next.js Line Chart | TailAdmin - Next.js Dashboard Template",
description:
"This is Next.js Line Chart page for TailAdmin - Next.js Tailwind CSS Admin Dashboard Template",
};
export default function LineChart() {
return (
<div>
<PageBreadcrumb pageTitle="Line Chart" />
<div className="space-y-6">
<ComponentCard title="Line Chart 1">
<LineChartOne />
</ComponentCard>
</div>
</div>
);
}
-129
View File
@@ -1,129 +0,0 @@
"use client";
import AccountDetails from "@/components/user-profile/AccountDetails";
import UserInfoCard from "@/components/user-profile/UserInfoCard";
import UserMetaCard from "@/components/user-profile/UserMetaCard";
import { UserMetaCardProps } from "@/interface/user";
import { apiGetCurrentUser } from "@/service/auth";
import { setUserData } from "@/store/features/userSlice";
import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { RootState } from "@/store/store";
import StickyHeader from "@/components/ui/StickyHeader";
import { SafeHTMLRenderer } from "@/components/ui/parse/SafeHTMLRenderer";
import { apiGetCurrentUserApplications } from "@/service/userService";
import Loading from "@/app/loading";
export default function Profile() {
const currentUser = useSelector((state: RootState) => state.user.data);
const dispatch = useDispatch();
const [application, setApplication] = useState<any>(null);
const [appLoading, setAppLoading] = useState(false);
const isHistorian = !!currentUser?.roles?.some(
(role: any) => role.name === "HISTORIAN"
);
// Background refresh of user data to ensure eventual consistency
useEffect(() => {
const fetchUser = async () => {
try {
const userData = await apiGetCurrentUser();
dispatch(setUserData(userData.data));
} catch (err) {
console.error("Lỗi:", err);
}
};
fetchUser();
}, [dispatch]);
// Fetch applications in parallel immediately if user is a historian
useEffect(() => {
if (isHistorian) {
const fetchApp = async () => {
try {
setAppLoading(true);
const res = await apiGetCurrentUserApplications();
if (res?.data) {
const approvedApp =
res.data.find((app: any) => app.status === "APPROVED") ||
res.data[0];
setApplication(approvedApp);
}
} catch (err) {
console.error("Lỗi khi tải hồ sơ nhà sử học:", err);
} finally {
setAppLoading(false);
}
};
fetchApp();
}
}, [isHistorian]);
if (!currentUser) {
return <Loading />;
}
const userMetaProps: UserMetaCardProps = {
data: currentUser
? {
id: currentUser.id,
email: currentUser.email,
profile: currentUser.profile,
roles: currentUser.roles?.map((role) => ({
id: Number(role.id) || undefined,
name: role.name,
})),
}
: undefined,
status: true,
};
// Nếu người dùng có role là HISTORIAN
if (isHistorian) {
return (
<div>
<StickyHeader header={`Thông tin tài khoản`} />
<div className="md:px-12 flex flex-col md:flex-row mx-auto gap-6 w-full max-w-7xl items-start">
<div className="w-full md:max-w-72 xl:max-w-82 pr-0 md:pr-4 border-b md:border-b-0 md:border-r border-gray-300 pb-6 md:pb-0 shrink-0 space-y-6">
<UserMetaCard data={userMetaProps} />
<UserInfoCard data={{ ...userMetaProps, openEdit: true }} />
<AccountDetails data={userMetaProps} />
</div>
<div className="flex-1 min-w-0 w-full">
{appLoading ? (
<div className="flex items-center justify-center p-20 w-full bg-zinc-50/50 dark:bg-zinc-950/30 rounded-2xl border border-zinc-200 dark:border-zinc-800">
<div className="w-8 h-8 border-4 border-zinc-200 border-t-blue-600 rounded-full animate-spin"></div>
</div>
) : application ? (
<div className="">
<SafeHTMLRenderer html={application.content} />
</div>
) : (
<div className="p-10 text-center text-zinc-500 font-medium bg-zinc-50/50 dark:bg-zinc-950/30 rounded-2xl border-2 border-zinc-50 dark:border-zinc-800">
Không tìm thấy thông tin hồ nhà sử học.
</div>
)}
</div>
</div>
</div>
);
}
return (
<div>
<div className="rounded-2xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/[0.03] lg:p-6 mt-[100px]">
<h3 className="mb-5 text-lg font-semibold text-gray-800 dark:text-white/90 lg:mb-7">
Thông tin tài khoản
</h3>
<div className="space-y-6">
<UserMetaCard data={userMetaProps} />
<UserInfoCard data={{ ...userMetaProps, openEdit: true }} />
<AccountDetails data={userMetaProps} />
</div>
</div>
</div>
);
}
+120 -35
View File
@@ -1,41 +1,126 @@
import type { Metadata } from "next";
import { EcommerceMetrics } from "@/components/ecommerce/EcommerceMetrics";
import React from "react";
import MonthlyTarget from "@/components/ecommerce/MonthlyTarget";
import MonthlySalesChart from "@/components/ecommerce/MonthlySalesChart";
import StatisticsChart from "@/components/ecommerce/StatisticsChart";
import RecentOrders from "@/components/ecommerce/RecentOrders";
import DemographicCard from "@/components/ecommerce/DemographicCard";
"use client";
export const metadata: Metadata = {
title:
"Home Page",
description: "This is Dashboard Home for History Web",
};
import AccountDetails from "@/components/user-profile/AccountDetails";
import UserInfoCard from "@/components/user-profile/UserInfoCard";
import UserMetaCard from "@/components/user-profile/UserMetaCard";
import { UserMetaCardProps } from "@/interface/user";
import { apiGetCurrentUser } from "@/service/auth";
import { setUserData } from "@/store/features/userSlice";
import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { RootState } from "@/store/store";
import StickyHeader from "@/components/ui/StickyHeader";
import { SafeHTMLRenderer } from "@/components/ui/parse/SafeHTMLRenderer";
import { apiGetCurrentUserApplications } from "@/service/userService";
import Loading from "@/app/loading";
export default function Profile() {
const currentUser = useSelector((state: RootState) => state.user.data);
const dispatch = useDispatch();
const [application, setApplication] = useState<any>(null);
const [appLoading, setAppLoading] = useState(false);
const isHistorian = !!currentUser?.roles?.some(
(role: any) => role.name === "HISTORIAN"
);
useEffect(() => {
const fetchUser = async () => {
try {
const userData = await apiGetCurrentUser();
dispatch(setUserData(userData.data));
} catch (err) {
console.error("Lỗi:", err);
}
};
fetchUser();
}, [dispatch]);
useEffect(() => {
if (isHistorian) {
const fetchApp = async () => {
try {
setAppLoading(true);
const res = await apiGetCurrentUserApplications();
if (res?.data) {
const approvedApp =
res.data.find((app: any) => app.status === "APPROVED") ||
res.data[0];
setApplication(approvedApp);
}
} catch (err) {
console.error("Lỗi khi tải hồ sơ nhà sử học:", err);
} finally {
setAppLoading(false);
}
};
fetchApp();
}
}, [isHistorian]);
if (!currentUser) {
return <Loading />;
}
const userMetaProps: UserMetaCardProps = {
data: currentUser
? {
id: currentUser.id,
email: currentUser.email,
profile: currentUser.profile,
roles: currentUser.roles?.map((role) => ({
id: Number(role.id) || undefined,
name: role.name,
})),
}
: undefined,
status: true,
};
// Nếu người dùng có role là HISTORIAN
if (isHistorian) {
return (
<div>
<StickyHeader header={`Thông tin tài khoản`} />
<div className="md:px-12 flex flex-col md:flex-row mx-auto gap-6 w-full max-w-7xl items-start">
<div className="w-full md:max-w-72 xl:max-w-82 pr-0 md:pr-4 border-b md:border-b-0 md:border-r border-gray-300 pb-6 md:pb-0 shrink-0 space-y-6">
<UserMetaCard data={userMetaProps} />
<UserInfoCard data={{ ...userMetaProps, openEdit: true }} />
<AccountDetails data={userMetaProps} />
</div>
<div className="flex-1 min-w-0 w-full">
{appLoading ? (
<div className="flex items-center justify-center p-20 w-full bg-zinc-50/50 dark:bg-zinc-950/30 rounded-2xl border border-zinc-200 dark:border-zinc-800">
<div className="w-8 h-8 border-4 border-zinc-200 border-t-blue-600 rounded-full animate-spin"></div>
</div>
) : application ? (
<div className="">
<SafeHTMLRenderer html={application.content} />
</div>
) : (
<div className="p-10 text-center text-zinc-500 font-medium bg-zinc-50/50 dark:bg-zinc-950/30 rounded-2xl border-2 border-zinc-50 dark:border-zinc-800">
Không tìm thấy thông tin hồ nhà sử học.
</div>
)}
</div>
</div>
</div>
);
}
export default function Ecommerce() {
return (
<div className="grid grid-cols-12 gap-4 md:gap-6 2xl:gap-8">
<div className="col-span-12 space-y-6 xl:col-span-7 2xl:col-span-8">
<EcommerceMetrics />
<MonthlySalesChart />
</div>
<div className="col-span-12 xl:col-span-5 2xl:col-span-4">
<MonthlyTarget />
</div>
<div className="col-span-12">
<StatisticsChart />
</div>
<div className="col-span-12 xl:col-span-5 2xl:col-span-4">
<DemographicCard />
</div>
<div className="col-span-12 xl:col-span-7 2xl:col-span-8">
<RecentOrders />
<div>
<div className="rounded-2xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/[0.03] lg:p-6 mt-[100px]">
<h3 className="mb-5 text-lg font-semibold text-gray-800 dark:text-white/90 lg:mb-7">
Thông tin tài khoản
</h3>
<div className="space-y-6">
<UserMetaCard data={userMetaProps} />
<UserInfoCard data={{ ...userMetaProps, openEdit: true }} />
<AccountDetails data={userMetaProps} />
</div>
</div>
</div>
);
-293
View File
@@ -1,293 +0,0 @@
"use client";
import React, { useState, useRef, useEffect } from "react";
import FullCalendar from "@fullcalendar/react";
import dayGridPlugin from "@fullcalendar/daygrid";
import timeGridPlugin from "@fullcalendar/timegrid";
import interactionPlugin from "@fullcalendar/interaction";
import {
EventInput,
DateSelectArg,
EventClickArg,
EventContentArg,
} from "@fullcalendar/core";
import { useModal } from "@/hooks/useModal";
import { Modal } from "@/components/ui/modal";
import { newId } from "@/uhm/lib/utils/id";
interface CalendarEvent extends EventInput {
extendedProps: {
calendar: string;
};
}
const Calendar: React.FC = () => {
const [selectedEvent, setSelectedEvent] = useState<CalendarEvent | null>(
null
);
const [eventTitle, setEventTitle] = useState("");
const [eventStartDate, setEventStartDate] = useState("");
const [eventEndDate, setEventEndDate] = useState("");
const [eventLevel, setEventLevel] = useState("");
const [events, setEvents] = useState<CalendarEvent[]>([]);
const calendarRef = useRef<FullCalendar>(null);
const { isOpen, openModal, closeModal } = useModal();
const calendarsEvents = {
Danger: "danger",
Success: "success",
Primary: "primary",
Warning: "warning",
};
useEffect(() => {
// Initialize with some events
setEvents([
{
id: "1",
title: "Event Conf.",
start: new Date().toISOString().split("T")[0],
extendedProps: { calendar: "Danger" },
},
{
id: "2",
title: "Meeting",
start: new Date(Date.now() + 86400000).toISOString().split("T")[0],
extendedProps: { calendar: "Success" },
},
{
id: "3",
title: "Workshop",
start: new Date(Date.now() + 172800000).toISOString().split("T")[0],
end: new Date(Date.now() + 259200000).toISOString().split("T")[0],
extendedProps: { calendar: "Primary" },
},
]);
}, []);
const handleDateSelect = (selectInfo: DateSelectArg) => {
resetModalFields();
setEventStartDate(selectInfo.startStr);
setEventEndDate(selectInfo.endStr || selectInfo.startStr);
openModal();
};
const handleEventClick = (clickInfo: EventClickArg) => {
const event = clickInfo.event;
setSelectedEvent({
id: event.id,
title: event.title,
start: event.startStr,
end: event.endStr,
extendedProps: {
calendar: event.extendedProps.calendar,
},
} as CalendarEvent);
setEventTitle(event.title);
setEventStartDate(event.start?.toISOString().split("T")[0] || "");
setEventEndDate(event.end?.toISOString().split("T")[0] || "");
setEventLevel(event.extendedProps.calendar);
openModal();
};
const handleAddOrUpdateEvent = () => {
if (selectedEvent) {
// Update existing event
setEvents((prevEvents) =>
prevEvents.map((event) =>
event.id === selectedEvent.id
? {
...event,
title: eventTitle,
start: eventStartDate,
end: eventEndDate,
extendedProps: { calendar: eventLevel },
}
: event
)
);
} else {
// Add new event
const newEvent: CalendarEvent = {
id: newId(),
title: eventTitle,
start: eventStartDate,
end: eventEndDate,
allDay: true,
extendedProps: { calendar: eventLevel },
};
setEvents((prevEvents) => [...prevEvents, newEvent]);
}
closeModal();
resetModalFields();
};
const resetModalFields = () => {
setEventTitle("");
setEventStartDate("");
setEventEndDate("");
setEventLevel("");
setSelectedEvent(null);
};
return (
<div className="rounded-2xl border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03]">
<div className="custom-calendar">
<FullCalendar
ref={calendarRef}
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
initialView="dayGridMonth"
headerToolbar={{
left: "prev,next addEventButton",
center: "title",
right: "dayGridMonth,timeGridWeek,timeGridDay",
}}
events={events}
selectable={true}
select={handleDateSelect}
eventClick={handleEventClick}
eventContent={renderEventContent}
customButtons={{
addEventButton: {
text: "Add Event +",
click: openModal,
},
}}
/>
</div>
<Modal
isOpen={isOpen}
onClose={closeModal}
className="max-w-[700px] p-6 lg:p-10"
>
<div className="flex flex-col px-2 overflow-y-auto custom-scrollbar">
<div>
<h5 className="mb-2 font-semibold text-gray-800 modal-title text-theme-xl dark:text-white/90 lg:text-2xl">
{selectedEvent ? "Edit Event" : "Add Event"}
</h5>
<p className="text-sm text-gray-500 dark:text-gray-400">
Plan your next big moment: schedule or edit an event to stay on
track
</p>
</div>
<div className="mt-8">
<div>
<div>
<label className="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
Event Title
</label>
<input
id="event-title"
type="text"
value={eventTitle}
onChange={(e) => setEventTitle(e.target.value)}
className="dark:bg-dark-900 h-11 w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
/>
</div>
</div>
<div className="mt-6">
<label className="block mb-4 text-sm font-medium text-gray-700 dark:text-gray-400">
Event Color
</label>
<div className="flex flex-wrap items-center gap-4 sm:gap-5">
{Object.entries(calendarsEvents).map(([key, value]) => (
<div key={key} className="n-chk">
<div
className={`form-check form-check-${value} form-check-inline`}
>
<label
className="flex items-center text-sm text-gray-700 form-check-label dark:text-gray-400"
htmlFor={`modal${key}`}
>
<span className="relative">
<input
className="sr-only form-check-input"
type="radio"
name="event-level"
value={key}
id={`modal${key}`}
checked={eventLevel === key}
onChange={() => setEventLevel(key)}
/>
<span className="flex items-center justify-center w-5 h-5 mr-2 border border-gray-300 rounded-full box dark:border-gray-700">
<span
className={`h-2 w-2 rounded-full bg-white ${
eventLevel === key ? "block" : "hidden"
}`}
></span>
</span>
</span>
{key}
</label>
</div>
</div>
))}
</div>
</div>
<div className="mt-6">
<label className="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
Enter Start Date
</label>
<div className="relative">
<input
id="event-start-date"
type="date"
value={eventStartDate}
onChange={(e) => setEventStartDate(e.target.value)}
className="dark:bg-dark-900 h-11 w-full appearance-none rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 pl-4 pr-11 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
/>
</div>
</div>
<div className="mt-6">
<label className="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
Enter End Date
</label>
<div className="relative">
<input
id="event-end-date"
type="date"
value={eventEndDate}
onChange={(e) => setEventEndDate(e.target.value)}
className="dark:bg-dark-900 h-11 w-full appearance-none rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 pl-4 pr-11 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
/>
</div>
</div>
</div>
<div className="flex items-center gap-3 mt-6 modal-footer sm:justify-end">
<button
onClick={closeModal}
type="button"
className="flex w-full justify-center rounded-lg border border-gray-300 bg-white px-4 py-2.5 text-sm font-medium text-gray-700 hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03] sm:w-auto"
>
Close
</button>
<button
onClick={handleAddOrUpdateEvent}
type="button"
className="btn btn-success btn-update-event flex w-full justify-center rounded-lg bg-brand-500 px-4 py-2.5 text-sm font-medium text-white hover:bg-brand-600 sm:w-auto"
>
{selectedEvent ? "Update Changes" : "Add Event"}
</button>
</div>
</div>
</Modal>
</div>
);
};
const renderEventContent = (eventInfo: EventContentArg) => {
const colorClass = `fc-bg-${eventInfo.event.extendedProps.calendar.toLowerCase()}`;
return (
<div
className={`event-fc-color flex fc-event-main ${colorClass} p-1 rounded-sm`}
>
<div className="fc-daygrid-event-dot"></div>
<div className="fc-event-time">{eventInfo.timeText}</div>
<div className="fc-event-title">{eventInfo.event.title}</div>
</div>
);
};
export default Calendar;
-110
View File
@@ -1,110 +0,0 @@
"use client";
import React from "react";
import { ApexOptions } from "apexcharts";
import dynamic from "next/dynamic";
// Dynamically import the ReactApexChart component
const ReactApexChart = dynamic(() => import("react-apexcharts"), {
ssr: false,
});
export default function BarChartOne() {
const options: ApexOptions = {
colors: ["#465fff"],
chart: {
fontFamily: "Outfit, sans-serif",
type: "bar",
height: 180,
toolbar: {
show: false,
},
},
plotOptions: {
bar: {
horizontal: false,
columnWidth: "39%",
borderRadius: 5,
borderRadiusApplication: "end",
},
},
dataLabels: {
enabled: false,
},
stroke: {
show: true,
width: 4,
colors: ["transparent"],
},
xaxis: {
categories: [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
],
axisBorder: {
show: false,
},
axisTicks: {
show: false,
},
},
legend: {
show: true,
position: "top",
horizontalAlign: "left",
fontFamily: "Outfit",
},
yaxis: {
title: {
text: undefined,
},
},
grid: {
yaxis: {
lines: {
show: true,
},
},
},
fill: {
opacity: 1,
},
tooltip: {
x: {
show: false,
},
y: {
formatter: (val: number) => `${val}`,
},
},
};
const series = [
{
name: "Sales",
data: [168, 385, 201, 298, 187, 195, 291, 110, 215, 390, 280, 112],
},
];
return (
<div className="max-w-full overflow-x-auto custom-scrollbar">
<div id="chartOne" className="min-w-[1000px]">
<ReactApexChart
options={options}
series={series}
type="bar"
height={180}
/>
</div>
</div>
);
}
-133
View File
@@ -1,133 +0,0 @@
"use client";
import React from "react";
import { ApexOptions } from "apexcharts";
import dynamic from "next/dynamic";
// Dynamically import the ReactApexChart component
const ReactApexChart = dynamic(() => import("react-apexcharts"), {
ssr: false,
});
export default function LineChartOne() {
const options: ApexOptions = {
legend: {
show: false, // Hide legend
position: "top",
horizontalAlign: "left",
},
colors: ["#465FFF", "#9CB9FF"], // Define line colors
chart: {
fontFamily: "Outfit, sans-serif",
height: 310,
type: "line", // Set the chart type to 'line'
toolbar: {
show: false, // Hide chart toolbar
},
},
stroke: {
curve: "straight", // Define the line style (straight, smooth, or step)
width: [2, 2], // Line width for each dataset
},
fill: {
type: "gradient",
gradient: {
opacityFrom: 0.55,
opacityTo: 0,
},
},
markers: {
size: 0, // Size of the marker points
strokeColors: "#fff", // Marker border color
strokeWidth: 2,
hover: {
size: 6, // Marker size on hover
},
},
grid: {
xaxis: {
lines: {
show: false, // Hide grid lines on x-axis
},
},
yaxis: {
lines: {
show: true, // Show grid lines on y-axis
},
},
},
dataLabels: {
enabled: false, // Disable data labels
},
tooltip: {
enabled: true, // Enable tooltip
x: {
format: "dd MMM yyyy", // Format for x-axis tooltip
},
},
xaxis: {
type: "category", // Category-based x-axis
categories: [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
],
axisBorder: {
show: false, // Hide x-axis border
},
axisTicks: {
show: false, // Hide x-axis ticks
},
tooltip: {
enabled: false, // Disable tooltip for x-axis points
},
},
yaxis: {
labels: {
style: {
fontSize: "12px", // Adjust font size for y-axis labels
colors: ["#6B7280"], // Color of the labels
},
},
title: {
text: "", // Remove y-axis title
style: {
fontSize: "0px",
},
},
},
};
const series = [
{
name: "Sales",
data: [180, 190, 170, 160, 175, 165, 170, 205, 230, 210, 240, 235],
},
{
name: "Revenue",
data: [40, 30, 50, 40, 55, 40, 70, 100, 110, 120, 150, 140],
},
];
return (
<div className="max-w-full overflow-x-auto custom-scrollbar">
<div id="chartEight" className="min-w-[1000px]">
<ReactApexChart
options={options}
series={series}
type="area"
height={310}
/>
</div>
</div>
);
}
-45
View File
@@ -1,45 +0,0 @@
import React, { useState } from "react";
const ChartTab: React.FC = () => {
const [selected, setSelected] = useState<
"optionOne" | "optionTwo" | "optionThree"
>("optionOne");
const getButtonClass = (option: "optionOne" | "optionTwo" | "optionThree") =>
selected === option
? "shadow-theme-xs text-gray-900 dark:text-white bg-white dark:bg-gray-800"
: "text-gray-500 dark:text-gray-400";
return (
<div className="flex items-center gap-0.5 rounded-lg bg-gray-100 p-0.5 dark:bg-gray-900">
<button
onClick={() => setSelected("optionOne")}
className={`px-3 py-2 font-medium w-full rounded-md text-theme-sm hover:text-gray-900 dark:hover:text-white ${getButtonClass(
"optionOne"
)}`}
>
Monthly
</button>
<button
onClick={() => setSelected("optionTwo")}
className={`px-3 py-2 font-medium w-full rounded-md text-theme-sm hover:text-gray-900 dark:hover:text-white ${getButtonClass(
"optionTwo"
)}`}
>
Quarterly
</button>
<button
onClick={() => setSelected("optionThree")}
className={`px-3 py-2 font-medium w-full rounded-md text-theme-sm hover:text-gray-900 dark:hover:text-white ${getButtonClass(
"optionThree"
)}`}
>
Annually
</button>
</div>
);
};
export default ChartTab;
-123
View File
@@ -1,123 +0,0 @@
import React from "react";
// import { VectorMap } from "@react-jvectormap/core";
import { worldMill } from "@react-jvectormap/world";
import dynamic from "next/dynamic";
const VectorMap = dynamic(
() => import("@react-jvectormap/core").then((mod) => mod.VectorMap),
{ ssr: false }
);
// Define the component props
interface CountryMapProps {
mapColor?: string;
}
type MarkerStyle = {
initial: {
fill: string;
r: number; // Radius for markers
};
};
type Marker = {
latLng: [number, number];
name: string;
style?: {
fill: string;
borderWidth: number;
borderColor: string;
stroke?: string;
strokeOpacity?: number;
};
};
const CountryMap: React.FC<CountryMapProps> = ({ mapColor }) => {
return (
<VectorMap
map={worldMill}
backgroundColor="transparent"
markerStyle={
{
initial: {
fill: "#465FFF",
r: 4, // Custom radius for markers
}, // Type assertion to bypass strict CSS property checks
} as MarkerStyle
}
markersSelectable={true}
markers={
[
{
latLng: [37.2580397, -104.657039],
name: "United States",
style: {
fill: "#465FFF",
borderWidth: 1,
borderColor: "white",
stroke: "#383f47",
},
},
{
latLng: [20.7504374, 73.7276105],
name: "India",
style: { fill: "#465FFF", borderWidth: 1, borderColor: "white" },
},
{
latLng: [53.613, -11.6368],
name: "United Kingdom",
style: { fill: "#465FFF", borderWidth: 1, borderColor: "white" },
},
{
latLng: [-25.0304388, 115.2092761],
name: "Sweden",
style: {
fill: "#465FFF",
borderWidth: 1,
borderColor: "white",
strokeOpacity: 0,
},
},
] as Marker[]
}
zoomOnScroll={false}
zoomMax={12}
zoomMin={1}
zoomAnimate={true}
zoomStep={1.5}
regionStyle={{
initial: {
fill: mapColor || "#D0D5DD",
fillOpacity: 1,
fontFamily: "Outfit",
stroke: "none",
strokeWidth: 0,
strokeOpacity: 0,
},
hover: {
fillOpacity: 0.7,
cursor: "pointer",
fill: "#465fff",
stroke: "none",
},
selected: {
fill: "#465FFF",
},
selectedHover: {},
}}
regionLabelStyle={{
initial: {
fill: "#35373e",
fontWeight: 500,
fontSize: "13px",
stroke: "none",
},
hover: {},
selected: {},
selectedHover: {},
}}
/>
);
};
export default CountryMap;
@@ -1,131 +0,0 @@
"use client";
import Image from "next/image";
import CountryMap from "./CountryMap";
import { useState } from "react";
import { MoreDotIcon } from "@/icons";
import { Dropdown } from "../ui/dropdown/Dropdown";
import { DropdownItem } from "../ui/dropdown/DropdownItem";
export default function DemographicCard() {
const [isOpen, setIsOpen] = useState(false);
function toggleDropdown() {
setIsOpen(!isOpen);
}
function closeDropdown() {
setIsOpen(false);
}
return (
<div className="rounded-2xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/[0.03] sm:p-6">
<div className="flex justify-between">
<div>
<h3 className="text-lg font-semibold text-gray-800 dark:text-white/90">
Customers Demographic
</h3>
<p className="mt-1 text-gray-500 text-theme-sm dark:text-gray-400">
Number of customer based on country
</p>
</div>
<div className="relative inline-block">
<button onClick={toggleDropdown} className="dropdown-toggle">
<MoreDotIcon className="text-gray-400 hover:text-gray-700 dark:hover:text-gray-300" />
</button>
<Dropdown
isOpen={isOpen}
onClose={closeDropdown}
className="w-40 p-2"
>
<DropdownItem
onItemClick={closeDropdown}
className="flex w-full font-normal text-left text-gray-500 rounded-lg hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300"
>
View More
</DropdownItem>
<DropdownItem
onItemClick={closeDropdown}
className="flex w-full font-normal text-left text-gray-500 rounded-lg hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300"
>
Delete
</DropdownItem>
</Dropdown>
</div>
</div>
<div className="px-4 py-6 my-6 overflow-hidden border border-gary-200 rounded-2xl bg-gray-50 dark:border-gray-800 dark:bg-gray-900 sm:px-6">
<div
id="mapOne"
className="mapOne map-btn -mx-4 -my-6 h-[212px] w-[252px] 2xsm:w-[307px] xsm:w-[358px] sm:-mx-6 md:w-[668px] lg:w-[634px] xl:w-[393px] 2xl:w-[554px]"
>
<CountryMap />
</div>
</div>
<div className="space-y-5">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="items-center w-full rounded-full max-w-8">
<Image
width={48}
height={48}
src="/images/country/country-01.svg"
alt="usa"
className="w-full"
/>
</div>
<div>
<p className="font-semibold text-gray-800 text-theme-sm dark:text-white/90">
USA
</p>
<span className="block text-gray-500 text-theme-xs dark:text-gray-400">
2,379 Customers
</span>
</div>
</div>
<div className="flex w-full max-w-[140px] items-center gap-3">
<div className="relative block h-2 w-full max-w-[100px] rounded-sm bg-gray-200 dark:bg-gray-800">
<div className="absolute left-0 top-0 flex h-full w-[79%] items-center justify-center rounded-sm bg-brand-500 text-xs font-medium text-white"></div>
</div>
<p className="font-medium text-gray-800 text-theme-sm dark:text-white/90">
79%
</p>
</div>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="items-center w-full rounded-full max-w-8">
<Image
width={48}
height={48}
className="w-full"
src="/images/country/country-02.svg"
alt="france"
/>
</div>
<div>
<p className="font-semibold text-gray-800 text-theme-sm dark:text-white/90">
France
</p>
<span className="block text-gray-500 text-theme-xs dark:text-gray-400">
589 Customers
</span>
</div>
</div>
<div className="flex w-full max-w-[140px] items-center gap-3">
<div className="relative block h-2 w-full max-w-[100px] rounded-sm bg-gray-200 dark:bg-gray-800">
<div className="absolute left-0 top-0 flex h-full w-[23%] items-center justify-center rounded-sm bg-brand-500 text-xs font-medium text-white"></div>
</div>
<p className="font-medium text-gray-800 text-theme-sm dark:text-white/90">
23%
</p>
</div>
</div>
</div>
</div>
);
}
@@ -1,56 +0,0 @@
"use client";
import React from "react";
import Badge from "../ui/badge/Badge";
import { ArrowDownIcon, ArrowUpIcon, BoxIconLine, GroupIcon } from "@/icons";
export const EcommerceMetrics = () => {
return (
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:gap-6">
{/* <!-- Metric Item Start --> */}
<div className="rounded-2xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/[0.03] md:p-6">
<div className="flex items-center justify-center w-12 h-12 bg-gray-100 rounded-xl dark:bg-gray-800">
<GroupIcon className="text-gray-800 size-6 dark:text-white/90" />
</div>
<div className="flex items-end justify-between mt-5">
<div>
<span className="text-sm text-gray-500 dark:text-gray-400">
Customers
</span>
<h4 className="mt-2 font-bold text-gray-800 text-title-sm dark:text-white/90">
3,782
</h4>
</div>
<Badge color="success">
<ArrowUpIcon />
11.01%
</Badge>
</div>
</div>
{/* <!-- Metric Item End --> */}
{/* <!-- Metric Item Start --> */}
<div className="rounded-2xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/[0.03] md:p-6">
<div className="flex items-center justify-center w-12 h-12 bg-gray-100 rounded-xl dark:bg-gray-800">
<BoxIconLine className="text-gray-800 dark:text-white/90" />
</div>
<div className="flex items-end justify-between mt-5">
<div>
<span className="text-sm text-gray-500 dark:text-gray-400">
Orders
</span>
<h4 className="mt-2 font-bold text-gray-800 text-title-sm dark:text-white/90">
5,359
</h4>
</div>
<Badge color="error">
<ArrowDownIcon className="text-error-500" />
9.05%
</Badge>
</div>
</div>
{/* <!-- Metric Item End --> */}
</div>
);
};
@@ -1,154 +0,0 @@
"use client";
import { ApexOptions } from "apexcharts";
import dynamic from "next/dynamic";
import { MoreDotIcon } from "@/icons";
import { DropdownItem } from "../ui/dropdown/DropdownItem";
import { useState } from "react";
import { Dropdown } from "../ui/dropdown/Dropdown";
// Dynamically import the ReactApexChart component
const ReactApexChart = dynamic(() => import("react-apexcharts"), {
ssr: false,
});
export default function MonthlySalesChart() {
const options: ApexOptions = {
colors: ["#465fff"],
chart: {
fontFamily: "Outfit, sans-serif",
type: "bar",
height: 180,
toolbar: {
show: false,
},
},
plotOptions: {
bar: {
horizontal: false,
columnWidth: "39%",
borderRadius: 5,
borderRadiusApplication: "end",
},
},
dataLabels: {
enabled: false,
},
stroke: {
show: true,
width: 4,
colors: ["transparent"],
},
xaxis: {
categories: [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
],
axisBorder: {
show: false,
},
axisTicks: {
show: false,
},
},
legend: {
show: true,
position: "top",
horizontalAlign: "left",
fontFamily: "Outfit",
},
yaxis: {
title: {
text: undefined,
},
},
grid: {
yaxis: {
lines: {
show: true,
},
},
},
fill: {
opacity: 1,
},
tooltip: {
x: {
show: false,
},
y: {
formatter: (val: number) => `${val}`,
},
},
};
const series = [
{
name: "Sales",
data: [168, 385, 201, 298, 187, 195, 291, 110, 215, 390, 280, 112],
},
];
const [isOpen, setIsOpen] = useState(false);
function toggleDropdown() {
setIsOpen(!isOpen);
}
function closeDropdown() {
setIsOpen(false);
}
return (
<div className="overflow-hidden rounded-2xl border border-gray-200 bg-white px-5 pt-5 dark:border-gray-800 dark:bg-white/[0.03] sm:px-6 sm:pt-6">
<div className="flex items-center justify-between">
<h3 className="text-lg font-semibold text-gray-800 dark:text-white/90">
Monthly Sales
</h3>
<div className="relative inline-block">
<button onClick={toggleDropdown} className="dropdown-toggle">
<MoreDotIcon className="text-gray-400 hover:text-gray-700 dark:hover:text-gray-300" />
</button>
<Dropdown
isOpen={isOpen}
onClose={closeDropdown}
className="w-40 p-2"
>
<DropdownItem
onItemClick={closeDropdown}
className="flex w-full font-normal text-left text-gray-500 rounded-lg hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300"
>
View More
</DropdownItem>
<DropdownItem
onItemClick={closeDropdown}
className="flex w-full font-normal text-left text-gray-500 rounded-lg hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300"
>
Delete
</DropdownItem>
</Dropdown>
</div>
</div>
<div className="max-w-full overflow-x-auto custom-scrollbar">
<div className="-ml-5 min-w-[650px] xl:min-w-full pl-2">
<ReactApexChart
options={options}
series={series}
type="bar"
height={180}
/>
</div>
</div>
</div>
);
}
-209
View File
@@ -1,209 +0,0 @@
"use client";
// import Chart from "react-apexcharts";
import { ApexOptions } from "apexcharts";
import dynamic from "next/dynamic";
import { Dropdown } from "../ui/dropdown/Dropdown";
import { MoreDotIcon } from "@/icons";
import { useState } from "react";
import { DropdownItem } from "../ui/dropdown/DropdownItem";
// Dynamically import the ReactApexChart component
const ReactApexChart = dynamic(() => import("react-apexcharts"), {
ssr: false,
});
export default function MonthlyTarget() {
const series = [75.55];
const options: ApexOptions = {
colors: ["#465FFF"],
chart: {
fontFamily: "Outfit, sans-serif",
type: "radialBar",
height: 330,
sparkline: {
enabled: true,
},
},
plotOptions: {
radialBar: {
startAngle: -85,
endAngle: 85,
hollow: {
size: "80%",
},
track: {
background: "#E4E7EC",
strokeWidth: "100%",
margin: 5, // margin is in pixels
},
dataLabels: {
name: {
show: false,
},
value: {
fontSize: "36px",
fontWeight: "600",
offsetY: -40,
color: "#1D2939",
formatter: function (val) {
return val + "%";
},
},
},
},
},
fill: {
type: "solid",
colors: ["#465FFF"],
},
stroke: {
lineCap: "round",
},
labels: ["Progress"],
};
const [isOpen, setIsOpen] = useState(false);
function toggleDropdown() {
setIsOpen(!isOpen);
}
function closeDropdown() {
setIsOpen(false);
}
return (
<div className="rounded-2xl border border-gray-200 bg-gray-100 dark:border-gray-800 dark:bg-white/[0.03]">
<div className="px-5 pt-5 bg-white shadow-default rounded-2xl pb-11 dark:bg-gray-900 sm:px-6 sm:pt-6">
<div className="flex justify-between">
<div>
<h3 className="text-lg font-semibold text-gray-800 dark:text-white/90">
Monthly Target
</h3>
<p className="mt-1 font-normal text-gray-500 text-theme-sm dark:text-gray-400">
Target youve set for each month
</p>
</div>
<div className="relative inline-block">
<button onClick={toggleDropdown} className="dropdown-toggle">
<MoreDotIcon className="text-gray-400 hover:text-gray-700 dark:hover:text-gray-300" />
</button>
<Dropdown
isOpen={isOpen}
onClose={closeDropdown}
className="w-40 p-2"
>
<DropdownItem
tag="a"
onItemClick={closeDropdown}
className="flex w-full font-normal text-left text-gray-500 rounded-lg hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300"
>
View More
</DropdownItem>
<DropdownItem
tag="a"
onItemClick={closeDropdown}
className="flex w-full font-normal text-left text-gray-500 rounded-lg hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300"
>
Delete
</DropdownItem>
</Dropdown>
</div>
</div>
<div className="relative ">
<div className="max-h-[330px]">
<ReactApexChart
options={options}
series={series}
type="radialBar"
height={330}
/>
</div>
<span className="absolute left-1/2 top-full -translate-x-1/2 -translate-y-[95%] rounded-full bg-success-50 px-3 py-1 text-xs font-medium text-success-600 dark:bg-success-500/15 dark:text-success-500">
+10%
</span>
</div>
<p className="mx-auto mt-10 w-full max-w-[380px] text-center text-sm text-gray-500 sm:text-base">
You earn $3287 today, it&apos;s higher than last month. Keep up your
good work!
</p>
</div>
<div className="flex items-center justify-center gap-5 px-6 py-3.5 sm:gap-8 sm:py-5">
<div>
<p className="mb-1 text-center text-gray-500 text-theme-xs dark:text-gray-400 sm:text-sm">
Target
</p>
<p className="flex items-center justify-center gap-1 text-base font-semibold text-gray-800 dark:text-white/90 sm:text-lg">
$20K
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M7.26816 13.6632C7.4056 13.8192 7.60686 13.9176 7.8311 13.9176C7.83148 13.9176 7.83187 13.9176 7.83226 13.9176C8.02445 13.9178 8.21671 13.8447 8.36339 13.6981L12.3635 9.70076C12.6565 9.40797 12.6567 8.9331 12.3639 8.6401C12.0711 8.34711 11.5962 8.34694 11.3032 8.63973L8.5811 11.36L8.5811 2.5C8.5811 2.08579 8.24531 1.75 7.8311 1.75C7.41688 1.75 7.0811 2.08579 7.0811 2.5L7.0811 11.3556L4.36354 8.63975C4.07055 8.34695 3.59568 8.3471 3.30288 8.64009C3.01008 8.93307 3.01023 9.40794 3.30321 9.70075L7.26816 13.6632Z"
fill="#D92D20"
/>
</svg>
</p>
</div>
<div className="w-px bg-gray-200 h-7 dark:bg-gray-800"></div>
<div>
<p className="mb-1 text-center text-gray-500 text-theme-xs dark:text-gray-400 sm:text-sm">
Revenue
</p>
<p className="flex items-center justify-center gap-1 text-base font-semibold text-gray-800 dark:text-white/90 sm:text-lg">
$20K
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M7.60141 2.33683C7.73885 2.18084 7.9401 2.08243 8.16435 2.08243C8.16475 2.08243 8.16516 2.08243 8.16556 2.08243C8.35773 2.08219 8.54998 2.15535 8.69664 2.30191L12.6968 6.29924C12.9898 6.59203 12.9899 7.0669 12.6971 7.3599C12.4044 7.6529 11.9295 7.65306 11.6365 7.36027L8.91435 4.64004L8.91435 13.5C8.91435 13.9142 8.57856 14.25 8.16435 14.25C7.75013 14.25 7.41435 13.9142 7.41435 13.5L7.41435 4.64442L4.69679 7.36025C4.4038 7.65305 3.92893 7.6529 3.63613 7.35992C3.34333 7.06693 3.34348 6.59206 3.63646 6.29926L7.60141 2.33683Z"
fill="#039855"
/>
</svg>
</p>
</div>
<div className="w-px bg-gray-200 h-7 dark:bg-gray-800"></div>
<div>
<p className="mb-1 text-center text-gray-500 text-theme-xs dark:text-gray-400 sm:text-sm">
Today
</p>
<p className="flex items-center justify-center gap-1 text-base font-semibold text-gray-800 dark:text-white/90 sm:text-lg">
$20K
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M7.60141 2.33683C7.73885 2.18084 7.9401 2.08243 8.16435 2.08243C8.16475 2.08243 8.16516 2.08243 8.16556 2.08243C8.35773 2.08219 8.54998 2.15535 8.69664 2.30191L12.6968 6.29924C12.9898 6.59203 12.9899 7.0669 12.6971 7.3599C12.4044 7.6529 11.9295 7.65306 11.6365 7.36027L8.91435 4.64004L8.91435 13.5C8.91435 13.9142 8.57856 14.25 8.16435 14.25C7.75013 14.25 7.41435 13.9142 7.41435 13.5L7.41435 4.64442L4.69679 7.36025C4.4038 7.65305 3.92893 7.6529 3.63613 7.35992C3.34333 7.06693 3.34348 6.59206 3.63646 6.29926L7.60141 2.33683Z"
fill="#039855"
/>
</svg>
</p>
</div>
</div>
</div>
);
}
-211
View File
@@ -1,211 +0,0 @@
import {
Table,
TableBody,
TableCell,
TableHeader,
TableRow,
} from "../ui/table";
import Badge from "../ui/badge/Badge";
import Image from "next/image";
// Define the TypeScript interface for the table rows
interface Product {
id: number; // Unique identifier for each product
name: string; // Product name
variants: string; // Number of variants (e.g., "1 Variant", "2 Variants")
category: string; // Category of the product
price: string; // Price of the product (as a string with currency symbol)
// status: string; // Status of the product
image: string; // URL or path to the product image
status: "Delivered" | "Pending" | "Canceled"; // Status of the product
}
// Define the table data using the interface
const tableData: Product[] = [
{
id: 1,
name: "MacBook Pro 13”",
variants: "2 Variants",
category: "Laptop",
price: "$2399.00",
status: "Delivered",
image: "/images/product/product-01.jpg", // Replace with actual image URL
},
{
id: 2,
name: "Apple Watch Ultra",
variants: "1 Variant",
category: "Watch",
price: "$879.00",
status: "Pending",
image: "/images/product/product-02.jpg", // Replace with actual image URL
},
{
id: 3,
name: "iPhone 15 Pro Max",
variants: "2 Variants",
category: "SmartPhone",
price: "$1869.00",
status: "Delivered",
image: "/images/product/product-03.jpg", // Replace with actual image URL
},
{
id: 4,
name: "iPad Pro 3rd Gen",
variants: "2 Variants",
category: "Electronics",
price: "$1699.00",
status: "Canceled",
image: "/images/product/product-04.jpg", // Replace with actual image URL
},
{
id: 5,
name: "AirPods Pro 2nd Gen",
variants: "1 Variant",
category: "Accessories",
price: "$240.00",
status: "Delivered",
image: "/images/product/product-05.jpg", // Replace with actual image URL
},
];
export default function RecentOrders() {
return (
<div className="overflow-hidden rounded-2xl border border-gray-200 bg-white px-4 pb-3 pt-4 dark:border-gray-800 dark:bg-white/[0.03] sm:px-6">
<div className="flex flex-col gap-2 mb-4 sm:flex-row sm:items-center sm:justify-between">
<div>
<h3 className="text-lg font-semibold text-gray-800 dark:text-white/90">
Recent Orders
</h3>
</div>
<div className="flex items-center gap-3">
<button className="inline-flex items-center gap-2 rounded-lg border border-gray-300 bg-white px-4 py-2.5 text-theme-sm font-medium text-gray-700 shadow-theme-xs hover:bg-gray-50 hover:text-gray-800 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03] dark:hover:text-gray-200">
<svg
className="stroke-current fill-white dark:fill-gray-800"
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M2.29004 5.90393H17.7067"
stroke=""
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M17.7075 14.0961H2.29085"
stroke=""
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M12.0826 3.33331C13.5024 3.33331 14.6534 4.48431 14.6534 5.90414C14.6534 7.32398 13.5024 8.47498 12.0826 8.47498C10.6627 8.47498 9.51172 7.32398 9.51172 5.90415C9.51172 4.48432 10.6627 3.33331 12.0826 3.33331Z"
fill=""
stroke=""
strokeWidth="1.5"
/>
<path
d="M7.91745 11.525C6.49762 11.525 5.34662 12.676 5.34662 14.0959C5.34661 15.5157 6.49762 16.6667 7.91745 16.6667C9.33728 16.6667 10.4883 15.5157 10.4883 14.0959C10.4883 12.676 9.33728 11.525 7.91745 11.525Z"
fill=""
stroke=""
strokeWidth="1.5"
/>
</svg>
Filter
</button>
<button className="inline-flex items-center gap-2 rounded-lg border border-gray-300 bg-white px-4 py-2.5 text-theme-sm font-medium text-gray-700 shadow-theme-xs hover:bg-gray-50 hover:text-gray-800 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03] dark:hover:text-gray-200">
See all
</button>
</div>
</div>
<div className="max-w-full overflow-x-auto">
<Table>
{/* Table Header */}
<TableHeader className="border-gray-100 dark:border-gray-800 border-y">
<TableRow>
<TableCell
isHeader
className="py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
>
Products
</TableCell>
<TableCell
isHeader
className="py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
>
Category
</TableCell>
<TableCell
isHeader
className="py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
>
Price
</TableCell>
<TableCell
isHeader
className="py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
>
Status
</TableCell>
</TableRow>
</TableHeader>
{/* Table Body */}
<TableBody className="divide-y divide-gray-100 dark:divide-gray-800">
{tableData.map((product) => (
<TableRow key={product.id} className="">
<TableCell className="py-3">
<div className="flex items-center gap-3">
<div className="h-[50px] w-[50px] overflow-hidden rounded-md">
<Image
width={50}
height={50}
src={product.image}
className="h-[50px] w-[50px]"
alt={product.name}
/>
</div>
<div>
<p className="font-medium text-gray-800 text-theme-sm dark:text-white/90">
{product.name}
</p>
<span className="text-gray-500 text-theme-xs dark:text-gray-400">
{product.variants}
</span>
</div>
</div>
</TableCell>
<TableCell className="py-3 text-gray-500 text-theme-sm dark:text-gray-400">
{product.price}
</TableCell>
<TableCell className="py-3 text-gray-500 text-theme-sm dark:text-gray-400">
{product.category}
</TableCell>
<TableCell className="py-3 text-gray-500 text-theme-sm dark:text-gray-400">
<Badge
size="sm"
color={
product.status === "Delivered"
? "success"
: product.status === "Pending"
? "warning"
: "error"
}
>
{product.status}
</Badge>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
);
}
@@ -1,181 +0,0 @@
"use client";
import { useEffect, useRef } from "react";
import dynamic from "next/dynamic";
import { ApexOptions } from "apexcharts";
import flatpickr from "flatpickr";
import "flatpickr/dist/flatpickr.css";
import ChartTab from "../common/ChartTab";
import { CalenderIcon } from "../../icons";
const Chart = dynamic(() => import("react-apexcharts"), { ssr: false });
export default function StatisticsChart() {
const datePickerRef = useRef<HTMLInputElement>(null);
useEffect(() => {
if (!datePickerRef.current) return;
const today = new Date();
const sevenDaysAgo = new Date();
sevenDaysAgo.setDate(today.getDate() - 6);
const fp = flatpickr(datePickerRef.current, {
mode: "range",
static: true,
monthSelectorType: "static",
dateFormat: "M d",
defaultDate: [sevenDaysAgo, today],
clickOpens: true,
prevArrow:
'<svg class="stroke-current" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12.5 15L7.5 10L12.5 5" stroke="" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>',
nextArrow:
'<svg class="stroke-current" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7.5 15L12.5 10L7.5 5" stroke="" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>',
});
return () => {
if (!Array.isArray(fp)) {
fp.destroy();
}
};
}, []);
const options: ApexOptions = {
legend: {
show: false, // Hide legend
position: "top",
horizontalAlign: "left",
},
colors: ["#465FFF", "#9CB9FF"], // Define line colors
chart: {
fontFamily: "Outfit, sans-serif",
height: 310,
type: "line", // Set the chart type to 'line'
toolbar: {
show: false, // Hide chart toolbar
},
},
stroke: {
curve: "straight", // Define the line style (straight, smooth, or step)
width: [2, 2], // Line width for each dataset
},
fill: {
type: "gradient",
gradient: {
opacityFrom: 0.55,
opacityTo: 0,
},
},
markers: {
size: 0, // Size of the marker points
strokeColors: "#fff", // Marker border color
strokeWidth: 2,
hover: {
size: 6, // Marker size on hover
},
},
grid: {
xaxis: {
lines: {
show: false, // Hide grid lines on x-axis
},
},
yaxis: {
lines: {
show: true, // Show grid lines on y-axis
},
},
},
dataLabels: {
enabled: false, // Disable data labels
},
tooltip: {
enabled: true, // Enable tooltip
x: {
format: "dd MMM yyyy", // Format for x-axis tooltip
},
},
xaxis: {
type: "category", // Category-based x-axis
categories: [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
],
axisBorder: {
show: false, // Hide x-axis border
},
axisTicks: {
show: false, // Hide x-axis ticks
},
tooltip: {
enabled: false, // Disable tooltip for x-axis points
},
},
yaxis: {
labels: {
style: {
fontSize: "12px", // Adjust font size for y-axis labels
colors: ["#6B7280"], // Color of the labels
},
},
title: {
text: "", // Remove y-axis title
style: {
fontSize: "0px",
},
},
},
};
const series = [
{
name: "Sales",
data: [180, 190, 170, 160, 175, 165, 170, 205, 230, 210, 240, 235],
},
{
name: "Revenue",
data: [40, 30, 50, 40, 55, 40, 70, 100, 110, 120, 150, 140],
},
];
return (
<div className="rounded-2xl border border-gray-200 bg-white px-5 pb-5 pt-5 dark:border-gray-800 dark:bg-white/[0.03] sm:px-6 sm:pt-6">
<div className="flex flex-col gap-5 mb-6 sm:flex-row sm:justify-between">
<div className="w-full">
<h3 className="text-lg font-semibold text-gray-800 dark:text-white/90">
Statistics
</h3>
<p className="mt-1 text-gray-500 text-theme-sm dark:text-gray-400">
Target you've set for each month
</p>
</div>
<div className="flex items-center gap-3 sm:justify-end">
<ChartTab />
<div className="relative inline-flex items-center">
<CalenderIcon className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 lg:left-3 lg:top-1/2 lg:translate-x-0 lg:-translate-y-1/2 text-gray-500 dark:text-gray-400 pointer-events-none z-10" />
<input
ref={datePickerRef}
className="h-10 w-10 lg:w-40 lg:h-auto lg:pl-10 lg:pr-3 lg:py-2 rounded-lg border border-gray-200 bg-white text-sm font-medium text-transparent lg:text-gray-700 outline-none dark:border-gray-700 dark:bg-gray-800 dark:lg:text-gray-300 cursor-pointer"
placeholder="Select date range"
/>
</div>
</div>
</div>
<div className="max-w-full overflow-x-auto custom-scrollbar">
<div className="min-w-[1000px] xl:min-w-full">
<Chart options={options} series={series} type="area" height={310} />
</div>
</div>
</div>
);
}
@@ -1,53 +0,0 @@
"use client";
import React from "react";
import ComponentCard from "../../common/ComponentCard";
import { Modal } from "../../ui/modal";
import Button from "../../ui/button/Button";
import { useModal } from "@/hooks/useModal";
export default function DefaultModal() {
const { isOpen, openModal, closeModal } = useModal();
const handleSave = () => {
// Handle save logic here
console.log("Saving changes...");
closeModal();
};
return (
<div>
<ComponentCard title="Default Modal">
<Button size="sm" onClick={openModal}>
Open Modal
</Button>
<Modal
isOpen={isOpen}
onClose={closeModal}
className="max-w-[600px] p-5 lg:p-10"
>
<h4 className="font-semibold text-gray-800 mb-7 text-title-sm dark:text-white/90">
Modal Heading
</h4>
<p className="text-sm leading-6 text-gray-500 dark:text-gray-400">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Pellentesque euismod est quis mauris lacinia pharetra. Sed a ligula
ac odio condimentum aliquet a nec nulla. Aliquam bibendum ex sit
amet ipsum rutrum feugiat ultrices enim quam.
</p>
<p className="mt-5 text-sm leading-6 text-gray-500 dark:text-gray-400">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Pellentesque euismod est quis mauris lacinia pharetra. Sed a ligula
ac odio.
</p>
<div className="flex items-center justify-end w-full gap-3 mt-8">
<Button size="sm" variant="outline" onClick={closeModal}>
Close
</Button>
<Button size="sm" onClick={handleSave}>
Save Changes
</Button>
</div>
</Modal>
</ComponentCard>
</div>
);
}
@@ -1,71 +0,0 @@
"use client";
import React from "react";
import ComponentCard from "../../common/ComponentCard";
import Button from "../../ui/button/Button";
import { Modal } from "../../ui/modal";
import Label from "../../form/Label";
import Input from "../../form/input/InputField";
import { useModal } from "@/hooks/useModal";
export default function FormInModal() {
const { isOpen, openModal, closeModal } = useModal();
const handleSave = () => {
// Handle save logic here
console.log("Saving changes...");
closeModal();
};
return (
<ComponentCard title="Form In Modal">
<Button size="sm" onClick={openModal}>
Open Modal
</Button>
<Modal
isOpen={isOpen}
onClose={closeModal}
className="max-w-[584px] p-5 lg:p-10"
>
<form className="">
<h4 className="mb-6 text-lg font-medium text-gray-800 dark:text-white/90">
Personal Information
</h4>
<div className="grid grid-cols-1 gap-x-6 gap-y-5 sm:grid-cols-2">
<div className="col-span-1">
<Label>First Name</Label>
<Input type="text" placeholder="Emirhan" />
</div>
<div className="col-span-1">
<Label>Last Name</Label>
<Input type="text" placeholder="Boruch" />
</div>
<div className="col-span-1">
<Label>Last Name</Label>
<Input type="email" placeholder="emirhanboruch55@gmail.com" />
</div>
<div className="col-span-1">
<Label>Phone</Label>
<Input type="text" placeholder="+09 363 398 46" />
</div>
<div className="col-span-1 sm:col-span-2">
<Label>Bio</Label>
<Input type="text" placeholder="Team Manager" />
</div>
</div>
<div className="flex items-center justify-end w-full gap-3 mt-6">
<Button size="sm" variant="outline" onClick={closeModal}>
Close
</Button>
<Button size="sm" onClick={handleSave}>
Save Changes
</Button>
</div>
</form>
</Modal>
</ComponentCard>
);
}
@@ -1,66 +0,0 @@
"use client";
import { useModal } from "@/hooks/useModal";
import ComponentCard from "../../common/ComponentCard";
import Button from "../../ui/button/Button";
import { Modal } from "../../ui/modal";
export default function FullScreenModal() {
const {
isOpen: isFullscreenModalOpen,
openModal: openFullscreenModal,
closeModal: closeFullscreenModal,
} = useModal();
const handleSave = () => {
// Handle save logic here
console.log("Saving changes...");
closeFullscreenModal();
};
return (
<ComponentCard title="Full Screen Modal">
<Button size="sm" onClick={openFullscreenModal}>
Open Modal
</Button>
<Modal
isOpen={isFullscreenModalOpen}
onClose={closeFullscreenModal}
isFullscreen={true}
showCloseButton={true}
>
<div className="fixed top-0 left-0 flex flex-col justify-between w-full h-screen p-6 overflow-x-hidden overflow-y-auto bg-white dark:bg-gray-900 lg:p-10">
<div>
<h4 className="font-semibold text-gray-800 mb-7 text-title-sm dark:text-white/90">
Modal Heading
</h4>
<p className="text-sm leading-6 text-gray-500 dark:text-gray-400">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Pellentesque euismod est quis mauris lacinia pharetra. Sed a
ligula ac odio condimentum aliquet a nec nulla. Aliquam bibendum
ex sit amet ipsum rutrum feugiat ultrices enim quam.
</p>
<p className="mt-5 text-sm leading-6 text-gray-500 dark:text-gray-400">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Pellentesque euismod est quis mauris lacinia pharetra. Sed a
ligula ac odio condimentum aliquet a nec nulla. Aliquam bibendum
ex sit amet ipsum rutrum feugiat ultrices enim quam odio
condimentum aliquet a nec nulla pellentesque euismod est quis
mauris lacinia pharetra.
</p>
<p className="mt-5 text-sm leading-6 text-gray-500 dark:text-gray-400">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Pellentesque euismod est quis mauris lacinia pharetra.
</p>
</div>
<div className="flex items-center justify-end w-full gap-3 mt-8">
<Button size="sm" variant="outline" onClick={closeFullscreenModal}>
Close
</Button>
<Button size="sm" onClick={handleSave}>
Save Changes
</Button>
</div>
</div>
</Modal>
</ComponentCard>
);
}
@@ -1,282 +0,0 @@
"use client";
import React from "react";
import ComponentCard from "../../common/ComponentCard";
import { Modal } from "../../ui/modal";
import { useModal } from "@/hooks/useModal";
export default function ModalBasedAlerts() {
const successModal = useModal();
const infoModal = useModal();
const warningModal = useModal();
const errorModal = useModal();
return (
<ComponentCard title="Modal Based Alerts">
<div className="flex flex-wrap items-center gap-3">
<button
onClick={successModal.openModal}
className="px-4 py-3 text-sm font-medium text-white rounded-lg bg-success-500 shadow-theme-xs hover:bg-success-600"
>
Success Alert
</button>
<button
onClick={infoModal.openModal}
className="px-4 py-3 text-sm font-medium text-white rounded-lg bg-blue-light-500 shadow-theme-xs hover:bg-blue-light-600"
>
Info Alert
</button>
<button
onClick={warningModal.openModal}
className="px-4 py-3 text-sm font-medium text-white rounded-lg bg-warning-500 shadow-theme-xs hover:bg-warning-600"
>
Warning Alert
</button>
<button
onClick={errorModal.openModal}
className="px-4 py-3 text-sm font-medium text-white rounded-lg bg-error-500 shadow-theme-xs hover:bg-error-600"
>
Danger Alert
</button>
</div>
{/* Success Modal */}
<Modal
isOpen={successModal.isOpen}
onClose={successModal.closeModal}
className="max-w-[600px] p-5 lg:p-10"
>
<div className="text-center">
<div className="relative flex items-center justify-center z-1 mb-7">
<svg
className="fill-success-50 dark:fill-success-500/15"
width="90"
height="90"
viewBox="0 0 90 90"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M34.364 6.85053C38.6205 -2.28351 51.3795 -2.28351 55.636 6.85053C58.0129 11.951 63.5594 14.6722 68.9556 13.3853C78.6192 11.0807 86.5743 21.2433 82.2185 30.3287C79.7862 35.402 81.1561 41.5165 85.5082 45.0122C93.3019 51.2725 90.4628 63.9451 80.7747 66.1403C75.3648 67.3661 71.5265 72.2695 71.5572 77.9156C71.6123 88.0265 60.1169 93.6664 52.3918 87.3184C48.0781 83.7737 41.9219 83.7737 37.6082 87.3184C29.8831 93.6664 18.3877 88.0266 18.4428 77.9156C18.4735 72.2695 14.6352 67.3661 9.22531 66.1403C-0.462787 63.9451 -3.30193 51.2725 4.49185 45.0122C8.84391 41.5165 10.2138 35.402 7.78151 30.3287C3.42572 21.2433 11.3808 11.0807 21.0444 13.3853C26.4406 14.6722 31.9871 11.951 34.364 6.85053Z"
fill=""
fillOpacity=""
/>
</svg>
<span className="absolute -translate-x-1/2 -translate-y-1/2 left-1/2 top-1/2">
<svg
className="fill-success-600 dark:fill-success-500"
width="38"
height="38"
viewBox="0 0 38 38"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M5.9375 19.0004C5.9375 11.7854 11.7864 5.93652 19.0014 5.93652C26.2164 5.93652 32.0653 11.7854 32.0653 19.0004C32.0653 26.2154 26.2164 32.0643 19.0014 32.0643C11.7864 32.0643 5.9375 26.2154 5.9375 19.0004ZM19.0014 2.93652C10.1296 2.93652 2.9375 10.1286 2.9375 19.0004C2.9375 27.8723 10.1296 35.0643 19.0014 35.0643C27.8733 35.0643 35.0653 27.8723 35.0653 19.0004C35.0653 10.1286 27.8733 2.93652 19.0014 2.93652ZM24.7855 17.0575C25.3713 16.4717 25.3713 15.522 24.7855 14.9362C24.1997 14.3504 23.25 14.3504 22.6642 14.9362L17.7177 19.8827L15.3387 17.5037C14.7529 16.9179 13.8031 16.9179 13.2173 17.5037C12.6316 18.0894 12.6316 19.0392 13.2173 19.625L16.657 23.0647C16.9383 23.346 17.3199 23.504 17.7177 23.504C18.1155 23.504 18.4971 23.346 18.7784 23.0647L24.7855 17.0575Z"
fill=""
/>
</svg>
</span>
</div>
<h4 className="mb-2 text-2xl font-semibold text-gray-800 dark:text-white/90 sm:text-title-sm">
Well Done!
</h4>
<p className="text-sm leading-6 text-gray-500 dark:text-gray-400">
Lorem ipsum dolor sit amet consectetur. Feugiat ipsum libero tempor
felis risus nisi non. Quisque eu ut tempor curabitur.
</p>
<div className="flex items-center justify-center w-full gap-3 mt-7">
<button
type="button"
className="flex justify-center w-full px-4 py-3 text-sm font-medium text-white rounded-lg bg-success-500 shadow-theme-xs hover:bg-success-600 sm:w-auto"
>
Okay, Got It
</button>
</div>
</div>
</Modal>
{/* Info Modal */}
<Modal
isOpen={infoModal.isOpen}
onClose={infoModal.closeModal}
className="max-w-[600px] p-5 lg:p-10"
>
<div className="text-center">
<div className="relative flex items-center justify-center z-1 mb-7">
<svg
className="fill-blue-light-50 dark:fill-blue-light-500/15"
width="90"
height="90"
viewBox="0 0 90 90"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M34.364 6.85053C38.6205 -2.28351 51.3795 -2.28351 55.636 6.85053C58.0129 11.951 63.5594 14.6722 68.9556 13.3853C78.6192 11.0807 86.5743 21.2433 82.2185 30.3287C79.7862 35.402 81.1561 41.5165 85.5082 45.0122C93.3019 51.2725 90.4628 63.9451 80.7747 66.1403C75.3648 67.3661 71.5265 72.2695 71.5572 77.9156C71.6123 88.0265 60.1169 93.6664 52.3918 87.3184C48.0781 83.7737 41.9219 83.7737 37.6082 87.3184C29.8831 93.6664 18.3877 88.0266 18.4428 77.9156C18.4735 72.2695 14.6352 67.3661 9.22531 66.1403C-0.462787 63.9451 -3.30193 51.2725 4.49185 45.0122C8.84391 41.5165 10.2138 35.402 7.78151 30.3287C3.42572 21.2433 11.3808 11.0807 21.0444 13.3853C26.4406 14.6722 31.9871 11.951 34.364 6.85053Z"
fill=""
fillOpacity=""
/>
</svg>
<span className="absolute -translate-x-1/2 -translate-y-1/2 left-1/2 top-1/2">
<svg
className="fill-blue-light-500 dark:fill-blue-light-500"
width="38"
height="38"
viewBox="0 0 38 38"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M5.85547 18.9998C5.85547 11.7396 11.7411 5.854 19.0013 5.854C26.2615 5.854 32.1471 11.7396 32.1471 18.9998C32.1471 26.2601 26.2615 32.1457 19.0013 32.1457C11.7411 32.1457 5.85547 26.2601 5.85547 18.9998ZM19.0013 2.854C10.0842 2.854 2.85547 10.0827 2.85547 18.9998C2.85547 27.9169 10.0842 35.1457 19.0013 35.1457C27.9184 35.1457 35.1471 27.9169 35.1471 18.9998C35.1471 10.0827 27.9184 2.854 19.0013 2.854ZM16.9999 11.9145C16.9999 13.0191 17.8953 13.9145 18.9999 13.9145H19.0015C20.106 13.9145 21.0015 13.0191 21.0015 11.9145C21.0015 10.81 20.106 9.91454 19.0015 9.91454H18.9999C17.8953 9.91454 16.9999 10.81 16.9999 11.9145ZM19.0014 27.8171C18.173 27.8171 17.5014 27.1455 17.5014 26.3171V17.3293C17.5014 16.5008 18.173 15.8293 19.0014 15.8293C19.8299 15.8293 20.5014 16.5008 20.5014 17.3293L20.5014 26.3171C20.5014 27.1455 19.8299 27.8171 19.0014 27.8171Z"
fill=""
/>
</svg>
</span>
</div>
<h4 className="mb-2 text-2xl font-semibold text-gray-800 dark:text-white/90 sm:text-title-sm">
Information Alert!
</h4>
<p className="text-sm leading-6 text-gray-500 dark:text-gray-400">
Lorem ipsum dolor sit amet consectetur. Feugiat ipsum libero tempor
felis risus nisi non. Quisque eu ut tempor curabitur.
</p>
<div className="flex items-center justify-center w-full gap-3 mt-7">
<button
type="button"
className="flex justify-center w-full px-4 py-3 text-sm font-medium text-white rounded-lg bg-blue-light-500 shadow-theme-xs hover:bg-blue-light-600 sm:w-auto"
>
Okay, Got It
</button>
</div>
</div>
</Modal>
{/* Warning Modal */}
<Modal
isOpen={warningModal.isOpen}
onClose={warningModal.closeModal}
className="max-w-[600px] p-5 lg:p-10"
>
<div className="text-center">
<div className="relative flex items-center justify-center z-1 mb-7">
<svg
className="fill-warning-50 dark:fill-warning-500/15"
width="90"
height="90"
viewBox="0 0 90 90"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M34.364 6.85053C38.6205 -2.28351 51.3795 -2.28351 55.636 6.85053C58.0129 11.951 63.5594 14.6722 68.9556 13.3853C78.6192 11.0807 86.5743 21.2433 82.2185 30.3287C79.7862 35.402 81.1561 41.5165 85.5082 45.0122C93.3019 51.2725 90.4628 63.9451 80.7747 66.1403C75.3648 67.3661 71.5265 72.2695 71.5572 77.9156C71.6123 88.0265 60.1169 93.6664 52.3918 87.3184C48.0781 83.7737 41.9219 83.7737 37.6082 87.3184C29.8831 93.6664 18.3877 88.0266 18.4428 77.9156C18.4735 72.2695 14.6352 67.3661 9.22531 66.1403C-0.462787 63.9451 -3.30193 51.2725 4.49185 45.0122C8.84391 41.5165 10.2138 35.402 7.78151 30.3287C3.42572 21.2433 11.3808 11.0807 21.0444 13.3853C26.4406 14.6722 31.9871 11.951 34.364 6.85053Z"
fill=""
fillOpacity=""
/>
</svg>
<span className="absolute -translate-x-1/2 -translate-y-1/2 left-1/2 top-1/2">
<svg
className="fill-warning-600 dark:fill-orange-400"
width="38"
height="38"
viewBox="0 0 38 38"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M32.1445 19.0002C32.1445 26.2604 26.2589 32.146 18.9987 32.146C11.7385 32.146 5.85287 26.2604 5.85287 19.0002C5.85287 11.7399 11.7385 5.85433 18.9987 5.85433C26.2589 5.85433 32.1445 11.7399 32.1445 19.0002ZM18.9987 35.146C27.9158 35.146 35.1445 27.9173 35.1445 19.0002C35.1445 10.0831 27.9158 2.85433 18.9987 2.85433C10.0816 2.85433 2.85287 10.0831 2.85287 19.0002C2.85287 27.9173 10.0816 35.146 18.9987 35.146ZM21.0001 26.0855C21.0001 24.9809 20.1047 24.0855 19.0001 24.0855L18.9985 24.0855C17.894 24.0855 16.9985 24.9809 16.9985 26.0855C16.9985 27.19 17.894 28.0855 18.9985 28.0855L19.0001 28.0855C20.1047 28.0855 21.0001 27.19 21.0001 26.0855ZM18.9986 10.1829C19.827 10.1829 20.4986 10.8545 20.4986 11.6829L20.4986 20.6707C20.4986 21.4992 19.827 22.1707 18.9986 22.1707C18.1701 22.1707 17.4986 21.4992 17.4986 20.6707L17.4986 11.6829C17.4986 10.8545 18.1701 10.1829 18.9986 10.1829Z"
fill=""
/>
</svg>
</span>
</div>
<h4 className="mb-2 text-2xl font-semibold text-gray-800 dark:text-white/90 sm:text-title-sm">
Warning Alert!
</h4>
<p className="text-sm leading-6 text-gray-500 dark:text-gray-400">
Lorem ipsum dolor sit amet consectetur. Feugiat ipsum libero tempor
felis risus nisi non. Quisque eu ut tempor curabitur.
</p>
<div className="flex items-center justify-center w-full gap-3 mt-7">
<button
type="button"
className="flex justify-center w-full px-4 py-3 text-sm font-medium text-white rounded-lg bg-warning-500 shadow-theme-xs hover:bg-warning-600 sm:w-auto"
>
Okay, Got It
</button>
</div>
</div>
</Modal>
{/* Error Modal */}
<Modal
isOpen={errorModal.isOpen}
onClose={errorModal.closeModal}
className="max-w-[600px] p-5 lg:p-10"
>
<div className="text-center">
<div className="relative flex items-center justify-center z-1 mb-7">
<svg
className="fill-error-50 dark:fill-error-500/15"
width="90"
height="90"
viewBox="0 0 90 90"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M34.364 6.85053C38.6205 -2.28351 51.3795 -2.28351 55.636 6.85053C58.0129 11.951 63.5594 14.6722 68.9556 13.3853C78.6192 11.0807 86.5743 21.2433 82.2185 30.3287C79.7862 35.402 81.1561 41.5165 85.5082 45.0122C93.3019 51.2725 90.4628 63.9451 80.7747 66.1403C75.3648 67.3661 71.5265 72.2695 71.5572 77.9156C71.6123 88.0265 60.1169 93.6664 52.3918 87.3184C48.0781 83.7737 41.9219 83.7737 37.6082 87.3184C29.8831 93.6664 18.3877 88.0266 18.4428 77.9156C18.4735 72.2695 14.6352 67.3661 9.22531 66.1403C-0.462787 63.9451 -3.30193 51.2725 4.49185 45.0122C8.84391 41.5165 10.2138 35.402 7.78151 30.3287C3.42572 21.2433 11.3808 11.0807 21.0444 13.3853C26.4406 14.6722 31.9871 11.951 34.364 6.85053Z"
fill=""
fillOpacity=""
/>
</svg>
<span className="absolute -translate-x-1/2 -translate-y-1/2 left-1/2 top-1/2">
<svg
className="fill-error-600 dark:fill-error-500"
width="38"
height="38"
viewBox="0 0 38 38"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M9.62684 11.7496C9.04105 11.1638 9.04105 10.2141 9.62684 9.6283C10.2126 9.04252 11.1624 9.04252 11.7482 9.6283L18.9985 16.8786L26.2485 9.62851C26.8343 9.04273 27.7841 9.04273 28.3699 9.62851C28.9556 10.2143 28.9556 11.164 28.3699 11.7498L21.1198 18.9999L28.3699 26.25C28.9556 26.8358 28.9556 27.7855 28.3699 28.3713C27.7841 28.9571 26.8343 28.9571 26.2485 28.3713L18.9985 21.1212L11.7482 28.3715C11.1624 28.9573 10.2126 28.9573 9.62684 28.3715C9.04105 27.7857 9.04105 26.836 9.62684 26.2502L16.8771 18.9999L9.62684 11.7496Z"
fill=""
/>
</svg>
</span>
</div>
<h4 className="mb-2 text-2xl font-semibold text-gray-800 dark:text-white/90 sm:text-title-sm">
Danger Alert!
</h4>
<p className="text-sm leading-6 text-gray-500 dark:text-gray-400">
Lorem ipsum dolor sit amet consectetur. Feugiat ipsum libero tempor
felis risus nisi non. Quisque eu ut tempor curabitur.
</p>
<div className="flex items-center justify-center w-full gap-3 mt-7">
<button
type="button"
className="flex justify-center w-full px-4 py-3 text-sm font-medium text-white rounded-lg bg-error-500 shadow-theme-xs hover:bg-error-600 sm:w-auto"
>
Okay, Got It
</button>
</div>
</div>
</Modal>
</ComponentCard>
);
}
@@ -1,47 +0,0 @@
"use client";
import React from "react";
import ComponentCard from "../../common/ComponentCard";
import Button from "../../ui/button/Button";
import { Modal } from "../../ui/modal";
import { useModal } from "@/hooks/useModal";
export default function VerticallyCenteredModal() {
const { isOpen, openModal, closeModal } = useModal();
const handleSave = () => {
// Handle save logic here
console.log("Saving changes...");
closeModal();
};
return (
<ComponentCard title="Vertically Centered Modal">
<Button size="sm" onClick={openModal}>
Open Modal
</Button>
<Modal
isOpen={isOpen}
onClose={closeModal}
showCloseButton={false}
className="max-w-[507px] p-6 lg:p-10"
>
<div className="text-center">
<h4 className="mb-2 text-2xl font-semibold text-gray-800 dark:text-white/90 sm:text-title-sm">
All Done! Success Confirmed
</h4>
<p className="text-sm leading-6 text-gray-500 dark:text-gray-400">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Pellentesque euismod est quis mauris lacinia pharetra.
</p>
<div className="flex items-center justify-center w-full gap-3 mt-8">
<Button size="sm" variant="outline" onClick={closeModal}>
Close
</Button>
<Button size="sm" onClick={handleSave}>
Save Changes
</Button>
</div>
</div>
</Modal>
</ComponentCard>
);
}
-23
View File
@@ -1,23 +0,0 @@
import React, { FC, ReactNode, FormEvent } from "react";
interface FormProps {
onSubmit: (event: FormEvent<HTMLFormElement>) => void;
children: ReactNode;
className?: string;
}
const Form: FC<FormProps> = ({ onSubmit, children, className }) => {
return (
<form
onSubmit={(event) => {
event.preventDefault(); // Prevent default form submission
onSubmit(event);
}}
className={` ${className}`} // Default spacing between form fields
>
{children}
</form>
);
};
export default Form;
-166
View File
@@ -1,166 +0,0 @@
import React, { useState } from "react";
interface Option {
value: string;
text: string;
selected: boolean;
}
interface MultiSelectProps {
label: string;
options: Option[];
defaultSelected?: string[];
onChange?: (selected: string[]) => void;
disabled?: boolean;
}
const MultiSelect: React.FC<MultiSelectProps> = ({
label,
options,
defaultSelected = [],
onChange,
disabled = false,
}) => {
const [selectedOptions, setSelectedOptions] =
useState<string[]>(defaultSelected);
const [isOpen, setIsOpen] = useState(false);
const toggleDropdown = () => {
if (disabled) return;
setIsOpen((prev) => !prev);
};
const handleSelect = (optionValue: string) => {
const newSelectedOptions = selectedOptions.includes(optionValue)
? selectedOptions.filter((value) => value !== optionValue)
: [...selectedOptions, optionValue];
setSelectedOptions(newSelectedOptions);
if (onChange) onChange(newSelectedOptions);
};
const removeOption = (index: number, value: string) => {
const newSelectedOptions = selectedOptions.filter((opt) => opt !== value);
setSelectedOptions(newSelectedOptions);
if (onChange) onChange(newSelectedOptions);
};
const selectedValuesText = selectedOptions.map(
(value) => options.find((option) => option.value === value)?.text || ""
);
return (
<div className="w-full">
<label className="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
{label}
</label>
<div className="relative z-20 inline-block w-full">
<div className="relative flex flex-col items-center">
<div onClick={toggleDropdown} className="w-full">
<div className="mb-2 flex h-11 rounded-lg border border-gray-300 py-1.5 pl-3 pr-3 shadow-theme-xs outline-hidden transition focus:border-brand-300 focus:shadow-focus-ring dark:border-gray-700 dark:bg-gray-900 dark:focus:border-brand-300">
<div className="flex flex-wrap flex-auto gap-2">
{selectedValuesText.length > 0 ? (
selectedValuesText.map((text, index) => (
<div
key={index}
className="group flex items-center justify-center rounded-full border-[0.7px] border-transparent bg-gray-100 py-1 pl-2.5 pr-2 text-sm text-gray-800 hover:border-gray-200 dark:bg-gray-800 dark:text-white/90 dark:hover:border-gray-800"
>
<span className="flex-initial max-w-full">{text}</span>
<div className="flex flex-row-reverse flex-auto">
<div
onClick={() =>
removeOption(index, selectedOptions[index])
}
className="pl-2 text-gray-500 cursor-pointer group-hover:text-gray-400 dark:text-gray-400"
>
<svg
className="fill-current"
role="button"
width="14"
height="14"
viewBox="0 0 14 14"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M3.40717 4.46881C3.11428 4.17591 3.11428 3.70104 3.40717 3.40815C3.70006 3.11525 4.17494 3.11525 4.46783 3.40815L6.99943 5.93975L9.53095 3.40822C9.82385 3.11533 10.2987 3.11533 10.5916 3.40822C10.8845 3.70112 10.8845 4.17599 10.5916 4.46888L8.06009 7.00041L10.5916 9.53193C10.8845 9.82482 10.8845 10.2997 10.5916 10.5926C10.2987 10.8855 9.82385 10.8855 9.53095 10.5926L6.99943 8.06107L4.46783 10.5927C4.17494 10.8856 3.70006 10.8856 3.40717 10.5927C3.11428 10.2998 3.11428 9.8249 3.40717 9.53201L5.93877 7.00041L3.40717 4.46881Z"
/>
</svg>
</div>
</div>
</div>
))
) : (
<input
placeholder="Select option"
className="w-full h-full p-1 pr-2 text-sm bg-transparent border-0 outline-hidden appearance-none placeholder:text-gray-800 focus:border-0 focus:outline-hidden focus:ring-0 dark:placeholder:text-white/90"
readOnly
value="Select option"
/>
)}
</div>
<div className="flex items-center py-1 pl-1 pr-1 w-7">
<button
type="button"
onClick={toggleDropdown}
className="w-5 h-5 text-gray-700 outline-hidden cursor-pointer focus:outline-hidden dark:text-gray-400"
>
<svg
className={`stroke-current ${isOpen ? "rotate-180" : ""}`}
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4.79175 7.39551L10.0001 12.6038L15.2084 7.39551"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</button>
</div>
</div>
</div>
{isOpen && (
<div
className="absolute left-0 z-40 w-full overflow-y-auto bg-white rounded-lg shadow-sm top-full max-h-select dark:bg-gray-900"
onClick={(e) => e.stopPropagation()}
>
<div className="flex flex-col">
{options.map((option, index) => (
<div key={index}>
<div
className={`hover:bg-primary/5 w-full cursor-pointer rounded-t border-b border-gray-200 dark:border-gray-800`}
onClick={() => handleSelect(option.value)}
>
<div
className={`relative flex w-full items-center p-2 pl-2 ${
selectedOptions.includes(option.value)
? "bg-primary/10"
: ""
}`}
>
<div className="mx-2 leading-6 text-gray-800 dark:text-white/90">
{option.text}
</div>
</div>
</div>
</div>
))}
</div>
</div>
)}
</div>
</div>
</div>
);
};
export default MultiSelect;
-64
View File
@@ -1,64 +0,0 @@
import React, { useState } from "react";
interface Option {
value: string;
label: string;
}
interface SelectProps {
options: Option[];
placeholder?: string;
onChange: (value: string) => void;
className?: string;
defaultValue?: string;
}
const Select: React.FC<SelectProps> = ({
options,
placeholder = "Select an option",
onChange,
className = "",
defaultValue = "",
}) => {
// Manage the selected value
const [selectedValue, setSelectedValue] = useState<string>(defaultValue);
const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const value = e.target.value;
setSelectedValue(value);
onChange(value); // Trigger parent handler
};
return (
<select
className={`h-11 w-full appearance-none rounded-lg border border-gray-300 px-4 py-2.5 pr-11 text-sm shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800 ${
selectedValue
? "text-gray-800 dark:text-white/90"
: "text-gray-400 dark:text-gray-400"
} ${className}`}
value={selectedValue}
onChange={handleChange}
>
{/* Placeholder option */}
<option
value=""
disabled
className="text-gray-700 dark:bg-gray-900 dark:text-gray-400"
>
{placeholder}
</option>
{/* Map over options */}
{options.map((option) => (
<option
key={option.value}
value={option.value}
className="text-gray-700 dark:bg-gray-900 dark:text-gray-400"
>
{option.label}
</option>
))}
</select>
);
};
export default Select;
-60
View File
@@ -1,60 +0,0 @@
import { useEffect } from 'react';
import flatpickr from 'flatpickr';
import 'flatpickr/dist/flatpickr.css';
import Label from './Label';
import { CalenderIcon } from '../../icons';
import Hook = flatpickr.Options.Hook;
import DateOption = flatpickr.Options.DateOption;
type PropsType = {
id: string;
mode?: "single" | "multiple" | "range" | "time";
onChange?: Hook | Hook[];
defaultDate?: DateOption;
label?: string;
placeholder?: string;
};
export default function DatePicker({
id,
mode,
onChange,
label,
defaultDate,
placeholder,
}: PropsType) {
useEffect(() => {
const flatPickr = flatpickr(`#${id}`, {
mode: mode || "single",
static: true,
monthSelectorType: "static",
dateFormat: "Y-m-d",
defaultDate,
onChange,
});
return () => {
if (!Array.isArray(flatPickr)) {
flatPickr.destroy();
}
};
}, [mode, onChange, id, defaultDate]);
return (
<div>
{label && <Label htmlFor={id}>{label}</Label>}
<div className="relative">
<input
id={id}
placeholder={placeholder}
className="h-11 w-full rounded-lg border appearance-none px-4 py-2.5 text-sm shadow-theme-xs placeholder:text-gray-400 focus:outline-hidden focus:ring-3 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 bg-transparent text-gray-800 border-gray-300 focus:border-brand-300 focus:ring-brand-500/20 dark:border-gray-700 dark:focus:border-brand-800"
/>
<span className="absolute text-gray-500 -translate-y-1/2 pointer-events-none right-3 top-1/2 dark:text-gray-400">
<CalenderIcon className="size-6" />
</span>
</div>
</div>
);
}
@@ -1,37 +0,0 @@
"use client";
import React, { useState } from "react";
import ComponentCard from "../../common/ComponentCard";
import Checkbox from "../input/Checkbox";
export default function CheckboxComponents() {
const [isChecked, setIsChecked] = useState(false);
const [isCheckedTwo, setIsCheckedTwo] = useState(true);
const [isCheckedDisabled, setIsCheckedDisabled] = useState(false);
return (
<ComponentCard title="Checkbox">
<div className="flex items-center gap-4">
<div className="flex items-center gap-3">
<Checkbox checked={isChecked} onChange={setIsChecked} />
<span className="block text-sm font-medium text-gray-700 dark:text-gray-400">
Default
</span>
</div>
<div className="flex items-center gap-3">
<Checkbox
checked={isCheckedTwo}
onChange={setIsCheckedTwo}
label="Checked"
/>
</div>
<div className="flex items-center gap-3">
<Checkbox
checked={isCheckedDisabled}
onChange={setIsCheckedDisabled}
disabled
label="Disabled"
/>
</div>
</div>
</ComponentCard>
);
}
@@ -1,120 +0,0 @@
"use client";
import React, { useState } from 'react';
import ComponentCard from '../../common/ComponentCard';
import Label from '../Label';
import Input from '../input/InputField';
import Select from '../Select';
import { ChevronDownIcon, EyeCloseIcon, EyeIcon, TimeIcon } from '../../../icons';
import DatePicker from '@/components/form/date-picker';
export default function DefaultInputs() {
const [showPassword, setShowPassword] = useState(false);
const options = [
{ value: "marketing", label: "Marketing" },
{ value: "template", label: "Template" },
{ value: "development", label: "Development" },
];
const handleSelectChange = (value: string) => {
console.log("Selected value:", value);
};
return (
<ComponentCard title="Default Inputs">
<div className="space-y-6">
<div>
<Label>Input</Label>
<Input type="text" />
</div>
<div>
<Label>Input with Placeholder</Label>
<Input type="text" placeholder="info@gmail.com" />
</div>
<div>
<Label>Select Input</Label>
<div className="relative">
<Select
options={options}
placeholder="Select an option"
onChange={handleSelectChange}
className="dark:bg-dark-900"
/>
<span className="absolute text-gray-500 -translate-y-1/2 pointer-events-none right-3 top-1/2 dark:text-gray-400">
<ChevronDownIcon/>
</span>
</div>
</div>
<div>
<Label>Password Input</Label>
<div className="relative">
<Input
type={showPassword ? "text" : "password"}
placeholder="Enter your password"
/>
<button
onClick={() => setShowPassword(!showPassword)}
className="absolute z-30 -translate-y-1/2 cursor-pointer right-4 top-1/2"
>
{showPassword ? (
<EyeIcon className="fill-gray-500 dark:fill-gray-400" />
) : (
<EyeCloseIcon className="fill-gray-500 dark:fill-gray-400" />
)}
</button>
</div>
</div>
<div>
<DatePicker
id="date-picker"
label="Date Picker Input"
placeholder="Select a date"
onChange={(dates, currentDateString) => {
// Handle your logic
console.log({ dates, currentDateString });
}}
/>
</div>
<div>
<Label htmlFor="tm">Time Picker Input</Label>
<div className="relative">
<Input
type="time"
id="tm"
name="tm"
onChange={(e) => console.log(e.target.value)}
/>
<span className="absolute text-gray-500 -translate-y-1/2 pointer-events-none right-3 top-1/2 dark:text-gray-400">
<TimeIcon />
</span>
</div>
</div>
<div>
<Label htmlFor="tm">Input with Payment</Label>
<div className="relative">
<Input
type="text"
placeholder="Card number"
className="pl-[62px]"
/>
<span className="absolute left-0 top-1/2 flex h-11 w-[46px] -translate-y-1/2 items-center justify-center border-r border-gray-200 dark:border-gray-800">
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="6.25" cy="10" r="5.625" fill="#E80B26" />
<circle cx="13.75" cy="10" r="5.625" fill="#F59D31" />
<path
d="M10 14.1924C11.1508 13.1625 11.875 11.6657 11.875 9.99979C11.875 8.33383 11.1508 6.8371 10 5.80713C8.84918 6.8371 8.125 8.33383 8.125 9.99979C8.125 11.6657 8.84918 13.1625 10 14.1924Z"
fill="#FC6020"
/>
</svg>
</span>
</div>
</div>
</div>
</ComponentCard>
);
}
@@ -1,77 +0,0 @@
"use client";
import React from "react";
import ComponentCard from "../../common/ComponentCard";
import { useDropzone } from "react-dropzone";
const DropzoneComponent: React.FC = () => {
const onDrop = (acceptedFiles: File[]) => {
console.log("Files dropped:", acceptedFiles);
// Handle file uploads here
};
const { getRootProps, getInputProps, isDragActive } = useDropzone({
onDrop,
accept: {
"image/png": [],
"image/jpeg": [],
"image/webp": [],
"image/svg+xml": [],
},
});
return (
<ComponentCard title="Dropzone">
<div className="transition border border-gray-300 border-dashed cursor-pointer dark:hover:border-brand-500 dark:border-gray-700 rounded-xl hover:border-brand-500">
<form
{...getRootProps()}
className={`dropzone rounded-xl border-dashed border-gray-300 p-7 lg:p-10
${
isDragActive
? "border-brand-500 bg-gray-100 dark:bg-gray-800"
: "border-gray-300 bg-gray-50 dark:border-gray-700 dark:bg-gray-900"
}
`}
id="demo-upload"
>
{/* Hidden Input */}
<input {...getInputProps()} />
<div className="dz-message flex flex-col items-center m-0!">
{/* Icon Container */}
<div className="mb-[22px] flex justify-center">
<div className="flex h-[68px] w-[68px] items-center justify-center rounded-full bg-gray-200 text-gray-700 dark:bg-gray-800 dark:text-gray-400">
<svg
className="fill-current"
width="29"
height="28"
viewBox="0 0 29 28"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M14.5019 3.91699C14.2852 3.91699 14.0899 4.00891 13.953 4.15589L8.57363 9.53186C8.28065 9.82466 8.2805 10.2995 8.5733 10.5925C8.8661 10.8855 9.34097 10.8857 9.63396 10.5929L13.7519 6.47752V18.667C13.7519 19.0812 14.0877 19.417 14.5019 19.417C14.9161 19.417 15.2519 19.0812 15.2519 18.667V6.48234L19.3653 10.5929C19.6583 10.8857 20.1332 10.8855 20.426 10.5925C20.7188 10.2995 20.7186 9.82463 20.4256 9.53184L15.0838 4.19378C14.9463 4.02488 14.7367 3.91699 14.5019 3.91699ZM5.91626 18.667C5.91626 18.2528 5.58047 17.917 5.16626 17.917C4.75205 17.917 4.41626 18.2528 4.41626 18.667V21.8337C4.41626 23.0763 5.42362 24.0837 6.66626 24.0837H22.3339C23.5766 24.0837 24.5839 23.0763 24.5839 21.8337V18.667C24.5839 18.2528 24.2482 17.917 23.8339 17.917C23.4197 17.917 23.0839 18.2528 23.0839 18.667V21.8337C23.0839 22.2479 22.7482 22.5837 22.3339 22.5837H6.66626C6.25205 22.5837 5.91626 22.2479 5.91626 21.8337V18.667Z"
/>
</svg>
</div>
</div>
{/* Text Content */}
<h4 className="mb-3 font-semibold text-gray-800 text-theme-xl dark:text-white/90">
{isDragActive ? "Drop Files Here" : "Drag & Drop Files Here"}
</h4>
<span className=" text-center mb-5 block w-full max-w-[290px] text-sm text-gray-700 dark:text-gray-400">
Drag and drop your PNG, JPG, WebP, SVG images here or browse
</span>
<span className="font-medium underline text-theme-sm text-brand-500">
Browse File
</span>
</div>
</form>
</div>
</ComponentCard>
);
};
export default DropzoneComponent;
@@ -1,23 +0,0 @@
"use client";
import React from "react";
import ComponentCard from "../../common/ComponentCard";
import FileInput from "../input/FileInput";
import Label from "../Label";
export default function FileInputExample() {
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file) {
console.log("Selected file:", file.name);
}
};
return (
<ComponentCard title="File Input">
<div>
<Label>Upload file</Label>
<FileInput onChange={handleFileChange} className="custom-class" />
</div>
</ComponentCard>
);
}
@@ -1,56 +0,0 @@
"use client";
import React from "react";
import ComponentCard from "../../common/ComponentCard";
import Label from "../Label";
import Input from "../input/InputField";
import { EnvelopeIcon } from "../../../icons";
import PhoneInput from "../group-input/PhoneInput";
export default function InputGroup() {
const countries = [
{ code: "US", label: "+1" },
{ code: "GB", label: "+44" },
{ code: "CA", label: "+1" },
{ code: "AU", label: "+61" },
];
const handlePhoneNumberChange = (phoneNumber: string) => {
console.log("Updated phone number:", phoneNumber);
};
return (
<ComponentCard title="Input Group">
<div className="space-y-6">
<div>
<Label>Email</Label>
<div className="relative">
<Input
placeholder="info@gmail.com"
type="text"
className="pl-[62px]"
/>
<span className="absolute left-0 top-1/2 -translate-y-1/2 border-r border-gray-200 px-3.5 py-3 text-gray-500 dark:border-gray-800 dark:text-gray-400">
<EnvelopeIcon />
</span>
</div>
</div>
<div>
<Label>Phone</Label>
<PhoneInput
selectPosition="start"
countries={countries}
placeholder="+1 (555) 000-0000"
onChange={handlePhoneNumberChange}
/>
</div>{" "}
<div>
<Label>Phone</Label>
<PhoneInput
selectPosition="end"
countries={countries}
placeholder="+1 (555) 000-0000"
onChange={handlePhoneNumberChange}
/>
</div>
</div>
</ComponentCard>
);
}
@@ -1,70 +0,0 @@
"use client";
import React, { useState } from "react";
import ComponentCard from "../../common/ComponentCard";
import Input from "../input/InputField";
import Label from "../Label";
export default function InputStates() {
const [email, setEmail] = useState("");
const [error, setError] = useState(false);
// Simulate a validation check
const validateEmail = (value: string) => {
const isValidEmail =
/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(value);
setError(!isValidEmail);
return isValidEmail;
};
const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setEmail(value);
validateEmail(value);
};
return (
<ComponentCard
title="Input States"
desc="Validation styles for error, success and disabled states on form controls."
>
<div className="space-y-5 sm:space-y-6">
{/* Error Input */}
<div>
<Label>Email</Label>
<Input
type="email"
defaultValue={email}
error={error}
onChange={handleEmailChange}
placeholder="Enter your email"
hint={error ? "This is an invalid email address." : ""}
/>
</div>
{/* Success Input */}
<div>
<Label>Email</Label>
<Input
type="email"
defaultValue={email}
success={!error}
onChange={handleEmailChange}
placeholder="Enter your email"
hint={!error ? "Valid email!" : ""}
/>
</div>
{/* Disabled Input */}
<div>
<Label>Email</Label>
<Input
type="text"
defaultValue="disabled@example.com"
disabled={true}
placeholder="Disabled email"
hint="This field is disabled."
/>
</div>
</div>
</ComponentCard>
);
}
@@ -1,43 +0,0 @@
"use client";
import React, { useState } from "react";
import ComponentCard from "../../common/ComponentCard";
import Radio from "../input/Radio";
export default function RadioButtons() {
const [selectedValue, setSelectedValue] = useState<string>("option2");
const handleRadioChange = (value: string) => {
setSelectedValue(value);
};
return (
<ComponentCard title="Radio Buttons">
<div className="flex flex-wrap items-center gap-8">
<Radio
id="radio1"
name="group1"
value="option1"
checked={selectedValue === "option1"}
onChange={handleRadioChange}
label="Default"
/>
<Radio
id="radio2"
name="group1"
value="option2"
checked={selectedValue === "option2"}
onChange={handleRadioChange}
label="Selected"
/>
<Radio
id="radio3"
name="group1"
value="option3"
checked={selectedValue === "option3"}
onChange={handleRadioChange}
label="Disabled"
disabled={true}
/>
</div>
</ComponentCard>
);
}
@@ -1,61 +0,0 @@
"use client";
import React, { useState } from "react";
import ComponentCard from "../../common/ComponentCard";
import Label from "../Label";
import Select from "../Select";
import MultiSelect from "../MultiSelect";
import { ChevronDownIcon } from "@/icons";
export default function SelectInputs() {
const options = [
{ value: "marketing", label: "Marketing" },
{ value: "template", label: "Template" },
{ value: "development", label: "Development" },
];
const [selectedValues, setSelectedValues] = useState<string[]>([]);
const handleSelectChange = (value: string) => {
console.log("Selected value:", value);
};
const multiOptions = [
{ value: "1", text: "Option 1", selected: false },
{ value: "2", text: "Option 2", selected: false },
{ value: "3", text: "Option 3", selected: false },
{ value: "4", text: "Option 4", selected: false },
{ value: "5", text: "Option 5", selected: false },
];
return (
<ComponentCard title="Select Inputs">
<div className="space-y-6">
<div>
<Label>Select Input</Label>
<div className="relative">
<Select
options={options}
placeholder="Select Option"
onChange={handleSelectChange}
className="dark:bg-dark-900"
/>
<span className="absolute text-gray-500 -translate-y-1/2 pointer-events-none right-3 top-1/2 dark:text-gray-400">
<ChevronDownIcon/>
</span>
</div>
</div>
<div className="relative">
<MultiSelect
label="Multiple Select Options"
options={multiOptions}
defaultSelected={["1", "3"]}
onChange={(values) => setSelectedValues(values)}
/>
<p className="sr-only">
Selected Values: {selectedValues.join(", ")}
</p>
</div>
</div>
</ComponentCard>
);
}
@@ -1,43 +0,0 @@
"use client";
import React, { useState } from "react";
import ComponentCard from "../../common/ComponentCard";
import TextArea from "../input/TextArea";
import Label from "../Label";
export default function TextAreaInput() {
const [message, setMessage] = useState("");
const [messageTwo, setMessageTwo] = useState("");
return (
<ComponentCard title="Textarea input field">
<div className="space-y-6">
{/* Default TextArea */}
<div>
<Label>Description</Label>
<TextArea
value={message}
onChange={(value) => setMessage(value)}
rows={6}
/>
</div>
{/* Disabled TextArea */}
<div>
<Label>Description</Label>
<TextArea rows={6} disabled />
</div>
{/* Error TextArea */}
<div>
<Label>Description</Label>
<TextArea
rows={6}
value={messageTwo}
error
onChange={(value) => setMessageTwo(value)}
hint="Please enter a valid message."
/>
</div>
</div>
</ComponentCard>
);
}
@@ -1,42 +0,0 @@
"use client";
import React from "react";
import ComponentCard from "../../common/ComponentCard";
import Switch from "../switch/Switch";
export default function ToggleSwitch() {
const handleSwitchChange = (checked: boolean) => {
console.log("Switch is now:", checked ? "ON" : "OFF");
};
return (
<ComponentCard title="Toggle switch input">
<div className="flex gap-4">
<Switch
label="Default"
defaultChecked={true}
onChange={handleSwitchChange}
/>
<Switch
label="Checked"
defaultChecked={true}
onChange={handleSwitchChange}
/>
<Switch label="Disabled" disabled={true} />
</div>{" "}
<div className="flex gap-4">
<Switch
label="Default"
defaultChecked={true}
onChange={handleSwitchChange}
color="gray"
/>
<Switch
label="Checked"
defaultChecked={true}
onChange={handleSwitchChange}
color="gray"
/>
<Switch label="Disabled" disabled={true} color="gray" />
</div>
</ComponentCard>
);
}
@@ -1,141 +0,0 @@
"use client";
import React, { useState } from "react";
interface CountryCode {
code: string;
label: string;
}
interface PhoneInputProps {
countries: CountryCode[];
placeholder?: string;
onChange?: (phoneNumber: string) => void;
selectPosition?: "start" | "end"; // New prop for dropdown position
}
const PhoneInput: React.FC<PhoneInputProps> = ({
countries,
placeholder = "+1 (555) 000-0000",
onChange,
selectPosition = "start", // Default position is 'start'
}) => {
const [selectedCountry, setSelectedCountry] = useState<string>("US");
const [phoneNumber, setPhoneNumber] = useState<string>("+1");
const countryCodes: Record<string, string> = countries.reduce(
(acc, { code, label }) => ({ ...acc, [code]: label }),
{}
);
const handleCountryChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const newCountry = e.target.value;
setSelectedCountry(newCountry);
setPhoneNumber(countryCodes[newCountry]);
if (onChange) {
onChange(countryCodes[newCountry]);
}
};
const handlePhoneNumberChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newPhoneNumber = e.target.value;
setPhoneNumber(newPhoneNumber);
if (onChange) {
onChange(newPhoneNumber);
}
};
return (
<div className="relative flex">
{/* Dropdown position: Start */}
{selectPosition === "start" && (
<div className="absolute">
<select
value={selectedCountry}
onChange={handleCountryChange}
className="appearance-none bg-none rounded-l-lg border-0 border-r border-gray-200 bg-transparent py-3 pl-3.5 pr-8 leading-tight text-gray-700 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-800 dark:text-gray-400"
>
{countries.map((country) => (
<option
key={country.code}
value={country.code}
className="text-gray-700 dark:bg-gray-900 dark:text-gray-400"
>
{country.code}
</option>
))}
</select>
<div className="absolute inset-y-0 flex items-center text-gray-700 pointer-events-none bg-none right-3 dark:text-gray-400">
<svg
className="stroke-current"
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4.79175 7.396L10.0001 12.6043L15.2084 7.396"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</div>
</div>
)}
{/* Input field */}
<input
type="tel"
value={phoneNumber}
onChange={handlePhoneNumberChange}
placeholder={placeholder}
className={`dark:bg-dark-900 h-11 w-full ${
selectPosition === "start" ? "pl-[84px]" : "pr-[84px]"
} rounded-lg border border-gray-300 bg-transparent py-3 px-4 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800`}
/>
{/* Dropdown position: End */}
{selectPosition === "end" && (
<div className="absolute right-0">
<select
value={selectedCountry}
onChange={handleCountryChange}
className="appearance-none bg-none rounded-r-lg border-0 border-l border-gray-200 bg-transparent py-3 pl-3.5 pr-8 leading-tight text-gray-700 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-800 dark:text-gray-400"
>
{countries.map((country) => (
<option
key={country.code}
value={country.code}
className="text-gray-700 dark:bg-gray-900 dark:text-gray-400"
>
{country.code}
</option>
))}
</select>
<div className="absolute inset-y-0 flex items-center text-gray-700 pointer-events-none right-3 dark:text-gray-400">
<svg
className="stroke-current"
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4.79175 7.396L10.0001 12.6043L15.2084 7.396"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</div>
</div>
)}
</div>
);
};
export default PhoneInput;
+1 -1
View File
@@ -119,7 +119,7 @@ export default function UserDropdown() {
<DropdownItem
onItemClick={closeDropdown}
tag="a"
href="/user/account"
href="/user"
className="flex items-center gap-3 px-3 py-2 font-medium text-gray-700 rounded-lg group text-theme-sm hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300"
>
<svg
-294
View File
@@ -1,294 +0,0 @@
"use client";
import {
Table,
TableBody,
TableCell,
TableHeader,
TableRow,
} from "../ui/table";
import Badge from "../ui/badge/Badge";
import { Application } from "@/interface/historian";
export type AppSortColumn =
| "created_at"
| "status"
| "verify_type"
| "reviewed_at"
| "reviewed_by"
| "updated_at";
interface ApplicationTableProps {
data: Application[];
onSort: (column: AppSortColumn) => void;
onViewDetail: (app: Application) => void;
sortBy?: AppSortColumn;
sortOrder?: "asc" | "desc";
}
export default function ApplicationTable({
data,
onSort,
onViewDetail,
sortBy,
sortOrder,
}: ApplicationTableProps) {
const formatDate = (dateString: string | null | undefined) => {
if (!dateString) return "-";
const date = new Date(dateString);
return date.toLocaleDateString("vi-VN", {
day: "2-digit",
month: "2-digit",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
});
};
const SortIcon = ({ column }: { column: AppSortColumn }) => {
const isActive = sortBy === column;
return (
<div className="flex flex-col ml-2 opacity-50 cursor-pointer hover:opacity-100">
<svg
className={`w-3 h-3 ${isActive && sortOrder === "asc" ? "text-blue-700 opacity-100" : "text-gray-400"}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={3}
d="M5 15l7-7 7 7"
/>
</svg>
<svg
className={`w-3 h-3 -mt-1 ${isActive && sortOrder === "desc" ? "text-blue-700 opacity-100" : "text-gray-400"}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={3}
d="M19 9l-7 7-7-7"
/>
</svg>
</div>
);
};
const getStatusBadge = (status: string | number) => {
const s = status?.toString();
switch (s) {
case "1":
case "PENDING":
return (
<Badge size="sm" variant="light" color="warning">
Đang chờ
</Badge>
);
case "2":
case "APPROVED":
return (
<Badge size="sm" variant="light" color="success">
Đã duyệt
</Badge>
);
case "3":
case "REJECTED":
return (
<Badge size="sm" variant="light" color="error">
Từ chối
</Badge>
);
default:
return (
<Badge size="sm" variant="light" color="light">
{status || "N/A"}
</Badge>
);
}
};
const renderVerifyTypes = (
verifyType: string | string[] | number | number[],
) => {
const typeMap: Record<string, string> = {
"1": "Thẻ nhận dạng nhà nghiên cứu",
ID_CARD: "Thẻ nhận dạng nhà nghiên cứu",
"2": "Bằng cấp",
EDUCATION: "Bằng cấp",
"3": "Chuyên gia",
EXPERT: "Chuyên gia",
"4": "Khác",
OTHER: "Khác",
};
const typesArray = Array.isArray(verifyType) ? verifyType : [verifyType];
return (
<div className="flex flex-wrap gap-1">
{typesArray.map((type, index) => {
const t = type?.toString();
if (!t) return null;
return (
<span
key={index}
className="px-2 py-0.5 rounded-md bg-blue-50 text-blue-600 dark:bg-blue-500/10 dark:text-blue-400 text-[11px] font-medium whitespace-nowrap"
>
{typeMap[t] || t}
</span>
);
})}
</div>
);
};
return (
<div className="overflow-hidden rounded-xl border border-gray-200 bg-white dark:border-white/[0.05] dark:bg-white/[0.03]">
<div className="max-w-full overflow-x-auto">
<div className="min-w-[1300px]">
<Table>
<TableHeader className="border-b border-gray-100 dark:border-white/[0.05]">
<TableRow>
<TableCell
isHeader
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
>
Người gửi (ID)
</TableCell>
<TableCell
isHeader
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400 min-w-[220px]"
>
Loại xác minh
</TableCell>
<TableCell
isHeader
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
>
Đính kèm
</TableCell>
<TableCell
isHeader
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
>
<div
className="flex items-center cursor-pointer select-none"
onClick={() => onSort("status")}
>
Trạng thái <SortIcon column="status" />
</div>
</TableCell>
<TableCell
isHeader
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
>
<div
className="flex items-center cursor-pointer select-none"
onClick={() => onSort("created_at")}
>
Ngày nộp <SortIcon column="created_at" />
</div>
</TableCell>
<TableCell
isHeader
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
>
<div
className="flex items-center cursor-pointer select-none"
onClick={() => onSort("reviewed_at")}
>
Cập nhật <SortIcon column="reviewed_at" />
</div>
</TableCell>
<TableCell
isHeader
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
>
Cập nhật bởi
</TableCell>
<TableCell
isHeader
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400 max-w-[200px]"
>
Ghi chú
</TableCell>
<TableCell
isHeader
className="px-5 py-3 font-medium text-center text-gray-500 text-theme-xs dark:text-gray-400"
>
Thao tác
</TableCell>
</TableRow>
</TableHeader>
<TableBody className="divide-y divide-gray-100 dark:divide-white/[0.05]">
{data.length > 0 ? (
data.map((app) => (
<TableRow
key={app.id}
className="hover:bg-gray-50/50 dark:hover:bg-white/[0.01] transition-colors"
>
<TableCell className="px-5 py-4 text-start font-mono text-theme-xs">
{app.user.display_name}
</TableCell>
<TableCell className="px-5 py-4 text-start">
{renderVerifyTypes(app.verify_type)}
</TableCell>
<TableCell className="px-5 py-4 text-center text-theme-sm">
<span className="font-bold text-gray-800 dark:text-white mr-1">
{app.media?.length || 0}
</span>
</TableCell>
<TableCell className="px-5 py-4 text-center ">
{getStatusBadge(app.status)}
</TableCell>
<TableCell className="px-5 py-4 text-gray-600 text-theme-sm dark:text-gray-400">
{formatDate(app.created_at)}
</TableCell>
<TableCell className="px-5 py-4 text-gray-600 text-theme-sm dark:text-gray-400">
{formatDate(app.reviewed_at)}
</TableCell>
<TableCell className="px-5 py-4 text-gray-600 text-theme-sm dark:text-gray-400">
{app.reviewer?.display_name || "-"}
</TableCell>
<TableCell className="group relative px-5 pb-4 text-start text-theme-xs text-gray-500 dark:text-gray-400 max-w-50 min-w-50">
<div className="truncate">{app.review_note || "-"}</div>
{app.review_note && (
<div className="invisible group-hover:visible absolute z-50 bottom-full left-1/2 -translate-x-1/2 w-max max-w-75 p-2 backdrop-blur-xs text-black text-xs rounded-lg shadow-xl wrap-break-words whitespace-normal">
{app.review_note}
<div className="absolute top-full left-1/2 border-6 border-transparent border-t-white"></div>
</div>
)}
</TableCell>
<TableCell className="px-5 py-4 text-center">
<button
onClick={() => onViewDetail(app)}
className="text-brand-500 hover:text-brand-600 font-medium text-theme-sm"
>
Chi tiết
</button>
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={9}
className="px-5 py-20 text-center text-gray-500 italic"
>
Không tìm thấy dữ liệu hồ
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
</div>
</div>
);
}
-293
View File
@@ -1,293 +0,0 @@
"use client";
import {
Table,
TableBody,
TableCell,
TableHeader,
TableRow,
} from "../ui/table";
import Badge from "../ui/badge/Badge";
import Image from "next/image";
import { fullDataUser } from "@/interface/admin";
type SortColumn = "created_at" | "updated_at" | "display_name" | "email";
interface Role {
id: string;
name: string;
}
interface BasicTableOneProps {
data: fullDataUser[];
onSort: (column: SortColumn) => void;
onViewDetail: (user: fullDataUser) => void;
sortBy?: SortColumn;
sortOrder?: "asc" | "desc";
onFilterRole?: (role: string) => void;
selectedRole?: string;
roles?: Role[];
}
export default function BasicTableOne({
data,
onSort,
onViewDetail,
sortBy,
sortOrder,
onFilterRole,
selectedRole,
roles = [],
}: BasicTableOneProps) {
const formatDate = (dateString: string) => {
if (!dateString) return "-";
const date = new Date(dateString);
return date.toLocaleDateString("en-GB", {
day: "2-digit",
month: "short",
year: "numeric",
});
};
const SortIcon = ({ column }: { column: SortColumn }) => {
const isActive = sortBy === column;
return (
<div className="flex flex-col ml-2 opacity-50 cursor-pointer hover:opacity-100">
<svg
className={`w-3 h-3 ${isActive && sortOrder === "asc" ? "text-blue-700 opacity-100" : "text-gray-400"}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={3}
d="M5 15l7-7 7 7"
/>
</svg>
<svg
className={`w-3 h-3 -mt-1 ${isActive && sortOrder === "desc" ? "text-blue-700 opacity-100" : "text-gray-400"}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={3}
d="M19 9l-7 7-7-7"
/>
</svg>
</div>
);
};
return (
<div className="overflow-hidden rounded-xl border border-gray-200 bg-white dark:border-white/[0.05] dark:bg-white/[0.03]">
<div className="max-w-full overflow-x-auto">
<div className="min-w-[1100px]">
<Table>
<TableHeader className="border-b border-gray-100 dark:border-white/[0.05]">
<TableRow>
<TableCell
isHeader
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400 min-w-[265px] max-w-[265px]"
>
<div
className="flex items-center cursor-pointer select-none"
onClick={() => onSort("display_name")}
>
Người dùng <SortIcon column="display_name" />
</div>
</TableCell>
<TableCell
isHeader
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400 min-w-[337px] max-w-[337px]"
>
<div
className="flex items-center cursor-pointer select-none"
onClick={() => onSort("email")}
>
Email <SortIcon column="email" />
</div>
</TableCell>
<TableCell
isHeader
className="px-5 py-3 font-medium text-start text-theme-xs min-w-[188px] max-w-[188px]"
>
<div className="relative inline-flex items-center group">
<select
value={selectedRole}
onChange={(e) => onFilterRole?.(e.target.value)}
className="bg-transparent border-none outline-none cursor-pointer appearance-none text-gray-500 dark:text-gray-400 pr-5 hover:text-brand-500 transition-colors font-medium"
>
<option value="">Vai trò (Tất cả)</option>
{roles.map((role) => (
<option key={role.id} value={role.id}>
{role.name}
</option>
))}
</select>
<svg
className="w-3 h-3 absolute right-0 text-gray-400 pointer-events-none group-hover:text-brand-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={3}
d="M19 9l-7 7-7-7"
/>
</svg>
</div>
</TableCell>
<TableCell
isHeader
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
>
Trạng thái
</TableCell>
<TableCell
isHeader
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
>
<div
className="flex items-center cursor-pointer select-none"
onClick={() => onSort("created_at")}
>
Ngày tham gia <SortIcon column="created_at" />
</div>
</TableCell>
<TableCell
isHeader
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
>
<div
className="flex items-center cursor-pointer select-none"
onClick={() => onSort("updated_at")}
>
Cập nhật <SortIcon column="updated_at" />
</div>
</TableCell>
<TableCell
isHeader
className="px-5 py-3 font-medium text-gray-500 text-center text-theme-xs dark:text-gray-400"
>
Thao tác
</TableCell>
</TableRow>
</TableHeader>
<TableBody className="divide-y divide-gray-100 dark:divide-white/[0.05]">
{data.length > 0 ? (
data.map((user) => (
<TableRow
key={user.id}
className="hover:bg-gray-50/50 dark:hover:bg-white/[0.01] transition-colors"
>
<TableCell className="px-5 py-4 text-start">
<div className="flex items-center gap-3">
<div className="w-10 h-10 overflow-hidden rounded-full flex-shrink-0 bg-gray-100 dark:bg-gray-800 flex items-center justify-center">
{user.profile?.avatar_url ? (
<Image
width={40}
height={40}
src={user.profile.avatar_url}
alt="Avatar"
className="object-cover w-full h-full"
/>
) : (
<span className="font-bold text-brand-500 uppercase text-xs">
{user.profile?.display_name?.charAt(0) || "U"}
</span>
)}
</div>
<div>
<span className="block font-medium text-gray-800 text-theme-sm dark:text-white/90">
{user.profile?.display_name || "N/A"}
</span>
<span className="block text-gray-500 text-theme-xs dark:text-gray-400">
ID: {user.id.slice(0, 8)}...
</span>
</div>
</div>
</TableCell>
<TableCell className="px-5 py-4 text-gray-600 text-start text-theme-sm dark:text-gray-400">
{user.email}
</TableCell>
<TableCell className="px-5 py-4 text-start text-theme-sm">
<div className="flex flex-wrap gap-1">
{user.roles?.map((role) => (
<span
key={role.id}
className="px-2 py-0.5 rounded-md bg-brand-50 text-brand-600 dark:bg-brand-500/10 dark:text-brand-400 text-[10px] uppercase"
>
{role.name}
</span>
)) || (
<span className="text-gray-400 italic text-[10px]">
No Role
</span>
)}
</div>
</TableCell>
<TableCell className="px-5 py-4 text-start">
<Badge
size="sm"
variant="light"
color={user.is_deleted ? "error" : "success"}
>
{user.is_deleted ? "Bị khóa" : "Hoạt động"}
</Badge>
</TableCell>
<TableCell className="px-5 py-4 text-gray-600 text-theme-sm dark:text-gray-400">
{formatDate(user.created_at)}
</TableCell>
<TableCell className="px-5 py-4 text-gray-600 text-theme-sm dark:text-gray-400">
{formatDate(user.updated_at)}
</TableCell>
<TableCell className="px-5 py-4 text-center">
<button
onClick={() => onViewDetail(user)}
className="text-brand-500 hover:text-brand-600 font-medium text-theme-sm"
>
Chi tiết
</button>
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={7}
className="px-5 py-24 text-center text-gray-500 italic"
>
<div className="flex flex-col items-center justify-center gap-2">
<p className="text-theme-sm">
Không tìm thấy dữ liệu người dùng
</p>
</div>
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
</div>
</div>
);
}
-170
View File
@@ -1,170 +0,0 @@
"use client";
import { useEffect, useState } from "react";
import { Modal } from "../ui/modal";
import Button from "../ui/button/Button";
import { fullDataUser } from "@/interface/admin";
import { apiGetAllRole, apiChangeRole } from "@/service/adminService";
import { toast } from "sonner";
interface Role {
id: string;
name: string;
}
interface ChangeRoleModalProps {
isOpen: boolean;
onClose: () => void;
user: fullDataUser | null;
onSuccess: () => void;
}
const DEFAULT_ROLE_NAME = "USER";
export default function ChangeRoleModal({ isOpen, onClose, user, onSuccess }: ChangeRoleModalProps) {
const [roles, setRoles] = useState<Role[]>([]);
const [selectedRoleIds, setSelectedRoleIds] = useState<string[]>([]);
const [loading, setLoading] = useState(false);
const [fetchingRoles, setFetchingRoles] = useState(true);
useEffect(() => {
if (isOpen && user) {
setFetchingRoles(true);
apiGetAllRole()
.then((res) => {
if (res?.status) setRoles(res.data);
})
.catch((err) => {
console.error("Lỗi fetch roles:", err);
toast.error("Không thể lấy danh sách vai trò");
})
.finally(() => setFetchingRoles(false));
const currentUserRoles = user.roles?.map((r) => r.id) || [];
setSelectedRoleIds(currentUserRoles);
}
}, [isOpen, user]);
const handleToggleRole = (roleId: string, isDefault: boolean) => {
if (isDefault) return;
setSelectedRoleIds((prev) =>
prev.includes(roleId)
? prev.filter((id) => id !== roleId)
: [...prev, roleId]
);
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!user) return;
try {
setLoading(true);
const payload = {
role_ids: selectedRoleIds,
user_id: user.id,
};
await apiChangeRole(user.id, payload);
toast.success("Cập nhật vai trò thành công!");
onSuccess();
onClose();
} catch (error) {
console.error(error);
toast.error("Lỗi khi cập nhật vai trò!");
} finally {
setLoading(false);
}
};
if (!user) return null;
return (
<Modal isOpen={isOpen} onClose={onClose} className="max-w-[400px] m-4">
<div className="bg-white dark:bg-gray-900 rounded-xl p-6 shadow-lg border border-gray-100 dark:border-gray-800 w-full relative">
<div className="mb-6 pr-6">
<h3 className="text-lg font-semibold text-gray-800 dark:text-white">
Vai trò người dùng
</h3>
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400 truncate">
{user.profile?.display_name || user.email}
</p>
</div>
<form onSubmit={handleSubmit}>
{fetchingRoles ? (
<div className="flex items-center justify-center py-8">
<div className="w-6 h-6 border-2 border-gray-200 border-t-brand-500 rounded-full animate-spin"></div>
</div>
) : (
<div className="flex flex-col space-y-1 max-h-[300px] overflow-y-auto custom-scrollbar -mx-2 px-2">
{roles.map((role) => {
const isSelected = selectedRoleIds.includes(role.id);
const isDefault = role.name === DEFAULT_ROLE_NAME;
return (
<label
key={role.id}
className={`flex items-center gap-3 p-2.5 rounded-lg transition-colors ${
isDefault
? "opacity-40 cursor-not-allowed bg-gray-50 dark:bg-gray-800/30"
: "cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800/50"
}`}
>
<input
type="checkbox"
checked={isDefault ? true : isSelected}
onChange={() => handleToggleRole(role.id, isDefault)}
disabled={isDefault}
className={`w-4 h-4 rounded border-gray-300 focus:ring-brand-500 dark:border-gray-600 dark:bg-gray-700 ${
isDefault ? "text-gray-400" : "text-brand-500"
}`}
/>
<div className="flex flex-col">
<span className={`text-sm ${
isDefault
? "text-gray-500 font-normal"
: isSelected
? "text-gray-900 dark:text-white font-medium"
: "text-gray-700 dark:text-gray-300"
}`}>
{role.name}
</span>
{isDefault && (
<span className="text-[10px] text-gray-400 italic">
</span>
)}
</div>
</label>
);
})}
</div>
)}
<div className="flex items-center justify-end gap-3 mt-8">
<Button
size="sm"
variant="outline"
type="button"
onClick={onClose}
disabled={loading || fetchingRoles}
>
Hủy
</Button>
<Button
size="sm"
type="submit"
disabled={loading || fetchingRoles}
className="min-w-[100px]"
>
{loading ? "Đang lưu..." : "Lưu"}
</Button>
</div>
</form>
</div>
</Modal>
);
}
-140
View File
@@ -1,140 +0,0 @@
"use client";
import { Modal } from "../ui/modal";
import UserMetaCard from "@/components/user-profile/UserMetaCard";
import UserInfoCard from "@/components/user-profile/UserInfoCard";
import { UserMetaCardProps } from "@/interface/user";
import { fullDataUser } from "@/interface/admin";
import { useEffect, useState } from "react";
import { MediaDto } from "@/interface/media";
import { apiGetUserMedia } from "@/service/adminService";
import MediaCard from "@/components/user-profile/Media";
interface UserDetailModalProps {
isOpen: boolean;
onClose: () => void;
user: fullDataUser | null;
onChangeRole: (user: fullDataUser) => void;
onDelete: (user: fullDataUser) => void;
onRestore: (user: fullDataUser) => void;
}
export default function UserDetailModal({
isOpen,
onClose,
user,
onChangeRole,
onDelete,
onRestore,
}: UserDetailModalProps) {
const [mediaData, setMediaData] = useState<MediaDto | null>(null);
const [loading, setLoading] = useState(true);
const formattedData: UserMetaCardProps = {
data: user
? {
id: user.id,
email: user.email,
profile: user.profile,
roles: user.roles as any, // UserRole and Role are slightly different, need a better fix or small cast
}
: undefined,
};
useEffect(() => {
if (user?.id && isOpen) {
const fetchUserMedia = async () => {
setLoading(true);
try {
const mediaResponse = await apiGetUserMedia(user.id);
setMediaData(mediaResponse);
} catch (err) {
console.error("Lỗi fetch media:", err);
setMediaData(null);
} finally {
setLoading(false);
}
};
fetchUserMedia();
}
}, [user?.id, isOpen]);
if (!user) return null;
return (
<Modal isOpen={isOpen} onClose={onClose} className="max-w-[850px] m-4">
<div className="no-scrollbar relative w-full max-w-[850px] overflow-y-auto rounded-3xl bg-white p-4 dark:bg-gray-900 lg:p-8">
<div className="flex justify-between items-center mb-6">
<h3 className="text-xl font-bold text-gray-800 dark:text-white">
Chi tiết người dùng
</h3>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-600 transition-colors"
>
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div className="space-y-6 custom-scrollbar max-h-[65vh] overflow-y-auto pr-2">
<UserMetaCard data={formattedData} />
<UserInfoCard data={formattedData} />
<div className="min-h-[150px] relative">
{loading ? (
<div className="flex flex-col items-center justify-center py-10 space-y-3">
<div className="w-10 h-10 border-4 border-gray-200 border-t-brand-500 rounded-full animate-spin"></div>
<p className="text-sm text-gray-500 animate-pulse">Đang tải tài liệu...</p>
</div>
) : (
<>
{(mediaData?.data?.length ?? 0) > 0 ? (
<MediaCard data={mediaData!} />
) : (
<div className="p-5 border border-dashed border-gray-200 rounded-2xl text-center text-gray-400 text-sm">
Người dùng này chưa dữ liệu media.
</div>
)}
</>
)}
</div>
</div>
<div className="mt-8 pt-6 border-t border-gray-100 dark:border-white/[0.05] flex items-center justify-between">
<div className="text-sm text-gray-500">
Thao tác quản trị viên
</div>
<div className="flex gap-3">
<button
onClick={() => onChangeRole(user)}
className="px-4 py-2 text-sm font-medium text-purple-600 bg-purple-50 rounded-lg hover:bg-purple-100 dark:bg-purple-500/10 dark:text-purple-400 dark:hover:bg-purple-500/20 transition-colors"
>
Đi vai trò
</button>
{user.is_deleted ? (
<button
onClick={() => onRestore(user)}
className="px-4 py-2 text-sm font-medium text-green-600 bg-green-50 rounded-lg hover:bg-green-100 dark:bg-green-500/10 dark:text-green-400 dark:hover:bg-green-500/20 transition-colors"
>
Khôi phục
</button>
) : (
<button
onClick={() => onDelete(user)}
className="px-4 py-2 text-sm font-medium text-red-600 bg-red-50 rounded-lg hover:bg-red-100 dark:bg-red-500/10 dark:text-red-400 dark:hover:bg-red-500/20 transition-colors"
>
Khóa / Xóa
</button>
)}
</div>
</div>
</div>
</Modal>
);
}
-145
View File
@@ -1,145 +0,0 @@
import Link from "next/link";
import React from "react";
interface AlertProps {
variant: "success" | "error" | "warning" | "info"; // Alert type
title: string; // Title of the alert
message: string; // Message of the alert
showLink?: boolean; // Whether to show the "Learn More" link
linkHref?: string; // Link URL
linkText?: string; // Link text
}
const Alert: React.FC<AlertProps> = ({
variant,
title,
message,
showLink = false,
linkHref = "#",
linkText = "Learn more",
}) => {
// Tailwind classes for each variant
const variantClasses = {
success: {
container:
"border-success-500 bg-success-50 dark:border-success-500/30 dark:bg-success-500/15",
icon: "text-success-500",
},
error: {
container:
"border-error-500 bg-error-50 dark:border-error-500/30 dark:bg-error-500/15",
icon: "text-error-500",
},
warning: {
container:
"border-warning-500 bg-warning-50 dark:border-warning-500/30 dark:bg-warning-500/15",
icon: "text-warning-500",
},
info: {
container:
"border-blue-light-500 bg-blue-light-50 dark:border-blue-light-500/30 dark:bg-blue-light-500/15",
icon: "text-blue-light-500",
},
};
// Icon for each variant
const icons = {
success: (
<svg
className="fill-current"
width="24"
height="24"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M3.70186 12.0001C3.70186 7.41711 7.41711 3.70186 12.0001 3.70186C16.5831 3.70186 20.2984 7.41711 20.2984 12.0001C20.2984 16.5831 16.5831 20.2984 12.0001 20.2984C7.41711 20.2984 3.70186 16.5831 3.70186 12.0001ZM12.0001 1.90186C6.423 1.90186 1.90186 6.423 1.90186 12.0001C1.90186 17.5772 6.423 22.0984 12.0001 22.0984C17.5772 22.0984 22.0984 17.5772 22.0984 12.0001C22.0984 6.423 17.5772 1.90186 12.0001 1.90186ZM15.6197 10.7395C15.9712 10.388 15.9712 9.81819 15.6197 9.46672C15.2683 9.11525 14.6984 9.11525 14.347 9.46672L11.1894 12.6243L9.6533 11.0883C9.30183 10.7368 8.73198 10.7368 8.38051 11.0883C8.02904 11.4397 8.02904 12.0096 8.38051 12.3611L10.553 14.5335C10.7217 14.7023 10.9507 14.7971 11.1894 14.7971C11.428 14.7971 11.657 14.7023 11.8257 14.5335L15.6197 10.7395Z"
/>
</svg>
),
error: (
<svg
className="fill-current"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M20.3499 12.0004C20.3499 16.612 16.6115 20.3504 11.9999 20.3504C7.38832 20.3504 3.6499 16.612 3.6499 12.0004C3.6499 7.38881 7.38833 3.65039 11.9999 3.65039C16.6115 3.65039 20.3499 7.38881 20.3499 12.0004ZM11.9999 22.1504C17.6056 22.1504 22.1499 17.6061 22.1499 12.0004C22.1499 6.3947 17.6056 1.85039 11.9999 1.85039C6.39421 1.85039 1.8499 6.3947 1.8499 12.0004C1.8499 17.6061 6.39421 22.1504 11.9999 22.1504ZM13.0008 16.4753C13.0008 15.923 12.5531 15.4753 12.0008 15.4753L11.9998 15.4753C11.4475 15.4753 10.9998 15.923 10.9998 16.4753C10.9998 17.0276 11.4475 17.4753 11.9998 17.4753L12.0008 17.4753C12.5531 17.4753 13.0008 17.0276 13.0008 16.4753ZM11.9998 6.62898C12.414 6.62898 12.7498 6.96476 12.7498 7.37898L12.7498 13.0555C12.7498 13.4697 12.414 13.8055 11.9998 13.8055C11.5856 13.8055 11.2498 13.4697 11.2498 13.0555L11.2498 7.37898C11.2498 6.96476 11.5856 6.62898 11.9998 6.62898Z"
fill="#F04438"
/>
</svg>
),
warning: (
<svg
className="fill-current"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M3.6501 12.0001C3.6501 7.38852 7.38852 3.6501 12.0001 3.6501C16.6117 3.6501 20.3501 7.38852 20.3501 12.0001C20.3501 16.6117 16.6117 20.3501 12.0001 20.3501C7.38852 20.3501 3.6501 16.6117 3.6501 12.0001ZM12.0001 1.8501C6.39441 1.8501 1.8501 6.39441 1.8501 12.0001C1.8501 17.6058 6.39441 22.1501 12.0001 22.1501C17.6058 22.1501 22.1501 17.6058 22.1501 12.0001C22.1501 6.39441 17.6058 1.8501 12.0001 1.8501ZM10.9992 7.52517C10.9992 8.07746 11.4469 8.52517 11.9992 8.52517H12.0002C12.5525 8.52517 13.0002 8.07746 13.0002 7.52517C13.0002 6.97289 12.5525 6.52517 12.0002 6.52517H11.9992C11.4469 6.52517 10.9992 6.97289 10.9992 7.52517ZM12.0002 17.3715C11.586 17.3715 11.2502 17.0357 11.2502 16.6215V10.945C11.2502 10.5308 11.586 10.195 12.0002 10.195C12.4144 10.195 12.7502 10.5308 12.7502 10.945V16.6215C12.7502 17.0357 12.4144 17.3715 12.0002 17.3715Z"
fill=""
/>
</svg>
),
info: (
<svg
className="fill-current"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M3.6501 11.9996C3.6501 7.38803 7.38852 3.64961 12.0001 3.64961C16.6117 3.64961 20.3501 7.38803 20.3501 11.9996C20.3501 16.6112 16.6117 20.3496 12.0001 20.3496C7.38852 20.3496 3.6501 16.6112 3.6501 11.9996ZM12.0001 1.84961C6.39441 1.84961 1.8501 6.39392 1.8501 11.9996C1.8501 17.6053 6.39441 22.1496 12.0001 22.1496C17.6058 22.1496 22.1501 17.6053 22.1501 11.9996C22.1501 6.39392 17.6058 1.84961 12.0001 1.84961ZM10.9992 7.52468C10.9992 8.07697 11.4469 8.52468 11.9992 8.52468H12.0002C12.5525 8.52468 13.0002 8.07697 13.0002 7.52468C13.0002 6.9724 12.5525 6.52468 12.0002 6.52468H11.9992C11.4469 6.52468 10.9992 6.9724 10.9992 7.52468ZM12.0002 17.371C11.586 17.371 11.2502 17.0352 11.2502 16.621V10.9445C11.2502 10.5303 11.586 10.1945 12.0002 10.1945C12.4144 10.1945 12.7502 10.5303 12.7502 10.9445V16.621C12.7502 17.0352 12.4144 17.371 12.0002 17.371Z"
fill=""
/>
</svg>
),
};
return (
<div
className={`rounded-xl border p-4 ${variantClasses[variant].container}`}
>
<div className="flex items-start gap-3">
<div className={`-mt-0.5 ${variantClasses[variant].icon}`}>
{icons[variant]}
</div>
<div>
<h4 className="mb-1 text-sm font-semibold text-gray-800 dark:text-white/90">
{title}
</h4>
<p className="text-sm text-gray-500 dark:text-gray-400">{message}</p>
{showLink && (
<Link
href={linkHref}
className="inline-block mt-3 text-sm font-medium text-gray-500 underline dark:text-gray-400"
>
{linkText}
</Link>
)}
</div>
</div>
</div>
);
};
export default Alert;
-47
View File
@@ -1,47 +0,0 @@
import React from "react";
interface AvatarTextProps {
name: string;
className?: string;
}
const AvatarText: React.FC<AvatarTextProps> = ({ name, className = "" }) => {
// Generate initials from name
const initials = name
.split(" ")
.map((word) => word[0])
.join("")
.toUpperCase()
.slice(0, 2);
// Generate a consistent pastel color based on the name
const getColorClass = (name: string) => {
const colors = [
"bg-brand-100 text-brand-600",
"bg-pink-100 text-pink-600",
"bg-cyan-100 text-cyan-600",
"bg-orange-100 text-orange-600",
"bg-green-100 text-green-600",
"bg-purple-100 text-purple-600",
"bg-yellow-100 text-yellow-600",
"bg-error-100 text-error-600",
];
const index = name
.split("")
.reduce((acc, char) => acc + char.charCodeAt(0), 0);
return colors[index % colors.length];
};
return (
<div
className={`flex h-10 w-10 ${className} items-center justify-center rounded-full ${getColorClass(
name
)}`}
>
<span className="text-sm font-medium">{initials}</span>
</div>
);
};
export default AvatarText;
+118 -20
View File
@@ -2,16 +2,20 @@
import React, { useState, useRef, useEffect } from "react";
import { ChatbotPayload } from "@/interface/chatbot";
import { apiChatbot } from "@/service/chatbotService";
import { apiChatbot, apiChatbotHistory } from "@/service/chatbotService";
import { AxiosError } from "axios";
type Message = {
id: string;
sender: "user" | "bot";
text: string;
};
const cleanAnswer = (text: string) => {
if (!text) return "";
return text.replace(/<\/?answer>/gi, "").trim();
};
export default function ChatbotWidget({
projectId = "",
hideFloatingButton = false,
@@ -20,21 +24,22 @@ export default function ChatbotWidget({
hideFloatingButton?: boolean;
}) {
const [isOpen, setIsOpen] = useState(false);
const [messages, setMessages] = useState<Message[]>([
{
id: "init",
sender: "bot",
text: "Xin chào! Tôi là trợ lý lịch sử thân thiện. Tôi có thể giúp gì cho bạn?",
},
]);
const [messages, setMessages] = useState<Message[]>([]);
const [preCursor, setPreCursor] = useState<string | null>(null);
const [isFetchingHistory, setIsFetchingHistory] = useState(false);
const [hasLoadedHistory, setHasLoadedHistory] = useState(false);
const [input, setInput] = useState("");
const [isLoading, setIsLoading] = useState(false);
const messagesEndRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
const prevMessagesLength = useRef(0);
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
};
// Toggle chatbot visibility via window event
useEffect(() => {
const handleToggle = () => setIsOpen((prev) => !prev);
window.addEventListener("toggle-chatbot", handleToggle);
@@ -43,19 +48,83 @@ export default function ChatbotWidget({
};
}, []);
// Fetch history from API
const loadHistory = async (cursor?: string) => {
setIsFetchingHistory(true);
try {
const res = await apiChatbotHistory({ cursor, limit: 10 });
if (res && res.status && res.data) {
const historyItems = res.data.items || [];
const newMessages: Message[] = [];
// Parse questions and answers from history items
historyItems.forEach((item: any) => {
newMessages.push({
id: `${item.id}_q`,
sender: "user",
text: item.question,
});
newMessages.push({
id: `${item.id}_a`,
sender: "bot",
text: cleanAnswer(item.answer),
});
});
if (cursor) {
// Prepended older messages should go to the top of the conversation list
setMessages((prev) => [...newMessages, ...prev]);
} else {
setMessages(newMessages);
setHasLoadedHistory(true);
}
setPreCursor(res.data.pre_cursor || null);
}
} catch (error) {
console.error("Failed to load chat history:", error);
} finally {
setIsFetchingHistory(false);
}
};
// Load history when chatbot is first opened
useEffect(() => {
if (isOpen && !hasLoadedHistory) {
loadHistory();
}
}, [isOpen, hasLoadedHistory]);
// Scroll to bottom when first opened
useEffect(() => {
if (isOpen) {
scrollToBottom();
}
}, [messages, isOpen]);
}, [isOpen]);
// Scroll to bottom only when new live messages are added at the end (not when older history is prepended)
useEffect(() => {
if (messages.length > prevMessagesLength.current) {
scrollToBottom();
}
prevMessagesLength.current = messages.length;
}, [messages]);
// Auto focus input when opened or loading ends
useEffect(() => {
if (isOpen && !isLoading) {
inputRef.current?.focus();
}
}, [isOpen, isLoading]);
const handleSend = async () => {
if (!input.trim()) return;
if (!input.trim() || isLoading) return;
const userText = input.trim();
const userMessage: Message = {
id: Date.now().toString(),
sender: "user",
text: input.trim(),
text: userText,
};
setMessages((prev) => [...prev, userMessage]);
@@ -65,7 +134,7 @@ export default function ChatbotWidget({
try {
const payload: ChatbotPayload = {
project_id: projectId,
question: userMessage.text,
question: userText,
};
const res = await apiChatbot(payload);
@@ -74,7 +143,7 @@ export default function ChatbotWidget({
id: (Date.now() + 1).toString(),
sender: "bot",
text: res?.status
? res?.data
? cleanAnswer(res?.data)
: "Xin lỗi, tôi không thể trả lời lúc này.",
};
@@ -90,7 +159,6 @@ export default function ChatbotWidget({
};
setMessages((prev) => [...prev, errorMessage]);
} finally {
setIsLoading(false);
}
};
@@ -101,6 +169,14 @@ export default function ChatbotWidget({
}
};
const initMessage: Message = {
id: "init",
sender: "bot",
text: "Xin chào! Tôi là trợ lý lịch sử thân thiện. Tôi có thể giúp gì cho bạn?",
};
const allMessages = [initMessage, ...messages];
return (
<div className="fixed bottom-8 right-8 z-50">
{!isOpen && !hideFloatingButton && (
@@ -146,11 +222,11 @@ export default function ChatbotWidget({
d="M9.75 3.104v5.714a2.25 2.25 0 01-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 014.5 0m0 0v5.714c0 .597.237 1.17.659 1.591L19.8 15.3M14.25 3.104c.251.023.501.05.75.082M19.8 15.3l-1.57.393A9.065 9.065 0 0112 15a9.065 9.065 0 00-6.23-.693L5 14.5m14.8.8l1.402 1.402c1.232 1.232.65 3.318-1.067 3.611A48.309 48.309 0 0112 21c-2.773 0-5.491-.235-8.135-.687-1.718-.293-2.3-2.379-1.067-3.61L5 14.5"
/>
</svg>
Trợ lịch sử.
<span>Trợ lịch sử</span>
</div>
<button
onClick={() => setIsOpen(false)}
className="text-white hover:text-gray-200 transition-colors"
className="p-1.5 hover:bg-brand-600 rounded-lg transition-colors text-white"
>
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -171,18 +247,32 @@ export default function ChatbotWidget({
{/* Nội dung Chat */}
<div className="flex-1 p-4 overflow-y-auto flex flex-col gap-3 bg-gray-50 dark:bg-[#0d1117] text-sm">
{messages.map((msg) => (
{/* Load older messages button */}
{preCursor && (
<div className="flex justify-center mb-1">
<button
onClick={() => loadHistory(preCursor)}
disabled={isFetchingHistory}
className="text-xs text-brand-500 hover:text-brand-600 font-medium py-1 px-3 bg-brand-50 dark:bg-brand-950/20 rounded-full transition-colors disabled:opacity-50"
>
{isFetchingHistory ? "Đang tải..." : "Xem tin nhắn cũ hơn"}
</button>
</div>
)}
{allMessages.map((msg) => (
<div
key={msg.id}
className={`max-w-[85%] rounded-2xl px-4 py-2 shadow-sm ${
msg.sender === "user"
? "bg-brand-500 text-white self-end rounded-br-sm"
: "bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-200 border border-gray-100 dark:border-gray-700 self-start rounded-bl-sm"
? "bg-brand-500 text-white self-end rounded-br-sm animate-in fade-in duration-200 slide-in-from-bottom-1"
: "bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-300 border border-gray-100 dark:border-gray-700 self-start rounded-bl-sm animate-in fade-in duration-200 slide-in-from-bottom-1"
}`}
>
{msg.text}
</div>
))}
{isLoading && (
<div className="bg-white dark:bg-gray-800 border border-gray-100 dark:border-gray-700 self-start rounded-2xl rounded-bl-sm px-4 py-3 shadow-sm flex items-center gap-1.5 max-w-[80%]">
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce"></div>
@@ -196,6 +286,13 @@ export default function ChatbotWidget({
></div>
</div>
)}
{isFetchingHistory && !preCursor && (
<div className="flex justify-center py-4">
<div className="w-5 h-5 border-2 border-brand-500 border-t-transparent rounded-full animate-spin"></div>
</div>
)}
<div ref={messagesEndRef} />
</div>
@@ -203,6 +300,7 @@ export default function ChatbotWidget({
<div className="p-3 bg-white dark:bg-gray-900 border-t border-gray-200 dark:border-gray-800">
<div className="flex items-center gap-2">
<input
ref={inputRef}
type="text"
placeholder="Nhập câu hỏi..."
value={input}
@@ -1,18 +0,0 @@
import Image from "next/image";
import React from "react";
export default function ResponsiveImage() {
return (
<div className="relative">
<div className="overflow-hidden">
<Image
src="/images/grid-image/image-01.png"
alt="Cover"
className="w-full border border-gray-200 rounded-xl dark:border-gray-800"
width={1054}
height={600}
/>
</div>
</div>
);
}
@@ -1,38 +0,0 @@
import Image from "next/image";
import React from "react";
export default function ThreeColumnImageGrid() {
return (
<div className="grid grid-cols-1 gap-5 sm:grid-cols-2 xl:grid-cols-3">
<div>
<Image
src="/images/grid-image/image-04.png"
alt=" grid"
className="w-full border border-gray-200 rounded-xl dark:border-gray-800"
width={338}
height={192}
/>
</div>
<div>
<Image
src="/images/grid-image/image-05.png"
alt=" grid"
className="w-full border border-gray-200 rounded-xl dark:border-gray-800"
width={338}
height={192}
/>
</div>
<div>
<Image
src="/images/grid-image/image-06.png"
alt=" grid"
className="w-full border border-gray-200 rounded-xl dark:border-gray-800"
width={338}
height={192}
/>
</div>
</div>
);
}
@@ -1,28 +0,0 @@
import Image from "next/image";
import React from "react";
export default function TwoColumnImageGrid() {
return (
<div className="grid grid-cols-1 gap-5 sm:grid-cols-2">
<div>
<Image
src="/images/grid-image/image-02.png"
alt=" grid"
className="w-full border border-gray-200 rounded-xl dark:border-gray-800"
width={517}
height={295}
/>
</div>
<div>
<Image
src="/images/grid-image/image-03.png"
alt=" grid"
className="w-full border border-gray-200 rounded-xl dark:border-gray-800"
width={517}
height={295}
/>
</div>
</div>
);
}
-28
View File
@@ -1,28 +0,0 @@
import React from "react";
import YouTubeEmbed from "./YouTubeEmbed";
import ComponentCard from "@/components/common/ComponentCard";
export default function VideosExample() {
return (
<div>
<div className="grid grid-cols-1 gap-5 sm:gap-6 xl:grid-cols-2">
<div className="space-y-5 sm:space-y-6">
<ComponentCard title="Video Ratio 16:9">
<YouTubeEmbed videoId="dQw4w9WgXcQ" />
</ComponentCard>
<ComponentCard title="Video Ratio 4:3">
<YouTubeEmbed videoId="dQw4w9WgXcQ" aspectRatio="4:3" />
</ComponentCard>
</div>
<div className="space-y-5 sm:space-y-6">
<ComponentCard title="Video Ratio 21:9">
<YouTubeEmbed videoId="dQw4w9WgXcQ" aspectRatio="21:9" />
</ComponentCard>
<ComponentCard title="Video Ratio 1:1">
<YouTubeEmbed videoId="dQw4w9WgXcQ" aspectRatio="1:1" />
</ComponentCard>
</div>
</div>
</div>
);
}
-41
View File
@@ -1,41 +0,0 @@
import React from "react";
type AspectRatio = "16:9" | "4:3" | "21:9" | "1:1";
interface YouTubeEmbedProps {
videoId: string;
aspectRatio?: AspectRatio;
title?: string;
className?: string;
}
const YouTubeEmbed: React.FC<YouTubeEmbedProps> = ({
videoId,
aspectRatio = "16:9",
title = "YouTube video",
className = "",
}) => {
const aspectRatioClass = {
"16:9": "aspect-video",
"4:3": "aspect-4/3",
"21:9": "aspect-21/9",
"1:1": "aspect-square",
}[aspectRatio];
return (
<div
className={`overflow-hidden rounded-lg ${aspectRatioClass} ${className}`}
>
<iframe
src={`https://www.youtube.com/embed/${videoId}`}
title={title}
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
className="w-full h-full"
></iframe>
</div>
);
};
export default YouTubeEmbed;
-16
View File
@@ -1,16 +0,0 @@
import React from "react";
export default function FourIsToThree() {
return (
<div className="aspect-4/3 overflow-hidden rounded-lg">
<iframe
src="https://www.youtube.com/embed/dQw4w9WgXcQ"
title="YouTube video"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
className="w-full h-full"
></iframe>
</div>
);
}
-16
View File
@@ -1,16 +0,0 @@
import React from "react";
export default function OneIsToOne() {
return (
<div className="overflow-hidden rounded-lg aspect-square">
<iframe
src="https://www.youtube.com/embed/dQw4w9WgXcQ"
title="YouTube video"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
className="w-full h-full"
></iframe>
</div>
);
}
-16
View File
@@ -1,16 +0,0 @@
import React from "react";
export default function SixteenIsToNine() {
return (
<div className="aspect-4/3 overflow-hidden rounded-lg">
<iframe
src="https://www.youtube.com/embed/dQw4w9WgXcQ"
title="YouTube video"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
className="w-full h-full"
></iframe>
</div>
);
}

Some files were not shown because too many files have changed in this diff Show More