Use title case for Starter Pack (#11653)

This commit is contained in:
DS Boyce
2026-09-03 12:26:51 -07:00
committed by Samuel Newman
parent 34cbf713d0
commit 5c053ecd0c
39 changed files with 295 additions and 393 deletions
-52
View File
@@ -296,19 +296,6 @@
"count": 1 "count": 1
} }
}, },
"src/components/StarterPack/Main/ProfilesList.tsx": {
"typescript/no-misused-promises": {
"count": 1
}
},
"src/components/StarterPack/ProfileStarterPacks.tsx": {
"typescript/no-explicit-any": {
"count": 1
},
"typescript/no-misused-promises": {
"count": 2
}
},
"src/components/StarterPack/QrCodeDialog.tsx": { "src/components/StarterPack/QrCodeDialog.tsx": {
"typescript/no-floating-promises": { "typescript/no-floating-promises": {
"count": 5 "count": 5
@@ -398,21 +385,11 @@
"count": 1 "count": 1
} }
}, },
"src/components/dialogs/StarterPackDialog.tsx": {
"typescript/no-misused-promises": {
"count": 1
}
},
"src/components/dialogs/SwitchAccount.tsx": { "src/components/dialogs/SwitchAccount.tsx": {
"typescript/no-floating-promises": { "typescript/no-floating-promises": {
"count": 1 "count": 1
} }
}, },
"src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx": {
"typescript/no-floating-promises": {
"count": 2
}
},
"src/components/dialogs/lists/CreateOrEditListDialog.tsx": { "src/components/dialogs/lists/CreateOrEditListDialog.tsx": {
"typescript/no-explicit-any": { "typescript/no-explicit-any": {
"count": 2 "count": 2
@@ -857,14 +834,6 @@
"count": 1 "count": 1
} }
}, },
"src/screens/ProfileList/components/MoreOptionsMenu.tsx": {
"typescript/no-floating-promises": {
"count": 1
},
"typescript/no-misused-promises": {
"count": 5
}
},
"src/screens/ProfileList/components/SubscribeMenu.tsx": { "src/screens/ProfileList/components/SubscribeMenu.tsx": {
"typescript/no-misused-promises": { "typescript/no-misused-promises": {
"count": 2 "count": 2
@@ -996,22 +965,6 @@
"count": 1 "count": 1
} }
}, },
"src/screens/StarterPack/StarterPackScreen.tsx": {
"typescript/no-floating-promises": {
"count": 1
},
"typescript/require-await": {
"count": 1
}
},
"src/screens/StarterPack/Wizard/index.tsx": {
"typescript/no-floating-promises": {
"count": 2
},
"typescript/require-await": {
"count": 1
}
},
"src/state/a11y.tsx": { "src/state/a11y.tsx": {
"typescript/no-floating-promises": { "typescript/no-floating-promises": {
"count": 1 "count": 1
@@ -1233,11 +1186,6 @@
"count": 2 "count": 2
} }
}, },
"src/state/queries/starter-packs.ts": {
"typescript/require-await": {
"count": 1
}
},
"src/state/queries/suggested-follows.ts": { "src/state/queries/suggested-follows.ts": {
"no-unused-vars": { "no-unused-vars": {
"count": 1 "count": 1
+2 -2
View File
@@ -563,12 +563,12 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
<Stack.Screen <Stack.Screen
name="StarterPackWizard" name="StarterPackWizard"
getComponent={() => Wizard} getComponent={() => Wizard}
options={{title: title(msg`Create a starter pack`), requireAuth: true}} options={{title: title(msg`Create a Starter Pack`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="StarterPackEdit" name="StarterPackEdit"
getComponent={() => Wizard} getComponent={() => Wizard}
options={{title: title(msg`Edit your starter pack`), requireAuth: true}} options={{title: title(msg`Edit your Starter Pack`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="VideoFeed" name="VideoFeed"
+2 -2
View File
@@ -102,7 +102,7 @@ function DialogInner({
if (isMe) { if (isMe) {
if (profile.joinedViaStarterPack) { if (profile.joinedViaStarterPack) {
return _( return _(
msg`You joined Bluesky using a starter pack ${timeAgoString} ago`, msg`You joined Bluesky using a Starter Pack ${timeAgoString} ago`,
) )
} else { } else {
return _(msg`You joined Bluesky ${timeAgoString} ago`) return _(msg`You joined Bluesky ${timeAgoString} ago`)
@@ -110,7 +110,7 @@ function DialogInner({
} else { } else {
if (profile.joinedViaStarterPack) { if (profile.joinedViaStarterPack) {
return _( return _(
msg`${profileName} joined Bluesky using a starter pack ${timeAgoString} ago`, msg`${profileName} joined Bluesky using a Starter Pack ${timeAgoString} ago`,
) )
} else { } else {
return _(msg`${profileName} joined Bluesky ${timeAgoString} ago`) return _(msg`${profileName} joined Bluesky ${timeAgoString} ago`)
@@ -2,8 +2,7 @@ import {forwardRef, useCallback, useImperativeHandle, useState} from 'react'
import {type ListRenderItemInfo, View} from 'react-native' import {type ListRenderItemInfo, View} from 'react-native'
import {AtUri} from '@atproto/syntax' import {AtUri} from '@atproto/syntax'
import {type ModerationOpts} from '@bsky/sdk/moderation' import {type ModerationOpts} from '@bsky/sdk/moderation'
import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro' import {Trans} from '@lingui/react/macro'
import {useBottomBarOffset} from '#/lib/hooks/useBottomBarOffset' import {useBottomBarOffset} from '#/lib/hooks/useBottomBarOffset'
@@ -107,6 +106,12 @@ export const ProfilesList = forwardRef<SectionRef, ProfilesListProps>(
) )
} }
const onRefresh = async () => {
setIsPTRing(true)
await refetch()
setIsPTRing(false)
}
if (!data) { if (!data) {
return ( return (
<View <View
@@ -140,11 +145,7 @@ export const ProfilesList = forwardRef<SectionRef, ProfilesListProps>(
desktopFixedHeight desktopFixedHeight
initialNumToRender={initialNumToRender} initialNumToRender={initialNumToRender}
refreshing={isPTRing} refreshing={isPTRing}
onRefresh={async () => { onRefresh={() => void onRefresh()}
setIsPTRing(true)
await refetch()
setIsPTRing(false)
}}
/> />
) )
}, },
@@ -159,14 +160,14 @@ function OptedOutControls({
listUri: string listUri: string
canRemove: boolean canRemove: boolean
}) { }) {
const {_} = useLingui() const {t: l} = useLingui()
const ax = useAnalytics() const ax = useAnalytics()
const [isRemoved, setIsRemoved] = useState(false) const [isRemoved, setIsRemoved] = useState(false)
const {mutate: removeMembership, isPending} = useListMembershipRemoveMutation( const {mutate: removeMembership, isPending} = useListMembershipRemoveMutation(
{ {
onSuccess: () => { onSuccess: () => {
setIsRemoved(true) setIsRemoved(true)
Toast.show(_(msg`Removed from starter pack`)) Toast.show(l`Removed from Starter Pack`)
}, },
onError: error => onError: error =>
Toast.show(cleanError(error), { Toast.show(cleanError(error), {
@@ -183,12 +184,12 @@ function OptedOutControls({
<Admonition.Icon /> <Admonition.Icon />
<Admonition.Content> <Admonition.Content>
<Admonition.Text> <Admonition.Text>
<Trans>Opted out of this starter pack</Trans> <Trans>Opted out of this Starter Pack</Trans>
</Admonition.Text> </Admonition.Text>
</Admonition.Content> </Admonition.Content>
{canRemove ? ( {canRemove ? (
<Admonition.Button <Admonition.Button
label={_(msg`Remove user from starter pack`)} label={l`Remove user from Starter Pack`}
color="secondary" color="secondary"
disabled={isPending} disabled={isPending}
onPress={() => { onPress={() => {
@@ -6,9 +6,7 @@ import {
View, View,
type ViewStyle, type ViewStyle,
} from 'react-native' } from 'react-native'
import {msg} from '@lingui/core/macro' import {Trans, useLingui} from '@lingui/react/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
import {useNavigation} from '@react-navigation/native' import {useNavigation} from '@react-navigation/native'
import {useGenerateStarterPackMutation} from '#/lib/generate-starterpack' import {useGenerateStarterPackMutation} from '#/lib/generate-starterpack'
@@ -54,7 +52,7 @@ interface ProfileFeedgensProps {
isMe: boolean isMe: boolean
emptyStateMessage?: string emptyStateMessage?: string
emptyStateButton?: EmptyStateButtonProps emptyStateButton?: EmptyStateButtonProps
emptyStateIcon?: React.ComponentType<any> | React.ReactElement emptyStateIcon?: React.ComponentType | React.ReactElement
} }
function keyExtractor(item: app.bsky.graph.defs.StarterPackViewBasic) { function keyExtractor(item: app.bsky.graph.defs.StarterPackViewBasic) {
@@ -90,7 +88,7 @@ export function ProfileStarterPacks({
const {isTabletOrDesktop} = useWebMediaQueries() const {isTabletOrDesktop} = useWebMediaQueries()
const items = data?.pages.flatMap(page => page.starterPacks) const items = data?.pages.flatMap(page => page.starterPacks)
const {_} = useLingui() const {t: l} = useLingui()
const EmptyComponent = useCallback(() => { const EmptyComponent = useCallback(() => {
if (emptyStateMessage || emptyStateButton || emptyStateIcon) { if (emptyStateMessage || emptyStateButton || emptyStateIcon) {
@@ -101,9 +99,7 @@ export function ProfileStarterPacks({
iconSize="3xl" iconSize="3xl"
message={ message={
emptyStateMessage ?? emptyStateMessage ??
_( l`Starter Packs let you share your favorite feeds and people with your friends.`
msg`Starter packs let you share your favorite feeds and people with your friends.`,
)
} }
button={emptyStateButton} button={emptyStateButton}
/> />
@@ -111,7 +107,7 @@ export function ProfileStarterPacks({
) )
} }
return <Empty /> return <Empty />
}, [_, emptyStateMessage, emptyStateButton, emptyStateIcon]) }, [l, emptyStateMessage, emptyStateButton, emptyStateIcon])
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
scrollToTop: () => {}, scrollToTop: () => {},
@@ -122,7 +118,7 @@ export function ProfileStarterPacks({
try { try {
await refetch() await refetch()
} catch (err) { } catch (err) {
logger.error('Failed to refresh starter packs', {message: err}) logger.error('Failed to refresh Starter Packs', {message: err})
} }
setIsPTRing(false) setIsPTRing(false)
}, [refetch, setIsPTRing]) }, [refetch, setIsPTRing])
@@ -132,7 +128,7 @@ export function ProfileStarterPacks({
try { try {
await fetchNextPage() await fetchNextPage()
} catch (err) { } catch (err) {
logger.error('Failed to load more starter packs', {message: err}) logger.error('Failed to load more Starter Packs', {message: err})
} }
}, [isFetchingNextPage, hasNextPage, isError, fetchNextPage]) }, [isFetchingNextPage, hasNextPage, isError, fetchNextPage])
@@ -179,8 +175,8 @@ export function ProfileStarterPacks({
}} }}
removeClippedSubviews={true} removeClippedSubviews={true}
desktopFixedHeight desktopFixedHeight
onEndReached={onEndReached} onEndReached={() => void onEndReached()}
onRefresh={onRefresh} onRefresh={() => void onRefresh()}
ListEmptyComponent={ ListEmptyComponent={
data ? (isMe ? EmptyComponent : undefined) : FeedLoadingPlaceholder data ? (isMe ? EmptyComponent : undefined) : FeedLoadingPlaceholder
} }
@@ -193,7 +189,7 @@ export function ProfileStarterPacks({
} }
function CreateAnother() { function CreateAnother() {
const {_} = useLingui() const {t: l} = useLingui()
const t = useTheme() const t = useTheme()
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
@@ -207,7 +203,7 @@ function CreateAnother() {
t.atoms.border_contrast_low, t.atoms.border_contrast_low,
]}> ]}>
<Button <Button
label={_(msg`Create a starter pack`)} label={l`Create a Starter Pack`}
variant="solid" variant="solid"
color="secondary" color="secondary"
size="small" size="small"
@@ -223,7 +219,7 @@ function CreateAnother() {
} }
function Empty() { function Empty() {
const {_} = useLingui() const {t: l} = useLingui()
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
const confirmDialogControl = useDialogControl() const confirmDialogControl = useDialogControl()
const followersDialogControl = useDialogControl() const followersDialogControl = useDialogControl()
@@ -244,7 +240,7 @@ function Empty() {
setIsGenerating(false) setIsGenerating(false)
}, },
onError: e => { onError: e => {
logger.error('Failed to generate starter pack', {safeMessage: e}) logger.error('Failed to generate Starter Pack', {safeMessage: e})
setIsGenerating(false) setIsGenerating(false)
if (e.message.includes('NOT_ENOUGH_FOLLOWERS')) { if (e.message.includes('NOT_ENOUGH_FOLLOWERS')) {
followersDialogControl.open() followersDialogControl.open()
@@ -265,7 +261,7 @@ function Empty() {
const wrappedOpenConfirmDialog = requireEmailVerification(openConfirmDialog, { const wrappedOpenConfirmDialog = requireEmailVerification(openConfirmDialog, {
instructions: [ instructions: [
<Trans key="confirm"> <Trans key="confirm">
Before creating a starter pack, you must first verify your email. Before creating a Starter Pack, you must first verify your email.
</Trans>, </Trans>,
], ],
}) })
@@ -275,7 +271,7 @@ function Empty() {
const wrappedNavToWizard = requireEmailVerification(navToWizard, { const wrappedNavToWizard = requireEmailVerification(navToWizard, {
instructions: [ instructions: [
<Trans key="nav"> <Trans key="nav">
Before creating a starter pack, you must first verify your email. Before creating a Starter Pack, you must first verify your email.
</Trans>, </Trans>,
], ],
}) })
@@ -292,18 +288,18 @@ function Empty() {
]}> ]}>
<View style={[a.gap_xs]}> <View style={[a.gap_xs]}>
<Text style={[a.font_semi_bold, a.text_lg, {color: 'white'}]}> <Text style={[a.font_semi_bold, a.text_lg, {color: 'white'}]}>
<Trans>You haven't created a starter pack yet!</Trans> <Trans>You haven't created a Starter Pack yet!</Trans>
</Text> </Text>
<Text style={[a.text_md, {color: 'white'}]}> <Text style={[a.text_md, {color: 'white'}]}>
<Trans> <Trans>
Starter packs let you easily share your favorite feeds and people Starter Packs let you easily share your favorite feeds and people
with your friends. with your friends.
</Trans> </Trans>
</Text> </Text>
</View> </View>
<View style={[a.flex_row, a.gap_md, {marginLeft: 'auto'}]}> <View style={[a.flex_row, a.gap_md, {marginLeft: 'auto'}]}>
<Button <Button
label={_(msg`Create a starter pack for me`)} label={l`Create a Starter Pack for me`}
variant="ghost" variant="ghost"
color="primary" color="primary"
size="small" size="small"
@@ -316,7 +312,7 @@ function Empty() {
{isGenerating && <Loader size="md" />} {isGenerating && <Loader size="md" />}
</Button> </Button>
<Button <Button
label={_(msg`Create a starter pack`)} label={l`Create a Starter Pack`}
variant="ghost" variant="ghost"
color="primary" color="primary"
size="small" size="small"
@@ -333,11 +329,10 @@ function Empty() {
</ButtonText> </ButtonText>
</Button> </Button>
</View> </View>
<Prompt.Outer control={confirmDialogControl}> <Prompt.Outer control={confirmDialogControl}>
<Prompt.Content> <Prompt.Content>
<Prompt.TitleText> <Prompt.TitleText>
<Trans>Generate a starter pack</Trans> <Trans>Generate a Starter Pack</Trans>
</Prompt.TitleText> </Prompt.TitleText>
<Prompt.DescriptionText> <Prompt.DescriptionText>
<Trans> <Trans>
@@ -349,12 +344,12 @@ function Empty() {
<Prompt.Actions> <Prompt.Actions>
<Prompt.Action <Prompt.Action
color="primary" color="primary"
cta={_(msg`Choose for me`)} cta={l`Choose for me`}
onPress={generate} onPress={generate}
/> />
<Prompt.Action <Prompt.Action
color="secondary" color="secondary"
cta={_(msg`Let me choose`)} cta={l`Let me choose`}
onPress={() => { onPress={() => {
navigation.navigate('StarterPackWizard', {}) navigation.navigate('StarterPackWizard', {})
}} }}
@@ -363,21 +358,17 @@ function Empty() {
</Prompt.Outer> </Prompt.Outer>
<Prompt.Basic <Prompt.Basic
control={followersDialogControl} control={followersDialogControl}
title={_(msg`Oops!`)} title={l`Oops!`}
description={_( description={l`You must be following at least seven other people to generate a Starter Pack.`}
msg`You must be following at least seven other people to generate a starter pack.`,
)}
onConfirm={() => {}} onConfirm={() => {}}
showCancel={false} showCancel={false}
/> />
<Prompt.Basic <Prompt.Basic
control={errorDialogControl} control={errorDialogControl}
title={_(msg`Oops!`)} title={l`Oops!`}
description={_( description={l`An error occurred while generating your Starter Pack. Want to try again?`}
msg`An error occurred while generating your starter pack. Want to try again?`,
)}
onConfirm={generate} onConfirm={generate}
confirmButtonCta={_(msg`Retry`)} confirmButtonCta={l`Retry`}
/> />
</LinearGradientBackground> </LinearGradientBackground>
) )
+1 -1
View File
@@ -164,7 +164,7 @@ export function QrCodeDialog({
<Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}> <Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}>
<Dialog.Handle /> <Dialog.Handle />
<Dialog.ScrollableInner <Dialog.ScrollableInner
label={_(msg`Create a QR code for a starter pack`)}> label={_(msg`Create a QR code for a Starter Pack`)}>
<View style={[a.flex_1, a.align_center, a.gap_5xl]}> <View style={[a.flex_1, a.align_center, a.gap_5xl]}>
<Suspense fallback={<Loading />}> <Suspense fallback={<Loading />}>
{!link ? ( {!link ? (
+2 -2
View File
@@ -81,11 +81,11 @@ function ShareDialogInner({
<View style={[!gtMobile && a.gap_lg]}> <View style={[!gtMobile && a.gap_lg]}>
<View style={[a.gap_sm, gtMobile && a.pb_lg]}> <View style={[a.gap_sm, gtMobile && a.pb_lg]}>
<Text style={[a.font_semi_bold, a.text_2xl]}> <Text style={[a.font_semi_bold, a.text_2xl]}>
<Trans>Invite people to this starter pack!</Trans> <Trans>Invite people to this Starter Pack!</Trans>
</Text> </Text>
<Text style={[a.text_md, t.atoms.text_contrast_medium]}> <Text style={[a.text_md, t.atoms.text_contrast_medium]}>
<Trans> <Trans>
Share this starter pack and help people join your community on Share this Starter Pack and help people join your community on
Bluesky. Bluesky.
</Trans> </Trans>
</Text> </Text>
+10 -12
View File
@@ -2,9 +2,7 @@ import {useMemo} from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {Image} from 'expo-image' import {Image} from 'expo-image'
import {AtUri} from '@atproto/syntax' import {AtUri} from '@atproto/syntax'
import {msg} from '@lingui/core/macro' import {Plural, Trans, useLingui} from '@lingui/react/macro'
import {useLingui} from '@lingui/react'
import {Plural, Trans} from '@lingui/react/macro'
import {useQueryClient} from '@tanstack/react-query' import {useQueryClient} from '@tanstack/react-query'
import {sanitizeHandle} from '#/lib/strings/handles' import {sanitizeHandle} from '#/lib/strings/handles'
@@ -59,7 +57,7 @@ export function Card({
}) { }) {
const {record, creator, joinedAllTimeCount} = starterPack const {record, creator, joinedAllTimeCount} = starterPack
const {_} = useLingui() const {t: l} = useLingui()
const t = useTheme() const t = useTheme()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const isOwnStarterPack = creator?.did === currentAccount?.did const isOwnStarterPack = creator?.did === currentAccount?.did
@@ -84,8 +82,8 @@ export function Card({
style={[a.leading_snug, t.atoms.text_contrast_medium]} style={[a.leading_snug, t.atoms.text_contrast_medium]}
numberOfLines={1}> numberOfLines={1}>
{isOwnStarterPack {isOwnStarterPack
? _(msg`Starter pack by you`) ? l`Starter Pack by you`
: _(msg`Starter pack by ${sanitizeHandle(creator.handle, '@')}`)} : l`Starter Pack by ${sanitizeHandle(creator.handle, '@')}`}
</Text> </Text>
</View> </View>
</View> </View>
@@ -96,7 +94,7 @@ export function Card({
) : null} ) : null}
{!!joinedAllTimeCount && joinedAllTimeCount >= 50 && ( {!!joinedAllTimeCount && joinedAllTimeCount >= 50 && (
<Text style={[a.font_semi_bold, t.atoms.text_contrast_medium]}> <Text style={[a.font_semi_bold, t.atoms.text_contrast_medium]}>
<Trans comment="Number of users (always at least 50) who have joined Bluesky using a specific starter pack"> <Trans comment="Number of users (always at least 50) who have joined Bluesky using a specific Starter Pack">
<Plural value={joinedAllTimeCount} other="# users have" /> joined! <Plural value={joinedAllTimeCount} other="# users have" /> joined!
</Trans> </Trans>
</Text> </Text>
@@ -110,7 +108,7 @@ export function useStarterPackLink({
}: { }: {
view: bsky.starterPack.AnyStarterPackView view: bsky.starterPack.AnyStarterPackView
}) { }) {
const {_} = useLingui() const {t: l} = useLingui()
const qc = useQueryClient() const qc = useQueryClient()
const {rkey, handleOrDid} = useMemo(() => { const {rkey, handleOrDid} = useMemo(() => {
const rkey = new AtUri(view.uri).rkey const rkey = new AtUri(view.uri).rkey
@@ -125,8 +123,8 @@ export function useStarterPackLink({
return { return {
to: `/starter-pack/${handleOrDid}/${rkey}`, to: `/starter-pack/${handleOrDid}/${rkey}`,
label: bsky.isType(app.bsky.graph.starterpack, view.record) label: bsky.isType(app.bsky.graph.starterpack, view.record)
? _(msg`Navigate to ${view.record.name}`) ? l`Navigate to ${view.record.name}`
: _(msg`Navigate to starter pack`), : l`Navigate to Starter Pack`,
precache, precache,
} }
} }
@@ -139,7 +137,7 @@ export function Link({
onPress?: () => void onPress?: () => void
children: BaseLinkProps['children'] children: BaseLinkProps['children']
}) { }) {
const {_} = useLingui() const {t: l} = useLingui()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const {record} = starterPack const {record} = starterPack
const {rkey, handleOrDid} = useMemo(() => { const {rkey, handleOrDid} = useMemo(() => {
@@ -155,7 +153,7 @@ export function Link({
return ( return (
<BaseLink <BaseLink
to={`/starter-pack/${handleOrDid}/${rkey}`} to={`/starter-pack/${handleOrDid}/${rkey}`}
label={_(msg`Navigate to ${record.name}`)} label={l`Navigate to ${record.name}`}
onPress={() => { onPress={() => {
precacheResolvedUri( precacheResolvedUri(
queryClient, queryClient,
@@ -5,9 +5,7 @@ import {
type ModerationOpts, type ModerationOpts,
type ModerationUI, type ModerationUI,
} from '@bsky/sdk/moderation' } from '@bsky/sdk/moderation'
import {msg} from '@lingui/core/macro' import {Trans, useLingui} from '@lingui/react/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
import {DISCOVER_FEED_URI, STARTER_PACK_MAX_SIZE} from '#/lib/constants' import {DISCOVER_FEED_URI, STARTER_PACK_MAX_SIZE} from '#/lib/constants'
import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeDisplayName} from '#/lib/strings/display-names'
@@ -53,15 +51,15 @@ function WizardListCard({
moderationUi: ModerationUI moderationUi: ModerationUI
}) { }) {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {t: l} = useLingui()
return ( return (
<Toggle.Item <Toggle.Item
name={type === 'user' ? _(msg`Person toggle`) : _(msg`Feed toggle`)} name={type === 'user' ? l`Person toggle` : l`Feed toggle`}
label={ label={
included included
? _(msg`Remove ${displayName} from starter pack`) ? l`Remove ${displayName} from Starter Pack`
: _(msg`Add ${displayName} to starter pack`) : l`Add ${displayName} to Starter Pack`
} }
value={included} value={included}
disabled={btnType === 'remove' || disabled} disabled={btnType === 'remove' || disabled}
@@ -109,7 +107,7 @@ function WizardListCard({
<Checkbox /> <Checkbox />
) : !disabled ? ( ) : !disabled ? (
<Button <Button
label={_(msg`Remove`)} label={l`Remove`}
variant="solid" variant="solid"
color="secondary" color="secondary"
size="small" size="small"
@@ -142,7 +140,7 @@ export function WizardProfileCard({
const ax = useAnalytics() const ax = useAnalytics()
const {currentAccount} = useSession() const {currentAccount} = useSession()
// Determine the "main" profile for this starter pack - either targetDid or current account // Determine the "main" profile for this Starter Pack - either targetDid or current account
const targetProfileDid = state.targetDid || currentAccount?.did const targetProfileDid = state.targetDid || currentAccount?.did
const isTarget = profile.did === targetProfileDid const isTarget = profile.did === targetProfileDid
const included = isTarget || state.profiles.some(p => p.did === profile.did) const included = isTarget || state.profiles.some(p => p.did === profile.did)
+1 -1
View File
@@ -166,7 +166,7 @@ export function useTopic(
} else if (link.startsWith('/starter-pack')) { } else if (link.startsWith('/starter-pack')) {
return { return {
type: 'starter-pack', type: 'starter-pack',
label: l`Browse starter pack ${displayName}`, label: l`Browse Starter Pack ${displayName}`,
displayName, displayName,
uri: undefined, uri: undefined,
url: link, url: link,
+21 -25
View File
@@ -1,8 +1,6 @@
import {useCallback} from 'react' import {useCallback} from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {msg} from '@lingui/core/macro' import {Plural, Trans, useLingui} from '@lingui/react/macro'
import {useLingui} from '@lingui/react'
import {Plural, Trans} from '@lingui/react/macro'
import {useNavigation} from '@react-navigation/native' import {useNavigation} from '@react-navigation/native'
import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification' import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification'
@@ -67,7 +65,7 @@ export function StarterPackDialog({
const wrappedNavToWizard = requireEmailVerification(navToWizard, { const wrappedNavToWizard = requireEmailVerification(navToWizard, {
instructions: [ instructions: [
<Trans key="nav"> <Trans key="nav">
Before creating a starter pack, you must first verify your email. Before creating a Starter Pack, you must first verify your email.
</Trans>, </Trans>,
], ],
}) })
@@ -85,7 +83,7 @@ export function StarterPackDialog({
} }
function Empty({onStartWizard}: {onStartWizard: () => void}) { function Empty({onStartWizard}: {onStartWizard: () => void}) {
const {_} = useLingui() const {t: l} = useLingui()
const t = useTheme() const t = useTheme()
return ( return (
@@ -96,18 +94,17 @@ function Empty({onStartWizard}: {onStartWizard: () => void}) {
fill={t.atoms.border_contrast_medium.borderColor} fill={t.atoms.border_contrast_medium.borderColor}
/> />
<Text style={[a.text_center]}> <Text style={[a.text_center]}>
<Trans>You have no starter packs.</Trans> <Trans>You have no Starter Packs.</Trans>
</Text> </Text>
</View> </View>
<View style={[a.align_center]}> <View style={[a.align_center]}>
<Button <Button
label={_(msg`Create starter pack`)} label={l`Create Starter Pack`}
color="secondary_inverted" color="secondary_inverted"
size="small" size="small"
onPress={onStartWizard}> onPress={onStartWizard}>
<ButtonText> <ButtonText>
<Trans comment="Text on button to create a new starter pack"> <Trans comment="Text on button to create a new Starter Pack">
Create Create
</Trans> </Trans>
</ButtonText> </ButtonText>
@@ -128,7 +125,7 @@ function StarterPackList({
enabled?: boolean enabled?: boolean
}) { }) {
const control = Dialog.useDialogContext() const control = Dialog.useDialogContext()
const {_} = useLingui() const {t: l} = useLingui()
const {data: subject} = useProfileQuery({did: targetDid}) const {data: subject} = useProfileQuery({did: targetDid})
const { const {
@@ -178,10 +175,10 @@ function StarterPackList({
native(a.pt_lg), native(a.pt_lg),
]}> ]}>
<Text style={[a.text_lg, a.font_semi_bold]}> <Text style={[a.text_lg, a.font_semi_bold]}>
<Trans>Add to starter packs</Trans> <Trans>Add to Starter Packs</Trans>
</Text> </Text>
<Button <Button
label={_(msg`Close`)} label={l`Close`}
onPress={onClose} onPress={onClose}
variant="ghost" variant="ghost"
color="secondary" color="secondary"
@@ -196,15 +193,15 @@ function StarterPackList({
<View <View
style={[a.flex_row, a.justify_between, a.align_center, a.py_md]}> style={[a.flex_row, a.justify_between, a.align_center, a.py_md]}>
<Text style={[a.text_md, a.font_semi_bold]}> <Text style={[a.text_md, a.font_semi_bold]}>
<Trans>New starter pack</Trans> <Trans>New Starter Pack</Trans>
</Text> </Text>
<Button <Button
label={_(msg`Create starter pack`)} label={l`Create Starter Pack`}
color="secondary_inverted" color="secondary_inverted"
size="small" size="small"
onPress={onStartWizard}> onPress={onStartWizard}>
<ButtonText> <ButtonText>
<Trans comment="Text on button to create a new starter pack"> <Trans comment="Text on button to create a new Starter Pack">
Create Create
</Trans> </Trans>
</ButtonText> </ButtonText>
@@ -234,7 +231,7 @@ function StarterPackList({
? () => 'starter_pack_dialog_loader' ? () => 'starter_pack_dialog_loader'
: (item: StarterPackWithMembership) => item.starterPack.uri : (item: StarterPackWithMembership) => item.starterPack.uri
} }
onEndReached={onEndReached} onEndReached={() => void onEndReached()}
onEndReachedThreshold={0.1} onEndReachedThreshold={0.1}
ListHeaderComponent={listHeader} ListHeaderComponent={listHeader}
ListEmptyComponent={<Empty onStartWizard={onStartWizard} />} ListEmptyComponent={<Empty onStartWizard={onStartWizard} />}
@@ -257,7 +254,7 @@ function StarterPackItem({
}) { }) {
const t = useTheme() const t = useTheme()
const ax = useAnalytics() const ax = useAnalytics()
const {_} = useLingui() const {t: l} = useLingui()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const isSelf = subject?.did === currentAccount?.did const isSelf = subject?.did === currentAccount?.did
@@ -268,26 +265,26 @@ function StarterPackItem({
useListMembershipAddMutation({ useListMembershipAddMutation({
subject, subject,
onSuccess: () => { onSuccess: () => {
Toast.show(_(msg`Added to starter pack`)) Toast.show(l`Added to Starter Pack`)
}, },
onError: err => { onError: err => {
if (!isNetworkError(err)) { if (!isNetworkError(err)) {
logger.error('Failed to add to starter pack', {safeMessage: err}) logger.error('Failed to add to Starter Pack', {safeMessage: err})
} }
Toast.show(_(msg`Failed to add to starter pack`), {type: 'error'}) Toast.show(l`Failed to add to Starter Pack`, {type: 'error'})
}, },
}) })
const {mutate: removeMembership, isPending: isPendingRemove} = const {mutate: removeMembership, isPending: isPendingRemove} =
useListMembershipRemoveMutation({ useListMembershipRemoveMutation({
onSuccess: () => { onSuccess: () => {
Toast.show(_(msg`Removed from starter pack`)) Toast.show(l`Removed from Starter Pack`)
}, },
onError: err => { onError: err => {
if (!isNetworkError(err)) { if (!isNetworkError(err)) {
logger.error('Failed to remove from starter pack', {safeMessage: err}) logger.error('Failed to remove from Starter Pack', {safeMessage: err})
} }
Toast.show(_(msg`Failed to remove from starter pack`), {type: 'error'}) Toast.show(l`Failed to remove from Starter Pack`, {type: 'error'})
}, },
}) })
@@ -363,9 +360,8 @@ function StarterPackItem({
)} )}
</View> </View>
</View> </View>
<Button <Button
label={isInPack ? _(msg`Remove`) : _(msg`Add`)} label={isInPack ? l`Remove` : l`Add`}
color={isInPack ? 'secondary' : 'primary_subtle'} color={isInPack ? 'secondary' : 'primary_subtle'}
size="tiny" size="tiny"
disabled={isPending || isSelf} disabled={isPending || isSelf}
@@ -2,8 +2,7 @@ import {View} from 'react-native'
import {TID} from '@atproto/common-web' import {TID} from '@atproto/common-web'
import {type $Typed} from '@atproto/lex' import {type $Typed} from '@atproto/lex'
import {AtUri, type AtUriString, toDatetimeString} from '@atproto/syntax' import {AtUri, type AtUriString, toDatetimeString} from '@atproto/syntax'
import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro' import {Trans} from '@lingui/react/macro'
import {useNavigation} from '@react-navigation/native' import {useNavigation} from '@react-navigation/native'
import {useQueryClient} from '@tanstack/react-query' import {useQueryClient} from '@tanstack/react-query'
@@ -33,7 +32,7 @@ export function CreateListFromStarterPackDialog({
control: Dialog.DialogControlProps control: Dialog.DialogControlProps
starterPack: app.bsky.graph.defs.StarterPackView starterPack: app.bsky.graph.defs.StarterPackView
}) { }) {
const {_} = useLingui() const {t: l} = useLingui()
const t = useTheme() const t = useTheme()
const appviewClient = useAppviewClient() const appviewClient = useAppviewClient()
const pdsClient = usePdsClient() const pdsClient = usePdsClient()
@@ -115,7 +114,7 @@ export function CreateListFromStarterPackDialog({
})(), })(),
) )
queryClient.invalidateQueries({queryKey: ['list-members', listUri]}) void queryClient.invalidateQueries({queryKey: ['list-members', listUri]})
ax.metric('starterPack:convertToList', { ax.metric('starterPack:convertToList', {
starterPack: starterPack.uri, starterPack: starterPack.uri,
@@ -123,7 +122,7 @@ export function CreateListFromStarterPackDialog({
}) })
} catch (e) { } catch (e) {
logger.error('Failed to add members to list', {safeMessage: e}) logger.error('Failed to add members to list', {safeMessage: e})
Toast.show(_(msg`List created, but failed to add some members`), { Toast.show(l`List created, but failed to add some members`, {
type: 'error', type: 'error',
}) })
} }
@@ -133,7 +132,7 @@ export function CreateListFromStarterPackDialog({
const onListCreated = (listUri: string) => { const onListCreated = (listUri: string) => {
loadingDialogControl.open() loadingDialogControl.open()
addMembersAndNavigate(listUri) void addMembersAndNavigate(listUri)
} }
return ( return (
@@ -144,24 +143,24 @@ export function CreateListFromStarterPackDialog({
nativeOptions={{preventExpansion: true}}> nativeOptions={{preventExpansion: true}}>
<Dialog.Handle /> <Dialog.Handle />
<Dialog.ScrollableInner <Dialog.ScrollableInner
label={_(msg`Create list from starter pack`)} label={l`Create list from Starter Pack`}
style={web({maxWidth: 400})}> style={web({maxWidth: 400})}>
<View style={[a.gap_lg]}> <View style={[a.gap_lg]}>
<Text style={[a.text_xl, a.font_bold]}> <Text style={[a.text_xl, a.font_bold]}>
<Trans>Create list from starter pack</Trans> <Trans>Create list from Starter Pack</Trans>
</Text> </Text>
<Text <Text
style={[a.text_md, a.leading_snug, t.atoms.text_contrast_high]}> style={[a.text_md, a.leading_snug, t.atoms.text_contrast_high]}>
<Trans> <Trans>
This will create a new list with the same name, description, and This will create a new list with the same name, description, and
members as this starter pack. members as this Starter Pack.
</Trans> </Trans>
</Text> </Text>
<Admonition type="tip"> <Admonition type="tip">
<Trans> <Trans>
Changes to the starter pack will not be reflected in the list Changes to the Starter Pack will not be reflected in the list
after creation. The list will be an independent copy. after creation. The list will be an independent copy.
</Trans> </Trans>
</Admonition> </Admonition>
@@ -176,7 +175,7 @@ export function CreateListFromStarterPackDialog({
a.pt_sm, a.pt_sm,
]}> ]}>
<Button <Button
label={_(msg`Create list`)} label={l`Create list`}
onPress={onPressCreate} onPress={onPressCreate}
size={platform({ size={platform({
web: 'small', web: 'small',
@@ -188,7 +187,7 @@ export function CreateListFromStarterPackDialog({
</ButtonText> </ButtonText>
</Button> </Button>
<Button <Button
label={_(msg`Cancel`)} label={l`Cancel`}
onPress={() => control.close()} onPress={() => control.close()}
size={platform({ size={platform({
web: 'small', web: 'small',
@@ -204,7 +203,6 @@ export function CreateListFromStarterPackDialog({
<Dialog.Close /> <Dialog.Close />
</Dialog.ScrollableInner> </Dialog.ScrollableInner>
</Dialog.Outer> </Dialog.Outer>
<CreateOrEditListDialog <CreateOrEditListDialog
control={createDialogControl} control={createDialogControl}
purpose="app.bsky.graph.defs#curatelist" purpose="app.bsky.graph.defs#curatelist"
@@ -215,13 +213,12 @@ export function CreateListFromStarterPackDialog({
avatar: starterPack.list?.avatar, avatar: starterPack.list?.avatar,
}} }}
/> />
<Dialog.Outer <Dialog.Outer
control={loadingDialogControl} control={loadingDialogControl}
nativeOptions={{preventDismiss: true}}> nativeOptions={{preventDismiss: true}}>
<Dialog.Handle /> <Dialog.Handle />
<Dialog.ScrollableInner <Dialog.ScrollableInner
label={_(msg`Adding members to list...`)} label={l`Adding members to list...`}
style={web({maxWidth: 400})}> style={web({maxWidth: 400})}>
<View style={[a.align_center, a.gap_lg, a.py_5xl]}> <View style={[a.align_center, a.gap_lg, a.py_5xl]}>
<Loader size="xl" /> <Loader size="xl" />
@@ -187,7 +187,7 @@ function DialogInner({
ImageMeta | undefined | null ImageMeta | undefined | null
>() >()
// When creating with pre-filled values (from starter pack), consider dirty // When creating with pre-filled values (from Starter Pack), consider dirty
// immediately so the Save button is enabled // immediately so the Save button is enabled
const hasInitialValuesForCreate = !list && initialValues != null const hasInitialValuesForCreate = !list && initialValues != null
const dirty = const dirty =
@@ -53,7 +53,7 @@ export function useLandingEntry() {
}, 500) }, 500)
void (async () => { void (async () => {
// Check for starter pack // Check for Starter Pack
let uri: string | null | undefined let uri: string | null | undefined
if (IS_ANDROID) { if (IS_ANDROID) {
+1 -1
View File
@@ -11,7 +11,7 @@ export function useLandingEntry() {
const href = window.location.href const href = window.location.href
const url = new URL(href) const url = new URL(href)
// Check for starter pack // Check for Starter Pack
const atUri = httpStarterPackUriToAtUri(href) const atUri = httpStarterPackUriToAtUri(href)
if (atUri) { if (atUri) {
// Determines if an App Clip is loading this landing page // Determines if an App Clip is loading this landing page
+21 -22
View File
@@ -1,71 +1,70 @@
import {useMemo} from 'react' import {useMemo} from 'react'
import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react/macro'
import {useLingui} from '@lingui/react'
import {type ParsedReportSubject} from './types' import {type ParsedReportSubject} from './types'
export function useCopyForSubject(subject: ParsedReportSubject) { export function useCopyForSubject(subject: ParsedReportSubject) {
const {_} = useLingui() const {t: l} = useLingui()
return useMemo(() => { return useMemo(() => {
switch (subject.type) { switch (subject.type) {
case 'account': { case 'account': {
return { return {
title: _(msg`Report this user`), title: l`Report this user`,
subtitle: _(msg`Why should this user be reviewed?`), subtitle: l`Why should this user be reviewed?`,
} }
} }
case 'status': { case 'status': {
return { return {
title: _(msg`Report this livestream`), title: l`Report this livestream`,
subtitle: _(msg`Why should this livestream be reviewed?`), subtitle: l`Why should this livestream be reviewed?`,
} }
} }
case 'post': { case 'post': {
return { return {
title: _(msg`Report this post`), title: l`Report this post`,
subtitle: _(msg`Why should this post be reviewed?`), subtitle: l`Why should this post be reviewed?`,
} }
} }
case 'list': { case 'list': {
return { return {
title: _(msg`Report this list`), title: l`Report this list`,
subtitle: _(msg`Why should this list be reviewed?`), subtitle: l`Why should this list be reviewed?`,
} }
} }
case 'feed': { case 'feed': {
return { return {
title: _(msg`Report this feed`), title: l`Report this feed`,
subtitle: _(msg`Why should this feed be reviewed?`), subtitle: l`Why should this feed be reviewed?`,
} }
} }
case 'starterPack': { case 'starterPack': {
return { return {
title: _(msg`Report this starter pack`), title: l`Report this Starter Pack`,
subtitle: _(msg`Why should this starter pack be reviewed?`), subtitle: l`Why should this Starter Pack be reviewed?`,
} }
} }
case 'convoMessage': { case 'convoMessage': {
switch (subject.view) { switch (subject.view) {
case 'convo': { case 'convo': {
return { return {
title: _(msg`Report this conversation`), title: l`Report this conversation`,
subtitle: _(msg`Why should this conversation be reviewed?`), subtitle: l`Why should this conversation be reviewed?`,
} }
} }
case 'message': { case 'message': {
return { return {
title: _(msg`Report this message`), title: l`Report this message`,
subtitle: _(msg`Why should this message be reviewed?`), subtitle: l`Why should this message be reviewed?`,
} }
} }
} }
} }
case 'convo': { case 'convo': {
return { return {
title: _(msg`Report this conversation`), title: l`Report this conversation`,
subtitle: _(msg`Why should this conversation be reviewed?`), subtitle: l`Why should this conversation be reviewed?`,
} }
} }
} }
}, [_, subject]) }, [l, subject])
} }
+1 -1
View File
@@ -406,7 +406,7 @@ export function shortLinkToHref(url: string): string {
try { try {
const urlp = new URL(url) const urlp = new URL(url)
// For now we only support starter packs, but in the future we should add additional paths to this check // For now we only support Starter Packs, but in the future we should add additional paths to this check
const parts = urlp.pathname.split('/').filter(Boolean) const parts = urlp.pathname.split('/').filter(Boolean)
if (parts.length === 1) { if (parts.length === 1) {
return `/starter-pack-short/${parts[0]}` return `/starter-pack-short/${parts[0]}`
+1 -1
View File
@@ -80,7 +80,7 @@ export const router = new Router<AllNavigatableRoutes>({
MessagesConversation: '/messages/:conversation', MessagesConversation: '/messages/:conversation',
MessagesConversationSettings: '/messages/:conversation/settings', MessagesConversationSettings: '/messages/:conversation/settings',
MessagesJoinRequests: '/messages/:conversation/requests', MessagesJoinRequests: '/messages/:conversation/requests',
// starter packs // Starter Packs
Start: '/start/:name/:rkey', Start: '/start/:name/:rkey',
StarterPackEdit: '/starter-pack/edit/:rkey', StarterPackEdit: '/starter-pack/edit/:rkey',
StarterPack: '/starter-pack/:name/:rkey', StarterPack: '/starter-pack/:name/:rkey',
@@ -75,7 +75,7 @@ export function StepFinished() {
}) })
starterPack = spRes.starterPack starterPack = spRes.starterPack
} catch (e) { } catch (e) {
logger.error('Failed to fetch starter pack', {safeMessage: e}) logger.error('Failed to fetch Starter Pack', {safeMessage: e})
// don't tell the user, just get them through onboarding. // don't tell the user, just get them through onboarding.
} }
const starterPackList = starterPack?.list const starterPackList = starterPack?.list
@@ -87,7 +87,7 @@ export function StepFinished() {
) )
} }
} catch (e) { } catch (e) {
logger.error('Failed to fetch starter pack list items', { logger.error('Failed to fetch Starter Pack list items', {
safeMessage: e, safeMessage: e,
}) })
// don't tell the user, just get them through onboarding. // don't tell the user, just get them through onboarding.
@@ -132,7 +132,7 @@ export function StepFinished() {
}, },
] ]
// Any starter pack feeds will be pinned _after_ the defaults // Any Starter Pack feeds will be pinned _after_ the defaults
if (starterPack && starterPack.feeds?.length) { if (starterPack && starterPack.feeds?.length) {
feedsToSave.push( feedsToSave.push(
...starterPack.feeds.map(f => ({ ...starterPack.feeds.map(f => ({
@@ -55,7 +55,7 @@ export function StarterPackCard({
Toast.show(_(msg`An error occurred while trying to follow all`), { Toast.show(_(msg`An error occurred while trying to follow all`), {
type: 'error', type: 'error',
}) })
logger.error('Failed to get list members for starter pack', { logger.error('Failed to get list members for Starter Pack', {
safeMessage: e, safeMessage: e,
}) })
return return
@@ -40,7 +40,7 @@ export function StepSuggestedStarterpacks() {
<View style={[a.align_start, a.gap_sm]} testID="onboardingInterests"> <View style={[a.align_start, a.gap_sm]} testID="onboardingInterests">
<OnboardingPosition /> <OnboardingPosition />
<OnboardingTitleText> <OnboardingTitleText>
<Trans comment="Starter packs suggested to the user for them to follow"> <Trans comment="Starter Packs suggested to the user for them to follow">
Find people to follow Find people to follow
</Trans> </Trans>
</OnboardingTitleText> </OnboardingTitleText>
+1 -1
View File
@@ -38,7 +38,7 @@ export function Onboarding() {
return bcp47Match.basicFilter('en', contentLanguages).length > 0 return bcp47Match.basicFilter('en', contentLanguages).length > 0
}, [contentLanguages]) }, [contentLanguages])
// starter packs screen is currently geared towards english-speaking accounts // Starter Packs screen is currently geared towards english-speaking accounts
const showSuggestedStarterpacks = ENV !== 'e2e' && probablySpeaksEnglish const showSuggestedStarterpacks = ENV !== 'e2e' && probablySpeaksEnglish
const findContactsEnabled = const findContactsEnabled =
@@ -1,6 +1,5 @@
import {AtUri} from '@atproto/syntax' import {AtUri} from '@atproto/syntax'
import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro' import {Trans} from '@lingui/react/macro'
import {useNavigation} from '@react-navigation/native' import {useNavigation} from '@react-navigation/native'
@@ -46,7 +45,7 @@ export function MoreOptionsMenu({
list: app.bsky.graph.defs.ListView list: app.bsky.graph.defs.ListView
savedFeedConfig?: app.bsky.actor.defs.SavedFeed savedFeedConfig?: app.bsky.actor.defs.SavedFeed
}) { }) {
const {_} = useLingui() const {t: l} = useLingui()
const ax = useAnalytics() const ax = useAnalytics()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const editListDialogControl = useDialogControl() const editListDialogControl = useDialogControl()
@@ -79,15 +78,15 @@ export function MoreOptionsMenu({
}) })
Toast.show( Toast.show(
action === 'optOut' action === 'optOut'
? _(msg`Opted out of starter pack`) ? l`Opted out of Starter Pack`
: _(msg`Opt-out undone`), : l`Opt-out undone`,
) )
}, },
onError: error => { onError: error => {
logger.error('Failed to update starter pack opt-out', { logger.error('Failed to update Starter Pack opt-out', {
safeMessage: error, safeMessage: error,
}) })
Toast.show(_(msg`Failed to update starter pack opt-out`), { Toast.show(l`Failed to update Starter Pack opt-out`, {
type: 'error', type: 'error',
}) })
}, },
@@ -96,16 +95,16 @@ export function MoreOptionsMenu({
const onPressShare = () => { const onPressShare = () => {
const {rkey} = new AtUri(list.uri) const {rkey} = new AtUri(list.uri)
const url = toShareUrl(`/profile/${list.creator.did}/lists/${rkey}`) const url = toShareUrl(`/profile/${list.creator.did}/lists/${rkey}`)
shareUrl(url) void shareUrl(url)
} }
const onRemoveFromSavedFeeds = async () => { const onRemoveFromSavedFeeds = async () => {
if (!savedFeedConfig) return if (!savedFeedConfig) return
try { try {
await removeSavedFeed(savedFeedConfig) await removeSavedFeed(savedFeedConfig)
Toast.show(_(msg`Removed from your feeds`)) Toast.show(l`Removed from your feeds`)
} catch (e) { } catch (e) {
Toast.show(_(msg`There was an issue contacting the server`), { Toast.show(l`There was an issue contacting the server`, {
type: 'error', type: 'error',
}) })
logger.error('Failed to remove pinned list', {message: e}) logger.error('Failed to remove pinned list', {message: e})
@@ -119,7 +118,7 @@ export function MoreOptionsMenu({
await removeSavedFeed(savedFeedConfig) await removeSavedFeed(savedFeedConfig)
} }
Toast.show(_(msg({message: 'List deleted', context: 'toast'}))) Toast.show(l({message: 'List deleted', context: 'toast'}))
if (navigation.canGoBack()) { if (navigation.canGoBack()) {
navigation.goBack() navigation.goBack()
} else { } else {
@@ -131,9 +130,9 @@ export function MoreOptionsMenu({
try { try {
if (!savedFeedConfig) return if (!savedFeedConfig) return
await removeSavedFeed(savedFeedConfig) await removeSavedFeed(savedFeedConfig)
Toast.show(_(msg`Unpinned list`)) Toast.show(l`Unpinned list`)
} catch { } catch {
Toast.show(_(msg`Failed to unpin list`), { Toast.show(l`Failed to unpin list`, {
type: 'error', type: 'error',
}) })
} }
@@ -142,13 +141,11 @@ export function MoreOptionsMenu({
const onUnsubscribeMute = async () => { const onUnsubscribeMute = async () => {
try { try {
await muteList({uri: list.uri, mute: false}) await muteList({uri: list.uri, mute: false})
Toast.show(_(msg({message: 'List unmuted', context: 'toast'}))) Toast.show(l({message: 'List unmuted', context: 'toast'}))
ax.metric('moderation:unsubscribedFromList', {listType: 'mute'}) ax.metric('moderation:unsubscribedFromList', {listType: 'mute'})
} catch { } catch {
Toast.show( Toast.show(
_( l`There was an issue. Please check your internet connection and try again.`,
msg`There was an issue. Please check your internet connection and try again.`,
),
) )
} }
} }
@@ -156,13 +153,11 @@ export function MoreOptionsMenu({
const onUnsubscribeBlock = async () => { const onUnsubscribeBlock = async () => {
try { try {
await blockList({uri: list.uri, block: false}) await blockList({uri: list.uri, block: false})
Toast.show(_(msg({message: 'List unblocked', context: 'toast'}))) Toast.show(l({message: 'List unblocked', context: 'toast'}))
ax.metric('moderation:unsubscribedFromList', {listType: 'block'}) ax.metric('moderation:unsubscribedFromList', {listType: 'block'})
} catch { } catch {
Toast.show( Toast.show(
_( l`There was an issue. Please check your internet connection and try again.`,
msg`There was an issue. Please check your internet connection and try again.`,
),
) )
} }
} }
@@ -170,7 +165,7 @@ export function MoreOptionsMenu({
return ( return (
<> <>
<Menu.Root> <Menu.Root>
<Menu.Trigger label={_(msg`More options`)}> <Menu.Trigger label={l`More options`}>
{({props}) => ( {({props}) => (
<Button <Button
label={props.accessibilityLabel} label={props.accessibilityLabel}
@@ -186,7 +181,7 @@ export function MoreOptionsMenu({
<Menu.Outer showCancel> <Menu.Outer showCancel>
<Menu.Group> <Menu.Group>
<Menu.Item <Menu.Item
label={IS_WEB ? _(msg`Copy link to list`) : _(msg`Share via...`)} label={IS_WEB ? l`Copy link to list` : l`Share via...`}
onPress={onPressShare}> onPress={onPressShare}>
<Menu.ItemText> <Menu.ItemText>
{IS_WEB ? ( {IS_WEB ? (
@@ -202,8 +197,8 @@ export function MoreOptionsMenu({
</Menu.Item> </Menu.Item>
{savedFeedConfig && ( {savedFeedConfig && (
<Menu.Item <Menu.Item
label={_(msg`Remove from my feeds`)} label={l`Remove from my feeds`}
onPress={onRemoveFromSavedFeeds}> onPress={() => void onRemoveFromSavedFeeds()}>
<Menu.ItemText> <Menu.ItemText>
<Trans>Remove from my feeds</Trans> <Trans>Remove from my feeds</Trans>
</Menu.ItemText> </Menu.ItemText>
@@ -217,7 +212,7 @@ export function MoreOptionsMenu({
{isOwner ? ( {isOwner ? (
<Menu.Group> <Menu.Group>
<Menu.Item <Menu.Item
label={_(msg`Edit list details`)} label={l`Edit list details`}
onPress={editListDialogControl.open}> onPress={editListDialogControl.open}>
<Menu.ItemText> <Menu.ItemText>
<Trans>Edit list details</Trans> <Trans>Edit list details</Trans>
@@ -225,7 +220,7 @@ export function MoreOptionsMenu({
<Menu.ItemIcon position="right" icon={PencilLineIcon} /> <Menu.ItemIcon position="right" icon={PencilLineIcon} />
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
label={_(msg`Delete list`)} label={l`Delete list`}
onPress={deleteListPromptControl.open}> onPress={deleteListPromptControl.open}>
<Menu.ItemText> <Menu.ItemText>
<Trans>Delete list</Trans> <Trans>Delete list</Trans>
@@ -237,7 +232,7 @@ export function MoreOptionsMenu({
<> <>
<Menu.Group> <Menu.Group>
<Menu.Item <Menu.Item
label={_(msg`Report list`)} label={l`Report list`}
onPress={reportDialogControl.open}> onPress={reportDialogControl.open}>
<Menu.ItemText> <Menu.ItemText>
<Trans>Report list</Trans> <Trans>Report list</Trans>
@@ -252,8 +247,8 @@ export function MoreOptionsMenu({
<Menu.Item <Menu.Item
label={ label={
referenceListOptOut referenceListOptOut
? _(msg`Undo opt-out from starter pack`) ? l`Undo opt-out from Starter Pack`
: _(msg`Opt out of starter pack`) : l`Opt out of Starter Pack`
} }
disabled={isOptOutPending} disabled={isOptOutPending}
onPress={optOutDialogControl.open}> onPress={optOutDialogControl.open}>
@@ -261,7 +256,7 @@ export function MoreOptionsMenu({
{referenceListOptOut ? ( {referenceListOptOut ? (
<Trans>Undo opt-out</Trans> <Trans>Undo opt-out</Trans>
) : ( ) : (
<Trans>Opt out of starter pack</Trans> <Trans>Opt out of Starter Pack</Trans>
)} )}
</Menu.ItemText> </Menu.ItemText>
</Menu.Item> </Menu.Item>
@@ -276,8 +271,8 @@ export function MoreOptionsMenu({
<Menu.Divider /> <Menu.Divider />
<Menu.Group> <Menu.Group>
<Menu.Item <Menu.Item
label={_(msg`Unpin moderation list`)} label={l`Unpin moderation list`}
onPress={onUnpinModList}> onPress={() => void onUnpinModList()}>
<Menu.ItemText> <Menu.ItemText>
<Trans>Unpin moderation list</Trans> <Trans>Unpin moderation list</Trans>
</Menu.ItemText> </Menu.ItemText>
@@ -293,8 +288,8 @@ export function MoreOptionsMenu({
<Menu.Group> <Menu.Group>
{isBlocking && ( {isBlocking && (
<Menu.Item <Menu.Item
label={_(msg`Unblock list`)} label={l`Unblock list`}
onPress={onUnsubscribeBlock}> onPress={() => void onUnsubscribeBlock()}>
<Menu.ItemText> <Menu.ItemText>
<Trans>Unblock list</Trans> <Trans>Unblock list</Trans>
</Menu.ItemText> </Menu.ItemText>
@@ -303,8 +298,8 @@ export function MoreOptionsMenu({
)} )}
{isMuting && ( {isMuting && (
<Menu.Item <Menu.Item
label={_(msg`Unmute list`)} label={l`Unmute list`}
onPress={onUnsubscribeMute}> onPress={() => void onUnsubscribeMute()}>
<Menu.ItemText> <Menu.ItemText>
<Trans>Unmute list</Trans> <Trans>Unmute list</Trans>
</Menu.ItemText> </Menu.ItemText>
@@ -316,37 +311,32 @@ export function MoreOptionsMenu({
)} )}
</Menu.Outer> </Menu.Outer>
</Menu.Root> </Menu.Root>
<CreateOrEditListDialog control={editListDialogControl} list={list} /> <CreateOrEditListDialog control={editListDialogControl} list={list} />
<Prompt.Basic <Prompt.Basic
control={deleteListPromptControl} control={deleteListPromptControl}
title={_(msg`Delete this list?`)} title={l`Delete this list?`}
description={_( description={l`If you delete this list, you won't be able to recover it.`}
msg`If you delete this list, you won't be able to recover it.`, onConfirm={() => void onPressDelete()}
)} confirmButtonCta={l`Delete`}
onConfirm={onPressDelete}
confirmButtonCta={_(msg`Delete`)}
confirmButtonColor="negative" confirmButtonColor="negative"
/> />
{isReferenceList ? ( {isReferenceList ? (
<Prompt.Outer control={optOutDialogControl}> <Prompt.Outer control={optOutDialogControl}>
<Prompt.TitleText> <Prompt.TitleText>
{referenceListOptOut ? ( {referenceListOptOut ? (
<Trans>Undo opt-out?</Trans> <Trans>Undo opt-out?</Trans>
) : ( ) : (
<Trans>Opt out of this starter pack?</Trans> <Trans>Opt out of this Starter Pack?</Trans>
)} )}
</Prompt.TitleText> </Prompt.TitleText>
<Prompt.DescriptionText> <Prompt.DescriptionText>
{referenceListOptOut ? ( {referenceListOptOut ? (
<Trans> <Trans>
You will be eligible to appear in this starter pack again. You will be eligible to appear in this Starter Pack again.
</Trans> </Trans>
) : ( ) : (
<Trans> <Trans>
You will no longer appear in this starter pack. The creator will You will no longer appear in this Starter Pack. The creator will
be able to see that you've opted out and remove you if they be able to see that you've opted out and remove you if they
wish. wish.
</Trans> </Trans>
@@ -356,8 +346,8 @@ export function MoreOptionsMenu({
<Prompt.Action <Prompt.Action
cta={ cta={
referenceListOptOut referenceListOptOut
? _(msg`Undo opt-out`) ? l`Undo opt-out`
: _(msg`Opt out of starter pack`) : l`Opt out of Starter Pack`
} }
color={referenceListOptOut ? 'primary' : 'negative'} color={referenceListOptOut ? 'primary' : 'negative'}
disabled={isOptOutPending} disabled={isOptOutPending}
@@ -369,7 +359,6 @@ export function MoreOptionsMenu({
</Prompt.Actions> </Prompt.Actions>
</Prompt.Outer> </Prompt.Outer>
) : null} ) : null}
<ReportDialog <ReportDialog
control={reportDialogControl} control={reportDialogControl}
subject={{ subject={{
+1 -1
View File
@@ -117,7 +117,7 @@ let SearchResults = ({
}, },
noFilters && noFilters &&
isStarterPacksEnabled && { isStarterPacksEnabled && {
title: l`Starter packs`, title: l`Starter Packs`,
component: ( component: (
<SearchScreenStarterPackResults <SearchScreenStarterPackResults
query={query} query={query}
@@ -387,7 +387,7 @@ export function NotificationSettingsScreen({}: Props) {
titleText={<Trans>Everything else</Trans>} titleText={<Trans>Everything else</Trans>}
subtitleText={ subtitleText={
<Trans> <Trans>
Get notifications for starter pack joins, verification, and other Get notifications for Starter Pack joins, verification, and other
activity. activity.
</Trans> </Trans>
} }
@@ -190,7 +190,7 @@ function LandingScreenLoaded({
a.text_md, a.text_md,
{color: 'white'}, {color: 'white'},
]}> ]}>
Starter pack by {`@${creator.handle}`} <Trans>Starter Pack by {`@${creator.handle}`}</Trans>
</Text> </Text>
</LinearGradientBackground> </LinearGradientBackground>
<View style={[a.gap_2xl, a.mx_lg, a.my_2xl]}> <View style={[a.gap_2xl, a.mx_lg, a.my_2xl]}>
@@ -299,14 +299,14 @@ function LandingScreenLoaded({
) : null} ) : null}
</View> </View>
<Button <Button
label={_(msg`Create an account without using this starter pack`)} label={_(msg`Create an account without using this Starter Pack`)}
variant="solid" variant="solid"
color="secondary" color="secondary"
size="large" size="large"
style={[a.py_lg]} style={[a.py_lg]}
onPress={onJoinWithoutPress}> onPress={onJoinWithoutPress}>
<ButtonText> <ButtonText>
<Trans>Create an account without using this starter pack</Trans> <Trans>Create an account without using this Starter Pack</Trans>
</ButtonText> </ButtonText>
</Button> </Button>
</View> </View>
@@ -396,7 +396,7 @@ export function AppClipOverlay({
Download Bluesky to get started! Download Bluesky to get started!
</Text> </Text>
<Text style={[a.text_lg, {color: 'white'}]}> <Text style={[a.text_lg, {color: 'white'}]}>
We'll remember the starter pack you chose and use it when you create Well remember the Starter Pack you chose and use it when you create
an account in the app. an account in the app.
</Text> </Text>
</View> </View>
+56 -64
View File
@@ -4,9 +4,7 @@ import {Image} from 'expo-image'
import {AtUri} from '@atproto/syntax' import {AtUri} from '@atproto/syntax'
import {type ModerationOpts} from '@bsky/sdk/moderation' import {type ModerationOpts} from '@bsky/sdk/moderation'
import {RichText as RichTextAPI} from '@bsky/sdk/richtext' import {RichText as RichTextAPI} from '@bsky/sdk/richtext'
import {msg} from '@lingui/core/macro' import {Plural, Trans, useLingui} from '@lingui/react/macro'
import {useLingui} from '@lingui/react'
import {Plural, Trans} from '@lingui/react/macro'
import {useNavigation} from '@react-navigation/native' import {useNavigation} from '@react-navigation/native'
import {type NativeStackScreenProps} from '@react-navigation/native-stack' import {type NativeStackScreenProps} from '@react-navigation/native-stack'
import {useQueryClient} from '@tanstack/react-query' import {useQueryClient} from '@tanstack/react-query'
@@ -96,7 +94,7 @@ export function StarterPackScreen({route}: StarterPackScreeProps) {
} }
export function StarterPackScreenShort({route}: StarterPackScreenShortProps) { export function StarterPackScreenShort({route}: StarterPackScreenShortProps) {
const {_} = useLingui() const {t: l} = useLingui()
const { const {
data: resolvedStarterPack, data: resolvedStarterPack,
isLoading, isLoading,
@@ -111,8 +109,8 @@ export function StarterPackScreenShort({route}: StarterPackScreenShortProps) {
<ListMaybePlaceholder <ListMaybePlaceholder
isLoading={isLoading} isLoading={isLoading}
isError={isError} isError={isError}
errorMessage={_(msg`That starter pack could not be found.`)} errorMessage={l`That Starter Pack could not be found.`}
emptyMessage={_(msg`That starter pack could not be found.`)} emptyMessage={l`That Starter Pack could not be found.`}
/> />
</Layout.Screen> </Layout.Screen>
) )
@@ -130,7 +128,7 @@ export function StarterPackScreenInner({
routeParams: StarterPackScreeProps['route']['params'] routeParams: StarterPackScreeProps['route']['params']
}) { }) {
const {name, rkey} = routeParams const {name, rkey} = routeParams
const {_} = useLingui() const {t: l} = useLingui()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const moderationOpts = useModerationOpts() const moderationOpts = useModerationOpts()
@@ -156,8 +154,8 @@ export function StarterPackScreenInner({
<ListMaybePlaceholder <ListMaybePlaceholder
isLoading={isLoadingDid || isLoadingStarterPack || !moderationOpts} isLoading={isLoadingDid || isLoadingStarterPack || !moderationOpts}
isError={isErrorDid || isErrorStarterPack || !isValid} isError={isErrorDid || isErrorStarterPack || !isValid}
errorMessage={_(msg`That starter pack could not be found.`)} errorMessage={l`That Starter Pack could not be found.`}
emptyMessage={_(msg`That starter pack could not be found.`)} emptyMessage={l`That Starter Pack could not be found.`}
/> />
) )
} }
@@ -187,13 +185,13 @@ function StarterPackScreenLoaded({
const showPeopleTab = Boolean(starterPack.list) const showPeopleTab = Boolean(starterPack.list)
const showFeedsTab = Boolean(starterPack.feeds?.length) const showFeedsTab = Boolean(starterPack.feeds?.length)
const showPostsTab = Boolean(starterPack.list) const showPostsTab = Boolean(starterPack.list)
const {_} = useLingui() const {t: l} = useLingui()
const ax = useAnalytics() const ax = useAnalytics()
const tabs = [ const tabs = [
...(showPeopleTab ? [_(msg`People`)] : []), ...(showPeopleTab ? [l`People`] : []),
...(showFeedsTab ? [_(msg`Feeds`)] : []), ...(showFeedsTab ? [l`Feeds`] : []),
...(showPostsTab ? [_(msg`Posts`)] : []), ...(showPostsTab ? [l`Posts`] : []),
] ]
const qrCodeDialogControl = useDialogControl() const qrCodeDialogControl = useDialogControl()
@@ -211,7 +209,7 @@ function StarterPackScreenLoaded({
const onOpenShareDialog = useCallback(() => { const onOpenShareDialog = useCallback(() => {
const rkey = new AtUri(starterPack.uri).rkey const rkey = new AtUri(starterPack.uri).rkey
shortenLink(makeStarterPackLink(starterPack.creator.did, rkey)).then( void shortenLink(makeStarterPackLink(starterPack.creator.did, rkey)).then(
res => { res => {
setLink(res.url) setLink(res.url)
}, },
@@ -301,7 +299,7 @@ function Header({
routeParams: StarterPackScreeProps['route']['params'] routeParams: StarterPackScreeProps['route']['params']
onOpenShareDialog: () => void onOpenShareDialog: () => void
}) { }) {
const {_} = useLingui() const {t: l} = useLingui()
const t = useTheme() const t = useTheme()
const {currentAccount, hasSession} = useSession() const {currentAccount, hasSession} = useSession()
const appviewClient = useAppviewClient() const appviewClient = useAppviewClient()
@@ -351,10 +349,10 @@ function Header({
listItems = await getAllListMembers(appviewClient, starterPack.list.uri) listItems = await getAllListMembers(appviewClient, starterPack.list.uri)
} catch (e) { } catch (e) {
setIsProcessing(false) setIsProcessing(false)
Toast.show(_(msg`An error occurred while trying to follow all`), { Toast.show(l`An error occurred while trying to follow all`, {
type: 'error', type: 'error',
}) })
logger.error('Failed to get list members for starter pack', { logger.error('Failed to get list members for Starter Pack', {
safeMessage: e, safeMessage: e,
}) })
return return
@@ -378,7 +376,7 @@ function Header({
}) })
} catch (e) { } catch (e) {
setIsProcessing(false) setIsProcessing(false)
Toast.show(_(msg`An error occurred while trying to follow all`), { Toast.show(l`An error occurred while trying to follow all`, {
type: 'error', type: 'error',
}) })
logger.error('Failed to follow all accounts', {safeMessage: e}) logger.error('Failed to follow all accounts', {safeMessage: e})
@@ -392,7 +390,7 @@ function Header({
}) })
} }
}) })
Toast.show(_(msg`All accounts have been followed!`)) Toast.show(l`All accounts have been followed!`)
captureAction(ProgressGuideAction.Follow, dids.length) captureAction(ProgressGuideAction.Follow, dids.length)
ax.metric('starterPack:followAll', { ax.metric('starterPack:followAll', {
logContext: 'StarterPackProfilesList', logContext: 'StarterPackProfilesList',
@@ -427,7 +425,7 @@ function Header({
<View style={[a.flex_row, a.gap_sm, a.align_center]}> <View style={[a.flex_row, a.gap_sm, a.align_center]}>
{isOwn ? ( {isOwn ? (
<Button <Button
label={_(msg`Share this starter pack`)} label={l`Share this Starter Pack`}
hitSlop={HITSLOP_20} hitSlop={HITSLOP_20}
variant="solid" variant="solid"
color="primary" color="primary"
@@ -439,7 +437,7 @@ function Header({
</Button> </Button>
) : ( ) : (
<Button <Button
label={_(msg`Follow all`)} label={l`Follow all`}
variant="solid" variant="solid"
color="primary" color="primary"
size="small" size="small"
@@ -465,7 +463,7 @@ function Header({
{richText ? <RichText value={richText} style={[a.text_md]} /> : null} {richText ? <RichText value={richText} style={[a.text_md]} /> : null}
{!hasSession ? ( {!hasSession ? (
<Button <Button
label={_(msg`Join Bluesky`)} label={l`Join Bluesky`}
onPress={() => { onPress={() => {
setActiveStarterPack({ setActiveStarterPack({
uri: starterPack.uri, uri: starterPack.uri,
@@ -491,12 +489,12 @@ function Header({
a.text_sm, a.text_sm,
t.atoms.text_contrast_medium, t.atoms.text_contrast_medium,
]}> ]}>
<Trans comment="Number of users (always at least 25) who have joined Bluesky using a specific starter pack"> <Trans comment="Number of users (always at least 25) who have joined Bluesky using a specific Starter Pack">
<Plural <Plural
value={starterPack.joinedAllTimeCount || 0} value={starterPack.joinedAllTimeCount || 0}
other="# people have" other="# people have"
/>{' '} />{' '}
joined Bluesky via this starter pack! joined Bluesky via this Starter Pack!
</Trans> </Trans>
</Text> </Text>
</View> </View>
@@ -517,7 +515,7 @@ function OverflowMenu({
onOpenShareDialog: () => void onOpenShareDialog: () => void
}) { }) {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {t: l} = useLingui()
const ax = useAnalytics() const ax = useAnalytics()
const {gtMobile} = useBreakpoints() const {gtMobile} = useBreakpoints()
const {currentAccount} = useSession() const {currentAccount} = useSession()
@@ -543,7 +541,7 @@ function OverflowMenu({
}) })
}, },
onError: e => { onError: e => {
logger.error('Failed to delete starter pack', {safeMessage: e}) logger.error('Failed to delete Starter Pack', {safeMessage: e})
}, },
}) })
@@ -559,21 +557,21 @@ function OverflowMenu({
}) })
Toast.show( Toast.show(
action === 'optOut' action === 'optOut'
? _(msg`Opted out of starter pack`) ? l`Opted out of Starter Pack`
: _(msg`Opt-out undone`), : l`Opt-out undone`,
) )
}, },
onError: error => { onError: error => {
logger.error('Failed to update starter pack opt-out', { logger.error('Failed to update Starter Pack opt-out', {
safeMessage: error, safeMessage: error,
}) })
Toast.show(_(msg`Failed to update starter pack opt-out`), { Toast.show(l`Failed to update Starter Pack opt-out`, {
type: 'error', type: 'error',
}) })
}, },
}) })
const onDeleteStarterPack = async () => { const onDeleteStarterPack = () => {
if (!starterPack.list) { if (!starterPack.list) {
logger.error(`Unable to delete starterpack because list is missing`) logger.error(`Unable to delete starterpack because list is missing`)
return return
@@ -589,12 +587,12 @@ function OverflowMenu({
return ( return (
<> <>
<Menu.Root> <Menu.Root>
<Menu.Trigger label={_(msg`Repost or quote post`)}> <Menu.Trigger label={l`Repost or quote post`}>
{({props}) => ( {({props}) => (
<Button <Button
{...props} {...props}
testID="headerDropdownBtn" testID="headerDropdownBtn"
label={_(msg`Open starter pack menu`)} label={l`Open Starter Pack menu`}
hitSlop={HITSLOP_20} hitSlop={HITSLOP_20}
variant="solid" variant="solid"
color="secondary" color="secondary"
@@ -608,7 +606,7 @@ function OverflowMenu({
{isOwn ? ( {isOwn ? (
<> <>
<Menu.Item <Menu.Item
label={_(msg`Edit starter pack`)} label={l`Edit Starter Pack`}
testID="editStarterPackLinkBtn" testID="editStarterPackLinkBtn"
onPress={() => { onPress={() => {
navigation.navigate('StarterPackEdit', { navigation.navigate('StarterPackEdit', {
@@ -621,7 +619,7 @@ function OverflowMenu({
<Menu.ItemIcon icon={Pencil} position="right" /> <Menu.ItemIcon icon={Pencil} position="right" />
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
label={_(msg`Delete starter pack`)} label={l`Delete Starter Pack`}
testID="deleteStarterPackBtn" testID="deleteStarterPackBtn"
onPress={() => { onPress={() => {
deleteDialogControl.open() deleteDialogControl.open()
@@ -632,7 +630,7 @@ function OverflowMenu({
<Menu.ItemIcon icon={Trash} position="right" /> <Menu.ItemIcon icon={Trash} position="right" />
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
label={_(msg`Create a list from this starter pack`)} label={l`Create a list from this Starter Pack`}
testID="convertToListBtn" testID="convertToListBtn"
onPress={() => { onPress={() => {
convertToListDialogControl.open() convertToListDialogControl.open()
@@ -648,9 +646,7 @@ function OverflowMenu({
<Menu.Group> <Menu.Group>
<Menu.Item <Menu.Item
label={ label={
IS_WEB IS_WEB ? l`Copy link to Starter Pack` : l`Share via...`
? _(msg`Copy link to starter pack`)
: _(msg`Share via...`)
} }
testID="shareStarterPackLinkBtn" testID="shareStarterPackLinkBtn"
onPress={onOpenShareDialog}> onPress={onOpenShareDialog}>
@@ -669,10 +665,10 @@ function OverflowMenu({
</Menu.Group> </Menu.Group>
<Menu.Item <Menu.Item
label={_(msg`Report starter pack`)} label={l`Report Starter Pack`}
onPress={() => reportDialogControl.open()}> onPress={() => reportDialogControl.open()}>
<Menu.ItemText> <Menu.ItemText>
<Trans>Report starter pack</Trans> <Trans>Report Starter Pack</Trans>
</Menu.ItemText> </Menu.ItemText>
<Menu.ItemIcon icon={CircleInfo} position="right" /> <Menu.ItemIcon icon={CircleInfo} position="right" />
</Menu.Item> </Menu.Item>
@@ -680,8 +676,8 @@ function OverflowMenu({
<Menu.Item <Menu.Item
label={ label={
referenceListOptOut referenceListOptOut
? _(msg`Undo opt-out from starter pack`) ? l`Undo opt-out from Starter Pack`
: _(msg`Opt out of starter pack`) : l`Opt out of Starter Pack`
} }
disabled={isOptOutPending} disabled={isOptOutPending}
onPress={() => optOutDialogControl.open()}> onPress={() => optOutDialogControl.open()}>
@@ -689,7 +685,7 @@ function OverflowMenu({
{referenceListOptOut ? ( {referenceListOptOut ? (
<Trans>Undo opt-out</Trans> <Trans>Undo opt-out</Trans>
) : ( ) : (
<Trans>Opt out of starter pack</Trans> <Trans>Opt out of Starter Pack</Trans>
)} )}
</Menu.ItemText> </Menu.ItemText>
</Menu.Item> </Menu.Item>
@@ -698,7 +694,6 @@ function OverflowMenu({
)} )}
</Menu.Outer> </Menu.Outer>
</Menu.Root> </Menu.Root>
{starterPack.list && ( {starterPack.list && (
<ReportDialog <ReportDialog
control={reportDialogControl} control={reportDialogControl}
@@ -708,13 +703,12 @@ function OverflowMenu({
}} }}
/> />
)} )}
<Prompt.Outer control={deleteDialogControl}> <Prompt.Outer control={deleteDialogControl}>
<Prompt.TitleText> <Prompt.TitleText>
<Trans>Delete starter pack?</Trans> <Trans>Delete Starter Pack?</Trans>
</Prompt.TitleText> </Prompt.TitleText>
<Prompt.DescriptionText> <Prompt.DescriptionText>
<Trans>Are you sure you want to delete this starter pack?</Trans> <Trans>Are you sure you want to delete this Starter Pack?</Trans>
</Prompt.DescriptionText> </Prompt.DescriptionText>
{deleteError && ( {deleteError && (
<View <View
@@ -742,7 +736,7 @@ function OverflowMenu({
variant="solid" variant="solid"
color="negative" color="negative"
size={gtMobile ? 'small' : 'large'} size={gtMobile ? 'small' : 'large'}
label={_(msg`Yes, delete this starter pack`)} label={l`Yes, delete this Starter Pack`}
onPress={onDeleteStarterPack}> onPress={onDeleteStarterPack}>
<ButtonText> <ButtonText>
<Trans>Delete</Trans> <Trans>Delete</Trans>
@@ -752,24 +746,23 @@ function OverflowMenu({
<Prompt.Cancel /> <Prompt.Cancel />
</Prompt.Actions> </Prompt.Actions>
</Prompt.Outer> </Prompt.Outer>
{starterPack.list ? ( {starterPack.list ? (
<Prompt.Outer control={optOutDialogControl}> <Prompt.Outer control={optOutDialogControl}>
<Prompt.TitleText> <Prompt.TitleText>
{referenceListOptOut ? ( {referenceListOptOut ? (
<Trans>Undo opt-out?</Trans> <Trans>Undo opt-out?</Trans>
) : ( ) : (
<Trans>Opt out of this starter pack?</Trans> <Trans>Opt out of this Starter Pack?</Trans>
)} )}
</Prompt.TitleText> </Prompt.TitleText>
<Prompt.DescriptionText> <Prompt.DescriptionText>
{referenceListOptOut ? ( {referenceListOptOut ? (
<Trans> <Trans>
You will be eligible to appear in this starter pack again. You will be eligible to appear in this Starter Pack again.
</Trans> </Trans>
) : ( ) : (
<Trans> <Trans>
You will no longer appear in this starter pack. The creator will You will no longer appear in this Starter Pack. The creator will
be able to see that you've opted out and remove you if they be able to see that you've opted out and remove you if they
wish. wish.
</Trans> </Trans>
@@ -782,8 +775,8 @@ function OverflowMenu({
size="large" size="large"
label={ label={
referenceListOptOut referenceListOptOut
? _(msg`Undo opt-out`) ? l`Undo opt-out`
: _(msg`Opt out of starter pack`) : l`Opt out of Starter Pack`
} }
disabled={isOptOutPending} disabled={isOptOutPending}
onPress={() => { onPress={() => {
@@ -804,7 +797,6 @@ function OverflowMenu({
</Prompt.Actions> </Prompt.Actions>
</Prompt.Outer> </Prompt.Outer>
) : null} ) : null}
<CreateListFromStarterPackDialog <CreateListFromStarterPackDialog
control={convertToListDialogControl} control={convertToListDialogControl}
starterPack={starterPack} starterPack={starterPack}
@@ -814,7 +806,7 @@ function OverflowMenu({
} }
function InvalidStarterPack({rkey}: {rkey: string}) { function InvalidStarterPack({rkey}: {rkey: string}) {
const {_} = useLingui() const {t: l} = useLingui()
const t = useTheme() const t = useTheme()
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
const {gtMobile} = useBreakpoints() const {gtMobile} = useBreakpoints()
@@ -835,8 +827,8 @@ function InvalidStarterPack({rkey}: {rkey: string}) {
}, },
onError: e => { onError: e => {
setIsProcessing(false) setIsProcessing(false)
logger.error('Failed to delete invalid starter pack', {safeMessage: e}) logger.error('Failed to delete invalid Starter Pack', {safeMessage: e})
Toast.show(_(msg`Failed to delete starter pack`), { Toast.show(l`Failed to delete Starter Pack`, {
type: 'error', type: 'error',
}) })
}, },
@@ -847,7 +839,7 @@ function InvalidStarterPack({rkey}: {rkey: string}) {
<View style={[a.py_4xl, a.px_xl, a.align_center, a.gap_5xl]}> <View style={[a.py_4xl, a.px_xl, a.align_center, a.gap_5xl]}>
<View style={[a.w_full, a.align_center, a.gap_lg]}> <View style={[a.w_full, a.align_center, a.gap_lg]}>
<Text style={[a.font_semi_bold, a.text_3xl]}> <Text style={[a.font_semi_bold, a.text_3xl]}>
<Trans>Starter pack is invalid</Trans> <Trans>Starter Pack is invalid</Trans>
</Text> </Text>
<Text <Text
style={[ style={[
@@ -858,8 +850,8 @@ function InvalidStarterPack({rkey}: {rkey: string}) {
gtMobile ? {width: 450} : [a.w_full, a.px_lg], gtMobile ? {width: 450} : [a.w_full, a.px_lg],
]}> ]}>
<Trans> <Trans>
The starter pack that you are trying to view is invalid. You may The Starter Pack that you are trying to view is invalid. You may
delete this starter pack instead. delete this Starter Pack instead.
</Trans> </Trans>
</Text> </Text>
</View> </View>
@@ -867,7 +859,7 @@ function InvalidStarterPack({rkey}: {rkey: string}) {
<Button <Button
variant="solid" variant="solid"
color="primary" color="primary"
label={_(msg`Delete starter pack`)} label={l`Delete Starter Pack`}
size="large" size="large"
style={[a.rounded_sm, a.overflow_hidden, {paddingVertical: 10}]} style={[a.rounded_sm, a.overflow_hidden, {paddingVertical: 10}]}
disabled={isProcessing} disabled={isProcessing}
@@ -883,7 +875,7 @@ function InvalidStarterPack({rkey}: {rkey: string}) {
<Button <Button
variant="solid" variant="solid"
color="secondary" color="secondary"
label={_(msg`Return to previous page`)} label={l`Return to previous page`}
size="large" size="large"
style={[a.rounded_sm, a.overflow_hidden, {paddingVertical: 10}]} style={[a.rounded_sm, a.overflow_hidden, {paddingVertical: 10}]}
disabled={isProcessing} disabled={isProcessing}
@@ -41,12 +41,12 @@ export function StepDetails() {
</View> </View>
<View> <View>
<TextField.LabelText> <TextField.LabelText>
<Trans>What do you want to call your starter pack?</Trans> <Trans>What do you want to call your Starter Pack?</Trans>
</TextField.LabelText> </TextField.LabelText>
<TextField.Root> <TextField.Root>
<TextField.Input <TextField.Input
label={ label={
name ? _(msg`${name}'s starter pack`) : _(msg`My starter pack`) name ? _(msg`${name}s Starter Pack`) : _(msg`My Starter Pack`)
} }
value={state.name} value={state.name}
onChangeText={text => dispatch({type: 'SetName', name: text})} onChangeText={text => dispatch({type: 'SetName', name: text})}
+31 -35
View File
@@ -5,9 +5,7 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {Image} from 'expo-image' import {Image} from 'expo-image'
import {AtUri} from '@atproto/syntax' import {AtUri} from '@atproto/syntax'
import {type ModerationOpts} from '@bsky/sdk/moderation' import {type ModerationOpts} from '@bsky/sdk/moderation'
import {msg} from '@lingui/core/macro' import {Plural, Trans, useLingui} from '@lingui/react/macro'
import {useLingui} from '@lingui/react'
import {Plural, Trans} from '@lingui/react/macro'
import {useNavigation} from '@react-navigation/native' import {useNavigation} from '@react-navigation/native'
import {type NativeStackScreenProps} from '@react-navigation/native-stack' import {type NativeStackScreenProps} from '@react-navigation/native-stack'
@@ -71,7 +69,7 @@ export function Wizard({
const {currentAccount} = useSession() const {currentAccount} = useSession()
const moderationOpts = useModerationOpts() const moderationOpts = useModerationOpts()
const {_} = useLingui() const {t: l} = useLingui()
// Use targetDid if provided (from dialog), otherwise use current account // Use targetDid if provided (from dialog), otherwise use current account
const profileDid = targetDid || currentAccount!.did const profileDid = targetDid || currentAccount!.did
@@ -109,7 +107,7 @@ export function Wizard({
isLoadingStarterPack || isLoadingProfiles || isLoadingProfile isLoadingStarterPack || isLoadingProfiles || isLoadingProfile
} }
isError={isErrorStarterPack || isErrorProfiles || isErrorProfile} isError={isErrorStarterPack || isErrorProfiles || isErrorProfile}
errorMessage={_(msg`That starter pack could not be found.`)} errorMessage={l`That Starter Pack could not be found.`}
/> />
</Layout.Screen> </Layout.Screen>
) )
@@ -119,7 +117,7 @@ export function Wizard({
<ListMaybePlaceholder <ListMaybePlaceholder
isLoading={false} isLoading={false}
isError={true} isError={true}
errorMessage={_(msg`That starter pack could not be found.`)} errorMessage={l`That Starter Pack could not be found.`}
/> />
</Layout.Screen> </Layout.Screen>
) )
@@ -164,7 +162,7 @@ function WizardInner({
}) { }) {
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
const ax = useAnalytics() const ax = useAnalytics()
const {_} = useLingui() const {t: l} = useLingui()
const [state, dispatch] = useWizardState() const [state, dispatch] = useWizardState()
const {currentAccount} = useSession() const {currentAccount} = useSession()
@@ -182,7 +180,7 @@ function WizardInner({
const getDefaultName = () => { const getDefaultName = () => {
const displayName = createSanitizedDisplayName(currentProfile!, true) const displayName = createSanitizedDisplayName(currentProfile!, true)
return _(msg`${displayName}'s Starter Pack`).slice(0, 50) return l`${displayName}s Starter Pack`.slice(0, 50)
} }
const wizardUiStrings: Record< const wizardUiStrings: Record<
@@ -190,16 +188,16 @@ function WizardInner({
{header: string; nextBtn: string; subtitle?: string} {header: string; nextBtn: string; subtitle?: string}
> = { > = {
Details: { Details: {
header: _(msg`Starter Pack`), header: l`Starter Pack`,
nextBtn: _(msg`Next`), nextBtn: l`Next`,
}, },
Profiles: { Profiles: {
header: _(msg`Choose People`), header: l`Choose People`,
nextBtn: _(msg`Next`), nextBtn: l`Next`,
}, },
Feeds: { Feeds: {
header: _(msg`Choose Feeds`), header: l`Choose Feeds`,
nextBtn: state.feeds.length === 0 ? _(msg`Skip`) : _(msg`Finish`), nextBtn: state.feeds.length === 0 ? l`Skip` : l`Finish`,
}, },
} }
const currUiStrings = wizardUiStrings[state.currentStep] const currUiStrings = wizardUiStrings[state.currentStep]
@@ -212,7 +210,7 @@ function WizardInner({
profilesCount: state.profiles.length, profilesCount: state.profiles.length,
feedsCount: state.feeds.length, feedsCount: state.feeds.length,
}) })
Image.prefetch([getStarterPackOgCard(currentProfile!.did, rkey)]) void Image.prefetch([getStarterPackOgCard(currentProfile!.did, rkey)])
dispatch({type: 'SetProcessing', processing: false}) dispatch({type: 'SetProcessing', processing: false})
if (fromDialog) { if (fromDialog) {
@@ -241,9 +239,9 @@ function WizardInner({
const {mutate: createStarterPack} = useCreateStarterPackMutation({ const {mutate: createStarterPack} = useCreateStarterPackMutation({
onSuccess: onSuccessCreate, onSuccess: onSuccessCreate,
onError: e => { onError: e => {
logger.error('Failed to create starter pack', {safeMessage: e}) logger.error('Failed to create Starter Pack', {safeMessage: e})
dispatch({type: 'SetProcessing', processing: false}) dispatch({type: 'SetProcessing', processing: false})
Toast.show(_(msg`Failed to create starter pack`), { Toast.show(l`Failed to create Starter Pack`, {
type: 'error', type: 'error',
}) })
}, },
@@ -251,15 +249,15 @@ function WizardInner({
const {mutate: editStarterPack} = useEditStarterPackMutation({ const {mutate: editStarterPack} = useEditStarterPackMutation({
onSuccess: onSuccessEdit, onSuccess: onSuccessEdit,
onError: e => { onError: e => {
logger.error('Failed to edit starter pack', {safeMessage: e}) logger.error('Failed to edit Starter Pack', {safeMessage: e})
dispatch({type: 'SetProcessing', processing: false}) dispatch({type: 'SetProcessing', processing: false})
Toast.show(_(msg`Failed to create starter pack`), { Toast.show(l`Failed to create Starter Pack`, {
type: 'error', type: 'error',
}) })
}, },
}) })
const submit = async () => { const submit = () => {
dispatch({type: 'SetProcessing', processing: true}) dispatch({type: 'SetProcessing', processing: true})
if (currentStarterPack && currentListItems) { if (currentStarterPack && currentListItems) {
editStarterPack({ editStarterPack({
@@ -308,8 +306,8 @@ function WizardInner({
<Layout.Center style={[a.flex_1]}> <Layout.Center style={[a.flex_1]}>
<Layout.Header.Outer> <Layout.Header.Outer>
<Layout.Header.BackButton <Layout.Header.BackButton
label={_(msg`Back`)} label={l`Back`}
accessibilityHint={_(msg`Returns to the previous step`)} accessibilityHint={l`Returns to the previous step`}
onPress={evt => { onPress={evt => {
if (state.currentStep !== 'Details') { if (state.currentStep !== 'Details') {
evt.preventDefault() evt.preventDefault()
@@ -324,7 +322,7 @@ function WizardInner({
</Layout.Header.Content> </Layout.Header.Content>
{isEditEnabled ? ( {isEditEnabled ? (
<Button <Button
label={_(msg`Edit`)} label={l`Edit`}
color="secondary" color="secondary"
size="small" size="small"
onPress={editDialogControl.open}> onPress={editDialogControl.open}>
@@ -336,7 +334,6 @@ function WizardInner({
<Layout.Header.Slot /> <Layout.Header.Slot />
)} )}
</Layout.Header.Outer> </Layout.Header.Outer>
<Container> <Container>
{state.currentStep === 'Details' ? ( {state.currentStep === 'Details' ? (
<StepDetails /> <StepDetails />
@@ -355,7 +352,6 @@ function WizardInner({
<StepFeeds moderationOpts={moderationOpts} /> <StepFeeds moderationOpts={moderationOpts} />
) : null} ) : null}
</Container> </Container>
{state.currentStep !== 'Details' && ( {state.currentStep !== 'Details' && (
<Footer onNext={onNext} nextBtnText={currUiStrings.nextBtn} /> <Footer onNext={onNext} nextBtnText={currUiStrings.nextBtn} />
)} )}
@@ -371,7 +367,7 @@ function WizardInner({
} }
function Container({children}: {children: React.ReactNode}) { function Container({children}: {children: React.ReactNode}) {
const {_} = useLingui() const {t: l} = useLingui()
const [state, dispatch] = useWizardState() const [state, dispatch] = useWizardState()
if (state.currentStep === 'Profiles' || state.currentStep === 'Feeds') { if (state.currentStep === 'Profiles' || state.currentStep === 'Feeds') {
@@ -386,7 +382,7 @@ function Container({children}: {children: React.ReactNode}) {
{state.currentStep === 'Details' && ( {state.currentStep === 'Details' && (
<> <>
<Button <Button
label={_(msg`Next`)} label={l`Next`}
variant="solid" variant="solid"
color="primary" color="primary"
size="large" size="large"
@@ -490,7 +486,7 @@ function Footer({
<Text style={[a.font_semi_bold, textStyles]} emoji> <Text style={[a.font_semi_bold, textStyles]} emoji>
{getName(items[0])}{' '} {getName(items[0])}{' '}
</Text> </Text>
right now! Add more people to your starter pack by searching right now! Add more people to your Starter Pack by searching
above. above.
</Trans> </Trans>
) )
@@ -502,7 +498,7 @@ function Footer({
<Text style={[a.font_semi_bold, textStyles]} emoji> <Text style={[a.font_semi_bold, textStyles]} emoji>
{getName(items[1] /* [0] is self, skip it */)}{' '} {getName(items[1] /* [0] is self, skip it */)}{' '}
</Text> </Text>
are included in your starter pack are included in your Starter Pack
</Trans> </Trans>
) : ( ) : (
<Trans> <Trans>
@@ -514,7 +510,7 @@ function Footer({
<Text style={[a.font_semi_bold, textStyles]} emoji> <Text style={[a.font_semi_bold, textStyles]} emoji>
{getName(items[1] /* [0] is self, skip it */)}{' '} {getName(items[1] /* [0] is self, skip it */)}{' '}
</Text> </Text>
are included in your starter pack are included in your Starter Pack
</Trans> </Trans>
) )
) : items.length > 2 ? ( ) : items.length > 2 ? (
@@ -531,7 +527,7 @@ function Footer({
one="# other" one="# other"
other="# others" other="# others"
/>{' '} />{' '}
are included in your starter pack are included in your Starter Pack
</Trans> </Trans>
) : null /* Should not happen. */ ) : null /* Should not happen. */
} }
@@ -540,7 +536,7 @@ function Footer({
items.length === 0 ? ( items.length === 0 ? (
<View style={[a.gap_sm]}> <View style={[a.gap_sm]}>
<Text style={[a.font_semi_bold, a.text_center, textStyles]}> <Text style={[a.font_semi_bold, a.text_center, textStyles]}>
<Trans>Add some feeds to your starter pack!</Trans> <Trans>Add some feeds to your Starter Pack!</Trans>
</Text> </Text>
<Text style={[a.text_center, textStyles]}> <Text style={[a.text_center, textStyles]}>
<Trans> <Trans>
@@ -556,7 +552,7 @@ function Footer({
<Text style={[a.font_semi_bold, textStyles]} emoji> <Text style={[a.font_semi_bold, textStyles]} emoji>
{getName(items[0])} {getName(items[0])}
</Text>{' '} </Text>{' '}
is included in your starter pack is included in your Starter Pack
</Trans> </Trans>
) : items.length === 2 ? ( ) : items.length === 2 ? (
<Trans> <Trans>
@@ -568,7 +564,7 @@ function Footer({
<Text style={[a.font_semi_bold, textStyles]} emoji> <Text style={[a.font_semi_bold, textStyles]} emoji>
{getName(items[1])}{' '} {getName(items[1])}{' '}
</Text> </Text>
are included in your starter pack are included in your Starter Pack
</Trans> </Trans>
) : items.length > 2 ? ( ) : items.length > 2 ? (
<Trans context="feeds"> <Trans context="feeds">
@@ -584,7 +580,7 @@ function Footer({
one="# other" one="# other"
other="# others" other="# others"
/>{' '} />{' '}
are included in your starter pack are included in your Starter Pack
</Trans> </Trans>
) : null /* Should not happen. */ ) : null /* Should not happen. */
} }
+3 -3
View File
@@ -25,7 +25,7 @@ export function useListMembershipAddMutation({
onError, onError,
}: { }: {
/** /**
* Needed for optimistic update of starter pack query * Needed for optimistic update of Starter Pack query
*/ */
subject?: bsky.profile.AnyProfileView subject?: bsky.profile.AnyProfileView
onSuccess?: (data: {uri: AtUriString; cid: string}) => void onSuccess?: (data: {uri: AtUriString; cid: string}) => void
@@ -66,7 +66,7 @@ export function useListMembershipAddMutation({
}) })
}, 1e3) }, 1e3)
// update WITH_MEMBERSHIPS query for starter packs // update WITH_MEMBERSHIPS query for Starter Packs
if (subject) { if (subject) {
queryClient.setQueryData< queryClient.setQueryData<
InfiniteData<app.bsky.graph.getStarterPacksWithMembership.$OutputBody> InfiniteData<app.bsky.graph.getStarterPacksWithMembership.$OutputBody>
@@ -161,7 +161,7 @@ export function useListMembershipRemoveMutation({
}) })
}, 1e3) }, 1e3)
// update WITH_MEMBERSHIPS query for starter packs // update WITH_MEMBERSHIPS query for Starter Packs
queryClient.setQueryData< queryClient.setQueryData<
InfiniteData<app.bsky.graph.getStarterPacksWithMembership.$OutputBody> InfiniteData<app.bsky.graph.getStarterPacksWithMembership.$OutputBody>
>(STARTER_PACKS_WITH_MEMBERSHIPS_RKEY(variables.actorDid), old => { >(STARTER_PACKS_WITH_MEMBERSHIPS_RKEY(variables.actorDid), old => {
@@ -39,7 +39,7 @@ function makeFollowNotification(
} }
describe('groupNotifications', () => { describe('groupNotifications', () => {
it('does not group a starter pack follow with an organic follow', () => { it('does not group a Starter Pack follow with an organic follow', () => {
const pack = 'at://did:plc:alice/app.bsky.graph.starterpack/a' const pack = 'at://did:plc:alice/app.bsky.graph.starterpack/a'
const grouped = groupNotifications([ const grouped = groupNotifications([
@@ -56,7 +56,7 @@ describe('groupNotifications', () => {
expect(grouped[1].additional).toBeUndefined() expect(grouped[1].additional).toBeUndefined()
}) })
it('groups follows by starter pack', () => { it('groups follows by Starter Pack', () => {
const packA = 'at://did:plc:alice/app.bsky.graph.starterpack/a' const packA = 'at://did:plc:alice/app.bsky.graph.starterpack/a'
const packB = 'at://did:plc:bob/app.bsky.graph.starterpack/b' const packB = 'at://did:plc:bob/app.bsky.graph.starterpack/b'
+3 -3
View File
@@ -98,7 +98,7 @@ export function useReferenceListOptOutMutation({
>({ >({
mutationFn: async ({referenceListOptOut}) => { mutationFn: async ({referenceListOptOut}) => {
if (!starterPack.list) { if (!starterPack.list) {
throw new Error('Starter pack does not have a reference list') throw new Error('Starter Pack does not have a reference list')
} }
let nextOptOut: AtUriString | undefined let nextOptOut: AtUriString | undefined
@@ -292,7 +292,7 @@ export function useCreateStarterPackMutation({
}) })
onSuccess(data) onSuccess(data)
}, },
onError: async error => { onError: error => {
onError(error) onError(error)
}, },
}) })
@@ -333,7 +333,7 @@ export function useEditStarterPackMutation({
} }
if (!bsky.isType(app.bsky.graph.starterpack, currentStarterPack.record)) { if (!bsky.isType(app.bsky.graph.starterpack, currentStarterPack.record)) {
throw new Error('Invalid starter pack') throw new Error('Invalid Starter Pack')
} }
const removedItems = currentListItems.filter( const removedItems = currentListItems.filter(
+2 -2
View File
@@ -44,14 +44,14 @@ const syntheticFullView = {
list: { list: {
uri: 'at://did:plc:abc/app.bsky.graph.list/123', uri: 'at://did:plc:abc/app.bsky.graph.list/123',
cid: '', cid: '',
name: 'Starter pack', name: 'Starter Pack',
purpose: 'app.bsky.graph.defs#referencelist', purpose: 'app.bsky.graph.defs#referencelist',
}, },
} }
/* /*
* Type-level assertions for the view alias: it must accept both the basic and * Type-level assertions for the view alias: it must accept both the basic and
* the full starter pack view. Compile-time only - a failure surfaces as a * the full Starter Pack view. Compile-time only - a failure surfaces as a
* typecheck error. * typecheck error.
*/ */
type Assignable<From, To> = From extends To ? true : false type Assignable<From, To> = From extends To ? true : false
+2 -2
View File
@@ -26,7 +26,7 @@ export function isView(v: unknown): v is app.bsky.graph.defs.StarterPackView {
} }
/** /**
* Accepts both forms of a full starter pack view used by the app: * Accepts both forms of a full Starter Pack view used by the app:
* *
* - direct lexicon refs returned by the app view, where `$type` may be omitted * - direct lexicon refs returned by the app view, where `$type` may be omitted
* - trusted synthetic cache entries, which carry `$type` but may contain * - trusted synthetic cache entries, which carry `$type` but may contain
@@ -39,7 +39,7 @@ export function isTrustedView(
} }
/** /**
* Matches any starter pack view exported by our SDK. * Matches any Starter Pack view exported by our SDK.
*/ */
export type AnyStarterPackView = export type AnyStarterPackView =
app.bsky.graph.defs.StarterPackViewBasic | app.bsky.graph.defs.StarterPackView app.bsky.graph.defs.StarterPackViewBasic | app.bsky.graph.defs.StarterPackView
+1 -1
View File
@@ -91,7 +91,7 @@ export function LoggedOut({onDismiss}: {onDismiss?: () => void}) {
/* /*
* Back from the login or create-account step. Where it returns depends on how * Back from the login or create-account step. Where it returns depends on how
* the view was entered: * the view was entered:
* - Entered on the splash or a landing screen (starter pack, group chat * - Entered on the splash or a landing screen (Starter Pack, group chat
* invite): return there, i.e. the screen the user actually saw first. * invite): return there, i.e. the screen the user actually saw first.
* - Dropped directly onto login/create-account (e.g. tapping "Create * - Dropped directly onto login/create-account (e.g. tapping "Create
* account" elsewhere in the app): there is no preceding screen in this * account" elsewhere in the app): there is no preceding screen in this
@@ -338,7 +338,7 @@ let NotificationFeedItem = ({
* see `src/state/queries/notifications/util.ts` * see `src/state/queries/notifications/util.ts`
*/ */
a11yLabel = starterPackName a11yLabel = starterPackName
? l`${firstAuthorName} followed you back via starter pack ${starterPackName}` ? l`${firstAuthorName} followed you back via Starter Pack ${starterPackName}`
: l`${firstAuthorName} followed you back` : l`${firstAuthorName} followed you back`
notificationContent = <Trans>{firstAuthorLink} followed you back</Trans> notificationContent = <Trans>{firstAuthorLink} followed you back</Trans>
} else { } else {
@@ -347,8 +347,8 @@ let NotificationFeedItem = ({
? l`${firstAuthorName} and ${plural(additionalAuthorsCount, { ? l`${firstAuthorName} and ${plural(additionalAuthorsCount, {
one: `${formattedAuthorsCount} other`, one: `${formattedAuthorsCount} other`,
other: `${formattedAuthorsCount} others`, other: `${formattedAuthorsCount} others`,
})} followed you via starter pack ${starterPackName}` })} followed you via Starter Pack ${starterPackName}`
: l`${firstAuthorName} followed you via starter pack ${starterPackName}` : l`${firstAuthorName} followed you via Starter Pack ${starterPackName}`
: hasMultipleAuthors : hasMultipleAuthors
? l`${firstAuthorName} and ${plural(additionalAuthorsCount, { ? l`${firstAuthorName} and ${plural(additionalAuthorsCount, {
one: `${formattedAuthorsCount} other`, one: `${formattedAuthorsCount} other`,
@@ -407,8 +407,8 @@ let NotificationFeedItem = ({
? l`${firstAuthorName} and ${plural(additionalAuthorsCount, { ? l`${firstAuthorName} and ${plural(additionalAuthorsCount, {
one: `${formattedAuthorsCount} other`, one: `${formattedAuthorsCount} other`,
other: `${formattedAuthorsCount} others`, other: `${formattedAuthorsCount} others`,
})} signed up with your starter pack` })} signed up with your Starter Pack`
: l`${firstAuthorName} signed up with your starter pack` : l`${firstAuthorName} signed up with your Starter Pack`
notificationContent = hasMultipleAuthors ? ( notificationContent = hasMultipleAuthors ? (
<Trans> <Trans>
{firstAuthorLink} and{' '} {firstAuthorLink} and{' '}
@@ -419,10 +419,10 @@ let NotificationFeedItem = ({
other={`${formattedAuthorsCount} others`} other={`${formattedAuthorsCount} others`}
/> />
</Text>{' '} </Text>{' '}
signed up with your starter pack signed up with your Starter Pack
</Trans> </Trans>
) : ( ) : (
<Trans>{firstAuthorLink} signed up with your starter pack</Trans> <Trans>{firstAuthorLink} signed up with your Starter Pack</Trans>
) )
icon = ( icon = (
<View style={{height: 30, width: 30}}> <View style={{height: 30, width: 30}}>
@@ -735,8 +735,8 @@ function FollowedViaStarterPack({
return ( return (
<Text style={[native(a.pt_xs), t.atoms.text_contrast_medium]}> <Text style={[native(a.pt_xs), t.atoms.text_contrast_medium]}>
<Trans comment="When the source of a follow is a starter pack, i.e., 'via starter pack {starterPackName}'."> <Trans comment="When the source of a follow is a Starter Pack, i.e., 'via Starter Pack {starterPackName}'.">
via starter pack{' '} via Starter Pack{' '}
<StarterPackIcon <StarterPackIcon
size="sm" size="sm"
gradient="sky" gradient="sky"
+2 -2
View File
@@ -365,10 +365,10 @@ let ProfileMenu = ({
)} )}
<Menu.Item <Menu.Item
testID="profileHeaderDropdownStarterPackAddRemoveBtn" testID="profileHeaderDropdownStarterPackAddRemoveBtn"
label={l`Add to starter packs`} label={l`Add to Starter Packs`}
onPress={onPressAddToStarterPacks}> onPress={onPressAddToStarterPacks}>
<Menu.ItemText> <Menu.ItemText>
<Trans>Add to starter packs</Trans> <Trans>Add to Starter Packs</Trans>
</Menu.ItemText> </Menu.ItemText>
<Menu.ItemIcon icon={StarterPackIcon} /> <Menu.ItemIcon icon={StarterPackIcon} />
</Menu.Item> </Menu.Item>
@@ -1,9 +1,7 @@
import {useCallback} from 'react' import {useCallback} from 'react'
import {Pressable, View} from 'react-native' import {Pressable, View} from 'react-native'
import Animated, {useAnimatedRef} from 'react-native-reanimated' import Animated, {useAnimatedRef} from 'react-native-reanimated'
import {msg} from '@lingui/core/macro' import {Trans, useLingui} from '@lingui/react/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
import {useNavigation} from '@react-navigation/native' import {useNavigation} from '@react-navigation/native'
import {usePalette} from '#/lib/hooks/usePalette' import {usePalette} from '#/lib/hooks/usePalette'
@@ -47,7 +45,7 @@ export function ProfileSubpageHeader({
avatarType: UserAvatarType | 'starter-pack' avatarType: UserAvatarType | 'starter-pack'
}>) { }>) {
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
const {_} = useLingui() const {t: l} = useLingui()
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
const {openLightbox} = useLightboxControls() const {openLightbox} = useLightboxControls()
const pal = usePalette('default') const pal = usePalette('default')
@@ -90,7 +88,6 @@ export function ProfileSubpageHeader({
<Layout.Header.Content /> <Layout.Header.Content />
{children} {children}
</Layout.Header.Outer> </Layout.Header.Outer>
<View <View
style={{ style={{
flexDirection: 'row', flexDirection: 'row',
@@ -105,7 +102,7 @@ export function ProfileSubpageHeader({
testID="headerAviButton" testID="headerAviButton"
onPress={onPressAvi} onPress={onPressAvi}
accessibilityRole="image" accessibilityRole="image"
accessibilityLabel={_(msg`View the avatar`)} accessibilityLabel={l`View the avatar`}
accessibilityHint="" accessibilityHint=""
style={{width: 58}}> style={{width: 58}}>
{avatarType === 'starter-pack' ? ( {avatarType === 'starter-pack' ? (
@@ -166,10 +163,10 @@ export function ProfileSubpageHeader({
) )
) : purpose === 'app.bsky.graph.defs#referencelist' ? ( ) : purpose === 'app.bsky.graph.defs#referencelist' ? (
isOwner ? ( isOwner ? (
<Trans>Starter pack by you</Trans> <Trans>Starter Pack by you</Trans>
) : ( ) : (
<Trans> <Trans>
Starter pack by{' '} Starter Pack by{' '}
<TextLink <TextLink
text={sanitizeHandle(creator.handle || '', '@')} text={sanitizeHandle(creator.handle || '', '@')}
href={makeProfileLink(creator)} href={makeProfileLink(creator)}
+1 -1
View File
@@ -349,7 +349,7 @@ function ProfileScreenLoaded({
const wrappedNavToWizard = requireEmailVerification(navToWizard, { const wrappedNavToWizard = requireEmailVerification(navToWizard, {
instructions: [ instructions: [
<Trans key="nav"> <Trans key="nav">
Before creating a starter pack, you must first verify your email. Before creating a Starter Pack, you must first verify your email.
</Trans>, </Trans>,
], ],
}) })