diff --git a/packages/components/src/components/cards/GenericMessageWithButtonView.tsx b/packages/components/src/components/cards/GenericMessageWithButtonView.tsx index 0d3b09ac3..ea8ee2ec2 100644 --- a/packages/components/src/components/cards/GenericMessageWithButtonView.tsx +++ b/packages/components/src/components/cards/GenericMessageWithButtonView.tsx @@ -11,9 +11,9 @@ import { SpringAnimatedText } from '../animated/spring/SpringAnimatedText' export interface GenericMessageWithButtonViewProps { buttonView: React.ReactNode - emoji: GitHubEmoji - subtitle: string | undefined - title: string + emoji: GitHubEmoji | null + subtitle: string | undefined | null + title: string | undefined | null } export const GenericMessageWithButtonView = React.memo( @@ -22,7 +22,7 @@ export const GenericMessageWithButtonView = React.memo( const springAnimatedTheme = useCSSVariablesOrSpringAnimatedTheme() - const emojiImageURL = getEmojiImageURL(emoji) + const emojiImageURL = emoji ? getEmojiImageURL(emoji) : null return ( { @@ -39,6 +39,8 @@ export const NoTokenView = React.memo((props: NoTokenViewProps) => { try { analytics.trackEvent('engagement', `relogin_add_token_${githubAppType}`) + setIsExecutingOAuth(true) + const params = await executeOAuth(githubAppType, { appToken: existingAppToken, scope: @@ -50,6 +52,7 @@ export const NoTokenView = React.memo((props: NoTokenViewProps) => { if (!appToken) throw new Error('No app token') loginRequest({ appToken }) + setIsExecutingOAuth(false) } catch (error) { const description = 'OAuth execution failed' console.error(description, error) @@ -58,7 +61,7 @@ export const NoTokenView = React.memo((props: NoTokenViewProps) => { if (error.message === 'Canceled' || error.message === 'Timeout') return bugsnag.notify(error, { description }) - alert(`Login failed. ${error || ''}`) + alert(`Authentication failed. ${error || ''}`) } } diff --git a/packages/components/src/components/cards/NotificationCard.tsx b/packages/components/src/components/cards/NotificationCard.tsx index 585d1b675..614cfe13c 100644 --- a/packages/components/src/components/cards/NotificationCard.tsx +++ b/packages/components/src/components/cards/NotificationCard.tsx @@ -14,8 +14,6 @@ import { isNotificationPrivate, trimNewLinesAndSpaces, } from '@devhub/core' -import { useReduxState } from '../../hooks/use-redux-state' -import * as selectors from '../../redux/selectors' import * as colors from '../../styles/colors' import { contentPadding } from '../../styles/variables' import { diff --git a/packages/components/src/components/cards/partials/rows/PrivateNotificationRow.tsx b/packages/components/src/components/cards/partials/rows/PrivateNotificationRow.tsx index 4d3bb3b50..215a5a470 100644 --- a/packages/components/src/components/cards/partials/rows/PrivateNotificationRow.tsx +++ b/packages/components/src/components/cards/partials/rows/PrivateNotificationRow.tsx @@ -1,8 +1,15 @@ -import React from 'react' -import { View } from 'react-native' +import React, { useState } from 'react' +import { StyleSheet, View } from 'react-native' -import { constants } from '@devhub/core' import { useCSSVariablesOrSpringAnimatedTheme } from '../../../../hooks/use-css-variables-or-spring--animated-theme' +import { useReduxAction } from '../../../../hooks/use-redux-action' +import { useReduxState } from '../../../../hooks/use-redux-state' +import { analytics } from '../../../../libs/analytics' +import { bugsnag } from '../../../../libs/bugsnag' +import { executeOAuth } from '../../../../libs/oauth' +import * as actions from '../../../../redux/actions' +import * as selectors from '../../../../redux/selectors' +import { tryParseOAuthParams } from '../../../../utils/helpers/auth' import { getGitHubAppInstallUri } from '../../../../utils/helpers/shared' import { SpringAnimatedText } from '../../../animated/spring/SpringAnimatedText' import { Link } from '../../../common/Link' @@ -18,9 +25,90 @@ export interface PrivateNotificationRowProps { export const PrivateNotificationRow = React.memo( (props: PrivateNotificationRowProps) => { + const { ownerId, repoId, smallLeftColumn } = props + const springAnimatedTheme = useCSSVariablesOrSpringAnimatedTheme() - const { ownerId, repoId, smallLeftColumn } = props + const existingAppToken = useReduxState(selectors.appTokenSelector) + const githubAppToken = useReduxState(selectors.githubAppTokenSelector) + const isLoggingIn = useReduxState(selectors.isLoggingInSelector) + const installationsLoadState = useReduxState( + selectors.installationsLoadStateSelector, + ) + const loginRequest = useReduxAction(actions.loginRequest) + + const showLoadingIndicator = + isLoggingIn || installationsLoadState === 'loading' + + async function startOAuth() { + try { + analytics.trackEvent('engagement', 'relogin_add_token_app') + + const params = await executeOAuth('app', { + appToken: existingAppToken, + scope: undefined, + }) + + const { appToken } = tryParseOAuthParams(params) + if (!appToken) throw new Error('No app token') + + loginRequest({ appToken }) + } catch (error) { + const description = 'OAuth execution failed' + console.error(description, error) + + if (error.message === 'Canceled' || error.message === 'Timeout') return + bugsnag.notify(error, { description }) + + alert(`Authentication failed. ${error || ''}`) + } + } + + function renderContent() { + if (!(existingAppToken && githubAppToken)) { + return ( + startOAuth()} + style={cardRowStyles.mainContentContainer} + > + + Required permission is missing. Tap to login again. + + + ) + } + + return ( + + + Install the GitHub App on this repo to unlock details from private + notifications. + + + ) + } return ( @@ -35,26 +123,37 @@ export const PrivateNotificationRow = React.memo( /> - - + + {!!showLoadingIndicator && ( + - Install the GitHub App to unlock details from private - notifications. - - + + Checking required permissions... + + + )} ) diff --git a/packages/components/src/components/common/Link.tsx b/packages/components/src/components/common/Link.tsx index 0839d8d46..124382d2e 100644 --- a/packages/components/src/components/common/Link.tsx +++ b/packages/components/src/components/common/Link.tsx @@ -80,7 +80,7 @@ export function Link(props: LinkProps) { } } - const renderTouchable = href || allowEmptyLink + const renderTouchable = href || otherProps.onPress || allowEmptyLink let finalProps: any if (renderTouchable) { diff --git a/packages/components/src/components/modals/AdvancedSettingsModal.tsx b/packages/components/src/components/modals/AdvancedSettingsModal.tsx index ba3507d9d..b5264b0b4 100644 --- a/packages/components/src/components/modals/AdvancedSettingsModal.tsx +++ b/packages/components/src/components/modals/AdvancedSettingsModal.tsx @@ -68,6 +68,7 @@ export const AdvancedSettingsModal = React.memo( if (!appToken) throw new Error('No app token') loginRequest({ appToken }) + setExecutingOAuth(null) } catch (error) { const description = 'OAuth execution failed' console.error(description, error) diff --git a/packages/components/src/screens/LoginScreen.tsx b/packages/components/src/screens/LoginScreen.tsx index a6df9c55c..b5c310872 100644 --- a/packages/components/src/screens/LoginScreen.tsx +++ b/packages/components/src/screens/LoginScreen.tsx @@ -179,6 +179,7 @@ export const LoginScreen = React.memo(() => { if (!appToken) throw new Error('No app token') loginRequest({ appToken }) + setIsExecutingOAuth(false) } catch (error) { const description = 'OAuth execution failed' console.error(description, error)