login.tsx 5.4 KB

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