Pārlūkot izejas kodu

Standardize auth forms layouts

Nicolas Meienberger 3 gadi atpakaļ
vecāks
revīzija
1719a14ec5

+ 28 - 0
dashboard/src/modules/Auth/components/AuthFormLayout.tsx

@@ -0,0 +1,28 @@
+import { Container, Flex, SlideFade, Text } from '@chakra-ui/react';
+import React from 'react';
+
+interface IProps {
+  title: string;
+  description: string;
+}
+
+const AuthFormLayout: React.FC<IProps> = ({ children, title, description }) => {
+  return (
+    <Container maxW="1250px">
+      <Flex flex={1} height="100vh" overflowY="hidden">
+        <SlideFade in className="flex flex-1 flex-col justify-center items-center" offsetY="20px">
+          <img className="self-center mb-5 logo" src="/tipi.png" width={512} height={512} />
+          <Text className="text-xl md:text-2xl lg:text-5xl font-bold" size="3xl">
+            {title}
+          </Text>
+          <Text className="md:text-lg lg:text-2xl text-center" color="gray.500">
+            {description}
+          </Text>
+          {children}
+        </SlideFade>
+      </Flex>
+    </Container>
+  );
+};
+
+export default AuthFormLayout;

+ 57 - 0
dashboard/src/modules/Auth/components/LoginForm.tsx

@@ -0,0 +1,57 @@
+import { Button } from '@chakra-ui/react';
+import React from 'react';
+import { Field, Form } from 'react-final-form';
+import validator from 'validator';
+import FormInput from '../../../components/Form/FormInput';
+
+type FormValues = { email: string; password: string };
+
+interface IProps {
+  onSubmit: (values: FormValues) => void;
+  loading: boolean;
+}
+
+const LoginForm: React.FC<IProps> = ({ onSubmit, loading }) => {
+  const validateFields = (values: FormValues) => {
+    const errors: Record<string, string> = {};
+
+    if (!validator.isEmail(values.email || '')) {
+      errors.email = 'Invalid email';
+    }
+
+    if (!values.password) {
+      errors.password = 'Required';
+    }
+
+    return errors;
+  };
+
+  return (
+    <Form<FormValues>
+      onSubmit={onSubmit}
+      validateOnBlur={true}
+      validate={(values) => validateFields(values)}
+      render={({ handleSubmit, validating, submitting }) => (
+        <form className="flex flex-col" onSubmit={handleSubmit}>
+          <Field
+            name="email"
+            render={({ input, meta }) => (
+              <FormInput size="lg" className="mt-3 w-full" error={meta.error} isInvalid={meta.invalid && (meta.submitError || meta.submitFailed)} placeholder="Email" {...input} />
+            )}
+          />
+          <Field
+            name="password"
+            render={({ input, meta }) => (
+              <FormInput size="lg" className="mt-3 w-full" error={meta.error} isInvalid={meta.invalid && (meta.submitError || meta.submitFailed)} placeholder="Password" type="password" {...input} />
+            )}
+          />
+          <Button isLoading={validating || submitting || loading} className="mt-2" colorScheme="green" type="submit">
+            Login
+          </Button>
+        </form>
+      )}
+    />
+  );
+};
+
+export default LoginForm;

+ 75 - 0
dashboard/src/modules/Auth/components/RegisterForm.tsx

