mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import {useCallback, useImperativeHandle, useRef, useState} from 'react'
2import {View} from 'react-native'
3import {useWindowDimensions} from 'react-native'
4import {msg, Trans} from '@lingui/macro'
5import {useLingui} from '@lingui/react'
6
7import {BSKY_SERVICE} from '#/lib/constants'
8import {logEvent} from '#/lib/statsig/statsig'
9import * as persisted from '#/state/persisted'
10import {useSession} from '#/state/session'
11import {atoms as a, useBreakpoints, useTheme} from '#/alf'
12import {Admonition} from '#/components/Admonition'
13import {Button, ButtonText} from '#/components/Button'
14import * as Dialog from '#/components/Dialog'
15import * as TextField from '#/components/forms/TextField'
16import * as ToggleButton from '#/components/forms/ToggleButton'
17import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe'
18import {InlineLinkText} from '#/components/Link'
19import {P, Text} from '#/components/Typography'
20
21export function ServerInputDialog({
22 control,
23 onSelect,
24}: {
25 control: Dialog.DialogOuterProps['control']
26 onSelect: (url: string) => void
27}) {
28 const {height} = useWindowDimensions()
29 const formRef = useRef<DialogInnerRef>(null)
30
31 // persist these options between dialog open/close
32 const [fixedOption, setFixedOption] = useState(BSKY_SERVICE)
33 const [previousCustomAddress, setPreviousCustomAddress] = useState('')
34
35 const onClose = useCallback(() => {
36 const result = formRef.current?.getFormState()
37 if (result) {
38 onSelect(result)
39 if (result !== BSKY_SERVICE) {
40 setPreviousCustomAddress(result)
41 }
42 }
43 logEvent('signin:hostingProviderPressed', {
44 hostingProviderDidChange: fixedOption !== BSKY_SERVICE,
45 })
46 }, [onSelect, fixedOption])
47
48 return (
49 <Dialog.Outer
50 control={control}
51 onClose={onClose}
52 nativeOptions={{minHeight: height / 2}}>
53 <Dialog.Handle />
54 <DialogInner
55 formRef={formRef}
56 fixedOption={fixedOption}
57 setFixedOption={setFixedOption}
58 initialCustomAddress={previousCustomAddress}
59 />
60 </Dialog.Outer>
61 )
62}
63
64type DialogInnerRef = {getFormState: () => string | null}
65
66function DialogInner({
67 formRef,
68 fixedOption,
69 setFixedOption,
70 initialCustomAddress,
71}: {
72 formRef: React.Ref<DialogInnerRef>
73 fixedOption: string
74 setFixedOption: (opt: string) => void
75 initialCustomAddress: string
76}) {
77 const control = Dialog.useDialogContext()
78 const {_} = useLingui()
79 const t = useTheme()
80 const {accounts} = useSession()
81 const {gtMobile} = useBreakpoints()
82 const [customAddress, setCustomAddress] = useState(initialCustomAddress)
83 const [pdsAddressHistory, setPdsAddressHistory] = useState<string[]>(
84 persisted.get('pdsAddressHistory') || [],
85 )
86
87 useImperativeHandle(
88 formRef,
89 () => ({
90 getFormState: () => {
91 let url
92 if (fixedOption === 'custom') {
93 url = customAddress.trim().toLowerCase()
94 if (!url) {
95 return null
96 }
97 } else {
98 url = fixedOption
99 }
100 if (!url.startsWith('http://') && !url.startsWith('https://')) {
101 if (url === 'localhost' || url.startsWith('localhost:')) {
102 url = `http://${url}`
103 } else {
104 url = `https://${url}`
105 }
106 }
107
108 if (fixedOption === 'custom') {
109 if (!pdsAddressHistory.includes(url)) {
110 const newHistory = [url, ...pdsAddressHistory.slice(0, 4)]
111 setPdsAddressHistory(newHistory)
112 persisted.write('pdsAddressHistory', newHistory)
113 }
114 }
115
116 return url
117 },
118 }),
119 [customAddress, fixedOption, pdsAddressHistory],
120 )
121
122 const isFirstTimeUser = accounts.length === 0
123
124 return (
125 <Dialog.ScrollableInner
126 accessibilityDescribedBy="dialog-description"
127 accessibilityLabelledBy="dialog-title">
128 <View style={[a.relative, a.gap_md, a.w_full]}>
129 <Text nativeID="dialog-title" style={[a.text_2xl, a.font_bold]}>
130 <Trans>Choose your account provider</Trans>
131 </Text>
132 <ToggleButton.Group
133 label="Preferences"
134 values={[fixedOption]}
135 onChange={values => setFixedOption(values[0])}>
136 <ToggleButton.Button name={BSKY_SERVICE} label={_(msg`Bluesky`)}>
137 <ToggleButton.ButtonText>{_(msg`Bluesky`)}</ToggleButton.ButtonText>
138 </ToggleButton.Button>
139 <ToggleButton.Button
140 testID="customSelectBtn"
141 name="custom"
142 label={_(msg`Custom`)}>
143 <ToggleButton.ButtonText>{_(msg`Custom`)}</ToggleButton.ButtonText>
144 </ToggleButton.Button>
145 </ToggleButton.Group>
146
147 {fixedOption === BSKY_SERVICE && isFirstTimeUser && (
148 <Admonition type="tip">
149 <Trans>
150 Bluesky is an open network where you can choose your own provider.
151 If you're new here, we recommend sticking with the default Bluesky
152 Social option.
153 </Trans>
154 </Admonition>
155 )}
156
157 {fixedOption === 'custom' && (
158 <View
159 style={[
160 a.border,
161 t.atoms.border_contrast_low,
162 a.rounded_sm,
163 a.px_md,
164 a.py_md,
165 ]}>
166 <TextField.LabelText nativeID="address-input-label">
167 <Trans>Server address</Trans>
168 </TextField.LabelText>
169 <TextField.Root>
170 <TextField.Icon icon={Globe} />
171 <Dialog.Input
172 testID="customServerTextInput"
173 value={customAddress}
174 onChangeText={setCustomAddress}
175 label="my-server.com"
176 accessibilityLabelledBy="address-input-label"
177 autoCapitalize="none"
178 keyboardType="url"
179 />
180 </TextField.Root>
181 {pdsAddressHistory.length > 0 && (
182 <View style={[a.flex_row, a.flex_wrap, a.mt_xs]}>
183 {pdsAddressHistory.map(uri => (
184 <Button
185 key={uri}
186 variant="ghost"
187 color="primary"
188 label={uri}
189 style={[a.px_sm, a.py_xs, a.rounded_sm, a.gap_sm]}
190 onPress={() => setCustomAddress(uri)}>
191 <ButtonText>{uri}</ButtonText>
192 </Button>
193 ))}
194 </View>
195 )}
196 </View>
197 )}
198
199 <View style={[a.py_xs]}>
200 <P
201 style={[
202 t.atoms.text_contrast_medium,
203 a.text_sm,
204 a.leading_snug,
205 a.flex_1,
206 ]}>
207 {isFirstTimeUser ? (
208 <Trans>
209 If you're a developer, you can host your own server.
210 </Trans>
211 ) : (
212 <Trans>
213 Bluesky is an open network where you can choose your hosting
214 provider. If you're a developer, you can host your own server.
215 </Trans>
216 )}{' '}
217 <InlineLinkText
218 label={_(msg`Learn more about self hosting your PDS.`)}
219 to="https://atproto.com/guides/self-hosting">
220 <Trans>Learn more.</Trans>
221 </InlineLinkText>
222 </P>
223 </View>
224
225 <View style={gtMobile && [a.flex_row, a.justify_end]}>
226 <Button
227 testID="doneBtn"
228 variant="outline"
229 color="primary"
230 size="small"
231 onPress={() => control.close()}
232 label={_(msg`Done`)}>
233 <ButtonText>{_(msg`Done`)}</ButtonText>
234 </Button>
235 </View>
236 </View>
237 </Dialog.ScrollableInner>
238 )
239}