Merge pull request #25 from GRA0007/dev

Update styles
This commit is contained in:
Benjamin Grant 2021-06-06 21:24:26 +10:00 committed by GitHub
commit 866e44f11e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
45 changed files with 307 additions and 581 deletions

View file

@ -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",

View file

@ -1,52 +0,0 @@
{
"name": "Privacy Policy",
"p1": "This SERVICE is provided by Benjamin Grant at no cost and is intended for use as is.",
"p2": "This page is used to inform visitors regarding the policies of the collection, use, and disclosure of Personal Information if using the Service.",
"p3": "If you choose to use the Service, then you agree to the collection and use of information in relation to this policy. The Personal Information that is collected is used for providing and improving the Service. Your information will not be used or shared with anyone except as described in this Privacy Policy.",
"h1": "Information Collection and Use",
"p4": "The Service uses third party services that may collect information used to identify you.",
"p5": "Links to privacy policies of the third party service providers used by the Service:",
"link": "Google Play Services",
"h2": "Log Data",
"p6": "When you use the Service, in the case of an error, data and information is collected to improve the Service, which may include your IP address, device name, operating system version, app configuration and the time and date of the error.",
"h3": "Cookies",
"p7": "Cookies are files with a small amount of data that are commonly used as anonymous unique identifiers. These are sent to your browser from the websites that you visit and are stored on your device's internal memory.",
"p8": "Cookies are used by Google Analytics to track you across the web and provide anonymous statistics to improve the Service.",
"h4": "Service Providers",
"p9": "Third-party companies may be employed for the following reasons:",
"l1": "To facilitate the Service",
"l2": "To provide the Service on our behalf",
"l3": "To perform Service-related services",
"l4": "To assist in analyzing how the Service is used",
"p10": "To perform these tasks, the third parties may have access to your Personal Information, but are obligated not to disclose or use this information for any purpose except the above.",
"h5": "Security",
"p11": "Personal Information that is shared via the Service is protected, however remember that no method of transmission over the internet, or method of electronic storage is 100% secure and reliable, so take care when sharing Personal Information.",
"h6": "Links to Other Sites",
"p12": "The Service may contain links to other sites. If you click on a third-party link, you will be directed to that site. Note that these external sites are not operated by the Service. Therefore, you are advised to review the Privacy Policy of these websites.",
"h7": "Children's Privacy",
"p13": "The Service does not address anyone under the age of 13. Personally identifiable information is not knowingly collected from children under 13. If discovered that a child under 13 has provided the Service with personal information, such information will be immediately deleted from the servers. If you are a parent or guardian and you are aware that your child has provided the Service with personal information, please <1>contact us</1> so that this information can be removed.",
"h8": "Changes to This Privacy Policy",
"p14": "This Privacy Policy may be updated from time to time. Thus, you are advised to review this page periodically for any changes.",
"p15": "This policy is effective as of 2021-04-20",
"h9": "Contact Us",
"p16": "If you have any questions or suggestions about the Privacy Policy, do not hesitate to contact us at <1>benjamin.grantGRA0007+crabfit@gmail.com</1>."
}

View file

@ -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"
} }
} }

View file

@ -48,5 +48,5 @@
"h9": "Contact Us", "h9": "Contact Us",
"p16": "If you have any questions or suggestions about the Privacy Policy, do not hesitate to contact us at <1>benjamin.grantGRA0007+crabfit@gmail.com</1>." "p16": "If you have any questions or suggestions about the Privacy Policy, do not hesitate to contact us at <1>crabfit@bengrant.dev</1>."
} }

View file

@ -1,52 +0,0 @@
{
"name": "Privacy Policy",
"p1": "This SERVICE is provided by Benjamin Grant at no cost and is intended for use as is.",
"p2": "This page is used to inform visitors regarding the policies of the collection, use, and disclosure of Personal Information if using the Service.",
"p3": "If you choose to use the Service, then you agree to the collection and use of information in relation to this policy. The Personal Information that is collected is used for providing and improving the Service. Your information will not be used or shared with anyone except as described in this Privacy Policy.",
"h1": "Information Collection and Use",
"p4": "The Service uses third party services that may collect information used to identify you.",
"p5": "Links to privacy policies of the third party service providers used by the Service:",
"link": "Google Play Services",
"h2": "Log Data",
"p6": "When you use the Service, in the case of an error, data and information is collected to improve the Service, which may include your IP address, device name, operating system version, app configuration and the time and date of the error.",
"h3": "Cookies",
"p7": "Cookies are files with a small amount of data that are commonly used as anonymous unique identifiers. These are sent to your browser from the websites that you visit and are stored on your device's internal memory.",
"p8": "Cookies are used by Google Analytics to track you across the web and provide anonymous statistics to improve the Service.",
"h4": "Service Providers",
"p9": "Third-party companies may be employed for the following reasons:",
"l1": "To facilitate the Service",
"l2": "To provide the Service on our behalf",
"l3": "To perform Service-related services",
"l4": "To assist in analyzing how the Service is used",
"p10": "To perform these tasks, the third parties may have access to your Personal Information, but are obligated not to disclose or use this information for any purpose except the above.",
"h5": "Security",
"p11": "Personal Information that is shared via the Service is protected, however remember that no method of transmission over the internet, or method of electronic storage is 100% secure and reliable, so take care when sharing Personal Information.",
"h6": "Links to Other Sites",
"p12": "The Service may contain links to other sites. If you click on a third-party link, you will be directed to that site. Note that these external sites are not operated by the Service. Therefore, you are advised to review the Privacy Policy of these websites.",
"h7": "Children's Privacy",
"p13": "The Service does not address anyone under the age of 13. Personally identifiable information is not knowingly collected from children under 13. If discovered that a child under 13 has provided the Service with personal information, such information will be immediately deleted from the servers. If you are a parent or guardian and you are aware that your child has provided the Service with personal information, please <1>contact us</1> so that this information can be removed.",
"h8": "Changes to This Privacy Policy",
"p14": "This Privacy Policy may be updated from time to time. Thus, you are advised to review this page periodically for any changes.",
"p15": "This policy is effective as of 2021-04-20",
"h9": "Contact Us",
"p16": "If you have any questions or suggestions about the Privacy Policy, do not hesitate to contact us at <1>benjamin.grantGRA0007+crabfit@gmail.com</1>."
}

