login.tsx 5.0 KB

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