summaryrefslogtreecommitdiff
path: root/front/src
diff options
context:
space:
mode:
authorphyscik <mynameisgennadiy@vk.com>2026-05-07 15:44:45 +0500
committerphyscik <mynameisgennadiy@vk.com>2026-05-07 15:44:45 +0500
commitffbeed38434ece7be28acdf7e3bd900c801a636d (patch)
treeac8f7087b4b4ee0ae4a6ac8f9673d2a90e424775 /front/src
parent164b76fad806b078aad8c537ba9bdcfbfc757dca (diff)
Ranged weapons edit page
Diffstat (limited to 'front/src')
-rw-r--r--front/src/App.tsx18
-rw-r--r--front/src/Authentication/Models.ts1
-rw-r--r--front/src/Emelents/Topbar.tsx2
-rw-r--r--front/src/ErrorHandler.tsx4
-rw-r--r--front/src/Pages/RangedWeapon.tsx359
-rw-r--r--front/src/Pages/Weapons.css4
-rw-r--r--front/src/Pages/Weapons.tsx18
7 files changed, 380 insertions, 26 deletions
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;