Update home page
This commit is contained in:
parent
a67aee24dc
commit
2d32a1b036
|
|
@ -63,9 +63,9 @@ const App = () => {
|
||||||
<Settings />
|
<Settings />
|
||||||
|
|
||||||
<Routes>
|
<Routes>
|
||||||
{/* <Route path="/" element={<Pages.Home />} /> */}
|
<Route path="/" element={<Pages.Home />} />
|
||||||
{/* <Route path="/" element={<Pages.Help />} /> */}
|
<Route path="/how-to" element={<Pages.Help />} />
|
||||||
<Route path="/" element={<Pages.Privacy />} />
|
<Route path="/privacy" element={<Pages.Privacy />} />
|
||||||
{/* <Route path="/create" element={<Pages.Create />} />
|
{/* <Route path="/create" element={<Pages.Create />} />
|
||||||
<Route path="/:id" element={<Pages.Event />} /> */}
|
<Route path="/:id" element={<Pages.Event />} /> */}
|
||||||
</Routes>
|
</Routes>
|
||||||
|
|
|
||||||
|
|
@ -84,8 +84,8 @@ const AvailabilityViewer = ({
|
||||||
<DayLabel>{parsedDate.format('ddd')}</DayLabel>
|
<DayLabel>{parsedDate.format('ddd')}</DayLabel>
|
||||||
|
|
||||||
<Times
|
<Times
|
||||||
borderRight={last}
|
$borderRight={last}
|
||||||
borderLeft={i === 0 || (parsedDate).diff(isSpecificDates ? dayjs(dates[i-1], 'DDMMYYYY') : dayjs().day(dates[i-1]), 'day') > 1}
|
$borderLeft={i === 0 || (parsedDate).diff(isSpecificDates ? dayjs(dates[i-1], 'DDMMYYYY') : dayjs().day(dates[i-1]), 'day') > 1}
|
||||||
>
|
>
|
||||||
{timeLabels.map((timeLabel, i) => {
|
{timeLabels.map((timeLabel, i) => {
|
||||||
if (!timeLabel.time) return null
|
if (!timeLabel.time) return null
|
||||||
|
|
@ -102,13 +102,13 @@ const AvailabilityViewer = ({
|
||||||
return (
|
return (
|
||||||
<Time
|
<Time
|
||||||
key={i}
|
key={i}
|
||||||
time={time}
|
$time={time}
|
||||||
className="time"
|
className="time"
|
||||||
peopleCount={focusCount !== null && focusCount !== peopleHere.length ? 0 : peopleHere.length}
|
$peopleCount={focusCount !== null && focusCount !== peopleHere.length ? 0 : peopleHere.length}
|
||||||
aria-label={peopleHere.join(', ')}
|
aria-label={peopleHere.join(', ')}
|
||||||
maxPeople={tempFocus !== null ? 1 : Math.min(max, filteredPeople.length)}
|
$maxPeople={tempFocus !== null ? 1 : Math.min(max, filteredPeople.length)}
|
||||||
minPeople={tempFocus !== null ? 0 : Math.min(min, filteredPeople.length)}
|
$minPeople={tempFocus !== null ? 0 : Math.min(min, filteredPeople.length)}
|
||||||
highlight={highlight}
|
$highlight={highlight}
|
||||||
onMouseEnter={e => {
|
onMouseEnter={e => {
|
||||||
const cellBox = e.currentTarget.getBoundingClientRect()
|
const cellBox = e.currentTarget.getBoundingClientRect()
|
||||||
const wrapperBox = wrapper?.current?.getBoundingClientRect() ?? { x: 0, y: 0 }
|
const wrapperBox = wrapper?.current?.getBoundingClientRect() ?? { x: 0, y: 0 }
|
||||||
|
|
@ -170,7 +170,7 @@ const AvailabilityViewer = ({
|
||||||
{people.map((person, i) =>
|
{people.map((person, i) =>
|
||||||
<Person
|
<Person
|
||||||
key={i}
|
key={i}
|
||||||
filtered={filteredPeople.includes(person.name)}
|
$filtered={filteredPeople.includes(person.name)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTempFocus(null)
|
setTempFocus(null)
|
||||||
if (filteredPeople.includes(person.name)) {
|
if (filteredPeople.includes(person.name)) {
|
||||||
|
|
@ -200,8 +200,8 @@ const AvailabilityViewer = ({
|
||||||
|
|
||||||
{tooltip && (
|
{tooltip && (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
x={tooltip.x}
|
$x={tooltip.x}
|
||||||
y={tooltip.y}
|
$y={tooltip.y}
|
||||||
>
|
>
|
||||||
<TooltipTitle>{tooltip.available}</TooltipTitle>
|
<TooltipTitle>{tooltip.available}</TooltipTitle>
|
||||||
<TooltipDate>{tooltip.date}</TooltipDate>
|
<TooltipDate>{tooltip.date}</TooltipDate>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { styled } from 'goober'
|
import { styled } from 'goober'
|
||||||
|
import { forwardRef } from 'react'
|
||||||
|
|
||||||
export const Wrapper = styled('div')`
|
export const Wrapper = styled('div', forwardRef)`
|
||||||
overflow-y: visible;
|
overflow-y: visible;
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
@ -40,12 +41,12 @@ export const Times = styled('div')`
|
||||||
border-left: 1px solid var(--text);
|
border-left: 1px solid var(--text);
|
||||||
border-right: 1px solid var(--text);
|
border-right: 1px solid var(--text);
|
||||||
|
|
||||||
${props => props.borderLeft && `
|
${props => props.$borderLeft && `
|
||||||
border-left: 2px solid var(--text);
|
border-left: 2px solid var(--text);
|
||||||
border-top-left-radius: 3px;
|
border-top-left-radius: 3px;
|
||||||
border-bottom-left-radius: 3px;
|
border-bottom-left-radius: 3px;
|
||||||
`}
|
`}
|
||||||
${props => props.borderRight && `
|
${props => props.$borderRight && `
|
||||||
border-right: 2px solid var(--text);
|
border-right: 2px solid var(--text);
|
||||||
border-top-right-radius: 3px;
|
border-top-right-radius: 3px;
|
||||||
border-bottom-right-radius: 3px;
|
border-bottom-right-radius: 3px;
|
||||||
|
|
@ -75,19 +76,19 @@ export const Time = styled('div')`
|
||||||
background-origin: border-box;
|
background-origin: border-box;
|
||||||
transition: background-color .1s;
|
transition: background-color .1s;
|
||||||
|
|
||||||
${props => props.time.slice(2, 4) === '00' && `
|
${props => props.$time.slice(2, 4) === '00' && `
|
||||||
border-top: 2px solid var(--text);
|
border-top: 2px solid var(--text);
|
||||||
`}
|
`}
|
||||||
${props => props.time.slice(2, 4) !== '00' && `
|
${props => props.$time.slice(2, 4) !== '00' && `
|
||||||
border-top: 2px solid transparent;
|
border-top: 2px solid transparent;
|
||||||
`}
|
`}
|
||||||
${props => props.time.slice(2, 4) === '30' && `
|
${props => props.$time.slice(2, 4) === '30' && `
|
||||||
border-top: 2px dotted var(--text);
|
border-top: 2px dotted var(--text);
|
||||||
`}
|
`}
|
||||||
|
|
||||||
background-color: ${props => `#FF0000${Math.round((props.peopleCount/props.maxPeople)*255).toString(16)}`};
|
background-color: ${props => `#FF0000${Math.round((props.$peopleCount/props.$maxPeople)*255).toString(16)}`};
|
||||||
|
|
||||||
${props => props.highlight && props.peopleCount === props.maxPeople && props.peopleCount > 0 && `
|
${props => props.$highlight && props.$peopleCount === props.$maxPeople && props.$peopleCount > 0 && `
|
||||||
background-image: repeating-linear-gradient(
|
background-image: repeating-linear-gradient(
|
||||||
45deg,
|
45deg,
|
||||||
transparent,
|
transparent,
|
||||||
|
|
@ -109,8 +110,8 @@ export const Spacer = styled('div')`
|
||||||
|
|
||||||
export const Tooltip = styled('div')`
|
export const Tooltip = styled('div')`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: ${props => props.y}px;
|
top: ${props => props.$y}px;
|
||||||
left: ${props => props.x}px;
|
left: ${props => props.$x}px;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
border: 1px solid var(--text);
|
border: 1px solid var(--text);
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
|
|
@ -214,7 +215,7 @@ export const Person = styled('button')`
|
||||||
padding: 2px 8px;
|
padding: 2px 8px;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
${props => props.filtered && `
|
${props => props.$filtered && `
|
||||||
background: var(--primary);
|
background: var(--primary);
|
||||||
color: #FFFFFF;
|
color: #FFFFFF;
|
||||||
border-color: var(--primary);
|
border-color: var(--primary);
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ const Button = ({
|
||||||
secondaryColor,
|
secondaryColor,
|
||||||
small,
|
small,
|
||||||
size,
|
size,
|
||||||
|
isLoading,
|
||||||
...props
|
...props
|
||||||
}) => (
|
}) => (
|
||||||
<Pressable
|
<Pressable
|
||||||
|
|
@ -21,6 +22,7 @@ const Button = ({
|
||||||
$secondaryColor={secondaryColor}
|
$secondaryColor={secondaryColor}
|
||||||
$small={small}
|
$small={small}
|
||||||
$size={size}
|
$size={size}
|
||||||
|
$isLoading={isLoading}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{icon}
|
{icon}
|
||||||
|
|
|
||||||
|
|
@ -160,12 +160,12 @@ const CalendarField = forwardRef(({
|
||||||
dateRow.map((date, x) =>
|
dateRow.map((date, x) =>
|
||||||
<Date
|
<Date
|
||||||
key={y+x}
|
key={y+x}
|
||||||
otherMonth={date.month() !== month}
|
$otherMonth={date.month() !== month}
|
||||||
isToday={date.isToday()}
|
$isToday={date.isToday()}
|
||||||
title={`${date.date()} ${dayjs.months()[date.month()]}${date.isToday() ? ` (${t('form.dates.tooltips.today')})` : ''}`}
|
title={`${date.date()} ${dayjs.months()[date.month()]}${date.isToday() ? ` (${t('form.dates.tooltips.today')})` : ''}`}
|
||||||
selected={selectedDates.includes(date.format('DDMMYYYY'))}
|
$selected={selectedDates.includes(date.format('DDMMYYYY'))}
|
||||||
selecting={selectingDates.includes(date)}
|
$selecting={selectingDates.includes(date)}
|
||||||
mode={mode}
|
$mode={mode}
|
||||||
type="button"
|
type="button"
|
||||||
onKeyPress={e => {
|
onKeyPress={e => {
|
||||||
if (e.key === ' ' || e.key === 'Enter') {
|
if (e.key === ' ' || e.key === 'Enter') {
|
||||||
|
|
@ -213,11 +213,11 @@ const CalendarField = forwardRef(({
|
||||||
{(weekStart ? [...dayjs.weekdaysShort().filter((_,i) => i !== 0), dayjs.weekdaysShort()[0]] : dayjs.weekdaysShort()).map((name, i) =>
|
{(weekStart ? [...dayjs.weekdaysShort().filter((_,i) => i !== 0), dayjs.weekdaysShort()[0]] : dayjs.weekdaysShort()).map((name, i) =>
|
||||||
<Date
|
<Date
|
||||||
key={name}
|
key={name}
|
||||||
isToday={(weekStart ? [...dayjs.weekdaysShort().filter((_,i) => i !== 0), dayjs.weekdaysShort()[0]] : dayjs.weekdaysShort())[dayjs().day()-weekStart === -1 ? 6 : dayjs().day()-weekStart] === name}
|
$isToday={(weekStart ? [...dayjs.weekdaysShort().filter((_,i) => i !== 0), dayjs.weekdaysShort()[0]] : dayjs.weekdaysShort())[dayjs().day()-weekStart === -1 ? 6 : dayjs().day()-weekStart] === name}
|
||||||
title={(weekStart ? [...dayjs.weekdaysShort().filter((_,i) => i !== 0), dayjs.weekdaysShort()[0]] : dayjs.weekdaysShort())[dayjs().day()-weekStart === -1 ? 6 : dayjs().day()-weekStart] === name ? t('form.dates.tooltips.today') : ''}
|
title={(weekStart ? [...dayjs.weekdaysShort().filter((_,i) => i !== 0), dayjs.weekdaysShort()[0]] : dayjs.weekdaysShort())[dayjs().day()-weekStart === -1 ? 6 : dayjs().day()-weekStart] === name ? t('form.dates.tooltips.today') : ''}
|
||||||
selected={selectedDays.includes(((i + weekStart) % 7 + 7) % 7)}
|
$selected={selectedDays.includes(((i + weekStart) % 7 + 7) % 7)}
|
||||||
selecting={selectingDays.includes(((i + weekStart) % 7 + 7) % 7)}
|
$selecting={selectingDays.includes(((i + weekStart) % 7 + 7) % 7)}
|
||||||
mode={mode}
|
$mode={mode}
|
||||||
type="button"
|
type="button"
|
||||||
onKeyPress={e => {
|
onKeyPress={e => {
|
||||||
if (e.key === ' ' || e.key === 'Enter') {
|
if (e.key === ' ' || e.key === 'Enter') {
|
||||||
|
|
|
||||||
|
|
@ -86,19 +86,19 @@ export const Date = styled('button')`
|
||||||
user-select: none;
|
user-select: none;
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
|
|
||||||
${props => props.otherMonth && `
|
${props => props.$otherMonth && `
|
||||||
color: var(--tertiary);
|
color: var(--tertiary);
|
||||||
`}
|
`}
|
||||||
${props => props.isToday && `
|
${props => props.$isToday && `
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
color: var(--secondary);
|
color: var(--secondary);
|
||||||
`}
|
`}
|
||||||
${props => (props.selected || (props.mode === 'add' && props.selecting)) && `
|
${props => (props.$selected || (props.$mode === 'add' && props.$selecting)) && `
|
||||||
color: ${props.otherMonth ? 'rgba(255,255,255,.5)' : '#FFF'};
|
color: ${props.$otherMonth ? 'rgba(255,255,255,.5)' : '#FFF'};
|
||||||
background-color: var(--primary);
|
background-color: var(--primary);
|
||||||
`}
|
`}
|
||||||
${props => props.mode === 'remove' && props.selecting && `
|
${props => props.$mode === 'remove' && props.$selecting && `
|
||||||
background-color: var(--surface);
|
background-color: var(--surface);
|
||||||
color: ${props.isToday ? 'var(--secondary)' : (props.otherMonth ? 'var(--tertiary)' : 'inherit')};
|
color: ${props.$isToday ? 'var(--secondary)' : (props.$otherMonth ? 'var(--tertiary)' : 'inherit')};
|
||||||
`}
|
`}
|
||||||
`
|
`
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,8 @@ const Legend = ({
|
||||||
{[...Array(max+1-min).keys()].map(i => i+min).map(i =>
|
{[...Array(max+1-min).keys()].map(i => i+min).map(i =>
|
||||||
<Grade
|
<Grade
|
||||||
key={i}
|
key={i}
|
||||||
color={`#FF0000${Math.round((i/(max))*255).toString(16)}`}
|
$color={`#FF0000${Math.round((i/(max))*255).toString(16)}`}
|
||||||
highlight={highlight && i === max && max > 0}
|
$highlight={highlight && i === max && max > 0}
|
||||||
onMouseOver={() => onSegmentFocus(i)}
|
onMouseOver={() => onSegmentFocus(i)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,9 @@ export const Bar = styled('div')`
|
||||||
|
|
||||||
export const Grade = styled('div')`
|
export const Grade = styled('div')`
|
||||||
flex: 1;
|
flex: 1;
|
||||||
background-color: ${props => props.color};
|
background-color: ${props => props.$color};
|
||||||
|
|
||||||
${props => props.highlight && `
|
${props => props.$highlight && `
|
||||||
background-image: repeating-linear-gradient(
|
background-image: repeating-linear-gradient(
|
||||||
45deg,
|
45deg,
|
||||||
var(--primary),
|
var(--primary),
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,8 @@ const TextField = forwardRef(({
|
||||||
inline = false,
|
inline = false,
|
||||||
...props
|
...props
|
||||||
}, ref) => (
|
}, ref) => (
|
||||||
<Wrapper inline={inline}>
|
<Wrapper $inline={inline}>
|
||||||
{label && <StyledLabel htmlFor={id} inline={inline}>{label}</StyledLabel>}
|
{label && <StyledLabel htmlFor={id} $inline={inline}>{label}</StyledLabel>}
|
||||||
{subLabel && <StyledSubLabel htmlFor={id}>{subLabel}</StyledSubLabel>}
|
{subLabel && <StyledSubLabel htmlFor={id}>{subLabel}</StyledSubLabel>}
|
||||||
<StyledInput id={id} ref={ref} {...props} />
|
<StyledInput id={id} ref={ref} {...props} />
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { forwardRef } from 'react'
|
||||||
export const Wrapper = styled('div')`
|
export const Wrapper = styled('div')`
|
||||||
margin: 30px 0;
|
margin: 30px 0;
|
||||||
|
|
||||||
${props => props.inline && `
|
${props => props.$inline && `
|
||||||
margin: 0;
|
margin: 0;
|
||||||
`}
|
`}
|
||||||
`
|
`
|
||||||
|
|
@ -14,7 +14,7 @@ export const StyledLabel = styled('label')`
|
||||||
padding-bottom: 4px;
|
padding-bottom: 4px;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
|
|
||||||
${props => props.inline && `
|
${props => props.$inline && `
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
`}
|
`}
|
||||||
`
|
`
|
||||||
|
|
|
||||||
|
|
@ -68,12 +68,12 @@ const TimeRangeField = forwardRef(({
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Range ref={rangeRef}>
|
<Range ref={rangeRef}>
|
||||||
<Selected start={start} end={start > end ? 24 : end} />
|
<Selected $start={start} $end={start > end ? 24 : end} />
|
||||||
{start > end && <Selected start={start > end ? 0 : start} end={end} />}
|
{start > end && <Selected $start={start > end ? 0 : start} $end={end} />}
|
||||||
<Handle
|
<Handle
|
||||||
value={start}
|
$value={start}
|
||||||
label={timeFormat === '24h' ? times[start] : dayjs().hour(times[start]).format('ha')}
|
label={timeFormat === '24h' ? times[start] : dayjs().hour(times[start]).format('ha')}
|
||||||
extraPadding={end - start === 1 ? 'padding-right: 20px;' : (start - end === 1 ? 'padding-left: 20px;' : '')}
|
$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
|
||||||
|
|
@ -105,9 +105,9 @@ const TimeRangeField = forwardRef(({
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Handle
|
<Handle
|
||||||
value={end}
|
$value={end}
|
||||||
label={timeFormat === '24h' ? times[end] : dayjs().hour(times[end]).format('ha')}
|
label={timeFormat === '24h' ? times[end] : dayjs().hour(times[end]).format('ha')}
|
||||||
extraPadding={end - start === 1 ? 'padding-left: 20px;' : (start - end === 1 ? 'padding-right: 20px;' : '')}
|
$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
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { styled } from 'goober'
|
import { styled } from 'goober'
|
||||||
|
import { forwardRef } from 'react'
|
||||||
|
|
||||||
export const Wrapper = styled('div')`
|
export const Wrapper = styled('div')`
|
||||||
margin: 30px 0;
|
margin: 30px 0;
|
||||||
|
|
@ -17,7 +18,7 @@ export const StyledSubLabel = styled('label')`
|
||||||
opacity: .6;
|
opacity: .6;
|
||||||
`
|
`
|
||||||
|
|
||||||
export const Range = styled('div')`
|
export const Range = styled('div', forwardRef)`
|
||||||
user-select: none;
|
user-select: none;
|
||||||
background-color: var(--surface);
|
background-color: var(--surface);
|
||||||
border: 1px solid var(--primary);
|
border: 1px solid var(--primary);
|
||||||
|
|
@ -35,7 +36,7 @@ export const Handle = styled('div')`
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -10px;
|
top: -10px;
|
||||||
left: calc(${props => props.value * 4.166}% - 11px);
|
left: calc(${props => props.$value * 4.166}% - 11px);
|
||||||
cursor: ew-resize;
|
cursor: ew-resize;
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
transition: left .1s;
|
transition: left .1s;
|
||||||
|
|
@ -65,15 +66,15 @@ export const Handle = styled('div')`
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
${props => props.extraPadding}
|
${props => props.$extraPadding}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const Selected = styled('div')`
|
export const Selected = styled('div')`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
left: ${props => props.start * 4.166}%;
|
left: ${props => props.$start * 4.166}%;
|
||||||
right: calc(100% - ${props => props.end * 4.166}%);
|
right: calc(100% - ${props => props.$end * 4.166}%);
|
||||||
top: 0;
|
top: 0;
|
||||||
background-color: var(--primary);
|
background-color: var(--primary);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ export const FakeTimeRange = styled('div')`
|
||||||
height: calc(100% + 20px);
|
height: calc(100% + 20px);
|
||||||
width: 20px;
|
width: 20px;
|
||||||
border: 1px solid var(--primary);
|
border: 1px solid var(--primary);
|
||||||
background-color: var(--secondary);
|
background-color: var(--tertiary);
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -10px;
|
top: -10px;
|
||||||
|
|
@ -79,7 +79,7 @@ export const FakeTimeRange = styled('div')`
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
color: var(--tertiary);
|
color: var(--secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react'
|
||||||
import { useHistory, Link } from 'react-router-dom';
|
import { useNavigate, Link } from 'react-router-dom'
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form'
|
||||||
import { useTranslation, Trans } from 'react-i18next';
|
import { useTranslation, Trans } from 'react-i18next'
|
||||||
|
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs'
|
||||||
import utc from 'dayjs/plugin/utc';
|
import utc from 'dayjs/plugin/utc'
|
||||||
import timezone from 'dayjs/plugin/timezone';
|
import timezone from 'dayjs/plugin/timezone'
|
||||||
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
import customParseFormat from 'dayjs/plugin/customParseFormat'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
TextField,
|
TextField,
|
||||||
|
|
@ -18,7 +18,7 @@ import {
|
||||||
Error,
|
Error,
|
||||||
Footer,
|
Footer,
|
||||||
Recents,
|
Recents,
|
||||||
} from 'components';
|
} from '/src/components'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
StyledMain,
|
StyledMain,
|
||||||
|
|
@ -37,82 +37,82 @@ import {
|
||||||
ButtonArea,
|
ButtonArea,
|
||||||
VideoWrapper,
|
VideoWrapper,
|
||||||
VideoLink,
|
VideoLink,
|
||||||
} from './homeStyle';
|
} from './Home.styles'
|
||||||
|
|
||||||
import api from 'services';
|
import api from '/src/services'
|
||||||
import { detect_browser } from 'utils';
|
import { detect_browser } from '/src/utils'
|
||||||
import { useTWAStore } from 'stores';
|
import { useTWAStore } from '/src/stores'
|
||||||
|
|
||||||
import logo from 'res/logo.svg';
|
import logo from '/src/res/logo.svg'
|
||||||
import video_thumb from 'res/video_thumb.jpg';
|
import video_thumb from '/src/res/video_thumb.jpg'
|
||||||
import timezones from 'res/timezones.json';
|
import timezones from '/src/res/timezones.json'
|
||||||
|
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc)
|
||||||
dayjs.extend(timezone);
|
dayjs.extend(timezone)
|
||||||
dayjs.extend(customParseFormat);
|
dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
const Home = ({ offline }) => {
|
const Home = ({ offline }) => {
|
||||||
const { register, handleSubmit, setValue } = useForm({
|
const { register, handleSubmit, setValue } = useForm({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null)
|
||||||
const [stats, setStats] = useState({
|
const [stats, setStats] = useState({
|
||||||
eventCount: null,
|
eventCount: null,
|
||||||
personCount: null,
|
personCount: null,
|
||||||
version: 'loading...',
|
version: 'loading...',
|
||||||
});
|
})
|
||||||
const [browser, setBrowser] = useState(undefined);
|
const [browser, setBrowser] = useState(undefined)
|
||||||
const [videoPlay, setVideoPlay] = useState(false);
|
const [videoPlay, setVideoPlay] = useState(false)
|
||||||
const { push } = useHistory();
|
const navigate = useNavigate()
|
||||||
const { t } = useTranslation(['common', 'home']);
|
const { t } = useTranslation(['common', 'home'])
|
||||||
const isTWA = useTWAStore(state => state.TWA);
|
const isTWA = useTWAStore(state => state.TWA)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetch = async () => {
|
const fetch = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await api.get('/stats');
|
const response = await api.get('/stats')
|
||||||
setStats(response.data);
|
setStats(response.data)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
fetch();
|
fetch()
|
||||||
document.title = 'Crab Fit';
|
document.title = 'Crab Fit'
|
||||||
setBrowser(detect_browser());
|
setBrowser(detect_browser())
|
||||||
}, []);
|
}, [])
|
||||||
|
|
||||||
const onSubmit = async data => {
|
const onSubmit = async data => {
|
||||||
setIsLoading(true);
|
setIsLoading(true)
|
||||||
setError(null);
|
setError(null)
|
||||||
try {
|
try {
|
||||||
const { start, end } = JSON.parse(data.times);
|
const { start, end } = JSON.parse(data.times)
|
||||||
const dates = JSON.parse(data.dates);
|
const dates = JSON.parse(data.dates)
|
||||||
|
|
||||||
if (dates.length === 0) {
|
if (dates.length === 0) {
|
||||||
return setError(t('home:form.errors.no_dates'));
|
return setError(t('home:form.errors.no_dates'))
|
||||||
}
|
}
|
||||||
const isSpecificDates = typeof dates[0] === 'string' && dates[0].length === 8;
|
const isSpecificDates = typeof dates[0] === 'string' && dates[0].length === 8
|
||||||
if (start === end) {
|
if (start === end) {
|
||||||
return setError(t('home:form.errors.same_times'));
|
return setError(t('home:form.errors.same_times'))
|
||||||
}
|
}
|
||||||
|
|
||||||
let times = dates.reduce((times, date) => {
|
const times = dates.reduce((times, date) => {
|
||||||
let day = [];
|
const day = []
|
||||||
for (let i = start; i < (start > end ? 24 : end); i++) {
|
for (let i = start; i < (start > end ? 24 : end); i++) {
|
||||||
if (isSpecificDates) {
|
if (isSpecificDates) {
|
||||||
day.push(
|
day.push(
|
||||||
dayjs.tz(date, 'DDMMYYYY', data.timezone)
|
dayjs.tz(date, 'DDMMYYYY', data.timezone)
|
||||||
.hour(i).minute(0).utc().format('HHmm-DDMMYYYY')
|
.hour(i).minute(0).utc().format('HHmm-DDMMYYYY')
|
||||||
);
|
)
|
||||||
} else {
|
} else {
|
||||||
day.push(
|
day.push(
|
||||||
dayjs().tz(data.timezone)
|
dayjs().tz(data.timezone)
|
||||||
.day(date).hour(i).minute(0).utc().format('HHmm-d')
|
.day(date).hour(i).minute(0).utc().format('HHmm-d')
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (start > end) {
|
if (start > end) {
|
||||||
|
|
@ -120,21 +120,21 @@ const Home = ({ offline }) => {
|
||||||
if (isSpecificDates) {
|
if (isSpecificDates) {
|
||||||
day.push(
|
day.push(
|
||||||
dayjs.tz(date, 'DDMMYYYY', data.timezone)
|
dayjs.tz(date, 'DDMMYYYY', data.timezone)
|
||||||
.hour(i).minute(0).utc().format('HHmm-DDMMYYYY')
|
.hour(i).minute(0).utc().format('HHmm-DDMMYYYY')
|
||||||
);
|
)
|
||||||
} else {
|
} else {
|
||||||
day.push(
|
day.push(
|
||||||
dayjs().tz(data.timezone)
|
dayjs().tz(data.timezone)
|
||||||
.day(date).hour(i).minute(0).utc().format('HHmm-d')
|
.day(date).hour(i).minute(0).utc().format('HHmm-d')
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return [...times, ...day];
|
return [...times, ...day]
|
||||||
}, []);
|
}, [])
|
||||||
|
|
||||||
if (times.length === 0) {
|
if (times.length === 0) {
|
||||||
return setError(t('home:form.errors.no_time'));
|
return setError(t('home:form.errors.no_time'))
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await api.post('/event', {
|
const response = await api.post('/event', {
|
||||||
|
|
@ -143,18 +143,18 @@ const Home = ({ offline }) => {
|
||||||
times: times,
|
times: times,
|
||||||
timezone: data.timezone,
|
timezone: data.timezone,
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
push(`/${response.data.id}`);
|
navigate(`/${response.data.id}`)
|
||||||
gtag('event', 'create_event', {
|
gtag('event', 'create_event', {
|
||||||
'event_category': 'home',
|
'event_category': 'home',
|
||||||
});
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(t('home:form.errors.unknown'));
|
setError(t('home:form.errors.unknown'))
|
||||||
console.error(e);
|
console.error(e)
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
@ -162,7 +162,7 @@ const Home = ({ offline }) => {
|
||||||
<Center>
|
<Center>
|
||||||
<Logo src={logo} alt="" />
|
<Logo src={logo} alt="" />
|
||||||
</Center>
|
</Center>
|
||||||
<TitleSmall altChars={/^[A-Za-z ]+$/.test(t('home:create'))}>{t('home:create')}</TitleSmall>
|
<TitleSmall $altChars={/^[A-Za-z ]+$/.test(t('home:create'))}>{t('home:create')}</TitleSmall>
|
||||||
<TitleLarge>CRAB FIT</TitleLarge>
|
<TitleLarge>CRAB FIT</TitleLarge>
|
||||||
<Links>
|
<Links>
|
||||||
<a href="#about">{t('home:nav.about')}</a> / <a href="#donate">{t('home:nav.donate')}</a>
|
<a href="#about">{t('home:nav.about')}</a> / <a href="#donate">{t('home:nav.donate')}</a>
|
||||||
|
|
@ -228,11 +228,11 @@ const Home = ({ offline }) => {
|
||||||
<h2>{t('home:about.name')}</h2>
|
<h2>{t('home:about.name')}</h2>
|
||||||
<Stats>
|
<Stats>
|
||||||
<Stat>
|
<Stat>
|
||||||
<StatNumber>{stats.eventCount ?? '1100+'}</StatNumber>
|
<StatNumber>{new Intl.NumberFormat().format(stats.eventCount ?? 7000)}{!stats.eventCount && '+'}</StatNumber>
|
||||||
<StatLabel>{t('home:about.events')}</StatLabel>
|
<StatLabel>{t('home:about.events')}</StatLabel>
|
||||||
</Stat>
|
</Stat>
|
||||||
<Stat>
|
<Stat>
|
||||||
<StatNumber>{stats.personCount ?? '3700+'}</StatNumber>
|
<StatNumber>{new Intl.NumberFormat().format(stats.personCount ?? 25000)}{!stats.personCount && '+'}</StatNumber>
|
||||||
<StatLabel>{t('home:about.availabilities')}</StatLabel>
|
<StatLabel>{t('home:about.availabilities')}</StatLabel>
|
||||||
</Stat>
|
</Stat>
|
||||||
</Stats>
|
</Stats>
|
||||||
|
|
@ -240,14 +240,14 @@ const Home = ({ offline }) => {
|
||||||
|
|
||||||
{videoPlay ? (
|
{videoPlay ? (
|
||||||
<VideoWrapper>
|
<VideoWrapper>
|
||||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/yXGd4VXZzcY?modestbranding=1&rel=0&autoplay=1" title={t('common:video.title')} frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
<iframe width="560" height="315" src="https://www.youtube.com/embed/yXGd4VXZzcY?modestbranding=1&rel=0&autoplay=1" title={t('common:video.title')} frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowFullScreen></iframe>
|
||||||
</VideoWrapper>
|
</VideoWrapper>
|
||||||
) : (
|
) : (
|
||||||
<VideoLink
|
<VideoLink
|
||||||
href="https://www.youtube.com/watch?v=yXGd4VXZzcY"
|
href="https://www.youtube.com/watch?v=yXGd4VXZzcY"
|
||||||
onClick={e => {
|
onClick={e => {
|
||||||
e.preventDefault();
|
e.preventDefault()
|
||||||
setVideoPlay(true);
|
setVideoPlay(true)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img src={video_thumb} alt={t('common:video.button')} />
|
<img src={video_thumb} alt={t('common:video.button')} />
|
||||||
|
|
@ -274,10 +274,10 @@ const Home = ({ offline }) => {
|
||||||
rel="noreferrer noopener"
|
rel="noreferrer noopener"
|
||||||
secondary
|
secondary
|
||||||
>{{
|
>{{
|
||||||
chrome: t('home:about.chrome_extension'),
|
chrome: t('home:about.chrome_extension'),
|
||||||
firefox: t('home:about.firefox_extension'),
|
firefox: t('home:about.firefox_extension'),
|
||||||
safari: t('home:about.safari_extension'),
|
safari: t('home:about.safari_extension'),
|
||||||
}[browser]}</Button>
|
}[browser]}</Button>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
href="https://play.google.com/store/apps/details?id=fit.crab"
|
href="https://play.google.com/store/apps/details?id=fit.crab"
|
||||||
|
|
@ -298,7 +298,7 @@ const Home = ({ offline }) => {
|
||||||
|
|
||||||
<Footer />
|
<Footer />
|
||||||
</>
|
</>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default Home;
|
export default Home
|
||||||
|
|
@ -21,7 +21,7 @@ export const TitleSmall = styled('span')`
|
||||||
line-height: 1em;
|
line-height: 1em;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
|
||||||
${props => !props.altChars && `
|
${props => !props.$altChars && `
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|
@ -163,10 +163,10 @@ export const VideoLink = styled('a')`
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
transition: transform .15s;
|
transition: transform .15s;
|
||||||
|
|
||||||
:hover, :focus {
|
&:hover, &:focus {
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
:active {
|
&:active {
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -188,7 +188,7 @@ export const VideoLink = styled('a')`
|
||||||
text-shadow: 0 0 20px rgba(0,0,0,.8);
|
text-shadow: 0 0 20px rgba(0,0,0,.8);
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
::before {
|
&::before {
|
||||||
content: '';
|
content: '';
|
||||||
display: block;
|
display: block;
|
||||||
height: 2em;
|
height: 2em;
|
||||||
|
|
@ -196,7 +196,7 @@ export const VideoLink = styled('a')`
|
||||||
background: currentColor;
|
background: currentColor;
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
margin: 0 auto .4em;
|
margin: 0 auto .4em;
|
||||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='${props => encodeURIComponent(props.theme.primaryDark)}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-play'%3E%3Cpolygon points='5 3 19 12 5 21 5 3'%3E%3C/polygon%3E%3C/svg%3E");
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='red' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-play'%3E%3Cpolygon points='5 3 19 12 5 21 5 3'%3E%3C/polygon%3E%3C/svg%3E");
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: 1em;
|
background-size: 1em;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { lazy } from 'react'
|
import { lazy } from 'react'
|
||||||
|
|
||||||
//export const Home = lazy(() => import('./Home/Home'))
|
export const Home = lazy(() => import('./Home/Home'))
|
||||||
//export const Event = lazy(() => import('./Event/Event'))
|
//export const Event = lazy(() => import('./Event/Event'))
|
||||||
//export const Create = lazy(() => import('./Create/Create'))
|
//export const Create = lazy(() => import('./Create/Create'))
|
||||||
//export const Help = lazy(() => import('./Help/Help'))
|
export const Help = lazy(() => import('./Help/Help'))
|
||||||
export const Privacy = lazy(() => import('./Privacy/Privacy'))
|
export const Privacy = lazy(() => import('./Privacy/Privacy'))
|
||||||
|
|
|
||||||
|
|
@ -8,30 +8,31 @@ const handleError = error => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const api = {
|
const api = {
|
||||||
get: async (endpoint, data) => {
|
get: async endpoint => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(API_URL + endpoint)
|
const response = await fetch(API_URL + endpoint)
|
||||||
return Promise.resolve(response)
|
const json = await response.json()
|
||||||
|
return Promise.resolve(json)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return handleError(error)
|
return handleError(error)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
post: async (endpoint, data, options = {}) => {
|
post: async (endpoint, data, options = {}) => {
|
||||||
try {
|
try {
|
||||||
const response = await instance.post(endpoint, data, options);
|
const response = await fetch(API_URL + endpoint, data, options)
|
||||||
return Promise.resolve(response);
|
return Promise.resolve(response)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return handleError(error);
|
return handleError(error)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
patch: async (endpoint, data) => {
|
patch: async (endpoint, data) => {
|
||||||
try {
|
try {
|
||||||
const response = await instance.patch(endpoint, data);
|
const response = await fetch(API_URL + endpoint, data)
|
||||||
return Promise.resolve(response);
|
return Promise.resolve(response)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return handleError(error);
|
return handleError(error)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
|
||||||
export default api;
|
export default api
|
||||||
|
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
const theme = {
|
|
||||||
light: {
|
|
||||||
mode: 'light',
|
|
||||||
background: '#FFFFFF',
|
|
||||||
text: '#000000',
|
|
||||||
primary: '#F79E00',
|
|
||||||
primaryDark: '#F48600',
|
|
||||||
primaryLight: '#F4BB60',
|
|
||||||
primaryBackground: '#FEF2DD',
|
|
||||||
error: '#D32F2F',
|
|
||||||
loading: '#DDDDDD',
|
|
||||||
},
|
|
||||||
dark: {
|
|
||||||
mode: 'dark',
|
|
||||||
background: '#111111',
|
|
||||||
text: '#DDDDDD',
|
|
||||||
primary: '#F79E00',
|
|
||||||
primaryDark: '#CC7313',
|
|
||||||
primaryLight: '#F4BB60',
|
|
||||||
primaryBackground: '#30240F',
|
|
||||||
error: '#E53935',
|
|
||||||
loading: '#444444',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default theme;
|
|
||||||
Loading…
Reference in a new issue