Tweaks, working on api error handling
This commit is contained in:
@@ -1,5 +1,3 @@
|
||||
// import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
|
||||
|
||||
const serviceHost = process.env.SERVICE_HOST || 'http://localhost';
|
||||
const servicePort = process.env.SERVICE_PORT || 5000;
|
||||
const baseURL = `${serviceHost}:${servicePort}`;
|
||||
@@ -18,17 +16,23 @@ export async function get(endpoint: string, params: Record<string, any> = {}): P
|
||||
|
||||
interface PostOptions {
|
||||
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 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, {
|
||||
method: 'POST',
|
||||
headers: options?.headers || {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
headers: headers,
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(body)
|
||||
body
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -11,10 +11,10 @@ export async function getPicture(): Promise<Blob | undefined> {
|
||||
|
||||
export async function setPicture(payload: File): Promise<boolean> {
|
||||
const data = new FormData();
|
||||
data.append('file', payload);
|
||||
data.append('data', payload);
|
||||
// TODO: Figure out why the form data object is empty
|
||||
const response = await post('users/picture', data, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
type: 'form'
|
||||
});
|
||||
if (response?.status === 200) {
|
||||
return true;
|
||||
|
||||
@@ -5,6 +5,7 @@ import React, { useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { userState } from '@/state/auth';
|
||||
import { Card, Container, Grid, SimpleGrid } from '@mantine/core';
|
||||
|
||||
export default function Page() {
|
||||
const [user, setUser] = useRecoilState(userState);
|
||||
@@ -23,8 +24,33 @@ export default function Page() {
|
||||
}, [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 {
|
||||
return <div>Not logged in</div>;
|
||||
return <></>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,9 +90,9 @@ function SpellSection({ title, spells, onClick }: { title: string; spells: Spell
|
||||
<Box>
|
||||
<h2>{title}</h2>
|
||||
<ul>
|
||||
{spells.map((spell) => (
|
||||
{spells.map((spell, index) => (
|
||||
<li
|
||||
key={spell.id}
|
||||
key={`spell-${index}`}
|
||||
className='link spell-item'
|
||||
style={{ width: 'fit-content' }}
|
||||
onClick={() => onClick(spell)}
|
||||
|
||||
@@ -106,6 +106,7 @@ export default function Header() {
|
||||
mx={'auto'}
|
||||
mt={-30}
|
||||
style={{ cursor: 'pointer' }}
|
||||
bg={profilePicture ? 'transparent' : 'white'}
|
||||
src={profilePicture ? URL.createObjectURL(profilePicture) : undefined}
|
||||
/>
|
||||
)}
|
||||
@@ -131,13 +132,12 @@ export default function Header() {
|
||||
size='xs'
|
||||
variant='default'
|
||||
onClick={async () => {
|
||||
const response = await logout();
|
||||
if (response?.status == 200) {
|
||||
Cookies.remove('logged_in');
|
||||
if (refreshId) {
|
||||
clearInterval(refreshId);
|
||||
}
|
||||
setUser(undefined);
|
||||
await logout();
|
||||
Cookies.remove('logged_in');
|
||||
setUser(undefined);
|
||||
setProfilePicture(null);
|
||||
if (refreshId) {
|
||||
clearInterval(refreshId);
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -167,7 +167,19 @@ export default function Header() {
|
||||
)}
|
||||
</div>
|
||||
</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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -38,8 +38,8 @@ export default function SpellModal({ spell, isOpen, onClose }: SpellModalProps)
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6}>
|
||||
<span style={{ fontWeight: 'bold', paddingRight: '1em' }}>Sources:</span>
|
||||
{spell.sources.map((s) => (
|
||||
<span style={{ paddingRight: '0.6em' }}>
|
||||
{spell.sources.map((s, index) => (
|
||||
<span style={{ paddingRight: '0.6em' }} key={`spell-source-${index}`}>
|
||||
{s.source}
|
||||
{s.page ? `.${s.page}` : ''}
|
||||
</span>
|
||||
@@ -48,8 +48,12 @@ export default function SpellModal({ spell, isOpen, onClose }: SpellModalProps)
|
||||
<Grid.Col span={6}>
|
||||
<span style={{ fontWeight: 'bold', marginRight: '1em' }}>Classes:</span>
|
||||
<span style={{ overflowWrap: 'break-word' }}>
|
||||
{spell.classes.map((c) => (
|
||||
<span style={{ paddingRight: '0.6em', display: 'inline-block' }} className='link'>
|
||||
{spell.classes.map((c, index) => (
|
||||
<span
|
||||
style={{ paddingRight: '0.6em', display: 'inline-block' }}
|
||||
className='link'
|
||||
key={`spell-class-${index}`}
|
||||
>
|
||||
{parseText(c, true)}
|
||||
</span>
|
||||
))}
|
||||
@@ -71,8 +75,8 @@ export default function SpellModal({ spell, isOpen, onClose }: SpellModalProps)
|
||||
<Grid.Col span={6}>
|
||||
<span style={{ fontWeight: 'bold', paddingRight: '1em' }}>Duration:</span>
|
||||
<span style={{ paddingRight: '0.6em' }}>
|
||||
{spell.durations.map((d) => (
|
||||
<span>
|
||||
{spell.durations.map((d, index) => (
|
||||
<span key={`duration-${index}`}>
|
||||
{capitalize(d.type)} {d.value} {capitalize(d.unit)}
|
||||
</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 matches = text.matchAll(regex);
|
||||
const result = [];
|
||||
@@ -148,18 +152,17 @@ function handleLink(type: string, name: string) {
|
||||
}
|
||||
|
||||
function SpellDescription({ spell }: { spell: Spell }) {
|
||||
|
||||
return (
|
||||
<>
|
||||
{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.list && (
|
||||
<ul>
|
||||
{e.list.map((text) => (
|
||||
<li>{parseText(text)}</li>
|
||||
{e.list.map((text, index) => (
|
||||
<li key={`spell-text-${index}`}>{parseText(text)}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
@@ -167,23 +170,23 @@ function SpellDescription({ spell }: { spell: Spell }) {
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
{e.table.headers.map((label) => (
|
||||
<th>{label}</th>
|
||||
{e.table.headers.map((label, index) => (
|
||||
<th key={`spell-header-${index}`}>{label}</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{e.table.rows.map((row) => (
|
||||
<tr>
|
||||
{row.map((cell) => (
|
||||
<td>{parseText(cell)}</td>
|
||||
{e.table.rows.map((row, index) => (
|
||||
<tr key={`spell-row-${index}`}>
|
||||
{row.map((cell, index) => (
|
||||
<td key={`spell-cell-${index}`}>{parseText(cell)}</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "ES2022",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "Node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
@@ -26,12 +26,24 @@
|
||||
],
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
"@api/*": ["src/api"],
|
||||
"@app/*": ["./src/app/*"],
|
||||
"@components/*": ["src/components/*"],
|
||||
"@js/*": ["src/js/*"],
|
||||
"@state/*": ["src/state/*"]
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
],
|
||||
"@api/*": [
|
||||
"src/api"
|
||||
],
|
||||
"@app/*": [
|
||||
"./src/app/*"
|
||||
],
|
||||
"@components/*": [
|
||||
"src/components/*"
|
||||
],
|
||||
"@js/*": [
|
||||
"src/js/*"
|
||||
],
|
||||
"@state/*": [
|
||||
"src/state/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
|
||||
Reference in New Issue
Block a user