mirror of
https://github.com/spacebarchat/client.git
synced 2024-11-22 10:22:30 +01:00
Merge branch 'react' into feat/instancepicker
This commit is contained in:
commit
8f7780992e
156
src/components/AuthComponents.tsx
Normal file
156
src/components/AuthComponents.tsx
Normal file
@ -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;
|
||||
`;
|
@ -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) {
|
||||
<Wrapper
|
||||
onClick={doNavigate}
|
||||
active={props.active}
|
||||
hasImage={!!guild?.icon}
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
>
|
||||
@ -87,7 +93,13 @@ function GuildItem(props: Props) {
|
||||
height={48}
|
||||
/>
|
||||
) : (
|
||||
<span style={{ fontSize: "18px", fontWeight: "bold", cursor: "pointer" }}>
|
||||
<span
|
||||
style={{
|
||||
fontSize: "18px",
|
||||
fontWeight: "bold",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
{guild?.acronym}
|
||||
</span>
|
||||
)}
|
||||
|
@ -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 (
|
||||
<Wrapper>
|
||||
<AuthBox>
|
||||
<AuthContainer>
|
||||
<HeaderContainer>
|
||||
<SpacebarLogoBlue height={48} width="auto" />
|
||||
<Header>Two-factor authentication</Header>
|
||||
<SubHeader>
|
||||
You can use a backup code or your two-factor
|
||||
@ -238,9 +108,7 @@ function MFA(props: IAPILoginResponseMFARequired) {
|
||||
style={{ marginTop: 0 }}
|
||||
>
|
||||
<LabelWrapper error={!!errors.code}>
|
||||
<InputLabel>
|
||||
Enter Spacebar Auth/Backup Code
|
||||
</InputLabel>
|
||||
<InputLabel>Enter 2FA/Backup Code</InputLabel>
|
||||
{errors.code && (
|
||||
<InputErrorText>
|
||||
<>
|
||||
@ -262,13 +130,13 @@ function MFA(props: IAPILoginResponseMFARequired) {
|
||||
</InputWrapper>
|
||||
</InputContainer>
|
||||
|
||||
<LoginButton
|
||||
<SubmitButton
|
||||
variant="primary"
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
>
|
||||
Log In
|
||||
</LoginButton>
|
||||
</SubmitButton>
|
||||
|
||||
{/* <Link
|
||||
onClick={() => {
|
||||
@ -295,7 +163,7 @@ function MFA(props: IAPILoginResponseMFARequired) {
|
||||
</Link>
|
||||
</FormContainer>
|
||||
</HeaderContainer>
|
||||
</AuthBox>
|
||||
</AuthContainer>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
81
src/components/modals/ForgotPasswordModal.tsx
Normal file
81
src/components/modals/ForgotPasswordModal.tsx
Normal file
@ -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 (
|
||||
<ModalContainer>
|
||||
<ModalWrapper>
|
||||
<ModalCloseWrapper>
|
||||
<button
|
||||
onClick={closeModal}
|
||||
style={{
|
||||
background: "none",
|
||||
border: "none",
|
||||
outline: "none",
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
icon="mdiClose"
|
||||
size={1}
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
color: "var(--text)",
|
||||
}}
|
||||
/>
|
||||
</button>
|
||||
</ModalCloseWrapper>
|
||||
|
||||
<ModalHeader>
|
||||
<ModalHeaderText>Instructions Sent</ModalHeaderText>
|
||||
</ModalHeader>
|
||||
|
||||
<ModelContentContainer>
|
||||
We sent instructions to change your password to
|
||||
user@example.com, please check both your inbox and spam
|
||||
folder.
|
||||
</ModelContentContainer>
|
||||
|
||||
<ModalFooter>
|
||||
<SubmitButton
|
||||
variant="filled"
|
||||
size="med"
|
||||
onClick={closeModal}
|
||||
>
|
||||
Okay
|
||||
</SubmitButton>
|
||||
</ModalFooter>
|
||||
</ModalWrapper>
|
||||
</ModalContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default ForgotPasswordModal;
|
@ -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<IAPILoginResponseMFARequired>();
|
||||
const captchaRef = React.useRef<HCaptchaLib>(null);
|
||||
const [debounce, setDebounce] = React.useState<NodeJS.Timeout | null>(null);
|
||||
const { openModal } = useModals();
|
||||
|
||||
const {
|
||||
register,
|
||||
@ -297,6 +171,10 @@ function LoginPage() {
|
||||
onSubmit();
|
||||
};
|
||||
|
||||
const forgotPassword = () => {
|
||||
openModal(ForgotPasswordModal);
|
||||
};
|
||||
|
||||
if (captchaSiteKey) {
|
||||
return (
|
||||
<HCaptcha
|
||||
@ -313,10 +191,18 @@ function LoginPage() {
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<AuthBox>
|
||||
<AuthContainer>
|
||||
<HeaderContainer>
|
||||
<Header>Welcome Back!</Header>
|
||||
<SubHeader>We're so excited to see you again!</SubHeader>
|
||||
{AUTH_NO_BRANDING ? (
|
||||
<>
|
||||
<Header>Login to Spacebar</Header>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<SpacebarLogoBlue height={48} width="auto" />
|
||||
<SubHeader noBranding>Log into Spacebar</SubHeader>
|
||||
</>
|
||||
)}
|
||||
</HeaderContainer>
|
||||
|
||||
<FormContainer onSubmit={onSubmit}>
|
||||
@ -394,6 +280,7 @@ function LoginPage() {
|
||||
<InputWrapper>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
autoFocus
|
||||
{...register("login", { required: true })}
|
||||
error={!!errors.login}
|
||||
@ -402,7 +289,7 @@ function LoginPage() {
|
||||
</InputWrapper>
|
||||
</InputContainer>
|
||||
|
||||
<InputContainer marginBottom={false}>
|
||||
<InputContainer marginBottom>
|
||||
<LabelWrapper error={!!errors.password}>
|
||||
<InputLabel>Password</InputLabel>
|
||||
{errors.password && (
|
||||
@ -417,6 +304,7 @@ function LoginPage() {
|
||||
<InputWrapper>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
{...register("password", { required: true })}
|
||||
error={!!errors.password}
|
||||
disabled={loading}
|
||||
@ -424,41 +312,34 @@ function LoginPage() {
|
||||
</InputWrapper>
|
||||
</InputContainer>
|
||||
|
||||
<PasswordResetLink
|
||||
onClick={() => {
|
||||
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 */}
|
||||
{/* <PasswordResetLink onClick={forgotPassword} type="button">
|
||||
Forgot your password?
|
||||
</PasswordResetLink>
|
||||
</PasswordResetLink> */}
|
||||
|
||||
<LoginButton
|
||||
<SubmitButton
|
||||
variant="primary"
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
>
|
||||
Log In
|
||||
</LoginButton>
|
||||
Login
|
||||
</SubmitButton>
|
||||
|
||||
<RegisterContainer>
|
||||
<RegisterLabel>
|
||||
Don't have an account?
|
||||
</RegisterLabel>
|
||||
<RegisterLink
|
||||
<AuthSwitchPageContainer>
|
||||
<AuthSwitchPageLabel>
|
||||
New to Spacebar?
|
||||
</AuthSwitchPageLabel>
|
||||
<AuthSwitchPageLink
|
||||
onClick={() => {
|
||||
navigate("/register");
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
Sign Up
|
||||
</RegisterLink>
|
||||
</RegisterContainer>
|
||||
Register
|
||||
</AuthSwitchPageLink>
|
||||
</AuthSwitchPageContainer>
|
||||
</FormContainer>
|
||||
</AuthBox>
|
||||
</AuthContainer>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
@ -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 (
|
||||
<Wrapper>
|
||||
<AuthBox>
|
||||
<HeaderContainer>
|
||||
<Header>Create an account</Header>
|
||||
{/* <SubHeader>We're so excited to see you again!</SubHeader> */}
|
||||
</HeaderContainer>
|
||||
<AuthContainer>
|
||||
{AUTH_NO_BRANDING ? (
|
||||
<>
|
||||
<Header>Create an account</Header>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<SpacebarLogoBlue height={48} width="auto" />
|
||||
<SubHeader noBranding>Create an account</SubHeader>
|
||||
</>
|
||||
)}
|
||||
|
||||
<FormContainer onSubmit={onSubmit}>
|
||||
<InputContainer
|
||||
@ -304,6 +203,7 @@ function RegistrationPage() {
|
||||
<InputWrapper>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
autoFocus
|
||||
{...register("email", { required: true })}
|
||||
error={!!errors.email}
|
||||
@ -330,6 +230,7 @@ function RegistrationPage() {
|
||||
<InputWrapper>
|
||||
<Input
|
||||
{...register("username", { required: true })}
|
||||
placeholder="Username"
|
||||
error={!!errors.username}
|
||||
disabled={loading}
|
||||
/>
|
||||
@ -351,6 +252,7 @@ function RegistrationPage() {
|
||||
<InputWrapper>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
{...register("password", { required: true })}
|
||||
error={!!errors.password}
|
||||
disabled={loading}
|
||||
@ -396,24 +298,29 @@ function RegistrationPage() {
|
||||
</InputWrapper>
|
||||
</InputContainer>
|
||||
|
||||
<LoginButton
|
||||
<SubmitButton
|
||||
variant="primary"
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
>
|
||||
Create Account
|
||||
</LoginButton>
|
||||
</SubmitButton>
|
||||
|
||||
<LoginLink
|
||||
onClick={() => {
|
||||
navigate("/login", { replace: true });
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
Already have an account?
|
||||
</LoginLink>
|
||||
<AuthSwitchPageContainer>
|
||||
<AuthSwitchPageLabel>
|
||||
Already have an account?
|
||||
</AuthSwitchPageLabel>
|
||||
<AuthSwitchPageLink
|
||||
onClick={() => {
|
||||
navigate("/login");
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
Login
|
||||
</AuthSwitchPageLink>
|
||||
</AuthSwitchPageContainer>
|
||||
</FormContainer>
|
||||
</AuthBox>
|
||||
</AuthContainer>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user