diff --git a/frontend/package.json b/frontend/package.json index d4116de..451dbea 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,28 +10,34 @@ "preview": "vite preview" }, "dependencies": { + "@material-tailwind/react": "2.1.10", "axios": "1.7.9", "react": "18.3.1", - "react-dom": "18.3.1" + "react-dom": "18.3.1", + "react-icon": "1.0.0", + "react-icons": "^5.4.0", + "react-redux": "^9.2.0", + "react-router-dom": "^7.1.0", + "slick-carousel": "^1.8.1" }, "devDependencies": { "@eslint/js": "9.17.0", "@types/react": "18.3.17", "@types/react-dom": "18.3.5", "@vitejs/plugin-react-swc": "3.5.0", + "autoprefixer": "^10.4.20", "eslint": "9.17.0", "eslint-plugin-react-hooks": "5.0.0", "eslint-plugin-react-refresh": "0.4.16", "globals": "15.13.0", + "postcss": "8.4.49", + "redux": "5.0.1", + "redux-persist": "6.0.0", + "redux-thunk": "3.1.0", + "tailwindcss": "3.4.17", "typescript": "5.6.2", "typescript-eslint": "8.18.1", "vite": "6.0.3", - "vite-tsconfig-paths": "5.1.4", - "postcss": "8.4.49", - "redux": "5.0.1", - "redux-persist": "6.0.0", - "redux-thunk": "3.1.0", - "tailwindcss": "3.4.17", - "autoprefixer": "^10.4.20" + "vite-tsconfig-paths": "5.1.4" } } diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 3dde870..220f773 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@material-tailwind/react': + specifier: 2.1.10 + version: 2.1.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1) axios: specifier: 1.7.9 version: 1.7.9 @@ -17,6 +20,21 @@ importers: react-dom: specifier: 18.3.1 version: 18.3.1(react@18.3.1) + react-icon: + specifier: 1.0.0 + version: 1.0.0(babel-runtime@5.8.38)(react@18.3.1) + react-icons: + specifier: ^5.4.0 + version: 5.4.0(react@18.3.1) + react-redux: + specifier: ^9.2.0 + version: 9.2.0(@types/react@18.3.17)(react@18.3.1)(redux@5.0.1) + react-router-dom: + specifier: ^7.1.0 + version: 7.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + slick-carousel: + specifier: ^1.8.1 + version: 1.8.1(jquery@3.7.1) devDependencies: '@eslint/js': specifier: 9.17.0 @@ -79,6 +97,12 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} + '@emotion/is-prop-valid@0.8.8': + resolution: {integrity: sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==} + + '@emotion/memoize@0.7.4': + resolution: {integrity: sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==} + '@esbuild/aix-ppc64@0.24.0': resolution: {integrity: sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==} engines: {node: '>=18'} @@ -257,6 +281,27 @@ packages: resolution: {integrity: sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@floating-ui/core@1.6.8': + resolution: {integrity: sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==} + + '@floating-ui/dom@1.6.12': + resolution: {integrity: sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==} + + '@floating-ui/react-dom@1.3.0': + resolution: {integrity: sha512-htwHm67Ji5E/pROEAr7f8IKFShuiCKHwUC/UY4vC3I5jiSvGFAYnSYiZO5MlGmads+QqvUkR9ANHEguGrDv72g==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/react@0.19.0': + resolution: {integrity: sha512-fgYvN4ksCi5OvmPXkyOT8o5a8PSKHMzPHt+9mR6KYWdF16IAjWRLZPAAziI2sznaWT23drRFrYw64wdvYqqaQw==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.8': + resolution: {integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==} + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -299,6 +344,30 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@material-tailwind/react@2.1.10': + resolution: {integrity: sha512-xGU/mLDKDBp/qZ8Dp2XR7fKcTpDuFeZEBqoL9Bk/29kakKxNxjUGYSRHEFLsyOFf4VIhU6WGHdIS7tOA3QGJHA==} + peerDependencies: + react: ^16 || ^17 || ^18 + react-dom: ^16 || ^17 || ^18 + + '@motionone/animation@10.18.0': + resolution: {integrity: sha512-9z2p5GFGCm0gBsZbi8rVMOAJCtw1WqBTIPw3ozk06gDvZInBPIsQcHgYogEJ4yuHJ+akuW8g1SEIOpTOvYs8hw==} + + '@motionone/dom@10.12.0': + resolution: {integrity: sha512-UdPTtLMAktHiqV0atOczNYyDd/d8Cf5fFsd1tua03PqTwwCe/6lwhLSQ8a7TbnQ5SN0gm44N1slBfj+ORIhrqw==} + + '@motionone/easing@10.18.0': + resolution: {integrity: sha512-VcjByo7XpdLS4o9T8t99JtgxkdMcNWD3yHU/n6CLEz3bkmKDRZyYQ/wmSf6daum8ZXqfUAgFeCZSpJZIMxaCzg==} + + '@motionone/generators@10.18.0': + resolution: {integrity: sha512-+qfkC2DtkDj4tHPu+AFKVfR/C30O1vYdvsGYaR13W/1cczPrrcjdvYCj0VLFuRMN+lP1xvpNZHCRNM4fBzn1jg==} + + '@motionone/types@10.17.1': + resolution: {integrity: sha512-KaC4kgiODDz8hswCrS0btrVrzyU2CSQKO7Ps90ibBVSQmjkrt2teqta6/sOG59v7+dPnKMAg13jyqtMKV2yJ7A==} + + '@motionone/utils@10.18.0': + resolution: {integrity: sha512-3XVF7sgyTSI2KWvTf6uLlBJ5iAgRgmvp3bpuOiQJvInd4nZ19ET8lX5unn30SlmRH7hXbBbH+Gxd0m0klJ3Xtw==} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -485,6 +554,9 @@ packages: '@swc/types@0.1.17': resolution: {integrity: sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==} + '@types/cookie@0.6.0': + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} @@ -502,6 +574,9 @@ packages: '@types/react@18.3.17': resolution: {integrity: sha512-opAQ5no6LqJNo9TqnxBKsgnkIYHozW9KSTlFVoSUJYh1Fl/sswkEoqIugRSm7tbh6pABtYjGAjW+GOS23j8qbw==} + '@types/use-sync-external-store@0.0.6': + resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + '@typescript-eslint/eslint-plugin@8.18.1': resolution: {integrity: sha512-Ncvsq5CT3Gvh+uJG0Lwlho6suwDfUXH0HztslDf5I+F2wAFAZMRwYLEorumpKLzmO2suAXZ/td1tBg4NZIi9CQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -596,6 +671,10 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-hidden@1.2.4: + resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==} + engines: {node: '>=10'} + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -609,6 +688,9 @@ packages: axios@1.7.9: resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==} + babel-runtime@5.8.38: + resolution: {integrity: sha512-KpgoA8VE/pMmNCrnEeeXqFG24TIH11Z3ZaimIhJWsin8EbfZy3WzFKUTIan10ZIDgRVvi9EkLbruJElJC9dRlg==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -650,6 +732,9 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + classnames@2.3.2: + resolution: {integrity: sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -668,6 +753,14 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + cookie@1.0.2: + resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} + engines: {node: '>=18'} + + core-js@1.2.7: + resolution: {integrity: sha512-ZiPp9pZlgxpWRu0M+YWbm6+aQ84XEfH1JRXvfOc/fILWI0VKhLC2LX13X1NYq4fULzLMq7Hfh43CSo2/aIaUPA==} + deprecated: core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js. + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -692,6 +785,10 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + deepmerge@4.2.2: + resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==} + engines: {node: '>=0.10.0'} + delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -835,6 +932,15 @@ packages: fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + framer-motion@6.5.1: + resolution: {integrity: sha512-o1BGqqposwi7cgDrtg0dNONhkmPsUFDaLcKXigzuTFC5x58mE8iyTazxSudFzmT6MEyJKfjjU8ItoMe3W+3fiw==} + peerDependencies: + react: '>=16.8 || ^17.0.0 || ^18.0.0' + react-dom: '>=16.8 || ^17.0.0 || ^18.0.0' + + framesync@6.0.1: + resolution: {integrity: sha512-fUY88kXvGiIItgNC7wcTOl0SNRCVXMKSWW2Yzfmn7EKNc+MpCzcz9DhdHcdjbrtN3c6R4H5dTY2jiCpPdysEjA==} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -877,6 +983,9 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hey-listen@1.0.8: + resolution: {integrity: sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -923,6 +1032,9 @@ packages: resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true + jquery@3.7.1: + resolution: {integrity: sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -967,6 +1079,9 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + material-ripple-effects@2.0.1: + resolution: {integrity: sha512-hHlUkZAuXbP94lu02VgrPidbZ3hBtgXBtjlwR8APNqOIgDZMV8MCIcsclL8FmGJQHvnORyvoQgC965vPsiyXLQ==} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -1076,6 +1191,9 @@ packages: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} + popmotion@11.0.3: + resolution: {integrity: sha512-Y55FLdj3UxkR7Vl3s7Qr4e9m0onSnP8W7d/xQLsoJM40vs6UKHFdygs6SWryasTZYqugMjm3BepCF4CWXDiHgA==} + postcss-import@15.1.0: resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} engines: {node: '>=14.0.0'} @@ -1121,6 +1239,9 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} @@ -1136,6 +1257,49 @@ packages: peerDependencies: react: ^18.3.1 + react-icon@1.0.0: + resolution: {integrity: sha512-VzSlpBHnLanVw79mOxyq98hWDi6DlxK9qPiZ1bAK6bLurMBCaxO/jjyYUrRx9+JGLc/NbnwOmyE/W5Qglbb2QA==} + peerDependencies: + babel-runtime: ^5.3.3 + react: '>=0.12.0' + + react-icons@5.4.0: + resolution: {integrity: sha512-7eltJxgVt7X64oHh6wSWNwwbKTCtMfK35hcjvJS0yxEAhPM8oUKdS3+kqaW1vicIltw+kR2unHaa12S9pPALoQ==} + peerDependencies: + react: '*' + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-redux@9.2.0: + resolution: {integrity: sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==} + peerDependencies: + '@types/react': ^18.2.25 || ^19 + react: ^18.0 || ^19 + redux: ^5.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + redux: + optional: true + + react-router-dom@7.1.0: + resolution: {integrity: sha512-F4/nYBC9e4s0/ZjxM8GkZ9a68DpX76LN1a9W9mfPl2GfbDJ9/vzJro6MThNR5qGBH6KkgcK1BziyEzXhHV46Xw==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + react-router@7.1.0: + resolution: {integrity: sha512-VcFhWqkNIcojDRYaUO8qV0Jib52s9ULpCp3nkBbmrvtoCVFRp6tmk3tJ2w9BZauVctA1YRnJlFYDn9iJRuCpGA==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + react@18.3.1: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} @@ -1193,6 +1357,9 @@ packages: engines: {node: '>=10'} hasBin: true + set-cookie-parser@2.7.1: + resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -1205,6 +1372,11 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + slick-carousel@1.8.1: + resolution: {integrity: sha512-XB9Ftrf2EEKfzoQXt3Nitrt/IPbT+f1fgqBdoxO3W/+JYvtEOW6EgxnWfr9GH6nmULv7Y2tPmEX3koxThVmebA==} + peerDependencies: + jquery: '>=1.8.0' + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -1229,6 +1401,9 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + style-value-types@5.0.0: + resolution: {integrity: sha512-08yq36Ikn4kx4YU6RD7jWEv27v4V+PUsOGa4n/as8Et3CuODMJQ00ENeAVXAeydX4Z2j1XHZF1K2sX4mGl18fA==} + sucrase@3.35.0: resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} engines: {node: '>=16 || 14 >=14.17'} @@ -1242,6 +1417,12 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + tabbable@6.2.0: + resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} + + tailwind-merge@1.8.1: + resolution: {integrity: sha512-+fflfPxvHFr81hTJpQ3MIwtqgvefHZFUHFiIHpVIRXvG/nX9+gu2P7JNlFu2bfDMJ+uHhi/pUgzaYacMoXv+Ww==} + tailwindcss@3.4.17: resolution: {integrity: sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==} engines: {node: '>=14.0.0'} @@ -1277,6 +1458,12 @@ packages: typescript: optional: true + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + turbo-stream@2.4.0: + resolution: {integrity: sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -1302,6 +1489,11 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + use-sync-external-store@1.4.0: + resolution: {integrity: sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -1383,6 +1575,14 @@ snapshots: '@alloc/quick-lru@5.2.0': {} + '@emotion/is-prop-valid@0.8.8': + dependencies: + '@emotion/memoize': 0.7.4 + optional: true + + '@emotion/memoize@0.7.4': + optional: true + '@esbuild/aix-ppc64@0.24.0': optional: true @@ -1496,6 +1696,31 @@ snapshots: dependencies: levn: 0.4.1 + '@floating-ui/core@1.6.8': + dependencies: + '@floating-ui/utils': 0.2.8 + + '@floating-ui/dom@1.6.12': + dependencies: + '@floating-ui/core': 1.6.8 + '@floating-ui/utils': 0.2.8 + + '@floating-ui/react-dom@1.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/dom': 1.6.12 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@floating-ui/react@0.19.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/react-dom': 1.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + aria-hidden: 1.2.4 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + tabbable: 6.2.0 + + '@floating-ui/utils@0.2.8': {} + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.6': @@ -1535,6 +1760,53 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@material-tailwind/react@2.1.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/react': 0.19.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.3.2 + deepmerge: 4.2.2 + framer-motion: 6.5.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + material-ripple-effects: 2.0.1 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + tailwind-merge: 1.8.1 + + '@motionone/animation@10.18.0': + dependencies: + '@motionone/easing': 10.18.0 + '@motionone/types': 10.17.1 + '@motionone/utils': 10.18.0 + tslib: 2.8.1 + + '@motionone/dom@10.12.0': + dependencies: + '@motionone/animation': 10.18.0 + '@motionone/generators': 10.18.0 + '@motionone/types': 10.17.1 + '@motionone/utils': 10.18.0 + hey-listen: 1.0.8 + tslib: 2.8.1 + + '@motionone/easing@10.18.0': + dependencies: + '@motionone/utils': 10.18.0 + tslib: 2.8.1 + + '@motionone/generators@10.18.0': + dependencies: + '@motionone/types': 10.17.1 + '@motionone/utils': 10.18.0 + tslib: 2.8.1 + + '@motionone/types@10.17.1': {} + + '@motionone/utils@10.18.0': + dependencies: + '@motionone/types': 10.17.1 + hey-listen: 1.0.8 + tslib: 2.8.1 + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -1659,6 +1931,8 @@ snapshots: dependencies: '@swc/counter': 0.1.3 + '@types/cookie@0.6.0': {} + '@types/estree@1.0.6': {} '@types/json-schema@7.0.15': {} @@ -1674,6 +1948,8 @@ snapshots: '@types/prop-types': 15.7.14 csstype: 3.1.3 + '@types/use-sync-external-store@0.0.6': {} + '@typescript-eslint/eslint-plugin@8.18.1(@typescript-eslint/parser@8.18.1(eslint@9.17.0(jiti@1.21.7))(typescript@5.6.2))(eslint@9.17.0(jiti@1.21.7))(typescript@5.6.2)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -1792,6 +2068,10 @@ snapshots: argparse@2.0.1: {} + aria-hidden@1.2.4: + dependencies: + tslib: 2.8.1 + asynckit@0.4.0: {} autoprefixer@10.4.20(postcss@8.4.49): @@ -1812,6 +2092,10 @@ snapshots: transitivePeerDependencies: - debug + babel-runtime@5.8.38: + dependencies: + core-js: 1.2.7 + balanced-match@1.0.2: {} binary-extensions@2.3.0: {} @@ -1859,6 +2143,8 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + classnames@2.3.2: {} + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -1873,6 +2159,10 @@ snapshots: concat-map@0.0.1: {} + cookie@1.0.2: {} + + core-js@1.2.7: {} + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -1889,6 +2179,8 @@ snapshots: deep-is@0.1.4: {} + deepmerge@4.2.2: {} + delayed-stream@1.0.0: {} didyoumean@1.2.2: {} @@ -2063,6 +2355,23 @@ snapshots: fraction.js@4.3.7: {} + framer-motion@6.5.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@motionone/dom': 10.12.0 + framesync: 6.0.1 + hey-listen: 1.0.8 + popmotion: 11.0.3 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + style-value-types: 5.0.0 + tslib: 2.8.1 + optionalDependencies: + '@emotion/is-prop-valid': 0.8.8 + + framesync@6.0.1: + dependencies: + tslib: 2.8.1 + fsevents@2.3.3: optional: true @@ -2099,6 +2408,8 @@ snapshots: dependencies: function-bind: 1.1.2 + hey-listen@1.0.8: {} + ignore@5.3.2: {} import-fresh@3.3.0: @@ -2136,6 +2447,8 @@ snapshots: jiti@1.21.7: {} + jquery@3.7.1: {} + js-tokens@4.0.0: {} js-yaml@4.1.0: @@ -2173,6 +2486,8 @@ snapshots: lru-cache@10.4.3: {} + material-ripple-effects@2.0.1: {} + merge2@1.4.1: {} micromatch@4.0.8: @@ -2260,6 +2575,13 @@ snapshots: pirates@4.0.6: {} + popmotion@11.0.3: + dependencies: + framesync: 6.0.1 + hey-listen: 1.0.8 + style-value-types: 5.0.0 + tslib: 2.8.1 + postcss-import@15.1.0(postcss@8.4.49): dependencies: postcss: 8.4.49 @@ -2299,6 +2621,12 @@ snapshots: prelude-ls@1.2.1: {} + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + proxy-from-env@1.1.0: {} punycode@2.3.1: {} @@ -2311,6 +2639,42 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 + react-icon@1.0.0(babel-runtime@5.8.38)(react@18.3.1): + dependencies: + babel-runtime: 5.8.38 + react: 18.3.1 + + react-icons@5.4.0(react@18.3.1): + dependencies: + react: 18.3.1 + + react-is@16.13.1: {} + + react-redux@9.2.0(@types/react@18.3.17)(react@18.3.1)(redux@5.0.1): + dependencies: + '@types/use-sync-external-store': 0.0.6 + react: 18.3.1 + use-sync-external-store: 1.4.0(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.17 + redux: 5.0.1 + + react-router-dom@7.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-router: 7.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + + react-router@7.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@types/cookie': 0.6.0 + cookie: 1.0.2 + react: 18.3.1 + set-cookie-parser: 2.7.1 + turbo-stream: 2.4.0 + optionalDependencies: + react-dom: 18.3.1(react@18.3.1) + react@18.3.1: dependencies: loose-envify: 1.4.0 @@ -2380,6 +2744,8 @@ snapshots: semver@7.6.3: {} + set-cookie-parser@2.7.1: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -2388,6 +2754,10 @@ snapshots: signal-exit@4.1.0: {} + slick-carousel@1.8.1(jquery@3.7.1): + dependencies: + jquery: 3.7.1 + source-map-js@1.2.1: {} string-width@4.2.3: @@ -2412,6 +2782,11 @@ snapshots: strip-json-comments@3.1.1: {} + style-value-types@5.0.0: + dependencies: + hey-listen: 1.0.8 + tslib: 2.8.1 + sucrase@3.35.0: dependencies: '@jridgewell/gen-mapping': 0.3.8 @@ -2428,6 +2803,10 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + tabbable@6.2.0: {} + + tailwind-merge@1.8.1: {} + tailwindcss@3.4.17: dependencies: '@alloc/quick-lru': 5.2.0 @@ -2477,6 +2856,10 @@ snapshots: optionalDependencies: typescript: 5.6.2 + tslib@2.8.1: {} + + turbo-stream@2.4.0: {} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -2503,6 +2886,10 @@ snapshots: dependencies: punycode: 2.3.1 + use-sync-external-store@1.4.0(react@18.3.1): + dependencies: + react: 18.3.1 + util-deprecate@1.0.2: {} vite-tsconfig-paths@5.1.4(typescript@5.6.2)(vite@6.0.3(jiti@1.21.7)(yaml@2.6.1)): diff --git a/frontend/postcss.config.cjs b/frontend/postcss.config.cjs new file mode 100644 index 0000000..33ad091 --- /dev/null +++ b/frontend/postcss.config.cjs @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/frontend/public/index.html b/frontend/public/index.html new file mode 100644 index 0000000..f890304 --- /dev/null +++ b/frontend/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + Evora + + + +
+ + + diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 7b99097..5e44e0b 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,9 +1,26 @@ import React from 'react'; +import { Route, Routes } from 'react-router-dom'; + +import { + Home, + HomePage, + LoginAdmin, + LoginUser, + RegisterUser, +} from '@/containers/public'; +import { path } from '@/ultils/constant'; const App: React.FC = () => { return ( -
-

Welcome to Evora!

+
+ + }> + } /> + + } /> + } /> + } /> +
); }; diff --git a/frontend/src/assets/.gitkeep b/frontend/src/assets/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/src/assets/bg.jpg b/frontend/src/assets/bg.jpg new file mode 100644 index 0000000..8167891 Binary files /dev/null and b/frontend/src/assets/bg.jpg differ diff --git a/frontend/src/assets/hero.jpg b/frontend/src/assets/hero.jpg new file mode 100644 index 0000000..7c00288 Binary files /dev/null and b/frontend/src/assets/hero.jpg differ diff --git a/frontend/src/assets/logo.jpg b/frontend/src/assets/logo.jpg new file mode 100644 index 0000000..db4acc1 Binary files /dev/null and b/frontend/src/assets/logo.jpg differ diff --git a/frontend/src/assets/logo1.png b/frontend/src/assets/logo1.png new file mode 100644 index 0000000..d14c2c2 Binary files /dev/null and b/frontend/src/assets/logo1.png differ diff --git a/frontend/src/assets/review-UI.txt b/frontend/src/assets/review-UI.txt new file mode 100644 index 0000000..98a1340 --- /dev/null +++ b/frontend/src/assets/review-UI.txt @@ -0,0 +1,11 @@ +MVP: https://evora-17c.onrender.com +Feedback UI: Nên follow vào MVP để điều chỉnh +- Web dùng full tiếng Việt +- Login cho User: + + Form nhỏ lại tí hoặc thêm content j đó vào phần bên phải chứ trống quá + + Chữ trong button Login hơi nhỏ +- Login cho Admin: thêm phần Quên mật khẩu +- Homepage: + + Tích hợp phần header phía trên vào thanh nav bar cho gọn + + Category trên nav bar chuyển xuống dưới thay bằng các option khác follow MVP như: Sự kiện, Đơn vị tổ chức, Giới thiệu,... + + Copy banner MVP thêm vào ngay bên dưới nav bar diff --git a/frontend/src/components/.gitkeep b/frontend/src/components/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/src/components/Banner.tsx b/frontend/src/components/Banner.tsx new file mode 100644 index 0000000..90d6957 --- /dev/null +++ b/frontend/src/components/Banner.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +// Đảm bảo đường dẫn hình ảnh đúng +import { NavLink } from 'react-router-dom'; + +import hero from '@/assets/hero.jpg'; +import { path } from '@/ultils/constant'; + +const Banner: React.FC = () => { + return ( +
+ Hero Image +
+ {' '} + {/* Đảm bảo chiều cao của container */} +
+ {' '} + {/* Căn giữa theo chiều dọc */} +
+

