+5
-1
src/components/Admonition.tsx
+5
-1
src/components/Admonition.tsx
···
7
7
import {CircleX_Stroke2_Corner0_Rounded as CircleXIcon} from '#/components/icons/CircleX'
8
8
import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning'
9
9
import {Text as BaseText, type TextProps} from '#/components/Typography'
10
+
import {EmojiSad_Stroke2_Corner0_Rounded as EmojiSadIcon} from './icons/Emoji'
10
11
11
12
export const colors = {
12
13
warning: '#FFC404',
13
14
}
14
15
15
16
type Context = {
16
-
type: 'info' | 'tip' | 'warning' | 'error'
17
+
type: 'info' | 'tip' | 'warning' | 'error' | 'apology'
17
18
}
18
19
19
20
const Context = createContext<Context>({
···
29
30
tip: CircleInfoIcon,
30
31
warning: WarningIcon,
31
32
error: CircleXIcon,
33
+
apology: EmojiSadIcon,
32
34
}[type]
33
35
const fill = {
34
36
info: t.atoms.text_contrast_medium.color,
35
37
tip: t.palette.primary_500,
36
38
warning: colors.warning,
37
39
error: t.palette.negative_500,
40
+
apology: t.atoms.text_contrast_medium.color,
38
41
}[type]
39
42
return <Icon fill={fill} size="md" />
40
43
}
···
109
112
tip: t.palette.primary_500,
110
113
warning: colors.warning,
111
114
error: t.palette.negative_500,
115
+
apology: t.atoms.border_contrast_high.borderColor,
112
116
}[type]
113
117
return (
114
118
<Context.Provider value={{type}}>
+1
-4
src/components/ProgressGuide/FollowDialog.tsx
+1
-4
src/components/ProgressGuide/FollowDialog.tsx
···
4
4
import {msg, Trans} from '@lingui/macro'
5
5
import {useLingui} from '@lingui/react'
6
6
7
+
import {popularInterests, useInterestsDisplayNames} from '#/lib/interests'
7
8
import {logEvent} from '#/lib/statsig/statsig'
8
9
import {isWeb} from '#/platform/detection'
9
10
import {useModerationOpts} from '#/state/preferences/moderation-opts'
···
13
14
import {useSession} from '#/state/session'
14
15
import {type Follow10ProgressGuide} from '#/state/shell/progress-guide'
15
16
import {type ListMethods} from '#/view/com/util/List'
16
-
import {
17
-
popularInterests,
18
-
useInterestsDisplayNames,
19
-
} from '#/screens/Onboarding/state'
20
17
import {
21
18
atoms as a,
22
19
native,
+78
src/lib/interests.ts
+78
src/lib/interests.ts
···
1
+
import {useMemo} from 'react'
2
+
import {msg} from '@lingui/macro'
3
+
import {useLingui} from '@lingui/react'
4
+
5
+
export const interests = [
6
+
'animals',
7
+
'art',
8
+
'books',
9
+
'comedy',
10
+
'comics',
11
+
'culture',
12
+
'dev',
13
+
'education',
14
+
'finance',
15
+
'food',
16
+
'gaming',
17
+
'journalism',
18
+
'movies',
19
+
'music',
20
+
'nature',
21
+
'news',
22
+
'pets',
23
+
'photography',
24
+
'politics',
25
+
'science',
26
+
'sports',
27
+
'tech',
28
+
'tv',
29
+
'writers',
30
+
] as const
31
+
export type Interest = (typeof interests)[number]
32
+
33
+
// most popular selected interests
34
+
export const popularInterests = [
35
+
'art',
36
+
'gaming',
37
+
'sports',
38
+
'comics',
39
+
'music',
40
+
'politics',
41
+
'photography',
42
+
'science',
43
+
'news',
44
+
] satisfies Interest[]
45
+
46
+
export function useInterestsDisplayNames() {
47
+
const {_} = useLingui()
48
+
49
+
return useMemo<Record<string, string>>(() => {
50
+
return {
51
+
// Keep this alphabetized
52
+
animals: _(msg`Animals`),
53
+
art: _(msg`Art`),
54
+
books: _(msg`Books`),
55
+
comedy: _(msg`Comedy`),
56
+
comics: _(msg`Comics`),
57
+
culture: _(msg`Culture`),
58
+
dev: _(msg`Software Dev`),
59
+
education: _(msg`Education`),
60
+
finance: _(msg`Finance`),
61
+
food: _(msg`Food`),
62
+
gaming: _(msg`Video Games`),
63
+
journalism: _(msg`Journalism`),
64
+
movies: _(msg`Movies`),
65
+
music: _(msg`Music`),
66
+
nature: _(msg`Nature`),
67
+
news: _(msg`News`),
68
+
pets: _(msg`Pets`),
69
+
photography: _(msg`Photography`),
70
+
politics: _(msg`Politics`),
71
+
science: _(msg`Science`),
72
+
sports: _(msg`Sports`),
73
+
tech: _(msg`Tech`),
74
+
tv: _(msg`TV`),
75
+
writers: _(msg`Writers`),
76
+
} satisfies Record<Interest, string>
77
+
}, [_])
78
+
}
+2
-2
src/screens/Onboarding/StepInterests/InterestButton.tsx
+2
-2
src/screens/Onboarding/StepInterests/InterestButton.tsx
···
1
1
import React from 'react'
2
2
import {type TextStyle, View, type ViewStyle} from 'react-native'
3
3
4
+
import {type Interest, useInterestsDisplayNames} from '#/lib/interests'
4
5
import {capitalize} from '#/lib/strings/capitalize'
5
-
import {useInterestsDisplayNames} from '#/screens/Onboarding/state'
6
6
import {atoms as a, native, useTheme} from '#/alf'
7
7
import * as Toggle from '#/components/forms/Toggle'
8
8
import {Text} from '#/components/Typography'
9
9
10
-
export function InterestButton({interest}: {interest: string}) {
10
+
export function InterestButton({interest}: {interest: Interest}) {
11
11
const t = useTheme()
12
12
const interestsDisplayNames = useInterestsDisplayNames()
13
13
const ctx = Toggle.useItemContext()
+42
-206
src/screens/Onboarding/StepInterests/index.tsx
+42
-206
src/screens/Onboarding/StepInterests/index.tsx
···
2
2
import {View} from 'react-native'
3
3
import {msg, Trans} from '@lingui/macro'
4
4
import {useLingui} from '@lingui/react'
5
-
import {useQuery} from '@tanstack/react-query'
6
5
6
+
import {interests, useInterestsDisplayNames} from '#/lib/interests'
7
7
import {logEvent} from '#/lib/statsig/statsig'
8
8
import {capitalize} from '#/lib/strings/capitalize'
9
9
import {logger} from '#/logger'
10
-
import {useAgent} from '#/state/session'
11
-
import {useOnboardingDispatch} from '#/state/shell'
12
10
import {
13
11
DescriptionText,
14
12
OnboardingControls,
15
13
TitleText,
16
14
} from '#/screens/Onboarding/Layout'
17
-
import {
18
-
type ApiResponseMap,
19
-
Context,
20
-
useInterestsDisplayNames,
21
-
} from '#/screens/Onboarding/state'
15
+
import {Context} from '#/screens/Onboarding/state'
22
16
import {InterestButton} from '#/screens/Onboarding/StepInterests/InterestButton'
23
-
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
17
+
import {atoms as a} from '#/alf'
24
18
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
25
19
import * as Toggle from '#/components/forms/Toggle'
26
20
import {IconCircle} from '#/components/IconCircle'
27
-
import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as ArrowRotateCounterClockwise} from '#/components/icons/ArrowRotateCounterClockwise'
28
21
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron'
29
-
import {EmojiSad_Stroke2_Corner0_Rounded as EmojiSad} from '#/components/icons/Emoji'
30
22
import {Hashtag_Stroke2_Corner0_Rounded as Hashtag} from '#/components/icons/Hashtag'
31
23
import {Loader} from '#/components/Loader'
32
-
import {Text} from '#/components/Typography'
33
24
34
25
export function StepInterests() {
35
26
const {_} = useLingui()
36
-
const t = useTheme()
37
-
const {gtMobile} = useBreakpoints()
38
27
const interestsDisplayNames = useInterestsDisplayNames()
39
28
40
29
const {state, dispatch} = React.useContext(Context)
41
30
const [saving, setSaving] = React.useState(false)
42
-
const [interests, setInterests] = React.useState<string[]>(
31
+
const [selectedInterests, setSelectedInterests] = React.useState<string[]>(
43
32
state.interestsStepResults.selectedInterests.map(i => i),
44
33
)
45
-
const onboardDispatch = useOnboardingDispatch()
46
-
const agent = useAgent()
47
-
const {isLoading, isError, error, data, refetch, isFetching} = useQuery({
48
-
queryKey: ['interests'],
49
-
queryFn: async () => {
50
-
try {
51
-
const {data} = await agent.app.bsky.unspecced.getTaggedSuggestions()
52
-
return data.suggestions.reduce(
53
-
(agg, s) => {
54
-
const {tag, subject, subjectType} = s
55
-
const isDefault = tag === 'default'
56
-
57
-
if (!agg.interests.includes(tag) && !isDefault) {
58
-
agg.interests.push(tag)
59
-
}
60
-
61
-
if (subjectType === 'user') {
62
-
agg.suggestedAccountDids[tag] =
63
-
agg.suggestedAccountDids[tag] || []
64
-
agg.suggestedAccountDids[tag].push(subject)
65
-
}
66
-
67
-
if (subjectType === 'feed') {
68
-
// agg all feeds into defaults
69
-
if (isDefault) {
70
-
agg.suggestedFeedUris[tag] = agg.suggestedFeedUris[tag] || []
71
-
} else {
72
-
agg.suggestedFeedUris[tag] = agg.suggestedFeedUris[tag] || []
73
-
agg.suggestedFeedUris[tag].push(subject)
74
-
agg.suggestedFeedUris.default.push(subject)
75
-
}
76
-
}
77
-
78
-
return agg
79
-
},
80
-
{
81
-
interests: [],
82
-
suggestedAccountDids: {},
83
-
suggestedFeedUris: {},
84
-
} as ApiResponseMap,
85
-
)
86
-
} catch (e: any) {
87
-
logger.info(
88
-
`onboarding: getTaggedSuggestions fetch or processing failed`,
89
-
)
90
-
logger.error(e)
91
-
92
-
throw new Error(`a network error occurred`)
93
-
}
94
-
},
95
-
})
96
34
97
35
const saveInterests = React.useCallback(async () => {
98
36
setSaving(true)
···
101
39
setSaving(false)
102
40
dispatch({
103
41
type: 'setInterestsStepResults',
104
-
apiResponse: data!,
105
-
selectedInterests: interests,
42
+
selectedInterests,
106
43
})
107
44
dispatch({type: 'next'})
108
45
logEvent('onboarding:interests:nextPressed', {
109
-
selectedInterests: interests,
110
-
selectedInterestsLength: interests.length,
46
+
selectedInterests,
47
+
selectedInterestsLength: selectedInterests.length,
111
48
})
112
49
} catch (e: any) {
113
50
logger.info(`onboading: error saving interests`)
114
51
logger.error(e)
115
52
}
116
-
}, [interests, data, setSaving, dispatch])
117
-
118
-
const skipOnboarding = React.useCallback(() => {
119
-
onboardDispatch({type: 'finish'})
120
-
dispatch({type: 'finish'})
121
-
}, [onboardDispatch, dispatch])
122
-
123
-
const title = isError ? (
124
-
<Trans>Oh no! Something went wrong.</Trans>
125
-
) : (
126
-
<Trans>What are your interests?</Trans>
127
-
)
128
-
const description = isError ? (
129
-
<Trans>
130
-
We weren't able to connect. Please try again to continue setting up your
131
-
account. If it continues to fail, you can skip this flow.
132
-
</Trans>
133
-
) : (
134
-
<Trans>We'll use this to help customize your experience.</Trans>
135
-
)
53
+
}, [selectedInterests, setSaving, dispatch])
136
54
137
55
return (
138
56
<View style={[a.align_start]} testID="onboardingInterests">
139
-
<IconCircle
140
-
icon={isError ? EmojiSad : Hashtag}
141
-
style={[
142
-
a.mb_2xl,
143
-
isError
144
-
? {
145
-
backgroundColor: t.palette.negative_50,
146
-
}
147
-
: {},
148
-
]}
149
-
iconStyle={[
150
-
isError
151
-
? {
152
-
color: t.palette.negative_900,
153
-
}
154
-
: {},
155
-
]}
156
-
/>
57
+
<IconCircle icon={Hashtag} style={[a.mb_2xl]} />
157
58
158
-
<TitleText>{title}</TitleText>
159
-
<DescriptionText>{description}</DescriptionText>
59
+
<TitleText>
60
+
<Trans>What are your interests?</Trans>
61
+
</TitleText>
62
+
<DescriptionText>
63
+
<Trans>We'll use this to help customize your experience.</Trans>
64
+
</DescriptionText>
160
65
161
66
<View style={[a.w_full, a.pt_2xl]}>
162
-
{isLoading ? (
163
-
<View
164
-
style={[
165
-
a.flex_1,
166
-
a.mt_md,
167
-
a.align_center,
168
-
a.justify_center,
169
-
{minHeight: 400},
170
-
]}>
171
-
<Loader size="xl" />
172
-
</View>
173
-
) : isError || !data ? (
174
-
<View
175
-
style={[
176
-
a.w_full,
177
-
a.p_lg,
178
-
a.rounded_md,
179
-
{
180
-
backgroundColor: t.palette.negative_50,
181
-
},
182
-
]}>
183
-
<Text style={[a.text_md]}>
184
-
<Text
185
-
style={[
186
-
a.text_md,
187
-
a.font_semi_bold,
188
-
{
189
-
color: t.palette.negative_900,
190
-
},
191
-
]}>
192
-
<Trans>Error:</Trans>{' '}
193
-
</Text>
194
-
{error?.message || _(msg`an unknown error occurred`)}
195
-
</Text>
67
+
<Toggle.Group
68
+
values={selectedInterests}
69
+
onChange={setSelectedInterests}
70
+
label={_(msg`Select your interests from the options below`)}>
71
+
<View style={[a.flex_row, a.gap_md, a.flex_wrap]}>
72
+
{interests.map(interest => (
73
+
<Toggle.Item
74
+
key={interest}
75
+
name={interest}
76
+
label={interestsDisplayNames[interest] || capitalize(interest)}>
77
+
<InterestButton interest={interest} />
78
+
</Toggle.Item>
79
+
))}
196
80
</View>
197
-
) : (
198
-
<Toggle.Group
199
-
values={interests}
200
-
onChange={setInterests}
201
-
label={_(msg`Select your interests from the options below`)}>
202
-
<View style={[a.flex_row, a.gap_md, a.flex_wrap]}>
203
-
{data.interests.map(interest => (
204
-
<Toggle.Item
205
-
key={interest}
206
-
name={interest}
207
-
label={
208
-
interestsDisplayNames[interest] || capitalize(interest)
209
-
}>
210
-
<InterestButton interest={interest} />
211
-
</Toggle.Item>
212
-
))}
213
-
</View>
214
-
</Toggle.Group>
215
-
)}
81
+
</Toggle.Group>
216
82
</View>
217
83
218
84
<OnboardingControls.Portal>
219
-
{isError ? (
220
-
<View style={[a.gap_md, gtMobile ? a.flex_row : a.flex_col]}>
221
-
<Button
222
-
disabled={isFetching}
223
-
variant="solid"
224
-
color="secondary"
225
-
size="large"
226
-
label={_(msg`Retry`)}
227
-
onPress={() => refetch()}>
228
-
<ButtonText>
229
-
<Trans>Retry</Trans>
230
-
</ButtonText>
231
-
<ButtonIcon icon={ArrowRotateCounterClockwise} position="right" />
232
-
</Button>
233
-
<Button
234
-
variant="outline"
235
-
color="secondary"
236
-
size="large"
237
-
label={_(msg`Skip this flow`)}
238
-
onPress={skipOnboarding}>
239
-
<ButtonText>
240
-
<Trans>Skip</Trans>
241
-
</ButtonText>
242
-
</Button>
243
-
</View>
244
-
) : (
245
-
<Button
246
-
disabled={saving || !data}
247
-
testID="onboardingContinue"
248
-
variant="solid"
249
-
color="primary"
250
-
size="large"
251
-
label={_(msg`Continue to next step`)}
252
-
onPress={saveInterests}>
253
-
<ButtonText>
254
-
<Trans>Continue</Trans>
255
-
</ButtonText>
256
-
<ButtonIcon
257
-
icon={saving ? Loader : ChevronRight}
258
-
position="right"
259
-
/>
260
-
</Button>
261
-
)}
85
+
<Button
86
+
disabled={saving}
87
+
testID="onboardingContinue"
88
+
variant="solid"
89
+
color="primary"
90
+
size="large"
91
+
label={_(msg`Continue to next step`)}
92
+
onPress={saveInterests}>
93
+
<ButtonText>
94
+
<Trans>Continue</Trans>
95
+
</ButtonText>
96
+
<ButtonIcon icon={saving ? Loader : ChevronRight} />
97
+
</Button>
262
98
</OnboardingControls.Portal>
263
99
</View>
264
100
)
+13
-6
src/screens/Onboarding/StepSuggestedAccounts/index.tsx
+13
-6
src/screens/Onboarding/StepSuggestedAccounts/index.tsx
···
7
7
import * as bcp47Match from 'bcp-47-match'
8
8
9
9
import {wait} from '#/lib/async/wait'
10
+
import {popularInterests, useInterestsDisplayNames} from '#/lib/interests'
10
11
import {isBlockedOrBlocking, isMuted} from '#/lib/moderation/blocked-and-muted'
11
12
import {logger} from '#/logger'
12
13
import {isWeb} from '#/platform/detection'
···
16
17
import {useAgent, useSession} from '#/state/session'
17
18
import {useOnboardingDispatch} from '#/state/shell'
18
19
import {OnboardingControls} from '#/screens/Onboarding/Layout'
19
-
import {
20
-
Context,
21
-
popularInterests,
22
-
useInterestsDisplayNames,
23
-
} from '#/screens/Onboarding/state'
20
+
import {Context} from '#/screens/Onboarding/state'
24
21
import {useSuggestedUsers} from '#/screens/Search/util/useSuggestedUsers'
25
22
import {atoms as a, tokens, useBreakpoints, useTheme} from '#/alf'
26
23
import {Admonition} from '#/components/Admonition'
···
77
74
})
78
75
79
76
const isError = !!error
77
+
const isEmpty =
78
+
!isLoading && suggestedUsers && suggestedUsers.actors.length === 0
80
79
81
80
const skipOnboarding = useCallback(() => {
82
81
onboardDispatch({type: 'finish'})
···
171
170
<Loader size="xl" />
172
171
</View>
173
172
) : isError ? (
174
-
<View style={[a.flex_1, a.px_xl, a.pt_5xl]}>
173
+
<View style={[a.flex_1, a.px_xl, a.pt_2xl]}>
175
174
<Admonition type="error">
176
175
<Trans>
177
176
An error occurred while fetching suggested accounts.
177
+
</Trans>
178
+
</Admonition>
179
+
</View>
180
+
) : isEmpty ? (
181
+
<View style={[a.flex_1, a.px_xl, a.pt_2xl]}>
182
+
<Admonition type="apology">
183
+
<Trans>
184
+
Sorry, we're unable to load account suggestions at this time.
178
185
</Trans>
179
186
</Admonition>
180
187
</View>
-66
src/screens/Onboarding/state.ts
-66
src/screens/Onboarding/state.ts
···
1
1
import React from 'react'
2
-
import {msg} from '@lingui/macro'
3
-
import {useLingui} from '@lingui/react'
4
2
5
3
import {logger} from '#/logger'
6
4
import {
···
22
20
23
21
interestsStepResults: {
24
22
selectedInterests: string[]
25
-
apiResponse: ApiResponseMap
26
23
}
27
24
profileStepResults: {
28
25
isCreatedAvatar: boolean
···
61
58
| {
62
59
type: 'setInterestsStepResults'
63
60
selectedInterests: string[]
64
-
apiResponse: ApiResponseMap
65
61
}
66
62
| {
67
63
type: 'setProfileStepResults'
···
77
73
| undefined
78
74
}
79
75
80
-
export type ApiResponseMap = {
81
-
interests: string[]
82
-
suggestedAccountDids: {
83
-
[key: string]: string[]
84
-
}
85
-
suggestedFeedUris: {
86
-
[key: string]: string[]
87
-
}
88
-
}
89
-
90
-
// most popular selected interests
91
-
export const popularInterests = [
92
-
'art',
93
-
'gaming',
94
-
'sports',
95
-
'comics',
96
-
'music',
97
-
'politics',
98
-
'photography',
99
-
'science',
100
-
'news',
101
-
]
102
-
103
-
export function useInterestsDisplayNames() {
104
-
const {_} = useLingui()
105
-
106
-
return React.useMemo<Record<string, string>>(() => {
107
-
return {
108
-
// Keep this alphabetized
109
-
animals: _(msg`Animals`),
110
-
art: _(msg`Art`),
111
-
books: _(msg`Books`),
112
-
comedy: _(msg`Comedy`),
113
-
comics: _(msg`Comics`),
114
-
culture: _(msg`Culture`),
115
-
dev: _(msg`Software Dev`),
116
-
education: _(msg`Education`),
117
-
food: _(msg`Food`),
118
-
gaming: _(msg`Video Games`),
119
-
journalism: _(msg`Journalism`),
120
-
movies: _(msg`Movies`),
121
-
music: _(msg`Music`),
122
-
nature: _(msg`Nature`),
123
-
news: _(msg`News`),
124
-
pets: _(msg`Pets`),
125
-
photography: _(msg`Photography`),
126
-
politics: _(msg`Politics`),
127
-
science: _(msg`Science`),
128
-
sports: _(msg`Sports`),
129
-
tech: _(msg`Tech`),
130
-
tv: _(msg`TV`),
131
-
writers: _(msg`Writers`),
132
-
}
133
-
}, [_])
134
-
}
135
-
136
76
export const initialState: OnboardingState = {
137
77
hasPrev: false,
138
78
totalSteps: 3,
···
142
82
143
83
interestsStepResults: {
144
84
selectedInterests: [],
145
-
apiResponse: {
146
-
interests: [],
147
-
suggestedAccountDids: {},
148
-
suggestedFeedUris: {},
149
-
},
150
85
},
151
86
profileStepResults: {
152
87
isCreatedAvatar: false,
···
212
147
case 'setInterestsStepResults': {
213
148
next.interestsStepResults = {
214
149
selectedInterests: a.selectedInterests,
215
-
apiResponse: a.apiResponse,
216
150
}
217
151
break
218
152
}
+1
-4
src/screens/Search/Explore.tsx
+1
-4
src/screens/Search/Explore.tsx
···
10
10
import {useQueryClient} from '@tanstack/react-query'
11
11
import * as bcp47Match from 'bcp-47-match'
12
12
13
+
import {popularInterests, useInterestsDisplayNames} from '#/lib/interests'
13
14
import {cleanError} from '#/lib/strings/errors'
14
15
import {sanitizeHandle} from '#/lib/strings/handles'
15
16
import {logger} from '#/logger'
···
41
42
import {List} from '#/view/com/util/List'
42
43
import {FeedFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
43
44
import {LoadMoreRetryBtn} from '#/view/com/util/LoadMoreRetryBtn'
44
-
import {
45
-
popularInterests,
46
-
useInterestsDisplayNames,
47
-
} from '#/screens/Onboarding/state'
48
45
import {
49
46
StarterPackCard,
50
47
StarterPackCardSkeleton,
+1
-1
src/screens/Search/modules/ExploreInterestsCard.tsx
+1
-1
src/screens/Search/modules/ExploreInterestsCard.tsx
···
3
3
import {msg, Trans} from '@lingui/macro'
4
4
import {useLingui} from '@lingui/react'
5
5
6
+
import {useInterestsDisplayNames} from '#/lib/interests'
6
7
import {Nux, useSaveNux} from '#/state/queries/nuxs'
7
8
import {usePreferencesQuery} from '#/state/queries/preferences'
8
-
import {useInterestsDisplayNames} from '#/screens/Onboarding/state'
9
9
import {atoms as a, useTheme} from '#/alf'
10
10
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
11
11
import {Shapes_Stroke2_Corner0_Rounded as Shapes} from '#/components/icons/Shapes'
+1
-4
src/screens/Search/modules/ExploreSuggestedAccounts.tsx
+1
-4
src/screens/Search/modules/ExploreSuggestedAccounts.tsx
···
5
5
import {useLingui} from '@lingui/react'
6
6
import {type InfiniteData} from '@tanstack/react-query'
7
7
8
+
import {popularInterests, useInterestsDisplayNames} from '#/lib/interests'
8
9
import {logger} from '#/logger'
9
10
import {usePreferencesQuery} from '#/state/queries/preferences'
10
11
import {BlockDrawerGesture} from '#/view/shell/BlockDrawerGesture'
11
-
import {
12
-
popularInterests,
13
-
useInterestsDisplayNames,
14
-
} from '#/screens/Onboarding/state'
15
12
import {useTheme} from '#/alf'
16
13
import {atoms as a} from '#/alf'
17
14
import {boostInterests, InterestTabs} from '#/components/InterestTabs'
+1
-1
src/screens/Search/util/useSuggestedUsers.ts
+1
-1
src/screens/Search/util/useSuggestedUsers.ts
···
1
1
import {useMemo} from 'react'
2
2
3
+
import {useInterestsDisplayNames} from '#/lib/interests'
3
4
import {useActorSearchPaginated} from '#/state/queries/actor-search'
4
5
import {useGetSuggestedUsersQuery} from '#/state/queries/trending/useGetSuggestedUsersQuery'
5
-
import {useInterestsDisplayNames} from '#/screens/Onboarding/state'
6
6
7
7
/**
8
8
* Conditional hook, used in case a user is a non-english speaker, in which
+7
-29
src/screens/Settings/InterestsSettings.tsx
+7
-29
src/screens/Settings/InterestsSettings.tsx
···
6
6
import {useQueryClient} from '@tanstack/react-query'
7
7
import debounce from 'lodash.debounce'
8
8
9
+
import {
10
+
type Interest,
11
+
interests as allInterests,
12
+
useInterestsDisplayNames,
13
+
} from '#/lib/interests'
9
14
import {type CommonNavigatorParams} from '#/lib/routes/types'
10
15
import {
11
16
preferencesQueryKey,
···
17
22
import {createSuggestedStarterPacksQueryKey} from '#/state/queries/useSuggestedStarterPacksQuery'
18
23
import {useAgent} from '#/state/session'
19
24
import * as Toast from '#/view/com/util/Toast'
20
-
import {useInterestsDisplayNames} from '#/screens/Onboarding/state'
21
25
import {atoms as a, useGutters, useTheme} from '#/alf'
22
26
import {Admonition} from '#/components/Admonition'
23
27
import {Divider} from '#/components/Divider'
···
160
164
onChange={onChangeInterests}
161
165
label={_(msg`Select your interests from the options below`)}>
162
166
<View style={[a.flex_row, a.flex_wrap, a.gap_sm]}>
163
-
{INTERESTS.map(interest => {
167
+
{allInterests.map(interest => {
164
168
const name = interestsDisplayNames[interest]
165
169
if (!name) return null
166
170
return (
···
178
182
)
179
183
}
180
184
181
-
export function InterestButton({interest}: {interest: string}) {
185
+
export function InterestButton({interest}: {interest: Interest}) {
182
186
const t = useTheme()
183
187
const interestsDisplayNames = useInterestsDisplayNames()
184
188
const ctx = Toggle.useItemContext()
···
230
234
</View>
231
235
)
232
236
}
233
-
234
-
const INTERESTS = [
235
-
'animals',
236
-
'art',
237
-
'books',
238
-
'comedy',
239
-
'comics',
240
-
'culture',
241
-
'dev',
242
-
'education',
243
-
'food',
244
-
'gaming',
245
-
'journalism',
246
-
'movies',
247
-
'music',
248
-
'nature',
249
-
'news',
250
-
'pets',
251
-
'photography',
252
-
'politics',
253
-
'science',
254
-
'sports',
255
-
'tech',
256
-
'tv',
257
-
'writers',
258
-
]
+28
-6
src/state/queries/trending/useGetSuggestedUsersQuery.ts
+28
-6
src/state/queries/trending/useGetSuggestedUsersQuery.ts
···
8
8
aggregateUserInterests,
9
9
createBskyTopicsHeader,
10
10
} from '#/lib/api/feed/utils'
11
+
import {logger} from '#/logger'
11
12
import {getContentLanguages} from '#/state/preferences/languages'
12
13
import {STALE} from '#/state/queries'
13
14
import {usePreferencesQuery} from '#/state/queries/preferences'
···
38
39
queryKey: createGetSuggestedUsersQueryKey(props),
39
40
queryFn: async () => {
40
41
const contentLangs = getContentLanguages().join(',')
41
-
const interests = aggregateUserInterests(preferences)
42
+
const userInterests = aggregateUserInterests(preferences)
43
+
44
+
const interests =
45
+
props.overrideInterests && props.overrideInterests.length > 0
46
+
? props.overrideInterests.join(',')
47
+
: userInterests
48
+
42
49
const {data} = await agent.app.bsky.unspecced.getSuggestedUsers(
43
50
{
44
51
category: props.category ?? undefined,
···
46
53
},
47
54
{
48
55
headers: {
49
-
...createBskyTopicsHeader(
50
-
props.overrideInterests && props.overrideInterests.length > 0
51
-
? props.overrideInterests.join(',')
52
-
: interests,
53
-
),
56
+
...createBskyTopicsHeader(interests),
54
57
'Accept-Language': contentLangs,
55
58
},
56
59
},
57
60
)
61
+
// FALLBACK: if no results for 'all', try again with no interests specified
62
+
if (!props.category && data.actors.length === 0) {
63
+
logger.error(
64
+
`Did not get any suggested users, falling back - interests: ${interests}`,
65
+
)
66
+
const {data: fallbackData} =
67
+
await agent.app.bsky.unspecced.getSuggestedUsers(
68
+
{
69
+
category: props.category ?? undefined,
70
+
limit: props.limit || 10,
71
+
},
72
+
{
73
+
headers: {
74
+
'Accept-Language': contentLangs,
75
+
},
76
+
},
77
+
)
78
+
return fallbackData
79
+
}
58
80
59
81
return data
60
82
},