diff options
| -rw-r--r-- | backend/src/API/AuthenticationAPI.zig | 1 | ||||
| -rw-r--r-- | front/src/App.tsx | 18 | ||||
| -rw-r--r-- | front/src/Authentication/Models.ts | 1 | ||||
| -rw-r--r-- | front/src/Emelents/Topbar.tsx | 2 | ||||
| -rw-r--r-- | front/src/ErrorHandler.tsx | 4 | ||||
| -rw-r--r-- | front/src/Pages/RangedWeapon.tsx | 359 | ||||
| -rw-r--r-- | front/src/Pages/Weapons.css | 4 | ||||
| -rw-r--r-- | front/src/Pages/Weapons.tsx | 18 |
8 files changed, 381 insertions, 26 deletions
diff --git a/backend/src/API/AuthenticationAPI.zig b/backend/src/API/AuthenticationAPI.zig index ea398ba..8792fd0 100644 --- a/backend/src/API/AuthenticationAPI.zig +++ b/backend/src/API/AuthenticationAPI.zig @@ -14,6 +14,7 @@ pub fn RegisterEndpoints(router: *httpz.Router(*Handler.Handler,*const fn (*Hand fn getUser(data: *Handler.RequestData, _: *httpz.Request, res: *httpz.Response) !void { if (data.User == null) { + res.setStatus(.unauthorized); try res.json(.{.status = "Unauthnticated"}, .{}); return; } diff --git a/front/src/App.tsx b/front/src/App.tsx index da9cbfd..c3d4b00 100644 --- a/front/src/App.tsx +++ b/front/src/App.tsx @@ -34,13 +34,14 @@ const router = createBrowserRouter([ element: (<WeaponsIndex />), }, { - path: "/weapons/:id", - element: (<RangedWeaponPage />), - }, - { path: "/weapons/:id/edit", + errorElement: (<ErrorHandler />), element: (<EditRangedWeaponPage />), }, + { + path: "/weapons/:id", + element: (<RangedWeaponPage />), + }, // auth { @@ -69,7 +70,14 @@ function App() { const [authCookie, ] = useCookies(['X-AUTH-TOKEN']); const [auth, setAuth] = useState<User | null>(null); useEffect(() => { - GetUserInfo(authCookie['X-AUTH-TOKEN']).then(v => setAuth(v)); + GetUserInfo(authCookie['X-AUTH-TOKEN']).then( + v => { + console.log(v); + if (!v || v == undefined) + document.cookie = "X-AUTH-TOKEN=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; + setAuth(v); + } + ); }, []); return ( diff --git a/front/src/Authentication/Models.ts b/front/src/Authentication/Models.ts index 1918b85..8d8c144 100644 --- a/front/src/Authentication/Models.ts +++ b/front/src/Authentication/Models.ts @@ -1,4 +1,5 @@ export type User = { Username: string, Role: "user" | "editor" + status?: string } diff --git a/front/src/Emelents/Topbar.tsx b/front/src/Emelents/Topbar.tsx index 284cbb9..8845bc8 100644 --- a/front/src/Emelents/Topbar.tsx +++ b/front/src/Emelents/Topbar.tsx @@ -8,7 +8,7 @@ const defaultPathName = 'index'; function Topbar() { var language = useContext(LanguageContext); var user = useContext(AuthContext); - let path = getTopbarElement(window.location.pathname); + var path = getTopbarElement(window.location.pathname); function getTopText() { if (user == null) { diff --git a/front/src/ErrorHandler.tsx b/front/src/ErrorHandler.tsx index 177a9d0..9b0c5c7 100644 --- a/front/src/ErrorHandler.tsx +++ b/front/src/ErrorHandler.tsx @@ -28,8 +28,8 @@ function ErrorHandler() { function forbid(language: AllowedLanguages) { return ( <div> - <h1> { GetLocalizedString("Forbidden", language) } </h1> - <p> { GetLocalizedString("*forbidden*", language) } </p> + <h1> { GetLocalizedString("Forbidden", language) } </h1> + <p> { GetLocalizedString("*forbidden*", language) } </p> </div> ); } diff --git a/front/src/Pages/RangedWeapon.tsx b/front/src/Pages/RangedWeapon.tsx index fa61644..5b66fad 100644 --- a/front/src/Pages/RangedWeapon.tsx +++ b/front/src/Pages/RangedWeapon.tsx @@ -1,17 +1,19 @@ -import { useContext, useState } from "react"; +import { useContext, useEffect, useState } from "react"; import { AllowedLanguages, LanguageContext } from "../Locales/Context"; import { BackendURL } from "../Config"; import axios from "axios"; -import { RangedWeapon } from "../Models/RangedWeapon"; +import { RangedWeapon, RangedWeaponRequest } from "../Models/RangedWeapon"; import { Description } from "../Models/Description"; -import { useParams } from "react-router"; +import { useNavigate, useParams } from "react-router"; import { GetLocalizedString } from "../Locales/Locales"; import "./Weapons.css" import { AuthContext } from "../Authentication/ContextProvider"; +import { useCookies } from 'react-cookie'; const RangedWeaponsURL = `${BackendURL}/weapons/ranged`; export function RangedWeaponPage() { + var user = useContext(AuthContext); const { id } = useParams(); const lang = useContext(LanguageContext); const [weapon, setWeapon] = useState<RangedWeapon | null>(null); @@ -26,6 +28,14 @@ export function RangedWeaponPage() { return <h1> Not found </h1>; // <Navigate to="/404" replace />; } + function editButton() { + if (!user || user.Role != "editor") { + return (<p></p>); + } else { + return ( <a href={`${id}/edit`}> Edit </a> ); + } + } + return ( <div className="WeaponPage"> <span> <h1> {weapon?.Name} </h1> </span> @@ -70,15 +80,15 @@ export function RangedWeaponPage() { <p> {description?.Contents} </p> + { editButton() } </div> ); } - -async function getWeapon(id: string | undefined): Promise<RangedWeapon | null> { +async function getWeaponDescription(id: string | undefined, lanuage: AllowedLanguages): Promise<Description | null> { if (id == undefined) return null; try { - const {data, status} = await axios.get<RangedWeapon>( - `${RangedWeaponsURL}/${id}`, + const {data, status} = await axios.get<Description>( + `${RangedWeaponsURL}/${id}/description?lang=${lanuage}`, { headers: { Accept: "application/json" } } @@ -94,11 +104,276 @@ async function getWeapon(id: string | undefined): Promise<RangedWeapon | null> } } -async function getWeaponDescription(id: string | undefined, lanuage: AllowedLanguages): Promise<Description | null> { +const defaultNewRangedWeapon: RangedWeapon = { + Id: "", + Name: "", + WeaponType: "Light pistol", + Accuracy: 0, + Concealability: "Pocket", + Avaliability: "Common", + Damage: "1d6", + Ammunition: "9mm", + NumberOfShots: 0, + RateOfFire: 0, + Reliability: "Standard", + Origin: "Corebook", + CreatedAt: 0, + UpdatedAt: 0, +} + +export function EditRangedWeaponPage() { + var user = useContext(AuthContext); + const [isLoading, setIsLoading] = useState(true); + const { id } = useParams(); + const lang = useContext(LanguageContext); + var [weapon, setWeapon] = useState<RangedWeapon | null>(null); + const [description, setDescription] = useState<Description | null>(null); + const [authCookie, ] = useCookies(['X-AUTH-TOKEN']); + + const [Name, setName] = useState<string>(); + const [WeaponType, setWeaponType] = useState<string>(); + const [Accuracy, setAccuracy] = useState<number>(); + const [Concealability, setConcealability] = useState<string>(); + const [Avaliability, setAvaliability] = useState<string>(); + const [Damage, setDamage] = useState<string>(); + const [Ammunition, setAmmunition] = useState<string>(); + const [NumberOfShots, setNumberOfShots] = useState<number>(); + const [RateOfFire, setRateOfFire] = useState<number>(); + const [Reliability, setReliability] = useState<string>(); + const [Origin, setOrigin] = useState<string>(); + const navigate = useNavigate(); + + useState(() => { + if (id == 'new') { + setWeapon(defaultNewRangedWeapon); + return + } + getWeapon(id).then(data => setWeapon(data)); + getWeaponDescription(id, lang).then(data => setDescription(data)); + }); + + useEffect(() => { + if (user !== null) { + setIsLoading(false); + } + }, [user]); + + useEffect(() => { + if (weapon) { + setName(weapon.Name); + setWeaponType(weapon.WeaponType); + setAccuracy(weapon.Accuracy); + setConcealability(weapon.Concealability); + setAvaliability(weapon.Avaliability); + setDamage(weapon.Damage); + setAmmunition(weapon.Ammunition); + setNumberOfShots(weapon.NumberOfShots); + setRateOfFire(weapon.RateOfFire); + setReliability(weapon.Reliability); + setOrigin(weapon.Origin); + } + }, [weapon]); + + function getUpdatedWeapon(): RangedWeaponRequest { + var outp: RangedWeaponRequest = { + Id: id || "", + Name: Name || "", + WeaponType: WeaponType || "", + Accuracy: Accuracy || 0, + Concealability: Concealability || "", + Avaliability: Avaliability || "", + Damage: Damage || "", + Ammunition: Ammunition || "", + NumberOfShots: NumberOfShots || 0, + RateOfFire: RateOfFire || 0, + Reliability: Reliability || "", + Origin: Origin || "", + }; + return outp; + } + + if (isLoading) { + return <div>Loading...</div>; + } + + if (!user || user.Role != "editor") { + throw new Response("Forbidden", { status: 403 }); + } + + if (weapon == null) { + return <h1> Not found </h1>; // <Navigate to="/404" replace />; + } + + return ( + <div className="WeaponPage"> + <span> + <input value={Name} onChange={(val) => { + setName(val.target.value); + }} /> + </span> + <select + value={WeaponType} + onChange={ (val) => { + const newWeaponType = val.target.value; + setWeaponType(newWeaponType); + }}> + <option value="Light pistol"> Light pistol </option> + <option value="Medium pistol"> Medium pistol </option> + <option value="Heavy pistol"> Heavy pistol </option> + <option value="Very heavy pistol"> Very heavy pistol </option> + + <option value="Light submachinegun"> Light submachinegun </option> + <option value="Medium submachinegun"> Medium submachinegun </option> + <option value="Heavy submachinegun"> Heavy submachinegun </option> + + <option value="Assault rifle"> Assault rifle </option> + <option value="Heavy weapon"> Heavy weapon </option> + <option value="Exotic"> Exotic </option> + + </select> + <table> + <tbody> + <tr> + <td> { GetLocalizedString("Accuracy", lang) } </td> + <td> + <input value={Accuracy} type="number" onChange={(val) => { + setAccuracy(+val.target.value); + }} /> + </td> + </tr> + + <tr> + <td> { GetLocalizedString("Concealability", lang) } </td> + <td> + <select + value={Concealability} + onChange={ (val) => { + const newConcealability = val.target.value; + setConcealability(newConcealability); + }}> + <option value="Pocket"> Pocket </option> + <option value="Jacket"> Jacket </option> + <option value="Long"> Long </option> + <option value="Nothing"> Nothing </option> + </select> + </td> + </tr> + + <tr> + <td> { GetLocalizedString("Avaliability", lang) } </td> + <td> + <select + value={Avaliability} + onChange={ (val) => { + const newAvaliability = val.target.value; + setAvaliability(newAvaliability); + }}> + <option value="Excellent"> Excellent </option> + <option value="Common"> Common </option> + <option value="Poor"> Poor </option> + <option value="Rare"> Rare </option> + </select> + </td> + </tr> + + <tr> + <td> { GetLocalizedString("Damage/Ammunition", lang) } </td> + <td> + <input + className="DmgInput" + value={Damage} + onChange={val => { + setDamage(val.target.value); + }}/> ( + <input + className="DmgInput" + value={Ammunition} + onChange={val => { + setAmmunition(val.target.value); + }}/> + ) + </td> + </tr> + + <tr> + <td> { GetLocalizedString("Number Of Shots", lang) } </td> + <td> + <input value={NumberOfShots} type="number" onChange={(val) => { + setNumberOfShots(+val.target.value); + }} /> + </td> + </tr> + + <tr> + <td> { GetLocalizedString("Rate Of Fire", lang) } </td> + <td> + <input value={RateOfFire} type="number" onChange={(val) => { + setRateOfFire(+val.target.value); + }} /> + </td> + </tr> + + <tr> + <td> { GetLocalizedString("Reliability", lang) } </td> + <td> + <select + value={Reliability} + onChange={ (val) => { + const newReliability = val.target.value; + setReliability(newReliability); + }}> + <option value="Very reliable"> Very reliable </option> + <option value="Standard"> Standard </option> + <option value="Unreliable"> Unreliable </option> + </select> + </td> + </tr> + + <tr> + <td> { GetLocalizedString("Origin", lang) } </td> + <td> + <select + value={Origin} + onChange={ (val) => { + const newOrigin = val.target.value; + setOrigin(newOrigin); + }}> + <option value="Corebook"> Corebook </option> + </select> + </td> + </tr> + </tbody> + </table> + + <button onClick={ () => { + const updated = getUpdatedWeapon(); + if (id == "new") { + CreateRangedWeapon(updated, authCookie['X-AUTH-TOKEN']); + } else { + UpdateRangedWeapon(id, updated, authCookie['X-AUTH-TOKEN']); + } + navigate('/weapons'); + document.location.reload(); + }}> Update </button> + <button onClick={ () => { + if (id == "new") return; + DeleteRangedWeapon(id, authCookie['X-AUTH-TOKEN']); + navigate('/weapons'); + document.location.reload(); + }}> Delete </button> + + <p> + {description?.Contents} + </p> + </div> + ); +} + +async function getWeapon(id: string | undefined): Promise<RangedWeapon | null> { if (id == undefined) return null; try { - const {data, status} = await axios.get<Description>( - `${RangedWeaponsURL}/${id}/description?lang=${lanuage}`, + const {data, status} = await axios.get<RangedWeapon>( + `${RangedWeaponsURL}/${id}`, { headers: { Accept: "application/json" } } @@ -114,14 +389,64 @@ async function getWeaponDescription(id: string | undefined, lanuage: AllowedLang } } -export function EditRangedWeaponPage() { - var user = useContext(AuthContext); +async function UpdateRangedWeapon(id: string | undefined, model: RangedWeaponRequest, token: string) { + if (id == undefined) return null; + const uri = `${RangedWeaponsURL}/${id}`; - if (user == null || user.Role != "editor") { + await axios.put(uri, model, { + headers: { + Authorization: `Bearer ${token}` + } + }) + .then(resp => { + console.log(resp); + }) + .catch(err => { + console.log(`Failed to update the model: ${err}`) + }); +} + +function generateId(Name: string): string { + return Name.toLowerCase().replaceAll(' ', '_'); +} + +async function CreateRangedWeapon(model: RangedWeaponRequest, token: string) { + if (model.Name.length == 0) { + throw new Error('The model name should not be empty'); + } + model.Id = generateId(model.Name); + if (model.Id == "new") { + throw new Error("Forbidden name"); } - return ( - <div> - </div> - ); + await axios.post(RangedWeaponsURL, model, { + headers: { + Authorization: `Bearer ${token}` + } + }) + .then(resp => { + console.log(resp); + }) + .catch(err => { + console.log(`Failed to create the model: ${err}`) + }); +} + +async function DeleteRangedWeapon(id: string | undefined, token: string) { + if (id == undefined || id == "new" || id.length < 1) { + throw new Error("Invalid ID"); + } + + const uri = `${RangedWeaponsURL}/${id}`; + await axios.delete(uri, { + headers: { + Authorization: `Bearer ${token}` + } + }) + .then(resp => { + console.log(resp); + }) + .catch(err => { + console.log(`Failed to create the model: ${err}`) + }); } diff --git a/front/src/Pages/Weapons.css b/front/src/Pages/Weapons.css index 41d820c..7f29770 100644 --- a/front/src/Pages/Weapons.css +++ b/front/src/Pages/Weapons.css @@ -23,3 +23,7 @@ td { .WeaponPage > span { display: flex; } + +.DmgInput { + max-width: 40%; +} diff --git a/front/src/Pages/Weapons.tsx b/front/src/Pages/Weapons.tsx index 1b72f16..d083ebf 100644 --- a/front/src/Pages/Weapons.tsx +++ b/front/src/Pages/Weapons.tsx @@ -5,11 +5,14 @@ import { LanguageContext } from "../Locales/Context"; import { GetLocalizedString } from "../Locales/Locales"; import { AllowedLanguages } from "../Locales/Context"; import { RangedWeapon } from "../Models/RangedWeapon"; +import { AuthContext } from "../Authentication/ContextProvider"; +import { User } from "../Authentication/Models"; const RangedWeaponsURL = `${BackendURL}/weapons/ranged`; function WeaponsIndex() { const lang = useContext(LanguageContext); + var user = useContext(AuthContext); const [rangedWeapons, setRangedWeapons] = useState<RangedWeapon[] | null>(null); useState(() => { @@ -22,7 +25,8 @@ function WeaponsIndex() { <h1> {GetLocalizedString("Weapons index", lang)} </h1> </span> <h2> {GetLocalizedString("Ranged weapons", lang)} </h2> - {generatedRangedWeaponsList(rangedWeapons, lang)} + { generatedRangedWeaponsList(rangedWeapons, lang) } + { adminButtons(user) } </div> ); } @@ -109,4 +113,16 @@ async function getRangedWeapons(): Promise<RangedWeapon[]> { } } +function adminButtons(user: User | null) { + if (!user || user.Role != "editor") { + return (<p></p>); + } else { + return ( + <div> + <a href="/weapons/new/edit"> New ranged weapon </a> + </div> + ) + } +} + export default WeaponsIndex; |
