mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import React from 'react'
2import {View, StyleSheet, Pressable, ScrollView} from 'react-native'
3import {AppBskyActorDefs, moderateProfile} from '@atproto/api'
4import {
5 FontAwesomeIcon,
6 FontAwesomeIconStyle,
7} from '@fortawesome/react-native-fontawesome'
8
9import * as Toast from '../util/Toast'
10import {usePalette} from 'lib/hooks/usePalette'
11import {Text} from 'view/com/util/text/Text'
12import {UserAvatar} from 'view/com/util/UserAvatar'
13import {Button} from 'view/com/util/forms/Button'
14import {sanitizeDisplayName} from 'lib/strings/display-names'
15import {sanitizeHandle} from 'lib/strings/handles'
16import {makeProfileLink} from 'lib/routes/links'
17import {Link} from 'view/com/util/Link'
18import {useAnalytics} from 'lib/analytics/analytics'
19import {isWeb} from 'platform/detection'
20import {useModerationOpts} from '#/state/queries/preferences'
21import {useSuggestedFollowsByActorQuery} from '#/state/queries/suggested-follows'
22import {useProfileShadow} from '#/state/cache/profile-shadow'
23import {useProfileFollowMutationQueue} from '#/state/queries/profile'
24import {Trans} from '@lingui/macro'
25
26const OUTER_PADDING = 10
27const INNER_PADDING = 14
28const TOTAL_HEIGHT = 250
29
30export function ProfileHeaderSuggestedFollows({
31 actorDid,
32 requestDismiss,
33}: {
34 actorDid: string
35 requestDismiss: () => void
36}) {
37 const pal = usePalette('default')
38 const {isLoading, data} = useSuggestedFollowsByActorQuery({
39 did: actorDid,
40 })
41 return (
42 <View
43 style={{paddingVertical: OUTER_PADDING, height: TOTAL_HEIGHT}}
44 pointerEvents="box-none">
45 <View
46 pointerEvents="box-none"
47 style={{
48 backgroundColor: pal.viewLight.backgroundColor,
49 height: '100%',
50 paddingTop: INNER_PADDING / 2,
51 }}>
52 <View
53 pointerEvents="box-none"
54 style={{
55 flexDirection: 'row',
56 justifyContent: 'space-between',
57 alignItems: 'center',
58 paddingTop: 4,
59 paddingBottom: INNER_PADDING / 2,
60 paddingLeft: INNER_PADDING,
61 paddingRight: INNER_PADDING / 2,
62 }}>
63 <Text type="sm-bold" style={[pal.textLight]}>
64 <Trans>Suggested for you</Trans>
65 </Text>
66
67 <Pressable
68 accessibilityRole="button"
69 onPress={requestDismiss}
70 hitSlop={10}
71 style={{padding: INNER_PADDING / 2}}>
72 <FontAwesomeIcon
73 icon="x"
74 size={12}
75 style={pal.textLight as FontAwesomeIconStyle}
76 />
77 </Pressable>
78 </View>
79
80 <ScrollView
81 horizontal={true}
82 showsHorizontalScrollIndicator={isWeb}
83 persistentScrollbar={true}
84 scrollIndicatorInsets={{bottom: 0}}
85 scrollEnabled={true}
86 contentContainerStyle={{
87 alignItems: 'flex-start',
88 paddingLeft: INNER_PADDING / 2,
89 paddingBottom: INNER_PADDING,
90 }}>
91 {isLoading ? (
92 <>
93 <SuggestedFollowSkeleton />
94 <SuggestedFollowSkeleton />
95 <SuggestedFollowSkeleton />
96 <SuggestedFollowSkeleton />
97 <SuggestedFollowSkeleton />
98 <SuggestedFollowSkeleton />
99 </>
100 ) : data ? (
101 data.suggestions.map(profile => (
102 <SuggestedFollow key={profile.did} profile={profile} />
103 ))
104 ) : (
105 <View />
106 )}
107 </ScrollView>
108 </View>
109 </View>
110 )
111}
112
113function SuggestedFollowSkeleton() {
114 const pal = usePalette('default')
115 return (
116 <View
117 style={[
118 styles.suggestedFollowCardOuter,
119 {
120 backgroundColor: pal.view.backgroundColor,
121 },
122 ]}>
123 <View
124 style={{
125 height: 60,
126 width: 60,
127 borderRadius: 60,
128 backgroundColor: pal.viewLight.backgroundColor,
129 opacity: 0.6,
130 }}
131 />
132 <View
133 style={{
134 height: 17,
135 width: 70,
136 borderRadius: 4,
137 backgroundColor: pal.viewLight.backgroundColor,
138 marginTop: 12,
139 marginBottom: 4,
140 }}
141 />
142 <View
143 style={{
144 height: 12,
145 width: 70,
146 borderRadius: 4,
147 backgroundColor: pal.viewLight.backgroundColor,
148 marginBottom: 12,
149 opacity: 0.6,
150 }}
151 />
152 <View
153 style={{
154 height: 32,
155 borderRadius: 32,
156 width: '100%',
157 backgroundColor: pal.viewLight.backgroundColor,
158 }}
159 />
160 </View>
161 )
162}
163
164function SuggestedFollow({
165 profile: profileUnshadowed,
166}: {
167 profile: AppBskyActorDefs.ProfileView
168}) {
169 const {track} = useAnalytics()
170 const pal = usePalette('default')
171 const moderationOpts = useModerationOpts()
172 const profile = useProfileShadow(profileUnshadowed)
173 const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(profile)
174
175 const onPressFollow = React.useCallback(async () => {
176 try {
177 track('ProfileHeader:SuggestedFollowFollowed')
178 await queueFollow()
179 } catch (e: any) {
180 if (e?.name !== 'AbortError') {
181 Toast.show('An issue occurred, please try again.')
182 }
183 }
184 }, [queueFollow, track])
185
186 const onPressUnfollow = React.useCallback(async () => {
187 try {
188 await queueUnfollow()
189 } catch (e: any) {
190 if (e?.name !== 'AbortError') {
191 Toast.show('An issue occurred, please try again.')
192 }
193 }
194 }, [queueUnfollow])
195
196 if (!moderationOpts) {
197 return null
198 }
199 const moderation = moderateProfile(profile, moderationOpts)
200 const following = profile.viewer?.following
201 return (
202 <Link
203 href={makeProfileLink(profile)}
204 title={profile.handle}
205 asAnchor
206 anchorNoUnderline>
207 <View
208 style={[
209 styles.suggestedFollowCardOuter,
210 {
211 backgroundColor: pal.view.backgroundColor,
212 },
213 ]}>
214 <UserAvatar
215 size={60}
216 avatar={profile.avatar}
217 moderation={moderation.avatar}
218 />
219
220 <View style={{width: '100%', paddingVertical: 12}}>
221 <Text
222 type="xs-medium"
223 style={[pal.text, {textAlign: 'center'}]}
224 numberOfLines={1}>
225 {sanitizeDisplayName(
226 profile.displayName || sanitizeHandle(profile.handle),
227 moderation.profile,
228 )}
229 </Text>
230 <Text
231 type="xs-medium"
232 style={[pal.textLight, {textAlign: 'center'}]}
233 numberOfLines={1}>
234 {sanitizeHandle(profile.handle, '@')}
235 </Text>
236 </View>
237
238 <Button
239 label={following ? 'Unfollow' : 'Follow'}
240 type="inverted"
241 labelStyle={{textAlign: 'center'}}
242 onPress={following ? onPressUnfollow : onPressFollow}
243 />
244 </View>
245 </Link>
246 )
247}
248
249const styles = StyleSheet.create({
250 suggestedFollowCardOuter: {
251 marginHorizontal: INNER_PADDING / 2,
252 paddingTop: 10,
253 paddingBottom: 12,
254 paddingHorizontal: 10,
255 borderRadius: 8,
256 width: 130,
257 alignItems: 'center',
258 overflow: 'hidden',
259 flexShrink: 1,
260 },
261})