mirror of
https://github.com/revoltchat/revite.git
synced 2024-11-22 07:00:58 -05:00
Port Login UI
This commit is contained in:
parent
aa81ebb298
commit
68a35751b3
18 changed files with 749 additions and 3 deletions
|
@ -25,6 +25,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@fontsource/open-sans": "^4.4.5",
|
||||
"@hcaptcha/react-hcaptcha": "^0.3.6",
|
||||
"@preact/preset-vite": "^2.0.0",
|
||||
"@styled-icons/bootstrap": "^10.34.0",
|
||||
"@styled-icons/feather": "^10.34.0",
|
||||
|
@ -36,6 +37,7 @@
|
|||
"@typescript-eslint/eslint-plugin": "^4.27.0",
|
||||
"@typescript-eslint/parser": "^4.27.0",
|
||||
"dayjs": "^1.10.5",
|
||||
"detect-browser": "^5.2.0",
|
||||
"eslint": "^7.28.0",
|
||||
"eslint-config-preact": "^1.1.4",
|
||||
"localforage": "^1.9.0",
|
||||
|
@ -43,6 +45,7 @@
|
|||
"prettier": "^2.3.1",
|
||||
"react-device-detect": "^1.17.0",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-hook-form": "6.3.0",
|
||||
"react-overlapping-panels": "1.1.2-patch.0",
|
||||
"react-redux": "^7.2.4",
|
||||
"react-router-dom": "^5.2.0",
|
||||
|
|
|
@ -2,13 +2,15 @@ import { CheckAuth } from "./context/revoltjs/CheckAuth";
|
|||
import { Route, Switch } from "react-router-dom";
|
||||
import Context from "./context";
|
||||
|
||||
import { Login } from "./pages/login/Login";
|
||||
|
||||
export function App() {
|
||||
return (
|
||||
<Context>
|
||||
<Switch>
|
||||
<Route path="/login">
|
||||
<CheckAuth>
|
||||
<h1>login</h1>
|
||||
<Login />
|
||||
</CheckAuth>
|
||||
</Route>
|
||||
<Route path="/">
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { isTouchscreenDevice } from "../lib/isTouchscreenDevice";
|
||||
import { createGlobalStyle } from "styled-components";
|
||||
import { Children } from "../types/Preact";
|
||||
import { createContext } from "preact";
|
||||
import { Helmet } from "react-helmet";
|
||||
|
||||
export type Variables =
|
||||
|
@ -111,6 +112,8 @@ const GlobalTheme = createGlobalStyle<{ theme: Theme }>`
|
|||
}
|
||||
`;
|
||||
|
||||
export const ThemeContext = createContext<Theme>({} as any);
|
||||
|
||||
interface Props {
|
||||
children: Children;
|
||||
}
|
||||
|
@ -119,7 +122,7 @@ export default function Theme(props: Props) {
|
|||
const theme = PRESETS.dark;
|
||||
|
||||
return (
|
||||
<>
|
||||
<ThemeContext.Provider value={theme}>
|
||||
<Helmet>
|
||||
<meta
|
||||
name="theme-color"
|
||||
|
@ -132,6 +135,6 @@ export default function Theme(props: Props) {
|
|||
</Helmet>
|
||||
<GlobalTheme theme={theme} />
|
||||
{props.children}
|
||||
</>
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
}
|
||||
|
|
18
src/context/revoltjs/error.ts
Normal file
18
src/context/revoltjs/error.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
export function takeError(
|
||||
error: any
|
||||
): string {
|
||||
const type = error?.response?.data?.type;
|
||||
let id = type;
|
||||
if (!type) {
|
||||
if (error?.response?.status === 403) {
|
||||
return "Unauthorized";
|
||||
} else if (error && (!!error.isAxiosError && !error.response)) {
|
||||
return "NetworkError";
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
return "UnknownError";
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
68
src/pages/login/FormField.tsx
Normal file
68
src/pages/login/FormField.tsx
Normal file
|
@ -0,0 +1,68 @@
|
|||
import Overline from '../../components/ui/Overline';
|
||||
import InputBox from '../../components/ui/InputBox';
|
||||
import { Text, Localizer } from 'preact-i18n';
|
||||
|
||||
interface Props {
|
||||
type: "email" | "username" | "password" | "invite" | "current_password";
|
||||
showOverline?: boolean;
|
||||
register: Function;
|
||||
error?: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export default function FormField({
|
||||
type,
|
||||
register,
|
||||
showOverline,
|
||||
error,
|
||||
name
|
||||
}: Props) {
|
||||
return (
|
||||
<>
|
||||
{showOverline && (
|
||||
<Overline error={error}>
|
||||
<Text id={`login.${type}`} />
|
||||
</Overline>
|
||||
)}
|
||||
<Localizer>
|
||||
<InputBox
|
||||
placeholder={(<Text id={`login.enter.${type}`} />) as any}
|
||||
name={
|
||||
type === "current_password" ? "password" : name ?? type
|
||||
}
|
||||
type={
|
||||
type === "invite" || type === "username"
|
||||
? "text"
|
||||
: type === "current_password"
|
||||
? "password"
|
||||
: type
|
||||
}
|
||||
ref={register(
|
||||
type === "password" || type === "current_password"
|
||||
? {
|
||||
validate: (value: string) =>
|
||||
value.length === 0
|
||||
? "RequiredField"
|
||||
: value.length < 8
|
||||
? "TooShort"
|
||||
: value.length > 1024
|
||||
? "TooLong"
|
||||
: undefined
|
||||
}
|
||||
: type === "email"
|
||||
? {
|
||||
required: "RequiredField",
|
||||
pattern: {
|
||||
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
|
||||
message: "InvalidEmail"
|
||||
}
|
||||
}
|
||||
: type === "username"
|
||||
? { required: "RequiredField" }
|
||||
: { required: "RequiredField" }
|
||||
)}
|
||||
/>
|
||||
</Localizer>
|
||||
</>
|
||||
);
|
||||
}
|
123
src/pages/login/Login.module.scss
Normal file
123
src/pages/login/Login.module.scss
Normal file
|
@ -0,0 +1,123 @@
|
|||
.login {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
svg {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
> div {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
justify-content: space-between;
|
||||
|
||||
.attribution {
|
||||
color: var(--tertiary-background);
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
margin: 8px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.modal {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.bg {
|
||||
background-size: cover !important;
|
||||
}
|
||||
}
|
||||
|
||||
.form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 14px;
|
||||
|
||||
img {
|
||||
width: 260px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
a {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
form {
|
||||
margin: 1em 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
button {
|
||||
margin-top: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.create {
|
||||
text-align: center;
|
||||
color: var(--tertiary-foreground);
|
||||
|
||||
a {
|
||||
margin: 0 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.success {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
|
||||
.note {
|
||||
color: var(--tertiary-foreground);
|
||||
}
|
||||
|
||||
.mailProvider {
|
||||
padding: 24px 0;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight: 300;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 12px;
|
||||
text-align: center;
|
||||
color: var(--tertiary6);
|
||||
|
||||
a {
|
||||
color: var(--tertiary-background) !important;
|
||||
cursor: pointer;
|
||||
margin: 0 2px;
|
||||
|
||||
&:hover {
|
||||
color: var(--tertiary-foreground) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.bg {
|
||||
display: none;
|
||||
}
|
||||
}
|
70
src/pages/login/Login.tsx
Normal file
70
src/pages/login/Login.tsx
Normal file
|
@ -0,0 +1,70 @@
|
|||
import { Text } from "preact-i18n";
|
||||
import { Helmet } from "react-helmet";
|
||||
import styles from "./Login.module.scss";
|
||||
import { useContext } from "preact/hooks";
|
||||
import { APP_VERSION } from "../../version";
|
||||
import { LIBRARY_VERSION } from "revolt.js";
|
||||
import { Route, Switch } from "react-router-dom";
|
||||
import { ThemeContext } from "../../context/Theme";
|
||||
import { RevoltClient } from "../../context/revoltjs/RevoltClient";
|
||||
|
||||
import background from "./background.jpg";
|
||||
|
||||
import { FormLogin } from "./forms/FormLogin";
|
||||
import { FormCreate } from "./forms/FormCreate";
|
||||
import { FormResend } from "./forms/FormResend";
|
||||
import { FormReset, FormSendReset } from "./forms/FormReset";
|
||||
|
||||
export const Login = () => {
|
||||
const theme = useContext(ThemeContext);
|
||||
|
||||
return (
|
||||
<div className={styles.login}>
|
||||
<Helmet>
|
||||
<meta name="theme-color" content={theme.background} />
|
||||
</Helmet>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.attribution}>
|
||||
<span>
|
||||
API:{" "}
|
||||
<code>{RevoltClient.configuration?.revolt ?? "???"}</code>{" "}
|
||||
· revolt.js: <code>{LIBRARY_VERSION}</code>{" "}
|
||||
· App: <code>{APP_VERSION}</code>
|
||||
</span>
|
||||
<span>
|
||||
{/*<LocaleSelector />*/}
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles.modal}>
|
||||
<Switch>
|
||||
<Route path="/login/create">
|
||||
<FormCreate />
|
||||
</Route>
|
||||
<Route path="/login/resend">
|
||||
<FormResend />
|
||||
</Route>
|
||||
<Route path="/login/reset/:token">
|
||||
<FormReset />
|
||||
</Route>
|
||||
<Route path="/login/reset">
|
||||
<FormSendReset />
|
||||
</Route>
|
||||
<Route path="/">
|
||||
<FormLogin />
|
||||
</Route>
|
||||
</Switch>
|
||||
</div>
|
||||
<div className={styles.attribution}>
|
||||
<span>
|
||||
<Text id="general.image_by" /> ‎@lorenzoherrera
|
||||
‏· unsplash.com
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={styles.bg}
|
||||
style={{ background: `url('${background}')` }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
BIN
src/pages/login/background.jpg
Normal file
BIN
src/pages/login/background.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 484 KiB |
36
src/pages/login/forms/CaptchaBlock.tsx
Normal file
36
src/pages/login/forms/CaptchaBlock.tsx
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { Text } from "preact-i18n";
|
||||
import { useEffect } from "preact/hooks";
|
||||
import styles from "../Login.module.scss";
|
||||
import HCaptcha from "@hcaptcha/react-hcaptcha";
|
||||
import Preloader from "../../../components/ui/Preloader";
|
||||
import { RevoltClient } from "../../../context/revoltjs/RevoltClient";
|
||||
|
||||
export interface CaptchaProps {
|
||||
onSuccess: (token?: string) => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
export function CaptchaBlock(props: CaptchaProps) {
|
||||
useEffect(() => {
|
||||
if (!RevoltClient.configuration?.features.captcha.enabled) {
|
||||
props.onSuccess();
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (!RevoltClient.configuration?.features.captcha.enabled)
|
||||
return <Preloader />;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<HCaptcha
|
||||
sitekey={RevoltClient.configuration.features.captcha.key}
|
||||
onVerify={token => props.onSuccess(token)}
|
||||
/>
|
||||
<div className={styles.footer}>
|
||||
<a onClick={props.onCancel}>
|
||||
<Text id="login.cancel" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
236
src/pages/login/forms/Form.tsx
Normal file
236
src/pages/login/forms/Form.tsx
Normal file
|
@ -0,0 +1,236 @@
|
|||
import { Legal } from "./Legal";
|
||||
import { Text } from "preact-i18n";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useState } from "preact/hooks";
|
||||
import styles from "../Login.module.scss";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { MailProvider } from "./MailProvider";
|
||||
import { CheckCircle, Mail } from "@styled-icons/feather";
|
||||
import { CaptchaBlock, CaptchaProps } from "./CaptchaBlock";
|
||||
import { takeError } from "../../../context/revoltjs/error";
|
||||
import { RevoltClient } from "../../../context/revoltjs/RevoltClient";
|
||||
|
||||
import FormField from "../FormField";
|
||||
import Button from "../../../components/ui/Button";
|
||||
import Overline from "../../../components/ui/Overline";
|
||||
import Preloader from "../../../components/ui/Preloader";
|
||||
|
||||
interface Props {
|
||||
page: "create" | "login" | "send_reset" | "reset" | "resend";
|
||||
callback: (fields: {
|
||||
email: string;
|
||||
password: string;
|
||||
invite: string;
|
||||
captcha?: string;
|
||||
}) => Promise<void>;
|
||||
}
|
||||
|
||||
function getInviteCode() {
|
||||
if (typeof window === 'undefined') return '';
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const code = urlParams.get('code');
|
||||
return code ?? '';
|
||||
}
|
||||
|
||||
export function Form({ page, callback }: Props) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [success, setSuccess] = useState<string | undefined>(undefined);
|
||||
const [error, setGlobalError] = useState<string | undefined>(undefined);
|
||||
const [captcha, setCaptcha] = useState<CaptchaProps | undefined>(undefined);
|
||||
|
||||
const { handleSubmit, register, errors, setError } = useForm({
|
||||
defaultValues: {
|
||||
email: '',
|
||||
password: '',
|
||||
invite: getInviteCode()
|
||||
}
|
||||
});
|
||||
|
||||
async function onSubmit(data: {
|
||||
email: string;
|
||||
password: string;
|
||||
invite: string;
|
||||
}) {
|
||||
setGlobalError(undefined);
|
||||
setLoading(true);
|
||||
|
||||
function onError(err: any) {
|
||||
setLoading(false);
|
||||
|
||||
const error = takeError(err);
|
||||
switch (error) {
|
||||
case "email_in_use":
|
||||
return setError("email", { type: "", message: error });
|
||||
case "unknown_user":
|
||||
return setError("email", { type: "", message: error });
|
||||
case "invalid_invite":
|
||||
return setError("invite", { type: "", message: error });
|
||||
}
|
||||
|
||||
setGlobalError(error);
|
||||
}
|
||||
|
||||
try {
|
||||
if (
|
||||
RevoltClient.configuration?.features.captcha.enabled &&
|
||||
page !== "reset"
|
||||
) {
|
||||
setCaptcha({
|
||||
onSuccess: async captcha => {
|
||||
setCaptcha(undefined);
|
||||
try {
|
||||
await callback({ ...data, captcha });
|
||||
setSuccess(data.email);
|
||||
} catch (err) {
|
||||
onError(err);
|
||||
}
|
||||
},
|
||||
onCancel: () => {
|
||||
setCaptcha(undefined);
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await callback(data);
|
||||
setSuccess(data.email);
|
||||
}
|
||||
} catch (err) {
|
||||
onError(err);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof success !== "undefined") {
|
||||
return (
|
||||
<div className={styles.success}>
|
||||
{RevoltClient.configuration?.features.email ? (
|
||||
<>
|
||||
<Mail size={72} />
|
||||
<h2>
|
||||
<Text id="login.check_mail" />
|
||||
</h2>
|
||||
<p className={styles.note}>
|
||||
<Text id="login.email_delay" />
|
||||
</p>
|
||||
<MailProvider email={success} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<CheckCircle size={72} />
|
||||
<h1>
|
||||
<Text id="login.successful_registration" />
|
||||
</h1>
|
||||
</>
|
||||
)}
|
||||
<span className={styles.footer}>
|
||||
<Link to="/login">
|
||||
<a>
|
||||
<Text id="login.remembered" />
|
||||
</a>
|
||||
</Link>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (captcha) return <CaptchaBlock {...captcha} />;
|
||||
if (loading) return <Preloader />;
|
||||
|
||||
return (
|
||||
<div className={styles.form}>
|
||||
<form onSubmit={handleSubmit(onSubmit) as any}>
|
||||
{page !== "reset" && (
|
||||
<FormField
|
||||
type="email"
|
||||
register={register}
|
||||
showOverline
|
||||
error={errors.email?.message}
|
||||
/>
|
||||
)}
|
||||
{(page === "login" ||
|
||||
page === "create" ||
|
||||
page === "reset") && (
|
||||
<FormField
|
||||
type="password"
|
||||
register={register}
|
||||
showOverline
|
||||
error={errors.password?.message}
|
||||
/>
|
||||
)}
|
||||
{RevoltClient.configuration?.features.invite_only &&
|
||||
page === "create" && (
|
||||
<FormField
|
||||
type="invite"
|
||||
register={register}
|
||||
showOverline
|
||||
error={errors.invite?.message}
|
||||
/>
|
||||
)}
|
||||
{error && (
|
||||
<Overline type="error" error={error}>
|
||||
<Text id={`login.error.${page}`} />
|
||||
</Overline>
|
||||
)}
|
||||
<Button>
|
||||
<Text
|
||||
id={
|
||||
page === "create"
|
||||
? "login.register"
|
||||
: page === "login"
|
||||
? "login.title"
|
||||
: page === "reset"
|
||||
? "login.set_password"
|
||||
: page === "resend"
|
||||
? "login.resend"
|
||||
: "login.reset"
|
||||
}
|
||||
/>
|
||||
</Button>
|
||||
</form>
|
||||
{page === "create" && (
|
||||
<>
|
||||
<span className={styles.create}>
|
||||
<Text id="login.existing" />
|
||||
<Link to="/login">
|
||||
<Text id="login.title" />
|
||||
</Link>
|
||||
</span>
|
||||
<span className={styles.create}>
|
||||
<Text id="login.missing_verification" />
|
||||
<Link to="/login/resend">
|
||||
<Text id="login.resend" />
|
||||
</Link>
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
{page === "login" && (
|
||||
<>
|
||||
<span className={styles.create}>
|
||||
<Text id="login.new" />
|
||||
<Link to="/login/create">
|
||||
<Text id="login.create" />
|
||||
</Link>
|
||||
</span>
|
||||
<span className={styles.create}>
|
||||
<Text id="login.forgot" />
|
||||
<Link to="/login/reset">
|
||||
<Text id="login.reset" />
|
||||
</Link>
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
{(page === "reset" ||
|
||||
page === "resend" ||
|
||||
page === "send_reset") && (
|
||||
<>
|
||||
<span className={styles.create}>
|
||||
<Link to="/login">
|
||||
<Text id="login.remembered" />
|
||||
</Link>
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
<Legal />
|
||||
</div>
|
||||
);
|
||||
}
|
13
src/pages/login/forms/FormCreate.tsx
Normal file
13
src/pages/login/forms/FormCreate.tsx
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { RevoltClient } from "../../../context/revoltjs/RevoltClient";
|
||||
import { Form } from "./Form";
|
||||
|
||||
export function FormCreate() {
|
||||
return (
|
||||
<Form
|
||||
page="create"
|
||||
callback={async data => {
|
||||
await RevoltClient.register(process.env.API_SERVER as string, data);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
29
src/pages/login/forms/FormLogin.tsx
Normal file
29
src/pages/login/forms/FormLogin.tsx
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { Form } from "./Form";
|
||||
import { useContext } from "preact/hooks";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { deviceDetect } from "react-device-detect";
|
||||
import { AppContext } from "../../../context/revoltjs/RevoltClient";
|
||||
|
||||
export function FormLogin() {
|
||||
const { operations } = useContext(AppContext);
|
||||
const history = useHistory();
|
||||
|
||||
return (
|
||||
<Form
|
||||
page="login"
|
||||
callback={async data => {
|
||||
const browser = deviceDetect();
|
||||
let device_name;
|
||||
if (browser) {
|
||||
const { name, os } = browser;
|
||||
device_name = `${name} on ${os}`;
|
||||
} else {
|
||||
device_name = "Unknown Device";
|
||||
}
|
||||
|
||||
await operations.login({ ...data, device_name });
|
||||
history.push("/");
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
13
src/pages/login/forms/FormResend.tsx
Normal file
13
src/pages/login/forms/FormResend.tsx
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { RevoltClient } from "../../../context/revoltjs/RevoltClient";
|
||||
import { Form } from "./Form";
|
||||
|
||||
export function FormResend() {
|
||||
return (
|
||||
<Form
|
||||
page="resend"
|
||||
callback={async data => {
|
||||
await RevoltClient.req("POST", "/auth/resend", data);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
32
src/pages/login/forms/FormReset.tsx
Normal file
32
src/pages/login/forms/FormReset.tsx
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { Form } from "./Form";
|
||||
import { useHistory, useParams } from "react-router-dom";
|
||||
import { RevoltClient } from "../../../context/revoltjs/RevoltClient";
|
||||
|
||||
export function FormSendReset() {
|
||||
return (
|
||||
<Form
|
||||
page="send_reset"
|
||||
callback={async data => {
|
||||
await RevoltClient.req("POST", "/auth/send_reset", data);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function FormReset() {
|
||||
const { token } = useParams<{ token: string }>();
|
||||
const history = useHistory();
|
||||
|
||||
return (
|
||||
<Form
|
||||
page="reset"
|
||||
callback={async data => {
|
||||
await RevoltClient.req("POST", "/auth/reset" as any, {
|
||||
token,
|
||||
...(data as any)
|
||||
});
|
||||
history.push("/login");
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
29
src/pages/login/forms/Legal.tsx
Normal file
29
src/pages/login/forms/Legal.tsx
Normal file
|
@ -0,0 +1,29 @@
|
|||
import styles from "../Login.module.scss";
|
||||
import { Text } from "preact-i18n";
|
||||
|
||||
export function Legal() {
|
||||
return (
|
||||
<span className={styles.footer}>
|
||||
<a
|
||||
href="https://revolt.chat/about"
|
||||
target="_blank"
|
||||
>
|
||||
<Text id="general.about" />
|
||||
</a>
|
||||
·
|
||||
<a
|
||||
href="https://revolt.chat/terms"
|
||||
target="_blank"
|
||||
>
|
||||
<Text id="general.tos" />
|
||||
</a>
|
||||
·
|
||||
<a
|
||||
href="https://revolt.chat/privacy"
|
||||
target="_blank"
|
||||
>
|
||||
<Text id="general.privacy" />
|
||||
</a>
|
||||
</span>
|
||||
);
|
||||
}
|
55
src/pages/login/forms/MailProvider.tsx
Normal file
55
src/pages/login/forms/MailProvider.tsx
Normal file
|
@ -0,0 +1,55 @@
|
|||
import { Text } from "preact-i18n";
|
||||
import styles from "../Login.module.scss";
|
||||
import Button from "../../../components/ui/Button";
|
||||
|
||||
interface Props {
|
||||
email?: string;
|
||||
}
|
||||
|
||||
function mapMailProvider(email?: string): [string, string] | undefined {
|
||||
if (!email) return;
|
||||
|
||||
const match = /@(.+)/.exec(email);
|
||||
if (match === null) return;
|
||||
|
||||
const domain = match[1];
|
||||
switch (domain) {
|
||||
case "gmail.com":
|
||||
return ["Gmail", "https://gmail.com"];
|
||||
case "tuta.io":
|
||||
return ["Tutanota", "https://mail.tutanota.com"];
|
||||
case "outlook.com":
|
||||
return ["Outlook", "https://outlook.live.com"];
|
||||
case "yahoo.com":
|
||||
return ["Yahoo", "https://mail.yahoo.com"];
|
||||
case "wp.pl":
|
||||
return ["WP Poczta", "https://poczta.wp.pl"];
|
||||
case "protonmail.com":
|
||||
case "protonmail.ch":
|
||||
return ["ProtonMail", "https://mail.protonmail.com"];
|
||||
case "seznam.cz":
|
||||
case "email.cz":
|
||||
case "post.cz":
|
||||
return ["Seznam", "https://email.seznam.cz"];
|
||||
default:
|
||||
return [domain, `https://${domain}`];
|
||||
}
|
||||
}
|
||||
|
||||
export function MailProvider({ email }: Props) {
|
||||
const provider = mapMailProvider(email);
|
||||
if (!provider) return null;
|
||||
|
||||
return (
|
||||
<div className={styles.mailProvider}>
|
||||
<a href={provider[1]} target="_blank">
|
||||
<Button>
|
||||
<Text
|
||||
id="login.open_mail_provider"
|
||||
fields={{ provider: provider[0] }}
|
||||
/>
|
||||
</Button>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
1
src/version.ts
Normal file
1
src/version.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export const APP_VERSION = "0.1.9-alpha.7";
|
15
yarn.lock
15
yarn.lock
|
@ -955,6 +955,11 @@
|
|||
dependencies:
|
||||
"@hapi/hoek" "^8.3.0"
|
||||
|
||||
"@hcaptcha/react-hcaptcha@^0.3.6":
|
||||
version "0.3.6"
|
||||
resolved "https://registry.yarnpkg.com/@hcaptcha/react-hcaptcha/-/react-hcaptcha-0.3.6.tgz#cbbb9abdaea451a4df408bc9d476e8b17f0b63f4"
|
||||
integrity sha512-DQ5nvGVbbhd2IednxRhCV9wiPcCmclEV7bH98yGynGCXzO5XftO/XC0a1M1kEf9Ee+CLO/u+1HM/uE/PSrC3vQ==
|
||||
|
||||
"@insertish/mutable@1.0.6":
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@insertish/mutable/-/mutable-1.0.6.tgz#f42eaba8528ff68cc8065d51f9bbbd30a24f34de"
|
||||
|
@ -1752,6 +1757,11 @@ define-properties@^1.1.3:
|
|||
dependencies:
|
||||
object-keys "^1.0.12"
|
||||
|
||||
detect-browser@^5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/detect-browser/-/detect-browser-5.2.0.tgz#c9cd5afa96a6a19fda0bbe9e9be48a6b6e1e9c97"
|
||||
integrity sha512-tr7XntDAu50BVENgQfajMLzacmSe34D+qZc4zjnniz0ZVuw/TZcLcyxHQjYpJTM36sGEkZZlYLnIM1hH7alTMA==
|
||||
|
||||
dir-glob@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
|
||||
|
@ -3061,6 +3071,11 @@ react-helmet@^6.1.0:
|
|||
react-fast-compare "^3.1.1"
|
||||
react-side-effect "^2.1.0"
|
||||
|
||||
react-hook-form@6.3.0:
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-6.3.0.tgz#5c1926d51d4532f44818ef73f96d1a8c11015a76"
|
||||
integrity sha512-Xz7xxnILftxttc6H+miTSi2eYPehiW3XdsPaqY5dW8HcURFZPrnpxnmaRqz6JtZcbfRM8qjjppP/pOBaUzhn4w==
|
||||
|
||||
react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
|
|
Loading…
Reference in a new issue