Bluesky app fork with some witchin' additions 馃挮
witchsky.app
bluesky
fork
client
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}