[FEAT]: add users list ui

This commit is contained in:
behnamrhp 2023-05-18 21:00:00 +03:00
commit 00ff73a2b1
65 changed files with 14976 additions and 0 deletions

22
.gitignore vendored Normal file
View File

@ -0,0 +1,22 @@
.DS_STORE
node_modules
scripts/flow/*/.flowconfig
.flowconfig
*~
*.pyc
.grunt
_SpecRunner.html
__benchmarks__
build/
remote-repo/
coverage/
.module-cache
*.log*
chrome-user-data
*.sublime-project
*.sublime-workspace
.idea
*.iml
.vscode
*.swp
*.swo

14
Dockerfile Normal file
View File

@ -0,0 +1,14 @@
FROM node:18.12.1
#create app directory
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
#installing the dependencies
COPY package*.json ./
RUN npm install
#copying source files
COPY . .
CMD ["yarn","dev"]

201
LICENSE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

8
docker-compose.prod.yml Normal file
View File

@ -0,0 +1,8 @@
version: "3.3"
services:
dipal-admin-prod:
ports:
- 8000:80
build:
context: .
dockerfile: dockerfile.prod

12
docker-compose.yml Normal file
View File

@ -0,0 +1,12 @@
version: "3.3"
services:
dipal-panel:
ports:
- 5173:5173
build:
context: ./
dockerfile: Dockerfile
volumes:
- ./:/usr/src/app
- /usr/src/app/node_modules

21
dockerfile.prod Normal file
View File

@ -0,0 +1,21 @@
FROM node:16.17.1-alpine3.16 as build
WORKDIR /usr/app
COPY . /usr/app
RUN npm i
RUN npm run build
FROM nginx:1.23.1-alpine
COPY ./nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf
COPY --from=build /usr/app/dist /usr/share/nginx/html
RUN touch /var/run/nginx.pid
RUN chown -R nginx:nginx /var/run/nginx.pid /var/cache/nginx /var/log/nginx /etc/nginx/conf.d
USER nginx
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

13
index.html Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/driving/main/index.tsx"></script>
</body>
</html>

12
jest.config.js Normal file
View File

@ -0,0 +1,12 @@
export default {
testEnvironment: "jsdom",
setupFilesAfterEnv: [
"<rootDir>/setupTests.ts"
],
moduleNameMapper: {
".+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2|svg|gif)$": "<rootDir>/fileMock.js",
"\\.(css|less)$": "identity-obj-proxy",
"^~(.*)$": "<rootDir>/src$1"
},
transformIgnorePatterns: ['<rootDir>/node_modules/'],
}

10
nginx/conf.d/default.conf Normal file
View File

@ -0,0 +1,10 @@
server_tokens off;
server {
listen 80;
root /usr/share/nginx/html;
index index.html;
server_name localhost;
location / {
try_files $uri $uri/ /index.html;
}
}

8243
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

57
package.json Normal file
View File

@ -0,0 +1,57 @@
{
"name": "dipal-panel",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"lint": "eslint --fix --ignore-path .eslintignore --ignore-pattern \"!**/.*\" .",
"prepare": "husky install",
"test": "jest",
"tsc": "tsc"
},
"dependencies": {
"@reduxjs/toolkit": "^1.9.3",
"axios": "^1.3.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-redux": "^8.0.5",
"react-router-dom": "^6.9.0"
},
"devDependencies": {
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.21.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",
"@types/jest": "^29.4.0",
"@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10",
"@typescript-eslint/eslint-plugin": "^5.54.1",
"@typescript-eslint/parser": "^5.54.1",
"@vitejs/plugin-react": "^3.1.0",
"autoprefixer": "^10.4.14",
"babel-plugin-transform-import-meta": "^2.2.0",
"eslint": "^8.36.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^8.7.0",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jest": "^27.2.1",
"eslint-plugin-jsx-a11y": "^6.7.1",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.32.2",
"husky": "^8.0.0",
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.5.0",
"postcss": "^8.4.21",
"prettier": "^2.8.4",
"styled-components": "^5.3.8",
"tailwindcss": "^3.2.7",
"typescript": "^4.9.3",
"vite": "^4.1.0"
}
}

6
postcss.config.cjs Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
plugins: [
require('tailwindcss'),
require('autoprefixer'),
]
}

View File

@ -0,0 +1,15 @@
<svg width="136" height="40" viewBox="0 0 136 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.4783 26.5658H15.718C14.0535 26.5628 12.4579 25.8979 11.2809 24.7167C10.1039 23.5355 9.44131 21.9343 9.43833 20.2638V16.4902L8.83668 15.8864C8.36687 15.4043 7.80508 15.0222 7.18486 14.7629C6.56463 14.5035 5.8987 14.3723 5.2268 14.3769C4.55491 14.3723 3.88897 14.5035 3.26875 14.7629C2.64853 15.0222 2.08674 15.4043 1.61692 15.8864L1.50412 15.9619C1.02752 16.4362 0.649227 17.0005 0.39107 17.6223C0.132913 18.244 0 18.911 0 19.5846C0 20.2581 0.132913 20.9251 0.39107 21.5468C0.649227 22.1686 1.02752 22.7329 1.50412 23.2072L12.7474 34.4904C13.7107 35.4434 15.0047 35.9845 16.3573 35.9999C17.0292 36.0045 17.6951 35.8733 18.3153 35.6139C18.9355 35.3546 19.4973 34.9725 19.9671 34.4904L20.0423 34.415C20.9999 33.4466 21.5372 32.1376 21.5372 30.7734C21.5372 29.4091 20.9999 28.1002 20.0423 27.1318L19.4783 26.5658Z" fill="#D700FE"/>
<path d="M26.8484 15.0187H22.1104V20.264C22.1056 21.2712 21.8607 22.2625 21.3963 23.1553C20.9318 24.0481 20.2613 24.8165 19.4406 25.3962H26.886C28.242 25.3952 29.5422 24.8541 30.501 23.8919C31.4599 22.9296 31.999 21.6248 32 20.264V20.1508C31.9833 18.7884 31.4339 17.4871 30.4704 16.5271C29.5068 15.5672 28.2061 15.0255 26.8484 15.0187Z" fill="#D700FE"/>
<path d="M12.108 16.5282C11.6277 16.9997 11.2469 17.5635 10.9885 18.1859C10.7301 18.8084 10.5993 19.4767 10.6039 20.1509V20.2641C10.6049 21.625 11.144 22.9298 12.1029 23.892C13.0617 24.8543 14.3619 25.3953 15.7179 25.3963H15.8307C17.1867 25.3953 18.4869 24.8543 19.4457 23.892C20.4046 22.9298 20.9437 21.625 20.9447 20.2641V15.0188H15.7179C15.046 15.0141 14.3801 15.1454 13.7598 15.4047C13.1396 15.6641 12.5778 16.0462 12.108 16.5282Z" fill="#D700FE"/>
<path d="M11.3186 15.736C12.4831 14.5591 14.0654 13.8941 15.7181 13.8869H20.9449V9.13215C20.9439 7.77132 20.4048 6.46652 19.4459 5.50428C18.4871 4.54203 17.1869 4.001 15.8309 4H15.7181C14.3621 4.001 13.0619 4.54203 12.103 5.50428C11.1442 6.46652 10.6051 7.77132 10.6041 9.13215V16.604C10.8002 16.2823 11.0408 15.99 11.3186 15.736Z" fill="#D700FE"/>
<path d="M39 15.116H40.8387V24.8544H39V15.116ZM46.0045 24.8544L41.9185 21.1334C41.7467 20.9634 41.6135 20.7587 41.5279 20.533C41.4423 20.3073 41.4063 20.066 41.4224 19.8253C41.4408 19.422 41.6069 19.0393 41.8894 18.7497L45.5959 15.0869H47.9015L43.6697 19.0985C43.4362 19.3311 43.2027 19.4764 43.2027 19.8253C43.2027 20.2323 43.4945 20.4939 43.7864 20.7264L48.4269 24.8544H46.0045Z" fill="white"/>
<path d="M58.0866 20C58.0866 23.9535 56.7149 25 53.0668 25C49.4186 25 48.0469 23.9535 48.0469 20C48.0469 16.0465 49.4478 15 53.0668 15C56.6857 15 58.0866 16.0465 58.0866 20ZM53.0668 23.5174C55.46 23.5174 56.1896 22.7907 56.1896 20C56.1896 17.2093 55.46 16.4826 53.0668 16.4826C50.6736 16.4826 49.9439 17.2093 49.9439 20C49.9439 22.7907 50.6444 23.5174 53.0668 23.5174Z" fill="white"/>
<path d="M66.8132 23.8081C66.5214 24.564 66.2587 24.9709 65.4124 24.9709C64.566 24.9709 64.3033 24.564 64.0115 23.8081L61.5307 17.2965C61.5015 17.1802 61.3848 16.9767 61.2972 16.9767C61.1513 16.9767 61.1513 17.2384 61.1513 17.2674L61.1805 24.8547H59.3418V16.2791C59.3418 15.3779 59.9547 15 60.9178 15C62.0268 15 62.3771 15.5814 62.6397 16.25L65.1789 22.7616C65.2664 22.9651 65.354 23.1105 65.4415 23.1105C65.5291 23.1105 65.6167 22.9651 65.7042 22.7616L68.2433 16.25C68.506 15.5814 68.8562 15 69.9653 15C70.9576 15 71.5413 15.3779 71.5413 16.2791V24.8547H69.7026L69.761 17.2674C69.761 17.0058 69.6442 16.9767 69.5859 16.9767C69.5275 16.9767 69.3816 17.1802 69.3524 17.2965L66.8132 23.8081Z" fill="white"/>
<path d="M79.5675 15.1162V15.9011H81.2311C84.0329 15.9011 84.5874 17.3837 84.5874 19.8837C84.5874 22.3837 84.0037 23.8953 81.2311 23.8953H79.5675V24.8546H77.7289V23.8953H76.0653C73.2635 23.8953 72.709 22.3837 72.709 19.8837C72.709 17.3837 73.2635 15.9011 76.0653 15.9011H77.7289V15.1162H79.5675ZM77.7289 17.3837H76.6198C74.9271 17.3837 74.606 18.0813 74.606 19.9127C74.606 21.7441 74.9271 22.4999 76.6198 22.4999H77.7289V17.3837ZM80.6766 22.4709C82.3693 22.4709 82.6904 21.715 82.6904 19.8837C82.6904 18.0523 82.3693 17.3546 80.6766 17.3546H79.5675V22.4418L80.6766 22.4709Z" fill="white"/>
<path d="M95.6198 20C95.6198 23.9535 94.2481 25 90.6 25C86.9518 25 85.5801 23.9535 85.5801 20C85.5801 16.0465 86.981 15 90.6 15C94.2189 15 95.6198 16.0465 95.6198 20ZM90.6 23.5174C92.9932 23.5174 93.7228 22.7907 93.7228 20C93.7228 17.2093 92.9932 16.4826 90.6 16.4826C88.2068 16.4826 87.4771 17.2093 87.4771 20C87.4771 22.7907 88.1776 23.5174 90.6 23.5174Z" fill="white"/>
<path d="M96.8457 16.5697C96.8457 15.465 97.4294 15.1162 98.2758 15.1162H103.15C105.251 15.1162 105.63 16.5697 105.63 18.2557C105.63 19.9418 105.251 21.4244 103.15 21.4244H98.7136V24.8546H96.8749V16.5697H96.8457ZM102.216 19.9418C103.471 19.9418 103.704 19.3604 103.704 18.2557C103.704 17.1511 103.471 16.5988 102.216 16.5988H99.3848C98.8303 16.5988 98.6844 16.7441 98.6844 17.2964V19.9709H102.216V19.9418Z" fill="white"/>
<path d="M109.98 24.8546V16.5697H106.156V15.1162H115.641V16.5697H111.818V24.8546H109.98Z" fill="white"/>
<path d="M124.337 24.8546H117.916C117.07 24.8546 116.486 24.5057 116.486 23.4011V16.5697C116.486 15.465 117.07 15.1162 117.916 15.1162H124.279V16.5697H119.025C118.471 16.5697 118.325 16.715 118.325 17.2674V19.1569H124.133V20.6104H118.325V22.7034C118.325 23.2557 118.471 23.4011 119.025 23.4011H124.337V24.8546Z" fill="white"/>
<path d="M128.657 15.9302C128.833 15.4942 129.124 15 130.146 15C131.167 15 131.459 15.4651 131.634 15.9302L135.428 24.8547H133.473L130.379 17.064C130.35 16.9477 130.321 16.7733 130.146 16.7733C129.971 16.7733 129.942 16.9477 129.912 17.064L126.848 24.8547H124.893L128.657 15.9302Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 5.6 KiB

