better isPublic handling

This commit is contained in:
2023-12-28 14:45:59 +01:00
parent 037ede2b9e
commit 76d3b6d423
10 changed files with 118 additions and 52 deletions
@@ -17,7 +17,7 @@ public class CharacterController(
: ControllerBase : ControllerBase
{ {
[HttpPost] [HttpPost]
public async Task<ActionResult<Guid>> StoreNewCharacter([FromBody] CharacterDto dto, bool isPublic = true) public async Task<ActionResult<Guid>> StoreNewCharacter([FromBody] CharacterDto dto, [FromQuery]bool isPublic = true)
{ {
if (!ModelState.IsValid) if (!ModelState.IsValid)
{ {
@@ -55,7 +55,7 @@ public class CharacterController(
} }
[HttpPost("{id:guid}")] [HttpPost("{id:guid}")]
public async Task<ActionResult<Guid>> UpdateCharacter([FromRoute] Guid id, [FromBody] CharacterDto dto, bool isPublic = true) public async Task<ActionResult<Guid>> UpdateCharacter([FromRoute] Guid id, [FromBody] CharacterDto dto, [FromQuery]bool isPublic = true)
{ {
if (!ModelState.IsValid) if (!ModelState.IsValid)
{ {
+2
View File
@@ -1,5 +1,6 @@
using System.Net; using System.Net;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using System.Text.Json.Serialization;
using System.Text.Unicode; using System.Text.Unicode;
using Kemkas.Web.Config; using Kemkas.Web.Config;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@@ -55,6 +56,7 @@ builder.Services.AddAuthentication()
builder.Services.AddControllersWithViews().AddJsonOptions(options => builder.Services.AddControllersWithViews().AddJsonOptions(options =>
{ {
options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault;
options.JsonSerializerOptions.AllowTrailingCommas = true; options.JsonSerializerOptions.AllowTrailingCommas = true;
options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All); options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
}); });
@@ -44,6 +44,7 @@ public class CharacterDbModelToDtoService : ICharacterDbModelToDtoService
PancelId = entity.Pancel, PancelId = entity.Pancel,
FegyverIds = entity.Felszereles.Where(x => x.IsFegyver).Select(x => x.Name).ToList(), FegyverIds = entity.Felszereles.Where(x => x.IsFegyver).Select(x => x.Name).ToList(),
}, },
IsPublic = entity.IsPublic,
}; };
} }
} }
+2
View File
@@ -53,4 +53,6 @@ public struct CharacterDto
[JsonPropertyName("felszereles")] [JsonPropertyName("felszereles")]
public KarakterFelszerelesDto Felszereles { get; set; } public KarakterFelszerelesDto Felszereles { get; set; }
public bool? IsPublic { get; set; }
} }
+29 -2
View File
@@ -7,6 +7,8 @@ import CharacterList from "./first-edition/pages/CharacterList";
import ErrorBoundary from "./shared/ErrorBoundary"; import ErrorBoundary from "./shared/ErrorBoundary";
import Header from "./shared/Header"; import Header from "./shared/Header";
import Footer from "./shared/Footer"; import Footer from "./shared/Footer";
import {useState} from "react";
import {UserContext, userDefaults} from './shared/contexts/UserContext';
function App(props: {faro?: Faro}) { function App(props: {faro?: Faro}) {
const router = createBrowserRouter([ const router = createBrowserRouter([
@@ -29,15 +31,40 @@ function App(props: {faro?: Faro}) {
} }
]); ]);
const [fetchedUser, setFetchedUser] = useState(userDefaults)
if (fetchedUser.state === "not-started") {
setFetchedUser({...fetchedUser, state: "loading"})
fetch('api/User/me')
.then(response => {
if (response.ok) {
return response.text();
}
console.log(response)
})
.then(userNameResponse => {
const userName = userNameResponse && userNameResponse.length > 0 ? userNameResponse : null;
setFetchedUser({
data: userName,
state: "finished"
});
})
.catch(() => {
setFetchedUser({
data: null,
state: "finished"
})
})
}
return ( return (
<> <UserContext.Provider value={fetchedUser}>
<Header/> <Header/>
<div className='container'> <div className='container'>
<RouterProvider router={router}/> <RouterProvider router={router}/>
</div> </div>
<Footer /> <Footer />
</> </UserContext.Provider>
); );
} }
@@ -1,9 +1,12 @@
import {KarakterInputs} from "../domain-models/karakter"; import {KarakterInputs} from "../domain-models/karakter";
import axios from "axios"; import axios from "axios";
export async function StoreNewCharacter(karakter: KarakterInputs) { export async function StoreNewCharacter(karakter: KarakterInputs, isPublic: boolean = false) {
let response = await axios.post(`${window.location.origin}/api/Character/`, karakter, { let response = await axios.post(`${window.location.origin}/api/Character/`, karakter, {
withCredentials: true, withCredentials: true,
params: {
isPublic,
}
}) })
if (response.status < 300){ if (response.status < 300){
return response.data as string return response.data as string
@@ -13,9 +16,12 @@ export async function StoreNewCharacter(karakter: KarakterInputs) {
} }
} }
export async function UpdateCharacter(id: string, karakter: KarakterInputs) { export async function UpdateCharacter(id: string, karakter: KarakterInputs, isPublic: boolean = false) {
let response = await axios.post(`${window.location.origin}/api/Character/${id}`, karakter, { let response = await axios.post(`${window.location.origin}/api/Character/${id}`, karakter, {
withCredentials: true, withCredentials: true,
params: {
isPublic,
}
}) })
if (response.status < 300){ if (response.status < 300){
return response.data as string return response.data as string
@@ -1,4 +1,4 @@
import React, {useState} from 'react'; import React, {useContext, useState} from 'react';
import './CreateCharacter.css' import './CreateCharacter.css'
import {Faj} from "../domain-models/faj"; import {Faj} from "../domain-models/faj";
import {Osztaly, SetFelszerelesForChangedOsztaly} from "../domain-models/osztaly" import {Osztaly, SetFelszerelesForChangedOsztaly} from "../domain-models/osztaly"
@@ -31,14 +31,16 @@ import {Faro} from "@grafana/faro-web-sdk";
import {StoreNewCharacter, UpdateCharacter} from "../api/character.api"; import {StoreNewCharacter, UpdateCharacter} from "../api/character.api";
import {useLoaderData, useParams} from "react-router-dom"; import {useLoaderData, useParams} from "react-router-dom";
import saveOverlayTooltip from "../components/SaveOverlayTooltip"; import saveOverlayTooltip from "../components/SaveOverlayTooltip";
import {UserContext} from "../../shared/contexts/UserContext";
function CreateCharacterPage(props: { function CreateCharacterPage(props: {
faro?: Faro, faro?: Faro,
}) { }) {
const {faro} = props const {faro} = props
const initialKarakterInputs = useLoaderData() as KarakterInputs; const { isPublic: initialIsPublic, ...initialKarakterInputs} = useLoaderData() as KarakterInputs & { isPublic: boolean };
const { id } = useParams(); const { id } = useParams();
const fetchedUser = useContext(UserContext);
let [karakter, changeKarakter] = useState(initialKarakterInputs ?? KarakterDefaults) let [karakter, changeKarakter] = useState(initialKarakterInputs ?? KarakterDefaults)
@@ -58,8 +60,30 @@ function CreateCharacterPage(props: {
const canLevelUp = CanLevelUp(karakter) const canLevelUp = CanLevelUp(karakter)
let [showSaved, setShowSaved] = useState(false); let [showSaved, setShowSaved] = useState(false);
let [isPublic, setIsPublic] = useState(initialIsPublic);
const hideSaved = () => setShowSaved(false); const hideSaved = () => setShowSaved(false);
const onSaveClicked = async () => {
faro?.api.pushEvent('character_stored', {
osztaly: karakter.osztaly,
szint: karakter.szint.toString(),
faj: karakter.faj.toString(),
is_public: isPublic.toString(),
})
if (id == null) {
let newId = await StoreNewCharacter(karakter, isPublic);
const win = window.open(`/${newId}`, '_blank');
if (win != null) {
win.focus();
}
} else {
await UpdateCharacter(id, karakter, isPublic);
setShowSaved(true);
setTimeout(() => {
hideSaved();
}, 5000)
}
}
return ( return (
<div> <div>
@@ -182,32 +206,34 @@ function CreateCharacterPage(props: {
<Felszereles osztaly={karakter.osztaly} felszereles={karakter.felszereles} <Felszereles osztaly={karakter.osztaly} felszereles={karakter.felszereles}
changeFelszereles={setFelszereles} harcosSpec={karakter.harcosSpecializaciok}/> changeFelszereles={setFelszereles} harcosSpec={karakter.harcosSpecializaciok}/>
<hr/>
<div className='row'>
<h5 className='col align-self-center'>Véglegesítés</h5>
</div>
{fetchedUser.data != null &&
<div className='row m-2'>
<label className='col-md-2 col-sm-3 col-form-label'>Publikus karakterlap?</label>
<select className="col form-select" value={isPublic.toString()}
onChange={(event) => setIsPublic(event.target.value === "true")}>
<option value="true">Igen</option>
<option value="false">Nem</option>
</select>
</div>
}
<div className="row"> <div className="row">
<div className="col-6"> <div className="col-6">
<div className='d-grid gap-2 m-5'> <div className='d-grid gap-2 m-5'>
<OverlayTrigger placement='top' overlay={saveOverlayTooltip} delay={0} defaultShow={false} flip={false}> {fetchedUser.data == null
<button className='btn btn-danger btn-lg' type='button' onClick={async () => { ? <OverlayTrigger placement='top' overlay={saveOverlayTooltip} delay={0}
faro?.api.pushEvent('public_character_stored', { defaultShow={false} flip={false}>
osztaly: karakter.osztaly, <button className='btn btn-danger btn-lg' type='button'
szint: karakter.szint.toString(), onClick={onSaveClicked}>Mentés
faj: karakter.faj.toString(), </button>
}) </OverlayTrigger>
if (id == null) { : <button className='btn btn-danger btn-lg' type='button'
let newId = await StoreNewCharacter(karakter); onClick={onSaveClicked}>Mentés</button>
const win = window.open(`/${newId}`, '_blank'); }
if (win != null) {
win.focus();
}
} else {
await UpdateCharacter(id, karakter);
setShowSaved(true);
setTimeout(() => {
hideSaved();
}, 5000)
}
}}>Mentés
</button>
</OverlayTrigger>
</div> </div>
</div> </div>
<div className="col-6"> <div className="col-6">
+6 -20
View File
@@ -1,9 +1,8 @@
import * as React from "react"; import * as React from "react";
import {useState} from "react"; import {useContext} from "react";
import axios from "axios"; import axios from "axios";
import {Container, Nav, Navbar} from "react-bootstrap"; import {Container, Nav, Navbar} from "react-bootstrap";
import { UserContext } from "./contexts/UserContext";
type LoadingState = "not-started" | "loading" | "finished"
async function logout() { async function logout() {
await axios.post("/Identity/Account/Logout") await axios.post("/Identity/Account/Logout")
@@ -12,20 +11,7 @@ async function logout() {
function Header(props: {}) { function Header(props: {}) {
let [loading, setLoading] = useState("not-started" as LoadingState); const fetchedUser = useContext(UserContext);
let [userName, setUserName] = useState(null as string | null);
if (loading === "not-started") {
setLoading("loading");
fetch('api/User/me')
.then(response => {
response.ok
? response.text()
.then(userName => userName?.length > 0 ? setUserName(userName) : setUserName(null))
: console.log(response)
})
.finally(() => setLoading("finished"))
}
return <header> return <header>
<Navbar expand="sm" className="navbar-light bg-white border-bottom box-shadow mb-3"> <Navbar expand="sm" className="navbar-light bg-white border-bottom box-shadow mb-3">
@@ -35,8 +21,8 @@ function Header(props: {}) {
<Navbar.Toggle aria-controls="basic-navbar-nav" /> <Navbar.Toggle aria-controls="basic-navbar-nav" />
<Navbar.Collapse id="basic-navbar-nav" className="d-sm-inline-flex flex-sm-row-reverse"> <Navbar.Collapse id="basic-navbar-nav" className="d-sm-inline-flex flex-sm-row-reverse">
<Nav> <Nav>
{loading !== "finished" ? "Loading..." : {fetchedUser.state !== "finished" ? "Loading..." :
userName == null fetchedUser.data == null
? <> ? <>
<li className="nav-item"> <li className="nav-item">
<a className="nav-link text-dark" href="/Identity/Account/Register">Register</a> <a className="nav-link text-dark" href="/Identity/Account/Register">Register</a>
@@ -48,7 +34,7 @@ function Header(props: {}) {
: <> : <>
<li className="nav-item"> <li className="nav-item">
<a className="nav-link text-dark" <a className="nav-link text-dark"
href="/Identity/Account/Manage">Hello {userName}!</a> href="/Identity/Account/Manage">Hello {fetchedUser.data}!</a>
</li> </li>
<li className="nav-item"> <li className="nav-item">
<button className="nav-link nav text-dark" onClick={logout}>Logout</button> <button className="nav-link nav text-dark" onClick={logout}>Logout</button>
@@ -0,0 +1,10 @@
import { Fetched } from "../models/Fetched";
import React from "react";
export const userDefaults: Fetched<string | null> = {
state: "not-started",
data: null,
}
export const UserContext = React.createContext(userDefaults)
UserContext.displayName = "UserEmailContext";
@@ -0,0 +1,6 @@
export type LoadingState = "not-started" | "loading" | "finished"
export interface Fetched<T> {
data: T;
state: LoadingState;
}