Merge pull request #2 from GRA0007/dev

Dev
This commit is contained in:
Benjamin Grant 2021-04-12 13:42:03 +10:00 committed by GitHub
commit 2abdece66f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 75 additions and 20 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
/graphics /graphics
.DS_Store

View file

@ -27,6 +27,10 @@ const datastore = new Datastore({
app.use(express.json()); app.use(express.json());
app.use((req, res, next) => { app.use((req, res, next) => {
req.datastore = datastore; req.datastore = datastore;
req.types = {
event: process.env.NODE_ENV === 'production' ? 'Event' : 'DevEvent',
person: process.env.NODE_ENV === 'production' ? 'Person' : 'DevPerson',
};
next(); next();
}); });
app.options('*', cors(corsOptions)); app.options('*', cors(corsOptions));
@ -43,5 +47,5 @@ app.post('/event/:eventId/people/:personName', login);
app.patch('/event/:eventId/people/:personName', updatePerson); app.patch('/event/:eventId/people/:personName', updatePerson);
app.listen(port, () => { app.listen(port, () => {
console.log(`Crabfit API listening at http://localhost:${port}`) console.log(`Crabfit API listening at http://localhost:${port} in ${process.env.NODE_ENV === 'production' ? 'prod' : 'dev'} mode`)
}); });

View file

@ -1,6 +1,6 @@
{ {
"name": "crabfit-backend", "name": "crabfit-backend",
"version": "1.0.0", "version": "1.1.0",
"description": "API for Crabfit", "description": "API for Crabfit",
"main": "index.js", "main": "index.js",
"author": "Ben Grant", "author": "Ben Grant",

View file

@ -24,7 +24,7 @@ module.exports = async (req, res) => {
const currentTime = dayjs().unix(); const currentTime = dayjs().unix();
const entity = { const entity = {
key: req.datastore.key(['Event', eventId]), key: req.datastore.key([req.types.event, eventId]),
data: { data: {
name: name, name: name,
created: currentTime, created: currentTime,

View file

@ -6,8 +6,8 @@ module.exports = async (req, res) => {
const { person } = req.body; const { person } = req.body;
try { try {
const event = (await req.datastore.get(req.datastore.key(['Event', eventId])))[0]; const event = (await req.datastore.get(req.datastore.key([req.types.event, eventId])))[0];
const query = req.datastore.createQuery('Person') const query = req.datastore.createQuery(req.types.person)
.filter('eventId', eventId) .filter('eventId', eventId)
.filter('name', person.name); .filter('name', person.name);
let personResult = (await req.datastore.runQuery(query))[0][0]; let personResult = (await req.datastore.runQuery(query))[0][0];
@ -23,7 +23,7 @@ module.exports = async (req, res) => {
} }
const entity = { const entity = {
key: req.datastore.key('Person'), key: req.datastore.key(req.types.person),
data: { data: {
name: person.name.trim(), name: person.name.trim(),
password: hash, password: hash,

View file

@ -2,7 +2,7 @@ module.exports = async (req, res) => {
const { eventId } = req.params; const { eventId } = req.params;
try { try {
const event = (await req.datastore.get(req.datastore.key(['Event', eventId])))[0]; const event = (await req.datastore.get(req.datastore.key([req.types.event, eventId])))[0];
if (event) { if (event) {
res.send({ res.send({

View file

@ -2,7 +2,7 @@ module.exports = async (req, res) => {
const { eventId } = req.params; const { eventId } = req.params;
try { try {
const query = req.datastore.createQuery('Person').filter('eventId', eventId); const query = req.datastore.createQuery(req.types.person).filter('eventId', eventId);
let people = (await req.datastore.runQuery(query))[0]; let people = (await req.datastore.runQuery(query))[0];
people = people.map(person => ({ people = people.map(person => ({
name: person.name, name: person.name,

View file

@ -5,7 +5,7 @@ module.exports = async (req, res) => {
const { person } = req.body; const { person } = req.body;
try { try {
const query = req.datastore.createQuery('Person') const query = req.datastore.createQuery(req.types.person)
.filter('eventId', eventId) .filter('eventId', eventId)
.filter('name', personName); .filter('name', personName);
let personResult = (await req.datastore.runQuery(query))[0][0]; let personResult = (await req.datastore.runQuery(query))[0][0];

View file

@ -5,8 +5,8 @@ module.exports = async (req, res) => {
let personCount = null; let personCount = null;
try { try {
const eventQuery = req.datastore.createQuery(['__Stat_Kind__']).filter('kind_name', 'Event'); const eventQuery = req.datastore.createQuery(['__Stat_Kind__']).filter('kind_name', req.types.event);
const personQuery = req.datastore.createQuery(['__Stat_Kind__']).filter('kind_name', 'Person'); const personQuery = req.datastore.createQuery(['__Stat_Kind__']).filter('kind_name', req.types.person);
eventCount = (await req.datastore.runQuery(eventQuery))[0][0].count; eventCount = (await req.datastore.runQuery(eventQuery))[0][0].count;
personCount = (await req.datastore.runQuery(personQuery))[0][0].count; personCount = (await req.datastore.runQuery(personQuery))[0][0].count;

View file

@ -5,7 +5,7 @@ module.exports = async (req, res) => {
const { person } = req.body; const { person } = req.body;
try { try {
const query = req.datastore.createQuery('Person') const query = req.datastore.createQuery(req.types.person)
.filter('eventId', eventId) .filter('eventId', eventId)
.filter('name', personName); .filter('name', personName);
let personResult = (await req.datastore.runQuery(query))[0][0]; let personResult = (await req.datastore.runQuery(query))[0][0];

View file

@ -1,6 +1,6 @@
{ {
"name": "crabfit-frontend", "name": "crabfit-frontend",
"version": "0.1.0", "version": "1.0.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@emotion/react": "^11.1.5", "@emotion/react": "^11.1.5",

View file

@ -1,4 +1,4 @@
import { useState, Suspense, lazy } from 'react'; import { useState, useEffect, Suspense, lazy } from 'react';
import { import {
BrowserRouter, BrowserRouter,
Switch, Switch,
@ -8,16 +8,22 @@ import { ThemeProvider, Global } from '@emotion/react';
import { Settings, Loading } from 'components'; import { Settings, Loading } from 'components';
import { useSettingsStore } from 'stores';
import theme from 'theme'; import theme from 'theme';
const Home = lazy(() => import('pages/Home/Home')); const Home = lazy(() => import('pages/Home/Home'));
const Event = lazy(() => import('pages/Event/Event')); const Event = lazy(() => import('pages/Event/Event'));
const App = () => { const App = () => {
const colortheme = useSettingsStore(state => state.theme);
const darkQuery = window.matchMedia('(prefers-color-scheme: dark)'); const darkQuery = window.matchMedia('(prefers-color-scheme: dark)');
const [isDark, setIsDark] = useState(darkQuery.matches); const [isDark, setIsDark] = useState(darkQuery.matches);
darkQuery.addListener(e => setIsDark(e.matches)); darkQuery.addListener(e => colortheme === 'System' && setIsDark(e.matches));
useEffect(() => {
setIsDark(colortheme === 'System' ? darkQuery.matches : colortheme === 'Dark');
}, [colortheme, darkQuery.matches]);
return ( return (
<BrowserRouter> <BrowserRouter>

View file

@ -2,7 +2,7 @@ import { Button } from 'components';
const Donate = () => ( const Donate = () => (
<div style={{ marginTop: 6, marginLeft: 12 }}> <div style={{ marginTop: 6, marginLeft: 12 }}>
<a href="https://www.paypal.com/donate?business=N89X6YXRT5HKW&item_name=Crab+Fit+Donation&currency_code=AUD" target="_blank" rel="noreferrer"> <a onClick={() => gtag('event', 'donate', { 'event_category': 'donate' })} href="https://www.paypal.com/donate?business=N89X6YXRT5HKW&item_name=Crab+Fit+Donation&currency_code=AUD" target="_blank" rel="noreferrer">
<Button <Button
buttonHeight="30px" buttonHeight="30px"
buttonWidth="90px" buttonWidth="90px"

View file

@ -49,6 +49,15 @@ const Settings = () => {
value={store.timeFormat} value={store.timeFormat}
onChange={value => store.setTimeFormat(value)} onChange={value => store.setTimeFormat(value)}
/> />
<ToggleField
label="Theme"
name="theme"
id="theme"
options={['System', 'Light', 'Dark']}
value={store.theme}
onChange={value => store.setTheme(value)}
/>
</Modal> </Modal>
</> </>
); );

View file

@ -50,7 +50,9 @@ export const Modal = styled.div`
top: 70px; top: 70px;
right: 12px; right: 12px;
background-color: ${props => props.theme.background}; background-color: ${props => props.theme.background};
border: 1px solid ${props => props.theme.primaryBackground}; ${props => props.theme.mode === 'dark' && `
border: 1px solid ${props.theme.primaryBackground};
`}
z-index: 150; z-index: 150;
padding: 10px 18px; padding: 10px 18px;
border-radius: 3px; border-radius: 3px;

View file

@ -64,7 +64,7 @@ const times = {
'21', '21',
'22', '22',
'23', '23',
'0', '24',
], ],
}; };
@ -124,6 +124,7 @@ const TimeRangeField = ({
<Handle <Handle
value={start} value={start}
label={times[timeFormat][start]} label={times[timeFormat][start]}
extraPadding={end - start === 1 ? 'padding-right: 20px;' : (start - end === 1 ? 'padding-left: 20px;' : '')}
onMouseDown={() => { onMouseDown={() => {
document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mousemove', handleMouseMove);
isStartMoving.current = true; isStartMoving.current = true;
@ -146,6 +147,7 @@ const TimeRangeField = ({
<Handle <Handle
value={end} value={end}
label={times[timeFormat][end]} label={times[timeFormat][end]}
extraPadding={end - start === 1 ? 'padding-left: 20px;' : (start - end === 1 ? 'padding-right: 20px;' : '')}
onMouseDown={() => { onMouseDown={() => {
document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mousemove', handleMouseMove);
isEndMoving.current = true; isEndMoving.current = true;

View file

@ -60,6 +60,7 @@ export const Handle = styled.div`
text-align: center; text-align: center;
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
${props => props.extraPadding}
} }
`; `;

View file

@ -65,6 +65,8 @@ const Event = (props) => {
const [min, setMin] = useState(0); const [min, setMin] = useState(0);
const [max, setMax] = useState(0); const [max, setMax] = useState(0);
const [copied, setCopied] = useState(null);
useEffect(() => { useEffect(() => {
const fetchEvent = async () => { const fetchEvent = async () => {
try { try {
@ -255,6 +257,9 @@ const Event = (props) => {
} }
} finally { } finally {
setIsLoginLoading(false); setIsLoginLoading(false);
gtag('event', 'login', {
'event_category': 'event',
});
} }
}; };
@ -272,10 +277,22 @@ const Event = (props) => {
{(!!event || isLoading) ? ( {(!!event || isLoading) ? (
<> <>
<EventName isLoading={isLoading}>{event?.name}</EventName> <EventName isLoading={isLoading}>{event?.name}</EventName>
<ShareInfo>https://crab.fit/{id}</ShareInfo> <ShareInfo
onClick={() => navigator.clipboard?.writeText(`https://crab.fit/${id}`)
.then(() => {
setCopied('Copied!');
setTimeout(() => setCopied(null), 1000);
gtag('event', 'copy_link', {
'event_category': 'event',
});
})
.catch((e) => console.error('Failed to copy', e))
}
title={!!navigator.clipboard ? 'Click to copy' : ''}
>{copied ?? `https://crab.fit/${id}`}</ShareInfo>
<ShareInfo isLoading={isLoading}> <ShareInfo isLoading={isLoading}>
{!!event?.name && {!!event?.name &&
<>Copy the link to this page, or share via <a href={`mailto:?subject=${encodeURIComponent(`Scheduling ${event?.name}`)}&body=${encodeURIComponent(`Visit this link to enter your availabilities: https://crab.fit/${id}`)}`}>email</a>.</> <>Copy the link to this page, or share via <a onClick={() => gtag('event', 'send_email', { 'event_category': 'event' })} href={`mailto:?subject=${encodeURIComponent(`Scheduling ${event?.name}`)}&body=${encodeURIComponent(`Visit this link to enter your availabilities: https://crab.fit/${id}`)}`}>email</a>.</>
} }
</ShareInfo> </ShareInfo>
</> </>

View file

@ -89,6 +89,14 @@ export const ShareInfo = styled.p`
border-radius: 3px; border-radius: 3px;
} }
`} `}
${props => props.onClick && `
cursor: pointer;
&:hover {
color: ${props.theme.primaryDark};
}
`}
`; `;
export const Tabs = styled.div` export const Tabs = styled.div`

View file

@ -131,6 +131,9 @@ const Home = () => {
}, },
}); });
push(`/${response.data.id}`); push(`/${response.data.id}`);
gtag('event', 'create_event', {
'event_category': 'home',
});
} catch (e) { } catch (e) {
setError('An error ocurred while creating the event. Please try again later.'); setError('An error ocurred while creating the event. Please try again later.');
console.error(e); console.error(e);

View file

@ -5,9 +5,11 @@ export const useSettingsStore = create(persist(
set => ({ set => ({
weekStart: 0, weekStart: 0,
timeFormat: '12h', timeFormat: '12h',
theme: 'System',
setWeekStart: weekStart => set({ weekStart }), setWeekStart: weekStart => set({ weekStart }),
setTimeFormat: timeFormat => set({ timeFormat }), setTimeFormat: timeFormat => set({ timeFormat }),
setTheme: theme => set({ theme }),
}), }),
{ name: 'crabfit-settings' }, { name: 'crabfit-settings' },
)); ));