refactor API calls and routers
This commit is contained in:
@@ -3,6 +3,11 @@ import { render } from '@testing-library/react';
|
|||||||
import { vi } from 'vitest';
|
import { vi } from 'vitest';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
|
|
||||||
|
vi.mock('./shared/api/http', () => ({
|
||||||
|
getText: vi.fn(async () => ''),
|
||||||
|
getJson: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
vi.mock('react-router-dom', async () => {
|
vi.mock('react-router-dom', async () => {
|
||||||
const actual = await vi.importActual<typeof import('react-router-dom')>('react-router-dom');
|
const actual = await vi.importActual<typeof import('react-router-dom')>('react-router-dom');
|
||||||
|
|
||||||
|
|||||||
+100
-72
@@ -1,9 +1,9 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
import {Faro} from "@grafana/faro-web-sdk";
|
import {Faro} from "@grafana/faro-web-sdk";
|
||||||
import {createBrowserRouter, Outlet, redirect, RouterProvider} from "react-router-dom";
|
import {createBrowserRouter, LoaderFunctionArgs, Navigate, Outlet, redirect, RouterProvider} from "react-router-dom";
|
||||||
import CreateCharacter from "./first-edition/pages/CreateCharacter";
|
import CreateCharacter from "./first-edition/pages/CreateCharacter";
|
||||||
import CharacterList from "./shared/pages/CharacterList";
|
import CharacterList, {CharacterListItemDto} from "./shared/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";
|
||||||
@@ -13,6 +13,11 @@ import { Provider } from "react-redux"
|
|||||||
import { useSelector, useDispatch } from 'react-redux'
|
import { useSelector, useDispatch } from 'react-redux'
|
||||||
import {load, setUser, unsetUser, userSelector} from './shared/domain-models/userSlice'
|
import {load, setUser, unsetUser, userSelector} from './shared/domain-models/userSlice'
|
||||||
import {setCharacter} from "./second-edition/domain-models/characterSlice";
|
import {setCharacter} from "./second-edition/domain-models/characterSlice";
|
||||||
|
import {getJson, getText} from "./shared/api/http";
|
||||||
|
import {KarakterInputs} from "./first-edition/domain-models/karakter";
|
||||||
|
import {Karakter2E} from "./second-edition/domain-models/karakter2E";
|
||||||
|
|
||||||
|
const FaroContext = React.createContext<Faro | undefined>(undefined)
|
||||||
|
|
||||||
function RootLayout() {
|
function RootLayout() {
|
||||||
return (
|
return (
|
||||||
@@ -26,7 +31,27 @@ function RootLayout() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function Router(props: {faro?: Faro}) {
|
function IndexRedirect() {
|
||||||
|
const fetchedUser = useSelector.withTypes<RootState>()(userSelector)
|
||||||
|
|
||||||
|
if (fetchedUser.state !== "finished") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Navigate replace to={fetchedUser.email == null ? "/2e/karakter" : "/karaktereim"} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function CreateCharacter1ERoute() {
|
||||||
|
const faro = React.useContext(FaroContext)
|
||||||
|
return <CreateCharacter faro={faro} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function CreateCharacter2ERoute() {
|
||||||
|
const faro = React.useContext(FaroContext)
|
||||||
|
return <CreateCharacter2E faro={faro} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function UserBootstrap() {
|
||||||
const dispatch = useDispatch.withTypes<AppDispatch>()()
|
const dispatch = useDispatch.withTypes<AppDispatch>()()
|
||||||
const fetchedUser = useSelector.withTypes<RootState>()(userSelector)
|
const fetchedUser = useSelector.withTypes<RootState>()(userSelector)
|
||||||
|
|
||||||
@@ -37,13 +62,7 @@ function Router(props: {faro?: Faro}) {
|
|||||||
|
|
||||||
let isCancelled = false;
|
let isCancelled = false;
|
||||||
dispatch(load())
|
dispatch(load())
|
||||||
fetch(`${window.location.origin}/api/User/me`)
|
getText("/api/User/me")
|
||||||
.then(response => {
|
|
||||||
if (response.ok) {
|
|
||||||
return response.text();
|
|
||||||
}
|
|
||||||
console.log(response)
|
|
||||||
})
|
|
||||||
.then(userNameResponse => {
|
.then(userNameResponse => {
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
return;
|
return;
|
||||||
@@ -68,74 +87,83 @@ function Router(props: {faro?: Faro}) {
|
|||||||
}
|
}
|
||||||
}, [dispatch, fetchedUser.state])
|
}, [dispatch, fetchedUser.state])
|
||||||
|
|
||||||
const router = React.useMemo(() => createBrowserRouter([
|
return null;
|
||||||
{
|
|
||||||
path: "/",
|
|
||||||
element: <RootLayout />,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
index: true,
|
|
||||||
loader: () => fetchedUser.email == null ? redirect("/2e/karakter") : redirect("/karaktereim")
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "karaktereim",
|
|
||||||
element: <CharacterList />,
|
|
||||||
loader: () => fetch(`${window.location.origin}/api/Character`),
|
|
||||||
ErrorBoundary: ErrorBoundary,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "1e",
|
|
||||||
loader: () => redirect("/1e/karakter")
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "1e/karakter",
|
|
||||||
element: <CreateCharacter faro={props.faro} />,
|
|
||||||
ErrorBoundary: ErrorBoundary,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "1e/karakter/:id",
|
|
||||||
element: <CreateCharacter faro={props.faro}/>,
|
|
||||||
loader: args => fetch(`${window.location.origin}/api/Character1E/${args.params.id}`),
|
|
||||||
ErrorBoundary: ErrorBoundary,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "2e",
|
|
||||||
loader: () => redirect("/2e/karakter")
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "2e/karakter",
|
|
||||||
element: <CreateCharacter2E faro={props.faro}/>,
|
|
||||||
ErrorBoundary: ErrorBoundary,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "2e/karakter/:id",
|
|
||||||
element: <CreateCharacter2E faro={props.faro}/>,
|
|
||||||
loader: async args => {
|
|
||||||
const response = await fetch(`${window.location.origin}/api/Character2E/${args.params.id}`)
|
|
||||||
if (response.ok) {
|
|
||||||
const loaded2Echaracter = await response.json()
|
|
||||||
dispatch(setCharacter(loaded2Echaracter))
|
|
||||||
return loaded2Echaracter;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
isPublic: false,
|
|
||||||
error: response.statusText,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
ErrorBoundary: ErrorBoundary,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]), [dispatch, fetchedUser.email, props.faro]);
|
|
||||||
|
|
||||||
return <RouterProvider router={router}/>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loadCharacterList = () => getJson<CharacterListItemDto[]>("/api/Character")
|
||||||
|
|
||||||
|
const loadCharacter1E = ({params}: LoaderFunctionArgs) => getJson<KarakterInputs & { isPublic: boolean }>(`/api/Character1E/${params.id}`)
|
||||||
|
|
||||||
|
const loadCharacter2E = async ({params}: LoaderFunctionArgs) => {
|
||||||
|
try {
|
||||||
|
const loaded2Echaracter = await getJson<Karakter2E & { isPublic: boolean }>(`/api/Character2E/${params.id}`)
|
||||||
|
store.dispatch(setCharacter(loaded2Echaracter))
|
||||||
|
return loaded2Echaracter;
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
isPublic: false,
|
||||||
|
error: error instanceof Error ? error.message : "Failed to load character",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const router = createBrowserRouter([
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
element: <RootLayout />,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
index: true,
|
||||||
|
element: <IndexRedirect />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "karaktereim",
|
||||||
|
element: <CharacterList />,
|
||||||
|
loader: loadCharacterList,
|
||||||
|
ErrorBoundary: ErrorBoundary,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "1e",
|
||||||
|
loader: () => redirect("/1e/karakter")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "1e/karakter",
|
||||||
|
element: <CreateCharacter1ERoute />,
|
||||||
|
ErrorBoundary: ErrorBoundary,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "1e/karakter/:id",
|
||||||
|
element: <CreateCharacter1ERoute />,
|
||||||
|
loader: loadCharacter1E,
|
||||||
|
ErrorBoundary: ErrorBoundary,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "2e",
|
||||||
|
loader: () => redirect("/2e/karakter")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "2e/karakter",
|
||||||
|
element: <CreateCharacter2ERoute />,
|
||||||
|
ErrorBoundary: ErrorBoundary,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "2e/karakter/:id",
|
||||||
|
element: <CreateCharacter2ERoute />,
|
||||||
|
loader: loadCharacter2E,
|
||||||
|
ErrorBoundary: ErrorBoundary,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
function App(props: { faro?: Faro }) {
|
function App(props: { faro?: Faro }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<Router faro={props.faro} />
|
<FaroContext.Provider value={props.faro}>
|
||||||
|
<UserBootstrap />
|
||||||
|
<RouterProvider router={router}/>
|
||||||
|
</FaroContext.Provider>
|
||||||
</Provider>
|
</Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,18 @@
|
|||||||
import {KarakterInputs} from "../domain-models/karakter";
|
import {KarakterInputs} from "../domain-models/karakter";
|
||||||
import axios from "axios";
|
import {postJson} from "../../shared/api/http";
|
||||||
|
|
||||||
export async function StoreNewCharacter(karakter: KarakterInputs, isPublic: boolean = false) {
|
export async function StoreNewCharacter(karakter: KarakterInputs, isPublic: boolean = false) {
|
||||||
let response = await axios.post(`${window.location.origin}/api/Character1E/`, karakter, {
|
return postJson<KarakterInputs, string>("/api/Character1E/", karakter, {
|
||||||
withCredentials: true,
|
|
||||||
params: {
|
params: {
|
||||||
isPublic,
|
isPublic,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (response.status < 300){
|
|
||||||
return response.data as string
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw Error(response.statusText)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function UpdateCharacter(id: string, karakter: KarakterInputs, isPublic: boolean = false) {
|
export async function UpdateCharacter(id: string, karakter: KarakterInputs, isPublic: boolean = false) {
|
||||||
let response = await axios.post(`${window.location.origin}/api/Character1E/${id}`, karakter, {
|
return postJson<KarakterInputs, string>(`/api/Character1E/${id}`, karakter, {
|
||||||
withCredentials: true,
|
|
||||||
params: {
|
params: {
|
||||||
isPublic,
|
isPublic,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (response.status < 300){
|
|
||||||
return response.data as string
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw Error(response.statusText)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,18 @@
|
|||||||
import axios from "axios";
|
import {postJson} from "../../shared/api/http";
|
||||||
import {Karakter2E} from "../domain-models/karakter2E";
|
import {Karakter2E} from "../domain-models/karakter2E";
|
||||||
|
|
||||||
export async function StoreNewCharacter2E(karakter: Karakter2E, isPublic: boolean = false) {
|
export async function StoreNewCharacter2E(karakter: Karakter2E, isPublic: boolean = false) {
|
||||||
let response = await axios.post(`${window.location.origin}/api/Character2E/`, karakter, {
|
return postJson<Karakter2E, string>("/api/Character2E/", karakter, {
|
||||||
withCredentials: true,
|
|
||||||
params: {
|
params: {
|
||||||
isPublic,
|
isPublic,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (response.status < 300){
|
|
||||||
return response.data as string
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw Error(response.statusText)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function UpdateCharacter2E(id: string, karakter: Karakter2E, isPublic: boolean = false) {
|
export async function UpdateCharacter2E(id: string, karakter: Karakter2E, isPublic: boolean = false) {
|
||||||
let response = await axios.post(`${window.location.origin}/api/Character2E/${id}`, karakter, {
|
return postJson<Karakter2E, string>(`/api/Character2E/${id}`, karakter, {
|
||||||
withCredentials: true,
|
|
||||||
params: {
|
params: {
|
||||||
isPublic,
|
isPublic,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (response.status < 300){
|
|
||||||
return response.data as string
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw Error(response.statusText)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import axios from "axios";
|
|
||||||
import {Container, Nav, Navbar} from "react-bootstrap";
|
import {Container, Nav, Navbar} from "react-bootstrap";
|
||||||
import {unsetUser, userSelector} from "./domain-models/userSlice";
|
import {unsetUser, userSelector} from "./domain-models/userSlice";
|
||||||
import {AppDispatch, RootState} from "../store";
|
import {AppDispatch, RootState} from "../store";
|
||||||
import {useDispatch, useSelector} from "react-redux";
|
import {useDispatch, useSelector} from "react-redux";
|
||||||
import {Link, useNavigate} from "react-router-dom";
|
import {Link, useNavigate} from "react-router-dom";
|
||||||
|
import {postVoid} from "./api/http";
|
||||||
|
|
||||||
function Header() {
|
function Header() {
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ function Header() {
|
|||||||
const fetchedUser = useSelector.withTypes<RootState>()(userSelector);
|
const fetchedUser = useSelector.withTypes<RootState>()(userSelector);
|
||||||
|
|
||||||
const logout = async () => {
|
const logout = async () => {
|
||||||
await axios.post("/Identity/Account/Logout")
|
await postVoid("/Identity/Account/Logout")
|
||||||
dispatch(unsetUser())
|
dispatch(unsetUser())
|
||||||
navigate("/")
|
navigate("/")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import axios, {AxiosRequestConfig} from "axios";
|
||||||
|
|
||||||
|
export const http = axios.create({
|
||||||
|
withCredentials: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export async function getJson<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
|
||||||
|
const response = await http.get<T>(url, config)
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getText(url: string, config?: AxiosRequestConfig): Promise<string> {
|
||||||
|
const response = await http.get<string>(url, {
|
||||||
|
...config,
|
||||||
|
responseType: "text",
|
||||||
|
})
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function postJson<TRequest, TResponse>(url: string, body: TRequest, config?: AxiosRequestConfig): Promise<TResponse> {
|
||||||
|
const response = await http.post<TResponse>(url, body, config)
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function postVoid<TRequest = undefined>(url: string, body?: TRequest, config?: AxiosRequestConfig): Promise<void> {
|
||||||
|
await http.post(url, body, config)
|
||||||
|
}
|
||||||
@@ -33,7 +33,7 @@ interface CharacterListItemDto2E {
|
|||||||
edition: "2e"
|
edition: "2e"
|
||||||
}
|
}
|
||||||
|
|
||||||
type CharacterListItemDto = CharacterListItemDto1E | CharacterListItemDto2E;
|
export type CharacterListItemDto = CharacterListItemDto1E | CharacterListItemDto2E;
|
||||||
|
|
||||||
function mapDtoToViewModel(dto: CharacterListItemDto): CharacterListItemViewModel {
|
function mapDtoToViewModel(dto: CharacterListItemDto): CharacterListItemViewModel {
|
||||||
if (dto.edition === "1e") {
|
if (dto.edition === "1e") {
|
||||||
|
|||||||
Reference in New Issue
Block a user