mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-26 13:26:40 +07:00
move auth containers from 'dialogs'
This commit is contained in:
parent
b3acef0a1e
commit
92efb1e97e
26 changed files with 8 additions and 8 deletions
|
@ -1,147 +0,0 @@
|
|||
import { FC, memo, useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { throttle } from 'throttle-debounce';
|
||||
|
||||
import { useWindowSize } from '~/hooks/dom/useWindowSize';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
interface LoginSceneProps {}
|
||||
|
||||
interface Layer {
|
||||
src: string;
|
||||
velocity: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
const layers: Layer[] = [
|
||||
{
|
||||
src: '/images/clouds__bg.svg',
|
||||
velocity: -0.3,
|
||||
width: 3840,
|
||||
height: 1080,
|
||||
},
|
||||
{
|
||||
src: '/images/clouds__cube.svg',
|
||||
velocity: -0.1,
|
||||
width: 3840,
|
||||
height: 1080,
|
||||
},
|
||||
{
|
||||
src: '/images/clouds__cloud.svg',
|
||||
velocity: 0.2,
|
||||
width: 3840,
|
||||
height: 1080,
|
||||
},
|
||||
{
|
||||
src: '/images/clouds__dudes.svg',
|
||||
velocity: 0.5,
|
||||
width: 3840,
|
||||
height: 1080,
|
||||
},
|
||||
{
|
||||
src: '/images/clouds__trash.svg',
|
||||
velocity: 0.8,
|
||||
width: 3840,
|
||||
height: 1080,
|
||||
},
|
||||
];
|
||||
|
||||
const LoginAnimatedScene: FC<LoginSceneProps> = memo(() => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
const imageRefs = useRef<Array<SVGImageElement | null>>([]);
|
||||
const { isTablet } = useWindowSize();
|
||||
const domRect = useRef<DOMRect>();
|
||||
|
||||
const onMouseMove = useCallback(
|
||||
(event: MouseEvent): any => {
|
||||
if (!containerRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!domRect.current) {
|
||||
domRect.current = containerRef.current.getBoundingClientRect();
|
||||
}
|
||||
|
||||
const { x, width } = domRect.current!;
|
||||
const middle = (width - x) / 2;
|
||||
const shift = event.pageX / middle / 2 - 0.5;
|
||||
|
||||
layers.forEach((it, index) => {
|
||||
const target = imageRefs.current[index];
|
||||
|
||||
if (target) {
|
||||
target.style.transform = `translate(${
|
||||
shift * it.velocity * 200
|
||||
}px, 0)`;
|
||||
}
|
||||
});
|
||||
},
|
||||
[containerRef],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const listener = throttle(100, onMouseMove);
|
||||
document.addEventListener('mousemove', listener);
|
||||
return () => document.removeEventListener('mousemove', listener);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
if (isTablet) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.scene} ref={containerRef}>
|
||||
<svg
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox="0 0 1920 1080"
|
||||
preserveAspectRatio="xMidYMid slice"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<defs>
|
||||
<linearGradient id="fallbackGradient" x1={0} x2={0} y1={1} y2={0}>
|
||||
<stop style={{ stopColor: '#ffccaa', stopOpacity: 1 }} offset="0" />
|
||||
<stop
|
||||
style={{ stopColor: '#fff6d5', stopOpacity: 1 }}
|
||||
offset="0.34655526"
|
||||
/>
|
||||
<stop
|
||||
style={{ stopColor: '#afc6e9', stopOpacity: 1 }}
|
||||
offset="0.765342"
|
||||
/>
|
||||
<stop style={{ stopColor: '#879fde', stopOpacity: 1 }} offset="1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<rect
|
||||
width={1920}
|
||||
height={1080}
|
||||
x={0}
|
||||
y={0}
|
||||
fill="url(#fallbackGradient)"
|
||||
/>
|
||||
|
||||
{layers.map((it) => (
|
||||
<image
|
||||
ref={(it) => imageRefs.current.push(it)}
|
||||
key={it.src}
|
||||
href={it.src}
|
||||
width={it.width}
|
||||
height={it.height}
|
||||
x={1920 / 2 - it.width / 2}
|
||||
y={0}
|
||||
opacity={loaded ? 1 : 0}
|
||||
onLoad={() => setLoaded(true)}
|
||||
className={styles.image}
|
||||
/>
|
||||
))}
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export { LoginAnimatedScene };
|
|
@ -1,18 +0,0 @@
|
|||
@import "src/styles/mixins";
|
||||
|
||||
.scene {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.image {
|
||||
transition: opacity 1s, transform 0.1s;
|
||||
will-change: opacity, transform;
|
||||
|
||||
@include tablet {
|
||||
display: none;
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
import React, { FC } from 'react';
|
||||
|
||||
import { Grid } from '~/components/containers/Grid';
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Button } from '~/components/input/Button';
|
||||
import { OAuthProvider } from '~/types/auth';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
|
||||
interface IProps {
|
||||
openOauthWindow: (provider: OAuthProvider) => void;
|
||||
}
|
||||
|
||||
const LoginDialogButtons: FC<IProps> = ({ openOauthWindow }) => (
|
||||
<Group className={styles.footer}>
|
||||
<Button>Войти</Button>
|
||||
|
||||
<Grid columns="repeat(2, 1fr)">
|
||||
<Button
|
||||
color="outline"
|
||||
iconLeft="google"
|
||||
type="button"
|
||||
onClick={() => openOauthWindow('google')}
|
||||
>
|
||||
<span>Google</span>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
color="outline"
|
||||
iconLeft="vk"
|
||||
type="button"
|
||||
onClick={() => openOauthWindow('vkontakte')}
|
||||
>
|
||||
<span>Вконтакте</span>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Group>
|
||||
);
|
||||
|
||||
export { LoginDialogButtons };
|
|
@ -1,5 +0,0 @@
|
|||
@import 'src/styles/variables';
|
||||
|
||||
.footer {
|
||||
padding: $gap;
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
import React, { FC } from 'react';
|
||||
|
||||
import { Button } from '~/components/input/Button';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
interface IProps {}
|
||||
|
||||
const LoginSocialRegisterButtons: FC<IProps> = () => (
|
||||
<div className={styles.wrap}>
|
||||
<Button>Впустите меня!</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
export { LoginSocialRegisterButtons };
|
|
@ -1,9 +0,0 @@
|
|||
@import "src/styles/variables";
|
||||
|
||||
.wrap {
|
||||
padding: $gap $gap * 2;
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
import React, { FC } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
interface LoginStaticSceneProps {
|
||||
scene?: 'clouds' | 'nowhere';
|
||||
}
|
||||
|
||||
const LoginStaticScene: FC<LoginStaticSceneProps> = ({ scene = 'login' }) => (
|
||||
<div className={classNames(styles.scene, styles[scene])} />
|
||||
);
|
||||
|
||||
export { LoginStaticScene };
|
|
@ -1,16 +0,0 @@
|
|||
@import 'src/styles/mixins';
|
||||
|
||||
.scene {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
|
||||
&.clouds {
|
||||
background: url('/images/clouds.svg') no-repeat 50% 50%;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
&.nowhere {
|
||||
background: url('/images/nowhere_motel.svg') no-repeat 50% 50%;
|
||||
background-size: cover;
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
import React, { FC } from 'react';
|
||||
|
||||
import TelegramLoginButton, {
|
||||
TelegramUser,
|
||||
} from '@v9v/ts-react-telegram-login';
|
||||
|
||||
import { LoaderCircle } from '~/components/input/LoaderCircle';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
interface TelegramLoginFormProps {
|
||||
botName: string;
|
||||
loading?: boolean;
|
||||
onSuccess?: (token: TelegramUser) => void;
|
||||
}
|
||||
|
||||
const TelegramLoginForm: FC<TelegramLoginFormProps> = ({
|
||||
botName,
|
||||
loading,
|
||||
onSuccess,
|
||||
}) => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.text}>
|
||||
{loading ? (
|
||||
<LoaderCircle />
|
||||
) : (
|
||||
<div>
|
||||
После успешной авторизации аккаунт появится в настройках вашего
|
||||
профиля
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={styles.button}>
|
||||
<TelegramLoginButton
|
||||
dataOnAuth={onSuccess}
|
||||
botName={botName}
|
||||
requestAccess
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { TelegramLoginForm };
|
|
@ -1,22 +0,0 @@
|
|||
@import 'src/styles/variables';
|
||||
|
||||
.button {
|
||||
flex: 0 0 48px;
|
||||
}
|
||||
|
||||
.container {
|
||||
min-height: 200px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
padding: $gap;
|
||||
}
|
||||
|
||||
.text {
|
||||
text-align: center;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
import { FC, useCallback, useRef } from 'react';
|
||||
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Padder } from '~/components/containers/Padder';
|
||||
import { BetterScrollDialog } from '~/components/dialogs/BetterScrollDialog';
|
||||
import { DialogTitle } from '~/components/dialogs/DialogTitle';
|
||||
import { Button } from '~/components/input/Button';
|
||||
import { InputText } from '~/components/input/InputText';
|
||||
import { Dialog } from '~/constants/modal';
|
||||
import { LoginDialogButtons } from '~/containers/dialogs/LoginDialog/components/LoginDialogButtons';
|
||||
import { LoginStaticScene } from '~/containers/dialogs/LoginDialog/components/LoginStaticScene';
|
||||
import { useCloseOnEscape } from '~/hooks';
|
||||
import { useAuth } from '~/hooks/auth/useAuth';
|
||||
import { useLoginForm } from '~/hooks/auth/useLoginForm';
|
||||
import { useOAuth } from '~/hooks/auth/useOAuth';
|
||||
import { useShowModal } from '~/hooks/modal/useShowModal';
|
||||
import { DialogComponentProps } from '~/types/modal';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
type LoginDialogProps = DialogComponentProps & {};
|
||||
|
||||
const LoginDialog: FC<LoginDialogProps> = ({ onRequestClose }) => {
|
||||
const feature = useRef<'clouds' | 'nowhere'>(
|
||||
Math.random() <= 0.5 ? 'clouds' : 'nowhere',
|
||||
).current;
|
||||
|
||||
useCloseOnEscape(onRequestClose);
|
||||
|
||||
const { login } = useAuth();
|
||||
const { openOauthWindow } = useOAuth();
|
||||
const showRestoreDialog = useShowModal(Dialog.RestoreRequest);
|
||||
const onRestoreRequest = useCallback(() => {
|
||||
showRestoreDialog({});
|
||||
}, [showRestoreDialog]);
|
||||
|
||||
const backdrop = <LoginStaticScene scene={feature} />;
|
||||
|
||||
const { values, errors, handleSubmit, handleChange } = useLoginForm(
|
||||
login,
|
||||
onRequestClose,
|
||||
);
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<BetterScrollDialog
|
||||
width={300}
|
||||
onClose={onRequestClose}
|
||||
footer={<LoginDialogButtons openOauthWindow={openOauthWindow} />}
|
||||
backdrop={backdrop}
|
||||
>
|
||||
<Padder>
|
||||
<div className={styles.wrap}>
|
||||
<Group>
|
||||
<DialogTitle>Решительно войти</DialogTitle>
|
||||
|
||||
<InputText
|
||||
title="Логин"
|
||||
handler={handleChange('username')}
|
||||
value={values.username}
|
||||
error={errors.username}
|
||||
autoFocus
|
||||
/>
|
||||
|
||||
<InputText
|
||||
title="Пароль"
|
||||
handler={handleChange('password')}
|
||||
value={values.password}
|
||||
error={errors.password}
|
||||
type="password"
|
||||
/>
|
||||
|
||||
<Button
|
||||
color="link"
|
||||
type="button"
|
||||
onClick={onRestoreRequest}
|
||||
className={styles.forgot_button}
|
||||
>
|
||||
Вспомнить пароль
|
||||
</Button>
|
||||
</Group>
|
||||
</div>
|
||||
</Padder>
|
||||
</BetterScrollDialog>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export { LoginDialog };
|
|
@ -1,16 +0,0 @@
|
|||
@import 'src/styles/variables';
|
||||
|
||||
.wrap {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
padding: $login_dialog_padding;
|
||||
|
||||
button {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.forgot_button {
|
||||
opacity: 0.5;
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
import React, { FC, useCallback, useState } from 'react';
|
||||
|
||||
import { apiLoginWithSocial } from '~/api/auth';
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Padder } from '~/components/containers/Padder';
|
||||
import { BetterScrollDialog } from '~/components/dialogs/BetterScrollDialog';
|
||||
import { DialogTitle } from '~/components/dialogs/DialogTitle';
|
||||
import { InputText } from '~/components/input/InputText';
|
||||
import { Toggle } from '~/components/input/Toggle';
|
||||
import { getRandomPhrase } from '~/constants/phrases';
|
||||
import { LoginSocialRegisterButtons } from '~/containers/dialogs/LoginDialog/components/LoginSocialRegisterButtons';
|
||||
import { useCloseOnEscape } from '~/hooks';
|
||||
import { useSocialRegisterForm } from '~/hooks/auth/useSocialRegisterForm';
|
||||
import { useModal } from '~/hooks/modal/useModal';
|
||||
import { useAuthStore } from '~/store/auth/useAuthStore';
|
||||
import { DialogComponentProps } from '~/types/modal';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
type LoginSocialRegisterDialogProps = DialogComponentProps & { token: string };
|
||||
|
||||
const phrase = getRandomPhrase('REGISTER');
|
||||
|
||||
const LoginSocialRegisterDialog: FC<LoginSocialRegisterDialogProps> = ({
|
||||
onRequestClose,
|
||||
token,
|
||||
}) => {
|
||||
useCloseOnEscape(onRequestClose);
|
||||
const { hideModal } = useModal();
|
||||
const auth = useAuthStore();
|
||||
|
||||
const [isDryingPants, setIsDryingPants] = useState(false);
|
||||
const onSuccess = useCallback(
|
||||
(loginToken: string) => {
|
||||
auth.setToken(loginToken);
|
||||
hideModal();
|
||||
},
|
||||
[auth, hideModal],
|
||||
);
|
||||
|
||||
const { values, errors, handleChange, handleSubmit } = useSocialRegisterForm(
|
||||
token,
|
||||
apiLoginWithSocial,
|
||||
onSuccess,
|
||||
);
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} autoComplete="new-password">
|
||||
<BetterScrollDialog
|
||||
onClose={onRequestClose}
|
||||
width={300}
|
||||
footer={<LoginSocialRegisterButtons />}
|
||||
>
|
||||
<Padder>
|
||||
<div className={styles.wrap}>
|
||||
<Group>
|
||||
<DialogTitle>Добро пожаловать в семью!</DialogTitle>
|
||||
|
||||
<InputText
|
||||
handler={handleChange('username')}
|
||||
value={values.username}
|
||||
title="Юзернэйм"
|
||||
error={errors.username}
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
|
||||
<InputText
|
||||
handler={handleChange('password')}
|
||||
value={values.password}
|
||||
title="Пароль"
|
||||
type="password"
|
||||
error={errors.password}
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
|
||||
<div
|
||||
className={styles.check}
|
||||
onClick={() => setIsDryingPants(!isDryingPants)}
|
||||
>
|
||||
<Toggle value={isDryingPants} color="primary" />
|
||||
<span>
|
||||
Это не мои штаны сушатся на радиаторе в третьей лаборатории
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={styles.check}
|
||||
onClick={() => setIsDryingPants(!isDryingPants)}
|
||||
>
|
||||
<Toggle value={!isDryingPants} color="primary" />
|
||||
<span>{phrase}</span>
|
||||
</div>
|
||||
</Group>
|
||||
</div>
|
||||
</Padder>
|
||||
</BetterScrollDialog>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export { LoginSocialRegisterDialog };
|
|
@ -1,18 +0,0 @@
|
|||
@import 'src/styles/variables';
|
||||
|
||||
.wrap {
|
||||
padding: $login_dialog_padding;
|
||||
}
|
||||
|
||||
.check {
|
||||
padding: $gap 0 0 $gap * 0.5;
|
||||
font: $font_12_medium;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
|
||||
span {
|
||||
padding-left: $gap;
|
||||
color: $gray_50;
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
import React, { VFC } from 'react';
|
||||
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Button } from '~/components/input/Button';
|
||||
import { Icon } from '~/components/input/Icon';
|
||||
import { ERROR_LITERAL, ERRORS } from '~/constants/errors';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
interface RestoreInvalidCodeProps {
|
||||
error: string;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const RestoreInvalidCode: VFC<RestoreInvalidCodeProps> = ({ error, onClose }) => (
|
||||
<Group className={styles.error_shade}>
|
||||
<Icon icon="close" size={64} />
|
||||
|
||||
<div>{error || ERROR_LITERAL[ERRORS.CODE_IS_INVALID]}</div>
|
||||
|
||||
<div className={styles.spacer} />
|
||||
|
||||
<Button color="primary" onClick={onClose}>
|
||||
Очень жаль
|
||||
</Button>
|
||||
</Group>
|
||||
);
|
||||
|
||||
export { RestoreInvalidCode };
|
|
@ -1,35 +0,0 @@
|
|||
@import 'src/styles/variables';
|
||||
|
||||
.error_shade {
|
||||
@include outer_shadow();
|
||||
|
||||
background: $content_bg;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: $radius;
|
||||
padding: $gap * 2;
|
||||
box-sizing: border-box;
|
||||
text-transform: uppercase;
|
||||
font: $font_18_semibold;
|
||||
text-align: center;
|
||||
color: $color_primary;
|
||||
|
||||
svg {
|
||||
fill: $color_primary;
|
||||
}
|
||||
}
|
||||
|
||||
.error_shade {
|
||||
color: $color_danger;
|
||||
|
||||
svg {
|
||||
fill: $color_danger;
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
import React, { VFC } from 'react';
|
||||
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Button } from '~/components/input/Button';
|
||||
import { Icon } from '~/components/input/Icon';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
interface RestoreSuccessProps {
|
||||
username?: string;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
const RestoreSuccess: VFC<RestoreSuccessProps> = ({ username, onClick }) => (
|
||||
<Group className={styles.shade}>
|
||||
<Icon icon="check" size={64} />
|
||||
|
||||
<div>Пароль обновлен</div>
|
||||
<div>Добро пожаловать домой{username ? `, ~${username}` : ''}!</div>
|
||||
|
||||
<div />
|
||||
|
||||
<Button onClick={onClick}>Ура!</Button>
|
||||
</Group>
|
||||
);
|
||||
|
||||
export { RestoreSuccess };
|
|
@ -1,27 +0,0 @@
|
|||
@import 'src/styles/variables';
|
||||
|
||||
.shade {
|
||||
@include outer_shadow();
|
||||
|
||||
background: $content_bg;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: $radius;
|
||||
padding: $gap * 2;
|
||||
box-sizing: border-box;
|
||||
text-transform: uppercase;
|
||||
font: $font_18_semibold;
|
||||
text-align: center;
|
||||
color: $color_primary;
|
||||
|
||||
svg {
|
||||
fill: $color_primary;
|
||||
}
|
||||
}
|
|
@ -1,113 +0,0 @@
|
|||
import React, { FC, useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { apiRestoreCode } from '~/api/auth';
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Button } from '~/components/input/Button';
|
||||
import { InputText } from '~/components/input/InputText';
|
||||
import { useCloseOnEscape } from '~/hooks';
|
||||
import { useRestoreCode } from '~/hooks/auth/useRestoreCode';
|
||||
import { useRestorePasswordForm } from '~/hooks/auth/useRestorePasswordForm';
|
||||
import { DialogComponentProps } from '~/types/modal';
|
||||
|
||||
import { BetterScrollDialog } from '../../../components/dialogs/BetterScrollDialog';
|
||||
|
||||
import { RestoreInvalidCode } from './components/RestoreInvalidCode';
|
||||
import { RestoreSuccess } from './components/RestoreSuccess';
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
type RestorePasswordDialogProps = DialogComponentProps & {
|
||||
code: string;
|
||||
};
|
||||
|
||||
const RestorePasswordDialog: FC<RestorePasswordDialogProps> = ({
|
||||
onRequestClose,
|
||||
code,
|
||||
}) => {
|
||||
useCloseOnEscape(onRequestClose);
|
||||
|
||||
const { codeUser, isLoading, error } = useRestoreCode(code);
|
||||
|
||||
const [isSent, setIsSent] = useState(false);
|
||||
const onSent = useCallback(() => setIsSent(true), [setIsSent]);
|
||||
|
||||
const { handleChange, handleSubmit, values, errors } = useRestorePasswordForm(
|
||||
code,
|
||||
apiRestoreCode,
|
||||
onSent,
|
||||
);
|
||||
|
||||
const buttons = useMemo(
|
||||
() => (
|
||||
<Group className={styles.buttons}>
|
||||
<Button color="primary">Восстановить</Button>
|
||||
</Group>
|
||||
),
|
||||
[],
|
||||
);
|
||||
|
||||
const overlay = useMemo(() => {
|
||||
if (isSent) {
|
||||
return (
|
||||
<RestoreSuccess
|
||||
username={codeUser?.username}
|
||||
onClick={onRequestClose}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <RestoreInvalidCode onClose={onRequestClose} error={error} />;
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return <div className={styles.shade} />;
|
||||
}
|
||||
}, [isLoading, error, isSent, codeUser, onRequestClose]);
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<BetterScrollDialog
|
||||
footer={buttons}
|
||||
overlay={overlay}
|
||||
width={300}
|
||||
onClose={onRequestClose}
|
||||
is_loading={isLoading}
|
||||
>
|
||||
<div className={styles.wrap}>
|
||||
<Group>
|
||||
<div className={styles.header}>
|
||||
Пришло время сменить пароль, {codeUser?.username}
|
||||
</div>
|
||||
|
||||
<InputText
|
||||
title="Новый пароль"
|
||||
value={values.newPassword}
|
||||
handler={handleChange('newPassword')}
|
||||
error={errors.newPassword}
|
||||
autoFocus
|
||||
type="password"
|
||||
/>
|
||||
|
||||
<InputText
|
||||
title="Ещё раз"
|
||||
type="password"
|
||||
value={values.newPasswordAgain}
|
||||
handler={handleChange('newPasswordAgain')}
|
||||
error={errors.newPasswordAgain}
|
||||
/>
|
||||
|
||||
<Group className={styles.text}>
|
||||
<p>Новый пароль должен быть не короче 6 символов.</p>
|
||||
<p>
|
||||
Вряд ли кто-нибудь будет пытаться нас взломать, но сложный
|
||||
пароль всегда лучше простого.
|
||||
</p>
|
||||
</Group>
|
||||
</Group>
|
||||
</div>
|
||||
</BetterScrollDialog>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export { RestorePasswordDialog };
|
|
@ -1,61 +0,0 @@
|
|||
@import 'src/styles/variables';
|
||||
|
||||
.wrap {
|
||||
padding: $gap $gap $gap * 4;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
padding: $gap;
|
||||
}
|
||||
|
||||
.text {
|
||||
font: $font_14_regular;
|
||||
padding: $gap;
|
||||
color: $gray_50;
|
||||
}
|
||||
|
||||
.shade,
|
||||
.error_shade {
|
||||
@include outer_shadow();
|
||||
|
||||
background: $content_bg;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: $radius;
|
||||
padding: $gap * 2;
|
||||
box-sizing: border-box;
|
||||
text-transform: uppercase;
|
||||
font: $font_18_semibold;
|
||||
text-align: center;
|
||||
color: $color_primary;
|
||||
|
||||
svg {
|
||||
fill: $color_primary;
|
||||
}
|
||||
}
|
||||
|
||||
.error_shade {
|
||||
color: $color_danger;
|
||||
|
||||
svg {
|
||||
fill: $color_danger;
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
font: $font_18_semibold;
|
||||
text-transform: uppercase;
|
||||
padding: $gap;
|
||||
padding-bottom: $gap * 2;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
height: $gap * 4;
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
import React, { VFC } from 'react';
|
||||
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Button } from '~/components/input/Button';
|
||||
import { Icon } from '~/components/input/Icon';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
interface RestoreSentProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const RestoreSent: VFC<RestoreSentProps> = ({ onClose }) => (
|
||||
<Group className={styles.shade}>
|
||||
<Icon icon="check" size={64} />
|
||||
|
||||
<div>Проверьте почту, мы отправили на неё код</div>
|
||||
|
||||
<div />
|
||||
|
||||
<Button onClick={onClose}>Отлично!</Button>
|
||||
</Group>
|
||||
);
|
||||
|
||||
export { RestoreSent };
|
|
@ -1,27 +0,0 @@
|
|||
@import 'src/styles/variables';
|
||||
|
||||
.shade {
|
||||
@include outer_shadow();
|
||||
|
||||
background: $content_bg;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: $radius;
|
||||
padding: $gap * 2;
|
||||
box-sizing: border-box;
|
||||
text-transform: uppercase;
|
||||
font: $font_18_semibold;
|
||||
text-align: center;
|
||||
color: $color_primary;
|
||||
|
||||
svg {
|
||||
fill: $color_primary;
|
||||
}
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
import React, { useCallback, useMemo, useState, VFC } from 'react';
|
||||
|
||||
import { apiRequestRestoreCode } from '~/api/auth';
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Button } from '~/components/input/Button';
|
||||
import { InputText } from '~/components/input/InputText';
|
||||
import { useCloseOnEscape } from '~/hooks';
|
||||
import { useRestoreRequestForm } from '~/hooks/auth/useRestoreRequestForm';
|
||||
import { DialogComponentProps } from '~/types/modal';
|
||||
|
||||
import { BetterScrollDialog } from '../../../components/dialogs/BetterScrollDialog';
|
||||
|
||||
import { RestoreSent } from './components/RestoreSent';
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
interface RestoreRequestDialogProps extends DialogComponentProps {}
|
||||
|
||||
const RestoreRequestDialog: VFC<RestoreRequestDialogProps> = ({
|
||||
onRequestClose,
|
||||
}) => {
|
||||
useCloseOnEscape(onRequestClose);
|
||||
|
||||
const [isSent, setIsSent] = useState(false);
|
||||
const onSent = useCallback(() => setIsSent(true), [setIsSent]);
|
||||
|
||||
const { isSubmitting, handleSubmit, handleChange, errors, values } =
|
||||
useRestoreRequestForm(apiRequestRestoreCode, onSent);
|
||||
|
||||
const buttons = useMemo(
|
||||
() => (
|
||||
<Group className={styles.buttons}>
|
||||
<Button>Восстановить</Button>
|
||||
</Group>
|
||||
),
|
||||
[],
|
||||
);
|
||||
|
||||
const header = useMemo(() => <div className={styles.illustration} />, []);
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<BetterScrollDialog
|
||||
header={header}
|
||||
footer={buttons}
|
||||
width={300}
|
||||
onClose={onRequestClose}
|
||||
is_loading={isSubmitting}
|
||||
overlay={isSent ? <RestoreSent onClose={onRequestClose} /> : undefined}
|
||||
>
|
||||
<div className={styles.wrap}>
|
||||
<Group>
|
||||
<InputText
|
||||
title="Имя или email"
|
||||
value={values.field}
|
||||
handler={handleChange('field')}
|
||||
error={errors.field}
|
||||
autoFocus
|
||||
/>
|
||||
|
||||
<div className={styles.text}>
|
||||
Введите имя пользователя или адрес почты. Мы пришлем ссылку для
|
||||
сброса пароля.
|
||||
</div>
|
||||
</Group>
|
||||
</div>
|
||||
</BetterScrollDialog>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export { RestoreRequestDialog };
|
|
@ -1,23 +0,0 @@
|
|||
@import 'src/styles/variables';
|
||||
|
||||
.wrap {
|
||||
padding: $gap $gap $gap * 4;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
padding: $gap;
|
||||
}
|
||||
|
||||
.text {
|
||||
font: $font_14_regular;
|
||||
padding: $gap;
|
||||
color: $gray_50;
|
||||
}
|
||||
|
||||
.illustration {
|
||||
min-height: 160px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font: $font_18_semibold;
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
import React, { FC, useCallback, useMemo } from 'react';
|
||||
|
||||
import { TelegramUser } from '@v9v/ts-react-telegram-login';
|
||||
|
||||
import { Padder } from '~/components/containers/Padder';
|
||||
import { Button } from '~/components/input/Button';
|
||||
import { useTelegramAccount } from '~/hooks/auth/useTelegramAccount';
|
||||
import { DialogComponentProps } from '~/types/modal';
|
||||
|
||||
import { BetterScrollDialog } from '../../../components/dialogs/BetterScrollDialog';
|
||||
import { TelegramLoginForm } from '../LoginDialog/components/TelegramLoginForm/index';
|
||||
|
||||
interface TelegramAttachDialogProps extends DialogComponentProps {}
|
||||
|
||||
const botName = process.env.NEXT_PUBLIC_BOT_USERNAME;
|
||||
|
||||
const TelegramAttachDialog: FC<TelegramAttachDialogProps> = ({
|
||||
onRequestClose,
|
||||
}) => {
|
||||
const { attach } = useTelegramAccount();
|
||||
|
||||
const onAttach = useCallback(
|
||||
(data: TelegramUser) => attach(data, onRequestClose),
|
||||
[attach, onRequestClose],
|
||||
);
|
||||
|
||||
const buttons = useMemo(
|
||||
() => (
|
||||
<Padder>
|
||||
<Button stretchy onClick={onRequestClose}>
|
||||
Отмена
|
||||
</Button>
|
||||
</Padder>
|
||||
),
|
||||
[onRequestClose],
|
||||
);
|
||||
|
||||
if (!botName) {
|
||||
onRequestClose();
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<BetterScrollDialog width={300} onClose={onRequestClose} footer={buttons}>
|
||||
<TelegramLoginForm botName={botName} onSuccess={onAttach} />
|
||||
</BetterScrollDialog>
|
||||
);
|
||||
};
|
||||
export { TelegramAttachDialog };
|
Loading…
Add table
Add a link
Reference in a new issue