@licklist/design 0.78.5-dev.60 → 0.78.5-dev.62

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.
Files changed (64) hide show
  1. package/dist/auth/Login/LoginPage.d.ts +8 -0
  2. package/dist/auth/Login/LoginPage.d.ts.map +1 -0
  3. package/dist/auth/Login/LoginPage.js +206 -0
  4. package/dist/auth/Login/index.d.ts +1 -0
  5. package/dist/auth/Login/index.d.ts.map +1 -1
  6. package/dist/index.js +1 -0
  7. package/dist/v2/components/FormField/FormField.d.ts +2 -0
  8. package/dist/v2/components/FormField/FormField.d.ts.map +1 -1
  9. package/dist/v2/components/FormField/FormField.js +12 -3
  10. package/dist/v2/components/FormField/FormField.scss.js +1 -1
  11. package/dist/v2/icons/index.d.ts +10 -0
  12. package/dist/v2/icons/index.d.ts.map +1 -1
  13. package/dist/v2/icons/index.js +1 -1
  14. package/dist/v2/index.d.ts +2 -0
  15. package/dist/v2/index.d.ts.map +1 -1
  16. package/dist/v2/pages/auth/AuthLayout/AuthLayout.d.ts +14 -0
  17. package/dist/v2/pages/auth/AuthLayout/AuthLayout.d.ts.map +1 -0
  18. package/dist/v2/pages/auth/AuthLayout/index.d.ts +3 -0
  19. package/dist/v2/pages/auth/AuthLayout/index.d.ts.map +1 -0
  20. package/dist/v2/pages/auth/CreatePassword/CreatePasswordPage.d.ts +10 -0
  21. package/dist/v2/pages/auth/CreatePassword/CreatePasswordPage.d.ts.map +1 -0
  22. package/dist/v2/pages/auth/CreatePassword/index.d.ts +3 -0
  23. package/dist/v2/pages/auth/CreatePassword/index.d.ts.map +1 -0
  24. package/dist/v2/pages/auth/Login/LoginPage.d.ts +11 -0
  25. package/dist/v2/pages/auth/Login/LoginPage.d.ts.map +1 -0
  26. package/dist/v2/pages/auth/Login/index.d.ts +3 -0
  27. package/dist/v2/pages/auth/Login/index.d.ts.map +1 -0
  28. package/dist/v2/pages/auth/ResetPassword/ResetPasswordPage.d.ts +12 -0
  29. package/dist/v2/pages/auth/ResetPassword/ResetPasswordPage.d.ts.map +1 -0
  30. package/dist/v2/pages/auth/ResetPassword/index.d.ts +3 -0
  31. package/dist/v2/pages/auth/ResetPassword/index.d.ts.map +1 -0
  32. package/dist/v2/pages/auth/VerifyEmail/VerifyEmailPage.d.ts +9 -0
  33. package/dist/v2/pages/auth/VerifyEmail/VerifyEmailPage.d.ts.map +1 -0
  34. package/dist/v2/pages/auth/VerifyEmail/index.d.ts +3 -0
  35. package/dist/v2/pages/auth/VerifyEmail/index.d.ts.map +1 -0
  36. package/dist/v2/pages/auth/index.d.ts +11 -0
  37. package/dist/v2/pages/auth/index.d.ts.map +1 -0
  38. package/package.json +2 -2
  39. package/src/auth/Login/LoginPage.tsx +52 -0
  40. package/src/auth/Login/index.ts +1 -0
  41. package/src/v2/components/FormField/FormField.scss +8 -0
  42. package/src/v2/components/FormField/FormField.tsx +31 -19
  43. package/src/v2/icons/index.tsx +103 -4
  44. package/src/v2/index.ts +4 -0
  45. package/src/v2/pages/auth/AuthLayout/AuthLayout.scss +135 -0
  46. package/src/v2/pages/auth/AuthLayout/AuthLayout.tsx +61 -0
  47. package/src/v2/pages/auth/AuthLayout/index.ts +2 -0
  48. package/src/v2/pages/auth/CreatePassword/CreatePasswordPage.scss +149 -0
  49. package/src/v2/pages/auth/CreatePassword/CreatePasswordPage.stories.tsx +45 -0
  50. package/src/v2/pages/auth/CreatePassword/CreatePasswordPage.tsx +181 -0
  51. package/src/v2/pages/auth/CreatePassword/index.ts +2 -0
  52. package/src/v2/pages/auth/Login/LoginPage.scss +49 -0
  53. package/src/v2/pages/auth/Login/LoginPage.stories.tsx +45 -0
  54. package/src/v2/pages/auth/Login/LoginPage.tsx +100 -0
  55. package/src/v2/pages/auth/Login/index.ts +2 -0
  56. package/src/v2/pages/auth/ResetPassword/ResetPasswordPage.scss +82 -0
  57. package/src/v2/pages/auth/ResetPassword/ResetPasswordPage.stories.tsx +53 -0
  58. package/src/v2/pages/auth/ResetPassword/ResetPasswordPage.tsx +110 -0
  59. package/src/v2/pages/auth/ResetPassword/index.ts +2 -0
  60. package/src/v2/pages/auth/VerifyEmail/VerifyEmailPage.scss +72 -0
  61. package/src/v2/pages/auth/VerifyEmail/VerifyEmailPage.stories.tsx +41 -0
  62. package/src/v2/pages/auth/VerifyEmail/VerifyEmailPage.tsx +110 -0
  63. package/src/v2/pages/auth/VerifyEmail/index.ts +2 -0
  64. package/src/v2/pages/auth/index.ts +14 -0
