Buttons hidden by permissions

This commit is contained in:
2025-03-15 16:51:13 -04:00
parent 56236fd2ac
commit a842c24d0d
44 changed files with 624 additions and 94 deletions

View File

@@ -1,29 +1,10 @@
import { useAuth } from "@/providers/AuthProvider"; import { useAuth } from "@/providers/AuthProvider";
import { isSiteAdmin } from "@/util/PermissionUtil";
import { NavLink } from "react-router"; import { NavLink } from "react-router";
const protectedLinks = [
{
name: "Games",
path: "/game"
},
{
name: "Groups",
path: "/raidGroup"
},
{
name: "Admin",
path: "/admin"
},
{
name: "Logout",
path: "/logout"
}
];
export default function ProtectedNavLinks(){ export default function ProtectedNavLinks(){
const { jwt } = useAuth(); const { jwt, accountPermissions } = useAuth();
if(!jwt){ if(!jwt){
@@ -31,6 +12,30 @@ export default function ProtectedNavLinks(){
} }
const protectedLinks = [
{
name: "Game",
path: "/game"
},
{
name: "Raid Group",
path: "/raidGroup"
}
];
if(isSiteAdmin(accountPermissions)){
protectedLinks.push({
name: "Admin",
path: "/admin"
});
}
protectedLinks.push({
name: "Logout",
path: "/logout"
});
console.log("is admin = " + isSiteAdmin(accountPermissions));
return ( return (
<> <>
{ {

View File

@@ -30,6 +30,10 @@
color: var(--text-color); color: var(--text-color);
} }
#root {
width: 100%;
}
a:hover{ a:hover{
color: var(--color-blue-300); color: var(--color-blue-300);
} }

View File

@@ -6,6 +6,11 @@ export enum AccountStatus {
UNCONFIRMED = "UNCONFIRMED" UNCONFIRMED = "UNCONFIRMED"
}; };
export enum AccountPermissionType {
ADMIN = "ADMIN",
USER = "USER"
}
export interface Account { export interface Account {
accountId: string; accountId: string;

View File

@@ -0,0 +1,8 @@
import { AccountPermissionType } from "./Account";
export interface AccountPermission {
accountPermissionId?: string;
accountId: string;
accountPermissionType: AccountPermissionType;
}

View File

@@ -4,7 +4,7 @@ import AllGamesDisplay from "@/ui/game/AllGamesDisplay";
export default function GamesPage(){ export default function GamesPage(){
return ( return (
<main <main
className="flex flex-col items-center justify-center gap-8" className="flex flex-col items-center justify-center gap-8 w-full"
> >
<h1 <h1
className="text-4xl" className="text-4xl"

View File

@@ -1,6 +1,8 @@
import DangerMessage from "@/components/message/DangerMessage";
import TabGroup, { Tab } from "@/components/tab/TabGroup"; import TabGroup, { Tab } from "@/components/tab/TabGroup";
import { useGetRaidGroup } from "@/hooks/RaidGroupHooks"; import { useGetRaidGroup } from "@/hooks/RaidGroupHooks";
import { RaidGroup } from "@/interface/RaidGroup"; import { RaidGroup } from "@/interface/RaidGroup";
import { useAuth } from "@/providers/AuthProvider";
import RaidGroupAccountsTab from "@/ui/account/RaidGroupAccountsTab"; import RaidGroupAccountsTab from "@/ui/account/RaidGroupAccountsTab";
import RaidGroupCalendarDisplay from "@/ui/calendar/RaidGroupCalendarDisplay"; import RaidGroupCalendarDisplay from "@/ui/calendar/RaidGroupCalendarDisplay";
import RaidGroupHeader from "@/ui/calendar/RaidGroupHeader"; import RaidGroupHeader from "@/ui/calendar/RaidGroupHeader";
@@ -9,12 +11,14 @@ import PersonTab from "@/ui/person/PersonTab";
import RaidGroupRequestTab from "@/ui/raidGroupRequest/RaidGroupRequestTab"; import RaidGroupRequestTab from "@/ui/raidGroupRequest/RaidGroupRequestTab";
import RaidInstanceTab from "@/ui/raidInstance/RaidInstanceTab"; import RaidInstanceTab from "@/ui/raidInstance/RaidInstanceTab";
import RaidLayoutTab from "@/ui/raidLayout/RaidLayoutTab"; import RaidLayoutTab from "@/ui/raidLayout/RaidLayoutTab";
import { isRaidGroupAdmin } from "@/util/PermissionUtil";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Navigate, useParams } from "react-router"; import { Navigate, useParams } from "react-router";
export default function RaidGroupPage(){ export default function RaidGroupPage(){
const { raidGroupId } = useParams(); const { raidGroupId } = useParams();
const { accountPermissions, raidGroupPermissions } = useAuth();
const [ raidGroup, setRaidGroup ] = useState<RaidGroup>(); const [ raidGroup, setRaidGroup ] = useState<RaidGroup>();
@@ -33,7 +37,7 @@ export default function RaidGroupPage(){
} }
else if(raidGroupQuery.status === "error"){ else if(raidGroupQuery.status === "error"){
return ( return (
<div>Error</div> <DangerMessage>Error {raidGroupQuery.error.message}</DangerMessage>
); );
} }
else if(raidGroupQuery.status === "success" && raidGroupQuery.data === undefined){ else if(raidGroupQuery.status === "success" && raidGroupQuery.data === undefined){
@@ -62,17 +66,20 @@ export default function RaidGroupPage(){
{ {
tabHeader: "Raid Instances", tabHeader: "Raid Instances",
tabContent: <RaidInstanceTab raidGroup={raidGroup}/> tabContent: <RaidInstanceTab raidGroup={raidGroup}/>
},
{
tabHeader: "Users",
tabContent: <RaidGroupAccountsTab raidGroup={raidGroup}/>
},
{
tabHeader: "Requests",
tabContent: <RaidGroupRequestTab raidGroup={raidGroup}/>
} }
]; ];
if(isRaidGroupAdmin(raidGroupId!, raidGroupPermissions, accountPermissions)){
tabs.push({
tabHeader: "Users",
tabContent: <RaidGroupAccountsTab raidGroup={raidGroup}/>
});
tabs.push({
tabHeader: "Requests",
tabContent: <RaidGroupRequestTab raidGroup={raidGroup}/>
});
}
return ( return (
<main <main

View File

@@ -1,3 +1,4 @@
import { AccountPermission } from "@/interface/AccountPermission";
import { GamePermission } from "@/interface/GamePermission"; import { GamePermission } from "@/interface/GamePermission";
import { RaidGroupPermission } from "@/interface/RaidGroupPermission"; import { RaidGroupPermission } from "@/interface/RaidGroupPermission";
import { RaidGroupRequest } from "@/interface/RaidGroupRequest"; import { RaidGroupRequest } from "@/interface/RaidGroupRequest";
@@ -18,6 +19,7 @@ type AuthProviderState = {
expiration: Date | null; expiration: Date | null;
setExpiration: (expiration: Date | null) => void; setExpiration: (expiration: Date | null) => void;
accountId: string | null; accountId: string | null;
accountPermissions: AccountPermission[];
raidGroupPermissions: RaidGroupPermission[]; raidGroupPermissions: RaidGroupPermission[];
gamePermissions: GamePermission[]; gamePermissions: GamePermission[];
raidGroupRequests: RaidGroupRequest[]; raidGroupRequests: RaidGroupRequest[];
@@ -29,6 +31,7 @@ const initialState: AuthProviderState = {
expiration: null, expiration: null,
setExpiration: () => null, setExpiration: () => null,
accountId: null, accountId: null,
accountPermissions: [],
raidGroupPermissions: [], raidGroupPermissions: [],
gamePermissions: [], gamePermissions: [],
raidGroupRequests: [] raidGroupRequests: []
@@ -44,6 +47,7 @@ export function AuthProvider({
const [ expiration, setExpiration ] = useState<Date | null>(null); const [ expiration, setExpiration ] = useState<Date | null>(null);
const [ firstFetch, setFirstFetch ] = useState(true); const [ firstFetch, setFirstFetch ] = useState(true);
const [ accountId, setAccountId ] = useState<string | null>(null); const [ accountId, setAccountId ] = useState<string | null>(null);
const [ accountPermissions, setAccountPermissions ] = useState<AccountPermission[]>([]);
const [ raidGroupPermissions, setRaidGroupPermissions ] = useState<RaidGroupPermission[]>([]); const [ raidGroupPermissions, setRaidGroupPermissions ] = useState<RaidGroupPermission[]>([]);
const [ gamePermissions, setGamePermissions ] = useState<GamePermission[]>([]); const [ gamePermissions, setGamePermissions ] = useState<GamePermission[]>([]);
const [ raidGroupRequests, setRaidGroupRequests ] = useState<RaidGroupRequest[]>([]); const [ raidGroupRequests, setRaidGroupRequests ] = useState<RaidGroupRequest[]>([]);
@@ -62,6 +66,7 @@ export function AuthProvider({
setExpiration(new Date(decodedToken.exp * 1000)); setExpiration(new Date(decodedToken.exp * 1000));
setFirstFetch(false); setFirstFetch(false);
setAccountId(decodedToken.accountId); setAccountId(decodedToken.accountId);
setAccountPermissions(JSON.parse(decodedToken.accountPermissions));
setRaidGroupPermissions(JSON.parse(decodedToken.raidGroupPermissions)); setRaidGroupPermissions(JSON.parse(decodedToken.raidGroupPermissions));
setGamePermissions(JSON.parse(decodedToken.gamePermissions)); setGamePermissions(JSON.parse(decodedToken.gamePermissions));
setRaidGroupRequests(JSON.parse(decodedToken.raidGroupRequests)); setRaidGroupRequests(JSON.parse(decodedToken.raidGroupRequests));
@@ -114,10 +119,11 @@ export function AuthProvider({
expiration, expiration,
setExpiration, setExpiration,
accountId, accountId,
accountPermissions,
raidGroupPermissions, raidGroupPermissions,
gamePermissions, gamePermissions,
raidGroupRequests raidGroupRequests
}), [ jwt, setJwt, expiration, setExpiration, accountId, raidGroupPermissions, gamePermissions, raidGroupRequests ]); }), [ jwt, setJwt, expiration, setExpiration, accountId, accountPermissions, raidGroupPermissions, gamePermissions, raidGroupRequests ]);
//TODO: Return a spinner while the first token is being fetched //TODO: Return a spinner while the first token is being fetched

View File

@@ -3,10 +3,14 @@ import DangerButton from "@/components/button/DangerButton";
import PrimaryButton from "@/components/button/PrimaryButton"; import PrimaryButton from "@/components/button/PrimaryButton";
import TertiaryButton from "@/components/button/TertiaryButton"; import TertiaryButton from "@/components/button/TertiaryButton";
import WarningButton from "@/components/button/WarningButton"; import WarningButton from "@/components/button/WarningButton";
import { Account } from "@/interface/Account";
import { useTheme } from "@/providers/ThemeProvider";
import { BsKeyFill, BsLockFill, BsPencilFill, BsTrash3, BsXCircle } from "react-icons/bs"; import { BsKeyFill, BsLockFill, BsPencilFill, BsTrash3, BsXCircle } from "react-icons/bs";
import { Tooltip } from "react-tooltip";
export default function AccountAdminButtons({ export default function AccountAdminButtons({
account,
buttonProps, buttonProps,
showForcePasswordResetModal, showForcePasswordResetModal,
showAccountPasswordSetModal, showAccountPasswordSetModal,
@@ -14,6 +18,7 @@ export default function AccountAdminButtons({
showUpdateAccountModal, showUpdateAccountModal,
showDeleteAccountModal showDeleteAccountModal
}:{ }:{
account?: Account;
buttonProps: ButtonProps; buttonProps: ButtonProps;
showForcePasswordResetModal: () => void; showForcePasswordResetModal: () => void;
showAccountPasswordSetModal: () => void; showAccountPasswordSetModal: () => void;
@@ -21,13 +26,20 @@ export default function AccountAdminButtons({
showUpdateAccountModal: () => void; showUpdateAccountModal: () => void;
showDeleteAccountModal: () => void; showDeleteAccountModal: () => void;
}){ }){
const { theme } = useTheme();
const componentId = crypto.randomUUID().replaceAll("-", "");
return ( return (
<div <div
className="flex flex-row gap-2" className="flex flex-row gap-2"
> >
<WarningButton <WarningButton
{...buttonProps} {...buttonProps}
id={`accountAdminButtonsForceReset${componentId}`}
onClick={showForcePasswordResetModal} onClick={showForcePasswordResetModal}
aria-label={`Force Password Reset for ${account?.username}`}
data-tooltip-delay-show={750}
> >
<BsLockFill <BsLockFill
size={22} size={22}
@@ -35,7 +47,10 @@ export default function AccountAdminButtons({
</WarningButton> </WarningButton>
<DangerButton <DangerButton
{...buttonProps} {...buttonProps}
id={`accountAdminButtonsPasswordSet${componentId}`}
onClick={showAccountPasswordSetModal} onClick={showAccountPasswordSetModal}
aria-label={`Set Password for ${account?.username}`}
data-tooltip-delay-show={750}
> >
<BsKeyFill <BsKeyFill
size={22} size={22}
@@ -43,7 +58,10 @@ export default function AccountAdminButtons({
</DangerButton> </DangerButton>
<TertiaryButton <TertiaryButton
{...buttonProps} {...buttonProps}
id={`accountAdminButtonsRevokeRefreshToken${componentId}`}
onClick={showRevokeRefreshTokenModal} onClick={showRevokeRefreshTokenModal}
aria-label={`Revoke Refresh Token for ${account?.username}`}
data-tooltip-delay-show={750}
> >
<BsXCircle <BsXCircle
size={22} size={22}
@@ -51,7 +69,10 @@ export default function AccountAdminButtons({
</TertiaryButton> </TertiaryButton>
<PrimaryButton <PrimaryButton
{...buttonProps} {...buttonProps}
id={`accountAdminButtonsUpdate${componentId}`}
onClick={showUpdateAccountModal} onClick={showUpdateAccountModal}
aria-label={`Update ${account?.username}`}
data-tooltip-delay-show={750}
> >
<BsPencilFill <BsPencilFill
size={22} size={22}
@@ -59,12 +80,50 @@ export default function AccountAdminButtons({
</PrimaryButton> </PrimaryButton>
<DangerButton <DangerButton
{...buttonProps} {...buttonProps}
id={`accountAdminButtonsDelete${componentId}`}
onClick={showDeleteAccountModal} onClick={showDeleteAccountModal}
aria-label={`Delete ${account?.username}`}
data-tooltip-delay-show={750}
> >
<BsTrash3 <BsTrash3
size={22} size={22}
/> />
</DangerButton> </DangerButton>
<Tooltip
anchorSelect={`#accountAdminButtonsForceReset${componentId}`}
place="top"
variant={theme === "dark" ? "light" : "dark"}
>
Force Password Reset
</Tooltip>
<Tooltip
anchorSelect={`#accountAdminButtonsPasswordSet${componentId}`}
place="top"
variant={theme === "dark" ? "light" : "dark"}
>
Set Password
</Tooltip>
<Tooltip
anchorSelect={`#accountAdminButtonsRevokeRefreshToken${componentId}`}
place="top"
variant={theme === "dark" ? "light" : "dark"}
>
Revoke Refresh Token
</Tooltip>
<Tooltip
anchorSelect={`#accountAdminButtonsUpdate${componentId}`}
place="top"
variant={theme === "dark" ? "light" : "dark"}
>
Update {account?.username}
</Tooltip>
<Tooltip
anchorSelect={`#accountAdminButtonsDelete${componentId}`}
place="top"
variant={theme === "dark" ? "light" : "dark"}
>
Delete {account?.username}
</Tooltip>
</div> </div>
); );
} }

View File

@@ -2,6 +2,8 @@ import { ButtonProps } from "@/components/button/Button";
import Table from "@/components/table/Table"; import Table from "@/components/table/Table";
import { Account } from "@/interface/Account"; import { Account } from "@/interface/Account";
import { RaidGroup } from "@/interface/RaidGroup"; import { RaidGroup } from "@/interface/RaidGroup";
import { useAuth } from "@/providers/AuthProvider";
import { isSiteAdmin } from "@/util/PermissionUtil";
import moment from "moment"; import moment from "moment";
import { useState } from "react"; import { useState } from "react";
import AccountAdminButtons from "./AccountAdminButtons"; import AccountAdminButtons from "./AccountAdminButtons";
@@ -22,6 +24,7 @@ export default function AccountsList({
accounts: Account[]; accounts: Account[];
raidGroup?: RaidGroup; raidGroup?: RaidGroup;
}){ }){
const { accountPermissions } = useAuth();
const [ selectedAccount, setSelectedAccount ] = useState<Account | undefined>(undefined); const [ selectedAccount, setSelectedAccount ] = useState<Account | undefined>(undefined);
const [ displayForcePasswordResetModal, setDisplayForcePasswordResetModal ] = useState(false); const [ displayForcePasswordResetModal, setDisplayForcePasswordResetModal ] = useState(false);
const [ displayAccountPasswordSetModal, setDisplayAccountPasswordSetModal ] = useState(false); const [ displayAccountPasswordSetModal, setDisplayAccountPasswordSetModal ] = useState(false);
@@ -40,12 +43,10 @@ export default function AccountsList({
const headElements: React.ReactNode[] = [ const headElements: React.ReactNode[] = [
<div>
ID
</div>,
<div> <div>
Username Username
</div>, </div>,
isSiteAdmin(accountPermissions) &&
<div> <div>
Email Email
</div>, </div>,
@@ -64,20 +65,18 @@ export default function AccountsList({
const bodyElements: React.ReactNode[][] = accounts.map((account) => [ const bodyElements: React.ReactNode[][] = accounts.map((account) => [
<div <div
className="text-nowrap" className="text-nowrap pl-2 text-start"
> >
{account.accountId}
</div>,
<div>
{account.username} {account.username}
</div>, </div>,
isSiteAdmin(accountPermissions) &&
<div> <div>
{account.email} {account.email}
</div>, </div>,
<div <div
className="text-nowrap" className="text-nowrap"
> >
{moment(account.loginDate).format("MM-DD-YYYY HH:mm")} {account.loginDate ? moment(account.loginDate).format("MM-DD-YYYY HH:mm") : <>&nbsp;</>}
</div>, </div>,
<div> <div>
{account.accountStatus} {account.accountStatus}
@@ -90,29 +89,33 @@ export default function AccountsList({
> >
&nbsp; &nbsp;
</div> </div>
<AccountAdminButtons {
buttonProps={buttonProps} isSiteAdmin(accountPermissions) &&
showForcePasswordResetModal={() => { <AccountAdminButtons
setSelectedAccount(account); account={account}
setDisplayForcePasswordResetModal(true); buttonProps={buttonProps}
}} showForcePasswordResetModal={() => {
showAccountPasswordSetModal={() => { setSelectedAccount(account);
setSelectedAccount(account); setDisplayForcePasswordResetModal(true);
setDisplayAccountPasswordSetModal(true); }}
}} showAccountPasswordSetModal={() => {
showRevokeRefreshTokenModal={() => { setSelectedAccount(account);
setSelectedAccount(account); setDisplayAccountPasswordSetModal(true);
setDisplayRevokeRefreshTokenModal(true); }}
}} showRevokeRefreshTokenModal={() => {
showUpdateAccountModal={() => { setSelectedAccount(account);
setSelectedAccount(account); setDisplayRevokeRefreshTokenModal(true);
setDisplayAccountModal(true); }}
}} showUpdateAccountModal={() => {
showDeleteAccountModal={() => { setSelectedAccount(account);
setSelectedAccount(account); setDisplayAccountModal(true);
setDisplayDeleteAccountModal(true); }}
}} showDeleteAccountModal={() => {
/> setSelectedAccount(account);
setDisplayDeleteAccountModal(true);
}}
/>
}
{ {
raidGroup && raidGroup &&
<RaidGroupAccountAdminButtons <RaidGroupAccountAdminButtons

View File

@@ -1,7 +1,9 @@
import { ButtonProps } from "@/components/button/Button"; import { ButtonProps } from "@/components/button/Button";
import DangerButton from "@/components/button/DangerButton"; import DangerButton from "@/components/button/DangerButton";
import WarningButton from "@/components/button/WarningButton"; import WarningButton from "@/components/button/WarningButton";
import { useTheme } from "@/providers/ThemeProvider";
import { BsKeyFill, BsTrash3 } from "react-icons/bs"; import { BsKeyFill, BsTrash3 } from "react-icons/bs";
import { Tooltip } from "react-tooltip";
export default function RaidGroupAccountAdminButtons({ export default function RaidGroupAccountAdminButtons({
@@ -13,13 +15,20 @@ export default function RaidGroupAccountAdminButtons({
showRaidGroupPermissionsModal: () => void; showRaidGroupPermissionsModal: () => void;
showRemoveFromRaidGroupModal: () => void; showRemoveFromRaidGroupModal: () => void;
}){ }){
const { theme } = useTheme();
const componentId = crypto.randomUUID().replaceAll("-", "");
return ( return (
<div <div
className="flex flex-row gap-2" className="flex flex-row gap-2"
> >
<WarningButton <WarningButton
{...buttonProps} {...buttonProps}
id={`raidGroupAccountAdminButtonsPermissions${componentId}`}
onClick={showRaidGroupPermissionsModal} onClick={showRaidGroupPermissionsModal}
aria-label="Edit Permissions"
data-tooltip-delay-show={750}
> >
<BsKeyFill <BsKeyFill
size={22} size={22}
@@ -27,12 +36,29 @@ export default function RaidGroupAccountAdminButtons({
</WarningButton> </WarningButton>
<DangerButton <DangerButton
{...buttonProps} {...buttonProps}
id={`raidGroupAccountAdminButtonsRemove${componentId}`}
onClick={showRemoveFromRaidGroupModal} onClick={showRemoveFromRaidGroupModal}
aria-label="Remove from Raid Group"
data-tooltip-delay-show={750}
> >
<BsTrash3 <BsTrash3
size={22} size={22}
/> />
</DangerButton> </DangerButton>
<Tooltip
anchorSelect={`#raidGroupAccountAdminButtonsPermissions${componentId}`}
place="top"
variant={theme === "dark" ? "light" : "dark"}
>
Edit Permissions
</Tooltip>
<Tooltip
anchorSelect={`#raidGroupAccountAdminButtonsRemove${componentId}`}
place="top"
variant={theme === "dark" ? "light" : "dark"}
>
Remove from Raid Group
</Tooltip>
</div> </div>
); );
} }

View File

@@ -43,7 +43,7 @@ export default function RaidGroupAccountsTab({
return ( return (
<> <>
<div <div
className="flex flex-row items-center justify-between w-full" className="flex flex-row items-center justify-between w-full mb-8"
> >
<div <div
className="flex flex-row items-center justify-center w-full" className="flex flex-row items-center justify-center w-full"

View File

@@ -31,7 +31,7 @@ export default function AccountModal({
setEmail(account?.email ?? ""); setEmail(account?.email ?? "");
setPassword(account?.password ?? ""); setPassword(account?.password ?? "");
setAccountStatus(account?.accountStatus ?? AccountStatus.ACTIVE); setAccountStatus(account?.accountStatus ?? AccountStatus.ACTIVE);
}, [ account, setUsername, setEmail, setPassword, setAccountStatus ]); }, [ display, account, setUsername, setEmail, setPassword, setAccountStatus ]);
const updateAccountMutate = useUpdateAccount(); const updateAccountMutate = useUpdateAccount();
@@ -103,10 +103,13 @@ export default function AccountModal({
/> />
) )
} }
<AccountStatusSelector {
value={accountStatus} account &&
onChange={(e) => setAccountStatus(e.currentTarget.value as AccountStatus)} <AccountStatusSelector
/> value={accountStatus}
onChange={(e) => setAccountStatus(e.currentTarget.value as AccountStatus)}
/>
}
</div> </div>
} }
modalFooter={ modalFooter={

View File

@@ -1,6 +1,8 @@
import { useCreateGameCalendarEvent, useCreateRaidGroupCalendarEvent, useDeleteGameCalendarEvent, useDeleteRaidGroupCalendarEvent, useUpdateGameCalendarEvent, useUpdateRaidGroupCalendarEvent } from "@/hooks/CalendarHooks"; import { useCreateGameCalendarEvent, useCreateRaidGroupCalendarEvent, useDeleteGameCalendarEvent, useDeleteRaidGroupCalendarEvent, useUpdateGameCalendarEvent, useUpdateRaidGroupCalendarEvent } from "@/hooks/CalendarHooks";
import { CalendarEvent } from "@/interface/Calendar"; import { CalendarEvent } from "@/interface/Calendar";
import { useAuth } from "@/providers/AuthProvider";
import { calendarEventToFullCalendarEvent } from "@/util/CalendarUtil"; import { calendarEventToFullCalendarEvent } from "@/util/CalendarUtil";
import { isGameAdmin, isRaidGroupAdmin, isRaidGroupLeader } from "@/util/PermissionUtil";
import { EventClickArg } from "@fullcalendar/core/index.js"; import { EventClickArg } from "@fullcalendar/core/index.js";
import dayGridPlugin from "@fullcalendar/daygrid"; import dayGridPlugin from "@fullcalendar/daygrid";
import interactionPlugin, { DateClickArg } from "@fullcalendar/interaction"; import interactionPlugin, { DateClickArg } from "@fullcalendar/interaction";
@@ -19,6 +21,7 @@ export default function CalendarDisplay({
raidGroupId?: string; raidGroupId?: string;
gameId?: string; gameId?: string;
}){ }){
const { accountPermissions, gamePermissions, raidGroupPermissions } = useAuth();
const [ displayCalendarEventModal, setDisplayCalendarEventModal ] = useState(false); const [ displayCalendarEventModal, setDisplayCalendarEventModal ] = useState(false);
const [ alterCalendarEvent, setAlterCalendarEvent ] = useState<CalendarEvent>(); const [ alterCalendarEvent, setAlterCalendarEvent ] = useState<CalendarEvent>();
const [ disableModal, setDisableModal ] = useState(false); const [ disableModal, setDisableModal ] = useState(false);
@@ -39,6 +42,15 @@ export default function CalendarDisplay({
eventStartDate: dateClickArg.date, eventStartDate: dateClickArg.date,
eventEndDate: moment(dateClickArg.date).add(1, "hours").toDate() eventEndDate: moment(dateClickArg.date).add(1, "hours").toDate()
} as CalendarEvent); } as CalendarEvent);
if(gameId && !isGameAdmin(gameId, gamePermissions, accountPermissions)){
setDisableModal(true);
}
if(raidGroupId && !isRaidGroupAdmin(raidGroupId, raidGroupPermissions, accountPermissions) && !isRaidGroupLeader(raidGroupId, raidGroupPermissions, accountPermissions)){
setDisableModal(true);
}
setDisplayCalendarEventModal(true); setDisplayCalendarEventModal(true);
} }
@@ -47,6 +59,12 @@ export default function CalendarDisplay({
if(raidGroupId && calendarEvent?.gameId){ if(raidGroupId && calendarEvent?.gameId){
setDisableModal(true); setDisableModal(true);
} }
else if(calendarEvent?.gameId && !isGameAdmin(calendarEvent?.gameId, gamePermissions, accountPermissions)){
setDisableModal(true);
}
else if(calendarEvent?.raidGroupId && !isRaidGroupAdmin(calendarEvent?.raidGroupId, raidGroupPermissions, accountPermissions) && !isRaidGroupLeader(calendarEvent?.raidGroupId, raidGroupPermissions, accountPermissions)){
setDisableModal(true);
}
setAlterCalendarEvent(calendarEvent); setAlterCalendarEvent(calendarEvent);
setDisplayCalendarEventModal(true); setDisplayCalendarEventModal(true);

View File

@@ -1,5 +1,7 @@
import { ButtonProps } from "@/components/button/Button"; import { ButtonProps } from "@/components/button/Button";
import { RaidGroup } from "@/interface/RaidGroup"; import { RaidGroup } from "@/interface/RaidGroup";
import { useAuth } from "@/providers/AuthProvider";
import { isRaidGroupAdmin } from "@/util/PermissionUtil";
import { useState } from "react"; import { useState } from "react";
import RaidGroupAdminButtons from "../raidGroup/RaidGroupAdminButtons"; import RaidGroupAdminButtons from "../raidGroup/RaidGroupAdminButtons";
import DeleteRaidGroupModal from "../raidGroup/modals/DeleteRaidGroupModal"; import DeleteRaidGroupModal from "../raidGroup/modals/DeleteRaidGroupModal";
@@ -11,6 +13,7 @@ export default function RaidGroupHeader({
}:{ }:{
raidGroup: RaidGroup; raidGroup: RaidGroup;
}){ }){
const { accountPermissions, raidGroupPermissions } = useAuth();
const [ displayEditRaidGroupModal, setDisplayEditRaidGroupModal ] = useState(false); const [ displayEditRaidGroupModal, setDisplayEditRaidGroupModal ] = useState(false);
const [ displayDeleteRaidGroupModal, setDisplayDeleteRaidGroupModal ] = useState(false); const [ displayDeleteRaidGroupModal, setDisplayDeleteRaidGroupModal ] = useState(false);
@@ -39,7 +42,11 @@ export default function RaidGroupHeader({
</div> </div>
<div> <div>
<RaidGroupAdminButtons <RaidGroupAdminButtons
buttonProps={buttonProps} raidGroup={raidGroup}
buttonProps={{
...buttonProps,
disabled: !isRaidGroupAdmin(raidGroup.raidGroupId ?? "", raidGroupPermissions, accountPermissions)
}}
showEditRaidGroupModal={() => setDisplayEditRaidGroupModal(true)} showEditRaidGroupModal={() => setDisplayEditRaidGroupModal(true)}
showDeleteRaidGroupModal={() => setDisplayDeleteRaidGroupModal(true)} showDeleteRaidGroupModal={() => setDisplayDeleteRaidGroupModal(true)}
hasRaidGroupPermissions={true} hasRaidGroupPermissions={true}

View File

@@ -182,6 +182,7 @@ export default function CalendarEventModal({
variant="ghost" variant="ghost"
shape="square" shape="square"
onClick={deleteCalendarEvent} onClick={deleteCalendarEvent}
disabled={disabled}
> >
<BsTrash3 <BsTrash3
size={22} size={22}

View File

@@ -1,25 +1,37 @@
import { ButtonProps } from "@/components/button/Button"; import { ButtonProps } from "@/components/button/Button";
import DangerButton from "@/components/button/DangerButton"; import DangerButton from "@/components/button/DangerButton";
import PrimaryButton from "@/components/button/PrimaryButton"; import PrimaryButton from "@/components/button/PrimaryButton";
import { ClassGroup } from "@/interface/ClassGroup";
import { useTheme } from "@/providers/ThemeProvider";
import { BsPencilFill, BsTrash3 } from "react-icons/bs"; import { BsPencilFill, BsTrash3 } from "react-icons/bs";
import { Tooltip } from "react-tooltip";
export default function ClassGroupButtons({ export default function ClassGroupButtons({
classGroup,
buttonProps, buttonProps,
showClassGroupModal, showClassGroupModal,
showDeleteClassGroupModal showDeleteClassGroupModal
}:{ }:{
classGroup: ClassGroup;
buttonProps: ButtonProps; buttonProps: ButtonProps;
showClassGroupModal: () => void; showClassGroupModal: () => void;
showDeleteClassGroupModal: () => void; showDeleteClassGroupModal: () => void;
}){ }){
const { theme } = useTheme();
const componentId = crypto.randomUUID().replaceAll("-", "");
return ( return (
<div <div
className="flex flex-row gap-2" className="flex flex-row gap-2"
> >
<PrimaryButton <PrimaryButton
{...buttonProps} {...buttonProps}
id={`classGroupAdminButtonsEdit${componentId}`}
onClick={showClassGroupModal} onClick={showClassGroupModal}
aria-label={`Edit ${classGroup.classGroupName}`}
data-tooltip-delay-show={750}
> >
<BsPencilFill <BsPencilFill
size={22} size={22}
@@ -27,12 +39,29 @@ export default function ClassGroupButtons({
</PrimaryButton> </PrimaryButton>
<DangerButton <DangerButton
{...buttonProps} {...buttonProps}
id={`classGroupAdminButtonsDelete${componentId}`}
onClick={showDeleteClassGroupModal} onClick={showDeleteClassGroupModal}
aria-label={`Delete ${classGroup.classGroupName}`}
data-tooltip-delay-show={750}
> >
<BsTrash3 <BsTrash3
size={22} size={22}
/> />
</DangerButton> </DangerButton>
<Tooltip
anchorSelect={`#classGroupAdminButtonsEdit${componentId}`}
place="top"
variant={theme === "dark" ? "light" : "dark"}
>
Edit {classGroup.classGroupName}
</Tooltip>
<Tooltip
anchorSelect={`#classGroupAdminButtonsDelete${componentId}`}
place="top"
variant={theme === "dark" ? "light" : "dark"}
>
Delete {classGroup.classGroupName}
</Tooltip>
</div> </div>
); );
} }

View File

@@ -4,6 +4,8 @@ import Table from "@/components/table/Table";
import { useGetGameClassesByClassGroup } from "@/hooks/GameClassHooks"; import { useGetGameClassesByClassGroup } from "@/hooks/GameClassHooks";
import { ClassGroup } from "@/interface/ClassGroup"; import { ClassGroup } from "@/interface/ClassGroup";
import { RaidGroup } from "@/interface/RaidGroup"; import { RaidGroup } from "@/interface/RaidGroup";
import { useAuth } from "@/providers/AuthProvider";
import { isRaidGroupAdmin, isRaidGroupLeader } from "@/util/PermissionUtil";
import { useState } from "react"; import { useState } from "react";
import ClassGroupButtons from "./ClassGroupButtons"; import ClassGroupButtons from "./ClassGroupButtons";
import ClassGroupModal from "./modal/ClassGroupModal"; import ClassGroupModal from "./modal/ClassGroupModal";
@@ -17,6 +19,7 @@ export default function ClassGroupList({
classGroups: ClassGroup[]; classGroups: ClassGroup[];
raidGroup: RaidGroup; raidGroup: RaidGroup;
}){ }){
const { accountPermissions, raidGroupPermissions } = useAuth();
const [ selectedClassGroup, setSelectedClassGroup ] = useState<ClassGroup>(); const [ selectedClassGroup, setSelectedClassGroup ] = useState<ClassGroup>();
const [ displayClassGroupModal, setDisplayClassGroupModal ] = useState(false); const [ displayClassGroupModal, setDisplayClassGroupModal ] = useState(false);
const [ displayDeleteClassGroupModal, setDisplayDeleteClassGroupModal ] = useState(false); const [ displayDeleteClassGroupModal, setDisplayDeleteClassGroupModal ] = useState(false);
@@ -26,7 +29,8 @@ export default function ClassGroupList({
const buttonProps: ButtonProps = { const buttonProps: ButtonProps = {
variant: "ghost", variant: "ghost",
size: "md", size: "md",
shape: "square" shape: "square",
disabled: !isRaidGroupAdmin(raidGroup.raidGroupId!, raidGroupPermissions, accountPermissions) && !isRaidGroupLeader(raidGroup.raidGroupId!, raidGroupPermissions, accountPermissions)
}; };
@@ -64,6 +68,7 @@ export default function ClassGroupList({
&nbsp; &nbsp;
</div> </div>
<ClassGroupButtons <ClassGroupButtons
classGroup={classGroup}
buttonProps={buttonProps} buttonProps={buttonProps}
showClassGroupModal={() => { showClassGroupModal={() => {
setSelectedClassGroup(classGroup); setSelectedClassGroup(classGroup);

View File

@@ -3,6 +3,8 @@ import TextInput from "@/components/input/TextInput";
import Pagination from "@/components/pagination/Pagination"; import Pagination from "@/components/pagination/Pagination";
import { useGetClassGroupsCount } from "@/hooks/ClassGroupHooks"; import { useGetClassGroupsCount } from "@/hooks/ClassGroupHooks";
import { RaidGroup } from "@/interface/RaidGroup"; import { RaidGroup } from "@/interface/RaidGroup";
import { useAuth } from "@/providers/AuthProvider";
import { isRaidGroupAdmin, isRaidGroupLeader } from "@/util/PermissionUtil";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useDebouncedCallback } from "use-debounce"; import { useDebouncedCallback } from "use-debounce";
import ClassGroupsLoader from "./ClassGroupsLoader"; import ClassGroupsLoader from "./ClassGroupsLoader";
@@ -14,6 +16,7 @@ export default function ClassGroupsTab({
}:{ }:{
raidGroup: RaidGroup; raidGroup: RaidGroup;
}){ }){
const { accountPermissions, raidGroupPermissions } = useAuth();
const [ displayCreateClassGroupModal, setDisplayCreateClassGroupModal ] = useState(false); const [ displayCreateClassGroupModal, setDisplayCreateClassGroupModal ] = useState(false);
const [ page, setPage ] = useState(1); const [ page, setPage ] = useState(1);
const [ totalPages, setTotalPages ] = useState(1); const [ totalPages, setTotalPages ] = useState(1);
@@ -60,6 +63,7 @@ export default function ClassGroupsTab({
<PrimaryButton <PrimaryButton
className="text-nowrap" className="text-nowrap"
onClick={() => setDisplayCreateClassGroupModal(true)} onClick={() => setDisplayCreateClassGroupModal(true)}
disabled={!isRaidGroupAdmin(raidGroup.raidGroupId!, raidGroupPermissions, accountPermissions) && !isRaidGroupLeader(raidGroup.raidGroupId!, raidGroupPermissions, accountPermissions)}
> >
Create Class Group Create Class Group
</PrimaryButton> </PrimaryButton>

View File

@@ -2,6 +2,8 @@ import PrimaryButton from "@/components/button/PrimaryButton";
import TextInput from "@/components/input/TextInput"; import TextInput from "@/components/input/TextInput";
import Pagination from "@/components/pagination/Pagination"; import Pagination from "@/components/pagination/Pagination";
import { useGetGamesCount } from "@/hooks/GameHooks"; import { useGetGamesCount } from "@/hooks/GameHooks";
import { useAuth } from "@/providers/AuthProvider";
import { isSiteAdmin } from "@/util/PermissionUtil";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useDebouncedCallback } from "use-debounce"; import { useDebouncedCallback } from "use-debounce";
import { GamesLoader } from "./GamesLoader"; import { GamesLoader } from "./GamesLoader";
@@ -9,6 +11,7 @@ import GameModal from "./modals/GameModal";
export default function AllGamesDisplay(){ export default function AllGamesDisplay(){
const { accountPermissions } = useAuth();
const [ displayCreateGameModal, setDisplayCreateGameModal ] = useState(false); const [ displayCreateGameModal, setDisplayCreateGameModal ] = useState(false);
const [ page, setPage ] = useState(1); const [ page, setPage ] = useState(1);
const [ totalPages, setTotalPages ] = useState(1); const [ totalPages, setTotalPages ] = useState(1);
@@ -54,6 +57,7 @@ export default function AllGamesDisplay(){
<PrimaryButton <PrimaryButton
className="mb-8" className="mb-8"
onClick={() => setDisplayCreateGameModal(true)} onClick={() => setDisplayCreateGameModal(true)}
disabled={!isSiteAdmin(accountPermissions)}
> >
Create Game Create Game
</PrimaryButton> </PrimaryButton>

View File

@@ -1,25 +1,37 @@
import { ButtonProps } from "@/components/button/Button"; import { ButtonProps } from "@/components/button/Button";
import DangerButton from "@/components/button/DangerButton"; import DangerButton from "@/components/button/DangerButton";
import PrimaryButton from "@/components/button/PrimaryButton"; import PrimaryButton from "@/components/button/PrimaryButton";
import { Game } from "@/interface/Game";
import { useTheme } from "@/providers/ThemeProvider";
import { BsPencilFill, BsTrash3 } from "react-icons/bs"; import { BsPencilFill, BsTrash3 } from "react-icons/bs";
import { Tooltip } from "react-tooltip";
export default function GameAdminButtons({ export default function GameAdminButtons({
game,
buttonProps, buttonProps,
showEditGameModal, showEditGameModal,
showDeleteGameModal showDeleteGameModal
}:{ }:{
game: Game;
buttonProps: ButtonProps; buttonProps: ButtonProps;
showEditGameModal: () => void; showEditGameModal: () => void;
showDeleteGameModal: () => void; showDeleteGameModal: () => void;
}){ }){
const { theme } = useTheme();
const componentId = crypto.randomUUID().replaceAll("-", "");
return ( return (
<div <div
className="flex flex-row items-center justify-center gap-2" className="flex flex-row items-center justify-center gap-2"
> >
<PrimaryButton <PrimaryButton
{...buttonProps} {...buttonProps}
id={`gameAdminButtonsEdit${componentId}`}
onClick={showEditGameModal} onClick={showEditGameModal}
aria-label={`Edit ${game.gameName}`}
data-tooltip-delay-show={750}
> >
<BsPencilFill <BsPencilFill
size={22} size={22}
@@ -27,12 +39,29 @@ export default function GameAdminButtons({
</PrimaryButton> </PrimaryButton>
<DangerButton <DangerButton
{...buttonProps} {...buttonProps}
id={`gameAdminButtonsDelete${componentId}`}
onClick={showDeleteGameModal} onClick={showDeleteGameModal}
aria-label={`Delete ${game.gameName}`}
data-tooltip-delay-show={750}
> >
<BsTrash3 <BsTrash3
size={22} size={22}
/> />
</DangerButton> </DangerButton>
<Tooltip
anchorSelect={`#gameAdminButtonsEdit${componentId}`}
place="top"
variant={theme === "dark" ? "light" : "dark"}
>
Edit {game.gameName}
</Tooltip>
<Tooltip
anchorSelect={`#gameAdminButtonsDelete${componentId}`}
place="top"
variant={theme === "dark" ? "light" : "dark"}
>
Delete {game.gameName}
</Tooltip>
</div> </div>
); );
} }

View File

@@ -1,5 +1,7 @@
import { ButtonProps } from "@/components/button/Button"; import { ButtonProps } from "@/components/button/Button";
import { Game } from "@/interface/Game"; import { Game } from "@/interface/Game";
import { useAuth } from "@/providers/AuthProvider";
import { isGameAdmin } from "@/util/PermissionUtil";
import { useState } from "react"; import { useState } from "react";
import GameAdminButtons from "./GameAdminButtons"; import GameAdminButtons from "./GameAdminButtons";
import DeleteGameModal from "./modals/DeleteGameModal"; import DeleteGameModal from "./modals/DeleteGameModal";
@@ -11,13 +13,15 @@ export default function GameHeader({
}:{ }:{
game: Game; game: Game;
}){ }){
const { gamePermissions, accountPermissions } = useAuth();
const [ displayEditGameModal, setDisplayEditGameModal ] = useState(false); const [ displayEditGameModal, setDisplayEditGameModal ] = useState(false);
const [ displayDeleteGameModal, setDisplayDeleteGameModal ] = useState(false); const [ displayDeleteGameModal, setDisplayDeleteGameModal ] = useState(false);
const buttonProps: ButtonProps = { const buttonProps: ButtonProps = {
variant: "ghost", variant: "ghost",
size: "md", size: "md",
shape: "square" shape: "square",
disabled: !isGameAdmin(game.gameId ?? "", gamePermissions, accountPermissions)
}; };
@@ -39,6 +43,7 @@ export default function GameHeader({
</div> </div>
<div> <div>
<GameAdminButtons <GameAdminButtons
game={game}
buttonProps={buttonProps} buttonProps={buttonProps}
showEditGameModal={() => setDisplayEditGameModal(true)} showEditGameModal={() => setDisplayEditGameModal(true)}
showDeleteGameModal={() => setDisplayDeleteGameModal(true)} showDeleteGameModal={() => setDisplayDeleteGameModal(true)}

View File

@@ -1,6 +1,8 @@
import { ButtonProps } from "@/components/button/Button"; import { ButtonProps } from "@/components/button/Button";
import Table from "@/components/table/Table"; import Table from "@/components/table/Table";
import { Game } from "@/interface/Game"; import { Game } from "@/interface/Game";
import { useAuth } from "@/providers/AuthProvider";
import { isGameAdmin } from "@/util/PermissionUtil";
import { useState } from "react"; import { useState } from "react";
import { Link } from "react-router"; import { Link } from "react-router";
import GameAdminButtons from "./GameAdminButtons"; import GameAdminButtons from "./GameAdminButtons";
@@ -13,6 +15,7 @@ export default function GamesList({
}:{ }:{
games: Game[]; games: Game[];
}){ }){
const { accountPermissions, gamePermissions } = useAuth();
const [ selectedGame, setSelectedGame ] = useState<Game>(); const [ selectedGame, setSelectedGame ] = useState<Game>();
const [ displayEditGameModal, setDisplayEditGameModal ] = useState(false); const [ displayEditGameModal, setDisplayEditGameModal ] = useState(false);
const [ displayDeleteGameModal, setDisplayDeleteGameModal ] = useState(false); const [ displayDeleteGameModal, setDisplayDeleteGameModal ] = useState(false);
@@ -68,7 +71,11 @@ export default function GamesList({
&nbsp; &nbsp;
</div> </div>
<GameAdminButtons <GameAdminButtons
buttonProps={buttonProps} game={game}
buttonProps={{
...buttonProps,
disabled: !isGameAdmin(game.gameId ?? "", gamePermissions, accountPermissions)
}}
showEditGameModal={() => { showEditGameModal={() => {
setSelectedGame(game); setSelectedGame(game);
setDisplayEditGameModal(true); setDisplayEditGameModal(true);

View File

@@ -1,5 +1,6 @@
import { ButtonShape, ButtonSizeType, ButtonVariant } from "@/components/button/Button"; import { ButtonShape, ButtonSizeType, ButtonVariant } from "@/components/button/Button";
import Table from "@/components/table/Table"; import Table from "@/components/table/Table";
import { Game } from "@/interface/Game";
import { elementBg } from "@/util/SkeletonUtil"; import { elementBg } from "@/util/SkeletonUtil";
import React from "react"; import React from "react";
import GameAdminButtons from "./GameAdminButtons"; import GameAdminButtons from "./GameAdminButtons";
@@ -64,7 +65,7 @@ function GameSkeleton(): React.ReactNode[]{
className={`flex flex-row items-center justify-center gap-2 pl-16`} className={`flex flex-row items-center justify-center gap-2 pl-16`}
> >
<div className="py-4 border-l border-neutral-500">&nbsp;</div> <div className="py-4 border-l border-neutral-500">&nbsp;</div>
<GameAdminButtons {...buttonsProps}/> <GameAdminButtons game={{} as Game} {...buttonsProps}/>
</div> </div>
]; ];

View File

@@ -1,25 +1,37 @@
import { ButtonProps } from "@/components/button/Button"; import { ButtonProps } from "@/components/button/Button";
import DangerButton from "@/components/button/DangerButton"; import DangerButton from "@/components/button/DangerButton";
import PrimaryButton from "@/components/button/PrimaryButton"; import PrimaryButton from "@/components/button/PrimaryButton";
import { GameClass } from "@/interface/GameClass";
import { useTheme } from "@/providers/ThemeProvider";
import { BsPencilFill, BsTrash3 } from "react-icons/bs"; import { BsPencilFill, BsTrash3 } from "react-icons/bs";
import { Tooltip } from "react-tooltip";
export default function GameClassAdminButtons({ export default function GameClassAdminButtons({
gameClass,
buttonProps, buttonProps,
showEditGameClassModal, showEditGameClassModal,
showDeleteGameClassModal showDeleteGameClassModal
}:{ }:{
gameClass: GameClass;
buttonProps: ButtonProps; buttonProps: ButtonProps;
showEditGameClassModal: () => void; showEditGameClassModal: () => void;
showDeleteGameClassModal: () => void; showDeleteGameClassModal: () => void;
}){ }){
const { theme } = useTheme();
const componentId = crypto.randomUUID().replaceAll("-", "");
return ( return (
<div <div
className="flex flex-row items-center justify-center gap-2" className="flex flex-row items-center justify-center gap-2"
> >
<PrimaryButton <PrimaryButton
{...buttonProps} {...buttonProps}
id={`gameClassAdminButtonsEdit${componentId}`}
onClick={showEditGameClassModal} onClick={showEditGameClassModal}
aria-label={`Edit ${gameClass.gameClassName}`}
data-tooltip-delay-show={750}
> >
<BsPencilFill <BsPencilFill
size={22} size={22}
@@ -27,12 +39,29 @@ export default function GameClassAdminButtons({
</PrimaryButton> </PrimaryButton>
<DangerButton <DangerButton
{...buttonProps} {...buttonProps}
id={`gameClassAdminButtonsDelete${componentId}`}
onClick={showDeleteGameClassModal} onClick={showDeleteGameClassModal}
aria-label={`Delete ${gameClass.gameClassName}`}
data-tooltip-delay-show={750}
> >
<BsTrash3 <BsTrash3
size={22} size={22}
/> />
</DangerButton> </DangerButton>
<Tooltip
anchorSelect={`#gameClassAdminButtonsEdit${componentId}`}
place="top"
variant={theme === "dark" ? "light" : "dark"}
>
Edit {gameClass.gameClassName}
</Tooltip>
<Tooltip
anchorSelect={`#gameClassAdminButtonsDelete${componentId}`}
place="top"
variant={theme === "dark" ? "light" : "dark"}
>
Delete {gameClass.gameClassName}
</Tooltip>
</div> </div>
); );
} }

View File

@@ -1,5 +1,7 @@
import PrimaryButton from "@/components/button/PrimaryButton"; import PrimaryButton from "@/components/button/PrimaryButton";
import TextInput from "@/components/input/TextInput"; import TextInput from "@/components/input/TextInput";
import { useAuth } from "@/providers/AuthProvider";
import { isGameAdmin } from "@/util/PermissionUtil";
import { useState } from "react"; import { useState } from "react";
import GameClassModal from "./modals/GameClassModal"; import GameClassModal from "./modals/GameClassModal";
@@ -13,6 +15,7 @@ export default function GameClassCreateAndSearch({
searchTerm: string; searchTerm: string;
setSearchTerm: (searchTerm: string) => void; setSearchTerm: (searchTerm: string) => void;
}){ }){
const { gamePermissions, accountPermissions } = useAuth();
const [ displayGameClassModal, setDisplayGameClassModal ] = useState(false); const [ displayGameClassModal, setDisplayGameClassModal ] = useState(false);
const modalId = crypto.randomUUID().replaceAll("-", ""); const modalId = crypto.randomUUID().replaceAll("-", "");
@@ -33,6 +36,7 @@ export default function GameClassCreateAndSearch({
<PrimaryButton <PrimaryButton
className="mb-8" className="mb-8"
onClick={() => setDisplayGameClassModal(true)} onClick={() => setDisplayGameClassModal(true)}
disabled={!isGameAdmin(gameId, gamePermissions, accountPermissions)}
> >
Create Game Class Create Game Class
</PrimaryButton> </PrimaryButton>

View File

@@ -1,6 +1,8 @@
import { ButtonProps } from "@/components/button/Button"; import { ButtonProps } from "@/components/button/Button";
import Table from "@/components/table/Table"; import Table from "@/components/table/Table";
import { GameClass } from "@/interface/GameClass"; import { GameClass } from "@/interface/GameClass";
import { useAuth } from "@/providers/AuthProvider";
import { isGameAdmin } from "@/util/PermissionUtil";
import { useState } from "react"; import { useState } from "react";
import GameClassAdminButtons from "./GameClassAdminButtons"; import GameClassAdminButtons from "./GameClassAdminButtons";
import DeleteGameClassModal from "./modals/DeleteGameClassModal"; import DeleteGameClassModal from "./modals/DeleteGameClassModal";
@@ -12,6 +14,7 @@ export default function GameClassList({
}:{ }:{
gameClasses: GameClass[]; gameClasses: GameClass[];
}){ }){
const { gamePermissions, accountPermissions } = useAuth();
const [ selectedGameClass, setSelectedGameClass ] = useState<GameClass>(); const [ selectedGameClass, setSelectedGameClass ] = useState<GameClass>();
const [ displayGameClassModal, setDisplayGameClassModal ] = useState(false); const [ displayGameClassModal, setDisplayGameClassModal ] = useState(false);
const [ displayDeleteGameClassModal, setDisplayDeleteGameClassModal ] = useState(false); const [ displayDeleteGameClassModal, setDisplayDeleteGameClassModal ] = useState(false);
@@ -65,7 +68,11 @@ export default function GameClassList({
&nbsp; &nbsp;
</div> </div>
<GameClassAdminButtons <GameClassAdminButtons
buttonProps={buttonProps} gameClass={gameClass}
buttonProps={{
...buttonProps,
disabled: !isGameAdmin(gameClass.gameId, gamePermissions, accountPermissions)
}}
showEditGameClassModal={() => { showEditGameClassModal={() => {
setSelectedGameClass(gameClass); setSelectedGameClass(gameClass);
setDisplayGameClassModal(true); setDisplayGameClassModal(true);

View File

@@ -1,5 +1,6 @@
import { ButtonShape, ButtonSizeType, ButtonVariant } from "@/components/button/Button"; import { ButtonShape, ButtonSizeType, ButtonVariant } from "@/components/button/Button";
import Table from "@/components/table/Table"; import Table from "@/components/table/Table";
import { GameClass } from "@/interface/GameClass";
import { elementBg } from "@/util/SkeletonUtil"; import { elementBg } from "@/util/SkeletonUtil";
import GameClassAdminButtons from "./GameClassAdminButtons"; import GameClassAdminButtons from "./GameClassAdminButtons";
@@ -49,6 +50,7 @@ function GameClassSkeleton(): React.ReactNode[]{
shape: "square" as ButtonShape, shape: "square" as ButtonShape,
disabled: true disabled: true
}, },
gameClass:{} as GameClass,
showEditGameClassModal: () => {}, showEditGameClassModal: () => {},
showDeleteGameClassModal: () => {} showDeleteGameClassModal: () => {}
} }

View File

@@ -2,20 +2,29 @@ import { ButtonProps } from "@/components/button/Button";
import DangerButton from "@/components/button/DangerButton"; import DangerButton from "@/components/button/DangerButton";
import PrimaryButton from "@/components/button/PrimaryButton"; import PrimaryButton from "@/components/button/PrimaryButton";
import SuccessButton from "@/components/button/SuccessButton"; import SuccessButton from "@/components/button/SuccessButton";
import { Person } from "@/interface/Person";
import { useTheme } from "@/providers/ThemeProvider";
import { BsPencilFill, BsPlusLg, BsTrash3 } from "react-icons/bs"; import { BsPencilFill, BsPlusLg, BsTrash3 } from "react-icons/bs";
import { Tooltip } from "react-tooltip";
export default function PersonAdminButtons({ export default function PersonAdminButtons({
person,
buttonProps, buttonProps,
showUpdatePersonModal, showUpdatePersonModal,
showDeletePersonModal, showDeletePersonModal,
showCreatePersonCharacterModal showCreatePersonCharacterModal
}:{ }:{
person?: Person;
buttonProps: ButtonProps; buttonProps: ButtonProps;
showUpdatePersonModal: () => void; showUpdatePersonModal: () => void;
showDeletePersonModal: () => void; showDeletePersonModal: () => void;
showCreatePersonCharacterModal?: () => void; showCreatePersonCharacterModal?: () => void;
}){ }){
const { theme } = useTheme();
const componentId = crypto.randomUUID().replaceAll("-", "");
return ( return (
<div <div
className="flex flex-row gap-2" className="flex flex-row gap-2"
@@ -24,8 +33,11 @@ export default function PersonAdminButtons({
showCreatePersonCharacterModal && showCreatePersonCharacterModal &&
<SuccessButton <SuccessButton
{...buttonProps} {...buttonProps}
id={`personAdminButtonsAddCharacter${componentId}`}
size="sm" size="sm"
onClick={showCreatePersonCharacterModal} onClick={showCreatePersonCharacterModal}
aria-label={`Add Character to ${person?.personName}`}
data-tooltip-delay-show={750}
> >
<BsPlusLg <BsPlusLg
size={30} size={30}
@@ -35,7 +47,10 @@ export default function PersonAdminButtons({
} }
<PrimaryButton <PrimaryButton
{...buttonProps} {...buttonProps}
id={`personAdminButtonsEdit${componentId}`}
onClick={showUpdatePersonModal} onClick={showUpdatePersonModal}
aria-label={`Edit ${person?.personName}`}
data-tooltip-delay-show={750}
> >
<BsPencilFill <BsPencilFill
size={22} size={22}
@@ -43,12 +58,36 @@ export default function PersonAdminButtons({
</PrimaryButton> </PrimaryButton>
<DangerButton <DangerButton
{...buttonProps} {...buttonProps}
id={`personAdminButtonsDelete${componentId}`}
onClick={showDeletePersonModal} onClick={showDeletePersonModal}
aria-label={`Delete ${person?.personName}`}
data-tooltip-delay-show={750}
> >
<BsTrash3 <BsTrash3
size={22} size={22}
/> />
</DangerButton> </DangerButton>
<Tooltip
anchorSelect={`#personAdminButtonsAddCharacter${componentId}`}
place="top"
variant={theme === "dark" ? "light" : "dark"}
>
Add Character to {person?.personName}
</Tooltip>
<Tooltip
anchorSelect={`#personAdminButtonsEdit${componentId}`}
place="top"
variant={theme === "dark" ? "light" : "dark"}
>
Edit {person?.personName}
</Tooltip>
<Tooltip
anchorSelect={`#personAdminButtonsDelete${componentId}`}
place="top"
variant={theme === "dark" ? "light" : "dark"}
>
Delete {person?.personName}
</Tooltip>
</div> </div>
); );
} }

View File

@@ -3,6 +3,8 @@ import PersonCharacterDisplay from "@/components/personCharacter/PersonCharacter
import Table from "@/components/table/Table"; import Table from "@/components/table/Table";
import { Person } from "@/interface/Person"; import { Person } from "@/interface/Person";
import { RaidGroup } from "@/interface/RaidGroup"; import { RaidGroup } from "@/interface/RaidGroup";
import { useAuth } from "@/providers/AuthProvider";
import { isRaidGroupAdmin, isRaidGroupLeader } from "@/util/PermissionUtil";
import { useState } from "react"; import { useState } from "react";
import { Link } from "react-router"; import { Link } from "react-router";
import PersonCharacterModal from "../personCharacter/modal/PersonCharacterModal"; import PersonCharacterModal from "../personCharacter/modal/PersonCharacterModal";
@@ -18,6 +20,7 @@ export default function PersonList({
people: Person[]; people: Person[];
raidGroup: RaidGroup; raidGroup: RaidGroup;
}){ }){
const { accountPermissions, raidGroupPermissions } = useAuth();
const [ selectedPerson, setSelectedPerson ] = useState<Person>(); const [ selectedPerson, setSelectedPerson ] = useState<Person>();
const [ displayUpdatePersonModal, setDisplayUpdatePersonModal ] = useState(false); const [ displayUpdatePersonModal, setDisplayUpdatePersonModal ] = useState(false);
const [ displayDeletePersonModal, setDisplayDeletePersonModal ] = useState(false); const [ displayDeletePersonModal, setDisplayDeletePersonModal ] = useState(false);
@@ -27,7 +30,8 @@ export default function PersonList({
const buttonProps: ButtonProps = { const buttonProps: ButtonProps = {
variant: "ghost", variant: "ghost",
size: "md", size: "md",
shape: "square" shape: "square",
disabled: !isRaidGroupAdmin(raidGroup.raidGroupId ?? "", raidGroupPermissions, accountPermissions) && !isRaidGroupLeader(raidGroup.raidGroupId ?? "", raidGroupPermissions, accountPermissions)
}; };
@@ -78,6 +82,7 @@ export default function PersonList({
&nbsp; &nbsp;
</div> </div>
<PersonAdminButtons <PersonAdminButtons
person={person}
buttonProps={buttonProps} buttonProps={buttonProps}
showUpdatePersonModal={() => { showUpdatePersonModal={() => {
setSelectedPerson(person); setSelectedPerson(person);

View File

@@ -3,6 +3,8 @@ import TextInput from "@/components/input/TextInput";
import Pagination from "@/components/pagination/Pagination"; import Pagination from "@/components/pagination/Pagination";
import { useGetPeopleByRaidGroupCount } from "@/hooks/PersonHooks"; import { useGetPeopleByRaidGroupCount } from "@/hooks/PersonHooks";
import { RaidGroup } from "@/interface/RaidGroup"; import { RaidGroup } from "@/interface/RaidGroup";
import { useAuth } from "@/providers/AuthProvider";
import { isRaidGroupAdmin, isRaidGroupLeader } from "@/util/PermissionUtil";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useDebouncedCallback } from "use-debounce"; import { useDebouncedCallback } from "use-debounce";
import PersonModal from "./modals/PersonModal"; import PersonModal from "./modals/PersonModal";
@@ -14,6 +16,7 @@ export default function PersonTab({
}:{ }:{
raidGroup: RaidGroup; raidGroup: RaidGroup;
}){ }){
const { accountPermissions, raidGroupPermissions } = useAuth();
const [ displayCreatePersonModal, setDisplayCreatePersonModal ] = useState(false); const [ displayCreatePersonModal, setDisplayCreatePersonModal ] = useState(false);
const [ page, setPage ] = useState(1); const [ page, setPage ] = useState(1);
const [ totalPages, setTotalPages ] = useState(1); const [ totalPages, setTotalPages ] = useState(1);
@@ -60,6 +63,7 @@ export default function PersonTab({
<PrimaryButton <PrimaryButton
className="text-nowrap" className="text-nowrap"
onClick={() => setDisplayCreatePersonModal(true)} onClick={() => setDisplayCreatePersonModal(true)}
disabled={!isRaidGroupAdmin(raidGroup.raidGroupId ?? "", raidGroupPermissions, accountPermissions) && !isRaidGroupLeader(raidGroup.raidGroupId ?? "", raidGroupPermissions, accountPermissions)}
> >
Create Person Create Person
</PrimaryButton> </PrimaryButton>

View File

@@ -2,10 +2,14 @@ import { ButtonProps } from "@/components/button/Button";
import DangerButton from "@/components/button/DangerButton"; import DangerButton from "@/components/button/DangerButton";
import PrimaryButton from "@/components/button/PrimaryButton"; import PrimaryButton from "@/components/button/PrimaryButton";
import TertiaryButton from "@/components/button/TertiaryButton"; import TertiaryButton from "@/components/button/TertiaryButton";
import { RaidGroup } from "@/interface/RaidGroup";
import { useTheme } from "@/providers/ThemeProvider";
import { BsChatRightText, BsPencilFill, BsTrash3 } from "react-icons/bs"; import { BsChatRightText, BsPencilFill, BsTrash3 } from "react-icons/bs";
import { Tooltip } from "react-tooltip";
export default function RaidGroupAdminButtons({ export default function RaidGroupAdminButtons({
raidGroup,
buttonProps, buttonProps,
showRaidGroupRequestModal, showRaidGroupRequestModal,
showEditRaidGroupModal, showEditRaidGroupModal,
@@ -13,6 +17,7 @@ export default function RaidGroupAdminButtons({
hasRaidGroupPermissions, hasRaidGroupPermissions,
hasRaidGroupRequest hasRaidGroupRequest
}:{ }:{
raidGroup: RaidGroup;
buttonProps: ButtonProps; buttonProps: ButtonProps;
showRaidGroupRequestModal?: () => void; showRaidGroupRequestModal?: () => void;
showEditRaidGroupModal: () => void; showEditRaidGroupModal: () => void;
@@ -20,6 +25,10 @@ export default function RaidGroupAdminButtons({
hasRaidGroupPermissions: boolean; hasRaidGroupPermissions: boolean;
hasRaidGroupRequest: boolean; hasRaidGroupRequest: boolean;
}){ }){
const { theme } = useTheme();
const componentId = crypto.randomUUID().replaceAll("-", "");
return ( return (
<div <div
className="flex flex-row items-center justify-center gap-2" className="flex flex-row items-center justify-center gap-2"
@@ -27,9 +36,12 @@ export default function RaidGroupAdminButtons({
{ {
!hasRaidGroupPermissions && !hasRaidGroupPermissions &&
<TertiaryButton <TertiaryButton
id={`raidGroupRequestButton${componentId}`}
{...buttonProps} {...buttonProps}
onClick={showRaidGroupRequestModal} onClick={showRaidGroupRequestModal}
disabled={hasRaidGroupRequest} disabled={hasRaidGroupRequest}
aria-label={`Request to join ${raidGroup.raidGroupName}`}
data-tooltip-delay-show={750}
> >
<BsChatRightText <BsChatRightText
size={22} size={22}
@@ -37,21 +49,48 @@ export default function RaidGroupAdminButtons({
</TertiaryButton> </TertiaryButton>
} }
<PrimaryButton <PrimaryButton
id={`raidGroupEditButton${componentId}`}
{...buttonProps} {...buttonProps}
onClick={showEditRaidGroupModal} onClick={showEditRaidGroupModal}
aria-label={`Edit ${raidGroup.raidGroupName}`}
data-tooltip-delay-show={750}
> >
<BsPencilFill <BsPencilFill
size={22} size={22}
/> />
</PrimaryButton> </PrimaryButton>
<DangerButton <DangerButton
id={`raidGroupDeleteButton${componentId}`}
{...buttonProps} {...buttonProps}
onClick={showDeleteRaidGroupModal} onClick={showDeleteRaidGroupModal}
aria-label={`Delete ${raidGroup.raidGroupName}`}
data-tooltip-delay-show={750}
> >
<BsTrash3 <BsTrash3
size={22} size={22}
/> />
</DangerButton> </DangerButton>
<Tooltip
anchorSelect={`#raidGroupRequestButton${componentId}`}
place="top"
variant={theme === "dark" ? "light" : "dark"}
>
Request to join {raidGroup.raidGroupName}
</Tooltip>
<Tooltip
anchorSelect={`#raidGroupEditButton${componentId}`}
place="top"
variant={theme === "dark" ? "light" : "dark"}
>
Edit {raidGroup.raidGroupName}
</Tooltip>
<Tooltip
anchorSelect={`#raidGroupDeleteButton${componentId}`}
place="top"
variant={theme === "dark" ? "light" : "dark"}
>
Delete {raidGroup.raidGroupName}
</Tooltip>
</div> </div>
); );
} }

View File

@@ -2,7 +2,7 @@ import { ButtonProps } from "@/components/button/Button";
import Table from "@/components/table/Table"; import Table from "@/components/table/Table";
import { RaidGroup } from "@/interface/RaidGroup"; import { RaidGroup } from "@/interface/RaidGroup";
import { useAuth } from "@/providers/AuthProvider"; import { useAuth } from "@/providers/AuthProvider";
import { containsRaidGroupPermission, containsRaidGroupRequest } from "@/util/PermissionUtil"; import { containsRaidGroupPermission, containsRaidGroupRequest, isRaidGroupAdmin } from "@/util/PermissionUtil";
import { useState } from "react"; import { useState } from "react";
import { Link } from "react-router"; import { Link } from "react-router";
import RaidGroupRequestModal from "../raidGroupRequest/modal/RaidGroupRequestModal"; import RaidGroupRequestModal from "../raidGroupRequest/modal/RaidGroupRequestModal";
@@ -20,7 +20,7 @@ export default function RaidGroupsList({
const [ displayRaidGroupRequestModal, setDisplayRaidGroupRequestModal ] = useState(false); const [ displayRaidGroupRequestModal, setDisplayRaidGroupRequestModal ] = useState(false);
const [ displayEditRaidGroupModal, setDisplayEditRaidGroupModal ] = useState(false); const [ displayEditRaidGroupModal, setDisplayEditRaidGroupModal ] = useState(false);
const [ displayDeleteRaidGroupModal, setDisplayDeleteRaidGroupModal ] = useState(false); const [ displayDeleteRaidGroupModal, setDisplayDeleteRaidGroupModal ] = useState(false);
const { raidGroupPermissions, raidGroupRequests } = useAuth(); const { accountPermissions, raidGroupPermissions, raidGroupRequests } = useAuth();
const buttonProps: ButtonProps = { const buttonProps: ButtonProps = {
@@ -73,7 +73,11 @@ export default function RaidGroupsList({
&nbsp; &nbsp;
</div> </div>
<RaidGroupAdminButtons <RaidGroupAdminButtons
buttonProps={buttonProps} raidGroup={raidGroup}
buttonProps={{
...buttonProps,
disabled: !isRaidGroupAdmin(raidGroup.raidGroupId ?? "", raidGroupPermissions, accountPermissions) && !containsRaidGroupRequest(raidGroup.raidGroupId ?? "", raidGroupRequests)
}}
showRaidGroupRequestModal={() => { showRaidGroupRequestModal={() => {
setSelectedRaidGroup(raidGroup); setSelectedRaidGroup(raidGroup);
setDisplayRaidGroupRequestModal(true); setDisplayRaidGroupRequestModal(true);
@@ -86,7 +90,7 @@ export default function RaidGroupsList({
setSelectedRaidGroup(raidGroup); setSelectedRaidGroup(raidGroup);
setDisplayDeleteRaidGroupModal(true); setDisplayDeleteRaidGroupModal(true);
}} }}
hasRaidGroupPermissions={containsRaidGroupPermission(raidGroup.raidGroupId ?? "", raidGroupPermissions)} hasRaidGroupPermissions={containsRaidGroupPermission(raidGroup.raidGroupId ?? "", raidGroupPermissions, accountPermissions)}
hasRaidGroupRequest={containsRaidGroupRequest(raidGroup.raidGroupId ?? "", raidGroupRequests)} hasRaidGroupRequest={containsRaidGroupRequest(raidGroup.raidGroupId ?? "", raidGroupRequests)}
/> />
</div> </div>

View File

@@ -1,5 +1,6 @@
import { ButtonShape, ButtonSizeType, ButtonVariant } from "@/components/button/Button"; import { ButtonShape, ButtonSizeType, ButtonVariant } from "@/components/button/Button";
import Table from "@/components/table/Table"; import Table from "@/components/table/Table";
import { RaidGroup } from "@/interface/RaidGroup";
import { elementBg } from "@/util/SkeletonUtil"; import { elementBg } from "@/util/SkeletonUtil";
import RaidGroupAdminButtons from "./RaidGroupAdminButtons"; import RaidGroupAdminButtons from "./RaidGroupAdminButtons";
@@ -49,8 +50,11 @@ function RaidGroupSkeleton(): React.ReactNode[]{
shape: "square" as ButtonShape, shape: "square" as ButtonShape,
disabled: true disabled: true
}, },
showRaidGroupRequestModal: () => {},
showEditRaidGroupModal: () => {}, showEditRaidGroupModal: () => {},
showDeleteRaidGroupModal: () => {} showDeleteRaidGroupModal: () => {},
hasRaidGroupPermissions: false,
hasRaidGroupRequest: false
} }
const elements: React.ReactNode[] = [ const elements: React.ReactNode[] = [
<div <div
@@ -63,7 +67,7 @@ function RaidGroupSkeleton(): React.ReactNode[]{
className={`flex flex-row items-center justify-center gap-2 pl-16`} className={`flex flex-row items-center justify-center gap-2 pl-16`}
> >
<div className="py-4 border-l border-neutral-500">&nbsp;</div> <div className="py-4 border-l border-neutral-500">&nbsp;</div>
<RaidGroupAdminButtons {...buttonsProps}/> <RaidGroupAdminButtons raidGroup={{} as RaidGroup} {...buttonsProps}/>
</div> </div>
]; ];

View File

@@ -32,7 +32,7 @@ export default function RaidGroupModal({
setRaidGroupName(raidGroup?.raidGroupName ?? ""); setRaidGroupName(raidGroup?.raidGroupName ?? "");
setRaidGroupIcon(raidGroup?.raidGroupIcon ?? ""); setRaidGroupIcon(raidGroup?.raidGroupIcon ?? "");
setIconFile(null); setIconFile(null);
}, [ raidGroup, setRaidGroupName, setRaidGroupIcon ]); }, [ display, raidGroup, setRaidGroupName, setRaidGroupIcon ]);
const updateRaidGroupMutate = useUpdateRaidGroup(); const updateRaidGroupMutate = useUpdateRaidGroup();
@@ -47,25 +47,25 @@ export default function RaidGroupModal({
useEffect(() => { useEffect(() => {
if(updateRaidGroupMutate.status === "success"){ if(updateRaidGroupMutate.status === "success"){
updateRaidGroupMutate.reset(); updateRaidGroupMutate.reset();
addSuccessMessage(`Updated raid group ${raidGroupName}`); addSuccessMessage(`Updated raid group ${raidGroup?.raidGroupName}`);
close(); close();
} }
else if(updateRaidGroupMutate.status === "error"){ else if(updateRaidGroupMutate.status === "error"){
updateRaidGroupMutate.reset(); updateRaidGroupMutate.reset();
addErrorMessage(`Error updating raid group ${raidGroupName}: ${updateRaidGroupMutate.error.message}`); addErrorMessage(`Error updating raid group ${raidGroup?.raidGroupName}: ${updateRaidGroupMutate.error.message}`);
console.log(updateRaidGroupMutate.error); console.log(updateRaidGroupMutate.error);
} }
else if(createRaidGroupMutate.status === "success"){ else if(createRaidGroupMutate.status === "success"){
createRaidGroupMutate.reset(); createRaidGroupMutate.reset();
addSuccessMessage(`Created raid group ${raidGroupName}`); addSuccessMessage(`Created raid group ${raidGroup?.raidGroupName}`);
close(); close();
} }
else if(createRaidGroupMutate.status === "error"){ else if(createRaidGroupMutate.status === "error"){
createRaidGroupMutate.reset(); createRaidGroupMutate.reset();
addErrorMessage(`Error creating raid group ${raidGroupName}: ${createRaidGroupMutate.error.message}`); addErrorMessage(`Error creating raid group ${raidGroup?.raidGroupName}: ${createRaidGroupMutate.error.message}`);
console.log(createRaidGroupMutate.error); console.log(createRaidGroupMutate.error);
} }
}, [ updateRaidGroupMutate, createRaidGroupMutate, raidGroupName, close, addSuccessMessage, addErrorMessage ]); }, [ updateRaidGroupMutate, createRaidGroupMutate, raidGroup, close, addSuccessMessage, addErrorMessage ]);
const updateRaidGroup = () => { const updateRaidGroup = () => {

View File

@@ -1,25 +1,37 @@
import { ButtonProps } from "@/components/button/Button"; import { ButtonProps } from "@/components/button/Button";
import DangerButton from "@/components/button/DangerButton"; import DangerButton from "@/components/button/DangerButton";
import PrimaryButton from "@/components/button/PrimaryButton"; import PrimaryButton from "@/components/button/PrimaryButton";
import { RaidGroupRequest } from "@/interface/RaidGroupRequest";
import { useTheme } from "@/providers/ThemeProvider";
import { BsPencilFill, BsTrash3 } from "react-icons/bs"; import { BsPencilFill, BsTrash3 } from "react-icons/bs";
import { Tooltip } from "react-tooltip";
export default function RaidGroupRequestButtons({ export default function RaidGroupRequestButtons({
request,
buttonProps, buttonProps,
showRaidGroupRequestModal, showRaidGroupRequestModal,
showDeleteRaidGroupRequestModal showDeleteRaidGroupRequestModal
}:{ }:{
request: RaidGroupRequest;
buttonProps: ButtonProps; buttonProps: ButtonProps;
showRaidGroupRequestModal: () => void; showRaidGroupRequestModal: () => void;
showDeleteRaidGroupRequestModal: () => void; showDeleteRaidGroupRequestModal: () => void;
}){ }){
const { theme } = useTheme();
const componentId = crypto.randomUUID().replaceAll("-", "");
return ( return (
<div <div
className="flex flex-row items-center justify-center w-full gap-2" className="flex flex-row items-center justify-center w-full gap-2"
> >
<PrimaryButton <PrimaryButton
{...buttonProps} {...buttonProps}
id={`raidGroupRequestButtonsResolve${componentId}`}
onClick={showRaidGroupRequestModal} onClick={showRaidGroupRequestModal}
aria-label={`Resolve raid group request from ${request.username}`}
data-tooltip-delay-show={750}
> >
<BsPencilFill <BsPencilFill
size={22} size={22}
@@ -27,12 +39,29 @@ export default function RaidGroupRequestButtons({
</PrimaryButton> </PrimaryButton>
<DangerButton <DangerButton
{...buttonProps} {...buttonProps}
id={`raidGroupRequestButtonsDelete${componentId}`}
onClick={showDeleteRaidGroupRequestModal} onClick={showDeleteRaidGroupRequestModal}
aria-label={`Delete raid group request from ${request.username}`}
data-tooltip-delay-show={750}
> >
<BsTrash3 <BsTrash3
size={22} size={22}
/> />
</DangerButton> </DangerButton>
<Tooltip
anchorSelect={`#raidGroupRequestButtonsResolve${componentId}`}
place="top"
variant={theme === "dark" ? "light" : "dark"}
>
Resolve raid group request from {request.username}
</Tooltip>
<Tooltip
anchorSelect={`#raidGroupRequestButtonsDelete${componentId}`}
place="top"
variant={theme === "dark" ? "light" : "dark"}
>
Delete raid group request from {request.username}
</Tooltip>
</div> </div>
); );
} }

View File

@@ -31,14 +31,22 @@ export default function RaidGroupRequestList({
<div> <div>
Username Username
</div>, </div>,
<div>
Message
</div>,
<div> <div>
Actions Actions
</div> </div>
]; ];
const bodyElements: React.ReactNode[][] = raidGroupRequests.map((request) => [ const bodyElements: React.ReactNode[][] = raidGroupRequests.map((request) => [
<div> <div
className="text-nowrap"
>
{request.username} {request.username}
</div>, </div>,
<div>
{request.requestMessage || <>&nbsp;</>}
</div>,
<div <div
className="flex flex-row items-center justify-center gap-2" className="flex flex-row items-center justify-center gap-2"
> >
@@ -50,6 +58,7 @@ export default function RaidGroupRequestList({
{ {
raidGroup && raidGroup &&
<RaidGroupRequestButtons <RaidGroupRequestButtons
request={request}
buttonProps={buttonProps} buttonProps={buttonProps}
showRaidGroupRequestModal={() => { setSelectedRequest(request); setDisplayRequestModal(true); }} showRaidGroupRequestModal={() => { setSelectedRequest(request); setDisplayRequestModal(true); }}
showDeleteRaidGroupRequestModal={() => { setSelectedRequest(request); setDisplayDeleteRequestModal(true); }} showDeleteRaidGroupRequestModal={() => { setSelectedRequest(request); setDisplayDeleteRequestModal(true); }}

View File

@@ -42,7 +42,7 @@ export default function RaidGroupRequestTab({
return ( return (
<> <>
<div <div
className="flex flex-row items-center justify-between w-full" className="flex flex-row items-center justify-between w-full mb-8"
> >
<div <div
className="flex flex-row items-center justify-start w-full" className="flex flex-row items-center justify-start w-full"

View File

@@ -1,25 +1,37 @@
import { ButtonProps } from "@/components/button/Button"; import { ButtonProps } from "@/components/button/Button";
import DangerButton from "@/components/button/DangerButton"; import DangerButton from "@/components/button/DangerButton";
import PrimaryButton from "@/components/button/PrimaryButton"; import PrimaryButton from "@/components/button/PrimaryButton";
import { RaidInstance } from "@/interface/RaidInstance";
import { useTheme } from "@/providers/ThemeProvider";
import { BsPencilFill, BsTrash3 } from "react-icons/bs"; import { BsPencilFill, BsTrash3 } from "react-icons/bs";
import { Tooltip } from "react-tooltip";
export default function RaidInstanceAdminButtons({ export default function RaidInstanceAdminButtons({
raidInstance,
buttonProps, buttonProps,
showRaidInstanceModal, showRaidInstanceModal,
showDeleteRaidInstanceModal showDeleteRaidInstanceModal
}:{ }:{
raidInstance?: RaidInstance;
buttonProps: ButtonProps; buttonProps: ButtonProps;
showRaidInstanceModal: () => void; showRaidInstanceModal: () => void;
showDeleteRaidInstanceModal: () => void; showDeleteRaidInstanceModal: () => void;
}){ }){
const { theme } = useTheme();
const componentId = crypto.randomUUID().replaceAll("-", "");
return ( return (
<div <div
className="flex flex-row gap-2" className="flex flex-row gap-2"
> >
<PrimaryButton <PrimaryButton
{...buttonProps} {...buttonProps}
id={`raidInstanceAdminButtonsEdit${componentId}`}
onClick={showRaidInstanceModal} onClick={showRaidInstanceModal}
aria-label={`Edit ${raidInstance?.raidInstanceName}`}
data-tooltip-delay-show={750}
> >
<BsPencilFill <BsPencilFill
size={22} size={22}
@@ -27,12 +39,29 @@ export default function RaidInstanceAdminButtons({
</PrimaryButton> </PrimaryButton>
<DangerButton <DangerButton
{...buttonProps} {...buttonProps}
id={`raidInstanceAdminButtonsDelete${componentId}`}
onClick={showDeleteRaidInstanceModal} onClick={showDeleteRaidInstanceModal}
aria-label={`Delete ${raidInstance?.raidInstanceName}`}
data-tooltip-delay-show={750}
> >
<BsTrash3 <BsTrash3
size={22} size={22}
/> />
</DangerButton> </DangerButton>
<Tooltip
anchorSelect={`#raidInstanceAdminButtonsEdit${componentId}`}
place="top"
variant={theme === "dark" ? "light" : "dark"}
>
Edit {raidInstance?.raidInstanceName}
</Tooltip>
<Tooltip
anchorSelect={`#raidInstanceAdminButtonsDelete${componentId}`}
place="top"
variant={theme === "dark" ? "light" : "dark"}
>
Delete {raidInstance?.raidInstanceName}
</Tooltip>
</div> </div>
); );
} }

View File

@@ -2,6 +2,8 @@ import { ButtonProps } from "@/components/button/Button";
import Table from "@/components/table/Table"; import Table from "@/components/table/Table";
import { RaidGroup } from "@/interface/RaidGroup"; import { RaidGroup } from "@/interface/RaidGroup";
import { RaidInstance } from "@/interface/RaidInstance"; import { RaidInstance } from "@/interface/RaidInstance";
import { useAuth } from "@/providers/AuthProvider";
import { isRaidGroupAdmin, isRaidGroupLeader } from "@/util/PermissionUtil";
import moment from "moment"; import moment from "moment";
import { useState } from "react"; import { useState } from "react";
import { Link } from "react-router"; import { Link } from "react-router";
@@ -17,6 +19,7 @@ export default function RaidInstanceList({
raidInstances: RaidInstance[]; raidInstances: RaidInstance[];
raidGroup: RaidGroup; raidGroup: RaidGroup;
}){ }){
const { accountPermissions, raidGroupPermissions } = useAuth();
const [ selectedRaidInstance, setSelectedRaidInstance ] = useState<RaidInstance>(); const [ selectedRaidInstance, setSelectedRaidInstance ] = useState<RaidInstance>();
const [ displayEditRaidInstanceModal, setDisplayEditRaidInstanceModal ] = useState(false); const [ displayEditRaidInstanceModal, setDisplayEditRaidInstanceModal ] = useState(false);
const [ displayDeleteRaidInstanceModal, setDisplayDeleteRaidInstanceModal ] = useState(false); const [ displayDeleteRaidInstanceModal, setDisplayDeleteRaidInstanceModal ] = useState(false);
@@ -25,7 +28,8 @@ export default function RaidInstanceList({
const buttonProps: ButtonProps = { const buttonProps: ButtonProps = {
variant: "ghost", variant: "ghost",
size: "md", size: "md",
shape: "square" shape: "square",
disabled: !isRaidGroupAdmin(raidGroup.raidGroupId ?? "", raidGroupPermissions, accountPermissions) || !isRaidGroupLeader(raidGroup.raidGroupId ?? "", raidGroupPermissions, accountPermissions)
}; };
@@ -94,6 +98,7 @@ export default function RaidInstanceList({
&nbsp; &nbsp;
</div> </div>
<RaidInstanceAdminButtons <RaidInstanceAdminButtons
raidInstance={raidInstance}
buttonProps={buttonProps} buttonProps={buttonProps}
showRaidInstanceModal={() => { setSelectedRaidInstance(raidInstance); setDisplayEditRaidInstanceModal(true); }} showRaidInstanceModal={() => { setSelectedRaidInstance(raidInstance); setDisplayEditRaidInstanceModal(true); }}
showDeleteRaidInstanceModal={() => { setSelectedRaidInstance(raidInstance); setDisplayDeleteRaidInstanceModal(true); }} showDeleteRaidInstanceModal={() => { setSelectedRaidInstance(raidInstance); setDisplayDeleteRaidInstanceModal(true); }}

View File

@@ -3,6 +3,8 @@ import TextInput from "@/components/input/TextInput";
import Pagination from "@/components/pagination/Pagination"; import Pagination from "@/components/pagination/Pagination";
import { useGetRaidInstancesByRaidGroupCount } from "@/hooks/RaidInstanceHooks"; import { useGetRaidInstancesByRaidGroupCount } from "@/hooks/RaidInstanceHooks";
import { RaidGroup } from "@/interface/RaidGroup"; import { RaidGroup } from "@/interface/RaidGroup";
import { useAuth } from "@/providers/AuthProvider";
import { isRaidGroupAdmin, isRaidGroupLeader } from "@/util/PermissionUtil";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useDebouncedCallback } from "use-debounce"; import { useDebouncedCallback } from "use-debounce";
import RaidInstanceLoader from "./RaidInstanceLoader"; import RaidInstanceLoader from "./RaidInstanceLoader";
@@ -14,6 +16,7 @@ export default function RaidInstanceTab({
}:{ }:{
raidGroup: RaidGroup; raidGroup: RaidGroup;
}){ }){
const { accountPermissions, raidGroupPermissions } = useAuth();
const [ displayCreateRaidInstanceModal, setDisplayCreateRaidInstanceModal ] = useState(false); const [ displayCreateRaidInstanceModal, setDisplayCreateRaidInstanceModal ] = useState(false);
const [ page, setPage ] = useState(1); const [ page, setPage ] = useState(1);
const [ totalPages, setTotalPages ] = useState(1); const [ totalPages, setTotalPages ] = useState(1);
@@ -59,6 +62,7 @@ export default function RaidInstanceTab({
<PrimaryButton <PrimaryButton
className="text-nowrap" className="text-nowrap"
onClick={() => setDisplayCreateRaidInstanceModal(true)} onClick={() => setDisplayCreateRaidInstanceModal(true)}
disabled={!isRaidGroupAdmin(raidGroup.raidGroupId ?? "", raidGroupPermissions, accountPermissions) && !isRaidGroupLeader(raidGroup.raidGroupId ?? "", raidGroupPermissions, accountPermissions)}
> >
Create Raid Instance Create Raid Instance
</PrimaryButton> </PrimaryButton>

View File

@@ -1,24 +1,37 @@
import { ButtonProps } from "@/components/button/Button"; import { ButtonProps } from "@/components/button/Button";
import DangerButton from "@/components/button/DangerButton"; import DangerButton from "@/components/button/DangerButton";
import PrimaryButton from "@/components/button/PrimaryButton"; import PrimaryButton from "@/components/button/PrimaryButton";
import { RaidLayout } from "@/interface/RaidLayout";
import { useTheme } from "@/providers/ThemeProvider";
import { BsPencilFill, BsTrash3 } from "react-icons/bs"; import { BsPencilFill, BsTrash3 } from "react-icons/bs";
import { Tooltip } from "react-tooltip";
export default function RaidLayoutAdminButtons({ export default function RaidLayoutAdminButtons({
raidLayout,
buttonProps, buttonProps,
showRaidLayoutModal, showRaidLayoutModal,
showDeleteRaidLayoutModal showDeleteRaidLayoutModal
}:{ }:{
raidLayout: RaidLayout;
buttonProps: ButtonProps; buttonProps: ButtonProps;
showRaidLayoutModal: () => void; showRaidLayoutModal: () => void;
showDeleteRaidLayoutModal: () => void; showDeleteRaidLayoutModal: () => void;
}){ }){
const { theme } = useTheme();
const componentId = crypto.randomUUID().replaceAll("-", "");
return ( return (
<div <div
className="flex flex-row gap-2" className="flex flex-row gap-2"
> >
<PrimaryButton <PrimaryButton
{...buttonProps} {...buttonProps}
id={`raidLayoutAdminButtonsEdit${componentId}`}
onClick={showRaidLayoutModal} onClick={showRaidLayoutModal}
aria-label={`Edit ${raidLayout.raidLayoutName}`}
data-tooltip-id={`raidLayoutAdminButtonsEdit${componentId}`}
> >
<BsPencilFill <BsPencilFill
size={22} size={22}
@@ -26,12 +39,29 @@ export default function RaidLayoutAdminButtons({
</PrimaryButton> </PrimaryButton>
<DangerButton <DangerButton
{...buttonProps} {...buttonProps}
id={`raidLayoutAdminButtonsDelete${componentId}`}
onClick={showDeleteRaidLayoutModal} onClick={showDeleteRaidLayoutModal}
aria-label={`Delete ${raidLayout.raidLayoutName}`}
data-tooltip-id={`raidLayoutAdminButtonsDelete${componentId}`}
> >
<BsTrash3 <BsTrash3
size={22} size={22}
/> />
</DangerButton> </DangerButton>
<Tooltip
anchorSelect={`#raidLayoutAdminButtonsEdit${componentId}`}
place="top"
variant={theme === "dark" ? "light" : "dark"}
>
Edit {raidLayout.raidLayoutName}
</Tooltip>
<Tooltip
anchorSelect={`#raidLayoutAdminButtonsDelete${componentId}`}
place="top"
variant={theme === "dark" ? "light" : "dark"}
>
Delete {raidLayout.raidLayoutName}
</Tooltip>
</div> </div>
); );
} }

View File

@@ -5,6 +5,8 @@ import { useGetClassGroupsByRaidLayout } from "@/hooks/ClassGroupHooks";
import { ClassGroup } from "@/interface/ClassGroup"; import { ClassGroup } from "@/interface/ClassGroup";
import { RaidGroup } from "@/interface/RaidGroup"; import { RaidGroup } from "@/interface/RaidGroup";
import { RaidLayout } from "@/interface/RaidLayout"; import { RaidLayout } from "@/interface/RaidLayout";
import { useAuth } from "@/providers/AuthProvider";
import { isRaidGroupAdmin, isRaidGroupLeader } from "@/util/PermissionUtil";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import DeleteRaidLayoutModal from "./modal/DeleteRaidLayoutModal"; import DeleteRaidLayoutModal from "./modal/DeleteRaidLayoutModal";
import RaidLayoutModal from "./modal/RaidLayoutModal"; import RaidLayoutModal from "./modal/RaidLayoutModal";
@@ -18,6 +20,7 @@ export default function RaidLayoutList({
raidLayouts: RaidLayout[]; raidLayouts: RaidLayout[];
raidGroup: RaidGroup; raidGroup: RaidGroup;
}){ }){
const { accountPermissions, raidGroupPermissions } = useAuth();
const [ selectedRaidLayout, setSelectedRaidLayout ] = useState<RaidLayout>(); const [ selectedRaidLayout, setSelectedRaidLayout ] = useState<RaidLayout>();
const [ displayEditRaidLayoutModal, showEditRaidLayoutModal ] = useState(false); const [ displayEditRaidLayoutModal, showEditRaidLayoutModal ] = useState(false);
const [ displayDeleteRaidLayoutModal, showDeleteRaidLayoutModal ] = useState(false); const [ displayDeleteRaidLayoutModal, showDeleteRaidLayoutModal ] = useState(false);
@@ -32,7 +35,8 @@ export default function RaidLayoutList({
const buttonProps: ButtonProps = { const buttonProps: ButtonProps = {
variant: "ghost", variant: "ghost",
size: "md", size: "md",
shape: "square" shape: "square",
disabled: !isRaidGroupAdmin(raidGroup.raidGroupId ?? "", raidGroupPermissions, accountPermissions) && !isRaidGroupLeader(raidGroup.raidGroupId ?? "", raidGroupPermissions, accountPermissions)
}; };
@@ -78,6 +82,7 @@ export default function RaidLayoutList({
&nbsp; &nbsp;
</div> </div>
<RaidLayoutAdminButtons <RaidLayoutAdminButtons
raidLayout={raidLayout}
buttonProps={buttonProps} buttonProps={buttonProps}
showRaidLayoutModal={() => { setSelectedRaidLayout(raidLayout); showEditRaidLayoutModal(true); }} showRaidLayoutModal={() => { setSelectedRaidLayout(raidLayout); showEditRaidLayoutModal(true); }}
showDeleteRaidLayoutModal={() => { setSelectedRaidLayout(raidLayout); showDeleteRaidLayoutModal(true); }} showDeleteRaidLayoutModal={() => { setSelectedRaidLayout(raidLayout); showDeleteRaidLayoutModal(true); }}

View File

@@ -3,6 +3,8 @@ import TextInput from "@/components/input/TextInput";
import Pagination from "@/components/pagination/Pagination"; import Pagination from "@/components/pagination/Pagination";
import { useGetRaidLayoutsByRaidGroupCount } from "@/hooks/RaidLayoutHooks"; import { useGetRaidLayoutsByRaidGroupCount } from "@/hooks/RaidLayoutHooks";
import { RaidGroup } from "@/interface/RaidGroup"; import { RaidGroup } from "@/interface/RaidGroup";
import { useAuth } from "@/providers/AuthProvider";
import { isRaidGroupAdmin, isRaidGroupLeader } from "@/util/PermissionUtil";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useDebouncedCallback } from "use-debounce"; import { useDebouncedCallback } from "use-debounce";
import RaidLayoutModal from "./modal/RaidLayoutModal"; import RaidLayoutModal from "./modal/RaidLayoutModal";
@@ -14,6 +16,7 @@ export default function RaidLayoutTab({
}:{ }:{
raidGroup: RaidGroup; raidGroup: RaidGroup;
}){ }){
const { accountPermissions, raidGroupPermissions } = useAuth();
const [ displayCreateRaidLayoutModal, setDisplayCreateRaidLayoutModal ] = useState(false); const [ displayCreateRaidLayoutModal, setDisplayCreateRaidLayoutModal ] = useState(false);
const [ page, setPage ] = useState(1); const [ page, setPage ] = useState(1);
const [ totalPages, setTotalPages ] = useState(1); const [ totalPages, setTotalPages ] = useState(1);
@@ -59,6 +62,7 @@ export default function RaidLayoutTab({
<PrimaryButton <PrimaryButton
className="text-nowrap" className="text-nowrap"
onClick={() => setDisplayCreateRaidLayoutModal(true)} onClick={() => setDisplayCreateRaidLayoutModal(true)}
disabled={!isRaidGroupAdmin(raidGroup.raidGroupId ?? "", raidGroupPermissions, accountPermissions) && !isRaidGroupLeader(raidGroup.raidGroupId ?? "", raidGroupPermissions, accountPermissions)}
> >
Create Raid Layout Create Raid Layout
</PrimaryButton> </PrimaryButton>

View File

@@ -1,8 +1,23 @@
import { AccountPermissionType } from "@/interface/Account";
import { AccountPermission } from "@/interface/AccountPermission";
import { GamePermission, GamePermissionType } from "@/interface/GamePermission";
import { RaidGroupPermissionType } from "@/interface/RaidGroup";
import { RaidGroupPermission } from "@/interface/RaidGroupPermission"; import { RaidGroupPermission } from "@/interface/RaidGroupPermission";
import { RaidGroupRequest } from "@/interface/RaidGroupRequest"; import { RaidGroupRequest } from "@/interface/RaidGroupRequest";
export function containsRaidGroupPermission(raidGroupId: string, permissions: RaidGroupPermission[]): boolean{ //! Site
export function isSiteAdmin(accountPermissions: AccountPermission[]): boolean{
return accountPermissions.find((permission) => permission.accountPermissionType === AccountPermissionType.ADMIN) !== undefined;
}
//! Raid Group
export function containsRaidGroupPermission(raidGroupId: string, permissions: RaidGroupPermission[], accountPermissions: AccountPermission[]): boolean{
if(accountPermissions.find((permission) => permission.accountPermissionType === AccountPermissionType.ADMIN)){
return true;
}
for(const permission of permissions){ for(const permission of permissions){
if(permission.raidGroupId === raidGroupId){ if(permission.raidGroupId === raidGroupId){
return true; return true;
@@ -11,7 +26,7 @@ export function containsRaidGroupPermission(raidGroupId: string, permissions: Ra
return false; return false;
} }
export function containsRaidGroupRequest(raidGroupId: string, requests: RaidGroupRequest[]){ export function containsRaidGroupRequest(raidGroupId: string, requests: RaidGroupRequest[]): boolean{
for(const request of requests){ for(const request of requests){
if(request.raidGroupId === raidGroupId){ if(request.raidGroupId === raidGroupId){
return true; return true;
@@ -19,3 +34,30 @@ export function containsRaidGroupRequest(raidGroupId: string, requests: RaidGrou
} }
return false; return false;
} }
export function isRaidGroupAdmin(raidGroupId: string, permissions: RaidGroupPermission[], accountPermissions: AccountPermission[]): boolean{
if(accountPermissions.find((permission) => permission.accountPermissionType === AccountPermissionType.ADMIN)){
return true;
}
const raidGroupPermission = permissions.find((permission) => permission.raidGroupId === raidGroupId);
return raidGroupPermission?.permission === RaidGroupPermissionType.ADMIN;
}
export function isRaidGroupLeader(raidGroupId: string, permissions: RaidGroupPermission[], accountPermissions: AccountPermission[]): boolean{
if(accountPermissions.find((permission) => permission.accountPermissionType === AccountPermissionType.ADMIN)){
return true;
}
const raidGroupPermission = permissions.find((permission) => permission.raidGroupId === raidGroupId);
return raidGroupPermission?.permission === RaidGroupPermissionType.LEADER;
}
//! Game
export function isGameAdmin(gameId: string, permissions: GamePermission[], accountPermissions: AccountPermission[]): boolean{
if(accountPermissions.find((permission) => permission.accountPermissionType === AccountPermissionType.ADMIN)){
return true;
}
const gamePermission = permissions.find((permission) => permission.gameId === gameId);
return gamePermission?.gamePermissionType === GamePermissionType.ADMIN;
}