diff --git a/crabfit-frontend/public/index.css b/crabfit-frontend/public/index.css index 399b4a0..80d01b8 100644 --- a/crabfit-frontend/public/index.css +++ b/crabfit-frontend/public/index.css @@ -1,7 +1,7 @@ @font-face { - font-family: Karla; - src: url('fonts/karla-variable.ttf') format('truetype'); - font-weight: 1 999; + font-family: Karla; + src: url('fonts/karla-variable.ttf') format('truetype'); + font-weight: 1 999; } @font-face { diff --git a/crabfit-frontend/public/index.html b/crabfit-frontend/public/index.html index 4ccb9ac..2352c9d 100644 --- a/crabfit-frontend/public/index.html +++ b/crabfit-frontend/public/index.html @@ -5,10 +5,10 @@ - + - - - + + + - + Crab Fit diff --git a/crabfit-frontend/src/App.tsx b/crabfit-frontend/src/App.tsx index 7a4b0aa..23b8b5b 100644 --- a/crabfit-frontend/src/App.tsx +++ b/crabfit-frontend/src/App.tsx @@ -20,8 +20,8 @@ const wb = new Workbox('sw.js'); const App = () => { const colortheme = useSettingsStore(state => state.theme); - const darkQuery = window.matchMedia('(prefers-color-scheme: dark)'); - const [isDark, setIsDark] = useState(darkQuery.matches); + const darkQuery = window.matchMedia('(prefers-color-scheme: dark)'); + const [isDark, setIsDark] = useState(darkQuery.matches); const [offline, setOffline] = useState(!window.navigator.onLine); const [eggCount, setEggCount] = useState(0); @@ -46,7 +46,7 @@ const App = () => { [eggCount, eggKey] ); - darkQuery.addListener(e => colortheme === 'System' && setIsDark(e.matches)); + darkQuery.addListener(e => colortheme === 'System' && setIsDark(e.matches)); useEffect(() => { const onOffline = () => setOffline(true); @@ -87,56 +87,56 @@ const App = () => { }, [colortheme, darkQuery.matches]); return ( - - - ({ - html: { - scrollBehavior: 'smooth', - }, - body: { - backgroundColor: theme.background, - color: theme.text, - fontFamily: `'Karla', sans-serif`, - fontWeight: theme.mode === 'dark' ? 500 : 600, - margin: 0, - }, - a: { - color: theme.primary, - }, - '*::-webkit-scrollbar': { - width: 16, - height: 16, - }, - '*::-webkit-scrollbar-track': { - background: `${theme.primaryBackground}`, - }, - '*::-webkit-scrollbar-thumb': { - borderRadius: 100, - border: `4px solid ${theme.primaryBackground}`, - width: 12, - background: `${theme.mode === 'light' ? theme.primaryLight : theme.primaryDark}AA`, - }, - '*::-webkit-scrollbar-thumb:hover': { - background: `${theme.mode === 'light' ? theme.primaryLight : theme.primaryDark}CC`, - }, - '*::-webkit-scrollbar-thumb:active': { - background: `${theme.mode === 'light' ? theme.primaryLight : theme.primaryDark}`, - }, - })} - /> + + + ({ + html: { + scrollBehavior: 'smooth', + }, + body: { + backgroundColor: theme.background, + color: theme.text, + fontFamily: `'Karla', sans-serif`, + fontWeight: theme.mode === 'dark' ? 500 : 600, + margin: 0, + }, + a: { + color: theme.primary, + }, + '*::-webkit-scrollbar': { + width: 16, + height: 16, + }, + '*::-webkit-scrollbar-track': { + background: `${theme.primaryBackground}`, + }, + '*::-webkit-scrollbar-thumb': { + borderRadius: 100, + border: `4px solid ${theme.primaryBackground}`, + width: 12, + background: `${theme.mode === 'light' ? theme.primaryLight : theme.primaryDark}AA`, + }, + '*::-webkit-scrollbar-thumb:hover': { + background: `${theme.mode === 'light' ? theme.primaryLight : theme.primaryDark}CC`, + }, + '*::-webkit-scrollbar-thumb:active': { + background: `${theme.mode === 'light' ? theme.primaryLight : theme.primaryDark}`, + }, + })} + /> }> - - ( + + ( }> )} /> - ( + ( }> @@ -146,17 +146,17 @@ const App = () => { )} /> - ( + ( }> )} /> - ( + ( }> )} /> - + {updateAvailable && ( }> @@ -165,8 +165,8 @@ const App = () => { )} {eggVisible && setEggVisible(false)} />} - - + + ); } diff --git a/crabfit-frontend/src/components/AvailabilityEditor/AvailabilityEditor.tsx b/crabfit-frontend/src/components/AvailabilityEditor/AvailabilityEditor.tsx index b1a93d7..165ed5b 100644 --- a/crabfit-frontend/src/components/AvailabilityEditor/AvailabilityEditor.tsx +++ b/crabfit-frontend/src/components/AvailabilityEditor/AvailabilityEditor.tsx @@ -9,17 +9,17 @@ import dayjs_timezone from 'dayjs/plugin/timezone'; import utc from 'dayjs/plugin/utc'; import { - Wrapper, + Wrapper, ScrollWrapper, - Container, - Date, - Times, - DateLabel, - DayLabel, - Spacer, - TimeLabels, - TimeLabel, - TimeSpace, + Container, + Date, + Times, + DateLabel, + DayLabel, + Spacer, + TimeLabels, + TimeLabel, + TimeSpace, StyledMain, } from 'components/AvailabilityViewer/availabilityViewerStyle'; import { Time } from './availabilityEditorStyle'; @@ -37,34 +37,34 @@ dayjs.extend(utc); dayjs.extend(dayjs_timezone); const AvailabilityEditor = ({ - times, - timeLabels, - dates, + times, + timeLabels, + dates, timezone, isSpecificDates, - value = [], - onChange, - ...props + value = [], + onChange, + ...props }) => { const { t } = useTranslation('event'); const locale = useLocaleUpdateStore(state => state.locale); - const [selectingTimes, _setSelectingTimes] = useState([]); - const staticSelectingTimes = useRef([]); - const setSelectingTimes = newTimes => { - staticSelectingTimes.current = newTimes; - _setSelectingTimes(newTimes); - }; + const [selectingTimes, _setSelectingTimes] = useState([]); + const staticSelectingTimes = useRef([]); + const setSelectingTimes = newTimes => { + staticSelectingTimes.current = newTimes; + _setSelectingTimes(newTimes); + }; - const startPos = useRef({}); - const staticMode = useRef(null); - const [mode, _setMode] = useState(staticMode.current); - const setMode = newMode => { - staticMode.current = newMode; - _setMode(newMode); - }; + const startPos = useRef({}); + const staticMode = useRef(null); + const [mode, _setMode] = useState(staticMode.current); + const setMode = newMode => { + staticMode.current = newMode; + _setMode(newMode); + }; - return ( + return ( <>
{t('event:you.info')}
@@ -98,89 +98,89 @@ const AvailabilityEditor = ({
)} - + - - - {!!timeLabels.length && timeLabels.map((label, i) => - - {label.label?.length !== '' && {label.label}} - - )} - - {dates.map((date, x) => { - const parsedDate = isSpecificDates ? dayjs(date, 'DDMMYYYY') : dayjs().day(date); - const last = dates.length === x+1 || (isSpecificDates ? dayjs(dates[x+1], 'DDMMYYYY') : dayjs().day(dates[x+1])).diff(parsedDate, 'day') > 1; - return ( - - - {isSpecificDates && {parsedDate.format('MMM D')}} - {parsedDate.format('ddd')} + + + {!!timeLabels.length && timeLabels.map((label, i) => + + {label.label?.length !== '' && {label.label}} + + )} + + {dates.map((date, x) => { + const parsedDate = isSpecificDates ? dayjs(date, 'DDMMYYYY') : dayjs().day(date); + const last = dates.length === x+1 || (isSpecificDates ? dayjs(dates[x+1], 'DDMMYYYY') : dayjs().day(dates[x+1])).diff(parsedDate, 'day') > 1; + return ( + + + {isSpecificDates && {parsedDate.format('MMM D')}} + {parsedDate.format('ddd')} 1} > - {timeLabels.map((timeLabel, y) => { - if (!timeLabel.time) return null; - if (!times.includes(`${timeLabel.time}-${date}`)) { - return ( - - ); - } - const time = `${timeLabel.time}-${date}`; + {timeLabels.map((timeLabel, y) => { + if (!timeLabel.time) return null; + if (!times.includes(`${timeLabel.time}-${date}`)) { + return ( + + ); + } + const time = `${timeLabel.time}-${date}`; - return ( - - - {last && dates.length !== x+1 && ( - - )} - - ); - })} - + document.addEventListener('pointerup', () => { + if (staticMode.current === 'add') { + onChange([...value, ...staticSelectingTimes.current]); + } else if (staticMode.current === 'remove') { + onChange(value.filter(t => !staticSelectingTimes.current.includes(t))); + } + setMode(null); + }, { once: true }); + }} + onPointerEnter={() => { + if (staticMode.current) { + let found = []; + for (let cy = Math.min(startPos.current.y, y); cy < Math.max(startPos.current.y, y)+1; cy++) { + for (let cx = Math.min(startPos.current.x, x); cx < Math.max(startPos.current.x, x)+1; cx++) { + found.push({y: cy, x: cx}); + } + } + setSelectingTimes(found.filter(d => timeLabels[d.y].time?.length === 4).map(d => `${timeLabels[d.y].time}-${dates[d.x]}`)); + } + }} + /> + ); + })} + + + {last && dates.length !== x+1 && ( + + )} + + ); + })} + - + - ); + ); }; export default AvailabilityEditor; diff --git a/crabfit-frontend/src/components/AvailabilityEditor/availabilityEditorStyle.ts b/crabfit-frontend/src/components/AvailabilityEditor/availabilityEditorStyle.ts index 417ae33..65cb48e 100644 --- a/crabfit-frontend/src/components/AvailabilityEditor/availabilityEditorStyle.ts +++ b/crabfit-frontend/src/components/AvailabilityEditor/availabilityEditorStyle.ts @@ -1,24 +1,24 @@ import styled from '@emotion/styled'; export const Time = styled.div` - height: 10px; - touch-action: none; + height: 10px; + touch-action: none; transition: background-color .1s; ${props => props.time.slice(2, 4) === '00' && ` - border-top: 2px solid ${props.theme.text}; - `} - ${props => props.time.slice(2, 4) !== '00' && ` - border-top: 2px solid transparent; - `} - ${props => props.time.slice(2, 4) === '30' && ` - border-top: 2px dotted ${props.theme.text}; - `} + border-top: 2px solid ${props.theme.text}; + `} + ${props => props.time.slice(2, 4) !== '00' && ` + border-top: 2px solid transparent; + `} + ${props => props.time.slice(2, 4) === '30' && ` + border-top: 2px dotted ${props.theme.text}; + `} - ${props => (props.selected || (props.mode === 'add' && props.selecting)) && ` - background-color: ${props.theme.primary}; - `}; - ${props => props.mode === 'remove' && props.selecting && ` - background-color: ${props.theme.background}; - `}; + ${props => (props.selected || (props.mode === 'add' && props.selecting)) && ` + background-color: ${props.theme.primary}; + `}; + ${props => props.mode === 'remove' && props.selecting && ` + background-color: ${props.theme.background}; + `}; `; diff --git a/crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.tsx b/crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.tsx index 02564f3..92ec00c 100644 --- a/crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.tsx +++ b/crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.tsx @@ -9,23 +9,23 @@ import { useSettingsStore, useLocaleUpdateStore } from 'stores'; import { Legend, Center } from 'components'; import { - Wrapper, - ScrollWrapper, - Container, - Date, - Times, - DateLabel, - DayLabel, - Time, - Spacer, - Tooltip, - TooltipTitle, - TooltipDate, - TooltipContent, + Wrapper, + ScrollWrapper, + Container, + Date, + Times, + DateLabel, + DayLabel, + Time, + Spacer, + Tooltip, + TooltipTitle, + TooltipDate, + TooltipContent, TooltipPerson, - TimeLabels, - TimeLabel, - TimeSpace, + TimeLabels, + TimeLabel, + TimeSpace, People, Person, StyledMain, @@ -38,16 +38,16 @@ dayjs.extend(customParseFormat); dayjs.extend(relativeTime); const AvailabilityViewer = ({ - times, - timeLabels, - dates, + times, + timeLabels, + dates, isSpecificDates, - people = [], - min = 0, - max = 0, - ...props + people = [], + min = 0, + max = 0, + ...props }) => { - const [tooltip, setTooltip] = useState(null); + const [tooltip, setTooltip] = useState(null); const timeFormat = useSettingsStore(state => state.timeFormat); const highlight = useSettingsStore(state => state.highlight); const [filteredPeople, setFilteredPeople] = useState([]); @@ -153,7 +153,7 @@ const AvailabilityViewer = ({ times, ]); - return ( + return ( <> - - - {heatmap} + + + {heatmap} - {tooltip && ( - - {tooltip.available} - {tooltip.date} + {tooltip && ( + + {tooltip.available} + {tooltip.date} {!!filteredPeople.length && ( - + {tooltip.people.map(person => {person} )} @@ -215,12 +215,12 @@ const AvailabilityViewer = ({ )} )} - - )} + + )} - + - ); + ); }; export default AvailabilityViewer; diff --git a/crabfit-frontend/src/components/AvailabilityViewer/availabilityViewerStyle.ts b/crabfit-frontend/src/components/AvailabilityViewer/availabilityViewerStyle.ts index 330b3cc..76cc3cf 100644 --- a/crabfit-frontend/src/components/AvailabilityViewer/availabilityViewerStyle.ts +++ b/crabfit-frontend/src/components/AvailabilityViewer/availabilityViewerStyle.ts @@ -1,8 +1,8 @@ import styled from '@emotion/styled'; export const Wrapper = styled.div` - overflow-y: visible; - margin: 20px 0; + overflow-y: visible; + margin: 20px 0; position: relative; `; @@ -11,30 +11,30 @@ export const ScrollWrapper = styled.div` `; export const Container = styled.div` - display: inline-flex; - box-sizing: border-box; + display: inline-flex; + box-sizing: border-box; min-width: 100%; - align-items: flex-end; - justify-content: center; - padding: 0 calc(calc(100% - 600px) / 2); + align-items: flex-end; + justify-content: center; + padding: 0 calc(calc(100% - 600px) / 2); - @media (max-width: 660px) { - padding: 0 30px; - } + @media (max-width: 660px) { + padding: 0 30px; + } `; export const Date = styled.div` - flex-shrink: 0; - display: flex; - flex-direction: column; - width: 60px; - min-width: 60px; - margin-bottom: 10px; + flex-shrink: 0; + display: flex; + flex-direction: column; + width: 60px; + min-width: 60px; + margin-bottom: 10px; `; export const Times = styled.div` - display: flex; - flex-direction: column; + display: flex; + flex-direction: column; border-bottom: 2px solid ${props => props.theme.text}; border-left: 1px solid ${props => props.theme.text}; @@ -57,44 +57,44 @@ export const Times = styled.div` `; export const DateLabel = styled.label` - display: block; - font-size: 12px; - text-align: center; - user-select: none; + display: block; + font-size: 12px; + text-align: center; + user-select: none; `; export const DayLabel = styled.label` - display: block; - font-size: 15px; - text-align: center; - user-select: none; + display: block; + font-size: 15px; + text-align: center; + user-select: none; `; export const Time = styled.div` - height: 10px; + height: 10px; background-origin: border-box; transition: background-color .1s; - ${props => props.time.slice(2, 4) === '00' && ` - border-top: 2px solid ${props.theme.text}; - `} - ${props => props.time.slice(2, 4) !== '00' && ` - border-top: 2px solid transparent; - `} - ${props => props.time.slice(2, 4) === '30' && ` - border-top: 2px dotted ${props.theme.text}; - `} + ${props => props.time.slice(2, 4) === '00' && ` + border-top: 2px solid ${props.theme.text}; + `} + ${props => props.time.slice(2, 4) !== '00' && ` + border-top: 2px solid transparent; + `} + ${props => props.time.slice(2, 4) === '30' && ` + border-top: 2px dotted ${props.theme.text}; + `} background-color: ${props => `${props.theme.primary}${Math.round((props.peopleCount/props.maxPeople)*255).toString(16)}`}; ${props => props.highlight && props.peopleCount === props.maxPeople && props.peopleCount > 0 && ` background-image: repeating-linear-gradient( 45deg, - transparent, - transparent 4.3px, - ${props.theme.primaryDark} 4.3px, - ${props.theme.primaryDark} 8.6px - ); + transparent, + transparent 4.3px, + ${props.theme.primaryDark} 4.3px, + ${props.theme.primaryDark} 8.6px + ); `} @media (prefers-reduced-motion: reduce) { @@ -103,40 +103,40 @@ export const Time = styled.div` `; export const Spacer = styled.div` - width: 12px; - flex-shrink: 0; + width: 12px; + flex-shrink: 0; `; export const Tooltip = styled.div` - position: absolute; - top: ${props => props.y}px; - left: ${props => props.x}px; - transform: translateX(-50%); - border: 1px solid ${props => props.theme.text}; - border-radius: 3px; - padding: 4px 8px; - background-color: ${props => props.theme.background}${props => props.theme.mode === 'light' ? 'EE' : 'DD'}; - max-width: 200px; - pointer-events: none; + position: absolute; + top: ${props => props.y}px; + left: ${props => props.x}px; + transform: translateX(-50%); + border: 1px solid ${props => props.theme.text}; + border-radius: 3px; + padding: 4px 8px; + background-color: ${props => props.theme.background}${props => props.theme.mode === 'light' ? 'EE' : 'DD'}; + max-width: 200px; + pointer-events: none; z-index: 100; user-select: none; `; export const TooltipTitle = styled.span` - font-size: 15px; - display: block; - font-weight: 700; + font-size: 15px; + display: block; + font-weight: 700; `; export const TooltipDate = styled.span` - font-size: 13px; - display: block; - opacity: .8; - font-weight: 600; + font-size: 13px; + display: block; + opacity: .8; + font-weight: 600; `; export const TooltipContent = styled.div` - font-size: 13px; + font-size: 13px; padding: 4px 0; `; @@ -154,38 +154,38 @@ export const TooltipPerson = styled.span` `; export const TimeLabels = styled.div` - flex-shrink: 0; - display: flex; - flex-direction: column; - width: 40px; - padding-right: 6px; + flex-shrink: 0; + display: flex; + flex-direction: column; + width: 40px; + padding-right: 6px; `; export const TimeSpace = styled.div` - height: 10px; - position: relative; - border-top: 2px solid transparent; + height: 10px; + position: relative; + border-top: 2px solid transparent; &.timespace { background-origin: border-box; background-image: repeating-linear-gradient( 45deg, - transparent, - transparent 4.3px, - ${props => props.theme.loading} 4.3px, - ${props => props.theme.loading} 8.6px - ); + transparent, + transparent 4.3px, + ${props => props.theme.loading} 4.3px, + ${props => props.theme.loading} 8.6px + ); } `; export const TimeLabel = styled.label` - display: block; - position: absolute; - top: -.7em; - font-size: 12px; - text-align: right; - user-select: none; - width: 100%; + display: block; + position: absolute; + top: -.7em; + font-size: 12px; + text-align: right; + user-select: none; + width: 100%; `; export const StyledMain = styled.div` diff --git a/crabfit-frontend/src/components/Button/Button.tsx b/crabfit-frontend/src/components/Button/Button.tsx index 64e788d..e7a067b 100644 --- a/crabfit-frontend/src/components/Button/Button.tsx +++ b/crabfit-frontend/src/components/Button/Button.tsx @@ -1,7 +1,7 @@ import { Pressable } from './buttonStyle'; const Button = ({ href, type = 'button', icon, children, ...props }) => ( - props.isLoading && ` - color: transparent; - cursor: wait; + color: transparent; + cursor: wait; & img { opacity: 0; } - @keyframes load { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } - } + @keyframes load { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } + } - &:after { - content: ''; - position: absolute; - top: calc(50% - 12px); - left: calc(50% - 12px); - height: 18px; - width: 18px; - border: 3px solid ${props.primaryColor ? '#FFF' : props.theme.background}; - border-left-color: transparent; - border-radius: 100px; - animation: load .5s linear infinite; - } + &:after { + content: ''; + position: absolute; + top: calc(50% - 12px); + left: calc(50% - 12px); + height: 18px; + width: 18px; + border: 3px solid ${props.primaryColor ? '#FFF' : props.theme.background}; + border-left-color: transparent; + border-radius: 100px; + animation: load .5s linear infinite; + } @media (prefers-reduced-motion: reduce) { &:after { @@ -106,7 +106,7 @@ export const Pressable = styled.button` justify-content: center; } } - `} + `} ${props => props.secondary && ` background: transparent; diff --git a/crabfit-frontend/src/components/CalendarField/CalendarField.tsx b/crabfit-frontend/src/components/CalendarField/CalendarField.tsx index 1927aa6..e90012c 100644 --- a/crabfit-frontend/src/components/CalendarField/CalendarField.tsx +++ b/crabfit-frontend/src/components/CalendarField/CalendarField.tsx @@ -9,14 +9,14 @@ import { Button, ToggleField } from 'components'; import { useSettingsStore, useLocaleUpdateStore } from 'stores'; import { - Wrapper, - StyledLabel, - StyledSubLabel, - CalendarHeader, - CalendarDays, - CalendarBody, - Date, - Day, + Wrapper, + StyledLabel, + StyledSubLabel, + CalendarHeader, + CalendarDays, + CalendarBody, + Date, + Day, } from './calendarFieldStyle'; dayjs.extend(isToday); @@ -24,90 +24,90 @@ dayjs.extend(localeData); dayjs.extend(updateLocale); const calculateMonth = (month, year, weekStart) => { - const date = dayjs().month(month).year(year); - const daysInMonth = date.daysInMonth(); - const daysBefore = date.date(1).day() - weekStart; - const daysAfter = 6 - date.date(daysInMonth).day() + weekStart; + const date = dayjs().month(month).year(year); + const daysInMonth = date.daysInMonth(); + const daysBefore = date.date(1).day() - weekStart; + const daysAfter = 6 - date.date(daysInMonth).day() + weekStart; - let dates = []; - let curDate = date.date(1).subtract(daysBefore, 'day'); - let y = 0; - let x = 0; - for (let i = 0; i < daysBefore + daysInMonth + daysAfter; i++) { - if (x === 0) dates[y] = []; - dates[y][x] = curDate.clone(); - curDate = curDate.add(1, 'day'); - x++; - if (x > 6) { - x = 0; - y++; - } - } + let dates = []; + let curDate = date.date(1).subtract(daysBefore, 'day'); + let y = 0; + let x = 0; + for (let i = 0; i < daysBefore + daysInMonth + daysAfter; i++) { + if (x === 0) dates[y] = []; + dates[y][x] = curDate.clone(); + curDate = curDate.add(1, 'day'); + x++; + if (x > 6) { + x = 0; + y++; + } + } - return dates; + return dates; }; const CalendarField = forwardRef(({ - label, - subLabel, - id, + label, + subLabel, + id, setValue, - ...props + ...props }, ref) => { const weekStart = useSettingsStore(state => state.weekStart); const locale = useLocaleUpdateStore(state => state.locale); const { t } = useTranslation('home'); - const [type, setType] = useState(0); + const [type, setType] = useState(0); const [dates, setDates] = useState(calculateMonth(dayjs().month(), dayjs().year(), weekStart)); - const [month, setMonth] = useState(dayjs().month()); - const [year, setYear] = useState(dayjs().year()); + const [month, setMonth] = useState(dayjs().month()); + const [year, setYear] = useState(dayjs().year()); - const [selectedDates, setSelectedDates] = useState([]); - const [selectingDates, _setSelectingDates] = useState([]); - const staticSelectingDates = useRef([]); - const setSelectingDates = newDates => { - staticSelectingDates.current = newDates; - _setSelectingDates(newDates); - }; + const [selectedDates, setSelectedDates] = useState([]); + const [selectingDates, _setSelectingDates] = useState([]); + const staticSelectingDates = useRef([]); + const setSelectingDates = newDates => { + staticSelectingDates.current = newDates; + _setSelectingDates(newDates); + }; - const [selectedDays, setSelectedDays] = useState([]); - const [selectingDays, _setSelectingDays] = useState([]); - const staticSelectingDays = useRef([]); - const setSelectingDays = newDays => { - staticSelectingDays.current = newDays; - _setSelectingDays(newDays); - }; + const [selectedDays, setSelectedDays] = useState([]); + const [selectingDays, _setSelectingDays] = useState([]); + const staticSelectingDays = useRef([]); + const setSelectingDays = newDays => { + staticSelectingDays.current = newDays; + _setSelectingDays(newDays); + }; - const startPos = useRef({}); - const staticMode = useRef(null); - const [mode, _setMode] = useState(staticMode.current); - const setMode = newMode => { - staticMode.current = newMode; - _setMode(newMode); - }; + const startPos = useRef({}); + const staticMode = useRef(null); + const [mode, _setMode] = useState(staticMode.current); + const setMode = newMode => { + staticMode.current = newMode; + _setMode(newMode); + }; useEffect(() => setValue(props.name, type ? JSON.stringify(selectedDays) : JSON.stringify(selectedDates)), [type, selectedDays, selectedDates, setValue, props.name]); - useEffect(() => { + useEffect(() => { if (dayjs.Ls.hasOwnProperty(locale) && weekStart !== dayjs.Ls[locale].weekStart) { dayjs.updateLocale(locale, { weekStart }); } - setDates(calculateMonth(month, year, weekStart)); - }, [weekStart, month, year, locale]); + setDates(calculateMonth(month, year, weekStart)); + }, [weekStart, month, year, locale]); - return ( - - {label && {label}} - {subLabel && {subLabel}} - + {label && {label}} + {subLabel && {subLabel}} + + value={type ? JSON.stringify(selectedDays) : JSON.stringify(selectedDates)} + {...props} + /> - - - {dayjs.months()[month]} {year} - - + + + {dayjs.months()[month]} {year} + + - - {(weekStart ? [...dayjs.weekdaysShort().filter((_,i) => i !== 0), dayjs.weekdaysShort()[0]] : dayjs.weekdaysShort()).map(name => - {name} - )} - - - {dates.length > 0 && dates.map((dateRow, y) => - dateRow.map((date, x) => - + {(weekStart ? [...dayjs.weekdaysShort().filter((_,i) => i !== 0), dayjs.weekdaysShort()[0]] : dayjs.weekdaysShort()).map(name => + {name} + )} + + + {dates.length > 0 && dates.map((dateRow, y) => + dateRow.map((date, x) => + { if (e.key === ' ' || e.key === 'Enter') { @@ -176,37 +176,37 @@ const CalendarField = forwardRef(({ } } }} - onPointerDown={e => { - startPos.current = {x, y}; - setMode(selectedDates.includes(date.format('DDMMYYYY')) ? 'remove' : 'add'); - setSelectingDates([date]); - e.currentTarget.releasePointerCapture(e.pointerId); + onPointerDown={e => { + startPos.current = {x, y}; + setMode(selectedDates.includes(date.format('DDMMYYYY')) ? 'remove' : 'add'); + setSelectingDates([date]); + e.currentTarget.releasePointerCapture(e.pointerId); - document.addEventListener('pointerup', () => { - if (staticMode.current === 'add') { - setSelectedDates([...selectedDates, ...staticSelectingDates.current.map(d => d.format('DDMMYYYY'))]); - } else if (staticMode.current === 'remove') { - const toRemove = staticSelectingDates.current.map(d => d.format('DDMMYYYY')); - setSelectedDates(selectedDates.filter(d => !toRemove.includes(d))); - } - setMode(null); - }, { once: true }); - }} - onPointerEnter={() => { - if (staticMode.current) { - let found = []; - for (let cy = Math.min(startPos.current.y, y); cy < Math.max(startPos.current.y, y)+1; cy++) { - for (let cx = Math.min(startPos.current.x, x); cx < Math.max(startPos.current.x, x)+1; cx++) { - found.push({y: cy, x: cx}); - } - } - setSelectingDates(found.map(d => dates[d.y][d.x])); - } - }} - >{date.date()} - ) - )} - + document.addEventListener('pointerup', () => { + if (staticMode.current === 'add') { + setSelectedDates([...selectedDates, ...staticSelectingDates.current.map(d => d.format('DDMMYYYY'))]); + } else if (staticMode.current === 'remove') { + const toRemove = staticSelectingDates.current.map(d => d.format('DDMMYYYY')); + setSelectedDates(selectedDates.filter(d => !toRemove.includes(d))); + } + setMode(null); + }, { once: true }); + }} + onPointerEnter={() => { + if (staticMode.current) { + let found = []; + for (let cy = Math.min(startPos.current.y, y); cy < Math.max(startPos.current.y, y)+1; cy++) { + for (let cx = Math.min(startPos.current.x, x); cx < Math.max(startPos.current.x, x)+1; cx++) { + found.push({y: cy, x: cx}); + } + } + setSelectingDates(found.map(d => dates[d.y][d.x])); + } + }} + >{date.date()} + ) + )} + ) : ( @@ -257,8 +257,8 @@ const CalendarField = forwardRef(({ )} )} - - ); + + ); }); export default CalendarField; diff --git a/crabfit-frontend/src/components/CalendarField/calendarFieldStyle.ts b/crabfit-frontend/src/components/CalendarField/calendarFieldStyle.ts index 7dcee99..fe6c4b8 100644 --- a/crabfit-frontend/src/components/CalendarField/calendarFieldStyle.ts +++ b/crabfit-frontend/src/components/CalendarField/calendarFieldStyle.ts @@ -1,68 +1,68 @@ import styled from '@emotion/styled'; export const Wrapper = styled.div` - margin: 30px 0; + margin: 30px 0; `; export const StyledLabel = styled.label` - display: block; - padding-bottom: 4px; - font-size: 18px; + display: block; + padding-bottom: 4px; + font-size: 18px; `; export const StyledSubLabel = styled.label` - display: block; - font-size: 13px; - opacity: .6; + display: block; + font-size: 13px; + opacity: .6; `; export const CalendarHeader = styled.div` - display: flex; - align-items: center; - justify-content: space-between; - user-select: none; - padding: 6px 0; - font-size: 1.2em; - font-weight: bold; + display: flex; + align-items: center; + justify-content: space-between; + user-select: none; + padding: 6px 0; + font-size: 1.2em; + font-weight: bold; `; export const CalendarDays = styled.div` - display: grid; - grid-template-columns: repeat(7, 1fr); - grid-gap: 2px; + display: grid; + grid-template-columns: repeat(7, 1fr); + grid-gap: 2px; `; export const Day = styled.div` - display: flex; - align-items: center; - justify-content: center; - padding: 3px 0; - font-weight: bold; - user-select: none; - opacity: .7; + display: flex; + align-items: center; + justify-content: center; + padding: 3px 0; + font-weight: bold; + user-select: none; + opacity: .7; - @media (max-width: 350px) { - font-size: 12px; - } + @media (max-width: 350px) { + font-size: 12px; + } `; export const CalendarBody = styled.div` - display: grid; - grid-template-columns: repeat(7, 1fr); - grid-gap: 2px; + display: grid; + grid-template-columns: repeat(7, 1fr); + grid-gap: 2px; - & button:first-of-type { - border-top-left-radius: 3px; - } - & button:nth-of-type(7) { - border-top-right-radius: 3px; - } - & button:nth-last-of-type(7) { - border-bottom-left-radius: 3px; - } - & button:last-of-type { - border-bottom-right-radius: 3px; - } + & button:first-of-type { + border-top-left-radius: 3px; + } + & button:nth-of-type(7) { + border-top-right-radius: 3px; + } + & button:nth-last-of-type(7) { + border-bottom-left-radius: 3px; + } + & button:last-of-type { + border-bottom-right-radius: 3px; + } `; export const Date = styled.button` @@ -77,28 +77,28 @@ export const Date = styled.button` transition: none; } - background-color: ${props => props.theme.primaryBackground}; - border: 1px solid ${props => props.theme.primary}; - display: flex; - align-items: center; - justify-content: center; - padding: 10px 0; - user-select: none; - touch-action: none; + background-color: ${props => props.theme.primaryBackground}; + border: 1px solid ${props => props.theme.primary}; + display: flex; + align-items: center; + justify-content: center; + padding: 10px 0; + user-select: none; + touch-action: none; - ${props => props.otherMonth && ` - color: ${props.theme.mode === 'light' ? props.theme.primaryLight : props.theme.primaryDark}; - `} - ${props => props.isToday && ` - font-weight: 900; - color: ${props.theme.mode === 'light' ? props.theme.primaryDark : props.theme.primaryLight}; - `} - ${props => (props.selected || (props.mode === 'add' && props.selecting)) && ` - color: ${props.otherMonth ? 'rgba(255,255,255,.5)' : '#FFF'}; - background-color: ${props.theme.primary}; - `} - ${props => props.mode === 'remove' && props.selecting && ` - background-color: ${props.theme.primaryBackground}; - color: ${props.isToday ? props.theme.primaryDark : (props.otherMonth ? props.theme.primaryLight : 'inherit')}; - `} + ${props => props.otherMonth && ` + color: ${props.theme.mode === 'light' ? props.theme.primaryLight : props.theme.primaryDark}; + `} + ${props => props.isToday && ` + font-weight: 900; + color: ${props.theme.mode === 'light' ? props.theme.primaryDark : props.theme.primaryLight}; + `} + ${props => (props.selected || (props.mode === 'add' && props.selecting)) && ` + color: ${props.otherMonth ? 'rgba(255,255,255,.5)' : '#FFF'}; + background-color: ${props.theme.primary}; + `} + ${props => props.mode === 'remove' && props.selecting && ` + background-color: ${props.theme.primaryBackground}; + color: ${props.isToday ? props.theme.primaryDark : (props.otherMonth ? props.theme.primaryLight : 'inherit')}; + `} `; diff --git a/crabfit-frontend/src/components/Center/Center.ts b/crabfit-frontend/src/components/Center/Center.ts index 0d691d0..bd722ff 100644 --- a/crabfit-frontend/src/components/Center/Center.ts +++ b/crabfit-frontend/src/components/Center/Center.ts @@ -1,9 +1,9 @@ import styled from '@emotion/styled'; const Center = styled.div` - display: flex; - align-items: center; - justify-content: center; + display: flex; + align-items: center; + justify-content: center; `; export default Center; diff --git a/crabfit-frontend/src/components/Donate/Donate.tsx b/crabfit-frontend/src/components/Donate/Donate.tsx index 85ef132..1a2d0ef 100644 --- a/crabfit-frontend/src/components/Donate/Donate.tsx +++ b/crabfit-frontend/src/components/Donate/Donate.tsx @@ -96,9 +96,9 @@ const Donate = () => { }; return ( - - + >{t('donate.button')} { {t('donate.options.$10')} {t('donate.options.choose')} - + ); } diff --git a/crabfit-frontend/src/components/Donate/donateStyle.ts b/crabfit-frontend/src/components/Donate/donateStyle.ts index ad32073..0353637 100644 --- a/crabfit-frontend/src/components/Donate/donateStyle.ts +++ b/crabfit-frontend/src/components/Donate/donateStyle.ts @@ -7,7 +7,7 @@ export const Wrapper = styled.div` `; export const Options = styled.div` - position: absolute; + position: absolute; bottom: calc(100% + 20px); right: 0; background-color: ${props => props.theme.background}; diff --git a/crabfit-frontend/src/components/Egg/Egg.tsx b/crabfit-frontend/src/components/Egg/Egg.tsx index afa2e82..0d65643 100644 --- a/crabfit-frontend/src/components/Egg/Egg.tsx +++ b/crabfit-frontend/src/components/Egg/Egg.tsx @@ -7,14 +7,14 @@ const Egg = ({ eggKey, onClose }) => { const [isLoading, setIsLoading] = useState(true); return ( - onClose()}> - onClose()}> + setIsLoading(true)} onLoad={() => setIsLoading(false)} /> {isLoading && } - + ); } diff --git a/crabfit-frontend/src/components/Egg/eggStyle.ts b/crabfit-frontend/src/components/Egg/eggStyle.ts index 19ac1f3..2aebe29 100644 --- a/crabfit-frontend/src/components/Egg/eggStyle.ts +++ b/crabfit-frontend/src/components/Egg/eggStyle.ts @@ -1,7 +1,7 @@ import styled from '@emotion/styled'; export const Wrapper = styled.div` - position: fixed; + position: fixed; background: rgba(0,0,0,.6); top: 0; left: 0; @@ -17,7 +17,7 @@ export const Wrapper = styled.div` `; export const Image = styled.img` - max-width: 80%; - max-height: 80%; + max-width: 80%; + max-height: 80%; position: absolute; `; diff --git a/crabfit-frontend/src/components/Error/Error.tsx b/crabfit-frontend/src/components/Error/Error.tsx index 0d51039..0d103a0 100644 --- a/crabfit-frontend/src/components/Error/Error.tsx +++ b/crabfit-frontend/src/components/Error/Error.tsx @@ -1,17 +1,17 @@ import { Wrapper, CloseButton } from './errorStyle'; const Error = ({ - children, - onClose, + children, + onClose, open = true, - ...props + ...props }) => ( - - {children} - - - - + + {children} + + + + ); export default Error; diff --git a/crabfit-frontend/src/components/Error/errorStyle.ts b/crabfit-frontend/src/components/Error/errorStyle.ts index 59f43df..201001e 100644 --- a/crabfit-frontend/src/components/Error/errorStyle.ts +++ b/crabfit-frontend/src/components/Error/errorStyle.ts @@ -1,14 +1,14 @@ import styled from '@emotion/styled'; export const Wrapper = styled.div` - border-radius: 3px; - background-color: ${props => props.theme.error}; - color: #FFFFFF; - padding: 0 16px; - display: flex; - align-items: center; - justify-content: space-between; - font-size: 18px; + border-radius: 3px; + background-color: ${props => props.theme.error}; + color: #FFFFFF; + padding: 0 16px; + display: flex; + align-items: center; + justify-content: space-between; + font-size: 18px; opacity: 0; max-height: 0; margin: 0; @@ -30,14 +30,14 @@ export const Wrapper = styled.div` `; export const CloseButton = styled.button` - border: 0; - background: none; - height: 30px; - width: 30px; - cursor: pointer; - color: inherit; - display: flex; - align-items: center; - justify-content: center; - margin-left: 16px; + border: 0; + background: none; + height: 30px; + width: 30px; + cursor: pointer; + color: inherit; + display: flex; + align-items: center; + justify-content: center; + margin-left: 16px; `; diff --git a/crabfit-frontend/src/components/Footer/footerStyle.ts b/crabfit-frontend/src/components/Footer/footerStyle.ts index 4e04db7..1d09889 100644 --- a/crabfit-frontend/src/components/Footer/footerStyle.ts +++ b/crabfit-frontend/src/components/Footer/footerStyle.ts @@ -1,12 +1,12 @@ 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; + width: 600px; + margin: 20px auto; + max-width: calc(100% - 60px); + display: flex; + align-items: center; + justify-content: space-between; ${props => props.small && ` margin: 60px auto 0; diff --git a/crabfit-frontend/src/components/GoogleCalendar/GoogleCalendar.tsx b/crabfit-frontend/src/components/GoogleCalendar/GoogleCalendar.tsx index 335f779..5b6f871 100644 --- a/crabfit-frontend/src/components/GoogleCalendar/GoogleCalendar.tsx +++ b/crabfit-frontend/src/components/GoogleCalendar/GoogleCalendar.tsx @@ -93,11 +93,11 @@ const GoogleCalendar = ({ timeZone, timeMin, timeMax, onImport }) => { } }, [signedIn]); - return ( + return ( <> {!signedIn ? (
- - + - + ); } diff --git a/crabfit-frontend/src/pages/Create/Create.tsx b/crabfit-frontend/src/pages/Create/Create.tsx index d84e2a9..f63213f 100644 --- a/crabfit-frontend/src/pages/Create/Create.tsx +++ b/crabfit-frontend/src/pages/Create/Create.tsx @@ -9,21 +9,21 @@ import timezone from 'dayjs/plugin/timezone'; import customParseFormat from 'dayjs/plugin/customParseFormat'; import { - TextField, - CalendarField, - TimeRangeField, - SelectField, - Button, - Error, + TextField, + CalendarField, + TimeRangeField, + SelectField, + Button, + Error, Recents, Footer, } from 'components'; import { - StyledMain, - CreateForm, - TitleSmall, - TitleLarge, + StyledMain, + CreateForm, + TitleSmall, + TitleLarge, P, OfflineMessage, ShareInfo, @@ -39,14 +39,14 @@ dayjs.extend(timezone); dayjs.extend(customParseFormat); const Create = ({ offline }) => { - const { register, handleSubmit, setValue } = useForm({ - defaultValues: { - timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, - }, - }); - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); - const [createdEvent, setCreatedEvent] = useState(null); + const { register, handleSubmit, setValue } = useForm({ + defaultValues: { + timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, + }, + }); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const [createdEvent, setCreatedEvent] = useState(null); const [copied, setCopied] = useState(null); const [showFooter, setShowFooter] = useState(true); @@ -55,11 +55,11 @@ const Create = ({ offline }) => { const addRecent = useRecentsStore(state => state.addRecent); - useEffect(() => { + useEffect(() => { if (window.self === window.top) { push('/'); } - document.title = 'Create a Crab Fit'; + document.title = 'Create a Crab Fit'; if (window.parent) { window.parent.postMessage('crabfit-create', '*'); @@ -71,67 +71,67 @@ const Create = ({ offline }) => { once: true }); } - }, [push]); + }, [push]); - const onSubmit = async data => { - setIsLoading(true); - setError(null); - try { - const { start, end } = JSON.parse(data.times); - const dates = JSON.parse(data.dates); + const onSubmit = async data => { + setIsLoading(true); + setError(null); + try { + const { start, end } = JSON.parse(data.times); + const dates = JSON.parse(data.dates); - if (dates.length === 0) { - return setError(t('home:form.errors.no_dates')); - } + if (dates.length === 0) { + return setError(t('home:form.errors.no_dates')); + } const isSpecificDates = typeof dates[0] === 'string' && dates[0].length === 8; - if (start === end) { - return setError(t('home:form.errors.same_times')); - } + if (start === end) { + return setError(t('home:form.errors.same_times')); + } - let times = dates.reduce((times, date) => { - let day = []; - for (let i = start; i < (start > end ? 24 : end); i++) { + let times = dates.reduce((times, date) => { + let day = []; + for (let i = start; i < (start > end ? 24 : end); i++) { if (isSpecificDates) { - day.push( - dayjs.tz(date, 'DDMMYYYY', data.timezone) - .hour(i).minute(0).utc().format('HHmm-DDMMYYYY') - ); + day.push( + dayjs.tz(date, 'DDMMYYYY', data.timezone) + .hour(i).minute(0).utc().format('HHmm-DDMMYYYY') + ); } else { day.push( dayjs().tz(data.timezone) .day(date).hour(i).minute(0).utc().format('HHmm-d') ); } - } - if (start > end) { - for (let i = 0; i < end; i++) { + } + if (start > end) { + for (let i = 0; i < end; i++) { if (isSpecificDates) { - day.push( - dayjs.tz(date, 'DDMMYYYY', data.timezone) - .hour(i).minute(0).utc().format('HHmm-DDMMYYYY') - ); + day.push( + dayjs.tz(date, 'DDMMYYYY', data.timezone) + .hour(i).minute(0).utc().format('HHmm-DDMMYYYY') + ); } else { day.push( dayjs().tz(data.timezone) .day(date).hour(i).minute(0).utc().format('HHmm-d') ); } - } - } - return [...times, ...day]; - }, []); + } + } + return [...times, ...day]; + }, []); - if (times.length === 0) { - return setError(t('home:form.errors.no_time')); - } + if (times.length === 0) { + return setError(t('home:form.errors.no_time')); + } - const response = await api.post('/event', { - event: { - name: data.name, - times: times, + const response = await api.post('/event', { + event: { + name: data.name, + times: times, timezone: data.timezone, - }, - }); + }, + }); setCreatedEvent(response.data); addRecent({ id: response.data.id, @@ -141,19 +141,19 @@ const Create = ({ offline }) => { gtag('event', 'create_event', { 'event_category': 'create', }); - } catch (e) { - setError(t('home:form.errors.unknown')); - console.error(e); - } finally { - setIsLoading(false); - } - }; + } catch (e) { + setError(t('home:form.errors.unknown')); + console.error(e); + } finally { + setIsLoading(false); + } + }; - return ( - <> - - {t('home:create')} - CRAB FIT + return ( + <> + + {t('home:create')} + CRAB FIT {createdEvent ? ( @@ -173,10 +173,10 @@ const Create = ({ offline }) => { } title={!!navigator.clipboard ? t('event:nav.title') : ''} >{copied ?? `https://crab.fit/${createdEvent?.id}`} - + {/* eslint-disable-next-line */} - Click the link above to copy it to your clipboard, or share via gtag('event', 'send_email', { 'event_category': 'event' })} href={`mailto:?subject=${encodeURIComponent(t('event:nav.email_subject', { event_name: createdEvent?.name }))}&body=${encodeURIComponent(`${t('event:nav.email_body')} https://crab.fit/${createdEvent?.id}`)}`} target="_blank">email. - + Click the link above to copy it to your clipboard, or share via gtag('event', 'send_email', { 'event_category': 'event' })} href={`mailto:?subject=${encodeURIComponent(t('event:nav.email_subject', { event_name: createdEvent?.name }))}&body=${encodeURIComponent(`${t('event:nav.email_body')} https://crab.fit/${createdEvent?.id}`)}`} target="_blank">email. + {showFooter &&