1
0
Fork 0
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:
Fedor Katurov 2023-11-19 17:02:10 +06:00
parent 1caf402ef3
commit b3acef0a1e
21 changed files with 37 additions and 19 deletions

View file

@ -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 };

View file

@ -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;
}
}

View file

@ -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 };

View file

@ -1,5 +0,0 @@
@import 'src/styles/variables';
.footer {
padding: $gap;
}

View file

@ -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 };

View file

@ -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;
}
}

View file

@ -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 };

View file

@ -1,9 +0,0 @@
@import "src/styles/variables";
.wrap {
padding: $gap $gap * 2;
button {
width: 100%;
}
}

View file

@ -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 };

View file

@ -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;
}

View file

@ -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 };

View file

@ -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;
}
}

View file

@ -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 };

View file

@ -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;
}
}

View file

@ -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 };

View file

@ -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;
}
}