diff --git a/app/assets/locales/en.json b/app/assets/locales/en.json index 40991f2bcb0a4475f7e7af648199cc07a4fa33c0..9de95263f4134f0914d4330d89a826029bff1a16 100644 --- a/app/assets/locales/en.json +++ b/app/assets/locales/en.json @@ -110,7 +110,6 @@ "shared_by": "shared by ", "last_session": "Last Session: {{ room.last_session }}", "no_last_session": "No previous session created", - "no_rooms_found": "No Rooms found", "search_not_found": "No Rooms found", "rooms_list_is_empty": "You don't have any rooms yet!", "rooms_list_empty_create_room": "Create your first room by clicking on the button below and entering a room name.", @@ -207,6 +206,14 @@ "user_created_at": "Created: {{user.created_at}}", "are_you_sure_delete_account": "Are you sure you want to delete {{user.name}}'s account?", "delete_account_warning": "If you choose to delete this account, it will NOT be recoverable.", + "empty_active_users": "There are no active users on this server yet!", + "empty_active_users_subtext": "When a user's status gets changed to active, they will appear here.", + "empty_pending_users": "There are no pending users on this server yet!", + "empty_pending_users_subtext": "When a user's status gets changed to pending, they will appear here.", + "empty_banned_users": "There are no banned users on this server yet!", + "empty_banned_users_subtext": "When a user's status gets changed to banned, they will appear here.", + "empty_invited_users": "There are no invited users on this server yet!", + "empty_invited_users_subtext": "When a user's status gets changed to invited, they will appear here.", "invited": { "time_sent": "Time Sent", "valid": "Valid" @@ -226,7 +233,9 @@ "last_session": "Last Session: {{lastSession}}", "no_meeting_yet": "No meeting yet.", "delete_server_rooms": "Delete Server Room", - "resync_recordings": "Re-Sync Recordings" + "resync_recordings": "Re-Sync Recordings", + "empty_room_list": "There are no server rooms yet!", + "empty_room_list_subtext": "Rooms will appear here after creating your first room." }, "server_recordings": { "server_recordings": "Server Recordings", diff --git a/app/assets/stylesheets/admin_panel.scss b/app/assets/stylesheets/admin_panel.scss index 07742bc82809f0f8a53d0ca34179104b1c9e6f77..0bba42030eaf9bd4ef92fdd90806d7c850673888 100644 --- a/app/assets/stylesheets/admin_panel.scss +++ b/app/assets/stylesheets/admin_panel.scss @@ -200,3 +200,9 @@ box-shadow: none !important; } } + +#list-empty { + svg { + padding-top: 2rem; + } +} diff --git a/app/javascript/components/admin/manage_users/BannedPendingUsersTable.jsx b/app/javascript/components/admin/manage_users/BannedPendingUsersTable.jsx index 47c3aa21ebcfd12805afd8ee6f9e2c1354493dcc..9c1da0602b640552b2978c844676b58925954b60 100644 --- a/app/javascript/components/admin/manage_users/BannedPendingUsersTable.jsx +++ b/app/javascript/components/admin/manage_users/BannedPendingUsersTable.jsx @@ -3,11 +3,19 @@ import { Table } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; import PropTypes from 'prop-types'; import BannedPendingRow from './BannedPendingRow'; +import EmptyUsersList from './EmptyUsersList'; // pendingTable prop is true when table is being used for pending data, false when table is being used for banned data export default function BannedPendingUsersTable({ users, pendingTable }) { const { t } = useTranslation(); + if (users.length === 0) { + if (pendingTable) { + return <EmptyUsersList text={t('admin.manage_users.empty_pending_users')} subtext={t('admin.manage_users.empty_pending_users_subtext')} />; + } + return <EmptyUsersList text={t('admin.manage_users.empty_banned_users')} subtext={t('admin.manage_users.empty_banned_users_subtext')} />; + } + return ( <div id="admin-table"> <Table className="table-bordered border border-2 mb-0" hover responsive> @@ -20,17 +28,10 @@ export default function BannedPendingUsersTable({ users, pendingTable }) { </thead> <tbody className="border-top-0"> {users?.length - ? ( + && ( users?.map((user) => ( <BannedPendingRow key={user.id} user={user} pendingTable={pendingTable} /> )) - ) - : ( - <tr> - <td className="fw-bold" colSpan="6"> - { t('user.no_user_found') } - </td> - </tr> )} </tbody> </Table> diff --git a/app/javascript/components/admin/manage_users/EmptyUsersList.jsx b/app/javascript/components/admin/manage_users/EmptyUsersList.jsx new file mode 100644 index 0000000000000000000000000000000000000000..cd9230348a4d97013c767d29bee2e436ca6749d9 --- /dev/null +++ b/app/javascript/components/admin/manage_users/EmptyUsersList.jsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { Card } from 'react-bootstrap'; +import { UsersIcon } from '@heroicons/react/24/outline'; +import PropTypes from 'prop-types'; + +export default function EmptyUsersList({ text, subtext }) { + return ( + <div id="list-empty"> + <Card className="border-0 text-center"> + <Card.Body className="py-5"> + <div className="icon-circle rounded-circle d-block mx-auto mb-3"> + <UsersIcon className="hi-l text-brand d-block mx-auto" /> + </div> + <Card.Title className="text-brand"> {text}</Card.Title> + <Card.Text> + {subtext} + </Card.Text> + </Card.Body> + </Card> + </div> + ); +} + +EmptyUsersList.propTypes = { + text: PropTypes.string.isRequired, + subtext: PropTypes.string.isRequired, +}; diff --git a/app/javascript/components/admin/manage_users/InvitedUsers.jsx b/app/javascript/components/admin/manage_users/InvitedUsers.jsx index 3f99ad53cbb29c9ad4f0920eb116145d3e101d5e..fa476d8d7bc478c5626685222fce4da5c817efc2 100644 --- a/app/javascript/components/admin/manage_users/InvitedUsers.jsx +++ b/app/javascript/components/admin/manage_users/InvitedUsers.jsx @@ -7,12 +7,17 @@ import SortBy from '../../shared_components/search/SortBy'; import useInvitations from '../../../hooks/queries/admin/manage_users/useInvitations'; import Pagination from '../../shared_components/Pagination'; import NoSearchResults from '../../shared_components/search/NoSearchResults'; +import EmptyUsersList from './EmptyUsersList'; export default function InvitedUsers({ searchInput }) { const { t } = useTranslation(); const [page, setPage] = useState(); const { data: invitations } = useInvitations(searchInput, page); + if (!searchInput && invitations?.data?.length === 0) { + return <EmptyUsersList text={t('admin.manage_users.empty_invited_users')} subtext={t('admin.manage_users.empty_invited_users_subtext')} />; + } + return ( <div> { @@ -33,7 +38,7 @@ export default function InvitedUsers({ searchInput }) { </thead> <tbody className="border-top-0"> {invitations?.data?.length - ? ( + && ( invitations?.data?.map((invitation) => ( <tr key={invitation.email} className="align-middle text-muted"> <td className="text-dark border-0">{invitation.email}</td> @@ -43,13 +48,6 @@ export default function InvitedUsers({ searchInput }) { </td> </tr> )) - ) - : ( - <tr> - <td className="fw-bold"> - { t('user.no_user_found') } - </td> - </tr> )} </tbody> </Table> diff --git a/app/javascript/components/admin/manage_users/ManageUsersTable.jsx b/app/javascript/components/admin/manage_users/ManageUsersTable.jsx index 10ed68ac31c6f29b2270864d5971f449e677ed2c..84b66ff43b9e6975c2086e40e1830dcdedf4fd35 100644 --- a/app/javascript/components/admin/manage_users/ManageUsersTable.jsx +++ b/app/javascript/components/admin/manage_users/ManageUsersTable.jsx @@ -6,9 +6,15 @@ import ManageUserRow from './ManageUserRow'; import SortBy from '../../shared_components/search/SortBy'; import ManageUsersRowPlaceHolder from './ManageUsersRowPlaceHolder'; +import EmptyUsersList from './EmptyUsersList'; + export default function ManageUsersTable({ users, isLoading }) { const { t } = useTranslation(); + if (!isLoading && users.length === 0) { + return <EmptyUsersList text={t('admin.manage_users.empty_active_users')} subtext={t('admin.manage_users.empty_active_users_subtext')} />; + } + return ( <Table id="manage-users-table" className="table-bordered border border-2 mb-0" hover responsive> <thead> @@ -28,16 +34,9 @@ export default function ManageUsersTable({ users, isLoading }) { ) : ( users?.length - ? ( + && ( users?.map((user) => <ManageUserRow key={user.id} user={user} />) ) - : ( - <tr> - <td className="fw-bold" colSpan="6"> - {t('user.no_user_found')} - </td> - </tr> - ) ) } </tbody> diff --git a/app/javascript/components/admin/server_rooms/ServerRooms.jsx b/app/javascript/components/admin/server_rooms/ServerRooms.jsx index 0db9d49483f48f66e9110f23cd0902def475813c..8d15e21339cea54ca74f59d388ad83db29a904d0 100644 --- a/app/javascript/components/admin/server_rooms/ServerRooms.jsx +++ b/app/javascript/components/admin/server_rooms/ServerRooms.jsx @@ -14,6 +14,7 @@ import SortBy from '../../shared_components/search/SortBy'; import { useAuth } from '../../../contexts/auth/AuthProvider'; import ServerRoomsRowPlaceHolder from './ServerRoomsRowPlaceHolder'; import NoSearchResults from '../../shared_components/search/NoSearchResults'; +import EmptyServerRoomsList from '../../rooms/EmptyServerRoomsList'; export default function ServerRooms() { const { t } = useTranslation(); @@ -46,65 +47,61 @@ export default function ServerRooms() { <div className="px-4 pt-4"> <SearchBar searchInput={searchInput} setSearchInput={setSearchInput} /> </div> - { - (searchInput && serverRooms?.data.length === 0) + (!searchInput && serverRooms?.data.length === 0) ? ( - <div className="mt-5"> - <NoSearchResults text={t('room.search_not_found')} searchInput={searchInput} /> - </div> + <EmptyServerRoomsList /> ) : ( - <div className="p-4"> - - <Table id="server-rooms-table" className="table-bordered border border-2 mt-4 mb-0" hover responsive> - <thead> - <tr className="text-muted small"> - <th className="fw-normal border-end-0">{ t('admin.server_rooms.name') }<SortBy fieldName="name" /></th> - <th className="fw-normal border-0">{ t('admin.server_rooms.owner') }<SortBy fieldName="users.name" /></th> - <th className="fw-normal border-0">{ t('admin.server_rooms.room_id') }</th> - <th className="fw-normal border-0">{ t('admin.server_rooms.participants') }</th> - <th className="fw-normal border-0">{ t('admin.server_rooms.status') }</th> - <th className="border-start-0" aria-label="options" /> - </tr> - </thead> - <tbody className="border-top-0"> - - { - isLoading + (searchInput && serverRooms?.data.length === 0) ? ( - // eslint-disable-next-line react/no-array-index-key - [...Array(10)].map((val, idx) => <ServerRoomsRowPlaceHolder key={idx} />) - ) - : ( - serverRooms?.data.length - ? ( - serverRooms?.data.map((room) => <ServerRoomRow key={room.friendly_id} room={room} />) - ) - : ( - <tr> - <td className="fw-bold" colSpan="6"> - { t('room.no_rooms_found') } - </td> - </tr> - ) - ) - } + <div className="mt-5"> + <NoSearchResults text={t('room.search_not_found')} searchInput={searchInput} /> + </div> + ) : ( + <div className="p-4"> + + <Table id="server-rooms-table" className="table-bordered border border-2 mt-4 mb-0" hover responsive> + <thead> + <tr className="text-muted small"> + <th className="fw-normal border-end-0">{ t('admin.server_rooms.name') }<SortBy fieldName="name" /></th> + <th className="fw-normal border-0">{ t('admin.server_rooms.owner') }<SortBy fieldName="users.name" /></th> + <th className="fw-normal border-0">{ t('admin.server_rooms.room_id') }</th> + <th className="fw-normal border-0">{ t('admin.server_rooms.participants') }</th> + <th className="fw-normal border-0">{ t('admin.server_rooms.status') }</th> + <th className="border-start-0" aria-label="options" /> + </tr> + </thead> - </tbody> - </Table> - {!isLoading - && ( - <Pagination - page={serverRooms.meta.page} - totalPages={serverRooms.meta.pages} - setPage={setPage} - /> - )} + <tbody className="border-top-0"> - </div> + { + isLoading + ? ( + // eslint-disable-next-line react/no-array-index-key + [...Array(10)].map((val, idx) => <ServerRoomsRowPlaceHolder key={idx} />) + ) + : ( + serverRooms?.data.length + && ( + serverRooms?.data.map((room) => <ServerRoomRow key={room.friendly_id} room={room} />) + ) + ) + } + </tbody> + </Table> + {!isLoading + && ( + <Pagination + page={serverRooms.meta.page} + totalPages={serverRooms.meta.pages} + setPage={setPage} + /> + )} + </div> + ) ) -} + } </Container> </Tab.Content> </Col> diff --git a/app/javascript/components/rooms/EmptyServerRoomsList.jsx b/app/javascript/components/rooms/EmptyServerRoomsList.jsx new file mode 100644 index 0000000000000000000000000000000000000000..18e03196ddae8d99a7a8603e3968fa73fd34ce60 --- /dev/null +++ b/app/javascript/components/rooms/EmptyServerRoomsList.jsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { Card } from 'react-bootstrap'; +import { useTranslation } from 'react-i18next'; + +import UserBoardIcon from './UserBoardIcon'; + +export default function EmptyServerRoomsList() { + const { t } = useTranslation(); + + return ( + <div id="list-empty"> + <Card className="border-0 text-center"> + <Card.Body className="py-5"> + <div className="icon-circle rounded-circle d-block mx-auto mb-3"> + <UserBoardIcon className="hi-l text-brand d-block mx-auto" /> + </div> + <Card.Title className="text-brand"> { t('admin.server_rooms.empty_room_list') } </Card.Title> + <Card.Text> + { t('admin.server_rooms.empty_room_list_subtext') } + </Card.Text> + </Card.Body> + </Card> + </div> + ); +}