WIP
This commit is contained in:
@@ -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 |
@@ -125,6 +125,7 @@ export function Group({
|
||||
return (
|
||||
<GroupContext.Provider value={context}>
|
||||
<View
|
||||
style={[a.w_full]}
|
||||
role={groupRole}
|
||||
{...(groupRole === 'radiogroup'
|
||||
? {
|
||||
|
||||
@@ -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',
|
||||
})
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,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>
|
||||
)
|
||||
}
|
||||
@@ -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,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: () => {},
|
||||
})
|
||||
@@ -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 />}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user