1
public/vite.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

1
setupTests.ts Normal file
View File

@ -0,0 +1 @@
import '@testing-library/jest-dom';

View File

@ -0,0 +1,11 @@
import axios, { AxiosRequestConfig } from 'axios';
import { staticMessages } from '~/driven/utils/configs/staticMessages';
export class HTTPBoundary {
async request<R>(options: AxiosRequestConfig) {
const response = await axios<R>(options);
if (!response) throw new Error(staticMessages.service.errors[500]);
return response.data;
}
}

View File

@ -0,0 +1,11 @@
import { NavigateFunction, Outlet, useLocation, useNavigate } from 'react-router-dom';
class RouterBoundary {
outletRoute = () => <Outlet />;
useRouterHook = () => useLocation();
useNavigate = (): NavigateFunction => useNavigate();
}
export default new RouterBoundary();

View File

@ -0,0 +1,8 @@
import store from '../store/store';
import userSlice from '../slices/userSlice';
import { UserState } from '../slices/protocols/userSliceProtocols';
export const userAdapter = {
get: store.getState().user,
update: (user: UserState) => store.dispatch(userSlice.actions.update(user)),
};

View File

@ -0,0 +1 @@
export { userAdapter } from './adapters/adapter';

View File

@ -0,0 +1,3 @@
import { UserModel } from '~/business-logic/generic/user/common/domain/model/userModel';
export type UserState = UserModel;