View file

@ -1,52 +0,0 @@
{
"name": "Privacy Policy",
"p1": "This SERVICE is provided by Benjamin Grant at no cost and is intended for use as is.",
"p2": "This page is used to inform visitors regarding the policies of the collection, use, and disclosure of Personal Information if using the Service.",
"p3": "If you choose to use the Service, then you agree to the collection and use of information in relation to this policy. The Personal Information that is collected is used for providing and improving the Service. Your information will not be used or shared with anyone except as described in this Privacy Policy.",
"h1": "Information Collection and Use",
"p4": "The Service uses third party services that may collect information used to identify you.",
"p5": "Links to privacy policies of the third party service providers used by the Service:",
"link": "Google Play Services",
"h2": "Log Data",
"p6": "When you use the Service, in the case of an error, data and information is collected to improve the Service, which may include your IP address, device name, operating system version, app configuration and the time and date of the error.",
"h3": "Cookies",
"p7": "Cookies are files with a small amount of data that are commonly used as anonymous unique identifiers. These are sent to your browser from the websites that you visit and are stored on your device's internal memory.",
"p8": "Cookies are used by Google Analytics to track you across the web and provide anonymous statistics to improve the Service.",
"h4": "Service Providers",
"p9": "Third-party companies may be employed for the following reasons:",
"l1": "To facilitate the Service",
"l2": "To provide the Service on our behalf",
"l3": "To perform Service-related services",
"l4": "To assist in analyzing how the Service is used",
"p10": "To perform these tasks, the third parties may have access to your Personal Information, but are obligated not to disclose or use this information for any purpose except the above.",
"h5": "Security",
"p11": "Personal Information that is shared via the Service is protected, however remember that no method of transmission over the internet, or method of electronic storage is 100% secure and reliable, so take care when sharing Personal Information.",
"h6": "Links to Other Sites",
"p12": "The Service may contain links to other sites. If you click on a third-party link, you will be directed to that site. Note that these external sites are not operated by the Service. Therefore, you are advised to review the Privacy Policy of these websites.",
"h7": "Children's Privacy",
"p13": "The Service does not address anyone under the age of 13. Personally identifiable information is not knowingly collected from children under 13. If discovered that a child under 13 has provided the Service with personal information, such information will be immediately deleted from the servers. If you are a parent or guardian and you are aware that your child has provided the Service with personal information, please <1>contact us</1> so that this information can be removed.",
"h8": "Changes to This Privacy Policy",
"p14": "This Privacy Policy may be updated from time to time. Thus, you are advised to review this page periodically for any changes.",
"p15": "This policy is effective as of 2021-04-20",
"h9": "Contact Us",
"p16": "If you have any questions or suggestions about the Privacy Policy, do not hesitate to contact us at <1>benjamin.grantGRA0007+crabfit@gmail.com</1>."
}

View file

@ -1,52 +0,0 @@
{
"name": "Privacy Policy",
"p1": "This SERVICE is provided by Benjamin Grant at no cost and is intended for use as is.",
"p2": "This page is used to inform visitors regarding the policies of the collection, use, and disclosure of Personal Information if using the Service.",
"p3": "If you choose to use the Service, then you agree to the collection and use of information in relation to this policy. The Personal Information that is collected is used for providing and improving the Service. Your information will not be used or shared with anyone except as described in this Privacy Policy.",
"h1": "Information Collection and Use",
"p4": "The Service uses third party services that may collect information used to identify you.",
"p5": "Links to privacy policies of the third party service providers used by the Service:",
"link": "Google Play Services",
"h2": "Log Data",
"p6": "When you use the Service, in the case of an error, data and information is collected to improve the Service, which may include your IP address, device name, operating system version, app configuration and the time and date of the error.",
"h3": "Cookies",
"p7": "Cookies are files with a small amount of data that are commonly used as anonymous unique identifiers. These are sent to your browser from the websites that you visit and are stored on your device's internal memory.",
"p8": "Cookies are used by Google Analytics to track you across the web and provide anonymous statistics to improve the Service.",
"h4": "Service Providers",
"p9": "Third-party companies may be employed for the following reasons:",
"l1": "To facilitate the Service",
"l2": "To provide the Service on our behalf",
"l3": "To perform Service-related services",
"l4": "To assist in analyzing how the Service is used",
"p10": "To perform these tasks, the third parties may have access to your Personal Information, but are obligated not to disclose or use this information for any purpose except the above.",
"h5": "Security",
"p11": "Personal Information that is shared via the Service is protected, however remember that no method of transmission over the internet, or method of electronic storage is 100% secure and reliable, so take care when sharing Personal Information.",
"h6": "Links to Other Sites",
"p12": "The Service may contain links to other sites. If you click on a third-party link, you will be directed to that site. Note that these external sites are not operated by the Service. Therefore, you are advised to review the Privacy Policy of these websites.",
"h7": "Children's Privacy",
"p13": "The Service does not address anyone under the age of 13. Personally identifiable information is not knowingly collected from children under 13. If discovered that a child under 13 has provided the Service with personal information, such information will be immediately deleted from the servers. If you are a parent or guardian and you are aware that your child has provided the Service with personal information, please <1>contact us</1> so that this information can be removed.",
"h8": "Changes to This Privacy Policy",
"p14": "This Privacy Policy may be updated from time to time. Thus, you are advised to review this page periodically for any changes.",
"p15": "This policy is effective as of 2021-04-20",
"h9": "Contact Us",
"p16": "If you have any questions or suggestions about the Privacy Policy, do not hesitate to contact us at <1>benjamin.grantGRA0007+crabfit@gmail.com</1>."
}