@@ -0,0 +1,75 @@
+import { Button } from '@chakra-ui/react';
+import React from 'react';
+import { Field, Form } from 'react-final-form';
+import validator from 'validator';
+import FormInput from '../../../components/Form/FormInput';
+
+interface IProps {
+  onSubmit: (values: FormValues) => void;
+  loading: boolean;
+}
+
+type FormValues = { email: string; password: string; passwordConfirm: string };
+
+const RegisterForm: React.FC<IProps> = ({ onSubmit, loading }) => {
+  const validateFields = (values: FormValues) => {
+    const errors: Record<string, string> = {};
+
+    if (!validator.isEmail(values.email || '')) {
+      errors.email = 'Invalid email';
+    }
+
+    if (!values.password) {
+      errors.password = 'Required';
+    }
+
+    if (values.password !== values.passwordConfirm) {
+      errors.passwordConfirm = 'Passwords do not match';
+    }
+
+    return errors;
+  };
+
+  return (
+    <Form<FormValues>
+      onSubmit={onSubmit}
+      validateOnBlur={true}
+      validate={(values) => validateFields(values)}
+      render={({ handleSubmit, validating, submitting }) => (
+        <form className="flex flex-col" onSubmit={handleSubmit}>
+          <Field
+            name="email"
+            render={({ input, meta }) => (
+              <FormInput size="lg" className="mt-3 w-full" error={meta.error} isInvalid={meta.invalid && (meta.submitError || meta.submitFailed)} placeholder="Email" {...input} />
+            )}
+          />
+          <Field
+            name="password"
+            render={({ input, meta }) => (
+              <FormInput size="lg" className="mt-3 w-full" error={meta.error} isInvalid={meta.invalid && (meta.submitError || meta.submitFailed)} placeholder="Password" type="password" {...input} />
+            )}
+          />
+          <Field
+            name="passwordConfirm"
+            render={({ input, meta }) => (
+              <FormInput
+                size="lg"
+                className="mt-3 w-full"
+                error={meta.error}
+                isInvalid={meta.invalid && (meta.submitError || meta.submitFailed)}
+                placeholder="Repeat password"
+                type="password"
+                {...input}
+              />
+            )}
+          />
+          <Button isLoading={validating || submitting || loading} className="mt-2" colorScheme="green" type="submit">
+            Enter
+          </Button>
+        </form>
+      )}
+    />
+  );
+};
+
+export default RegisterForm;

+ 3 - 35
dashboard/src/modules/Auth/containers/AuthWrapper.tsx

@@ -1,4 +1,3 @@
-import { useToast } from '@chakra-ui/react';
 import React, { useEffect, useState } from 'react';
 import LoadingScreen from '../../../components/LoadingScreen';
 import { useAuthStore } from '../../../state/authStore';
