| ... | ... | @@ -21,6 +21,7 @@ import { copyTextWithHaptic } from "../../lib/copyTextWithHaptic"; |
| 21 | 21 | import { useThemeColor } from "../../lib/useThemeColor"; |
| 22 | 22 | import type { ConnectedEnvironmentSummary } from "../../state/remote-runtime-types"; |
| 23 | 23 | import { availableCloudEnvironmentPresentation } from "../cloud/cloudEnvironmentPresentation"; |
| 24 | import { hasCloudPublicConfig } from "../cloud/publicConfig"; |
| 24 | 25 | import { ConnectionStatusDot } from "./ConnectionStatusDot"; |
| 25 | 26 | import { type RelayEnvironmentView, useConnectionController } from "./useConnectionController"; |
| 26 | 27 | |
| ... | ... | @@ -42,6 +43,11 @@ interface CloudEnvironmentRowsProps { |
| 42 | 43 | * with connect switches, availability status, refresh, and loading/error |
| 43 | 44 | * states. Shared between the Settings environments screen and the T3 Connect |
| 44 | 45 | * onboarding sheet. |
| 46 | * |
| 47 | * Already-connected relay environments render even without cloud config or a |
| 48 | * signed-in account — they are registered on this device and must stay |
| 49 | * reachable and removable. Only discovery (the available list, refresh, and |
| 50 | * its errors) requires a signed-in session. |
| 45 | 51 | */ |
| 46 | 52 | export function CloudEnvironmentRows(props: CloudEnvironmentRowsProps) { |
| 47 | 53 | // Showcase captures run without a Clerk publishable key, so `ClerkProvider` |
| ... | ... | @@ -50,20 +56,33 @@ export function CloudEnvironmentRows(props: CloudEnvironmentRowsProps) { |
| 50 | 56 | if (props.showcaseSignedIn !== undefined) { |
| 51 | 57 | return props.showcaseSignedIn ? <CloudEnvironmentRowsContent {...props} /> : null; |
| 52 | 58 | } |
| 59 | // No cloud config means no `ClerkProvider` either, so `useAuth` would throw. |
| 60 | if (!hasCloudPublicConfig()) { |
| 61 | return <ConnectedOnlyCloudEnvironmentRows {...props} />; |
| 62 | } |
| 53 | 63 | return <SignedInCloudEnvironmentRows {...props} />; |
| 54 | 64 | } |
| 55 | 65 | |
| 56 | 66 | function SignedInCloudEnvironmentRows(props: CloudEnvironmentRowsProps) { |
| 57 | 67 | const { isSignedIn } = useAuth({ treatPendingAsSignedOut: false }); |
| 58 | | if (!isSignedIn) return null; |
| 68 | if (!isSignedIn) return <ConnectedOnlyCloudEnvironmentRows {...props} />; |
| 59 | 69 | return <CloudEnvironmentRowsContent {...props} />; |
| 60 | 70 | } |
| 61 | 71 | |
| 62 | | function CloudEnvironmentRowsContent(props: CloudEnvironmentRowsProps) { |
| 72 | function ConnectedOnlyCloudEnvironmentRows(props: CloudEnvironmentRowsProps) { |
| 73 | if (props.connectedCloudEnvironments.length === 0) return null; |
| 74 | return <CloudEnvironmentRowsContent {...props} discoveryAvailable={false} />; |
| 75 | } |
| 76 | |
| 77 | function CloudEnvironmentRowsContent( |
| 78 | props: CloudEnvironmentRowsProps & { readonly discoveryAvailable?: boolean }, |
| 79 | ) { |
| 63 | 80 | const controller = useConnectionController(); |
| 64 | 81 | const iconColor = useThemeColor("--color-icon"); |
| 65 | | const availableCloudEnvironments = |
| 66 | | props.showcaseAvailableEnvironments ?? controller.availableRelayEnvironments; |
| 82 | const discoveryAvailable = props.discoveryAvailable ?? true; |
| 83 | const availableCloudEnvironments = discoveryAvailable |
| 84 | ? (props.showcaseAvailableEnvironments ?? controller.availableRelayEnvironments) |
| 85 | : []; |
| 67 | 86 | const [expandedErrorId, setExpandedErrorId] = useState<string | null>(null); |
| 68 | 87 | const hasCloudRows = |
| 69 | 88 | props.connectedCloudEnvironments.length > 0 || availableCloudEnvironments.length > 0; |
| ... | ... | @@ -89,25 +108,27 @@ function CloudEnvironmentRowsContent(props: CloudEnvironmentRowsProps) { |
| 89 | 108 | {showHeader ? ( |
| 90 | 109 | <View className="flex-row items-center justify-between px-1"> |
| 91 | 110 | <Text className="text-sm font-t3-bold uppercase text-foreground-muted">T3 Connect</Text> |
| 92 | | <Pressable |
| 93 | | accessibilityRole="button" |
| 94 | | disabled={controller.relayDiscovery.isRefreshing} |
| 95 | | onPress={() => { |
| 96 | | void controller.refreshRelayEnvironments(); |
| 97 | | }} |
| 98 | | className="h-9 w-9 items-center justify-center rounded-full bg-subtle active:opacity-70 disabled:opacity-50" |
| 99 | | > |
| 100 | | {controller.relayDiscovery.isRefreshing ? ( |
| 101 | | <ActivityIndicator color={iconColor} size="small" /> |
| 102 | | ) : ( |
| 103 | | <SymbolView |
| 104 | | name="arrow.clockwise" |
| 105 | | size={14} |
| 106 | | tintColor={iconColor} |
| 107 | | type="monochrome" |
| 108 | | /> |
| 109 | | )} |
| 110 | | </Pressable> |
| 111 | {discoveryAvailable ? ( |
| 112 | <Pressable |
| 113 | accessibilityRole="button" |
| 114 | disabled={controller.relayDiscovery.isRefreshing} |
| 115 | onPress={() => { |
| 116 | void controller.refreshRelayEnvironments(); |
| 117 | }} |
| 118 | className="h-9 w-9 items-center justify-center rounded-full bg-subtle active:opacity-70 disabled:opacity-50" |
| 119 | > |
| 120 | {controller.relayDiscovery.isRefreshing ? ( |
| 121 | <ActivityIndicator color={iconColor} size="small" /> |
| 122 | ) : ( |
| 123 | <SymbolView |
| 124 | name="arrow.clockwise" |
| 125 | size={14} |
| 126 | tintColor={iconColor} |
| 127 | type="monochrome" |
| 128 | /> |
| 129 | )} |
| 130 | </Pressable> |
| 131 | ) : null} |
| 111 | 132 | </View> |
| 112 | 133 | ) : null} |
| 113 | 134 | |
| ... | ... | @@ -152,7 +173,9 @@ function CloudEnvironmentRowsContent(props: CloudEnvironmentRowsProps) { |
| 152 | 173 | |
| 153 | 174 | {/* Rendered alongside any connected rows — a failed discovery must not |
| 154 | 175 | hide behind an otherwise-healthy list. */} |
| 155 | | {controller.relayDiscovery.error && !controller.relayDiscovery.isRefreshing ? ( |
| 176 | {discoveryAvailable && |
| 177 | controller.relayDiscovery.error && |
| 178 | !controller.relayDiscovery.isRefreshing ? ( |
| 156 | 179 | <View collapsable={false} className="gap-3 rounded-[24px] bg-card p-5"> |
| 157 | 180 | <Text className="text-base font-t3-bold text-foreground"> |
| 158 | 181 | Could not load T3 Connect environments |