Compare commits
75 Commits
2186889b11
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
bba79a9e70
|
|||
| 0031e1c9e7 | |||
| 8b5efb0879 | |||
| c4265bbcad | |||
| 567ac0aa86 | |||
| 1670cd5b7a | |||
| f3f50dcd14 | |||
| 6bb0f18ea9 | |||
| 7c2a322fa4 | |||
| e3dd099785 | |||
| f4a0ca4320 | |||
| e8e576a200 | |||
| c5fbb1d83f | |||
| 3b738c337b | |||
| 4d7585c770 | |||
| 81507afbcc | |||
| 031184b666 | |||
| d07bd8a97d | |||
| 1d50d452bc | |||
| 3f6b6a9e63 | |||
| 6155b4784d | |||
| 538a96d909 | |||
| 624f3bf369 | |||
| 73628fded6 | |||
| 5f238ea185 | |||
| f10e54ddfd | |||
| 7fcabf69f1 | |||
| abf8a08f83 | |||
| ea5794aa22 | |||
| 220c9969e2 | |||
| 490385788a | |||
| 6bc38ec748 | |||
| fddfeaf9ca | |||
| c623680ea8 | |||
| add7b97056 | |||
| 59a57dbdc3 | |||
| 10d8159353 | |||
| d42f625540 | |||
| 49243a71a1 | |||
| 0eeedf1e3b | |||
| ea0018bae2 | |||
| a842c24d0d | |||
| 56236fd2ac | |||
| cff5e098b8 | |||
| c9ceeea3b4 | |||
| 5a2c8a8936 | |||
| 439e82b821 | |||
| 65fb3479fd | |||
| 8b8538a968 | |||
| c2f13d9900 | |||
| 90337f92ab | |||
| b763a1c7bd | |||
| 0dfb971bc2 | |||
| 61789d7ca2 | |||
| 6c80becf71 | |||
| a463bb734f | |||
| 28462776ac | |||
| ef6da3ea64 | |||
| b78b6109b3 | |||
| 58d1e83a2f | |||
| ffe51d6fbb | |||
| 91800574e4 | |||
| 7c3b462651 | |||
| d06d421d03 | |||
| 3d06d8189d | |||
| 6f3fe1798b | |||
| 843970e229 | |||
| d68e8864a0 | |||
| 1499f4055f | |||
| d1b0a499a8 | |||
| 29724da42f | |||
| bc7ffcd5bf | |||
| f6982951eb | |||
| eba1676c15 | |||
| 5bb6e0a37f |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -22,3 +22,8 @@ dist-ssr
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# Sonarqube
|
||||
sonarBuild.sh
|
||||
sonarBuild.ps1
|
||||
.scannerwork/
|
||||
|
||||
51
README.md
51
README.md
@@ -1,50 +1,3 @@
|
||||
# React + TypeScript + Vite
|
||||
# Raid Builder Web
|
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||
|
||||
Currently, two official plugins are available:
|
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
|
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||
|
||||
## Expanding the ESLint configuration
|
||||
|
||||
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
|
||||
|
||||
- Configure the top-level `parserOptions` property like this:
|
||||
|
||||
```js
|
||||
export default tseslint.config({
|
||||
languageOptions: {
|
||||
// other options...
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked`
|
||||
- Optionally add `...tseslint.configs.stylisticTypeChecked`
|
||||
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config:
|
||||
|
||||
```js
|
||||
// eslint.config.js
|
||||
import react from 'eslint-plugin-react'
|
||||
|
||||
export default tseslint.config({
|
||||
// Set the react version
|
||||
settings: { react: { version: '18.3' } },
|
||||
plugins: {
|
||||
// Add the react plugin
|
||||
react,
|
||||
},
|
||||
rules: {
|
||||
// other rules...
|
||||
// Enable its recommended rules
|
||||
...react.configs.recommended.rules,
|
||||
...react.configs['jsx-runtime'].rules,
|
||||
},
|
||||
})
|
||||
```
|
||||
[](https://sonarqube.mattrixwv.com/dashboard?id=RaidBuilderWeb)
|
||||
|
||||
3
TODO.txt
Normal file
3
TODO.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
Change page layout to tanstack router
|
||||
Make raid layout page
|
||||
Move providers to context and components to providers
|
||||
42
eslint.config.js
Normal file
42
eslint.config.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import js from "@eslint/js";
|
||||
import react from "eslint-plugin-react";
|
||||
import reactHooks from "eslint-plugin-react-hooks";
|
||||
import reactRefresh from "eslint-plugin-react-refresh";
|
||||
import globals from "globals";
|
||||
import tseslint from "typescript-eslint";
|
||||
|
||||
|
||||
export default tseslint.config(
|
||||
{ ignores: ["dist"] },
|
||||
{
|
||||
extends: [js.configs.recommended, ...tseslint.configs.recommendedTypeChecked],
|
||||
files: ["**/*.{ts,tsx}"],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
parserOptions: {
|
||||
project: ["./tsconfig.node.json", "./tsconfig.app.json"],
|
||||
tsconfigRootDir: import.meta.dirname
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
"react-hooks": reactHooks,
|
||||
"react-refresh": reactRefresh,
|
||||
react: react
|
||||
},
|
||||
rules: {
|
||||
...react.configs.recommended.rules,
|
||||
...react.configs["jsx-runtime"].rules,
|
||||
...reactHooks.configs.recommended.rules,
|
||||
"react-refresh/only-export-components": [
|
||||
"warn",
|
||||
{ allowConstantExport: true }
|
||||
]
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: "detect"
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
13
index.html
Normal file
13
index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/png" href="/favicon.ico"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>Raid Builder</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
5918
package-lock.json
generated
Normal file
5918
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
47
package.json
Normal file
47
package.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "raid-builder-web-react",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && eslint && vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fullcalendar/core": "^6.1.19",
|
||||
"@fullcalendar/daygrid": "^6.1.19",
|
||||
"@fullcalendar/interaction": "^6.1.19",
|
||||
"@fullcalendar/react": "^6.1.19",
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"@tanstack/react-query": "^5.90.12",
|
||||
"@types/node": "^25.0.3",
|
||||
"axios": "^1.13.2",
|
||||
"clsx": "^2.1.1",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"moment": "^2.30.1",
|
||||
"react": "^19.2.3",
|
||||
"react-dom": "^19.2.3",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-joyride": "^3.0.0-7",
|
||||
"react-router": "^7.11.0",
|
||||
"react-tooltip": "^5.30.0",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"use-debounce": "^10.0.6",
|
||||
"zod": "^4.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@types/react": "^19.2.7",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^5.1.2",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.4.26",
|
||||
"globals": "^16.5.0",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.50.0",
|
||||
"vite": "^7.3.0"
|
||||
}
|
||||
}
|
||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
114
src/App.tsx
Normal file
114
src/App.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
import { createBrowserRouter, RouterProvider } from "react-router";
|
||||
import NavBar from "./components/nav/NavBar";
|
||||
import AccountPage from "./pages/protected/AccountPage";
|
||||
import AdminPage from "./pages/protected/AdminPage";
|
||||
import GamePage from "./pages/protected/GamePage";
|
||||
import GamesPage from "./pages/protected/GamesPage";
|
||||
import LogoutPage from "./pages/protected/LogoutPage";
|
||||
import PersonPage from "./pages/protected/PersonPage";
|
||||
import RaidGroupPage from "./pages/protected/RaidGroupPage";
|
||||
import RaidGroupsPage from "./pages/protected/RaidGroupsPage";
|
||||
import RaidInstancePage from "./pages/protected/RaidInstancePage";
|
||||
import RaidLayoutPage from "./pages/protected/RaidLayoutPage";
|
||||
import ConfirmPage from "./pages/public/ConfirmPage";
|
||||
import ForgotPasswordPage from "./pages/public/ForgotPassword";
|
||||
import ForgotTokenPage from "./pages/public/ForgotTokenPage";
|
||||
import HomePage from "./pages/public/HomePage";
|
||||
import LoginPage from "./pages/public/LoginPage";
|
||||
import SignupPage from "./pages/public/SignupPage";
|
||||
import { ProtectedRoute } from "./providers/components/AuthProviderComponent";
|
||||
import ErrorBoundary from "./providers/components/ErrorBoundary";
|
||||
|
||||
|
||||
const routes = createBrowserRouter([
|
||||
{
|
||||
element: <NavBar/>,
|
||||
children: [
|
||||
{
|
||||
errorElement: <ErrorBoundary/>,
|
||||
children: [
|
||||
{
|
||||
path: "/",
|
||||
element: <HomePage/>
|
||||
},
|
||||
{
|
||||
path: "/login",
|
||||
element: <LoginPage/>
|
||||
},
|
||||
{
|
||||
path: "/signup",
|
||||
element: <SignupPage/>
|
||||
},
|
||||
{
|
||||
path: "/forgotPassword",
|
||||
element: <ForgotPasswordPage/>
|
||||
},
|
||||
{
|
||||
path: "/forgotPassword/:forgotToken",
|
||||
element: <ForgotTokenPage/>
|
||||
},
|
||||
{
|
||||
path: "/confirm/:confirmToken",
|
||||
element: <ConfirmPage/>
|
||||
},
|
||||
{
|
||||
element: <ProtectedRoute/>,
|
||||
children: [
|
||||
{
|
||||
path: "/account",
|
||||
element: <AccountPage/>
|
||||
},
|
||||
{
|
||||
path: "/logout",
|
||||
element: <LogoutPage/>
|
||||
},
|
||||
{
|
||||
path: "/admin",
|
||||
element: <AdminPage/>
|
||||
},
|
||||
{
|
||||
path: "/game",
|
||||
element: <GamesPage/>
|
||||
},
|
||||
{
|
||||
path: "/game/:gameId",
|
||||
element: <GamePage/>
|
||||
},
|
||||
{
|
||||
path: "/raidGroup",
|
||||
element: <RaidGroupsPage/>
|
||||
},
|
||||
{
|
||||
path: "/raidGroup/:raidGroupId",
|
||||
element: <RaidGroupPage/>
|
||||
},
|
||||
{
|
||||
path: "/raidGroup/:raidGroupId/person/:personId",
|
||||
element: <PersonPage/>
|
||||
},
|
||||
{
|
||||
path: "/raidGroup/:raidGroupId/raidLayout/:raidLayoutId",
|
||||
element: <RaidLayoutPage/>
|
||||
},
|
||||
{
|
||||
path: "/raidGroup/:raidGroupId/raidInstance",
|
||||
element: <RaidInstancePage/>
|
||||
},
|
||||
{
|
||||
path: "/raidGroup/:raidGroupId/raidInstance/:raidInstanceId",
|
||||
element: <RaidInstancePage/>
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
||||
|
||||
|
||||
export default function App(){
|
||||
return (
|
||||
<RouterProvider router={routes}/>
|
||||
);
|
||||
}
|
||||
309
src/assets/raidBuilderIcon.svg
Normal file
309
src/assets/raidBuilderIcon.svg
Normal file
@@ -0,0 +1,309 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="135.44383mm"
|
||||
height="213.09181mm"
|
||||
viewBox="0 0 135.44383 213.09181"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
|
||||
sodipodi:docname="RaidBuilderIcon.svg"
|
||||
inkscape:export-filename="..\..\..\..\Programs\Web\raidBuilderWeb\public\raidBuilderIcon.svg"
|
||||
inkscape:export-xdpi="59.635048"
|
||||
inkscape:export-ydpi="59.635048"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#505050"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:zoom="1"
|
||||
inkscape:cx="-5.9999998"
|
||||
inkscape:cy="413.49999"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1369"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer2" />
|
||||
<defs
|
||||
id="defs1">
|
||||
<linearGradient
|
||||
id="linearGradient225"
|
||||
inkscape:label="Shield Outer">
|
||||
<stop
|
||||
style="stop-color:#666600;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop224" />
|
||||
<stop
|
||||
style="stop-color:#cccc00;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop225" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient217"
|
||||
inkscape:label="Shield Fill">
|
||||
<stop
|
||||
style="stop-color:#4c1500;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop216" />
|
||||
<stop
|
||||
style="stop-color:#7a2500;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop217" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="swatch15"
|
||||
inkscape:swatch="solid">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop15" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="swatch14"
|
||||
inkscape:swatch="solid">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop14" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="swatch13"
|
||||
inkscape:swatch="solid"
|
||||
inkscape:label="Letter">
|
||||
<stop
|
||||
style="stop-color:#6372ff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop13" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="swatch12"
|
||||
inkscape:swatch="solid"
|
||||
inkscape:label="Letter Outline">
|
||||
<stop
|
||||
style="stop-color:#5a001b;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop12" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="swatch11"
|
||||
inkscape:swatch="solid">
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop11" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="swatch10"
|
||||
inkscape:swatch="solid">
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop10" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="swatch9"
|
||||
inkscape:swatch="solid">
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop9" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="swatch3"
|
||||
inkscape:label="Swords"
|
||||
inkscape:swatch="solid">
|
||||
<stop
|
||||
style="stop-color:#671500;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop3" />
|
||||
</linearGradient>
|
||||
<inkscape:path-effect
|
||||
effect="mirror_symmetry"
|
||||
start_point="109.2765,26.728528"
|
||||
end_point="109.2765,143.17813"
|
||||
center_point="109.2765,84.953329"
|
||||
id="path-effect10"
|
||||
is_visible="true"
|
||||
lpeversion="1.2"
|
||||
lpesatellites=""
|
||||
mode="free"
|
||||
discard_orig_path="false"
|
||||
fuse_paths="false"
|
||||
oposite_fuse="false"
|
||||
split_items="false"
|
||||
split_open="false"
|
||||
link_styles="false" />
|
||||
<linearGradient
|
||||
id="swatch1"
|
||||
inkscape:label="Shield Outer">
|
||||
<stop
|
||||
style="stop-color:#646400;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#swatch1"
|
||||
id="linearGradient4"
|
||||
x1="37.570831"
|
||||
y1="148.51927"
|
||||
x2="172.80254"
|
||||
y2="148.51927"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#swatch9"
|
||||
id="linearGradient9"
|
||||
x1="164.49226"
|
||||
y1="563.35052"
|
||||
x2="630.75256"
|
||||
y2="563.35052"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#swatch10"
|
||||
id="linearGradient10"
|
||||
x1="45.840488"
|
||||
y1="84.953331"
|
||||
x2="172.71251"
|
||||
y2="84.953331"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#swatch13"
|
||||
id="linearGradient212"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="25.538338"
|
||||
y1="191.0405"
|
||||
x2="49.956448"
|
||||
y2="191.0405" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#swatch12"
|
||||
id="linearGradient213"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="25.538338"
|
||||
y1="191.0405"
|
||||
x2="49.956448"
|
||||
y2="191.0405" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#swatch13"
|
||||
id="linearGradient214"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="-7.043222"
|
||||
y1="170.3968"
|
||||
x2="20.277262"
|
||||
y2="170.3968" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#swatch12"
|
||||
id="linearGradient215"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="-7.043222"
|
||||
y1="170.3968"
|
||||
x2="20.277262"
|
||||
y2="170.3968" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient217"
|
||||
id="linearGradient216"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="166.86644"
|
||||
y1="346.35645"
|
||||
x2="543.37854"
|
||||
y2="784.35645"
|
||||
spreadMethod="pad" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#swatch3"
|
||||
id="linearGradient223"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="45.74139"
|
||||
y1="85.041733"
|
||||
x2="172.81161"
|
||||
y2="85.041733" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient225"
|
||||
id="linearGradient224"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="40.481247"
|
||||
y1="63.588024"
|
||||
x2="149.78378"
|
||||
y2="214.13594" />
|
||||
</defs>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer2"
|
||||
inkscape:label="Swords"
|
||||
style="display:inline"
|
||||
transform="translate(-37.465061,-23.621729)"
|
||||
sodipodi:insensitive="true">
|
||||
<path
|
||||
d="m 48.133501,27.1261 c -0.875794,0.461739 -1.679385,1.506522 -2.059419,2.732917 -0.257264,0.830207 -0.291602,1.692177 -0.15403,2.440154 l 4.88442,-0.10376 2.847281,-3.969745 c -0.539125,-0.53641 -1.269351,-0.995251 -2.099749,-1.252574 -1.226398,-0.380035 -2.54245,-0.308219 -3.418373,0.153254 z m 0.864049,8.67163 5.691829,10.178023 c 0.818709,-0.6755 1.614517,-1.223595 2.487958,-1.683773 0.873387,-0.459994 1.775488,-0.806623 2.795568,-1.099891 l -5.175707,-10.44963 -2.136009,2.977396 z m 9.84112,11.647241 c -0.759743,0.400274 -1.496586,0.938015 -2.369671,1.712745 L 66.79801,67.627649 c 0.694006,-0.470403 1.350315,-0.869995 2.027566,-1.226797 0.6775,-0.356951 1.378187,-0.672317 2.158583,-0.978711 L 61.591265,46.459012 c -1.132678,0.28202 -1.992851,0.585671 -2.752595,0.985959 z m 11.607841,22.03188 c -2.257736,1.189661 -4.5455,3.244091 -9.71083,7.478415 l 1.447631,2.747663 c 9.485159,-7.760186 9.605743,-7.823559 21.36968,-11.2588 l -1.44763,-2.747658 c -6.412968,1.865959 -9.401233,2.591094 -11.658908,3.78056 z m 3.137683,5.955757 c -0.990293,0.521596 -1.933369,1.171019 -3.101667,2.077697 l 32.006043,59.162725 6.78793,6.5051 -1.52696,-9.27693 -30.697841,-59.851933 c -1.408251,0.450961 -2.477331,0.861509 -3.467505,1.383341 z M 170.4195,27.1261 c 0.87579,0.461739 1.67938,1.506522 2.05942,2.732917 0.25726,0.830207 0.2916,1.692177 0.15403,2.440154 l -4.88442,-0.10376 -2.84728,-3.969745 c 0.53912,-0.53641 1.26935,-0.995251 2.09975,-1.252574 1.22639,-0.380035 2.54245,-0.308219 3.41837,0.153254 z m -0.86405,8.67163 -5.69183,10.178023 c -0.81871,-0.6755 -1.61452,-1.223595 -2.48796,-1.683773 -0.87338,-0.459994 -1.77549,-0.806623 -2.79557,-1.099891 l 5.17571,-10.44963 2.13601,2.977396 z m -9.84112,11.647241 c 0.75974,0.400274 1.49659,0.938015 2.36967,1.712745 l -10.32901,18.469933 c -0.69401,-0.470403 -1.35032,-0.869995 -2.02757,-1.226797 -0.6775,-0.356951 -1.37818,-0.672317 -2.15858,-0.978711 l 9.39289,-18.963129 c 1.13268,0.28202 1.99286,0.585671 2.7526,0.985959 z m -11.60784,22.03188 c 2.25773,1.189661 4.5455,3.244091 9.71083,7.478415 l -1.44763,2.747663 c -9.48516,-7.760186 -9.60575,-7.823559 -21.36968,-11.2588 l 1.44763,-2.747658 c 6.41297,1.865959 9.40123,2.591094 11.65891,3.78056 z m -3.13768,5.955757 c 0.99029,0.521596 1.93336,1.171019 3.10166,2.077697 l -32.00604,59.162725 -6.78793,6.5051 1.52696,-9.27693 30.69784,-59.851933 c 1.40825,0.450961 2.47733,0.861509 3.46751,1.383341 z"
|
||||
id="path1-0"
|
||||
style="display:inline;fill:url(#linearGradient223);stroke:url(#linearGradient10);stroke-width:0.197985"
|
||||
inkscape:label="Outline"
|
||||
inkscape:path-effect="#path-effect10"
|
||||
inkscape:original-d="m 48.133501,27.1261 c -0.875794,0.461739 -1.679385,1.506522 -2.059419,2.732917 -0.257264,0.830207 -0.291602,1.692177 -0.15403,2.440154 l 4.88442,-0.10376 2.847281,-3.969745 c -0.539125,-0.53641 -1.269351,-0.995251 -2.099749,-1.252574 -1.226398,-0.380035 -2.54245,-0.308219 -3.418373,0.153254 z m 0.864049,8.67163 5.691829,10.178023 c 0.818709,-0.6755 1.614517,-1.223595 2.487958,-1.683773 0.873387,-0.459994 1.775488,-0.806623 2.795568,-1.099891 l -5.175707,-10.44963 -2.136009,2.977396 z m 9.84112,11.647241 c -0.759743,0.400274 -1.496586,0.938015 -2.369671,1.712745 L 66.79801,67.627649 c 0.694006,-0.470403 1.350315,-0.869995 2.027566,-1.226797 0.6775,-0.356951 1.378187,-0.672317 2.158583,-0.978711 L 61.591265,46.459012 c -1.132678,0.28202 -1.992851,0.585671 -2.752595,0.985959 z m 11.607841,22.03188 c -2.257736,1.189661 -4.5455,3.244091 -9.71083,7.478415 l 1.447631,2.747663 c 9.485159,-7.760186 9.605743,-7.823559 21.36968,-11.2588 l -1.44763,-2.747658 c -6.412968,1.865959 -9.401233,2.591094 -11.658908,3.78056 z m 3.137683,5.955757 c -0.990293,0.521596 -1.933369,1.171019 -3.101667,2.077697 l 32.006043,59.162725 6.78793,6.5051 -1.52696,-9.27693 -30.697841,-59.851933 c -1.408251,0.450961 -2.477331,0.861509 -3.467505,1.383341 z"
|
||||
transform="matrix(1.0658976,0,0,1.0647436,-11.290577,-4.7306918)"
|
||||
sodipodi:insensitive="true" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
inkscape:label="Shield"
|
||||
style="display:inline;fill:url(#linearGradient4)"
|
||||
transform="translate(-37.465061,-23.621729)"
|
||||
sodipodi:insensitive="true">
|
||||
<path
|
||||
style="fill:url(#linearGradient216);fill-rule:nonzero;stroke:url(#linearGradient9);stroke-width:3.77953;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 384.22136,866.17854 C 346.2122,852.01472 314.71308,833.25777 283.18273,806.01253 225.33369,756.02543 184.27358,682.43474 170.38271,603.84478 c -5.2229,-29.54949 -5.11093,-26.51519 -5.5145,-149.43544 l -0.37594,-114.50623 8.42628,-0.5587 c 4.63445,-0.30728 15.9397,-1.23054 25.12278,-2.05169 61.95284,-5.53977 120.20353,-24.92782 165.60529,-55.11975 12.12441,-8.06267 31.38578,-23.06982 33.03537,-25.73892 0.31293,-0.50634 4.31215,2.31655 8.88713,6.27308 40.26094,34.81846 97.69777,60.08028 160.7126,70.68449 13.80381,2.32293 36.3077,4.74423 55.08405,5.92677 l 9.3868,0.59118 -0.40303,113.54603 c -0.27563,77.65784 -0.75271,116.41945 -1.50941,122.6372 -5.85838,48.13787 -16.71599,85.33197 -36.5955,125.36238 -31.02772,62.479 -77.90359,111.42228 -139.1663,145.30408 -16.17168,8.94387 -44.87489,21.43604 -54.02402,23.51225 -1.57112,0.35654 -6.66308,-1.04852 -14.83295,-4.09297 z"
|
||||
id="path9"
|
||||
inkscape:label="Fill"
|
||||
transform="scale(0.26458333)"
|
||||
sodipodi:insensitive="true" />
|
||||
<path
|
||||
d="m 169.8625,83.843633 c -42.92141,0 -62.02998,-22.04861 -62.32419,-22.342477 -0.58773,-0.882289 -1.46968,-1.176156 -2.35162,-1.176156 -0.88195,0 -1.76389,0.293867 -2.35197,1.175812 0,0 -19.402777,22.342477 -62.324187,22.342477 -1.763889,0 -2.9397,1.175811 -2.9397,2.9397 v 56.150581 c 0,30.57418 13.817244,59.67835 36.747799,77.90497 9.407522,7.34942 19.402777,12.64109 29.986108,15.875 0.29387,0 0.58808,0 0.88195,0 0.29386,0 0.58807,0 0.88194,0 10.58333,-3.23391 20.57859,-8.52558 29.98611,-15.875 22.93056,-18.22697 36.7478,-47.33113 36.7478,-77.90497 V 86.783333 c 0,-1.763889 -1.17615,-2.9397 -2.94004,-2.9397 z m -2.9397,59.090277 c 0,28.8103 -12.9353,56.15058 -34.6897,73.20139 -8.52558,6.46747 -17.63889,11.46527 -27.04641,14.40497 C 95.779165,227.60057 86.665854,222.89664 78.140276,216.1353 56.385877,199.08414 43.450577,171.74386 43.450577,142.93391 V 89.723033 c 35.865855,-0.881944 55.562499,-16.168866 61.736113,-22.342477 6.17361,6.173611 25.87025,21.460533 61.73611,22.342477 z"
|
||||
id="path1"
|
||||
style="fill:url(#linearGradient224);stroke-width:0.345;stroke-dasharray:none"
|
||||
inkscape:label="Outline"
|
||||
sodipodi:insensitive="true" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer4"
|
||||
inkscape:label="Runes"
|
||||
transform="matrix(1.2396599,0,0,1.3573618,35.341625,-64.747823)"
|
||||
sodipodi:insensitive="true">
|
||||
<path
|
||||
style="font-size:72.5698px;font-family:'Elder Futhark';-inkscape-font-specification:'Elder Futhark, Normal';fill:url(#linearGradient214);stroke:url(#linearGradient215);stroke-width:0.354849"
|
||||
d="m -2.7908332,176.61555 v 25.12305 l -2.019765,5.06713 -2.0197649,-5.06713 -0.035435,-67.75072 21.4024221,21.11895 -16.6187682,16.72507 19.4535262,19.59526 2.728455,5.138 -5.421475,-2.40954 z M 9.0797154,155.10683 -2.7908332,143.16541 v 23.8474 z"
|
||||
id="text3"
|
||||
inkscape:label="R"
|
||||
transform="scale(1.242653,0.80472988)"
|
||||
aria-label="r"
|
||||
sodipodi:insensitive="true" />
|
||||
<path
|
||||
d="M 25.778794,231.8298 25.738765,150.25119 49.75602,173.86816 32.983971,191.0405 49.75602,208.21284 Z M 43.511534,208.21284 30.42213,194.68312 v 27.05944 z m 0,-34.34468 -13.089404,-13.52972 v 27.01941 z"
|
||||
id="text2"
|
||||
style="font-size:81.9789px;font-family:'Elder Futhark';-inkscape-font-specification:'Elder Futhark, Normal';fill:url(#linearGradient212);stroke:url(#linearGradient213);stroke-width:0.400855"
|
||||
inkscape:label="B"
|
||||
transform="scale(1.393201,0.71777152)"
|
||||
aria-label="b"
|
||||
sodipodi:insensitive="true" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 15 KiB |
17
src/components/TutorialComponent.tsx
Normal file
17
src/components/TutorialComponent.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { CallBackProps, Joyride, Props } from "react-joyride";
|
||||
|
||||
|
||||
interface TutorialComponentProps extends Props {
|
||||
updateFunction: () => void;
|
||||
}
|
||||
|
||||
export default function TutorialComponent(props: TutorialComponentProps){
|
||||
return Joyride({
|
||||
...props,
|
||||
callback: (callbackProps: CallBackProps) => {
|
||||
if(callbackProps.type === "tour:end"){
|
||||
props.updateFunction();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
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().replaceAll("-", "");
|
||||
|
||||
|
||||
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 as AccountStatus}
|
||||
/>
|
||||
<span
|
||||
className="ml-1"
|
||||
>
|
||||
{status}
|
||||
</span>
|
||||
</label>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
69
src/components/button/Button.tsx
Normal file
69
src/components/button/Button.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import clsx from "clsx";
|
||||
|
||||
|
||||
export type ButtonRounding = "none" | "sm" | "md" | "lg" | "full";
|
||||
export type ButtonShape = "vertical" | "horizontal" | "square";
|
||||
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>{
|
||||
rounding?: ButtonRounding;
|
||||
shape?: ButtonShape;
|
||||
size?: ButtonSizeType;
|
||||
variant?: ButtonVariant;
|
||||
}
|
||||
|
||||
|
||||
export default function Button(props: ButtonProps){
|
||||
const {
|
||||
rounding = "lg",
|
||||
shape = "horizontal",
|
||||
size = "md"
|
||||
} = props;
|
||||
|
||||
const buttonProps = {...props};
|
||||
delete buttonProps.rounding;
|
||||
delete buttonProps.shape;
|
||||
delete buttonProps.size;
|
||||
delete buttonProps.variant;
|
||||
|
||||
|
||||
return (
|
||||
<button
|
||||
{...props}
|
||||
className={clsx(
|
||||
props.className,
|
||||
"transition-colors duration-300",
|
||||
//Rounding
|
||||
{
|
||||
"rounded-none": rounding === "none",
|
||||
"rounded-sm": rounding === "sm",
|
||||
"rounded-md": rounding === "md",
|
||||
"rounded-lg": rounding === "lg",
|
||||
"rounded-full": rounding === "full"
|
||||
},
|
||||
//Shape & Size
|
||||
{
|
||||
//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 === "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 === "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",
|
||||
"px-4 py-8": size === "xl" && shape === "vertical",
|
||||
}
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
46
src/components/button/DangerButton.tsx
Normal file
46
src/components/button/DangerButton.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import clsx from "clsx";
|
||||
import Button, { ButtonProps } from "./Button";
|
||||
|
||||
|
||||
export default function DangerButton(props: ButtonProps){
|
||||
const {
|
||||
variant = "solid",
|
||||
disabled
|
||||
} = props;
|
||||
|
||||
|
||||
return (
|
||||
<Button
|
||||
{...props}
|
||||
className={clsx(
|
||||
props.className,
|
||||
//Background
|
||||
{
|
||||
"bg-transparent": variant === "outline" || variant === "icon",
|
||||
"bg-red-600 hover:bg-red-700 active:bg-red-800": (variant === "solid") && (!disabled),
|
||||
"bg-red-400/80": (variant === "solid") && (disabled),
|
||||
"bg-transparent hover:bg-red-600 active:bg-red-700": (variant === "ghost" || variant === "outline-ghost") && (!disabled),
|
||||
"bg-transparent ": (variant === "ghost" || variant === "outline-ghost") && (disabled)
|
||||
},
|
||||
//Text
|
||||
{
|
||||
"text-white": variant === "solid",
|
||||
"text-red-600 hover:text-red-700 active:text-red-800": (variant === "outline" || variant === "icon") && (!disabled),
|
||||
"text-red-400/80": (variant === "outline" || variant === "icon") && (disabled),
|
||||
"text-red-600 hover:text-white active:text-white": (variant === "ghost" || variant === "outline-ghost") && (!disabled),
|
||||
"text-red-400/80 ": (variant === "ghost" || variant === "outline-ghost") && (disabled)
|
||||
},
|
||||
//Outline
|
||||
{
|
||||
"outline-none": variant === "ghost" || variant === "icon",
|
||||
"outline outline-red-600 hover:outline-red-700 active:outline-red-800": (variant === "solid" || variant === "outline") && (!disabled),
|
||||
"outline outline-red-400/80": (variant === "solid" || variant === "outline") && (disabled),
|
||||
"outline hover:outline-red-600 active:outline-red-700": (variant === "outline-ghost") && (!disabled),
|
||||
"outline outline-red-400/80 ": (variant === "outline-ghost") && (disabled)
|
||||
}
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
46
src/components/button/DarkButton.tsx
Normal file
46
src/components/button/DarkButton.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import clsx from "clsx";
|
||||
import Button, { ButtonProps } from "./Button";
|
||||
|
||||
|
||||
export default function DarkButton(props: ButtonProps){
|
||||
const {
|
||||
variant = "solid",
|
||||
disabled
|
||||
} = props;
|
||||
|
||||
|
||||
return (
|
||||
<Button
|
||||
{...props}
|
||||
className={clsx(
|
||||
props.className,
|
||||
//Background
|
||||
{
|
||||
"bg-transparent": variant === "outline" || variant === "icon",
|
||||
"bg-black hover:bg-neutral-700 active:bg-neutral-500": (variant === "solid") && (!disabled),
|
||||
"bg-neutral-700": (variant === "solid") && (disabled),
|
||||
"bg-transparent hover:bg-black active:bg-neutral-700": (variant === "ghost" || variant === "outline-ghost") && (!disabled),
|
||||
"bg-transparent ": (variant === "ghost" || variant === "outline-ghost") && (disabled)
|
||||
},
|
||||
//Text
|
||||
{
|
||||
"text-white": variant === "solid",
|
||||
"text-black hover:text-neutral-700 active:text-neutral-500": (variant === "outline" || variant === "icon") && (!disabled),
|
||||
"text-neutral-700": (variant === "outline" || variant === "icon") && (disabled),
|
||||
"text-black hover:text-white active:text-white": (variant === "ghost" || variant === "outline-ghost") && (!disabled),
|
||||
"text-neutral-700 ": (variant === "ghost" || variant === "outline-ghost") && (disabled)
|
||||
},
|
||||
//Outline
|
||||
{
|
||||
"outline-none": variant === "ghost" || variant === "icon",
|
||||
"outline outline-black hover:outline-neutral-700 active:outline-neutral-500": (variant === "solid" || variant === "outline") && (!disabled),
|
||||
"outline outline-neutral-700": (variant === "solid" || variant === "outline") && (disabled),
|
||||
"outline hover:outline-black active:outline-neutral-700": (variant === "outline-ghost") && (!disabled),
|
||||
"outline outline-neutral-700 ": (variant === "outline-ghost") && (disabled)
|
||||
}
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
39
src/components/button/InfoButton.tsx
Normal file
39
src/components/button/InfoButton.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import clsx from "clsx";
|
||||
import Button, { ButtonProps } from "./Button";
|
||||
|
||||
|
||||
export default function InfoButton(props: ButtonProps){
|
||||
const {
|
||||
variant = "solid"
|
||||
} = props;
|
||||
|
||||
|
||||
return (
|
||||
<Button
|
||||
{...props}
|
||||
className={clsx(
|
||||
props.className,
|
||||
//Background
|
||||
{
|
||||
"bg-transparent": variant === "outline" || variant === "icon",
|
||||
"bg-cyan-300 hover:bg-cyan-400 active:bg-cyan-500": variant === "solid",
|
||||
"bg-transparent hover:bg-cyan-300 active:bg-cyan-400": variant === "ghost" || variant === "outline-ghost"
|
||||
},
|
||||
//Text
|
||||
{
|
||||
"text-black": variant === "solid",
|
||||
"text-cyan-300 hover:text-cyan-400 active:text-cyan-500": variant === "outline" || variant === "icon",
|
||||
"text-cyan-300 hover:text-black active:text-black": variant === "ghost" || variant === "outline-ghost"
|
||||
},
|
||||
//Outline
|
||||
{
|
||||
"outline-none": variant === "ghost" || variant === "icon",
|
||||
"outline outline-cyan-300 hover:outline-cyan-400 active:outline-cyan-500": variant === "solid" || variant === "outline",
|
||||
"outline hover:outline-cyan-300 active:outline-cyan-400": variant === "outline-ghost"
|
||||
}
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
46
src/components/button/LightButton.tsx
Normal file
46
src/components/button/LightButton.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import clsx from "clsx";
|
||||
import Button, { ButtonProps } from "./Button";
|
||||
|
||||
|
||||
export default function LightButton(props: ButtonProps){
|
||||
const {
|
||||
variant = "solid",
|
||||
disabled
|
||||
} = props;
|
||||
|
||||
|
||||
return (
|
||||
<Button
|
||||
{...props}
|
||||
className={clsx(
|
||||
props.className,
|
||||
//Background
|
||||
{
|
||||
"bg-transparent": variant === "outline" || variant === "icon",
|
||||
"bg-white hover:bg-neutral-300 active:bg-neutral-400": (variant === "solid") && (!disabled),
|
||||
"bg-neutral-400/80": (variant === "solid") && (disabled),
|
||||
"bg-transparent hover:bg-white active:bg-neutral-300": (variant === "ghost" || variant === "outline-ghost") && (!disabled),
|
||||
"bg-transparent ": (variant === "ghost" || variant === "outline-ghost") && (disabled)
|
||||
},
|
||||
//Text
|
||||
{
|
||||
"text-black": variant === "solid",
|
||||
"text-white hover:text-neutral-300 active:text-neutral-400": (variant === "outline" || variant === "icon") && (!disabled),
|
||||
"text-neutral-400/80": (variant === "outline" || variant === "icon") && (disabled),
|
||||
"text-white hover:text-black active:text-black": (variant === "ghost" || variant === "outline-ghost") && (!disabled),
|
||||
"text-neutral-400/80 ": (variant === "ghost" || variant === "outline-ghost") && (disabled)
|
||||
},
|
||||
//Outline
|
||||
{
|
||||
"outline-none": variant === "ghost" || variant === "icon",
|
||||
"outline outline-white hover:outline-neutral-300 active:outline-neutral-400": (variant === "solid" || variant === "outline") && (!disabled),
|
||||
"outline outline-neutral-400/80": (variant === "solid" || variant === "outline") && (disabled),
|
||||
"outline hover:outline-neutral-300 active:outline-neutral-400": (variant === "outline-ghost") && (!disabled),
|
||||
"outline outline-neutral-400/80 ": (variant === "outline-ghost") && (disabled)
|
||||
}
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
46
src/components/button/MoltenButton.tsx
Normal file
46
src/components/button/MoltenButton.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import clsx from "clsx";
|
||||
import Button, { ButtonProps } from "./Button";
|
||||
|
||||
|
||||
export default function MoltenButton(props: ButtonProps){
|
||||
const {
|
||||
variant = "solid",
|
||||
disabled
|
||||
} = props;
|
||||
|
||||
|
||||
return (
|
||||
<Button
|
||||
{...props}
|
||||
className={clsx(
|
||||
props.className,
|
||||
//Background
|
||||
{
|
||||
"bg-transparent": variant === "outline" || variant === "icon",
|
||||
"bg-orange-600 hover:bg-orange-700 active:bg-orange-800": (variant === "solid") && (!disabled),
|
||||
"bg-orange-400/80": (variant === "solid") && (disabled),
|
||||
"bg-transparent hover:bg-orange-600 active:bg-orange-700": (variant === "ghost" || variant === "outline-ghost") && (!disabled),
|
||||
"bg-transparent ": (variant === "ghost" || variant === "outline-ghost") && (disabled)
|
||||
},
|
||||
//Text
|
||||
{
|
||||
"text-white": variant === "solid",
|
||||
"text-orange-600 hover:text-orange-700 active:text-orange-800": (variant === "outline" || variant === "icon") && (!disabled),
|
||||
"text-orange-400/80": (variant === "outline" || variant === "icon") && (disabled),
|
||||
"text-orange-600 hover:text-white active:text-white": (variant === "ghost" || variant === "outline-ghost") && (!disabled),
|
||||
"text-orange-400/80 ": (variant === "ghost" || variant === "outline-ghost") && (disabled)
|
||||
},
|
||||
//Outline
|
||||
{
|
||||
"outline-none": variant === "ghost" || variant === "icon",
|
||||
"outline outline-orange-600 hover:outline-orange-700 active:outline-orange-800": (variant === "solid" || variant === "outline") && (!disabled),
|
||||
"outline outline-orange-400/80": (variant === "solid" || variant === "outline") && (disabled),
|
||||
"outline hover:outline-orange-600 active:outline-orange-700": (variant === "outline-ghost") && (!disabled),
|
||||
"outline outline-orange-400/80 ": (variant === "outline-ghost") && (disabled)
|
||||
}
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
46
src/components/button/PrimaryButton.tsx
Normal file
46
src/components/button/PrimaryButton.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import clsx from "clsx";
|
||||
import Button, { ButtonProps } from "./Button";
|
||||
|
||||
|
||||
export default function PrimaryButton(props: ButtonProps){
|
||||
const {
|
||||
variant = "solid",
|
||||
disabled
|
||||
} = props;
|
||||
|
||||
|
||||
return (
|
||||
<Button
|
||||
{...props}
|
||||
className={clsx(
|
||||
props.className,
|
||||
//Background
|
||||
{
|
||||
"bg-transparent": variant === "outline" || variant === "icon",
|
||||
"bg-blue-600 hover:bg-blue-700 active:bg-blue-800": (variant === "solid") && (!disabled),
|
||||
"bg-blue-400/80": (variant === "solid") && (disabled),
|
||||
"bg-transparent hover:bg-blue-600 active:bg-blue-700": (variant === "ghost" || variant === "outline-ghost") && (!disabled),
|
||||
"bg-transparent ": (variant === "ghost" || variant === "outline-ghost") && (disabled)
|
||||
},
|
||||
//Text
|
||||
{
|
||||
"text-white": variant === "solid",
|
||||
"text-blue-600 hover:text-blue-700 active:text-blue-800": (variant === "outline" || variant === "icon") && (!disabled),
|
||||
"text-blue-400/80": (variant === "outline" || variant === "icon") && (disabled),
|
||||
"text-blue-600 hover:text-white active:text-white": (variant === "ghost" || variant === "outline-ghost") && (!disabled),
|
||||
"text-blue-400/80 ": (variant === "ghost" || variant === "outline-ghost") && (disabled)
|
||||
},
|
||||
//Outline
|
||||
{
|
||||
"outline-none": variant === "ghost" || variant === "icon",
|
||||
"outline outline-blue-600 hover:outline-blue-700 active:outline-blue-800": (variant === "solid" || variant === "outline") && (!disabled),
|
||||
"outline outline-blue-400/80": (variant === "solid" || variant === "outline") && (disabled),
|
||||
"outline hover:outline-blue-600 active:outline-blue-700": (variant === "outline-ghost") && (!disabled),
|
||||
"outline outline-blue-400/80 ": (variant === "outline-ghost") && (disabled)
|
||||
}
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
46
src/components/button/SecondaryButton.tsx
Normal file
46
src/components/button/SecondaryButton.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import clsx from "clsx";
|
||||
import Button, { ButtonProps } from "./Button";
|
||||
|
||||
|
||||
export default function SecondaryButton(props: ButtonProps){
|
||||
const {
|
||||
variant = "solid",
|
||||
disabled
|
||||
} = props;
|
||||
|
||||
|
||||
return (
|
||||
<Button
|
||||
{...props}
|
||||
className={clsx(
|
||||
props.className,
|
||||
//Background
|
||||
{
|
||||
"bg-transparent": variant === "outline" || variant === "icon",
|
||||
"bg-neutral-500 hover:bg-neutral-600 active:bg-neutral-700": (variant === "solid") && (!disabled),
|
||||
"bg-neutral-700": (variant === "solid") && (disabled),
|
||||
"bg-transparent hover:bg-neutral-500 active:bg-neutral-600": (variant === "ghost" || variant === "outline-ghost") && (!disabled),
|
||||
"bg-transparent ": (variant === "ghost" || variant === "outline-ghost") && (disabled)
|
||||
},
|
||||
//Text
|
||||
{
|
||||
"text-white": variant === "solid",
|
||||
"text-neutral-500 hover:text-neutral-600 active:text-neutral-700": (variant === "outline" || variant === "icon") && (!disabled),
|
||||
"text-neutral-700": (variant === "outline" || variant === "icon") && (disabled),
|
||||
"text-neutral-500 hover:text-white active:text-white": (variant === "ghost" || variant === "outline-ghost") && (!disabled),
|
||||
"text-neutral-700 ": (variant === "ghost" || variant === "outline-ghost") && (disabled)
|
||||
},
|
||||
//Outline
|
||||
{
|
||||
"outline-none": variant === "ghost" || variant === "icon",
|
||||
"outline outline-neutral-500 hover:outline-neutral-600 active:outline-neutral-700": (variant === "solid" || variant === "outline") && (!disabled),
|
||||
"outline outline-neutral-700": (variant === "solid" || variant === "outline") && (disabled),
|
||||
"outline hover:outline-neutral-500 active:outline-neutral-600": (variant === "outline-ghost") && (!disabled),
|
||||
"outline outline-neutral-700 ": (variant === "outline-ghost") && (disabled)
|
||||
}
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
46
src/components/button/SuccessButton.tsx
Normal file
46
src/components/button/SuccessButton.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import clsx from "clsx";
|
||||
import Button, { ButtonProps } from "./Button";
|
||||
|
||||
|
||||
export default function SuccessButton(props: ButtonProps){
|
||||
const {
|
||||
variant = "solid",
|
||||
disabled
|
||||
} = props;
|
||||
|
||||
|
||||
return (
|
||||
<Button
|
||||
{...props}
|
||||
className={clsx(
|
||||
props.className,
|
||||
//Background
|
||||
{
|
||||
"bg-transparent": variant === "outline" || variant === "icon",
|
||||
"bg-green-600 hover:bg-green-700 active:bg-green-800": (variant === "solid") && (!disabled),
|
||||
"bg-green-300/80": (variant === "solid") && (disabled),
|
||||
"bg-transparent hover:bg-green-600 active:bg-green-700": (variant === "ghost" || variant === "outline-ghost") && (!disabled),
|
||||
"bg-transparent ": (variant === "ghost" || variant === "outline-ghost") && (disabled)
|
||||
},
|
||||
//Text
|
||||
{
|
||||
"text-white": variant === "solid",
|
||||
"text-green-600 hover:text-green-700 active:text-green-800": (variant === "outline" || variant === "icon") && (!disabled),
|
||||
"text-green-300/80": (variant === "outline" || variant === "icon") && (disabled),
|
||||
"text-green-600 hover:text-white active:text-white": (variant === "ghost" || variant === "outline-ghost") && (!disabled),
|
||||
"text-green-300/80 ": (variant === "ghost" || variant === "outline-ghost") && (disabled)
|
||||
},
|
||||
//Outline
|
||||
{
|
||||
"outline-none": variant === "ghost" || variant === "icon",
|
||||
"outline outline-green-600 hover:outline-green-700 active:outline-green-800": (variant === "solid" || variant === "outline") && (!disabled),
|
||||
"outline outline-green-300/80": (variant === "solid" || variant === "outline") && (disabled),
|
||||
"outline hover:outline-green-600 active:outline-green-700": (variant === "outline-ghost") && (!disabled),
|
||||
"outline outline-green-300/80 ": (variant === "outline-ghost") && (disabled)
|
||||
}
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
46
src/components/button/TertiaryButton.tsx
Normal file
46
src/components/button/TertiaryButton.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import clsx from "clsx";
|
||||
import Button, { ButtonProps } from "./Button";
|
||||
|
||||
|
||||
export default function TertiaryButton(props: ButtonProps){
|
||||
const {
|
||||
variant = "solid",
|
||||
disabled
|
||||
} = props;
|
||||
|
||||
|
||||
return (
|
||||
<Button
|
||||
{...props}
|
||||
className={clsx(
|
||||
props.className,
|
||||
//Background
|
||||
{
|
||||
"bg-transparent": variant === "outline" || variant === "icon",
|
||||
"bg-purple-600 hover:bg-purple-700 active:bg-purple-800": (variant === "solid") && (!disabled),
|
||||
"bg-purple-400/80": (variant === "solid") && (disabled),
|
||||
"bg-transparent hover:bg-purple-600 active:bg-purple-700": (variant === "ghost" || variant === "outline-ghost") && (!disabled),
|
||||
"bg-transparent ": (variant === "ghost" || variant === "outline-ghost") && (disabled)
|
||||
},
|
||||
//Text
|
||||
{
|
||||
"text-white": variant === "solid",
|
||||
"text-purple-600 hover:text-purple-700 active:text-purple-800": (variant === "outline" || variant === "icon") && (!disabled),
|
||||
"text-purple-400/80": (variant === "outline" || variant === "icon") && (disabled),
|
||||
"text-purple-600 hover:text-white active:text-white": (variant === "ghost" || variant === "outline-ghost") && (!disabled),
|
||||
"text-purple-400/80 ": (variant === "ghost" || variant === "outline-ghost") && (disabled)
|
||||
},
|
||||
//Outline
|
||||
{
|
||||
"outline-none": variant === "ghost" || variant === "icon",
|
||||
"outline outline-purple-600 hover:outline-purple-700 active:outline-purple-800": (variant === "solid" || variant === "outline") && (!disabled),
|
||||
"outline outline-purple-400/80": (variant === "solid" || variant === "outline") && (disabled),
|
||||
"outline hover:outline-purple-600 active:outline-purple-700": (variant === "outline-ghost") && (!disabled),
|
||||
"outline outline-purple-400/80 ": (variant === "outline-ghost") && (disabled)
|
||||
}
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
46
src/components/button/WarningButton.tsx
Normal file
46
src/components/button/WarningButton.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import clsx from "clsx";
|
||||
import Button, { ButtonProps } from "./Button";
|
||||
|
||||
|
||||
export default function WarningButton(props: ButtonProps){
|
||||
const {
|
||||
variant = "solid",
|
||||
disabled
|
||||
} = props;
|
||||
|
||||
|
||||
return (
|
||||
<Button
|
||||
{...props}
|
||||
className={clsx(
|
||||
props.className,
|
||||
//Background
|
||||
{
|
||||
"bg-transparent": variant === "outline" || variant === "icon",
|
||||
"bg-yellow-400 hover:bg-yellow-500 active:bg-yellow-600": (variant === "solid") && (!disabled),
|
||||
"bg-yellow-600/80": (variant === "solid") && (disabled),
|
||||
"bg-transparent hover:bg-yellow-400 active:bg-yellow-500": (variant === "ghost" || variant === "outline-ghost") && (!disabled),
|
||||
"bg-transparent ": (variant === "ghost" || variant === "outline-ghost") && (disabled)
|
||||
},
|
||||
//Text
|
||||
{
|
||||
"text-black": variant === "solid",
|
||||
"text-yellow-400 hover:text-yellow-500 active:text-yellow-600": (variant === "outline" || variant === "icon") && (!disabled),
|
||||
"text-yellow-600/80": (variant === "outline" || variant === "icon") && (disabled),
|
||||
"text-yellow-400 hover:text-black active:text-black": (variant === "ghost" || variant === "outline-ghost") && (!disabled),
|
||||
"text-yellow-600/80 ": (variant === "ghost" || variant === "outline-ghost") && (disabled)
|
||||
},
|
||||
//Outline
|
||||
{
|
||||
"outline-none": variant === "ghost" || variant === "icon",
|
||||
"outline outline-yellow-400 hover:outline-yellow-500 active:outline-yellow-600": (variant === "solid" || variant === "outline") && (!disabled),
|
||||
"outline outline-yellow-600/80": (variant === "solid" || variant === "outline") && (disabled),
|
||||
"outline hover:outline-yellow-400 active:outline-yellow-500": (variant === "outline-ghost") && (!disabled),
|
||||
"outline outline-yellow-600/80 ": (variant === "outline-ghost") && (disabled)
|
||||
}
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
68
src/components/classGroup/ClassGroupSelector.tsx
Normal file
68
src/components/classGroup/ClassGroupSelector.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import { ClassGroup } from "@/interface/ClassGroup";
|
||||
import SelectClassGroupModal from "@/ui/classGroup/modal/SelectClassGroupModal";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
|
||||
export default function ClassGroupSelector({
|
||||
raidGroupId,
|
||||
selectedClassGroups,
|
||||
onChange
|
||||
}:{
|
||||
raidGroupId: string;
|
||||
selectedClassGroups: (ClassGroup | null)[];
|
||||
onChange: (classGroups: (ClassGroup | null)[]) => void;
|
||||
}){
|
||||
const [ classGroups, setClassGroups ] = useState(selectedClassGroups);
|
||||
const [ displaySelectClassGroupModal, setDisplaySelectClassGroupModal ] = useState(false);
|
||||
const [ selectedCell, setSelectedCell ] = useState(0);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
setClassGroups(selectedClassGroups);
|
||||
}, [selectedClassGroups]);
|
||||
|
||||
|
||||
const updateClassGroups = (classGroup?: ClassGroup | null) => {
|
||||
const newClassGroups = [...classGroups];
|
||||
if(classGroup){
|
||||
newClassGroups[selectedCell] = classGroup;
|
||||
}
|
||||
else{
|
||||
newClassGroups[selectedCell] = null;
|
||||
}
|
||||
setClassGroups(newClassGroups);
|
||||
onChange(newClassGroups);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="grid grid-cols-3 gap-4"
|
||||
style={{flex: "0 0 33.333333333%"}}
|
||||
>
|
||||
{
|
||||
classGroups.map((classGroup, index) => (
|
||||
<div
|
||||
key={`${index}`}
|
||||
className="cursor-pointer border px-2 py-1"
|
||||
onClick={() => {
|
||||
setDisplaySelectClassGroupModal(true);
|
||||
setSelectedCell(index);
|
||||
}}
|
||||
>
|
||||
{classGroup?.classGroupName ?? "Any"}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<SelectClassGroupModal
|
||||
display={displaySelectClassGroupModal}
|
||||
close={() => setDisplaySelectClassGroupModal(false)}
|
||||
selectedClassGroup={classGroups[selectedCell]}
|
||||
onChange={updateClassGroups}
|
||||
raidGroupId={raidGroupId}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
42
src/components/classGroup/ClassGroupsByRaidLayoutDisplay.tsx
Normal file
42
src/components/classGroup/ClassGroupsByRaidLayoutDisplay.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { useGetClassGroupsByRaidLayout } from "@/hooks/ClassGroupHooks";
|
||||
import DangerMessage from "../message/DangerMessage";
|
||||
|
||||
|
||||
export default function ClassGroupsByRaidLayoutDisplay({
|
||||
raidGroupId,
|
||||
raidLayoutId
|
||||
}:{
|
||||
raidGroupId: string;
|
||||
raidLayoutId: string;
|
||||
}){
|
||||
const classGroupsQuery = useGetClassGroupsByRaidLayout(raidGroupId, raidLayoutId);
|
||||
|
||||
|
||||
if(classGroupsQuery.status === "pending"){
|
||||
return (<div>Loading...</div>);
|
||||
}
|
||||
else if(classGroupsQuery.status === "error"){
|
||||
return (<DangerMessage>Error: {classGroupsQuery.error.message}</DangerMessage>);
|
||||
}
|
||||
else{
|
||||
return (
|
||||
<div
|
||||
className="flex flex-row items-center justify-center gap-x-4"
|
||||
>
|
||||
{
|
||||
classGroupsQuery.data.map((classGroup, index) => (
|
||||
<div
|
||||
key={classGroup?.classGroupId ?? index}
|
||||
>
|
||||
{classGroup?.classGroupName ?? "Any"}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
{
|
||||
classGroupsQuery.data.length === 0 &&
|
||||
<> </>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
100
src/components/game/GameSelector.tsx
Normal file
100
src/components/game/GameSelector.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
import { useGetGames } from "@/hooks/GameHooks";
|
||||
import { Game } from "@/interface/Game";
|
||||
import clsx from "clsx";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
import TextInput from "../input/TextInput";
|
||||
|
||||
export default function GameSelector({
|
||||
disabled,
|
||||
game,
|
||||
onChange
|
||||
}:{
|
||||
disabled: boolean;
|
||||
game?: Game;
|
||||
onChange: (game: Game | undefined) => void;
|
||||
}){
|
||||
const [ gameSearch, setGameSearch ] = useState(game?.gameName ?? "");
|
||||
const [ searchTerm, setSearchTerm ] = useState(game?.gameName ?? "");
|
||||
const [ searching, setSearching ] = useState(false);
|
||||
|
||||
const modalId = crypto.randomUUID().replaceAll("-", "");
|
||||
|
||||
|
||||
const gameSearchQuery = useGetGames(0, 5, gameSearch);
|
||||
const games = gameSearchQuery.data;
|
||||
|
||||
const setGame = (selectedGame?: Game) => {
|
||||
setSearchTerm(selectedGame?.gameName ?? "");
|
||||
setGameSearch(selectedGame?.gameName ?? "");
|
||||
setSearching(false);
|
||||
onChange?.(selectedGame);
|
||||
}
|
||||
|
||||
|
||||
const updateGameSearch = useDebouncedCallback((searchTerm: string) => {
|
||||
setGameSearch(searchTerm);
|
||||
}, 500);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
updateGameSearch(searchTerm);
|
||||
}, [ searchTerm, updateGameSearch ]);
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative flex flex-col"
|
||||
>
|
||||
<TextInput
|
||||
id={`gameSearchTextInput${modalId}`}
|
||||
placeholder="Game Search"
|
||||
onChange={(e) => { setSearchTerm(e.target.value); onChange?.(undefined); setSearching(true); }}
|
||||
value={searchTerm}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<div
|
||||
className="relative mx-4 z-10"
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
"absolute flex flex-col justify-center items-center w-full min-h-6 rounded-lg bg-neutral-700",
|
||||
{
|
||||
"hidden": !searching
|
||||
}
|
||||
)}
|
||||
>
|
||||
{
|
||||
games && games.map((searchGame, index) => (
|
||||
<div
|
||||
key={searchGame.gameId}
|
||||
className={clsx("w-full border-x-2 border-black dark:border-gray-400 cursor-pointer",
|
||||
{
|
||||
"rounded-t-lg border-t-2 border-b": index === 0,
|
||||
"rounded-b-lg border-b-2 border-t": index === games.length - 1,
|
||||
"border-y": index > 0 && index < games.length - 1,
|
||||
}
|
||||
)}
|
||||
onClick={() => setGame(searchGame)}
|
||||
>
|
||||
<div
|
||||
className="mx-4 py-2"
|
||||
>
|
||||
{searchGame.gameName}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
{
|
||||
games?.length == 0 &&
|
||||
<div
|
||||
className="mx-4 py-2"
|
||||
>
|
||||
No games found
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
45
src/components/gameClass/GameClassByClassGroupDisplay.tsx
Normal file
45
src/components/gameClass/GameClassByClassGroupDisplay.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import { useGetGameClassesByClassGroup } from "@/hooks/GameClassHooks";
|
||||
import DangerMessage from "../message/DangerMessage";
|
||||
|
||||
|
||||
export default function GameClassByClassGroupDisplay({
|
||||
classGroupId
|
||||
}:{
|
||||
classGroupId: string;
|
||||
}){
|
||||
const gameClassesQuery = useGetGameClassesByClassGroup(classGroupId);
|
||||
const displayId = crypto.randomUUID().replaceAll("-", "");
|
||||
|
||||
|
||||
if(gameClassesQuery.status === "pending"){
|
||||
return (<div>Loading...</div>);
|
||||
}
|
||||
else if(gameClassesQuery.status === "error"){
|
||||
return (<DangerMessage>Error: {gameClassesQuery.error.message}</DangerMessage>);
|
||||
}
|
||||
else{
|
||||
return (
|
||||
<div
|
||||
className="flex flex-row flex-wrap items-center justify-center gap-x-4"
|
||||
>
|
||||
{
|
||||
gameClassesQuery.data.map((gameClass) => (
|
||||
<div
|
||||
key={`gameClassByClassGroupDisplay${classGroupId}${gameClass.gameClassId}${displayId}`}
|
||||
className="flex flex-row items-center justify-center"
|
||||
>
|
||||
{
|
||||
gameClass.gameClassIcon &&
|
||||
<img
|
||||
className="max-h-6 max-w-6 mr-2"
|
||||
src={`${import.meta.env.VITE_ICON_URL}/gameClass/${gameClass.gameClassIcon}`}
|
||||
/>
|
||||
}
|
||||
{gameClass.gameClassName}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
32
src/components/gameClass/GameClassCellDisplay.tsx
Normal file
32
src/components/gameClass/GameClassCellDisplay.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { useGetGameClass } from "@/hooks/GameClassHooks";
|
||||
import DangerMessage from "../message/DangerMessage";
|
||||
|
||||
|
||||
export default function GameClassCellDisplay({
|
||||
gameClassId
|
||||
}:{
|
||||
gameClassId: string;
|
||||
}){
|
||||
const gameClassQuery = useGetGameClass(gameClassId);
|
||||
|
||||
|
||||
if(gameClassQuery.status === "pending"){
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
else if(gameClassQuery.status === "error"){
|
||||
return <DangerMessage>Error: {gameClassQuery.error.message}</DangerMessage>
|
||||
}
|
||||
else{
|
||||
return (
|
||||
<div
|
||||
className="flex flex-row items-center justify-center"
|
||||
>
|
||||
<img
|
||||
className="max-h-14 max-w-14 mr-2"
|
||||
src={`${import.meta.env.VITE_ICON_URL}/gameClass/${gameClassQuery.data.gameClassIcon}`}
|
||||
/>
|
||||
<span>{gameClassQuery.data.gameClassName}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
73
src/components/gameClass/GameClassSelector.tsx
Normal file
73
src/components/gameClass/GameClassSelector.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import { useGetGameClasses } from "@/hooks/GameClassHooks";
|
||||
import { useEffect, useState } from "react";
|
||||
import DangerMessage from "../message/DangerMessage";
|
||||
|
||||
|
||||
export function GameClassSelector({
|
||||
gameId,
|
||||
gameClassId,
|
||||
onChange
|
||||
}:{
|
||||
gameId: string;
|
||||
gameClassId?: string;
|
||||
onChange?: (gameClassId?: string) => void;
|
||||
}){
|
||||
const [ selectedGameClassId, setSelectedGameClassId ] = useState(gameClassId);
|
||||
const selectorId = crypto.randomUUID().replaceAll("-", "");
|
||||
|
||||
|
||||
const gameClassesQuery = useGetGameClasses(gameId, 0, 100, undefined);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
onChange?.(selectedGameClassId);
|
||||
}, [ selectedGameClassId, onChange ]);
|
||||
|
||||
|
||||
if(gameClassesQuery.status === "pending"){
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
else if(gameClassesQuery.status === "error"){
|
||||
return <DangerMessage>Error loading Game Classes: {gameClassesQuery.error.message}</DangerMessage>
|
||||
}
|
||||
else{
|
||||
return (
|
||||
<div
|
||||
className="grid grid-cols-3 gap-4"
|
||||
style={{flex: "0 0 33.333333333%"}}
|
||||
>
|
||||
{
|
||||
gameClassesQuery.data.map((gameClass) => (
|
||||
<div
|
||||
key={gameClass.gameClassId}
|
||||
className="flex flex-row"
|
||||
>
|
||||
<input
|
||||
id={`gameClassSelector${gameClass.gameClassId}${selectorId}`}
|
||||
className="cursor-pointer"
|
||||
type="radio"
|
||||
name="gameClassId"
|
||||
value={gameClass.gameClassId}
|
||||
checked={selectedGameClassId === gameClass.gameClassId}
|
||||
onChange={(e) => setSelectedGameClassId(e.target.value)}
|
||||
/>
|
||||
<label
|
||||
className="ml-2 flex flex-row flex-nowrap justify-center items-center text-nowrap cursor-pointer"
|
||||
htmlFor={`gameClassSelector${gameClass.gameClassId}${selectorId}`}
|
||||
>
|
||||
{
|
||||
gameClass.gameClassIcon &&
|
||||
<img
|
||||
className="m-auto max-h-6 max-w-6 mr-2"
|
||||
src={`${import.meta.env.VITE_ICON_URL}/gameClass/${gameClass.gameClassIcon}`}
|
||||
/>
|
||||
}
|
||||
{gameClass.gameClassName}
|
||||
</label>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
83
src/components/gameClass/GameClassesSelector.tsx
Normal file
83
src/components/gameClass/GameClassesSelector.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import { useGetGameClasses } from "@/hooks/GameClassHooks";
|
||||
import { useEffect, useState } from "react";
|
||||
import DangerMessage from "../message/DangerMessage";
|
||||
|
||||
|
||||
export function GameClassesSelector({
|
||||
gameId,
|
||||
gameClassIds,
|
||||
onChange
|
||||
}:{
|
||||
gameId: string;
|
||||
gameClassIds?: string[];
|
||||
onChange?: (gameClassIds: string[]) => void;
|
||||
}){
|
||||
const [ selectedGameClassIds, setSelectedGameClassIds ] = useState<string[]>(gameClassIds ?? []);
|
||||
const selectorId = crypto.randomUUID().replaceAll("-", "");
|
||||
|
||||
|
||||
const gameClassesQuery = useGetGameClasses(gameId, 0, 100, undefined);
|
||||
|
||||
|
||||
const updateSelectedGameClassIds = (selectedGameClassId: string) => {
|
||||
if(selectedGameClassIds.includes(selectedGameClassId)){
|
||||
setSelectedGameClassIds(selectedGameClassIds.filter((id) => id !== selectedGameClassId));
|
||||
}
|
||||
else{
|
||||
setSelectedGameClassIds([...selectedGameClassIds, selectedGameClassId]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
onChange?.(selectedGameClassIds);
|
||||
}, [ selectedGameClassIds, onChange ]);
|
||||
|
||||
|
||||
if(gameClassesQuery.status === "pending"){
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
else if(gameClassesQuery.status === "error"){
|
||||
return <DangerMessage>Error loading Game Classes: {gameClassesQuery.error.message}</DangerMessage>
|
||||
}
|
||||
else{
|
||||
return (
|
||||
<div
|
||||
className="grid grid-cols-3 gap-4"
|
||||
style={{flex: "0 0 33.333333333%"}}
|
||||
>
|
||||
{
|
||||
gameClassesQuery.data.map((gameClass) => (
|
||||
<div
|
||||
key={gameClass.gameClassId}
|
||||
className="flex flex-row"
|
||||
>
|
||||
<input
|
||||
id={`gameClassSelector${gameClass.gameClassId}${selectorId}`}
|
||||
className="cursor-pointer"
|
||||
type="checkbox"
|
||||
name="gameClassId"
|
||||
value={gameClass.gameClassId}
|
||||
checked={selectedGameClassIds.includes(gameClass.gameClassId ?? "")}
|
||||
onChange={(e) => updateSelectedGameClassIds(e.target.value)}
|
||||
/>
|
||||
<label
|
||||
className="ml-2 flex flex-row flex-nowrap justify-center items-center text-nowrap cursor-pointer"
|
||||
htmlFor={`gameClassSelector${gameClass.gameClassId}${selectorId}`}
|
||||
>
|
||||
{
|
||||
gameClass.gameClassIcon &&
|
||||
<img
|
||||
className="m-auto max-h-6 max-w-6 mr-2"
|
||||
src={`${import.meta.env.VITE_ICON_URL}/gameClass/${gameClass.gameClassIcon}`}
|
||||
/>
|
||||
}
|
||||
{gameClass.gameClassName}
|
||||
</label>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
47
src/components/input/DateInput.tsx
Normal file
47
src/components/input/DateInput.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import clsx from "clsx";
|
||||
import { ComponentProps } from "react";
|
||||
|
||||
|
||||
interface DateInputProps extends ComponentProps<"input">{
|
||||
id: string;
|
||||
inputClasses?: string;
|
||||
labelClasses?: string;
|
||||
accepted?: boolean;
|
||||
}
|
||||
|
||||
|
||||
export default function DateInput(props: DateInputProps){
|
||||
const { id, placeholder, inputClasses, labelClasses } = props;
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
className="bg-inherit px-4 pb-4 rounded-sm w-full md:flex md:justify-center"
|
||||
>
|
||||
<div
|
||||
className="relative bg-inherit w-full"
|
||||
>
|
||||
<input
|
||||
{...props}
|
||||
id={id}
|
||||
className={clsx(
|
||||
"peer px-2 py-1 rounded-lg ring-2 ring-gray-500 focus:ring-sky-600 outline-hidden w-full",
|
||||
inputClasses
|
||||
)}
|
||||
type="datetime-local"
|
||||
/>
|
||||
<label
|
||||
htmlFor={id}
|
||||
id={`${id}Label`}
|
||||
className={clsx(
|
||||
"absolute cursor-pointer left-0 -top-3 mx-1 px-1",
|
||||
"bg-white dark:bg-neutral-825 text-sm peer-focus:text-sky-600",
|
||||
labelClasses
|
||||
)}
|
||||
>
|
||||
{placeholder}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
49
src/components/input/FileInput.tsx
Normal file
49
src/components/input/FileInput.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import { BsCloudUpload } from "react-icons/bs";
|
||||
|
||||
|
||||
export default function FileInput({
|
||||
file,
|
||||
setFile
|
||||
}:{
|
||||
file: File | null | undefined;
|
||||
setFile: (input: File | null) => void;
|
||||
}){
|
||||
return (
|
||||
<div
|
||||
className="relative border-2 rounded-lg border-gray-500 h-24 mx-4 w-md z-0"
|
||||
>
|
||||
<div
|
||||
className="absolute cursor-text left-0 -top-3 bg-white dark:bg-neutral-800 text-gray-500 mx-1 px-1"
|
||||
>
|
||||
Icon File
|
||||
</div>
|
||||
<input
|
||||
type="file"
|
||||
name="iconFile"
|
||||
className="relative opacity-0 w-full h-full z-50 cursor-pointer"
|
||||
onChange={(e) => setFile(e.target.files?.[0] ?? null)}
|
||||
/>
|
||||
<div
|
||||
className="absolute top-0 left-0 flex flex-col justify-center items-center w-full h-full"
|
||||
>
|
||||
<div
|
||||
className="flex flex-row gap-2"
|
||||
>
|
||||
<BsCloudUpload
|
||||
size={24}
|
||||
/>
|
||||
Drop files anywhere or click to select file
|
||||
</div>
|
||||
{
|
||||
file && (
|
||||
<p
|
||||
className="text-green-600"
|
||||
>
|
||||
Name: {file.name}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
37
src/components/input/IconInput.tsx
Normal file
37
src/components/input/IconInput.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import FileInput from "./FileInput";
|
||||
|
||||
|
||||
export default function IconInput({
|
||||
file,
|
||||
setFile,
|
||||
addErrorMessage
|
||||
}:{
|
||||
file: File | null | undefined;
|
||||
setFile: (input: File | null) => void;
|
||||
addErrorMessage: (message: string) => void;
|
||||
}){
|
||||
const setIconFile = (inputFile: File | null) => {
|
||||
if((inputFile) && (!inputFile.type.startsWith("image"))){
|
||||
addErrorMessage("File is invalid image format: " + inputFile.type);
|
||||
}
|
||||
//Prevent files larger than 10MB form being uploaded
|
||||
else if((inputFile) && (inputFile.size > 10485760)){
|
||||
addErrorMessage("File is too large: " + inputFile.size + " bytes");
|
||||
}
|
||||
//Prevent empty files
|
||||
else if((inputFile) && (inputFile.size <= 0)){
|
||||
addErrorMessage("File is empty");
|
||||
}
|
||||
else{
|
||||
setFile(inputFile);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<FileInput
|
||||
file={file}
|
||||
setFile={setIconFile}
|
||||
/>
|
||||
);
|
||||
}
|
||||
88
src/components/input/NumberInput.tsx
Normal file
88
src/components/input/NumberInput.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import clsx from "clsx";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
|
||||
export default function NumberInput({
|
||||
id,
|
||||
name,
|
||||
label,
|
||||
defaultValue,
|
||||
value,
|
||||
min,
|
||||
max,
|
||||
onChange,
|
||||
disabled
|
||||
}:{
|
||||
id: string;
|
||||
name?: string;
|
||||
accepted?: boolean;
|
||||
label?: string;
|
||||
defaultValue?: number;
|
||||
value?: number;
|
||||
min?: number;
|
||||
max?: number;
|
||||
onChange?: (value: number) => void;
|
||||
disabled?: boolean;
|
||||
}){
|
||||
const [ inputValue, setInputValue ] = useState(value ?? 1);
|
||||
const [ minValue, setMinValue ] = useState(min ?? Number.MIN_VALUE);
|
||||
const [ maxValue, setMaxValue ] = useState(max ?? Number.MAX_VALUE);
|
||||
const inputId = crypto.randomUUID().replaceAll("-", "");
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
//TODO: Fix this warning. Use component library
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||
setMinValue(min ?? Number.MIN_VALUE);
|
||||
setMaxValue(max ?? Number.MAX_VALUE);
|
||||
setInputValue(value ?? defaultValue ?? 0);
|
||||
}, [ id, min, max, defaultValue, value ]);
|
||||
|
||||
|
||||
const changeInput = (value: number) => {
|
||||
if(value < minValue){
|
||||
value = maxValue;
|
||||
}
|
||||
else if(value > maxValue){
|
||||
value = minValue;
|
||||
}
|
||||
|
||||
setInputValue(value);
|
||||
onChange?.(value);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
className="bg-inherit px-4 rounded-sm w-full md:flex md:justify-center"
|
||||
>
|
||||
<div
|
||||
className="relative bg-inherit w-18"
|
||||
>
|
||||
<input
|
||||
id={`${id}NumberInput${inputId}`}
|
||||
type="number"
|
||||
name={name}
|
||||
className={clsx(
|
||||
"peer px-2 py-1 w-16 rounded-lg ring-2 ring-gray-500 focus:ring-sky-600 outline-hidden",
|
||||
)}
|
||||
onChange={(e) => changeInput(parseInt(e.target.value || "1"))}
|
||||
value={inputValue}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<label
|
||||
htmlFor={`${id}NumberInput${inputId}`}
|
||||
id={`${id}NumberInput${inputId}Label`}
|
||||
className={clsx(
|
||||
"absolute cursor-pointer left-0 -top-3 mx-1 px-1",
|
||||
"bg-white dark:bg-neutral-800 text-sm",
|
||||
"text-gray-500 peer-focus:text-sky-600"
|
||||
)}
|
||||
style={{transitionProperty: "top, font-size, line-height", transitionTimingFunction: "cubic-bezier(0.4, 0, 0.2, 1)", transitionDuration: "150ms"}}
|
||||
>
|
||||
{label}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
63
src/components/input/PasswordInput.tsx
Normal file
63
src/components/input/PasswordInput.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import clsx from "clsx";
|
||||
import { ComponentProps } from "react";
|
||||
|
||||
|
||||
|
||||
interface PasswordInputProps extends ComponentProps<"input">{
|
||||
id: string;
|
||||
inputClasses?: string;
|
||||
labelClasses?: string;
|
||||
accepted?: boolean;
|
||||
}
|
||||
|
||||
|
||||
export default function PasswordInput(props: PasswordInputProps){
|
||||
const { id, placeholder, inputClasses, labelClasses, accepted } = props;
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex flex-row justify-center w-full rounded-sm bg-inherit"
|
||||
>
|
||||
<div
|
||||
className="relative bg-inherit w-full"
|
||||
>
|
||||
<input
|
||||
{...props}
|
||||
type="password"
|
||||
className={clsx(
|
||||
"peer bg-transparent w-full min-w-72 px-2 py-1 rounded-lg",
|
||||
"ring-2 focus:ring-sky-600 placeholder-transparent outline-hidden",
|
||||
inputClasses,
|
||||
{
|
||||
"ring-gray-500": accepted === undefined,
|
||||
"ring-red-600": accepted === false,
|
||||
"ring-green-600": accepted === true
|
||||
}
|
||||
)}
|
||||
/>
|
||||
<label
|
||||
htmlFor={id}
|
||||
id={`${id}Label`}
|
||||
className={clsx(
|
||||
"absolute cursor-text left-0 -top-3 mx-1 px-1",
|
||||
"bg-white dark:bg-neutral-825 text-sm",
|
||||
"peer-placeholder-shown:text-base peer-placeholder-shown:text-gray-500 peer-placeholder-shown:top-1 peer-focus:-top-3 peer-focus:text-sky-600 peer-focus:text-sm",
|
||||
labelClasses,
|
||||
{
|
||||
"text-gray-500": accepted === undefined,
|
||||
"text-red-600": accepted === false,
|
||||
"text-green-600": accepted === true
|
||||
}
|
||||
)}
|
||||
style={{transitionProperty: "top, font-size, line-height",
|
||||
transitionTimingFunction: "cubic-bezier(0.4, 0, 0.2, 1)",
|
||||
transitionDuration: "150ms"
|
||||
}}
|
||||
>
|
||||
{placeholder}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
64
src/components/input/TextArea.tsx
Normal file
64
src/components/input/TextArea.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import clsx from "clsx";
|
||||
import { ComponentProps } from "react";
|
||||
|
||||
|
||||
export interface TextAreaProps extends ComponentProps<"textarea">{
|
||||
id: string;
|
||||
inputClasses?: string;
|
||||
labelClasses?: string;
|
||||
accepted?: boolean;
|
||||
}
|
||||
|
||||
export default function TextArea(props: TextAreaProps){
|
||||
const { id, placeholder, name, inputClasses, labelClasses, accepted } = props;
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
className="bg-inherit p-4 rounded-sm w-full md:flex md:justify-center"
|
||||
>
|
||||
<div
|
||||
className="relative bg-inherit w-full"
|
||||
>
|
||||
<textarea
|
||||
{...props}
|
||||
id={id}
|
||||
name={name}
|
||||
className={clsx(
|
||||
"peer bg-transparent w-full md:min-w-72 h-24 px-2 py-1 rounded-lg",
|
||||
"ring-2 ring-gray-500 focus:ring-sky-600 placeholder-transparent outline-hidden",
|
||||
inputClasses,
|
||||
{
|
||||
"ring-gray-500": accepted === undefined,
|
||||
"ring-red-600": accepted === false,
|
||||
"ring-green-600": accepted === true
|
||||
}
|
||||
)}
|
||||
style={{resize: "none"}}
|
||||
/>
|
||||
<label
|
||||
htmlFor={id}
|
||||
id={`${id}Label`}
|
||||
className={clsx(
|
||||
"absolute cursor-text left-0 -top-3 mx-1 px-1",
|
||||
"bg-white dark:bg-neutral-825 text-sm",
|
||||
"peer-placeholder-shown:text-base peer-placeholder-shown:text-gray-500 peer-placeholder-shown:top-1 peer-focus:-top-3 peer-focus:text-sky-600 peer-focus:text-sm",
|
||||
labelClasses,
|
||||
{
|
||||
"text-gray-500": accepted === undefined,
|
||||
"text-red-600": accepted === false,
|
||||
"text-green-600": accepted === true
|
||||
}
|
||||
)}
|
||||
style={{
|
||||
transitionProperty: "top, font-size, line-height",
|
||||
transitionTimingFunction: "cubic-bezier(0.4, 0, 0.2, 1)",
|
||||
transitionDuration: "150ms"
|
||||
}}
|
||||
>
|
||||
{placeholder}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
65
src/components/input/TextInput.tsx
Normal file
65
src/components/input/TextInput.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import clsx from "clsx";
|
||||
import { ComponentProps } from "react";
|
||||
|
||||
|
||||
|
||||
interface TextInputProps extends ComponentProps<"input">{
|
||||
id: string;
|
||||
inputClasses?: string;
|
||||
labelClasses?: string;
|
||||
accepted?: boolean;
|
||||
}
|
||||
|
||||
|
||||
export default function TextInput(props: TextInputProps){
|
||||
const { id, placeholder, name, inputClasses, labelClasses, accepted } = props;
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex flex-row justify-center w-full rounded-sm bg-inherit"
|
||||
>
|
||||
<div
|
||||
className="relative bg-inherit w-full"
|
||||
>
|
||||
<input
|
||||
{...props}
|
||||
type="text"
|
||||
className={clsx(
|
||||
"peer bg-transparent w-full min-w-72 px-2 py-1 rounded-lg",
|
||||
"ring-2 focus:ring-sky-600 placeholder-transparent outline-hidden",
|
||||
inputClasses,
|
||||
{
|
||||
"ring-gray-500": accepted === undefined,
|
||||
"ring-red-600": accepted === false,
|
||||
"ring-green-600": accepted === true
|
||||
}
|
||||
)}
|
||||
name={name}
|
||||
/>
|
||||
<label
|
||||
htmlFor={id}
|
||||
id={`${id}Label`}
|
||||
className={clsx(
|
||||
"absolute cursor-text left-0 -top-3 mx-1 px-1",
|
||||
"bg-white dark:bg-neutral-825 text-sm",
|
||||
"peer-placeholder-shown:text-base peer-placeholder-shown:text-gray-500 peer-placeholder-shown:top-1 peer-focus:-top-3 peer-focus:text-sky-600 peer-focus:text-sm",
|
||||
labelClasses,
|
||||
{
|
||||
"text-gray-500": accepted === undefined,
|
||||
"text-red-600": accepted === false,
|
||||
"text-green-600": accepted === true
|
||||
}
|
||||
)}
|
||||
style={{
|
||||
transitionProperty: "top, font-size, line-height",
|
||||
transitionTimingFunction: "cubic-bezier(0.4, 0, 0.2, 1)",
|
||||
transitionDuration: "150ms"
|
||||
}}
|
||||
>
|
||||
{placeholder}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
16
src/components/message/DangerMessage.tsx
Normal file
16
src/components/message/DangerMessage.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import clsx from "clsx";
|
||||
import { HTMLProps } from "react";
|
||||
import Message from "./Message";
|
||||
|
||||
|
||||
export default function DangerMessage(props: HTMLProps<HTMLDivElement>){
|
||||
return (
|
||||
<Message
|
||||
{...props}
|
||||
className={clsx(
|
||||
"bg-red-100 text-red-500",
|
||||
props.className
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
16
src/components/message/DarkMessage.tsx
Normal file
16
src/components/message/DarkMessage.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import clsx from "clsx";
|
||||
import { HTMLProps } from "react";
|
||||
import Message from "./Message";
|
||||
|
||||
|
||||
export default function DarkMessage(props: HTMLProps<HTMLDivElement>){
|
||||
return (
|
||||
<Message
|
||||
{...props}
|
||||
className={clsx(
|
||||
"bg-black text-white",
|
||||
props.className
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
16
src/components/message/InfoMessage.tsx
Normal file
16
src/components/message/InfoMessage.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import clsx from "clsx";
|
||||
import { HTMLProps } from "react";
|
||||
import Message from "./Message";
|
||||
|
||||
|
||||
export default function InfoMessage(props: HTMLProps<HTMLDivElement>){
|
||||
return (
|
||||
<Message
|
||||
{...props}
|
||||
className={clsx(
|
||||
"bg-cyan-100 text-sky-500",
|
||||
props.className
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
16
src/components/message/LightMessage.tsx
Normal file
16
src/components/message/LightMessage.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import clsx from "clsx";
|
||||
import { HTMLProps } from "react";
|
||||
import Message from "./Message";
|
||||
|
||||
|
||||
export default function LightMessage(props: HTMLProps<HTMLDivElement>){
|
||||
return (
|
||||
<Message
|
||||
{...props}
|
||||
className={clsx(
|
||||
"bg-white text-black",
|
||||
props.className
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
15
src/components/message/Message.tsx
Normal file
15
src/components/message/Message.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import clsx from "clsx";
|
||||
import { HTMLProps } from "react";
|
||||
|
||||
|
||||
export default function Message(props: HTMLProps<HTMLDivElement>){
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
className={clsx(
|
||||
"px-2 py-1 outline rounded-lg",
|
||||
props.className
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
16
src/components/message/MoltenMessage.tsx
Normal file
16
src/components/message/MoltenMessage.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import clsx from "clsx";
|
||||
import { HTMLProps } from "react";
|
||||
import Message from "./Message";
|
||||
|
||||
|
||||
export default function MoltenMessage(props: HTMLProps<HTMLDivElement>){
|
||||
return (
|
||||
<Message
|
||||
{...props}
|
||||
className={clsx(
|
||||
"bg-orange-100 text-orange-500",
|
||||
props.className
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
16
src/components/message/PrimaryMessage.tsx
Normal file
16
src/components/message/PrimaryMessage.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import clsx from "clsx";
|
||||
import { HTMLProps } from "react";
|
||||
import Message from "./Message";
|
||||
|
||||
|
||||
export default function PrimaryMessage(props: HTMLProps<HTMLDivElement>){
|
||||
return (
|
||||
<Message
|
||||
{...props}
|
||||
className={clsx(
|
||||
"bg-blue-200 text-blue-500",
|
||||
props.className
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
16
src/components/message/SecondaryMessage.tsx
Normal file
16
src/components/message/SecondaryMessage.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import clsx from "clsx";
|
||||
import { HTMLProps } from "react";
|
||||
import Message from "./Message";
|
||||
|
||||
|
||||
export default function SecondaryMessage(props: HTMLProps<HTMLDivElement>){
|
||||
return (
|
||||
<Message
|
||||
{...props}
|
||||
className={clsx(
|
||||
"bg-neutral-200 text-neutral-600",
|
||||
props.className
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
16
src/components/message/SuccessMessage.tsx
Normal file
16
src/components/message/SuccessMessage.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import clsx from "clsx";
|
||||
import { HTMLProps } from "react";
|
||||
import Message from "./Message";
|
||||
|
||||
|
||||
export default function SuccessMessage(props: HTMLProps<HTMLDivElement>){
|
||||
return (
|
||||
<Message
|
||||
{...props}
|
||||
className={clsx(
|
||||
"bg-green-200 text-green-600",
|
||||
props.className
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
16
src/components/message/TertiaryMessage.tsx
Normal file
16
src/components/message/TertiaryMessage.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import clsx from "clsx";
|
||||
import { HTMLProps } from "react";
|
||||
import Message from "./Message";
|
||||
|
||||
|
||||
export default function TertiaryMessage(props: HTMLProps<HTMLDivElement>){
|
||||
return (
|
||||
<Message
|
||||
{...props}
|
||||
className={clsx(
|
||||
"bg-purple-200 text-purple-500",
|
||||
props.className
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
16
src/components/message/WarningMessage.tsx
Normal file
16
src/components/message/WarningMessage.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import clsx from "clsx";
|
||||
import { HTMLProps } from "react";
|
||||
import Message from "./Message";
|
||||
|
||||
|
||||
export default function WarningMessage(props: HTMLProps<HTMLDivElement>){
|
||||
return (
|
||||
<Message
|
||||
{...props}
|
||||
className={clsx(
|
||||
"bg-yellow-100 text-yellow-600",
|
||||
props.className
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
54
src/components/modal/Modal.tsx
Normal file
54
src/components/modal/Modal.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import { ModalProps } from "@/interface/ModalInterfaces";
|
||||
import clsx from "clsx";
|
||||
import ModalBackground from "./ModalBackground";
|
||||
|
||||
|
||||
export default function Modal(props: ModalProps){
|
||||
const {
|
||||
display,
|
||||
backgroundType = "blur",
|
||||
backgroundClassName,
|
||||
top = false,
|
||||
close,
|
||||
className,
|
||||
children
|
||||
} = props;
|
||||
const divProps = {...props};
|
||||
delete divProps["children"];
|
||||
delete divProps["display"];
|
||||
delete divProps["backgroundType"];
|
||||
delete divProps["backgroundClassName"];
|
||||
delete divProps["close"];
|
||||
delete divProps["className"];
|
||||
delete divProps["top"];
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
display &&
|
||||
<>
|
||||
<ModalBackground
|
||||
backgroundType={backgroundType}
|
||||
className={backgroundClassName}
|
||||
close={close}
|
||||
/>
|
||||
<div
|
||||
{...divProps}
|
||||
className={clsx(
|
||||
"fixed left-1/2 -translate-x-1/2 max-w-(--breakpoint-sm) z-50",
|
||||
{
|
||||
"top-1/2 -translate-y-1/2": !top,
|
||||
"top-0": top
|
||||
},
|
||||
"flex flex-col rounded-lg max-h-full shadow-lg shadow-[#00000066]",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
43
src/components/modal/ModalBackground.tsx
Normal file
43
src/components/modal/ModalBackground.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { ModalBackgroundProps } from "@/interface/ModalInterfaces";
|
||||
import clsx from "clsx";
|
||||
|
||||
|
||||
export default function ModalBackground(props: ModalBackgroundProps){
|
||||
const {
|
||||
backgroundType = "blur",
|
||||
close,
|
||||
className
|
||||
} = props;
|
||||
const divProps = { ...props };
|
||||
delete divProps["backgroundType"];
|
||||
delete divProps["close"];
|
||||
delete divProps["className"];
|
||||
|
||||
|
||||
if(backgroundType === "none"){
|
||||
return (<></>);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
{...divProps}
|
||||
className={clsx(
|
||||
"fixed left-0 top-0 w-full h-full z-40",
|
||||
"flex flex-row justify-center items-center",
|
||||
className,
|
||||
{
|
||||
"bg-[#00000044]": backgroundType === "darken",
|
||||
"bg-[#FFFFFF44]": backgroundType === "lighten",
|
||||
"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"
|
||||
}
|
||||
)}
|
||||
onClick={close}
|
||||
/>
|
||||
);
|
||||
}
|
||||
23
src/components/modal/ModalBody.tsx
Normal file
23
src/components/modal/ModalBody.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import clsx from "clsx";
|
||||
import { HTMLProps } from "react";
|
||||
|
||||
|
||||
export default function ModalBody(props: HTMLProps<HTMLDivElement>){
|
||||
const {
|
||||
className,
|
||||
children
|
||||
} = props;
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
className={clsx(
|
||||
"flex flex-col items-center px-8 py-4 overflow-auto",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
27
src/components/modal/ModalFooter.tsx
Normal file
27
src/components/modal/ModalFooter.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import clsx from "clsx";
|
||||
import { HTMLProps } from "react";
|
||||
|
||||
|
||||
export default function ModalFooter(props: HTMLProps<HTMLDivElement>){
|
||||
const {
|
||||
className,
|
||||
children
|
||||
} = props;
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
className={clsx(
|
||||
"flex flex-row justify-center w-full rounded-b-lg",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className="flex flex-row justify-center items-center w-full mx-8 my-3"
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
51
src/components/modal/ModalHeader.tsx
Normal file
51
src/components/modal/ModalHeader.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
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){
|
||||
const {
|
||||
close,
|
||||
className,
|
||||
children
|
||||
} = props;
|
||||
const divProps = {...props};
|
||||
delete divProps["close"];
|
||||
delete divProps["className"];
|
||||
delete divProps["children"];
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
{...divProps}
|
||||
className={clsx(
|
||||
"flex flex-row justify-center w-full rounded-t-lg",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className="flex flex-row justify-center mx-8 my-3"
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
{
|
||||
close &&
|
||||
<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}
|
||||
/>
|
||||
</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 w-full"
|
||||
>
|
||||
{modalFooter}
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
32
src/components/nav/DarkModeToggle.tsx
Normal file
32
src/components/nav/DarkModeToggle.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { useTheme } from "@/providers/ThemeProvider";
|
||||
import { BsLightbulb, BsLightbulbFill } from "react-icons/bs";
|
||||
|
||||
|
||||
export default function DarkModeToggle(){
|
||||
const { theme, setTheme } = useTheme();
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
id="darkModeCheckbox"
|
||||
type="checkbox"
|
||||
className="peer hidden"
|
||||
onChange={() => setTheme(theme === "dark" ? "light" : "dark")}
|
||||
defaultChecked={theme === "dark"}
|
||||
/>
|
||||
<label
|
||||
htmlFor="darkModeCheckbox"
|
||||
className="block peer-checked:hidden"
|
||||
>
|
||||
<BsLightbulbFill/>
|
||||
</label>
|
||||
<label
|
||||
htmlFor="darkModeCheckbox"
|
||||
className="hidden peer-checked:block"
|
||||
>
|
||||
<BsLightbulb/>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
59
src/components/nav/NavBar.tsx
Normal file
59
src/components/nav/NavBar.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import raidBuilderIcon from "@/assets/raidBuilderIcon.svg";
|
||||
import clsx from "clsx";
|
||||
import { BsList } from "react-icons/bs";
|
||||
import { Link, Outlet } from "react-router";
|
||||
import DarkModeToggle from "./DarkModeToggle";
|
||||
import ProtectedNavLinks from "./ProtectedNavLinks";
|
||||
import PublicNavLinks from "./PublicNavLinks";
|
||||
|
||||
|
||||
export default function NavBar(){
|
||||
return (
|
||||
<>
|
||||
<nav
|
||||
className={clsx(
|
||||
"border-b-2 z-40",
|
||||
"bg-gray-700 border-gray-600 dark:bg-zinc-900 dark:border-neutral-850 text-white"
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className="navContents"
|
||||
>
|
||||
<Link
|
||||
to="/"
|
||||
className="flex items-center space-x-3 rtl:space-x-reverse"
|
||||
>
|
||||
<img
|
||||
src={raidBuilderIcon}
|
||||
alt="Raid Builder Logo"
|
||||
width={30}
|
||||
height={30}
|
||||
fetchPriority="high"
|
||||
/>
|
||||
<span
|
||||
className="self-center text-2xl font-semibold whitespace-nowrap"
|
||||
>
|
||||
Raid Builder
|
||||
</span>
|
||||
</Link>
|
||||
<div
|
||||
className="peer md:hidden text-3xl"
|
||||
>
|
||||
<BsList/>
|
||||
</div>
|
||||
<div
|
||||
className={clsx(
|
||||
"relative top-0 left-0 flex flex-row items-center rounded-lg space-x-4",
|
||||
"bg-gray-700 dark:bg-zinc-900"
|
||||
)}
|
||||
>
|
||||
<PublicNavLinks/>
|
||||
<ProtectedNavLinks/>
|
||||
<DarkModeToggle/>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<Outlet/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
62
src/components/nav/ProtectedNavLinks.tsx
Normal file
62
src/components/nav/ProtectedNavLinks.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import { useAuth } from "@/providers/AuthProvider";
|
||||
import { isSiteAdmin } from "@/util/PermissionUtil";
|
||||
import { BsFillPersonFill } from "react-icons/bs";
|
||||
import { NavLink } from "react-router";
|
||||
|
||||
|
||||
export default function ProtectedNavLinks(){
|
||||
const { jwt, accountPermissions } = useAuth();
|
||||
|
||||
|
||||
if(!jwt){
|
||||
return <></>;
|
||||
}
|
||||
|
||||
|
||||
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"
|
||||
});
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
protectedLinks.map((link) => (
|
||||
<NavLink
|
||||
key={link.name}
|
||||
to={link.path}
|
||||
>
|
||||
{link.name}
|
||||
</NavLink>
|
||||
))
|
||||
}
|
||||
{
|
||||
jwt &&
|
||||
<NavLink
|
||||
to="/account"
|
||||
>
|
||||
<BsFillPersonFill
|
||||
size={22}
|
||||
/>
|
||||
</NavLink>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
46
src/components/nav/PublicNavLinks.tsx
Normal file
46
src/components/nav/PublicNavLinks.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { useAuth } from "@/providers/AuthProvider";
|
||||
import { NavLink } from "react-router";
|
||||
|
||||
|
||||
const publicLinks = [
|
||||
{
|
||||
name: "Home",
|
||||
path: "/"
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
export default function PublicNavLinks(){
|
||||
const { jwt } = useAuth();
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
publicLinks.map((link) => (
|
||||
<NavLink
|
||||
key={link.name}
|
||||
to={link.path}
|
||||
>
|
||||
{link.name}
|
||||
</NavLink>
|
||||
))
|
||||
}
|
||||
{
|
||||
!jwt &&
|
||||
<>
|
||||
<NavLink
|
||||
to="/login"
|
||||
>
|
||||
Login
|
||||
</NavLink>
|
||||
<NavLink
|
||||
to="/signup"
|
||||
>
|
||||
Signup
|
||||
</NavLink>
|
||||
</>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
65
src/components/pagination/Pagination.tsx
Normal file
65
src/components/pagination/Pagination.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import { generatePagination } from "@/util/PaginationUtil";
|
||||
import { BsChevronLeft, BsChevronRight } from "react-icons/bs";
|
||||
import PrimaryButton from "../button/PrimaryButton";
|
||||
|
||||
export default function Pagination({
|
||||
currentPage,
|
||||
totalPages,
|
||||
onChange
|
||||
}:{
|
||||
currentPage: number;
|
||||
totalPages: number;
|
||||
onChange: (page: number) => void;
|
||||
}){
|
||||
const pages = generatePagination(currentPage, totalPages);
|
||||
|
||||
|
||||
return(
|
||||
<div
|
||||
className="flex flex-row items-center justify-center w-full text-xl text-white"
|
||||
>
|
||||
<div
|
||||
className="mr-8"
|
||||
>
|
||||
<PrimaryButton
|
||||
shape="square"
|
||||
className="w-9 h-9"
|
||||
disabled={currentPage <= 1}
|
||||
onClick={() => onChange(currentPage - 1)}
|
||||
>
|
||||
<BsChevronLeft/>
|
||||
</PrimaryButton>
|
||||
</div>
|
||||
<div
|
||||
className="flex flex-row items-center justify-center gap-4"
|
||||
>
|
||||
{
|
||||
pages.map((page, index) => (
|
||||
<PrimaryButton
|
||||
key={index}
|
||||
size="sm"
|
||||
shape="square"
|
||||
className="w-9 h-9"
|
||||
disabled={(page === "...") || (page === currentPage)}
|
||||
onClick={() => onChange(page as number)}
|
||||
>
|
||||
{page}
|
||||
</PrimaryButton>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<div
|
||||
className="ml-8"
|
||||
>
|
||||
<PrimaryButton
|
||||
shape="square"
|
||||
className="w-9 h-9"
|
||||
disabled={currentPage >= totalPages}
|
||||
onClick={() => onChange(currentPage + 1)}
|
||||
>
|
||||
<BsChevronRight/>
|
||||
</PrimaryButton>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
49
src/components/personCharacter/PersonCharacterDisplay.tsx
Normal file
49
src/components/personCharacter/PersonCharacterDisplay.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import { useGetPersonCharactersByPersonId } from "@/hooks/PersonCharacterHooks";
|
||||
import DangerMessage from "../message/DangerMessage";
|
||||
|
||||
|
||||
export default function PersonCharacterDisplay({
|
||||
personId,
|
||||
raidGroupId
|
||||
}:{
|
||||
personId: string;
|
||||
raidGroupId: string;
|
||||
}){
|
||||
const personCharacterQuery = useGetPersonCharactersByPersonId(personId, raidGroupId);
|
||||
|
||||
|
||||
if(personCharacterQuery.status === "pending"){
|
||||
return (<div>Loading...</div>);
|
||||
}
|
||||
else if(personCharacterQuery.status === "error"){
|
||||
return (<DangerMessage>Error loading characters: {personCharacterQuery.error.message}</DangerMessage>);
|
||||
}
|
||||
else{
|
||||
return (
|
||||
<div
|
||||
className="flex flex-row flex-wrap items-center justify-center"
|
||||
>
|
||||
{
|
||||
personCharacterQuery.data.map((personCharacter) => {
|
||||
return (
|
||||
<div
|
||||
key={personCharacter.personCharacterId}
|
||||
className="flex flex-row flex-nowrap items-center justify-center mr-8"
|
||||
>
|
||||
<img
|
||||
className="m-auto max-h-6 max-w-6 mr-2"
|
||||
src={`${import.meta.env.VITE_ICON_URL}/gameClass/id/${personCharacter.gameClassId}`}
|
||||
/>
|
||||
{personCharacter.characterName}
|
||||
</div>
|
||||
);
|
||||
})
|
||||
}
|
||||
{
|
||||
personCharacterQuery.data.length === 0 &&
|
||||
<div>No characters</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
76
src/components/personCharacter/PersonCharacterSelector.tsx
Normal file
76
src/components/personCharacter/PersonCharacterSelector.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import { PersonCharacter } from "@/interface/PersonCharacter";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
|
||||
export default function PersonCharacterSelector({
|
||||
personCharacters,
|
||||
selectedCharacterId,
|
||||
onChange
|
||||
}:{
|
||||
personCharacters: PersonCharacter[];
|
||||
selectedCharacterId?: string;
|
||||
onChange?: (characterId: string | undefined) => void;
|
||||
}){
|
||||
const [ currentlySelectedCharacterId, setCurrentlySelectedCharacterId ] = useState(selectedCharacterId);
|
||||
const selectorId = crypto.randomUUID().replaceAll("-", "");
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentlySelectedCharacterId(selectedCharacterId);
|
||||
}, [ selectedCharacterId ]);
|
||||
|
||||
|
||||
const updateInput = (newCharacterId?: string) => {
|
||||
if(newCharacterId === currentlySelectedCharacterId){
|
||||
setCurrentlySelectedCharacterId(undefined);
|
||||
onChange?.(undefined);
|
||||
}
|
||||
else{
|
||||
setCurrentlySelectedCharacterId(newCharacterId);
|
||||
onChange?.(newCharacterId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
className="grid grid-cols-3 grid-rows-3 gap-x-8 gap-y-4"
|
||||
style={{flex: "0 0 33.333333333%"}}
|
||||
>
|
||||
{
|
||||
personCharacters.map((ch) => (
|
||||
<div
|
||||
key={ch.personCharacterId}
|
||||
className="flex flex-row flex-nowrap"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
id={`personCharacter${ch.personCharacterId}Selector${selectorId}`}
|
||||
name="character"
|
||||
value={ch.personCharacterId}
|
||||
checked={ch.personCharacterId === currentlySelectedCharacterId}
|
||||
onChange={() => {}}
|
||||
onClick={() => updateInput(ch.personCharacterId)}
|
||||
/>
|
||||
<label
|
||||
className="ml-2"
|
||||
htmlFor={`personCharacter${ch.personCharacterId}Selector${selectorId}`}
|
||||
>
|
||||
<div
|
||||
className="flex flex-row flex-nowrap text-nowrap"
|
||||
>
|
||||
{
|
||||
<img
|
||||
className="mr-2 max-h-8 max-w-8"
|
||||
src={`${import.meta.env.VITE_ICON_URL}/gameClass/id/${ch.gameClassId}`}
|
||||
/>
|
||||
}
|
||||
{ch.characterName}
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
61
src/components/personCharacter/RatingSelector.tsx
Normal file
61
src/components/personCharacter/RatingSelector.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import NumberInput from "../input/NumberInput";
|
||||
|
||||
export default function RatingSelector({
|
||||
rating,
|
||||
onChange
|
||||
}:{
|
||||
rating?: number;
|
||||
onChange?: (rating?: number) => void;
|
||||
}){
|
||||
const ratings = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
const selectorId = crypto.randomUUID().replaceAll("-", "");
|
||||
const [ currentRating, setCurrentRating ] = useState(rating);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentRating(rating);
|
||||
}, [ rating, setCurrentRating ]);
|
||||
|
||||
useEffect(() => {
|
||||
onChange?.(currentRating);
|
||||
}, [ currentRating, onChange ]);
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<NumberInput
|
||||
id={`characterRatingSelector${selectorId}`}
|
||||
label="Rating"
|
||||
value={currentRating}
|
||||
onChange={(value) => setCurrentRating(value)}
|
||||
min={0}
|
||||
max={10}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label>
|
||||
<select
|
||||
onChange={(e) => setCurrentRating(parseInt(e.target.value))}
|
||||
value={currentRating}
|
||||
>
|
||||
<option value={undefined}></option>
|
||||
{
|
||||
ratings.map((rating) => (
|
||||
<option
|
||||
key={rating}
|
||||
value={rating}
|
||||
>
|
||||
{rating}
|
||||
</option>
|
||||
))
|
||||
}
|
||||
</select>
|
||||
<span>Character Rating</span>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
41
src/components/raidGroup/RaidGroupPermissionSelector.tsx
Normal file
41
src/components/raidGroup/RaidGroupPermissionSelector.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import { RaidGroupPermissionType } from "@/interface/RaidGroup";
|
||||
|
||||
|
||||
export default function RaidGroupPermissionSelector({
|
||||
value,
|
||||
onChange
|
||||
}:{
|
||||
value?: RaidGroupPermissionType;
|
||||
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
}){
|
||||
const modalId = crypto.randomUUID().replaceAll("-", "");
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex flex-row flex-wrap justify-start gap-x-4"
|
||||
>
|
||||
{
|
||||
Object.keys(RaidGroupPermissionType).map((permissionType) => (
|
||||
<label
|
||||
key={permissionType}
|
||||
className="whitespace-nowrap"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name={`raidGroupPermissionTypeSelector${modalId}`}
|
||||
value={permissionType}
|
||||
onChange={onChange}
|
||||
checked={value === permissionType as RaidGroupPermissionType}
|
||||
/>
|
||||
<span
|
||||
className="ml-1"
|
||||
>
|
||||
{permissionType}
|
||||
</span>
|
||||
</label>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
126
src/components/tab/TabGroup.tsx
Normal file
126
src/components/tab/TabGroup.tsx
Normal file
@@ -0,0 +1,126 @@
|
||||
import clsx from "clsx";
|
||||
import { HTMLProps, useState } from "react";
|
||||
|
||||
|
||||
export interface Tab {
|
||||
tabId?: string;
|
||||
tabHeader: React.ReactNode;
|
||||
headerClasses?: string;
|
||||
tabContent: React.ReactNode;
|
||||
contentClasses?: string;
|
||||
active?: boolean;
|
||||
onTabClick?: () => void;
|
||||
}
|
||||
|
||||
export interface TabGroupProps extends HTMLProps<HTMLDivElement>{
|
||||
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
|
||||
{...divProps}
|
||||
className={clsx(
|
||||
className,
|
||||
"flex flex-col w-full"
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className="flex flex-row items-center justify-start"
|
||||
>
|
||||
{
|
||||
tabs.map((tab, index) => (
|
||||
<TabHeader
|
||||
id={tab.tabId}
|
||||
key={index}
|
||||
tab={tab}
|
||||
active={activeTab === index}
|
||||
onClick={() => { setActiveTab(index); tab.onTabClick?.(); }}
|
||||
/>
|
||||
))
|
||||
}
|
||||
<div
|
||||
className="w-full h-full py-2 border-b border-(--text-color)"
|
||||
>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="flex flex-col items-center justify-center mt-8"
|
||||
>
|
||||
{
|
||||
tabs.map((tab, index) => (
|
||||
<TabContent
|
||||
key={index}
|
||||
tab={tab}
|
||||
active={activeTab === index}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function TabHeader({
|
||||
id,
|
||||
tab,
|
||||
onClick,
|
||||
active
|
||||
}:{
|
||||
id?: string;
|
||||
tab: Tab;
|
||||
onClick: () => void;
|
||||
active: boolean;
|
||||
}){
|
||||
return (
|
||||
<div
|
||||
id={id}
|
||||
className={clsx(
|
||||
tab.headerClasses,
|
||||
"px-4 py-2 rounded-t-lg cursor-pointer whitespace-nowrap",
|
||||
"border-x border-t border-(--text-color)",
|
||||
{
|
||||
"border-b border-(--text-color)": !active
|
||||
}
|
||||
)}
|
||||
onClick={onClick}
|
||||
>
|
||||
{tab.tabHeader}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TabContent({
|
||||
tab,
|
||||
active
|
||||
}:{
|
||||
tab: Tab;
|
||||
active: boolean;
|
||||
}){
|
||||
if(!active){
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"w-full",
|
||||
tab.contentClasses
|
||||
)}
|
||||
>
|
||||
{tab.tabContent}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
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 h-14",
|
||||
{
|
||||
"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>
|
||||
);
|
||||
}
|
||||
250
src/hooks/AccountHooks.ts
Normal file
250
src/hooks/AccountHooks.ts
Normal file
@@ -0,0 +1,250 @@
|
||||
import { Account } from "@/interface/Account";
|
||||
import { AccountTutorialStatus } from "@/interface/AccountTutorialStatus";
|
||||
import { Counter } from "@/interface/Counters";
|
||||
import { RaidGroupPermissionType } from "@/interface/RaidGroup";
|
||||
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("searchTerm", searchTerm ?? "");
|
||||
}
|
||||
|
||||
const response = await api.get(`/account?${params}`);
|
||||
|
||||
return response.data as Account[];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useGetAccountsByRaidGroup(raidGroupId: string, page: number, pageSize: number, searchTerm?: string){
|
||||
return useQuery({
|
||||
queryKey: ["accounts", "raidGroup", raidGroupId, {page, pageSize, searchTerm}],
|
||||
queryFn: async () => {
|
||||
const params = new URLSearchParams();
|
||||
params.append("page", page.toString());
|
||||
params.append("pageSize", pageSize.toString());
|
||||
if(searchTerm){
|
||||
params.append("searchTerm", searchTerm ?? "");
|
||||
}
|
||||
|
||||
const response = await api.get(`/account/raidGroup/${raidGroupId}?${params}`);
|
||||
|
||||
return response.data as Account[];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useGetRaidGroupPermissionsForAccount(raidGroupId?: string, accountId?: string){
|
||||
return useQuery({
|
||||
queryKey: ["accounts", "raidGroup", raidGroupId, "account", accountId],
|
||||
queryFn: async () => {
|
||||
const response = await api.get(`/account/${accountId}/raidGroup/${raidGroupId}/permission`);
|
||||
|
||||
return (response.data as {permission: RaidGroupPermissionType;}).permission;
|
||||
},
|
||||
enabled: !!raidGroupId && !!accountId
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
export function useGetAccountsCount(searchTerm?: string){
|
||||
const searchParams = new URLSearchParams();
|
||||
if(searchTerm){
|
||||
searchParams.append("searchTerm", searchTerm);
|
||||
}
|
||||
|
||||
|
||||
return useQuery({
|
||||
queryKey: [ "accounts", "count", searchTerm],
|
||||
queryFn: async () => {
|
||||
const response = await api.get(`/account/count?${searchParams}`);
|
||||
|
||||
return (response.data as Counter).count;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useGetAccountsByRaidGroupCount(raidGroupId: string, searchTerm?: string){
|
||||
const searchParams = new URLSearchParams();
|
||||
if(searchTerm){
|
||||
searchParams.append("searchTerm", searchTerm);
|
||||
}
|
||||
|
||||
|
||||
return useQuery({
|
||||
queryKey: [ "accounts", "raidGroup", raidGroupId, "count", searchTerm],
|
||||
queryFn: async () => {
|
||||
const response = await api.get(`/account/raidGroup/${raidGroupId}/count?${searchParams}`);
|
||||
|
||||
return (response.data as Counter).count;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useGetTutorialsStatus(accountId: string | null){
|
||||
return useQuery({
|
||||
queryKey: ["tutorials", "account", accountId],
|
||||
queryFn: async () => {
|
||||
const response = await api.get(`/account/tutorial`);
|
||||
|
||||
return response.data as AccountTutorialStatus;
|
||||
},
|
||||
enabled: !!accountId
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateTutorialsStatus(){
|
||||
return useMutation({
|
||||
mutationKey: ["tutorials", "accounts"],
|
||||
mutationFn: async (tutorials: AccountTutorialStatus) => {
|
||||
await api.put(`/account/tutorial`, tutorials);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdatePassword(){
|
||||
return useMutation({
|
||||
mutationKey: ["updatePassword"],
|
||||
mutationFn: async ({currentPassword, newPassword}:{currentPassword: string; newPassword: string;}) => {
|
||||
await api.post("/auth/resetPassword", {
|
||||
currentPassword,
|
||||
newPassword
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function useForcePasswordReset(accountId: string){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["forcePasswordReset", accountId],
|
||||
mutationFn: async () => {
|
||||
await api.put(`/account/${accountId}/forcePasswordReset`);
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["accounts"] });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useResetPassword(accountId: string){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["resetPassword", accountId],
|
||||
mutationFn: async (password: string) => {
|
||||
await api.put(`/account/${accountId}/resetPassword`, {
|
||||
password
|
||||
});
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["accounts"] });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useRevokeRefreshToken(accountId: string){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["revokeRefreshToken", accountId],
|
||||
mutationFn: async () => {
|
||||
await api.put(`/account/${accountId}/revokeRefreshToken`);
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["accounts"] });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateAccount(){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["createAccount"],
|
||||
mutationFn: async (account: Account) => {
|
||||
await api.post("/account", account);
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["accounts"] });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateAccount(){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["updateAccount"],
|
||||
mutationFn: async (account: Account) => {
|
||||
await api.put(`/account/${account.accountId}`, account);
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["accounts"] });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateRaidGroupPermissionsForAccount(raidGroupId?: string, accountId?: string){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["updateRaidGroupPermissionsForAccount", raidGroupId, accountId],
|
||||
mutationFn: async (permission: RaidGroupPermissionType) => {
|
||||
await api.put(`/account/${accountId}/raidGroup/${raidGroupId}/permission`, {
|
||||
permission
|
||||
});
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["accounts"] });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteAccount(accountId: string){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["deleteAccount", accountId],
|
||||
mutationFn: async () => {
|
||||
await api.delete(`/account/${accountId}`);
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["accounts"] });
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
export function useRemoveAccountFromRaidGroup(raidGroupId?: string, accountId?: string){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["removeAccountFromRaidGroup", raidGroupId, accountId],
|
||||
mutationFn: async () => {
|
||||
await api.delete(`/account/${accountId}/raidGroup/${raidGroupId}/permission`);
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["accounts"] });
|
||||
}
|
||||
});
|
||||
}
|
||||
45
src/hooks/AuthHooks.ts
Normal file
45
src/hooks/AuthHooks.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { Account } from "@/interface/Account";
|
||||
import api from "@/util/AxiosUtil";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
|
||||
|
||||
export function useSignup(){
|
||||
return useMutation({
|
||||
mutationKey: ["signup"],
|
||||
mutationFn: async (account: Account) => {
|
||||
await api.post("/auth/signup", account);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export function useConfirm(){
|
||||
return useMutation({
|
||||
mutationKey: ["confirm"],
|
||||
mutationFn: async (confirmToken: string) => {
|
||||
await api.post(`/auth/confirm/${confirmToken}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useForgotPassword(){
|
||||
return useMutation({
|
||||
mutationKey: ["forgotPassword"],
|
||||
mutationFn: async (username: string) => {
|
||||
const params = new URLSearchParams();
|
||||
params.append("username", username);
|
||||
|
||||
|
||||
await api.post(`/auth/forgot?${params}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useForgotResetPassword(forgotToken: string){
|
||||
return useMutation({
|
||||
mutationKey: ["forgotResetPassword"],
|
||||
mutationFn: async (password: string) => {
|
||||
await api.post(`/auth/forgot/${forgotToken}`, {password});
|
||||
}
|
||||
});
|
||||
}
|
||||
133
src/hooks/CalendarHooks.ts
Normal file
133
src/hooks/CalendarHooks.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import { CalendarEvent } from "@/interface/Calendar";
|
||||
import api from "@/util/AxiosUtil";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
|
||||
export function useGetGameCalendar(gameId: string){
|
||||
return useQuery({
|
||||
queryKey: ["gameCalendar", gameId],
|
||||
queryFn: async () => {
|
||||
const response = await api.get(`/calendar/game/${gameId}`);
|
||||
|
||||
return response.data as CalendarEvent[];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useGetRaidGroupCalendar(raidGroupId: string){
|
||||
return useQuery({
|
||||
queryKey: ["raidGroupCalendar", raidGroupId],
|
||||
queryFn: async () => {
|
||||
const response = await api.get(`/calendar/raidGroup/${raidGroupId}`);
|
||||
|
||||
return response.data as CalendarEvent[];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export function useCreateGameCalendarEvent(gameId: string){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (calendarEvent: CalendarEvent) => {
|
||||
await api.post(`/calendar/game/${gameId}`, {...calendarEvent, gameCalendarEventId: calendarEvent.calendarEventId, calendarEventId: undefined});
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["gameCalendar"]})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateGameCalendarEvent(gameId: string){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (calendarEvent: CalendarEvent) => {
|
||||
await api.put(`/calendar/game/${gameId}`,
|
||||
{
|
||||
...calendarEvent,
|
||||
gameCalendarEventId: calendarEvent.calendarEventId,
|
||||
calendarEventId: undefined
|
||||
});
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["gameCalendar"]})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteGameCalendarEvent(gameId: string){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (calendarEvent: CalendarEvent) => {
|
||||
await api.delete(`/calendar/game/${gameId}/${calendarEvent.calendarEventId}`);
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["gameCalendar"]})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateRaidGroupCalendarEvent(raidGroupId: string){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (calendarEvent: CalendarEvent) => {
|
||||
await api.post(`/calendar/raidGroup/${raidGroupId}`,
|
||||
{
|
||||
...calendarEvent,
|
||||
raidGroupCalendarEventId: calendarEvent.calendarEventId,
|
||||
calendarEventId: undefined
|
||||
});
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["raidGroupCalendar"]})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateRaidGroupCalendarEvent(raidGroupId: string){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (calendarEvent: CalendarEvent) => {
|
||||
await api.put(`/calendar/raidGroup/${raidGroupId}`, {...calendarEvent, raidGroupCalendarEventId: calendarEvent.calendarEventId, calendarEventId: undefined});
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["raidGroupCalendar"]})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteRaidGroupCalendarEvent(raidGroupId: string){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (calendarEvent: CalendarEvent) => {
|
||||
await api.delete(`/calendar/raidGroup/${raidGroupId}/${calendarEvent.calendarEventId}`);
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["raidGroupCalendar"]})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useGetRaidInstanceCalendarEvents(raidGroupId?: string){
|
||||
return useQuery({
|
||||
queryKey: ["raidInstanceCalendarEvents", raidGroupId],
|
||||
queryFn: async () => {
|
||||
const response = await api.get(`/calendar/raidGroup/${raidGroupId}/raidInstance`);
|
||||
|
||||
return response.data as CalendarEvent[];
|
||||
},
|
||||
enabled: !!raidGroupId && raidGroupId !== ""
|
||||
});
|
||||
}
|
||||
112
src/hooks/ClassGroupHooks.ts
Normal file
112
src/hooks/ClassGroupHooks.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { ClassGroup } from "@/interface/ClassGroup";
|
||||
import { Counter } from "@/interface/Counters";
|
||||
import api from "@/util/AxiosUtil";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
|
||||
export function useGetClassGroups(raidGroupId: string, page: number, pageSize: number, searchTerm?: string){
|
||||
return useQuery({
|
||||
queryKey: ["classGroups", raidGroupId, { page, pageSize, searchTerm }],
|
||||
queryFn: async () => {
|
||||
const params = new URLSearchParams();
|
||||
params.append("page", page.toString());
|
||||
params.append("pageSize", pageSize.toString());
|
||||
if(searchTerm){
|
||||
params.append("searchTerm", searchTerm);
|
||||
}
|
||||
|
||||
const response = await api.get(`/raidGroup/${raidGroupId}/classGroup?${params}`);
|
||||
|
||||
return response.data as ClassGroup[];
|
||||
},
|
||||
enabled: !!raidGroupId
|
||||
});
|
||||
}
|
||||
|
||||
export function useGetClassGroupsCount(raidGroupId: string, searchTerm?: string){
|
||||
const searchParams = new URLSearchParams();
|
||||
if(searchTerm){
|
||||
searchParams.append("searchTerm", searchTerm);
|
||||
}
|
||||
|
||||
|
||||
return useQuery({
|
||||
queryKey: ["classGroups", "count", searchTerm],
|
||||
queryFn: async () => {
|
||||
const response = await api.get(`/raidGroup/${raidGroupId}/classGroup/count?${searchParams}`);
|
||||
|
||||
return (response.data as Counter).count;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useGetClassGroupsByRaidLayout(raidGroupId: string, raidLayoutId: string | undefined){
|
||||
return useQuery({
|
||||
queryKey: ["classGroups", "raidLayout", raidLayoutId],
|
||||
queryFn: async () => {
|
||||
const response = await api.get(`/raidGroup/${raidGroupId}/classGroup/raidLayout/${raidLayoutId}`);
|
||||
|
||||
return response.data as (ClassGroup | null)[];
|
||||
},
|
||||
enabled: !!raidGroupId && !!raidLayoutId
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export function useCreateClassGroup(raidGroupId: string){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["createClassGroup"],
|
||||
mutationFn: async ({classGroupName, gameClassIds}:{classGroupName: string; gameClassIds: string[];}) => {
|
||||
await api.post(`/raidGroup/${raidGroupId}/classGroup`,
|
||||
{
|
||||
classGroup: {
|
||||
classGroupName: classGroupName,
|
||||
raidGroupId: raidGroupId
|
||||
},
|
||||
gameClassIds
|
||||
});
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({queryKey: ["classGroups"]});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateClassGroup(raidGroupId: string){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["updateClassGroup"],
|
||||
mutationFn: async ({classGroup, gameClassIds}:{classGroup: ClassGroup; gameClassIds: string[];}) => {
|
||||
await api.put(`/raidGroup/${raidGroupId}/classGroup/${classGroup.classGroupId}`,
|
||||
{
|
||||
classGroup,
|
||||
gameClassIds
|
||||
}
|
||||
);
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({queryKey: ["gameClasses", "classGroups"]});
|
||||
void queryClient.invalidateQueries({queryKey: ["classGroups"]});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteClassGroup(raidGroupId: string, classGroupId: string){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["deleteClassGroup", classGroupId, raidGroupId],
|
||||
mutationFn: async () => {
|
||||
await api.delete(`/raidGroup/${raidGroupId}/classGroup/${classGroupId}`);
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({queryKey: ["classGroups"]});
|
||||
}
|
||||
});
|
||||
}
|
||||
131
src/hooks/GameClassHooks.ts
Normal file
131
src/hooks/GameClassHooks.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import { Counter } from "@/interface/Counters";
|
||||
import { GameClass } from "@/interface/GameClass";
|
||||
import api from "@/util/AxiosUtil";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
|
||||
export function useGetGameClass(gameClassId: string){
|
||||
return useQuery({
|
||||
queryKey: ["gameClasses", gameClassId],
|
||||
queryFn: async () => {
|
||||
const response = await api.get(`/gameClass/${gameClassId}`);
|
||||
|
||||
return response.data as GameClass;
|
||||
},
|
||||
enabled: !!gameClassId
|
||||
})
|
||||
}
|
||||
|
||||
export function useGetGameClasses(gameId: string, page: number, pageSize: number, searchTerm?: string){
|
||||
return useQuery({
|
||||
queryKey: ["gameClasses", gameId, { page, pageSize, searchTerm }],
|
||||
queryFn: async () => {
|
||||
const params = new URLSearchParams();
|
||||
params.append("page", page.toString());
|
||||
params.append("pageSize", pageSize.toString());
|
||||
if(searchTerm){
|
||||
params.append("searchTerm", searchTerm);
|
||||
}
|
||||
|
||||
const response = await api.get(`/gameClass/game/${gameId}?${params}`);
|
||||
|
||||
return response.data as GameClass[];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useGetGameClassesByClassGroup(classGroupId?: string){
|
||||
return useQuery({
|
||||
queryKey: ["gameClasses", "classGroups", classGroupId],
|
||||
queryFn: async () => {
|
||||
const response = await api.get(`/gameClass/classGroup/${classGroupId}`);
|
||||
|
||||
return response.data as GameClass[];
|
||||
},
|
||||
enabled: !!classGroupId
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
export function useGetGameClassesCount(gameId: string, searchTerm?: string){
|
||||
const searchParams = new URLSearchParams();
|
||||
if(searchTerm){
|
||||
searchParams.append("searchTerm", searchTerm);
|
||||
}
|
||||
|
||||
|
||||
return useQuery({
|
||||
queryKey: ["gameClasses", gameId, "count", searchTerm],
|
||||
queryFn: async () => {
|
||||
const response = await api.get(`/gameClass/game/${gameId}/count?${searchParams}`);
|
||||
|
||||
return (response.data as Counter).count;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export function useCreateGameClass(){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["createGameClass"],
|
||||
mutationFn: async ({gameId, gameClassName, iconFile}:{gameId: string; gameClassName: string; iconFile: File | null}) => {
|
||||
const formData = new FormData();
|
||||
if(iconFile){
|
||||
formData.append("iconFile", iconFile);
|
||||
}
|
||||
formData.append("gameClassName", gameClassName);
|
||||
formData.append("gameId", gameId);
|
||||
|
||||
await api.post(
|
||||
`/gameClass/game/${gameId}`,
|
||||
formData
|
||||
);
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["gameClasses"] });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateGameClass(){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["updateGameClass"],
|
||||
mutationFn: async ({gameClass, iconFile}:{gameClass: GameClass; iconFile: File | null}) => {
|
||||
const formData = new FormData();
|
||||
if(iconFile){
|
||||
formData.append("iconFile", iconFile);
|
||||
}
|
||||
formData.append("gameClassName", gameClass.gameClassName);
|
||||
formData.append("gameId", gameClass.gameId);
|
||||
if(gameClass.gameClassIcon){
|
||||
formData.append("gameClassIcon", gameClass.gameClassIcon);
|
||||
}
|
||||
|
||||
await api.put(`/gameClass/${gameClass.gameClassId}/game/${gameClass.gameId}`, formData);
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["gameClasses"] });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteGameClass(){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["deleteGameClass"],
|
||||
mutationFn: async (gameClass: GameClass) => {
|
||||
await api.delete(`/gameClass/${gameClass.gameClassId}/game/${gameClass.gameId}`);
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["gameClasses"] });
|
||||
}
|
||||
});
|
||||
}
|
||||
115
src/hooks/GameHooks.ts
Normal file
115
src/hooks/GameHooks.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { Counter } from "@/interface/Counters";
|
||||
import { Game } from "@/interface/Game";
|
||||
import api from "@/util/AxiosUtil";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
|
||||
export function useGetGame(gameId: string, disabled: boolean){
|
||||
return useQuery({
|
||||
queryKey: ["games", gameId],
|
||||
queryFn: async () => {
|
||||
const response = await api.get(`/game/${gameId}`);
|
||||
|
||||
return (response.data as Game)?.gameId ? response.data as Game : undefined;
|
||||
},
|
||||
enabled: !disabled
|
||||
});
|
||||
}
|
||||
|
||||
export function useGetGames(page: number, pageSize: number, searchTerm?: string){
|
||||
return useQuery({
|
||||
queryKey: ["games", { page, pageSize, searchTerm }],
|
||||
queryFn: async () => {
|
||||
const params = new URLSearchParams();
|
||||
params.append("page", page.toString());
|
||||
params.append("pageSize", pageSize.toString());
|
||||
if(searchTerm){
|
||||
params.append("searchTerm", searchTerm);
|
||||
}
|
||||
|
||||
const response = await api.get(`/game?${params}`);
|
||||
|
||||
return response.data as Game[];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useGetGamesCount(searchTerm?: string){
|
||||
const searchParams = new URLSearchParams();
|
||||
if(searchTerm){
|
||||
searchParams.append("searchTerm", searchTerm);
|
||||
}
|
||||
|
||||
|
||||
return useQuery({
|
||||
queryKey: ["games", "count", searchTerm],
|
||||
queryFn: async () => {
|
||||
const response = await api.get(`/game/count?${searchParams}`);
|
||||
|
||||
return (response.data as Counter).count;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateGame(){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["createGame"],
|
||||
mutationFn: async ({gameName, iconFile}:{gameName: string; iconFile: File | null;}) => {
|
||||
const formData = new FormData();
|
||||
if(iconFile){
|
||||
formData.append("iconFile", iconFile);
|
||||
}
|
||||
formData.append("gameName", gameName);
|
||||
|
||||
await api.post(
|
||||
"/game",
|
||||
formData
|
||||
);
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["games"] });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateGame(){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["updateGame"],
|
||||
mutationFn: async ({game, iconFile}:{game: Game, iconFile: File | null}) => {
|
||||
const formData = new FormData();
|
||||
if(iconFile){
|
||||
formData.append("iconFile", iconFile);
|
||||
}
|
||||
formData.append("gameName", game.gameName);
|
||||
if(game.gameIcon){
|
||||
formData.append("gameIcon", game.gameIcon);
|
||||
}
|
||||
|
||||
await api.put(`/game/${game.gameId}`, formData);
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["games"] });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteGame(){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["deleteGame"],
|
||||
mutationFn: async (gameId: string) => {
|
||||
await api.delete(`/game/${gameId}`);
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["games"] });
|
||||
}
|
||||
});
|
||||
}
|
||||
108
src/hooks/PersonCharacterHooks.ts
Normal file
108
src/hooks/PersonCharacterHooks.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import { Counter } from "@/interface/Counters";
|
||||
import { PersonCharacter } from "@/interface/PersonCharacter";
|
||||
import api from "@/util/AxiosUtil";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
|
||||
export function useGetPersonCharactersByPersonId(personId: string, raidGroupId: string){
|
||||
return useQuery({
|
||||
queryKey: ["personCharacters", personId],
|
||||
queryFn: async () => {
|
||||
const response = await api.get(`/raidGroup/${raidGroupId}/person/${personId}/character`);
|
||||
|
||||
return response.data as PersonCharacter[];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useGetPersonCharactersByPersonIdSearch(personId: string, raidGroupId: string, page: number, pageSize: number, searchTerm?: string){
|
||||
const params = new URLSearchParams();
|
||||
params.append("page", page.toString());
|
||||
params.append("pageSize", pageSize.toString());
|
||||
if(searchTerm){
|
||||
params.append("searchTerm", searchTerm);
|
||||
}
|
||||
|
||||
|
||||
return useQuery({
|
||||
queryKey: ["personCharacters", personId, { page, pageSize, searchTerm }],
|
||||
queryFn: async () => {
|
||||
const response = await api.get(`/raidGroup/${raidGroupId}/person/${personId}/character/page?${params}`);
|
||||
|
||||
return response.data as PersonCharacter[];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useGetPersonCharactersCountByPersonIdSearch(personId: string, raidGroupId: string, searchTerm?: string){
|
||||
const params = new URLSearchParams();
|
||||
if(searchTerm){
|
||||
params.append("searchTerm", searchTerm);
|
||||
}
|
||||
|
||||
return useQuery({
|
||||
queryKey: ["personCharactersCount", personId, { searchTerm }],
|
||||
queryFn: async () => {
|
||||
const response = await api.get(`/raidGroup/${raidGroupId}/person/${personId}/character/count?${params}`);
|
||||
|
||||
return (response.data as Counter).count;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useGetPersonCharactersByRaidGroup(raidGroupId: string){
|
||||
return useQuery({
|
||||
queryKey: ["personCharacters", raidGroupId],
|
||||
queryFn: async () => {
|
||||
const response = await api.get(`/raidGroup/${raidGroupId}/person/character`);
|
||||
|
||||
return response.data as PersonCharacter[];
|
||||
},
|
||||
enabled: !!raidGroupId
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreatePersonCharacter(raidGroupId: string, personId: string){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["createPersonCharacter"],
|
||||
mutationFn: async (personCharacter: PersonCharacter) => {
|
||||
await api.post(`/raidGroup/${raidGroupId}/person/${personId}/character`, personCharacter);
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["personCharacters"] });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdatePersonCharacter(raidGroupId: string, personId: string){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["updatePersonCharacter"],
|
||||
mutationFn: async (personCharacter: PersonCharacter) => {
|
||||
await api.put(`/raidGroup/${raidGroupId}/person/${personId}/character/${personCharacter.personCharacterId}`, personCharacter);
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["personCharacters"] });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeletePersonCharacter(raidGroupId: string, personId: string){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["deletePersonCharacter"],
|
||||
mutationFn: async (personCharacterId: string) => {
|
||||
await api.delete(`/raidGroup/${raidGroupId}/person/${personId}/character/${personCharacterId}`);
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["personCharacters"] });
|
||||
}
|
||||
});
|
||||
}
|
||||
97
src/hooks/PersonHooks.ts
Normal file
97
src/hooks/PersonHooks.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { Counter } from "@/interface/Counters";
|
||||
import { Person } from "@/interface/Person";
|
||||
import api from "@/util/AxiosUtil";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
|
||||
export function useGetPerson(raidGroupId: string, personId: string, disabled: boolean){
|
||||
return useQuery({
|
||||
queryKey: ["people", raidGroupId, personId],
|
||||
queryFn: async () => {
|
||||
const response = await api.get(`/raidGroup/${raidGroupId}/person/${personId}`);
|
||||
|
||||
return response.data as Person;
|
||||
},
|
||||
enabled: !disabled
|
||||
});
|
||||
}
|
||||
|
||||
export function useGetPeopleByRaidGroup(raidGroupId: string, page: number, pageSize: number, searchTerm?: string){
|
||||
return useQuery({
|
||||
queryKey: ["people", raidGroupId, { page, pageSize, searchTerm }],
|
||||
queryFn: async () => {
|
||||
const searchParams = new URLSearchParams();
|
||||
searchParams.append("page", page.toString());
|
||||
searchParams.append("pageSize", pageSize.toString());
|
||||
if(searchTerm){
|
||||
searchParams.append("searchTerm", searchTerm);
|
||||
}
|
||||
|
||||
const response = await api.get(`/raidGroup/${raidGroupId}/person?${searchParams}`);
|
||||
|
||||
return response.data as Person[];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useGetPeopleByRaidGroupCount(raidGroupId: string, searchTerm?: string){
|
||||
const searchParams = new URLSearchParams();
|
||||
if(searchTerm){
|
||||
searchParams.append("searchTerm", searchTerm);
|
||||
}
|
||||
|
||||
|
||||
return useQuery({
|
||||
queryKey: ["people", raidGroupId, "count", searchTerm],
|
||||
queryFn: async () => {
|
||||
const response = await api.get(`/raidGroup/${raidGroupId}/person/count?${searchParams}`);
|
||||
|
||||
return (response.data as Counter).count;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreatePerson(){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["createPerson"],
|
||||
mutationFn: async ({raidGroupId, personName, discordId}:{raidGroupId: string; personName: string; discordId?: string;}) => {
|
||||
await api.post(`/raidGroup/${raidGroupId}/person`, {raidGroupId, personName, discordId});
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["people"] });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdatePerson(){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["updatePerson"],
|
||||
mutationFn: async (person: Person) => {
|
||||
await api.put(`/raidGroup/${person.raidGroupId}/person/${person.personId}`, person);
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["people"] });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeletePerson(){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["deletePerson"],
|
||||
mutationFn: async ({raidGroupId, personId}:{raidGroupId: string; personId: string;}) => {
|
||||
await api.delete(`/raidGroup/${raidGroupId}/person/${personId}`);
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["people"] });
|
||||
}
|
||||
});
|
||||
}
|
||||
189
src/hooks/RaidGroupHooks.ts
Normal file
189
src/hooks/RaidGroupHooks.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
import { Counter } from "@/interface/Counters";
|
||||
import { RaidGroup } from "@/interface/RaidGroup";
|
||||
import api from "@/util/AxiosUtil";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
|
||||
export function useGetRaidGroup(raidGroupId: string, disabled: boolean){
|
||||
return useQuery({
|
||||
queryKey: ["raidGroups", raidGroupId],
|
||||
queryFn: async () => {
|
||||
const response = await api.get(`/raidGroup/${raidGroupId}`);
|
||||
|
||||
return (response.data as RaidGroup)?.raidGroupId ? response.data as RaidGroup : undefined;
|
||||
},
|
||||
enabled: !disabled
|
||||
});
|
||||
}
|
||||
|
||||
export function useGetRaidGroups(page: number, pageSize: number, searchTerm?: string){
|
||||
return useQuery({
|
||||
queryKey: ["raidGroups", { page, pageSize, searchTerm }],
|
||||
queryFn: async () => {
|
||||
const params = new URLSearchParams();
|
||||
params.append("page", page.toString());
|
||||
params.append("pageSize", pageSize.toString());
|
||||
if(searchTerm){
|
||||
params.append("searchTerm", searchTerm);
|
||||
}
|
||||
|
||||
const response = await api.get(`/raidGroup?${params}`);
|
||||
|
||||
return response.data as RaidGroup[];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useGetRaidGroupsCount(searchTerm?: string){
|
||||
const searchParams = new URLSearchParams();
|
||||
if(searchTerm){
|
||||
searchParams.append("searchTerm", searchTerm);
|
||||
}
|
||||
|
||||
|
||||
return useQuery({
|
||||
queryKey: ["raidGroups", "count", searchTerm],
|
||||
queryFn: async () => {
|
||||
const response = await api.get(`/raidGroup/count?${searchParams}`);
|
||||
|
||||
return (response.data as Counter).count;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useGetRaidGroupsByGame(gameId: string, page: number, pageSize: number, searchTerm?: string){
|
||||
return useQuery({
|
||||
queryKey: ["raidGroups", gameId, { page, pageSize, searchTerm }],
|
||||
queryFn: async () => {
|
||||
const params = new URLSearchParams();
|
||||
params.append("page", page.toString());
|
||||
params.append("pageSize", pageSize.toString());
|
||||
if(searchTerm){
|
||||
params.append("searchTerm", searchTerm);
|
||||
}
|
||||
|
||||
const response = await api.get(`/raidGroup/game/${gameId}?${params}`);
|
||||
|
||||
return response.data as RaidGroup[];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useGetRaidGroupsByGameCount(gameId: string, searchTerm?: string){
|
||||
const searchParams = new URLSearchParams();
|
||||
if(searchTerm){
|
||||
searchParams.append("searchTerm", searchTerm);
|
||||
}
|
||||
|
||||
|
||||
return useQuery({
|
||||
queryKey: ["raidGroups", gameId, "count", searchTerm],
|
||||
queryFn: async () => {
|
||||
const response = await api.get(`/raidGroup/game/${gameId}/count?${searchParams}`);
|
||||
|
||||
return (response.data as Counter).count;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useGetRaidGroupsByAccount(accountId: string, page: number, pageSize: number, searchTerm?: string){
|
||||
return useQuery({
|
||||
queryKey: ["raidGroups", accountId, { page, pageSize, searchTerm }],
|
||||
queryFn: async () => {
|
||||
const params = new URLSearchParams();
|
||||
params.append("page", page.toString());
|
||||
params.append("pageSize", pageSize.toString());
|
||||
if(searchTerm){
|
||||
params.append("searchTerm", searchTerm);
|
||||
}
|
||||
|
||||
const response = await api.get(`/raidGroup/account/${accountId}?${params}`);
|
||||
|
||||
return response.data as RaidGroup[];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useGetRaidGroupsCountByAccount(accountId: string, searchTerm?: string){
|
||||
const searchParams = new URLSearchParams();
|
||||
if(searchTerm){
|
||||
searchParams.append("searchTerm", searchTerm);
|
||||
}
|
||||
|
||||
|
||||
return useQuery({
|
||||
queryKey: ["raidGroups", accountId, "count", searchTerm],
|
||||
queryFn: async () => {
|
||||
const response = await api.get(`/raidGroup/account/${accountId}/count?${searchParams}`);
|
||||
|
||||
return (response.data as Counter).count;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
export function useCreateRaidGroup(){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["createRaidGroup"],
|
||||
mutationFn: async ({raidGroupName, gameId, iconFile}:{raidGroupName: string; gameId: string; iconFile: File | null;}) => {
|
||||
const formData = new FormData();
|
||||
if(iconFile){
|
||||
formData.append("iconFile", iconFile);
|
||||
}
|
||||
formData.append("raidGroupName", raidGroupName);
|
||||
formData.append("gameId", gameId);
|
||||
|
||||
await api.post(
|
||||
"/raidGroup",
|
||||
formData
|
||||
);
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["raidGroups"] });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateRaidGroup(){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["updateRaidGroup"],
|
||||
mutationFn: async ({raidGroup, iconFile}:{raidGroup: RaidGroup; iconFile: File | null;}) => {
|
||||
const formData = new FormData();
|
||||
if(iconFile){
|
||||
formData.append("iconFile", iconFile);
|
||||
}
|
||||
formData.append("raidGroupName", raidGroup.raidGroupName);
|
||||
formData.append("gameId", raidGroup.gameId);
|
||||
if(raidGroup.raidGroupIcon){
|
||||
formData.append("raidGroupIcon", raidGroup.raidGroupIcon);
|
||||
}
|
||||
|
||||
await api.put(`/raidGroup/${raidGroup.raidGroupId}`, formData);
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["raidGroups"] });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteRaidGroup(){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["deleteRaidGroup"],
|
||||
mutationFn: async (raidGroupId: string) => {
|
||||
await api.delete(`/raidGroup/${raidGroupId}`);
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["raidGroups"] });
|
||||
}
|
||||
});
|
||||
}
|
||||
86
src/hooks/RaidGroupRequestHooks.ts
Normal file
86
src/hooks/RaidGroupRequestHooks.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { Counter } from "@/interface/Counters";
|
||||
import { RaidGroupPermissionType } from "@/interface/RaidGroup";
|
||||
import { RaidGroupRequest } from "@/interface/RaidGroupRequest";
|
||||
import api from "@/util/AxiosUtil";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
|
||||
export function useGetRaidGroupRequests(raidGroupId: string, page: number, pageSize: number, searchTerm?: string){
|
||||
const searchParams = new URLSearchParams();
|
||||
searchParams.append("page", page.toString());
|
||||
searchParams.append("pageSize", pageSize.toString());
|
||||
if(searchTerm){
|
||||
searchParams.append("searchTerm", searchTerm);
|
||||
}
|
||||
|
||||
return useQuery({
|
||||
queryKey: ["raidGroupRequest", raidGroupId, {page, pageSize, searchTerm}],
|
||||
queryFn: async () => {
|
||||
const response = await api.get(`/raidGroup/${raidGroupId}/raidGroupRequest?${searchParams}`);
|
||||
|
||||
return response.data as RaidGroupRequest[];
|
||||
},
|
||||
enabled: !!raidGroupId && raidGroupId !== ""
|
||||
});
|
||||
}
|
||||
|
||||
export function useGetRaidGroupRequestCount(raidGroupId?: string, searchTerm?: string){
|
||||
const searchParams = new URLSearchParams();
|
||||
if(searchTerm){
|
||||
searchParams.append("searchTerm", searchTerm);
|
||||
}
|
||||
|
||||
return useQuery({
|
||||
queryKey: ["raidGroupRequest", raidGroupId, "count", searchTerm],
|
||||
queryFn: async () => {
|
||||
const response = await api.get(`/raidGroup/${raidGroupId}/raidGroupRequest/count?${searchParams}`);
|
||||
|
||||
return (response.data as Counter).count;
|
||||
},
|
||||
enabled: !!raidGroupId && raidGroupId !== ""
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export function useCreateRaidGroupRequest(raidGroupId: string){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["raidGroupRequest", raidGroupId],
|
||||
mutationFn: async (requestMessage: string) => {
|
||||
await api.post(`/raidGroup/${raidGroupId}/raidGroupRequest`, {requestMessage});
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["raidGroupRequest", raidGroupId] });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useResolveRaidGroupRequest(raidGroupId: string, raidGroupRequestId: string){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["raidGroupRequest", raidGroupId, raidGroupRequestId],
|
||||
mutationFn: async (permission: RaidGroupPermissionType) => {
|
||||
await api.put(`/raidGroup/${raidGroupId}/raidGroupRequest/${raidGroupRequestId}/resolve`, {resolution: permission});
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["raidGroupRequest", raidGroupId] });
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
export function useDeleteRaidGroupRequest(raidGroupId: string, raidGroupRequestId: string){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["raidGroupRequest", raidGroupId, raidGroupRequestId],
|
||||
mutationFn: async () => {
|
||||
await api.delete(`/raidGroup/${raidGroupId}/raidGroupRequest/${raidGroupRequestId}`);
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["raidGroupRequest", raidGroupId] });
|
||||
}
|
||||
});
|
||||
}
|
||||
138
src/hooks/RaidInstanceHooks.ts
Normal file
138
src/hooks/RaidInstanceHooks.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
import { Counter } from "@/interface/Counters";
|
||||
import { RaidInstance } from "@/interface/RaidInstance";
|
||||
import { RaidInstancePersonCharacterXref } from "@/interface/RaidInstancePersonCharacterXref";
|
||||
import api from "@/util/AxiosUtil";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
|
||||
export function useGetRaidInstance(raidInstanceId: string, raidGroupId: string){
|
||||
return useQuery({
|
||||
queryKey: ["raidInstances", raidInstanceId, raidGroupId],
|
||||
queryFn: async () => {
|
||||
const response = await api.get(`/raidGroup/${raidGroupId}/raidInstance/${raidInstanceId}`);
|
||||
|
||||
return response.data as RaidInstance;
|
||||
},
|
||||
enabled: !!raidInstanceId && !!raidGroupId
|
||||
});
|
||||
}
|
||||
|
||||
export function useGetRaidInstancesByRaidGroup(raidGroupId: string, page: number, pageSize: number, searchTerm?: string){
|
||||
return useQuery({
|
||||
queryKey: ["raidInstances", raidGroupId, {page, pageSize, searchTerm}],
|
||||
queryFn: async () => {
|
||||
const params = new URLSearchParams();
|
||||
params.append("page", page.toString());
|
||||
params.append("pageSize", pageSize.toString());
|
||||
if(searchTerm){
|
||||
params.append("searchTerm", searchTerm);
|
||||
}
|
||||
|
||||
const response = await api.get(`/raidGroup/${raidGroupId}/raidInstance?${params}`);
|
||||
|
||||
return response.data as RaidInstance[];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export function useGetRaidInstancesByRaidGroupCount(raidGroupId: string, searchTerm?: string){
|
||||
return useQuery({
|
||||
queryKey: ["raidInstances", raidGroupId, "count", {searchTerm}],
|
||||
queryFn: async () => {
|
||||
const params = new URLSearchParams();
|
||||
if(searchTerm){
|
||||
params.append("searchTerm", searchTerm);
|
||||
}
|
||||
|
||||
const response = await api.get(`/raidGroup/${raidGroupId}/raidInstance/count?${params}`);
|
||||
|
||||
return (response.data as Counter).count;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export function useCreateRaidInstance(raidGroupId: string){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["createRaidInstance", raidGroupId],
|
||||
mutationFn: async (raidInstance: RaidInstance) => {
|
||||
const response = await api.post(`/raidGroup/${raidGroupId}/raidInstance`, raidInstance);
|
||||
|
||||
return (response.data as RaidInstance).raidInstanceId;
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["raidInstances"] });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateRaidInstance(raidGroupId: string){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["updateRaidInstance", raidGroupId],
|
||||
mutationFn: async (raidInstance: RaidInstance) => {
|
||||
await api.put(`/raidGroup/${raidGroupId}/raidInstance/${raidInstance.raidInstanceId}`, raidInstance);
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["raidInstances"] });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateRaidInstanceNoInvalidation(raidGroupId: string){
|
||||
return useMutation({
|
||||
mutationKey: ["updateRaidInstance", raidGroupId],
|
||||
mutationFn: async (raidInstance: RaidInstance) => {
|
||||
await api.put(`/raidGroup/${raidGroupId}/raidInstance/${raidInstance.raidInstanceId}`, raidInstance);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteRaidInstance(raidGroupId: string, raidInstanceId: string){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["deleteRaidInstance", raidGroupId, raidInstanceId],
|
||||
mutationFn: async () => {
|
||||
await api.delete(`/raidGroup/${raidGroupId}/raidInstance/${raidInstanceId}`);
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["raidInstances"] });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export function useGetRaidInstancePersonCharacterXrefs(raidGroupId?: string, raidInstanceId?: string){
|
||||
return useQuery({
|
||||
queryKey: ["raidInstancePersonCharacterXrefs", raidGroupId, raidInstanceId],
|
||||
queryFn: async () => {
|
||||
const response = await api.get(`/raidGroup/${raidGroupId}/raidInstance/${raidInstanceId}/personCharacterXref`);
|
||||
|
||||
return response.data as RaidInstancePersonCharacterXref[];
|
||||
},
|
||||
enabled: !!raidGroupId && !!raidInstanceId
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateRaidInstancePersonCharacterXrefs(raidGroupId?: string, raidInstanceId?: string){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["updateRaidInstancePersonCharacterXrefs", raidGroupId, raidInstanceId],
|
||||
mutationFn: async (raidInstancePersonCharacterXrefs: RaidInstancePersonCharacterXref[]) => {
|
||||
await api.post(`/raidGroup/${raidGroupId}/raidInstance/${raidInstanceId}/personCharacterXref`, raidInstancePersonCharacterXrefs);
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["raidInstancePersonCharacterXrefs", raidGroupId, raidInstanceId] });
|
||||
}
|
||||
});
|
||||
}
|
||||
107
src/hooks/RaidLayoutHooks.ts
Normal file
107
src/hooks/RaidLayoutHooks.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { Counter } from "@/interface/Counters";
|
||||
import { RaidLayout, RaidLayoutClassGroupXref } from "@/interface/RaidLayout";
|
||||
import api from "@/util/AxiosUtil";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
|
||||
export function useGetRaidLayout(raidGroupId?: string, raidLayoutId?: string){
|
||||
return useQuery({
|
||||
queryKey: ["raidLayout", raidGroupId, raidLayoutId],
|
||||
queryFn: async () => {
|
||||
const response = await api.get(`/raidGroup/${raidGroupId}/raidLayout/${raidLayoutId}`);
|
||||
|
||||
return response.data as RaidLayout;
|
||||
},
|
||||
enabled: !!raidGroupId && !!raidLayoutId
|
||||
});
|
||||
}
|
||||
|
||||
export function useGetRaidLayoutsByRaidGroup(raidGroupId: string, page: number, pageSize: number, searchTerm?: string){
|
||||
return useQuery({
|
||||
queryKey: ["raidLayouts", raidGroupId, {page, pageSize, searchTerm}],
|
||||
queryFn: async () => {
|
||||
const params = new URLSearchParams();
|
||||
params.append("page", page.toString());
|
||||
params.append("pageSize", pageSize.toString());
|
||||
if(searchTerm){
|
||||
params.append("searchTerm", searchTerm);
|
||||
}
|
||||
|
||||
const response = await api.get(`/raidGroup/${raidGroupId}/raidLayout?${params}`);
|
||||
|
||||
return response.data as RaidLayout[];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useGetRaidLayoutsByRaidGroupCount(raidGroupId: string, searchTerm?: string){
|
||||
return useQuery({
|
||||
queryKey: ["raidLayouts", raidGroupId, "count", searchTerm],
|
||||
queryFn: async () => {
|
||||
const params = new URLSearchParams();
|
||||
if(searchTerm){
|
||||
params.append("searchTerm", searchTerm);
|
||||
}
|
||||
|
||||
const response = await api.get(`/raidGroup/${raidGroupId}/raidLayout/count?${params}`);
|
||||
|
||||
return (response.data as Counter).count;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateRaidLayout(raidGroupId: string){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["createRaidLayout", raidGroupId],
|
||||
mutationFn: async ({raidLayout, raidLayoutClassGroupXrefs}:{raidLayout: RaidLayout; raidLayoutClassGroupXrefs: RaidLayoutClassGroupXref[];}) => {
|
||||
await api.post(`/raidGroup/${raidGroupId}/raidLayout`,
|
||||
{
|
||||
raidLayout,
|
||||
raidLayoutClassGroupXrefs
|
||||
}
|
||||
);
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["raidLayouts", raidGroupId] });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateRaidLayout(raidGroupId: string){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["updateRaidLayout", raidGroupId],
|
||||
mutationFn: async ({raidLayout, raidLayoutClassGroupXrefs}:{raidLayout: RaidLayout; raidLayoutClassGroupXrefs: RaidLayoutClassGroupXref[];}) => {
|
||||
await api.put(`/raidGroup/${raidGroupId}/raidLayout/${raidLayout.raidLayoutId}`,
|
||||
{
|
||||
raidLayout,
|
||||
raidLayoutClassGroupXrefs
|
||||
}
|
||||
);
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["raidLayouts", raidGroupId] });
|
||||
void queryClient.invalidateQueries({ queryKey: ["classGroups", "raidLayout"] });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteRaidLayout(raidGroupId: string, raidLayoutId: string){
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["deleteRaidLayout", raidGroupId, raidLayoutId],
|
||||
mutationFn: async () => {
|
||||
await api.delete(`/raidGroup/${raidGroupId}/raidLayout/${raidLayoutId}`)
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["raidLayouts", raidGroupId] });
|
||||
}
|
||||
});
|
||||
}
|
||||
82
src/index.css
Normal file
82
src/index.css
Normal file
@@ -0,0 +1,82 @@
|
||||
@import "tailwindcss";
|
||||
@custom-variant dark (&:where(.dark, .dark *));
|
||||
|
||||
@theme {
|
||||
--color-neutral-825: oklch(0.253 0 0);
|
||||
--color-neutral-850: oklch(0.237 0 0);
|
||||
}
|
||||
|
||||
|
||||
:root.dark {
|
||||
--text-color: #FFFFFFDE;
|
||||
--bg-color: var(--color-neutral-825);
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
:root.light {
|
||||
--text-color: #213547;
|
||||
--bg-color: #FFFFFF;
|
||||
}
|
||||
|
||||
:root {
|
||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
#root {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
a:hover{
|
||||
color: var(--color-blue-300);
|
||||
}
|
||||
|
||||
a.active {
|
||||
color: var(--color-blue-400);
|
||||
}
|
||||
|
||||
body {
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
max-width: var(--breakpoint-2xl);
|
||||
|
||||
margin-inline: auto;
|
||||
padding-top: 90px;
|
||||
padding-inline: 1rem;
|
||||
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
}
|
||||
button:disabled {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
nav {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.navContents {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
max-width: var(--breakpoint-2xl);
|
||||
|
||||
margin-inline: auto;
|
||||
padding: 1rem;
|
||||
}
|
||||
26
src/interface/Account.ts
Normal file
26
src/interface/Account.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
export enum AccountStatus {
|
||||
ACTIVE = "ACTIVE",
|
||||
LOCKED = "LOCKED",
|
||||
INACTIVE = "INACTIVE",
|
||||
DELETED = "DELETED",
|
||||
UNCONFIRMED = "UNCONFIRMED"
|
||||
};
|
||||
|
||||
export enum AccountPermissionType {
|
||||
ADMIN = "ADMIN",
|
||||
USER = "USER"
|
||||
}
|
||||
|
||||
|
||||
export interface Account {
|
||||
accountId: string;
|
||||
username: string;
|
||||
password: string;
|
||||
loginDate: Date;
|
||||
email: string;
|
||||
forceReset: boolean;
|
||||
refreshToken?: string;
|
||||
refreshTokenExpiration?: Date;
|
||||
accountStatus: AccountStatus;
|
||||
token?: string;
|
||||
}
|
||||
8
src/interface/AccountPermission.ts
Normal file
8
src/interface/AccountPermission.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { AccountPermissionType } from "./Account";
|
||||
|
||||
|
||||
export interface AccountPermission {
|
||||
accountPermissionId?: string;
|
||||
accountId: string;
|
||||
accountPermissionType: AccountPermissionType;
|
||||
}
|
||||
15
src/interface/AccountTutorialStatus.ts
Normal file
15
src/interface/AccountTutorialStatus.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export enum TutorialStatus {
|
||||
COMPLETED = "COMPLETED",
|
||||
NOT_COMPLETED = "NOT_COMPLETED"
|
||||
}
|
||||
|
||||
|
||||
export interface AccountTutorialStatus {
|
||||
accountTutorialStatusId?: string;
|
||||
accountId: string;
|
||||
gamesTutorialStatus: TutorialStatus;
|
||||
gameTutorialStatus: TutorialStatus;
|
||||
raidGroupsTutorialStatus: TutorialStatus;
|
||||
raidGroupTutorialStatus: TutorialStatus;
|
||||
instanceTutorialStatus: TutorialStatus;
|
||||
}
|
||||
13
src/interface/Calendar.ts
Normal file
13
src/interface/Calendar.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export interface CalendarEvent {
|
||||
eventName: string;
|
||||
eventDescription: string;
|
||||
eventStartDate: Date;
|
||||
eventEndDate: Date;
|
||||
|
||||
backgroundColor?: string;
|
||||
|
||||
calendarEventId?: string;
|
||||
gameId?: string;
|
||||
raidGroupId?: string;
|
||||
raidInstanceId?: string;
|
||||
}
|
||||
5
src/interface/ClassGroup.ts
Normal file
5
src/interface/ClassGroup.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export interface ClassGroup {
|
||||
classGroupId?: string;
|
||||
raidGroupId: string;
|
||||
classGroupName: string;
|
||||
}
|
||||
3
src/interface/Counters.ts
Normal file
3
src/interface/Counters.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export interface Counter {
|
||||
count: number;
|
||||
}
|
||||
4
src/interface/Errors.ts
Normal file
4
src/interface/Errors.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface ResponseError {
|
||||
status: string;
|
||||
errors: string[];
|
||||
}
|
||||
5
src/interface/Game.ts
Normal file
5
src/interface/Game.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export interface Game{
|
||||
gameId?: string;
|
||||
gameName: string;
|
||||
gameIcon?: string;
|
||||
}
|
||||
6
src/interface/GameClass.ts
Normal file
6
src/interface/GameClass.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export interface GameClass {
|
||||
gameClassId?: string;
|
||||
gameId: string;
|
||||
gameClassName: string;
|
||||
gameClassIcon?: string;
|
||||
}
|
||||
11
src/interface/GamePermission.ts
Normal file
11
src/interface/GamePermission.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export enum GamePermissionType {
|
||||
ADMIN = "ADMIN"
|
||||
}
|
||||
|
||||
|
||||
export interface GamePermission {
|
||||
gamePermissionId?: string;
|
||||
accountId: string;
|
||||
gameId: string;
|
||||
gamePermissionType: GamePermissionType;
|
||||
}
|
||||
4
src/interface/GridLocation.ts
Normal file
4
src/interface/GridLocation.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface GridLocation {
|
||||
row: number;
|
||||
col: number;
|
||||
}
|
||||
24
src/interface/ModalInterfaces.ts
Normal file
24
src/interface/ModalInterfaces.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { HTMLProps } from "react";
|
||||
|
||||
|
||||
export type ModalBackgroundType = "darken" | "lighten" | "blur" | "darken-blur" | "lighten-blur" | "darken-blur-radial" | "lighten-blur-radial" | "transparent" | "none";
|
||||
export type ModalHeaderFooterBackgroundType = "darken" | "lighten" | "none";
|
||||
|
||||
|
||||
export interface ModalBackgroundProps extends HTMLProps<HTMLDivElement>{
|
||||
backgroundType?: ModalBackgroundType;
|
||||
close?: () => void;
|
||||
}
|
||||
|
||||
export interface ModalProps extends HTMLProps<HTMLDivElement>{
|
||||
display?: boolean;
|
||||
backgroundType?: ModalBackgroundType;
|
||||
backgroundClassName?: string;
|
||||
top?: boolean;
|
||||
close?: () => void;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface ModalHeaderProps extends HTMLProps<HTMLDivElement>{
|
||||
close?: () => void;
|
||||
}
|
||||
6
src/interface/Person.ts
Normal file
6
src/interface/Person.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export interface Person {
|
||||
personId?: string;
|
||||
raidGroupId: string;
|
||||
personName: string;
|
||||
discordId?: string;
|
||||
}
|
||||
8
src/interface/PersonCharacter.ts
Normal file
8
src/interface/PersonCharacter.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export interface PersonCharacter {
|
||||
personCharacterId?: string;
|
||||
personId: string;
|
||||
gameClassId: string;
|
||||
characterName: string;
|
||||
characterRating?: number;
|
||||
characterComments?: string;
|
||||
}
|
||||
13
src/interface/RaidGroup.ts
Normal file
13
src/interface/RaidGroup.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export enum RaidGroupPermissionType {
|
||||
ADMIN = "ADMIN",
|
||||
LEADER = "LEADER",
|
||||
RAIDER = "RAIDER"
|
||||
}
|
||||
|
||||
|
||||
export interface RaidGroup {
|
||||
raidGroupId?: string;
|
||||
gameId: string;
|
||||
raidGroupName: string;
|
||||
raidGroupIcon?: string;
|
||||
}
|
||||
9
src/interface/RaidGroupPermission.ts
Normal file
9
src/interface/RaidGroupPermission.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { RaidGroupPermissionType } from "./RaidGroup";
|
||||
|
||||
|
||||
export interface RaidGroupPermission {
|
||||
raidGroupPermissionId?: string;
|
||||
accountId: string;
|
||||
raidGroupId: string;
|
||||
permission: RaidGroupPermissionType;
|
||||
}
|
||||
8
src/interface/RaidGroupRequest.ts
Normal file
8
src/interface/RaidGroupRequest.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export interface RaidGroupRequest {
|
||||
raidGroupRequestId?: string;
|
||||
raidGroupId: string;
|
||||
accountId: string;
|
||||
requestMessage: string;
|
||||
|
||||
username: string;
|
||||
}
|
||||
10
src/interface/RaidInstance.ts
Normal file
10
src/interface/RaidInstance.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export interface RaidInstance {
|
||||
raidInstanceId?: string;
|
||||
raidGroupId: string;
|
||||
raidLayoutId?: string;
|
||||
raidInstanceName?: string;
|
||||
raidSize?: number;
|
||||
numberRuns: number;
|
||||
raidStartDate: Date;
|
||||
raidEndDate: Date;
|
||||
}
|
||||
7
src/interface/RaidInstancePersonCharacterXref.ts
Normal file
7
src/interface/RaidInstancePersonCharacterXref.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export interface RaidInstancePersonCharacterXref {
|
||||
raidInstancePersonCharacterXrefId?: string;
|
||||
raidInstanceId: string;
|
||||
personCharacterId: string;
|
||||
groupNumber: number;
|
||||
positionNumber: number;
|
||||
}
|
||||
13
src/interface/RaidLayout.ts
Normal file
13
src/interface/RaidLayout.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export interface RaidLayout {
|
||||
raidLayoutId?: string;
|
||||
raidGroupId: string;
|
||||
raidLayoutName: string;
|
||||
raidSize: number;
|
||||
}
|
||||
|
||||
export interface RaidLayoutClassGroupXref {
|
||||
raidLayoutClassGroupXrefId?: string;
|
||||
raidLayoutId: string;
|
||||
classGroupId?: string;
|
||||
layoutLocation: number;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user