This commit is contained in:
Eric Bailey
2024-01-22 12:20:21 -06:00
parent f0a602a51c
commit 2389b8301f
18 changed files with 679 additions and 210 deletions
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M21.59 3.193a1 1 0 0 1 .217 1.397l-11.706 16a1 1 0 0 1-1.429.193l-6.294-5a1 1 0 1 1 1.244-1.566l5.48 4.353 11.09-15.16a1 1 0 0 1 1.398-.217Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 283 B

+1
View File
@@ -125,6 +125,7 @@ export function Group({
return (
<GroupContext.Provider value={context}>
<View
style={[a.w_full]}
role={groupRole}
{...(groupRole === 'radiogroup'
? {
+5
View File
@@ -0,0 +1,5 @@
import {createSinglePathSVG} from './TEMPLATE'
export const Check_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M21.59 3.193a1 1 0 0 1 .217 1.397l-11.706 16a1 1 0 0 1-1.429.193l-6.294-5a1 1 0 1 1 1.244-1.566l5.48 4.353 11.09-15.16a1 1 0 0 1 1.398-.217Z',
})
+1 -1
View File
@@ -21,7 +21,7 @@ import {Button, ButtonIcon} from '#/components/Button'
import {ScrollView} from '#/view/com/util/Views'
import {createPortalGroup} from '#/components/Portal'
import {Context} from '#/screens/Onboarding/context'
import {Context} from '#/screens/Onboarding/state'
const COL_WIDTH = 500
+1 -1
View File
@@ -5,7 +5,7 @@ import {atoms as a} from '#/alf'
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {Context} from '#/screens/Onboarding/context'
import {Context} from '#/screens/Onboarding/state'
import {
Title,
Description,
+1 -1
View File
@@ -5,7 +5,7 @@ import {atoms as a} from '#/alf'
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {Context} from '#/screens/Onboarding/context'
import {Context} from '#/screens/Onboarding/state'
import {
Title,
Description,
-63
View File
@@ -1,63 +0,0 @@
import React from 'react'
import {View} from 'react-native'
import {useTheme, atoms as a} from '#/alf'
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron'
import {At_Stroke2_Corner0_Rounded as At} from '#/components/icons/At'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {Loader} from '#/components/Loader'
import {Context} from '#/screens/Onboarding/context'
import {
Title,
Description,
OnboardingControls,
} from '#/screens/Onboarding/Layout'
export function StepInterests() {
const t = useTheme()
const {state, dispatch} = React.useContext(Context)
const [saving, setSaving] = React.useState(false)
const saveInterests = React.useCallback(async () => {
setSaving(true)
await new Promise(y => setTimeout(y, 1000))
setSaving(false)
dispatch({type: 'next'})
}, [setSaving, dispatch])
return (
<View style={[a.align_start]}>
<View
style={[
a.p_lg,
a.rounded_full,
{
backgroundColor:
t.name === 'light' ? t.palette.primary_25 : t.palette.primary_975,
},
]}>
<At size="xl" fill={t.palette.primary_500} />
</View>
<Title>What are your interests?</Title>
<Description>
We'll use this to help customize your experience.
</Description>
<OnboardingControls.Portal>
<Button
disabled={saving}
key={state.activeStep} // remove focus state on nav
variant="gradient"
color="gradient_sky"
size="large"
label="Continue setting up your account"
onPress={saveInterests}>
<ButtonText>Continue</ButtonText>
<ButtonIcon icon={saving ? Loader : ChevronRight} />
</Button>
</OnboardingControls.Portal>
</View>
)
}
@@ -0,0 +1,78 @@
import React from 'react'
import {View, ViewStyle, TextStyle} from 'react-native'
import {useTheme, atoms as a} from '#/alf'
import * as Toggle from '#/components/forms/Toggle'
import {Text} from '#/components/Typography'
import {InterestItem} from '#/screens/Onboarding/StepInterests/data'
export function InterestButton({interest}: {interest: InterestItem}) {
const t = useTheme()
const ctx = Toggle.useItemContext()
const styles = React.useMemo(() => {
const hovered: ViewStyle[] = [
{
backgroundColor:
t.name === 'light' ? t.palette.contrast_200 : t.palette.contrast_50,
},
]
const focused: ViewStyle[] = []
const pressed: ViewStyle[] = []
const selected: ViewStyle[] = [
{
backgroundColor: t.palette.contrast_900,
},
]
const selectedHover: ViewStyle[] = [
{
backgroundColor: t.palette.contrast_800,
},
]
const textSelected: TextStyle[] = [
{
color: t.palette.contrast_100,
},
]
return {
hovered,
focused,
pressed,
selected,
selectedHover,
textSelected,
}
}, [t])
return (
<View
style={[
{
backgroundColor: t.palette.contrast_100,
paddingVertical: 15,
},
a.rounded_full,
a.px_2xl,
ctx.hovered ? styles.hovered : {},
ctx.focused ? styles.hovered : {},
ctx.pressed ? styles.hovered : {},
ctx.selected ? styles.selected : {},
ctx.selected && (ctx.hovered || ctx.focused || ctx.pressed)
? styles.selectedHover
: {},
]}>
<Text
style={[
{
color: t.palette.contrast_900,
},
a.font_bold,
ctx.selected ? styles.textSelected : {},
]}>
{interest.title}
</Text>
</View>
)
}
@@ -0,0 +1,139 @@
export type InterestItem = {
title: string
name: string
tags: string[]
}
export const INTERESTS: InterestItem[] = [
{
title: 'News',
name: 'news',
tags: [],
},
{
title: 'Journalism',
name: 'journalism',
tags: [],
},
{
title: 'Writers',
name: 'writers',
tags: [],
},
{
title: 'Culture',
name: 'culture',
tags: [],
},
{
title: 'Books',
name: 'books',
tags: [],
},
{
title: 'Sports',
name: 'sports',
tags: [],
},
{
title: 'Comedy',
name: 'comedy',
tags: [],
},
{
title: 'Pets',
name: 'pets',
tags: [],
},
{
title: 'Nature',
name: 'nature',
tags: [],
},
{
title: 'Art',
name: 'art',
tags: [],
},
{
title: 'Comics',
name: 'comics',
tags: [],
},
{
title: 'Education',
name: 'education',
tags: [],
},
{
title: 'Climate',
name: 'climate',
tags: [],
},
{
title: 'Science',
name: 'science',
tags: [],
},
{
title: 'Politics',
name: 'politics',
tags: [],
},
{
title: 'Fitness',
name: 'fitness',
tags: [],
},
{
title: 'Health',
name: 'health',
tags: [],
},
{
title: 'Tech & Society',
name: 'tech_and_society',
tags: [],
},
{
title: 'Software Dev',
name: 'software_development',
tags: [],
},
{
title: 'Video Games',
name: 'video_games',
tags: [],
},
{
title: 'Food & Cooking',
name: 'food_and_cooking',
tags: [],
},
]
export const TEMP_ACCOUNT_MAPPING: {
[key: string]: string[]
} = {
news: ['bsky.app', 'jay.bsky.team'],
journalism: ['bsky.app', 'jay.bsky.team'],
writers: ['bsky.app', 'jay.bsky.team'],
culture: ['bsky.app', 'jay.bsky.team'],
books: ['bsky.app', 'jay.bsky.team'],
sports: ['bsky.app', 'jay.bsky.team'],
comedy: ['bsky.app', 'jay.bsky.team'],
pets: ['bsky.app', 'jay.bsky.team'],
nature: ['bsky.app', 'jay.bsky.team'],
art: ['bsky.app', 'jay.bsky.team'],
comics: ['bsky.app', 'jay.bsky.team'],
education: ['bsky.app', 'jay.bsky.team'],
climate: ['bsky.app', 'jay.bsky.team'],
science: ['bsky.app', 'jay.bsky.team'],
politics: ['bsky.app', 'jay.bsky.team'],
fitness: ['bsky.app', 'jay.bsky.team'],
health: ['bsky.app', 'jay.bsky.team'],
tech_and_society: ['bsky.app', 'jay.bsky.team'],
software_development: ['bsky.app', 'jay.bsky.team'],
video_games: ['bsky.app', 'jay.bsky.team'],
food_and_cooking: ['bsky.app', 'jay.bsky.team'],
}
@@ -0,0 +1,116 @@
import React from 'react'
import {View} from 'react-native'
import {useQueryClient} from '@tanstack/react-query'
import {logger} from '#/logger'
import {useTheme, atoms as a} from '#/alf'
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron'
import {At_Stroke2_Corner0_Rounded as At} from '#/components/icons/At'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {Loader} from '#/components/Loader'
import * as Toggle from '#/components/forms/Toggle'
import {profilesQueryKey} from '#/state/queries/profile'
import {Context} from '#/screens/Onboarding/state'
import {
Title,
Description,
OnboardingControls,
} from '#/screens/Onboarding/Layout'
import {
INTERESTS as INTEREST_OPTIONS,
TEMP_ACCOUNT_MAPPING,
} from '#/screens/Onboarding/StepInterests/data'
import {InterestButton} from '#/screens/Onboarding/StepInterests/InterestButton'
export function StepInterests() {
const t = useTheme()
const query = useQueryClient()
const {state, dispatch} = React.useContext(Context)
const [saving, setSaving] = React.useState(false)
const [interests, setInterests] = React.useState<string[]>([])
const saveInterests = React.useCallback(async () => {
setSaving(true)
try {
const {
data: {accounts},
} = {data: {accounts: interests.map(i => TEMP_ACCOUNT_MAPPING[i]).flat()}}
const suggestedAccounts = Array.from(new Set(accounts))
// wait max 2s for query to finish
await Promise.race([
await query.prefetchQuery({
queryKey: profilesQueryKey(suggestedAccounts),
}),
await new Promise(y => setTimeout(y, 2000)),
])
// done
setSaving(false)
dispatch({
type: 'setSuggestedAccountHandles',
suggestedAccountHandles: suggestedAccounts,
})
dispatch({type: 'next'})
} catch (e: any) {
logger.info(`onboading: error saving interests`)
logger.error(e)
}
}, [interests, setSaving, dispatch, query])
return (
<View style={[a.align_start]}>
<View
style={[
a.p_lg,
a.mb_3xl,
a.rounded_full,
{
backgroundColor:
t.name === 'light' ? t.palette.primary_25 : t.palette.primary_975,
},
]}>
<At size="xl" fill={t.palette.primary_500} />
</View>
<Title>What are your interests?</Title>
<Description>
We'll use this to help customize your experience.
</Description>
<View style={[a.pt_2xl]}>
<Toggle.Group
values={interests}
onChange={setInterests}
label="Select your interests">
<View style={[a.flex_row, a.gap_md, a.flex_wrap]}>
{INTEREST_OPTIONS.map(interest => (
<Toggle.Item
key={interest.name}
name={interest.name}
label={interest.title}>
<InterestButton interest={interest} />
</Toggle.Item>
))}
</View>
</Toggle.Group>
</View>
<OnboardingControls.Portal>
<Button
disabled={saving}
key={state.activeStep} // remove focus state on nav
variant="gradient"
color="gradient_sky"
size="large"
label="Continue setting up your account"
onPress={saveInterests}>
<ButtonText>Continue</ButtonText>
<ButtonIcon icon={saving ? Loader : ChevronRight} />
</Button>
</OnboardingControls.Portal>
</View>
)
}
@@ -0,0 +1,110 @@
import React from 'react'
import {View, ViewStyle} from 'react-native'
import {AppBskyActorDefs} from '@atproto/api'
import {useTheme, atoms as a, flatten} from '#/alf'
import {Text} from '#/components/Typography'
import {useItemContext} from '#/components/forms/Toggle'
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
export function SuggestedAccountCard({
profile,
}: {
profile: AppBskyActorDefs.ProfileViewDetailed
}) {
const t = useTheme()
const ctx = useItemContext()
const styles = React.useMemo(() => {
const base: ViewStyle[] = [t.atoms.bg_contrast_25]
const hover: ViewStyle[] = [t.atoms.bg_contrast_50]
const selected: ViewStyle[] = [
{
backgroundColor: t.palette.primary_975,
},
]
const selectedHover: ViewStyle[] = [
{
backgroundColor: t.palette.primary_950,
},
]
const checkboxBase: ViewStyle[] = [t.atoms.bg_contrast_25]
const checkboxSelected: ViewStyle[] = [
{
backgroundColor: t.palette.primary_500,
},
]
const avatarBase: ViewStyle[] = [t.atoms.bg_contrast_100]
const avatarSelected: ViewStyle[] = [
{
backgroundColor: t.palette.primary_900,
},
]
return {
base,
hover: flatten(hover),
selected: flatten(selected),
selectedHover: flatten(selectedHover),
checkboxBase: flatten(checkboxBase),
checkboxSelected: flatten(checkboxSelected),
avatarBase: flatten(avatarBase),
avatarSelected: flatten(avatarSelected),
}
}, [t])
return (
<View style={[a.w_full, a.py_xs]}>
<View
style={[
a.w_full,
a.flex_row,
a.justify_between,
a.align_center,
a.p_md,
a.pr_lg,
a.gap_xl,
a.rounded_md,
styles.base,
(ctx.hovered || ctx.focused || ctx.pressed) && styles.hover,
ctx.selected && styles.selected,
ctx.selected &&
(ctx.hovered || ctx.focused || ctx.pressed) &&
styles.selectedHover,
]}>
<View style={[a.flex_row, a.align_center, a.gap_md]}>
<View
style={[
{width: 48, height: 48},
a.relative,
a.rounded_full,
styles.avatarBase,
ctx.selected && styles.avatarSelected,
]}
/>
<View>
<Text style={[a.font_bold, a.text_md, a.pb_xs]}>
{profile.displayName}
</Text>
<Text style={[t.atoms.text_contrast_600]}>{profile.handle}</Text>
</View>
</View>
<View
style={[
a.justify_center,
a.align_center,
a.rounded_sm,
styles.checkboxBase,
ctx.selected && styles.checkboxSelected,
{
width: 28,
height: 28,
},
]}>
{ctx.selected && <Check size="sm" style={[t.atoms.text]} />}
</View>
</View>
</View>
)
}
@@ -0,0 +1,107 @@
import React from 'react'
import {View} from 'react-native'
import {AppBskyActorDefs} from '@atproto/api'
import {useTheme, atoms as a, useBreakpoints} from '#/alf'
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
import {At_Stroke2_Corner0_Rounded as At} from '#/components/icons/At'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {Text} from '#/components/Typography'
import {useProfilesQuery} from '#/state/queries/profile'
import {Loader} from '#/components/Loader'
import * as Toggle from '#/components/forms/Toggle'
import {Context} from '#/screens/Onboarding/state'
import {
Title,
Description,
OnboardingControls,
} from '#/screens/Onboarding/Layout'
import {SuggestedAccountCard} from '#/screens/Onboarding/StepSuggestedAccounts/SuggestedAccountCard'
export function Inner({
profiles,
}: {
profiles: AppBskyActorDefs.ProfileViewDetailed[]
}) {
const [dids, setDids] = React.useState<string[]>(profiles.map(p => p.did))
return (
<Toggle.Group
values={dids}
onChange={setDids}
label="Select accounts to follow">
{profiles.map(profile => (
<Toggle.Item
key={profile.did}
name={profile.did}
label={`Follow ${profile.handle}`}>
<SuggestedAccountCard profile={profile} />
</Toggle.Item>
))}
</Toggle.Group>
)
}
export function StepSuggestedAccounts() {
const t = useTheme()
const {state, dispatch} = React.useContext(Context)
const {gtMobile} = useBreakpoints()
const {isLoading, isError, data} = useProfilesQuery({
handles: state.suggestedAccountHandles,
})
return (
<View style={[a.align_start]}>
<View
style={[
a.p_lg,
a.mb_3xl,
a.rounded_full,
{
backgroundColor:
t.name === 'light' ? t.palette.primary_25 : t.palette.primary_975,
},
]}>
<At size="xl" fill={t.palette.primary_500} />
</View>
<Title>Here are some accounts for your to follow:</Title>
<Description>Based on your interest in Pets and Books.</Description>
<View style={[a.w_full, a.pt_xl]}>
{isLoading ? (
<Loader />
) : isError || !data ? (
<Text>Error</Text>
) : (
<Inner profiles={data.profiles} />
)}
</View>
<OnboardingControls.Portal>
<View style={[a.gap_md, gtMobile ? a.flex_row : a.flex_col]}>
<Button
key={state.activeStep} // remove focus state on nav
variant="gradient"
color="gradient_sky"
size="large"
label="Continue setting up your account"
onPress={() => dispatch({type: 'next'})}>
<ButtonText>Follow All</ButtonText>
<ButtonIcon icon={Plus} />
</Button>
<Button
key={state.activeStep + '2'} // remove focus state on nav
variant="outline"
color="secondary"
size="large"
label="Continue setting up your account"
onPress={() => dispatch({type: 'next'})}>
Skip
</Button>
</View>
</OnboardingControls.Portal>
</View>
)
}
@@ -1,49 +0,0 @@
import React from 'react'
import {View} from 'react-native'
import {atoms as a, useBreakpoints} from '#/alf'
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {Context} from '#/screens/Onboarding/context'
import {
Title,
Description,
OnboardingControls,
} from '#/screens/Onboarding/Layout'
export function StepSuggestedFollows() {
const {state, dispatch} = React.useContext(Context)
const {gtMobile} = useBreakpoints()
return (
<View style={[a.align_start]}>
<Title>Here are some accounts for your to follow:</Title>
<Description>Based on your interest in Pets and Books.</Description>
<OnboardingControls.Portal>
<View style={[a.gap_md, gtMobile ? a.flex_row : a.flex_col]}>
<Button
key={state.activeStep} // remove focus state on nav
variant="gradient"
color="gradient_sky"
size="large"
label="Continue setting up your account"
onPress={() => dispatch({type: 'next'})}>
<ButtonText>Follow All</ButtonText>
<ButtonIcon icon={Plus} />
</Button>
<Button
key={state.activeStep + '2'} // remove focus state on nav
variant="outline"
color="secondary"
size="large"
label="Continue setting up your account"
onPress={() => dispatch({type: 'next'})}>
Skip
</Button>
</View>
</OnboardingControls.Portal>
</View>
)
}
+1 -1
View File
@@ -5,7 +5,7 @@ import {atoms as a} from '#/alf'
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {Context} from '#/screens/Onboarding/context'
import {Context} from '#/screens/Onboarding/state'
import {
Title,
Description,
-36
View File
@@ -1,36 +0,0 @@
import React from 'react'
export type OnboardingState = {
hasPrev: boolean
totalSteps: number
activeStep:
| 'interests'
| 'suggestedFollows'
| 'followingFeed'
| 'algoFeeds'
| 'topicalFeeds'
activeStepIndex: number
}
export type OnboardingAction =
| {
type: 'next'
}
| {
type: 'prev'
}
export const initialState: OnboardingState = {
hasPrev: false,
totalSteps: 5,
activeStep: 'interests',
activeStepIndex: 1,
}
export const Context = React.createContext<{
state: OnboardingState
dispatch: React.Dispatch<OnboardingAction>
}>({
state: initialState,
dispatch: () => {},
})
+5 -58
View File
@@ -1,69 +1,16 @@
import React from 'react'
import {logger} from '#/logger'
import {Portal} from '#/components/Portal'
import {
Context,
OnboardingState,
OnboardingAction,
initialState,
} from '#/screens/Onboarding/context'
import {Context, initialState, reducer} from '#/screens/Onboarding/state'
import {Layout, OnboardingControls} from '#/screens/Onboarding/Layout'
import {StepInterests} from '#/screens/Onboarding/StepInterests'
import {StepSuggestedFollows} from '#/screens/Onboarding/StepSuggestedFollows'
import {StepSuggestedAccounts} from '#/screens/Onboarding/StepSuggestedAccounts'
import {StepFollowingFeed} from '#/screens/Onboarding/StepFollowingFeed'
import {StepAlgoFeeds} from '#/screens/Onboarding/StepAlgoFeeds'
import {StepTopicalFeeds} from '#/screens/Onboarding/StepTopicalFeeds'
function reducer(s: OnboardingState, a: OnboardingAction): OnboardingState {
const next = s
switch (a.type) {
case 'next': {
if (s.activeStep === 'interests') {
next.activeStep = 'suggestedFollows'
next.activeStepIndex = 2
} else if (s.activeStep === 'suggestedFollows') {
next.activeStep = 'followingFeed'
next.activeStepIndex = 3
} else if (s.activeStep === 'followingFeed') {
next.activeStep = 'algoFeeds'
next.activeStepIndex = 4
} else if (s.activeStep === 'algoFeeds') {
next.activeStep = 'topicalFeeds'
next.activeStepIndex = 5
}
break
}
case 'prev': {
if (s.activeStep === 'suggestedFollows') {
next.activeStep = 'interests'
next.activeStepIndex = 1
} else if (s.activeStep === 'followingFeed') {
next.activeStep = 'suggestedFollows'
next.activeStepIndex = 2
} else if (s.activeStep === 'algoFeeds') {
next.activeStep = 'followingFeed'
next.activeStepIndex = 3
} else if (s.activeStep === 'topicalFeeds') {
next.activeStep = 'algoFeeds'
next.activeStepIndex = 4
}
break
}
}
const state = {
...next,
hasPrev: next.activeStep !== 'interests',
}
logger.debug(`onboarding`, state)
return state
}
// TODO lock focus?
export function Onboarding() {
const [state, dispatch] = React.useReducer(reducer, initialState)
@@ -74,8 +21,8 @@ export function Onboarding() {
value={React.useMemo(() => ({state, dispatch}), [state, dispatch])}>
<Layout>
{state.activeStep === 'interests' && <StepInterests />}
{state.activeStep === 'suggestedFollows' && (
<StepSuggestedFollows />
{state.activeStep === 'suggestedAccounts' && (
<StepSuggestedAccounts />
)}
{state.activeStep === 'followingFeed' && <StepFollowingFeed />}
{state.activeStep === 'algoFeeds' && <StepAlgoFeeds />}
+101
View File
@@ -0,0 +1,101 @@
import React from 'react'
import {logger} from '#/logger'
export type OnboardingState = {
hasPrev: boolean
totalSteps: number
activeStep:
| 'interests'
| 'suggestedAccounts'
| 'followingFeed'
| 'algoFeeds'
| 'topicalFeeds'
activeStepIndex: number
suggestedAccountHandles: string[]
}
export type OnboardingAction =
| {
type: 'next'
}
| {
type: 'prev'
}
| {
type: 'setSuggestedAccountHandles'
suggestedAccountHandles: string[]
}
export const initialState: OnboardingState = {
hasPrev: false,
totalSteps: 5,
activeStep: 'interests',
activeStepIndex: 1,
// result of interests step
suggestedAccountHandles: [],
}
export const Context = React.createContext<{
state: OnboardingState
dispatch: React.Dispatch<OnboardingAction>
}>({
state: initialState,
dispatch: () => {},
})
export function reducer(
s: OnboardingState,
a: OnboardingAction,
): OnboardingState {
const next = s
switch (a.type) {
case 'next': {
if (s.activeStep === 'interests') {
next.activeStep = 'suggestedAccounts'
next.activeStepIndex = 2
} else if (s.activeStep === 'suggestedAccounts') {
next.activeStep = 'followingFeed'
next.activeStepIndex = 3
} else if (s.activeStep === 'followingFeed') {
next.activeStep = 'algoFeeds'
next.activeStepIndex = 4
} else if (s.activeStep === 'algoFeeds') {
next.activeStep = 'topicalFeeds'
next.activeStepIndex = 5
}
break
}
case 'prev': {
if (s.activeStep === 'suggestedAccounts') {
next.activeStep = 'interests'
next.activeStepIndex = 1
} else if (s.activeStep === 'followingFeed') {
next.activeStep = 'suggestedAccounts'
next.activeStepIndex = 2
} else if (s.activeStep === 'algoFeeds') {
next.activeStep = 'followingFeed'
next.activeStepIndex = 3
} else if (s.activeStep === 'topicalFeeds') {
next.activeStep = 'algoFeeds'
next.activeStepIndex = 4
}
break
}
case 'setSuggestedAccountHandles': {
next.suggestedAccountHandles = a.suggestedAccountHandles
break
}
}
const state = {
...next,
hasPrev: next.activeStep !== 'interests',
}
logger.debug(`onboarding`, state)
return state
}
+12
View File
@@ -24,6 +24,7 @@ import {STALE} from '#/state/queries'
import {track} from '#/lib/analytics/analytics'
export const RQKEY = (did: string) => ['profile', did]
export const profilesQueryKey = (handles: string[]) => ['profiles', handles]
export function useProfileQuery({did}: {did: string | undefined}) {
const {currentAccount} = useSession()
@@ -45,6 +46,17 @@ export function useProfileQuery({did}: {did: string | undefined}) {
})
}
export function useProfilesQuery({handles}: {handles: string[]}) {
return useQuery({
staleTime: STALE.MINUTES.FIVE,
queryKey: profilesQueryKey(handles),
queryFn: async () => {
const res = await getAgent().getProfiles({actors: handles})
return res.data
},
})
}
interface ProfileUpdateParams {
profile: AppBskyActorDefs.ProfileView
updates: