about summary refs log tree commit diff
diff options
context:
space:
mode:
authorChenyu Huang <itschenyu@gmail.com>2025-08-08 16:10:35 -0700
committerChenyu Huang <itschenyu@gmail.com>2025-08-19 15:28:37 -0700
commitf42b5831bb831e3b9f925730477a27e01d2b33f4 (patch)
treef0df61f8c22f91f7474646bea84023cef92be01f
parent7182cd3d5e157d7ad80f2e5c4a458730c46939a0 (diff)
downloadvoidsky-f42b5831bb831e3b9f925730477a27e01d2b33f4.tar.zst
parameterize the initial profile for starter pack profile select wizard screen
-rw-r--r--src/components/StarterPack/Wizard/WizardEditListDialog.tsx6
-rw-r--r--src/components/StarterPack/Wizard/WizardListCard.tsx11
-rw-r--r--src/components/dialogs/StarterPackDialog.tsx112
-rw-r--r--src/lib/generate-starterpack.ts23
-rw-r--r--src/lib/routes/types.ts2
-rw-r--r--src/screens/StarterPack/Wizard/State.tsx17
-rw-r--r--src/screens/StarterPack/Wizard/index.tsx47
-rw-r--r--src/state/queries/actor-starter-packs.ts10
8 files changed, 116 insertions, 112 deletions
diff --git a/src/components/StarterPack/Wizard/WizardEditListDialog.tsx b/src/components/StarterPack/Wizard/WizardEditListDialog.tsx
index 731323f7f..7c3d1a40a 100644
--- a/src/components/StarterPack/Wizard/WizardEditListDialog.tsx
+++ b/src/components/StarterPack/Wizard/WizardEditListDialog.tsx
@@ -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>) =>
diff --git a/src/components/StarterPack/Wizard/WizardListCard.tsx b/src/components/StarterPack/Wizard/WizardListCard.tsx
index fbaa185a9..09c265d78 100644
--- a/src/components/StarterPack/Wizard/WizardListCard.tsx
+++ b/src/components/StarterPack/Wizard/WizardListCard.tsx
@@ -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})
diff --git a/src/components/dialogs/StarterPackDialog.tsx b/src/components/dialogs/StarterPackDialog.tsx
index efd157723..0570859c4 100644
--- a/src/components/dialogs/StarterPackDialog.tsx
+++ b/src/components/dialogs/StarterPackDialog.tsx
@@ -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 {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: addMembership} = useListMembershipAddMutation({
-    onSuccess: () => {
-      Toast.show(_(msg`Added to starter pack`))
-    },
-    onError: () => {
-      Toast.show(_(msg`Failed to add to starter pack`), 'xmark')
-    },
-  })
+  const isMutating = isAddingPending || isRemovingPending
 
-  const {mutateAsync: removeMembership} = useListMembershipRemoveMutation({
-    onSuccess: () => {
-      Toast.show(_(msg`Removed from starter pack`))
-    },
-    onError: () => {
-      Toast.show(_(msg`Failed to remove from starter pack`), 'xmark')
-    },
-  })
-
-  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>}
diff --git a/src/lib/generate-starterpack.ts b/src/lib/generate-starterpack.ts
index 11e334329..76bef3fbe 100644
--- a/src/lib/generate-starterpack.ts
+++ b/src/lib/generate-starterpack.ts
@@ -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
diff --git a/src/lib/routes/types.ts b/src/lib/routes/types.ts
index 6eb5cb609..f7e7c7eed 100644
--- a/src/lib/routes/types.ts
+++ b/src/lib/routes/types.ts
@@ -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
 }
diff --git a/src/screens/StarterPack/Wizard/State.tsx b/src/screens/StarterPack/Wizard/State.tsx
index 7fae8ca6d..f34218219 100644
--- a/src/screens/StarterPack/Wizard/State.tsx
+++ b/src/screens/StarterPack/Wizard/State.tsx
@@ -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,
     }
   }
 
diff --git a/src/screens/StarterPack/Wizard/index.tsx b/src/screens/StarterPack/Wizard/index.tsx
index b918e8baf..b2f74257b 100644
--- a/src/screens/StarterPack/Wizard/index.tsx
+++ b/src/screens/StarterPack/Wizard/index.tsx
@@ -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 */)}{' '}
diff --git a/src/state/queries/actor-starter-packs.ts b/src/state/queries/actor-starter-packs.ts
index bde719743..d40e05453 100644
--- a/src/state/queries/actor-starter-packs.ts
+++ b/src/state/queries/actor-starter-packs.ts
@@ -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)})
+}