Modals and API calls working for admin tab
This commit is contained in:
70
src/ui/account/AccountAdminButtons.tsx
Normal file
70
src/ui/account/AccountAdminButtons.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import { ButtonProps } from "@/components/button/Button";
|
||||
import DangerButton from "@/components/button/DangerButton";
|
||||
import PrimaryButton from "@/components/button/PrimaryButton";
|
||||
import TertiaryButton from "@/components/button/TertiaryButton";
|
||||
import WarningButton from "@/components/button/WarningButton";
|
||||
import { BsKeyFill, BsLockFill, BsPencilFill, BsTrash3, BsXCircle } from "react-icons/bs";
|
||||
|
||||
|
||||
export default function AccountAdminButtons({
|
||||
buttonProps,
|
||||
showForcePasswordResetModal,
|
||||
showAccountPasswordSetModal,
|
||||
showRevokeRefreshTokenModal,
|
||||
showUpdateAccountModal,
|
||||
showDeleteAccountModal
|
||||
}:{
|
||||
buttonProps: ButtonProps;
|
||||
showForcePasswordResetModal: () => void;
|
||||
showAccountPasswordSetModal: () => void;
|
||||
showRevokeRefreshTokenModal: () => void;
|
||||
showUpdateAccountModal: () => void;
|
||||
showDeleteAccountModal: () => void;
|
||||
}){
|
||||
return (
|
||||
<div
|
||||
className="flex flex-row gap-2"
|
||||
>
|
||||
<WarningButton
|
||||
{...buttonProps}
|
||||
onClick={showForcePasswordResetModal}
|
||||
>
|
||||
<BsLockFill
|
||||
size={22}
|
||||
/>
|
||||
</WarningButton>
|
||||
<DangerButton
|
||||
{...buttonProps}
|
||||
onClick={showAccountPasswordSetModal}
|
||||
>
|
||||
<BsKeyFill
|
||||
size={22}
|
||||
/>
|
||||
</DangerButton>
|
||||
<TertiaryButton
|
||||
{...buttonProps}
|
||||
onClick={showRevokeRefreshTokenModal}
|
||||
>
|
||||
<BsXCircle
|
||||
size={22}
|
||||
/>
|
||||
</TertiaryButton>
|
||||
<PrimaryButton
|
||||
{...buttonProps}
|
||||
onClick={showUpdateAccountModal}
|
||||
>
|
||||
<BsPencilFill
|
||||
size={22}
|
||||
/>
|
||||
</PrimaryButton>
|
||||
<DangerButton
|
||||
{...buttonProps}
|
||||
onClick={showDeleteAccountModal}
|
||||
>
|
||||
<BsTrash3
|
||||
size={22}
|
||||
/>
|
||||
</DangerButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
149
src/ui/account/AccountsList.tsx
Normal file
149
src/ui/account/AccountsList.tsx
Normal file
@@ -0,0 +1,149 @@
|
||||
import { ButtonProps } from "@/components/button/Button";
|
||||
import Table from "@/components/table/Table";
|
||||
import { Account } from "@/interface/Account";
|
||||
import moment from "moment";
|
||||
import { useState } from "react";
|
||||
import AccountAdminButtons from "./AccountAdminButtons";
|
||||
import AccountModal from "./modals/AccountModal";
|
||||
import AccountPasswordRestModal from "./modals/AccountPasswordResetModal";
|
||||
import DeleteAccountModal from "./modals/DeleteAccountModal";
|
||||
import ForcePasswordResetModal from "./modals/ForcePasswordResetModal";
|
||||
import RevokeRefreshTokenModal from "./modals/RevokeRefreshTokenModal";
|
||||
|
||||
|
||||
export interface AccountsListProps {
|
||||
accounts: Account[];
|
||||
}
|
||||
|
||||
|
||||
export default function AccountsList(props: AccountsListProps){
|
||||
const { accounts } = props;
|
||||
|
||||
|
||||
const [ selectedAccount, setSelectedAccount ] = useState<Account | undefined>(undefined);
|
||||
const [ displayForcePasswordResetModal, setDisplayForcePasswordResetModal ] = useState(false);
|
||||
const [ displayAccountPasswordSetModal, setDisplayAccountPasswordSetModal ] = useState(false);
|
||||
const [ displayRevokeRefreshTokenModal, setDisplayRevokeRefreshTokenModal ] = useState(false);
|
||||
const [ displayAccountModal, setDisplayAccountModal ] = useState(false);
|
||||
const [ displayDeleteAccountModal, setDisplayDeleteAccountModal ] = useState(false);
|
||||
|
||||
|
||||
const buttonProps: ButtonProps = {
|
||||
variant: "ghost",
|
||||
size: "md",
|
||||
shape: "square"
|
||||
};
|
||||
|
||||
|
||||
const headElements: React.ReactNode[] = [
|
||||
<div>
|
||||
ID
|
||||
</div>,
|
||||
<div>
|
||||
Username
|
||||
</div>,
|
||||
<div>
|
||||
Email
|
||||
</div>,
|
||||
<div>
|
||||
Login Date
|
||||
</div>,
|
||||
<div>
|
||||
Status
|
||||
</div>,
|
||||
<div
|
||||
className="pl-16"
|
||||
>
|
||||
Actions
|
||||
</div>
|
||||
];
|
||||
|
||||
const bodyElements: React.ReactNode[][] = accounts.map((account) => [
|
||||
<div
|
||||
className="text-nowrap"
|
||||
>
|
||||
{account.accountId}
|
||||
</div>,
|
||||
<div>
|
||||
{account.username}
|
||||
</div>,
|
||||
<div>
|
||||
{account.email}
|
||||
</div>,
|
||||
<div
|
||||
className="text-nowrap"
|
||||
>
|
||||
{moment(account.loginDate).format("MM-DD-YYYY HH:mm")}
|
||||
</div>,
|
||||
<div>
|
||||
{account.accountStatus}
|
||||
</div>,
|
||||
<div
|
||||
className="flex flex-row items-center justify-center gap-2 pl-16"
|
||||
>
|
||||
<div
|
||||
className="py-4 border-l border-neutral-500"
|
||||
>
|
||||
|
||||
</div>
|
||||
<AccountAdminButtons
|
||||
buttonProps={buttonProps}
|
||||
showForcePasswordResetModal={() => {
|
||||
setSelectedAccount(account);
|
||||
setDisplayForcePasswordResetModal(true);
|
||||
}}
|
||||
showAccountPasswordSetModal={() => {
|
||||
setSelectedAccount(account);
|
||||
setDisplayAccountPasswordSetModal(true);
|
||||
}}
|
||||
showRevokeRefreshTokenModal={() => {
|
||||
setSelectedAccount(account);
|
||||
setDisplayRevokeRefreshTokenModal(true);
|
||||
}}
|
||||
showUpdateAccountModal={() => {
|
||||
setSelectedAccount(account);
|
||||
setDisplayAccountModal(true);
|
||||
}}
|
||||
showDeleteAccountModal={() => {
|
||||
setSelectedAccount(account);
|
||||
setDisplayDeleteAccountModal(true);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
]);
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<Table
|
||||
tableHeadElements={headElements}
|
||||
tableBodyElements={bodyElements}
|
||||
/>
|
||||
<ForcePasswordResetModal
|
||||
display={displayForcePasswordResetModal}
|
||||
close={() => {setDisplayForcePasswordResetModal(false); setSelectedAccount(undefined);}}
|
||||
account={selectedAccount}
|
||||
/>
|
||||
<AccountPasswordRestModal
|
||||
display={displayAccountPasswordSetModal}
|
||||
close={() => {setDisplayAccountPasswordSetModal(false); setSelectedAccount(undefined);}}
|
||||
account={selectedAccount}
|
||||
/>
|
||||
<RevokeRefreshTokenModal
|
||||
display={displayRevokeRefreshTokenModal}
|
||||
close={() => {setDisplayRevokeRefreshTokenModal(false); setSelectedAccount(undefined);}}
|
||||
account={selectedAccount}
|
||||
/>
|
||||
<AccountModal
|
||||
display={displayAccountModal}
|
||||
close={() => {setDisplayAccountModal(false); setSelectedAccount(undefined);}}
|
||||
account={selectedAccount}
|
||||
/>
|
||||
<DeleteAccountModal
|
||||
display={displayDeleteAccountModal}
|
||||
close={() => {setDisplayDeleteAccountModal(false); setSelectedAccount(undefined);}}
|
||||
account={selectedAccount}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
7
src/ui/account/AccountsListSkeleton.tsx
Normal file
7
src/ui/account/AccountsListSkeleton.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
export default function AccountsListSkeleton(){
|
||||
return (
|
||||
<div>
|
||||
Accounts List Skeleton
|
||||
</div>
|
||||
);
|
||||
}
|
||||
45
src/ui/account/AccountsLoader.tsx
Normal file
45
src/ui/account/AccountsLoader.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import PrimaryButton from "@/components/button/PrimaryButton";
|
||||
import { useGetAccounts } from "@/hooks/AccountHooks";
|
||||
import { useState } from "react";
|
||||
import AccountsList from "./AccountsList";
|
||||
import AccountsListSkeleton from "./AccountsListSkeleton";
|
||||
import AccountModal from "./modals/AccountModal";
|
||||
|
||||
|
||||
export default function AccountsLoader(){
|
||||
const [ displayCreateAccountModal, setDisplayCreateAccountModal ] = useState(false);
|
||||
|
||||
const accountsQuery = useGetAccounts(0, 20);
|
||||
|
||||
|
||||
if(accountsQuery.isLoading){
|
||||
return <AccountsListSkeleton/>
|
||||
}
|
||||
else if(accountsQuery.isError){
|
||||
//TODO:
|
||||
return <div>Error: {accountsQuery.error.message}</div>
|
||||
}
|
||||
else{
|
||||
return (
|
||||
<>
|
||||
{/* TODO: Add Account Button */}
|
||||
<PrimaryButton
|
||||
className="mb-8"
|
||||
onClick={() => setDisplayCreateAccountModal(true)}
|
||||
>
|
||||
Create Account
|
||||
</PrimaryButton>
|
||||
<AccountModal
|
||||
display={displayCreateAccountModal}
|
||||
close={() => setDisplayCreateAccountModal(false)}
|
||||
account={undefined}
|
||||
/>
|
||||
{/* Account Search Bar */}
|
||||
<AccountsList
|
||||
accounts={accountsQuery.data ?? []}
|
||||
/>
|
||||
{/* TODO: Add Pagination */}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
113
src/ui/account/modals/AccountModal.tsx
Normal file
113
src/ui/account/modals/AccountModal.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
import AccountStatusSelector from "@/components/account/AccountStatusSelector";
|
||||
import PrimaryButton from "@/components/button/PrimaryButton";
|
||||
import SecondaryButton from "@/components/button/SecondaryButton";
|
||||
import PasswordInput from "@/components/input/PasswordInput";
|
||||
import TextInput from "@/components/input/TextInput";
|
||||
import RaidBuilderModal from "@/components/modal/RaidBuilderModal";
|
||||
import { useCreateAccount, useUpdateAccount } from "@/hooks/AccountHooks";
|
||||
import { Account, AccountStatus } from "@/interface/Account";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
|
||||
export default function AccountModal({
|
||||
display,
|
||||
close,
|
||||
account
|
||||
}:{
|
||||
display: boolean;
|
||||
close: () => void;
|
||||
account: Account | undefined;
|
||||
}){
|
||||
const [ username, setUsername ] = useState<string>(account?.username ?? "");
|
||||
const [ email, setEmail ] = useState<string>(account?.email ?? "");
|
||||
const [ password, setPassword ] = useState<string>("");
|
||||
const [ accountStatus, setAccountStatus ] = useState<AccountStatus>(account?.accountStatus ?? AccountStatus.ACTIVE);
|
||||
const modalId = crypto.randomUUID().replace("-", "");
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
setUsername(account?.username ?? "");
|
||||
setEmail(account?.email ?? "");
|
||||
setPassword(account?.password ?? "");
|
||||
setAccountStatus(account?.accountStatus ?? AccountStatus.ACTIVE);
|
||||
}, [ account, setUsername, setEmail, setPassword, setAccountStatus ]);
|
||||
|
||||
|
||||
const updateAccountMutate = useUpdateAccount();
|
||||
const createAccountMutate = useCreateAccount();
|
||||
|
||||
if((updateAccountMutate.isSuccess) || (createAccountMutate.isSuccess)){
|
||||
updateAccountMutate.reset();
|
||||
createAccountMutate.reset();
|
||||
close();
|
||||
}
|
||||
else if((updateAccountMutate.isError) || (updateAccountMutate.isError)){
|
||||
//TODO: Add message modal here
|
||||
console.log(updateAccountMutate.error);
|
||||
console.log(createAccountMutate.error);
|
||||
}
|
||||
|
||||
|
||||
const updateAccount = () => {
|
||||
updateAccountMutate.mutate({accountId: account?.accountId, username, email, password, accountStatus} as Account);
|
||||
}
|
||||
|
||||
const createAccount = () => {
|
||||
createAccountMutate.mutate({username, email, password, accountStatus} as Account);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<RaidBuilderModal
|
||||
display={display}
|
||||
close={close}
|
||||
modalHeader={account ? "Update Account" : "Create Account"}
|
||||
modalBody={
|
||||
<div
|
||||
className="flex flex-col items-center justify-center gap-4"
|
||||
>
|
||||
<TextInput
|
||||
id={`accountModalUsername${modalId}`}
|
||||
placeholder="Username"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
/>
|
||||
<TextInput
|
||||
id={`accountModalEmail${modalId}`}
|
||||
placeholder="Email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
{
|
||||
!account && (
|
||||
<PasswordInput
|
||||
id={`accountModalPassword${modalId}`}
|
||||
placeholder="Password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<AccountStatusSelector
|
||||
value={accountStatus}
|
||||
onChange={(e) => setAccountStatus(e.currentTarget.value as AccountStatus)}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
modalFooter={
|
||||
<>
|
||||
<PrimaryButton
|
||||
onClick={account ? updateAccount : createAccount}
|
||||
>
|
||||
{account ? "Update" : "Create"}
|
||||
</PrimaryButton>
|
||||
<SecondaryButton
|
||||
onClick={close}
|
||||
>
|
||||
Cancel
|
||||
</SecondaryButton>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
74
src/ui/account/modals/AccountPasswordResetModal.tsx
Normal file
74
src/ui/account/modals/AccountPasswordResetModal.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import PrimaryButton from "@/components/button/PrimaryButton";
|
||||
import SecondaryButton from "@/components/button/SecondaryButton";
|
||||
import PasswordInput from "@/components/input/PasswordInput";
|
||||
import RaidBuilderModal from "@/components/modal/RaidBuilderModal";
|
||||
import { useResetPassword } from "@/hooks/AccountHooks";
|
||||
import { Account } from "@/interface/Account";
|
||||
import { useState } from "react";
|
||||
|
||||
|
||||
export default function AccountPasswordRestModal({
|
||||
display,
|
||||
close,
|
||||
account
|
||||
}:{
|
||||
display: boolean;
|
||||
close: () => void;
|
||||
account: Account | undefined;
|
||||
}){
|
||||
const [ newPassword, setNewPassword ] = useState<string>("");
|
||||
|
||||
|
||||
const passwordResetMutate = useResetPassword(account?.accountId ?? "");
|
||||
const modalId = crypto.randomUUID().replace("-", "");
|
||||
|
||||
|
||||
const resetPassword = () => {
|
||||
passwordResetMutate.mutate(newPassword);
|
||||
}
|
||||
|
||||
if(passwordResetMutate.isSuccess){
|
||||
passwordResetMutate.reset();
|
||||
close();
|
||||
}
|
||||
else if(passwordResetMutate.isError){
|
||||
//TODO: Add message modal here
|
||||
console.log(passwordResetMutate.error);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<RaidBuilderModal
|
||||
display={display}
|
||||
close={close}
|
||||
modalHeader={"Reset Password"}
|
||||
modalBody={
|
||||
<div
|
||||
className="flex flex-col gap-4"
|
||||
>
|
||||
<div>Enter new password for {account?.username}.</div>
|
||||
<PasswordInput
|
||||
id={`passwordResetModal${modalId}`}
|
||||
value={newPassword}
|
||||
onChange={(e) => setNewPassword(e.target.value)}
|
||||
placeholder="Password"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
modalFooter={
|
||||
<>
|
||||
<PrimaryButton
|
||||
onClick={resetPassword}
|
||||
>
|
||||
Reset
|
||||
</PrimaryButton>
|
||||
<SecondaryButton
|
||||
onClick={close}
|
||||
>
|
||||
Cancel
|
||||
</SecondaryButton>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
55
src/ui/account/modals/DeleteAccountModal.tsx
Normal file
55
src/ui/account/modals/DeleteAccountModal.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import DangerButton from "@/components/button/DangerButton";
|
||||
import SecondaryButton from "@/components/button/SecondaryButton";
|
||||
import RaidBuilderModal from "@/components/modal/RaidBuilderModal";
|
||||
import { useDeleteAccount } from "@/hooks/AccountHooks";
|
||||
import { Account } from "@/interface/Account";
|
||||
|
||||
|
||||
export default function DeleteAccountModal({
|
||||
display,
|
||||
close,
|
||||
account
|
||||
}:{
|
||||
display: boolean;
|
||||
close: () => void;
|
||||
account: Account | undefined;
|
||||
}){
|
||||
const deleteAccountMutate = useDeleteAccount(account?.accountId ?? "");
|
||||
|
||||
|
||||
const deleteAccount = () => {
|
||||
deleteAccountMutate.mutate();
|
||||
}
|
||||
|
||||
if(deleteAccountMutate.isSuccess){
|
||||
deleteAccountMutate.reset();
|
||||
close();
|
||||
}
|
||||
else if(deleteAccountMutate.isError){
|
||||
//TODO: Add message modal here
|
||||
console.log(deleteAccountMutate.error);
|
||||
}
|
||||
|
||||
return (
|
||||
<RaidBuilderModal
|
||||
display={display}
|
||||
close={close}
|
||||
modalHeader={"Delete Account"}
|
||||
modalBody={`Are you sure you want to delete ${account?.username}?`}
|
||||
modalFooter={
|
||||
<>
|
||||
<DangerButton
|
||||
onClick={deleteAccount}
|
||||
>
|
||||
Delete
|
||||
</DangerButton>
|
||||
<SecondaryButton
|
||||
onClick={close}
|
||||
>
|
||||
Cancel
|
||||
</SecondaryButton>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
56
src/ui/account/modals/ForcePasswordResetModal.tsx
Normal file
56
src/ui/account/modals/ForcePasswordResetModal.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import DangerButton from "@/components/button/DangerButton";
|
||||
import SecondaryButton from "@/components/button/SecondaryButton";
|
||||
import RaidBuilderModal from "@/components/modal/RaidBuilderModal";
|
||||
import { useForcePasswordReset } from "@/hooks/AccountHooks";
|
||||
import { Account } from "@/interface/Account";
|
||||
|
||||
|
||||
export default function ForcePasswordResetModal({
|
||||
display,
|
||||
close,
|
||||
account
|
||||
}:{
|
||||
display: boolean;
|
||||
close: () => void;
|
||||
account: Account | undefined;
|
||||
}){
|
||||
const accountMutate = useForcePasswordReset(account?.accountId ?? "");
|
||||
|
||||
|
||||
const forcePasswordReset = () => {
|
||||
accountMutate.mutate();
|
||||
}
|
||||
|
||||
if(accountMutate.isSuccess){
|
||||
accountMutate.reset();
|
||||
close();
|
||||
}
|
||||
else if(accountMutate.isError){
|
||||
//TODO: Add message modal here
|
||||
console.log(accountMutate.error);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<RaidBuilderModal
|
||||
display={display}
|
||||
close={close}
|
||||
modalHeader={"Force Password Reset"}
|
||||
modalBody={`Are you sure you want to force reset the password for ${account?.username}?`}
|
||||
modalFooter={
|
||||
<>
|
||||
<DangerButton
|
||||
onClick={forcePasswordReset}
|
||||
>
|
||||
Reset
|
||||
</DangerButton>
|
||||
<SecondaryButton
|
||||
onClick={close}
|
||||
>
|
||||
Cancel
|
||||
</SecondaryButton>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
56
src/ui/account/modals/RevokeRefreshTokenModal.tsx
Normal file
56
src/ui/account/modals/RevokeRefreshTokenModal.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import DangerButton from "@/components/button/DangerButton";
|
||||
import SecondaryButton from "@/components/button/SecondaryButton";
|
||||
import RaidBuilderModal from "@/components/modal/RaidBuilderModal";
|
||||
import { useRevokeRefreshToken } from "@/hooks/AccountHooks";
|
||||
import { Account } from "@/interface/Account";
|
||||
|
||||
|
||||
export default function RevokeRefreshTokenModal({
|
||||
display,
|
||||
close,
|
||||
account
|
||||
}:{
|
||||
display: boolean;
|
||||
close: () => void;
|
||||
account: Account | undefined;
|
||||
}){
|
||||
const revokeRefreshTokenMutate = useRevokeRefreshToken(account?.accountId ?? "");
|
||||
|
||||
|
||||
const revokeRefreshToken = () => {
|
||||
revokeRefreshTokenMutate.mutate();
|
||||
}
|
||||
|
||||
if(revokeRefreshTokenMutate.isSuccess){
|
||||
revokeRefreshTokenMutate.reset();
|
||||
close();
|
||||
}
|
||||
else if(revokeRefreshTokenMutate.isError){
|
||||
//TODO: Add message modal here
|
||||
console.log(revokeRefreshTokenMutate.error);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<RaidBuilderModal
|
||||
display={display}
|
||||
close={close}
|
||||
modalHeader={"Revoke Refresh Token"}
|
||||
modalBody={`Are you sure you want to revoke the refresh token for ${account?.username}?`}
|
||||
modalFooter={
|
||||
<>
|
||||
<DangerButton
|
||||
onClick={revokeRefreshToken}
|
||||
>
|
||||
Revoke
|
||||
</DangerButton>
|
||||
<SecondaryButton
|
||||
onClick={close}
|
||||
>
|
||||
Cancel
|
||||
</SecondaryButton>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user