forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {useCallback, useMemo, useState} 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] = useState(preferences.birthDate || getDateAgo(18))
129 const {isPending, error, mutateAsync: setBirthDate} = useBirthdateMutation()
130 const hasChanged = date !== preferences.birthDate
131 const errorMessage = useMemo(() => {
132 if (error) {
133 const {raw, clean} = cleanError(error)
134 return clean || raw || error.toString()
135 }
136 }, [error, cleanError])
137
138 const age = getAge(new Date(date))
139 const isUnder13 = age < 13
140 const isUnder18 = age >= 13 && age < 18
141
142 const onSave = useCallback(async () => {
143 try {
144 // skip if date is the same
145 if (hasChanged) {
146 await setBirthDate({birthDate: date})
147 }
148 control.close()
149 } catch (e: any) {
150 logger.error(`setBirthDate failed`, {message: e.message})
151 }
152 }, [date, setBirthDate, control, hasChanged])
153
154 return (
155 <View style={a.gap_lg} testID="birthDateSettingsDialog">
156 <View style={IS_IOS && [a.w_full, a.align_center]}>
157 <DateField
158 testID="birthdayInput"
159 value={date}
160 onChangeDate={newDate => setDate(new Date(newDate))}
161 label={_(msg`Birthdate`)}
162 accessibilityHint={_(msg`Enter your birthdate`)}
163 />
164 </View>
165
166 {isUnder18 && hasChanged && (
167 <Admonition type="info">
168 <Trans>
169 The birthdate you've entered means you are under 18 years old.
170 Certain content and features may be unavailable to you.
171 </Trans>
172 </Admonition>
173 )}
174
175 {isUnder13 && (
176 <Admonition type="error">
177 <Trans>
178 You must be at least 13 years old to use Bluesky. Read our{' '}
179 <SimpleInlineLinkText
180 to="https://bsky.social/about/support/tos"
181 label={_(msg`Terms of Service`)}>
182 Terms of Service
183 </SimpleInlineLinkText>{' '}
184 for more information.
185 </Trans>
186 </Admonition>
187 )}
188
189 {errorMessage ? (
190 <ErrorMessage message={errorMessage} style={[a.rounded_sm]} />
191 ) : undefined}
192
193 <View style={IS_WEB && [a.flex_row, a.justify_end]}>
194 <Button
195 label={hasChanged ? _(msg`Save birthdate`) : _(msg`Done`)}
196 size="large"
197 onPress={onSave}
198 variant="solid"
199 color="primary"
200 disabled={isUnder13}>
201 <ButtonText>
202 {hasChanged ? <Trans>Save</Trans> : <Trans>Done</Trans>}
203 </ButtonText>
204 {isPending && <ButtonIcon icon={Loader} />}
205 </Button>
206 </View>
207 </View>
208 )
209}