Skip to content
89 linesCodeBlameRaw
1import { router, useLocalSearchParams } from "expo-router";
2import { Server as ServerIcon } from "lucide-react-native";
3import { useEffect, useState } from "react";
4import { View } from "react-native";
5
6import { G1tMark } from "@/components/mark";
7import { Button, Loading, Screen, Text } from "@/components/ui";
8import { type Found, discover } from "@/lib/discover";
9import { isHosted } from "@/lib/server";
10import { useSession } from "@/lib/session";
11import { 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 */
19export 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}