diff options
-rw-r--r-- | src/screens/Search/Explore.tsx | 24 | ||||
-rw-r--r-- | src/screens/Search/modules/ExploreInterestsCard.tsx | 128 | ||||
-rw-r--r-- | src/screens/Settings/SettingsInterests.tsx | 50 | ||||
-rw-r--r-- | src/state/queries/nuxs/definitions.ts | 20 |
4 files changed, 195 insertions, 27 deletions
diff --git a/src/screens/Search/Explore.tsx b/src/screens/Search/Explore.tsx index 00eb6c433..088bc5724 100644 --- a/src/screens/Search/Explore.tsx +++ b/src/screens/Search/Explore.tsx @@ -21,6 +21,7 @@ import { useFeedPreviews, } from '#/state/queries/explore-feed-previews' import {useGetPopularFeedsQuery} from '#/state/queries/feed' +import {Nux, useNux} from '#/state/queries/nuxs' import {usePreferencesQuery} from '#/state/queries/preferences' import {useSuggestedFollowsQuery} from '#/state/queries/suggested-follows' import {useGetSuggestedFeedsQuery} from '#/state/queries/trending/useGetSuggestedFeedsQuery' @@ -36,6 +37,7 @@ import { StarterPackCard, StarterPackCardSkeleton, } from '#/screens/Search/components/StarterPackCard' +import {ExploreInterestsCard} from '#/screens/Search/modules/ExploreInterestsCard' import {ExploreRecommendations} from '#/screens/Search/modules/ExploreRecommendations' import {ExploreTrendingTopics} from '#/screens/Search/modules/ExploreTrendingTopics' import {ExploreTrendingVideos} from '#/screens/Search/modules/ExploreTrendingVideos' @@ -181,6 +183,10 @@ type ExploreScreenItems = key: string } | FeedPreviewItem + | { + type: 'interests-card' + key: 'interests-card' + } export function Explore({ focusSearchInput, @@ -233,6 +239,9 @@ export function Explore({ error: feedsError, fetchNextPage: fetchNextFeedsPage, } = useGetPopularFeedsQuery({limit: 10}) + const interestsNux = useNux(Nux.ExploreInterestsCard) + const showInterestsNux = + interestsNux.status === 'ready' && !interestsNux.nux?.completed const profiles: typeof suggestedProfiles & typeof interestProfiles = !selectedInterest ? suggestedProfiles : interestProfiles @@ -558,6 +567,16 @@ export function Explore({ return i }, [feedPreviewSlices, isFetchingNextPageFeedPreviews]) + const interestsNuxModule = useMemo<ExploreScreenItems[]>(() => { + if (!showInterestsNux) return [] + return [ + { + type: 'interests-card', + key: 'interests-card', + }, + ] + }, [showInterestsNux]) + const isNewUser = guide?.guide === 'follow-10' && !guide.isComplete const items = useMemo<ExploreScreenItems[]>(() => { const i: ExploreScreenItems[] = [] @@ -565,6 +584,7 @@ export function Explore({ // Dynamic module ordering i.push(topBorder) + i.push(...interestsNuxModule) if (isNewUser) { i.push(...suggestedFollowsModule) i.push(...suggestedStarterPacksModule) @@ -588,6 +608,7 @@ export function Explore({ suggestedFeedsModule, trendingTopicsModule, feedPreviewsModule, + interestsNuxModule, gate, ]) @@ -854,6 +875,9 @@ export function Explore({ /> ) } + case 'interests-card': { + return <ExploreInterestsCard /> + } } }, [ diff --git a/src/screens/Search/modules/ExploreInterestsCard.tsx b/src/screens/Search/modules/ExploreInterestsCard.tsx new file mode 100644 index 000000000..fde5c3b1e --- /dev/null +++ b/src/screens/Search/modules/ExploreInterestsCard.tsx @@ -0,0 +1,128 @@ +import {useState} from 'react' +import {View} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {Nux, useSaveNux} from '#/state/queries/nuxs' +import {usePreferencesQuery} from '#/state/queries/preferences' +import {useInterestsDisplayNames} from '#/screens/Onboarding/state' +import {atoms as a, useGutters, useTheme} from '#/alf' +import {Button, ButtonIcon, ButtonText} from '#/components/Button' +import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' +import {Link} from '#/components/Link' +import * as Prompt from '#/components/Prompt' +import {Text} from '#/components/Typography' + +export function ExploreInterestsCard() { + const t = useTheme() + const {_} = useLingui() + const gutters = useGutters([0, 'base']) + const {data: preferences} = usePreferencesQuery() + const interestsDisplayNames = useInterestsDisplayNames() + const {mutateAsync: saveNux} = useSaveNux() + const trendingPrompt = Prompt.usePromptControl() + const [closing, setClosing] = useState(false) + + const onClose = () => { + trendingPrompt.open() + } + const onConfirmClose = () => { + setClosing(true) + // if this fails, they can try again later + saveNux({ + id: Nux.ExploreInterestsCard, + completed: true, + data: undefined, + }).catch(() => {}) + } + + return closing ? null : ( + <> + <Prompt.Basic + control={trendingPrompt} + title={_(msg`Your interests`)} + description={_( + msg`You can adjust your interests at any time from your "Content and media" settings.`, + )} + confirmButtonCta={_( + msg({ + message: `Copy that!`, + comment: `Confirm button text. Can be a short cheeky phrase that means "OK" e.g. "Copy that!"`, + }), + )} + onConfirm={onConfirmClose} + /> + + <View style={[gutters, a.pt_lg, a.pb_2xs]}> + <View + style={[ + a.p_lg, + a.rounded_md, + a.border, + a.gap_sm, + t.atoms.border_contrast_medium, + t.atoms.bg_contrast_25, + ]}> + <Text style={[a.text_md, a.font_bold, a.leading_tight]}> + <Trans>Your interests</Trans> + </Text> + + {preferences?.interests?.tags && + preferences.interests.tags.length > 0 ? ( + <View style={[a.flex_row, a.flex_wrap, {gap: 6}]}> + {preferences.interests.tags.map(tag => ( + <View + key={tag} + style={[ + a.justify_center, + a.align_center, + a.rounded_full, + a.border, + t.atoms.border_contrast_medium, + a.px_lg, + {height: 32}, + ]}> + <Text style={[a.text_sm, t.atoms.text_contrast_medium]}> + {interestsDisplayNames[tag]} + </Text> + </View> + ))} + </View> + ) : null} + + <Text style={[a.text_sm, a.leading_snug, a.pb_xs]}> + <Trans> + Your selected interests help us serve you content you care about. + </Trans> + </Text> + + <Link + label={_(msg`Edit interests`)} + to="/settings/interests" + size="small" + variant="solid" + color="primary" + style={[a.justify_center]}> + <ButtonText> + <Trans>Edit interests</Trans> + </ButtonText> + </Link> + + <Button + label={_(msg`Hide this card`)} + size="small" + variant="solid" + color="secondary" + shape="round" + onPress={onClose} + style={[ + a.absolute, + {top: a.pt_xs.paddingTop, right: a.pr_xs.paddingRight}, + ]}> + <ButtonIcon icon={X} /> + </Button> + </View> + </View> + </> + ) +} diff --git a/src/screens/Settings/SettingsInterests.tsx b/src/screens/Settings/SettingsInterests.tsx index 266545560..9a6132946 100644 --- a/src/screens/Settings/SettingsInterests.tsx +++ b/src/screens/Settings/SettingsInterests.tsx @@ -14,6 +14,7 @@ import {useAgent} from '#/state/session' import * as Toast from '#/view/com/util/Toast' import {useInterestsDisplayNames} from '#/screens/Onboarding/state' import {atoms as a, useGutters, useTheme} from '#/alf' +import {Admonition} from '#/components/Admonition' import {Divider} from '#/components/Divider' import * as Toggle from '#/components/forms/Toggle' import * as Layout from '#/components/Layout' @@ -47,8 +48,7 @@ export function SettingsInterests() { t.atoms.text_contrast_medium, ]}> <Trans> - Selecting interests from the list below helps us deliver you - higher quality content. + Your selected interests help us serve you content you care about. </Trans> </Text> @@ -124,25 +124,33 @@ function Inner({ } return ( - <Toggle.Group - values={interests} - onChange={onChangeInterests} - label={_(msg`Select your interests from the options below`)}> - <View style={[a.flex_row, a.flex_wrap, a.gap_sm]}> - {INTERESTS.map(interest => { - const name = interestsDisplayNames[interest] - if (!name) return null - return ( - <Toggle.Item - key={interest} - name={interest} - label={interestsDisplayNames[interest]}> - <InterestButton interest={interest} /> - </Toggle.Item> - ) - })} - </View> - </Toggle.Group> + <> + {interests.length === 0 && ( + <Admonition type="tip"> + <Trans>We recommend selecting at least two interests.</Trans> + </Admonition> + )} + + <Toggle.Group + values={interests} + onChange={onChangeInterests} + label={_(msg`Select your interests from the options below`)}> + <View style={[a.flex_row, a.flex_wrap, a.gap_sm]}> + {INTERESTS.map(interest => { + const name = interestsDisplayNames[interest] + if (!name) return null + return ( + <Toggle.Item + key={interest} + name={interest} + label={interestsDisplayNames[interest]}> + <InterestButton interest={interest} /> + </Toggle.Item> + ) + })} + </View> + </Toggle.Group> + </> ) } diff --git a/src/state/queries/nuxs/definitions.ts b/src/state/queries/nuxs/definitions.ts index 8166602c8..8eb53a0a4 100644 --- a/src/state/queries/nuxs/definitions.ts +++ b/src/state/queries/nuxs/definitions.ts @@ -1,18 +1,26 @@ -import zod from 'zod' +import type zod from 'zod' -import {BaseNux} from '#/state/queries/nuxs/types' +import {type BaseNux} from '#/state/queries/nuxs/types' export enum Nux { NeueTypography = 'NeueTypography', + ExploreInterestsCard = 'ExploreInterestsCard', } export const nuxNames = new Set(Object.values(Nux)) -export type AppNux = BaseNux<{ - id: Nux.NeueTypography - data: undefined -}> +export type AppNux = BaseNux< + | { + id: Nux.NeueTypography + data: undefined + } + | { + id: Nux.ExploreInterestsCard + data: undefined + } +> export const NuxSchemas: Record<Nux, zod.ZodObject<any> | undefined> = { [Nux.NeueTypography]: undefined, + [Nux.ExploreInterestsCard]: undefined, } |