diff --git a/src/components/StarterPack/ProfileStarterPacks.tsx b/src/components/StarterPack/ProfileStarterPacks.tsx
index de19b0bce8..73aee28f4e 100644
--- a/src/components/StarterPack/ProfileStarterPacks.tsx
+++ b/src/components/StarterPack/ProfileStarterPacks.tsx
@@ -180,7 +180,7 @@ function CreateAnother() {
color="secondary"
size="small"
style={[a.self_center]}
- onPress={() => navigation.navigate('StarterPackWizard')}>
+ onPress={() => navigation.navigate('StarterPackWizard', {})}>
Create another
@@ -238,7 +238,7 @@ function Empty() {
],
})
const navToWizard = useCallback(() => {
- navigation.navigate('StarterPackWizard')
+ navigation.navigate('StarterPackWizard', {})
}, [navigation])
const wrappedNavToWizard = requireEmailVerification(navToWizard, {
instructions: [
@@ -322,7 +322,7 @@ function Empty() {
color="secondary"
cta={_(msg`Let me choose`)}
onPress={() => {
- navigation.navigate('StarterPackWizard')
+ navigation.navigate('StarterPackWizard', {})
}}
/>
diff --git a/src/components/StarterPack/Wizard/WizardEditListDialog.tsx b/src/components/StarterPack/Wizard/WizardEditListDialog.tsx
index 731323f7ff..7dfde900f8 100644
--- a/src/components/StarterPack/Wizard/WizardEditListDialog.tsx
+++ b/src/components/StarterPack/Wizard/WizardEditListDialog.tsx
@@ -11,7 +11,6 @@ import {useLingui} from '@lingui/react'
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
import {isWeb} from '#/platform/detection'
-import {useSession} from '#/state/session'
import {type ListMethods} from '#/view/com/util/List'
import {
type WizardAction,
@@ -48,7 +47,6 @@ export function WizardEditListDialog({
}) {
const {_} = useLingui()
const t = useTheme()
- const {currentAccount} = useSession()
const initialNumToRender = useInitialNumToRender()
const listRef = useRef(null)
@@ -56,10 +54,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) =>
diff --git a/src/components/StarterPack/Wizard/WizardListCard.tsx b/src/components/StarterPack/Wizard/WizardListCard.tsx
index fbaa185a90..09c265d780 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
new file mode 100644
index 0000000000..ec041d401f
--- /dev/null
+++ b/src/components/dialogs/StarterPackDialog.tsx
@@ -0,0 +1,399 @@
+import React from 'react'
+import {View} from 'react-native'
+import {
+ type AppBskyGraphGetStarterPacksWithMembership,
+ AppBskyGraphStarterpack,
+} from '@atproto/api'
+import {msg, Plural, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+import {useNavigation} from '@react-navigation/native'
+import {useQueryClient} from '@tanstack/react-query'
+
+import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification'
+import {type NavigationProp} from '#/lib/routes/types'
+import {isWeb} from '#/platform/detection'
+import {
+ invalidateActorStarterPacksWithMembershipQuery,
+ useActorStarterPacksWithMembershipsQuery,
+} from '#/state/queries/actor-starter-packs'
+import {
+ useListMembershipAddMutation,
+ useListMembershipRemoveMutation,
+} from '#/state/queries/list-memberships'
+import * as Toast from '#/view/com/util/Toast'
+import {atoms as a, useTheme} from '#/alf'
+import {Button, ButtonIcon, ButtonText} from '#/components/Button'
+import * as Dialog from '#/components/Dialog'
+import {Divider} from '#/components/Divider'
+import {Loader} from '#/components/Loader'
+import {Text} from '#/components/Typography'
+import * as bsky from '#/types/bsky'
+import {AvatarStack} from '../AvatarStack'
+import {PlusLarge_Stroke2_Corner0_Rounded} from '../icons/Plus'
+import {StarterPack} from '../icons/StarterPack'
+import {TimesLarge_Stroke2_Corner0_Rounded} from '../icons/Times'
+
+type StarterPackWithMembership =
+ AppBskyGraphGetStarterPacksWithMembership.StarterPackWithMembership
+
+export type StarterPackDialogProps = {
+ control: Dialog.DialogControlProps
+ targetDid: string
+ enabled?: boolean
+}
+
+export function StarterPackDialog({
+ control,
+ targetDid,
+ enabled,
+}: StarterPackDialogProps) {
+ const {_} = useLingui()
+ const navigation = useNavigation()
+ const requireEmailVerification = useRequireEmailVerification()
+
+ const navToWizard = React.useCallback(() => {
+ control.close()
+ navigation.navigate('StarterPackWizard', {
+ fromDialog: true,
+ targetDid: targetDid,
+ onSuccess: () => {
+ setTimeout(() => {
+ if (!control.isOpen) {
+ control.open()
+ }
+ }, 0)
+ },
+ })
+ }, [navigation, control, targetDid])
+
+ const wrappedNavToWizard = requireEmailVerification(navToWizard, {
+ instructions: [
+
+ Before creating a starter pack, you must first verify your email.
+ ,
+ ],
+ })
+
+ return (
+
+
+
+
+ )
+}
+
+function Empty({onStartWizard}: {onStartWizard: () => void}) {
+ const {_} = useLingui()
+ const t = useTheme()
+
+ isWeb
+ return (
+
+
+
+
+ You have no starter packs.
+
+
+
+
+
+
+
+ )
+}
+
+function StarterPackList({
+ control,
+ onStartWizard,
+ targetDid,
+ enabled,
+}: {
+ control: Dialog.DialogControlProps
+ onStartWizard: () => void
+ targetDid: string
+ enabled?: boolean
+}) {
+ const {_} = useLingui()
+ const t = useTheme()
+
+ const {
+ data,
+ refetch,
+ isError,
+ isLoading,
+ hasNextPage,
+ isFetchingNextPage,
+ fetchNextPage,
+ } = useActorStarterPacksWithMembershipsQuery({did: targetDid, enabled})
+
+ const membershipItems =
+ data?.pages.flatMap(page => page.starterPacksWithMembership) || []
+
+ const _onRefresh = React.useCallback(async () => {
+ try {
+ await refetch()
+ } catch (err) {
+ // Error handling is optional since this is just a refresh
+ }
+ }, [refetch])
+
+ const _onEndReached = React.useCallback(async () => {
+ if (isFetchingNextPage || !hasNextPage || isError) return
+ try {
+ await fetchNextPage()
+ } catch (err) {
+ // Error handling is optional since this is just pagination
+ }
+ }, [isFetchingNextPage, hasNextPage, isError, fetchNextPage])
+
+ const renderItem = React.useCallback(
+ ({item}: {item: StarterPackWithMembership}) => (
+
+ ),
+ [targetDid],
+ )
+
+ const onClose = React.useCallback(() => {
+ control.close()
+ }, [control])
+
+ const XIcon = React.useMemo(() => {
+ return (
+
+ )
+ }, [t])
+
+ const listHeader = (
+ <>
+
+
+ Add to starter packs
+
+
+
+ {membershipItems.length > 0 && (
+ <>
+
+
+ New starter pack
+
+
+
+
+ >
+ )}
+ >
+ )
+
+ return (
+ (
+
+
+
+ )
+ : renderItem
+ }
+ keyExtractor={
+ isLoading
+ ? () => 'starter_pack_dialog_loader'
+ : (item: StarterPackWithMembership) => item.starterPack.uri
+ }
+ refreshing={false}
+ onRefresh={_onRefresh}
+ onEndReached={_onEndReached}
+ onEndReachedThreshold={0.1}
+ ListHeaderComponent={listHeader}
+ ListEmptyComponent={}
+ style={isWeb ? [a.px_md, {minHeight: 500}] : [a.px_2xl, a.pt_lg]}
+ />
+ )
+}
+
+function StarterPackItem({
+ starterPackWithMembership,
+ targetDid,
+}: {
+ starterPackWithMembership: StarterPackWithMembership
+ targetDid: string
+}) {
+ const {_} = useLingui()
+ const t = useTheme()
+ const queryClient = useQueryClient()
+
+ const starterPack = starterPackWithMembership.starterPack
+ const isInPack = !!starterPackWithMembership.listItem
+
+ const [isPendingRefresh, setIsPendingRefresh] = React.useState(false)
+
+ const {mutate: addMembership} = useListMembershipAddMutation({
+ onSuccess: () => {
+ Toast.show(_(msg`Added to starter pack`))
+ // Use a timeout to wait for the appview to update, matching the pattern
+ // in list-memberships.ts
+ setTimeout(() => {
+ invalidateActorStarterPacksWithMembershipQuery({
+ queryClient,
+ did: targetDid,
+ })
+ setIsPendingRefresh(false)
+ }, 1e3)
+ },
+ onError: () => {
+ Toast.show(_(msg`Failed to add to starter pack`), 'xmark')
+ setIsPendingRefresh(false)
+ },
+ })
+
+ const {mutate: removeMembership} = useListMembershipRemoveMutation({
+ onSuccess: () => {
+ Toast.show(_(msg`Removed from starter pack`))
+ // Use a timeout to wait for the appview to update, matching the pattern
+ // in list-memberships.ts
+ setTimeout(() => {
+ invalidateActorStarterPacksWithMembershipQuery({
+ queryClient,
+ did: targetDid,
+ })
+ setIsPendingRefresh(false)
+ }, 1e3)
+ },
+ onError: () => {
+ Toast.show(_(msg`Failed to remove from starter pack`), 'xmark')
+ setIsPendingRefresh(false)
+ },
+ })
+
+ const handleToggleMembership = () => {
+ if (!starterPack.list?.uri || isPendingRefresh) return
+
+ const listUri = starterPack.list.uri
+
+ setIsPendingRefresh(true)
+
+ if (!isInPack) {
+ addMembership({
+ listUri: listUri,
+ actorDid: targetDid,
+ })
+ } else {
+ if (!starterPackWithMembership.listItem?.uri) {
+ console.error('Cannot remove: missing membership URI')
+ setIsPendingRefresh(false)
+ return
+ }
+ removeMembership({
+ listUri: listUri,
+ actorDid: targetDid,
+ membershipUri: starterPackWithMembership.listItem.uri,
+ })
+ }
+ }
+
+ const {record} = starterPack
+
+ if (
+ !bsky.dangerousIsType(
+ record,
+ AppBskyGraphStarterpack.isRecord,
+ )
+ ) {
+ return null
+ }
+
+ return (
+
+
+
+ {record.name}
+
+
+
+ {starterPack.listItemsSample &&
+ starterPack.listItemsSample.length > 0 && (
+ <>
+ p.subject)}
+ />
+
+ {starterPack.list?.listItemCount &&
+ starterPack.list.listItemCount > 4 && (
+
+
+
+
+
+ )}
+ >
+ )}
+
+
+
+
+
+ )
+}
diff --git a/src/lib/generate-starterpack.ts b/src/lib/generate-starterpack.ts
index 11e3343293..76bef3fbe9 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 b1db5caa64..1725fdfb4c 100644
--- a/src/lib/routes/types.ts
+++ b/src/lib/routes/types.ts
@@ -79,7 +79,11 @@ export type CommonNavigatorParams = {
Start: {name: string; rkey: string}
StarterPack: {name: string; rkey: string; new?: boolean}
StarterPackShort: {code: string}
- StarterPackWizard: undefined
+ StarterPackWizard: {
+ fromDialog?: boolean
+ targetDid?: string
+ onSuccess?: () => void
+ }
StarterPackEdit: {rkey?: string}
VideoFeed: VideoFeedSourceContext
}
diff --git a/src/screens/StarterPack/Wizard/State.tsx b/src/screens/StarterPack/Wizard/State.tsx
index 7fae8ca6df..f34218219b 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 8256349dfa..839faf9aa4 100644
--- a/src/screens/StarterPack/Wizard/index.tsx
+++ b/src/screens/StarterPack/Wizard/index.tsx
@@ -68,12 +68,19 @@ export function Wizard({
CommonNavigatorParams,
'StarterPackEdit' | 'StarterPackWizard'
>) {
- const {rkey} = route.params ?? {}
+ 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 onSuccess = 'onSuccess' in params ? params.onSuccess : 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,
@@ -91,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 =
@@ -127,12 +134,17 @@ export function Wizard({
-
+
@@ -144,17 +156,22 @@ function WizardInner({
currentListItems,
profile,
moderationOpts,
+ fromDialog,
+ onSuccess,
}: {
currentStarterPack?: AppBskyGraphDefs.StarterPackView
currentListItems?: AppBskyGraphDefs.ListItemView[]
profile: AppBskyActorDefs.ProfileViewDetailed
moderationOpts: ModerationOpts
+ fromDialog?: boolean
+ onSuccess?: () => void
}) {
const navigation = useNavigation()
const {_} = useLingui()
const setMinimalShellMode = useSetMinimalShellMode()
const [state, dispatch] = useWizardState()
const {currentAccount} = useSession()
+
const {data: currentProfile} = useProfileQuery({
did: currentAccount?.did,
staleTime: 0,
@@ -213,11 +230,17 @@ function WizardInner({
})
Image.prefetch([getStarterPackOgCard(currentProfile!.did, rkey)])
dispatch({type: 'SetProcessing', processing: false})
- navigation.replace('StarterPack', {
- name: currentAccount!.handle,
- rkey,
- new: true,
- })
+
+ if (fromDialog) {
+ navigation.goBack()
+ onSuccess?.()
+ } else {
+ navigation.replace('StarterPack', {
+ name: profile!.handle,
+ rkey,
+ new: true,
+ })
+ }
}
const onSuccessEdit = () => {
@@ -285,10 +308,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) ||
@@ -340,11 +360,7 @@ function WizardInner({
{state.currentStep !== 'Details' && (
-
+
)}
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
@@ -471,19 +482,44 @@ function Footer({
{
items.length < 2 ? (
-
- It's just you right now! Add more people to your starter pack
- by searching above.
-
+ currentAccount?.did === items[0].did ? (
+
+ It's just you right now! Add more people to your starter
+ pack by searching above.
+
+ ) : (
+
+ It's just{' '}
+
+ {getName(items[0])}{' '}
+
+ right now! Add more people to your starter pack by searching
+ above.
+
+ )
) : items.length === 2 ? (
-
- You and
-
-
- {getName(items[1] /* [0] is self, skip it */)}{' '}
-
- are included in your starter pack
-
+ currentAccount?.did === items[0].did ? (
+
+ You and
+
+
+ {getName(items[1] /* [0] is self, skip it */)}{' '}
+
+ are included in your starter pack
+
+ ) : (
+
+
+ {getName(items[0])}
+ {' '}
+ and
+
+
+ {getName(items[1] /* [0] is self, skip it */)}{' '}
+
+ are included in your starter pack
+
+ )
) : items.length > 2 ? (
diff --git a/src/state/queries/actor-starter-packs.ts b/src/state/queries/actor-starter-packs.ts
index 670544dfea..d40e054535 100644
--- a/src/state/queries/actor-starter-packs.ts
+++ b/src/state/queries/actor-starter-packs.ts
@@ -1,15 +1,23 @@
-import {AppBskyGraphGetActorStarterPacks} from '@atproto/api'
import {
- InfiniteData,
- QueryClient,
- QueryKey,
+ type AppBskyGraphGetActorStarterPacks,
+ type AppBskyGraphGetStarterPacksWithMembership,
+} from '@atproto/api'
+import {
+ type InfiniteData,
+ type QueryClient,
+ type QueryKey,
useInfiniteQuery,
} from '@tanstack/react-query'
import {useAgent} from '#/state/session'
export const RQKEY_ROOT = 'actor-starter-packs'
+export const RQKEY_WITH_MEMBERSHIP_ROOT = 'actor-starter-packs-with-membership'
export const RQKEY = (did?: string) => [RQKEY_ROOT, did]
+export const RQKEY_WITH_MEMBERSHIP = (did?: string) => [
+ RQKEY_WITH_MEMBERSHIP_ROOT,
+ did,
+]
export function useActorStarterPacksQuery({
did,
@@ -42,6 +50,37 @@ export function useActorStarterPacksQuery({
})
}
+export function useActorStarterPacksWithMembershipsQuery({
+ did,
+ enabled = true,
+}: {
+ did?: string
+ enabled?: boolean
+}) {
+ const agent = useAgent()
+
+ return useInfiniteQuery<
+ AppBskyGraphGetStarterPacksWithMembership.OutputSchema,
+ Error,
+ InfiniteData,
+ QueryKey,
+ string | undefined
+ >({
+ queryKey: RQKEY_WITH_MEMBERSHIP(did),
+ queryFn: async ({pageParam}: {pageParam?: string}) => {
+ const res = await agent.app.bsky.graph.getStarterPacksWithMembership({
+ actor: did!,
+ limit: 10,
+ cursor: pageParam,
+ })
+ return res.data
+ },
+ enabled: Boolean(did) && enabled,
+ initialPageParam: undefined,
+ getNextPageParam: lastPage => lastPage.cursor,
+ })
+}
+
export async function invalidateActorStarterPacksQuery({
queryClient,
did,
@@ -51,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)})
+}
diff --git a/src/view/com/profile/ProfileMenu.tsx b/src/view/com/profile/ProfileMenu.tsx
index 879bf22f98..df8b2e4817 100644
--- a/src/view/com/profile/ProfileMenu.tsx
+++ b/src/view/com/profile/ProfileMenu.tsx
@@ -27,6 +27,7 @@ import {EventStopper} from '#/view/com/util/EventStopper'
import * as Toast from '#/view/com/util/Toast'
import {Button, ButtonIcon} from '#/components/Button'
import {useDialogControl} from '#/components/Dialog'
+import {StarterPackDialog} from '#/components/dialogs/StarterPackDialog'
import {ArrowOutOfBoxModified_Stroke2_Corner2_Rounded as ArrowOutOfBoxIcon} from '#/components/icons/ArrowOutOfBox'
import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink'
import {CircleCheck_Stroke2_Corner0_Rounded as CircleCheckIcon} from '#/components/icons/CircleCheck'
@@ -45,6 +46,7 @@ import {
} from '#/components/icons/Person'
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute} from '#/components/icons/Speaker'
+import {StarterPack} from '#/components/icons/StarterPack'
import {EditLiveDialog} from '#/components/live/EditLiveDialog'
import {GoLiveDialog} from '#/components/live/GoLiveDialog'
import * as Menu from '#/components/Menu'
@@ -88,6 +90,7 @@ let ProfileMenu = ({
const blockPromptControl = Prompt.usePromptControl()
const loggedOutWarningPromptControl = Prompt.usePromptControl()
const goLiveDialogControl = useDialogControl()
+ const addToStarterPacksDialogControl = useDialogControl()
const showLoggedOutWarning = React.useMemo(() => {
return (
@@ -300,6 +303,15 @@ let ProfileMenu = ({
)}
>
)}
+
+
+ Add to starter packs
+
+
+
+
+