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