View file

@ -1,52 +0,0 @@
{
"name": "Privacy Policy",
"p1": "This SERVICE is provided by Benjamin Grant at no cost and is intended for use as is.",
"p2": "This page is used to inform visitors regarding the policies of the collection, use, and disclosure of Personal Information if using the Service.",
"p3": "If you choose to use the Service, then you agree to the collection and use of information in relation to this policy. The Personal Information that is collected is used for providing and improving the Service. Your information will not be used or shared with anyone except as described in this Privacy Policy.",
"h1": "Information Collection and Use",
"p4": "The Service uses third party services that may collect information used to identify you.",
"p5": "Links to privacy policies of the third party service providers used by the Service:",
"link": "Google Play Services",
"h2": "Log Data",
"p6": "When you use the Service, in the case of an error, data and information is collected to improve the Service, which may include your IP address, device name, operating system version, app configuration and the time and date of the error.",
"h3": "Cookies",
"p7": "Cookies are files with a small amount of data that are commonly used as anonymous unique identifiers. These are sent to your browser from the websites that you visit and are stored on your device's internal memory.",
"p8": "Cookies are used by Google Analytics to track you across the web and provide anonymous statistics to improve the Service.",
"h4": "Service Providers",
"p9": "Third-party companies may be employed for the following reasons:",
"l1": "To facilitate the Service",
"l2": "To provide the Service on our behalf",
"l3": "To perform Service-related services",
"l4": "To assist in analyzing how the Service is used",
"p10": "To perform these tasks, the third parties may have access to your Personal Information, but are obligated not to disclose or use this information for any purpose except the above.",
"h5": "Security",
"p11": "Personal Information that is shared via the Service is protected, however remember that no method of transmission over the internet, or method of electronic storage is 100% secure and reliable, so take care when sharing Personal Information.",
"h6": "Links to Other Sites",
"p12": "The Service may contain links to other sites. If you click on a third-party link, you will be directed to that site. Note that these external sites are not operated by the Service. Therefore, you are advised to review the Privacy Policy of these websites.",
"h7": "Children's Privacy",
"p13": "The Service does not address anyone under the age of 13. Personally identifiable information is not knowingly collected from children under 13. If discovered that a child under 13 has provided the Service with personal information, such information will be immediately deleted from the servers. If you are a parent or guardian and you are aware that your child has provided the Service with personal information, please <1>contact us</1> so that this information can be removed.",
"h8": "Changes to This Privacy Policy",
"p14": "This Privacy Policy may be updated from time to time. Thus, you are advised to review this page periodically for any changes.",
"p15": "This policy is effective as of 2021-04-20",
"h9": "Contact Us",
"p16": "If you have any questions or suggestions about the Privacy Policy, do not hesitate to contact us at <1>benjamin.grantGRA0007+crabfit@gmail.com</1>."
}

View file

@ -1,52 +0,0 @@
{
"name": "Privacy Policy",
"p1": "This SERVICE is provided by Benjamin Grant at no cost and is intended for use as is.",
"p2": "This page is used to inform visitors regarding the policies of the collection, use, and disclosure of Personal Information if using the Service.",
"p3": "If you choose to use the Service, then you agree to the collection and use of information in relation to this policy. The Personal Information that is collected is used for providing and improving the Service. Your information will not be used or shared with anyone except as described in this Privacy Policy.",
"h1": "Information Collection and Use",
"p4": "The Service uses third party services that may collect information used to identify you.",
"p5": "Links to privacy policies of the third party service providers used by the Service:",
"link": "Google Play Services",
"h2": "Log Data",
"p6": "When you use the Service, in the case of an error, data and information is collected to improve the Service, which may include your IP address, device name, operating system version, app configuration and the time and date of the error.",
"h3": "Cookies",
"p7": "Cookies are files with a small amount of data that are commonly used as anonymous unique identifiers. These are sent to your browser from the websites that you visit and are stored on your device's internal memory.",
"p8": "Cookies are used by Google Analytics to track you across the web and provide anonymous statistics to improve the Service.",
"h4": "Service Providers",
"p9": "Third-party companies may be employed for the following reasons:",
"l1": "To facilitate the Service",
"l2": "To provide the Service on our behalf",
"l3": "To perform Service-related services",
"l4": "To assist in analyzing how the Service is used",
"p10": "To perform these tasks, the third parties may have access to your Personal Information, but are obligated not to disclose or use this information for any purpose except the above.",
"h5": "Security",
"p11": "Personal Information that is shared via the Service is protected, however remember that no method of transmission over the internet, or method of electronic storage is 100% secure and reliable, so take care when sharing Personal Information.",
"h6": "Links to Other Sites",
"p12": "The Service may contain links to other sites. If you click on a third-party link, you will be directed to that site. Note that these external sites are not operated by the Service. Therefore, you are advised to review the Privacy Policy of these websites.",
"h7": "Children's Privacy",
"p13": "The Service does not address anyone under the age of 13. Personally identifiable information is not knowingly collected from children under 13. If discovered that a child under 13 has provided the Service with personal information, such information will be immediately deleted from the servers. If you are a parent or guardian and you are aware that your child has provided the Service with personal information, please <1>contact us</1> so that this information can be removed.",
"h8": "Changes to This Privacy Policy",
"p14": "This Privacy Policy may be updated from time to time. Thus, you are advised to review this page periodically for any changes.",
"p15": "This policy is effective as of 2021-04-20",
"h9": "Contact Us",
"p16": "If you have any questions or suggestions about the Privacy Policy, do not hesitate to contact us at <1>benjamin.grantGRA0007+crabfit@gmail.com</1>."
}

