mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-24 20:36:40 +07:00
refactor auth components
This commit is contained in:
parent
1caf402ef3
commit
b3acef0a1e
21 changed files with 37 additions and 19 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 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,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,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,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,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,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;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue