diff --git a/crabfit-backend/routes/createEvent.js b/crabfit-backend/routes/createEvent.js index 4e54479..08562f3 100644 --- a/crabfit-backend/routes/createEvent.js +++ b/crabfit-backend/routes/createEvent.js @@ -29,6 +29,7 @@ module.exports = async (req, res) => { name: name, created: currentTime, times: event.times, + timezone: event.timezone, }, }; @@ -39,6 +40,7 @@ module.exports = async (req, res) => { name: name, created: currentTime, times: event.times, + timezone: event.timezone, }); } catch (e) { console.error(e); diff --git a/crabfit-backend/swagger.yaml b/crabfit-backend/swagger.yaml index 750b507..f9656a6 100644 --- a/crabfit-backend/swagger.yaml +++ b/crabfit-backend/swagger.yaml @@ -19,6 +19,8 @@ definitions: type: "string" name: type: "string" + timezone: + type: "string" created: type: "integer" times: @@ -81,6 +83,8 @@ paths: properties: name: type: "string" + timezone: + type: "string" times: type: "array" items: diff --git a/crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.tsx b/crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.tsx index 37a6ac1..fad5a09 100644 --- a/crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.tsx +++ b/crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.tsx @@ -1,10 +1,11 @@ -import { useState, Fragment } from 'react'; +import { useState, useEffect, Fragment } from 'react'; import dayjs from 'dayjs'; import localeData from 'dayjs/plugin/localeData'; import customParseFormat from 'dayjs/plugin/customParseFormat'; import { useSettingsStore } from 'stores'; +import { Legend, Center } from 'components'; import { Wrapper, Container, @@ -21,6 +22,9 @@ import { TimeLabels, TimeLabel, TimeSpace, + People, + Person, + StyledMain, } from './availabilityViewerStyle'; dayjs.extend(localeData); @@ -38,83 +42,134 @@ const AvailabilityViewer = ({ }) => { const [tooltip, setTooltip] = useState(null); const timeFormat = useSettingsStore(state => state.timeFormat); + const [filteredPeople, setFilteredPeople] = useState([]); + const [touched, setTouched] = useState(false); + const [tempFocus, setTempFocus] = useState(null); + const [focusCount, setFocusCount] = useState(null); + + useEffect(() => { + setFilteredPeople(people.map(p => p.name)); + setTouched(people.length <= 1); + }, [people]); return ( - - - - {!!timeLabels.length && timeLabels.map((label, i) => - - {label.label?.length !== '' && {label.label}} - - )} - - {dates.map((date, i) => { - const parsedDate = isSpecificDates ? dayjs(date, 'DDMMYYYY') : dayjs().day(date); - const last = dates.length === i+1 || (isSpecificDates ? dayjs(dates[i+1], 'DDMMYYYY') : dayjs().day(dates[i+1])).diff(parsedDate, 'day') > 1; - return ( - - - {isSpecificDates && {parsedDate.format('MMM D')}} - {parsedDate.format('ddd')} + <> + + p.availability.length > 0).length} + onSegmentFocus={count => setFocusCount(count)} + /> +
Hover or tap the calendar below to see who is available
+ {people.length > 1 && ( + <> +
Click the names below to view people individually
+ + {people.map((person, i) => + { + setTempFocus(null); + if (filteredPeople.includes(person.name)) { + if (!touched) { + setTouched(true); + setFilteredPeople([person.name]); + } else { + setFilteredPeople(filteredPeople.filter(n => n !== person.name)); + } + } else { + setFilteredPeople([...filteredPeople, person.name]); + } + }} + onMouseOver={() => setTempFocus(person.name)} + onMouseOut={() => setTempFocus(null)} + >{person.name} + )} + + + )} +
- - {timeLabels.map((timeLabel, i) => { - if (!timeLabel.time) return null; - if (!times.includes(`${timeLabel.time}-${date}`)) { - return ( - - ); - } - const time = `${timeLabel.time}-${date}`; - const peopleHere = people.filter(person => person.availability.includes(time)).map(person => person.name); + + + + {!!timeLabels.length && timeLabels.map((label, i) => + + {label.label?.length !== '' && {label.label}} + + )} + + {dates.map((date, i) => { + const parsedDate = isSpecificDates ? dayjs(date, 'DDMMYYYY') : dayjs().day(date); + const last = dates.length === i+1 || (isSpecificDates ? dayjs(dates[i+1], 'DDMMYYYY') : dayjs().day(dates[i+1])).diff(parsedDate, 'day') > 1; + return ( + + + {isSpecificDates && {parsedDate.format('MMM D')}} + {parsedDate.format('ddd')} - return ( - -
- {last && dates.length !== i+1 && ( - - )} -
- ); - })} -
- {tooltip && ( - - {tooltip.available} - {tooltip.date} - {tooltip.people} - - )} -
+ + {timeLabels.map((timeLabel, i) => { + if (!timeLabel.time) return null; + if (!times.includes(`${timeLabel.time}-${date}`)) { + return ( + + ); + } + const time = `${timeLabel.time}-${date}`; + const peopleHere = tempFocus !== null + ? people.filter(person => person.availability.includes(time) && tempFocus === person.name).map(person => person.name) + : people.filter(person => person.availability.includes(time) && filteredPeople.includes(person.name)).map(person => person.name); + + return ( + + + {last && dates.length !== i+1 && ( + + )} + + ); + })} + + {tooltip && ( + + {tooltip.available} + {tooltip.date} + {tooltip.people} + + )} + + ); }; diff --git a/crabfit-frontend/src/components/AvailabilityViewer/availabilityViewerStyle.ts b/crabfit-frontend/src/components/AvailabilityViewer/availabilityViewerStyle.ts index 1abf22a..707488f 100644 --- a/crabfit-frontend/src/components/AvailabilityViewer/availabilityViewerStyle.ts +++ b/crabfit-frontend/src/components/AvailabilityViewer/availabilityViewerStyle.ts @@ -127,3 +127,35 @@ export const TimeLabel = styled.label` user-select: none; width: 100%; `; + +export const StyledMain = styled.div` + width: 600px; + margin: 20px auto; + max-width: calc(100% - 60px); +`; + +export const People = styled.div` + display: flex; + flex-wrap: wrap; + gap: 5px; + justify-content: center; + margin: 14px auto; +`; + +export const Person = styled.button` + font: inherit; + font-size: 15px; + border-radius: 3px; + border: 1px solid ${props => props.theme.text}; + color: ${props => props.theme.text}; + font-weight: 500; + background: transparent; + cursor: pointer; + padding: 2px 8px; + + ${props => props.filtered && ` + background: ${props.theme.primary}; + color: ${props.theme.background}; + border-color: ${props.theme.primary}; + `} +`; diff --git a/crabfit-frontend/src/components/Donate/Donate.tsx b/crabfit-frontend/src/components/Donate/Donate.tsx index 5d0b194..db42fb3 100644 --- a/crabfit-frontend/src/components/Donate/Donate.tsx +++ b/crabfit-frontend/src/components/Donate/Donate.tsx @@ -5,7 +5,7 @@ import { useTWAStore } from 'stores'; const PAYMENT_METHOD = 'https://play.google.com/billing'; const SKU = 'crab_donation'; -const Donate = () => { +const Donate = ({ onDonate = null }) => { const store = useTWAStore(); useEffect(() => { @@ -80,9 +80,12 @@ const Donate = () => { alert('Cannot make donation through Google. Please try donating through the website crab.fit 🦀'); } } + } else if (onDonate !== null) { + event.preventDefault(); + onDonate(); } }} - href="https://www.paypal.com/donate?business=N89X6YXRT5HKW&item_name=Crab+Fit+Donation¤cy_code=AUD" + href="https://www.paypal.com/donate?business=N89X6YXRT5HKW&item_name=Crab+Fit+Donation¤cy_code=AUD&amount=5" target="_blank" rel="noreferrer" > diff --git a/crabfit-frontend/src/components/Footer/Footer.tsx b/crabfit-frontend/src/components/Footer/Footer.tsx new file mode 100644 index 0000000..388a484 --- /dev/null +++ b/crabfit-frontend/src/components/Footer/Footer.tsx @@ -0,0 +1,28 @@ +import { useState } from 'react'; + +import { Donate } from 'components'; +import { Wrapper, Link } from './footerStyle'; + +const Footer = () => { + const [donateMode, setDonateMode] = useState(false); + + return ( + + ); +}; + +export default Footer; diff --git a/crabfit-frontend/src/components/Footer/footerStyle.ts b/crabfit-frontend/src/components/Footer/footerStyle.ts new file mode 100644 index 0000000..b15a9e1 --- /dev/null +++ b/crabfit-frontend/src/components/Footer/footerStyle.ts @@ -0,0 +1,23 @@ +import styled from '@emotion/styled'; + +export const Wrapper = styled.footer` + width: 600px; + margin: 20px auto; + max-width: calc(100% - 60px); + display: flex; + align-items: center; + justify-content: space-between; + + ${props => props.donateMode && ` + flex-wrap: wrap; + `} +`; + +export const Link = styled.a` + padding: 11px 10px; + white-space: nowrap; + + & strong { + font-weight: 800; + } +`; diff --git a/crabfit-frontend/src/components/Legend/Legend.tsx b/crabfit-frontend/src/components/Legend/Legend.tsx index 91b8645..16a51e3 100644 --- a/crabfit-frontend/src/components/Legend/Legend.tsx +++ b/crabfit-frontend/src/components/Legend/Legend.tsx @@ -11,6 +11,7 @@ const Legend = ({ min, max, total, + onSegmentFocus, ...props }) => { const theme = useTheme(); @@ -19,9 +20,13 @@ const Legend = ({ - + onSegmentFocus(null)}> {[...Array(max-min+1).keys()].map(i => - + onSegmentFocus(i+min)} + /> )} diff --git a/crabfit-frontend/src/components/index.ts b/crabfit-frontend/src/components/index.ts index ada1a57..0d60028 100644 --- a/crabfit-frontend/src/components/index.ts +++ b/crabfit-frontend/src/components/index.ts @@ -15,3 +15,4 @@ export { default as Center } from './Center/Center'; export { default as Donate } from './Donate/Donate'; export { default as Settings } from './Settings/Settings'; export { default as Egg } from './Egg/Egg'; +export { default as Footer } from './Footer/Footer'; diff --git a/crabfit-frontend/src/pages/Create/Create.tsx b/crabfit-frontend/src/pages/Create/Create.tsx index c5d600a..e221521 100644 --- a/crabfit-frontend/src/pages/Create/Create.tsx +++ b/crabfit-frontend/src/pages/Create/Create.tsx @@ -119,6 +119,7 @@ const Create = ({ offline }) => { event: { name: data.name, times: times, + timezone: data.timezone, }, }); setCreatedEvent(response.data); diff --git a/crabfit-frontend/src/pages/Event/Event.tsx b/crabfit-frontend/src/pages/Event/Event.tsx index 382ba33..a0184e1 100644 --- a/crabfit-frontend/src/pages/Event/Event.tsx +++ b/crabfit-frontend/src/pages/Event/Event.tsx @@ -9,11 +9,10 @@ import customParseFormat from 'dayjs/plugin/customParseFormat'; import { Center, - Donate, + Footer, TextField, SelectField, Button, - Legend, AvailabilityViewer, AvailabilityEditor, Error, @@ -21,7 +20,6 @@ import { import { StyledMain, - Footer, Logo, Title, EventName, @@ -398,14 +396,6 @@ const Event = (props) => { {tab === 'group' ? (
- - p.availability.length > 0).length} - /> -
Hover or tap the calendar below to see who is available
-
{ )} -
- Thank you for using Crab Fit. If you like it, consider donating. - -
+