mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import React from 'react'
2import {View} from 'react-native'
3import {useNavigation} from '@react-navigation/native'
4import {useLingui} from '@lingui/react'
5import {msg, Trans} from '@lingui/macro'
6
7import {atoms as a, native, useTheme} from '#/alf'
8import * as Dialog from '#/components/Dialog'
9import {Text} from '#/components/Typography'
10import {Button, ButtonText} from '#/components/Button'
11import {MagnifyingGlass2_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass2'
12import {Person_Stroke2_Corner0_Rounded as Person} from '#/components/icons/Person'
13import {Mute_Stroke2_Corner0_Rounded as Mute} from '#/components/icons/Mute'
14import {Divider} from '#/components/Divider'
15import {Link} from '#/components/Link'
16import {makeSearchLink} from '#/lib/routes/links'
17import {NavigationProp} from '#/lib/routes/types'
18import {
19 usePreferencesQuery,
20 useUpsertMutedWordsMutation,
21 useRemoveMutedWordMutation,
22} from '#/state/queries/preferences'
23import {Loader} from '#/components/Loader'
24import {isInvalidHandle} from '#/lib/strings/handles'
25
26export function useTagMenuControl() {
27 return Dialog.useDialogControl()
28}
29
30export function TagMenu({
31 children,
32 control,
33 tag,
34 authorHandle,
35}: React.PropsWithChildren<{
36 control: Dialog.DialogOuterProps['control']
37 /**
38 * This should be the sanitized tag value from the facet itself, not the
39 * "display" value with a leading `#`.
40 */
41 tag: string
42 authorHandle?: string
43}>) {
44 const {_} = useLingui()
45 const t = useTheme()
46 const navigation = useNavigation<NavigationProp>()
47 const {isLoading: isPreferencesLoading, data: preferences} =
48 usePreferencesQuery()
49 const {
50 mutateAsync: upsertMutedWord,
51 variables: optimisticUpsert,
52 reset: resetUpsert,
53 } = useUpsertMutedWordsMutation()
54 const {
55 mutateAsync: removeMutedWord,
56 variables: optimisticRemove,
57 reset: resetRemove,
58 } = useRemoveMutedWordMutation()
59 const displayTag = '#' + tag
60
61 const isMuted = Boolean(
62 (preferences?.mutedWords?.find(
63 m => m.value === tag && m.targets.includes('tag'),
64 ) ??
65 optimisticUpsert?.find(
66 m => m.value === tag && m.targets.includes('tag'),
67 )) &&
68 !(optimisticRemove?.value === tag),
69 )
70
71 return (
72 <>
73 {children}
74
75 <Dialog.Outer control={control}>
76 <Dialog.Handle />
77
78 <Dialog.Inner label={_(msg`Tag menu: ${displayTag}`)}>
79 {isPreferencesLoading ? (
80 <View style={[a.w_full, a.align_center]}>
81 <Loader size="lg" />
82 </View>
83 ) : (
84 <>
85 <View
86 style={[
87 a.rounded_md,
88 a.border,
89 a.mb_md,
90 t.atoms.border_contrast_low,
91 t.atoms.bg_contrast_25,
92 ]}>
93 <Link
94 label={_(msg`Search for all posts with tag ${displayTag}`)}
95 to={makeSearchLink({query: displayTag})}
96 onPress={e => {
97 e.preventDefault()
98
99 control.close(() => {
100 navigation.push('Hashtag', {
101 tag: tag.replaceAll('#', '%23'),
102 })
103 })
104
105 return false
106 }}>
107 <View
108 style={[
109 a.w_full,
110 a.flex_row,
111 a.align_center,
112 a.justify_start,
113 a.gap_md,
114 a.px_lg,
115 a.py_md,
116 ]}>
117 <Search size="lg" style={[t.atoms.text_contrast_medium]} />
118 <Text
119 numberOfLines={1}
120 ellipsizeMode="middle"
121 style={[
122 a.flex_1,
123 a.text_md,
124 a.font_bold,
125 native({top: 2}),
126 t.atoms.text_contrast_medium,
127 ]}>
128 <Trans>
129 See{' '}
130 <Text style={[a.text_md, a.font_bold, t.atoms.text]}>
131 {displayTag}
132 </Text>{' '}
133 posts
134 </Trans>
135 </Text>
136 </View>
137 </Link>
138
139 {authorHandle && !isInvalidHandle(authorHandle) && (
140 <>
141 <Divider />
142
143 <Link
144 label={_(
145 msg`Search for all posts by @${authorHandle} with tag ${displayTag}`,
146 )}
147 to={makeSearchLink({
148 query: displayTag,
149 from: authorHandle,
150 })}
151 onPress={e => {
152 e.preventDefault()
153
154 control.close(() => {
155 navigation.push('Hashtag', {
156 tag: tag.replaceAll('#', '%23'),
157 author: authorHandle,
158 })
159 })
160
161 return false
162 }}>
163 <View
164 style={[
165 a.w_full,
166 a.flex_row,
167 a.align_center,
168 a.justify_start,
169 a.gap_md,
170 a.px_lg,
171 a.py_md,
172 ]}>
173 <Person
174 size="lg"
175 style={[t.atoms.text_contrast_medium]}
176 />
177 <Text
178 numberOfLines={1}
179 ellipsizeMode="middle"
180 style={[
181 a.flex_1,
182 a.text_md,
183 a.font_bold,
184 native({top: 2}),
185 t.atoms.text_contrast_medium,
186 ]}>
187 <Trans>
188 See{' '}
189 <Text
190 style={[a.text_md, a.font_bold, t.atoms.text]}>
191 {displayTag}
192 </Text>{' '}
193 posts by this user
194 </Trans>
195 </Text>
196 </View>
197 </Link>
198 </>
199 )}
200
201 {preferences ? (
202 <>
203 <Divider />
204
205 <Button
206 label={
207 isMuted
208 ? _(msg`Unmute all ${displayTag} posts`)
209 : _(msg`Mute all ${displayTag} posts`)
210 }
211 onPress={() => {
212 control.close(() => {
213 if (isMuted) {
214 resetUpsert()
215 removeMutedWord({
216 value: tag,
217 targets: ['tag'],
218 })
219 } else {
220 resetRemove()
221 upsertMutedWord([{value: tag, targets: ['tag']}])
222 }
223 })
224 }}>
225 <View
226 style={[
227 a.w_full,
228 a.flex_row,
229 a.align_center,
230 a.justify_start,
231 a.gap_md,
232 a.px_lg,
233 a.py_md,
234 ]}>
235 <Mute
236 size="lg"
237 style={[t.atoms.text_contrast_medium]}
238 />
239 <Text
240 numberOfLines={1}
241 ellipsizeMode="middle"
242 style={[
243 a.flex_1,
244 a.text_md,
245 a.font_bold,
246 native({top: 2}),
247 t.atoms.text_contrast_medium,
248 ]}>
249 {isMuted ? _(msg`Unmute`) : _(msg`Mute`)}{' '}
250 <Text style={[a.text_md, a.font_bold, t.atoms.text]}>
251 {displayTag}
252 </Text>{' '}
253 <Trans>posts</Trans>
254 </Text>
255 </View>
256 </Button>
257 </>
258 ) : null}
259 </View>
260
261 <Button
262 label={_(msg`Close this dialog`)}
263 size="small"
264 variant="ghost"
265 color="secondary"
266 onPress={() => control.close()}>
267 <ButtonText>Cancel</ButtonText>
268 </Button>
269 </>
270 )}
271 </Dialog.Inner>
272 </Dialog.Outer>
273 </>
274 )
275}