Bluesky app fork with some witchin' additions 馃挮 witchsky.app
bluesky fork client
at main 211 lines 6.9 kB view raw
1import React from 'react' 2import {View} from 'react-native' 3import {msg} from '@lingui/core/macro' 4import {useLingui} from '@lingui/react' 5import {Trans} from '@lingui/react/macro' 6 7import {useCleanError} from '#/lib/hooks/useCleanError' 8import {isAppPassword} from '#/lib/jwt' 9import {getAge, getDateAgo} from '#/lib/strings/time' 10import {logger} from '#/logger' 11import { 12 useBirthdateMutation, 13 useIsBirthdateUpdateAllowed, 14} from '#/state/birthdate' 15import { 16 usePreferencesQuery, 17 type UsePreferencesQueryResponse, 18} from '#/state/queries/preferences' 19import {useSession} from '#/state/session' 20import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' 21import {atoms as a, useTheme, web} from '#/alf' 22import {Admonition} from '#/components/Admonition' 23import {Button, ButtonIcon, ButtonText} from '#/components/Button' 24import * as Dialog from '#/components/Dialog' 25import {DateField} from '#/components/forms/DateField' 26import {SimpleInlineLinkText} from '#/components/Link' 27import {Loader} from '#/components/Loader' 28import {Span, Text} from '#/components/Typography' 29import {IS_IOS, IS_WEB} from '#/env' 30 31export function BirthDateSettingsDialog({ 32 control, 33}: { 34 control: Dialog.DialogControlProps 35}) { 36 const t = useTheme() 37 const {_} = useLingui() 38 const {isLoading, error, data: preferences} = usePreferencesQuery() 39 const isBirthdateUpdateAllowed = useIsBirthdateUpdateAllowed() 40 const {currentAccount} = useSession() 41 const isUsingAppPassword = isAppPassword(currentAccount?.accessJwt || '') 42 43 return ( 44 <Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}> 45 <Dialog.Handle /> 46 {isBirthdateUpdateAllowed ? ( 47 <Dialog.ScrollableInner 48 label={_(msg`My Birthdate`)} 49 style={web({maxWidth: 400})}> 50 <View style={[a.gap_md]}> 51 <Text style={[a.text_xl, a.font_semi_bold]}> 52 <Trans>My Birthdate</Trans> 53 </Text> 54 <Text 55 style={[a.text_md, a.leading_snug, t.atoms.text_contrast_medium]}> 56 <Trans> 57 This information is private and not shared with other users. 58 </Trans> 59 </Text> 60 61 {isLoading ? ( 62 <Loader size="xl" /> 63 ) : error || !preferences ? ( 64 <ErrorMessage 65 message={ 66 error?.toString() || 67 _( 68 msg`We were unable to load your birthdate preferences. Please try again.`, 69 ) 70 } 71 style={[a.rounded_sm]} 72 /> 73 ) : isUsingAppPassword ? ( 74 <Admonition type="info"> 75 <Trans> 76 Hmm, it looks like you're signed in with an{' '} 77 <Span style={[a.italic]}>App Password</Span>. To set your 78 birthdate, you'll need to sign in with your main account 79 password, or ask whomever controls this account to do so. 80 </Trans> 81 </Admonition> 82 ) : ( 83 <BirthdayInner control={control} preferences={preferences} /> 84 )} 85 </View> 86 87 <Dialog.Close /> 88 </Dialog.ScrollableInner> 89 ) : ( 90 <Dialog.ScrollableInner 91 label={_(msg`You recently changed your birthdate`)} 92 style={web({maxWidth: 400})}> 93 <View style={[a.gap_sm]}> 94 <Text 95 style={[ 96 a.text_xl, 97 a.font_semi_bold, 98 a.leading_snug, 99 {paddingRight: 32}, 100 ]}> 101 <Trans>You recently changed your birthdate</Trans> 102 </Text> 103 <Text 104 style={[a.text_md, a.leading_snug, t.atoms.text_contrast_medium]}> 105 <Trans> 106 There is a limit to how often you can change your birthdate. You 107 may need to wait a day or two before updating it again. 108 </Trans> 109 </Text> 110 </View> 111 112 <Dialog.Close /> 113 </Dialog.ScrollableInner> 114 )} 115 </Dialog.Outer> 116 ) 117} 118 119function BirthdayInner({ 120 control, 121 preferences, 122}: { 123 control: Dialog.DialogControlProps 124 preferences: UsePreferencesQueryResponse 125}) { 126 const {_} = useLingui() 127 const cleanError = useCleanError() 128 const [date, setDate] = React.useState( 129 preferences.birthDate || getDateAgo(18), 130 ) 131 const {isPending, error, mutateAsync: setBirthDate} = useBirthdateMutation() 132 const hasChanged = date !== preferences.birthDate 133 const errorMessage = React.useMemo(() => { 134 if (error) { 135 const {raw, clean} = cleanError(error) 136 return clean || raw || error.toString() 137 } 138 }, [error, cleanError]) 139 140 const age = getAge(new Date(date)) 141 const isUnder13 = age < 13 142 const isUnder18 = age >= 13 && age < 18 143 144 const onSave = React.useCallback(async () => { 145 try { 146 // skip if date is the same 147 if (hasChanged) { 148 await setBirthDate({birthDate: date}) 149 } 150 control.close() 151 } catch (e: any) { 152 logger.error(`setBirthDate failed`, {message: e.message}) 153 } 154 }, [date, setBirthDate, control, hasChanged]) 155 156 return ( 157 <View style={a.gap_lg} testID="birthDateSettingsDialog"> 158 <View style={IS_IOS && [a.w_full, a.align_center]}> 159 <DateField 160 testID="birthdayInput" 161 value={date} 162 onChangeDate={newDate => setDate(new Date(newDate))} 163 label={_(msg`Birthdate`)} 164 accessibilityHint={_(msg`Enter your birthdate`)} 165 /> 166 </View> 167 168 {isUnder18 && hasChanged && ( 169 <Admonition type="info"> 170 <Trans> 171 The birthdate you've entered means you are under 18 years old. 172 Certain content and features may be unavailable to you. 173 </Trans> 174 </Admonition> 175 )} 176 177 {isUnder13 && ( 178 <Admonition type="error"> 179 <Trans> 180 You must be at least 13 years old to use Bluesky. Read our{' '} 181 <SimpleInlineLinkText 182 to="https://bsky.social/about/support/tos" 183 label={_(msg`Terms of Service`)}> 184 Terms of Service 185 </SimpleInlineLinkText>{' '} 186 for more information. 187 </Trans> 188 </Admonition> 189 )} 190 191 {errorMessage ? ( 192 <ErrorMessage message={errorMessage} style={[a.rounded_sm]} /> 193 ) : undefined} 194 195 <View style={IS_WEB && [a.flex_row, a.justify_end]}> 196 <Button 197 label={hasChanged ? _(msg`Save birthdate`) : _(msg`Done`)} 198 size="large" 199 onPress={onSave} 200 variant="solid" 201 color="primary" 202 disabled={isUnder13}> 203 <ButtonText> 204 {hasChanged ? <Trans>Save</Trans> : <Trans>Done</Trans>} 205 </ButtonText> 206 {isPending && <ButtonIcon icon={Loader} />} 207 </Button> 208 </View> 209 </View> 210 ) 211}