* Improve a11y on ios * Format * Remove android * Fix android * ALF confirmation dialog * Use ALF for Delete Post confirmation organize diff fix text minimize change copy alternative confirm prompt revert type changes add ButtonColor param * small adjustment to buttons in prompt * full width below gtmobile * update hide post dialog * space out dialogs * update dialogs for lists * add example * add to app passwords * Revert some changes * use sharedvalue for `importantForAccessibility` * add back `isOpen` * fix some more types * small adjustment to buttons in prompt * full width below gtmobile * update the rest of the prompts rm old confirm modal rm update prompt feed error prompt feed source card and profile block/unblock composer discard * Update src/view/screens/AppPasswords.tsx Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * lint * How about a default * Reverse reverse * Port over confirm dialogs * Add some comments * Remove unused file * complete merge * add testID where needed --------- Co-authored-by: Eric Bailey <git@esb.lol> Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>
353 lines
9 KiB
TypeScript
353 lines
9 KiB
TypeScript
import React from 'react'
|
|
import {
|
|
ActivityIndicator,
|
|
StyleSheet,
|
|
TouchableOpacity,
|
|
View,
|
|
} from 'react-native'
|
|
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
|
import {ScrollView} from 'react-native-gesture-handler'
|
|
import {Text} from '../com/util/text/Text'
|
|
import {Button} from '../com/util/forms/Button'
|
|
import * as Toast from '../com/util/Toast'
|
|
import {usePalette} from 'lib/hooks/usePalette'
|
|
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
|
import {NativeStackScreenProps} from '@react-navigation/native-stack'
|
|
import {CommonNavigatorParams} from 'lib/routes/types'
|
|
import {useAnalytics} from 'lib/analytics/analytics'
|
|
import {useFocusEffect} from '@react-navigation/native'
|
|
import {ViewHeader} from '../com/util/ViewHeader'
|
|
import {CenteredView} from 'view/com/util/Views'
|
|
import {Trans, msg} from '@lingui/macro'
|
|
import {useLingui} from '@lingui/react'
|
|
import {useSetMinimalShellMode} from '#/state/shell'
|
|
import {useModalControls} from '#/state/modals'
|
|
import {useLanguagePrefs} from '#/state/preferences'
|
|
import {
|
|
useAppPasswordsQuery,
|
|
useAppPasswordDeleteMutation,
|
|
} from '#/state/queries/app-passwords'
|
|
import {ErrorScreen} from '../com/util/error/ErrorScreen'
|
|
import {cleanError} from '#/lib/strings/errors'
|
|
import * as Prompt from '#/components/Prompt'
|
|
import {useDialogControl} from '#/components/Dialog'
|
|
|
|
type Props = NativeStackScreenProps<CommonNavigatorParams, 'AppPasswords'>
|
|
export function AppPasswords({}: Props) {
|
|
const pal = usePalette('default')
|
|
const {_} = useLingui()
|
|
const setMinimalShellMode = useSetMinimalShellMode()
|
|
const {screen} = useAnalytics()
|
|
const {isTabletOrDesktop} = useWebMediaQueries()
|
|
const {openModal} = useModalControls()
|
|
const {data: appPasswords, error} = useAppPasswordsQuery()
|
|
|
|
useFocusEffect(
|
|
React.useCallback(() => {
|
|
screen('AppPasswords')
|
|
setMinimalShellMode(false)
|
|
}, [screen, setMinimalShellMode]),
|
|
)
|
|
|
|
const onAdd = React.useCallback(async () => {
|
|
openModal({name: 'add-app-password'})
|
|
}, [openModal])
|
|
|
|
if (error) {
|
|
return (
|
|
<CenteredView
|
|
style={[
|
|
styles.container,
|
|
isTabletOrDesktop && styles.containerDesktop,
|
|
pal.view,
|
|
pal.border,
|
|
]}
|
|
testID="appPasswordsScreen">
|
|
<ErrorScreen
|
|
title={_(msg`Oops!`)}
|
|
message={_(msg`There was an issue with fetching your app passwords`)}
|
|
details={cleanError(error)}
|
|
/>
|
|
</CenteredView>
|
|
)
|
|
}
|
|
|
|
// no app passwords (empty) state
|
|
if (appPasswords?.length === 0) {
|
|
return (
|
|
<CenteredView
|
|
style={[
|
|
styles.container,
|
|
isTabletOrDesktop && styles.containerDesktop,
|
|
pal.view,
|
|
pal.border,
|
|
]}
|
|
testID="appPasswordsScreen">
|
|
<AppPasswordsHeader />
|
|
<View style={[styles.empty, pal.viewLight]}>
|
|
<Text type="lg" style={[pal.text, styles.emptyText]}>
|
|
<Trans>
|
|
You have not created any app passwords yet. You can create one by
|
|
pressing the button below.
|
|
</Trans>
|
|
</Text>
|
|
</View>
|
|
{!isTabletOrDesktop && <View style={styles.flex1} />}
|
|
<View
|
|
style={[
|
|
styles.btnContainer,
|
|
isTabletOrDesktop && styles.btnContainerDesktop,
|
|
]}>
|
|
<Button
|
|
testID="appPasswordBtn"
|
|
type="primary"
|
|
label={_(msg`Add App Password`)}
|
|
style={styles.btn}
|
|
labelStyle={styles.btnLabel}
|
|
onPress={onAdd}
|
|
/>
|
|
</View>
|
|
</CenteredView>
|
|
)
|
|
}
|
|
|
|
if (appPasswords?.length) {
|
|
// has app passwords
|
|
return (
|
|
<CenteredView
|
|
style={[
|
|
styles.container,
|
|
isTabletOrDesktop && styles.containerDesktop,
|
|
pal.view,
|
|
pal.border,
|
|
]}
|
|
testID="appPasswordsScreen">
|
|
<AppPasswordsHeader />
|
|
<ScrollView
|
|
style={[
|
|
styles.scrollContainer,
|
|
pal.border,
|
|
!isTabletOrDesktop && styles.flex1,
|
|
]}>
|
|
{appPasswords.map((password, i) => (
|
|
<AppPassword
|
|
key={password.name}
|
|
testID={`appPassword-${i}`}
|
|
name={password.name}
|
|
createdAt={password.createdAt}
|
|
/>
|
|
))}
|
|
{isTabletOrDesktop && (
|
|
<View style={[styles.btnContainer, styles.btnContainerDesktop]}>
|
|
<Button
|
|
testID="appPasswordBtn"
|
|
type="primary"
|
|
label={_(msg`Add App Password`)}
|
|
style={styles.btn}
|
|
labelStyle={styles.btnLabel}
|
|
onPress={onAdd}
|
|
/>
|
|
</View>
|
|
)}
|
|
</ScrollView>
|
|
{!isTabletOrDesktop && (
|
|
<View style={styles.btnContainer}>
|
|
<Button
|
|
testID="appPasswordBtn"
|
|
type="primary"
|
|
label={_(msg`Add App Password`)}
|
|
style={styles.btn}
|
|
labelStyle={styles.btnLabel}
|
|
onPress={onAdd}
|
|
/>
|
|
</View>
|
|
)}
|
|
</CenteredView>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<CenteredView
|
|
style={[
|
|
styles.container,
|
|
isTabletOrDesktop && styles.containerDesktop,
|
|
pal.view,
|
|
pal.border,
|
|
]}
|
|
testID="appPasswordsScreen">
|
|
<ActivityIndicator />
|
|
</CenteredView>
|
|
)
|
|
}
|
|
|
|
function AppPasswordsHeader() {
|
|
const {isTabletOrDesktop} = useWebMediaQueries()
|
|
const pal = usePalette('default')
|
|
const {_} = useLingui()
|
|
return (
|
|
<>
|
|
<ViewHeader title={_(msg`App Passwords`)} showOnDesktop />
|
|
<Text
|
|
type="sm"
|
|
style={[
|
|
styles.description,
|
|
pal.text,
|
|
isTabletOrDesktop && styles.descriptionDesktop,
|
|
]}>
|
|
<Trans>
|
|
Use app passwords to login to other Bluesky clients without giving
|
|
full access to your account or password.
|
|
</Trans>
|
|
</Text>
|
|
</>
|
|
)
|
|
}
|
|
|
|
function AppPassword({
|
|
testID,
|
|
name,
|
|
createdAt,
|
|
}: {
|
|
testID: string
|
|
name: string
|
|
createdAt: string
|
|
}) {
|
|
const pal = usePalette('default')
|
|
const {_} = useLingui()
|
|
const control = useDialogControl()
|
|
const {contentLanguages} = useLanguagePrefs()
|
|
const deleteMutation = useAppPasswordDeleteMutation()
|
|
|
|
const onDelete = React.useCallback(async () => {
|
|
await deleteMutation.mutateAsync({name})
|
|
Toast.show(_(msg`App password deleted`))
|
|
}, [deleteMutation, name, _])
|
|
|
|
const onPress = React.useCallback(() => {
|
|
control.open()
|
|
}, [control])
|
|
|
|
const primaryLocale =
|
|
contentLanguages.length > 0 ? contentLanguages[0] : 'en-US'
|
|
|
|
return (
|
|
<TouchableOpacity
|
|
testID={testID}
|
|
style={[styles.item, pal.border]}
|
|
onPress={onPress}
|
|
accessibilityRole="button"
|
|
accessibilityLabel={_(msg`Delete app password`)}
|
|
accessibilityHint="">
|
|
<View>
|
|
<Text type="md-bold" style={pal.text}>
|
|
{name}
|
|
</Text>
|
|
<Text type="md" style={[pal.text, styles.pr10]} numberOfLines={1}>
|
|
<Trans>
|
|
Created{' '}
|
|
{Intl.DateTimeFormat(primaryLocale, {
|
|
year: 'numeric',
|
|
month: 'numeric',
|
|
day: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
second: '2-digit',
|
|
}).format(new Date(createdAt))}
|
|
</Trans>
|
|
</Text>
|
|
</View>
|
|
<FontAwesomeIcon icon={['far', 'trash-can']} style={styles.trashIcon} />
|
|
|
|
<Prompt.Basic
|
|
control={control}
|
|
title={_(msg`Delete app password?`)}
|
|
description={_(
|
|
msg`Are you sure you want to delete the app password "${name}"?`,
|
|
)}
|
|
onConfirm={onDelete}
|
|
confirmButtonCta={_(msg`Delete`)}
|
|
confirmButtonColor="negative"
|
|
/>
|
|
</TouchableOpacity>
|
|
)
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
paddingBottom: 100,
|
|
},
|
|
containerDesktop: {
|
|
borderLeftWidth: 1,
|
|
borderRightWidth: 1,
|
|
paddingBottom: 0,
|
|
},
|
|
title: {
|
|
textAlign: 'center',
|
|
marginTop: 12,
|
|
marginBottom: 12,
|
|
},
|
|
description: {
|
|
textAlign: 'center',
|
|
paddingHorizontal: 20,
|
|
marginBottom: 14,
|
|
},
|
|
descriptionDesktop: {
|
|
marginTop: 14,
|
|
},
|
|
|
|
scrollContainer: {
|
|
borderTopWidth: 1,
|
|
marginTop: 4,
|
|
marginBottom: 16,
|
|
},
|
|
|
|
flex1: {
|
|
flex: 1,
|
|
},
|
|
empty: {
|
|
paddingHorizontal: 20,
|
|
paddingVertical: 20,
|
|
borderRadius: 16,
|
|
marginHorizontal: 24,
|
|
marginTop: 10,
|
|
},
|
|
emptyText: {
|
|
textAlign: 'center',
|
|
},
|
|
|
|
item: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
borderBottomWidth: 1,
|
|
paddingHorizontal: 20,
|
|
paddingVertical: 14,
|
|
},
|
|
pr10: {
|
|
marginRight: 10,
|
|
},
|
|
btnContainer: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'center',
|
|
},
|
|
btnContainerDesktop: {
|
|
marginTop: 14,
|
|
},
|
|
btn: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
borderRadius: 32,
|
|
paddingHorizontal: 60,
|
|
paddingVertical: 14,
|
|
},
|
|
btnLabel: {
|
|
fontSize: 18,
|
|
},
|
|
|
|
trashIcon: {
|
|
color: 'red',
|
|
minWidth: 16,
|
|
},
|
|
})
|