@cirrobio/react-auth 0.0.2 → 0.0.3

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.
@@ -1,109 +1,109 @@
1
- import { Alert, Dialog, IconButton, Stack, Typography } from '@mui/material';
2
- import React, { ReactElement, useCallback, useEffect, useMemo, useState } from 'react';
3
- import { LoginOptions } from './LoginOptions';
4
- import { LoginProvider } from '@cirrobio/api-client';
5
- import { handlePromiseError } from "@cirrobio/sdk";
6
- import { useAppConfig } from "@cirrobio/react-core";
7
- import { CloseOutlined } from '@mui/icons-material';
8
- import { COGNITO_PROVIDER_ID, LOGIN_SENT_SUCCESS_MSG } from "../amplify/magic-link";
9
-
10
- interface IProps {
11
- onClose: () => void;
12
- open: boolean;
13
- }
14
-
15
- function getLoginMessage(loginProviders: LoginProvider[]): string {
16
- const loginMessage = 'Sign in using one of the options below.';
17
- const cognitoEnabled = loginProviders.some(p => p.id === COGNITO_PROVIDER_ID);
18
- const onlyCognito = loginProviders.length === 1 && cognitoEnabled;
19
- // Only email sign in is enabled.
20
- if (onlyCognito) {
21
- return `Sign in with your email below.`;
22
- }
23
- // Both SSO and email sign in is enabled
24
- if (!onlyCognito && cognitoEnabled) {
25
- return `${loginMessage} If your sign in method isn't listed, please enter your email.`;
26
- }
27
- // Only SSO sign in is enabled
28
- return loginMessage;
29
- }
30
-
31
- export function LoginModal({ onClose, open }: Readonly<IProps>): ReactElement {
32
- const { authProvider } = useAppConfig();
33
- const loginProviders = authProvider.getLoginProviders();
34
- const loginDescription = useMemo(() => getLoginMessage(loginProviders), [loginProviders]);
35
-
36
- const [error, setError] = useState('');
37
- const [message, setMessage] = useState('');
38
- const [busy, setBusy] = useState(false);
39
-
40
- const handleLogin = useCallback(async (providerId: string, email?: string): Promise<void> => {
41
- setError('');
42
- setMessage('');
43
- setBusy(true);
44
- if (providerId === COGNITO_PROVIDER_ID) {
45
- try {
46
- await authProvider.loginEmail({ username: email });
47
- setBusy(false);
48
- setMessage(LOGIN_SENT_SUCCESS_MSG);
49
- } catch (e) {
50
- setError(e instanceof Error ? e.message : 'An error occurred');
51
- setBusy(false);
52
- }
53
- } else {
54
- authProvider.loginSSO(providerId).catch(handlePromiseError);
55
- if (onClose) onClose();
56
- }
57
- }, [onClose]);
58
-
59
- useEffect(() => {
60
- // Log in automatically if there is only one provider (and it's not Cognito)
61
- const firstProviderId = loginProviders.at(0)?.id;
62
- if (loginProviders.length === 1 && firstProviderId !== COGNITO_PROVIDER_ID) {
63
- void handleLogin(firstProviderId);
64
- }
65
- }, [loginProviders, handleLogin]);
66
-
67
- return (
68
- <Dialog
69
- open={open}
70
- onClose={onClose}
71
- PaperProps={{ sx: { p: 3, background: "#FFF", width: '480px' } }}
72
-
73
- aria-label="login dialog"
74
- >
75
- <Stack alignItems="left" justifyContent="space-between" gap={1} direction={{ xs: 'row' }}>
76
- <Stack direction="column">
77
- <Typography id="dialog-title" variant="h4" color="secondary">
78
- Login
79
- </Typography>
80
- <Typography variant="body2">
81
- {loginDescription}
82
- </Typography>
83
- </Stack>
84
- {!!onClose &&
85
- <Stack alignItems="right" direction="row" gap={0}>
86
- <IconButton
87
- aria-label="Close"
88
- onClick={() => onClose()}
89
- size="small"
90
- color="secondary"
91
- sx={{ transform: 'translate(10px, 0px)' }}>
92
- <CloseOutlined sx={{ fontSize: '22px' }} />
93
- </IconButton>
94
- </Stack>
95
- }
96
- </Stack>
97
- <Stack sx={{ pt: 0 }}>
98
- <LoginOptions loginProviders={loginProviders} onSelect={handleLogin} busy={busy} success={!!message} />
99
- {error && (
100
- <Alert severity="error" sx={{ mt: 4 }}>{error}</Alert>
101
- )}
102
- {message && (
103
- <Alert severity="success" sx={{ mt: 4 }}>{message}</Alert>
104
- )}
105
- </Stack>
106
- </Dialog>
107
- );
108
- }
109
-
1
+ import { Alert, Dialog, IconButton, Stack, Typography } from '@mui/material';
2
+ import React, { ReactElement, useCallback, useEffect, useMemo, useState } from 'react';
3
+ import { LoginOptions } from './LoginOptions';
4
+ import { LoginProvider } from '@cirrobio/api-client';
5
+ import { handlePromiseError } from "@cirrobio/sdk";
6
+ import { useAppConfig } from "@cirrobio/react-core";
7
+ import { CloseOutlined } from '@mui/icons-material';
8
+ import { COGNITO_PROVIDER_ID, LOGIN_SENT_SUCCESS_MSG } from "../amplify/magic-link";
9
+
10
+ interface IProps {
11
+ onClose: () => void;
12
+ open: boolean;
13
+ }
14
+
15
+ function getLoginMessage(loginProviders: LoginProvider[]): string {
16
+ const loginMessage = 'Sign in using one of the options below.';
17
+ const cognitoEnabled = loginProviders.some(p => p.id === COGNITO_PROVIDER_ID);
18
+ const onlyCognito = loginProviders.length === 1 && cognitoEnabled;
19
+ // Only email sign in is enabled.
20
+ if (onlyCognito) {
21
+ return `Sign in with your email below.`;
22
+ }
23
+ // Both SSO and email sign in is enabled
24
+ if (!onlyCognito && cognitoEnabled) {
25
+ return `${loginMessage} If your sign in method isn't listed, please enter your email.`;
26
+ }
27
+ // Only SSO sign in is enabled
28
+ return loginMessage;
29
+ }
30
+
31
+ export function LoginModal({ onClose, open }: Readonly<IProps>): ReactElement {
32
+ const { authProvider } = useAppConfig();
33
+ const loginProviders = authProvider.getLoginProviders();
34
+ const loginDescription = useMemo(() => getLoginMessage(loginProviders), [loginProviders]);
35
+
36
+ const [error, setError] = useState('');
37
+ const [message, setMessage] = useState('');
38
+ const [busy, setBusy] = useState(false);
39
+
40
+ const handleLogin = useCallback(async (providerId: string, email?: string): Promise<void> => {
41
+ setError('');
42
+ setMessage('');
43
+ setBusy(true);
44
+ if (providerId === COGNITO_PROVIDER_ID) {
45
+ try {
46
+ await authProvider.loginEmail({ username: email });
47
+ setBusy(false);
48
+ setMessage(LOGIN_SENT_SUCCESS_MSG);
49
+ } catch (e) {
50
+ setError(e instanceof Error ? e.message : 'An error occurred');
51
+ setBusy(false);
52
+ }
53
+ } else {
54
+ authProvider.loginSSO(providerId).catch(handlePromiseError);
55
+ if (onClose) onClose();
56
+ }
57
+ }, [onClose]);
58
+
59
+ useEffect(() => {
60
+ // Log in automatically if there is only one provider (and it's not Cognito)
61
+ const firstProviderId = loginProviders.at(0)?.id;
62
+ if (loginProviders.length === 1 && firstProviderId !== COGNITO_PROVIDER_ID) {
63
+ void handleLogin(firstProviderId);
64
+ }
65
+ }, [loginProviders, handleLogin]);
66
+
67
+ return (
68
+ <Dialog
69
+ open={open}
70
+ onClose={onClose}
71
+ PaperProps={{ sx: { p: 3, background: "#FFF", width: '480px' } }}
72
+
73
+ aria-label="login dialog"
74
+ >
75
+ <Stack alignItems="left" justifyContent="space-between" gap={1} direction={{ xs: 'row' }}>
76
+ <Stack direction="column">
77
+ <Typography id="dialog-title" variant="h4" color="secondary">
78
+ Login
79
+ </Typography>
80
+ <Typography variant="body2">
81
+ {loginDescription}
82
+ </Typography>
83
+ </Stack>
84
+ {!!onClose &&
85
+ <Stack alignItems="right" direction="row" gap={0}>
86
+ <IconButton
87
+ aria-label="Close"
88
+ onClick={() => onClose()}
89
+ size="small"
90
+ color="secondary"
91
+ sx={{ transform: 'translate(10px, 0px)' }}>
92
+ <CloseOutlined sx={{ fontSize: '22px' }} />
93
+ </IconButton>
94
+ </Stack>
95
+ }
96
+ </Stack>
97
+ <Stack sx={{ pt: 0 }}>
98
+ <LoginOptions loginProviders={loginProviders} onSelect={handleLogin} busy={busy} success={!!message} />
99
+ {error && (
100
+ <Alert severity="error" sx={{ mt: 4 }}>{error}</Alert>
101
+ )}
102
+ {message && (
103
+ <Alert severity="success" sx={{ mt: 4 }}>{message}</Alert>
104
+ )}
105
+ </Stack>
106
+ </Dialog>
107
+ );
108
+ }
109
+
@@ -1,67 +1,67 @@
1
- import React, { Children, ReactElement, useMemo, useState } from 'react';
2
- import { Button, Divider, Stack, TextField } from '@mui/material';
3
- import { LoginProvider } from '@cirrobio/api-client';
4
- import { LoadingButton } from '@mui/lab';
5
- import { COGNITO_PROVIDER_ID } from "../amplify/magic-link";
6
-
7
- interface IProps {
8
- loginProviders: LoginProvider[];
9
- onSelect: (providerId: string, email?: string) => void;
10
- busy: boolean;
11
- success: boolean;
12
- }
13
-
14
-
15
- export function LoginOptions({ loginProviders, onSelect, busy, success }: Readonly<IProps>): ReactElement {
16
- const [email, setEmail] = useState('');
17
-
18
- const ssoProviders = useMemo(() =>
19
- loginProviders.filter(p => p.id !== COGNITO_PROVIDER_ID), [loginProviders]);
20
- const cognitoEnabled = useMemo(() =>
21
- loginProviders.some(p => p.id === COGNITO_PROVIDER_ID), [loginProviders]);
22
-
23
- return (
24
- <Stack alignItems="center" gap={3} direction="column" sx={{ pt: 2 }}>
25
- {Children.toArray(ssoProviders.map(provider => {
26
- return (<Button
27
- sx={{ p: 2, width: '100%' }}
28
- variant="contained"
29
- color="secondary"
30
- fullWidth={true}
31
- startIcon={<img style={{ maxHeight: '17px' }} alt={provider.name} src={provider.logoUrl} />}
32
- onClick={() => onSelect(provider.id)}
33
- >{provider.name}</Button>)
34
- }))}
35
- {cognitoEnabled && (
36
- <form style={{ width: '100%' }}>
37
- <Stack alignItems="center" gap={3} direction="column">
38
- {ssoProviders.length > 0 && (<Divider textAlign="center" color="secondary" flexItem>OR</Divider>)}
39
- <TextField
40
- label="Email"
41
- size="medium"
42
- value={email}
43
- variant="outlined"
44
- autoComplete="off"
45
- type="email"
46
- fullWidth={true}
47
- required
48
- onChange={(e) => setEmail(e.target.value)}
49
- />
50
- <LoadingButton
51
- type="submit"
52
- sx={{ p: 2, width: '100%' }}
53
- loading={busy}
54
- disabled={!email?.trim() || success}
55
- variant="contained"
56
- color="secondary"
57
- fullWidth={true}
58
- onClick={() => onSelect(COGNITO_PROVIDER_ID, email)}
59
- >
60
- Sign in with email
61
- </LoadingButton>
62
- </Stack>
63
- </form>
64
- )}
65
- </Stack>
66
- )
67
- }
1
+ import React, { Children, ReactElement, useMemo, useState } from 'react';
2
+ import { Button, Divider, Stack, TextField } from '@mui/material';
3
+ import { LoginProvider } from '@cirrobio/api-client';
4
+ import { LoadingButton } from '@mui/lab';
5
+ import { COGNITO_PROVIDER_ID } from "../amplify/magic-link";
6
+
7
+ interface IProps {
8
+ loginProviders: LoginProvider[];
9
+ onSelect: (providerId: string, email?: string) => void;
10
+ busy: boolean;
11
+ success: boolean;
12
+ }
13
+
14
+
15
+ export function LoginOptions({ loginProviders, onSelect, busy, success }: Readonly<IProps>): ReactElement {
16
+ const [email, setEmail] = useState('');
17
+
18
+ const ssoProviders = useMemo(() =>
19
+ loginProviders.filter(p => p.id !== COGNITO_PROVIDER_ID), [loginProviders]);
20
+ const cognitoEnabled = useMemo(() =>
21
+ loginProviders.some(p => p.id === COGNITO_PROVIDER_ID), [loginProviders]);
22
+
23
+ return (
24
+ <Stack alignItems="center" gap={3} direction="column" sx={{ pt: 2 }}>
25
+ {Children.toArray(ssoProviders.map(provider => {
26
+ return (<Button
27
+ sx={{ p: 2, width: '100%' }}
28
+ variant="contained"
29
+ color="secondary"
30
+ fullWidth={true}
31
+ startIcon={<img style={{ maxHeight: '17px' }} alt={provider.name} src={provider.logoUrl} />}
32
+ onClick={() => onSelect(provider.id)}
33
+ >{provider.name}</Button>)
34
+ }))}
35
+ {cognitoEnabled && (
36
+ <form style={{ width: '100%' }}>
37
+ <Stack alignItems="center" gap={3} direction="column">
38
+ {ssoProviders.length > 0 && (<Divider textAlign="center" color="secondary" flexItem>OR</Divider>)}
39
+ <TextField
40
+ label="Email"
41
+ size="medium"
42
+ value={email}
43
+ variant="outlined"
44
+ autoComplete="off"
45
+ type="email"
46
+ fullWidth={true}
47
+ required
48
+ onChange={(e) => setEmail(e.target.value)}
49
+ />
50
+ <LoadingButton
51
+ type="submit"
52
+ sx={{ p: 2, width: '100%' }}
53
+ loading={busy}
54
+ disabled={!email?.trim() || success}
55
+ variant="contained"
56
+ color="secondary"
57
+ fullWidth={true}
58
+ onClick={() => onSelect(COGNITO_PROVIDER_ID, email)}
59
+ >
60
+ Sign in with email
61
+ </LoadingButton>
62
+ </Stack>
63
+ </form>
64
+ )}
65
+ </Stack>
66
+ )
67
+ }
@@ -1,25 +1,25 @@
1
- import React from "react";
2
- import { useAuthenticator } from "../auth-context/useAuthenticator";
3
- import { LoginModal } from "./LoginModal";
4
-
5
- interface IProps {
6
- children?: React.ReactNode;
7
- }
8
-
9
- /**
10
- * LoginWrapper component is used to conditionally render children based on the authentication status
11
- * or display a login modal if the user is unauthenticated.
12
- */
13
- export function LoginWrapper({ children }: IProps) {
14
- const { authStatus } = useAuthenticator();
15
-
16
- if (!authStatus || authStatus === 'configuring') {
17
- return null;
18
- }
19
-
20
- if (authStatus === 'unauthenticated') {
21
- return <LoginModal onClose={null} open={true} />
22
- }
23
-
24
- return children;
25
- }
1
+ import React from "react";
2
+ import { useAuthenticator } from "../auth-context/useAuthenticator";
3
+ import { LoginModal } from "./LoginModal";
4
+
5
+ interface IProps {
6
+ children?: React.ReactNode;
7
+ }
8
+
9
+ /**
10
+ * LoginWrapper component is used to conditionally render children based on the authentication status
11
+ * or display a login modal if the user is unauthenticated.
12
+ */
13
+ export function LoginWrapper({ children }: IProps) {
14
+ const { authStatus } = useAuthenticator();
15
+
16
+ if (!authStatus || authStatus === 'configuring') {
17
+ return null;
18
+ }
19
+
20
+ if (authStatus === 'unauthenticated') {
21
+ return <LoginModal onClose={null} open={true} />
22
+ }
23
+
24
+ return children;
25
+ }
package/src/index.ts CHANGED
@@ -1,12 +1,12 @@
1
- export { AmplifyAuthProvider } from './amplify/amplify-auth-provider';
2
- export type { AuthStatus } from './models/auth-status';
3
-
4
- // Components
5
- export { LoginModal } from './components/LoginModal';
6
- export { LoginOptions } from './components/LoginOptions';
7
- export { LoginWrapper } from './components/LoginWrapper';
8
-
9
- export { AuthenticationContextProvider } from './auth-context/authentication-context-provider';
10
- export { useAuthenticator } from './auth-context/useAuthenticator';
11
-
12
- export { StaticInteractiveAuthTokenProvider } from './static/static-token-provider';
1
+ export { AmplifyAuthProvider } from './amplify/amplify-auth-provider';
2
+ export type { AuthStatus } from './models/auth-status';
3
+
4
+ // Components
5
+ export { LoginModal } from './components/LoginModal';
6
+ export { LoginOptions } from './components/LoginOptions';
7
+ export { LoginWrapper } from './components/LoginWrapper';
8
+
9
+ export { AuthenticationContextProvider } from './auth-context/authentication-context-provider';
10
+ export { useAuthenticator } from './auth-context/useAuthenticator';
11
+
12
+ export { StaticInteractiveAuthTokenProvider } from './static/static-token-provider';
@@ -1 +1 @@
1
- export type AuthStatus = 'configuring' | 'authenticated' | 'unauthenticated';
1
+ export type AuthStatus = 'configuring' | 'authenticated' | 'unauthenticated';
@@ -1,44 +1,44 @@
1
- import { StaticTokenAuthProvider } from "@cirrobio/sdk";
2
- import { InteractiveAuthenticationProvider } from "@cirrobio/react-core";
3
-
4
-
5
- /**
6
- * StaticInteractiveAuthTokenProvider is a simple implementation of the InteractiveAuthenticationProvider
7
- * that uses a static token for authentication. This is useful for testing or when you have a known token.
8
- */
9
- export class StaticInteractiveAuthTokenProvider
10
- extends StaticTokenAuthProvider
11
- implements InteractiveAuthenticationProvider {
12
-
13
- constructor(token: string) {
14
- super(token);
15
- }
16
-
17
- async getAccessToken(): Promise<string> {
18
- return this.token;
19
- }
20
-
21
- loginSSO(): Promise<void> {
22
- return Promise.resolve();
23
- }
24
-
25
- loginEmail(_): Promise<void> {
26
- return Promise.resolve();
27
- }
28
-
29
- finishLoginEmail(_): Promise<void> {
30
- return Promise.resolve();
31
- }
32
-
33
- signOut(): Promise<void> {
34
- return Promise.resolve();
35
- }
36
-
37
- getLoginProviders() {
38
- return [];
39
- }
40
-
41
- registerAuthEventHandler(_): void {
42
- // No-op for static token provider
43
- }
44
- }
1
+ import { StaticTokenAuthProvider } from "@cirrobio/sdk";
2
+ import { InteractiveAuthenticationProvider } from "@cirrobio/react-core";
3
+
4
+
5
+ /**
6
+ * StaticInteractiveAuthTokenProvider is a simple implementation of the InteractiveAuthenticationProvider
7
+ * that uses a static token for authentication. This is useful for testing or when you have a known token.
8
+ */
9
+ export class StaticInteractiveAuthTokenProvider
10
+ extends StaticTokenAuthProvider
11
+ implements InteractiveAuthenticationProvider {
12
+
13
+ constructor(token: string) {
14
+ super(token);
15
+ }
16
+
17
+ async getAccessToken(): Promise<string> {
18
+ return this.token;
19
+ }
20
+
21
+ loginSSO(): Promise<void> {
22
+ return Promise.resolve();
23
+ }
24
+
25
+ loginEmail(_): Promise<void> {
26
+ return Promise.resolve();
27
+ }
28
+
29
+ finishLoginEmail(_): Promise<void> {
30
+ return Promise.resolve();
31
+ }
32
+
33
+ signOut(): Promise<void> {
34
+ return Promise.resolve();
35
+ }
36
+
37
+ getLoginProviders() {
38
+ return [];
39
+ }
40
+
41
+ registerAuthEventHandler(_): void {
42
+ // No-op for static token provider
43
+ }
44
+ }