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