Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add first time login dialog #553

Merged
merged 1 commit into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions example/AppDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ function App() {
primaryColor: '#fb5607',
},
apiBaseURL: baseURL,
showPreLoginWarning: false,
}}
>
<RootProviders account="mockaccount" authConfig={authConfig}>
Expand Down
1 change: 1 addition & 0 deletions src/common/DeveloperConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ export type DeveloperConfig = {
navigationLinking?: LinkingOptions<any>;
skipInviteParams?: boolean;
tileListMode?: 'list' | 'column';
showPreLoginWarning?: boolean;
};

export type LogoHeaderConfig = { [key in Route]?: LogoHeaderOptions };
Expand Down
49 changes: 44 additions & 5 deletions src/components/OAuthLoginButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,35 @@ import { useStyles } from '../hooks/useStyles';
import { LoginParams, useOAuthFlow } from '../hooks/useOAuthFlow';
import { tID } from '../common/testID';
import { createStyles } from './BrandConfigProvider';
import { useStoredValue } from '../hooks/useStoredValue';
import { useDeveloperConfig } from '../hooks/useDeveloperConfig';
import { t } from 'i18next';
import { Alert } from 'react-native';
import { usePendingInvite } from './Invitations/InviteProvider';

type Props = Omit<ButtonProps, 'onPress' | 'style' | 'children'> &
LoginParams & {
label: string;
style?: OAuthLoginButtonStyles;
};

const newLoginAlert = () => {
return new Promise<Boolean>((resolve) => {
Alert.alert(
t('new-login-title', 'New Login Notice'),
t(
'new-login-notice',
'NOTE: This device has not yet been used to login to the app.\nIf this is your first time using this app then you must first be invited to use this app via email.',
),
[
{ text: 'Proceed Anyway', onPress: () => resolve(true) },
{ text: 'Return', onPress: () => resolve(false) },
],
{ cancelable: false },
);
});
};

export function OAuthLoginButton({
onSuccess,
onFail,
Expand All @@ -21,13 +43,30 @@ export function OAuthLoginButton({
}: Props) {
const { styles } = useStyles(defaultStyles, instanceStyles);
const { login } = useOAuthFlow();
const [priorLoginDetected] = useStoredValue('loginDetected');
const { showPreLoginWarning } = useDeveloperConfig();
const pendingInvite = usePendingInvite();

const handleOnPress = useCallback(async () => {
await login({
onSuccess,
onFail,
});
}, [login, onSuccess, onFail]);
if (showPreLoginWarning && !pendingInvite && !priorLoginDetected) {
// If attempting to login for the first time without a pending invite
// let the user know that they are probably making a mistake
// This can be disabled via developerConfig
const result = await newLoginAlert();
if (result) {
await login({ onSuccess, onFail });
}
} else {
await login({ onSuccess, onFail });
}
}, [
pendingInvite,
priorLoginDetected,
showPreLoginWarning,
login,
onSuccess,
onFail,
]);

return (
<Button
Expand Down
11 changes: 10 additions & 1 deletion src/hooks/useOAuthFlow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { usePendingInvite } from '../components/Invitations/InviteProvider';
import { useQueryClient } from '@tanstack/react-query';
import { _sdkAnalyticsEvent } from '../common/Analytics';
import { useDeveloperConfig } from './useDeveloperConfig';
import { useStoredValue } from './useStoredValue';

export interface OAuthConfig {
login: (params: LoginParams) => Promise<void>;
Expand Down Expand Up @@ -78,6 +79,7 @@ export const OAuthContextProvider = ({
const pendingInvite = usePendingInvite();
const queryClient = useQueryClient();
const { skipInviteParams } = useDeveloperConfig();
const [_, setLoginDetected] = useStoredValue('loginDetected');

const authConfig = useMemo(
() =>
Expand Down Expand Up @@ -159,6 +161,7 @@ export const OAuthContextProvider = ({
try {
const result = await authorize(authConfig);
await storeAuthResult(result);
setLoginDetected('detected');
_sdkAnalyticsEvent.track('Login', { usedInvite: !!pendingInvite?.evc });
onSuccess?.(result);
} catch (error) {
Expand All @@ -170,7 +173,13 @@ export const OAuthContextProvider = ({
onFail?.(error);
}
},
[authConfig, clearAuthResult, pendingInvite?.evc, storeAuthResult],
[
authConfig,
clearAuthResult,
pendingInvite?.evc,
setLoginDetected,
storeAuthResult,
],
);

const refreshHandler = useCallback(
Expand Down
Loading