Initial Post list

This commit is contained in:
2024-07-25 20:55:35 +02:00
parent 9a2673aba2
commit bb1e0eebf5
16 changed files with 327 additions and 96 deletions
@@ -1,5 +1,6 @@
import { Box, Button, TextField, Typography } from '@mui/material';
import { useForm } from '@tanstack/react-form';
import { useRouter } from '@tanstack/react-router';
import { FC, useState } from 'react';
import { useTranslation } from 'react-i18next';
import Api from '../../../api/Api';
@@ -12,6 +13,7 @@ const Login: FC = () => {
const setUser = useGuestBookStore((state) => state.setUser);
const { t } = useTranslation();
const router = useRouter();
const form = useForm({
defaultValues: {
@@ -20,8 +22,10 @@ const Login: FC = () => {
},
onSubmit: async ({ value }) => {
try {
setUser(await Api.logIn(value.email, value.password));
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const [user, token] = await Api.logIn(value.email, value.password);
setUser(user, token);
router.invalidate();
//eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
setError(error);
}
@@ -42,7 +46,7 @@ const Login: FC = () => {
}}
noValidate
>
<Box sx={{ display: 'grid', gap: 1, padding: 1, minWidth: '100px' }}>
<Box sx={{ display: 'grid', gap: 2, padding: 1 }}>
<form.Field
name="email"
validators={{
@@ -67,6 +71,9 @@ const Login: FC = () => {
required
error={field.state.meta.isTouched && field.state.meta.errors.length > 0}
helperText={field.state.meta.isTouched ? field.state.meta.errors.join(',') : ''}
type="email"
autoComplete="username"
inputMode="email"
/>
</>
);
@@ -96,6 +103,8 @@ const Login: FC = () => {
required
error={field.state.meta.isTouched && field.state.meta.errors.length > 0}
helperText={field.state.meta.isTouched ? field.state.meta.errors.join(',') : ''}
type="password"
autoComplete="password"
/>
</>
);
@@ -111,7 +120,7 @@ const Login: FC = () => {
</>
)}
/>
{error && <Typography color="error.main">{t(...handleError(error, 'login'))}</Typography>}
{error && <Typography color="error.main">{handleError(error, 'login', t)}</Typography>}
</Box>
</form>
);
+74 -48
View File
@@ -1,5 +1,17 @@
import { AccountCircle } from '@mui/icons-material';
import { AppBar, Avatar, IconButton, Menu, MenuItem, Toolbar, Typography, useScrollTrigger } from '@mui/material';
import {
AppBar,
Avatar,
Box,
CircularProgress,
IconButton,
Menu,
MenuItem,
Link as MUILink,
Toolbar,
useScrollTrigger,
} from '@mui/material';
import { Link, useRouter, useRouterState } from '@tanstack/react-router';
import { cloneElement, FC, ReactElement, useState } from 'react';
import { useTranslation } from 'react-i18next';
import Api from '../../api/Api';
@@ -24,6 +36,8 @@ const Header: FC = () => {
const setUser = useGuestBookStore((state) => state.setUser);
const { t } = useTranslation();
const router = useRouter();
const isLoading = useRouterState({ select: (s) => s.status === 'pending' });
const handleMenu = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
@@ -35,56 +49,68 @@ const Header: FC = () => {
return (
<ElevationScroll>
<AppBar>
<Toolbar>
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
{t('GuestBook')}
</Typography>
{user ? (
<IconButton onClick={handleMenu} sx={{ p: 0 }}>
<Avatar alt={user.username} src={`storage/${user.image}`} />
</IconButton>
) : (
<IconButton size="large" onClick={handleMenu} color="inherit">
<AccountCircle />
</IconButton>
)}
<Menu
id="menu-appbar"
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
keepMounted
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
open={Boolean(anchorEl)}
onClose={handleClose}
>
<>
<AppBar>
<Toolbar>
<Box sx={{ flexGrow: 1, alignItems: 'center', display: 'flex', gap: 1 }}>
<MUILink component={Link} to="/" color="#FFF" variant="h6" underline="none">
{t('GuestBook')}
</MUILink>
{isLoading && <CircularProgress size={16} thickness={10} sx={{ color: 'white' }} />}
</Box>
{user ? (
[
<MenuItem key="profile" onClick={handleClose}>
{t('Profile')}
</MenuItem>,
<MenuItem
key="logout"
onClick={() => {
Api.logOut();
setUser(undefined);
}}
>
{t('Log out')}
</MenuItem>,
]
<IconButton onClick={handleMenu} sx={{ p: 0 }}>
<Avatar alt={user.username} src={`storage/${user.image}`} />
</IconButton>
) : (
<Login />
<IconButton size="large" onClick={handleMenu} color="inherit">
<AccountCircle />
</IconButton>
)}
</Menu>
</Toolbar>
</AppBar>
<Menu
id="menu-appbar"
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
keepMounted
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
open={Boolean(anchorEl)}
onClose={handleClose}
sx={{
'& .MuiMenu-paper': {
minWidth: '240px',
},
}}
>
{user ? (
[
<MenuItem key="profile" onClick={handleClose}>
{t('Profile')}
</MenuItem>,
<MenuItem
key="logout"
onClick={() => {
Api.logOut();
setUser();
router.invalidate();
}}
>
{t('Log out')}
</MenuItem>,
]
) : (
<Login />
)}
</Menu>
</Toolbar>
</AppBar>
<Toolbar />
</>
</ElevationScroll>
);
};
+38
View File
@@ -0,0 +1,38 @@
import { Avatar, Card, CardContent, CardHeader, Link as MUILink, Typography } from '@mui/material';
import { Link } from '@tanstack/react-router';
import { FC } from 'react';
import { PostAuth, PostNonAuth } from '../../types/Post';
interface Props {
post: PostNonAuth | PostAuth;
}
const Post: FC<Props> = ({ post }) => {
return (
<Card sx={{ display: 'flex', flexDirection: 'column', flexGrow: 1 }}>
<CardHeader
avatar={
<MUILink component={Link} to="/" color="#FFF" variant="h6" underline="none">
<Avatar alt={post.user.username} src={`storage/${post.user.image}`} />
</MUILink>
}
title={post.user.username}
subheader={new Date(post.postedAt.date).toLocaleString(navigator.languages[0] ?? 'de-DE', {
timeZone: post.postedAt.timezone,
weekday: 'short',
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
hour12: false,
minute: '2-digit',
})}
/>
<CardContent>
<Typography>{post.content}</Typography>
</CardContent>
</Card>
);
};
export default Post;