add redux and user slice
This commit is contained in:
+29
-24
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import './App.css';
|
||||
import * as React from "react";
|
||||
import "./App.css";
|
||||
import {Faro} from "@grafana/faro-web-sdk";
|
||||
import {createBrowserRouter, redirect, RouterProvider} from "react-router-dom";
|
||||
import CreateCharacter from "./first-edition/pages/CreateCharacter";
|
||||
@@ -7,16 +7,18 @@ import CharacterList from "./shared/pages/CharacterList";
|
||||
import ErrorBoundary from "./shared/ErrorBoundary";
|
||||
import Header from "./shared/Header";
|
||||
import Footer from "./shared/Footer";
|
||||
import {useState} from "react";
|
||||
import {UserContext, userDefaults} from './shared/contexts/UserContext';
|
||||
import CreateCharacter2E from "./second-edition/pages/CreateCharacter2E";
|
||||
import store, {AppDispatch, RootState} from './store'
|
||||
import { Provider } from "react-redux"
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import {load, setUser, unsetUser, userSelector} from './shared/domain-models/userSlice'
|
||||
|
||||
function App(props: {faro?: Faro}) {
|
||||
|
||||
const [fetchedUser, setFetchedUser] = useState(userDefaults)
|
||||
function Router(props: {faro?: Faro}) {
|
||||
const dispatch = useDispatch.withTypes<AppDispatch>()()
|
||||
const fetchedUser = useSelector.withTypes<RootState>()(userSelector)
|
||||
|
||||
if (fetchedUser.state === "not-started") {
|
||||
setFetchedUser({...fetchedUser, state: "loading"})
|
||||
dispatch(load())
|
||||
fetch(`${window.location.origin}/api/User/me`)
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
@@ -26,23 +28,19 @@ function App(props: {faro?: Faro}) {
|
||||
})
|
||||
.then(userNameResponse => {
|
||||
const userName = userNameResponse && userNameResponse.length > 0 ? userNameResponse : null;
|
||||
setFetchedUser({
|
||||
data: userName,
|
||||
state: "finished"
|
||||
});
|
||||
dispatch(setUser(userName))
|
||||
})
|
||||
.catch(() => {
|
||||
setFetchedUser({
|
||||
data: null,
|
||||
state: "finished"
|
||||
})
|
||||
dispatch(unsetUser())
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
path: "/",
|
||||
loader: () => fetchedUser.data == null ? redirect("/2e/karakter") : redirect("/karaktereim")
|
||||
loader: () => fetchedUser.email == null ? redirect("/2e/karakter") : redirect("/karaktereim")
|
||||
},
|
||||
{
|
||||
path: "/karaktereim",
|
||||
@@ -81,15 +79,22 @@ function App(props: {faro?: Faro}) {
|
||||
ErrorBoundary: ErrorBoundary,
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
return (
|
||||
<UserContext.Provider value={fetchedUser}>
|
||||
<div className='container'>
|
||||
<RouterProvider router={router}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function App(props: { faro?: Faro }) {
|
||||
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<Header/>
|
||||
<div className='container'>
|
||||
<RouterProvider router={router}/>
|
||||
</div>
|
||||
<Router faro={props.faro} />
|
||||
<Footer />
|
||||
</UserContext.Provider>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {useContext, useState} from 'react';
|
||||
import React, {useState} from 'react';
|
||||
import './CreateCharacter.css'
|
||||
import {Faj, TulajdonsagokFajjal} from "../domain-models/faj";
|
||||
import {Osztaly, SetFelszerelesForChangedOsztaly} from "../domain-models/osztaly"
|
||||
@@ -31,8 +31,10 @@ import {Faro} from "@grafana/faro-web-sdk";
|
||||
import {StoreNewCharacter, UpdateCharacter} from "../api/character.api";
|
||||
import {useLoaderData, useNavigate, useParams} from "react-router-dom";
|
||||
import saveOverlayTooltip from "../components/SaveOverlayTooltip";
|
||||
import {UserContext} from "../../shared/contexts/UserContext";
|
||||
import {KarakterTulajdonsagok} from "../domain-models/tulajdonsag";
|
||||
import {useSelector} from "react-redux";
|
||||
import {RootState} from "../../store";
|
||||
import {userSelector} from "../../shared/domain-models/userSlice";
|
||||
|
||||
function CreateCharacterPage(props: {
|
||||
faro?: Faro,
|
||||
@@ -46,9 +48,9 @@ function CreateCharacterPage(props: {
|
||||
initialKarakterInputs = t2
|
||||
}
|
||||
const { id } = useParams();
|
||||
const fetchedUser = useContext(UserContext);
|
||||
const fetchedUser = useSelector.withTypes<RootState>()(userSelector);
|
||||
|
||||
const initialIsPublic = loaderData?.isPublic ?? fetchedUser?.data == null;
|
||||
const initialIsPublic = loaderData?.isPublic ?? fetchedUser?.email == null;
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -257,7 +259,7 @@ function CreateCharacterPage(props: {
|
||||
<h5 className='col align-self-center'>Véglegesítés</h5>
|
||||
</div>
|
||||
|
||||
{fetchedUser.data != null &&
|
||||
{fetchedUser.email != 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()}
|
||||
@@ -270,7 +272,7 @@ function CreateCharacterPage(props: {
|
||||
<div className="row">
|
||||
<div className="col-6">
|
||||
<div className='d-grid gap-2 m-5'>
|
||||
{fetchedUser.data == null
|
||||
{fetchedUser.email == null
|
||||
? <OverlayTrigger placement='top' overlay={saveOverlayTooltip} delay={0}
|
||||
defaultShow={false} flip={false}>
|
||||
<button className='btn btn-danger btn-lg' type='button'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {useContext, useState} from 'react'
|
||||
import React, {useState} from 'react'
|
||||
import {useLoaderData, useNavigate, useParams} from "react-router-dom";
|
||||
import {Button, InputGroup, Modal, OverlayTrigger, Toast, ToastContainer} from "react-bootstrap";
|
||||
import {ChangeLvl1Osztaly, DefaultKarakter, Karakter2E} from "../domain-models/karakter2E";
|
||||
@@ -25,7 +25,6 @@ import {LevelDown, LevelUp} from "../domain-models/szintlepes";
|
||||
import HarcosFegyverSpecializacio from "../components/HarcosFegyverSpec";
|
||||
import {arraySetN} from "../../util";
|
||||
import saveOverlayTooltip from "../../first-edition/components/SaveOverlayTooltip";
|
||||
import {UserContext} from "../../shared/contexts/UserContext";
|
||||
import {
|
||||
StoreNewCharacter2E,
|
||||
UpdateCharacter2E
|
||||
@@ -34,20 +33,23 @@ import {Faro} from "@grafana/faro-web-sdk";
|
||||
import Form from "react-bootstrap/Form";
|
||||
import {CreatePDF} from "../pdf/character.pdf";
|
||||
import {KarakterInputToPdfView} from "../pdf/karakter_pdf_view";
|
||||
import {useSelector} from "react-redux";
|
||||
import {RootState} from "../../store";
|
||||
import {userSelector} from "../../shared/domain-models/userSlice";
|
||||
|
||||
function CreateCharacter2E(props: {
|
||||
faro?: Faro
|
||||
}) {
|
||||
const {faro} = props
|
||||
const { id } = useParams();
|
||||
const fetchedUser = useContext(UserContext);
|
||||
const fetchedUser = useSelector.withTypes<RootState>()(userSelector);
|
||||
const loaderData = useLoaderData() as Karakter2E & { isPublic: boolean } | undefined;
|
||||
let initialKarakterInputs = DefaultKarakter;
|
||||
if (loaderData != null) {
|
||||
const { isPublic: t1, ...t2 } = loaderData;
|
||||
initialKarakterInputs = t2
|
||||
}
|
||||
const initialIsPublic = loaderData?.isPublic ?? fetchedUser?.data == null;
|
||||
const initialIsPublic = loaderData?.isPublic ?? fetchedUser?.email == null;
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [karakter, setKarakter] = useState(initialKarakterInputs)
|
||||
@@ -234,7 +236,7 @@ function CreateCharacter2E(props: {
|
||||
<h5 className='col align-self-center'>Véglegesítés</h5>
|
||||
</div>
|
||||
|
||||
{fetchedUser.data != null &&
|
||||
{fetchedUser.email != 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()}
|
||||
@@ -247,7 +249,7 @@ function CreateCharacter2E(props: {
|
||||
<div className="row">
|
||||
<div className="col-6">
|
||||
<div className='d-grid gap-2 m-5'>
|
||||
{fetchedUser.data == null
|
||||
{fetchedUser.email == null
|
||||
? <OverlayTrigger
|
||||
placement='top'
|
||||
overlay={saveOverlayTooltip}
|
||||
|
||||
@@ -1,28 +1,29 @@
|
||||
import * as React from "react";
|
||||
import {useContext} from "react";
|
||||
import axios from "axios";
|
||||
import {Container, Nav, Navbar} from "react-bootstrap";
|
||||
import { UserContext } from "./contexts/UserContext";
|
||||
import {userSelector} from "./domain-models/userSlice";
|
||||
import {RootState} from "../store";
|
||||
import {useSelector} from "react-redux";
|
||||
|
||||
async function logout() {
|
||||
await axios.post("/Identity/Account/Logout")
|
||||
document.location.reload()
|
||||
}
|
||||
|
||||
function Header(props: {}) {
|
||||
function Header() {
|
||||
|
||||
const fetchedUser = useContext(UserContext);
|
||||
const fetchedUser = useSelector.withTypes<RootState>()(userSelector);
|
||||
|
||||
return <header>
|
||||
<Navbar expand="sm" className="navbar-light bg-white border-bottom box-shadow mb-3">
|
||||
<Container className="ps-4 pe-4">
|
||||
<Navbar.Brand href="/">Kemkas</Navbar.Brand>
|
||||
{fetchedUser.data != null && <Nav.Link className="text-dark" href="/karaktereim">Karaktereim</Nav.Link>}
|
||||
{fetchedUser.email != null && <Nav.Link className="text-dark" href="/karaktereim">Karaktereim</Nav.Link>}
|
||||
<Navbar.Toggle aria-controls="basic-navbar-nav" />
|
||||
<Navbar.Collapse id="basic-navbar-nav" className="d-sm-inline-flex flex-sm-row-reverse">
|
||||
<Nav>
|
||||
{fetchedUser.state !== "finished" ? "Loading..." :
|
||||
fetchedUser.data == null
|
||||
fetchedUser.email == null
|
||||
? <>
|
||||
<li className="nav-item">
|
||||
<a className="nav-link text-dark" href="/Identity/Account/Register">Register</a>
|
||||
@@ -34,7 +35,7 @@ function Header(props: {}) {
|
||||
: <>
|
||||
<li className="nav-item">
|
||||
<a className="nav-link text-dark"
|
||||
href="/Identity/Account/Manage">Hello {fetchedUser.data}!</a>
|
||||
href="/Identity/Account/Manage">Hello {fetchedUser.email}!</a>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<button className="nav-link nav text-dark" onClick={logout}>Logout</button>
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { createSlice } from '@reduxjs/toolkit'
|
||||
|
||||
type LoadingState = "not-started" | "loading" | "finished"
|
||||
|
||||
export const userSlice = createSlice({
|
||||
name: 'user',
|
||||
initialState: {
|
||||
state: "not-started" as LoadingState,
|
||||
email: null,
|
||||
},
|
||||
reducers: {
|
||||
load: (state) => {
|
||||
state.state = "loading"
|
||||
},
|
||||
setUser: (state, action) => {
|
||||
state.state = "finished"
|
||||
state.email = action.payload
|
||||
},
|
||||
unsetUser: (state) => {
|
||||
state.state = "finished"
|
||||
state.email = null
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
// Action creators are generated for each case reducer function
|
||||
export const { load, setUser, unsetUser } = userSlice.actions
|
||||
|
||||
export const userSelector = userSlice.selectSlice
|
||||
|
||||
export default userSlice.reducer
|
||||
@@ -0,0 +1,15 @@
|
||||
import { configureStore } from "@reduxjs/toolkit"
|
||||
import userReducer from './shared/domain-models/userSlice'
|
||||
|
||||
const store = configureStore({
|
||||
reducer: {
|
||||
user: userReducer
|
||||
},
|
||||
});
|
||||
|
||||
// Infer the `RootState` and `AppDispatch` types from the store itself
|
||||
export type RootState = ReturnType<typeof store.getState>
|
||||
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
|
||||
export type AppDispatch = typeof store.dispatch
|
||||
|
||||
export default store
|
||||
Reference in New Issue
Block a user