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 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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 <></>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)}
|
||||||
|
|||||||
@@ -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,13 +132,12 @@ 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);
|
||||||
if (refreshId) {
|
setProfilePicture(null);
|
||||||
clearInterval(refreshId);
|
if (refreshId) {
|
||||||
}
|
clearInterval(refreshId);
|
||||||
setUser(undefined);
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -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}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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": [
|
||||||
|
|||||||
Reference in New Issue
Block a user