mirror of
https://github.com/Amperra-Group/xcs.git
synced 2025-08-17 05:42:13 -06:00
Refactor components: update CheckActivationCodeModal, Footer, Nav, and PlatformNav; add Reset Password functionality in Login; implement license files.
This commit is contained in:
parent
77922dcfe3
commit
4b926982cf
21
public/LICENSE.txt
Normal file
21
public/LICENSE.txt
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 Pete Pongpeauk
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
21
public/images/LICENSE.txt
Normal file
21
public/images/LICENSE.txt
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 Pete Pongpeauk
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -116,15 +116,15 @@ export default function CheckActivationCodeModal({
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
|
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
|
<Button onClick={onClose}>Cancel</Button>
|
||||||
<Button
|
<Button
|
||||||
colorScheme={'black'}
|
colorScheme={'black'}
|
||||||
mr={3}
|
ml={3}
|
||||||
isLoading={props.isSubmitting}
|
isLoading={props.isSubmitting}
|
||||||
type={'submit'}
|
type={'submit'}
|
||||||
>
|
>
|
||||||
Submit
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={onClose}>Cancel</Button>
|
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
|
@ -47,8 +47,9 @@ export default function Footer({ type = 'platform' }: { type?: 'public' | 'platf
|
||||||
fontWeight={'bold'}
|
fontWeight={'bold'}
|
||||||
letterSpacing={'tight'}
|
letterSpacing={'tight'}
|
||||||
>
|
>
|
||||||
PT Amperra Sambung Semesta Sentosa
|
PT Amperra Sambung Semesta Sentosa{' '}
|
||||||
</Text>
|
</Text>
|
||||||
|
under the <Link fontWeight={'bold'} as={NextLink} href={'/LICENSE.txt'}>MIT License.</Link>
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex align={'center'} justify={'center'} fontSize={'sm'}>
|
<Flex align={'center'} justify={'center'} fontSize={'sm'}>
|
||||||
|
|
|
@ -122,6 +122,7 @@ export default function Nav({ type }: { type?: string }) {
|
||||||
h={'6rem'}
|
h={'6rem'}
|
||||||
align={'center'}
|
align={'center'}
|
||||||
bg={useColorModeValue('white', 'gray.800')}
|
bg={useColorModeValue('white', 'gray.800')}
|
||||||
|
borderBottom={'1px solid'}
|
||||||
borderColor={useColorModeValue('gray.300', 'gray.700')}
|
borderColor={useColorModeValue('gray.300', 'gray.700')}
|
||||||
zIndex={50}
|
zIndex={50}
|
||||||
>
|
>
|
||||||
|
@ -149,7 +150,7 @@ export default function Nav({ type }: { type?: string }) {
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex
|
||||||
position={'relative'}
|
position={'relative'}
|
||||||
w={'128px'}
|
w={'150px'}
|
||||||
h={'100%'}
|
h={'100%'}
|
||||||
>
|
>
|
||||||
<NextImage
|
<NextImage
|
||||||
|
|
|
@ -368,7 +368,7 @@ export default function PlatformNav({ type, title }: { type?: string; title?: st
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex
|
||||||
position={'relative'}
|
position={'relative'}
|
||||||
w={'128px'}
|
w={'150px'}
|
||||||
h={'100%'}
|
h={'100%'}
|
||||||
>
|
>
|
||||||
<NextImage
|
<NextImage
|
||||||
|
|
0
src/pages/api/v1/gumroad/verify-license.ts
Normal file
0
src/pages/api/v1/gumroad/verify-license.ts
Normal file
|
@ -18,7 +18,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
const db = mongoClient.db(process.env.MONGODB_DB as string);
|
const db = mongoClient.db(process.env.MONGODB_DB as string);
|
||||||
const statistics = db.collection('statistics');
|
const statistics = db.collection('statistics');
|
||||||
|
|
||||||
const globalStatistics = await statistics.findOne({ id: 'global' });
|
const globalStatistics = await statistics.findOne({ id: 'global ' });
|
||||||
if (!globalStatistics) {
|
if (!globalStatistics) {
|
||||||
return res.status(404).json({ message: 'Statistics not found' });
|
return res.status(404).json({ message: 'Statistics not found' });
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,21 +24,25 @@ import {
|
||||||
Text,
|
Text,
|
||||||
useColorModeValue,
|
useColorModeValue,
|
||||||
useDisclosure,
|
useDisclosure,
|
||||||
useToast
|
useToast,
|
||||||
|
Collapse // Added Collapse
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
|
import NextLink from 'next/link';
|
||||||
|
|
||||||
// Icons
|
// Icons
|
||||||
import { MdEmail } from 'react-icons/md';
|
import { MdEmail } from 'react-icons/md';
|
||||||
import { RiLockPasswordFill } from 'react-icons/ri';
|
import { RiLockPasswordFill } from 'react-icons/ri';
|
||||||
|
import { BsDisplayFill } from 'react-icons/bs'; // Added
|
||||||
|
import { FaUser, FaKey } from 'react-icons/fa'; // Added
|
||||||
|
|
||||||
// Authentication
|
// Authentication
|
||||||
import { getAuth, signInWithEmailAndPassword } from 'firebase/auth';
|
import { getAuth, signInWithEmailAndPassword, sendPasswordResetEmail } from 'firebase/auth';
|
||||||
import { Field, Form, Formik } from 'formik';
|
import { useAuthState } from 'react-firebase-hooks/auth'; // Corrected import for useAuthState
|
||||||
|
import { Formik, Form, Field } from 'formik';
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
import NextLink from 'next/link';
|
|
||||||
import { usePathname } from 'next/navigation';
|
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useAuthState } from 'react-firebase-hooks/auth';
|
import { usePathname } from 'next/navigation';
|
||||||
|
import { useState } from 'react'; // Added useState
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import CheckActivationCodeModal from '@/components/CheckActivationCodeModal';
|
import CheckActivationCodeModal from '@/components/CheckActivationCodeModal';
|
||||||
|
@ -48,17 +52,17 @@ import Layout from '@/layouts/PublicLayout';
|
||||||
export default function Login() {
|
export default function Login() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
|
const [isRegisterMode, setIsRegisterMode] = useState(false); // Added state for register mode
|
||||||
|
|
||||||
const auth = getAuth();
|
const auth = getAuth();
|
||||||
const [user, loading, error] = useAuthState(auth);
|
const [user, loading, error] = useAuthState(auth);
|
||||||
|
|
||||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
const { isOpen: isActivationCodeOpen, onOpen: onActivationCodeOpen, onClose: onActivationCodeClose } = useDisclosure();
|
const { isOpen: isActivationCodeOpen, onOpen: onActivationCodeOpen, onClose: onActivationCodeClose } = useDisclosure();
|
||||||
|
const { isOpen: isResetModalOpen, onOpen: onResetModalOpen, onClose: onResetModalClose } = useDisclosure(); // New state for reset modal
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
function redirectOnAuth() {
|
function redirectOnAuth() {
|
||||||
// Check to see if there are any redirect query parameters
|
|
||||||
// otherwise, redirect to the platform home
|
|
||||||
if (router.query.redirect) {
|
if (router.query.redirect) {
|
||||||
router.push(router.query.redirect as string);
|
router.push(router.query.redirect as string);
|
||||||
} else {
|
} else {
|
||||||
|
@ -146,61 +150,230 @@ export default function Login() {
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
<Section>
|
|
||||||
<Box
|
{/* Reset Password Modal */}
|
||||||
position={'relative'}
|
<Modal
|
||||||
minH={"calc(100dvh - 6rem)"}
|
onClose={onResetModalClose}
|
||||||
h={'calc(100dvh - 6rem)'}
|
isOpen={isResetModalOpen}
|
||||||
|
isCentered
|
||||||
|
size={'md'}
|
||||||
>
|
>
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent bg={useColorModeValue('white', 'gray.800')}>
|
||||||
|
<ModalHeader>Reset Password</ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<Formik
|
||||||
|
initialValues={{ email: '' }}
|
||||||
|
onSubmit={(values, actions) => {
|
||||||
|
sendPasswordResetEmail(auth, values.email)
|
||||||
|
.then(() => {
|
||||||
|
toast({
|
||||||
|
title: 'Password reset email sent.',
|
||||||
|
description: 'Please check your inbox to reset your password.',
|
||||||
|
status: 'success',
|
||||||
|
duration: 9000,
|
||||||
|
isClosable: true,
|
||||||
|
});
|
||||||
|
onResetModalClose();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
const errorCode = error.code;
|
||||||
|
let errorMessage = error.message;
|
||||||
|
switch (errorCode) {
|
||||||
|
case 'auth/invalid-email':
|
||||||
|
errorMessage = "The email address you've entered is invalid. Please try again.";
|
||||||
|
break;
|
||||||
|
case 'auth/user-not-found':
|
||||||
|
errorMessage = "No account found with that email address.";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
errorMessage = 'An unknown error occurred. Please try again.';
|
||||||
|
}
|
||||||
|
toast({
|
||||||
|
title: 'Error sending reset email',
|
||||||
|
description: errorMessage,
|
||||||
|
status: 'error',
|
||||||
|
duration: 5000,
|
||||||
|
isClosable: true,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
actions.setSubmitting(false);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(props) => (
|
||||||
|
<Form>
|
||||||
|
<ModalBody>
|
||||||
|
<Text mb={4}>Enter your email address below and we'll send you a link to reset your password.</Text>
|
||||||
|
<Field name="email">
|
||||||
|
{({ field, form }: any) => (
|
||||||
|
<FormControl isInvalid={form.errors.email && form.touched.email}>
|
||||||
|
<FormLabel htmlFor="email-reset">Email address</FormLabel>
|
||||||
|
<InputGroup>
|
||||||
|
<InputLeftElement pointerEvents="none">
|
||||||
|
<MdEmail color="gray.300" />
|
||||||
|
</InputLeftElement>
|
||||||
|
<Input {...field} id="email-reset" placeholder="you@example.com" />
|
||||||
|
</InputGroup>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button variant="ghost" mr={3} onClick={onResetModalClose}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
colorScheme="blue"
|
||||||
|
isLoading={props.isSubmitting}
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
Send Reset Email
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<Section>
|
||||||
<Flex
|
<Flex
|
||||||
position={'relative'}
|
position={'relative'}
|
||||||
align={'center'}
|
height={"calc(100dvh - 6rem)"} // Changed from minH to height
|
||||||
justify={'center'}
|
w="full"
|
||||||
height={'100%'}
|
|
||||||
bottom={{ base: '0', md: '3em' }}
|
|
||||||
>
|
>
|
||||||
{
|
{/* Background Image */}
|
||||||
!loading && !user ?
|
<Image
|
||||||
(<Flex
|
position="absolute"
|
||||||
position={'relative'}
|
top={0}
|
||||||
flexDir={'row'}
|
left={0}
|
||||||
align={{ base: 'center', md: 'flex-start' }}
|
right={0}
|
||||||
outline={['0px solid', '1px solid']}
|
bottom={0}
|
||||||
outlineColor={['unset', useColorModeValue('gray.200', 'gray.700')]}
|
src={'/images/login4.png'}
|
||||||
rounded={'lg'}
|
alt={'Background'}
|
||||||
maxW={"container.md"}
|
objectFit={'cover'}
|
||||||
overflow={'hidden'}
|
|
||||||
height={{ base: 'auto', md: '500px' }}
|
|
||||||
boxShadow={'rgba(0, 0, 0, 0.05) 0px 6px 24px 0px, rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;'}
|
|
||||||
>
|
|
||||||
<Image flex={"0 0 auto"} display={{ base: "none", md: "flex" }} src={'/images/login4.png'} alt={'Login'} objectFit={'cover'} w={'sm'} h={"full"} />
|
|
||||||
{/* <Flex flex={"0 0 auto"} display={{ base: "none", md: "flex" }} objectFit={'cover'} w={'sm'} h={"full"} flexDir={'column'}>
|
|
||||||
<Box
|
|
||||||
backgroundColor={useColorModeValue('gray.200', 'gray.700')}
|
|
||||||
w={'full'}
|
w={'full'}
|
||||||
h={'full'}
|
h={'full'}
|
||||||
|
zIndex={0}
|
||||||
/>
|
/>
|
||||||
</Flex> */}
|
|
||||||
<Flex flex={"1 1 auto"} flexDir={'column'} justify={"center"} align={"flex-start"} py={8} px={10}>
|
{/* Login Panel (Left Side) */}
|
||||||
<Box
|
<Flex
|
||||||
w={'full'}
|
position="relative"
|
||||||
|
zIndex={1}
|
||||||
|
w={user ? 'full' : { base: 'full', md: '480px' }} // Conditionally set width
|
||||||
|
h="full" // This will now refer to the explicit height of the parent Flex
|
||||||
|
bg={useColorModeValue('white', 'gray.800')}
|
||||||
|
transition="width 0.6s ease-in-out" // Added transition property
|
||||||
|
// backdropFilter="blur(8px)" // Optional: for a frosted glass effect
|
||||||
|
direction="column"
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="center"
|
||||||
|
p={{ base: 6, md: 10 }}
|
||||||
>
|
>
|
||||||
|
{
|
||||||
|
loading ? (
|
||||||
|
<Spinner size="xl" />
|
||||||
|
) : !user ? (
|
||||||
|
<Flex
|
||||||
|
direction="column"
|
||||||
|
w="full"
|
||||||
|
maxW="md" // Max width for the form elements
|
||||||
|
align="stretch"
|
||||||
|
>
|
||||||
|
<Box w={'full'} textAlign={{ base: "center", md: "left" }}>
|
||||||
<Text
|
<Text
|
||||||
fontSize={"3xl"}
|
fontSize={"3xl"}
|
||||||
fontWeight={'bold'}
|
fontWeight={'bold'}
|
||||||
>
|
>
|
||||||
Welcome!
|
Welcome!
|
||||||
</Text>
|
</Text>
|
||||||
<Text color={"gray.500"} fontSize={'md'}>Please present your credentials to continue.</Text>
|
<Text color={useColorModeValue("gray.600", "gray.400")} fontSize={'md'}>Please present your credentials to continue.</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<br />
|
<br />
|
||||||
<Box px={[0, 4]} w={"full"}>
|
<Box w={"full"}>
|
||||||
<Formik
|
<Formik
|
||||||
initialValues={{
|
initialValues={{
|
||||||
email: '',
|
email: '',
|
||||||
password: '',
|
password: '',
|
||||||
|
displayName: '', // Added
|
||||||
|
username: '', // Added
|
||||||
|
activationCode: '', // Added
|
||||||
|
confirmPassword: '' // Added for confirm password
|
||||||
}}
|
}}
|
||||||
onSubmit={(values, actions) => {
|
onSubmit={(values, actions) => {
|
||||||
|
if (isRegisterMode) {
|
||||||
|
// Registration Logic
|
||||||
|
if (values.password !== values.confirmPassword) {
|
||||||
|
toast({
|
||||||
|
title: 'Passwords do not match.',
|
||||||
|
description: 'Please ensure your password and confirmation match.',
|
||||||
|
status: 'error',
|
||||||
|
duration: 5000,
|
||||||
|
isClosable: true
|
||||||
|
});
|
||||||
|
actions.setSubmitting(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!values.activationCode) {
|
||||||
|
toast({
|
||||||
|
title: 'Activation code required.',
|
||||||
|
description: 'Please enter your activation code.',
|
||||||
|
status: 'error',
|
||||||
|
duration: 5000,
|
||||||
|
isClosable: true
|
||||||
|
});
|
||||||
|
actions.setSubmitting(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fetch(`/api/v1/activation/${values.activationCode}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
displayName: values.displayName,
|
||||||
|
email: values.email,
|
||||||
|
username: values.username,
|
||||||
|
password: values.password
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
return res.json();
|
||||||
|
} else {
|
||||||
|
return res.json().then((json) => {
|
||||||
|
throw new Error(json.message || 'Registration failed');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
toast({
|
||||||
|
title: 'Account created.',
|
||||||
|
description: 'You can now log in.',
|
||||||
|
status: 'success',
|
||||||
|
duration: 5000,
|
||||||
|
isClosable: true
|
||||||
|
});
|
||||||
|
setIsRegisterMode(false); // Switch back to login mode
|
||||||
|
actions.resetForm();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
toast({
|
||||||
|
title: 'There was an error while creating your account.',
|
||||||
|
description: error.message,
|
||||||
|
status: 'error',
|
||||||
|
duration: 5000,
|
||||||
|
isClosable: true
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
actions.setSubmitting(false);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Login Logic
|
||||||
signInWithEmailAndPassword(auth, values.email, values.password)
|
signInWithEmailAndPassword(auth, values.email, values.password)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
redirectOnAuth();
|
redirectOnAuth();
|
||||||
|
@ -212,20 +385,17 @@ export default function Login() {
|
||||||
case 'auth/invalid-email':
|
case 'auth/invalid-email':
|
||||||
errorMessage = 'The email address you provided is invalid.';
|
errorMessage = 'The email address you provided is invalid.';
|
||||||
break;
|
break;
|
||||||
case 'auth/invalid-password':
|
case 'auth/invalid-credential':
|
||||||
|
case 'auth/user-not-found':
|
||||||
|
case 'auth/wrong-password':
|
||||||
errorMessage = "Invalid email address or password. Please try again.";
|
errorMessage = "Invalid email address or password. Please try again.";
|
||||||
break;
|
break;
|
||||||
case 'auth/user-disabled':
|
case 'auth/user-disabled':
|
||||||
errorMessage = 'Your account has been disabled.';
|
errorMessage = 'Your account has been disabled.';
|
||||||
break;
|
break;
|
||||||
case 'auth/user-not-found':
|
|
||||||
errorMessage = "Invalid email address or password. Please try again.";
|
|
||||||
break;
|
|
||||||
case 'auth/wrong-password':
|
|
||||||
errorMessage = "Invalid email address or password. Please try again.";
|
|
||||||
break;
|
|
||||||
case 'auth/too-many-requests':
|
case 'auth/too-many-requests':
|
||||||
errorMessage = 'Too many attempts. Please try again later.';
|
errorMessage = 'Too many attempts. Please try again later.';
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
errorMessage = 'An unknown error occurred.';
|
errorMessage = 'An unknown error occurred.';
|
||||||
}
|
}
|
||||||
|
@ -239,13 +409,14 @@ export default function Login() {
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
actions.setSubmitting(false);
|
actions.setSubmitting(false);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{(props) => (
|
{(props) => (
|
||||||
<Form>
|
<Form>
|
||||||
<Field name="email">
|
<Field name="email">
|
||||||
{({ field, form }: any) => (
|
{({ field, form }: any) => (
|
||||||
<FormControl mt={2}>
|
<FormControl mt={2} isRequired isInvalid={form.errors.email && form.touched.email}>
|
||||||
<FormLabel>Email</FormLabel>
|
<FormLabel>Email</FormLabel>
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
<InputLeftElement pointerEvents="none">
|
<InputLeftElement pointerEvents="none">
|
||||||
|
@ -253,7 +424,7 @@ export default function Login() {
|
||||||
</InputLeftElement>
|
</InputLeftElement>
|
||||||
<Input
|
<Input
|
||||||
{...field}
|
{...field}
|
||||||
type="text"
|
type="email"
|
||||||
placeholder="Email address"
|
placeholder="Email address"
|
||||||
variant={'outline'}
|
variant={'outline'}
|
||||||
/>
|
/>
|
||||||
|
@ -263,7 +434,7 @@ export default function Login() {
|
||||||
</Field>
|
</Field>
|
||||||
<Field name="password">
|
<Field name="password">
|
||||||
{({ field, form }: any) => (
|
{({ field, form }: any) => (
|
||||||
<FormControl my={2}>
|
<FormControl mt={2} isRequired isInvalid={form.errors.password && form.touched.password}>
|
||||||
<FormLabel>Password</FormLabel>
|
<FormLabel>Password</FormLabel>
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
<InputLeftElement pointerEvents="none">
|
<InputLeftElement pointerEvents="none">
|
||||||
|
@ -271,7 +442,7 @@ export default function Login() {
|
||||||
</InputLeftElement>
|
</InputLeftElement>
|
||||||
<Input
|
<Input
|
||||||
{...field}
|
{...field}
|
||||||
type={'password'}
|
type="password"
|
||||||
placeholder="Password"
|
placeholder="Password"
|
||||||
variant={'outline'}
|
variant={'outline'}
|
||||||
/>
|
/>
|
||||||
|
@ -279,35 +450,155 @@ export default function Login() {
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
|
<Collapse in={isRegisterMode} animateOpacity>
|
||||||
|
<Box>
|
||||||
|
<Field name="confirmPassword">
|
||||||
|
{({ field, form }: any) => (
|
||||||
|
<FormControl mt={2} isRequired={isRegisterMode} isInvalid={form.errors.confirmPassword && form.touched.confirmPassword}>
|
||||||
|
<FormLabel>Confirm Password</FormLabel>
|
||||||
|
<InputGroup>
|
||||||
|
<InputLeftElement pointerEvents="none">
|
||||||
|
<RiLockPasswordFill color="gray.300" />
|
||||||
|
</InputLeftElement>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
type="password"
|
||||||
|
placeholder="Confirm Password"
|
||||||
|
variant={'outline'}
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
<Flex direction={{ base: 'column', md: 'row' }} mt={2} gap={2}>
|
||||||
|
<Field name="displayName">
|
||||||
|
{({ field, form }: any) => (
|
||||||
|
<FormControl mt={{ base: 2, md: 0 }} isRequired={isRegisterMode} isInvalid={form.errors.displayName && form.touched.displayName} flex={1}>
|
||||||
|
<FormLabel>Display Name</FormLabel>
|
||||||
|
<InputGroup>
|
||||||
|
<InputLeftElement pointerEvents="none">
|
||||||
|
<BsDisplayFill color="gray.300" />
|
||||||
|
</InputLeftElement>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
type="text"
|
||||||
|
placeholder="Display Name"
|
||||||
|
variant={'outline'}
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
<Field name="username">
|
||||||
|
{({ field, form }: any) => (
|
||||||
|
<FormControl mt={{ base: 2, md: 0 }} isRequired={isRegisterMode} isInvalid={form.errors.username && form.touched.username} flex={1}>
|
||||||
|
<FormLabel>Username</FormLabel>
|
||||||
|
<InputGroup>
|
||||||
|
<InputLeftElement pointerEvents="none">
|
||||||
|
<FaUser color="gray.300" />
|
||||||
|
</InputLeftElement>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
type="text"
|
||||||
|
placeholder="Username"
|
||||||
|
variant={'outline'}
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Field name="activationCode">
|
||||||
|
{({ field, form }: any) => (
|
||||||
|
<FormControl mt={2} isRequired={isRegisterMode} isInvalid={form.errors.activationCode && form.touched.activationCode}>
|
||||||
|
<FormLabel>Activation Code</FormLabel>
|
||||||
|
<InputGroup>
|
||||||
|
<InputLeftElement pointerEvents="none">
|
||||||
|
<FaKey color="gray.300" />
|
||||||
|
</InputLeftElement>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
type="text"
|
||||||
|
placeholder="Activation Code"
|
||||||
|
variant={'outline'}
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</Box>
|
||||||
|
</Collapse>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
my={2}
|
my={4}
|
||||||
w={'full'}
|
w={'full'}
|
||||||
isLoading={props.isSubmitting}
|
isLoading={props.isSubmitting}
|
||||||
type={'submit'}
|
type={'submit'}
|
||||||
>
|
>
|
||||||
Log in
|
{isRegisterMode ? 'Create Account' : 'Log in'}
|
||||||
</Button>
|
</Button>
|
||||||
</Form>
|
</Form>
|
||||||
)}
|
)}
|
||||||
</Formik>
|
</Formik>
|
||||||
|
|
||||||
|
{isRegisterMode ? (
|
||||||
|
<Text fontSize={'sm'} mt={2} textAlign={{ base: "center", md: "left" }}>
|
||||||
|
Already have an account?{' '}
|
||||||
|
<Link onClick={() => setIsRegisterMode(false)} cursor="pointer" textDecor={'underline'} textUnderlineOffset={4}>
|
||||||
|
Login
|
||||||
|
</Link>
|
||||||
|
</Text>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
w={'full'}
|
||||||
|
variant={'outline'}
|
||||||
|
onClick={() => setIsRegisterMode(true)}
|
||||||
|
>
|
||||||
|
Create an Account
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{isRegisterMode && (
|
||||||
<Text fontSize={'sm'}>
|
<Text fontSize={'sm'}>
|
||||||
|
By creating an account, you agree to our{' '}
|
||||||
|
<Text as={'span'}>
|
||||||
<Link
|
<Link
|
||||||
as={NextLink}
|
as={NextLink}
|
||||||
href="/auth/reset"
|
href={'/legal/terms'}
|
||||||
|
textDecor={'underline'}
|
||||||
textUnderlineOffset={4}
|
textUnderlineOffset={4}
|
||||||
|
whiteSpace={'nowrap'}
|
||||||
|
>
|
||||||
|
Terms of Use
|
||||||
|
</Link>
|
||||||
|
</Text>{' '}
|
||||||
|
and{' '}
|
||||||
|
<Text as={'span'}>
|
||||||
|
<Link
|
||||||
|
as={NextLink}
|
||||||
|
href={'/legal/privacy'}
|
||||||
|
textDecor={'underline'}
|
||||||
|
textUnderlineOffset={4}
|
||||||
|
whiteSpace={'nowrap'}
|
||||||
|
>
|
||||||
|
Privacy Policy
|
||||||
|
</Link>
|
||||||
|
</Text>
|
||||||
|
.
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
<Text fontSize={'sm'} mt={4} textAlign={{ base: "center", md: "left" }}>
|
||||||
|
<Link
|
||||||
|
onClick={onResetModalOpen}
|
||||||
|
textUnderlineOffset={4}
|
||||||
|
cursor="pointer"
|
||||||
|
_hover={{ textDecoration: 'underline' }}
|
||||||
>
|
>
|
||||||
Forgot your password?
|
Forgot your password?
|
||||||
</Link>
|
</Link>
|
||||||
</Text>
|
</Text>
|
||||||
<Text fontSize={"sm"}>
|
<Text fontSize={'sm'} mt={2} textAlign={{ base: "center", md: "left" }}>
|
||||||
<Link
|
|
||||||
onClick={onActivationCodeOpen}
|
|
||||||
textUnderlineOffset={4}
|
|
||||||
>
|
|
||||||
Have an activation code?
|
|
||||||
</Link>
|
|
||||||
</Text>
|
|
||||||
<Text fontSize={'sm'}>
|
|
||||||
Need help?{' '}
|
Need help?{' '}
|
||||||
<Box
|
<Box
|
||||||
as="button"
|
as="button"
|
||||||
|
@ -315,22 +606,22 @@ export default function Login() {
|
||||||
textDecor={'underline'}
|
textDecor={'underline'}
|
||||||
textUnderlineOffset={4}
|
textUnderlineOffset={4}
|
||||||
transition={'all 0.15s ease'}
|
transition={'all 0.15s ease'}
|
||||||
_hover={{ color: ['gray.300', 'gray.500'] }}
|
_hover={{ color: useColorModeValue('gray.600', 'gray.400') }}
|
||||||
>
|
>
|
||||||
View the FAQ.
|
View the FAQ.
|
||||||
</Box>
|
</Box>
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
|
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
) : (
|
||||||
) : <>
|
// User is logged in, redirectOnAuth has been called. Show spinner during transition.
|
||||||
<AbsoluteCenter>
|
<Spinner size="xl" />
|
||||||
<Spinner />
|
)
|
||||||
</AbsoluteCenter>
|
|
||||||
</>
|
|
||||||
}
|
}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
</Flex>
|
||||||
</Section>
|
</Section>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in a new issue