Skip to content

Commit 7af01d0

Browse files
committed
Use a modal manager
1 parent e6f7ae3 commit 7af01d0

32 files changed

+291
-251
lines changed

frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"classnames": "^2.5.1",
2525
"country-flag-icons": "^1.5.20",
2626
"date-fns": "^4.1.0",
27+
"ez-modal-react": "^1.0.5",
2728
"formik": "^2.4.6",
2829
"generate-password-browser": "^1.1.0",
2930
"humps": "^2.0.1",

frontend/src/App.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
22
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
3+
import EasyModal from "ez-modal-react";
34
import { RawIntlProvider } from "react-intl";
45
import { ToastContainer } from "react-toastify";
56
import { AuthProvider, LocaleProvider, ThemeProvider } from "src/context";
@@ -16,7 +17,9 @@ function App() {
1617
<ThemeProvider>
1718
<QueryClientProvider client={queryClient}>
1819
<AuthProvider>
19-
<Router />
20+
<EasyModal.Provider>
21+
<Router />
22+
</EasyModal.Provider>
2023
<ToastContainer
2124
position="top-right"
2225
autoClose={5000}

frontend/src/components/SiteHeader.tsx

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
11
import { IconLock, IconLogout, IconUser } from "@tabler/icons-react";
2-
import { useState } from "react";
32
import { LocalePicker, ThemeSwitcher } from "src/components";
43
import { useAuthState } from "src/context";
54
import { useUser } from "src/hooks";
65
import { T } from "src/locale";
7-
import { ChangePasswordModal, UserModal } from "src/modals";
6+
import { showChangePasswordModal, showUserModal } from "src/modals";
87
import styles from "./SiteHeader.module.css";
98

109
export function SiteHeader() {
1110
const { data: currentUser } = useUser("me");
1211
const isAdmin = currentUser?.roles.includes("admin");
1312
const { logout } = useAuthState();
14-
const [showProfileEdit, setShowProfileEdit] = useState(false);
15-
const [showChangePassword, setShowChangePassword] = useState(false);
1613

1714
return (
1815
<header className="navbar navbar-expand-md d-print-none">
@@ -76,7 +73,7 @@ export function SiteHeader() {
7673
className="dropdown-item"
7774
onClick={(e) => {
7875
e.preventDefault();
79-
setShowProfileEdit(true);
76+
showUserModal("me");
8077
}}
8178
>
8279
<IconUser width={18} />
@@ -87,7 +84,7 @@ export function SiteHeader() {
8784
className="dropdown-item"
8885
onClick={(e) => {
8986
e.preventDefault();
90-
setShowChangePassword(true);
87+
showChangePasswordModal("me");
9188
}}
9289
>
9390
<IconLock width={18} />
@@ -110,10 +107,6 @@ export function SiteHeader() {
110107
</div>
111108
</div>
112109
</div>
113-
{showProfileEdit ? <UserModal userId="me" onClose={() => setShowProfileEdit(false)} /> : null}
114-
{showChangePassword ? (
115-
<ChangePasswordModal userId="me" onClose={() => setShowChangePassword(false)} />
116-
) : null}
117110
</header>
118111
);
119112
}

frontend/src/components/Table/Formatter/EventFormatter.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { IconArrowsCross, IconBolt, IconBoltOff, IconDisc, IconUser } from "@tabler/icons-react";
1+
import { IconArrowsCross, IconBolt, IconBoltOff, IconDisc, IconLock, IconUser } from "@tabler/icons-react";
22
import type { AuditLog } from "src/api/backend";
33
import { DateTimeFormat, T } from "src/locale";
44

55
const getEventValue = (event: AuditLog) => {
66
switch (event.objectType) {
7+
case "access-list":
78
case "user":
89
return event.meta?.name;
910
case "proxy-host":
@@ -47,6 +48,9 @@ const getIcon = (row: AuditLog) => {
4748
case "stream":
4849
ico = <IconDisc size={16} className={c} />;
4950
break;
51+
case "access-list":
52+
ico = <IconLock size={16} className={c} />;
53+
break;
5054
}
5155

5256
return ico;

frontend/src/locale/lang/en.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
"error.max-domains": "Too many domains, max is {max}",
8686
"error.passwords-must-match": "Passwords must match",
8787
"error.required": "This is required",
88+
"event.created-access-list": "Created Access List",
8889
"event.created-dead-host": "Created 404 Host",
8990
"event.created-redirection-host": "Created Redirection Host",
9091
"event.created-stream": "Created Stream",
@@ -127,6 +128,7 @@
127128
"notification.host-deleted": "Host has been deleted",
128129
"notification.host-disabled": "Host has been disabled",
129130
"notification.host-enabled": "Host has been enabled",
131+
"notification.proxy-host-saved": "Proxy Host has been saved",
130132
"notification.redirection-host-saved": "Redirection Host has been saved",
131133
"notification.stream-deleted": "Stream has been deleted",
132134
"notification.stream-disabled": "Stream has been disabled",
@@ -148,6 +150,7 @@
148150
"permissions.visibility.all": "All Items",
149151
"permissions.visibility.title": "Item Visibility",
150152
"permissions.visibility.user": "Created Items Only",
153+
"proxy-host.edit": "Edit Proxy Host",
151154
"proxy-host.forward-host": "Forward Hostname / IP",
152155
"proxy-host.new": "New Proxy Host",
153156
"proxy-hosts.actions-title": "Proxy Host #{id}",

frontend/src/locale/src/en.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,9 @@
257257
"error.required": {
258258
"defaultMessage": "This is required"
259259
},
260+
"event.created-access-list": {
261+
"defaultMessage": "Created Access List"
262+
},
260263
"event.created-dead-host": {
261264
"defaultMessage": "Created 404 Host"
262265
},
@@ -383,6 +386,9 @@
383386
"notification.host-enabled": {
384387
"defaultMessage": "Host has been enabled"
385388
},
389+
"notification.proxy-host-saved": {
390+
"defaultMessage": "Proxy Host has been saved"
391+
},
386392
"notification.redirection-host-saved": {
387393
"defaultMessage": "Redirection Host has been saved"
388394
},
@@ -446,6 +452,9 @@
446452
"permissions.visibility.user": {
447453
"defaultMessage": "Created Items Only"
448454
},
455+
"proxy-host.edit": {
456+
"defaultMessage": "Edit Proxy Host"
457+
},
449458
"proxy-host.forward-host": {
450459
"defaultMessage": "Forward Hostname / IP"
451460
},

frontend/src/modals/AccessListModal.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import cn from "classnames";
2+
import EasyModal, { type InnerModalProps } from "ez-modal-react";
23
import { Field, Form, Formik } from "formik";
34
import { type ReactNode, useState } from "react";
45
import { Alert } from "react-bootstrap";
@@ -10,11 +11,14 @@ import { intl, T } from "src/locale";
1011
import { validateString } from "src/modules/Validations";
1112
import { showSuccess } from "src/notifications";
1213

13-
interface Props {
14+
const showAccessListModal = (id: number | "new") => {
15+
EasyModal.show(AccessListModal, { id });
16+
};
17+
18+
interface Props extends InnerModalProps {
1419
id: number | "new";
15-
onClose: () => void;
1620
}
17-
export function AccessListModal({ id, onClose }: Props) {
21+
const AccessListModal = EasyModal.create(({ id, visible, remove }: Props) => {
1822
const { data, isLoading, error } = useAccessList(id, ["items", "clients"]);
1923
const { mutate: setAccessList } = useSetAccessList();
2024
const [errorMsg, setErrorMsg] = useState<ReactNode | null>(null);
@@ -69,7 +73,7 @@ export function AccessListModal({ id, onClose }: Props) {
6973
onError: (err: any) => setErrorMsg(<T id={err.message} />),
7074
onSuccess: () => {
7175
showSuccess(intl.formatMessage({ id: "notification.access-saved" }));
72-
onClose();
76+
remove();
7377
},
7478
onSettled: () => {
7579
setIsSubmitting(false);
@@ -82,7 +86,7 @@ export function AccessListModal({ id, onClose }: Props) {
8286
const toggleEnabled = cn(toggleClasses, "bg-cyan");
8387

8488
return (
85-
<Modal show onHide={onClose} animation={false}>
89+
<Modal show={visible} onHide={remove}>
8690
{!isLoading && error && (
8791
<Alert variant="danger" className="m-3">
8892
{error?.message || "Unknown error"}
@@ -263,7 +267,7 @@ export function AccessListModal({ id, onClose }: Props) {
263267
</div>
264268
</Modal.Body>
265269
<Modal.Footer>
266-
<Button data-bs-dismiss="modal" onClick={onClose} disabled={isSubmitting}>
270+
<Button data-bs-dismiss="modal" onClick={remove} disabled={isSubmitting}>
267271
<T id="cancel" />
268272
</Button>
269273
<Button
@@ -283,4 +287,6 @@ export function AccessListModal({ id, onClose }: Props) {
283287
)}
284288
</Modal>
285289
);
286-
}
290+
});
291+
292+
export { showAccessListModal };

frontend/src/modals/ChangePasswordModal.tsx

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import EasyModal, { type InnerModalProps } from "ez-modal-react";
12
import { Field, Form, Formik } from "formik";
23
import { type ReactNode, useState } from "react";
34
import { Alert } from "react-bootstrap";
@@ -7,11 +8,14 @@ import { Button } from "src/components";
78
import { intl, T } from "src/locale";
89
import { validateString } from "src/modules/Validations";
910

10-
interface Props {
11-
userId: number | "me";
12-
onClose: () => void;
11+
const showChangePasswordModal = (id: number | "me") => {
12+
EasyModal.show(ChangePasswordModal, { id });
13+
};
14+
15+
interface Props extends InnerModalProps {
16+
id: number | "me";
1317
}
14-
export function ChangePasswordModal({ userId, onClose }: Props) {
18+
const ChangePasswordModal = EasyModal.create(({ id, visible, remove }: Props) => {
1519
const [error, setError] = useState<ReactNode | null>(null);
1620
const [isSubmitting, setIsSubmitting] = useState(false);
1721

@@ -27,8 +31,8 @@ export function ChangePasswordModal({ userId, onClose }: Props) {
2731
setError(null);
2832

2933
try {
30-
await updateAuth(userId, values.new, values.current);
31-
onClose();
34+
await updateAuth(id, values.new, values.current);
35+
remove();
3236
} catch (err: any) {
3337
setError(<T id={err.message} />);
3438
}
@@ -37,7 +41,7 @@ export function ChangePasswordModal({ userId, onClose }: Props) {
3741
};
3842

3943
return (
40-
<Modal show onHide={onClose} animation={false}>
44+
<Modal show={visible} onHide={remove}>
4145
<Formik
4246
initialValues={
4347
{
@@ -142,7 +146,7 @@ export function ChangePasswordModal({ userId, onClose }: Props) {
142146
</div>
143147
</Modal.Body>
144148
<Modal.Footer>
145-
<Button data-bs-dismiss="modal" onClick={onClose} disabled={isSubmitting}>
149+
<Button data-bs-dismiss="modal" onClick={remove} disabled={isSubmitting}>
146150
<T id="cancel" />
147151
</Button>
148152
<Button
@@ -161,4 +165,6 @@ export function ChangePasswordModal({ userId, onClose }: Props) {
161165
</Formik>
162166
</Modal>
163167
);
164-
}
168+
});
169+
170+
export { showChangePasswordModal };

frontend/src/modals/DeadHostModal.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { IconSettings } from "@tabler/icons-react";
2+
import EasyModal, { type InnerModalProps } from "ez-modal-react";
23
import { Form, Formik } from "formik";
34
import { type ReactNode, useState } from "react";
45
import { Alert } from "react-bootstrap";
@@ -15,11 +16,14 @@ import { useDeadHost, useSetDeadHost } from "src/hooks";
1516
import { intl, T } from "src/locale";
1617
import { showSuccess } from "src/notifications";
1718

18-
interface Props {
19+
const showDeadHostModal = (id: number | "new") => {
20+
EasyModal.show(DeadHostModal, { id });
21+
};
22+
23+
interface Props extends InnerModalProps {
1924
id: number | "new";
20-
onClose: () => void;
2125
}
22-
export function DeadHostModal({ id, onClose }: Props) {
26+
const DeadHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
2327
const { data, isLoading, error } = useDeadHost(id);
2428
const { mutate: setDeadHost } = useSetDeadHost();
2529
const [errorMsg, setErrorMsg] = useState<ReactNode | null>(null);
@@ -39,7 +43,7 @@ export function DeadHostModal({ id, onClose }: Props) {
3943
onError: (err: any) => setErrorMsg(<T id={err.message} />),
4044
onSuccess: () => {
4145
showSuccess(intl.formatMessage({ id: "notification.dead-host-saved" }));
42-
onClose();
46+
remove();
4347
},
4448
onSettled: () => {
4549
setIsSubmitting(false);
@@ -49,7 +53,7 @@ export function DeadHostModal({ id, onClose }: Props) {
4953
};
5054

5155
return (
52-
<Modal show onHide={onClose} animation={false}>
56+
<Modal show={visible} onHide={remove}>
5357
{!isLoading && error && (
5458
<Alert variant="danger" className="m-3">
5559
{error?.message || "Unknown error"}
@@ -145,7 +149,7 @@ export function DeadHostModal({ id, onClose }: Props) {
145149
</div>
146150
</Modal.Body>
147151
<Modal.Footer>
148-
<Button data-bs-dismiss="modal" onClick={onClose} disabled={isSubmitting}>
152+
<Button data-bs-dismiss="modal" onClick={remove} disabled={isSubmitting}>
149153
<T id="cancel" />
150154
</Button>
151155
<Button
@@ -165,4 +169,6 @@ export function DeadHostModal({ id, onClose }: Props) {
165169
)}
166170
</Modal>
167171
);
168-
}
172+
});
173+
174+
export { showDeadHostModal };

frontend/src/modals/DeleteConfirmModal.tsx

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,25 @@
11
import { useQueryClient } from "@tanstack/react-query";
2+
import EasyModal, { type InnerModalProps } from "ez-modal-react";
23
import { type ReactNode, useState } from "react";
34
import { Alert } from "react-bootstrap";
45
import Modal from "react-bootstrap/Modal";
56
import { Button } from "src/components";
67
import { T } from "src/locale";
78

8-
interface Props {
9+
interface ShowProps {
910
title: string;
1011
children: ReactNode;
1112
onConfirm: () => Promise<void> | void;
12-
onClose: () => void;
1313
invalidations?: any[];
1414
}
15-
export function DeleteConfirmModal({ title, children, onConfirm, onClose, invalidations }: Props) {
15+
16+
interface Props extends InnerModalProps, ShowProps {}
17+
18+
const showDeleteConfirmModal = (props: ShowProps) => {
19+
EasyModal.show(DeleteConfirmModal, props);
20+
};
21+
22+
const DeleteConfirmModal = EasyModal.create(({ title, children, onConfirm, invalidations, visible, remove }: Props) => {
1623
const queryClient = useQueryClient();
1724
const [error, setError] = useState<ReactNode | null>(null);
1825
const [isSubmitting, setIsSubmitting] = useState(false);
@@ -23,7 +30,7 @@ export function DeleteConfirmModal({ title, children, onConfirm, onClose, invali
2330
setError(null);
2431
try {
2532
await onConfirm();
26-
onClose();
33+
remove();
2734
// invalidate caches as requested
2835
invalidations?.forEach((inv) => {
2936
queryClient.invalidateQueries({ queryKey: inv });
@@ -35,7 +42,7 @@ export function DeleteConfirmModal({ title, children, onConfirm, onClose, invali
3542
};
3643

3744
return (
38-
<Modal show onHide={onClose} animation={false}>
45+
<Modal show={visible} onHide={remove}>
3946
<Modal.Header closeButton>
4047
<Modal.Title>
4148
<T id={title} />
@@ -48,7 +55,7 @@ export function DeleteConfirmModal({ title, children, onConfirm, onClose, invali
4855
{children}
4956
</Modal.Body>
5057
<Modal.Footer>
51-
<Button data-bs-dismiss="modal" onClick={onClose} disabled={isSubmitting}>
58+
<Button data-bs-dismiss="modal" onClick={remove} disabled={isSubmitting}>
5259
<T id="cancel" />
5360
</Button>
5461
<Button
@@ -65,4 +72,6 @@ export function DeleteConfirmModal({ title, children, onConfirm, onClose, invali
6572
</Modal.Footer>
6673
</Modal>
6774
);
68-
}
75+
});
76+
77+
export { showDeleteConfirmModal };

0 commit comments

Comments
 (0)