Skip to content

Commit 2cf8f8c

Browse files
committed
v1.0
1 parent 43b74bf commit 2cf8f8c

32 files changed

+819
-79
lines changed

package-lock.json

Lines changed: 68 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
"private": true,
66
"dependencies": {
77
"@testing-library/jest-dom": "^5.16.1",
8-
"@testing-library/react": "^12.1.2",
98
"@testing-library/user-event": "^13.5.0",
109
"react": "^17.0.2",
1110
"react-dom": "^17.0.2",
@@ -38,8 +37,11 @@
3837
]
3938
},
4039
"devDependencies": {
40+
"@testing-library/react": "^12.1.2",
4141
"autoprefixer": "^10.4.1",
42+
"jest-dom": "^4.0.0",
4243
"postcss": "^8.4.5",
44+
"react-test-renderer": "^17.0.2",
4345
"tailwindcss": "^3.0.8"
4446
}
4547
}

src/App.js

Lines changed: 70 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,20 @@ import React, { useEffect, useState, Fragment } from 'react'
33
import "./App.css"
44
import Api from "./Services/Api";
55
import ThemeToggle from "./Components/DarkMode/ThemeToggle";
6-
import { DeleteSelectedBtn, SearchUser, EditableRow, DisplayRows, Pagination } from "./Components";
7-
import icon from "./icon.png";
6+
import { DeleteSelectedBtn, SearchUser, EditableRow, DisplayRows, Pagination, Modal, Skeleton } from "./Components";
7+
import icon from "./Images/icon.png";
88

