Modals and API calls working for admin tab
This commit is contained in:
10
package-lock.json
generated
10
package-lock.json
generated
@@ -17,6 +17,7 @@
|
||||
"@types/node": "^22.13.4",
|
||||
"axios": "^1.7.9",
|
||||
"clsx": "^2.1.1",
|
||||
"moment": "^2.30.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-icons": "^5.5.0",
|
||||
@@ -3471,6 +3472,15 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/moment": {
|
||||
"version": "2.30.1",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
|
||||
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"@types/node": "^22.13.4",
|
||||
"axios": "^1.7.9",
|
||||
"clsx": "^2.1.1",
|
||||
"moment": "^2.30.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-icons": "^5.5.0",
|
||||
|
||||
@@ -12,7 +12,6 @@ import RaidLayoutPage from "./pages/protected/RaidLayoutPage";
|
||||
import HomePage from "./pages/public/HomePage";
|
||||
import LoginPage from "./pages/public/LoginPage";
|
||||
import SignupPage from "./pages/public/SignupPage";
|
||||
import TestPage from "./pages/public/TestPage";
|
||||
import { ProtectedRoute } from "./providers/AuthProvider";
|
||||
import ErrorBoundary from "./providers/ErrorBoundary";
|
||||
|
||||
@@ -28,10 +27,6 @@ const routes = createBrowserRouter([
|
||||
path: "/",
|
||||
element: <HomePage/>
|
||||
},
|
||||
{
|
||||
path: "/test",
|
||||
element: <TestPage/>
|
||||
},
|
||||
{
|
||||
path: "/login",
|
||||
element: <LoginPage/>
|
||||
|
||||
41
src/components/account/AccountStatusSelector.tsx
Normal file
41
src/components/account/AccountStatusSelector.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import { AccountStatus } from "@/interface/Account";
|
||||
|
||||
|
||||
export default function AccountStatusSelector({
|
||||
value,
|
||||
onChange
|
||||
}:{
|
||||
value: AccountStatus;
|
||||
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
}){
|
||||
const modalId = crypto.randomUUID().replace("-", "");
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex flex-row flex-wrap justify-start gap-x-4"
|
||||
>
|
||||
{
|
||||
Object.keys(AccountStatus).map((status: string) => (
|
||||
<label
|
||||
key={status}
|
||||
className="whitespace-nowrap"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name={`accountStatusSelector${modalId}`}
|
||||
value={status}
|
||||
onChange={onChange}
|
||||
checked={value === status}
|
||||
/>
|
||||
<span
|
||||
className="ml-1"
|
||||
>
|
||||
{status}
|
||||
</span>
|
||||
</label>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import clsx from "clsx";
|
||||
|
||||
export type ButtonRounding = "none" | "sm" | "md" | "lg" | "full";
|
||||
export type ButtonShape = "vertical" | "horizontal" | "square";
|
||||
export type ButtonSizeType = "xsm" | "sm" | "md" | "lg" | "xl";
|
||||
export type ButtonSizeType = "xs" | "sm" | "md" | "lg" | "xl";
|
||||
export type ButtonVariant = "solid" | "outline" | "ghost" | "outline-ghost" | "icon";
|
||||
|
||||
export interface ButtonProps extends React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>{
|
||||
@@ -17,7 +17,7 @@ export interface ButtonProps extends React.DetailedHTMLProps<React.ButtonHTMLAtt
|
||||
export default function Button(props: ButtonProps){
|
||||
const {
|
||||
rounding = "lg",
|
||||
shape = "vertical",
|
||||
shape = "horizontal",
|
||||
size = "md"
|
||||
} = props;
|
||||
|
||||
@@ -33,6 +33,7 @@ export default function Button(props: ButtonProps){
|
||||
{...props}
|
||||
className={clsx(
|
||||
props.className,
|
||||
"transition-colors duration-300",
|
||||
//Rounding
|
||||
{
|
||||
"rounded-none": rounding === "none",
|
||||
@@ -44,19 +45,19 @@ export default function Button(props: ButtonProps){
|
||||
//Shape & Size
|
||||
{
|
||||
//Square
|
||||
"p-0": size === "xsm" && shape === "square",
|
||||
"p-0": size === "xs" && shape === "square",
|
||||
"p-1": size === "sm" && shape === "square",
|
||||
"p-2": size === "md" && shape === "square",
|
||||
"p-3": size === "lg" && shape === "square",
|
||||
"p-4": size === "xl" && shape === "square",
|
||||
//Horizontal
|
||||
"px-1 py-0": size === "xsm" && shape === "horizontal",
|
||||
"px-1 py-0": size === "xs" && shape === "horizontal",
|
||||
"px-2 py-1": size === "sm" && shape === "horizontal",
|
||||
"px-4 py-2": size === "md" && shape === "horizontal",
|
||||
"px-6 py-3": size === "lg" && shape === "horizontal",
|
||||
"px-8 py-4": size === "xl" && shape === "horizontal",
|
||||
//Vertical
|
||||
"px-0 py-1": size === "xsm" && shape === "vertical",
|
||||
"px-0 py-1": size === "xs" && shape === "vertical",
|
||||
"px-1 py-2": size === "sm" && shape === "vertical",
|
||||
"px-2 py-4": size === "md" && shape === "vertical",
|
||||
"px-3 py-6": size === "lg" && shape === "vertical",
|
||||
|
||||
@@ -34,7 +34,7 @@ export default function Modal(props: ModalProps){
|
||||
<div
|
||||
{...divProps}
|
||||
className={clsx(
|
||||
"fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-50",
|
||||
"fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 max-w-(--breakpoint-sm) z-50",
|
||||
"flex flex-col rounded-lg max-h-full shadow-lg shadow-[#00000066]",
|
||||
className
|
||||
)}
|
||||
|
||||
@@ -29,8 +29,10 @@ export default function ModalBackground(props: ModalBackgroundProps){
|
||||
{
|
||||
"bg-[#00000044]": backgroundType === "darken",
|
||||
"bg-[#FFFFFF44]": backgroundType === "lighten",
|
||||
"backdrop-blur-sm bg-radial-[circle] from-transparent from-25% to-[#00000066]": backgroundType === "darken-blur",
|
||||
"backdrop-blur-sm bg-radial-[circle] from-transparent from-25% to-[#FFFFFF66]": backgroundType === "lighten-blur",
|
||||
"backdrop-blur-sm bg-black/15": backgroundType === "darken-blur",
|
||||
"backdrop-blur-sm bg-white/5": backgroundType === "lighten-blur",
|
||||
"backdrop-blur-sm bg-radial-[circle] from-transparent from-25% to-[#00000066]": backgroundType === "darken-blur-radial",
|
||||
"backdrop-blur-sm bg-radial-[circle] from-transparent from-25% to-[#FFFFFF66]": backgroundType === "lighten-blur-radial",
|
||||
"bg-[#00000000]": backgroundType === "transparent",
|
||||
"backdrop-blur-sm": backgroundType === "blur"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ModalHeaderProps } from "@/interface/ModalInterfaces";
|
||||
import clsx from "clsx";
|
||||
import { BsXLg } from "react-icons/bs";
|
||||
import Button from "../button/Button";
|
||||
|
||||
|
||||
export default function ModalHeader(props: ModalHeaderProps){
|
||||
@@ -30,14 +31,20 @@ export default function ModalHeader(props: ModalHeaderProps){
|
||||
</div>
|
||||
{
|
||||
close &&
|
||||
<div
|
||||
className="absolute top-1 right-1 cursor-pointer"
|
||||
<Button
|
||||
variant="ghost"
|
||||
shape="square"
|
||||
size="sm"
|
||||
className={clsx(
|
||||
"absolute top-1 right-1 cursor-pointer",
|
||||
"hover:bg-red-500 hover:text-white active:bg-red-600 active:text-white"
|
||||
)}
|
||||
onClick={close}
|
||||
>
|
||||
<BsXLg
|
||||
size={20}
|
||||
/>
|
||||
</div>
|
||||
</Button>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
||||
59
src/components/modal/RaidBuilderModal.tsx
Normal file
59
src/components/modal/RaidBuilderModal.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import { useTheme } from "@/providers/ThemeProvider";
|
||||
import Modal from "./Modal";
|
||||
import ModalBody from "./ModalBody";
|
||||
import ModalFooter from "./ModalFooter";
|
||||
import ModalHeader from "./ModalHeader";
|
||||
|
||||
|
||||
export default function RaidBuilderModal({
|
||||
display,
|
||||
modalHeader,
|
||||
modalBody,
|
||||
modalFooter,
|
||||
close
|
||||
}:{
|
||||
display: boolean;
|
||||
modalHeader: React.ReactNode;
|
||||
modalBody: React.ReactNode;
|
||||
modalFooter: React.ReactNode;
|
||||
close: () => void;
|
||||
}){
|
||||
const { theme } = useTheme();
|
||||
|
||||
|
||||
return (
|
||||
<Modal
|
||||
display={display}
|
||||
close={close}
|
||||
className="bg-(--bg-color) text-(--text-color)"
|
||||
backgroundType={theme === "dark" ? "lighten-blur" : "darken-blur"}
|
||||
>
|
||||
<ModalHeader
|
||||
className="bg-[#00000022] dark:bg-[#FFFFFF16]"
|
||||
close={close}
|
||||
>
|
||||
<h3
|
||||
className="text-2xl"
|
||||
>
|
||||
{modalHeader}
|
||||
</h3>
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<div
|
||||
className="my-8"
|
||||
>
|
||||
{modalBody}
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter
|
||||
className="bg-[#00000022] dark:bg-[#FFFFFF16]"
|
||||
>
|
||||
<div
|
||||
className="flex flex-row items-center justify-center gap-4"
|
||||
>
|
||||
{modalFooter}
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -6,10 +6,6 @@ const publicLinks = [
|
||||
{
|
||||
name: "Home",
|
||||
path: "/"
|
||||
},
|
||||
{
|
||||
name: "Test",
|
||||
path: "/test"
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -12,19 +12,22 @@ export interface Tab {
|
||||
}
|
||||
|
||||
export interface TabGroupProps extends HTMLProps<HTMLDivElement>{
|
||||
tabs: Tab[];
|
||||
tabs?: Tab[];
|
||||
}
|
||||
|
||||
|
||||
export default function TabGroup(props: TabGroupProps){
|
||||
const { tabs, className } = props;
|
||||
if(!tabs){ throw new Error("Tabs must be present"); }
|
||||
const [ activeTab, setActiveTab ] = useState<number>(tabs.map((tab, index) => tab.active ? index : undefined)[0] ?? 0);
|
||||
//TODO: Possible to maintain state of past tabs if we "cache" them in a useState<JSX.Element>() on their first render
|
||||
const divProps = {...props};
|
||||
delete divProps.tabs;
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
{...divProps}
|
||||
className={clsx(
|
||||
className,
|
||||
"flex flex-col w-full"
|
||||
@@ -50,7 +53,7 @@ export default function TabGroup(props: TabGroupProps){
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="flex flex-col items-center justify-center"
|
||||
className="flex flex-col items-center justify-center mt-8"
|
||||
>
|
||||
{
|
||||
tabs.map((tab, index) => (
|
||||
@@ -108,8 +111,8 @@ function TabContent({
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
tab.headerClasses,
|
||||
""
|
||||
"w-full",
|
||||
tab.contentClasses
|
||||
)}
|
||||
>
|
||||
{tab.tabContent}
|
||||
|
||||
39
src/components/table/Table.tsx
Normal file
39
src/components/table/Table.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import clsx from "clsx";
|
||||
import { HTMLProps } from "react";
|
||||
import TableBody from "./TableBody";
|
||||
import TableHead from "./TableHead";
|
||||
|
||||
|
||||
export interface TableProps extends HTMLProps<HTMLTableElement>{
|
||||
tableHeadElements?: React.ReactNode[];
|
||||
tableBodyElements?: React.ReactNode[][];
|
||||
}
|
||||
|
||||
|
||||
export default function Table(props: TableProps){
|
||||
const {
|
||||
tableHeadElements,
|
||||
tableBodyElements
|
||||
} = props;
|
||||
const tableProps = {...props};
|
||||
delete tableProps.tableHeadElements;
|
||||
delete tableProps.tableBodyElements;
|
||||
|
||||
|
||||
return (
|
||||
<table
|
||||
{...tableProps}
|
||||
className={clsx(
|
||||
"w-full",
|
||||
props.className
|
||||
)}
|
||||
>
|
||||
<TableHead
|
||||
headElements={tableHeadElements ?? []}
|
||||
/>
|
||||
<TableBody
|
||||
bodyElements={tableBodyElements ?? []}
|
||||
/>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
46
src/components/table/TableBody.tsx
Normal file
46
src/components/table/TableBody.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import clsx from "clsx";
|
||||
|
||||
|
||||
export default function TableBody({
|
||||
bodyElements
|
||||
}:{
|
||||
bodyElements: React.ReactNode[][];
|
||||
}){
|
||||
return (
|
||||
<tbody>
|
||||
{
|
||||
bodyElements.map((row, rowIndex) => (
|
||||
<tr
|
||||
key={rowIndex}
|
||||
>
|
||||
{
|
||||
row.map((element, elementIndex) => (
|
||||
<td
|
||||
key={elementIndex}
|
||||
className={clsx(
|
||||
{
|
||||
"w-0": elementIndex === 0 || elementIndex === row.length - 1
|
||||
}
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
"bg-neutral-200 dark:bg-neutral-700",
|
||||
{
|
||||
"py-4 my-2": elementIndex < row.length - 1,
|
||||
"rounded-l pl-2": elementIndex === 0,
|
||||
"rounded-r pr-2": elementIndex === row.length - 1
|
||||
}
|
||||
)}
|
||||
>
|
||||
{element}
|
||||
</div>
|
||||
</td>
|
||||
))
|
||||
}
|
||||
</tr>
|
||||
))
|
||||
}
|
||||
</tbody>
|
||||
);
|
||||
}
|
||||
30
src/components/table/TableHead.tsx
Normal file
30
src/components/table/TableHead.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import clsx from "clsx";
|
||||
|
||||
|
||||
export default function TableHead({
|
||||
headElements
|
||||
}:{
|
||||
headElements: React.ReactNode[];
|
||||
}){
|
||||
return (
|
||||
<thead>
|
||||
<tr>
|
||||
{
|
||||
headElements.map((element, index) => (
|
||||
<th
|
||||
key={index}
|
||||
className={clsx(
|
||||
{
|
||||
"pl-2": index === 0,
|
||||
"pr-2": index === headElements.length - 1
|
||||
}
|
||||
)}
|
||||
>
|
||||
{element}
|
||||
</th>
|
||||
))
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
);
|
||||
}
|
||||
164
src/hooks/AccountHooks.ts
Normal file
164
src/hooks/AccountHooks.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
import { Account } from "@/interface/Account";
|
||||
import { api } from "@/util/AxiosUtil";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
|
||||
export function useGetAccounts(page: number, pageSize: number, searchTerm?: string){
|
||||
return useQuery({
|
||||
queryKey: ["accounts", {page, pageSize, searchTerm}],
|
||||
queryFn: async () => {
|
||||
const params = new URLSearchParams();
|
||||
params.append("page", page.toString());
|
||||
params.append("pageSize", pageSize.toString());
|
||||
if(searchTerm){
|
||||
params.append("search", searchTerm ?? "");
|
||||
}
|
||||
|
||||
const response = await api.get(`/account?${params}`);
|
||||
|
||||
if(response.status !== 200){
|
||||
throw new Error("Failed to get accounts");
|
||||
}
|
||||
else if(response.data.errors){
|
||||
throw new Error(response.data.errors.join(", "));
|
||||
}
|
||||
|
||||
return response.data as Account[];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useForcePasswordReset(accountId: string){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["forcePasswordReset", accountId],
|
||||
mutationFn: async () => {
|
||||
const response = await api.put(`/account/${accountId}/forcePasswordReset`);
|
||||
|
||||
if(response.status !== 200){
|
||||
throw new Error("Failed to force password reset");
|
||||
}
|
||||
else if(response.data.errors){
|
||||
throw new Error(response.data.errors.join(", "));
|
||||
}
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["accounts"] });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useResetPassword(accountId: string){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["resetPassword", accountId],
|
||||
mutationFn: async (password: string) => {
|
||||
const response = await api.put(`/account/${accountId}/resetPassword`, {
|
||||
password
|
||||
});
|
||||
|
||||
if(response.status !== 200){
|
||||
throw new Error("Failed to reset password");
|
||||
}
|
||||
else if(response.data.errors){
|
||||
throw new Error(response.data.errors.join(", "));
|
||||
}
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["accounts"] });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useRevokeRefreshToken(accountId: string){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["revokeRefreshToken", accountId],
|
||||
mutationFn: async () => {
|
||||
const response = await api.put(`/account/${accountId}/revokeRefreshToken`);
|
||||
|
||||
if(response.status !== 200){
|
||||
throw new Error("Failed to revoke refresh token");
|
||||
}
|
||||
else if(response.data.errors){
|
||||
throw new Error(response.data.errors.join(", "));
|
||||
}
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["accounts"] });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateAccount(){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["createAccount"],
|
||||
mutationFn: async (account: Account) => {
|
||||
const response = await api.post("/account", account);
|
||||
|
||||
if(response.status !== 200){
|
||||
throw new Error("Failed to create account");
|
||||
}
|
||||
else if(response.data.errors){
|
||||
throw new Error(response.data.errors.join(", "));
|
||||
}
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["accounts"] });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateAccount(){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["updateAccount"],
|
||||
mutationFn: async (account: Account) => {
|
||||
const response = await api.put(`/account/${account.accountId}`, account);
|
||||
|
||||
if(response.status !== 200){
|
||||
throw new Error("Failed to update account");
|
||||
}
|
||||
else if(response.data.errors){
|
||||
throw new Error(response.data.errors.join(", "));
|
||||
}
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["accounts"] });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteAccount(accountId: string){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["deleteAccount", accountId],
|
||||
mutationFn: async () => {
|
||||
const response = await api.delete(`/account/${accountId}`);
|
||||
|
||||
if(response.status !== 200){
|
||||
throw new Error("Failed to delete account");
|
||||
}
|
||||
else if(response.data.errors){
|
||||
throw new Error(response.data.errors.join(", "));
|
||||
}
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["accounts"] });
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
|
||||
|
||||
|
||||
@@ -29,6 +29,10 @@
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
a:hover{
|
||||
color: var(--color-blue-300);
|
||||
}
|
||||
|
||||
a.active {
|
||||
color: var(--color-blue-400);
|
||||
}
|
||||
@@ -40,7 +44,7 @@ body {
|
||||
max-width: var(--breakpoint-2xl);
|
||||
|
||||
margin-inline: auto;
|
||||
padding-top: 82px;
|
||||
padding-top: 90px;
|
||||
padding-inline: 1rem;
|
||||
|
||||
text-align: center;
|
||||
|
||||
20
src/interface/Account.ts
Normal file
20
src/interface/Account.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
export enum AccountStatus {
|
||||
ACTIVE = "ACTIVE",
|
||||
LOCKED = "LOCKED",
|
||||
INACTIVE = "INACTIVE",
|
||||
DELETED = "DELETED",
|
||||
UNCONFIRMED = "UNCONFIRMED"
|
||||
};
|
||||
|
||||
|
||||
export interface Account {
|
||||
accountId: string;
|
||||
username: string;
|
||||
password: string;
|
||||
loginDate: Date;
|
||||
email: string;
|
||||
forceReset: boolean;
|
||||
refreshToken?: string;
|
||||
refreshTokenExpiration?: Date;
|
||||
accountStatus: AccountStatus;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { HTMLProps } from "react";
|
||||
|
||||
|
||||
export type ModalBackgroundType = "darken" | "lighten" | "blur" | "darken-blur" | "lighten-blur" | "transparent" | "none";
|
||||
export type ModalBackgroundType = "darken" | "lighten" | "blur" | "darken-blur" | "lighten-blur" | "darken-blur-radial" | "lighten-blur-radial" | "transparent" | "none";
|
||||
export type ModalHeaderFooterBackgroundType = "darken" | "lighten" | "none";
|
||||
|
||||
|
||||
|
||||
26
src/main.tsx
26
src/main.tsx
@@ -1,3 +1,4 @@
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import App from './App.tsx'
|
||||
@@ -6,18 +7,23 @@ import { AuthProvider } from './providers/AuthProvider.tsx'
|
||||
import { ThemeProvider } from './providers/ThemeProvider.tsx'
|
||||
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<ThemeProvider
|
||||
defaultTheme="dark"
|
||||
storageKey="vite-ui-theme"
|
||||
>
|
||||
<AuthProvider
|
||||
jwtStorageKey="jwt"
|
||||
refreshTokenStorageKey="refreshToken"
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ThemeProvider
|
||||
defaultTheme="dark"
|
||||
storageKey="vite-ui-theme"
|
||||
>
|
||||
<App />
|
||||
</AuthProvider>
|
||||
</ThemeProvider>
|
||||
<AuthProvider
|
||||
jwtStorageKey="jwt"
|
||||
refreshTokenStorageKey="refreshToken"
|
||||
>
|
||||
<App />
|
||||
</AuthProvider>
|
||||
</ThemeProvider>
|
||||
</QueryClientProvider>
|
||||
</StrictMode>
|
||||
);
|
||||
|
||||
@@ -1,10 +1,33 @@
|
||||
import TabGroup, { Tab } from "@/components/tab/TabGroup";
|
||||
import AccountsLoader from "@/ui/account/AccountsLoader";
|
||||
|
||||
|
||||
export default function AdminPage(){
|
||||
//TODO:
|
||||
const tabs: Tab[] = [
|
||||
{
|
||||
tabHeader: "Accounts",
|
||||
tabContent: <AccountsLoader/>
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
Admin Page
|
||||
</div>
|
||||
<main
|
||||
className="flex flex-col items-center justify-center"
|
||||
>
|
||||
<h1
|
||||
className="text-4xl"
|
||||
>
|
||||
Admin Functions
|
||||
</h1>
|
||||
<div
|
||||
className="w-full"
|
||||
>
|
||||
<TabGroup
|
||||
tabs={tabs}
|
||||
>
|
||||
</TabGroup>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useNavigate } from "react-router";
|
||||
|
||||
|
||||
export default function LogoutPage(){
|
||||
const { setJwt } = useAuth();
|
||||
const { setJwt, setExpiration } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ export default function LogoutPage(){
|
||||
const response = await api.get("/auth/logout");
|
||||
if(response.status === 200){
|
||||
setJwt(null);
|
||||
setExpiration(null);
|
||||
navigate("/");
|
||||
}
|
||||
else{
|
||||
|
||||
@@ -40,26 +40,39 @@ export default function LoginPage(){
|
||||
|
||||
|
||||
return (
|
||||
<form
|
||||
action={login}
|
||||
className="flex flex-col justify-center space-y-8"
|
||||
>
|
||||
<TextInput
|
||||
id="username"
|
||||
name="username"
|
||||
placeholder="Username"
|
||||
/>
|
||||
<PasswordInput
|
||||
id="password"
|
||||
name="password"
|
||||
placeholder="Password"
|
||||
/>
|
||||
<PrimaryButton
|
||||
className="mx-auto"
|
||||
type="submit"
|
||||
<main>
|
||||
<form
|
||||
action={login}
|
||||
className="flex flex-col items-center justify-center space-y-8"
|
||||
>
|
||||
Login
|
||||
</PrimaryButton>
|
||||
</form>
|
||||
<div
|
||||
className="mx-auto"
|
||||
>
|
||||
<TextInput
|
||||
id="username"
|
||||
name="username"
|
||||
placeholder="Username"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="mx-auto"
|
||||
>
|
||||
<PasswordInput
|
||||
id="password"
|
||||
name="password"
|
||||
placeholder="Password"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="flex flex-row justify-center items-center"
|
||||
>
|
||||
<PrimaryButton
|
||||
type="submit"
|
||||
>
|
||||
Login
|
||||
</PrimaryButton>
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
import TabGroup, { Tab } from "@/components/tab/TabGroup";
|
||||
|
||||
|
||||
export default function TestPage(){
|
||||
const tabs: Tab[] = [
|
||||
{
|
||||
tabHeader: "Tab 1",
|
||||
tabContent: <Tab1/>
|
||||
},
|
||||
{
|
||||
tabHeader: "Tab 2",
|
||||
tabContent: <Tab2/>
|
||||
},
|
||||
{
|
||||
tabHeader: "Tab 3",
|
||||
tabContent: <Tab3/>
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<main
|
||||
className="flex flex-col items-center justify-center gap-4 mt-8"
|
||||
>
|
||||
<TabGroup
|
||||
tabs={tabs}
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function Tab1(){
|
||||
console.log("Tab 1");
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
Tab 1 Content
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Tab2(){
|
||||
console.log("Tab 2");
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
Tab 2 Content
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Tab3(){
|
||||
console.log("Tab 3");
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
Tab 3 Content
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -82,6 +82,8 @@ export function AuthProvider({
|
||||
setExpiration
|
||||
}), [ jwt, setJwt, expiration, setExpiration ]);
|
||||
|
||||
//TODO: Return a spinner while the first token is being fetched
|
||||
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={currentTokens}>
|
||||
|
||||
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