login.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. import { useFormState } from "react-use-form-state";
  2. import React, { useEffect, useState } from "react";
  3. import { Flex } from "reflexbox/styled-components";
  4. import emailValidator from "email-validator";
  5. import styled from "styled-components";
  6. import Router from "next/router";
  7. import Link from "next/link";
  8. import axios from "axios";
  9. import { useStoreState, useStoreActions } from "../store";
  10. import { APIv2, DISALLOW_REGISTRATION } from "../consts";
  11. import { ColCenterV } from "../components/Layout";
  12. import AppWrapper from "../components/AppWrapper";
  13. import { TextInput } from "../components/Input";
  14. import { fadeIn } from "../helpers/animations";
  15. import { Button } from "../components/Button";
  16. import Text, { H2 } from "../components/Text";
  17. import ALink from "../components/ALink";
  18. import Icon from "../components/Icon";
  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" && !DISALLOW_REGISTRATION) {
  68. setLoading(s => ({ ...s, signup: true }));
  69. try {
  70. await axios.post(APIv2.AuthSignup, { 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 maxWidth="100%" px={3} flex="0 0 auto" mt={4}>
  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. fontSize={[15, 16]}
  100. px={[4, 40]}
  101. mb={[24, 4]}
  102. width={[300, 400]}
  103. maxWidth="100%"
  104. autoFocus
  105. />
  106. <Text {...label("password")} as="label" mb={2} bold>
  107. Password{!DISALLOW_REGISTRATION ? " (min chars: 8)" : ""}:
  108. </Text>
  109. <TextInput
  110. {...password("password")}
  111. placeholder="Password..."
  112. px={[4, 40]}
  113. height={[56, 64, 72]}
  114. fontSize={[15, 16]}
  115. width={[300, 400]}
  116. maxWidth="100%"
  117. mb={[24, 4]}
  118. />
  119. <Flex justifyContent="center">
  120. <Button
  121. flex="1 1 auto"
  122. mr={!DISALLOW_REGISTRATION ? ["8px", 16] : 0}
  123. height={[44, 56]}
  124. onClick={onSubmit("login")}
  125. >
  126. <Icon
  127. name={loading.login ? "spinner" : "login"}
  128. stroke="white"
  129. mr={2}
  130. />
  131. Log in
  132. </Button>
  133. {!DISALLOW_REGISTRATION && (
  134. <Button
  135. flex="1 1 auto"
  136. ml={["8px", 16]}
  137. height={[44, 56]}
  138. color="purple"
  139. onClick={onSubmit("signup")}
  140. >
  141. <Icon
  142. name={loading.signup ? "spinner" : "signup"}
  143. stroke="white"
  144. mr={2}
  145. />
  146. Sign up
  147. </Button>
  148. )}
  149. </Flex>
  150. <Link href="/reset-password">
  151. <ALink
  152. href="/reset-password"
  153. title="Forget password"
  154. fontSize={14}
  155. alignSelf="flex-start"
  156. my={16}
  157. >
  158. Forgot your password?
  159. </ALink>
  160. </Link>
  161. <Text color="red" mt={1} normal>
  162. {error}
  163. </Text>
  164. </LoginForm>
  165. )}
  166. </ColCenterV>
  167. </AppWrapper>
  168. );
  169. };
  170. export default LoginPage;