View file

@ -1,3 +0,0 @@
{
"name": "개인 정보 보호 정책에"
}

View file

@ -1,52 +0,0 @@
{
"name": "Privacy Policy",
"p1": "This SERVICE is provided by Benjamin Grant at no cost and is intended for use as is.",
"p2": "This page is used to inform visitors regarding the policies of the collection, use, and disclosure of Personal Information if using the Service.",
"p3": "If you choose to use the Service, then you agree to the collection and use of information in relation to this policy. The Personal Information that is collected is used for providing and improving the Service. Your information will not be used or shared with anyone except as described in this Privacy Policy.",
"h1": "Information Collection and Use",
"p4": "The Service uses third party services that may collect information used to identify you.",
"p5": "Links to privacy policies of the third party service providers used by the Service:",
"link": "Google Play Services",
"h2": "Log Data",
"p6": "When you use the Service, in the case of an error, data and information is collected to improve the Service, which may include your IP address, device name, operating system version, app configuration and the time and date of the error.",
"h3": "Cookies",
"p7": "Cookies are files with a small amount of data that are commonly used as anonymous unique identifiers. These are sent to your browser from the websites that you visit and are stored on your device's internal memory.",
"p8": "Cookies are used by Google Analytics to track you across the web and provide anonymous statistics to improve the Service.",
"h4": "Service Providers",
"p9": "Third-party companies may be employed for the following reasons:",
"l1": "To facilitate the Service",
"l2": "To provide the Service on our behalf",
"l3": "To perform Service-related services",
"l4": "To assist in analyzing how the Service is used",
"p10": "To perform these tasks, the third parties may have access to your Personal Information, but are obligated not to disclose or use this information for any purpose except the above.",
"h5": "Security",
"p11": "Personal Information that is shared via the Service is protected, however remember that no method of transmission over the internet, or method of electronic storage is 100% secure and reliable, so take care when sharing Personal Information.",
"h6": "Links to Other Sites",
"p12": "The Service may contain links to other sites. If you click on a third-party link, you will be directed to that site. Note that these external sites are not operated by the Service. Therefore, you are advised to review the Privacy Policy of these websites.",
"h7": "Children's Privacy",
"p13": "The Service does not address anyone under the age of 13. Personally identifiable information is not knowingly collected from children under 13. If discovered that a child under 13 has provided the Service with personal information, such information will be immediately deleted from the servers. If you are a parent or guardian and you are aware that your child has provided the Service with personal information, please <1>contact us</1> so that this information can be removed.",
"h8": "Changes to This Privacy Policy",
"p14": "This Privacy Policy may be updated from time to time. Thus, you are advised to review this page periodically for any changes.",
"p15": "This policy is effective as of 2021-04-20",
"h9": "Contact Us",
"p16": "If you have any questions or suggestions about the Privacy Policy, do not hesitate to contact us at <1>benjamin.grantGRA0007+crabfit@gmail.com</1>."
}

View file

@ -97,13 +97,13 @@ const App = () => {
borderRadius: 100, borderRadius: 100,
border: `4px solid ${theme.primaryBackground}`, border: `4px solid ${theme.primaryBackground}`,
width: 12, width: 12,
background: `${theme.primaryLight}AA`, background: `${theme.mode === 'light' ? theme.primaryLight : theme.primaryDark}AA`,
}, },
'*::-webkit-scrollbar-thumb:hover': { '*::-webkit-scrollbar-thumb:hover': {
background: `${theme.primaryLight}CC`, background: `${theme.mode === 'light' ? theme.primaryLight : theme.primaryDark}CC`,
}, },
'*::-webkit-scrollbar-thumb:active': { '*::-webkit-scrollbar-thumb:active': {
background: `${theme.primaryLight}`, background: `${theme.mode === 'light' ? theme.primaryLight : theme.primaryDark}`,
}, },
})} })}
/> />

View file

@ -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;

View file

@ -1,16 +1,16 @@
import { Wrapper, Top, Bottom } from './buttonStyle'; import { Pressable } from './buttonStyle';
const Button = ({ const Button = ({ href, type = 'button', icon, children, ...props }) => (
buttonHeight, <Pressable
buttonWidth, type={href ? undefined : type}
primaryColor, role={href && 'button'}
secondaryColor, as={href ? 'a' : 'button'}
...props href={href}
}) => ( {...props}
<Wrapper buttonHeight={buttonHeight} buttonWidth={buttonWidth}> >
<Top primaryColor={primaryColor} secondaryColor={secondaryColor} {...props} /> {icon}
<Bottom secondaryColor={secondaryColor} /> {children}
</Wrapper> </Pressable>
); );
export default Button; export default Button;

View file

