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