Modals and API calls working for admin tab

This commit is contained in:
2025-03-01 23:32:41 -05:00
parent d68e8864a0
commit 843970e229
34 changed files with 1150 additions and 131 deletions

View 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>
);
}

View 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"
>
&nbsp;
</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}
/>
</>
);
}

View File

@@ -0,0 +1,7 @@
export default function AccountsListSkeleton(){
return (
<div>
Accounts List Skeleton
</div>
);
}

View 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 */}
</>
);
}
}

View 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>
</>
}
/>
);
}

View 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>
</>
}
/>
);
}

View 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>
</>
}
/>
);
}

View 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>
</>
}
/>
);
}

View 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>
</>
}
/>
);
}