+ nơi gắn kết sự kiện và trải nghiệm của bạn! +

+

EVORA

+

+ ĐẶT SỰ KIỆN TRỰC TUYẾN +

+
+
+
+ Xem thêm +
+
+
+
+
+
+
+ ); +}; + +export default Banner; diff --git a/frontend/src/components/Button.tsx b/frontend/src/components/Button.tsx new file mode 100644 index 0000000..c51bcf1 --- /dev/null +++ b/frontend/src/components/Button.tsx @@ -0,0 +1,37 @@ +import React, { FC, memo } from 'react'; + +interface ButtonProps { + text: string; + textColor?: string; + bgColor?: string; + IcAfter?: React.ElementType; + IcBefor?: React.ElementType; + onClick?: () => void; + fullWidth?: boolean; +} + +export const Button: FC = ({ + text, + textColor = 'text-black', + bgColor = 'bg-white', + IcAfter, + onClick, + fullWidth = false, + IcBefor, +}) => { + return ( + + ); +}; + +export default memo(Button); diff --git a/frontend/src/components/ButtonForLogin.tsx b/frontend/src/components/ButtonForLogin.tsx new file mode 100644 index 0000000..67bf6cd --- /dev/null +++ b/frontend/src/components/ButtonForLogin.tsx @@ -0,0 +1,22 @@ +interface ButtonProps { + label: string; + type?: 'button' | 'submit' | 'reset'; + onClick?: () => void; +} + +const ButtonForLogin: React.FC = ({ + label, + type = 'button', + onClick, +}) => { + return ( + + ); +}; +export default ButtonForLogin; diff --git a/frontend/src/components/Contact.tsx b/frontend/src/components/Contact.tsx new file mode 100644 index 0000000..866aca8 --- /dev/null +++ b/frontend/src/components/Contact.tsx @@ -0,0 +1,43 @@ +import { text } from '../ultils/dataContact'; +import React from 'react'; + +interface Contact { + text: string; + phone: string; + zalo: string; +} + +const Contacts: React.FC = () => { + return ( +
+ thumbnail +

{text.content}

+
+ {text.contact.map((item: Contact) => { + return ( +
+ + {item.text} + + + {item.phone} + + + {item.zalo} + +
+ ); + })} +
+
+ ); +}; + +export default Contacts; diff --git a/frontend/src/components/InputField.tsx b/frontend/src/components/InputField.tsx new file mode 100644 index 0000000..030e1c5 --- /dev/null +++ b/frontend/src/components/InputField.tsx @@ -0,0 +1,25 @@ +interface InputFieldProps { + id: string; + label: string; + type: string; +} + +const InputField: React.FC = ({ id, label, type }) => { + return ( +
+ + +
+ ); +}; + +export default InputField; diff --git a/frontend/src/components/Intro.tsx b/frontend/src/components/Intro.tsx new file mode 100644 index 0000000..29b869e --- /dev/null +++ b/frontend/src/components/Intro.tsx @@ -0,0 +1,54 @@ +import { text } from '../ultils/dataIntro'; +import icons from '../ultils/icons'; +import React, { memo } from 'react'; + +const { FaStar } = icons; +const stars = [1, 2, 3, 4, 5]; + +interface Statistic { + value: string; + name: string; +} + +const Intro: React.FC = () => { + return ( +
+

{text.title}

+

+ {text.description} + + {text.description2} +

+
+ {text.statistic.map((item: Statistic) => { + return ( +
+

{item.value}

+

{item.name}

+
+ ); + })} +
+

{text.price}

+
+ {stars.map((item) => { + return ( + + + + ); + })} +
+

{text.comment}

+ {text.author} +

{text.question}

+

{text.answer}

+
+
+ ); +}; + +export default memo(Intro); diff --git a/frontend/src/components/Item.tsx b/frontend/src/components/Item.tsx new file mode 100644 index 0000000..fb3839e --- /dev/null +++ b/frontend/src/components/Item.tsx @@ -0,0 +1,59 @@ +import React from 'react'; + +const Item: React.FC = () => { + return ( +
+
+ House +
+
+
+
+

+ Wooden House, Florida +

+
+ + + + 5.0 +
+
+

+ Enter a freshly updated and thoughtfully furnished peaceful home + surrounded by ancient trees, stone walls, and open meadows. +

+
+
+ 💲 + $129 per night +
+
+ 📶 + Free wifi +
+
+
+
+ +
+
+ ); +}; + +export default Item; diff --git a/frontend/src/components/List.tsx b/frontend/src/components/List.tsx new file mode 100644 index 0000000..f9fa1bf --- /dev/null +++ b/frontend/src/components/List.tsx @@ -0,0 +1,27 @@ +import React from 'react'; + +import { Item } from '@/components'; + +interface ListProps { + categoriesCode?: string; +} + +const List: React.FC = () => { + return ( +
+
+

Danh sách tin đăng

+ Cập nhật: 12:05 24/12/2024 +
+
+
+ + + + +
+
+ ); +}; + +export default List; diff --git a/frontend/src/components/TopBar.tsx b/frontend/src/components/TopBar.tsx new file mode 100644 index 0000000..c5dad9a --- /dev/null +++ b/frontend/src/components/TopBar.tsx @@ -0,0 +1,50 @@ +import React from 'react'; + +import icons from '@/ultils/icons'; + +const { FaBirthdayCake, BiPhoneCall, BiEnvelope } = icons; + +const TopBar: React.FC = () => { + return ( +
+
+ {/* Email Section */} +
+
+ +
+
+ Email Us +
+ evora.17c@gmail.com +
+
+
+ +
+
+
+ +

Evora

+
+
+
+ + {/* Contact Section */} +
+
+ +
+
+ Liên hệ +
+ +012 345 6789 +
+
+
+
+
+ ); +}; + +export default TopBar; diff --git a/frontend/src/components/index.ts b/frontend/src/components/index.ts new file mode 100644 index 0000000..0f32271 --- /dev/null +++ b/frontend/src/components/index.ts @@ -0,0 +1,9 @@ +export { default as Button } from './Button'; +export { default as Intro } from './Intro'; +export { default as Contact } from './Contact'; +export { default as List } from './List'; +export { default as Item } from './Item'; +export { default as TopBar } from './TopBar'; +export { default as Banner } from './Banner'; +export { default as InputField } from './InputField'; +export { default as ButtonForLogin } from './ButtonForLogin'; diff --git a/frontend/src/containers/public/.gitkeep b/frontend/src/containers/public/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/src/containers/public/Footer.tsx b/frontend/src/containers/public/Footer.tsx new file mode 100644 index 0000000..4177143 --- /dev/null +++ b/frontend/src/containers/public/Footer.tsx @@ -0,0 +1,123 @@ +import React from 'react'; + +import bg from '@/assets/bg.jpg'; +// Đảm bảo đường dẫn chính xác +import icons from '@/ultils/icons'; + +const { + FaBirthdayCake, + CiLocationOn, + IoMailOpenOutline, + CiPhone, + FaArrowRightLong, +} = icons; + +const Footer: React.FC = () => { + return ( +
+ Background Footer +
+
+ {/* Phần Evora */} +
+
+
+ +

