diff --git a/src/screens/Search/Explore.tsx b/src/screens/Search/Explore.tsx
index 523e5a1ec5..3f2a3cc9f2 100644
--- a/src/screens/Search/Explore.tsx
+++ b/src/screens/Search/Explore.tsx
@@ -1,6 +1,6 @@
import {useCallback, useMemo, useRef, useState} from 'react'
import {View, type ViewabilityConfig, type ViewToken} from 'react-native'
-import {type AppBskyActorDefs, type AppBskyFeedDefs} from '@atproto/api'
+import {type AppBskyActorDefs, type AppBskyFeedDefs, type AppBskyGraphDefs} from '@atproto/api'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
@@ -25,10 +25,12 @@ import {ExploreTrendingVideos} from '#/screens/Search/modules/ExploreTrendingVid
import {atoms as a, useTheme} from '#/alf'
import {Button} from '#/components/Button'
import * as FeedCard from '#/components/FeedCard'
+import {StarterPackCard} from '#/screens/Search/components/StarterPackCard'
import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDownIcon} from '#/components/icons/Chevron'
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
import {type Props as SVGIconProps} from '#/components/icons/common'
import {ListSparkle_Stroke2_Corner0_Rounded as ListSparkle} from '#/components/icons/ListSparkle'
+import {StarterPack} from '#/components/icons/StarterPack'
import {UserCircle_Stroke2_Corner0_Rounded as Person} from '#/components/icons/UserCircle'
import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography'
@@ -38,6 +40,7 @@ import {
SuggestedProfileCard,
useLoadEnoughProfiles,
} from './modules/ExploreSuggestedAccounts'
+import {useSuggestedStarterPacksQuery} from '#/state/queries/useSuggestedStarterPacksQuery'
function LoadMore({item}: {item: ExploreScreenItems & {type: 'loadMore'}}) {
const t = useTheme()
@@ -152,6 +155,11 @@ type ExploreScreenItems =
message: string
error: string
}
+ | {
+ type: 'starterPack',
+ key: string
+ view: AppBskyGraphDefs.StarterPackViewBasic
+ }
export function Explore({
focusSearchInput,
@@ -235,6 +243,11 @@ export function Explore({
profilesError,
fetchNextProfilesPage,
])
+ const {
+ data: suggestedSPs,
+ isLoading: isLoadingSuggestedSPs,
+ error: suggestedSPsError,
+ } = useSuggestedStarterPacksQuery()
const isLoadingMoreFeeds = isFetchingNextFeedsPage && !isLoadingFeeds
const [hasPressedLoadMoreFeeds, setHasPressedLoadMoreFeeds] = useState(false)
@@ -433,16 +446,47 @@ export function Explore({
}
}
+ const addSuggestedStarterPacksModule = () => {
+ i.push({
+ type: 'header',
+ key: 'suggested-starterPacks-header',
+ title: _(msg`Starter Packs`),
+ icon: StarterPack,
+ })
+
+ if (isLoadingSuggestedSPs) {
+ // nothing
+ } else if (suggestedSPsError || !suggestedSPs) {
+ i.pop()
+ // i.push({
+ // type: 'error',
+ // key: 'suggestedSPsError',
+ // message: _(msg`Failed to load suggested starter packs`),
+ // error: cleanError(suggestedSPsError),
+ // })
+ } else {
+ suggestedSPs.map(s => {
+ i.push({
+ type: 'starterPack',
+ key: s.uri,
+ view: s,
+ })
+ })
+ }
+ }
+
// Dynamic module ordering
addTopBorder()
if (guide?.guide === 'follow-10' && !guide.isComplete) {
addSuggestedFollowsModule()
+ addSuggestedStarterPacksModule()
addTrendingTopicsModule()
} else {
addTrendingTopicsModule()
addSuggestedFollowsModule()
+ addSuggestedStarterPacksModule()
}
if (gate('explore_show_suggested_feeds')) {
@@ -547,6 +591,19 @@ export function Explore({
)
}
+ case 'starterPack': {
+ return (
+
+
+
+ )
+ }
case 'loadMore': {
return (
diff --git a/src/screens/Search/components/StarterPackCard.tsx b/src/screens/Search/components/StarterPackCard.tsx
new file mode 100644
index 0000000000..bb39dc1553
--- /dev/null
+++ b/src/screens/Search/components/StarterPackCard.tsx
@@ -0,0 +1,57 @@
+import React from 'react'
+import {View} from 'react-native'
+import {AppBskyGraphDefs} from '@atproto/api'
+import {Image} from 'expo-image'
+import {useLingui} from '@lingui/react'
+import {Trans, msg} from '@lingui/macro'
+
+import {getStarterPackOgCard} from '#/lib/strings/starter-pack'
+import * as Card from '#/components/StarterPack/StarterPackCard'
+import {atoms as a, useTheme, useBreakpoints} from '#/alf'
+import {Button, ButtonText} from '#/components/Button'
+
+export function StarterPackCard({view}: {view: AppBskyGraphDefs.StarterPackViewBasic}) {
+ const t = useTheme()
+ const {_} = useLingui()
+ const imageUri = getStarterPackOgCard(view)
+ const {gtPhone} = useBreakpoints()
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/state/queries/useSuggestedStarterPacksQuery.ts b/src/state/queries/useSuggestedStarterPacksQuery.ts
new file mode 100644
index 0000000000..f40d511469
--- /dev/null
+++ b/src/state/queries/useSuggestedStarterPacksQuery.ts
@@ -0,0 +1,102 @@
+import {useQuery} from '@tanstack/react-query'
+import {AppBskyGraphDefs} from '@atproto/api'
+
+import {useAgent} from '#/state/session'
+
+export const createSuggestedStarterPacksQueryKey = () => [
+ 'suggested-starter-packs',
+]
+
+export function useSuggestedStarterPacksQuery() {
+ const agent = useAgent()
+
+ return useQuery({
+ queryKey: createSuggestedStarterPacksQueryKey(),
+ async queryFn() {
+ // // @ts-expect-error
+ // const {data} = await agent.app.bsky.unspecced.getSuggestedStarterPacks()
+ // const {starterPacks} = data
+ const {starterPacks} = {starterPacks: [temp]}
+ return starterPacks as AppBskyGraphDefs.StarterPackViewBasic[]
+ },
+ })
+}
+
+const temp = {
+ uri: 'at://did:plc:dpajgwmnecpdyjyqzjzm6bnb/app.bsky.graph.starterpack/3l3bhtqkjfo2m',
+ cid: 'bafyreifvtjkrvuq3qcyz32p6lacdxbwhcmux5amnhokfputfarlpuyqqhu',
+ record: {
+ $type: 'app.bsky.graph.starterpack',
+ createdAt: '2024-09-03T18:44:43.158Z',
+ feeds: [
+ {
+ avatar:
+ 'https://cdn.bsky.app/img/avatar/plain/did:plc:z72i7hdynmk6r22z27h6tvur/bafkreieppnjpayqgjbyqnbeqzwj3pzqtiwbe5a4m6whzqnp3k7gpqs2gai@jpeg',
+ cid: 'bafyreigreonzn577vy6i4qh2so7aqfjztqrrj4jpssg2jczc27p6x4y6wi',
+ creator: {
+ associated: {
+ chat: {
+ allowIncoming: 'none',
+ },
+ },
+ avatar:
+ 'https://cdn.bsky.app/img/avatar/plain/did:plc:z72i7hdynmk6r22z27h6tvur/bafkreihagr2cmvl2jt4mgx3sppwe2it3fwolkrbtjrhcnwjk4jdijhsoze@jpeg',
+ createdAt: '2023-04-12T04:53:57.057Z',
+ description:
+ 'official Bluesky account (check username👆)\n\nBugs, feature requests, feedback: support@bsky.app',
+ did: 'did:plc:z72i7hdynmk6r22z27h6tvur',
+ displayName: 'Bluesky',
+ handle: 'bsky.app',
+ indexedAt: '2024-12-18T07:43:57.513Z',
+ labels: [
+ {
+ cts: '2024-05-31T04:01:47.907Z',
+ src: 'did:plc:z3yk2cflhmn6vmzo3f5ixqh4',
+ uri: 'did:plc:z72i7hdynmk6r22z27h6tvur',
+ val: 'cornerstone',
+ ver: 1,
+ },
+ ],
+ viewer: {
+ blockedBy: false,
+ muted: false,
+ },
+ },
+ description:
+ 'A mix of popular content from accounts you follow and content that your follows like.',
+ did: 'did:web:discover.bsky.app',
+ displayName: 'Popular With Friends',
+ indexedAt: '2023-05-19T23:19:21.076Z',
+ labels: [],
+ likeCount: 33616,
+ uri: 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/with-friends',
+ viewer: {},
+ },
+ ],
+ list: 'at://did:plc:dpajgwmnecpdyjyqzjzm6bnb/app.bsky.graph.list/3l3bhtqgyrn2e',
+ name: "Tom Sawyeeeeeee's Starter Pack",
+ updatedAt: '2024-12-19T22:44:58.411Z',
+ },
+ creator: {
+ did: 'did:plc:dpajgwmnecpdyjyqzjzm6bnb',
+ handle: 'test.esb.lol',
+ displayName: 'e',
+ avatar:
+ 'https://cdn.bsky.app/img/avatar/plain/did:plc:dpajgwmnecpdyjyqzjzm6bnb/bafkreia6dx7fhoi6fxwfpgm7jrxijpqci7ap53wpilkpazojwvqlmgud2m@jpeg',
+ associated: {
+ chat: {
+ allowIncoming: 'all',
+ },
+ },
+ viewer: {
+ muted: false,
+ blockedBy: false,
+ },
+ labels: [],
+ createdAt: '2023-07-17T15:57:52.014Z',
+ },
+ joinedAllTimeCount: 1,
+ joinedWeekCount: 0,
+ labels: [],
+ indexedAt: '2024-12-19T22:45:06.322Z',
+}