Tweaks, working on api error handling

This commit is contained in:
Benjamin Sherriff
2023-10-23 20:19:17 -04:00
parent 3eb888b57d
commit 8c246c96e7
7 changed files with 104 additions and 47 deletions

View File

@@ -1,5 +1,3 @@
// import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
const serviceHost = process.env.SERVICE_HOST || 'http://localhost'; const serviceHost = process.env.SERVICE_HOST || 'http://localhost';
const servicePort = process.env.SERVICE_PORT || 5000; const servicePort = process.env.SERVICE_PORT || 5000;
const baseURL = `${serviceHost}:${servicePort}`; const baseURL = `${serviceHost}:${servicePort}`;
@@ -18,17 +16,23 @@ export async function get(endpoint: string, params: Record<string, any> = {}): P
interface PostOptions { interface PostOptions {
headers?: Record<string, any>; headers?: Record<string, any>;
type?: 'json' | 'form';
} }
export async function post(endpoint: string, body = {}, options?: PostOptions): Promise<Response> { export async function post(endpoint: string, body: any, options?: PostOptions): Promise<Response> {
const url = `${baseURL}/${endpoint}`; const url = `${baseURL}/${endpoint}`;
const headers = options?.headers || {};
if (!options?.type || options.type === 'json') {
body = JSON.stringify(body);
headers['Content-Type'] = 'application/json';
} else if (options.type === 'form') {
headers['Content-Type'] = 'multipart/form-data';
}
const response = await fetch(url, { const response = await fetch(url, {
method: 'POST', method: 'POST',
headers: options?.headers || { headers: headers,
'Content-Type': 'application/json'
},
credentials: 'include', credentials: 'include',
body: JSON.stringify(body) body
}); });
return response; return response;
} }

View File

