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