1import { ScreenScrollView as ScrollView } from "../../components/ScreenScrollView";
2import { NativeHeaderToolbar } from "../../native/StackHeader";
3import { useAuth } from "@clerk/expo";
4import { StackActions, useNavigation } from "@react-navigation/native";
5import { useCallback, useEffect, useState } from "react";
6import { Platform, Pressable, RefreshControl, View } from "react-native";
7import { useSafeAreaInsets } from "react-native-safe-area-context";
8
9import { reportAtomCommandResult, settlePromise } from "@t3tools/client-runtime/state/runtime";
10import { AndroidSheetHeader } from "../../components/AndroidScreenHeader";
11import { AppText as Text } from "../../components/AppText";
12import { useRemoteConnections } from "../../state/use-remote-environment-registry";
13import { CloudEnvironmentRows } from "../connection/CloudEnvironmentRows";
14import { splitEnvironmentSections } from "../connection/environmentSections";
15import { useConnectionController } from "../connection/useConnectionController";
16import { optOutOfConnectOnboarding } from "./connectOnboardingOptOut";
17import { useCloudAuthAvailable } from "./cloudAvailability";
18
19/**
20 * Post-sign-in onboarding sheet for T3 Connect. Mobile never publishes
21 * environments itself — it consumes ones published elsewhere — so this simply
22 * surfaces the account's T3 Connect environments right after sign-in so every
23 * device can be connected in one go. It shows on every sign-in: sign-out
24 * clears the connected environments, so each new session starts from zero.
25 */
26export function ConnectOnboardingRouteScreen() {
27 const navigation = useNavigation();
28 const cloudAvailable = useCloudAuthAvailable();
29
30 // The route is deep-linkable; without cloud config the sheet would present
31 // empty with no chrome to dismiss it, so bail back out instead.
32 useEffect(() => {
33 if (cloudAvailable) {
34 return;
35 }
36 if (navigation.canGoBack()) {
37 navigation.goBack();
38 } else {
39 navigation.dispatch(StackActions.replace("Home"));
40 }
41 }, [cloudAvailable, navigation]);
42
43 return cloudAvailable ? <ConfiguredConnectOnboardingRouteScreen /> : null;
44}
45
46function ConfiguredConnectOnboardingRouteScreen() {
47 const navigation = useNavigation();
48 const insets = useSafeAreaInsets();
49 const { isSignedIn, userId } = useAuth({ treatPendingAsSignedOut: false });
50 const { connectedEnvironments, onSetEnvironmentEnabled, onRemoveEnvironmentPress } =
51 useRemoteConnections();
52 const { refreshRelayEnvironments } = useConnectionController();
53 const { connectedCloudEnvironments } = splitEnvironmentSections({
54 connectedEnvironments,
55 cloudEnvironments: null,
56 });
57
58 const [isRefreshing, setIsRefreshing] = useState(false);
59 const handleRefresh = useCallback(() => {
60 void (async () => {
61 setIsRefreshing(true);
62 try {
63 await refreshRelayEnvironments();
64 } finally {
65 setIsRefreshing(false);
66 }
67 })();
68 }, [refreshRelayEnvironments]);
69
70 const handleClose = useCallback(() => {
71 navigation.goBack();
72 }, [navigation]);
73
74 // Persist before dismissing so a quick sign-out/sign-in cannot race ahead
75 // of the preference write; the write is a local secure-store update.
76 const handleDontShowAgain = useCallback(() => {
77 void (async () => {
78 if (userId) {
79 const result = await settlePromise(() => optOutOfConnectOnboarding(userId));
80 reportAtomCommandResult(result, { label: "connect onboarding opt-out" });
81 }
82 navigation.goBack();
83 })();
84 }, [navigation, userId]);
85
86 return (
87 <View collapsable={false} className="flex-1 bg-sheet">
88 {Platform.OS === "android" ? (
89 <AndroidSheetHeader
90 title="Set up T3 Connect"
91 actions={[
92 {
93 accessibilityLabel: "Refresh environments",
94 disabled: isRefreshing,
95 icon: "arrow.clockwise",
96 loading: isRefreshing,
97 onPress: handleRefresh,
98 },
99 { accessibilityLabel: "Close", icon: "xmark", onPress: handleClose },
100 ]}
101 />
102 ) : (
103 <NativeHeaderToolbar placement="right">
104 <NativeHeaderToolbar.Button icon="xmark" onPress={handleClose} separateBackground />
105 </NativeHeaderToolbar>
106 )}
107 <ScrollView
108 nestedScrollEnabled
109 alwaysBounceVertical
110 contentInsetAdjustmentBehavior="automatic"
111 showsVerticalScrollIndicator={false}
112 className="flex-1"
113 contentInset={{ bottom: Math.max(insets.bottom, 18) + 18 }}
114 contentContainerStyle={{
115 gap: 16,
116 paddingHorizontal: 20,
117 paddingTop: 16,
118 }}
119 refreshControl={
120 Platform.OS === "ios" ? (
121 <RefreshControl refreshing={isRefreshing} onRefresh={handleRefresh} />
122 ) : undefined
123 }
124 >
125 {isSignedIn ? (
126 <CloudEnvironmentRows
127 connectedCloudEnvironments={connectedCloudEnvironments}
128 onSetEnvironmentEnabled={onSetEnvironmentEnabled}
129 onRemoveEnvironment={onRemoveEnvironmentPress}
130 showHeader={false}
131 />
132 ) : (
133 <View collapsable={false} className="rounded-[24px] bg-grouped-card p-5">
134 <Text className="text-sm leading-normal text-foreground-muted">
135 Sign in to your T3 account to set up T3 Connect.
136 </Text>
137 </View>
138 )}
139
140 {userId ? (
141 <Pressable
142 accessibilityRole="button"
143 hitSlop={8}
144 onPress={handleDontShowAgain}
145 className="items-center py-1 active:opacity-70"
146 >
147 <Text className="text-xs text-foreground-muted">{"Don't show this again"}</Text>
148 </Pressable>
149 ) : null}
150 </ScrollView>
151 </View>
152 );
153}