mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at schema-errors 287 lines 7.5 kB view raw
1import React from 'react' 2import { 3 ActivityIndicator, 4 StyleSheet, 5 TouchableOpacity, 6 View, 7} from 'react-native' 8import {setStringAsync} from 'expo-clipboard' 9import {ComAtprotoServerDefs} from '@atproto/api' 10import { 11 FontAwesomeIcon, 12 FontAwesomeIconStyle, 13} from '@fortawesome/react-native-fontawesome' 14import {msg, Trans} from '@lingui/macro' 15import {useLingui} from '@lingui/react' 16 17import {makeProfileLink} from '#/lib/routes/links' 18import {useInvitesAPI, useInvitesState} from '#/state/invites' 19import {useModalControls} from '#/state/modals' 20import { 21 InviteCodesQueryResponse, 22 useInviteCodesQuery, 23} from '#/state/queries/invites' 24import {usePalette} from 'lib/hooks/usePalette' 25import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 26import {cleanError} from 'lib/strings/errors' 27import {isWeb} from 'platform/detection' 28import {ErrorMessage} from '../util/error/ErrorMessage' 29import {Button} from '../util/forms/Button' 30import {Link} from '../util/Link' 31import {Text} from '../util/text/Text' 32import * as Toast from '../util/Toast' 33import {UserInfoText} from '../util/UserInfoText' 34import {ScrollView} from './util' 35 36export const snapPoints = ['70%'] 37 38export function Component() { 39 const {isLoading, data: invites, error} = useInviteCodesQuery() 40 41 return error ? ( 42 <ErrorMessage message={cleanError(error)} /> 43 ) : isLoading || !invites ? ( 44 <View style={{padding: 18}}> 45 <ActivityIndicator /> 46 </View> 47 ) : ( 48 <Inner invites={invites} /> 49 ) 50} 51 52export function Inner({invites}: {invites: InviteCodesQueryResponse}) { 53 const pal = usePalette('default') 54 const {_} = useLingui() 55 const {closeModal} = useModalControls() 56 const {isTabletOrDesktop} = useWebMediaQueries() 57 58 const onClose = React.useCallback(() => { 59 closeModal() 60 }, [closeModal]) 61 62 if (invites.all.length === 0) { 63 return ( 64 <View style={[styles.container, pal.view]} testID="inviteCodesModal"> 65 <View style={[styles.empty, pal.viewLight]}> 66 <Text type="lg" style={[pal.text, styles.emptyText]}> 67 <Trans> 68 You don't have any invite codes yet! We'll send you some when 69 you've been on Bluesky for a little longer. 70 </Trans> 71 </Text> 72 </View> 73 <View style={styles.flex1} /> 74 <View 75 style={[ 76 styles.btnContainer, 77 isTabletOrDesktop && styles.btnContainerDesktop, 78 ]}> 79 <Button 80 type="primary" 81 label={_(msg`Done`)} 82 style={styles.btn} 83 labelStyle={styles.btnLabel} 84 onPress={onClose} 85 /> 86 </View> 87 </View> 88 ) 89 } 90 91 return ( 92 <View style={[styles.container, pal.view]} testID="inviteCodesModal"> 93 <Text type="title-xl" style={[styles.title, pal.text]}> 94 <Trans>Invite a Friend</Trans> 95 </Text> 96 <Text type="lg" style={[styles.description, pal.text]}> 97 <Trans> 98 Each code works once. You'll receive more invite codes periodically. 99 </Trans> 100 </Text> 101 <ScrollView style={[styles.scrollContainer, pal.border]}> 102 {invites.available.map((invite, i) => ( 103 <InviteCode 104 testID={`inviteCode-${i}`} 105 key={invite.code} 106 invite={invite} 107 invites={invites} 108 /> 109 ))} 110 {invites.used.map((invite, i) => ( 111 <InviteCode 112 used 113 testID={`inviteCode-${i}`} 114 key={invite.code} 115 invite={invite} 116 invites={invites} 117 /> 118 ))} 119 </ScrollView> 120 <View style={styles.btnContainer}> 121 <Button 122 testID="closeBtn" 123 type="primary" 124 label={_(msg`Done`)} 125 style={styles.btn} 126 labelStyle={styles.btnLabel} 127 onPress={onClose} 128 /> 129 </View> 130 </View> 131 ) 132} 133 134function InviteCode({ 135 testID, 136 invite, 137 used, 138 invites, 139}: { 140 testID: string 141 invite: ComAtprotoServerDefs.InviteCode 142 used?: boolean 143 invites: InviteCodesQueryResponse 144}) { 145 const pal = usePalette('default') 146 const {_} = useLingui() 147 const invitesState = useInvitesState() 148 const {setInviteCopied} = useInvitesAPI() 149 const uses = invite.uses 150 151 const onPress = React.useCallback(() => { 152 setStringAsync(invite.code) 153 Toast.show(_(msg`Copied to clipboard`)) 154 setInviteCopied(invite.code) 155 }, [setInviteCopied, invite, _]) 156 157 return ( 158 <View 159 style={[ 160 pal.border, 161 {borderBottomWidth: 1, paddingHorizontal: 20, paddingVertical: 14}, 162 ]}> 163 <TouchableOpacity 164 testID={testID} 165 style={[styles.inviteCode]} 166 onPress={onPress} 167 accessibilityRole="button" 168 accessibilityLabel={ 169 invites.available.length === 1 170 ? _(msg`Invite codes: 1 available`) 171 : _(msg`Invite codes: ${invites.available.length} available`) 172 } 173 accessibilityHint={_(msg`Opens list of invite codes`)}> 174 <Text 175 testID={`${testID}-code`} 176 type={used ? 'md' : 'md-bold'} 177 style={used ? [pal.textLight, styles.strikeThrough] : pal.text}> 178 {invite.code} 179 </Text> 180 <View style={styles.flex1} /> 181 {!used && invitesState.copiedInvites.includes(invite.code) && ( 182 <Text style={[pal.textLight, styles.codeCopied]}> 183 <Trans>Copied</Trans> 184 </Text> 185 )} 186 {!used && ( 187 <FontAwesomeIcon 188 icon={['far', 'clone']} 189 style={pal.text as FontAwesomeIconStyle} 190 /> 191 )} 192 </TouchableOpacity> 193 {uses.length > 0 ? ( 194 <View 195 style={{ 196 flexDirection: 'column', 197 gap: 8, 198 paddingTop: 6, 199 }}> 200 <Text style={pal.text}> 201 <Trans>Used by:</Trans>{' '} 202 {uses.map((use, i) => ( 203 <Link 204 key={use.usedBy} 205 href={makeProfileLink({handle: use.usedBy, did: ''})} 206 style={{ 207 flexDirection: 'row', 208 }}> 209 <UserInfoText did={use.usedBy} style={pal.link} /> 210 {i !== uses.length - 1 && <Text style={pal.text}>, </Text>} 211 </Link> 212 ))} 213 </Text> 214 </View> 215 ) : null} 216 </View> 217 ) 218} 219 220const styles = StyleSheet.create({ 221 container: { 222 flex: 1, 223 paddingBottom: isWeb ? 0 : 50, 224 }, 225 title: { 226 textAlign: 'center', 227 marginTop: 12, 228 marginBottom: 12, 229 }, 230 description: { 231 textAlign: 'center', 232 paddingHorizontal: 42, 233 marginBottom: 14, 234 }, 235 236 scrollContainer: { 237 flex: 1, 238 borderTopWidth: 1, 239 marginTop: 4, 240 marginBottom: 16, 241 }, 242 243 flex1: { 244 flex: 1, 245 }, 246 empty: { 247 paddingHorizontal: 20, 248 paddingVertical: 20, 249 borderRadius: 16, 250 marginHorizontal: 24, 251 marginTop: 10, 252 }, 253 emptyText: { 254 textAlign: 'center', 255 }, 256 257 inviteCode: { 258 flexDirection: 'row', 259 alignItems: 'center', 260 }, 261 codeCopied: { 262 marginRight: 8, 263 }, 264 strikeThrough: { 265 textDecorationLine: 'line-through', 266 textDecorationStyle: 'solid', 267 }, 268 269 btnContainer: { 270 flexDirection: 'row', 271 justifyContent: 'center', 272 }, 273 btnContainerDesktop: { 274 marginTop: 14, 275 }, 276 btn: { 277 flexDirection: 'row', 278 alignItems: 'center', 279 justifyContent: 'center', 280 borderRadius: 32, 281 paddingHorizontal: 60, 282 paddingVertical: 14, 283 }, 284 btnLabel: { 285 fontSize: 18, 286 }, 287})