View File

@ -0,0 +1,19 @@
import { createSlice, SliceCaseReducers } from '@reduxjs/toolkit';
import { UserState } from './protocols/userSliceProtocols';
export const userStateName = 'user';
const userSlice = createSlice<UserState, SliceCaseReducers<UserState>>({
name: userStateName,
initialState: null,
reducers: {
update: (state, action) => {
if (!action.payload) return state;
return {
...state,
...action.payload,
};
},
},
});
export default userSlice;

View File

@ -0,0 +1,12 @@
import { combineReducers, configureStore } from '@reduxjs/toolkit';
import userSlice, { userStateName } from '../slices/userSlice';
const combinedReducers = combineReducers({
[userStateName]: userSlice.reducer,
});
const store = configureStore({
reducer: combinedReducers,
});
export default store;

View File

@ -0,0 +1,14 @@
import React from 'react'
interface IPrimaryButtonProps {
title: string;
onClick: (e: React.MouseEvent) => void;
className?: string;
}
export default function PrimaryButton(props: IPrimaryButtonProps) {
const { onClick, title, className } = props;
return (
<button onClick={onClick} className={`py-1 px-5 transition-all hover:bg-primary-300 bg-primary-main rounded-lg text-white text-center ${className}`}>{ title }</button>
)
}

View File