@@ -11,10 +11,10 @@ export async function getPicture(): Promise<Blob | undefined> {
export async function setPicture(payload: File): Promise<boolean> { export async function setPicture(payload: File): Promise<boolean> {
const data = new FormData(); const data = new FormData();
data.append('file', payload); data.append('data', payload);
// TODO: Figure out why the form data object is empty // TODO: Figure out why the form data object is empty
const response = await post('users/picture', data, { const response = await post('users/picture', data, {
headers: { 'Content-Type': 'multipart/form-data' } type: 'form'
}); });
if (response?.status === 200) { if (response?.status === 200) {
return true; return true;

View File

@@ -5,6 +5,7 @@ import React, { useEffect } from 'react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useRecoilState } from 'recoil'; import { useRecoilState } from 'recoil';
import { userState } from '@/state/auth'; import { userState } from '@/state/auth';
import { Card, Container, Grid, SimpleGrid } from '@mantine/core';
export default function Page() { export default function Page() {
const [user, setUser] = useRecoilState(userState); const [user, setUser] = useRecoilState(userState);
@@ -23,8 +24,33 @@ export default function Page() {
}, [user]); }, [user]);
if (user) { if (user) {
return <div>Logged in as {user.email}</div>; return (
<Container mt={'2rem'}>
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing={'md'}>
<Card withBorder radius='md' padding='xl'>
<Card.Section p={'1rem'}>
<h2>
{user.first_name} {user.last_name}
</h2>
{user.role}
</Card.Section>
</Card>
<Grid gutter={'md'}>
<Grid.Col>
<Card withBorder radius='md' padding='xl'>
<Card.Section p={'1rem'}>test</Card.Section>
</Card>
</Grid.Col>
<Grid.Col>
<Card withBorder radius='md' padding='xl'>
<Card.Section p={'1rem'}>test</Card.Section>
</Card>
</Grid.Col>
</Grid>
</SimpleGrid>
</Container>
);
} else { } else {
return <div>Not logged in</div>; return <></>;
} }
} }

View File

@@ -90,9 +90,9 @@ function SpellSection({ title, spells, onClick }: { title: string; spells: Spell
<Box> <Box>
<h2>{title}</h2> <h2>{title}</h2>
<ul> <ul>
{spells.map((spell) => ( {spells.map((spell, index) => (
<li <li
key={spell.id} key={`spell-${index}`}
className='link spell-item' className='link spell-item'
style={{ width: 'fit-content' }} style={{ width: 'fit-content' }}
onClick={() => onClick(spell)} onClick={() => onClick(spell)}

View File

@@ -106,6 +106,7 @@ export default function Header() {
mx={'auto'} mx={'auto'}
mt={-30} mt={-30}
style={{ cursor: 'pointer' }} style={{ cursor: 'pointer' }}
bg={profilePicture ? 'transparent' : 'white'}
src={profilePicture ? URL.createObjectURL(profilePicture) : undefined} src={profilePicture ? URL.createObjectURL(profilePicture) : undefined}
/> />
)} )}
@@ -131,14 +132,13 @@ export default function Header() {
size='xs' size='xs'
variant='default' variant='default'
onClick={async () => { onClick={async () => {
const response = await logout(); await logout();
if (response?.status == 200) {
Cookies.remove('logged_in'); Cookies.remove('logged_in');
setUser(undefined);
setProfilePicture(null);
if (refreshId) { if (refreshId) {
clearInterval(refreshId); clearInterval(refreshId);
} }
setUser(undefined);
}
}} }}
> >
Logout Logout
@@ -167,7 +167,19 @@ export default function Header() {
)} )}
</div> </div>
</nav> </nav>
<HeaderModal type={modalType} toggle={toggle} setUser={setUser} setRefreshId={setRefreshId} /> <HeaderModal
type={modalType}
toggle={toggle}
setUser={(u) => {
setUser(u);
getPicture().then((response) => {
if (response) {
setProfilePicture(response as File);
}
});
}}
setRefreshId={setRefreshId}
/>
</> </>
); );
} }

View File

@@ -38,8 +38,8 @@ export default function SpellModal({ spell, isOpen, onClose }: SpellModalProps)
</Grid.Col> </Grid.Col>
<Grid.Col span={6}> <Grid.Col span={6}>
<span style={{ fontWeight: 'bold', paddingRight: '1em' }}>Sources:</span> <span style={{ fontWeight: 'bold', paddingRight: '1em' }}>Sources:</span>
{spell.sources.map((s) => ( {spell.sources.map((s, index) => (
<span style={{ paddingRight: '0.6em' }}> <span style={{ paddingRight: '0.6em' }} key={`spell-source-${index}`}>
{s.source} {s.source}
{s.page ? `.${s.page}` : ''} {s.page ? `.${s.page}` : ''}
</span> </span>
@@ -48,8 +48,12 @@ export default function SpellModal({ spell, isOpen, onClose }: SpellModalProps)
<Grid.Col span={6}> <Grid.Col span={6}>
<span style={{ fontWeight: 'bold', marginRight: '1em' }}>Classes:</span> <span style={{ fontWeight: 'bold', marginRight: '1em' }}>Classes:</span>
<span style={{ overflowWrap: 'break-word' }}> <span style={{ overflowWrap: 'break-word' }}>
{spell.classes.map((c) => ( {spell.classes.map((c, index) => (
<span style={{ paddingRight: '0.6em', display: 'inline-block' }} className='link'> <span
style={{ paddingRight: '0.6em', display: 'inline-block' }}
className='link'
key={`spell-class-${index}`}
>
{parseText(c, true)} {parseText(c, true)}
</span> </span>
))} ))}
@@ -71,8 +75,8 @@ export default function SpellModal({ spell, isOpen, onClose }: SpellModalProps)
<Grid.Col span={6}> <Grid.Col span={6}>
<span style={{ fontWeight: 'bold', paddingRight: '1em' }}>Duration:</span> <span style={{ fontWeight: 'bold', paddingRight: '1em' }}>Duration:</span>
<span style={{ paddingRight: '0.6em' }}> <span style={{ paddingRight: '0.6em' }}>
{spell.durations.map((d) => ( {spell.durations.map((d, index) => (
<span> <span key={`duration-${index}`}>
{capitalize(d.type)} {d.value} {capitalize(d.unit)} {capitalize(d.type)} {d.value} {capitalize(d.unit)}
</span> </span>
))} ))}
@@ -86,7 +90,7 @@ export default function SpellModal({ spell, isOpen, onClose }: SpellModalProps)
); );
} }
function parseText(text: string, capitalizeFirst?: boolean) { function parseText(text: string, capitalizeFirst?: boolean): (string | JSX.Element)[] {
const regex = /{@(.*?) (.*?)}/g; const regex = /{@(.*?) (.*?)}/g;
const matches = text.matchAll(regex); const matches = text.matchAll(regex);
const result = []; const result = [];
@@ -148,18 +152,17 @@ function handleLink(type: string, name: string) {
} }
function SpellDescription({ spell }: { spell: Spell }) { function SpellDescription({ spell }: { spell: Spell }) {
return ( return (
<> <>
{spell.description && ( {spell.description && (
<> <>
{spell.description.entries.map((e) => ( {spell.description.entries.map((e, index) => (
<> <div key={`spell-description-${index}`}>
{e.text && <p>{parseText(e.text)}</p>} {e.text && <p>{parseText(e.text)}</p>}
{e.list && ( {e.list && (
<ul> <ul>
{e.list.map((text) => ( {e.list.map((text, index) => (
<li>{parseText(text)}</li> <li key={`spell-text-${index}`}>{parseText(text)}</li>
))} ))}
</ul> </ul>
)} )}
@@ -167,23 +170,23 @@ function SpellDescription({ spell }: { spell: Spell }) {
<table> <table>
<thead> <thead>
<tr> <tr>
{e.table.headers.map((label) => ( {e.table.headers.map((label, index) => (
<th>{label}</th> <th key={`spell-header-${index}`}>{label}</th>
))} ))}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{e.table.rows.map((row) => ( {e.table.rows.map((row, index) => (
<tr> <tr key={`spell-row-${index}`}>
{row.map((cell) => ( {row.map((cell, index) => (
<td>{parseText(cell)}</td> <td key={`spell-cell-${index}`}>{parseText(cell)}</td>
))} ))}
</tr> </tr>
))} ))}
</tbody> </tbody>
</table> </table>
)} )}
</> </div>
))} ))}
</> </>
)} )}

View File

@@ -13,7 +13,7 @@
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"noEmit": true, "noEmit": true,
"esModuleInterop": true, "esModuleInterop": true,
"module": "ES2022", "module": "esnext",
"moduleResolution": "Node", "moduleResolution": "Node",
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
@@ -26,12 +26,24 @@
], ],
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"@/*": ["./src/*"], "@/*": [
"@api/*": ["src/api"], "./src/*"
"@app/*": ["./src/app/*"], ],
"@components/*": ["src/components/*"], "@api/*": [
"@js/*": ["src/js/*"], "src/api"
"@state/*": ["src/state/*"] ],
"@app/*": [
"./src/app/*"
],
"@components/*": [
"src/components/*"
],
"@js/*": [
"src/js/*"
],
"@state/*": [
"src/state/*"
]
} }
}, },
"include": [ "include": [