@skroz/frontend 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENCE.md +21 -0
- package/dist/auth/Auth.d.ts +8 -0
- package/dist/auth/Auth.js +52 -0
- package/dist/auth/AuthFooterLinks.d.ts +4 -0
- package/dist/auth/AuthFooterLinks.js +26 -0
- package/dist/auth/Forgot.d.ts +7 -0
- package/dist/auth/Forgot.js +68 -0
- package/dist/auth/Login.d.ts +8 -0
- package/dist/auth/Login.js +65 -0
- package/dist/auth/LoginForm.d.ts +6 -0
- package/dist/auth/LoginForm.js +48 -0
- package/dist/auth/RecoverPassword.d.ts +2 -0
- package/dist/auth/RecoverPassword.js +96 -0
- package/dist/auth/Register.d.ts +8 -0
- package/dist/auth/Register.js +68 -0
- package/dist/auth/ResendLinkButton.d.ts +11 -0
- package/dist/auth/ResendLinkButton.js +50 -0
- package/dist/auth/index.d.ts +8 -0
- package/dist/auth/index.js +22 -0
- package/dist/graphql/ForgotPasswordMutation.graphql.d.ts +24 -0
- package/dist/graphql/ForgotPasswordMutation.graphql.js +76 -0
- package/dist/graphql/LoginMutation.graphql.d.ts +26 -0
- package/dist/graphql/LoginMutation.graphql.js +69 -0
- package/dist/graphql/RegisterMutation.graphql.d.ts +26 -0
- package/dist/graphql/RegisterMutation.graphql.js +69 -0
- package/dist/graphql/ResendLinkButtonMutation.graphql.d.ts +25 -0
- package/dist/graphql/ResendLinkButtonMutation.graphql.js +76 -0
- package/dist/graphql/index.d.ts +5 -0
- package/dist/graphql/index.js +16 -0
- package/dist/graphql/recoveryMutation.graphql.d.ts +19 -0
- package/dist/graphql/recoveryMutation.graphql.js +67 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +20 -0
- package/dist/ui/AreYouSure.d.ts +10 -0
- package/dist/ui/AreYouSure.js +43 -0
- package/dist/ui/FormError.d.ts +3 -0
- package/dist/ui/FormError.js +15 -0
- package/dist/ui/FormItem.d.ts +12 -0
- package/dist/ui/FormItem.js +27 -0
- package/dist/ui/H.d.ts +16 -0
- package/dist/ui/H.js +39 -0
- package/dist/ui/Panel.d.ts +16 -0
- package/dist/ui/Panel.js +24 -0
- package/dist/ui/SeoHead.d.ts +13 -0
- package/dist/ui/SeoHead.js +14 -0
- package/dist/ui/index.d.ts +6 -0
- package/dist/ui/index.js +18 -0
- package/dist/utils/FrontendContext.d.ts +14 -0
- package/dist/utils/FrontendContext.js +30 -0
- package/dist/utils/getError.d.ts +11 -0
- package/dist/utils/getError.js +73 -0
- package/dist/utils/handleFormErrors.d.ts +15 -0
- package/dist/utils/handleFormErrors.js +62 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.js +28 -0
- package/dist/utils/isObject.d.ts +2 -0
- package/dist/utils/isObject.js +6 -0
- package/dist/utils/limitExpiresAt.d.ts +3 -0
- package/dist/utils/limitExpiresAt.js +19 -0
- package/package.json +48 -0
- package/src/auth/Auth.tsx +76 -0
- package/src/auth/AuthFooterLinks.tsx +27 -0
- package/src/auth/Forgot.tsx +122 -0
- package/src/auth/Login.tsx +115 -0
- package/src/auth/LoginForm.tsx +74 -0
- package/src/auth/RecoverPassword.tsx +185 -0
- package/src/auth/Register.tsx +174 -0
- package/src/auth/ResendLinkButton.tsx +71 -0
- package/src/auth/index.ts +8 -0
- package/src/graphql/ForgotPasswordMutation.graphql.ts +100 -0
- package/src/graphql/LoginMutation.graphql.ts +95 -0
- package/src/graphql/RegisterMutation.graphql.ts +95 -0
- package/src/graphql/ResendLinkButtonMutation.graphql.ts +101 -0
- package/src/graphql/index.ts +5 -0
- package/src/graphql/recoveryMutation.graphql.ts +91 -0
- package/src/index.ts +4 -0
- package/src/locales/ru/common.json +271 -0
- package/src/styles/auth.less +142 -0
- package/src/styles/colors.less +55 -0
- package/src/styles/components.less +2 -0
- package/src/styles/panels.less +61 -0
- package/src/styles/sizes.less +92 -0
- package/src/ui/AreYouSure.tsx +55 -0
- package/src/ui/FormError.tsx +21 -0
- package/src/ui/FormItem.tsx +60 -0
- package/src/ui/H.tsx +76 -0
- package/src/ui/Panel.tsx +44 -0
- package/src/ui/SeoHead.tsx +69 -0
- package/src/ui/index.ts +6 -0
- package/src/utils/FrontendContext.tsx +30 -0
- package/src/utils/getError.ts +101 -0
- package/src/utils/handleFormErrors.ts +77 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/isObject.ts +4 -0
- package/src/utils/limitExpiresAt.ts +14 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import React, { useCallback } from 'react';
|
|
2
|
+
import { Button, Form, Input, message } from 'antd';
|
|
3
|
+
import { useRouter } from 'next/router';
|
|
4
|
+
import { UserOutlined } from '@ant-design/icons';
|
|
5
|
+
import { useTranslation } from 'next-i18next';
|
|
6
|
+
import Link from 'next/link';
|
|
7
|
+
import { useExistingForm } from '@os-design/form';
|
|
8
|
+
import { graphql, useMutation } from 'react-relay';
|
|
9
|
+
import { setLimitExpiresAt } from '../utils/limitExpiresAt';
|
|
10
|
+
import handleFormErrors from '../utils/handleFormErrors';
|
|
11
|
+
import H from '../ui/H';
|
|
12
|
+
import { useFrontendConfig } from '../utils/FrontendContext';
|
|
13
|
+
import { RegisterInput } from '../graphql/RegisterMutation.graphql';
|
|
14
|
+
import {
|
|
15
|
+
ForgotPasswordMutation,
|
|
16
|
+
ForgotPasswordMutation$data,
|
|
17
|
+
} from '../graphql/ForgotPasswordMutation.graphql';
|
|
18
|
+
import FormItem from '../ui/FormItem';
|
|
19
|
+
|
|
20
|
+
interface ForgotProps {
|
|
21
|
+
onFinish: () => void;
|
|
22
|
+
onLoginClick?: () => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const Forgot: React.FC<ForgotProps> = ({ onFinish, onLoginClick }) => {
|
|
26
|
+
const { t } = useTranslation();
|
|
27
|
+
const router = useRouter();
|
|
28
|
+
const config = useFrontendConfig();
|
|
29
|
+
|
|
30
|
+
const { form, useValue, Field } = useExistingForm<RegisterInput>();
|
|
31
|
+
const email = useValue('email');
|
|
32
|
+
|
|
33
|
+
const [commit, loading] = useMutation<ForgotPasswordMutation>(
|
|
34
|
+
graphql`
|
|
35
|
+
mutation ForgotPasswordMutation($input: ForgotPasswordInput!) {
|
|
36
|
+
forgotPassword(input: $input) {
|
|
37
|
+
recoveryLinkIsSent
|
|
38
|
+
limitExpiresAt
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
`
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const forgotPasswordHandler = useCallback(() => {
|
|
45
|
+
commit({
|
|
46
|
+
variables: {
|
|
47
|
+
input: {
|
|
48
|
+
email,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
onError: (error) => handleFormErrors(form, error),
|
|
52
|
+
onCompleted: (res: ForgotPasswordMutation$data) => {
|
|
53
|
+
setLimitExpiresAt(res.forgotPassword.limitExpiresAt);
|
|
54
|
+
if (!res.forgotPassword.recoveryLinkIsSent)
|
|
55
|
+
message.error(t('common:auth.forgotSendError'));
|
|
56
|
+
router.push(`/recovery?email=${email}`);
|
|
57
|
+
onFinish();
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
}, [commit, email, form, onFinish, router, t]);
|
|
61
|
+
|
|
62
|
+
const loginButton = (
|
|
63
|
+
<Button type='link' onClick={onLoginClick} shape='round'>
|
|
64
|
+
{t('common:buttons.login')}
|
|
65
|
+
</Button>
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div className='auth no-text-selection'>
|
|
70
|
+
<H type='h1' textAlign='center'>
|
|
71
|
+
{t('common:auth.forgotTitle')}
|
|
72
|
+
</H>
|
|
73
|
+
<div className='auth-subtitle'>
|
|
74
|
+
{t('common:auth.forgotSubtitle')}{' '}
|
|
75
|
+
{onLoginClick ? (
|
|
76
|
+
loginButton
|
|
77
|
+
) : (
|
|
78
|
+
<Link href={config.loginPath || '/login'} passHref>
|
|
79
|
+
{loginButton}
|
|
80
|
+
</Link>
|
|
81
|
+
)}
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<Form size='large'>
|
|
85
|
+
<Field
|
|
86
|
+
name='email'
|
|
87
|
+
render={({ value, onChange }: { value: string, onChange: (val: string) => void }, { error }: { error?: string }) => (
|
|
88
|
+
<FormItem error={error}>
|
|
89
|
+
<Input
|
|
90
|
+
value={value}
|
|
91
|
+
prefix={<UserOutlined />}
|
|
92
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onChange(e.target.value)}
|
|
93
|
+
placeholder='Email'
|
|
94
|
+
size='large'
|
|
95
|
+
/>
|
|
96
|
+
</FormItem>
|
|
97
|
+
)}
|
|
98
|
+
/>
|
|
99
|
+
</Form>
|
|
100
|
+
|
|
101
|
+
<div className='auth-button'>
|
|
102
|
+
<Button
|
|
103
|
+
type='primary'
|
|
104
|
+
shape='round'
|
|
105
|
+
size='large'
|
|
106
|
+
loading={loading}
|
|
107
|
+
onClick={forgotPasswordHandler}
|
|
108
|
+
>
|
|
109
|
+
{t('common:auth.forgotSendInstructions')}
|
|
110
|
+
</Button>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<div className='auth-footer'>
|
|
114
|
+
<div className='auth-footer-issues'>
|
|
115
|
+
{t('common:auth.forgotIssues')}
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export default Forgot;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import React, { useCallback } from 'react';
|
|
2
|
+
import { Button, message } from 'antd';
|
|
3
|
+
import { useRouter } from 'next/router';
|
|
4
|
+
import { useTranslation } from 'next-i18next';
|
|
5
|
+
import Link from 'next/link';
|
|
6
|
+
import { graphql, useMutation } from 'react-relay';
|
|
7
|
+
import { useExistingForm } from '@os-design/form';
|
|
8
|
+
import {
|
|
9
|
+
RegisterInput,
|
|
10
|
+
LoginMutation,
|
|
11
|
+
LoginMutation$data,
|
|
12
|
+
} from '../graphql/LoginMutation.graphql';
|
|
13
|
+
import handleFormErrors from '../utils/handleFormErrors';
|
|
14
|
+
import AuthFooterLinks from './AuthFooterLinks';
|
|
15
|
+
import H from '../ui/H';
|
|
16
|
+
import { useFrontendConfig } from '../utils/FrontendContext';
|
|
17
|
+
import LoginForm from './LoginForm';
|
|
18
|
+
|
|
19
|
+
interface LoginProps {
|
|
20
|
+
onFinish: () => void;
|
|
21
|
+
isModal: boolean;
|
|
22
|
+
onForgotClick?: () => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const Login: React.FC<LoginProps> = ({ onFinish, onForgotClick, isModal }) => {
|
|
26
|
+
const { t } = useTranslation();
|
|
27
|
+
const router = useRouter();
|
|
28
|
+
const { form } = useExistingForm<RegisterInput>();
|
|
29
|
+
const config = useFrontendConfig();
|
|
30
|
+
|
|
31
|
+
const [commit, loading] = useMutation<LoginMutation>(
|
|
32
|
+
graphql`
|
|
33
|
+
mutation LoginMutation($input: RegisterInput!) {
|
|
34
|
+
login(input: $input) {
|
|
35
|
+
ok
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
`
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const loginHandler = useCallback(() => {
|
|
42
|
+
commit({
|
|
43
|
+
variables: {
|
|
44
|
+
input: {
|
|
45
|
+
...form.values.getAll(),
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
onError: (error) => {
|
|
49
|
+
handleFormErrors(form, error);
|
|
50
|
+
},
|
|
51
|
+
onCompleted: (res: LoginMutation$data) => {
|
|
52
|
+
if (!res.login.ok) {
|
|
53
|
+
message.error(t('common:auth.loginError'));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
onFinish();
|
|
57
|
+
message.success(t('common:auth.loginSuccess'));
|
|
58
|
+
router.reload();
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}, [commit, form, router, onFinish, t]);
|
|
62
|
+
|
|
63
|
+
const forgotButton = (
|
|
64
|
+
<Button type='link' onClick={onForgotClick}>
|
|
65
|
+
{t('common:auth.forgot')}
|
|
66
|
+
</Button>
|
|
67
|
+
);
|
|
68
|
+
const registerButton = (
|
|
69
|
+
<Button type='link' shape='round' className='register-link'>
|
|
70
|
+
{t('common:buttons.register')}
|
|
71
|
+
</Button>
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<div className='auth no-text-selection'>
|
|
76
|
+
<H type='h1' textAlign='center'>
|
|
77
|
+
{t('common:auth.loginTitle')}
|
|
78
|
+
</H>
|
|
79
|
+
{!isModal && (
|
|
80
|
+
<div className='auth-subtitle'>
|
|
81
|
+
{t('common:auth.loginSubtitle')}{' '}
|
|
82
|
+
<Link href={config.registerPath || '/register'} passHref>
|
|
83
|
+
{registerButton}
|
|
84
|
+
</Link>
|
|
85
|
+
</div>
|
|
86
|
+
)}
|
|
87
|
+
<LoginForm onPressEnter={loginHandler} />
|
|
88
|
+
<div className='auth-button'>
|
|
89
|
+
<Button
|
|
90
|
+
type='primary'
|
|
91
|
+
shape='round'
|
|
92
|
+
size='large'
|
|
93
|
+
loading={loading}
|
|
94
|
+
onClick={loginHandler}
|
|
95
|
+
>
|
|
96
|
+
{t('common:buttons.login')}
|
|
97
|
+
</Button>
|
|
98
|
+
</div>
|
|
99
|
+
<div className='auth-footer'>
|
|
100
|
+
<div>
|
|
101
|
+
{onForgotClick ? (
|
|
102
|
+
forgotButton
|
|
103
|
+
) : (
|
|
104
|
+
<Link href={config.forgotPasswordPath || '/forgot'} passHref>
|
|
105
|
+
{forgotButton}
|
|
106
|
+
</Link>
|
|
107
|
+
)}
|
|
108
|
+
</div>
|
|
109
|
+
<AuthFooterLinks />
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export default Login;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Form, Input } from 'antd';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { LockOutlined, UserOutlined } from '@ant-design/icons';
|
|
4
|
+
import { useTranslation } from 'next-i18next';
|
|
5
|
+
import { useExistingForm } from '@os-design/form';
|
|
6
|
+
import FormItem from '../ui/FormItem';
|
|
7
|
+
import { RegisterInput } from '../graphql/LoginMutation.graphql';
|
|
8
|
+
import FormError from '../ui/FormError';
|
|
9
|
+
|
|
10
|
+
interface LoginFormProps {
|
|
11
|
+
onPressEnter: () => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const LoginForm: React.FC<LoginFormProps> = ({ onPressEnter }) => {
|
|
15
|
+
const { t } = useTranslation();
|
|
16
|
+
const { Field } = useExistingForm<RegisterInput>();
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<Form size='large'>
|
|
20
|
+
<FormError />
|
|
21
|
+
<Field
|
|
22
|
+
name='email'
|
|
23
|
+
render={(
|
|
24
|
+
{
|
|
25
|
+
value,
|
|
26
|
+
onChange,
|
|
27
|
+
}: { value: string; onChange: (val: string) => void },
|
|
28
|
+
{ error }: { error?: string }
|
|
29
|
+
) => (
|
|
30
|
+
<FormItem error={error}>
|
|
31
|
+
<Input
|
|
32
|
+
prefix={<UserOutlined />}
|
|
33
|
+
value={value}
|
|
34
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
|
35
|
+
onChange(e.target.value)
|
|
36
|
+
}
|
|
37
|
+
onKeyDown={(e: React.KeyboardEvent) => {
|
|
38
|
+
if (e.key === 'Enter') onPressEnter();
|
|
39
|
+
}}
|
|
40
|
+
placeholder='Email'
|
|
41
|
+
/>
|
|
42
|
+
</FormItem>
|
|
43
|
+
)}
|
|
44
|
+
/>
|
|
45
|
+
|
|
46
|
+
<Field
|
|
47
|
+
name='password'
|
|
48
|
+
render={(
|
|
49
|
+
{
|
|
50
|
+
value,
|
|
51
|
+
onChange,
|
|
52
|
+
}: { value: string; onChange: (val: string) => void },
|
|
53
|
+
{ error }: { error?: string }
|
|
54
|
+
) => (
|
|
55
|
+
<FormItem error={error}>
|
|
56
|
+
<Input.Password
|
|
57
|
+
prefix={<LockOutlined />}
|
|
58
|
+
value={value}
|
|
59
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
|
60
|
+
onChange(e.target.value)
|
|
61
|
+
}
|
|
62
|
+
onKeyDown={(e: React.KeyboardEvent) => {
|
|
63
|
+
if (e.key === 'Enter') onPressEnter();
|
|
64
|
+
}}
|
|
65
|
+
placeholder={t('common:auth.passwordPlaceholder')}
|
|
66
|
+
/>
|
|
67
|
+
</FormItem>
|
|
68
|
+
)}
|
|
69
|
+
/>
|
|
70
|
+
</Form>
|
|
71
|
+
);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export default LoginForm;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { Button, Form, Input, message } from 'antd';
|
|
2
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
3
|
+
import { useRouter } from 'next/router';
|
|
4
|
+
import { useTranslation } from 'next-i18next';
|
|
5
|
+
import { FormProvider, useForm } from '@os-design/form';
|
|
6
|
+
import { graphql, useMutation } from 'react-relay';
|
|
7
|
+
import Link from 'next/link';
|
|
8
|
+
import useInterval from '@os-design/use-interval';
|
|
9
|
+
import handleFormErrors from '../utils/handleFormErrors';
|
|
10
|
+
import { useFrontendConfig } from '../utils/FrontendContext';
|
|
11
|
+
import {
|
|
12
|
+
RecoverPasswordInput,
|
|
13
|
+
recoveryMutation,
|
|
14
|
+
} from '../graphql/recoveryMutation.graphql';
|
|
15
|
+
import H from '../ui/H';
|
|
16
|
+
import Panel from '../ui/Panel';
|
|
17
|
+
import FormItem from '../ui/FormItem';
|
|
18
|
+
import FormError from '../ui/FormError';
|
|
19
|
+
import ResendLinkButton from './ResendLinkButton';
|
|
20
|
+
import { ResendLinkButtonMutation$data } from '../graphql/ResendLinkButtonMutation.graphql';
|
|
21
|
+
import { getLimitExpiresIn, setLimitExpiresAt } from '../utils/limitExpiresAt';
|
|
22
|
+
|
|
23
|
+
const RecoverPassword = () => {
|
|
24
|
+
const router = useRouter();
|
|
25
|
+
const { t } = useTranslation(['common']);
|
|
26
|
+
const config = useFrontendConfig();
|
|
27
|
+
|
|
28
|
+
let { email } = router.query;
|
|
29
|
+
if (typeof email !== 'string' || !email) email = '';
|
|
30
|
+
|
|
31
|
+
const { form, Field } = useForm<RecoverPasswordInput>({
|
|
32
|
+
token: '',
|
|
33
|
+
password: '',
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const [commit, loading] = useMutation<recoveryMutation>(
|
|
37
|
+
graphql`
|
|
38
|
+
mutation recoveryMutation($input: RecoverPasswordInput!) {
|
|
39
|
+
recoverPassword(input: $input) {
|
|
40
|
+
ok
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
`
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const recoverPasswordHandler = useCallback(() => {
|
|
47
|
+
commit({
|
|
48
|
+
variables: {
|
|
49
|
+
input: {
|
|
50
|
+
...form.values.getAll(),
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
onError: (error) => handleFormErrors(form, error),
|
|
54
|
+
onCompleted: () => {
|
|
55
|
+
message.success(t('common:auth.recoverySuccess'));
|
|
56
|
+
const profilePath = config.defaultPath
|
|
57
|
+
? `${config.defaultPath === '/' ? '' : config.defaultPath}/profile`
|
|
58
|
+
: '/profile';
|
|
59
|
+
router.push(profilePath);
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}, [commit, form, router, t, config.defaultPath]);
|
|
63
|
+
|
|
64
|
+
const [limitExpiresIn, setLimitExpiresIn] = useState(0);
|
|
65
|
+
|
|
66
|
+
// Update limitExpiresIn
|
|
67
|
+
const updateLimitExpiresIn = useCallback(
|
|
68
|
+
() => setLimitExpiresIn(getLimitExpiresIn()),
|
|
69
|
+
[]
|
|
70
|
+
);
|
|
71
|
+
useEffect(updateLimitExpiresIn, [updateLimitExpiresIn]); // Initial update
|
|
72
|
+
useInterval(updateLimitExpiresIn, limitExpiresIn > 0 ? 1000 : null); // Update every second
|
|
73
|
+
|
|
74
|
+
const resendIsDenied = limitExpiresIn > 0;
|
|
75
|
+
|
|
76
|
+
const getResendButtonTitle = () => {
|
|
77
|
+
if (resendIsDenied) {
|
|
78
|
+
return `${t('common:auth.openEmailResendDenied')} ${Math.ceil(
|
|
79
|
+
limitExpiresIn / 1000
|
|
80
|
+
)} ${t('common:sec')}`;
|
|
81
|
+
}
|
|
82
|
+
return t('common:auth.openEmailResend');
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<div className='recovery'>
|
|
87
|
+
<div className='recovery-header'>
|
|
88
|
+
{t('common:auth.setNewPassword')}
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
{email.length > 0 ? (
|
|
92
|
+
<>
|
|
93
|
+
<div style={{ color: 'red', marginBottom: 15 }}>
|
|
94
|
+
{t('common:auth.findCodeOnEmail')}
|
|
95
|
+
</div>
|
|
96
|
+
<FormProvider form={form}>
|
|
97
|
+
<Form layout='vertical' size='large'>
|
|
98
|
+
<FormError />
|
|
99
|
+
<Field
|
|
100
|
+
name='token'
|
|
101
|
+
render={({ value, onChange }: { value: string, onChange: (val: string) => void }, { error }: { error?: string }) => (
|
|
102
|
+
<FormItem
|
|
103
|
+
label='Код из письма'
|
|
104
|
+
error={error}
|
|
105
|
+
style={{ marginBottom: 0 }}
|
|
106
|
+
>
|
|
107
|
+
<Input
|
|
108
|
+
placeholder='------'
|
|
109
|
+
value={value}
|
|
110
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onChange(e.target.value)}
|
|
111
|
+
/>
|
|
112
|
+
</FormItem>
|
|
113
|
+
)}
|
|
114
|
+
/>
|
|
115
|
+
<div style={{ marginBottom: 25 }}>
|
|
116
|
+
<ResendLinkButton
|
|
117
|
+
email={email}
|
|
118
|
+
type='recovery'
|
|
119
|
+
disabled={resendIsDenied}
|
|
120
|
+
onError={(error: any) => handleFormErrors(form, error)}
|
|
121
|
+
onCompleted={(res: ResendLinkButtonMutation$data) => {
|
|
122
|
+
setLimitExpiresAt(res.resendLink.limitExpiresAt);
|
|
123
|
+
setLimitExpiresIn(getLimitExpiresIn());
|
|
124
|
+
|
|
125
|
+
// Отображаем сообщение
|
|
126
|
+
if (res.resendLink.ok)
|
|
127
|
+
message.success(t('common:auth.openEmailResendOk'));
|
|
128
|
+
else
|
|
129
|
+
message.error(t('common:auth.openEmailResendError'));
|
|
130
|
+
}}
|
|
131
|
+
>
|
|
132
|
+
{getResendButtonTitle()}
|
|
133
|
+
</ResendLinkButton>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
<Panel bg='secondary'>
|
|
137
|
+
<Field
|
|
138
|
+
name='password'
|
|
139
|
+
render={({ value, onChange }: { value: string, onChange: (val: string) => void }, { error }: { error?: string }) => (
|
|
140
|
+
<FormItem
|
|
141
|
+
label={t('common:auth.setNewPassword')}
|
|
142
|
+
error={error}
|
|
143
|
+
>
|
|
144
|
+
<Input.Password
|
|
145
|
+
value={value}
|
|
146
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onChange(e.target.value)}
|
|
147
|
+
placeholder={t('common:auth.newPassword')}
|
|
148
|
+
/>
|
|
149
|
+
</FormItem>
|
|
150
|
+
)}
|
|
151
|
+
/>
|
|
152
|
+
<Button
|
|
153
|
+
loading={loading}
|
|
154
|
+
onClick={recoverPasswordHandler}
|
|
155
|
+
type='primary'
|
|
156
|
+
>
|
|
157
|
+
{t('common:auth.saveNewPassword')}
|
|
158
|
+
</Button>
|
|
159
|
+
</Panel>
|
|
160
|
+
</Form>
|
|
161
|
+
</FormProvider>
|
|
162
|
+
</>
|
|
163
|
+
) : (
|
|
164
|
+
<>
|
|
165
|
+
<H
|
|
166
|
+
type='h2'
|
|
167
|
+
subHeader='Вы перешли на эту страницу с неверными параметрами, вернитесь назад, чтобы исправить ошибку'
|
|
168
|
+
textAlign='center'
|
|
169
|
+
>
|
|
170
|
+
Ошибочные параметры
|
|
171
|
+
</H>
|
|
172
|
+
<div style={{ textAlign: 'center' }}>
|
|
173
|
+
<Link href={config.forgotPasswordPath || '/forgot'}>
|
|
174
|
+
<Button type='primary' shape='round' size='large'>
|
|
175
|
+
Вернуться назад
|
|
176
|
+
</Button>
|
|
177
|
+
</Link>
|
|
178
|
+
</div>
|
|
179
|
+
</>
|
|
180
|
+
)}
|
|
181
|
+
</div>
|
|
182
|
+
);
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
export default RecoverPassword;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import React, { useCallback } from 'react';
|
|
2
|
+
import { Button, Checkbox, message } from 'antd';
|
|
3
|
+
import { useRouter } from 'next/router';
|
|
4
|
+
import { useTranslation } from 'next-i18next';
|
|
5
|
+
import Link from 'next/link';
|
|
6
|
+
import { useExistingForm } from '@os-design/form';
|
|
7
|
+
import { graphql, useMutation } from 'react-relay';
|
|
8
|
+
import handleFormErrors from '../utils/handleFormErrors';
|
|
9
|
+
import AuthFooterLinks from './AuthFooterLinks';
|
|
10
|
+
import H from '../ui/H';
|
|
11
|
+
import { useFrontendConfig } from '../utils/FrontendContext';
|
|
12
|
+
import {
|
|
13
|
+
RegisterInput,
|
|
14
|
+
RegisterMutation,
|
|
15
|
+
} from '../graphql/RegisterMutation.graphql';
|
|
16
|
+
import LoginForm from './LoginForm';
|
|
17
|
+
import FormItem from '../ui/FormItem';
|
|
18
|
+
|
|
19
|
+
interface RegisterProps {
|
|
20
|
+
onFinish: () => void;
|
|
21
|
+
isModal: boolean;
|
|
22
|
+
onForgotClick?: () => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const Register: React.FC<RegisterProps> = ({
|
|
26
|
+
onFinish,
|
|
27
|
+
isModal,
|
|
28
|
+
onForgotClick,
|
|
29
|
+
}) => {
|
|
30
|
+
const { t } = useTranslation();
|
|
31
|
+
const router = useRouter();
|
|
32
|
+
const config = useFrontendConfig();
|
|
33
|
+
|
|
34
|
+
const { form, Field } = useExistingForm<RegisterInput>();
|
|
35
|
+
|
|
36
|
+
const [commit, loading] = useMutation<RegisterMutation>(
|
|
37
|
+
graphql`
|
|
38
|
+
mutation RegisterMutation($input: RegisterInput!) {
|
|
39
|
+
register(input: $input) {
|
|
40
|
+
ok
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
`
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const registerHandler = useCallback(() => {
|
|
47
|
+
commit({
|
|
48
|
+
variables: {
|
|
49
|
+
input: {
|
|
50
|
+
...form.values.getAll(),
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
onError: (error) => handleFormErrors(form, error),
|
|
54
|
+
onCompleted: () => {
|
|
55
|
+
onFinish();
|
|
56
|
+
router.push(config.defaultPath || '/');
|
|
57
|
+
message.success(t('common:auth.successRegister'));
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
}, [commit, form, onFinish, router, t, config.defaultPath]);
|
|
61
|
+
|
|
62
|
+
const forgotButton = (
|
|
63
|
+
<Button type='link' onClick={onForgotClick}>
|
|
64
|
+
{t('common:auth.forgot')}
|
|
65
|
+
</Button>
|
|
66
|
+
);
|
|
67
|
+
const loginButton = (
|
|
68
|
+
<Button type='link' shape='round'>
|
|
69
|
+
{t('common:buttons.login')}
|
|
70
|
+
</Button>
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<div className='auth no-text-selection'>
|
|
75
|
+
<H type='h1' textAlign='center'>
|
|
76
|
+
{t('common:auth.registerTitle')}
|
|
77
|
+
</H>
|
|
78
|
+
{!isModal && (
|
|
79
|
+
<div className='auth-subtitle'>
|
|
80
|
+
{t('common:auth.registerSubtitle')}{' '}
|
|
81
|
+
<Link href={config.loginPath || '/login'} passHref>
|
|
82
|
+
{loginButton}
|
|
83
|
+
</Link>
|
|
84
|
+
</div>
|
|
85
|
+
)}
|
|
86
|
+
|
|
87
|
+
{/* Форма входа и регистрации одинаковые */}
|
|
88
|
+
<LoginForm onPressEnter={registerHandler} />
|
|
89
|
+
|
|
90
|
+
<div>
|
|
91
|
+
<Field
|
|
92
|
+
name='isUserAgreementAgree'
|
|
93
|
+
render={(
|
|
94
|
+
{
|
|
95
|
+
value,
|
|
96
|
+
onChange,
|
|
97
|
+
}: {
|
|
98
|
+
value: boolean | null | undefined;
|
|
99
|
+
onChange: (val: boolean | null | undefined) => void;
|
|
100
|
+
},
|
|
101
|
+
{ error }: { error?: string }
|
|
102
|
+
) => (
|
|
103
|
+
<FormItem error={error} style={{ marginBottom: 0 }}>
|
|
104
|
+
<Checkbox
|
|
105
|
+
style={{ fontSize: '16px' }}
|
|
106
|
+
checked={value || false}
|
|
107
|
+
onChange={(e: any) => onChange(e.target.checked)}
|
|
108
|
+
>
|
|
109
|
+
{t('common:auth.acceptUserAgreement')}{' '}
|
|
110
|
+
<Link href='/docs/user-agreement'>
|
|
111
|
+
{t('common:links.userAgreement')}
|
|
112
|
+
</Link>
|
|
113
|
+
</Checkbox>
|
|
114
|
+
</FormItem>
|
|
115
|
+
)}
|
|
116
|
+
/>
|
|
117
|
+
|
|
118
|
+
<Field
|
|
119
|
+
name='isPrivacyPolicyAgree'
|
|
120
|
+
render={(
|
|
121
|
+
{
|
|
122
|
+
value,
|
|
123
|
+
onChange,
|
|
124
|
+
}: {
|
|
125
|
+
value: boolean | null | undefined;
|
|
126
|
+
onChange: (val: boolean | null | undefined) => void;
|
|
127
|
+
},
|
|
128
|
+
{ error }: { error?: string }
|
|
129
|
+
) => (
|
|
130
|
+
<FormItem error={error}>
|
|
131
|
+
<Checkbox
|
|
132
|
+
checked={value || false}
|
|
133
|
+
onChange={(e: any) => onChange(e.target.checked)}
|
|
134
|
+
style={{ fontSize: '16px' }}
|
|
135
|
+
>
|
|
136
|
+
{t('common:auth.acceptPrivacyPolicy')}{' '}
|
|
137
|
+
<Link href='/docs/privacy-policy'>
|
|
138
|
+
{t('common:links.privacyPolicy')}
|
|
139
|
+
</Link>
|
|
140
|
+
</Checkbox>
|
|
141
|
+
</FormItem>
|
|
142
|
+
)}
|
|
143
|
+
/>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
<div className='auth-button'>
|
|
147
|
+
<Button
|
|
148
|
+
type='primary'
|
|
149
|
+
shape='round'
|
|
150
|
+
size='large'
|
|
151
|
+
loading={loading}
|
|
152
|
+
onClick={registerHandler}
|
|
153
|
+
>
|
|
154
|
+
{t('common:buttons.register')}
|
|
155
|
+
</Button>
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
<div className='auth-footer'>
|
|
159
|
+
<div>
|
|
160
|
+
{onForgotClick ? (
|
|
161
|
+
forgotButton
|
|
162
|
+
) : (
|
|
163
|
+
<Link href={config.forgotPasswordPath || '/forgot'} passHref>
|
|
164
|
+
{forgotButton}
|
|
165
|
+
</Link>
|
|
166
|
+
)}
|
|
167
|
+
</div>
|
|
168
|
+
<AuthFooterLinks />
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
);
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export default Register;
|