Begin consumption of API
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
import { User } from '../types/User';
|
||||
|
||||
const BASE = 'https://khofmann.userpage.fu-berlin.de/phpCourse/exam/api/';
|
||||
|
||||
let instance: ApiImpl;
|
||||
|
||||
class ApiImpl {
|
||||
private token: string = '';
|
||||
|
||||
constructor() {
|
||||
if (instance) {
|
||||
throw new Error('New instance cannot be created!!');
|
||||
}
|
||||
|
||||
instance = this;
|
||||
}
|
||||
|
||||
public logIn = async (email: string, password: string): Promise<User> => {
|
||||
const { user, token } = await (await this.post('login', { email, password })).json();
|
||||
this.token = token;
|
||||
|
||||
return user;
|
||||
};
|
||||
|
||||
public logOut = async (): Promise<boolean> => {
|
||||
return await (await this.postAuth('logout')).json();
|
||||
};
|
||||
|
||||
private post = async (
|
||||
endpoint: string,
|
||||
body: Record<string, unknown> | undefined = undefined,
|
||||
headers: HeadersInit | undefined = undefined
|
||||
) => {
|
||||
const response = await fetch(`${BASE}${endpoint}`, {
|
||||
mode: 'cors',
|
||||
method: 'post',
|
||||
headers,
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
if (response.ok) return response;
|
||||
throw await response.json();
|
||||
};
|
||||
|
||||
private postAuth = async (
|
||||
endpoint: string,
|
||||
body: Record<string, unknown> | undefined = undefined,
|
||||
headers: HeadersInit | undefined = undefined
|
||||
) => {
|
||||
const response = await fetch(`${BASE}${endpoint}`, {
|
||||
mode: 'cors',
|
||||
method: 'post',
|
||||
headers: { token: this.token, ...headers },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
if (response.ok) return response;
|
||||
throw await response.json();
|
||||
};
|
||||
}
|
||||
|
||||
const Api = new ApiImpl();
|
||||
export default Api;
|
||||
+20
-2
@@ -1,12 +1,27 @@
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { RouterProvider, createRouter } from '@tanstack/react-router';
|
||||
import { StrictMode } from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
|
||||
// Import the generated route tree
|
||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||
import { routeTree } from './routeTree.gen';
|
||||
|
||||
// Query Client
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
// Create a new router instance
|
||||
const router = createRouter({ routeTree });
|
||||
const router = createRouter({
|
||||
routeTree,
|
||||
context: {
|
||||
queryClient,
|
||||
},
|
||||
defaultPreload: 'intent',
|
||||
// Since we're using React Query, we don't want loader calls to ever be stale
|
||||
// This will ensure that the loader is always called when the route is preloaded or visited
|
||||
defaultPreloadStaleTime: 0,
|
||||
basepath: process.env.NODE_ENV === 'development' ? 'phpCourse/exam/dist' : '/phpCourse/exam',
|
||||
});
|
||||
|
||||
// Register the router instance for type safety
|
||||
declare module '@tanstack/react-router' {
|
||||
@@ -21,7 +36,10 @@ if (!rootElement.innerHTML) {
|
||||
const root = ReactDOM.createRoot(rootElement);
|
||||
root.render(
|
||||
<StrictMode>
|
||||
<RouterProvider router={router} />
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<RouterProvider router={router} />
|
||||
<ReactQueryDevtools initialIsOpen={false} />
|
||||
</QueryClientProvider>
|
||||
</StrictMode>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,18 +11,32 @@
|
||||
// Import Routes
|
||||
|
||||
import { Route as rootRoute } from './routes/__root'
|
||||
import { Route as IndexImport } from './routes/index'
|
||||
|
||||
// Create/Update Routes
|
||||
|
||||
const IndexRoute = IndexImport.update({
|
||||
path: '/',
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any)
|
||||
|
||||
// Populate the FileRoutesByPath interface
|
||||
|
||||
declare module '@tanstack/react-router' {
|
||||
interface FileRoutesByPath {}
|
||||
interface FileRoutesByPath {
|
||||
'/': {
|
||||
id: '/'
|
||||
path: '/'
|
||||
fullPath: '/'
|
||||
preLoaderRoute: typeof IndexImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create and export the route tree
|
||||
|
||||
export const routeTree = rootRoute.addChildren({})
|
||||
export const routeTree = rootRoute.addChildren({ IndexRoute })
|
||||
|
||||
/* prettier-ignore-end */
|
||||
|
||||
@@ -31,7 +45,12 @@ export const routeTree = rootRoute.addChildren({})
|
||||
"routes": {
|
||||
"__root__": {
|
||||
"filePath": "__root.tsx",
|
||||
"children": []
|
||||
"children": [
|
||||
"/"
|
||||
]
|
||||
},
|
||||
"/": {
|
||||
"filePath": "index.tsx"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
import { createRootRoute, Outlet } from '@tanstack/react-router';
|
||||
import { QueryClient } from '@tanstack/react-query';
|
||||
import { createRootRouteWithContext, Link, Outlet } from '@tanstack/react-router';
|
||||
import { TanStackRouterDevtools } from '@tanstack/router-devtools';
|
||||
|
||||
export const Route = createRootRoute({
|
||||
export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()({
|
||||
component: () => (
|
||||
<>
|
||||
<Link
|
||||
to="/"
|
||||
activeProps={{
|
||||
className: 'font-bold',
|
||||
}}
|
||||
activeOptions={{ exact: true }}
|
||||
>
|
||||
Home
|
||||
</Link>
|
||||
<Outlet />
|
||||
<TanStackRouterDevtools />
|
||||
</>
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Button } from '@mui/material';
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
import Api from '../api/Api';
|
||||
import useGuestBookStore from '../store/store';
|
||||
|
||||
export const Route = createFileRoute('/')({
|
||||
component: Home,
|
||||
});
|
||||
|
||||
function Home() {
|
||||
const setUser = useGuestBookStore((state) => state.setUser);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
try {
|
||||
setUser(await Api.logIn('max@moritz.net', 'max'));
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Log In
|
||||
</Button>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
try {
|
||||
await Api.logOut();
|
||||
setUser(undefined);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Log Out
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import type {} from '@redux-devtools/extension'; // required for devtools typing
|
||||
import { create } from 'zustand';
|
||||
import { devtools } from 'zustand/middleware';
|
||||
import { User } from '../types/User';
|
||||
|
||||
interface GuestBookState {
|
||||
user: User | undefined;
|
||||
setUser: (user: User | undefined) => void;
|
||||
}
|
||||
|
||||
const useGuestBookStore = create<GuestBookState>()(
|
||||
devtools((set) => ({
|
||||
user: undefined,
|
||||
setUser: (user: User | undefined) => set(() => ({ user })),
|
||||
}))
|
||||
);
|
||||
|
||||
export default useGuestBookStore;
|
||||
@@ -0,0 +1,6 @@
|
||||
import { User } from './User';
|
||||
|
||||
export interface LoginResponse {
|
||||
token: string;
|
||||
user: User;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface Timestamp {
|
||||
date: string;
|
||||
timezone_type: number;
|
||||
timezone: string;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Timestamp } from './Timestamp';
|
||||
|
||||
export interface User {
|
||||
id: number;
|
||||
username: string;
|
||||
status: number;
|
||||
email: string;
|
||||
image: string;
|
||||
isAdmin: boolean;
|
||||
memberSince: Timestamp;
|
||||
postCount: number;
|
||||
}
|
||||
Reference in New Issue
Block a user