mirror of
https://github.com/Amperra-Group/xcs.git
synced 2025-08-16 13:22:14 -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>
|
||||
|
||||
<ModalFooter>
|
||||
<Button onClick={onClose}>Cancel</Button>
|
||||
<Button
|
||||
colorScheme={'black'}
|
||||
mr={3}
|
||||
ml={3}
|
||||
isLoading={props.isSubmitting}
|
||||
type={'submit'}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
<Button onClick={onClose}>Cancel</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Form>
|
||||
|
|
|
@ -47,8 +47,9 @@ export default function Footer({ type = 'platform' }: { type?: 'public' | 'platf
|
|||
fontWeight={'bold'}
|
||||
letterSpacing={'tight'}
|
||||
>
|
||||
PT Amperra Sambung Semesta Sentosa
|
||||
PT Amperra Sambung Semesta Sentosa{' '}
|
||||
</Text>
|
||||
under the <Link fontWeight={'bold'} as={NextLink} href={'/LICENSE.txt'}>MIT License.</Link>
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex align={'center'} justify={'center'} fontSize={'sm'}>
|
||||
|
|
|
@ -122,6 +122,7 @@ export default function Nav({ type }: { type?: string }) {
|
|||
h={'6rem'}
|
||||
align={'center'}
|
||||
bg={useColorModeValue('white', 'gray.800')}
|
||||
borderBottom={'1px solid'}
|
||||
borderColor={useColorModeValue('gray.300', 'gray.700')}
|
||||
zIndex={50}
|
||||
>
|
||||
|
@ -149,7 +150,7 @@ export default function Nav({ type }: { type?: string }) {
|
|||
>
|
||||
<Flex
|
||||
position={'relative'}
|
||||
w={'128px'}
|
||||
w={'150px'}
|
||||
h={'100%'}
|
||||
>
|
||||
<NextImage
|
||||
|
|
|
@ -368,7 +368,7 @@ export default function PlatformNav({ type, title }: { type?: string; title?: st
|
|||
>
|
||||
<Flex
|
||||
position={'relative'}
|
||||
w={'128px'}
|
||||
w={'150px'}
|
||||
h={'100%'}
|
||||
>
|
||||
<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 statistics = db.collection('statistics');
|
||||
|
||||
const globalStatistics = await statistics.findOne({ id: 'global' });
|
||||
const globalStatistics = await statistics.findOne({ id: 'global ' });
|
||||
if (!globalStatistics) {
|
||||
return res.status(404).json({ message: 'Statistics not found' });
|
||||
}
|
||||
|
|
|
@ -24,21 +24,25 @@ import {
|
|||
Text,
|
||||
useColorModeValue,
|
||||
useDisclosure,
|
||||
useToast
|
||||
useToast,
|
||||
Collapse // Added Collapse
|
||||
} from '@chakra-ui/react';
|
||||
import NextLink from 'next/link';
|
||||
|
||||
// Icons
|
||||
import { MdEmail } from 'react-icons/md';
|
||||
import { RiLockPasswordFill } from 'react-icons/ri';
|
||||
import { BsDisplayFill } from 'react-icons/bs'; // Added
|
||||
import { FaUser, FaKey } from 'react-icons/fa'; // Added
|
||||
|
||||
// Authentication
|
||||
import { getAuth, signInWithEmailAndPassword } from 'firebase/auth';
|
||||
import { Field, Form, Formik } from 'formik';
|
||||
import { getAuth, signInWithEmailAndPassword, sendPasswordResetEmail } from 'firebase/auth';
|
||||
import { useAuthState } from 'react-firebase-hooks/auth'; // Corrected import for useAuthState
|
||||
import { Formik, Form, Field } from 'formik';
|
||||
import Head from 'next/head';
|
||||
import NextLink from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useAuthState } from 'react-firebase-hooks/auth';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { useState } from 'react'; // Added useState
|
||||
|
||||
// Components
|
||||
import CheckActivationCodeModal from '@/components/CheckActivationCodeModal';
|
||||
|
@ -48,22 +52,22 @@ import Layout from '@/layouts/PublicLayout';
|
|||
export default function Login() {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const [isRegisterMode, setIsRegisterMode] = useState(false); // Added state for register mode
|
||||
|
||||
const auth = getAuth();
|
||||
const [user, loading, error] = useAuthState(auth);
|
||||
|
||||
const { isOpen, onOpen, onClose } = 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();
|
||||
|
||||
function redirectOnAuth() {
|
||||
// Check to see if there are any redirect query parameters
|
||||
// otherwise, redirect to the platform home
|
||||
if (router.query.redirect) {
|
||||
router.push(router.query.redirect as string);
|
||||
} else {
|
||||
router.push('/home');
|
||||
}
|
||||
if (router.query.redirect) {
|
||||
router.push(router.query.redirect as string);
|
||||
} else {
|
||||
router.push('/home');
|
||||
}
|
||||
}
|
||||
|
||||
if (user) {
|
||||
|
@ -146,61 +150,230 @@ export default function Login() {
|
|||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
|
||||
{/* Reset Password Modal */}
|
||||
<Modal
|
||||
onClose={onResetModalClose}
|
||||
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>
|
||||
<Box
|
||||
<Flex
|
||||
position={'relative'}
|
||||
minH={"calc(100dvh - 6rem)"}
|
||||
h={'calc(100dvh - 6rem)'}
|
||||
height={"calc(100dvh - 6rem)"} // Changed from minH to height
|
||||
w="full"
|
||||
>
|
||||
{/* Background Image */}
|
||||
<Image
|
||||
position="absolute"
|
||||
top={0}
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
src={'/images/login4.png'}
|
||||
alt={'Background'}
|
||||
objectFit={'cover'}
|
||||
w={'full'}
|
||||
h={'full'}
|
||||
zIndex={0}
|
||||
/>
|
||||
|
||||
{/* Login Panel (Left Side) */}
|
||||
<Flex
|
||||
position={'relative'}
|
||||
align={'center'}
|
||||
justify={'center'}
|
||||
height={'100%'}
|
||||
bottom={{ base: '0', md: '3em' }}
|
||||
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 && !user ?
|
||||
(<Flex
|
||||
position={'relative'}
|
||||
flexDir={'row'}
|
||||
align={{ base: 'center', md: 'flex-start' }}
|
||||
outline={['0px solid', '1px solid']}
|
||||
outlineColor={['unset', useColorModeValue('gray.200', 'gray.700')]}
|
||||
rounded={'lg'}
|
||||
maxW={"container.md"}
|
||||
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;'}
|
||||
loading ? (
|
||||
<Spinner size="xl" />
|
||||
) : !user ? (
|
||||
<Flex
|
||||
direction="column"
|
||||
w="full"
|
||||
maxW="md" // Max width for the form elements
|
||||
align="stretch"
|
||||
>
|
||||
<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'}
|
||||
h={'full'}
|
||||
/>
|
||||
</Flex> */}
|
||||
<Flex flex={"1 1 auto"} flexDir={'column'} justify={"center"} align={"flex-start"} py={8} px={10}>
|
||||
<Box
|
||||
w={'full'}
|
||||
<Box w={'full'} textAlign={{ base: "center", md: "left" }}>
|
||||
<Text
|
||||
fontSize={"3xl"}
|
||||
fontWeight={'bold'}
|
||||
>
|
||||
<Text
|
||||
fontSize={"3xl"}
|
||||
fontWeight={'bold'}
|
||||
>
|
||||
Welcome!
|
||||
</Text>
|
||||
<Text color={"gray.500"} fontSize={'md'}>Please present your credentials to continue.</Text>
|
||||
</Box>
|
||||
<br />
|
||||
<Box px={[0, 4]} w={"full"}>
|
||||
<Formik
|
||||
initialValues={{
|
||||
email: '',
|
||||
password: '',
|
||||
}}
|
||||
onSubmit={(values, actions) => {
|
||||
Welcome!
|
||||
</Text>
|
||||
<Text color={useColorModeValue("gray.600", "gray.400")} fontSize={'md'}>Please present your credentials to continue.</Text>
|
||||
</Box>
|
||||
<br />
|
||||
<Box w={"full"}>
|
||||
<Formik
|
||||
initialValues={{
|
||||
email: '',
|
||||
password: '',
|
||||
displayName: '', // Added
|
||||
username: '', // Added
|
||||
activationCode: '', // Added
|
||||
confirmPassword: '' // Added for confirm password
|
||||
}}
|
||||
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)
|
||||
.then(() => {
|
||||
redirectOnAuth();
|
||||
|
@ -212,20 +385,17 @@ export default function Login() {
|
|||
case 'auth/invalid-email':
|
||||
errorMessage = 'The email address you provided is invalid.';
|
||||
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.";
|
||||
break;
|
||||
case 'auth/user-disabled':
|
||||
errorMessage = 'Your account has been disabled.';
|
||||
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':
|
||||
errorMessage = 'Too many attempts. Please try again later.';
|
||||
break;
|
||||
default:
|
||||
errorMessage = 'An unknown error occurred.';
|
||||
}
|
||||
|
@ -239,98 +409,219 @@ export default function Login() {
|
|||
.finally(() => {
|
||||
actions.setSubmitting(false);
|
||||
});
|
||||
}}
|
||||
}
|
||||
}}
|
||||
>
|
||||
{(props) => (
|
||||
<Form>
|
||||
<Field name="email">
|
||||
{({ field, form }: any) => (
|
||||
<FormControl mt={2} isRequired isInvalid={form.errors.email && form.touched.email}>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<InputGroup>
|
||||
<InputLeftElement pointerEvents="none">
|
||||
<MdEmail color="gray.300" />
|
||||
</InputLeftElement>
|
||||
<Input
|
||||
{...field}
|
||||
type="email"
|
||||
placeholder="Email address"
|
||||
variant={'outline'}
|
||||
/>
|
||||
</InputGroup>
|
||||
</FormControl>
|
||||
)}
|
||||
</Field>
|
||||
<Field name="password">
|
||||
{({ field, form }: any) => (
|
||||
<FormControl mt={2} isRequired isInvalid={form.errors.password && form.touched.password}>
|
||||
<FormLabel>Password</FormLabel>
|
||||
<InputGroup>
|
||||
<InputLeftElement pointerEvents="none">
|
||||
<RiLockPasswordFill color="gray.300" />
|
||||
</InputLeftElement>
|
||||
<Input
|
||||
{...field}
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
variant={'outline'}
|
||||
/>
|
||||
</InputGroup>
|
||||
</FormControl>
|
||||
)}
|
||||
</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
|
||||
my={4}
|
||||
w={'full'}
|
||||
isLoading={props.isSubmitting}
|
||||
type={'submit'}
|
||||
>
|
||||
{isRegisterMode ? 'Create Account' : 'Log in'}
|
||||
</Button>
|
||||
</Form>
|
||||
)}
|
||||
</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)}
|
||||
>
|
||||
{(props) => (
|
||||
<Form>
|
||||
<Field name="email">
|
||||
{({ field, form }: any) => (
|
||||
<FormControl mt={2}>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<InputGroup>
|
||||
<InputLeftElement pointerEvents="none">
|
||||
<MdEmail color="gray.300" />
|
||||
</InputLeftElement>
|
||||
<Input
|
||||
{...field}
|
||||
type="text"
|
||||
placeholder="Email address"
|
||||
variant={'outline'}
|
||||
/>
|
||||
</InputGroup>
|
||||
</FormControl>
|
||||
)}
|
||||
</Field>
|
||||
<Field name="password">
|
||||
{({ field, form }: any) => (
|
||||
<FormControl my={2}>
|
||||
<FormLabel>Password</FormLabel>
|
||||
<InputGroup>
|
||||
<InputLeftElement pointerEvents="none">
|
||||
<RiLockPasswordFill color="gray.300" />
|
||||
</InputLeftElement>
|
||||
<Input
|
||||
{...field}
|
||||
type={'password'}
|
||||
placeholder="Password"
|
||||
variant={'outline'}
|
||||
/>
|
||||
</InputGroup>
|
||||
</FormControl>
|
||||
)}
|
||||
</Field>
|
||||
<Button
|
||||
my={2}
|
||||
w={'full'}
|
||||
isLoading={props.isSubmitting}
|
||||
type={'submit'}
|
||||
>
|
||||
Log in
|
||||
</Button>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
Create an Account
|
||||
</Button>
|
||||
)}
|
||||
{isRegisterMode && (
|
||||
<Text fontSize={'sm'}>
|
||||
<Link
|
||||
as={NextLink}
|
||||
href="/auth/reset"
|
||||
textUnderlineOffset={4}
|
||||
>
|
||||
Forgot your password?
|
||||
</Link>
|
||||
By creating an account, you agree to our{' '}
|
||||
<Text as={'span'}>
|
||||
<Link
|
||||
as={NextLink}
|
||||
href={'/legal/terms'}
|
||||
textDecor={'underline'}
|
||||
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"}>
|
||||
<Link
|
||||
onClick={onActivationCodeOpen}
|
||||
textUnderlineOffset={4}
|
||||
>
|
||||
Have an activation code?
|
||||
</Link>
|
||||
</Text>
|
||||
<Text fontSize={'sm'}>
|
||||
Need help?{' '}
|
||||
<Box
|
||||
as="button"
|
||||
onClick={onOpen}
|
||||
textDecor={'underline'}
|
||||
textUnderlineOffset={4}
|
||||
transition={'all 0.15s ease'}
|
||||
_hover={{ color: ['gray.300', 'gray.500'] }}
|
||||
>
|
||||
View the FAQ.
|
||||
</Box>
|
||||
</Text>
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
<Text fontSize={'sm'} mt={4} textAlign={{ base: "center", md: "left" }}>
|
||||
<Link
|
||||
onClick={onResetModalOpen}
|
||||
textUnderlineOffset={4}
|
||||
cursor="pointer"
|
||||
_hover={{ textDecoration: 'underline' }}
|
||||
>
|
||||
Forgot your password?
|
||||
</Link>
|
||||
</Text>
|
||||
<Text fontSize={'sm'} mt={2} textAlign={{ base: "center", md: "left" }}>
|
||||
Need help?{' '}
|
||||
<Box
|
||||
as="button"
|
||||
onClick={onOpen}
|
||||
textDecor={'underline'}
|
||||
textUnderlineOffset={4}
|
||||
transition={'all 0.15s ease'}
|
||||
_hover={{ color: useColorModeValue('gray.600', 'gray.400') }}
|
||||
>
|
||||
View the FAQ.
|
||||
</Box>
|
||||
</Text>
|
||||
|
||||
|
||||
</Box>
|
||||
</Flex>
|
||||
) : <>
|
||||
<AbsoluteCenter>
|
||||
<Spinner />
|
||||
</AbsoluteCenter>
|
||||
</>
|
||||
) : (
|
||||
// User is logged in, redirectOnAuth has been called. Show spinner during transition.
|
||||
<Spinner size="xl" />
|
||||
)
|
||||
}
|
||||
</Flex>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Section>
|
||||
</>
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue