import {useCallback} from 'react' import {View} from 'react-native' import { type AppBskyGraphGetStarterPacksWithMembership, AppBskyGraphStarterpack, } from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Plural, Trans} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification' import {type NavigationProp} from '#/lib/routes/types' import {isNetworkError} from '#/lib/strings/errors' import {logger} from '#/logger' import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' import {useActorStarterPacksWithMembershipsQuery} from '#/state/queries/actor-starter-packs' import { useListMembershipAddMutation, useListMembershipRemoveMutation, } from '#/state/queries/list-memberships' import {useProfileQuery} from '#/state/queries/profile' import {atoms as a, native, platform, useTheme} from '#/alf' import {AvatarStack} from '#/components/AvatarStack' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' import {Divider} from '#/components/Divider' import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus' import {StarterPack} from '#/components/icons/StarterPack' import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times' import {Loader} from '#/components/Loader' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {IS_WEB} from '#/env' import * as bsky from '#/types/bsky' type StarterPackWithMembership = AppBskyGraphGetStarterPacksWithMembership.StarterPackWithMembership export type StarterPackDialogProps = { control: Dialog.DialogControlProps targetDid: string enabled?: boolean } export function StarterPackDialog({ control, targetDid, enabled, }: StarterPackDialogProps) { const navigation = useNavigation() const requireEmailVerification = useRequireEmailVerification() const navToWizard = 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() return ( You have no starter packs. ) } function StarterPackList({ onStartWizard, targetDid, enabled, }: { onStartWizard: () => void targetDid: string enabled?: boolean }) { const control = Dialog.useDialogContext() const {_} = useLingui() const {data: subject} = useProfileQuery({did: targetDid}) const enableSquareButtons = useEnableSquareButtons() const { data, isError, isLoading, hasNextPage, isFetchingNextPage, fetchNextPage, } = useActorStarterPacksWithMembershipsQuery({did: targetDid, enabled}) const membershipItems = data?.pages.flatMap(page => page.starterPacksWithMembership) || [] const onEndReached = 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 = useCallback( ({item}: {item: StarterPackWithMembership}) => ( ), [targetDid, subject], ) const onClose = useCallback(() => { control.close() }, [control]) 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 } onEndReached={onEndReached} onEndReachedThreshold={0.1} ListHeaderComponent={listHeader} ListEmptyComponent={} style={platform({ web: [a.px_2xl, {minHeight: 500}], native: [a.px_2xl, a.pt_lg], })} /> ) } function StarterPackItem({ starterPackWithMembership, targetDid, subject, }: { starterPackWithMembership: StarterPackWithMembership targetDid: string subject?: bsky.profile.AnyProfileView }) { const t = useTheme() const ax = useAnalytics() const {_} = useLingui() const starterPack = starterPackWithMembership.starterPack const isInPack = !!starterPackWithMembership.listItem const {mutate: addMembership, isPending: isPendingAdd} = useListMembershipAddMutation({ subject, onSuccess: () => { Toast.show(_(msg`Added to starter pack`)) }, onError: err => { if (!isNetworkError(err)) { logger.error('Failed to add to starter pack', {safeMessage: err}) } Toast.show(_(msg`Failed to add to starter pack`), {type: 'error'}) }, }) const {mutate: removeMembership, isPending: isPendingRemove} = useListMembershipRemoveMutation({ onSuccess: () => { Toast.show(_(msg`Removed from starter pack`)) }, onError: err => { if (!isNetworkError(err)) { logger.error('Failed to remove from starter pack', {safeMessage: err}) } Toast.show(_(msg`Failed to remove from starter pack`), {type: 'error'}) }, }) const isPending = isPendingAdd || isPendingRemove const handleToggleMembership = () => { if (!starterPack.list?.uri || isPending) return const listUri = starterPack.list.uri const starterPackUri = starterPack.uri if (!isInPack) { addMembership({ listUri: listUri, actorDid: targetDid, }) ax.metric('starterPack:addUser', {starterPack: starterPackUri}) } else { if (!starterPackWithMembership.listItem?.uri) { console.error('Cannot remove: missing membership URI') return } removeMembership({ listUri: listUri, actorDid: targetDid, membershipUri: starterPackWithMembership.listItem.uri, }) ax.metric('starterPack:removeUser', {starterPack: starterPackUri}) } } 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 && ( )} )} ) }