@ -0,0 +1,13 @@
import React from 'react'
interface IPageTitleProps {
title: string;
className?: string;
}
export default function PageTitle(props: IPageTitleProps) {
const { title, className } = props;
return (
<div className={`w-full shadow-sm shadow-txt-light font-semibold ${className}`}>{title}</div>
)
}

View File

@ -0,0 +1,10 @@
export const appConfig = {};
export const routes = {
home: '/',
};
const baseApiUrl = import.meta.env.BASE_API_URL;
export const apiUrls = {
};

View File

@ -0,0 +1,5 @@
const baseAssetsUrl = 'assets/';
const baseIconsUrl = baseAssetsUrl + 'icons/';
export const icons = {
logo: baseIconsUrl + 'logo.svg'
}

View File

@ -0,0 +1,22 @@
export const staticMessages = {
global: {
errors: {
input: 'please fill all inputs correctly',
},
users: 'Users',
submit: 'Submit',
fistname: 'Firstname',
lastname: 'Lastname',
place_id: 'Place id',
title: 'title',
status: 'Status',
address: 'Address',
qrCode: 'qrCode'
},
service: {
errors: {
500: 'server not respond please try again later!',
401: 'Authentication error!',
},
},
};

View File

@ -0,0 +1,15 @@
import { errorHandlingStateTypes, UIErrorHandling } from './protocols/globalHelpersProtocols';
export const UIErrorHandlingFactory = <DATA_RESPONSE>({
state,
message,
data,
}: {
state: `${errorHandlingStateTypes}`;
message: string;
data: DATA_RESPONSE;
}): UIErrorHandling<DATA_RESPONSE> => ({
data,
message,
state,
});

View File

@ -0,0 +1,11 @@
export enum errorHandlingStateTypes {
error = 'error',
warning = 'warning',
success = 'success',
}
export type UIErrorHandling<DATA_RESPONSE> = {
state: `${errorHandlingStateTypes}`;
message: string;
data: DATA_RESPONSE;
};

View File

@ -0,0 +1 @@
export type RequestMethods = 'get' | 'post' | 'put' | 'delete';

View File

@ -0,0 +1,3 @@
import PlacesList from "./infra/PlacesList";
export default PlacesList;

View File

@ -0,0 +1,8 @@
import React from 'react'
import PlacesListView from '../view/PlacesListView'
import usePlacesListVM from '../viewmodel/placesListVM'
export default function PlacessList() {
const { selectedRowId, setSelectedRowId } = usePlacesListVM()
return <PlacesListView selectedRowId={selectedRowId} setSelectedRowId={setSelectedRowId} />
}

View File

@ -0,0 +1,3 @@
export interface IPlacesListInfraProps {
}

View File

@ -0,0 +1,72 @@
import React from 'react'
import RowItem from '../../table-row/view/table-row-item/view/RowItem'
import TableRow from '../../table-row'
import { IPlacesListProps } from './protocols'
import { staticMessages } from '~/driven/utils/constants/staticMessages';
export default function UsersListView(props: IPlacesListProps) {
const { selectedRowId, setSelectedRowId } = props;
const rows = () => {
const placesdata = [
{
id: '1',
place_id: '6440020b89366fdcaf15a8c2',
title: 'flat demoplace ',
status: 'demo',
address: 'demoplace'
},
{
id: '2',
place_id: '6440020b89366fdcaf15asdfa',
title: 'flat demoplace second ',
status: 'demo second',
address: 'demoplace second'
}
]
const rowData = {
rowItemsTitle: placesdata.map(places => {
return [
places.id,
places.title,
places.status,
places.address,
]
}),
rowId: placesdata[0].id
}
return placesdata.map(places => {
const rowData = {
rowItemsTitle: [
places.id,
places.title,
places.status,
places.address,
''
],
rowId: places.id,
}
return <TableRow rowData={rowData} selectedRowId={selectedRowId} setSelectedRowId={setSelectedRowId} />
})
}
return (
<table className='table-auto rounded-md w-full text-sm'>
<thead className='text-txt-medium font-bold'>
<tr>
<th className='py-3'>{staticMessages.global.place_id}</th>
<th className='py-3'>{staticMessages.global.title}</th>
<th className='py-3'>{staticMessages.global.status}</th>
<th className='py-3'>{staticMessages.global.address}</th>
<th className='py-3'>{staticMessages.global.qrCode}</th>
</tr>
</thead>
<tbody>
{ rows() }
</tbody>
</table>
)
}

