@ttoss/react-auth 1.1.0
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/README.md +146 -0
- package/dist/esm/index.js +529 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +564 -0
- package/i18n/lang/en.json +74 -0
- package/package.json +53 -0
- package/src/Auth.tsx +199 -0
- package/src/AuthCard.tsx +85 -0
- package/src/AuthConfirmSignUp.tsx +70 -0
- package/src/AuthContainer.tsx +21 -0
- package/src/AuthProvider.tsx +84 -0
- package/src/AuthSignIn.tsx +111 -0
- package/src/AuthSignUp.tsx +97 -0
- package/src/index.ts +4 -0
- package/src/types.ts +15 -0
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ttoss/react-auth",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "ttoss authentication module for React apps.",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"author": "ttoss",
|
|
7
|
+
"contributors": [
|
|
8
|
+
"Pedro Arantes <pedro@arantespp.com> (https://arantespp.com/contact)"
|
|
9
|
+
],
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"module": "./dist/esm/index.js",
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"i18n",
|
|
15
|
+
"src"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"prebuild": "ttoss-i18n --no-compile",
|
|
19
|
+
"build": "tsup",
|
|
20
|
+
"test": "jest"
|
|
21
|
+
},
|
|
22
|
+
"typings": "./dist/index.d.ts",
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@ttoss/cloud-auth": "^0.2.0",
|
|
25
|
+
"@ttoss/forms": "^0.11.4",
|
|
26
|
+
"@xstate/react": "^3.0.1",
|
|
27
|
+
"xstate": "^4.35.0"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"@ttoss/react-i18n": "^1.17.2",
|
|
31
|
+
"@ttoss/react-notifications": "^1.18.3",
|
|
32
|
+
"@ttoss/ui": "^1.26.3",
|
|
33
|
+
"aws-amplify": "5.x.x",
|
|
34
|
+
"react": ">=16.8.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@ttoss/config": "^1.25.0",
|
|
38
|
+
"@ttoss/i18n-cli": "^0.2.0",
|
|
39
|
+
"@ttoss/react-i18n": "^1.17.2",
|
|
40
|
+
"@ttoss/react-notifications": "^1.19.0",
|
|
41
|
+
"@ttoss/test-utils": "^1.18.3",
|
|
42
|
+
"@ttoss/ui": "^1.27.0",
|
|
43
|
+
"aws-amplify": "^5.0.7"
|
|
44
|
+
},
|
|
45
|
+
"keywords": [
|
|
46
|
+
"React",
|
|
47
|
+
"authentication"
|
|
48
|
+
],
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
},
|
|
52
|
+
"gitHead": "01759d9c247bed2fe52ca580e87c0b21544cac49"
|
|
53
|
+
}
|
package/src/Auth.tsx
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Auth as AmplifyAuth } from 'aws-amplify';
|
|
3
|
+
import { AuthConfirmSignUp } from './AuthConfirmSignUp';
|
|
4
|
+
import { AuthSignIn } from './AuthSignIn';
|
|
5
|
+
import { AuthSignUp } from './AuthSignUp';
|
|
6
|
+
import { LogoContextProps, LogoProvider } from './AuthCard';
|
|
7
|
+
import { assign, createMachine } from 'xstate';
|
|
8
|
+
import { useAuth } from './AuthProvider';
|
|
9
|
+
import { useMachine } from '@xstate/react';
|
|
10
|
+
import { useNotifications } from '@ttoss/react-notifications';
|
|
11
|
+
import type { OnConfirmSignUp, OnSignIn, OnSignUp } from './types';
|
|
12
|
+
|
|
13
|
+
type AuthState =
|
|
14
|
+
| {
|
|
15
|
+
value: 'signIn';
|
|
16
|
+
context: { email?: string };
|
|
17
|
+
}
|
|
18
|
+
| {
|
|
19
|
+
value: 'signUp';
|
|
20
|
+
context: Record<string, never>;
|
|
21
|
+
}
|
|
22
|
+
| {
|
|
23
|
+
value: 'signUpConfirm';
|
|
24
|
+
context: { email: string };
|
|
25
|
+
}
|
|
26
|
+
| {
|
|
27
|
+
value: 'signUpResendConfirmation';
|
|
28
|
+
context: { email: string };
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type AuthEvent =
|
|
32
|
+
| { type: 'SIGN_UP' }
|
|
33
|
+
| { type: 'SIGN_UP_CONFIRM'; email: string }
|
|
34
|
+
| { type: 'SIGN_UP_CONFIRMED'; email: string }
|
|
35
|
+
| { type: 'SIGN_UP_RESEND_CONFIRMATION'; email: string }
|
|
36
|
+
| { type: 'RETURN_TO_SIGN_IN' };
|
|
37
|
+
|
|
38
|
+
type AuthContext = { email?: string };
|
|
39
|
+
|
|
40
|
+
const authMachine = createMachine<AuthContext, AuthEvent, AuthState>(
|
|
41
|
+
{
|
|
42
|
+
predictableActionArguments: true,
|
|
43
|
+
initial: 'signIn',
|
|
44
|
+
states: {
|
|
45
|
+
signIn: {
|
|
46
|
+
on: {
|
|
47
|
+
SIGN_UP: { target: 'signUp' },
|
|
48
|
+
SIGN_UP_RESEND_CONFIRMATION: {
|
|
49
|
+
actions: ['assignEmail'],
|
|
50
|
+
target: 'signUpConfirm',
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
signUp: {
|
|
55
|
+
on: {
|
|
56
|
+
SIGN_UP_CONFIRM: {
|
|
57
|
+
actions: ['assignEmail'],
|
|
58
|
+
target: 'signUpConfirm',
|
|
59
|
+
},
|
|
60
|
+
RETURN_TO_SIGN_IN: { target: 'signIn' },
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
signUpConfirm: {
|
|
64
|
+
on: {
|
|
65
|
+
SIGN_UP_CONFIRMED: {
|
|
66
|
+
actions: ['assignEmail'],
|
|
67
|
+
target: 'signIn',
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
actions: {
|
|
75
|
+
assignEmail: assign({
|
|
76
|
+
email: (_, event) => {
|
|
77
|
+
return (event as any).email;
|
|
78
|
+
},
|
|
79
|
+
}),
|
|
80
|
+
},
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const AuthWithoutLogo = () => {
|
|
85
|
+
const { isAuthenticated } = useAuth();
|
|
86
|
+
|
|
87
|
+
const [state, send] = useMachine(authMachine);
|
|
88
|
+
|
|
89
|
+
const { setLoading } = useNotifications();
|
|
90
|
+
|
|
91
|
+
const onSignIn = React.useCallback<OnSignIn>(
|
|
92
|
+
async ({ email, password }) => {
|
|
93
|
+
try {
|
|
94
|
+
setLoading(true);
|
|
95
|
+
await AmplifyAuth.signIn(email, password);
|
|
96
|
+
// toast('Signed In');
|
|
97
|
+
} catch (error) {
|
|
98
|
+
switch ((error as any).code) {
|
|
99
|
+
case 'UserNotConfirmedException':
|
|
100
|
+
await AmplifyAuth.resendSignUp(email);
|
|
101
|
+
send({ type: 'SIGN_UP_RESEND_CONFIRMATION', email } as any);
|
|
102
|
+
break;
|
|
103
|
+
default:
|
|
104
|
+
// toast(JSON.stringify(error, null, 2));
|
|
105
|
+
}
|
|
106
|
+
} finally {
|
|
107
|
+
setLoading(false);
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
[send, setLoading]
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const onSignUp = React.useCallback<OnSignUp>(
|
|
114
|
+
async ({ email, password }) => {
|
|
115
|
+
try {
|
|
116
|
+
setLoading(true);
|
|
117
|
+
await AmplifyAuth.signUp({
|
|
118
|
+
username: email,
|
|
119
|
+
password,
|
|
120
|
+
attributes: { email },
|
|
121
|
+
});
|
|
122
|
+
// toast('Signed Up');
|
|
123
|
+
send({ type: 'SIGN_UP_CONFIRM', email } as any);
|
|
124
|
+
} catch (error) {
|
|
125
|
+
// toast(JSON.stringify(error, null, 2));
|
|
126
|
+
} finally {
|
|
127
|
+
setLoading(false);
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
[send, setLoading]
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
const onConfirmSignUp = React.useCallback<OnConfirmSignUp>(
|
|
134
|
+
async ({ email, code }) => {
|
|
135
|
+
try {
|
|
136
|
+
setLoading(true);
|
|
137
|
+
await AmplifyAuth.confirmSignUp(email, code);
|
|
138
|
+
// toast('Confirmed Signed In');
|
|
139
|
+
send({ type: 'SIGN_UP_CONFIRMED', email } as any);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
// toast(JSON.stringify(error, null, 2));
|
|
142
|
+
} finally {
|
|
143
|
+
setLoading(false);
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
[send, setLoading]
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const onReturnToSignIn = React.useCallback(() => {
|
|
150
|
+
send({ type: 'RETURN_TO_SIGN_IN' });
|
|
151
|
+
}, [send]);
|
|
152
|
+
|
|
153
|
+
if (isAuthenticated) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (state.matches('signUp')) {
|
|
158
|
+
return (
|
|
159
|
+
<AuthSignUp onSignUp={onSignUp} onReturnToSignIn={onReturnToSignIn} />
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (state.matches('signUpConfirm')) {
|
|
164
|
+
return (
|
|
165
|
+
<AuthConfirmSignUp
|
|
166
|
+
onConfirmSignUp={onConfirmSignUp}
|
|
167
|
+
email={(state.context as any).email}
|
|
168
|
+
/>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
<AuthSignIn
|
|
174
|
+
onSignIn={onSignIn}
|
|
175
|
+
onSignUp={() => {
|
|
176
|
+
return send('SIGN_UP');
|
|
177
|
+
}}
|
|
178
|
+
defaultValues={{ email: (state.context as any).email }}
|
|
179
|
+
/>
|
|
180
|
+
);
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const withLogo = <T extends Record<string, unknown>>(
|
|
184
|
+
Component: React.ComponentType<T>
|
|
185
|
+
) => {
|
|
186
|
+
const WithLogo = ({ logo, ...componentProps }: T & LogoContextProps) => {
|
|
187
|
+
return (
|
|
188
|
+
<LogoProvider logo={logo}>
|
|
189
|
+
<Component {...(componentProps as T)} />
|
|
190
|
+
</LogoProvider>
|
|
191
|
+
);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
WithLogo.displayName = 'WithLogo';
|
|
195
|
+
|
|
196
|
+
return WithLogo;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
export const Auth = withLogo(AuthWithoutLogo);
|
package/src/AuthCard.tsx
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Button, Card, Flex, Link, Text } from '@ttoss/ui';
|
|
3
|
+
import { useNotifications } from '@ttoss/react-notifications';
|
|
4
|
+
|
|
5
|
+
export type LogoContextProps = {
|
|
6
|
+
logo?: React.ReactNode;
|
|
7
|
+
children?: React.ReactNode;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const LogoContext = React.createContext<LogoContextProps>({});
|
|
11
|
+
|
|
12
|
+
export const LogoProvider = ({ children, ...values }: LogoContextProps) => {
|
|
13
|
+
return <LogoContext.Provider value={values}>{children}</LogoContext.Provider>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type LinkProps = {
|
|
17
|
+
label: string;
|
|
18
|
+
onClick: () => void;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type AuthCardProps = {
|
|
22
|
+
children: React.ReactNode;
|
|
23
|
+
title: string;
|
|
24
|
+
buttonLabel: string;
|
|
25
|
+
links?: LinkProps[];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const AuthCard = ({
|
|
29
|
+
children,
|
|
30
|
+
title,
|
|
31
|
+
buttonLabel,
|
|
32
|
+
links = [],
|
|
33
|
+
}: AuthCardProps) => {
|
|
34
|
+
const { logo } = React.useContext(LogoContext);
|
|
35
|
+
|
|
36
|
+
const { isLoading } = useNotifications();
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Card sx={{ maxWidth: '564px' }}>
|
|
40
|
+
<Flex sx={{ flexDirection: 'column', gap: 3 }}>
|
|
41
|
+
{logo && (
|
|
42
|
+
<Flex sx={{ width: '100%', justifyContent: 'center' }}>{logo}</Flex>
|
|
43
|
+
)}
|
|
44
|
+
<Text
|
|
45
|
+
variant="title"
|
|
46
|
+
sx={{ alignSelf: 'center', marginY: 4, fontSize: 5 }}
|
|
47
|
+
>
|
|
48
|
+
{title}
|
|
49
|
+
</Text>
|
|
50
|
+
{children}
|
|
51
|
+
<Flex sx={{ justifyContent: 'space-between', marginTop: 3 }}>
|
|
52
|
+
<Button
|
|
53
|
+
type="submit"
|
|
54
|
+
aria-label="submit-login"
|
|
55
|
+
variant="cta"
|
|
56
|
+
disabled={isLoading}
|
|
57
|
+
sx={{ width: '100%' }}
|
|
58
|
+
>
|
|
59
|
+
{buttonLabel}
|
|
60
|
+
</Button>
|
|
61
|
+
</Flex>
|
|
62
|
+
|
|
63
|
+
<Flex
|
|
64
|
+
sx={{
|
|
65
|
+
justifyContent: 'space-between',
|
|
66
|
+
flexDirection: 'column',
|
|
67
|
+
gap: 3,
|
|
68
|
+
marginTop: 4,
|
|
69
|
+
color: 'text',
|
|
70
|
+
}}
|
|
71
|
+
>
|
|
72
|
+
{links.map((link) => {
|
|
73
|
+
return (
|
|
74
|
+
link && (
|
|
75
|
+
<Link key={link.label} onClick={link.onClick}>
|
|
76
|
+
{link.label}
|
|
77
|
+
</Link>
|
|
78
|
+
)
|
|
79
|
+
);
|
|
80
|
+
})}
|
|
81
|
+
</Flex>
|
|
82
|
+
</Flex>
|
|
83
|
+
</Card>
|
|
84
|
+
);
|
|
85
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { AuthCard } from './AuthCard';
|
|
2
|
+
import { Form, FormFieldInput, useForm, yup, yupResolver } from '@ttoss/forms';
|
|
3
|
+
import { useI18n } from '@ttoss/react-i18n';
|
|
4
|
+
import type { OnConfirmSignUp } from './types';
|
|
5
|
+
|
|
6
|
+
export const AuthConfirmSignUp = ({
|
|
7
|
+
email,
|
|
8
|
+
onConfirmSignUp,
|
|
9
|
+
}: {
|
|
10
|
+
email: string;
|
|
11
|
+
onConfirmSignUp: OnConfirmSignUp;
|
|
12
|
+
}) => {
|
|
13
|
+
const { intl } = useI18n();
|
|
14
|
+
|
|
15
|
+
const schema = yup
|
|
16
|
+
.object()
|
|
17
|
+
.shape({
|
|
18
|
+
code: yup
|
|
19
|
+
.string()
|
|
20
|
+
.required(
|
|
21
|
+
intl.formatMessage({
|
|
22
|
+
description: 'Required field.',
|
|
23
|
+
defaultMessage: 'Required field',
|
|
24
|
+
})
|
|
25
|
+
)
|
|
26
|
+
.max(
|
|
27
|
+
6,
|
|
28
|
+
intl.formatMessage(
|
|
29
|
+
{
|
|
30
|
+
description: 'Minimum {value} characters.',
|
|
31
|
+
defaultMessage: 'Minimum {value} characters',
|
|
32
|
+
},
|
|
33
|
+
{ value: 6 }
|
|
34
|
+
)
|
|
35
|
+
),
|
|
36
|
+
})
|
|
37
|
+
.required();
|
|
38
|
+
|
|
39
|
+
const formMethods = useForm<yup.TypeOf<typeof schema>>({
|
|
40
|
+
resolver: yupResolver(schema),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Form
|
|
45
|
+
{...formMethods}
|
|
46
|
+
onSubmit={({ code }) => {
|
|
47
|
+
return onConfirmSignUp({ code, email });
|
|
48
|
+
}}
|
|
49
|
+
>
|
|
50
|
+
<AuthCard
|
|
51
|
+
buttonLabel={intl.formatMessage({
|
|
52
|
+
description: 'Confirm',
|
|
53
|
+
defaultMessage: 'Confirm',
|
|
54
|
+
})}
|
|
55
|
+
title={intl.formatMessage({
|
|
56
|
+
description: 'Confirmation',
|
|
57
|
+
defaultMessage: 'Confirmation',
|
|
58
|
+
})}
|
|
59
|
+
>
|
|
60
|
+
<FormFieldInput
|
|
61
|
+
name="code"
|
|
62
|
+
label={intl.formatMessage({
|
|
63
|
+
description: 'Sign up confirmation code',
|
|
64
|
+
defaultMessage: 'Code',
|
|
65
|
+
})}
|
|
66
|
+
/>
|
|
67
|
+
</AuthCard>
|
|
68
|
+
</Form>
|
|
69
|
+
);
|
|
70
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Flex, FlexProps } from '@ttoss/ui';
|
|
2
|
+
|
|
3
|
+
export type AuthContainerProps = FlexProps & { backgroundImageUrl?: string };
|
|
4
|
+
|
|
5
|
+
export const AuthContainer = ({ sx, ...props }: AuthContainerProps) => {
|
|
6
|
+
return (
|
|
7
|
+
<Flex
|
|
8
|
+
{...props}
|
|
9
|
+
sx={{
|
|
10
|
+
height: '100vh',
|
|
11
|
+
justifyContent: 'center',
|
|
12
|
+
alignItems: 'center',
|
|
13
|
+
margin: 0,
|
|
14
|
+
backgroundPosition: 'center',
|
|
15
|
+
backgroundRepeat: 'no-repeat',
|
|
16
|
+
backgroundSize: 'cover',
|
|
17
|
+
...sx,
|
|
18
|
+
}}
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Auth, Hub } from 'aws-amplify';
|
|
3
|
+
|
|
4
|
+
type User = {
|
|
5
|
+
id: string;
|
|
6
|
+
email: string;
|
|
7
|
+
emailVerified: string;
|
|
8
|
+
} | null;
|
|
9
|
+
|
|
10
|
+
type Tokens = {
|
|
11
|
+
idToken: string;
|
|
12
|
+
accessToken: string;
|
|
13
|
+
refreshToken: string;
|
|
14
|
+
} | null;
|
|
15
|
+
|
|
16
|
+
const signOut = () => {
|
|
17
|
+
return Auth.signOut();
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const AuthContext = React.createContext<{
|
|
21
|
+
signOut: () => Promise<any>;
|
|
22
|
+
isAuthenticated: boolean;
|
|
23
|
+
user: User;
|
|
24
|
+
tokens: Tokens;
|
|
25
|
+
}>({
|
|
26
|
+
signOut,
|
|
27
|
+
isAuthenticated: false,
|
|
28
|
+
user: null,
|
|
29
|
+
tokens: null,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
|
33
|
+
const [user, setUser] = React.useState<User>(null);
|
|
34
|
+
|
|
35
|
+
const [tokens, setTokens] = React.useState<Tokens>(null);
|
|
36
|
+
|
|
37
|
+
React.useEffect(() => {
|
|
38
|
+
const updateUser = () => {
|
|
39
|
+
Auth.currentAuthenticatedUser()
|
|
40
|
+
.then(({ attributes, signInUserSession }) => {
|
|
41
|
+
setUser({
|
|
42
|
+
id: attributes.sub,
|
|
43
|
+
email: attributes.email,
|
|
44
|
+
emailVerified: attributes['email_verified'],
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
setTokens({
|
|
48
|
+
idToken: signInUserSession.idToken.jwtToken,
|
|
49
|
+
accessToken: signInUserSession.accessToken.jwtToken,
|
|
50
|
+
refreshToken: signInUserSession.refreshToken.token,
|
|
51
|
+
});
|
|
52
|
+
})
|
|
53
|
+
.catch(() => {
|
|
54
|
+
setUser(null);
|
|
55
|
+
setTokens(null);
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const updateUserListener = Hub.listen('auth', updateUser);
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Check manually the first time.
|
|
63
|
+
*/
|
|
64
|
+
updateUser();
|
|
65
|
+
|
|
66
|
+
return () => {
|
|
67
|
+
updateUserListener();
|
|
68
|
+
};
|
|
69
|
+
}, []);
|
|
70
|
+
|
|
71
|
+
const isAuthenticated = !!user;
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<AuthContext.Provider value={{ signOut, isAuthenticated, user, tokens }}>
|
|
75
|
+
{children}
|
|
76
|
+
</AuthContext.Provider>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const useAuth = () => {
|
|
81
|
+
return React.useContext(AuthContext);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export default AuthProvider;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { AuthCard } from './AuthCard';
|
|
2
|
+
import { Form, FormFieldInput, useForm, yup, yupResolver } from '@ttoss/forms';
|
|
3
|
+
import { PASSWORD_MINIMUM_LENGTH } from '@ttoss/cloud-auth';
|
|
4
|
+
import { useI18n } from '@ttoss/react-i18n';
|
|
5
|
+
import type { OnSignIn, OnSignInInput } from './types';
|
|
6
|
+
|
|
7
|
+
export type AuthSignInProps = {
|
|
8
|
+
onSignIn: OnSignIn;
|
|
9
|
+
onSignUp: () => void;
|
|
10
|
+
defaultValues?: Partial<OnSignInInput>;
|
|
11
|
+
urlLogo?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const AuthSignIn = ({
|
|
15
|
+
onSignIn,
|
|
16
|
+
onSignUp,
|
|
17
|
+
defaultValues,
|
|
18
|
+
}: AuthSignInProps) => {
|
|
19
|
+
const { intl } = useI18n();
|
|
20
|
+
|
|
21
|
+
const schema = yup.object().shape({
|
|
22
|
+
email: yup
|
|
23
|
+
.string()
|
|
24
|
+
.required(
|
|
25
|
+
intl.formatMessage({
|
|
26
|
+
description: 'Email is a required field.',
|
|
27
|
+
defaultMessage: 'Email field is required',
|
|
28
|
+
})
|
|
29
|
+
)
|
|
30
|
+
.email(
|
|
31
|
+
intl.formatMessage({
|
|
32
|
+
description: 'Invalid email.',
|
|
33
|
+
defaultMessage: 'Invalid email',
|
|
34
|
+
})
|
|
35
|
+
),
|
|
36
|
+
password: yup
|
|
37
|
+
.string()
|
|
38
|
+
.required(
|
|
39
|
+
intl.formatMessage({
|
|
40
|
+
description: 'Password is required.',
|
|
41
|
+
defaultMessage: 'Password field is required',
|
|
42
|
+
})
|
|
43
|
+
)
|
|
44
|
+
.min(
|
|
45
|
+
PASSWORD_MINIMUM_LENGTH,
|
|
46
|
+
intl.formatMessage(
|
|
47
|
+
{
|
|
48
|
+
description: 'Password must be at least {value} characters long.',
|
|
49
|
+
defaultMessage: 'Password requires {value} characters',
|
|
50
|
+
},
|
|
51
|
+
{ value: PASSWORD_MINIMUM_LENGTH }
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
.trim(),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const formMethods = useForm<OnSignInInput>({
|
|
58
|
+
defaultValues,
|
|
59
|
+
resolver: yupResolver(schema),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const onSubmitForm = (data: OnSignInInput) => {
|
|
63
|
+
return onSignIn(data);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<Form {...formMethods} onSubmit={onSubmitForm}>
|
|
68
|
+
<AuthCard
|
|
69
|
+
title={intl.formatMessage({
|
|
70
|
+
description: 'Sign in title.',
|
|
71
|
+
defaultMessage: 'Login',
|
|
72
|
+
})}
|
|
73
|
+
buttonLabel={intl.formatMessage({
|
|
74
|
+
description: 'Button label.',
|
|
75
|
+
defaultMessage: 'Login',
|
|
76
|
+
})}
|
|
77
|
+
links={[
|
|
78
|
+
{
|
|
79
|
+
onClick: onSignUp,
|
|
80
|
+
label: intl.formatMessage({
|
|
81
|
+
description: 'Link to retrieve password.',
|
|
82
|
+
defaultMessage: 'Do you forgot your password?',
|
|
83
|
+
}),
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
onClick: onSignUp,
|
|
87
|
+
label: intl.formatMessage({
|
|
88
|
+
description: 'Link to sign up.',
|
|
89
|
+
defaultMessage: "Don't have an account? Sign up",
|
|
90
|
+
}),
|
|
91
|
+
},
|
|
92
|
+
]}
|
|
93
|
+
>
|
|
94
|
+
<FormFieldInput
|
|
95
|
+
name="email"
|
|
96
|
+
label={intl.formatMessage({
|
|
97
|
+
description: 'Email label.',
|
|
98
|
+
defaultMessage: 'Email',
|
|
99
|
+
})}
|
|
100
|
+
/>
|
|
101
|
+
<FormFieldInput
|
|
102
|
+
name="password"
|
|
103
|
+
label={intl.formatMessage({
|
|
104
|
+
description: 'Password label.',
|
|
105
|
+
defaultMessage: 'Password',
|
|
106
|
+
})}
|
|
107
|
+
/>
|
|
108
|
+
</AuthCard>
|
|
109
|
+
</Form>
|
|
110
|
+
);
|
|
111
|
+
};
|