Update react-hook-form, focus login on tab press
This commit is contained in:
parent
2c43a8e0d2
commit
77277c8c3e
|
|
@ -22,7 +22,7 @@
|
||||||
"i18next-http-backend": "^1.2.4",
|
"i18next-http-backend": "^1.2.4",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-hook-form": "^6.15.4",
|
"react-hook-form": "^7.8.1",
|
||||||
"react-i18next": "^11.8.15",
|
"react-i18next": "^11.8.15",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"react-scripts": "4.0.3",
|
"react-scripts": "4.0.3",
|
||||||
|
|
|
||||||
|
|
@ -48,10 +48,13 @@
|
||||||
"availabilities": "Availabilities entered",
|
"availabilities": "Availabilities entered",
|
||||||
"content": {
|
"content": {
|
||||||
"p1": "Crab Fit helps you fit your event around everyone's schedules. Simply create an event above and send the link to everyone that is participating. Results update live and you will be able to see a heat-map of when everyone is free.<1/><2>Learn more about how to Crab Fit</2>.",
|
"p1": "Crab Fit helps you fit your event around everyone's schedules. Simply create an event above and send the link to everyone that is participating. Results update live and you will be able to see a heat-map of when everyone is free.<1/><2>Learn more about how to Crab Fit</2>.",
|
||||||
"p2": "Create a lot of Crab Fits? Get the <1>Chrome extension</1> or <3>Firefox extension</3> for your browser! You can also download the <5>Android app</5> to Crab Fit on the go.",
|
|
||||||
"p3": "Created by <1>Ben Grant</1>, Crab Fit is the modern-day solution to your group event planning debates.",
|
"p3": "Created by <1>Ben Grant</1>, Crab Fit is the modern-day solution to your group event planning debates.",
|
||||||
"p4": "The code for Crab Fit is open source, if you find any issues or want to contribute, you can visit the <1>repository</1>. By using Crab Fit you agree to the <3>privacy policy</3>.",
|
"p4": "The code for Crab Fit is open source, if you find any issues or want to contribute, you can visit the <1>repository</1>. By using Crab Fit you agree to the <3>privacy policy</3>.",
|
||||||
"p5": "Consider donating below if it helped you out so it can stay free for everyone. 🦀"
|
"p5": "Consider donating below if it helped you out so Crab Fit can stay free for everyone. 🦀"
|
||||||
}
|
},
|
||||||
|
"chrome_extension": "Get the Chrome Extension",
|
||||||
|
"firefox_extension": "Get the Firefox Extension",
|
||||||
|
"safari_extension": "Get the Safari Extension",
|
||||||
|
"android_app": "Download the Android app"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -206,7 +206,7 @@ export const Person = styled.button`
|
||||||
font: inherit;
|
font: inherit;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
border: 2px solid ${props => props.theme.text};
|
border: 1px solid ${props => props.theme.text};
|
||||||
color: ${props => props.theme.text};
|
color: ${props => props.theme.text};
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,16 @@
|
||||||
import { Pressable } from './buttonStyle';
|
import { Pressable } from './buttonStyle';
|
||||||
|
|
||||||
const Button = ({ href, type = 'button', ...props }) => (
|
const Button = ({ href, type = 'button', icon, children, ...props }) => (
|
||||||
<Pressable
|
<Pressable
|
||||||
type={href ? undefined : type}
|
type={href ? undefined : type}
|
||||||
role="button"
|
role={href && 'button'}
|
||||||
as={href ? 'a' : 'button'}
|
as={href ? 'a' : 'button'}
|
||||||
href={href}
|
href={href}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
>
|
||||||
|
{icon}
|
||||||
|
{children}
|
||||||
|
</Pressable>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default Button;
|
export default Button;
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,12 @@ export const Pressable = styled.button`
|
||||||
transform-style: preserve-3d;
|
transform-style: preserve-3d;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
|
|
||||||
|
& svg, & img {
|
||||||
|
height: 1.2em;
|
||||||
|
width: 1.2em;
|
||||||
|
margin-right: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
${props => props.size && `
|
${props => props.size && `
|
||||||
padding: 0;
|
padding: 0;
|
||||||
height: ${props.size};
|
height: ${props.size};
|
||||||
|
|
@ -39,7 +45,7 @@ export const Pressable = styled.button`
|
||||||
transition: transform 150ms cubic-bezier(0, 0, 0.58, 1), box-shadow 150ms cubic-bezier(0, 0, 0.58, 1);
|
transition: transform 150ms cubic-bezier(0, 0, 0.58, 1), box-shadow 150ms cubic-bezier(0, 0, 0.58, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover, &:focus {
|
||||||
transform: translate(0, 1px);
|
transform: translate(0, 1px);
|
||||||
&::before {
|
&::before {
|
||||||
transform: translate3d(0, 4px, -1em);
|
transform: translate3d(0, 4px, -1em);
|
||||||
|
|
@ -102,7 +108,7 @@ export const Pressable = styled.button`
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
|
|
||||||
${props => props.alt && `
|
${props => props.secondary && `
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 1px solid ${props.primaryColor || props.theme.mode === 'light' ? props.theme.primaryDark : props.theme.primaryLight};
|
border: 1px solid ${props.primaryColor || props.theme.mode === 'light' ? props.theme.primaryDark : props.theme.primaryLight};
|
||||||
color: ${props.primaryColor || props.theme.mode === 'light' ? props.theme.primaryDark : props.theme.primaryLight};
|
color: ${props.primaryColor || props.theme.mode === 'light' ? props.theme.primaryDark : props.theme.primaryLight};
|
||||||
|
|
@ -111,7 +117,7 @@ export const Pressable = styled.button`
|
||||||
&::before {
|
&::before {
|
||||||
content: none;
|
content: none;
|
||||||
}
|
}
|
||||||
&:hover, &:active {
|
&:hover, &:active, &:focus {
|
||||||
transform: none;
|
transform: none;
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useState, useEffect, useRef } from 'react';
|
import { useState, useEffect, useRef, forwardRef } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import isToday from 'dayjs/plugin/isToday';
|
import isToday from 'dayjs/plugin/isToday';
|
||||||
|
|
@ -47,13 +47,13 @@ const calculateMonth = (month, year, weekStart) => {
|
||||||
return dates;
|
return dates;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CalendarField = ({
|
const CalendarField = forwardRef(({
|
||||||
label,
|
label,
|
||||||
subLabel,
|
subLabel,
|
||||||
id,
|
id,
|
||||||
register,
|
setValue,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}, ref) => {
|
||||||
const weekStart = useSettingsStore(state => state.weekStart);
|
const weekStart = useSettingsStore(state => state.weekStart);
|
||||||
const locale = useLocaleUpdateStore(state => state.locale);
|
const locale = useLocaleUpdateStore(state => state.locale);
|
||||||
const { t } = useTranslation('home');
|
const { t } = useTranslation('home');
|
||||||
|
|
@ -88,6 +88,8 @@ const CalendarField = ({
|
||||||
_setMode(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) {
|
if (dayjs.Ls.hasOwnProperty(locale) && weekStart !== dayjs.Ls[locale].weekStart) {
|
||||||
dayjs.updateLocale(locale, { weekStart });
|
dayjs.updateLocale(locale, { weekStart });
|
||||||
|
|
@ -102,7 +104,7 @@ const CalendarField = ({
|
||||||
<input
|
<input
|
||||||
id={id}
|
id={id}
|
||||||
type="hidden"
|
type="hidden"
|
||||||
ref={register}
|
ref={ref}
|
||||||
value={type ? JSON.stringify(selectedDays) : JSON.stringify(selectedDates)}
|
value={type ? JSON.stringify(selectedDays) : JSON.stringify(selectedDates)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|
@ -211,8 +213,8 @@ const CalendarField = ({
|
||||||
{(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] === 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] === 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}
|
||||||
|
|
@ -257,6 +259,6 @@ const CalendarField = ({
|
||||||
)}
|
)}
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
export default CalendarField;
|
export default CalendarField;
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ const Donate = () => {
|
||||||
const { t } = useTranslation('common');
|
const { t } = useTranslation('common');
|
||||||
|
|
||||||
const firstLinkRef = useRef();
|
const firstLinkRef = useRef();
|
||||||
const buttonRef = useRef();
|
|
||||||
const modalRef = useRef();
|
const modalRef = useRef();
|
||||||
const [isOpen, _setIsOpen] = useState(false);
|
const [isOpen, _setIsOpen] = useState(false);
|
||||||
|
|
||||||
|
|
@ -117,7 +116,6 @@ const Donate = () => {
|
||||||
href="https://www.paypal.com/donate?business=N89X6YXRT5HKW&item_name=Crab+Fit+Donation¤cy_code=AUD&amount=5"
|
href="https://www.paypal.com/donate?business=N89X6YXRT5HKW&item_name=Crab+Fit+Donation¤cy_code=AUD&amount=5"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
ref={buttonRef}
|
|
||||||
style={{ whiteSpace: 'nowrap' }}
|
style={{ whiteSpace: 'nowrap' }}
|
||||||
>{t('donate.button')}</Button>
|
>{t('donate.button')}</Button>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ const Error = ({
|
||||||
open = true,
|
open = true,
|
||||||
...props
|
...props
|
||||||
}) => (
|
}) => (
|
||||||
<Wrapper open={open} {...props}>
|
<Wrapper role="alert" open={open} {...props}>
|
||||||
{children}
|
{children}
|
||||||
<CloseButton type="button" onClick={onClose} title="Close error">
|
<CloseButton type="button" onClick={onClose} title="Close error">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import { useTranslation } from 'react-i18next';
|
||||||
import { Button, Center } from 'components';
|
import { Button, Center } from 'components';
|
||||||
import { Loader } from '../Loading/loadingStyle';
|
import { Loader } from '../Loading/loadingStyle';
|
||||||
import {
|
import {
|
||||||
LoginButton,
|
|
||||||
CalendarList,
|
CalendarList,
|
||||||
CheckboxInput,
|
CheckboxInput,
|
||||||
CheckboxLabel,
|
CheckboxLabel,
|
||||||
|
|
@ -101,11 +100,10 @@ const GoogleCalendar = ({ timeZone, timeMin, timeMax, onImport }) => {
|
||||||
onClick={() => signIn()}
|
onClick={() => signIn()}
|
||||||
isLoading={signedIn === undefined}
|
isLoading={signedIn === undefined}
|
||||||
primaryColor="#4286F5"
|
primaryColor="#4286F5"
|
||||||
secondaryColor="#3367BD">
|
secondaryColor="#3367BD"
|
||||||
<LoginButton>
|
icon={<img src={googleLogo} alt="" />}
|
||||||
<img src={googleLogo} alt="" />
|
>
|
||||||
<span>{t('event:you.google_cal.login')}</span>
|
{t('event:you.google_cal.login')}
|
||||||
</LoginButton>
|
|
||||||
</Button>
|
</Button>
|
||||||
</Center>
|
</Center>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,5 @@
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
export const LoginButton = styled.div`
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
& img {
|
|
||||||
height: 1em;
|
|
||||||
margin-right: .8em;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const CalendarList = styled.div`
|
export const CalendarList = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
& > div {
|
& > div {
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ export const Bar = styled.div`
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin: 0 8px;
|
margin: 0 8px;
|
||||||
border: 2px solid ${props => props.theme.text};
|
border: 1px solid ${props => props.theme.text};
|
||||||
|
|
||||||
@media (max-width: 400px) {
|
@media (max-width: 400px) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import { useTranslation } from 'react-i18next';
|
||||||
import { Button, Center } from 'components';
|
import { Button, Center } from 'components';
|
||||||
import { Loader } from '../Loading/loadingStyle';
|
import { Loader } from '../Loading/loadingStyle';
|
||||||
import {
|
import {
|
||||||
LoginButton,
|
|
||||||
CalendarList,
|
CalendarList,
|
||||||
CheckboxInput,
|
CheckboxInput,
|
||||||
CheckboxLabel,
|
CheckboxLabel,
|
||||||
|
|
@ -168,11 +167,10 @@ const OutlookCalendar = ({ timeZone, timeMin, timeMax, onImport }) => {
|
||||||
onClick={() => signIn()}
|
onClick={() => signIn()}
|
||||||
isLoading={client === undefined}
|
isLoading={client === undefined}
|
||||||
primaryColor="#0364B9"
|
primaryColor="#0364B9"
|
||||||
secondaryColor="#02437D">
|
secondaryColor="#02437D"
|
||||||
<LoginButton>
|
icon={<img src={outlookLogo} alt="" />}
|
||||||
<img src={outlookLogo} alt="" />
|
>
|
||||||
<span>{t('event:you.outlook_cal')}</span>
|
{t('event:you.outlook_cal')}
|
||||||
</LoginButton>
|
|
||||||
</Button>
|
</Button>
|
||||||
</Center>
|
</Center>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import styled from '@emotion/styled';
|
||||||
|
|
||||||
export const Recent = styled.a`
|
export const Recent = styled.a`
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: inherit;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
@ -20,6 +19,7 @@ export const Recent = styled.a`
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
opacity: .8;
|
opacity: .8;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
color: ${props => props.theme.text};
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover .name {
|
&:hover .name {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { forwardRef } from 'react';
|
||||||
import {
|
import {
|
||||||
Wrapper,
|
Wrapper,
|
||||||
StyledLabel,
|
StyledLabel,
|
||||||
|
|
@ -5,7 +6,7 @@ import {
|
||||||
StyledSelect,
|
StyledSelect,
|
||||||
} from './selectFieldStyle';
|
} from './selectFieldStyle';
|
||||||
|
|
||||||
const SelectField = ({
|
const SelectField = forwardRef(({
|
||||||
label,
|
label,
|
||||||
subLabel,
|
subLabel,
|
||||||
id,
|
id,
|
||||||
|
|
@ -13,17 +14,16 @@ const SelectField = ({
|
||||||
inline = false,
|
inline = false,
|
||||||
small = false,
|
small = false,
|
||||||
defaultOption,
|
defaultOption,
|
||||||
register,
|
|
||||||
...props
|
...props
|
||||||
}) => (
|
}, ref) => (
|
||||||
<Wrapper inline={inline} small={small}>
|
<Wrapper inline={inline} small={small}>
|
||||||
{label && <StyledLabel htmlFor={id} inline={inline} small={small}>{label}</StyledLabel>}
|
{label && <StyledLabel htmlFor={id} inline={inline} small={small}>{label}</StyledLabel>}
|
||||||
{subLabel && <StyledSubLabel htmlFor={id}>{subLabel}</StyledSubLabel>}
|
{subLabel && <StyledSubLabel htmlFor={id}>{subLabel}</StyledSubLabel>}
|
||||||
|
|
||||||
<StyledSelect
|
<StyledSelect
|
||||||
id={id}
|
id={id}
|
||||||
ref={register}
|
|
||||||
small={small}
|
small={small}
|
||||||
|
ref={ref}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{defaultOption && <option value="">{defaultOption}</option>}
|
{defaultOption && <option value="">{defaultOption}</option>}
|
||||||
|
|
@ -38,6 +38,6 @@ const SelectField = ({
|
||||||
)}
|
)}
|
||||||
</StyledSelect>
|
</StyledSelect>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
));
|
||||||
|
|
||||||
export default SelectField;
|
export default SelectField;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { forwardRef } from 'react';
|
||||||
import {
|
import {
|
||||||
Wrapper,
|
Wrapper,
|
||||||
StyledLabel,
|
StyledLabel,
|
||||||
|
|
@ -5,19 +6,18 @@ import {
|
||||||
StyledInput,
|
StyledInput,
|
||||||
} from './textFieldStyle';
|
} from './textFieldStyle';
|
||||||
|
|
||||||
const TextField = ({
|
const TextField = forwardRef(({
|
||||||
label,
|
label,
|
||||||
subLabel,
|
subLabel,
|
||||||
id,
|
id,
|
||||||
inline = false,
|
inline = false,
|
||||||
register,
|
|
||||||
...props
|
...props
|
||||||
}) => (
|
}, 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={register} {...props} />
|
<StyledInput id={id} ref={ref} {...props} />
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
));
|
||||||
|
|
||||||
export default TextField;
|
export default TextField;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useState, useEffect, useRef } from 'react';
|
import { useState, useEffect, useRef, forwardRef } from 'react';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
import { useSettingsStore, useLocaleUpdateStore } from 'stores';
|
import { useSettingsStore, useLocaleUpdateStore } from 'stores';
|
||||||
|
|
@ -14,13 +14,13 @@ import {
|
||||||
|
|
||||||
const times = ['00','01','02','03','04','05','06','07','08','09','10','11','12','13','14','15','16','17','18','19','20','21','22','23','24'];
|
const times = ['00','01','02','03','04','05','06','07','08','09','10','11','12','13','14','15','16','17','18','19','20','21','22','23','24'];
|
||||||
|
|
||||||
const TimeRangeField = ({
|
const TimeRangeField = forwardRef(({
|
||||||
label,
|
label,
|
||||||
subLabel,
|
subLabel,
|
||||||
id,
|
id,
|
||||||
register,
|
setValue,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}, ref) => {
|
||||||
const timeFormat = useSettingsStore(state => state.timeFormat);
|
const timeFormat = useSettingsStore(state => state.timeFormat);
|
||||||
const locale = useLocaleUpdateStore(state => state.locale);
|
const locale = useLocaleUpdateStore(state => state.locale);
|
||||||
|
|
||||||
|
|
@ -38,6 +38,8 @@ const TimeRangeField = ({
|
||||||
}
|
}
|
||||||
}, [rangeRef]);
|
}, [rangeRef]);
|
||||||
|
|
||||||
|
useEffect(() => setValue(props.name, JSON.stringify({start, end})), [start, end, setValue, props.name]);
|
||||||
|
|
||||||
const handleMouseMove = e => {
|
const handleMouseMove = e => {
|
||||||
if (isStartMoving.current || isEndMoving.current) {
|
if (isStartMoving.current || isEndMoving.current) {
|
||||||
let step = Math.round(((e.pageX - rangeRect.current.left) / rangeRect.current.width) * 24);
|
let step = Math.round(((e.pageX - rangeRect.current.left) / rangeRect.current.width) * 24);
|
||||||
|
|
@ -60,8 +62,8 @@ const TimeRangeField = ({
|
||||||
<input
|
<input
|
||||||
id={id}
|
id={id}
|
||||||
type="hidden"
|
type="hidden"
|
||||||
ref={register}
|
|
||||||
value={JSON.stringify({start, end})}
|
value={JSON.stringify({start, end})}
|
||||||
|
ref={ref}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
@ -139,6 +141,6 @@ const TimeRangeField = ({
|
||||||
</Range>
|
</Range>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
export default TimeRangeField;
|
export default TimeRangeField;
|
||||||
|
|
|
||||||
|
|
@ -35,14 +35,17 @@ export const StyledLabel = styled.label`
|
||||||
|
|
||||||
export const Option = styled.div`
|
export const Option = styled.div`
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const HiddenInput = styled.input`
|
export const HiddenInput = styled.input`
|
||||||
height: 0;
|
height: 0;
|
||||||
width: 0;
|
width: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: -1000px;
|
top: 0;
|
||||||
|
left: 0;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
appearance: none;
|
||||||
|
|
||||||
&:checked + label {
|
&:checked + label {
|
||||||
color: ${props => props.theme.background};
|
color: ${props => props.theme.background};
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ dayjs.extend(timezone);
|
||||||
dayjs.extend(customParseFormat);
|
dayjs.extend(customParseFormat);
|
||||||
|
|
||||||
const Create = ({ offline }) => {
|
const Create = ({ offline }) => {
|
||||||
const { register, handleSubmit } = useForm({
|
const { register, handleSubmit, setValue } = useForm({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||||
},
|
},
|
||||||
|
|
@ -196,36 +196,34 @@ const Create = ({ offline }) => {
|
||||||
label={t('home:form.name.label')}
|
label={t('home:form.name.label')}
|
||||||
subLabel={t('home:form.name.sublabel')}
|
subLabel={t('home:form.name.sublabel')}
|
||||||
type="text"
|
type="text"
|
||||||
name="name"
|
|
||||||
id="name"
|
id="name"
|
||||||
register={register}
|
{...register('name')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<CalendarField
|
<CalendarField
|
||||||
label={t('home:form.dates.label')}
|
label={t('home:form.dates.label')}
|
||||||
subLabel={t('home:form.dates.sublabel')}
|
subLabel={t('home:form.dates.sublabel')}
|
||||||
name="dates"
|
|
||||||
id="dates"
|
id="dates"
|
||||||
required
|
required
|
||||||
register={register}
|
setValue={setValue}
|
||||||
|
{...register('dates')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TimeRangeField
|
<TimeRangeField
|
||||||
label={t('home:form.times.label')}
|
label={t('home:form.times.label')}
|
||||||
subLabel={t('home:form.times.sublabel')}
|
subLabel={t('home:form.times.sublabel')}
|
||||||
name="times"
|
|
||||||
id="times"
|
id="times"
|
||||||
required
|
required
|
||||||
register={register}
|
setValue={setValue}
|
||||||
|
{...register('times')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SelectField
|
<SelectField
|
||||||
label={t('home:form.timezone.label')}
|
label={t('home:form.timezone.label')}
|
||||||
name="timezone"
|
|
||||||
id="timezone"
|
id="timezone"
|
||||||
register={register}
|
|
||||||
options={timezones}
|
options={timezones}
|
||||||
required
|
required
|
||||||
|
{...register('timezone')}
|
||||||
defaultOption={t('home:form.timezone.defaultOption')}
|
defaultOption={t('home:form.timezone.defaultOption')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ const Event = (props) => {
|
||||||
|
|
||||||
const { t } = useTranslation(['common', 'event']);
|
const { t } = useTranslation(['common', 'event']);
|
||||||
|
|
||||||
const { register, handleSubmit } = useForm();
|
const { register, handleSubmit, setFocus } = useForm();
|
||||||
const { id } = props.match.params;
|
const { id } = props.match.params;
|
||||||
const { offline } = props;
|
const { offline } = props;
|
||||||
const [timezone, setTimezone] = useState(Intl.DateTimeFormat().resolvedOptions().timeZone);
|
const [timezone, setTimezone] = useState(Intl.DateTimeFormat().resolvedOptions().timeZone);
|
||||||
|
|
@ -329,20 +329,18 @@ const Event = (props) => {
|
||||||
<TextField
|
<TextField
|
||||||
label={t('event:form.name')}
|
label={t('event:form.name')}
|
||||||
type="text"
|
type="text"
|
||||||
name="name"
|
|
||||||
id="name"
|
id="name"
|
||||||
inline
|
inline
|
||||||
required
|
required
|
||||||
register={register}
|
{...register('name')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
label={t('event:form.password')}
|
label={t('event:form.password')}
|
||||||
type="password"
|
type="password"
|
||||||
name="password"
|
|
||||||
id="password"
|
id="password"
|
||||||
inline
|
inline
|
||||||
register={register}
|
{...register('password')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -394,7 +392,9 @@ const Event = (props) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (user) {
|
if (user) {
|
||||||
setTab('you');
|
setTab('you');
|
||||||
}
|
} else {
|
||||||
|
setFocus('name');
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
selected={tab === 'you'}
|
selected={tab === 'you'}
|
||||||
disabled={!user}
|
disabled={!user}
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ export const Tab = styled.a`
|
||||||
color: ${props => props.theme.text};
|
color: ${props => props.theme.text};
|
||||||
padding: 8px 18px;
|
padding: 8px 18px;
|
||||||
background-color: ${props => props.theme.primaryBackground};
|
background-color: ${props => props.theme.primaryBackground};
|
||||||
border: 1px solid ${props => props.theme.primaryLight};
|
border: 1px solid ${props => props.theme.primary};
|
||||||
border-bottom: 0;
|
border-bottom: 0;
|
||||||
margin: 0 4px;
|
margin: 0 4px;
|
||||||
border-top-left-radius: 5px;
|
border-top-left-radius: 5px;
|
||||||
|
|
|
||||||
|
|
@ -34,9 +34,11 @@ import {
|
||||||
StatNumber,
|
StatNumber,
|
||||||
StatLabel,
|
StatLabel,
|
||||||
OfflineMessage,
|
OfflineMessage,
|
||||||
|
ButtonArea,
|
||||||
} from './homeStyle';
|
} from './homeStyle';
|
||||||
|
|
||||||
import api from 'services';
|
import api from 'services';
|
||||||
|
import { detect_browser } from 'utils';
|
||||||
|
|
||||||
import logo from 'res/logo.svg';
|
import logo from 'res/logo.svg';
|
||||||
import timezones from 'res/timezones.json';
|
import timezones from 'res/timezones.json';
|
||||||
|
|
@ -46,7 +48,7 @@ dayjs.extend(timezone);
|
||||||
dayjs.extend(customParseFormat);
|
dayjs.extend(customParseFormat);
|
||||||
|
|
||||||
const Home = ({ offline }) => {
|
const Home = ({ offline }) => {
|
||||||
const { register, handleSubmit } = useForm({
|
const { register, handleSubmit, setValue } = useForm({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||||
},
|
},
|
||||||
|
|
@ -58,6 +60,7 @@ const Home = ({ offline }) => {
|
||||||
personCount: null,
|
personCount: null,
|
||||||
version: 'loading...',
|
version: 'loading...',
|
||||||
});
|
});
|
||||||
|
const [browser, setBrowser] = useState(undefined);
|
||||||
const { push } = useHistory();
|
const { push } = useHistory();
|
||||||
const { t } = useTranslation(['common', 'home']);
|
const { t } = useTranslation(['common', 'home']);
|
||||||
|
|
||||||
|
|
@ -73,6 +76,7 @@ const Home = ({ offline }) => {
|
||||||
|
|
||||||
fetch();
|
fetch();
|
||||||
document.title = 'Crab Fit';
|
document.title = 'Crab Fit';
|
||||||
|
setBrowser(detect_browser());
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onSubmit = async data => {
|
const onSubmit = async data => {
|
||||||
|
|
@ -173,36 +177,34 @@ const Home = ({ offline }) => {
|
||||||
label={t('home:form.name.label')}
|
label={t('home:form.name.label')}
|
||||||
subLabel={t('home:form.name.sublabel')}
|
subLabel={t('home:form.name.sublabel')}
|
||||||
type="text"
|
type="text"
|
||||||
name="name"
|
|
||||||
id="name"
|
id="name"
|
||||||
register={register}
|
{...register('name')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<CalendarField
|
<CalendarField
|
||||||
label={t('home:form.dates.label')}
|
label={t('home:form.dates.label')}
|
||||||
subLabel={t('home:form.dates.sublabel')}
|
subLabel={t('home:form.dates.sublabel')}
|
||||||
name="dates"
|
|
||||||
id="dates"
|
id="dates"
|
||||||
required
|
required
|
||||||
register={register}
|
setValue={setValue}
|
||||||
|
{...register('dates')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TimeRangeField
|
<TimeRangeField
|
||||||
label={t('home:form.times.label')}
|
label={t('home:form.times.label')}
|
||||||
subLabel={t('home:form.times.sublabel')}
|
subLabel={t('home:form.times.sublabel')}
|
||||||
name="times"
|
|
||||||
id="times"
|
id="times"
|
||||||
required
|
required
|
||||||
register={register}
|
setValue={setValue}
|
||||||
|
{...register('times')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SelectField
|
<SelectField
|
||||||
label={t('home:form.timezone.label')}
|
label={t('home:form.timezone.label')}
|
||||||
name="timezone"
|
|
||||||
id="timezone"
|
id="timezone"
|
||||||
register={register}
|
|
||||||
options={timezones}
|
options={timezones}
|
||||||
required
|
required
|
||||||
|
{...register('timezone')}
|
||||||
defaultOption={t('home:form.timezone.defaultOption')}
|
defaultOption={t('home:form.timezone.defaultOption')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
@ -224,15 +226,37 @@ const Home = ({ offline }) => {
|
||||||
<StatLabel>{t('home:about.events')}</StatLabel>
|
<StatLabel>{t('home:about.events')}</StatLabel>
|
||||||
</Stat>
|
</Stat>
|
||||||
<Stat>
|
<Stat>
|
||||||
<StatNumber>{stats.personCount ?? '500+'}</StatNumber>
|
<StatNumber>{stats.personCount ?? '550+'}</StatNumber>
|
||||||
<StatLabel>{t('home:about.availabilities')}</StatLabel>
|
<StatLabel>{t('home:about.availabilities')}</StatLabel>
|
||||||
</Stat>
|
</Stat>
|
||||||
</Stats>
|
</Stats>
|
||||||
<P><Trans i18nKey="home:about.content.p1">Crab Fit helps you fit your event around everyone's schedules. Simply create an event above and send the link to everyone that is participating. Results update live and you will be able to see a heat-map of when everyone is free.<br /><Link to="/how-to">Learn more about how to Crab Fit</Link>.</Trans></P>
|
<P><Trans i18nKey="home:about.content.p1">Crab Fit helps you fit your event around everyone's schedules. Simply create an event above and send the link to everyone that is participating. Results update live and you will be able to see a heat-map of when everyone is free.<br /><Link to="/how-to">Learn more about how to Crab Fit</Link>.</Trans></P>
|
||||||
{/* eslint-disable-next-line */}
|
<ButtonArea>
|
||||||
<P><Trans i18nKey="home:about.content.p2">Create a lot of Crab Fits? Get the <a href="https://chrome.google.com/webstore/detail/crab-fit/pnafiibmjbiljofcpjlbonpgdofjhhkj" target="_blank">Chrome extension</a> or <a href="https://addons.mozilla.org/en-US/firefox/addon/crab-fit/" target="_blank">Firefox extension</a> for your browser! You can also download the <a href="https://play.google.com/store/apps/details?id=fit.crab" target="_blank">Android app</a> to Crab Fit on the go.</Trans></P>
|
{['chrome', 'firefox'].includes(browser) && (
|
||||||
{/* eslint-disable-next-line */}
|
<Button
|
||||||
<P><Trans i18nKey="home:about.content.p3">Created by <a href="https://bengrant.dev" target="_blank">Ben Grant</a>, Crab Fit is the modern-day solution to your group event planning debates.</Trans></P>
|
href={{
|
||||||
|
chrome: 'https://chrome.google.com/webstore/detail/crab-fit/pnafiibmjbiljofcpjlbonpgdofjhhkj',
|
||||||
|
firefox: 'https://addons.mozilla.org/en-US/firefox/addon/crab-fit/',
|
||||||
|
}[browser]}
|
||||||
|
icon={{
|
||||||
|
chrome: <svg viewBox="0 0 24 24"><path fill="currentColor" d="M12,20L15.46,14H15.45C15.79,13.4 16,12.73 16,12C16,10.8 15.46,9.73 14.62,9H19.41C19.79,9.93 20,10.94 20,12A8,8 0 0,1 12,20M4,12C4,10.54 4.39,9.18 5.07,8L8.54,14H8.55C9.24,15.19 10.5,16 12,16C12.45,16 12.88,15.91 13.29,15.77L10.89,19.91C7,19.37 4,16.04 4,12M15,12A3,3 0 0,1 12,15A3,3 0 0,1 9,12A3,3 0 0,1 12,9A3,3 0 0,1 15,12M12,4C14.96,4 17.54,5.61 18.92,8H12C10.06,8 8.45,9.38 8.08,11.21L5.7,7.08C7.16,5.21 9.44,4 12,4M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" /></svg>,
|
||||||
|
firefox: <svg viewBox="0 0 24 24"><path fill="currentColor" d="M9.27 7.94C9.27 7.94 9.27 7.94 9.27 7.94M6.85 6.74C6.86 6.74 6.86 6.74 6.85 6.74M21.28 8.6C20.85 7.55 19.96 6.42 19.27 6.06C19.83 7.17 20.16 8.28 20.29 9.1L20.29 9.12C19.16 6.3 17.24 5.16 15.67 2.68C15.59 2.56 15.5 2.43 15.43 2.3C15.39 2.23 15.36 2.16 15.32 2.09C15.26 1.96 15.2 1.83 15.17 1.69C15.17 1.68 15.16 1.67 15.15 1.67H15.13L15.12 1.67L15.12 1.67L15.12 1.67C12.9 2.97 11.97 5.26 11.74 6.71C11.05 6.75 10.37 6.92 9.75 7.22C9.63 7.27 9.58 7.41 9.62 7.53C9.67 7.67 9.83 7.74 9.96 7.68C10.5 7.42 11.1 7.27 11.7 7.23L11.75 7.23C11.83 7.22 11.92 7.22 12 7.22C12.5 7.21 12.97 7.28 13.44 7.42L13.5 7.44C13.6 7.46 13.67 7.5 13.75 7.5C13.8 7.54 13.86 7.56 13.91 7.58L14.05 7.64C14.12 7.67 14.19 7.7 14.25 7.73C14.28 7.75 14.31 7.76 14.34 7.78C14.41 7.82 14.5 7.85 14.54 7.89C14.58 7.91 14.62 7.94 14.66 7.96C15.39 8.41 16 9.03 16.41 9.77C15.88 9.4 14.92 9.03 14 9.19C17.6 11 16.63 17.19 11.64 16.95C11.2 16.94 10.76 16.85 10.34 16.7C10.24 16.67 10.14 16.63 10.05 16.58C10 16.56 9.93 16.53 9.88 16.5C8.65 15.87 7.64 14.68 7.5 13.23C7.5 13.23 8 11.5 10.83 11.5C11.14 11.5 12 10.64 12.03 10.4C12.03 10.31 10.29 9.62 9.61 8.95C9.24 8.59 9.07 8.42 8.92 8.29C8.84 8.22 8.75 8.16 8.66 8.1C8.43 7.3 8.42 6.45 8.63 5.65C7.6 6.12 6.8 6.86 6.22 7.5H6.22C5.82 7 5.85 5.35 5.87 5C5.86 5 5.57 5.16 5.54 5.18C5.19 5.43 4.86 5.71 4.56 6C4.21 6.37 3.9 6.74 3.62 7.14C3 8.05 2.5 9.09 2.28 10.18C2.28 10.19 2.18 10.59 2.11 11.1L2.08 11.33C2.06 11.5 2.04 11.65 2 11.91L2 11.94L2 12.27L2 12.32C2 17.85 6.5 22.33 12 22.33C16.97 22.33 21.08 18.74 21.88 14C21.9 13.89 21.91 13.76 21.93 13.63C22.13 11.91 21.91 10.11 21.28 8.6Z" /></svg>,
|
||||||
|
}[browser]}
|
||||||
|
target="_blank"
|
||||||
|
secondary
|
||||||
|
>{{
|
||||||
|
chrome: t('home:about.chrome_extension'),
|
||||||
|
firefox: t('home:about.safari_extension'),
|
||||||
|
}[browser]}</Button>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
href="https://play.google.com/store/apps/details?id=fit.crab"
|
||||||
|
icon={<svg viewBox="0 0 24 24"><path fill="currentColor" d="M3,20.5V3.5C3,2.91 3.34,2.39 3.84,2.15L13.69,12L3.84,21.85C3.34,21.6 3,21.09 3,20.5M16.81,15.12L6.05,21.34L14.54,12.85L16.81,15.12M20.16,10.81C20.5,11.08 20.75,11.5 20.75,12C20.75,12.5 20.53,12.9 20.18,13.18L17.89,14.5L15.39,12L17.89,9.5L20.16,10.81M6.05,2.66L16.81,8.88L14.54,11.15L6.05,2.66Z" /></svg>}
|
||||||
|
target="_blank"
|
||||||
|
secondary
|
||||||
|
>{t('home:about.android_app')}</Button>
|
||||||
|
</ButtonArea>
|
||||||
|
<P><Trans i18nKey="home:about.content.p3">Created by <a href="https://bengrant.dev" target="_blank" rel="noreferrer">Ben Grant</a>, Crab Fit is the modern-day solution to your group event planning debates.</Trans></P>
|
||||||
<P><Trans i18nKey="home:about.content.p4">The code for Crab Fit is open source, if you find any issues or want to contribute, you can visit the <a href="https://github.com/GRA0007/crab.fit" target="_blank" rel="noreferrer">repository</a>. By using Crab Fit you agree to the <Link to="/privacy">privacy policy</Link>.</Trans></P>
|
<P><Trans i18nKey="home:about.content.p4">The code for Crab Fit is open source, if you find any issues or want to contribute, you can visit the <a href="https://github.com/GRA0007/crab.fit" target="_blank" rel="noreferrer">repository</a>. By using Crab Fit you agree to the <Link to="/privacy">privacy policy</Link>.</Trans></P>
|
||||||
<P><Trans i18nKey="home:about.content.p5">Consider donating below if it helped you out so it can stay free for everyone. 🦀</Trans></P>
|
<P><Trans i18nKey="home:about.content.p5">Consider donating below if it helped you out so it can stay free for everyone. 🦀</Trans></P>
|
||||||
</StyledMain>
|
</StyledMain>
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,10 @@ export const AboutSection = styled.section`
|
||||||
margin: 30px 0 0;
|
margin: 30px 0 0;
|
||||||
background-color: ${props => props.theme.primaryBackground};
|
background-color: ${props => props.theme.primaryBackground};
|
||||||
padding: 20px 0;
|
padding: 20px 0;
|
||||||
|
|
||||||
|
& a {
|
||||||
|
color: ${props => props.theme.mode === 'light' ? props.theme.primaryDark : props.theme.primaryLight};
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const P = styled.p`
|
export const P = styled.p`
|
||||||
|
|
@ -125,3 +129,12 @@ export const OfflineMessage = styled.div`
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin: 50px 0 20px;
|
margin: 50px 0 20px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const ButtonArea = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 12px;
|
||||||
|
margin: 30px 0;
|
||||||
|
`;
|
||||||
|
|
|
||||||
31
crabfit-frontend/src/utils/index.ts
Normal file
31
crabfit-frontend/src/utils/index.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
export const detect_browser = () => {
|
||||||
|
// Opera 8.0+
|
||||||
|
const isOpera = (!!window.opr && !!opr.addons) || !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;
|
||||||
|
|
||||||
|
// Firefox 1.0+
|
||||||
|
const isFirefox = typeof InstallTrigger !== 'undefined';
|
||||||
|
|
||||||
|
// Safari 3.0+ "[object HTMLElementConstructor]"
|
||||||
|
const isSafari = /constructor/i.test(window.HTMLElement) || (function (p) { return p.toString() === "[object SafariRemoteNotification]"; })(!window['safari'] || (typeof safari !== 'undefined' && window['safari'].pushNotification));
|
||||||
|
|
||||||
|
// Internet Explorer 6-11
|
||||||
|
const isIE = /*@cc_on!@*/false || !!document.documentMode;
|
||||||
|
|
||||||
|
// Edge 20+
|
||||||
|
const isEdge = !isIE && !!window.StyleMedia;
|
||||||
|
|
||||||
|
// Chrome 1 - 79
|
||||||
|
const isChrome = !!window.chrome;
|
||||||
|
|
||||||
|
// Edge (based on chromium) detection
|
||||||
|
// eslint-disable-next-line
|
||||||
|
const isEdgeChromium = isChrome && (navigator.userAgent.indexOf("Edg") != -1);
|
||||||
|
|
||||||
|
if (isEdgeChromium) return 'edge_chromium';
|
||||||
|
if (isChrome) return 'chrome';
|
||||||
|
if (isEdge) return 'edge';
|
||||||
|
if (isIE) return 'ie';
|
||||||
|
if (isSafari) return 'safari';
|
||||||
|
if (isFirefox) return 'firefox';
|
||||||
|
if (isOpera) return 'opera';
|
||||||
|
};
|
||||||
|
|
@ -9232,10 +9232,10 @@ react-error-overlay@^6.0.9:
|
||||||
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a"
|
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a"
|
||||||
integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==
|
integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==
|
||||||
|
|
||||||
react-hook-form@^6.15.4:
|
react-hook-form@^7.8.1:
|
||||||
version "6.15.4"
|
version "7.8.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-6.15.4.tgz#328003e1ccc096cd158899ffe7e3b33735a9b024"
|
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.8.1.tgz#291609c50778bf8bc94e5d4c586e9ee5c6dd512f"
|
||||||
integrity sha512-K+Sw33DtTMengs8OdqFJI3glzNl1wBzSefD/ksQw/hJf9CnOHQAU6qy82eOrh0IRNt2G53sjr7qnnw1JDjvx1w==
|
integrity sha512-pwWxd4UfwsKVh/W2YE2Hnu7KBY4KTCZq3QO1PMISzP31AZ5Kmv6ji085QVIHVWVSLT4oX6IhmZ2bWJ2oBIwobQ==
|
||||||
|
|
||||||
react-i18next@^11.8.15:
|
react-i18next@^11.8.15:
|
||||||
version "11.8.15"
|
version "11.8.15"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue