parameterize the initial profile for starter pack profile select wizard screen
This commit is contained in:
@@ -48,7 +48,6 @@ export function WizardEditListDialog({
|
||||
}) {
|
||||
const {_} = useLingui()
|
||||
const t = useTheme()
|
||||
const {currentAccount} = useSession()
|
||||
const initialNumToRender = useInitialNumToRender()
|
||||
|
||||
const listRef = useRef<ListMethods>(null)
|
||||
@@ -56,10 +55,7 @@ export function WizardEditListDialog({
|
||||
const getData = () => {
|
||||
if (state.currentStep === 'Feeds') return state.feeds
|
||||
|
||||
return [
|
||||
profile,
|
||||
...state.profiles.filter(p => p.did !== currentAccount?.did),
|
||||
]
|
||||
return [profile, ...state.profiles.filter(p => p.did !== profile.did)]
|
||||
}
|
||||
|
||||
const renderItem = ({item}: ListRenderItemInfo<any>) =>
|
||||
|
||||
@@ -131,10 +131,13 @@ export function WizardProfileCard({
|
||||
}) {
|
||||
const {currentAccount} = useSession()
|
||||
|
||||
const isMe = profile.did === currentAccount?.did
|
||||
const included = isMe || state.profiles.some(p => p.did === profile.did)
|
||||
// Determine the "main" profile for this starter pack - either targetDid or current account
|
||||
const targetProfileDid = state.targetDid || currentAccount?.did
|
||||
const isTarget = profile.did === targetProfileDid
|
||||
const included = isTarget || state.profiles.some(p => p.did === profile.did)
|
||||
const disabled =
|
||||
isMe || (!included && state.profiles.length >= STARTER_PACK_MAX_SIZE - 1)
|
||||
isTarget ||
|
||||
(!included && state.profiles.length >= STARTER_PACK_MAX_SIZE - 1)
|
||||
const moderationUi = moderateProfile(profile, moderationOpts).ui('avatar')
|
||||
const displayName = profile.displayName
|
||||
? sanitizeDisplayName(profile.displayName)
|
||||
@@ -144,7 +147,7 @@ export function WizardProfileCard({
|
||||
if (disabled) return
|
||||
|
||||
Keyboard.dismiss()
|
||||
if (profile.did === currentAccount?.did) return
|
||||
if (profile.did === targetProfileDid) return
|
||||
|
||||
if (!included) {
|
||||
dispatch({type: 'AddProfile', profile})
|
||||
|
||||
@@ -12,7 +12,7 @@ import {useQueryClient} from '@tanstack/react-query'
|
||||
import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification'
|
||||
import {type NavigationProp} from '#/lib/routes/types'
|
||||
import {
|
||||
RQKEY_WITH_MEMBERSHIP,
|
||||
invalidateActorStarterPacksWithMembershipQuery,
|
||||
useActorStarterPacksWithMembershipsQuery,
|
||||
} from '#/state/queries/actor-starter-packs'
|
||||
import {
|
||||
@@ -35,7 +35,6 @@ import {TimesLarge_Stroke2_Corner0_Rounded} from '../icons/Times'
|
||||
type StarterPackWithMembership =
|
||||
AppBskyGraphGetStarterPacksWithMembership.StarterPackWithMembership
|
||||
|
||||
// Simple module-level state for dialog coordination
|
||||
let dialogCallbacks: {
|
||||
onSuccess?: () => void
|
||||
} = {}
|
||||
@@ -48,14 +47,12 @@ export function notifyDialogSuccess() {
|
||||
|
||||
export type StarterPackDialogProps = {
|
||||
control: Dialog.DialogControlProps
|
||||
accountDid: string
|
||||
targetDid: string
|
||||
enabled?: boolean
|
||||
}
|
||||
|
||||
export function StarterPackDialog({
|
||||
control,
|
||||
accountDid: _accountDid,
|
||||
targetDid,
|
||||
enabled,
|
||||
}: StarterPackDialogProps) {
|
||||
@@ -73,8 +70,11 @@ export function StarterPackDialog({
|
||||
|
||||
const navToWizard = React.useCallback(() => {
|
||||
control.close()
|
||||
navigation.navigate('StarterPackWizard', {fromDialog: true})
|
||||
}, [navigation, control])
|
||||
navigation.navigate('StarterPackWizard', {
|
||||
fromDialog: true,
|
||||
targetDid: targetDid,
|
||||
})
|
||||
}, [navigation, control, targetDid])
|
||||
|
||||
const wrappedNavToWizard = requireEmailVerification(navToWizard, {
|
||||
instructions: [
|
||||
@@ -85,7 +85,6 @@ export function StarterPackDialog({
|
||||
})
|
||||
|
||||
const onClose = React.useCallback(() => {
|
||||
// setCurrentView('initial')
|
||||
control.close()
|
||||
}, [control])
|
||||
|
||||
@@ -252,69 +251,60 @@ function StarterPackItem({
|
||||
const {_} = useLingui()
|
||||
const t = useTheme()
|
||||
const queryClient = useQueryClient()
|
||||
const [isUpdating, setIsUpdating] = React.useState(false)
|
||||
|
||||
const starterPack = starterPackWithMembership.starterPack
|
||||
const isInPack = !!starterPackWithMembership.listItem
|
||||
console.log('StarterPackItem render. 111', {
|
||||
starterPackWithMembership: starterPackWithMembership.listItem?.subject,
|
||||
})
|
||||
|
||||
console.log('StarterPackItem render', {
|
||||
starterPackWithMembership,
|
||||
})
|
||||
const {mutate: addMembership, isPending: isAddingPending} =
|
||||
useListMembershipAddMutation({
|
||||
onSuccess: () => {
|
||||
Toast.show(_(msg`Added to starter pack`))
|
||||
invalidateActorStarterPacksWithMembershipQuery({
|
||||
queryClient,
|
||||
did: targetDid,
|
||||
})
|
||||
},
|
||||
onError: () => {
|
||||
Toast.show(_(msg`Failed to add to starter pack`), 'xmark')
|
||||
},
|
||||
})
|
||||
|
||||
const {mutateAsync: addMembership} = useListMembershipAddMutation({
|
||||
onSuccess: () => {
|
||||
Toast.show(_(msg`Added to starter pack`))
|
||||
},
|
||||
onError: () => {
|
||||
Toast.show(_(msg`Failed to add to starter pack`), 'xmark')
|
||||
},
|
||||
})
|
||||
const {mutate: removeMembership, isPending: isRemovingPending} =
|
||||
useListMembershipRemoveMutation({
|
||||
onSuccess: () => {
|
||||
Toast.show(_(msg`Removed from starter pack`))
|
||||
invalidateActorStarterPacksWithMembershipQuery({
|
||||
queryClient,
|
||||
did: targetDid,
|
||||
})
|
||||
},
|
||||
onError: () => {
|
||||
Toast.show(_(msg`Failed to remove from starter pack`), 'xmark')
|
||||
},
|
||||
})
|
||||
|
||||
const {mutateAsync: removeMembership} = useListMembershipRemoveMutation({
|
||||
onSuccess: () => {
|
||||
Toast.show(_(msg`Removed from starter pack`))
|
||||
},
|
||||
onError: () => {
|
||||
Toast.show(_(msg`Failed to remove from starter pack`), 'xmark')
|
||||
},
|
||||
})
|
||||
const isMutating = isAddingPending || isRemovingPending
|
||||
|
||||
const handleToggleMembership = async () => {
|
||||
if (!starterPack.list?.uri || isUpdating) return
|
||||
const handleToggleMembership = () => {
|
||||
if (!starterPack.list?.uri || isMutating) return
|
||||
|
||||
const listUri = starterPack.list.uri
|
||||
setIsUpdating(true)
|
||||
|
||||
try {
|
||||
if (!isInPack) {
|
||||
await addMembership({
|
||||
listUri: listUri,
|
||||
actorDid: targetDid,
|
||||
})
|
||||
} else {
|
||||
if (!starterPackWithMembership.listItem?.uri) {
|
||||
console.error('Cannot remove: missing membership URI')
|
||||
return
|
||||
}
|
||||
await removeMembership({
|
||||
listUri: listUri,
|
||||
actorDid: targetDid,
|
||||
membershipUri: starterPackWithMembership.listItem.uri,
|
||||
})
|
||||
if (!isInPack) {
|
||||
addMembership({
|
||||
listUri: listUri,
|
||||
actorDid: targetDid,
|
||||
})
|
||||
} else {
|
||||
if (!starterPackWithMembership.listItem?.uri) {
|
||||
console.error('Cannot remove: missing membership URI')
|
||||
return
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: RQKEY_WITH_MEMBERSHIP(targetDid),
|
||||
}),
|
||||
])
|
||||
} catch (error) {
|
||||
console.error('Failed to toggle membership:', error)
|
||||
} finally {
|
||||
setIsUpdating(false)
|
||||
removeMembership({
|
||||
listUri: listUri,
|
||||
actorDid: targetDid,
|
||||
membershipUri: starterPackWithMembership.listItem.uri,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,7 +367,7 @@ function StarterPackItem({
|
||||
label={isInPack ? _(msg`Remove`) : _(msg`Add`)}
|
||||
color={isInPack ? 'secondary' : 'primary'}
|
||||
size="tiny"
|
||||
disabled={isUpdating}
|
||||
disabled={isMutating}
|
||||
onPress={handleToggleMembership}>
|
||||
<ButtonText>
|
||||
{isInPack ? <Trans>Remove</Trans> : <Trans>Add</Trans>}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import {
|
||||
$Typed,
|
||||
AppBskyActorDefs,
|
||||
AppBskyGraphGetStarterPack,
|
||||
BskyAgent,
|
||||
ComAtprotoRepoApplyWrites,
|
||||
Facet,
|
||||
type $Typed,
|
||||
type AppBskyActorDefs,
|
||||
type AppBskyGraphGetStarterPack,
|
||||
type BskyAgent,
|
||||
type ComAtprotoRepoApplyWrites,
|
||||
type Facet,
|
||||
} from '@atproto/api'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
@@ -15,7 +15,7 @@ import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {enforceLen} from '#/lib/strings/helpers'
|
||||
import {useAgent} from '#/state/session'
|
||||
import * as bsky from '#/types/bsky'
|
||||
import type * as bsky from '#/types/bsky'
|
||||
|
||||
export const createStarterPackList = async ({
|
||||
name,
|
||||
@@ -46,14 +46,7 @@ export const createStarterPackList = async ({
|
||||
if (!list) throw new Error('List creation failed')
|
||||
await agent.com.atproto.repo.applyWrites({
|
||||
repo: agent.session!.did,
|
||||
writes: [
|
||||
createListItem({did: agent.session!.did, listUri: list.uri}),
|
||||
].concat(
|
||||
profiles
|
||||
// Ensure we don't have ourselves in this list twice
|
||||
.filter(p => p.did !== agent.session!.did)
|
||||
.map(p => createListItem({did: p.did, listUri: list.uri})),
|
||||
),
|
||||
writes: profiles.map(p => createListItem({did: p.did, listUri: list.uri})),
|
||||
})
|
||||
|
||||
return list
|
||||
|
||||
@@ -79,7 +79,7 @@ export type CommonNavigatorParams = {
|
||||
Start: {name: string; rkey: string}
|
||||
StarterPack: {name: string; rkey: string; new?: boolean}
|
||||
StarterPackShort: {code: string}
|
||||
StarterPackWizard: {fromDialog?: boolean}
|
||||
StarterPackWizard: {fromDialog?: boolean; targetDid?: string}
|
||||
StarterPackEdit: {rkey?: string}
|
||||
VideoFeed: VideoFeedSourceContext
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
import {msg, plural} from '@lingui/macro'
|
||||
|
||||
import {STARTER_PACK_MAX_SIZE} from '#/lib/constants'
|
||||
import {useSession} from '#/state/session'
|
||||
import * as Toast from '#/view/com/util/Toast'
|
||||
import * as bsky from '#/types/bsky'
|
||||
|
||||
@@ -37,6 +36,7 @@ interface State {
|
||||
processing: boolean
|
||||
error?: string
|
||||
transitionDirection: 'Backward' | 'Forward'
|
||||
targetDid?: string
|
||||
}
|
||||
|
||||
type TStateContext = [State, (action: Action) => void]
|
||||
@@ -118,15 +118,17 @@ function reducer(state: State, action: Action): State {
|
||||
export function Provider({
|
||||
starterPack,
|
||||
listItems,
|
||||
targetProfile,
|
||||
children,
|
||||
}: {
|
||||
starterPack?: AppBskyGraphDefs.StarterPackView
|
||||
listItems?: AppBskyGraphDefs.ListItemView[]
|
||||
targetProfile: bsky.profile.AnyProfileView
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const {currentAccount} = useSession()
|
||||
|
||||
const createInitialState = (): State => {
|
||||
const targetDid = targetProfile?.did
|
||||
|
||||
if (
|
||||
starterPack &&
|
||||
bsky.validate(starterPack.record, AppBskyGraphStarterpack.validateRecord)
|
||||
@@ -136,23 +138,22 @@ export function Provider({
|
||||
currentStep: 'Details',
|
||||
name: starterPack.record.name,
|
||||
description: starterPack.record.description,
|
||||
profiles:
|
||||
listItems
|
||||
?.map(i => i.subject)
|
||||
.filter(p => p.did !== currentAccount?.did) ?? [],
|
||||
profiles: listItems?.map(i => i.subject) ?? [],
|
||||
feeds: starterPack.feeds ?? [],
|
||||
processing: false,
|
||||
transitionDirection: 'Forward',
|
||||
targetDid,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
canNext: true,
|
||||
currentStep: 'Details',
|
||||
profiles: [],
|
||||
profiles: [targetProfile],
|
||||
feeds: [],
|
||||
processing: false,
|
||||
transitionDirection: 'Forward',
|
||||
targetDid,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -72,11 +72,15 @@ export function Wizard({
|
||||
const params = route.params ?? {}
|
||||
const rkey = 'rkey' in params ? params.rkey : undefined
|
||||
const fromDialog = 'fromDialog' in params ? params.fromDialog : false
|
||||
const targetDid = 'targetDid' in params ? params.targetDid : undefined
|
||||
const {currentAccount} = useSession()
|
||||
const moderationOpts = useModerationOpts()
|
||||
|
||||
const {_} = useLingui()
|
||||
|
||||
// Use targetDid if provided (from dialog), otherwise use current account
|
||||
const profileDid = targetDid || currentAccount!.did
|
||||
|
||||
const {
|
||||
data: starterPack,
|
||||
isLoading: isLoadingStarterPack,
|
||||
@@ -94,7 +98,7 @@ export function Wizard({
|
||||
data: profile,
|
||||
isLoading: isLoadingProfile,
|
||||
isError: isErrorProfile,
|
||||
} = useProfileQuery({did: currentAccount?.did})
|
||||
} = useProfileQuery({did: profileDid})
|
||||
|
||||
const isEdit = Boolean(rkey)
|
||||
const isReady =
|
||||
@@ -130,7 +134,10 @@ export function Wizard({
|
||||
<Layout.Screen
|
||||
testID="starterPackWizardScreen"
|
||||
style={web([{minHeight: 0}, a.flex_1])}>
|
||||
<Provider starterPack={starterPack} listItems={listItems}>
|
||||
<Provider
|
||||
starterPack={starterPack}
|
||||
listItems={listItems}
|
||||
targetProfile={profile}>
|
||||
<WizardInner
|
||||
currentStarterPack={starterPack}
|
||||
currentListItems={listItems}
|
||||
@@ -228,7 +235,7 @@ function WizardInner({
|
||||
} else {
|
||||
// Original behavior for other entry points
|
||||
navigation.replace('StarterPack', {
|
||||
name: currentAccount!.handle,
|
||||
name: profile!.handle,
|
||||
rkey,
|
||||
new: true,
|
||||
})
|
||||
@@ -245,7 +252,7 @@ function WizardInner({
|
||||
navigation.goBack()
|
||||
} else {
|
||||
navigation.replace('StarterPack', {
|
||||
name: currentAccount!.handle,
|
||||
name: profile!.handle,
|
||||
rkey: parsed!.rkey,
|
||||
})
|
||||
}
|
||||
@@ -281,6 +288,7 @@ function WizardInner({
|
||||
currentListItems: currentListItems,
|
||||
})
|
||||
} else {
|
||||
console.log('Creating new starter pack: ', state.profiles)
|
||||
createStarterPack({
|
||||
name: state.name?.trim() || getDefaultName(),
|
||||
description: state.description?.trim(),
|
||||
@@ -306,10 +314,7 @@ function WizardInner({
|
||||
)
|
||||
}
|
||||
|
||||
const items =
|
||||
state.currentStep === 'Profiles'
|
||||
? [profile, ...state.profiles]
|
||||
: state.feeds
|
||||
const items = state.currentStep === 'Profiles' ? state.profiles : state.feeds
|
||||
|
||||
const isEditEnabled =
|
||||
(state.currentStep === 'Profiles' && items.length > 1) ||
|
||||
@@ -413,20 +418,15 @@ function Container({children}: {children: React.ReactNode}) {
|
||||
function Footer({
|
||||
onNext,
|
||||
nextBtnText,
|
||||
profile,
|
||||
}: {
|
||||
onNext: () => void
|
||||
nextBtnText: string
|
||||
profile: AppBskyActorDefs.ProfileViewDetailed
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const [state] = useWizardState()
|
||||
const {bottom: bottomInset} = useSafeAreaInsets()
|
||||
|
||||
const items =
|
||||
state.currentStep === 'Profiles'
|
||||
? [profile, ...state.profiles]
|
||||
: state.feeds
|
||||
const {currentAccount} = useSession()
|
||||
const items = state.currentStep === 'Profiles' ? state.profiles : state.feeds
|
||||
|
||||
const minimumItems = state.currentStep === 'Profiles' ? 8 : 0
|
||||
|
||||
@@ -493,12 +493,23 @@ function Footer({
|
||||
{
|
||||
items.length < 2 ? (
|
||||
<Trans>
|
||||
It's just you right now! Add more people to your starter pack
|
||||
by searching above.
|
||||
It's just{' '}
|
||||
<Text style={[a.font_bold, textStyles]} emoji>
|
||||
{currentAccount?.did === items[0].did
|
||||
? 'you'
|
||||
: getName(items[0])}{' '}
|
||||
</Text>
|
||||
right now! Add more people to your starter pack by searching
|
||||
above.
|
||||
</Trans>
|
||||
) : items.length === 2 ? (
|
||||
<Trans>
|
||||
<Text style={[a.font_bold, textStyles]}>You</Text> and
|
||||
<Text style={[a.font_bold, textStyles]}>
|
||||
{currentAccount?.did === items[0].did
|
||||
? 'you'
|
||||
: getName(items[0])}
|
||||
</Text>{' '}
|
||||
and
|
||||
<Text> </Text>
|
||||
<Text style={[a.font_bold, textStyles]} emoji>
|
||||
{getName(items[1] /* [0] is self, skip it */)}{' '}
|
||||
|
||||
@@ -90,3 +90,13 @@ export async function invalidateActorStarterPacksQuery({
|
||||
}) {
|
||||
await queryClient.invalidateQueries({queryKey: RQKEY(did)})
|
||||
}
|
||||
|
||||
export async function invalidateActorStarterPacksWithMembershipQuery({
|
||||
queryClient,
|
||||
did,
|
||||
}: {
|
||||
queryClient: QueryClient
|
||||
did: string
|
||||
}) {
|
||||
await queryClient.invalidateQueries({queryKey: RQKEY_WITH_MEMBERSHIP(did)})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user