1import { useAtomValue } from "@effect/atom-react";
2import {
3 connectionCatalogDisplayUrl,
4 gitHubRoutingConnectionKey,
5 gitHubRoutingPermissionFor,
6 type GitHubRoutingPermission,
7} from "@t3tools/client-runtime/connection";
8import { useState } from "react";
9import { Alert, Pressable, View } from "react-native";
10
11import { AppText as Text } from "../../components/AppText";
12import { SymbolView } from "../../components/AppSymbol";
13import { environmentCatalog } from "../../connection/catalog";
14import { useAtomCommand } from "../../state/use-atom-command";
15import { SettingsSection } from "../settings/components/SettingsSection";
16
17const options: ReadonlyArray<{
18 value: GitHubRoutingPermission;
19 label: string;
20 description: string;
21}> = [
22 { value: "off", label: "Off", description: "Keep GitHub requests on this environment." },
23 {
24 value: "read",
25 label: "Read PRs",
26 description: "Share PR data with other enabled environments.",
27 },
28 {
29 value: "read-write",
30 label: "Read and act",
31 description: "Actions may use broader GitHub permissions than the original environment.",
32 },
33];
34
35export function GitHubRoutingSettings() {
36 const catalog = useAtomValue(environmentCatalog.catalogValueAtom);
37 const permissions = useAtomValue(environmentCatalog.githubRoutingPermissionsValueAtom);
38 const update = useAtomCommand(environmentCatalog.setGitHubRoutingPermission);
39 const [expanded, setExpanded] = useState<string | null>(null);
40 const [saving, setSaving] = useState(false);
41 if (catalog.entries.size === 0) return null;
42
43 return (
44 <View className="mt-5 gap-3">
45 <SettingsSection title="GitHub routing">
46 {[...catalog.entries.values()].map((entry) => {
47 const environmentId = entry.target.environmentId;
48 const selected = gitHubRoutingPermissionFor(entry, permissions);
49 const disabled = !catalog.isReady || saving || gitHubRoutingConnectionKey(entry) === null;
50 return (
51 <View key={environmentId}>
52 <Pressable
53 accessibilityRole="button"
54 accessibilityLabel={`${entry.target.label} GitHub routing`}
55 accessibilityState={{ expanded: expanded === environmentId }}
56 className="flex-row items-center gap-3 p-4"
57 onPress={() => setExpanded(expanded === environmentId ? null : environmentId)}
58 >
59 <View className="min-w-0 flex-1 gap-0.5">
60 <Text className="text-base font-t3-bold text-foreground">
61 {entry.target.label}
62 </Text>
63 <Text className="text-xs text-foreground-muted" numberOfLines={1}>
64 {connectionCatalogDisplayUrl(entry) ?? "T3 Connect"}
65 </Text>
66 </View>
67 <Text className="text-sm text-foreground-muted">
68 {options.find((option) => option.value === selected)?.label}
69 </Text>
70 <SymbolView
71 name={expanded === environmentId ? "chevron.up" : "chevron.down"}
72 size={12}
73 tintColorClassName="accent-icon-muted"
74 />
75 </Pressable>
76 {expanded === environmentId
77 ? options.map((option) => (
78 <Pressable
79 key={option.value}
80 accessibilityRole="radio"
81 accessibilityState={{ checked: selected === option.value, disabled }}
82 disabled={disabled}
83 className="flex-row items-center gap-4 p-4 disabled:opacity-50"
84 onPress={() => {
85 setSaving(true);
86 void update({ environmentId, permission: option.value }).then((result) => {
87 setSaving(false);
88 if (result._tag === "Failure")
89 Alert.alert(
90 "Could not save GitHub routing permission",
91 "Try again before leaving this screen.",
92 );
93 });
94 }}
95 >
96 <View className="min-w-0 flex-1 gap-1">
97 <Text className="text-base text-foreground">{option.label}</Text>
98 <Text className="text-sm leading-normal text-foreground-muted">
99 {option.description}
100 </Text>
101 </View>
102 {selected === option.value ? (
103 <SymbolView
104 name="checkmark"
105 size={18}
106 tintColorClassName="accent-icon"
107 weight="semibold"
108 />
109 ) : null}
110 </Pressable>
111 ))
112 : null}
113 </View>
114 );
115 })}
116 </SettingsSection>
117 <Text className="px-2 text-sm text-foreground-muted">
118 Choose environments you trust to share PR data and use each other's GitHub access. Enable
119 both environments. This applies only to this client.
120 </Text>
121 </View>
122 );
123}