mirror of
https://github.com/NginxProxyManager/nginx-proxy-manager.git
synced 2025-09-14 10:52:34 +00:00
User table polishing, user delete modal
This commit is contained in:
@@ -13,7 +13,7 @@ import { global as logger } from "../logger.js";
|
|||||||
const ALGO = "RS256";
|
const ALGO = "RS256";
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
let token_data = {};
|
let tokenData = {};
|
||||||
|
|
||||||
const self = {
|
const self = {
|
||||||
/**
|
/**
|
||||||
@@ -37,7 +37,7 @@ export default () => {
|
|||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
} else {
|
} else {
|
||||||
token_data = payload;
|
tokenData = payload;
|
||||||
resolve({
|
resolve({
|
||||||
token: token,
|
token: token,
|
||||||
payload: payload,
|
payload: payload,
|
||||||
@@ -72,18 +72,18 @@ export default () => {
|
|||||||
reject(err);
|
reject(err);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
token_data = result;
|
tokenData = result;
|
||||||
|
|
||||||
// Hack: some tokens out in the wild have a scope of 'all' instead of 'user'.
|
// Hack: some tokens out in the wild have a scope of 'all' instead of 'user'.
|
||||||
// For 30 days at least, we need to replace 'all' with user.
|
// For 30 days at least, we need to replace 'all' with user.
|
||||||
if (
|
if (
|
||||||
typeof token_data.scope !== "undefined" &&
|
typeof tokenData.scope !== "undefined" &&
|
||||||
_.indexOf(token_data.scope, "all") !== -1
|
_.indexOf(tokenData.scope, "all") !== -1
|
||||||
) {
|
) {
|
||||||
token_data.scope = ["user"];
|
tokenData.scope = ["user"];
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(token_data);
|
resolve(tokenData);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -100,15 +100,15 @@ export default () => {
|
|||||||
* @param {String} scope
|
* @param {String} scope
|
||||||
* @returns {Boolean}
|
* @returns {Boolean}
|
||||||
*/
|
*/
|
||||||
hasScope: (scope) => typeof token_data.scope !== "undefined" && _.indexOf(token_data.scope, scope) !== -1,
|
hasScope: (scope) => typeof tokenData.scope !== "undefined" && _.indexOf(tokenData.scope, scope) !== -1,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {String} key
|
* @param {String} key
|
||||||
* @return {*}
|
* @return {*}
|
||||||
*/
|
*/
|
||||||
get: (key) => {
|
get: (key) => {
|
||||||
if (typeof token_data[key] !== "undefined") {
|
if (typeof tokenData[key] !== "undefined") {
|
||||||
return token_data[key];
|
return tokenData[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -119,7 +119,7 @@ export default () => {
|
|||||||
* @param {*} value
|
* @param {*} value
|
||||||
*/
|
*/
|
||||||
set: (key, value) => {
|
set: (key, value) => {
|
||||||
token_data[key] = value;
|
tokenData[key] = value;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -66,7 +66,9 @@ export function SiteHeader() {
|
|||||||
<div className="d-none d-xl-block ps-2">
|
<div className="d-none d-xl-block ps-2">
|
||||||
<div>{currentUser?.nickname}</div>
|
<div>{currentUser?.nickname}</div>
|
||||||
<div className="mt-1 small text-secondary">
|
<div className="mt-1 small text-secondary">
|
||||||
{intl.formatMessage({ id: isAdmin ? "administrator" : "standard-user" })}
|
{intl.formatMessage({
|
||||||
|
id: isAdmin ? "role.admin" : "role.standard-user",
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
@@ -10,9 +10,9 @@ export function DomainsFormatter({ domains, createdOn }: Props) {
|
|||||||
<div className="flex-fill">
|
<div className="flex-fill">
|
||||||
<div className="font-weight-medium">
|
<div className="font-weight-medium">
|
||||||
{domains.map((domain: string) => (
|
{domains.map((domain: string) => (
|
||||||
<span key={domain} className="badge badge-lg domain-name">
|
<a key={domain} href={`http://${domain}`} className="badge bg-yellow-lt domain-name">
|
||||||
{domain}
|
{domain}
|
||||||
</span>
|
</a>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{createdOn ? (
|
{createdOn ? (
|
||||||
|
10
frontend/src/components/Table/Formatter/EmailFormatter.tsx
Normal file
10
frontend/src/components/Table/Formatter/EmailFormatter.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
interface Props {
|
||||||
|
email: string;
|
||||||
|
}
|
||||||
|
export function EmailFormatter({ email }: Props) {
|
||||||
|
return (
|
||||||
|
<a href={`mailto:${email}`} className="badge bg-yellow-lt">
|
||||||
|
{email}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
20
frontend/src/components/Table/Formatter/RolesFormatter.tsx
Normal file
20
frontend/src/components/Table/Formatter/RolesFormatter.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { intl } from "src/locale";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
roles: string[];
|
||||||
|
}
|
||||||
|
export function RolesFormatter({ roles }: Props) {
|
||||||
|
const r = roles || [];
|
||||||
|
if (r.length === 0) {
|
||||||
|
r[0] = "standard-user";
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{r.map((role: string) => (
|
||||||
|
<span key={role} className="badge bg-yellow-lt me-1">
|
||||||
|
{intl.formatMessage({ id: `role.${role}` })}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@@ -4,16 +4,19 @@ import { intl } from "src/locale";
|
|||||||
interface Props {
|
interface Props {
|
||||||
value: string;
|
value: string;
|
||||||
createdOn?: string;
|
createdOn?: string;
|
||||||
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
export function ValueWithDateFormatter({ value, createdOn }: Props) {
|
export function ValueWithDateFormatter({ value, createdOn, disabled }: Props) {
|
||||||
return (
|
return (
|
||||||
<div className="flex-fill">
|
<div className="flex-fill">
|
||||||
<div className="font-weight-medium">
|
<div className="font-weight-medium">
|
||||||
<div className="font-weight-medium">{value}</div>
|
<div className={`font-weight-medium ${disabled ? "text-red" : ""}`}>{value}</div>
|
||||||
</div>
|
</div>
|
||||||
{createdOn ? (
|
{createdOn ? (
|
||||||
<div className="text-secondary mt-1">
|
<div className={`text-secondary mt-1 ${disabled ? "text-red" : ""}`}>
|
||||||
{intl.formatMessage({ id: "created-on" }, { date: intlFormat(parseISO(createdOn)) })}
|
{disabled
|
||||||
|
? intl.formatMessage({ id: "disabled" })
|
||||||
|
: intl.formatMessage({ id: "created-on" }, { date: intlFormat(parseISO(createdOn)) })}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
export * from "./CertificateFormatter";
|
export * from "./CertificateFormatter";
|
||||||
export * from "./DomainsFormatter";
|
export * from "./DomainsFormatter";
|
||||||
|
export * from "./EmailFormatter";
|
||||||
export * from "./GravatarFormatter";
|
export * from "./GravatarFormatter";
|
||||||
|
export * from "./RolesFormatter";
|
||||||
export * from "./StatusFormatter";
|
export * from "./StatusFormatter";
|
||||||
export * from "./ValueWithDateFormatter";
|
export * from "./ValueWithDateFormatter";
|
||||||
|
@@ -12,7 +12,6 @@
|
|||||||
"action.edit": "Edit",
|
"action.edit": "Edit",
|
||||||
"action.enable": "Enable",
|
"action.enable": "Enable",
|
||||||
"action.permissions": "Permissions",
|
"action.permissions": "Permissions",
|
||||||
"administrator": "Administrator",
|
|
||||||
"auditlog.title": "Audit Log",
|
"auditlog.title": "Audit Log",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"certificates.title": "SSL Certificates",
|
"certificates.title": "SSL Certificates",
|
||||||
@@ -37,6 +36,7 @@
|
|||||||
"dead-hosts.count": "{count} 404 Hosts",
|
"dead-hosts.count": "{count} 404 Hosts",
|
||||||
"dead-hosts.empty": "There are no 404 Hosts",
|
"dead-hosts.empty": "There are no 404 Hosts",
|
||||||
"dead-hosts.title": "404 Hosts",
|
"dead-hosts.title": "404 Hosts",
|
||||||
|
"disabled": "Disabled",
|
||||||
"email-address": "Email address",
|
"email-address": "Email address",
|
||||||
"empty-subtitle": "Why don't you create one?",
|
"empty-subtitle": "Why don't you create one?",
|
||||||
"error.invalid-auth": "Invalid email or password",
|
"error.invalid-auth": "Invalid email or password",
|
||||||
@@ -53,6 +53,7 @@
|
|||||||
"notfound.title": "Oops… You just found an error page",
|
"notfound.title": "Oops… You just found an error page",
|
||||||
"notification.error": "Error",
|
"notification.error": "Error",
|
||||||
"notification.success": "Success",
|
"notification.success": "Success",
|
||||||
|
"notification.user-deleted": "User has been deleted",
|
||||||
"notification.user-saved": "User has been saved",
|
"notification.user-saved": "User has been saved",
|
||||||
"offline": "Offline",
|
"offline": "Offline",
|
||||||
"online": "Online",
|
"online": "Online",
|
||||||
@@ -67,10 +68,11 @@
|
|||||||
"redirection-hosts.count": "{count} Redirection Hosts",
|
"redirection-hosts.count": "{count} Redirection Hosts",
|
||||||
"redirection-hosts.empty": "There are no Redirection Hosts",
|
"redirection-hosts.empty": "There are no Redirection Hosts",
|
||||||
"redirection-hosts.title": "Redirection Hosts",
|
"redirection-hosts.title": "Redirection Hosts",
|
||||||
|
"role.admin": "Administrator",
|
||||||
|
"role.standard-user": "Standard User",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"settings.title": "Settings",
|
"settings.title": "Settings",
|
||||||
"sign-in": "Sign in",
|
"sign-in": "Sign in",
|
||||||
"standard-user": "Apache Helicopter",
|
|
||||||
"streams.actions-title": "Stream #{id}",
|
"streams.actions-title": "Stream #{id}",
|
||||||
"streams.add": "Add Stream",
|
"streams.add": "Add Stream",
|
||||||
"streams.count": "{count} Streams",
|
"streams.count": "{count} Streams",
|
||||||
@@ -81,8 +83,11 @@
|
|||||||
"user.change-password": "Change Password",
|
"user.change-password": "Change Password",
|
||||||
"user.confirm-password": "Confirm Password",
|
"user.confirm-password": "Confirm Password",
|
||||||
"user.current-password": "Current Password",
|
"user.current-password": "Current Password",
|
||||||
|
"user.delete.content": "Are you sure you want to delete this user?",
|
||||||
|
"user.delete.title": "Delete User",
|
||||||
"user.edit": "Edit User",
|
"user.edit": "Edit User",
|
||||||
"user.edit-profile": "Edit Profile",
|
"user.edit-profile": "Edit Profile",
|
||||||
|
"user.flags.title": "Properties",
|
||||||
"user.full-name": "Full Name",
|
"user.full-name": "Full Name",
|
||||||
"user.logout": "Logout",
|
"user.logout": "Logout",
|
||||||
"user.new": "New User",
|
"user.new": "New User",
|
||||||
|
@@ -38,9 +38,6 @@
|
|||||||
"action.permissions": {
|
"action.permissions": {
|
||||||
"defaultMessage": "Permissions"
|
"defaultMessage": "Permissions"
|
||||||
},
|
},
|
||||||
"administrator": {
|
|
||||||
"defaultMessage": "Administrator"
|
|
||||||
},
|
|
||||||
"auditlog.title": {
|
"auditlog.title": {
|
||||||
"defaultMessage": "Audit Log"
|
"defaultMessage": "Audit Log"
|
||||||
},
|
},
|
||||||
@@ -113,6 +110,9 @@
|
|||||||
"dead-hosts.title": {
|
"dead-hosts.title": {
|
||||||
"defaultMessage": "404 Hosts"
|
"defaultMessage": "404 Hosts"
|
||||||
},
|
},
|
||||||
|
"disabled": {
|
||||||
|
"defaultMessage": "Disabled"
|
||||||
|
},
|
||||||
"email-address": {
|
"email-address": {
|
||||||
"defaultMessage": "Email address"
|
"defaultMessage": "Email address"
|
||||||
},
|
},
|
||||||
@@ -158,6 +158,9 @@
|
|||||||
"notification.error": {
|
"notification.error": {
|
||||||
"defaultMessage": "Error"
|
"defaultMessage": "Error"
|
||||||
},
|
},
|
||||||
|
"notification.user-deleted": {
|
||||||
|
"defaultMessage": "User has been deleted"
|
||||||
|
},
|
||||||
"notification.user-saved": {
|
"notification.user-saved": {
|
||||||
"defaultMessage": "User has been saved"
|
"defaultMessage": "User has been saved"
|
||||||
},
|
},
|
||||||
@@ -203,6 +206,12 @@
|
|||||||
"redirection-hosts.title": {
|
"redirection-hosts.title": {
|
||||||
"defaultMessage": "Redirection Hosts"
|
"defaultMessage": "Redirection Hosts"
|
||||||
},
|
},
|
||||||
|
"role.admin": {
|
||||||
|
"defaultMessage": "Administrator"
|
||||||
|
},
|
||||||
|
"role.standard-user": {
|
||||||
|
"defaultMessage": "Standard User"
|
||||||
|
},
|
||||||
"save": {
|
"save": {
|
||||||
"defaultMessage": "Save"
|
"defaultMessage": "Save"
|
||||||
},
|
},
|
||||||
@@ -212,9 +221,6 @@
|
|||||||
"sign-in": {
|
"sign-in": {
|
||||||
"defaultMessage": "Sign in"
|
"defaultMessage": "Sign in"
|
||||||
},
|
},
|
||||||
"standard-user": {
|
|
||||||
"defaultMessage": "Apache Helicopter"
|
|
||||||
},
|
|
||||||
"streams.actions-title": {
|
"streams.actions-title": {
|
||||||
"defaultMessage": "Stream #{id}"
|
"defaultMessage": "Stream #{id}"
|
||||||
},
|
},
|
||||||
@@ -245,12 +251,21 @@
|
|||||||
"user.current-password": {
|
"user.current-password": {
|
||||||
"defaultMessage": "Current Password"
|
"defaultMessage": "Current Password"
|
||||||
},
|
},
|
||||||
|
"user.delete.title": {
|
||||||
|
"defaultMessage": "Delete User"
|
||||||
|
},
|
||||||
|
"user.delete.content": {
|
||||||
|
"defaultMessage": "Are you sure you want to delete this user?"
|
||||||
|
},
|
||||||
"user.edit": {
|
"user.edit": {
|
||||||
"defaultMessage": "Edit User"
|
"defaultMessage": "Edit User"
|
||||||
},
|
},
|
||||||
"user.edit-profile": {
|
"user.edit-profile": {
|
||||||
"defaultMessage": "Edit Profile"
|
"defaultMessage": "Edit Profile"
|
||||||
},
|
},
|
||||||
|
"user.flags.title": {
|
||||||
|
"defaultMessage": "Properties"
|
||||||
|
},
|
||||||
"user.full-name": {
|
"user.full-name": {
|
||||||
"defaultMessage": "Full Name"
|
"defaultMessage": "Full Name"
|
||||||
},
|
},
|
||||||
|
65
frontend/src/modals/DeleteConfirmModal.tsx
Normal file
65
frontend/src/modals/DeleteConfirmModal.tsx
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { type ReactNode, useState } from "react";
|
||||||
|
import { Alert } from "react-bootstrap";
|
||||||
|
import Modal from "react-bootstrap/Modal";
|
||||||
|
import { Button } from "src/components";
|
||||||
|
import { intl } from "src/locale";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
title: string;
|
||||||
|
children: ReactNode;
|
||||||
|
onConfirm: () => Promise<void> | void;
|
||||||
|
onClose: () => void;
|
||||||
|
invalidations?: any[];
|
||||||
|
}
|
||||||
|
export function DeleteConfirmModal({ title, children, onConfirm, onClose, invalidations }: Props) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
|
||||||
|
const onSubmit = async () => {
|
||||||
|
setSubmitting(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
await onConfirm();
|
||||||
|
onClose();
|
||||||
|
// invalidate caches as requested
|
||||||
|
invalidations?.forEach((inv) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: inv });
|
||||||
|
});
|
||||||
|
} catch (err: any) {
|
||||||
|
setError(intl.formatMessage({ id: err.message }));
|
||||||
|
}
|
||||||
|
setSubmitting(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal show onHide={onClose} animation={false}>
|
||||||
|
<Modal.Header closeButton>
|
||||||
|
<Modal.Title>{title}</Modal.Title>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Body>
|
||||||
|
<Alert variant="danger" show={!!error} onClose={() => setError(null)} dismissible>
|
||||||
|
{error}
|
||||||
|
</Alert>
|
||||||
|
{children}
|
||||||
|
</Modal.Body>
|
||||||
|
<Modal.Footer>
|
||||||
|
<Button data-bs-dismiss="modal" onClick={onClose} disabled={submitting}>
|
||||||
|
{intl.formatMessage({ id: "cancel" })}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
actionType="primary"
|
||||||
|
className="ms-auto btn-red"
|
||||||
|
data-bs-dismiss="modal"
|
||||||
|
isLoading={submitting}
|
||||||
|
disabled={submitting}
|
||||||
|
onClick={onSubmit}
|
||||||
|
>
|
||||||
|
{intl.formatMessage({ id: "action.delete" })}
|
||||||
|
</Button>
|
||||||
|
</Modal.Footer>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
@@ -18,11 +18,6 @@ export function UserModal({ userId, onClose }: Props) {
|
|||||||
const { mutate: setUser } = useSetUser();
|
const { mutate: setUser } = useSetUser();
|
||||||
const [errorMsg, setErrorMsg] = useState<string | null>(null);
|
const [errorMsg, setErrorMsg] = useState<string | null>(null);
|
||||||
|
|
||||||
if (data && currentUser) {
|
|
||||||
console.log("DATA:", data);
|
|
||||||
console.log("CURRENT:", currentUser);
|
|
||||||
}
|
|
||||||
|
|
||||||
const onSubmit = async (values: any, { setSubmitting }: any) => {
|
const onSubmit = async (values: any, { setSubmitting }: any) => {
|
||||||
setErrorMsg(null);
|
setErrorMsg(null);
|
||||||
const { ...payload } = {
|
const { ...payload } = {
|
||||||
@@ -161,12 +156,13 @@ export function UserModal({ userId, onClose }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
{currentUser && data && currentUser?.id !== data?.id ? (
|
{currentUser && data && currentUser?.id !== data?.id ? (
|
||||||
<div className="my-3">
|
<div className="my-3">
|
||||||
<h3 className="py-2">Properties</h3>
|
<h3 className="py-2">{intl.formatMessage({ id: "user.flags.title" })}</h3>
|
||||||
|
|
||||||
<div className="divide-y">
|
<div className="divide-y">
|
||||||
<div>
|
<div>
|
||||||
<label className="row" htmlFor="isAdmin">
|
<label className="row" htmlFor="isAdmin">
|
||||||
<span className="col">Administrator</span>
|
<span className="col">
|
||||||
|
{intl.formatMessage({ id: "role.admin" })}
|
||||||
|
</span>
|
||||||
<span className="col-auto">
|
<span className="col-auto">
|
||||||
<Field name="isAdmin" type="checkbox">
|
<Field name="isAdmin" type="checkbox">
|
||||||
{({ field }: any) => (
|
{({ field }: any) => (
|
||||||
@@ -185,7 +181,9 @@ export function UserModal({ userId, onClose }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="row" htmlFor="isDisabled">
|
<label className="row" htmlFor="isDisabled">
|
||||||
<span className="col">Disabled</span>
|
<span className="col">
|
||||||
|
{intl.formatMessage({ id: "disabled" })}
|
||||||
|
</span>
|
||||||
<span className="col-auto">
|
<span className="col-auto">
|
||||||
<Field name="isDisabled" type="checkbox">
|
<Field name="isDisabled" type="checkbox">
|
||||||
{({ field }: any) => (
|
{({ field }: any) => (
|
||||||
|
@@ -1,2 +1,3 @@
|
|||||||
export * from "./ChangePasswordModal";
|
export * from "./ChangePasswordModal";
|
||||||
|
export * from "./DeleteConfirmModal";
|
||||||
export * from "./UserModal";
|
export * from "./UserModal";
|
||||||
|
@@ -2,7 +2,7 @@ import { IconDotsVertical, IconEdit, IconLock, IconShield, IconTrash } from "@ta
|
|||||||
import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table";
|
import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import type { User } from "src/api/backend";
|
import type { User } from "src/api/backend";
|
||||||
import { GravatarFormatter, ValueWithDateFormatter } from "src/components";
|
import { EmailFormatter, GravatarFormatter, RolesFormatter, ValueWithDateFormatter } from "src/components";
|
||||||
import { TableLayout } from "src/components/Table/TableLayout";
|
import { TableLayout } from "src/components/Table/TableLayout";
|
||||||
import { intl } from "src/locale";
|
import { intl } from "src/locale";
|
||||||
import Empty from "./Empty";
|
import Empty from "./Empty";
|
||||||
@@ -12,9 +12,10 @@ interface Props {
|
|||||||
isFetching?: boolean;
|
isFetching?: boolean;
|
||||||
currentUserId?: number;
|
currentUserId?: number;
|
||||||
onEditUser?: (id: number) => void;
|
onEditUser?: (id: number) => void;
|
||||||
|
onDeleteUser?: (id: number) => void;
|
||||||
onNewUser?: () => void;
|
onNewUser?: () => void;
|
||||||
}
|
}
|
||||||
export default function Table({ data, isFetching, currentUserId, onEditUser, onNewUser }: Props) {
|
export default function Table({ data, isFetching, currentUserId, onEditUser, onDeleteUser, onNewUser }: Props) {
|
||||||
const columnHelper = createColumnHelper<User>();
|
const columnHelper = createColumnHelper<User>();
|
||||||
const columns = useMemo(
|
const columns = useMemo(
|
||||||
() => [
|
() => [
|
||||||
@@ -34,14 +35,20 @@ export default function Table({ data, isFetching, currentUserId, onEditUser, onN
|
|||||||
cell: (info: any) => {
|
cell: (info: any) => {
|
||||||
const value = info.getValue();
|
const value = info.getValue();
|
||||||
// Hack to reuse domains formatter
|
// Hack to reuse domains formatter
|
||||||
return <ValueWithDateFormatter value={value.name} createdOn={value.createdOn} />;
|
return (
|
||||||
|
<ValueWithDateFormatter
|
||||||
|
value={value.name}
|
||||||
|
createdOn={value.createdOn}
|
||||||
|
disabled={value.isDisabled}
|
||||||
|
/>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor((row: any) => row.email, {
|
columnHelper.accessor((row: any) => row.email, {
|
||||||
id: "email",
|
id: "email",
|
||||||
header: intl.formatMessage({ id: "column.email" }),
|
header: intl.formatMessage({ id: "column.email" }),
|
||||||
cell: (info: any) => {
|
cell: (info: any) => {
|
||||||
return info.getValue();
|
return <EmailFormatter email={info.getValue()} />;
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
// TODO: formatter for roles
|
// TODO: formatter for roles
|
||||||
@@ -49,7 +56,7 @@ export default function Table({ data, isFetching, currentUserId, onEditUser, onN
|
|||||||
id: "roles",
|
id: "roles",
|
||||||
header: intl.formatMessage({ id: "column.roles" }),
|
header: intl.formatMessage({ id: "column.roles" }),
|
||||||
cell: (info: any) => {
|
cell: (info: any) => {
|
||||||
return JSON.stringify(info.getValue());
|
return <RolesFormatter roles={info.getValue()} />;
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
columnHelper.display({
|
columnHelper.display({
|
||||||
@@ -96,7 +103,14 @@ export default function Table({ data, isFetching, currentUserId, onEditUser, onN
|
|||||||
{currentUserId !== info.row.original.id ? (
|
{currentUserId !== info.row.original.id ? (
|
||||||
<>
|
<>
|
||||||
<div className="dropdown-divider" />
|
<div className="dropdown-divider" />
|
||||||
<a className="dropdown-item" href="#">
|
<a
|
||||||
|
className="dropdown-item"
|
||||||
|
href="#"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
onDeleteUser?.(info.row.original.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<IconTrash size={16} />
|
<IconTrash size={16} />
|
||||||
{intl.formatMessage({ id: "action.delete" })}
|
{intl.formatMessage({ id: "action.delete" })}
|
||||||
</a>
|
</a>
|
||||||
@@ -111,7 +125,7 @@ export default function Table({ data, isFetching, currentUserId, onEditUser, onN
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
[columnHelper, currentUserId, onEditUser],
|
[columnHelper, currentUserId, onEditUser, onDeleteUser],
|
||||||
);
|
);
|
||||||
|
|
||||||
const tableInstance = useReactTable<User>({
|
const tableInstance = useReactTable<User>({
|
||||||
|
@@ -1,14 +1,17 @@
|
|||||||
import { IconSearch } from "@tabler/icons-react";
|
import { IconSearch } from "@tabler/icons-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import Alert from "react-bootstrap/Alert";
|
import Alert from "react-bootstrap/Alert";
|
||||||
|
import { deleteUser } from "src/api/backend";
|
||||||
import { Button, LoadingPage } from "src/components";
|
import { Button, LoadingPage } from "src/components";
|
||||||
import { useUser, useUsers } from "src/hooks";
|
import { useUser, useUsers } from "src/hooks";
|
||||||
import { intl } from "src/locale";
|
import { intl } from "src/locale";
|
||||||
import { UserModal } from "src/modals";
|
import { DeleteConfirmModal, UserModal } from "src/modals";
|
||||||
|
import { showSuccess } from "src/notifications";
|
||||||
import Table from "./Table";
|
import Table from "./Table";
|
||||||
|
|
||||||
export default function TableWrapper() {
|
export default function TableWrapper() {
|
||||||
const [editUserId, setEditUserId] = useState(0 as number | "new");
|
const [editUserId, setEditUserId] = useState(0 as number | "new");
|
||||||
|
const [deleteUserId, setDeleteUserId] = useState(0);
|
||||||
const { isFetching, isLoading, isError, error, data } = useUsers(["permissions"]);
|
const { isFetching, isLoading, isError, error, data } = useUsers(["permissions"]);
|
||||||
const { data: currentUser } = useUser("me");
|
const { data: currentUser } = useUser("me");
|
||||||
|
|
||||||
@@ -20,6 +23,11 @@ export default function TableWrapper() {
|
|||||||
return <Alert variant="danger">{error?.message || "Unknown error"}</Alert>;
|
return <Alert variant="danger">{error?.message || "Unknown error"}</Alert>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleDelete = async () => {
|
||||||
|
await deleteUser(deleteUserId);
|
||||||
|
showSuccess(intl.formatMessage({ id: "notification.user-deleted" }));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="card mt-4">
|
<div className="card mt-4">
|
||||||
<div className="card-status-top bg-orange" />
|
<div className="card-status-top bg-orange" />
|
||||||
@@ -54,9 +62,20 @@ export default function TableWrapper() {
|
|||||||
isFetching={isFetching}
|
isFetching={isFetching}
|
||||||
currentUserId={currentUser?.id}
|
currentUserId={currentUser?.id}
|
||||||
onEditUser={(id: number) => setEditUserId(id)}
|
onEditUser={(id: number) => setEditUserId(id)}
|
||||||
|
onDeleteUser={(id: number) => setDeleteUserId(id)}
|
||||||
onNewUser={() => setEditUserId("new")}
|
onNewUser={() => setEditUserId("new")}
|
||||||
/>
|
/>
|
||||||
{editUserId ? <UserModal userId={editUserId} onClose={() => setEditUserId(0)} /> : null}
|
{editUserId ? <UserModal userId={editUserId} onClose={() => setEditUserId(0)} /> : null}
|
||||||
|
{deleteUserId ? (
|
||||||
|
<DeleteConfirmModal
|
||||||
|
title={intl.formatMessage({ id: "user.delete.title" })}
|
||||||
|
onConfirm={handleDelete}
|
||||||
|
onClose={() => setDeleteUserId(0)}
|
||||||
|
invalidations={[["users"], ["user", deleteUserId]]}
|
||||||
|
>
|
||||||
|
{intl.formatMessage({ id: "user.delete.content" })}
|
||||||
|
</DeleteConfirmModal>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
Reference in New Issue
Block a user