login.tsx 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. import React, { useEffect, useState } from "react";
  2. import Router from "next/router";
  3. import Link from "next/link";
  4. import axios from "axios";
  5. import styled from "styled-components";
  6. import emailValidator from "email-validator";
  7. import { useFormState } from "react-use-form-state";
  8. import { Flex } from "reflexbox/styled-components";
  9. import { useStoreState, useStoreActions } from "../store";
  10. import BodyWrapper from "../components/BodyWrapper";
  11. import { fadeIn } from "../helpers/animations";
  12. import { API } from "../consts";
  13. import TextInput from "../components/TextInput";
  14. import { Button } from "../components/Button";
  15. import Text from "../components/Text";
  16. import ALink from "../components/ALink";
  17. import { ColCenterV } from "../components/Layout";
  18. const LoginForm = styled(Flex).attrs({
  19. as: "form",
  20. flexDirection: "column"
  21. })`
  22. animation: ${fadeIn} 0.8s ease-out;
  23. `;
  24. const Email = styled.span`
  25. font-weight: normal;
  26. color: #512da8;
  27. border-bottom: 1px dotted #999;
  28. `;
  29. const LoginPage = () => {
  30. const { isAuthenticated } = useStoreState(s => s.auth);
  31. const login = useStoreActions(s => s.auth.login);
  32. const [error, setError] = useState("");
  33. const [verifying, setVerifying] = useState(false);
  34. const [loading, setLoading] = useState({ login: false, signup: false });
  35. const [formState, { email, password, label }] = useFormState<{
  36. email: string;
  37. password: string;
  38. }>(null, { withIds: true });
  39. useEffect(() => {
  40. if (isAuthenticated) Router.push("/");
  41. }, [isAuthenticated]);
  42. function onSubmit(type: "login" | "signup") {
  43. return async e => {
  44. e.preventDefault();
  45. const { email, password } = formState.values;
  46. if (loading.login || loading.signup) return null;
  47. if (!email) {
  48. return setError("Email address must not be empty.");
  49. }
  50. if (!emailValidator.validate(email)) {
  51. return setError("Email address is not valid.");
  52. }
  53. if (password.trim().length < 8) {
  54. return setError("Password must be at least 8 chars long.");
  55. }
  56. setError("");
  57. if (type === "login") {
  58. setLoading(s => ({ ...s, login: true }));
  59. try {
  60. await login(formState.values);
  61. Router.push("/");
  62. } catch (error) {
  63. setError(error.response.data.error);
  64. }
  65. }
  66. if (type === "signup") {
  67. setLoading(s => ({ ...s, signup: true }));
  68. try {
  69. await axios.post(API.SIGNUP, { email, password });
  70. setVerifying(true);
  71. } catch (error) {
  72. setError(error.response.data.error);
  73. }
  74. }
  75. setLoading({ login: false, signup: false });
  76. };
  77. }
  78. if (isAuthenticated) {
  79. return null;
  80. }
  81. return (
  82. <BodyWrapper>
  83. <ColCenterV flex="0 0 auto" mt={24} mb={64}>
  84. {verifying ? (
  85. <Text fontWeight={300} as="h2" textAlign="center">
  86. A verification email has been sent to{" "}
  87. <Email>{formState.values.email}</Email>.
  88. </Text>
  89. ) : (
  90. <LoginForm id="login-form" onSubmit={onSubmit("login")}>
  91. <Text {...label("email")} as="label" fontWeight={700} mb={2}>
  92. Email address:
  93. </Text>
  94. <TextInput
  95. {...email("email")}
  96. height={[56, 64, 72]}
  97. mb={[24, 32, 36]}
  98. autoFocus
  99. />
  100. <Text {...label("password")} as="label" fontWeight={700} mb={2}>
  101. Password (min chars: 8):
  102. </Text>
  103. <TextInput
  104. {...password("password")}
  105. height={[56, 64, 72]}
  106. mb={[24, 32, 36]}
  107. />
  108. <Flex justifyContent="center">
  109. <Button
  110. flex="1 1 auto"
  111. mr={["8px", 16]}
  112. height={[44, 56]}
  113. icon={loading.login ? "loader" : "login"}
  114. onClick={onSubmit("login")}
  115. >
  116. Log in
  117. </Button>
  118. <Button
  119. flex="1 1 auto"
  120. ml={["8px", 16]}
  121. height={[44, 56]}
  122. icon={loading.signup ? "loader" : "signup"}
  123. color="purple"
  124. onClick={onSubmit("signup")}
  125. >
  126. Sign up
  127. </Button>
  128. </Flex>
  129. <Link href="/reset-password">
  130. <ALink
  131. href="/reset-password"
  132. title="Forget password"
  133. fontSize={14}
  134. alignSelf="flex-start"
  135. my={16}
  136. >
  137. Forgot your password?
  138. </ALink>
  139. </Link>
  140. <Text color="red" fontWeight={400} mt={1}>
  141. {error}
  142. </Text>
  143. </LoginForm>
  144. )}
  145. </ColCenterV>
  146. </BodyWrapper>
  147. );
  148. };
  149. export default LoginPage;