+ Evora +

+
+
+
+ + {/* Phần liên kết */} +
+
+

+ Liên hệ ngay +

+
    +
  • + + Khu đô thị mới, Quy Nhơn +
  • +
  • + + evora.17c@gmail.com +
  • +
  • + + +012 345 67890 +
  • +
+
+
+

+ Liên kết nhanh +

+
    +
  • + + Trang chủ +
  • +
  • + + Danh sách dịch vụ +
  • +
  • + + Đơn vị tổ chức +
  • +
  • + + Giới thiệu +
  • +
  • + + Liên hệ +
  • +
+
+
+

+ Nhận thông báo +

+
    +
  • Vui lòng gửi thông tin email của
  • +
  • bạn để nhận thông báo về
  • +
  • các ưu đãi mới nhất!
  • +
  • +
    + + +
    +
  • +
+
+
+
+ {/* Footer bottom */} +
+
+
+ + © 2023 Evora. Bản quyền. Được phát triển bởi nhóm Evora + +
+
+
+
+ ); +}; + +export default Footer; diff --git a/frontend/src/containers/public/Header.tsx b/frontend/src/containers/public/Header.tsx new file mode 100644 index 0000000..07416ae --- /dev/null +++ b/frontend/src/containers/public/Header.tsx @@ -0,0 +1,53 @@ +import React, { useState } from 'react'; +import { Link } from 'react-router-dom'; + +import logo1 from '@/assets/logo1.png'; +import { Button } from '@/components'; +import icons from '@/ultils/icons'; + +const { CiLogin, RiAdminLine, RiUserLine } = icons; + +const Header: React.FC = () => { + const [isShowMenu, setIsShowMenu] = useState(false); + + return ( +
+ logo +
+
+
+ ); +}; + +export default Header; diff --git a/frontend/src/containers/public/Home.tsx b/frontend/src/containers/public/Home.tsx new file mode 100644 index 0000000..0ba80c6 --- /dev/null +++ b/frontend/src/containers/public/Home.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { Outlet } from 'react-router-dom'; + +import { Banner, Contact, Intro, TopBar } from '@/components'; +import { Footer, Navigation } from '@/containers/public'; + +const Home: React.FC = () => { + return ( +
+ + + +
+
+ +
+ + +
+
+
+
+ ); +}; + +export default Home; diff --git a/frontend/src/containers/public/HomePage.tsx b/frontend/src/containers/public/HomePage.tsx new file mode 100644 index 0000000..05ae9ec --- /dev/null +++ b/frontend/src/containers/public/HomePage.tsx @@ -0,0 +1,25 @@ +import React from 'react'; + +import { List } from '@/components'; + +const HomePage: React.FC = () => { + return ( +
+
+

+ Danh sách sự kiện nổi bật +

+

+ KHÁM PHÁ NGAY +

+
+
+
+ +
+
+
+ ); +}; + +export default HomePage; diff --git a/frontend/src/containers/public/LoginAdmin.tsx b/frontend/src/containers/public/LoginAdmin.tsx new file mode 100644 index 0000000..5c729a2 --- /dev/null +++ b/frontend/src/containers/public/LoginAdmin.tsx @@ -0,0 +1,62 @@ +import React from 'react'; + +import logo1 from '@/assets/logo1.png'; +import { ButtonForLogin, InputField } from '@/components'; + +const LoginAdmin: React.FC = () => { + return ( +
+
+
+
+
+
+ {/* Left Column */} +
+
+ logo +

+ Login for Admin! +

+
+ + {/* Username and Password Fields */} + + + + {/* Login ButtonForLogin */} + +
+ + {/* Right Column */} +
+
+

+ We are more than just a company +

+

+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, + sed do eiusmod tempor incididunt ut labore et dolore magna + aliqua. Ut enim ad minim veniam, quis nostrud exercitation + ullamco laboris nisi ut aliquip ex ea commodo consequat. +

+
+
+
+
+
+
+
+
+ ); +}; + +export default LoginAdmin; diff --git a/frontend/src/containers/public/LoginUser.tsx b/frontend/src/containers/public/LoginUser.tsx new file mode 100644 index 0000000..87dc768 --- /dev/null +++ b/frontend/src/containers/public/LoginUser.tsx @@ -0,0 +1,75 @@ +import React from 'react'; +import { NavLink } from 'react-router-dom'; + +import logo1 from '@/assets/logo1.png'; +import { ButtonForLogin, InputField } from '@/components'; +import { path } from '@/ultils/constant'; + +const LoginUser: React.FC = () => { + return ( +
+
+
+
+
+
+ {/* Left Column */} +
+
+ logo +

+ Login for User! +

+
+ + {/* Username and Password Fields */} + + + + {/* Login Button */} + + + {/* Register Section */} +
+

Don't have an account?

+ + Register + +
+
+ + {/* Right Column */} +
+
+

+ We are more than just a company +

+

+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, + sed do eiusmod tempor incididunt ut labore et dolore magna + aliqua. Ut enim ad minim veniam, quis nostrud exercitation + ullamco laboris nisi ut aliquip ex ea commodo consequat. +

+
+
+
+
+
+
+
+
+ ); +}; + +export default LoginUser; diff --git a/frontend/src/containers/public/Navigation.tsx b/frontend/src/containers/public/Navigation.tsx new file mode 100644 index 0000000..31b5457 --- /dev/null +++ b/frontend/src/containers/public/Navigation.tsx @@ -0,0 +1,114 @@ +import React, { useState } from 'react'; +import { Link, NavLink } from 'react-router-dom'; + +import { path } from '@/ultils/constant'; +import icons from '@/ultils/icons'; + +const { RiAdminLine, RiUserLine } = icons; + +interface NavigationProps { + isAdmin?: boolean; +} + +const notActive = + 'hover:bg-amber-600 px-4 h-full flex items-center bg-[#2B2825]'; +const active = 'hover:bg-amber-600 px-4 h-full flex items-center bg-[#2B2825]'; + +const Navigation: React.FC = ({ isAdmin = false }) => { + const [isShowMenu, setIsShowMenu] = useState(false); + + return ( +
+
+ (isActive ? active : notActive)} + > + TRANG CHỦ + + (isActive ? active : notActive)} + > + SINH NHẬT + + (isActive ? active : notActive)} + > + THÔI NÔI + + (isActive ? active : notActive)} + > + KHAI TRƯƠNG + + (isActive ? active : notActive)} + > + ĐÁM CƯỚI + + (isActive ? active : notActive)} + > + SỰ KIỆN + + (isActive ? active : notActive)} + > + ĐƠN VỊ TỔ CHỨC + + (isActive ? active : notActive)} + > + GIỚI THIỆU + + (isActive ? active : notActive)} + > + LIÊN HỆ + + + {/* Dropdown Button for Login */} +
+ + + {/* Dropdown Menu */} + {isShowMenu && ( +
+ + + Đăng nhập cho người dùng + + + + Đăng nhập cho admin + +
+ )} +
+
+
+ ); +}; + +export default Navigation; diff --git a/frontend/src/containers/public/RegisterUser.tsx b/frontend/src/containers/public/RegisterUser.tsx new file mode 100644 index 0000000..dd0dd1a --- /dev/null +++ b/frontend/src/containers/public/RegisterUser.tsx @@ -0,0 +1,176 @@ +import React from 'react'; +import { NavLink } from 'react-router-dom'; + +import logo1 from '@/assets/logo1.png'; +import { path } from '@/ultils/constant'; + +const RegisterUser: React.FC = () => { + return ( +
+
+
+
+ logo +

+ Register for a New Account +

+
+ +
+ {/* Left Column */} +
+ {/* Full Name Input */} +
+ + +
+ + {/* Email Input */} +
+ + +
+ + {/* Phone Number Input */} +
+ + +
+ + {/* Date of Birth Input */} +
+ + +
+
+ + {/* Right Column */} +
+ {/* Address Input */} +
+ + +
+ + {/* Gender Dropdown */} +
+ + +
+ + {/* Password Input */} +
+ + +
+ + {/* Confirm Password Input */} +
+ + +
+
+
+ +
+ + +
+

Already have an account?

+ + Log in + +
+
+
+
+
+ ); +}; + +export default RegisterUser; diff --git a/frontend/src/containers/public/index.ts b/frontend/src/containers/public/index.ts new file mode 100644 index 0000000..4efd14f --- /dev/null +++ b/frontend/src/containers/public/index.ts @@ -0,0 +1,8 @@ +export { default as Header } from './Header'; +export { default as Footer } from './Footer'; +export { default as LoginUser } from './LoginUser'; +export { default as LoginAdmin } from './LoginAdmin'; +export { default as RegisterUser } from './RegisterUser'; +export { default as Home } from './Home'; +export { default as Navigation } from './Navigation'; +export { default as HomePage } from './HomePage'; diff --git a/frontend/src/index.css b/frontend/src/index.css new file mode 100644 index 0000000..b5c61c9 --- /dev/null +++ b/frontend/src/index.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 4f41ed0..4db8c99 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,10 +1,22 @@ -import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; +import { Provider } from 'react-redux'; +import { BrowserRouter } from 'react-router-dom'; +import { PersistGate } from 'redux-persist/integration/react'; +import 'slick-carousel/slick/slick-theme.css'; +import 'slick-carousel/slick/slick.css'; import App from '@/App'; +import './index.css'; +import reduxStore from './redux'; + +const { store, persistor } = reduxStore(); createRoot(document.getElementById('root')!).render( - - - , + + + + + + + , ); diff --git a/frontend/src/redux.ts b/frontend/src/redux.ts new file mode 100644 index 0000000..b8595de --- /dev/null +++ b/frontend/src/redux.ts @@ -0,0 +1,14 @@ +import { Store, createStore } from 'redux'; +import { persistStore } from 'redux-persist'; +import { Persistor } from 'redux-persist/es/types'; + +import rootReducer from '@/stores/reducers/rootReducer'; + +const reduxStore = (): { store: Store; persistor: Persistor } => { + const store = createStore(rootReducer); + const persistor = persistStore(store); + + return { store, persistor }; +}; + +export default reduxStore; diff --git a/frontend/src/stores/reducers/.gitkeep b/frontend/src/stores/reducers/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/src/stores/reducers/authReducer.ts b/frontend/src/stores/reducers/authReducer.ts new file mode 100644 index 0000000..60f460c --- /dev/null +++ b/frontend/src/stores/reducers/authReducer.ts @@ -0,0 +1,32 @@ +type AuthState = { + isLogin: boolean; + token: string | null; + msg: string; + update: boolean; + msgSuccess: string; +}; + +type Action = { + type: string; + data?: string; +}; + +const initState: AuthState = { + isLogin: false, + token: null, + msg: '', + update: false, + msgSuccess: '', +}; + +const authReducer = ( + state: AuthState = initState, + action: Action, +): AuthState => { + switch (action.type) { + default: + return state; + } +}; + +export default authReducer; diff --git a/frontend/src/stores/reducers/rootReducer.ts b/frontend/src/stores/reducers/rootReducer.ts new file mode 100644 index 0000000..905ba8a --- /dev/null +++ b/frontend/src/stores/reducers/rootReducer.ts @@ -0,0 +1,34 @@ +import { combineReducers } from 'redux'; +import { persistReducer } from 'redux-persist'; +import autoMergeLevel2 from 'redux-persist/es/stateReconciler/autoMergeLevel2'; +import storage from 'redux-persist/lib/storage'; + +import authReducer from './authReducer'; +import userReducer from './userReducer'; + +type AuthState = { + isLogin: boolean; + token: string | null; + msg: string; + update: boolean; + msgSuccess: string; +}; +const commonConfig = { + storage, + stateReconciler: autoMergeLevel2, +}; + +const authConfig = { + ...commonConfig, + key: 'auth', + whitelist: ['isLoggedIn', 'token'], +}; + +const rootReducer = combineReducers({ + auth: persistReducer(authConfig, authReducer), + user: userReducer, +}); + +export type RootState = ReturnType; + +export default rootReducer; diff --git a/frontend/src/stores/reducers/userReducer.ts b/frontend/src/stores/reducers/userReducer.ts new file mode 100644 index 0000000..f479b06 --- /dev/null +++ b/frontend/src/stores/reducers/userReducer.ts @@ -0,0 +1,24 @@ +type UserState = { + currentData: Record; +}; + +type Action = { + type: string; + currentData?: Record; +}; + +const initState: UserState = { + currentData: {}, +}; + +const userReducer = ( + state: UserState = initState, + action: Action, +): UserState => { + switch (action.type) { + default: + return state; + } +}; + +export default userReducer; diff --git a/frontend/src/ultils/constant.ts b/frontend/src/ultils/constant.ts new file mode 100644 index 0000000..e1ad258 --- /dev/null +++ b/frontend/src/ultils/constant.ts @@ -0,0 +1,20 @@ +export const path = { + HOME: '/*', + LOGIN_USER: '/user/login', + REGISTER_USER: '/user/register', + LOGIN_ADMIN: '/admin/login', + BIRTHDAY: '/sinh-nhat', + EVENT: '/su-kien', + WEDDING: '/dam-cuoi', + F_BIRTHDAY: '/thoi-noi', + OPENING: '/khai-truong', + ORGANIZE: '/don-vi-to-chuc', + INTRO: '/gioi-thieu', + CONTACT: '/lien-he', +}; + +export const text = { + HOME_TITLE: 'Tìm kiếm nơi tổ chức sự kiện, dịch vụ ưng ý', + HOME_DESCRIPTION: + 'Kênh thông tin Evora - Website tìm kiếm nơi tổ chức sự kiện, dịch vụ nhanh gọn, hiệu quả với 100.000+ tin đăng và 2.500.000 lượt xem mỗi tháng.', +}; diff --git a/frontend/src/ultils/dataContact.ts b/frontend/src/ultils/dataContact.ts new file mode 100644 index 0000000..5e4872f --- /dev/null +++ b/frontend/src/ultils/dataContact.ts @@ -0,0 +1,21 @@ +export const text = { + image: 'https://phongtro123.com/images/support-bg.jpg', + content: 'Liên hệ với chúng tôi nếu bạn cần hỗ trợ:', + contact: [ + { + text: 'Hỗ trợ đăng tin', + phone: 'Điện thoại: 0867438577', + zalo: 'Zalo: 0867438577', + }, + { + text: 'Hỗ trợ đăng tin', + phone: 'Điện thoại: 0867438577', + zalo: 'Zalo: 0867438577', + }, + { + text: 'Phản ánh/khiếu nại', + phone: 'Điện thoại: 0867438577', + zalo: 'Zalo: 0867438577', + }, + ], +}; diff --git a/frontend/src/ultils/dataIntro.ts b/frontend/src/ultils/dataIntro.ts new file mode 100644 index 0000000..6eca8dd --- /dev/null +++ b/frontend/src/ultils/dataIntro.ts @@ -0,0 +1,48 @@ +interface Statistic { + value: string; + name: string; +} + +interface Text { + title: string; + description: string; + description2: string; + statistic: Statistic[]; + price: string; + comment: string; + author: string; + question: string; + answer: string; +} + +export const text: Text = { + title: 'Tại sao lại chọn Evora?', + description: + 'Chúng tôi biết bạn có rất nhiều lựa chọn, nhưng Evora tự hào là trang web đứng top google về các từ khóa: ', + description2: + '...Vì vậy tin của bạn đăng trên website sẽ tiếp cận được với nhiều khách hàng hơn, do đó giao dịch nhanh hơn, tiết kiệm chi phí hơn', + statistic: [ + { + name: 'Thành viên', + value: '116.998+', + }, + { + name: 'Tin đăng', + value: '103.348+', + }, + { + name: 'Lượt truy cập/tháng', + value: '300.000+', + }, + { + name: 'Lượt xem/tháng', + value: '2.500.000+', + }, + ], + price: 'Chi phí thấp, hiệu quả tối đa', + comment: + '"Trước khi biết website Evora, mình phải tốn nhiều công sức và chi phí cho việc đăng tin dịch vụ, sự kiện: từ việc phát tờ rơi, dán giấy, và đăng lên các website khác nhưng hiệu quả không cao. Từ khi biết website Evora, mình đã thử đăng tin lên và đánh giá hiệu quả khá cao trong khi chi phí khá thấp."', + author: 'Anh Vĩ (chủ hệ thống nhà hàng, khách sạn chia sẻ)', + question: 'Bạn đang có sự kiện, dịch vụ cần đăng tin?', + answer: 'Không phải lo tìm người cho thuê dịch vụ', +}; diff --git a/frontend/src/ultils/icons.ts b/frontend/src/ultils/icons.ts new file mode 100644 index 0000000..4e3668d --- /dev/null +++ b/frontend/src/ultils/icons.ts @@ -0,0 +1,23 @@ +import { BiEnvelope, BiPhoneCall } from 'react-icons/bi'; +import { CiLocationOn, CiLogin, CiPhone } from 'react-icons/ci'; +import { FaBirthdayCake } from 'react-icons/fa'; +import { FaArrowRightLong, FaStar } from 'react-icons/fa6'; +import { FiUserPlus } from 'react-icons/fi'; +import { IoMailOpenOutline } from 'react-icons/io5'; +import { RiAdminLine, RiUserLine } from 'react-icons/ri'; + +const icons = { + FiUserPlus, + CiLogin, + FaStar, + RiAdminLine, + RiUserLine, + BiPhoneCall, + FaBirthdayCake, + BiEnvelope, + CiLocationOn, + IoMailOpenOutline, + CiPhone, + FaArrowRightLong, +}; +export default icons; diff --git a/frontend/src/ultils/loginManage.ts b/frontend/src/ultils/loginManage.ts new file mode 100644 index 0000000..18486d0 --- /dev/null +++ b/frontend/src/ultils/loginManage.ts @@ -0,0 +1,21 @@ +interface LoginOption { + id: number; + text: string; + path: string; +} + +// Khai báo loginManage với kiểu dữ liệu rõ ràng +const loginManage: LoginOption[] = [ + { + id: 1, + text: 'Đăng nhập cho người dùng', + path: '/user/login', + }, + { + id: 2, + text: 'Đăng nhập cho admin', + path: '/admin/login', + }, +]; + +export default loginManage; diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js new file mode 100644 index 0000000..e6741fa --- /dev/null +++ b/frontend/tailwind.config.js @@ -0,0 +1,36 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'], + theme: { + extend: { + width: { + 1100: '1100px', + }, + backgroundColor: { + primary: '#F5F5F5', + secondary1: '#1266dd', + secondary2: '#f73859', + 'overlay-30': 'rgba(0,0,0,0.3)', + 'overlay-70': 'rgba(0,0,0,0.7)', + }, + }, + maxWidth: { + 600: '600px', + 1100: '1100px', + }, + minWidth: { + 200: '200px', + 1100: '1100px', + }, + cursor: { + pointer: 'pointer', + }, + flex: { + 3: '3 3 0%', + }, + fontFamily: { + pacifico: ['Pacifico', 'cursive'], + }, + }, + plugins: [], +}; diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index b713cee..44ab307 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -6,4 +6,15 @@ import tsconfigPaths from 'vite-tsconfig-paths'; export default defineConfig({ plugins: [react(), tsconfigPaths()], base: '/', + optimizeDeps: { + include: ['slick-carousel'], + }, + build: { + rollupOptions: { + external: [ + 'slick-carousel/slick/slick-theme.css', + 'slick-carousel/slick/slick.css', + ], + }, + }, });