@@ -0,0 +1,45 @@
1
+ import React from 'react'
2
+ import type { Meta, StoryObj } from '@storybook/react'
3
+ import { CreatePasswordPage } from './CreatePasswordPage'
4
+
5
+ const meta: Meta<typeof CreatePasswordPage> = {
6
+ title: 'v2/Pages/Auth/Create Password',
7
+ component: CreatePasswordPage,
8
+ parameters: {
9
+ layout: 'fullscreen',
10
+ },
11
+ argTypes: {
12
+ onSubmit: { action: 'submitted' },
13
+ onResetInstead: { action: 'reset-instead' },
14
+ isLoading: { control: 'boolean' },
15
+ error: { control: 'text' },
16
+ },
17
+ }
18
+
19
+ export default meta
20
+ type Story = StoryObj<typeof CreatePasswordPage>
21
+
22
+ export const Default: Story = {
23
+ args: {
24
+ onSubmit: async (password) => {
25
+ console.log('Set password:', password)
26
+ },
27
+ onResetInstead: () => {
28
+ console.log('Reset instead clicked')
29
+ },
30
+ },
31
+ }
32
+
33
+ export const Loading: Story = {
34
+ args: {
35
+ ...Default.args,
36
+ isLoading: true,
37
+ },
38
+ }
39
+
40
+ export const WithError: Story = {
41
+ args: {
42
+ ...Default.args,
43
+ error: 'Failed to update password. Please try again.',
44
+ },
45
+ }
@@ -0,0 +1,181 @@
1
+ import React from 'react'
2
+ import { useForm } from 'react-hook-form'
3
+ import { useTranslation } from 'react-i18next'
4
+ import { AuthLayout } from '../AuthLayout'
5
+ import { FormField } from '../../../components/FormField'
6
+ import { Button } from '../../../components/Button'
7
+ import { PasswordTypeIcon, PasswordRequirementIcon } from '../../../icons'
8
+ import './CreatePasswordPage.scss'
9
+
10
+ type FormValues = {
11
+ password: string
12
+ confirmPassword: string
13
+ }
14
+
15
+ export interface CreatePasswordPageProps {
16
+ onSubmit: (password: string) => Promise<void> | void
17
+ onResetInstead?: () => void
18
+ isLoading?: boolean
19
+ error?: string
20
+ }
21
+
22
+ export const CreatePasswordPage: React.FC<CreatePasswordPageProps> = ({
23
+ onSubmit,
24
+ onResetInstead,
25
+ isLoading = false,
26
+ error,
27
+ }) => {
28
+ const { t } = useTranslation(['Validation', 'User'])
29
+ const { register, handleSubmit, watch, formState: { errors, touchedFields } } = useForm<FormValues>({ mode: 'onChange' })
30
+
31
+ const password = watch('password', '')
32
+ const confirmPassword = watch('confirmPassword', '')
33
+
34
+ const hasNumber = /\d/.test(password)
35
+ const hasCapital = /[A-Z]/.test(password)
36
+ const hasMinLength = password.length >= 8
37
+
38
+ const criteriaCount = [hasNumber, hasCapital, hasMinLength].filter(Boolean).length
39
+
40
+ const getStrengthColor = () => {
41
+ if (password.length === 0) return 'transparent'
42
+ if (criteriaCount <= 1) return 'var(--red-600)'
43
+ if (criteriaCount === 2) return 'var(--orange-500)'
44
+ return 'var(--green-600)'
45
+ }
46
+
47
+ const getStrengthWidth = () => {
48
+ if (password.length === 0) return '0%'
49
+ if (criteriaCount <= 1) return '33%'
50
+ if (criteriaCount === 2) return '66%'
51
+ return '100%'
52
+ }
53
+
54
+ const getStrengthLabel = () => {
55
+ if (password.length === 0) return ''
56
+ if (criteriaCount <= 1) return t('User:passwordStrengthWeak')
57
+ if (criteriaCount === 2) return t('User:passwordStrengthMedium')
58
+ return t('User:passwordStrengthStrong')
59
+ }
60
+
61
+ const passwordsMatch = password === confirmPassword && confirmPassword.length > 0
62
+ const showMatchIndicator = confirmPassword.length > 0
63
+
64
+ const onFormSubmit = async (data: FormValues) => {
65
+ await onSubmit(data.password)
66
+ }
67
+
68
+ return (
69
+ <AuthLayout
70
+ badgeLabel={t('User:newPassword')}
71
+ icon={<PasswordTypeIcon />}
72
+ title={t('User:newPassword')}
73
+ subtitle={t('User:newPasswordSubtitle')}
74
+ error={error}
75
+ >
76
+ <form className="auth-create-password" onSubmit={handleSubmit(onFormSubmit)} noValidate>
77
+
78
+ <div className="auth-create-password__requirements">
79
+ <p className="auth-create-password__requirements-title">
80
+ {t('User:passwordsMustContain')}
81
+ </p>
82
+ <ul className="auth-create-password__requirements-list">
83
+ <li className="auth-create-password__requirement-item">
84
+ <PasswordRequirementIcon met={hasNumber} visible={password.length > 0} />
85
+ {t('User:passwordRequireNumber')}
86
+ </li>
87
+ <li className="auth-create-password__requirement-item">
88
+ <PasswordRequirementIcon met={hasCapital} visible={password.length > 0} />
89
+ {t('User:passwordRequireCapital')}
90
+ </li>
91
+ <li className="auth-create-password__requirement-item">
92
+ <PasswordRequirementIcon met={hasMinLength} visible={password.length > 0} />
93
+ {t('User:passwordRequireMinLength')}
94
+ </li>
95
+ </ul>
96
+ </div>
97
+
98
+ <div className="auth-create-password__field">
99
+ <FormField
100
+ label={t('User:password')}
101
+ type="password"
102
+ disabled={isLoading}
103
+ error={errors.password?.message}
104
+ success={!!touchedFields.password && !errors.password}
105
+ {...register('password', {
106
+ required: t('Validation:fieldRequired', { attribute: t('User:password') }) as string,
107
+ minLength: {
108
+ value: 8,
109
+ message: t('Validation:fieldMinLength', { attribute: t('User:password'), min: 8 }) as string,
110
+ },
111
+ })}
112
+ />
113
+ {password.length > 0 && (
114
+ <div className="auth-create-password__strength">
115
+ <div className="auth-create-password__strength-header">
116
+ <span>{t('User:passwordStrength')}</span>
117
+ <span>{getStrengthLabel()}</span>
118
+ </div>
119
+ <div className="auth-create-password__strength-bar-track">
120
+ <div
121
+ className="auth-create-password__strength-bar-fill"
122
+ style={{
123
+ width: getStrengthWidth(),
124
+ backgroundColor: getStrengthColor(),
125
+ }}
126
+ />
127
+ </div>
128
+ </div>
129
+ )}
130
+ </div>
131
+
132
+ <div className="auth-create-password__field--last">
133
+ <FormField
134
+ label={t('User:confirmPassword')}
135
+ type="password"
136
+ disabled={isLoading}
137
+ error={errors.confirmPassword?.message}
138
+ success={!!touchedFields.confirmPassword && !errors.confirmPassword}
139
+ {...register('confirmPassword', {
140
+ required: t('Validation:fieldRequired', { attribute: t('User:confirmPassword') }) as string,
141
+ validate: (value) =>
142
+ value === password ||
143
+ (t('Validation:fieldDoesntMatchField', {
144
+ field1: t('User:confirmPassword'),
145
+ field2: t('User:password'),
146
+ }) as string),
147
+ })}
148
+ />
149
+ {showMatchIndicator && (
150
+ <div className={`auth-create-password__match auth-create-password__match--${passwordsMatch ? 'match' : 'no-match'}`}>
151
+ <PasswordRequirementIcon met={passwordsMatch} />
152
+ {passwordsMatch ? t('User:passwordsMatch') : t('User:passwordsDoNotMatch')}
153
+ </div>
154
+ )}
155
+ </div>
156
+
157
+ <div className="auth-create-password__actions">
158
+ <Button
159
+ type="submit"
160
+ variant="primary"
161
+ isLoading={isLoading}
162
+ disabled={isLoading}
163
+ >
164
+ {t('User:submit')}
165
+ </Button>
166
+
167
+ {onResetInstead && (
168
+ <button
169
+ type="button"
170
+ className="auth-create-password__reset-link"
171
+ onClick={onResetInstead}
172
+ disabled={isLoading}
173
+ >
174
+ {t('User:resetPasswordInstead')}
175
+ </button>
176
+ )}
177
+ </div>
178
+ </form>
179
+ </AuthLayout>
180
+ )
181
+ }
@@ -0,0 +1,2 @@
1
+ export { CreatePasswordPage } from './CreatePasswordPage'
2
+ export type { CreatePasswordPageProps } from './CreatePasswordPage'
@@ -0,0 +1,49 @@
1
+ .auth-login {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: 0;
5
+
6
+ &__field {
7
+ margin-bottom: 16px;
8
+ }
9
+
10
+ &__field--last {
11
+ margin-bottom: 24px;
12
+ }
13
+
14
+ &__forgot {
15
+ display: inline-block;
16
+ margin-top: 8px;
17
+ font-family: var(--font-family-sans, 'Geist', sans-serif);
18
+ font-size: 15px;
19
+ font-weight: 400;
20
+ color: var(--label-action);
21
+ text-decoration: underline;
22
+ background: none;
23
+ border: none;
24
+ padding: 0;
25
+ cursor: pointer;
26
+
27
+ &:hover {
28
+ opacity: 0.8;
29
+ }
30
+ }
31
+
32
+ &__actions {
33
+ margin-bottom: 0;
34
+ }
35
+
36
+ &__error {
37
+ display: flex;
38
+ align-items: center;
39
+ gap: 8px;
40
+ padding: 10px 12px;
41
+ border-radius: 8px;
42
+ background-color: var(--surface-status-error);
43
+ border: 1px solid var(--border-status-error);
44
+ font-family: var(--font-family-sans, 'Geist', sans-serif);
45
+ font-size: 13px;
46
+ color: var(--label-status-error);
47
+ margin-bottom: 16px;
48
+ }
49
+ }
@@ -0,0 +1,45 @@
1
+ import React from 'react'
2
+ import type { Meta, StoryObj } from '@storybook/react'
3
+ import { LoginPage } from './LoginPage'
4
+
5
+ const meta: Meta<typeof LoginPage> = {
6
+ title: 'v2/Pages/Auth/Login',
7
+ component: LoginPage,
8
+ parameters: {
9
+ layout: 'fullscreen',
10
+ },
11
+ argTypes: {
12
+ onSubmit: { action: 'submitted' },
13
+ onForgotPassword: { action: 'forgot-password' },
14
+ isLoading: { control: 'boolean' },
15
+ error: { control: 'text' },
16
+ },
17
+ }
18
+
19
+ export default meta
20
+ type Story = StoryObj<typeof LoginPage>
21
+
22
+ export const Default: Story = {
23
+ args: {
24
+ onSubmit: async (email, password) => {
25
+ console.log('Login:', email, password)
26
+ },
27
+ onForgotPassword: () => {
28
+ console.log('Forgot password clicked')
29
+ },
30
+ },
31
+ }
32
+
33
+ export const Loading: Story = {
34
+ args: {
35
+ ...Default.args,
36
+ isLoading: true,
37
+ },
38
+ }
39
+
40
+ export const WithError: Story = {
41
+ args: {
42
+ ...Default.args,
43
+ error: 'Invalid email or password. Please try again.',
44
+ },
45
+ }
@@ -0,0 +1,100 @@
1
+ import React from 'react'
2
+ import { useForm } from 'react-hook-form'
3
+ import { useTranslation } from 'react-i18next'
4
+ import { AuthLayout } from '../AuthLayout'
5
+ import { FormField } from '../../../components/FormField'
6
+ import { Button } from '../../../components/Button'
7
+ import { SecurityIcon } from '../../../icons'
8
+ import emailRule from '@licklist/plugins/dist/validation/Rules/emailRule'
9
+ import './LoginPage.scss'
10
+
11
+ type FormValues = {
12
+ email: string
13
+ password: string
14
+ }
15
+
16
+ export interface LoginPageProps {
17
+ onSubmit: (email: string, password: string) => Promise<void> | void
18
+ onForgotPassword?: () => void
19
+ isLoading?: boolean
20
+ error?: string
21
+ version?: string
22
+ }
23
+
24
+ export const LoginPage: React.FC<LoginPageProps> = ({
25
+ onSubmit,
26
+ onForgotPassword,
27
+ isLoading = false,
28
+ error,
29
+ version,
30
+ }) => {
31
+ const { t } = useTranslation(['Validation', 'User'])
32
+ const { register, handleSubmit, formState: { errors, touchedFields } } = useForm<FormValues>({ mode: 'onChange' })
33
+
34
+ const onFormSubmit = async (data: FormValues) => {
35
+ await onSubmit(data.email, data.password)
36
+ }
37
+
38
+ return (
39
+ <AuthLayout
40
+ badgeLabel={t('User:login')}
41
+ icon={<SecurityIcon />}
42
+ title={t('User:login')}
43
+ subtitle={t('User:loginSubtitle')}
44
+ version={version}
45
+ error={error}
46
+ >
47
+ <form className="auth-login" onSubmit={handleSubmit(onFormSubmit)} noValidate>
48
+ <FormField
49
+ className="auth-login__field"
50
+ label={t('User:emailAddress')}
51
+ type="email"
52
+ disabled={isLoading}
53
+ error={errors.email?.message}
54
+ success={!!touchedFields.email && !errors.email}
55
+ {...register('email', {
56
+ required: t('Validation:fieldRequired', { attribute: t('User:email') }) as string,
57
+ pattern: {
58
+ value: emailRule,
59
+ message: t('Validation:fieldValidEmail', { attribute: t('User:email') }) as string,
60
+ },
61
+ })}
62
+ />
63
+
64
+ <div className="auth-login__field--last">
65
+ <FormField
66
+ label={t('User:password')}
67
+ type="password"
68
+ disabled={isLoading}
69
+ error={errors.password?.message}
70
+ success={!!touchedFields.password && !errors.password}
71
+ {...register('password', {
72
+ required: t('Validation:fieldRequired', { attribute: t('User:password') }) as string,
73
+ })}
74
+ />
75
+ {onForgotPassword && (
76
+ <button
77
+ type="button"
78
+ className="auth-login__forgot"
79
+ onClick={onForgotPassword}
80
+ disabled={isLoading}
81
+ >
82
+ {t('User:forgotPassword')}
83
+ </button>
84
+ )}
85
+ </div>
86
+
87
+ <div className="auth-login__actions">
88
+ <Button
89
+ type="submit"
90
+ variant="primary"
91
+ isLoading={isLoading}
92
+ disabled={isLoading}
93
+ >
94
+ {t('User:login')}
95
+ </Button>
96
+ </div>
97
+ </form>
98
+ </AuthLayout>
99
+ )
100
+ }
@@ -0,0 +1,2 @@
1
+ export { LoginPage } from './LoginPage'
2
+ export type { LoginPageProps } from './LoginPage'
@@ -0,0 +1,82 @@
1
+ .auth-reset {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: 0;
5
+
6
+ &__description {
7
+ font-family: var(--font-family-sans, 'Geist', sans-serif);
8
+ font-size: 15px;
9
+ font-weight: 400;
10
+ line-height: 1.4;
11
+ color: var(--label-primary);
12
+ margin: 0 0 24px;
13
+ padding: 0;
14
+ }
15
+
16
+ &__field {
17
+ margin-bottom: 24px;
18
+ }
19
+
20
+ &__actions {
21
+ margin-bottom: 16px;
22
+ }
23
+
24
+ &__footer {
25
+ display: flex;
26
+ flex-direction: column;
27
+ gap: 8px;
28
+ }
29
+
30
+ &__footer-text {
31
+ font-family: var(--font-family-sans, 'Geist', sans-serif);
32
+ font-size: 15px;
33
+ font-weight: 400;
34
+ color: var(--label-primary);
35
+ margin: 0;
36
+ padding: 0;
37
+ }
38
+
39
+ &__link {
40
+ color: var(--label-action);
41
+ text-decoration: underline;
42
+ background: none;
43
+ border: none;
44
+ padding: 0;
45
+ cursor: pointer;
46
+ font-family: inherit;
47
+ font-size: inherit;
48
+ font-weight: inherit;
49
+
50
+ &:hover {
51
+ opacity: 0.8;
52
+ }
53
+ }
54
+
55
+ &__error {
56
+ display: flex;
57
+ align-items: center;
58
+ gap: 8px;
59
+ padding: 10px 12px;
60
+ border-radius: 8px;
61
+ background-color: var(--surface-status-error);
62
+ border: 1px solid var(--border-status-error);
63
+ font-family: var(--font-family-sans, 'Geist', sans-serif);
64
+ font-size: 13px;
65
+ color: var(--label-status-error);
66
+ margin-bottom: 16px;
67
+ }
68
+
69
+ &__success {
70
+ display: flex;
71
+ align-items: center;
72
+ gap: 8px;
73
+ padding: 10px 12px;
74
+ border-radius: 8px;
75
+ background-color: var(--surface-status-success);
76
+ border: 1px solid var(--border-status-success);
77
+ font-family: var(--font-family-sans, 'Geist', sans-serif);
78
+ font-size: 13px;
79
+ color: var(--label-status-success);
80
+ margin-bottom: 16px;
81
+ }
82
+ }
@@ -0,0 +1,53 @@
1
+ import React from 'react'
2
+ import type { Meta, StoryObj } from '@storybook/react'
3
+ import { ResetPasswordPage } from './ResetPasswordPage'
4
+
5
+ const meta: Meta<typeof ResetPasswordPage> = {
6
+ title: 'v2/Pages/Auth/Reset Password',
7
+ component: ResetPasswordPage,
8
+ parameters: {
9
+ layout: 'fullscreen',
10
+ },
11
+ argTypes: {
12
+ onSubmit: { action: 'submitted' },
13
+ onBackToLogin: { action: 'back-to-login' },
14
+ isLoading: { control: 'boolean' },
15
+ error: { control: 'text' },
16
+ successMessage: { control: 'text' },
17
+ },
18
+ }
19
+
20
+ export default meta
21
+ type Story = StoryObj<typeof ResetPasswordPage>
22
+
23
+ export const Default: Story = {
24
+ args: {
25
+ onSubmit: async (email) => {
26
+ console.log('Reset password for:', email)
27
+ },
28
+ onBackToLogin: () => {
29
+ console.log('Back to login clicked')
30
+ },
31
+ },
32
+ }
33
+
34
+ export const Loading: Story = {
35
+ args: {
36
+ ...Default.args,
37
+ isLoading: true,
38
+ },
39
+ }
40
+
41
+ export const WithError: Story = {
42
+ args: {
43
+ ...Default.args,
44
+ error: 'Failed to send reset email. Please try again.',
45
+ },
46
+ }
47
+
48
+ export const WithSuccess: Story = {
49
+ args: {
50
+ ...Default.args,
51
+ successMessage: 'Password reset email sent! Check your inbox.',
52
+ },
53
+ }
@@ -0,0 +1,110 @@
1
+ import React from 'react'
2
+ import { useForm } from 'react-hook-form'
3
+ import { useTranslation } from 'react-i18next'
4
+ import { AuthLayout } from '../AuthLayout'
5
+ import { FormField } from '../../../components/FormField'
6
+ import { Button } from '../../../components/Button'
7
+ import { RefreshIcon } from '../../../icons'
8
+ import emailRule from '@licklist/plugins/dist/validation/Rules/emailRule'
9
+ import './ResetPasswordPage.scss'
10
+
11
+ type FormValues = {
12
+ email: string
13
+ }
14
+
15
+ export interface ResetPasswordPageProps {
16
+ onSubmit: (email: string) => Promise<void> | void
17
+ onBackToLogin?: () => void
18
+ isLoading?: boolean
19
+ error?: string
20
+ successMessage?: string
21
+ version?: string
22
+ }
23
+
24
+ export const ResetPasswordPage: React.FC<ResetPasswordPageProps> = ({
25
+ onSubmit,
26
+ onBackToLogin,
27
+ isLoading = false,
28
+ error,
29
+ successMessage,
30
+ version,
31
+ }) => {
32
+ const { t } = useTranslation(['Validation', 'User'])
33
+ const { register, handleSubmit, formState: { errors, touchedFields } } = useForm<FormValues>({ mode: 'onChange' })
34
+
35
+ const onFormSubmit = async (data: FormValues) => {
36
+ await onSubmit(data.email)
37
+ }
38
+
39
+ return (
40
+ <AuthLayout
41
+ badgeLabel={t('User:resetPasswordTitle')}
42
+ icon={<RefreshIcon />}
43
+ title={t('User:resetPasswordTitle')}
44
+ subtitle={t('User:resetPasswordSubtitle')}
45
+ version={version}
46
+ error={error}
47
+ successMessage={successMessage}
48
+ >
49
+ <form className="auth-reset" onSubmit={handleSubmit(onFormSubmit)} noValidate>
50
+ <p className="auth-reset__description">
51
+ {t('User:resetPasswordDescription')}
52
+ </p>
53
+
54
+ <FormField
55
+ className="auth-reset__field"
56
+ label={t('User:emailAddress')}
57
+ type="email"
58
+ disabled={isLoading}
59
+ error={errors.email?.message}
60
+ success={!!touchedFields.email && !errors.email}
61
+ {...register('email', {
62
+ required: t('Validation:fieldRequired', { attribute: t('User:email') }) as string,
63
+ pattern: {
64
+ value: emailRule,
65
+ message: t('Validation:fieldValidEmail', { attribute: t('User:email') }) as string,
66
+ },
67
+ })}
68
+ />
69
+
70
+ <div className="auth-reset__actions">
71
+ <Button
72
+ type="submit"
73
+ variant="primary"
74
+ isLoading={isLoading}
75
+ disabled={isLoading}
76
+ >
77
+ {t('User:resetPasswordTitle')}
78
+ </Button>
79
+ </div>
80
+
81
+ <div className="auth-reset__footer">
82
+ {onBackToLogin && (
83
+ <p className="auth-reset__footer-text">
84
+ {t('User:resetPasswordAlreadyAccount')}{' '}
85
+ <button
86
+ type="button"
87
+ className="auth-reset__link"
88
+ onClick={onBackToLogin}
89
+ disabled={isLoading}
90
+ >
91
+ {t('User:login')}
92
+ </button>
93
+ .
94
+ </p>
95
+ )}
96
+ <p className="auth-reset__footer-text">
97
+ {t('User:forFurtherSupport')}{' '}
98
+ <a
99
+ href="mailto:support@booked.it"
100
+ className="auth-reset__link"
101
+ >
102
+ {t('User:contactUs')}
103
+ </a>
104
+ .
105
+ </p>
106
+ </div>
107
+ </form>
108
+ </AuthLayout>
109
+ )
110
+ }
@@ -0,0 +1,2 @@
1
+ export { ResetPasswordPage } from './ResetPasswordPage'
2
+ export type { ResetPasswordPageProps } from './ResetPasswordPage'