diff --git a/src/components/AuthComponents.tsx b/src/components/AuthComponents.tsx new file mode 100644 index 0000000..106f981 --- /dev/null +++ b/src/components/AuthComponents.tsx @@ -0,0 +1,156 @@ +import styled from "styled-components"; +import Button from "./Button"; +import Container from "./Container"; + +export const Wrapper = styled(Container)` + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + background-color: var(--background-tertiary); +`; + +export const AuthContainer = styled(Container)` + background-color: var(--background-primary-alt); + padding: 32px; + font-size: 18px; + color: var(--text-muted); + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + + @media (max-width: 480px) { + width: 100%; + height: 100%; + } + + @media (min-width: 480px) { + width: 480px; + border-radius: 18px; + } +`; + +export const HeaderContainer = styled.div` + width: 100%; +`; + +export const Header = styled.h1` + margin-bottom: 3px; + color: var(--text); + font-weight: 600; + font-size: 24px; +`; + +export const SubHeader = styled.h2<{ noBranding?: boolean }>` + margin-top: 3px; + color: var(--text-muted); + font-weight: 400; + font-size: ${(props) => (props.noBranding ? "20px" : "16px")}; +`; + +export const FormContainer = styled.form` + width: 100%; +`; + +export const InputContainer = styled.h1<{ marginBottom: boolean }>` + margin-bottom: ${(props) => (props.marginBottom ? "20px" : "0")}; + display: flex; + flex-direction: column; + align-items: flex-start; +`; + +export const LabelWrapper = styled.div<{ error?: boolean }>` + display: flex; + flex-direction: row; + margin-bottom: 8px; + color: ${(props) => (props.error ? "var(--error)" : "var(--text)")}; +`; + +export const InputErrorText = styled.label` + font-size: 14px; + font-weight: 400; + font-style: italic; +`; + +export const InputLabel = styled.label` + font-size: 14px; + font-weight: 700; +`; + +export const InputWrapper = styled.div` + width: 100%; + display: flex; +`; + +// TODO: Fix border hover causing small layout shift +export const Input = styled.input<{ error?: boolean }>` + outline: none; + background: var(--background-secondary); + padding: 10px; + font-size: 16px; + flex: 1; + border-radius: 12px; + color: var(--text); + margin: 0; + border: none; + aria-invalid: ${(props) => (props.error ? "true" : "false")}; + border: ${(props) => (props.error ? "1px solid var(--error)" : "none")}; + + &:focus { + border: 1px solid var(--primary); + } +`; + +export const Link = styled.button` + margin-bottom: 20px; + margin-top: 4px; + padding: 2px 0; + font-size: 14px; + display: flex; + color: var(--text-link); + background: none; + border: none; + + &:hover { + text-decoration: underline; + cursor: pointer; + } +`; + +export const SubmitButton = styled(Button)` + margin-bottom: 8px; + width: 100%; + min-width: 130px; + min-height: 44px; + font-size: 14px; +`; + +export const AuthSwitchPageContainer = styled.div` + margin-top: 4px; + text-align: initial; +`; + +export const AuthSwitchPageLabel = styled.label` + font-size: 14px; +`; + +export const AuthSwitchPageLink = styled.button` + font-size: 14px; + background: none; + border: none; + color: var(--text-link); + + @media (max-width: 480px) { + display: inline-block; + } + + &:hover { + text-decoration: underline; + cursor: pointer; + } +`; + +export const Divider = styled.span` + padding: 0 4px; +`; diff --git a/src/components/GuildItem.tsx b/src/components/GuildItem.tsx index 0872609..80b92ae 100644 --- a/src/components/GuildItem.tsx +++ b/src/components/GuildItem.tsx @@ -13,7 +13,7 @@ import GuildSidebarListItem from "./GuildSidebarListItem"; import SidebarPill, { PillType } from "./SidebarPill"; import Tooltip from "./Tooltip"; -const Wrapper = styled(Container)<{ active?: boolean }>` +const Wrapper = styled(Container)<{ active?: boolean; hasImage?: boolean }>` display: flex; align-items: center; justify-content: center; @@ -21,13 +21,18 @@ const Wrapper = styled(Container)<{ active?: boolean }>` height: 48px; border-radius: ${(props) => (props.active ? "30%" : "50%")}; background-color: ${(props) => - props.active ? "var(--primary)" : "var(--background-secondary)"}; + props.hasImage + ? "transparent" + : props.active + ? "var(--primary)" + : "var(--background-secondary)"}; transition: border-radius 0.2s ease, background-color 0.2s ease; cursor: pointer; &:hover { border-radius: 30%; - background-color: var(--primary); + background-color: ${(props) => + props.hasImage ? "transparent" : "var(--primary)"}; } `; @@ -71,6 +76,7 @@ function GuildItem(props: Props) { setHovered(true)} onMouseLeave={() => setHovered(false)} > @@ -87,7 +93,13 @@ function GuildItem(props: Props) { height={48} /> ) : ( - + {guild?.acronym} )} diff --git a/src/components/MFA.tsx b/src/components/MFA.tsx index 461069b..5845bf7 100644 --- a/src/components/MFA.tsx +++ b/src/components/MFA.tsx @@ -2,7 +2,7 @@ import { Routes } from "@spacebarchat/spacebar-api-types/v9"; import React from "react"; import { useForm } from "react-hook-form"; import { useNavigate } from "react-router-dom"; -import styled from "styled-components"; +import { ReactComponent as SpacebarLogoBlue } from "../assets/images/logo/Logo-Blue.svg"; import { useAppStore } from "../stores/AppStore"; import { IAPIError, @@ -11,154 +11,23 @@ import { IAPITOTPRequest, } from "../utils/interfaces/api"; import { messageFromFieldError } from "../utils/messageFromFieldError"; -import Button from "./Button"; -import Container from "./Container"; - -export const Wrapper = styled(Container)` - display: flex; - justify-content: center; - align-items: center; - height: 100vh; - background-color: var(--background-secondary); -`; - -export const AuthBox = styled(Container)` - background-color: var(--background-primary-alt); - padding: 32px; - font-size: 18px; - color: var(--text-muted); - display: flex; - flex-direction: column; - align-items: center; - text-align: center; - - @media (max-width: 480px) { - width: 100%; - height: 100%; - } - - @media (min-width: 480px) { - width: 480px; - border-radius: 18px; - } -`; - -export const HeaderContainer = styled.div` - width: 100%; -`; - -export const Header = styled.h1` - font-weight: 600; - margin-bottom: 8px; - font-size: 24px; - color: var(--text); -`; - -export const SubHeader = styled.h2` - color: var(--text-muted); - font-weight: 400; - font-size: 16px; - margin-bottom: 40px; -`; - -export const FormContainer = styled.form` - width: 100%; -`; - -export const InputContainer = styled.h1<{ marginBottom: boolean }>` - margin-bottom: ${(props) => (props.marginBottom ? "20px" : "0")}; - display: flex; - flex-direction: column; - align-items: flex-start; -`; - -export const LabelWrapper = styled.div<{ error?: boolean }>` - display: flex; - flex-direction: row; - margin-bottom: 8px; - color: ${(props) => (props.error ? "var(--error)" : "#b1b5bc")}; -`; - -export const InputErrorText = styled.label` - font-size: 14px; - font-weight: 400; - font-style: italic; -`; - -export const InputLabel = styled.label` - font-size: 14px; - font-weight: 700; -`; - -export const InputWrapper = styled.div` - width: 100%; - display: flex; -`; - -export const Input = styled.input<{ error?: boolean }>` - outline: none; - background: var(--background-secondary); - padding: 10px; - font-size: 16px; - flex: 1; - border-radius: 12px; - color: var(--text); - margin: 0; - border: none; - aria-invalid: ${(props) => (props.error ? "true" : "false")}; -`; - -export const Link = styled.button` - margin-bottom: 20px; - margin-top: 4px; - padding: 2px 0; - font-size: 14px; - display: flex; - color: var(--text-link); - background: none; - border: none; - - &:hover { - text-decoration: underline; - cursor: pointer; - } -`; - -export const LoginButton = styled(Button)` - margin-bottom: 8px; - width: 100%; - min-width: 130px; - min-height: 44px; -`; - -export const RegisterContainer = styled.div` - margin-top: 4px; - text-align: initial; -`; - -export const RegisterLabel = styled.label` - font-size: 14px; -`; - -export const RegisterLink = styled.button` - font-size: 14px; - background: none; - border: none; - color: var(--text-link); - - @media (max-width: 480px) { - display: inline-block; - } - - &:hover { - text-decoration: underline; - cursor: pointer; - } -`; - -export const Divider = styled.span` - padding: 0 4px; -`; +import { + AuthContainer, + Divider, + FormContainer, + Header, + HeaderContainer, + Input, + InputContainer, + InputErrorText, + InputLabel, + InputWrapper, + LabelWrapper, + Link, + SubHeader, + SubmitButton, + Wrapper, +} from "./AuthComponents"; type FormValues = { code: string; @@ -224,8 +93,9 @@ function MFA(props: IAPILoginResponseMFARequired) { return ( - + +
Two-factor authentication
You can use a backup code or your two-factor @@ -238,9 +108,7 @@ function MFA(props: IAPILoginResponseMFARequired) { style={{ marginTop: 0 }} > - - Enter Spacebar Auth/Backup Code - + Enter 2FA/Backup Code {errors.code && ( <> @@ -262,13 +130,13 @@ function MFA(props: IAPILoginResponseMFARequired) { - Log In - + {/* { @@ -295,7 +163,7 @@ function MFA(props: IAPILoginResponseMFARequired) {
-
+
); } diff --git a/src/components/modals/ForgotPasswordModal.tsx b/src/components/modals/ForgotPasswordModal.tsx new file mode 100644 index 0000000..badbc28 --- /dev/null +++ b/src/components/modals/ForgotPasswordModal.tsx @@ -0,0 +1,81 @@ +import { useModals } from "@mattjennings/react-modal-stack"; +import styled from "styled-components"; +import Icon from "../Icon"; +import { + ModalActionItem, + ModalCloseWrapper, + ModalContainer, + ModalFooter, + ModalHeaderText, + ModalWrapper, + ModelContentContainer, +} from "./ModalComponents"; + +export const ModalHeader = styled.div` + padding: 16px; +`; + +const SubmitButton = styled(ModalActionItem)` + transition: background-color 0.2s ease-in-out; + font-size: 16px; + + &:hover { + background-color: var(--background-secondary-highlight); + } +`; + +function ForgotPasswordModal() { + const { openModal, closeModal } = useModals(); + + if (!open) { + return null; + } + + return ( + + + + + + + + Instructions Sent + + + + We sent instructions to change your password to + user@example.com, please check both your inbox and spam + folder. + + + + + Okay + + + + + ); +} + +export default ForgotPasswordModal; diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx index 0fdfd2d..00c06fd 100644 --- a/src/pages/LoginPage.tsx +++ b/src/pages/LoginPage.tsx @@ -1,14 +1,34 @@ import HCaptchaLib from "@hcaptcha/react-hcaptcha"; +import { useModals } from "@mattjennings/react-modal-stack"; import { Routes } from "@spacebarchat/spacebar-api-types/v9"; import React from "react"; import { useForm } from "react-hook-form"; import { useNavigate } from "react-router-dom"; -import styled from "styled-components"; -import Button from "../components/Button"; -import Container from "../components/Container"; -import HCaptcha from "../components/HCaptcha"; +import { ReactComponent as SpacebarLogoBlue } from "../assets/images/logo/Logo-Blue.svg"; +import { + AuthContainer, + AuthSwitchPageContainer, + AuthSwitchPageLabel, + AuthSwitchPageLink, + Divider, + FormContainer, + Header, + Input, + InputContainer, + InputErrorText, + InputLabel, + InputWrapper, + LabelWrapper, + SubHeader, + SubmitButton, + Wrapper, +} from "../components/AuthComponents"; +import HCaptcha, { HeaderContainer } from "../components/HCaptcha"; import MFA from "../components/MFA"; -import { useAppStore } from "../stores/AppStore"; +import ForgotPasswordModal from "../components/modals/ForgotPasswordModal"; +import { AUTH_NO_BRANDING, useAppStore } from "../stores/AppStore"; +import { Globals } from "../utils/Globals"; +import REST from "../utils/REST"; import { IAPILoginRequest, IAPILoginResponse, @@ -16,153 +36,6 @@ import { IAPILoginResponseMFARequired, } from "../utils/interfaces/api"; import { messageFromFieldError } from "../utils/messageFromFieldError"; -import REST from "../utils/REST"; -import { Globals } from "../utils/Globals"; - -export const Wrapper = styled(Container)` - display: flex; - justify-content: center; - align-items: center; - height: 100vh; - background-color: var(--background-tertiary); -`; - -export const AuthBox = styled(Container)` - background-color: var(--background-primary-alt); - padding: 32px; - font-size: 18px; - color: var(--text-muted); - display: flex; - flex-direction: column; - align-items: center; - text-align: center; - - @media (max-width: 480px) { - width: 100%; - height: 100%; - } - - @media (min-width: 480px) { - width: 480px; - border-radius: 18px; - } -`; - -export const HeaderContainer = styled.div` - width: 100%; -`; - -export const Header = styled.h1` - font-weight: 600; - margin-bottom: 8px; - font-size: 24px; - color: var(--text); -`; - -export const SubHeader = styled.h2` - color: var(--text-muted); - font-weight: 400; - font-size: 16px; -`; - -export const FormContainer = styled.form` - width: 100%; -`; - -export const InputContainer = styled.h1<{ marginBottom: boolean }>` - margin-bottom: ${(props) => (props.marginBottom ? "20px" : "0")}; - display: flex; - flex-direction: column; - align-items: flex-start; -`; - -export const LabelWrapper = styled.div<{ error?: boolean }>` - display: flex; - flex-direction: row; - margin-bottom: 8px; - color: ${(props) => (props.error ? "var(--error)" : "#b1b5bc")}; -`; - -export const InputErrorText = styled.label` - font-size: 14px; - font-weight: 400; - font-style: italic; -`; - -export const InputLabel = styled.label` - font-size: 14px; - font-weight: 700; -`; - -export const InputWrapper = styled.div` - width: 100%; - display: flex; -`; - -export const Input = styled.input<{ error?: boolean }>` - outline: none; - background: var(--background-secondary); - padding: 10px; - font-size: 16px; - flex: 1; - border-radius: 12px; - color: var(--text); - margin: 0; - border: none; - aria-invalid: ${(props) => (props.error ? "true" : "false")}; -`; - -export const PasswordResetLink = styled.button` - margin-bottom: 20px; - margin-top: 4px; - padding: 2px 0; - font-size: 14px; - display: flex; - color: var(--text-link); - background: none; - border: none; - - &:hover { - text-decoration: underline; - cursor: pointer; - } -`; - -export const LoginButton = styled(Button)` - margin-bottom: 8px; - width: 100%; - min-width: 130px; - min-height: 44px; -`; - -export const RegisterContainer = styled.div` - margin-top: 4px; - text-align: initial; -`; - -export const RegisterLabel = styled.label` - font-size: 14px; -`; - -export const RegisterLink = styled.button` - font-size: 14px; - background: none; - border: none; - color: var(--text-link); - - @media (max-width: 480px) { - display: inline-block; - } - - &:hover { - text-decoration: underline; - cursor: pointer; - } -`; - -export const Divider = styled.span` - padding: 0 4px; -`; type FormValues = { login: string; @@ -180,6 +53,7 @@ function LoginPage() { React.useState(); const captchaRef = React.useRef(null); const [debounce, setDebounce] = React.useState(null); + const { openModal } = useModals(); const { register, @@ -297,6 +171,10 @@ function LoginPage() { onSubmit(); }; + const forgotPassword = () => { + openModal(ForgotPasswordModal); + }; + if (captchaSiteKey) { return ( - + -
Welcome Back!
- We're so excited to see you again! + {AUTH_NO_BRANDING ? ( + <> +
Login to Spacebar
+ + ) : ( + <> + + Log into Spacebar + + )}
@@ -394,6 +280,7 @@ function LoginPage() { - + Password {errors.password && ( @@ -417,6 +304,7 @@ function LoginPage() { - { - window.open( - "https://youtu.be/dQw4w9WgXcQ", - "_blank", - ); - }} - type="button" - > + {/* TODO: I need to figure this out, clicking this should submit the form or even a different function with only email being required */} + {/* Forgot your password? - + */} - - Log In - + Login + - - - Don't have an account?  - - + + New to Spacebar?  + + { navigate("/register"); }} type="button" > - Sign Up - - + Register + + -
+
); } diff --git a/src/pages/RegistrationPage.tsx b/src/pages/RegistrationPage.tsx index 33f3735..ac2e48b 100644 --- a/src/pages/RegistrationPage.tsx +++ b/src/pages/RegistrationPage.tsx @@ -3,12 +3,28 @@ import { Routes } from "@spacebarchat/spacebar-api-types/v9"; import React from "react"; import { useForm } from "react-hook-form"; import { useNavigate } from "react-router-dom"; -import styled from "styled-components"; -import Button from "../components/Button"; -import Container from "../components/Container"; +import { ReactComponent as SpacebarLogoBlue } from "../assets/images/logo/Logo-Blue.svg"; +import { + AuthContainer, + AuthSwitchPageContainer, + AuthSwitchPageLabel, + AuthSwitchPageLink, + Divider, + FormContainer, + Header, + Input, + InputContainer, + InputErrorText, + InputLabel, + InputWrapper, + LabelWrapper, + SubHeader, + SubmitButton, + Wrapper, +} from "../components/AuthComponents"; import DOBInput from "../components/DOBInput"; import HCaptcha from "../components/HCaptcha"; -import { useAppStore } from "../stores/AppStore"; +import { AUTH_NO_BRANDING, useAppStore } from "../stores/AppStore"; import { IAPILoginResponseSuccess, IAPIRegisterRequest, @@ -16,129 +32,6 @@ import { } from "../utils/interfaces/api"; import { messageFromFieldError } from "../utils/messageFromFieldError"; -const Wrapper = styled(Container)` - display: flex; - justify-content: center; - align-items: center; - height: 100vh; - background-color: var(--background-tertiary); -`; - -const AuthBox = styled(Container)` - background-color: var(--background-primary-alt); - padding: 32px; - font-size: 18px; - color: var(--text-muted); - display: flex; - flex-direction: column; - align-items: center; - text-align: center; - - @media (max-width: 480px) { - width: 100%; - height: 100%; - } - - @media (min-width: 480px) { - width: 480px; - border-radius: 18px; - } -`; - -const HeaderContainer = styled.div` - width: 100%; -`; - -const Header = styled.h1` - font-weight: 600; - margin-bottom: 8px; - font-size: 24px; - color: var(--text); -`; - -// const SubHeader = styled.h2` -// color: var(--text-muted); -// font-weight: 400; -// font-size: 16px; -// `; - -const FormContainer = styled.form` - width: 100%; -`; - -const InputContainer = styled.h1<{ marginBottom: boolean }>` - margin-bottom: ${(props) => (props.marginBottom ? "20px" : "0")}; - display: flex; - flex-direction: column; - align-items: flex-start; -`; - -const LabelWrapper = styled.div<{ error?: boolean }>` - display: flex; - flex-direction: row; - margin-bottom: 8px; - color: ${(props) => (props.error ? "var(--error)" : "#b1b5bc")}; -`; - -const InputErrorText = styled.label` - font-size: 14px; - font-weight: 400; - font-style: italic; -`; - -const InputLabel = styled.label` - font-size: 14px; - font-weight: 700; -`; - -const InputWrapper = styled.div` - width: 100%; - display: flex; -`; - -const Input = styled.input<{ error?: boolean }>` - outline: none; - background: var(--background-secondary); - padding: 10px; - font-size: 16px; - border-radius: 12px; - color: var(--text); - margin: 0; - border: none; - aria-invalid: ${(props) => (props.error ? "true" : "false")}; - box-sizing: border-box; - width: 100%; -`; - -const LoginButton = styled(Button)` - margin-bottom: 8px; - width: 100%; - min-width: 130px; - min-height: 44px; -`; - -const LoginLink = styled.button` - margin-top: 4px; - float: left; - font-size: 14px; - background: none; - border: none; - color: var(--text-link); - - @media (max-width: 480px) { - display: inline-block; - } - - &:hover { - text-decoration: underline; - cursor: pointer; - } -`; - -const Divider = styled.span` - padding: 0 4px; -`; - type FormValues = { email: string; username: string; @@ -279,11 +172,17 @@ function RegistrationPage() { return ( - - -
Create an account
- {/* We're so excited to see you again! */} -
+ + {AUTH_NO_BRANDING ? ( + <> +
Create an account
+ + ) : ( + <> + + Create an account + + )} @@ -351,6 +252,7 @@ function RegistrationPage() { - Create Account - + - { - navigate("/login", { replace: true }); - }} - type="button" - > - Already have an account? - + + + Already have an account?  + + { + navigate("/login"); + }} + type="button" + > + Login + + -
+
); } diff --git a/src/stores/AppStore.ts b/src/stores/AppStore.ts index 05d80b5..71d2b00 100644 --- a/src/stores/AppStore.ts +++ b/src/stores/AppStore.ts @@ -12,6 +12,9 @@ import PrivateChannelStore from "./PrivateChannelStore"; import ThemeStore from "./ThemeStore"; import UserStore from "./UserStore"; +// dev thing to force toggle branding on auth pages for testing. +export const AUTH_NO_BRANDING = false; + export default class AppStore { // whether the gateway is ready @observable isGatewayReady = false;