| 1 | import { router, useLocalSearchParams } from "expo-router"; |
| 2 | import { Server as ServerIcon } from "lucide-react-native"; |
| 3 | import { useEffect, useState } from "react"; |
| 4 | import { View } from "react-native"; |
| 5 | |
| 6 | import { G1tMark } from "@/components/mark"; |
| 7 | import { Button, Loading, Screen, Text } from "@/components/ui"; |
| 8 | import { type Found, discover } from "@/lib/discover"; |
| 9 | import { isHosted } from "@/lib/server"; |
| 10 | import { useSession } from "@/lib/session"; |
| 11 | import { space, useColors } from "@/theme"; |
| 12 | |
| 13 | /** |
| 14 | * `g1t://connect?server=<site>`: a link (or the QR code on a self-hosted |
| 15 | * g1t's download page) that points the app at a g1t. It asks first: the |
| 16 | * app talks to one g1t at a time, and switching signs out of the one |
| 17 | * before. |
| 18 | */ |
| 19 | export default function Connect() { |
| 20 | const { server: given } = useLocalSearchParams<{ server?: string }>(); |
| 21 | const { state, changeServer } = useSession(); |
| 22 | const colors = useColors(); |
| 23 | const [found, setFound] = useState<Found | null>(null); |
| 24 | const [error, setError] = useState<string | null>(null); |
| 25 | const [busy, setBusy] = useState(false); |
| 26 | |
| 27 | useEffect(() => { |
| 28 | if (!given) return setError("This link does not say which g1t."); |
| 29 | discover(given) |
| 30 | .then(setFound) |
| 31 | .catch((failure: unknown) => setError(failure instanceof Error ? failure.message : "That g1t did not answer.")); |
| 32 | }, [given]); |
| 33 | |
| 34 | const leave = () => (router.canGoBack() ? router.back() : router.replace("/")); |
| 35 | const host = found ? new URL(found.server.site).host : ""; |
| 36 | const already = found && state.server && state.server.site === found.server.site; |
| 37 | const signedInElsewhere = state.status === "signed-in" && !already; |
| 38 | |
| 39 | return ( |
| 40 | <Screen safe> |
| 41 | <View style={{ flex: 1, padding: space.xl, justifyContent: "center", gap: space.xl }}> |
| 42 | <G1tMark size={44} /> |
| 43 | {!found && !error ? <Loading /> : null} |
| 44 | {error ? ( |
| 45 | <View style={{ gap: space.md }}> |
| 46 | <Text size="title">Can't reach that g1t</Text> |
| 47 | <Text tone="muted">{error}</Text> |
| 48 | <Button title="Back" variant="secondary" onPress={leave} /> |
| 49 | </View> |
| 50 | ) : null} |
| 51 | {found ? ( |
| 52 | <View style={{ gap: space.md }}> |
| 53 | <Text size="title">{already ? `Already on ${host}` : `Use ${host}?`}</Text> |
| 54 | <Text tone="muted"> |
| 55 | {isHosted(found.server) |
| 56 | ? "g1t.sh, hosted by g1t." |
| 57 | : `A g1t run by its own people at ${found.server.site}. The app signs in there, and talks to it alone.`} |
| 58 | </Text> |
| 59 | {!found.push ? ( |
| 60 | <Text size="small" tone="faint"> |
| 61 | This g1t sends no notifications to phones; Chat still works while the app is open. |
| 62 | </Text> |
| 63 | ) : null} |
| 64 | {signedInElsewhere ? ( |
| 65 | <Text size="small" style={{ color: colors.warn }}> |
| 66 | You'll be signed out of {state.server ? new URL(state.server.site).host : "the g1t you are on"} first. |
| 67 | </Text> |
| 68 | ) : null} |
| 69 | {already ? ( |
| 70 | <Button title="Continue" onPress={() => router.replace("/")} /> |
| 71 | ) : ( |
| 72 | <Button |
| 73 | title={`Use ${host}`} |
| 74 | icon={ServerIcon} |
| 75 | busy={busy} |
| 76 | onPress={async () => { |
| 77 | setBusy(true); |
| 78 | await changeServer(found.server); |
| 79 | router.replace("/sign-in"); |
| 80 | }} |
| 81 | /> |
| 82 | )} |
| 83 | {!already ? <Button title="Not now" variant="secondary" onPress={leave} /> : null} |
| 84 | </View> |
| 85 | ) : null} |
| 86 | </View> |
| 87 | </Screen> |
| 88 | ); |
| 89 | } |