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