+2
-2
src/components/FeedCard.tsx
+2
-2
src/components/FeedCard.tsx
···
21
21
import {useSession} from '#/state/session'
22
22
import * as Toast from '#/view/com/util/Toast'
23
23
import {UserAvatar} from '#/view/com/util/UserAvatar'
24
-
import {useTheme} from '#/alf'
25
-
import {atoms as a} from '#/alf'
24
+
import {atoms as a, useTheme} from '#/alf'
26
25
import {
27
26
Button,
28
27
ButtonIcon,
···
40
39
41
40
type Props = {
42
41
view: AppBskyFeedDefs.GeneratorView
42
+
onPress?: () => void
43
43
}
44
44
45
45
export function Default(props: Props) {
+6
src/logger/metrics.ts
+6
src/logger/metrics.ts
+178
-92
src/screens/Search/Explore.tsx
+178
-92
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 {useGate} from '#/lib/statsig/statsig'
14
13
import {cleanError} from '#/lib/strings/errors'
15
14
import {sanitizeHandle} from '#/lib/strings/handles'
16
15
import {logger} from '#/logger'
···
35
34
createSuggestedStarterPacksQueryKey,
36
35
useSuggestedStarterPacksQuery,
37
36
} from '#/state/queries/useSuggestedStarterPacksQuery'
38
-
import {useProgressGuide} from '#/state/shell/progress-guide'
39
37
import {isThreadChildAt, isThreadParentAt} from '#/view/com/posts/PostFeed'
40
38
import {PostFeedItem} from '#/view/com/posts/PostFeedItem'
41
39
import {ViewFullThread} from '#/view/com/posts/ViewFullThread'
···
61
59
import * as FeedCard from '#/components/FeedCard'
62
60
import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDownIcon} from '#/components/icons/Chevron'
63
61
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
64
-
import {type Props as IcoProps} from '#/components/icons/common'
65
-
import {type Props as SVGIconProps} from '#/components/icons/common'
62
+
import {
63
+
type Props as IcoProps,
64
+
type Props as SVGIconProps,
65
+
} from '#/components/icons/common'
66
66
import {ListSparkle_Stroke2_Corner0_Rounded as ListSparkle} from '#/components/icons/ListSparkle'
67
67
import {StarterPack} from '#/components/icons/StarterPack'
68
-
import {Trending2_Stroke2_Corner2_Rounded as Graph} from '#/components/icons/Trending'
69
68
import {UserCircle_Stroke2_Corner0_Rounded as Person} from '#/components/icons/UserCircle'
70
69
import {Loader} from '#/components/Loader'
71
70
import * as ProfileCard from '#/components/ProfileCard'
···
216
215
const t = useTheme()
217
216
const {data: preferences, error: preferencesError} = usePreferencesQuery()
218
217
const moderationOpts = useModerationOpts()
219
-
const gate = useGate()
220
-
const guide = useProgressGuide('follow-10')
221
218
const [selectedInterest, setSelectedInterest] = useState<string | null>(null)
222
219
223
220
/*
···
284
281
hasPressedLoadMoreFeeds,
285
282
])
286
283
287
-
const {data: suggestedFeeds} = useGetSuggestedFeedsQuery({
288
-
enabled: useFullExperience,
289
-
})
284
+
const {data: suggestedFeeds, error: suggestedFeedsError} =
285
+
useGetSuggestedFeedsQuery({
286
+
enabled: useFullExperience,
287
+
})
290
288
const {
291
289
data: feedPreviewSlices,
292
290
query: {
···
442
440
},
443
441
})
444
442
445
-
if (feeds && preferences) {
446
-
// Currently the responses contain duplicate items.
447
-
// Needs to be fixed on backend, but let's dedupe to be safe.
448
-
let seen = new Set()
449
-
const feedItems: ExploreScreenItems[] = []
450
-
for (const page of feeds.pages) {
451
-
for (const feed of page.feeds) {
443
+
if (useFullExperience) {
444
+
if (suggestedFeeds && preferences) {
445
+
let seen = new Set()
446
+
const feedItems: ExploreScreenItems[] = []
447
+
for (const feed of suggestedFeeds.feeds) {
452
448
if (!seen.has(feed.uri)) {
453
449
seen.add(feed.uri)
454
450
feedItems.push({
···
458
454
})
459
455
}
460
456
}
461
-
}
462
457
463
-
// feeds errors can occur during pagination, so feeds is truthy
464
-
if (feedsError) {
465
-
i.push({
466
-
type: 'error',
467
-
key: 'feedsError',
468
-
message: _(msg`Failed to load suggested feeds`),
469
-
error: cleanError(feedsError),
470
-
})
471
-
} else if (preferencesError) {
472
-
i.push({
473
-
type: 'error',
474
-
key: 'preferencesError',
475
-
message: _(msg`Failed to load feeds preferences`),
476
-
error: cleanError(preferencesError),
477
-
})
478
-
} else {
479
-
if (feedItems.length === 0) {
480
-
if (!hasNextFeedsPage) {
458
+
// feeds errors can occur during pagination, so feeds is truthy
459
+
if (suggestedFeedsError) {
460
+
i.push({
461
+
type: 'error',
462
+
key: 'feedsError',
463
+
message: _(msg`Failed to load suggested feeds`),
464
+
error: cleanError(feedsError),
465
+
})
466
+
} else if (preferencesError) {
467
+
i.push({
468
+
type: 'error',
469
+
key: 'preferencesError',
470
+
message: _(msg`Failed to load feeds preferences`),
471
+
error: cleanError(preferencesError),
472
+
})
473
+
} else {
474
+
if (feedItems.length === 0) {
481
475
i.pop()
476
+
} else {
477
+
// This query doesn't follow the limit very well, so the first press of the
478
+
// load more button just unslices the array back to ~10 items
479
+
if (!hasPressedLoadMoreFeeds) {
480
+
i.push(...feedItems.slice(0, 6))
481
+
} else {
482
+
i.push(...feedItems)
483
+
}
484
+
485
+
for (const [index, item] of feedItems.entries()) {
486
+
if (item.type !== 'feed') {
487
+
continue
488
+
}
489
+
// don't log the ones we've already sent
490
+
if (hasPressedLoadMoreFeeds && index < 6) {
491
+
continue
492
+
}
493
+
logger.metric(
494
+
'feed:suggestion:seen',
495
+
{feedUrl: item.feed.uri},
496
+
{statsig: false},
497
+
)
498
+
}
482
499
}
483
-
} else {
484
-
// This query doesn't follow the limit very well, so the first press of the
485
-
// load more button just unslices the array back to ~10 items
486
500
if (!hasPressedLoadMoreFeeds) {
487
-
i.push(...feedItems.slice(0, 3))
488
-
} else {
489
-
i.push(...feedItems)
501
+
i.push({
502
+
type: 'loadMore',
503
+
key: 'loadMoreFeeds',
504
+
message: _(msg`Load more suggested feeds`),
505
+
isLoadingMore: isLoadingMoreFeeds,
506
+
onLoadMore: onLoadMoreFeeds,
507
+
})
490
508
}
491
509
}
492
-
if (hasNextFeedsPage) {
510
+
} else {
511
+
if (feedsError) {
512
+
i.push({
513
+
type: 'error',
514
+
key: 'feedsError',
515
+
message: _(msg`Failed to load suggested feeds`),
516
+
error: cleanError(feedsError),
517
+
})
518
+
} else if (preferencesError) {
493
519
i.push({
494
-
type: 'loadMore',
495
-
key: 'loadMoreFeeds',
496
-
message: _(msg`Load more suggested feeds`),
497
-
isLoadingMore: isLoadingMoreFeeds,
498
-
onLoadMore: onLoadMoreFeeds,
520
+
type: 'error',
521
+
key: 'preferencesError',
522
+
message: _(msg`Failed to load feeds preferences`),
523
+
error: cleanError(preferencesError),
499
524
})
525
+
} else {
526
+
i.push({type: 'feedPlaceholder', key: 'feedPlaceholder'})
500
527
}
501
528
}
502
529
} else {
503
-
if (feedsError) {
504
-
i.push({
505
-
type: 'error',
506
-
key: 'feedsError',
507
-
message: _(msg`Failed to load suggested feeds`),
508
-
error: cleanError(feedsError),
509
-
})
510
-
} else if (preferencesError) {
511
-
i.push({
512
-
type: 'error',
513
-
key: 'preferencesError',
514
-
message: _(msg`Failed to load feeds preferences`),
515
-
error: cleanError(preferencesError),
516
-
})
530
+
if (feeds && preferences) {
531
+
// Currently the responses contain duplicate items.
532
+
// Needs to be fixed on backend, but let's dedupe to be safe.
533
+
let seen = new Set()
534
+
const feedItems: ExploreScreenItems[] = []
535
+
for (const page of feeds.pages) {
536
+
for (const feed of page.feeds) {
537
+
if (!seen.has(feed.uri)) {
538
+
seen.add(feed.uri)
539
+
feedItems.push({
540
+
type: 'feed',
541
+
key: feed.uri,
542
+
feed,
543
+
})
544
+
}
545
+
}
546
+
}
547
+
548
+
// feeds errors can occur during pagination, so feeds is truthy
549
+
if (feedsError) {
550
+
i.push({
551
+
type: 'error',
552
+
key: 'feedsError',
553
+
message: _(msg`Failed to load suggested feeds`),
554
+
error: cleanError(feedsError),
555
+
})
556
+
} else if (preferencesError) {
557
+
i.push({
558
+
type: 'error',
559
+
key: 'preferencesError',
560
+
message: _(msg`Failed to load feeds preferences`),
561
+
error: cleanError(preferencesError),
562
+
})
563
+
} else {
564
+
if (feedItems.length === 0) {
565
+
if (!hasNextFeedsPage) {
566
+
i.pop()
567
+
}
568
+
} else {
569
+
// This query doesn't follow the limit very well, so the first press of the
570
+
// load more button just unslices the array back to ~10 items
571
+
if (!hasPressedLoadMoreFeeds) {
572
+
i.push(...feedItems.slice(0, 3))
573
+
} else {
574
+
i.push(...feedItems)
575
+
}
576
+
}
577
+
if (hasNextFeedsPage) {
578
+
i.push({
579
+
type: 'loadMore',
580
+
key: 'loadMoreFeeds',
581
+
message: _(msg`Load more suggested feeds`),
582
+
isLoadingMore: isLoadingMoreFeeds,
583
+
onLoadMore: onLoadMoreFeeds,
584
+
})
585
+
}
586
+
}
517
587
} else {
518
-
i.push({type: 'feedPlaceholder', key: 'feedPlaceholder'})
588
+
if (feedsError) {
589
+
i.push({
590
+
type: 'error',
591
+
key: 'feedsError',
592
+
message: _(msg`Failed to load suggested feeds`),
593
+
error: cleanError(feedsError),
594
+
})
595
+
} else if (preferencesError) {
596
+
i.push({
597
+
type: 'error',
598
+
key: 'preferencesError',
599
+
message: _(msg`Failed to load feeds preferences`),
600
+
error: cleanError(preferencesError),
601
+
})
602
+
} else {
603
+
i.push({type: 'feedPlaceholder', key: 'feedPlaceholder'})
604
+
}
519
605
}
520
606
}
521
607
return i
522
608
}, [
523
-
feeds,
524
609
_,
610
+
useFullExperience,
611
+
suggestedFeeds,
612
+
preferences,
613
+
suggestedFeedsError,
614
+
preferencesError,
525
615
feedsError,
526
616
hasNextFeedsPage,
527
617
hasPressedLoadMoreFeeds,
528
618
isLoadingMoreFeeds,
529
619
onLoadMoreFeeds,
530
-
preferences,
531
-
preferencesError,
620
+
feeds,
532
621
])
622
+
533
623
const suggestedStarterPacksModule = useMemo(() => {
534
624
const i: ExploreScreenItems[] = []
535
625
i.push({
···
589
679
]
590
680
}, [showInterestsNux])
591
681
592
-
const isNewUser = guide?.guide === 'follow-10' && !guide.isComplete
593
682
const items = useMemo<ExploreScreenItems[]>(() => {
594
683
const i: ExploreScreenItems[] = []
595
684
···
599
688
i.push(...interestsNuxModule)
600
689
601
690
if (useFullExperience) {
602
-
if (isNewUser) {
603
-
i.push(...suggestedFollowsModule)
604
-
i.push(...suggestedStarterPacksModule)
605
-
i.push({
606
-
type: 'header',
607
-
key: 'trending-topics-header',
608
-
title: _(msg`Trending topics`),
609
-
icon: Graph,
610
-
bottomBorder: true,
611
-
})
612
-
i.push(trendingTopicsModule)
613
-
} else {
614
-
i.push(trendingTopicsModule)
615
-
i.push(...suggestedFollowsModule)
616
-
i.push(...suggestedStarterPacksModule)
617
-
}
618
-
if (gate('explore_show_suggested_feeds')) {
619
-
i.push(...suggestedFeedsModule)
620
-
}
691
+
i.push(trendingTopicsModule)
692
+
i.push(...suggestedFeedsModule)
693
+
i.push(...suggestedFollowsModule)
694
+
i.push(...suggestedStarterPacksModule)
621
695
i.push(...feedPreviewsModule)
622
696
} else {
623
697
i.push(...suggestedFollowsModule)
···
625
699
626
700
return i
627
701
}, [
628
-
_,
629
702
topBorder,
630
-
isNewUser,
631
703
suggestedFollowsModule,
632
704
suggestedStarterPacksModule,
633
705
suggestedFeedsModule,
634
706
trendingTopicsModule,
635
707
feedPreviewsModule,
636
708
interestsNuxModule,
637
-
gate,
638
709
useFullExperience,
639
710
])
640
711
···
731
802
a.px_lg,
732
803
a.py_lg,
733
804
]}>
734
-
<FeedCard.Default view={item.feed} />
805
+
<FeedCard.Default
806
+
view={item.feed}
807
+
onPress={() => {
808
+
if (!useFullExperience) {
809
+
return
810
+
}
811
+
logger.metric('feed:suggestion:press', {
812
+
feedUrl: item.feed.uri,
813
+
})
814
+
}}
815
+
/>
735
816
</View>
736
817
)
737
818
}
···
918
999
}
919
1000
},
920
1001
[
921
-
t,
1002
+
t.atoms.border_contrast_low,
1003
+
t.atoms.bg_contrast_25,
1004
+
t.atoms.text_contrast_medium,
1005
+
t.atoms.bg,
1006
+
t.palette.negative_400,
922
1007
focusSearchInput,
923
-
moderationOpts,
924
1008
selectedInterest,
1009
+
moderationOpts,
925
1010
interestsDisplayNames,
1011
+
useFullExperience,
926
1012
_,
927
1013
fetchNextPageFeedPreviews,
928
1014
],
+1
-1
src/state/queries/trending/useGetSuggestedFeedsQuery.ts
+1
-1
src/state/queries/trending/useGetSuggestedFeedsQuery.ts