add group creation
This commit is contained in:
6
bun.lock
6
bun.lock
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 0,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "bun-react-template",
|
||||
@@ -10,7 +11,8 @@
|
||||
"@mui/icons-material": "7.3.5",
|
||||
"@mui/material": "7.3.5",
|
||||
"cookie": "^1.0.2",
|
||||
"mobx-react-lite": "^4.1.1",
|
||||
"mobx": "^6.15.0",
|
||||
"mobx-react": "^9.2.1",
|
||||
"react": "19",
|
||||
"react-dom": "19",
|
||||
"react-router-dom": "^7.9.5",
|
||||
@@ -538,6 +540,8 @@
|
||||
|
||||
"mobx": ["mobx@6.15.0", "", {}, "sha512-UczzB+0nnwGotYSgllfARAqWCJ5e/skuV2K/l+Zyck/H6pJIhLXuBnz+6vn2i211o7DtbE78HQtsYEKICHGI+g=="],
|
||||
|
||||
"mobx-react": ["mobx-react@9.2.1", "", { "dependencies": { "mobx-react-lite": "^4.1.1" }, "peerDependencies": { "mobx": "^6.9.0", "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-WJNNm0FB2n0Z0u+jS1QHmmWyV8l2WiAj8V8I/96kbUEN2YbYCoKW+hbbqKKRUBqElu0llxM7nWKehvRIkhBVJw=="],
|
||||
|
||||
"mobx-react-lite": ["mobx-react-lite@4.1.1", "", { "dependencies": { "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "mobx": "^6.9.0", "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-iUxiMpsvNraCKXU+yPotsOncNNmyeS2B5DKL+TL6Tar/xm+wwNJAubJmtRSeAoYawdZqwv8Z/+5nPRHeQxTiXg=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
BIN
data.sqlite
BIN
data.sqlite
Binary file not shown.
@@ -16,7 +16,8 @@
|
||||
"@mui/icons-material": "7.3.5",
|
||||
"@mui/material": "7.3.5",
|
||||
"cookie": "^1.0.2",
|
||||
"mobx-react-lite": "^4.1.1",
|
||||
"mobx": "^6.15.0",
|
||||
"mobx-react": "^9.2.1",
|
||||
"react": "19",
|
||||
"react-dom": "19",
|
||||
"react-router-dom": "^7.9.5"
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
import SignIn from './components/SignIn';
|
||||
import { createTheme, ThemeProvider } from '@mui/material';
|
||||
import { useStore } from './Store';
|
||||
import SignIn from './components/authentication/SignIn';
|
||||
import { createTheme, CssBaseline, ThemeProvider } from '@mui/material';
|
||||
import { useStore, type View } from './Store';
|
||||
import Group from './components/Group';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { type ReactElement } from 'react';
|
||||
import NewGroup from './components/authentication/NewGroup';
|
||||
|
||||
export const views: Record<View, ReactElement> = {
|
||||
signIn: <SignIn />,
|
||||
group: <Group />,
|
||||
newGroup: <NewGroup />,
|
||||
};
|
||||
|
||||
const App = observer(() => {
|
||||
const theme = createTheme({
|
||||
@@ -12,15 +20,18 @@ const App = observer(() => {
|
||||
});
|
||||
|
||||
const store = useStore();
|
||||
const { loggedIn } = store;
|
||||
const { currentView } = store;
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<div className="app">
|
||||
{loggedIn ?
|
||||
<Group /> :
|
||||
<SignIn />
|
||||
}
|
||||
<CssBaseline enableColorScheme />
|
||||
<div className="app"
|
||||
style={{
|
||||
backgroundImage: 'radial-gradient(at 50% 50%, hsla(210, 100%, 16%, 0.5), hsl(220, 30%, 5%))',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
}}
|
||||
>
|
||||
{views[currentView]}
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
@@ -1,36 +1,64 @@
|
||||
import { createContext, useContext } from 'react';
|
||||
import { makeAutoObservable } from 'mobx';
|
||||
import cookie from 'cookie';
|
||||
import type { Group, User } from '@/interfaces';
|
||||
import { makeAutoObservable } from 'mobx';
|
||||
|
||||
export type CookieData = {
|
||||
user: User;
|
||||
group: Group;
|
||||
};
|
||||
|
||||
export type View = 'signIn' | 'group' | 'newGroup';
|
||||
|
||||
export class Store {
|
||||
cookieData: CookieData | null = null;
|
||||
loggedIn = false;
|
||||
currentView: View = 'signIn';
|
||||
user: User | null = null;
|
||||
group: Group | null = null;
|
||||
|
||||
processCookie() {
|
||||
const parsed = cookie.parse(document.cookie);
|
||||
|
||||
this.cookieData = {
|
||||
user: parsed.user ? JSON.parse(parsed.user) : null,
|
||||
group: parsed.group ? JSON.parse(parsed.group) : null,
|
||||
} as CookieData;
|
||||
setCurrentView = (view: View) => {
|
||||
this.currentView = view;
|
||||
}
|
||||
|
||||
get loggedIn(): boolean {
|
||||
if (this.cookieData === null) {
|
||||
return false;
|
||||
};
|
||||
return !!this.cookieData.user && !!this.cookieData.group;
|
||||
setLoggedIn = (value: boolean) => {
|
||||
this.loggedIn = value;
|
||||
}
|
||||
|
||||
setUser = (user: User | null) => {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
setGroup = (group: Group | null) => {
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
logout = () => {
|
||||
void cookieStore.delete('user');
|
||||
void cookieStore.delete('group');
|
||||
this.user = null;
|
||||
this.group = null;
|
||||
this.loggedIn = false;
|
||||
this.currentView = 'signIn';
|
||||
}
|
||||
|
||||
processCookie = () => {
|
||||
const parsed = cookie.parse(document.cookie);
|
||||
if (!parsed.user || !parsed.group) {
|
||||
this.loggedIn = false;
|
||||
return;
|
||||
}
|
||||
this.user = JSON.parse(parsed.user) as User[][0];
|
||||
this.group = JSON.parse(parsed.group) as Group[][0];
|
||||
|
||||
this.loggedIn = !!(this.user && this.group);
|
||||
}
|
||||
|
||||
constructor() {
|
||||
console.log('Initializing Store');
|
||||
this.processCookie();
|
||||
makeAutoObservable(this);
|
||||
this.processCookie();
|
||||
if (this.loggedIn) {
|
||||
this.setCurrentView('group');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,14 +29,12 @@ export default function ForgotPassword({ open, handleClose }: ForgotPasswordProp
|
||||
}}
|
||||
>
|
||||
<DialogTitle>Gruppencode vergessen</DialogTitle>
|
||||
|
||||
<DialogContent
|
||||
sx={{ display: 'flex', flexDirection: 'column', gap: 2, width: '100%' }}
|
||||
>
|
||||
<DialogContentText>
|
||||
Gib deine E-Mail Adresse ein und wir senden dir eine E-Mail mit deinem Gruppencode zu.
|
||||
</DialogContentText>
|
||||
|
||||
<OutlinedInput
|
||||
autoFocus
|
||||
required
|
||||
@@ -49,10 +47,8 @@ export default function ForgotPassword({ open, handleClose }: ForgotPasswordProp
|
||||
fullWidth
|
||||
/>
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions sx={{ pb: 3, px: 3 }}>
|
||||
<Button onClick={handleClose}>Cancel</Button>
|
||||
|
||||
<Button variant="contained" type="submit">
|
||||
Continue
|
||||
</Button>
|
||||
|
||||
@@ -1,30 +1,26 @@
|
||||
import * as React from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import CssBaseline from '@mui/material/CssBaseline';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import Card from './Card';
|
||||
import { useStore } from '../Store';
|
||||
import { CardHeader, IconButton } from '@mui/material';
|
||||
import LogoutIcon from '@mui/icons-material/Logout';
|
||||
|
||||
const Group = () => {
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
|
||||
const theme = useTheme();
|
||||
const store = useStore();
|
||||
const { group } = store.cookieData!;
|
||||
const { group, logout } = store;
|
||||
|
||||
console.log('Group component rendered with group:', group.name);
|
||||
if (!group) {
|
||||
return (<Typography>Keine Gruppe gefunden.</Typography>);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
backgroundImage: 'radial-gradient(at 50% 50%, hsla(210, 100%, 16%, 0.5), hsl(220, 30%, 5%))',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
}}>
|
||||
<CssBaseline enableColorScheme />
|
||||
<div>
|
||||
<Stack direction="column" justifyContent="space-between"
|
||||
sx={{
|
||||
height: 'calc((1 - var(--template-frame-height, 0)) * 100dvh)',
|
||||
height: '100dvh',
|
||||
minHeight: '100%',
|
||||
padding: theme.spacing(2),
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
@@ -33,13 +29,14 @@ const Group = () => {
|
||||
}}
|
||||
>
|
||||
<Card variant="outlined">
|
||||
<Typography
|
||||
component="h1"
|
||||
variant="h4"
|
||||
sx={{ width: '100%', fontSize: 'clamp(2rem, 10vw, 2.15rem)' }}
|
||||
>
|
||||
{group.name}
|
||||
</Typography>
|
||||
<CardHeader
|
||||
action={
|
||||
<IconButton aria-label="logout" onClick={logout}>
|
||||
<LogoutIcon />
|
||||
</IconButton>
|
||||
}
|
||||
title={group.name}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
|
||||
196
src/client/components/authentication/NewGroup.tsx
Normal file
196
src/client/components/authentication/NewGroup.tsx
Normal file
@@ -0,0 +1,196 @@
|
||||
import * as React from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import FormLabel from '@mui/material/FormLabel';
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import ForgotPassword from '../ForgotPassword';
|
||||
import Card from '../Card';
|
||||
import { createGroup, createUser, fetchUser } from '../../serverApi';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useStore } from '../../Store';
|
||||
import LoginIcon from '@mui/icons-material/Login';
|
||||
|
||||
const NewGroup = observer(() => {
|
||||
const [emailInput, setEmailInput] = React.useState('');
|
||||
const [emailError, setEmailError] = React.useState('');
|
||||
const [groupInput, setGroupINput] = React.useState('');
|
||||
const [groupError, setGroupError] = React.useState('');
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
|
||||
const {setLoggedIn, setCurrentView, setGroup, setUser} = useStore();
|
||||
const theme = useTheme();
|
||||
|
||||
React.useEffect(() => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const groupParam = params.get('gruppe');
|
||||
if (groupParam) {
|
||||
setGroupINput(groupParam);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const handleCreateGroup = async (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
event.preventDefault();
|
||||
|
||||
if (!isMailValid() || !isGroupValid()) return;
|
||||
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
// handle user
|
||||
let user = await fetchUser(emailInput);
|
||||
|
||||
if (!user) {
|
||||
user = await createUser(emailInput);
|
||||
if (!user) throw new Error('Error creating user');
|
||||
}
|
||||
|
||||
// handle group
|
||||
const groupData = await createGroup({ name: groupInput, mail: emailInput });
|
||||
if (!groupData) throw new Error('Error creating group');
|
||||
|
||||
await cookieStore.set('user', JSON.stringify(user));
|
||||
await cookieStore.set('group', JSON.stringify(groupData));
|
||||
setUser(user);
|
||||
setGroup(groupData);
|
||||
|
||||
setLoggedIn(true);
|
||||
setCurrentView('group');
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error during group creation:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const isMailValid = (): boolean => {
|
||||
let isValid = true;
|
||||
if (emailInput === '') { return true; }
|
||||
if (!/\S+@\S+\.\S+/.test(emailInput)) {
|
||||
setEmailError('Bitte gib eine gültige E-Mail Adresse ein.');
|
||||
isValid = false;
|
||||
} else {
|
||||
setEmailError('');
|
||||
}
|
||||
return isValid;
|
||||
};
|
||||
|
||||
const isGroupValid = (): boolean => {
|
||||
let isValid = true;
|
||||
if (groupInput === '') { return true; }
|
||||
if (!/^[A-Z0-9]{6,}$/.test(groupInput)) {
|
||||
setGroupError('Bitte gib einen gültigen Gruppenname ein. [A-Z, 0-9, mindestens 6 Zeichen]');
|
||||
isValid = false;
|
||||
} else {
|
||||
setGroupError('');
|
||||
}
|
||||
return isValid;
|
||||
};
|
||||
|
||||
const handleClickSignIn = () => {
|
||||
setCurrentView('signIn');
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Stack direction="column" justifyContent="space-between"
|
||||
sx={{
|
||||
height: '100dvh',
|
||||
minHeight: '100%',
|
||||
padding: theme.spacing(2),
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
padding: theme.spacing(4),
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Card variant="outlined">
|
||||
<Typography
|
||||
component="h1"
|
||||
variant="h4"
|
||||
sx={{ width: '100%', fontSize: 'clamp(2rem, 10vw, 2.15rem)' }}
|
||||
>
|
||||
{'Neue Gruppe erstellen'}
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: '100%',
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
<FormControl>
|
||||
<FormLabel htmlFor="email">E-Mail des Gruppen-Administrator</FormLabel>
|
||||
<TextField
|
||||
error={emailError !== ''}
|
||||
helperText={emailError}
|
||||
id="email"
|
||||
type="email"
|
||||
name="email"
|
||||
placeholder="ren@tier.de"
|
||||
autoComplete="email"
|
||||
required
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
onBlur={isMailValid}
|
||||
onChange={event => setEmailInput(event.target.value.trim())}
|
||||
value={emailInput}
|
||||
color={emailError ? 'error' : 'primary'} />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel>Gruppenname</FormLabel>
|
||||
<TextField
|
||||
error={groupError !== ''}
|
||||
helperText={groupError}
|
||||
name="groupcode"
|
||||
placeholder="GRUPPE123"
|
||||
type="text"
|
||||
id="groupcode"
|
||||
autoComplete="current-groupcode"
|
||||
required
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
onBlur={() => void isGroupValid()}
|
||||
onChange={event => setGroupINput(event.target.value.toUpperCase())}
|
||||
value={groupInput}
|
||||
color={groupError ? 'error' : 'primary'}
|
||||
/>
|
||||
</FormControl>
|
||||
<ForgotPassword open={open} handleClose={handleClose} />
|
||||
<Button
|
||||
fullWidth
|
||||
variant="contained"
|
||||
onClick={event => {void handleCreateGroup(event)}}
|
||||
loading={loading}
|
||||
>
|
||||
{'Gruppe erstellen'}
|
||||
</Button>
|
||||
</Box>
|
||||
<Divider>oder</Divider>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
<Button
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
onClick={handleClickSignIn}
|
||||
startIcon={<LoginIcon />}
|
||||
>
|
||||
{'Gruppe beitreten'}
|
||||
</Button>
|
||||
</Box>
|
||||
</Card>
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default NewGroup;
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
import CssBaseline from '@mui/material/CssBaseline';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import FormLabel from '@mui/material/FormLabel';
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
@@ -11,26 +10,28 @@ import Typography from '@mui/material/Typography';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import GroupAddIcon from '@mui/icons-material/GroupAdd';
|
||||
import ForgotPassword from './ForgotPassword';
|
||||
import Card from './Card';
|
||||
import { createUser, fetchGroupByCode, fetchUser } from '../serverApi';
|
||||
import ForgotPassword from '../ForgotPassword';
|
||||
import Card from '../Card';
|
||||
import { createUser, fetchGroupByCode, fetchUser } from '../../serverApi';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useStore } from '../../Store';
|
||||
|
||||
const SignIn = observer(() => {
|
||||
const [email, setEmail] = React.useState('');
|
||||
const [emailInput, setEmailInput] = React.useState('');
|
||||
const [emailError, setEmailError] = React.useState('');
|
||||
const [group, setGroup] = React.useState('');
|
||||
const [groupInput, setGroupInput] = React.useState('');
|
||||
const [groupError, setGroupError] = React.useState('');
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
|
||||
const {setLoggedIn, setCurrentView, setGroup, setUser} = useStore();
|
||||
const theme = useTheme();
|
||||
|
||||
React.useEffect(() => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const groupParam = params.get('gruppe');
|
||||
if (groupParam) {
|
||||
setGroup(groupParam);
|
||||
setGroupInput(groupParam);
|
||||
}
|
||||
}, []);
|
||||
|
||||
@@ -49,23 +50,27 @@ const SignIn = observer(() => {
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const groupData = await fetchGroupByCode(group);
|
||||
const groupData = await fetchGroupByCode(groupInput);
|
||||
|
||||
if (!groupData) {
|
||||
setGroupError('Gruppencode existiert nicht.');
|
||||
return;
|
||||
}
|
||||
|
||||
let user = await fetchUser(email);
|
||||
let user = await fetchUser(emailInput);
|
||||
|
||||
if (!user) {
|
||||
user = await createUser(email);
|
||||
user = await createUser(emailInput);
|
||||
if (!user) throw new Error('Error creating user');
|
||||
}
|
||||
|
||||
await cookieStore.set('user', JSON.stringify(user));
|
||||
await cookieStore.set('group', JSON.stringify(groupData));
|
||||
setGroup(groupData);
|
||||
setUser(user);
|
||||
|
||||
setLoggedIn(true);
|
||||
setCurrentView('group');
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error during sign-in process:', error);
|
||||
@@ -75,7 +80,7 @@ const SignIn = observer(() => {
|
||||
|
||||
const isMailValid = (): boolean => {
|
||||
let isValid = true;
|
||||
if (!/\S+@\S+\.\S+/.test(email)) {
|
||||
if (!/\S+@\S+\.\S+/.test(emailInput)) {
|
||||
setEmailError('Bitte gib eine gültige E-Mail Adresse ein.');
|
||||
isValid = false;
|
||||
} else {
|
||||
@@ -86,7 +91,7 @@ const SignIn = observer(() => {
|
||||
|
||||
const isGroupValid = (): boolean => {
|
||||
let isValid = true;
|
||||
if (!/^[A-Z0-9]{6}$/.test(group)) {
|
||||
if (!/^[A-Z0-9]{6}$/.test(groupInput)) {
|
||||
setGroupError('Bitte gib einen gültigen Gruppencode ein.');
|
||||
isValid = false;
|
||||
} else {
|
||||
@@ -95,15 +100,15 @@ const SignIn = observer(() => {
|
||||
return isValid;
|
||||
};
|
||||
|
||||
const handleClickNewGroup = () => {
|
||||
setCurrentView('newGroup');
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
backgroundImage: 'radial-gradient(at 50% 50%, hsla(210, 100%, 16%, 0.5), hsl(220, 30%, 5%))',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
}}>
|
||||
<CssBaseline enableColorScheme />
|
||||
<div>
|
||||
<Stack direction="column" justifyContent="space-between"
|
||||
sx={{
|
||||
height: 'calc((1 - var(--template-frame-height, 0)) * 100dvh)',
|
||||
height: '100dvh',
|
||||
minHeight: '100%',
|
||||
padding: theme.spacing(2),
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
@@ -137,13 +142,12 @@ const SignIn = observer(() => {
|
||||
name="email"
|
||||
placeholder="ren@tier.de"
|
||||
autoComplete="email"
|
||||
autoFocus
|
||||
required
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
onBlur={isMailValid}
|
||||
onChange={event => setEmail(event.target.value)}
|
||||
value={email}
|
||||
onChange={event => setEmailInput(event.target.value)}
|
||||
value={emailInput}
|
||||
color={emailError ? 'error' : 'primary'} />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
@@ -160,8 +164,8 @@ const SignIn = observer(() => {
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
onBlur={isGroupValid}
|
||||
onChange={event => setGroup(event.target.value)}
|
||||
value={group}
|
||||
onChange={event => setGroupInput(event.target.value.toUpperCase())}
|
||||
value={groupInput}
|
||||
color={groupError ? 'error' : 'primary'} />
|
||||
</FormControl>
|
||||
<ForgotPassword open={open} handleClose={handleClose} />
|
||||
@@ -188,7 +192,7 @@ const SignIn = observer(() => {
|
||||
<Button
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
onClick={() => alert('Sign in with Google')}
|
||||
onClick={handleClickNewGroup}
|
||||
startIcon={<GroupAddIcon />}
|
||||
>
|
||||
{'Neue Gruppe erstellen'}
|
||||
@@ -34,7 +34,7 @@ export async function fetchGroupByCode(code: string): Promise<Group | null> {
|
||||
try {
|
||||
const res = await fetch(`/api/group/${code}`);
|
||||
if (!res.ok) return null;
|
||||
const data: Group = await res.json();
|
||||
const data: Group | null = await res.json();
|
||||
return data;
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch group:', err);
|
||||
@@ -42,14 +42,14 @@ export async function fetchGroupByCode(code: string): Promise<Group | null> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function createGroup(group: Group): Promise<Group | null> {
|
||||
export async function createGroup(group: Partial<Group>): Promise<Group | null> {
|
||||
try {
|
||||
const res = await fetch('/api/group', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ group }),
|
||||
body: JSON.stringify(group),
|
||||
});
|
||||
if (!res.ok) return null;
|
||||
const data: Group = await res.json();
|
||||
|
||||
@@ -14,7 +14,6 @@ const server = serve({
|
||||
console.log('Received request for user:', mail);
|
||||
const user = await db.getUserByMail(mail);
|
||||
console.log('Fetching user with mail:', mail, 'Result:', user);
|
||||
if (!user) return new Response(JSON.stringify({ error: 'User not found' }), { status: 404 });
|
||||
return new Response(JSON.stringify(user), { headers: { 'Content-Type': 'application/json' } });
|
||||
},
|
||||
|
||||
@@ -37,7 +36,6 @@ const server = serve({
|
||||
console.log('Received request for group:', code);
|
||||
const group = await db.getGroupByCode(code);
|
||||
console.log('Fetching group with ID:', code, 'Result:', group);
|
||||
if (!group) return new Response(JSON.stringify({ error: 'Group not found' }), { status: 404 });
|
||||
return new Response(JSON.stringify(group), { headers: { 'Content-Type': 'application/json' } });
|
||||
},
|
||||
|
||||
@@ -46,7 +44,7 @@ const server = serve({
|
||||
try {
|
||||
const { name, mail } = await req.json() as { name: string; mail: string };
|
||||
console.log('Received request to create group with name:', name, 'and mail:', mail);
|
||||
const response = db.createGroup({ name, mail });
|
||||
const response = await db.createGroup({ name, mail });
|
||||
return new Response(JSON.stringify(response), { headers: { 'Content-Type': 'application/json' } });
|
||||
} catch (err) {
|
||||
console.error('Error creating group:', err);
|
||||
|
||||
@@ -80,25 +80,25 @@ class DB {
|
||||
* @param mail: string
|
||||
* @returns created user
|
||||
*/
|
||||
public async createUser(mail: string): Promise<User> {
|
||||
const user: User = await this.instance`
|
||||
public async createUser(mail: string): Promise<User | null> {
|
||||
const user: User[] = await this.instance`
|
||||
INSERT INTO users (mail) VALUES (${mail})
|
||||
RETURNING *
|
||||
`;
|
||||
|
||||
return user;
|
||||
return user[0] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user by mail
|
||||
* @param mail: string
|
||||
* @returns user object or undefined
|
||||
* @returns user object or null
|
||||
*/
|
||||
public async getUserByMail(mail: string): Promise<User | undefined> {
|
||||
const user: User = await this.instance`
|
||||
public async getUserByMail(mail: string): Promise<User | null> {
|
||||
const user: User[] = await this.instance`
|
||||
SELECT * FROM users WHERE mail = ${mail}
|
||||
`;
|
||||
return user;
|
||||
return user[0] ?? null;
|
||||
}
|
||||
|
||||
/* GROUPS */
|
||||
@@ -109,25 +109,26 @@ class DB {
|
||||
* @param mail: string
|
||||
* @returns object with id of the created group
|
||||
*/
|
||||
public async createGroup( {name, mail}: {name: string, mail: string}): Promise<Group> {
|
||||
public async createGroup( {name, mail}: {name: string, mail: string}): Promise<Group | null> {
|
||||
const code = Math.random().toString(36).substring(2, 8).toUpperCase();
|
||||
const group: Group = await this.instance`
|
||||
const group: Group[] = await this.instance`
|
||||
INSERT INTO groups (code, name, mail) VALUES (${code}, ${name}, ${mail})
|
||||
RETURNING *
|
||||
`;
|
||||
return group;
|
||||
console.log('Created group:', group);
|
||||
return group[0] ?? null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get group by ID
|
||||
* @param id: string
|
||||
* @returns group object or undefined
|
||||
* @returns group object or null
|
||||
*/
|
||||
public async getGroupByCode(code: string): Promise<Group | undefined> {
|
||||
const group: Group | undefined = await this.instance`
|
||||
public async getGroupByCode(code: string): Promise<Group | null> {
|
||||
const group: Group[] = await this.instance`
|
||||
SELECT * FROM groups WHERE code = ${code}
|
||||
`;
|
||||
return group;
|
||||
return group[0] ?? null;
|
||||
}
|
||||
|
||||
/* GROUP MEMBER */
|
||||
|
||||
Reference in New Issue
Block a user