@ -1,41 +1,65 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
export const Wrapper = styled.div` export const Pressable = styled.button`
display: inline-block; position: relative;
position: relative; display: inline-flex;
align-items: center;
justify-content: center;
text-align: center;
cursor: pointer;
border: 0;
text-decoration: none;
font: inherit;
box-sizing: border-box;
background: ${props => props.primaryColor || props.theme.primary};
color: ${props => props.primaryColor ? '#FFF' : props.theme.background};
font-weight: 600;
transition: transform 150ms cubic-bezier(0, 0, 0.58, 1);
border-radius: 3px;
padding: ${props => props.small ? '.4em 1.3em' : '.6em 1.5em'};
transform-style: preserve-3d;
margin-bottom: 5px;
--btn-height: ${props => props.buttonHeight || '40px'}; & svg, & img {
--btn-width: ${props => props.buttonWidth || '100px'}; height: 1.2em;
width: 1.2em;
margin-right: .5em;
}
height: var(--btn-height); ${props => props.size && `
width: var(--btn-width); padding: 0;
`; height: ${props.size};
width: ${props.size};
`}
export const Top = styled.button` &::before {
border: 0; content: '';
cursor: pointer; position: absolute;
font: inherit; height: 100%;
box-sizing: border-box; width: 100%;
background: ${props => props.primaryColor || props.theme.primary}; top: 0;
color: #FFF; left: 0;
font-weight: 600; background: ${props => props.secondaryColor || props.theme.primaryDark};
text-shadow: 0 -1.5px .5px ${props => props.secondaryColor || props.theme.primaryDark}; border-radius: inherit;
padding: 0; transform: translate3d(0, 5px, -1em);
border-radius: 3px; transition: transform 150ms cubic-bezier(0, 0, 0.58, 1), box-shadow 150ms cubic-bezier(0, 0, 0.58, 1);
height: var(--btn-height); }
width: var(--btn-width);
position: absolute;
top: -4px;
left: 0;
user-select: none;
transition: top .15s;
&:active { &:hover, &:focus {
top: 0; transform: translate(0, 1px);
} &::before {
transform: translate3d(0, 4px, -1em);
}
}
${props => props.isLoading && ` &:active {
text-shadow: none; transform: translate(0, 5px);
&::before {
transform: translate3d(0, 0, -1em);
}
}
${props => props.isLoading && `
color: transparent; color: transparent;
cursor: wait; cursor: wait;
@ -83,12 +107,18 @@ export const Top = styled.button`
} }
} }
`} `}
`;
export const Bottom = styled.div` ${props => props.secondary && `
box-sizing: border-box; background: transparent;
background: ${props => props.secondaryColor || props.theme.primaryDark}; border: 1px solid ${props.primaryColor || props.theme.mode === 'light' ? props.theme.primaryDark : props.theme.primaryLight};
border-radius: 3px; color: ${props.primaryColor || props.theme.mode === 'light' ? props.theme.primaryDark : props.theme.primaryLight};
height: var(--btn-height); margin-bottom: 0;
width: var(--btn-width);
&::before {
content: none;
}
&:hover, &:active, &:focus {
transform: none;
}
`}
`; `;

View file

@ -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}
/> />
@ -122,10 +124,8 @@ const CalendarField = ({
<> <>
<CalendarHeader> <CalendarHeader>
<Button <Button
buttonHeight="30px" size="30px"
buttonWidth="30px"
title={t('form.dates.tooltips.previous')} title={t('form.dates.tooltips.previous')}
type="button"
onClick={() => { onClick={() => {
if (month-1 < 0) { if (month-1 < 0) {
setYear(year-1); setYear(year-1);
@ -137,10 +137,8 @@ const CalendarField = ({
>&lt;</Button> >&lt;</Button>
<span>{dayjs.months()[month]} {year}</span> <span>{dayjs.months()[month]} {year}</span>
<Button <Button
buttonHeight="30px" size="30px"
buttonWidth="30px"
title={t('form.dates.tooltips.next')} title={t('form.dates.tooltips.next')}
type="button"
onClick={() => { onClick={() => {
if (month+1 > 11) { if (month+1 > 11) {
setYear(year+1); setYear(year+1);
@ -215,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}
@ -261,6 +259,6 @@ const CalendarField = ({
)} )}
</Wrapper> </Wrapper>
); );
}; });
export default CalendarField; export default CalendarField;

View file

@ -78,7 +78,7 @@ export const Date = styled.button`
} }
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};
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@ -87,20 +87,18 @@ export const Date = styled.button`
touch-action: none; touch-action: none;
${props => props.otherMonth && ` ${props => props.otherMonth && `
color: ${props.theme.primaryLight}; color: ${props.theme.mode === 'light' ? props.theme.primaryLight : props.theme.primaryDark};
`} `}
${props => props.isToday && ` ${props => props.isToday && `
font-weight: 900; font-weight: 900;
color: ${props.theme.primaryDark}; color: ${props.theme.mode === 'light' ? props.theme.primaryDark : props.theme.primaryLight};
`} `}
${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: ${props.theme.primary}; background-color: ${props.theme.primary};
border-color: ${props.theme.primary};
`} `}
${props => props.mode === 'remove' && props.selecting && ` ${props => props.mode === 'remove' && props.selecting && `
background-color: ${props.theme.primaryBackground}; background-color: ${props.theme.primaryBackground};
border: 1px solid ${props.theme.primaryLight};
color: ${props.isToday ? props.theme.primaryDark : (props.otherMonth ? props.theme.primaryLight : 'inherit')}; color: ${props.isToday ? props.theme.primaryDark : (props.otherMonth ? props.theme.primaryLight : 'inherit')};
`} `}
`; `;

View file

@ -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);
@ -97,7 +96,9 @@ const Donate = () => {
return ( return (
<Wrapper> <Wrapper>
<a <Button
small
title={t('donate.title')}
onClick={event => { onClick={event => {
if (store.TWA) { if (store.TWA) {
gtag('event', 'donate', { 'event_category': 'donate' }); gtag('event', 'donate', { 'event_category': 'donate' });
@ -115,16 +116,8 @@ const Donate = () => {
href="https://www.paypal.com/donate?business=N89X6YXRT5HKW&item_name=Crab+Fit+Donation&currency_code=AUD&amount=5" href="https://www.paypal.com/donate?business=N89X6YXRT5HKW&item_name=Crab+Fit+Donation&currency_code=AUD&amount=5"
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
ref={buttonRef} style={{ whiteSpace: 'nowrap' }}
> >{t('donate.button')}</Button>
<Button
buttonHeight="30px"
buttonWidth={`${Math.max(t('donate.button').length*10, 90)}px`}
type="button"
tabIndex="-1"
title={t('donate.title')}
>{t('donate.button')}</Button>
</a>
<Options <Options
isOpen={isOpen} isOpen={isOpen}

View file

@ -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>

View file

@ -4,7 +4,7 @@ export const Wrapper = styled.div`
border-radius: 3px; border-radius: 3px;
background-color: ${props => props.theme.error}; background-color: ${props => props.theme.error};
color: #FFFFFF; color: #FFFFFF;
padding: 12px 16px; padding: 0 16px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
@ -13,14 +13,15 @@ export const Wrapper = styled.div`
max-height: 0; max-height: 0;
margin: 0; margin: 0;
visibility: hidden; visibility: hidden;
transition: margin .2s, max-height .2s; transition: margin .2s, padding .2s, max-height .2s;
${props => props.open && ` ${props => props.open && `
opacity: 1; opacity: 1;
visibility: visible; visibility: visible;
margin: 20px 0; margin: 20px 0;
padding: 12px 16px;
max-height: 60px; max-height: 60px;
transition: opacity .15s .2s, max-height .2s, margin .2s, visibility .2s; transition: opacity .15s .2s, max-height .2s, margin .2s, padding .2s, visibility .2s;
`} `}
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {

View file

@ -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,
@ -100,13 +99,11 @@ const GoogleCalendar = ({ timeZone, timeMin, timeMax, onImport }) => {
<Button <Button
onClick={() => signIn()} onClick={() => signIn()}
isLoading={signedIn === undefined} isLoading={signedIn === undefined}
buttonWidth={`${Math.max(t('event:you.google_cal.login').length*10, 270)}px`}
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>
) : ( ) : (
@ -156,8 +153,7 @@ const GoogleCalendar = ({ timeZone, timeMin, timeMax, onImport }) => {
<> <>
<Info>{t('event:you.google_cal.info')}</Info> <Info>{t('event:you.google_cal.info')}</Info>
<Button <Button
buttonWidth="170px" small
buttonHeight="35px"
isLoading={freeBusyLoading} isLoading={freeBusyLoading}
disabled={freeBusyLoading} disabled={freeBusyLoading}
onClick={() => importAvailability()} onClick={() => importAvailability()}

View file

@ -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 {

View file

@ -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%;

View file

@ -58,7 +58,7 @@ export const Title = styled.span`
export const Tagline = styled.span` export const Tagline = styled.span`
text-decoration: underline; text-decoration: underline;
font-size: 14px; font-size: 14px;
padding-top: 6px; padding-top: 2px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;

