updated design for groups

This commit is contained in:
Joshua Hsueh 2021-04-10 23:26:45 -04:00
commit 7fc9f17f44
18 changed files with 9056 additions and 9372 deletions

View File

@ -5,6 +5,7 @@
"dependencies": {
"@material-ui/core": "^4.11.3",
"@material-ui/icons": "^4.11.2",
"@react-google-maps/api": "^2.1.1",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",

View File

@ -16,6 +16,7 @@ import Main from './components/Main';
import './App.css';
import Authenticator from './components/Authenticator';
import AuthenticationWrapper from './components/AuthenticationWrapper';
import Logout from './components/Logout';
function App() {
return (
@ -27,12 +28,13 @@ function App() {
<Route component={Authenticator} path="/auth/:provider/callback" />
<Route component={CreatePool} path="/create_pool" />
<Route component={CreateGroup} path="/create_group" />
<Route component={Groups} path="/groups" />
<Route component={MyGroups} path="/mygroups" />
<Route component={UpdatePool} path="/update_pool" />
<Route component={Group} path="/group/:id" />
<Route component={Pool} path="/pool/:id" />
<Route component={Group} path="/groups/:id" />
<Route component={Pool} path="/pools/:id" />
<Route component={Groups} path="/groups" />
<Route component={Profile} path="/profile" />
<Route component={Logout} path="/logout" />
<Route component={Home} path="/" />
</Switch>
</BrowserRouter>

View File

@ -32,11 +32,11 @@ export async function createSession(
}
export async function getMe(): Promise<Carpool.User> {
let result = await makeAPIGetCall('/user', { userID: '@me' });
let result = await makeAPIGetCall('/users/@me');
return result.data.data;
}
export async function getPublicUser(id: string): Promise<Carpool.PublicUser> {
let result = await makeAPIGetCall('/user', { userID: id });
let result = await makeAPIGetCall(`/users/${id}`);
return result.data.data;
}

16
src/api/google.ts Normal file
View File

@ -0,0 +1,16 @@
export const GOOGLE_MAPS_API_KEY = 'AIzaSyDUnWIrt-H4RuP2YFLpVPz4oAjBhpOOoyI';
export async function searchForPlaces(query: string) {
const url = new URL(
'https://maps.googleapis.com/maps/api/place/findplacefromtext/json'
);
url.searchParams.set('key', GOOGLE_MAPS_API_KEY);
url.searchParams.set('input', query);
url.searchParams.set('inputtype', 'textquery');
url.searchParams.set('fields', 'place_id,name,formatted_address');
let res = await fetch(url.toString(), { mode: 'no-cors' });
let json = await res.json();
return json;
}

View File

@ -1,6 +1,6 @@
import { useContext, useEffect, useState } from 'react';
import { Redirect, useLocation, useParams } from 'react-router-dom';
import { API_ENDPOINT } from '../api/api';
import { makeAPIPostCall } from '../api/utils';
import AuthenticationContext from './AuthenticationContext';
export default function Authenticator() {
@ -13,26 +13,15 @@ export default function Authenticator() {
);
useEffect(() => {
fetch(`${API_ENDPOINT}/create_session`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
code,
provider,
}),
})
makeAPIPostCall('/create_session', { code, provider })
.then((response) => {
response.json().then((json) => {
if (json.status === 'success') {
localStorage.setItem('session_token', json.token);
refreshAuthState && refreshAuthState();
setStatus('authenticated');
} else {
setStatus('errored');
}
});
if (response.data.status === 'success') {
localStorage.setItem('session_token', response.data.token);
refreshAuthState && refreshAuthState();
setStatus('authenticated');
} else {
setStatus('errored');
}
})
.catch(() => {
setStatus('errored');

View File

@ -1,12 +1,11 @@
import { useCallback } from 'react';
import { makeAPIPostCall } from '../api/utils';
import Button from '@material-ui/core/Button';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import { makeStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography';
import { useState, useEffect } from 'react';
import CloudUploadIcon from '@material-ui/icons/CloudUpload';
import { useState } from 'react';
import { makeAPIPostCall } from '../api/utils';
const useStyles = makeStyles((theme) => ({
root: {
@ -22,14 +21,9 @@ const useStyles = makeStyles((theme) => ({
const CreateGroup = () => {
const [title, setTitle] = useState('No Title');
const classes = useStyles();
useEffect(() => {}, []);
const onClick = () => {
console.log({
title: title,
});
makeAPIPostCall('/group', {
title,
});
makeAPIPostCall('/groups/', { title });
};
return (

View File

@ -1,11 +1,12 @@
import { makeAPIPostCall } from '../api/utils';
import Button from '@material-ui/core/Button';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import { makeStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography';
import CloudUploadIcon from '@material-ui/icons/CloudUpload';
import { useState, useEffect } from 'react';
import { useEffect, useState } from 'react';
import { searchForPlaces } from '../api/google';
import { makeAPIPostCall } from '../api/utils';
const useStyles = makeStyles((theme) => ({
root: {
maxWidth: 345,
@ -30,7 +31,7 @@ const CreatePool = () => {
const [group, setGroup] = useState('');
const onClick = () => {
makeAPIPostCall('/pool', {
makeAPIPostCall('/pools/', {
title,
description,
start_time: start,
@ -49,128 +50,140 @@ const CreatePool = () => {
style={{ margin: '0.5rem', background: '#F3F5F4' }}
>
<CardContent>
<Typography gutterBottom variant="h5" component="h2"></Typography>
<Typography variant="body2" color="textSecondary" component="p">
<form>
<div className="form-group">
<h1 className="form-title" style={{ fontFamily: 'Impact' }}>
Create Pool
</h1>
<label className="" htmlFor="title">
Pool Title:{' '}
</label>
<input
type="text"
id="title"
name="title"
className="form-control d-flex"
placeholder="Enter title here..."
onChange={(event) => setTitle(event.target.value)}
></input>
</div>
<div className="form-group">
<label className="" htmlFor="capacity">
Pool Capacity:
</label>
<input
type="number"
id="capacity"
name="capacity"
className="form-control d-flex"
placeholder="0"
onChange={(event) =>
setCapacity(parseInt(event.target.value))
}
></input>
</div>
<div className="form-group">
<label className="" htmlFor="pool_start">
Start Time:
</label>
<input
type="datetime-local"
id="pool_start"
name="pool_start"
className="form-control"
placeholder=""
onChange={(event) => setStart(event.target.value)}
></input>
</div>
<div className="form-group">
<label className="" htmlFor="pool_end">
End Time:
</label>
<input
type="datetime-local"
id="pool_end"
name="pool_end"
className="form-control"
placeholder="Enter text here..."
onChange={(event) => setEnd(event.target.value)}
></input>
</div>
<div className="form-group">
<label className="" htmlFor="pool_direction">
Direction:
</label>
<select
id="direction"
name="direction"
onChange={(event) => setDirection(event.target.value)}
>
<option value="pickup">Picking Up</option>
<option value="dropoff">Dropping Off</option>
</select>
</div>
<div className="form-group">
<label className="" htmlFor="pool_type">
Type:
</label>
<select
id="type"
name="type"
onChange={(event) => setType(event.target.value)}
>
<option value="offer">Offering carpool</option>
<option value="request">Requesting carpooll</option>
</select>
</div>
<div className="form-group">
<label className="" htmlFor="title">
Pool Description:
</label>
<textarea
onChange={(event) => setDescription(event.target.value)}
id="Pool-text"
name="Pool-text"
style={{ height: '200px' }}
className="form-control"
placeholder="Enter text here..."
/>
</div>
<div className="form-group">
<label className="" htmlFor="pool_start">
Group:
</label>
<input
type="text"
className="form-control"
placeholder=""
onChange={(event) => setGroup(event.target.value)}
></input>
</div>
<Button
variant="contained"
color="primary"
className={classes.button}
onClick={onClick}
startIcon={<CloudUploadIcon />}
>
Submit
</Button>
<br />
</form>
</Typography>
<div className="form-group">
<h1 className="form-title">Create Pool</h1>
<label className="" htmlFor="title">
Pool Title:{' '}
</label>
<input
type="text"
id="title"
name="title"
className="form-control d-flex"
placeholder="Enter title here..."
onChange={(event) => setTitle(event.target.value)}
></input>
</div>
<div className="form-group">
<label className="" htmlFor="capacity">
Pool Capacity:
</label>
<input
type="number"
id="capacity"
name="capacity"
className="form-control d-flex"
placeholder="0"
onChange={(event) => setCapacity(parseInt(event.target.value))}
></input>
</div>
<div className="form-group">
<label className="" htmlFor="pool_start">
Start Time:
</label>
<input
type="datetime-local"
id="pool_start"
name="pool_start"
className="form-control"
placeholder=""
onChange={(event) => setStart(event.target.value)}
></input>
</div>
<div className="form-group">
<label className="" htmlFor="pool_end">
End Time:
</label>
<input
type="datetime-local"
id="pool_end"
name="pool_end"
className="form-control"
placeholder="Enter text here..."
onChange={(event) => setEnd(event.target.value)}
></input>
</div>
<div className="form-group">
<label className="" htmlFor="pool_direction">
Direction:
</label>
<select
id="direction"
name="direction"
onChange={(event) => setDirection(event.target.value)}
>
<option value="pickup">Picking Up</option>
<option value="dropoff">Dropping Off</option>
</select>
</div>
<div className="form-group">
<label className="" htmlFor="pool_type">
Type:
</label>
<select
id="type"
name="type"
onChange={(event) => setType(event.target.value)}
>
<option value="offer">Offering carpool</option>
<option value="request">Requesting carpool</option>
</select>
</div>
<div className="form-group">
<label className="" htmlFor="title">
Pool Description:
</label>
<textarea
onChange={(event) => setDescription(event.target.value)}
id="Pool-text"
name="Pool-text"
style={{ height: '200px' }}
className="form-control"
placeholder="Enter text here..."
/>
</div>
<div className="form-group">
<label className="" htmlFor="pool_start">
Group:
</label>
<input
type="text"
className="form-control"
placeholder=""
onChange={(event) => setGroup(event.target.value)}
></input>
</div>
<div className="form-group">
<label className="" htmlFor="location">
Location:
</label>
<input
type="text"
className="form-control"
id="location_input"
></input>
<button
onClick={(e) => {
e.preventDefault();
let input = document.getElementById(
'location_input'
) as HTMLInputElement;
let places = searchForPlaces(input.value);
console.log(places);
}}
>
Search
</button>
</div>
<Button
variant="contained"
color="primary"
className={classes.button}
onClick={onClick}
startIcon={<CloudUploadIcon />}
>
Submit
</Button>
</CardContent>
</Card>
</div>

View File

@ -10,7 +10,7 @@ const maybePluralize = (count: number, noun: string, suffix = 's') =>
const SAMPLE_POOLS: Carpool.Pool[] = [
{
id: '1234',
_id: '1234',
title: 'TJ Carpool',
description: 'Carpool from TJ track to homes',
start_time: '4/10/2021 3:00 PM',
@ -43,7 +43,7 @@ export default function Group() {
const [pools, setPools] = useState<Carpool.Pool[]>(SAMPLE_POOLS);
useEffect(() => {
makeAPIGetCall('/group', { groupID: id }).then((res) => {
makeAPIGetCall(`/groups/${id}`).then((res) => {
if ('error' in res.data) {
setError(true);
} else {
@ -51,7 +51,7 @@ export default function Group() {
}
});
makeAPIGetCall('/group_pools', { groupID: id }).then((res) => {
makeAPIGetCall(`/groups/${id}/pools`).then((res) => {
setPools(res.data.data);
});
}, [id]);
@ -90,7 +90,7 @@ export default function Group() {
{pools.map((pool, index) => {
return (
<Card style={{ margin: '0.5em' }} key={index}>
<a href={'/Pool/' + pool.id} className="card-title">
<a href={'/pools/' + pool._id} className="card-title">
{pool.title}
</a>
<p className="text-left">

View File

@ -8,6 +8,7 @@ import { makeStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import CloudUploadIcon from '@material-ui/icons/CloudUpload';
import Box from '@material-ui/core/Box';
import { makeAPIGetCall } from '../api/utils';
const useStyles = makeStyles((theme) => ({
root: {
@ -45,10 +46,23 @@ const Groups = () => {
}
});
};
const [groups, setGroups] = useState<Carpool.Group[]>([
{
_id: '1234',
name: 'TJ',
creator_id: 'michael',
member_ids: [],
},
]);
useEffect(() => {
callAPI();
makeAPIGetCall('/browse/groups').then((res) => {
if (res.data.data) {
setGroups(res.data.data);
}
});
}, []);
return (
<div
className=""
@ -72,22 +86,17 @@ const Groups = () => {
</Box>
<div className="container" style={{ fontFamily: 'Courier New' }}>
<br></br>
{state.MyGroups.map((group, index) => {
let background;
if (index % 2 === 0) {
background = '#F1EAE8';
} else {
background = '#FFFFFF';
}
{groups.map((group, index) => {
return (
<Card
className={classes.root + 'd-inline-flex'}
style={{ margin: '0.5rem' }}
>
<CardActionArea href={'/group/' + group.id}>
<CardActionArea href={'/group/' + group._id}>
<CardContent>
<Typography gutterBottom variant="h5" component="h2">
{group.group_title}
{group.name}
</Typography>
<Typography
variant="body2"
@ -102,19 +111,27 @@ const Groups = () => {
size="small"
color="primary"
onClick={() => {
let link: string = 'localhost:3000/group/' + group.id;
alert('Copied to Clipboard');
let link: string = 'localhost:3000/group/' + group._id;
navigator.clipboard.writeText(link);
}}
>
Share
</Button>
<Button
href={'/group/' + group.id}
href={'/group/' + group._id}
size="small"
color="primary"
>
Learn More
</Button>
<form action={'/requestgroup/' + group._id} method="POST">
<input
type="submit"
value="Request to Join"
className="btn btn-success d-flex"
/>
</form>
</CardActions>
</Card>
);

View File

@ -8,6 +8,7 @@ import { makeStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import CloudUploadIcon from '@material-ui/icons/CloudUpload';
import Box from '@material-ui/core/Box';
import { makeAPIGetCall } from '../api/utils';
const useStyles = makeStyles((theme) => ({
root: {
@ -45,10 +46,23 @@ const MyGroups = () => {
}
});
};
const [groups, setGroups] = useState<Carpool.Group[]>([
{
_id: '1234',
name: 'TJ',
creator_id: '12345Q',
member_ids: [],
},
]);
useEffect(() => {
callAPI();
makeAPIGetCall('/browse/groups').then((res) => {
if (res.data.data) {
setGroups(res.data.data);
}
});
}, []);
return (
<div
className=""
@ -72,7 +86,7 @@ const MyGroups = () => {
</Box>
<div className="container" style={{ fontFamily: 'Courier New' }}>
<br></br>
{state.MyGroups.map((group, index) => {
{groups.map((group, index) => {
let background;
if (index % 2 === 0) {
background = '#F1EAE8';
@ -84,10 +98,10 @@ const MyGroups = () => {
className={classes.root + 'd-inline-flex'}
style={{ margin: '0.5rem' }}
>
<CardActionArea href={'/group/' + group.id}>
<CardActionArea href={'/group/' + group._id}>
<CardContent>
<Typography gutterBottom variant="h5" component="h2">
{group.group_title}
{group.name}
</Typography>
<Typography
variant="body2"
@ -102,14 +116,15 @@ const MyGroups = () => {
size="small"
color="primary"
onClick={() => {
let link: string = 'localhost:3000/group/' + group.id;
alert('Copied to Clipboard');
let link: string = 'localhost:3000/group/' + group._id;
navigator.clipboard.writeText(link);
}}
>
Share
</Button>
<Button
href={'/group/' + group.id}
href={'/group/' + group._id}
size="small"
color="primary"
>

View File

@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react';
import { API_ENDPOINT } from '../api/api';
import { makeAPIGetCall } from '../api/utils';
const MyPools = () => {
// const id = props.match.params.id;
@ -55,14 +56,11 @@ const MyPools = () => {
]);
useEffect(() => {
console.log(process.env);
fetch(`${API_ENDPOINT}/my_pools`)
.then((response) => response.json())
.then((json) => {
if (json) {
setPools(json.data);
}
});
makeAPIGetCall('/users/@me/pools').then((res) => {
if (res.data.data) {
setPools(res.data.data);
}
});
}, []);
const maybePluralize = (count: number, noun: string, suffix = 's') =>
@ -96,7 +94,7 @@ const MyPools = () => {
className="card card-body text-left"
style={{ backgroundColor: background }}
>
<a href={'/Pool/' + pool.id} className="card-title">
<a href={'/pools/' + pool.id} className="card-title">
{pool.pool_title}
</a>
<p className="text-left">

View File

@ -5,7 +5,7 @@ import Card from '@material-ui/core/Card';
import Textarea from '@material-ui/core/TextareaAutosize';
import Typography from '@material-ui/core/Typography';
import Comment from './Comment';
import { makeAPIPostCall } from '../api/utils';
import { makeAPIGetCall, makeAPIPostCall } from '../api/utils';
import AuthenticationContext from './AuthenticationContext';
// eslint-disable-next-line
@ -76,8 +76,8 @@ export default function Pool() {
const onRegister = useCallback(() => {
if (user) {
let userID = user.id;
makeAPIPostCall('/join_pool', { id }).then(() => {
let userID = user._id;
makeAPIPostCall(`/pools/${id}/join`).then(() => {
if (pool) {
setPool({
...pool,
@ -89,13 +89,11 @@ export default function Pool() {
}, [user, id, pool]);
useEffect(() => {
fetch(`${process.env.REACT_APP_API_ENDPOINT}/pool/${id}`)
.then((response) => response.json())
.then((data) => {
if (data !== undefined) {
setPool(data);
}
});
makeAPIGetCall(`/pools/${id}`).then((response) => {
if (response.data.data) {
setPool(response.data.data);
}
});
}, [id]);
return (
@ -106,7 +104,7 @@ export default function Pool() {
{pool.title}
</Typography>
<Typography variant="subtitle1">
<b>Capacity</b>: {pool.participant_ids.length} / {pool.capacity}
<b>Capacity</b>: {pool.participant_ids?.length} / {pool.capacity}
</Typography>
<Typography variant="subtitle1">
<b>Start Time</b>: {pool.start_time}
@ -122,7 +120,7 @@ export default function Pool() {
style={{ marginTop: '0.5rem' }}
onClick={onRegister}
>
{pool.participant_ids.includes(user.id)
{pool.participant_ids?.includes(user._id)
? 'Unregister'
: 'Register'}
</Button>

View File

@ -0,0 +1,3 @@
export default function PoolMap() {
//
}

View File

@ -1,5 +1,6 @@
import { useState, useEffect } from 'react';
import { API_ENDPOINT } from '../api/api';
import { makeAPIGetCall } from '../api/utils';
const maybePluralize = (count: number, noun: string, suffix = 's') =>
`${count} ${noun}${count !== 1 ? suffix : ''}`;
@ -58,14 +59,11 @@ const Pools = () => {
]);
useEffect(() => {
console.log(process.env);
fetch(`${API_ENDPOINT}/my_pools`)
.then((response) => response.json())
.then((json) => {
if (json) {
setPools(json.data);
}
});
makeAPIGetCall(`/users/@me/pools`).then((res) => {
if (res.data.data) {
setPools(res.data.data);
}
});
}, []);
return (
@ -97,7 +95,7 @@ const Pools = () => {
className="card card-body text-left"
style={{ backgroundColor: background }}
>
<a href={'/Pool/' + pool.id} className="card-title">
<a href={'/pools/' + pool._id} className="card-title">
{pool.title}
</a>
<p className="text-left">
@ -105,7 +103,7 @@ const Pools = () => {
</p>
<p className="text-left">Start Time: {pool.start_time}</p>
<p className="text-left">End Time: {pool.end_time}</p>
<p className="" style={{color: '#9E6105'}}>
<p className="" style={{ color: '#9E6105' }}>
{maybePluralize(pool.comments.length, 'comment')}
</p>
</div>

View File

@ -20,14 +20,18 @@ const useStyles = makeStyles({
const Profile = () => {
const { user } = useContext(AuthenticationContext);
// const [groups, setGroups] = useState<Carpool.Group[]>([]);
const [groups, setGroups] = useState<Carpool.Group[]>([]);
const [pools, setPools] = useState<Carpool.Pool[]>([]);
const classes = useStyles();
useEffect(() => {
makeAPIGetCall('/my_pools').then((res) => {
makeAPIGetCall('/users/@me/pools').then((res) => {
if (res.data.data) setPools(res.data.data);
});
makeAPIGetCall('/users/@me/groups').then((res) => {
if (res.data.data) setGroups(res.data.data);
});
}, []);
if (!user) {
@ -47,16 +51,16 @@ const Profile = () => {
</h1>
<div className="container">
<h2>
<u>{user.username}'s Pools</u>
<u>My Pools (private)</u>
</h2>
<div className="">
<div>
{pools.map((pool) => {
return (
<Card
className={classes.root + 'd-inline-flex'}
style={{ margin: '0.5rem' }}
>
<CardActionArea href={'/pool/' + pool.id}>
<CardActionArea href={'/pools/' + pool._id}>
<CardContent>
<Typography gutterBottom variant="h5" component="h2">
{pool.title}
@ -75,14 +79,14 @@ const Profile = () => {
size="small"
color="primary"
onClick={() => {
let link: string = 'localhost:3000/pool/' + pool.id;
let link: string = 'localhost:3000/pools/' + pool._id;
navigator.clipboard.writeText(link);
}}
>
Share
</Button>
<Button
href={'/pool/' + pool.id}
href={'/pools/' + pool._id}
size="small"
color="primary"
>
@ -93,6 +97,24 @@ const Profile = () => {
);
})}
</div>
<h2>
<u>My Groups (private)</u>
<div>
{groups.map((group) => {
return (
<Card
key={group._id}
style={{ padding: '0.5rem', margin: '0.5rem' }}
>
<h1>
<a href={'/groups/' + group._id}>{group.name}</a>
</h1>
</Card>
);
})}
</div>
</h2>
</div>
</div>
);

View File

@ -1,10 +1,6 @@
import React, {
useState,
useEffect,
useCallback,
FormEventHandler,
} from 'react';
import { useState, useEffect, FormEventHandler } from 'react';
import { useParams } from 'react-router-dom';
import { makeAPIGetCall } from '../api/utils';
const UpdatePool = () => {
const id = useParams<{ id: string }>().id;
@ -21,26 +17,17 @@ const UpdatePool = () => {
comments: ['What is the covid vaccination status of all the participants?'],
});
const callAPI = useCallback(() => {
fetch(`${process.env.REACT_APP_API_ENDPOINT}/pool/${id}`)
.then((response) => response.json())
.then((data) => {
if (data !== undefined) {
setPool(data);
}
});
}, [id]);
const onSubmit: FormEventHandler<HTMLFormElement> = (e) => {
e.preventDefault();
fetch(`${process.env.REACT_APP_API_ENDPOINT}/update_pool`)
.then((response) => response.json())
.then((data) => {
console.log(data);
});
makeAPIGetCall(`/pools/${id}`).then((res) => {
console.log(res);
});
};
useEffect(() => {
callAPI();
}, [callAPI]);
makeAPIGetCall(`/pools/${id}`).then((res) => {
if (res.data.data) setPool(res.data.data);
});
}, [id]);
return (
<div
className="bg-dark"

8
src/types.d.ts vendored
View File

@ -1,6 +1,6 @@
declare namespace Carpool {
export interface Group {
id: string;
_id: string;
name: string;
member_ids: string[];
creator_id: string;
@ -8,14 +8,14 @@ declare namespace Carpool {
// Omits the email attribute
export interface PublicUser {
id: string;
_id: string;
username: string;
first_name: string;
last_name: string;
}
export interface User {
id: string;
_id: string;
email: string;
username: string;
first_name: string;
@ -31,7 +31,7 @@ declare namespace Carpool {
export type Status = 'pending' | 'cancelled' | 'completed' | 'interrupted';
export interface Pool {
id: string;
_id: string;
title: string;
description: string;
participant_ids: string[];

17877
yarn.lock

File diff suppressed because it is too large Load Diff