From 7af01d0fc78f9f37c21139a92b7d94ce6cb80a34 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Tue, 14 Oct 2025 17:49:56 +1000 Subject: [PATCH] Use a modal manager --- frontend/package.json | 1 + frontend/src/App.tsx | 5 +- frontend/src/components/SiteHeader.tsx | 13 ++---- .../Table/Formatter/EventFormatter.tsx | 6 ++- frontend/src/locale/lang/en.json | 3 ++ frontend/src/locale/src/en.json | 9 ++++ frontend/src/modals/AccessListModal.tsx | 20 +++++--- frontend/src/modals/ChangePasswordModal.tsx | 24 ++++++---- frontend/src/modals/DeadHostModal.tsx | 20 +++++--- frontend/src/modals/DeleteConfirmModal.tsx | 23 +++++++--- frontend/src/modals/EventDetailsModal.tsx | 18 +++++--- frontend/src/modals/PermissionsModal.tsx | 26 +++++++---- frontend/src/modals/ProxyHostModal.tsx | 20 +++++--- frontend/src/modals/RedirectionHostModal.tsx | 20 +++++--- frontend/src/modals/SetPasswordModal.tsx | 28 ++++++----- frontend/src/modals/StreamModal.tsx | 20 +++++--- frontend/src/modals/UserModal.tsx | 26 +++++++---- frontend/src/pages/Access/Empty.tsx | 2 +- frontend/src/pages/Access/TableWrapper.tsx | 44 +++++++----------- frontend/src/pages/AuditLog/TableWrapper.tsx | 7 +-- frontend/src/pages/Certificates/Empty.tsx | 2 +- frontend/src/pages/Nginx/DeadHosts/Empty.tsx | 2 +- .../pages/Nginx/DeadHosts/TableWrapper.tsx | 34 ++++++-------- frontend/src/pages/Nginx/ProxyHosts/Empty.tsx | 2 +- .../pages/Nginx/ProxyHosts/TableWrapper.tsx | 35 ++++++-------- .../pages/Nginx/RedirectionHosts/Empty.tsx | 2 +- .../Nginx/RedirectionHosts/TableWrapper.tsx | 38 ++++++++------- frontend/src/pages/Nginx/Streams/Empty.tsx | 2 +- .../src/pages/Nginx/Streams/TableWrapper.tsx | 35 ++++++-------- frontend/src/pages/Users/Table.tsx | 4 +- frontend/src/pages/Users/TableWrapper.tsx | 46 +++++++------------ frontend/yarn.lock | 5 ++ 32 files changed, 291 insertions(+), 251 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 8117f959..bf0aed9e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -24,6 +24,7 @@ "classnames": "^2.5.1", "country-flag-icons": "^1.5.20", "date-fns": "^4.1.0", + "ez-modal-react": "^1.0.5", "formik": "^2.4.6", "generate-password-browser": "^1.1.0", "humps": "^2.0.1", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index f2312f3f..b6f0bba7 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,5 +1,6 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import EasyModal from "ez-modal-react"; import { RawIntlProvider } from "react-intl"; import { ToastContainer } from "react-toastify"; import { AuthProvider, LocaleProvider, ThemeProvider } from "src/context"; @@ -16,7 +17,9 @@ function App() { - + + + @@ -76,7 +73,7 @@ export function SiteHeader() { className="dropdown-item" onClick={(e) => { e.preventDefault(); - setShowProfileEdit(true); + showUserModal("me"); }} > @@ -87,7 +84,7 @@ export function SiteHeader() { className="dropdown-item" onClick={(e) => { e.preventDefault(); - setShowChangePassword(true); + showChangePasswordModal("me"); }} > @@ -110,10 +107,6 @@ export function SiteHeader() { - {showProfileEdit ? setShowProfileEdit(false)} /> : null} - {showChangePassword ? ( - setShowChangePassword(false)} /> - ) : null} ); } diff --git a/frontend/src/components/Table/Formatter/EventFormatter.tsx b/frontend/src/components/Table/Formatter/EventFormatter.tsx index a296e04b..6476d717 100644 --- a/frontend/src/components/Table/Formatter/EventFormatter.tsx +++ b/frontend/src/components/Table/Formatter/EventFormatter.tsx @@ -1,9 +1,10 @@ -import { IconArrowsCross, IconBolt, IconBoltOff, IconDisc, IconUser } from "@tabler/icons-react"; +import { IconArrowsCross, IconBolt, IconBoltOff, IconDisc, IconLock, IconUser } from "@tabler/icons-react"; import type { AuditLog } from "src/api/backend"; import { DateTimeFormat, T } from "src/locale"; const getEventValue = (event: AuditLog) => { switch (event.objectType) { + case "access-list": case "user": return event.meta?.name; case "proxy-host": @@ -47,6 +48,9 @@ const getIcon = (row: AuditLog) => { case "stream": ico = ; break; + case "access-list": + ico = ; + break; } return ico; diff --git a/frontend/src/locale/lang/en.json b/frontend/src/locale/lang/en.json index c7a206b6..d94a9427 100644 --- a/frontend/src/locale/lang/en.json +++ b/frontend/src/locale/lang/en.json @@ -85,6 +85,7 @@ "error.max-domains": "Too many domains, max is {max}", "error.passwords-must-match": "Passwords must match", "error.required": "This is required", + "event.created-access-list": "Created Access List", "event.created-dead-host": "Created 404 Host", "event.created-redirection-host": "Created Redirection Host", "event.created-stream": "Created Stream", @@ -127,6 +128,7 @@ "notification.host-deleted": "Host has been deleted", "notification.host-disabled": "Host has been disabled", "notification.host-enabled": "Host has been enabled", + "notification.proxy-host-saved": "Proxy Host has been saved", "notification.redirection-host-saved": "Redirection Host has been saved", "notification.stream-deleted": "Stream has been deleted", "notification.stream-disabled": "Stream has been disabled", @@ -148,6 +150,7 @@ "permissions.visibility.all": "All Items", "permissions.visibility.title": "Item Visibility", "permissions.visibility.user": "Created Items Only", + "proxy-host.edit": "Edit Proxy Host", "proxy-host.forward-host": "Forward Hostname / IP", "proxy-host.new": "New Proxy Host", "proxy-hosts.actions-title": "Proxy Host #{id}", diff --git a/frontend/src/locale/src/en.json b/frontend/src/locale/src/en.json index b2b5cdf1..ebf66ecf 100644 --- a/frontend/src/locale/src/en.json +++ b/frontend/src/locale/src/en.json @@ -257,6 +257,9 @@ "error.required": { "defaultMessage": "This is required" }, + "event.created-access-list": { + "defaultMessage": "Created Access List" + }, "event.created-dead-host": { "defaultMessage": "Created 404 Host" }, @@ -383,6 +386,9 @@ "notification.host-enabled": { "defaultMessage": "Host has been enabled" }, + "notification.proxy-host-saved": { + "defaultMessage": "Proxy Host has been saved" + }, "notification.redirection-host-saved": { "defaultMessage": "Redirection Host has been saved" }, @@ -446,6 +452,9 @@ "permissions.visibility.user": { "defaultMessage": "Created Items Only" }, + "proxy-host.edit": { + "defaultMessage": "Edit Proxy Host" + }, "proxy-host.forward-host": { "defaultMessage": "Forward Hostname / IP" }, diff --git a/frontend/src/modals/AccessListModal.tsx b/frontend/src/modals/AccessListModal.tsx index cadee428..b229ef62 100644 --- a/frontend/src/modals/AccessListModal.tsx +++ b/frontend/src/modals/AccessListModal.tsx @@ -1,4 +1,5 @@ import cn from "classnames"; +import EasyModal, { type InnerModalProps } from "ez-modal-react"; import { Field, Form, Formik } from "formik"; import { type ReactNode, useState } from "react"; import { Alert } from "react-bootstrap"; @@ -10,11 +11,14 @@ import { intl, T } from "src/locale"; import { validateString } from "src/modules/Validations"; import { showSuccess } from "src/notifications"; -interface Props { +const showAccessListModal = (id: number | "new") => { + EasyModal.show(AccessListModal, { id }); +}; + +interface Props extends InnerModalProps { id: number | "new"; - onClose: () => void; } -export function AccessListModal({ id, onClose }: Props) { +const AccessListModal = EasyModal.create(({ id, visible, remove }: Props) => { const { data, isLoading, error } = useAccessList(id, ["items", "clients"]); const { mutate: setAccessList } = useSetAccessList(); const [errorMsg, setErrorMsg] = useState(null); @@ -69,7 +73,7 @@ export function AccessListModal({ id, onClose }: Props) { onError: (err: any) => setErrorMsg(), onSuccess: () => { showSuccess(intl.formatMessage({ id: "notification.access-saved" })); - onClose(); + remove(); }, onSettled: () => { setIsSubmitting(false); @@ -82,7 +86,7 @@ export function AccessListModal({ id, onClose }: Props) { const toggleEnabled = cn(toggleClasses, "bg-cyan"); return ( - + {!isLoading && error && ( {error?.message || "Unknown error"} @@ -263,7 +267,7 @@ export function AccessListModal({ id, onClose }: Props) { - @@ -49,4 +53,6 @@ export function EventDetailsModal({ id, onClose }: Props) { )} ); -} +}); + +export { showEventDetailsModal }; diff --git a/frontend/src/modals/PermissionsModal.tsx b/frontend/src/modals/PermissionsModal.tsx index 4761e08b..5e5d6e6d 100644 --- a/frontend/src/modals/PermissionsModal.tsx +++ b/frontend/src/modals/PermissionsModal.tsx @@ -1,5 +1,6 @@ import { useQueryClient } from "@tanstack/react-query"; import cn from "classnames"; +import EasyModal, { type InnerModalProps } from "ez-modal-react"; import { Field, Form, Formik } from "formik"; import { type ReactNode, useState } from "react"; import { Alert } from "react-bootstrap"; @@ -9,14 +10,17 @@ import { Button, Loading } from "src/components"; import { useUser } from "src/hooks"; import { T } from "src/locale"; -interface Props { - userId: number; - onClose: () => void; +const showPermissionsModal = (id: number) => { + EasyModal.show(PermissionsModal, { id }); +}; + +interface Props extends InnerModalProps { + id: number; } -export function PermissionsModal({ userId, onClose }: Props) { +const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => { const queryClient = useQueryClient(); const [errorMsg, setErrorMsg] = useState(null); - const { data, isLoading, error } = useUser(userId); + const { data, isLoading, error } = useUser(id); const [isSubmitting, setIsSubmitting] = useState(false); const onSubmit = async (values: any, { setSubmitting }: any) => { @@ -24,8 +28,8 @@ export function PermissionsModal({ userId, onClose }: Props) { setIsSubmitting(true); setErrorMsg(null); try { - await setPermissions(userId, values); - onClose(); + await setPermissions(id, values); + remove(); queryClient.invalidateQueries({ queryKey: ["users"] }); queryClient.invalidateQueries({ queryKey: ["user"] }); } catch (err: any) { @@ -86,7 +90,7 @@ export function PermissionsModal({ userId, onClose }: Props) { const isAdmin = data?.roles.indexOf("admin") !== -1; return ( - + {!isLoading && error && ( {error?.message || "Unknown error"} @@ -216,7 +220,7 @@ export function PermissionsModal({ userId, onClose }: Props) { )} - @@ -82,21 +74,17 @@ export default function TableWrapper() { data={filtered ?? data ?? []} isFetching={isFetching} isFiltered={!!filtered} - onEdit={(id: number) => setEditId(id)} - onDelete={(id: number) => setDeleteId(id)} - onNew={() => setEditId("new")} + onEdit={(id: number) => showAccessListModal(id)} + onDelete={(id: number) => + showDeleteConfirmModal({ + title: "access.delete.title", + onConfirm: () => handleDelete(id), + invalidations: [["access-lists"], ["access-list", id]], + children: , + }) + } + onNew={() => showAccessListModal("new")} /> - {editId ? setEditId(0)} /> : null} - {deleteId ? ( - setDeleteId(0)} - invalidations={[["access-lists"], ["access-list", deleteId]]} - > - - - ) : null} ); diff --git a/frontend/src/pages/AuditLog/TableWrapper.tsx b/frontend/src/pages/AuditLog/TableWrapper.tsx index 9fbdcc09..7412aeea 100644 --- a/frontend/src/pages/AuditLog/TableWrapper.tsx +++ b/frontend/src/pages/AuditLog/TableWrapper.tsx @@ -1,13 +1,11 @@ -import { useState } from "react"; import Alert from "react-bootstrap/Alert"; import { LoadingPage } from "src/components"; import { useAuditLogs } from "src/hooks"; import { T } from "src/locale"; -import { EventDetailsModal } from "src/modals"; +import { showEventDetailsModal } from "src/modals"; import Table from "./Table"; export default function TableWrapper() { - const [eventId, setEventId] = useState(0); const { isFetching, isLoading, isError, error, data } = useAuditLogs(["user"]); if (isLoading) { @@ -31,8 +29,7 @@ export default function TableWrapper() { - - {eventId ? setEventId(0)} /> : null} +
); diff --git a/frontend/src/pages/Certificates/Empty.tsx b/frontend/src/pages/Certificates/Empty.tsx index a39d6e74..cae5034f 100644 --- a/frontend/src/pages/Certificates/Empty.tsx +++ b/frontend/src/pages/Certificates/Empty.tsx @@ -14,7 +14,7 @@ export default function Empty({ tableInstance, onNew, onNewCustom, isFiltered }:
{isFiltered ? (

- +

) : ( <> diff --git a/frontend/src/pages/Nginx/DeadHosts/Empty.tsx b/frontend/src/pages/Nginx/DeadHosts/Empty.tsx index e9fb6097..69f1da48 100644 --- a/frontend/src/pages/Nginx/DeadHosts/Empty.tsx +++ b/frontend/src/pages/Nginx/DeadHosts/Empty.tsx @@ -14,7 +14,7 @@ export default function Empty({ tableInstance, onNew, isFiltered }: Props) {
{isFiltered ? (

- +

) : ( <> diff --git a/frontend/src/pages/Nginx/DeadHosts/TableWrapper.tsx b/frontend/src/pages/Nginx/DeadHosts/TableWrapper.tsx index 139c8095..575e7b2b 100644 --- a/frontend/src/pages/Nginx/DeadHosts/TableWrapper.tsx +++ b/frontend/src/pages/Nginx/DeadHosts/TableWrapper.tsx @@ -6,15 +6,13 @@ import { deleteDeadHost, toggleDeadHost } from "src/api/backend"; import { Button, LoadingPage } from "src/components"; import { useDeadHosts } from "src/hooks"; import { intl, T } from "src/locale"; -import { DeadHostModal, DeleteConfirmModal } from "src/modals"; +import { showDeadHostModal, showDeleteConfirmModal } from "src/modals"; import { showSuccess } from "src/notifications"; import Table from "./Table"; export default function TableWrapper() { const queryClient = useQueryClient(); const [search, setSearch] = useState(""); - const [deleteId, setDeleteId] = useState(0); - const [editId, setEditId] = useState(0 as number | "new"); const { isFetching, isLoading, isError, error, data } = useDeadHosts(["owner", "certificate"]); if (isLoading) { @@ -25,8 +23,8 @@ export default function TableWrapper() { return {error?.message || "Unknown error"}; } - const handleDelete = async () => { - await deleteDeadHost(deleteId); + const handleDelete = async (id: number) => { + await deleteDeadHost(id); showSuccess(intl.formatMessage({ id: "notification.host-deleted" })); }; @@ -73,7 +71,7 @@ export default function TableWrapper() { onChange={(e: any) => setSearch(e.target.value.toLowerCase().trim())} />
-
@@ -85,22 +83,18 @@ export default function TableWrapper() { data={filtered ?? data ?? []} isFiltered={!!search} isFetching={isFetching} - onEdit={(id: number) => setEditId(id)} - onDelete={(id: number) => setDeleteId(id)} + onEdit={(id: number) => showDeadHostModal(id)} + onDelete={(id: number) => + showDeleteConfirmModal({ + title: "dead-host.delete.title", + onConfirm: () => handleDelete(id), + invalidations: [["dead-hosts"], ["dead-host", id]], + children: , + }) + } onDisableToggle={handleDisableToggle} - onNew={() => setEditId("new")} + onNew={() => showDeadHostModal("new")} /> - {editId ? setEditId(0)} /> : null} - {deleteId ? ( - setDeleteId(0)} - invalidations={[["dead-hosts"], ["dead-host", deleteId]]} - > - - - ) : null} ); diff --git a/frontend/src/pages/Nginx/ProxyHosts/Empty.tsx b/frontend/src/pages/Nginx/ProxyHosts/Empty.tsx index 6710eef0..bafd186a 100644 --- a/frontend/src/pages/Nginx/ProxyHosts/Empty.tsx +++ b/frontend/src/pages/Nginx/ProxyHosts/Empty.tsx @@ -14,7 +14,7 @@ export default function Empty({ tableInstance, onNew, isFiltered }: Props) {
{isFiltered ? (

- +

) : ( <> diff --git a/frontend/src/pages/Nginx/ProxyHosts/TableWrapper.tsx b/frontend/src/pages/Nginx/ProxyHosts/TableWrapper.tsx index 8cad4935..3c326f08 100644 --- a/frontend/src/pages/Nginx/ProxyHosts/TableWrapper.tsx +++ b/frontend/src/pages/Nginx/ProxyHosts/TableWrapper.tsx @@ -6,15 +6,13 @@ import { deleteProxyHost, toggleProxyHost } from "src/api/backend"; import { Button, LoadingPage } from "src/components"; import { useProxyHosts } from "src/hooks"; import { intl, T } from "src/locale"; -import { DeleteConfirmModal, ProxyHostModal } from "src/modals"; +import { showDeleteConfirmModal, showProxyHostModal } from "src/modals"; import { showSuccess } from "src/notifications"; import Table from "./Table"; export default function TableWrapper() { const queryClient = useQueryClient(); const [search, setSearch] = useState(""); - const [deleteId, setDeleteId] = useState(0); - const [editId, setEditId] = useState(0 as number | "new"); const { isFetching, isLoading, isError, error, data } = useProxyHosts(["owner", "access_list", "certificate"]); if (isLoading) { @@ -25,8 +23,8 @@ export default function TableWrapper() { return {error?.message || "Unknown error"}; } - const handleDelete = async () => { - await deleteProxyHost(deleteId); + const handleDelete = async (id: number) => { + await deleteProxyHost(id); showSuccess(intl.formatMessage({ id: "notification.host-deleted" })); }; @@ -74,9 +72,10 @@ export default function TableWrapper() { type="text" className="form-control form-control-sm" autoComplete="off" + onChange={(e: any) => setSearch(e.target.value.toLowerCase().trim())} />
- @@ -88,22 +87,18 @@ export default function TableWrapper() { data={filtered ?? data ?? []} isFiltered={!!search} isFetching={isFetching} - onEdit={(id: number) => setEditId(id)} - onDelete={(id: number) => setDeleteId(id)} + onEdit={(id: number) => showProxyHostModal(id)} + onDelete={(id: number) => + showDeleteConfirmModal({ + title: "proxy-host.delete.title", + onConfirm: () => handleDelete(id), + invalidations: [["proxy-hosts"], ["proxy-host", id]], + children: , + }) + } onDisableToggle={handleDisableToggle} - onNew={() => setEditId("new")} + onNew={() => showProxyHostModal("new")} /> - {editId ? setEditId(0)} /> : null} - {deleteId ? ( - setDeleteId(0)} - invalidations={[["proxy-hosts"], ["proxy-host", deleteId]]} - > - - - ) : null} ); diff --git a/frontend/src/pages/Nginx/RedirectionHosts/Empty.tsx b/frontend/src/pages/Nginx/RedirectionHosts/Empty.tsx index 09d0211f..405996c6 100644 --- a/frontend/src/pages/Nginx/RedirectionHosts/Empty.tsx +++ b/frontend/src/pages/Nginx/RedirectionHosts/Empty.tsx @@ -14,7 +14,7 @@ export default function Empty({ tableInstance, onNew, isFiltered }: Props) {
{isFiltered ? (

- +

) : ( <> diff --git a/frontend/src/pages/Nginx/RedirectionHosts/TableWrapper.tsx b/frontend/src/pages/Nginx/RedirectionHosts/TableWrapper.tsx index 72979383..00679e5b 100644 --- a/frontend/src/pages/Nginx/RedirectionHosts/TableWrapper.tsx +++ b/frontend/src/pages/Nginx/RedirectionHosts/TableWrapper.tsx @@ -6,15 +6,13 @@ import { deleteRedirectionHost, toggleRedirectionHost } from "src/api/backend"; import { Button, LoadingPage } from "src/components"; import { useRedirectionHosts } from "src/hooks"; import { intl, T } from "src/locale"; -import { DeleteConfirmModal, RedirectionHostModal } from "src/modals"; +import { showDeleteConfirmModal, showRedirectionHostModal } from "src/modals"; import { showSuccess } from "src/notifications"; import Table from "./Table"; export default function TableWrapper() { const queryClient = useQueryClient(); const [search, setSearch] = useState(""); - const [deleteId, setDeleteId] = useState(0); - const [editId, setEditId] = useState(0 as number | "new"); const { isFetching, isLoading, isError, error, data } = useRedirectionHosts(["owner", "certificate"]); if (isLoading) { @@ -25,8 +23,8 @@ export default function TableWrapper() { return {error?.message || "Unknown error"}; } - const handleDelete = async () => { - await deleteRedirectionHost(deleteId); + const handleDelete = async (id: number) => { + await deleteRedirectionHost(id); showSuccess(intl.formatMessage({ id: "notification.host-deleted" })); }; @@ -76,7 +74,11 @@ export default function TableWrapper() { onChange={(e: any) => setSearch(e.target.value.toLowerCase().trim())} />
- @@ -88,22 +90,18 @@ export default function TableWrapper() { data={filtered ?? data ?? []} isFiltered={!!search} isFetching={isFetching} - onEdit={(id: number) => setEditId(id)} - onDelete={(id: number) => setDeleteId(id)} + onEdit={(id: number) => showRedirectionHostModal(id)} + onDelete={(id: number) => + showDeleteConfirmModal({ + title: "redirection-host.delete.title", + onConfirm: () => handleDelete(id), + invalidations: [["redirection-hosts"], ["redirection-host", id]], + children: , + }) + } onDisableToggle={handleDisableToggle} - onNew={() => setEditId("new")} + onNew={() => showRedirectionHostModal("new")} /> - {editId ? setEditId(0)} /> : null} - {deleteId ? ( - setDeleteId(0)} - invalidations={[["redirection-hosts"], ["redirection-host", deleteId]]} - > - - - ) : null} ); diff --git a/frontend/src/pages/Nginx/Streams/Empty.tsx b/frontend/src/pages/Nginx/Streams/Empty.tsx index 732d92b2..7af0be75 100644 --- a/frontend/src/pages/Nginx/Streams/Empty.tsx +++ b/frontend/src/pages/Nginx/Streams/Empty.tsx @@ -14,7 +14,7 @@ export default function Empty({ tableInstance, onNew, isFiltered }: Props) {
{isFiltered ? (

- +

) : ( <> diff --git a/frontend/src/pages/Nginx/Streams/TableWrapper.tsx b/frontend/src/pages/Nginx/Streams/TableWrapper.tsx index 82fc3e63..345b3467 100644 --- a/frontend/src/pages/Nginx/Streams/TableWrapper.tsx +++ b/frontend/src/pages/Nginx/Streams/TableWrapper.tsx @@ -6,15 +6,14 @@ import { deleteStream, toggleStream } from "src/api/backend"; import { Button, LoadingPage } from "src/components"; import { useStreams } from "src/hooks"; import { intl, T } from "src/locale"; -import { DeleteConfirmModal, StreamModal } from "src/modals"; +import { showDeleteConfirmModal, showStreamModal } from "src/modals"; import { showSuccess } from "src/notifications"; import Table from "./Table"; export default function TableWrapper() { const queryClient = useQueryClient(); const [search, setSearch] = useState(""); - const [editId, setEditId] = useState(0 as number | "new"); - const [deleteId, setDeleteId] = useState(0); + const [_deleteId, _setDeleteIdd] = useState(0); const { isFetching, isLoading, isError, error, data } = useStreams(["owner", "certificate"]); if (isLoading) { @@ -25,8 +24,8 @@ export default function TableWrapper() { return {error?.message || "Unknown error"}; } - const handleDelete = async () => { - await deleteStream(deleteId); + const handleDelete = async (id: number) => { + await deleteStream(id); showSuccess(intl.formatMessage({ id: "notification.stream-deleted" })); }; @@ -79,7 +78,7 @@ export default function TableWrapper() { onChange={(e: any) => setSearch(e.target.value.toLowerCase().trim())} />
- @@ -91,22 +90,18 @@ export default function TableWrapper() { data={filtered ?? data ?? []} isFetching={isFetching} isFiltered={!!filtered} - onEdit={(id: number) => setEditId(id)} - onDelete={(id: number) => setDeleteId(id)} + onEdit={(id: number) => showStreamModal(id)} + onDelete={(id: number) => + showDeleteConfirmModal({ + title: "stream.delete.title", + onConfirm: () => handleDelete(id), + invalidations: [["streams"], ["stream", id]], + children: , + }) + } onDisableToggle={handleDisableToggle} - onNew={() => setEditId("new")} + onNew={() => showStreamModal("new")} /> - {editId ? setEditId(0)} /> : null} - {deleteId ? ( - setDeleteId(0)} - invalidations={[["streams"], ["stream", deleteId]]} - > - - - ) : null} ); diff --git a/frontend/src/pages/Users/Table.tsx b/frontend/src/pages/Users/Table.tsx index 870bf85d..4e6fd871 100644 --- a/frontend/src/pages/Users/Table.tsx +++ b/frontend/src/pages/Users/Table.tsx @@ -87,7 +87,7 @@ export default function Table({ }, }), columnHelper.display({ - id: "id", // todo: not needed for a display? + id: "id", cell: (info: any) => { return ( @@ -112,7 +112,7 @@ export default function Table({ }} > - + {currentUserId !== info.row.original.id ? ( <> diff --git a/frontend/src/pages/Users/TableWrapper.tsx b/frontend/src/pages/Users/TableWrapper.tsx index b2768422..c4a6dd0f 100644 --- a/frontend/src/pages/Users/TableWrapper.tsx +++ b/frontend/src/pages/Users/TableWrapper.tsx @@ -6,17 +6,13 @@ import { deleteUser, toggleUser } from "src/api/backend"; import { Button, LoadingPage } from "src/components"; import { useUser, useUsers } from "src/hooks"; import { intl, T } from "src/locale"; -import { DeleteConfirmModal, PermissionsModal, SetPasswordModal, UserModal } from "src/modals"; +import { showDeleteConfirmModal, showPermissionsModal, showSetPasswordModal, showUserModal } from "src/modals"; import { showSuccess } from "src/notifications"; import Table from "./Table"; export default function TableWrapper() { const queryClient = useQueryClient(); const [search, setSearch] = useState(""); - const [editUserId, setEditUserId] = useState(0 as number | "new"); - const [editUserPermissionsId, setEditUserPermissionsId] = useState(0); - const [editUserPasswordId, setEditUserPasswordId] = useState(0); - const [deleteUserId, setDeleteUserId] = useState(0); const { isFetching, isLoading, isError, error, data } = useUsers(["permissions"]); const { data: currentUser } = useUser("me"); @@ -28,8 +24,8 @@ export default function TableWrapper() { return {error?.message || "Unknown error"}; } - const handleDelete = async () => { - await deleteUser(deleteUserId); + const handleDelete = async (id: number) => { + await deleteUser(id); showSuccess(intl.formatMessage({ id: "notification.user-deleted" })); }; @@ -81,7 +77,7 @@ export default function TableWrapper() { /> - @@ -94,30 +90,20 @@ export default function TableWrapper() { isFiltered={!!search} isFetching={isFetching} currentUserId={currentUser?.id} - onEditUser={(id: number) => setEditUserId(id)} - onEditPermissions={(id: number) => setEditUserPermissionsId(id)} - onSetPassword={(id: number) => setEditUserPasswordId(id)} - onDeleteUser={(id: number) => setDeleteUserId(id)} + onEditUser={(id: number) => showUserModal(id)} + onEditPermissions={(id: number) => showPermissionsModal(id)} + onSetPassword={(id: number) => showSetPasswordModal(id)} + onDeleteUser={(id: number) => + showDeleteConfirmModal({ + title: "user.delete.title", + onConfirm: () => handleDelete(id), + invalidations: [["users"], ["user", id]], + children: , + }) + } onDisableToggle={handleDisableToggle} - onNewUser={() => setEditUserId("new")} + onNewUser={() => showUserModal("new")} /> - {editUserId ? setEditUserId(0)} /> : null} - {editUserPermissionsId ? ( - setEditUserPermissionsId(0)} /> - ) : null} - {deleteUserId ? ( - setDeleteUserId(0)} - invalidations={[["users"], ["user", deleteUserId]]} - > - - - ) : null} - {editUserPasswordId ? ( - setEditUserPasswordId(0)} /> - ) : null} ); diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 0c9abba8..aac44c2e 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1530,6 +1530,11 @@ extend@^3.0.0: resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== +ez-modal-react@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/ez-modal-react/-/ez-modal-react-1.0.5.tgz#38d36c5e31f54f6b7cb7afa0cc79a8d1190c2805" + integrity sha512-/A8yLK54tpmWCMkW8Pwqc2xxspmimGOOw/m+1Y+tNtUIheuDHhLynHP1Q0utciJEGDAK849aQcd+6DrJ88hggQ== + fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"