@@ -7,8 +6,7 @@ import Onboarding from './Onboarding';
 
 const AuthWrapper: React.FC = ({ children }) => {
   const [initialLoad, setInitialLoad] = useState(true);
-  const { configured, loading, user, login, me, fetchConfigured, register } = useAuthStore();
-  const toast = useToast();
+  const { configured, user, me, fetchConfigured } = useAuthStore();
 
   useEffect(() => {
     const fetchUser = async () => {
@@ -20,36 +18,6 @@ const AuthWrapper: React.FC = ({ children }) => {
     if (!user) fetchUser();
   }, [fetchConfigured, me, user]);
 
-  const handleError = (error: unknown) => {
-    if (error instanceof Error) {
-      toast({
-        title: 'Error',
-        description: error.message,
-        status: 'error',
-        position: 'top',
-        isClosable: true,
-      });
-    }
-  };
-
-  const handleLogin = async (values: { email: string; password: string }) => {
-    try {
-      await login(values.email, values.password);
-      await me();
-    } catch (error) {
-      handleError(error);
-    }
-  };
-
-  const handleRegister = async (values: { email: string; password: string }) => {
-    try {
-      await register(values.email, values.password);
-      await me();
-    } catch (error) {
-      handleError(error);
-    }
-  };
-
   if (initialLoad && !user) {
     return <LoadingScreen />;
   }
@@ -59,10 +27,10 @@ const AuthWrapper: React.FC = ({ children }) => {
   }
 
   if (!configured) {
-    return <Onboarding loading={loading} onSubmit={handleRegister} />;
+    return <Onboarding />;
   }
 
-  return <Login loading={loading} onSubmit={handleLogin} />;
+  return <Login />;
 };
 
 export default AuthWrapper;

+ 27 - 64
dashboard/src/modules/Auth/containers/Login.tsx

@@ -1,77 +1,40 @@
-import { Button, Container, Flex, SlideFade, Text } from '@chakra-ui/react';
+import { useToast } from '@chakra-ui/react';
 import React from 'react';
-import { Field, Form } from 'react-final-form';
-import validator from 'validator';
-import FormInput from '../../../components/Form/FormInput';
+import { useAuthStore } from '../../../state/authStore';
+import AuthFormLayout from '../components/AuthFormLayout';
+import LoginForm from '../components/LoginForm';
 
 type FormValues = { email: string; password: string };
 
-interface IProps {
-  onSubmit: (values: FormValues) => void;
-  loading: boolean;
-}
-
-const Login: React.FC<IProps> = ({ onSubmit, loading }) => {
-  const validateFields = (values: FormValues) => {
-    const errors: Record<string, string> = {};
-
-    if (!validator.isEmail(values.email || '')) {
-      errors.email = 'Invalid email';
+const Login: React.FC = () => {
+  const { me, login, loading } = useAuthStore();
+  const toast = useToast();
+
+  const handleError = (error: unknown) => {
+    if (error instanceof Error) {
+      toast({
+        title: 'Error',
+        description: error.message,
+        status: 'error',
+        position: 'top',
+        isClosable: true,
+      });
     }
+  };
 
-    if (!values.password) {
-      errors.password = 'Required';
+  const handleLogin = async (values: FormValues) => {
+    try {
+      await login(values.email, values.password);
+      await me();
+    } catch (error) {
+      handleError(error);
     }
-
-    return errors;
   };
 
   return (
-    <Container maxW="1250px">
-      <Flex flex={1} height="100vh" overflowY="hidden">
-        <SlideFade in className="flex flex-1 flex-col justify-center items-center" offsetY="20px">
-          <img className="self-center mb-5 logo" src="/tipi.png" width={512} height={512} />
-          <Text className="text-xl md:text-2xl lg:text-5xl font-bold" size="3xl">
-            Welcome back
-          </Text>
-          <Text className="md:text-lg lg:text-2xl text-center" color="gray.500">
-            Enter your credentials to login to your Tipi
-          </Text>
-          <Form<FormValues>
-            onSubmit={onSubmit}
-            validateOnBlur={true}
-            validate={(values) => validateFields(values)}
-            render={({ handleSubmit, validating, submitting }) => (
-              <form className="flex flex-col" onSubmit={handleSubmit}>
-                <Field
-                  name="email"
-                  render={({ input, meta }) => (
-                    <FormInput size="lg" className="mt-3 w-full" error={meta.error} isInvalid={meta.invalid && (meta.submitError || meta.submitFailed)} placeholder="Email" {...input} />
-                  )}
-                />
-                <Field
-                  name="password"
-                  render={({ input, meta }) => (
-                    <FormInput
-                      size="lg"
-                      className="mt-3 w-full"
-                      error={meta.error}
-                      isInvalid={meta.invalid && (meta.submitError || meta.submitFailed)}
-                      placeholder="Password"
-                      type="password"
-                      {...input}
-                    />
-                  )}
-                />
-                <Button isLoading={validating || submitting || loading} className="mt-2" colorScheme="green" type="submit">
-                  Login
-                </Button>
-              </form>
-            )}
-          />
-        </SlideFade>
-      </Flex>
-    </Container>
+    <AuthFormLayout title="Welcome back" description="Enter your credentials to login to your Tipi">
+      <LoginForm onSubmit={handleLogin} loading={loading} />
+    </AuthFormLayout>
   );
 };
 

+ 28 - 85
dashboard/src/modules/Auth/containers/Onboarding.tsx

@@ -1,95 +1,38 @@
-import { Button, Container, Flex, SlideFade, Text } from '@chakra-ui/react';
+import { useToast } from '@chakra-ui/react';
 import React from 'react';
-import { Field, Form } from 'react-final-form';
-import validator from 'validator';
-import FormInput from '../../../components/Form/FormInput';
-
-type FormValues = { email: string; password: string; passwordConfirm: string };
-
-interface IProps {
-  onSubmit: (values: FormValues) => void;
-  loading: boolean;
-}
-
-const Onboarding: React.FC<IProps> = ({ onSubmit, loading }) => {
-  const validateFields = (values: FormValues) => {
-    const errors: Record<string, string> = {};
-
-    if (!validator.isEmail(values.email || '')) {
-      errors.email = 'Invalid email';
-    }
-
-    if (!values.password) {
-      errors.password = 'Required';
+import { useAuthStore } from '../../../state/authStore';
+import AuthFormLayout from '../components/AuthFormLayout';
+import RegisterForm from '../components/RegisterForm';
+
+const Onboarding: React.FC = () => {
+  const toast = useToast();
+  const { me, register, loading } = useAuthStore();
+
+  const handleError = (error: unknown) => {
+    if (error instanceof Error) {
+      toast({
+        title: 'Error',
+        description: error.message,
+        status: 'error',
+        position: 'top',
+        isClosable: true,
+      });
     }
+  };
 
-    if (values.password !== values.passwordConfirm) {
-      errors.passwordConfirm = 'Passwords do not match';
+  const handleRegister = async (values: { email: string; password: string }) => {
+    try {
+      await register(values.email, values.password);
+      await me();
+    } catch (error) {
+      handleError(error);
     }
-
-    return errors;
   };
 
   return (
-    <Container maxW="1250px">
-      <Flex flex={1} height="100vh" overflowY="hidden">
-        <SlideFade in className="flex flex-1 flex-col justify-center items-center" offsetY="20px">
-          <img className="self-center mb-5 logo" src="/tipi.png" width={512} height={512} />
-          <Text className="text-xl md:text-2xl lg:text-5xl font-bold" size="3xl">
-            Welcome to your Tipi
-          </Text>
-          <Text className="md:text-lg lg:text-2xl text-center" color="gray.500">
-            Register your account to get started
-          </Text>
-          <Form<FormValues>
-            onSubmit={onSubmit}
-            validateOnBlur={true}
-            validate={(values) => validateFields(values)}
-            render={({ handleSubmit, validating, submitting }) => (
-              <form className="flex flex-col" onSubmit={handleSubmit}>
-                <Field
-                  name="email"
-                  render={({ input, meta }) => (
-                    <FormInput size="lg" className="mt-3 w-full" error={meta.error} isInvalid={meta.invalid && (meta.submitError || meta.submitFailed)} placeholder="Email" {...input} />
-                  )}
-                />
-                <Field
-                  name="password"
-                  render={({ input, meta }) => (
-                    <FormInput
-                      size="lg"
-                      className="mt-3 w-full"
-                      error={meta.error}
-                      isInvalid={meta.invalid && (meta.submitError || meta.submitFailed)}
-                      placeholder="Password"
-                      type="password"
-                      {...input}
-                    />
-                  )}
-                />
-                <Field
-                  name="passwordConfirm"
-                  render={({ input, meta }) => (
-                    <FormInput
-                      size="lg"
-                      className="mt-3 w-full"
-                      error={meta.error}
-                      isInvalid={meta.invalid && (meta.submitError || meta.submitFailed)}
-                      placeholder="Repeat password"
-                      type="password"
-                      {...input}
-                    />
-                  )}
-                />
-                <Button isLoading={validating || submitting || loading} className="mt-2" colorScheme="green" type="submit">
-                  Enter
-                </Button>
-              </form>
-            )}
-          />
-        </SlideFade>
-      </Flex>
-    </Container>
+    <AuthFormLayout title="Welcome to your Tipi" description="Register your account to get started">
+      <RegisterForm onSubmit={handleRegister} loading={loading} />
+    </AuthFormLayout>
   );
 };