View file

@ -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,
@ -167,13 +166,11 @@ const OutlookCalendar = ({ timeZone, timeMin, timeMax, onImport }) => {
<Button <Button
onClick={() => signIn()} onClick={() => signIn()}
isLoading={client === undefined} isLoading={client === undefined}
buttonWidth={`${Math.max(t('event:you.outlook_cal').length*10, 270)}px`}
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>
) : ( ) : (
@ -223,8 +220,7 @@ const OutlookCalendar = ({ timeZone, timeMin, timeMax, onImport }) => {
<> <>
<Info>{t('event:you.google_cal.info')}</Info> <Info>{t('event:you.google_cal.info')}</Info>
<Button <Button
buttonWidth="170px" small
buttonHeight="35px"
isLoading={freeBusyLoading} isLoading={freeBusyLoading}
disabled={freeBusyLoading} disabled={freeBusyLoading}
onClick={() => importAvailability()} onClick={() => importAvailability()}

View file

@ -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;
@ -12,7 +11,7 @@ export const Recent = styled.a`
& .name { & .name {
font-weight: 700; font-weight: 700;
font-size: 1.1em; font-size: 1.1em;
color: ${props => props.theme.primaryDark}; color: ${props => props.theme.mode === 'light' ? props.theme.primaryDark : props.theme.primaryLight};
flex: 1; flex: 1;
display: block; display: block;
} }
@ -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 {

View file

@ -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;

View file

@ -38,8 +38,8 @@ export const StyledSelect = styled.select`
background: ${props => props.theme.primaryBackground}; background: ${props => props.theme.primaryBackground};
color: inherit; color: inherit;
padding: 10px 14px; padding: 10px 14px;
border: 1px solid ${props => props.theme.primaryLight}; border: 1px solid ${props => props.theme.primary};
box-shadow: inset 0 0 0 0 ${props => props.theme.primaryLight}; box-shadow: inset 0 0 0 0 ${props => props.theme.primary};
border-radius: 3px; border-radius: 3px;
outline: none; outline: none;
transition: border-color .15s, box-shadow .15s; transition: border-color .15s, box-shadow .15s;
@ -50,8 +50,8 @@ export const StyledSelect = styled.select`
background-size: 1em; background-size: 1em;
&:focus { &:focus {
border: 1px solid ${props => props.theme.primary}; border: 1px solid ${props => props.theme.mode === 'light' ? props.theme.primaryDark : props.theme.primaryLight};
box-shadow: inset 0 -3px 0 0 ${props => props.theme.primary}; box-shadow: inset 0 -3px 0 0 ${props => props.theme.mode === 'light' ? props.theme.primaryDark : props.theme.primaryLight};
} }
${props => props.small && ` ${props => props.small && `

View file

@ -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;

View file

@ -32,15 +32,15 @@ export const StyledInput = styled.input`
background: ${props => props.theme.primaryBackground}; background: ${props => props.theme.primaryBackground};
color: inherit; color: inherit;
padding: 10px 14px; padding: 10px 14px;
border: 1px solid ${props => props.theme.primaryLight}; border: 1px solid ${props => props.theme.primary};
box-shadow: inset 0 0 0 0 ${props => props.theme.primaryLight}; box-shadow: inset 0 0 0 0 ${props => props.theme.primary};
border-radius: 3px; border-radius: 3px;
font-size: 18px; font-size: 18px;
outline: none; outline: none;
transition: border-color .15s, box-shadow .15s; transition: border-color .15s, box-shadow .15s;
&:focus { &:focus {
border: 1px solid ${props => props.theme.primary}; border: 1px solid ${props => props.theme.mode === 'light' ? props.theme.primaryDark : props.theme.primaryLight};
box-shadow: inset 0 -3px 0 0 ${props => props.theme.primary}; box-shadow: inset 0 -3px 0 0 ${props => props.theme.mode === 'light' ? props.theme.primaryDark : props.theme.primaryLight};
} }
`; `;

View file

@ -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;

View file

@ -20,7 +20,7 @@ export const StyledSubLabel = styled.label`
export const Range = styled.div` export const Range = styled.div`
user-select: none; user-select: none;
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-radius: 3px; border-radius: 3px;
height: 50px; height: 50px;
position: relative; position: relative;

View file

@ -19,7 +19,7 @@ const ToggleField = ({
...props ...props
}) => ( }) => (
<Wrapper> <Wrapper>
{label && <StyledLabel title={title}>{label}</StyledLabel>} {label && <StyledLabel title={title}>{label} {title !== '' && <svg viewBox="0 0 24 24"><path fill="currentColor" d="M11,9H13V7H11M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M11,17H13V11H11V17Z" /></svg>}</StyledLabel>}
<ToggleContainer> <ToggleContainer>
{Object.entries(options).map(([key, label]) => {Object.entries(options).map(([key, label]) =>

View file

@ -9,9 +9,15 @@ export const ToggleContainer = styled.div`
border: 1px solid ${props => props.theme.primary}; border: 1px solid ${props => props.theme.primary};
border-radius: 3px; border-radius: 3px;
overflow: hidden; overflow: hidden;
--focus-color: ${props => props.theme.primary};
transition: border .15s;
&:focus-within label { &:focus-within {
box-shadow: inset 0 -3px 0 0 var(--focus-color); --focus-color: ${props => props.theme.mode === 'light' ? props.theme.primaryDark : props.theme.primaryLight};
border: 1px solid var(--focus-color);
& label {
box-shadow: inset 0 -3px 0 0 var(--focus-color);
}
} }
& > div:first-of-type label { & > div:first-of-type label {
@ -26,23 +32,31 @@ export const StyledLabel = styled.label`
display: block; display: block;
padding-bottom: 4px; padding-bottom: 4px;
font-size: .9rem; font-size: .9rem;
& svg {
height: 1em;
width: 1em;
vertical-align: middle;
}
`; `;
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};
background-color: ${props => props.theme.primary}; background-color: var(--focus-color);
--focus-color: ${props => props.theme.primaryDark};
} }
`; `;
@ -56,6 +70,5 @@ export const LabelButton = styled.label`
box-sizing: border-box; box-sizing: border-box;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
transition: box-shadow .15s; transition: box-shadow .15s, background-color .15s;
--focus-color: ${props => props.theme.primary};
`; `;

View file

@ -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,
}, },
@ -156,7 +156,7 @@ const Create = ({ offline }) => {
<TitleLarge>CRAB FIT</TitleLarge> <TitleLarge>CRAB FIT</TitleLarge>
</StyledMain> </StyledMain>
{!createdEvent ? ( {createdEvent ? (
<StyledMain> <StyledMain>
<OfflineMessage> <OfflineMessage>
<h2>{t('common:created', { date: createdEvent?.name })}</h2> <h2>{t('common:created', { date: createdEvent?.name })}</h2>
@ -196,42 +196,40 @@ 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')}
/> />
<Error open={!!error} onClose={() => setError(null)}>{error}</Error> <Error open={!!error} onClose={() => setError(null)}>{error}</Error>
<Button type="submit" isLoading={isLoading} disabled={isLoading} buttonWidth="100%">{t('home:form.button')}</Button> <Button type="submit" isLoading={isLoading} disabled={isLoading} style={{ width: '100%' }}>{t('home:form.button')}</Button>
</CreateForm> </CreateForm>
)} )}
</StyledMain> </StyledMain>

View file

@ -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,27 +329,24 @@ 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
type="submit" type="submit"
isLoading={isLoginLoading} isLoading={isLoginLoading}
disabled={isLoginLoading || isLoading} disabled={isLoginLoading || isLoading}
buttonWidth={`${Math.max(t('event:form.button').length*11, 100)}px`}
>{t('event:form.button')}</Button> >{t('event:form.button')}</Button>
</LoginForm> </LoginForm>
<Error open={!!error} onClose={() => setError(null)}>{error}</Error> <Error open={!!error} onClose={() => setError(null)}>{error}</Error>
@ -395,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}

View file

@ -90,7 +90,7 @@ export const ShareInfo = styled.p`
cursor: pointer; cursor: pointer;
&:hover { &:hover {
color: ${props.theme.primaryDark}; color: ${props.theme.mode === 'light' ? props.theme.primaryDark : props.theme.primaryLight};
} }
`} `}
`; `;
@ -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;

View file

@ -84,7 +84,7 @@ const Help = () => {
<AboutSection id="about"> <AboutSection id="about">
<StyledMain> <StyledMain>
<Center><Button buttonWidth="230px" onClick={() => push('/')}>{t('common:cta')}</Button></Center> <Center><Button onClick={() => push('/')}>{t('common:cta')}</Button></Center>
</StyledMain> </StyledMain>
</AboutSection> </AboutSection>

View file

@ -29,7 +29,7 @@ export const FakeCalendar = styled.div`
} }
& .dates span { & .dates span {
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};
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@ -38,15 +38,22 @@ export const FakeCalendar = styled.div`
&.selected { &.selected {
color: #FFF; color: #FFF;
background-color: ${props => props.theme.primary}; background-color: ${props => props.theme.primary};
border-color: ${props => props.theme.primary};
} }
} }
& .dates span:first-of-type {
border-start-start-radius: 3px;
border-end-start-radius: 3px;
}
& .dates span:last-of-type {
border-end-end-radius: 3px;
border-start-end-radius: 3px;
}
`; `;
export const FakeTimeRange = styled.div` export const FakeTimeRange = styled.div`
user-select: none; user-select: none;
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-radius: 3px; border-radius: 3px;
height: 50px; height: 50px;
position: relative; position: relative;

View file

@ -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,39 @@ 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"
rel="noreferrer"
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"
rel="noreferrer"
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>

View file

@ -17,7 +17,7 @@ export const TitleSmall = styled.span`
text-align: center; text-align: center;
font-family: 'Samurai Bob', sans-serif; font-family: 'Samurai Bob', sans-serif;
font-weight: 400; font-weight: 400;
color: ${props => props.theme.primaryDark}; color: ${props => props.theme.mode === 'light' ? props.theme.primaryDark : props.theme.primaryLight};
line-height: 1em; line-height: 1em;
text-transform: uppercase; text-transform: uppercase;
@ -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`
@ -113,7 +117,7 @@ export const Stat = styled.div`
export const StatNumber = styled.span` export const StatNumber = styled.span`
display: block; display: block;
font-weight: 900; font-weight: 900;
color: ${props => props.theme.primaryDark}; color: ${props => props.theme.mode === 'light' ? props.theme.primaryDark : props.theme.primaryLight};
font-size: 2em; font-size: 2em;
`; `;
@ -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;
`;

View file

@ -71,19 +71,19 @@ const Privacy = () => {
<P>{t('privacy:p12')}</P> <P>{t('privacy:p12')}</P>
<h2>{t('privacy:h7')}</h2> <h2>{t('privacy:h7')}</h2>
<P><Trans i18nKey="privacy:p13">The Service does not address anyone under the age of 13. Personally identifiable information is not knowingly collected from children under 13. If discovered that a child under 13 has provided the Service with personal information, such information will be immediately deleted from the servers. If you are a parent or guardian and you are aware that your child has provided the Service with personal information, please <a href="mailto:benjamin.grantGRA0007+crabfit@gmail.com">contact us</a> so that this information can be removed.</Trans></P> <P><Trans i18nKey="privacy:p13">The Service does not address anyone under the age of 13. Personally identifiable information is not knowingly collected from children under 13. If discovered that a child under 13 has provided the Service with personal information, such information will be immediately deleted from the servers. If you are a parent or guardian and you are aware that your child has provided the Service with personal information, please <a href="mailto:crabfit@bengrant.dev">contact us</a> so that this information can be removed.</Trans></P>
<h2>{t('privacy:h8')}</h2> <h2>{t('privacy:h8')}</h2>
<P>{t('privacy:p14')}</P> <P>{t('privacy:p14')}</P>
<P>{t('privacy:p15')}</P> <P>{t('privacy:p15')}</P>
<h2>{t('privacy:h9')}</h2> <h2>{t('privacy:h9')}</h2>
<P><Trans i18nKey="privacy:p16">If you have any questions or suggestions about the Privacy Policy, do not hesitate to contact us at <a href="mailto:benjamin.grantGRA0007+crabfit@gmail.com">benjamin.grantGRA0007+crabfit@gmail.com</a>.</Trans></P> <P><Trans i18nKey="privacy:p16">If you have any questions or suggestions about the Privacy Policy, do not hesitate to contact us at <a href="mailto:crabfit@bengrant.dev">crabfit@bengrant.dev</a>.</Trans></P>
</StyledMain> </StyledMain>
<AboutSection id="about"> <AboutSection id="about">
<StyledMain> <StyledMain>
<Center><Button buttonWidth="230px" onClick={() => push('/')}>{t('common:cta')}</Button></Center> <Center><Button onClick={() => push('/')}>{t('common:cta')}</Button></Center>
</StyledMain> </StyledMain>
</AboutSection> </AboutSection>

View file

@ -15,8 +15,8 @@ const theme = {
background: '#111111', background: '#111111',
text: '#DDDDDD', text: '#DDDDDD',
primary: '#F79E00', primary: '#F79E00',
primaryDark: '#F4BB60', primaryDark: '#CC7313',
primaryLight: '#F48600', primaryLight: '#F4BB60',
primaryBackground: '#30240F', primaryBackground: '#30240F',
error: '#E53935', error: '#E53935',
loading: '#444444', loading: '#444444',

View 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';
};

View file

@ -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"