diff options
author | Chenyu Huang <itschenyu@gmail.com> | 2025-08-08 16:10:35 -0700 |
---|---|---|
committer | Chenyu Huang <itschenyu@gmail.com> | 2025-08-19 15:28:37 -0700 |
commit | f42b5831bb831e3b9f925730477a27e01d2b33f4 (patch) | |
tree | f0df61f8c22f91f7474646bea84023cef92be01f | |
parent | 7182cd3d5e157d7ad80f2e5c4a458730c46939a0 (diff) | |
download | voidsky-f42b5831bb831e3b9f925730477a27e01d2b33f4.tar.zst |
parameterize the initial profile for starter pack profile select wizard screen
-rw-r--r-- | src/components/StarterPack/Wizard/WizardEditListDialog.tsx | 6 | ||||
-rw-r--r-- | src/components/StarterPack/Wizard/WizardListCard.tsx | 11 | ||||
-rw-r--r-- | src/components/dialogs/StarterPackDialog.tsx | 112 | ||||
-rw-r--r-- | src/lib/generate-starterpack.ts | 23 | ||||
-rw-r--r-- | src/lib/routes/types.ts | 2 | ||||
-rw-r--r-- | src/screens/StarterPack/Wizard/State.tsx | 17 | ||||
-rw-r--r-- | src/screens/StarterPack/Wizard/index.tsx | 47 | ||||
-rw-r--r-- | src/state/queries/actor-starter-packs.ts | 10 |
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)}) +} |