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

[Settings] App passwords revamp #5777

Open
wants to merge 3 commits into
base: samuel/settings-5
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion src/components/Admonition.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {Leaf_Stroke2_Corner0_Rounded as TipIcon} from '#/components/icons/Leaf'
import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning'
import {Text as BaseText, TextProps} from '#/components/Typography'

const colors = {
export const colors = {
warning: {
light: '#DFBC00',
dark: '#BFAF1F',
Expand Down
209 changes: 209 additions & 0 deletions src/screens/Settings/AppPasswords.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import React, {useCallback} from 'react'
import {View} from 'react-native'
import Animated, {
FadeIn,
FadeOut,
LayoutAnimationConfig,
LinearTransition,
StretchOutY,
} from 'react-native-reanimated'
import {ComAtprotoServerListAppPasswords} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {NativeStackScreenProps} from '@react-navigation/native-stack'

import {CommonNavigatorParams} from '#/lib/routes/types'
import {cleanError} from '#/lib/strings/errors'
import {isWeb} from '#/platform/detection'
import {
useAppPasswordDeleteMutation,
useAppPasswordsQuery,
} from '#/state/queries/app-passwords'
import {EmptyState} from '#/view/com/util/EmptyState'
import {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
import * as Toast from '#/view/com/util/Toast'
import {atoms as a, useTheme} from '#/alf'
import {Admonition, colors} from '#/components/Admonition'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {useDialogControl} from '#/components/Dialog'
import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus'
import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash'
import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning'
import * as Layout from '#/components/Layout'
import {Loader} from '#/components/Loader'
import * as Prompt from '#/components/Prompt'
import {Text} from '#/components/Typography'
import {AddAppPasswordDialog} from './components/AddAppPasswordDialog'
import * as SettingsList from './components/SettingsList'

type Props = NativeStackScreenProps<CommonNavigatorParams, 'AppPasswords'>
export function AppPasswordsScreen({}: Props) {
const {_} = useLingui()
const {data: appPasswords, error} = useAppPasswordsQuery()
const createAppPasswordControl = useDialogControl()

return (
<Layout.Screen testID="AppPasswordsScreen">
<Layout.Header title={_(msg`App Passwords`)} />
<Layout.Content>
{error ? (
<ErrorScreen
title={_(msg`Oops!`)}
message={_(msg`There was an issue fetching your app passwords`)}
details={cleanError(error)}
/>
) : (
<SettingsList.Container>
<SettingsList.Item>
<Admonition type="tip" style={[a.flex_1]}>
<Trans>
Use app passwords to sign in to other Bluesky clients without
giving full access to your account or password.
</Trans>
</Admonition>
</SettingsList.Item>
<SettingsList.Item>
<Button
label={_(msg`Add App Password`)}
size="large"
color="primary"
variant="solid"
onPress={() => createAppPasswordControl.open()}
style={[a.flex_1]}>
<ButtonIcon icon={PlusIcon} />
<ButtonText>
<Trans>Add App Password</Trans>
</ButtonText>
</Button>
</SettingsList.Item>
<SettingsList.Divider />
<LayoutAnimationConfig skipEntering skipExiting>
{appPasswords ? (
appPasswords.length > 0 ? (
<View style={[a.overflow_hidden]}>
{appPasswords.map(appPassword => (
<Animated.View
key={appPassword.name}
style={a.w_full}
entering={FadeIn}
exiting={isWeb ? FadeOut : StretchOutY}
layout={LinearTransition.delay(150)}>
<SettingsList.Item>
<AppPasswordCard appPassword={appPassword} />
</SettingsList.Item>
</Animated.View>
))}
</View>
) : (
<EmptyState
icon="growth"
message={_(msg`No app passwords yet`)}
/>
)
) : (
<View
style={[
a.flex_1,
a.justify_center,
a.align_center,
a.py_4xl,
]}>
<Loader size="xl" />
</View>
)}
</LayoutAnimationConfig>
</SettingsList.Container>
)}
</Layout.Content>

<AddAppPasswordDialog
control={createAppPasswordControl}
passwords={appPasswords?.map(p => p.name) || []}
/>
</Layout.Screen>
)
}

function AppPasswordCard({
appPassword,
}: {
appPassword: ComAtprotoServerListAppPasswords.AppPassword
}) {
const t = useTheme()
const {i18n, _} = useLingui()
const deleteControl = Prompt.usePromptControl()
const {mutateAsync: deleteMutation} = useAppPasswordDeleteMutation()

const onDelete = useCallback(async () => {
await deleteMutation({name: appPassword.name})
Toast.show(_(msg`App password deleted`))
}, [deleteMutation, appPassword.name, _])

return (
<View
style={[
a.w_full,
a.border,
a.rounded_sm,
a.px_md,
a.py_sm,
t.atoms.bg_contrast_25,
t.atoms.border_contrast_low,
]}>
<View
style={[
a.flex_row,
a.justify_between,
a.align_start,
a.w_full,
a.gap_sm,
]}>
<View style={[a.gap_xs]}>
<Text style={[t.atoms.text, a.text_md, a.font_bold]}>
{appPassword.name}
</Text>
<Text style={[t.atoms.text_contrast_medium]}>
<Trans>
Created{' '}
{i18n.date(appPassword.createdAt, {
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
})}
</Trans>
</Text>
</View>
<Button
label={_(msg`Delete app password`)}
variant="ghost"
color="negative"
size="small"
style={[a.bg_transparent]}
onPress={() => deleteControl.open()}>
<ButtonIcon icon={TrashIcon} />
</Button>
</View>
{appPassword.privileged && (
<View style={[a.flex_row, a.gap_sm, a.align_center, a.mt_md]}>
<WarningIcon style={[{color: colors.warning[t.scheme]}]} />
<Text style={t.atoms.text_contrast_high}>
<Trans>Allows access to direct messages</Trans>
</Text>
</View>
)}

<Prompt.Basic
control={deleteControl}
title={_(msg`Delete app password?`)}
description={_(
msg`Are you sure you want to delete the app password "${appPassword.name}"?`,
)}
onConfirm={onDelete}
confirmButtonCta={_(msg`Delete`)}
confirmButtonColor="negative"
/>
</View>
)
}
1 change: 1 addition & 0 deletions src/screens/Settings/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export function SettingsScreen({}: Props) {
<View
style={[
a.px_xl,
a.pt_md,
a.pb_md,
a.flex_1,
a.gap_2xs,
Expand Down
Loading
Loading