Skip to content
95 linesCodeBlameRaw
1import { useQuery, useQueryClient } from "@tanstack/react-query";
2import Constants from "expo-constants";
3import { Stack, router } from "expo-router";
4import * as WebBrowser from "expo-web-browser";
5import { Bell, Blocks, Check, ExternalLink, LogOut } from "lucide-react-native";
6import { Linking, View } from "react-native";
7
8import { Avatar, Badge, Button, Row, Screen, SectionHeader, Text } from "@/components/ui";
9import { pushState, registerForPush } from "@/lib/push";
10import { isHosted, siteUrl } from "@/lib/server";
11import { useSignedIn } from "@/lib/session";
12import { space, useColors } from "@/theme";
13
14/** More: Customize, your workspaces, notifications on this phone, how you signed in, and signing out. */
15export default function More() {
16 const { me, workspace, server, client, selectWorkspace, signOut } = useSignedIn();
17 const colors = useColors();
18 const queries = useQueryClient();
19 const name = me.display_username ?? me.username;
20 const oauth = client.credentials.kind === "oauth";
21 const push = useQuery({ queryKey: ["push-state"], queryFn: pushState });
22
23 const pushRow = {
24 unsupported: { label: "Not here", detail: "Notifications need a build of the app on a phone: not Expo Go on Android, nor a simulator." },
25 denied: { label: "Off", detail: "Turned off for g1t in your phone's settings. Tap to open them." },
26 undetermined: { label: "Off", detail: "Tap to get notified of messages to you, mentions and agents waiting on you." },
27 granted: { label: "On", detail: "Messages to you, mentions, replies and agents waiting on you, when you're not at g1t elsewhere." },
28 }[push.data ?? "undetermined"];
29
30 return (
31 <Screen scroll style={{ paddingHorizontal: 0 }}>
32 <Stack.Screen options={{ title: "More" }} />
33 <View style={{ flexDirection: "row", gap: space.md, alignItems: "center", paddingHorizontal: space.lg }}>
34 <Avatar name={name} uri={me.avatar ? `${server.site}/avatars/${me.avatar}` : null} size={48} />
35 <View style={{ flex: 1 }}>
36 <Text size="heading">{name}</Text>
37 <Text size="small" tone="muted">
38 @{me.username} · {isHosted(server) ? "g1t.sh" : new URL(server.site).host}
39 </Text>
40 </View>
41 </View>
42
43 <View>
44 <Row leading={<Blocks size={22} color={colors.accent} />} title="Customize" subtitle="Skills, connectors and plugins for your agents" onPress={() => router.push("/customize")} />
45 <Row
46 leading={<Bell size={22} color={colors.accent} />}
47 title="Notifications on this phone"
48 subtitle={pushRow.detail}
49 trailing={<Badge label={pushRow.label} tone={push.data === "granted" ? "success" : "neutral"} />}
50 chevron={false}
51 onPress={
52 push.data === "denied"
53 ? () => void Linking.openSettings()
54 : push.data === "undetermined"
55 ? () => void registerForPush(client, true).finally(() => queries.invalidateQueries({ queryKey: ["push-state"] }))
56 : undefined
57 }
58 />
59 </View>
60
61 <View>
62 <SectionHeader title="Workspaces" />
63 {(me.workspaces ?? []).map((one) => (
64 <Row
65 key={one.slug}
66 leading={<Avatar name={one.name ?? one.slug} uri={one.avatar ? `${server.site}/avatars/${one.avatar}` : null} />}
67 title={one.name ?? one.slug}
68 subtitle={`${one.slug} · ${one.role}`}
69 trailing={one.slug === workspace?.slug ? <Check size={18} color={colors.accent} /> : null}
70 chevron={false}
71 onPress={() => selectWorkspace(one.slug)}
72 />
73 ))}
74 </View>
75
76 <View>
77 <SectionHeader title="Signed in" />
78 <Row
79 title={oauth ? "With g1t" : "With an access token"}
80 subtitle={oauth ? "End or narrow the app's access in Settings → Connected applications." : "The token you pasted. Delete it in Settings → Access tokens to end it."}
81 trailing={<ExternalLink size={16} color={colors.faint} />}
82 chevron={false}
83 onPress={() => WebBrowser.openBrowserAsync(siteUrl(server, oauth ? "/settings/applications" : "/settings/tokens"))}
84 />
85 </View>
86
87 <View style={{ paddingHorizontal: space.lg, gap: space.md }}>
88 <Button title="Sign out" variant="danger" icon={LogOut} onPress={signOut} />
89 <Text size="small" tone="faint" style={{ textAlign: "center" }}>
90 g1t {Constants.expoConfig?.version ?? ""}
91 </Text>
92 </View>
93 </Screen>
94 );
95}