99
const App = () => {
1010

11+
// Use state to show initial loading skeleton state.
12+
const [loading, setLoading] = useState(true);
13+
1114
// Use state to store the users data from the API call and set it to an empty array initially.
1215
const [users, setUsers] = useState([]);
1316

17+
// Use state to set the error to be true if the API call fails and set it to false if the API call succeeds.
18+
const [error, setError] = useState(false);
19+
1420
// Use state to store the edited user data and set it to an empty object initially.
1521
const [editUserForm, setEditUserForm] = useState({
1622
id: '',
@@ -34,8 +40,12 @@ const App = () => {
3440
// Get the users from the API when the component mounts and set the users to the state using the setUsers() method.
3541
useEffect(() => {
3642
Api.getUsers()
37-
.then(data => setUsers(data))
38-
.catch(error => console.log(error));
43+
.then(data => {
44+
setError(false)
45+
setUsers(data)
46+
})
47+
.catch(error => setError(true))
48+
.finally(setLoading(false));
3949
}, []); // [] is used to tell React that this effect should only run once.
4050

4151
// Checked all the users in the current page and set the users to the state using the setUsers() method.
@@ -83,41 +93,47 @@ const App = () => {
8393
const currentItems = filteredUsers.slice(indexOfFirstItem, indexOfLastItem); // Get the users in the current page.
8494

8595
return (
86-
<div className="min-h-screen min-w-screen bg-background dark:bg-background-dark dark:text-white transition-all ease-out text-gray text-center flex justify-center items-center overflow-hidden">
87-
<div className=" h-full sm:h-auto w-full sm:w-auto mt-0 sm:mt-4 px-2 sm:px-8 text-sm md:text-md">
88-
89-
<div className="py-1 pt-0 w-full flex justify-between items-center">
90-
<DeleteSelectedBtn users={users} setUsers={setUsers} />
91-
<div className="flex justify-center items-center">
92-
<SearchUser searchResult={searchResult} setSearchResult={setSearchResult} />
93-
<ThemeToggle />
94-
</div>
96+
<div className="relative min-h-screen min-w-screen bg-background dark:bg-background-dark dark:text-white transition-all ease-out text-gray text-center flex justify-center items-center overflow-hidden">
97+
<div className="h-full sm:h-auto w-full sm:w-auto mt-0 sm:mt-4 px-2 sm:px-8 text-sm md:text-md">
98+
99+
{
100+
error && <Modal />
101+
}
102+
103+
<div className="py-1 pt-0 w-full flex justify-between items-center">
104+
<DeleteSelectedBtn users={users} setUsers={setUsers} />
105+
<div className="flex justify-center items-center">
106+
<SearchUser searchResult={searchResult} setSearchResult={setSearchResult} />
107+
<ThemeToggle />
95108
</div>
96-
97-
<div className="flex flex-col mt-1 min-h-[75vh] min-w-auto md:min-w-[75vw] xl:min-w-[55vw]">
98-
<div className="py-2 pb-0 overflow-auto">
99-
<form onSubmit={handleUserEditFormSubmit}>
100-
<table className="w-full sm:min-w-full table-auto">
101-
<thead className="relative">
102-
<tr className="text-md font-medium text-gray-deep uppercase tracking-wider">
103-
<th scope="col" className="px-6 py-2 w-auto text-center">
104-
<input
105-
type="checkbox"
106-
value="checkedAll"
107-
className="form-checkbox h-4 w-4"
108-
onChange={handleAllChecked}
109-
checked={ users.slice(indexOfFirstItem, indexOfLastItem).filter( user => user?.isChecked !== true ).length < 1 }
110-
/>
111-
</th>
112-
<th scope="col" className="px-6 py-2 text-left">Name</th>
113-
<th scope="col" className="px-6 py-2 text-left">Email</th>
114-
<th scope="col" className="px-6 py-2 text-left">Role</th>
115-
<th scope="col" className="px-6 py-2 text-center">Action</th>
116-
</tr>
117-
</thead>
118-
<tbody className="relative text-md divide-y-4 divide-transparent">
119-
{
120-
users && users.length > 0 && filteredUsers.length > 0
109+
</div>
110+
111+
<div className="flex flex-col mt-1 min-h-[75vh] min-w-auto md:min-w-[75vw] xl:min-w-[55vw]">
112+
<div className="py-2 pb-0 overflow-auto">
113+
<form onSubmit={handleUserEditFormSubmit}>
114+
<table className="w-full sm:min-w-full table-auto">
115+
<thead className="relative">
116+
<tr className="text-md font-medium text-gray-deep uppercase tracking-wider">
117+
<th scope="col" className="px-6 py-2 w-auto text-center">
118+
<input
119+
type="checkbox"
120+
value="checkedAll"
121+
className="form-checkbox h-4 w-4"
122+
onChange={handleAllChecked}
123+
checked={ users.slice(indexOfFirstItem, indexOfLastItem).filter( user => user?.isChecked !== true ).length < 1 }
124+
/>
125+
</th>
126+
<th scope="col" className="px-6 py-2 text-left">Name</th>
127+
<th scope="col" className="px-6 py-2 text-left">Email</th>
128+
<th scope="col" className="px-6 py-2 text-left">Role</th>
129+
<th scope="col" className="px-6 py-2 text-center">Action</th>
130+
</tr>
131+
</thead>
132+
<tbody className="relative text-md divide-y-4 divide-transparent">
133+
{
134+
loading
135+
? <Skeleton />
136+
: users && users.length > 0 && filteredUsers.length > 0
121137
? currentItems
122138
.map((user, index) => (
123139
<Fragment key={index}>
@@ -149,22 +165,22 @@ const App = () => {
149165
</div>
150166
</td>
151167
</tr>
152-
}
153-
</tbody>
154-
</table>
155-
</form>
156-
</div>
157-
</div>
158-
159-
<div className={`${users.length !== 0 ? 'flex' : 'invisible' } justify-center items-center py-2 md:py-6`}>
160-
<Pagination
161-
filteredUsers={filteredUsers}
162-
itemPerPage={itemPerPage}
163-
currentPage={currentPage}
164-
setCurrentPage={setCurrentPage}
165-
/>
166-
</div>
167-
168+
}
169+
</tbody>
170+
</table>
171+
</form>
172+
</div>
173+
</div>
174+
175+
<div className={`${users.length !== 0 ? 'flex' : 'invisible' } justify-center items-center py-2 md:py-6`}>
176+
<Pagination
177+
filteredUsers={filteredUsers}
178+
itemPerPage={itemPerPage}
179+
currentPage={currentPage}
180+
setCurrentPage={setCurrentPage}
181+
/>
182+
</div>
183+
168184
</div>
169185
</div>
170186
)

src/App.test.js

Lines changed: 0 additions & 8 deletions
This file was deleted.

src/Components/DarkMode/ThemeToggle.js

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,24 @@ const Toggle = () => {
88
const { theme, setTheme } = useContext(ThemeContext);
99

1010
return (
11-
<div className="transition duration-500 ease-in-out p-1 mx-0 md:mx-16 ml-2 md:ml-5 border-dashed border-2 rounded-full border-yellow-300">
12-
{theme === 'dark' ? (
13-
<AiFillFire
14-
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
15-
className="text-amber-500 dark:text-amber-500 text-2xl cursor-pointer"
16-
title='Dark Mode'
17-
/>
18-
) : (
11+
<div data-testid="theme-toggle" className="transition duration-500 ease-in-out p-1 mx-0 md:mx-16 ml-2 md:ml-5 border-dashed border-2 rounded-full border-yellow-300">
12+
{
13+
theme === 'dark'
14+
? (
15+
<AiFillFire
16+
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
17+
className="text-amber-500 dark:text-amber-500 text-2xl cursor-pointer"
18+
title='Dark Mode'
19+
/>
20+
)
21+
: (
1922
<AiOutlineFire
2023
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
2124
className="text-amber-500 dark:text-amber-500 text-2xl cursor-pointer"
2225
title='Light Mode'
2326
/>
24-
)}
27+
)
28+
}
2529
</div>
2630
);
2731
};

src/Components/DeleteSelectedBtn.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const DeleteSelectedBtn = ({ users, setUsers }) => {
1616

1717
return (
1818
<button
19+
data-testid="delete-selected-btn"
1920
id="first"
2021
type="button"
2122
title="Delete selected"

src/Components/DisplayRows.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const DisplayRows = ({ user, users, setUsers, setEditUserId, setEditUserForm })
3333
};
3434

3535
return (
36-
<tr id={user.id} className={`data-row bg-white dark:bg-dark whitespace-nowrap hover:cursor-default hover:bg-blue-light dark:hover:bg-blue-dark hover:text-white ${user?.isChecked ? 'bg-gray-100 dark:bg-gray-800' : null}`}>
36+
<tr data-testid="display-rows" id={user.id} className={`data-row bg-white dark:bg-dark whitespace-nowrap hover:cursor-default hover:bg-blue-light dark:hover:bg-blue-dark hover:text-white ${user?.isChecked ? 'bg-gray-100 dark:bg-gray-800' : null}`}>
3737
<td className="px-6 py-2 whitespace-nowrap text-center">
3838
<input
3939
type="checkbox"

src/Components/EditableRow.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ const EditableRow = ({ user, editUserForm ,setEditUserForm, setEditUserId }) =>
2222
};
2323

2424
return (
25-
<tr id={user.id} className="data-row bg-white dark:bg-dark whitespace-nowrap hover:cursor-default hover:bg-blue-light dark:hover:bg-blue-dark hover:text-gray">
25+
<tr data-testid="editable-rows" id={user.id} className="data-row bg-white dark:bg-dark whitespace-nowrap hover:cursor-default hover:bg-blue-light dark:hover:bg-blue-dark hover:text-gray">
2626
<td className="px-6 py-2 whitespace-nowrap text-center">
2727
<input
2828
type="checkbox"

src/Components/Modal.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import React from 'react'
2+
import icon from "../Images/1140-error-outline.webp";
3+
4+
const Modal = () => {
5+
return (
6+
<div className="bg-slate-800 bg-opacity-50 flex justify-center items-center z-50 absolute top-0 right-0 bottom-0 left-0">
7+
<div className="bg-red-100 text-red-600 border border-red-200 px-2 md:px-16 py-6 md:py-10 rounded-md text-center" role="alert">
8+
<div className="flex justify-center items-center mb-4">
9+
<img src={icon} alt="error" className="w-20" />
10+
</div>
11+
<h1 className="text-xl mb-4 font-bold">Failed to load Data from Server</h1>
12+
<p className="text-sm">Please check your internet connection and try again</p>
13+
</div>
14+
</div>
15+
)
16+
}
17+
18+
export default Modal

0 commit comments

Comments
 (0)