[Onboarding] Add starterpacks step (#9007)
* add onboarding starterpacks step * tweak starter pack cards * fix web styling * use new API * add screen transitions to onboarding * skip host account * bump limit to 6 * tweak font weight * rm commented out code * fix yarn.lock * limit starter packs experiment to english speakers
This commit is contained in:
+2
-2
@@ -71,7 +71,7 @@
|
|||||||
"icons:optimize": "svgo -f ./assets/icons"
|
"icons:optimize": "svgo -f ./assets/icons"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@atproto/api": "^0.17.0",
|
"@atproto/api": "^0.17.1",
|
||||||
"@bitdrift/react-native": "^0.6.8",
|
"@bitdrift/react-native": "^0.6.8",
|
||||||
"@braintree/sanitize-url": "^6.0.2",
|
"@braintree/sanitize-url": "^6.0.2",
|
||||||
"@bsky.app/alf": "^0.1.5",
|
"@bsky.app/alf": "^0.1.5",
|
||||||
@@ -220,7 +220,7 @@
|
|||||||
"zod": "^3.20.2"
|
"zod": "^3.20.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@atproto/dev-env": "^0.3.172",
|
"@atproto/dev-env": "^0.3.181",
|
||||||
"@babel/core": "^7.26.0",
|
"@babel/core": "^7.26.0",
|
||||||
"@babel/preset-env": "^7.26.0",
|
"@babel/preset-env": "^7.26.0",
|
||||||
"@babel/runtime": "^7.26.0",
|
"@babel/runtime": "^7.26.0",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export type Gate =
|
|||||||
| 'feed_reply_button_open_thread'
|
| 'feed_reply_button_open_thread'
|
||||||
| 'old_postonboarding'
|
| 'old_postonboarding'
|
||||||
| 'onboarding_add_video_feed'
|
| 'onboarding_add_video_feed'
|
||||||
|
| 'onboarding_suggested_starterpacks'
|
||||||
| 'post_follow_profile_suggested_accounts'
|
| 'post_follow_profile_suggested_accounts'
|
||||||
| 'remove_show_latest_button'
|
| 'remove_show_latest_button'
|
||||||
| 'test_gate_1'
|
| 'test_gate_1'
|
||||||
|
|||||||
@@ -0,0 +1,174 @@
|
|||||||
|
import {useState} from 'react'
|
||||||
|
import {View} from 'react-native'
|
||||||
|
import {type AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api'
|
||||||
|
import {msg, Trans} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
|
import {batchedUpdates} from '#/lib/batchedUpdates'
|
||||||
|
import {isBlockedOrBlocking, isMuted} from '#/lib/moderation/blocked-and-muted'
|
||||||
|
import {logger} from '#/logger'
|
||||||
|
import {updateProfileShadow} from '#/state/cache/profile-shadow'
|
||||||
|
import {getAllListMembers} from '#/state/queries/list-members'
|
||||||
|
import {useAgent, useSession} from '#/state/session'
|
||||||
|
import {bulkWriteFollows} from '#/screens/Onboarding/util'
|
||||||
|
import {AvatarStack} from '#/screens/Search/components/StarterPackCard'
|
||||||
|
import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
|
||||||
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
|
import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check'
|
||||||
|
import {Loader} from '#/components/Loader'
|
||||||
|
import * as Toast from '#/components/Toast'
|
||||||
|
import {Text} from '#/components/Typography'
|
||||||
|
import * as bsky from '#/types/bsky'
|
||||||
|
|
||||||
|
const IGNORED_ACCOUNT = 'did:plc:pifkcjimdcfwaxkanzhwxufp'
|
||||||
|
|
||||||
|
export function StarterPackCard({
|
||||||
|
view,
|
||||||
|
}: {
|
||||||
|
view: AppBskyGraphDefs.StarterPackView
|
||||||
|
}) {
|
||||||
|
const t = useTheme()
|
||||||
|
const {_} = useLingui()
|
||||||
|
const {currentAccount} = useSession()
|
||||||
|
const {gtPhone} = useBreakpoints()
|
||||||
|
const agent = useAgent()
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
const record = view.record
|
||||||
|
const [isProcessing, setIsProcessing] = useState(false)
|
||||||
|
const [isFollowingAll, setIsFollowingAll] = useState(false)
|
||||||
|
|
||||||
|
const onFollowAll = async () => {
|
||||||
|
if (!view.list) return
|
||||||
|
|
||||||
|
setIsProcessing(true)
|
||||||
|
|
||||||
|
let listItems: AppBskyGraphDefs.ListItemView[] = []
|
||||||
|
try {
|
||||||
|
listItems = await getAllListMembers(agent, view.list.uri)
|
||||||
|
} catch (e) {
|
||||||
|
setIsProcessing(false)
|
||||||
|
Toast.show(_(msg`An error occurred while trying to follow all`), {
|
||||||
|
type: 'error',
|
||||||
|
})
|
||||||
|
logger.error('Failed to get list members for starter pack', {
|
||||||
|
safeMessage: e,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const dids = listItems
|
||||||
|
.filter(
|
||||||
|
li =>
|
||||||
|
li.subject.did !== IGNORED_ACCOUNT &&
|
||||||
|
li.subject.did !== currentAccount?.did &&
|
||||||
|
!isBlockedOrBlocking(li.subject) &&
|
||||||
|
!isMuted(li.subject) &&
|
||||||
|
!li.subject.viewer?.following,
|
||||||
|
)
|
||||||
|
.map(li => li.subject.did)
|
||||||
|
|
||||||
|
let followUris: Map<string, string>
|
||||||
|
try {
|
||||||
|
followUris = await bulkWriteFollows(agent, dids)
|
||||||
|
} catch (e) {
|
||||||
|
setIsProcessing(false)
|
||||||
|
Toast.show(_(msg`An error occurred while trying to follow all`), {
|
||||||
|
type: 'error',
|
||||||
|
})
|
||||||
|
logger.error('Failed to follow all accounts', {safeMessage: e})
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsFollowingAll(true)
|
||||||
|
setIsProcessing(false)
|
||||||
|
batchedUpdates(() => {
|
||||||
|
for (let did of dids) {
|
||||||
|
updateProfileShadow(queryClient, did, {
|
||||||
|
followingUri: followUris.get(did),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
Toast.show(_(msg`All accounts have been followed!`), {type: 'success'})
|
||||||
|
logger.metric('starterPack:followAll', {
|
||||||
|
logContext: 'Onboarding',
|
||||||
|
starterPack: view.uri,
|
||||||
|
count: dids.length,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!bsky.dangerousIsType<AppBskyGraphStarterpack.Record>(
|
||||||
|
record,
|
||||||
|
AppBskyGraphStarterpack.isRecord,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const profileCount = gtPhone ? 11 : 8
|
||||||
|
const profiles = view.listItemsSample
|
||||||
|
?.slice(0, profileCount)
|
||||||
|
.map(item => item.subject)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.w_full,
|
||||||
|
a.p_lg,
|
||||||
|
a.gap_md,
|
||||||
|
a.border,
|
||||||
|
a.rounded_lg,
|
||||||
|
a.overflow_hidden,
|
||||||
|
t.atoms.border_contrast_medium,
|
||||||
|
]}>
|
||||||
|
<AvatarStack
|
||||||
|
profiles={profiles ?? []}
|
||||||
|
numPending={profileCount}
|
||||||
|
total={view.list?.listItemCount}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.w_full,
|
||||||
|
a.flex_row,
|
||||||
|
a.align_end,
|
||||||
|
a.gap_lg,
|
||||||
|
web({
|
||||||
|
position: 'static',
|
||||||
|
zIndex: 'unset',
|
||||||
|
}),
|
||||||
|
]}>
|
||||||
|
<View style={[a.flex_1, a.gap_2xs]}>
|
||||||
|
<Text
|
||||||
|
emoji
|
||||||
|
style={[a.text_md, a.font_semi_bold, a.leading_snug]}
|
||||||
|
numberOfLines={1}>
|
||||||
|
{record.name}
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
emoji
|
||||||
|
style={[a.text_xs, t.atoms.text_contrast_medium, a.leading_snug]}
|
||||||
|
numberOfLines={2}>
|
||||||
|
{record.description}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<Button
|
||||||
|
label={_(msg`Follow all`)}
|
||||||
|
disabled={isProcessing || isFollowingAll}
|
||||||
|
onPress={onFollowAll}
|
||||||
|
color="secondary"
|
||||||
|
size="small"
|
||||||
|
style={[a.z_50]}>
|
||||||
|
<ButtonText>
|
||||||
|
<Trans>Follow all</Trans>
|
||||||
|
</ButtonText>
|
||||||
|
{isFollowingAll ? (
|
||||||
|
<ButtonIcon icon={CheckIcon} />
|
||||||
|
) : (
|
||||||
|
isProcessing && <ButtonIcon icon={Loader} />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
import {useCallback, useContext} from 'react'
|
||||||
|
import {View} from 'react-native'
|
||||||
|
import {msg, Trans} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||||
|
import {useOnboardingSuggestedStarterPacksQuery} from '#/state/queries/useOnboardingSuggestedStarterPacksQuery'
|
||||||
|
import {useOnboardingDispatch} from '#/state/shell'
|
||||||
|
import {OnboardingControls} from '#/screens/Onboarding/Layout'
|
||||||
|
import {Context} from '#/screens/Onboarding/state'
|
||||||
|
import {atoms as a, useBreakpoints} from '#/alf'
|
||||||
|
import {Admonition} from '#/components/Admonition'
|
||||||
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
|
import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as ArrowRotateCounterClockwiseIcon} from '#/components/icons/ArrowRotateCounterClockwise'
|
||||||
|
import {Loader} from '#/components/Loader'
|
||||||
|
import {Text} from '#/components/Typography'
|
||||||
|
import {StarterPackCard} from './StarterPackCard'
|
||||||
|
|
||||||
|
export function StepSuggestedStarterpacks() {
|
||||||
|
const {_} = useLingui()
|
||||||
|
const {gtMobile} = useBreakpoints()
|
||||||
|
const moderationOpts = useModerationOpts()
|
||||||
|
|
||||||
|
const {state, dispatch} = useContext(Context)
|
||||||
|
const onboardDispatch = useOnboardingDispatch()
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: suggestedStarterPacks,
|
||||||
|
isLoading,
|
||||||
|
error,
|
||||||
|
isRefetching,
|
||||||
|
refetch,
|
||||||
|
} = useOnboardingSuggestedStarterPacksQuery({
|
||||||
|
enabled: true,
|
||||||
|
overrideInterests: state.interestsStepResults.selectedInterests,
|
||||||
|
})
|
||||||
|
|
||||||
|
const isError = !!error
|
||||||
|
|
||||||
|
const skipOnboarding = useCallback(() => {
|
||||||
|
onboardDispatch({type: 'finish'})
|
||||||
|
dispatch({type: 'finish'})
|
||||||
|
}, [onboardDispatch, dispatch])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[a.align_start]} testID="onboardingInterests">
|
||||||
|
<Text style={[a.font_bold, a.text_3xl]}>
|
||||||
|
<Trans comment="Accounts suggested to the user for them to follow">
|
||||||
|
Suggested for you
|
||||||
|
</Trans>
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.overflow_hidden,
|
||||||
|
a.mt_lg,
|
||||||
|
a.flex_1,
|
||||||
|
a.justify_start,
|
||||||
|
a.w_full,
|
||||||
|
]}>
|
||||||
|
{isLoading || !moderationOpts ? (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.flex_1,
|
||||||
|
a.mt_md,
|
||||||
|
a.align_center,
|
||||||
|
a.justify_center,
|
||||||
|
{minHeight: 400},
|
||||||
|
]}>
|
||||||
|
<Loader size="xl" />
|
||||||
|
</View>
|
||||||
|
) : isError ? (
|
||||||
|
<View style={[a.flex_1, a.px_xl, a.pt_5xl]}>
|
||||||
|
<Admonition type="error">
|
||||||
|
<Trans>
|
||||||
|
An error occurred while fetching suggested accounts.
|
||||||
|
</Trans>
|
||||||
|
</Admonition>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<View style={[a.flex_1, a.mt_md]}>
|
||||||
|
{suggestedStarterPacks?.starterPacks.map(starterPack => (
|
||||||
|
<View style={[a.pb_lg]} key={starterPack.uri}>
|
||||||
|
<StarterPackCard view={starterPack} />
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<OnboardingControls.Portal>
|
||||||
|
{isError ? (
|
||||||
|
<View style={[a.gap_md, gtMobile ? a.flex_row : a.flex_col]}>
|
||||||
|
<Button
|
||||||
|
disabled={isRefetching}
|
||||||
|
color="secondary"
|
||||||
|
size="large"
|
||||||
|
label={_(msg`Retry`)}
|
||||||
|
onPress={() => refetch()}>
|
||||||
|
<ButtonText>
|
||||||
|
<Trans>Retry</Trans>
|
||||||
|
</ButtonText>
|
||||||
|
<ButtonIcon icon={ArrowRotateCounterClockwiseIcon} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="secondary"
|
||||||
|
size="large"
|
||||||
|
label={_(msg`Skip this flow`)}
|
||||||
|
onPress={skipOnboarding}>
|
||||||
|
<ButtonText>
|
||||||
|
<Trans>Skip</Trans>
|
||||||
|
</ButtonText>
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<View style={[a.gap_md, gtMobile ? a.flex_row : a.flex_col]}>
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
size="large"
|
||||||
|
label={_(msg`Continue to next step`)}
|
||||||
|
onPress={() => dispatch({type: 'next'})}>
|
||||||
|
<ButtonText>
|
||||||
|
<Trans>Continue</Trans>
|
||||||
|
</ButtonText>
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</OnboardingControls.Portal>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
import {useMemo, useReducer} from 'react'
|
import {useMemo, useReducer} from 'react'
|
||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
import * as bcp47Match from 'bcp-47-match'
|
||||||
|
|
||||||
|
import {useGate} from '#/lib/statsig/statsig'
|
||||||
|
import {useLanguagePrefs} from '#/state/preferences'
|
||||||
import {
|
import {
|
||||||
Layout,
|
Layout,
|
||||||
OnboardingControls,
|
OnboardingControls,
|
||||||
@@ -12,19 +15,34 @@ import {StepFinished} from '#/screens/Onboarding/StepFinished'
|
|||||||
import {StepInterests} from '#/screens/Onboarding/StepInterests'
|
import {StepInterests} from '#/screens/Onboarding/StepInterests'
|
||||||
import {StepProfile} from '#/screens/Onboarding/StepProfile'
|
import {StepProfile} from '#/screens/Onboarding/StepProfile'
|
||||||
import {Portal} from '#/components/Portal'
|
import {Portal} from '#/components/Portal'
|
||||||
|
import {ScreenTransition} from '#/components/ScreenTransition'
|
||||||
|
import {ENV} from '#/env'
|
||||||
import {StepSuggestedAccounts} from './StepSuggestedAccounts'
|
import {StepSuggestedAccounts} from './StepSuggestedAccounts'
|
||||||
|
import {StepSuggestedStarterpacks} from './StepSuggestedStarterpacks'
|
||||||
|
|
||||||
export function Onboarding() {
|
export function Onboarding() {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
|
const gate = useGate()
|
||||||
|
|
||||||
|
const {contentLanguages} = useLanguagePrefs()
|
||||||
|
const probablySpeaksEnglish = useMemo(() => {
|
||||||
|
if (contentLanguages.length === 0) return true
|
||||||
|
return bcp47Match.basicFilter('en', contentLanguages).length > 0
|
||||||
|
}, [contentLanguages])
|
||||||
|
|
||||||
|
// starter packs screen is currently geared towards english-speaking accounts
|
||||||
|
const showSuggestedStarterpacks =
|
||||||
|
ENV !== 'e2e' &&
|
||||||
|
probablySpeaksEnglish &&
|
||||||
|
gate('onboarding_suggested_starterpacks')
|
||||||
|
|
||||||
const [state, dispatch] = useReducer(reducer, {
|
const [state, dispatch] = useReducer(reducer, {
|
||||||
...initialState,
|
...initialState,
|
||||||
totalSteps: 4,
|
totalSteps: 4 + (showSuggestedStarterpacks ? 1 : 0),
|
||||||
experiments: {
|
experiments: {
|
||||||
// let's leave this flag logic in for now to avoid rebase churn
|
|
||||||
// TODO: remove this flag logic once we've finished with all experiments -sfn
|
|
||||||
onboarding_suggested_accounts: true,
|
onboarding_suggested_accounts: true,
|
||||||
onboarding_value_prop: true,
|
onboarding_value_prop: true,
|
||||||
|
onboarding_suggested_starterpacks: showSuggestedStarterpacks,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -65,12 +83,19 @@ export function Onboarding() {
|
|||||||
[state, dispatch, interestsDisplayNames],
|
[state, dispatch, interestsDisplayNames],
|
||||||
)}>
|
)}>
|
||||||
<Layout>
|
<Layout>
|
||||||
{state.activeStep === 'profile' && <StepProfile />}
|
<ScreenTransition
|
||||||
{state.activeStep === 'interests' && <StepInterests />}
|
key={state.activeStep}
|
||||||
{state.activeStep === 'suggested-accounts' && (
|
direction={state.stepTransitionDirection}>
|
||||||
<StepSuggestedAccounts />
|
{state.activeStep === 'profile' && <StepProfile />}
|
||||||
)}
|
{state.activeStep === 'interests' && <StepInterests />}
|
||||||
{state.activeStep === 'finished' && <StepFinished />}
|
{state.activeStep === 'suggested-accounts' && (
|
||||||
|
<StepSuggestedAccounts />
|
||||||
|
)}
|
||||||
|
{state.activeStep === 'suggested-starterpacks' && (
|
||||||
|
<StepSuggestedStarterpacks />
|
||||||
|
)}
|
||||||
|
{state.activeStep === 'finished' && <StepFinished />}
|
||||||
|
</ScreenTransition>
|
||||||
</Layout>
|
</Layout>
|
||||||
</Context.Provider>
|
</Context.Provider>
|
||||||
</OnboardingHeaderSlot.Provider>
|
</OnboardingHeaderSlot.Provider>
|
||||||
|
|||||||
@@ -11,8 +11,14 @@ import {
|
|||||||
export type OnboardingState = {
|
export type OnboardingState = {
|
||||||
hasPrev: boolean
|
hasPrev: boolean
|
||||||
totalSteps: number
|
totalSteps: number
|
||||||
activeStep: 'profile' | 'interests' | 'suggested-accounts' | 'finished'
|
activeStep:
|
||||||
|
| 'profile'
|
||||||
|
| 'interests'
|
||||||
|
| 'suggested-accounts'
|
||||||
|
| 'suggested-starterpacks'
|
||||||
|
| 'finished'
|
||||||
activeStepIndex: number
|
activeStepIndex: number
|
||||||
|
stepTransitionDirection: 'Forward' | 'Backward'
|
||||||
|
|
||||||
interestsStepResults: {
|
interestsStepResults: {
|
||||||
selectedInterests: string[]
|
selectedInterests: string[]
|
||||||
@@ -38,6 +44,7 @@ export type OnboardingState = {
|
|||||||
experiments?: {
|
experiments?: {
|
||||||
onboarding_suggested_accounts?: boolean
|
onboarding_suggested_accounts?: boolean
|
||||||
onboarding_value_prop?: boolean
|
onboarding_value_prop?: boolean
|
||||||
|
onboarding_suggested_starterpacks?: boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,6 +138,7 @@ export const initialState: OnboardingState = {
|
|||||||
totalSteps: 3,
|
totalSteps: 3,
|
||||||
activeStep: 'profile',
|
activeStep: 'profile',
|
||||||
activeStepIndex: 1,
|
activeStepIndex: 1,
|
||||||
|
stepTransitionDirection: 'Forward',
|
||||||
|
|
||||||
interestsStepResults: {
|
interestsStepResults: {
|
||||||
selectedInterests: [],
|
selectedInterests: [],
|
||||||
@@ -163,52 +171,38 @@ export function reducer(
|
|||||||
): OnboardingState {
|
): OnboardingState {
|
||||||
let next = {...s}
|
let next = {...s}
|
||||||
|
|
||||||
|
const stepOrder: OnboardingState['activeStep'][] = [
|
||||||
|
'profile',
|
||||||
|
'interests',
|
||||||
|
...(s.experiments?.onboarding_suggested_accounts
|
||||||
|
? (['suggested-accounts'] as const)
|
||||||
|
: []),
|
||||||
|
...(s.experiments?.onboarding_suggested_starterpacks
|
||||||
|
? (['suggested-starterpacks'] as const)
|
||||||
|
: []),
|
||||||
|
'finished',
|
||||||
|
]
|
||||||
|
|
||||||
switch (a.type) {
|
switch (a.type) {
|
||||||
case 'next': {
|
case 'next': {
|
||||||
if (s.experiments?.onboarding_suggested_accounts) {
|
// 1-indexed for some reason
|
||||||
if (s.activeStep === 'profile') {
|
const nextIndex = s.activeStepIndex
|
||||||
next.activeStep = 'interests'
|
const nextStep = stepOrder[nextIndex]
|
||||||
next.activeStepIndex = 2
|
if (nextStep) {
|
||||||
} else if (s.activeStep === 'interests') {
|
next.activeStep = nextStep
|
||||||
next.activeStep = 'suggested-accounts'
|
next.activeStepIndex = nextIndex + 1
|
||||||
next.activeStepIndex = 3
|
|
||||||
}
|
|
||||||
if (s.activeStep === 'suggested-accounts') {
|
|
||||||
next.activeStep = 'finished'
|
|
||||||
next.activeStepIndex = 4
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (s.activeStep === 'profile') {
|
|
||||||
next.activeStep = 'interests'
|
|
||||||
next.activeStepIndex = 2
|
|
||||||
} else if (s.activeStep === 'interests') {
|
|
||||||
next.activeStep = 'finished'
|
|
||||||
next.activeStepIndex = 3
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
next.stepTransitionDirection = 'Forward'
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'prev': {
|
case 'prev': {
|
||||||
if (s.experiments?.onboarding_suggested_accounts) {
|
const prevIndex = s.activeStepIndex - 2
|
||||||
if (s.activeStep === 'interests') {
|
const prevStep = stepOrder[prevIndex]
|
||||||
next.activeStep = 'profile'
|
if (prevStep) {
|
||||||
next.activeStepIndex = 1
|
next.activeStep = prevStep
|
||||||
} else if (s.activeStep === 'suggested-accounts') {
|
next.activeStepIndex = prevIndex + 1
|
||||||
next.activeStep = 'interests'
|
|
||||||
next.activeStepIndex = 2
|
|
||||||
} else if (s.activeStep === 'finished') {
|
|
||||||
next.activeStep = 'suggested-accounts'
|
|
||||||
next.activeStepIndex = 3
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (s.activeStep === 'interests') {
|
|
||||||
next.activeStep = 'profile'
|
|
||||||
next.activeStepIndex = 1
|
|
||||||
} else if (s.activeStep === 'finished') {
|
|
||||||
next.activeStep = 'interests'
|
|
||||||
next.activeStepIndex = 2
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
next.stepTransitionDirection = 'Backward'
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'finish': {
|
case 'finish': {
|
||||||
|
|||||||
@@ -445,7 +445,7 @@ function Header({
|
|||||||
<ButtonText>
|
<ButtonText>
|
||||||
<Trans>Follow all</Trans>
|
<Trans>Follow all</Trans>
|
||||||
</ButtonText>
|
</ButtonText>
|
||||||
{isProcessing && <Loader size="xs" />}
|
{isProcessing && <ButtonIcon icon={Loader} />}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<OverflowMenu
|
<OverflowMenu
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import {useQuery} from '@tanstack/react-query'
|
||||||
|
|
||||||
|
import {
|
||||||
|
aggregateUserInterests,
|
||||||
|
createBskyTopicsHeader,
|
||||||
|
} from '#/lib/api/feed/utils'
|
||||||
|
import {getContentLanguages} from '#/state/preferences/languages'
|
||||||
|
import {STALE} from '#/state/queries'
|
||||||
|
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||||
|
import {useAgent} from '#/state/session'
|
||||||
|
|
||||||
|
export const createOnboardingSuggestedStarterPacksQueryKey = (
|
||||||
|
interests?: string[],
|
||||||
|
) => ['onboarding-suggested-starter-packs', interests?.join(',')]
|
||||||
|
|
||||||
|
export function useOnboardingSuggestedStarterPacksQuery({
|
||||||
|
enabled,
|
||||||
|
overrideInterests,
|
||||||
|
}: {
|
||||||
|
enabled?: boolean
|
||||||
|
overrideInterests?: string[]
|
||||||
|
}) {
|
||||||
|
const agent = useAgent()
|
||||||
|
const {data: preferences} = usePreferencesQuery()
|
||||||
|
const contentLangs = getContentLanguages().join(',')
|
||||||
|
|
||||||
|
return useQuery({
|
||||||
|
enabled: !!preferences && enabled !== false,
|
||||||
|
staleTime: STALE.MINUTES.THREE,
|
||||||
|
queryKey: createOnboardingSuggestedStarterPacksQueryKey(overrideInterests),
|
||||||
|
queryFn: async () => {
|
||||||
|
const {data} =
|
||||||
|
await agent.app.bsky.unspecced.getOnboardingSuggestedStarterPacks(
|
||||||
|
{limit: 6},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
...createBskyTopicsHeader(
|
||||||
|
overrideInterests
|
||||||
|
? overrideInterests.join(',')
|
||||||
|
: aggregateUserInterests(preferences),
|
||||||
|
),
|
||||||
|
'Accept-Language': contentLangs,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -9,11 +9,18 @@ import {STALE} from '#/state/queries'
|
|||||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||||
import {useAgent} from '#/state/session'
|
import {useAgent} from '#/state/session'
|
||||||
|
|
||||||
export const createSuggestedStarterPacksQueryKey = () => [
|
export const createSuggestedStarterPacksQueryKey = (interests?: string[]) => [
|
||||||
'suggested-starter-packs',
|
'suggested-starter-packs',
|
||||||
|
interests?.join(','),
|
||||||
]
|
]
|
||||||
|
|
||||||
export function useSuggestedStarterPacksQuery({enabled}: {enabled?: boolean}) {
|
export function useSuggestedStarterPacksQuery({
|
||||||
|
enabled,
|
||||||
|
overrideInterests,
|
||||||
|
}: {
|
||||||
|
enabled?: boolean
|
||||||
|
overrideInterests?: string[]
|
||||||
|
}) {
|
||||||
const agent = useAgent()
|
const agent = useAgent()
|
||||||
const {data: preferences} = usePreferencesQuery()
|
const {data: preferences} = usePreferencesQuery()
|
||||||
const contentLangs = getContentLanguages().join(',')
|
const contentLangs = getContentLanguages().join(',')
|
||||||
@@ -21,13 +28,17 @@ export function useSuggestedStarterPacksQuery({enabled}: {enabled?: boolean}) {
|
|||||||
return useQuery({
|
return useQuery({
|
||||||
enabled: !!preferences && enabled !== false,
|
enabled: !!preferences && enabled !== false,
|
||||||
staleTime: STALE.MINUTES.THREE,
|
staleTime: STALE.MINUTES.THREE,
|
||||||
queryKey: createSuggestedStarterPacksQueryKey(),
|
queryKey: createSuggestedStarterPacksQueryKey(overrideInterests),
|
||||||
async queryFn() {
|
queryFn: async () => {
|
||||||
const {data} = await agent.app.bsky.unspecced.getSuggestedStarterPacks(
|
const {data} = await agent.app.bsky.unspecced.getSuggestedStarterPacks(
|
||||||
undefined,
|
undefined,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
...createBskyTopicsHeader(aggregateUserInterests(preferences)),
|
...createBskyTopicsHeader(
|
||||||
|
overrideInterests
|
||||||
|
? overrideInterests.join(',')
|
||||||
|
: aggregateUserInterests(preferences),
|
||||||
|
),
|
||||||
'Accept-Language': contentLangs,
|
'Accept-Language': contentLangs,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user