View File

@ -0,0 +1,4 @@
export interface IPlacesListProps {
selectedRowId: string;
setSelectedRowId: React.Dispatch<React.SetStateAction<string>>;
}

View File

@ -0,0 +1,13 @@
import { useState } from "react";
import { placesListReturnType } from "./protocols";
const usePlacesListVM = (): placesListReturnType => {
const [ selectedRowId, setSelectedRowId ] = useState<string>('');
return {
selectedRowId,
setSelectedRowId,
}
}
export default usePlacesListVM;

View File

@ -0,0 +1,9 @@
import React from "react";
export interface IPlacesListVM {
}
export type placesListReturnType = {
selectedRowId: string;
setSelectedRowId: React.Dispatch<React.SetStateAction<string>>;
}

View File

@ -0,0 +1,3 @@
import TableRow from "./infra/TableRowInfra";
export default TableRow;

View File

@ -0,0 +1,13 @@
import React from 'react'
import { ITableRowInfra } from './protocols'
import useTableRowVM from '../viewmodel/tableRowVM';
import TableRowView from '../view/TableRow';
export default function TableRow(props: ITableRowInfra) {
const { rowData, selectedRowId, setSelectedRowId } = props;
const { rowId } = rowData;
const { isRowSelected } = useTableRowVM({selectedRowId, rowId});
return <TableRowView isSelected={isRowSelected} rowData={rowData} setSelectedRowId={setSelectedRowId}/>
}

View File

@ -0,0 +1,8 @@
export interface ITableRowInfra {
selectedRowId: string;
rowData: {
rowItemsTitle: string[];
rowId: string;
}
setSelectedRowId: React.Dispatch<React.SetStateAction<string>>
}

View File

@ -0,0 +1,15 @@
import React from 'react'
import RowItem from './table-row-item/view/RowItem';
import { ITableRowProps } from './protocols';
export default function TableRowView(props: ITableRowProps) {
const { isSelected, setSelectedRowId, rowData } = props;
const { rowId, rowItemsTitle } = rowData;
const columns = rowItemsTitle.map((rowItemTitle, index) => {
return <RowItem hasCheckbox={index === 0} isSelected={isSelected} title={rowItemTitle} />
})
return (
<tr onClick={() => setSelectedRowId(rowId)}>{ columns }</tr>
)
}

View File

@ -0,0 +1,8 @@
export interface ITableRowProps {
isSelected: boolean;
rowData: {
rowItemsTitle: string[];
rowId: string;
}
setSelectedRowId: React.Dispatch<React.SetStateAction<string>>
}

View File

@ -0,0 +1,24 @@
import React, { useState } from 'react'
interface IRowItemProp {
title: string;
hasCheckbox: boolean;
isSelected: boolean;
}
export default function RowItem(props: IRowItemProp) {
const { title, hasCheckbox, isSelected } = props;
return (
<td className={`px-1 py-2 ${isSelected ? 'bg-primary-100' : ''}`}>
<div className='w-full flex'>
{
hasCheckbox &&
<span className={`checkmak-container flex justify-center items-center mr-2 transition-all ${isSelected ? 'opacity-100' : 'opacity-0'}`}>
<span className={`${isSelected ? 'visible' : 'hidden'} transition-all`}>&#10003;</span>
</span>
}
{ title }
</div>
</td>
)
}

View File

@ -0,0 +1,8 @@
export interface IUserTableRowVM {
selectedRowId: string;
rowId: string;
}
export type tableRowVMReturnType = {
isRowSelected: boolean;
}

View File

