Skip to content

Commit

Permalink
feat: Add Invite Loading State
Browse files Browse the repository at this point in the history
  • Loading branch information
sternetj committed Feb 14, 2024
1 parent c9ec0de commit ac3eb38
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 7 deletions.
5 changes: 5 additions & 0 deletions src/components/Invitations/InviteNotifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ export type InviteParams = PendingInvite;

export type EventTypeHandlers = {
inviteDetected: (inviteParams: InviteParams) => void;
inviteLoadingStateChanged: (state: {
loading?: boolean;
failedToDecode?: boolean;
failureMessage?: string;
}) => void;
};

export type EventTypes = keyof EventTypeHandlers;
Expand Down
23 changes: 20 additions & 3 deletions src/components/Invitations/InviteProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,32 @@ export type PendingInvite = {
const usePendingInviteStore = create<PendingInvite | undefined>(
() => undefined,
);

inviteNotifier.addListener('inviteDetected', (invite) =>
usePendingInviteStore.setState(invite),
const usePendingInviteStateStore = create<{
loading?: boolean;
failedToDecode?: boolean;
failureMessage?: string;
}>(() => ({}));

inviteNotifier.addListener('inviteDetected', (invite) => {
usePendingInviteStore.setState(invite);
usePendingInviteStateStore.setState({
loading: false,
failedToDecode: false,
failureMessage: undefined,
});
});
inviteNotifier.addListener('inviteLoadingStateChanged', (state) =>
usePendingInviteStateStore.setState(state, true),
);

export const usePendingInvite = () => {
return usePendingInviteStore();
};

export const usePendingInviteState = () => {
return usePendingInviteStateStore();
};

export const clearPendingInvite = () => {
usePendingInviteStore.setState(undefined);
};
Expand Down
14 changes: 14 additions & 0 deletions src/screens/LoginScreen.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,20 @@ describe('LoginScreen', () => {
expect(getByText('Login')).toBeDefined();
});

it('renders invite loading', () => {
inviteNotifier.emit('inviteLoadingStateChanged', { loading: true });

const { getByText } = render(loginScreenInContext);
expect(getByText('Loading')).toBeDefined();
});

it('renders invite error invite failed to decode', () => {
inviteNotifier.emit('inviteLoadingStateChanged', { failedToDecode: true });

const { getByText } = render(loginScreenInContext);
expect(getByText('Invite Error Occurred')).toBeDefined();
});

it('renders accept invite if inviteID present', () => {
inviteNotifier.emit('inviteDetected', { inviteId: 'someInviteId' });

Expand Down
39 changes: 35 additions & 4 deletions src/screens/LoginScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ import { View, StyleSheet } from 'react-native';
import { t } from 'i18next';
import { OAuthLoginButton } from '../components/OAuthLoginButton';
import { useStyles, useDeveloperConfig } from '../hooks/';
import { usePendingInvite } from '../components/Invitations/InviteProvider';
import {
usePendingInvite,
usePendingInviteState,
} from '../components/Invitations/InviteProvider';
import { createStyles, useIcons } from '../components/BrandConfigProvider';
import LaunchScreen from '../components/LaunchScreen';
import { Dialog, Portal, Text } from 'react-native-paper';
import compact from 'lodash/compact';

export const LoginScreen: FC = () => {
const { renderCustomLoginScreen, componentProps = {} } = useDeveloperConfig();
Expand All @@ -15,6 +19,7 @@ export const LoginScreen: FC = () => {
const [errorText, setErrorText] = useState('');
const { AlertTriangle } = useIcons();
const pendingInvite = usePendingInvite();
const { loading, failedToDecode, failureMessage } = usePendingInviteState();
const { LoginScreen: loginScreenProps = {} } = componentProps;

const hideDialog = () => {
Expand All @@ -23,6 +28,9 @@ export const LoginScreen: FC = () => {
};

const getLoginButtonText = () => {
if (loading) {
return t('login-button-loading-invite', 'Loading');
}
if (pendingInvite) {
return (
loginScreenProps.acceptInviteText ??
Expand Down Expand Up @@ -61,7 +69,23 @@ export const LoginScreen: FC = () => {
<View style={styles.containerView}>
<LaunchScreen key="launch-screen" style={StyleSheet.absoluteFill} />
<View style={styles.buttonContainer}>
<OAuthLoginButton label={getLoginButtonText()} onFail={onFail} />
<OAuthLoginButton
label={getLoginButtonText()}
onFail={onFail}
loading={loading}
disabled={loading || failedToDecode}
/>
{failedToDecode && (
<Text
style={styles.inviteErrorText}
suppressHighlighting
onLongPress={() => {
setVisible(true);
}}
>
{t('login-invite-error', 'Invite Error Occurred')}
</Text>
)}
</View>
</View>
<Portal>
Expand All @@ -71,15 +95,17 @@ export const LoginScreen: FC = () => {
{t('login-error-title', 'Authentication Error')}
</Dialog.Title>
<Dialog.Content>
<Text variant="bodyMedium">{errorText}</Text>
<Text variant="bodyMedium">
{compact([errorText, failureMessage]).join('\n')}
</Text>
</Dialog.Content>
</Dialog>
</Portal>
</>
);
};

const defaultStyles = createStyles('LoginScreen', () => ({
const defaultStyles = createStyles('LoginScreen', (theme) => ({
containerView: {
flex: 1,
justifyContent: 'flex-end',
Expand All @@ -90,6 +116,11 @@ const defaultStyles = createStyles('LoginScreen', () => ({
width: '100%',
paddingHorizontal: 30,
},
inviteErrorText: {
color: theme.colors.error,
textAlign: 'center',
paddingVertical: theme.spacing.small,
},
}));

declare module '@styles' {
Expand Down

0 comments on commit ac3eb38

Please sign in to comment.