From decfc2b7e6e5d17895c8788ff238dfc86f60c42b Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Tue, 1 Jun 2021 01:02:48 +1000 Subject: [PATCH 1/7] Memoized heatmap --- .../AvailabilityViewer/AvailabilityViewer.tsx | 160 ++++++++++-------- .../availabilityViewerStyle.ts | 2 +- .../src/components/Legend/Legend.tsx | 2 +- 3 files changed, 92 insertions(+), 72 deletions(-) diff --git a/crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.tsx b/crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.tsx index 881eaee..4597830 100644 --- a/crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.tsx +++ b/crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useRef, Fragment } from 'react'; +import { useState, useEffect, useRef, useMemo, Fragment } from 'react'; import { useTranslation } from 'react-i18next'; import dayjs from 'dayjs'; import localeData from 'dayjs/plugin/localeData'; @@ -65,6 +65,94 @@ const AvailabilityViewer = ({ setTouched(people.length <= 1); }, [people]); + const heatmap = useMemo(() => ( + + + {!!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')} + + 1} + > + {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 && ( + + )} + + ); + })} + + ), [ + people, + filteredPeople, + tempFocus, + focusCount, + highlight, + locale, + dates, + isSpecificDates, + max, + min, + t, + timeFormat, + timeLabels, + times, + ]); + return ( <> @@ -108,76 +196,8 @@ const AvailabilityViewer = ({ - - - {!!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')} + {heatmap} - 1} - > - {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 && ( props.highlight && props.peopleCount === props.maxPeople ? ` + ${props => props.highlight && props.peopleCount === props.maxPeople && props.peopleCount > 0 ? ` background-image: repeating-linear-gradient( 45deg, ${props.theme.primary}, diff --git a/crabfit-frontend/src/components/Legend/Legend.tsx b/crabfit-frontend/src/components/Legend/Legend.tsx index e8477a0..2760ded 100644 --- a/crabfit-frontend/src/components/Legend/Legend.tsx +++ b/crabfit-frontend/src/components/Legend/Legend.tsx @@ -34,7 +34,7 @@ const Legend = ({ 0} onMouseOver={() => onSegmentFocus(i)} /> )} From 2544f3b312440a95ed7f569dc55a0ae681e45cc3 Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Tue, 1 Jun 2021 02:45:15 +1000 Subject: [PATCH 2/7] A11y and donate popup --- crabfit-frontend/src/App.tsx | 9 ++-- .../src/components/Donate/Donate.tsx | 45 +++++++++++++--- .../src/components/Donate/donateStyle.ts | 52 +++++++++++++++++++ .../src/components/Footer/Footer.tsx | 21 ++------ .../src/components/Footer/footerStyle.ts | 16 ------ .../src/components/Settings/Settings.tsx | 15 ++++-- .../src/components/Settings/settingsStyle.ts | 4 +- .../components/ToggleField/ToggleField.tsx | 2 + .../ToggleField/toggleFieldStyle.ts | 1 + 9 files changed, 118 insertions(+), 47 deletions(-) create mode 100644 crabfit-frontend/src/components/Donate/donateStyle.ts diff --git a/crabfit-frontend/src/App.tsx b/crabfit-frontend/src/App.tsx index a31812d..743c72f 100644 --- a/crabfit-frontend/src/App.tsx +++ b/crabfit-frontend/src/App.tsx @@ -107,6 +107,11 @@ const App = () => { }, })} /> + + }> + + + ( }> @@ -135,10 +140,6 @@ const App = () => { )} /> - }> - - - {eggVisible && setEggVisible(false)} />} diff --git a/crabfit-frontend/src/components/Donate/Donate.tsx b/crabfit-frontend/src/components/Donate/Donate.tsx index 03dabfe..d245e57 100644 --- a/crabfit-frontend/src/components/Donate/Donate.tsx +++ b/crabfit-frontend/src/components/Donate/Donate.tsx @@ -1,15 +1,33 @@ -import { useEffect } from 'react'; +import { useState, useEffect, useRef } from 'react'; import { Button } from 'components'; import { useTWAStore } from 'stores'; import { useTranslation } from 'react-i18next'; +import { + Wrapper, + Options, +} from './donateStyle'; + const PAYMENT_METHOD = 'https://play.google.com/billing'; const SKU = 'crab_donation'; -const Donate = ({ onDonate = null }) => { +const Donate = () => { const store = useTWAStore(); const { t } = useTranslation('common'); + const firstLinkRef = useRef(); + const buttonRef = useRef(); + const modalRef = useRef(); + const [isOpen, _setIsOpen] = useState(false); + + const setIsOpen = open => { + _setIsOpen(open); + + if (open) { + window.setTimeout(() => firstLinkRef.current.focus(), 150); + } + }; + useEffect(() => { if (store.TWA === undefined) { store.setTWA(document.referrer.includes('android-app://fit.crab')); @@ -71,7 +89,7 @@ const Donate = ({ onDonate = null }) => { }; return ( - + + { + if (modalRef.current.contains(e.relatedTarget)) return; + setIsOpen(false); + }} + > + setIsOpen(false)} ref={firstLinkRef} href="https://www.paypal.com/donate?business=N89X6YXRT5HKW&item_name=Crab+Fit+Donation¤cy_code=AUD&amount=2" target="_blank" rel="noreferrer">{t('donate.options.$2')} + setIsOpen(false)} href="https://www.paypal.com/donate?business=N89X6YXRT5HKW&item_name=Crab+Fit+Donation¤cy_code=AUD&amount=5" target="_blank" rel="noreferrer">{t('donate.options.$5')} + setIsOpen(false)} href="https://www.paypal.com/donate?business=N89X6YXRT5HKW&item_name=Crab+Fit+Donation¤cy_code=AUD&amount=10" target="_blank" rel="noreferrer">{t('donate.options.$10')} + setIsOpen(false)} href="https://www.paypal.com/donate?business=N89X6YXRT5HKW&item_name=Crab+Fit+Donation¤cy_code=AUD" target="_blank" rel="noreferrer">{t('donate.options.choose')} + + ); } diff --git a/crabfit-frontend/src/components/Donate/donateStyle.ts b/crabfit-frontend/src/components/Donate/donateStyle.ts new file mode 100644 index 0000000..280813f --- /dev/null +++ b/crabfit-frontend/src/components/Donate/donateStyle.ts @@ -0,0 +1,52 @@ +import styled from '@emotion/styled'; + +export const Wrapper = styled.div` + margin-top: 6px; + margin-left: 12px; + position: relative; +`; + +export const Options = styled.div` + position: absolute; + bottom: calc(100% + 20px); + right: 0; + background-color: ${props => props.theme.background}; + ${props => props.theme.mode === 'dark' && ` + border: 1px solid ${props.theme.primaryBackground}; + `} + z-index: 60; + padding: 4px 10px; + border-radius: 14px; + box-sizing: border-box; + max-width: calc(100vw - 20px); + box-shadow: 0 3px 6px 0 rgba(0,0,0,.3); + + visibility: hidden; + pointer-events: none; + opacity: 0; + transform: translateY(5px); + transition: opacity .15s, transform .15s, visibility .15s; + + ${props => props.isOpen && ` + pointer-events: all; + opacity: 1; + transform: translateY(0); + visibility: visible; + `} + + & a { + display: block; + white-space: nowrap; + text-align: center; + padding: 4px 20px; + margin: 6px 0; + text-decoration: none; + border-radius: 100px; + background-color: ${props => props.theme.primary}; + color: ${props => props.theme.background}; + + &:hover { + text-decoration: underline; + } + } +`; diff --git a/crabfit-frontend/src/components/Footer/Footer.tsx b/crabfit-frontend/src/components/Footer/Footer.tsx index cbbfcef..8603aaf 100644 --- a/crabfit-frontend/src/components/Footer/Footer.tsx +++ b/crabfit-frontend/src/components/Footer/Footer.tsx @@ -1,28 +1,15 @@ -import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Donate } from 'components'; -import { Wrapper, Link } from './footerStyle'; +import { Wrapper } from './footerStyle'; const Footer = (props) => { - const [donateMode, setDonateMode] = useState(false); const { t } = useTranslation('common'); return ( -