@ -0,0 +1,10 @@
import { IUserTableRowVM, tableRowVMReturnType } from "./protocols";
const useTableRowVM = (dependencies: IUserTableRowVM): tableRowVMReturnType => {
const { rowId, selectedRowId } = dependencies;
return {
isRowSelected: rowId === selectedRowId
}
}
export default useTableRowVM;

View File

@ -0,0 +1,3 @@
import UsersList from "./infra/UsersList";
export default UsersList;

View File

@ -0,0 +1,8 @@
import React from 'react'
import useUsersListVM from '../viewmodel/usersListVM'
import UsersListView from '../view/UsersListView'
export default function UsersList() {
const { selectedRowId, setSelectedRowId } = useUsersListVM()
return <UsersListView selectedRowId={selectedRowId} setSelectedRowId={setSelectedRowId} />
}

View File

@ -0,0 +1,3 @@
export interface IUsersListInfraProps {
}

View File

@ -0,0 +1,60 @@
import React from 'react'
import RowItem from '../../table-row/view/table-row-item/view/RowItem'
import TableRow from '../../table-row'
import { IUserListProps } from './protocols'
import { staticMessages } from '~/driven/utils/constants/staticMessages';
export default function UsersListView(props: IUserListProps) {
const { selectedRowId, setSelectedRowId } = props;
const rows = () => {
const userdata = [
{
id: '1',
firstname: 'behnam',
lastname: 'rahimpour'
},
{
id: '2',
firstname: 'Salar',
lastname: 'Sali'
}
]
const rowData = {
rowItemsTitle: userdata.map(user => {
return [
user.firstname,
user.lastname
]
}),
rowId: userdata[0].id
}
return userdata.map(user => {
const rowData = {
rowItemsTitle: [
user.firstname,
user.lastname
],
rowId: user.id,
}
return <TableRow rowData={rowData} selectedRowId={selectedRowId} setSelectedRowId={setSelectedRowId} />
})
}
return (
<table className='table-auto rounded-md w-full text-sm'>
<thead className='text-txt-medium font-bold'>
<tr>
<th className='py-3'>{staticMessages.global.fistname}</th>
<th className='py-3'>{staticMessages.global.lastname}</th>
</tr>
</thead>
<tbody>
{ rows() }
</tbody>
</table>
)
}

View File

@ -0,0 +1,4 @@
export interface IUserListProps {
selectedRowId: string;
setSelectedRowId: React.Dispatch<React.SetStateAction<string>>;
}

View File

@ -0,0 +1,9 @@
import React from "react";
export interface IUsersListVM {
}
export type userListReturnType = {
selectedRowId: string;
setSelectedRowId: React.Dispatch<React.SetStateAction<string>>;
}

View File

@ -0,0 +1,13 @@
import { useState } from "react";
import { userListReturnType } from "./protocols";
const useUsersListVM = (): userListReturnType => {
const [ selectedRowId, setSelectedRowId ] = useState<string>('');
return {
selectedRowId,
setSelectedRowId,
}
}
export default useUsersListVM;

View File

@ -0,0 +1,3 @@
import Sidebar from "./view/Sidebar";
export default Sidebar;

View File

@ -0,0 +1,12 @@
import React from 'react'
import { icons } from '~/driven/utils/constants/assertUrls'
export default function Sidebar() {
return (
<aside className='w-3/12 min-w-[10rem] [background:var(--color-gradient-primary-dark)]'>
<div className='logo p-4'>
<img src={icons.logo} alt="logo icon" />
</div>
</aside>
)
}

13
src/driving/main/App.tsx Normal file
View File

@ -0,0 +1,13 @@
import { BrowserRouter as RouterWrapper } from 'react-router-dom';
import Router from './Router/Router';
import './style/App.css';
function App() {
return (
<RouterWrapper>
<Router />
</RouterWrapper>
);
}
export default App;

View File

@ -0,0 +1,14 @@
import { Navigate, Outlet, Route, Routes, useLocation } from 'react-router-dom';
import Home from '~/driving/main/pages';
import { routes } from '~/driven/utils/configs/appConfig';
export default function Router() {
const location = useLocation();
return (
<Routes location={location} key={location.key}>
<Route index element={<Home />} />
<Route path='*' element={<Navigate to={routes.home} replace />} />
</Routes>
);
}

View File

@ -0,0 +1,10 @@
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import App from './App';
import store from '~/driven/boundaries/state-management-boundary/store/store';
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<Provider store={store}>
<App />
</Provider>,
);

View File

@ -0,0 +1,28 @@
import React from 'react';
import PrimaryButton from '~/driven/utils/components/buttons/primary-button/PrimaryButton';
import PageTitle from '~/driven/utils/components/page-title/pageTitle';
import { staticMessages } from '~/driven/utils/constants/staticMessages';
import PlacesList from '~/driving/application/core/places-list';
import UsersList from '~/driving/application/core/users-list';
import Sidebar from '~/driving/application/support/sidebar';
export default function index() {
return (
<div className='flex flex-nowrap h-screen'>
<Sidebar />
<main className='dipal-panel w-full text-black bg-white h-fit'>
<PageTitle className='px-4 py-5' title={staticMessages.global.users} />
<div className='container mx-auto px-4'>
<div className='w-full flex flex-row-reverse items-center py-2'>
<PrimaryButton className='text-sm' title={staticMessages.global.submit} onClick={() => {}} />
</div>
<div className='md:grid-cols-2 gap-x-4 grid grid-cols-1 mx-auto'>
<UsersList />
<PlacesList />
</div>
</div>
</main>
</div>
);
}

View File

@ -0,0 +1,54 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
* {
box-sizing: border-box;
}
html,body {
padding: 0;
margin: 0;
}
:root {
--color-text-light: #D1D1D1;
--color-text-medium: #8C8C8C;
--color-gradient-primary-dark: linear-gradient(179.87deg, #001641 -7.06%, #03379C 108%);
--color-primary-main: #0092DF;
--color-primary-100: #C7E4F4;
--color-primary-300: #0461B8;
--color-primary-200: #80C8EF;
}
th,
td {
border: .1px solid var(--color-text-light);
border-spacing:0;
border-collapse: separate;
overflow: hidden;
padding: 4px 0;
transition: all .2s;
}
tr {
transition: all .2s;
}
tbody tr:hover {
cursor: pointer;
}
tr:hover .checkmak-container {
opacity: 1;
visibility: visible;
}
.checkmak-container {
width: 1rem;
height: 1rem;
cursor: pointer;
border-radius: 100%;
border: 1px solid var(--color-text-medium);
}

1
src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

35
tailwind.config.cjs Normal file
View File

@ -0,0 +1,35 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/**/*.{js,jsx,ts,tsx}'],
theme: {
extend: {
colors: {
primary: {
main: 'var(--color-primary-main)',
100: 'var(--color-primary-100)',
200: 'var(--color-primary-200)',
300: 'var(--color-primary-300)',
},
secondary: {
main: '#D700FE',
dark: '#9E00C5'
},
bg: {
white: '#FFFFFF',
gray: '#F2F3F5'
},
txt: {
black: '#000000',
medium: 'var(--color-text-medium)',
second: '#D1D1D1',
light: 'var(--color-text-light)',
second: '#B4B4B4',
},
gradient: {
primary: 'var(--color-gradient-primary-dark)',
}
}
},
},
plugins: [],
}

30
tsconfig.json Normal file
View File

@ -0,0 +1,30 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"types": [
"node"
],
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"paths": {
"~/*": [
"./src/*"
]
},
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

9
tsconfig.node.json Normal file
View File

@ -0,0 +1,9 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

16
vite.config.ts Normal file
View File

@ -0,0 +1,16 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'~': path.resolve(__dirname, './src'),
},
},
server: {
host: true,
},
})

5658
yarn.lock Normal file

File diff suppressed because it is too large Load Diff