Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 47 additions & 24 deletions apps/mobile/src/features/connection/CloudEnvironmentRows.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { copyTextWithHaptic } from "../../lib/copyTextWithHaptic";
import { useThemeColor } from "../../lib/useThemeColor";
import type { ConnectedEnvironmentSummary } from "../../state/remote-runtime-types";
import { availableCloudEnvironmentPresentation } from "../cloud/cloudEnvironmentPresentation";
import { hasCloudPublicConfig } from "../cloud/publicConfig";
import { ConnectionStatusDot } from "./ConnectionStatusDot";
import { type RelayEnvironmentView, useConnectionController } from "./useConnectionController";

Expand All @@ -42,6 +43,11 @@ interface CloudEnvironmentRowsProps {
* with connect switches, availability status, refresh, and loading/error
* states. Shared between the Settings environments screen and the T3 Connect
* onboarding sheet.
*
* Already-connected relay environments render even without cloud config or a
* signed-in account — they are registered on this device and must stay
* reachable and removable. Only discovery (the available list, refresh, and
* its errors) requires a signed-in session.
*/
export function CloudEnvironmentRows(props: CloudEnvironmentRowsProps) {
// Showcase captures run without a Clerk publishable key, so `ClerkProvider`
Expand All @@ -50,20 +56,33 @@ export function CloudEnvironmentRows(props: CloudEnvironmentRowsProps) {
if (props.showcaseSignedIn !== undefined) {
return props.showcaseSignedIn ? <CloudEnvironmentRowsContent {...props} /> : null;
}
// No cloud config means no `ClerkProvider` either, so `useAuth` would throw.
if (!hasCloudPublicConfig()) {
return <ConnectedOnlyCloudEnvironmentRows {...props} />;
}
return <SignedInCloudEnvironmentRows {...props} />;
}

function SignedInCloudEnvironmentRows(props: CloudEnvironmentRowsProps) {
const { isSignedIn } = useAuth({ treatPendingAsSignedOut: false });
if (!isSignedIn) return null;
if (!isSignedIn) return <ConnectedOnlyCloudEnvironmentRows {...props} />;
return <CloudEnvironmentRowsContent {...props} />;
}

function CloudEnvironmentRowsContent(props: CloudEnvironmentRowsProps) {
function ConnectedOnlyCloudEnvironmentRows(props: CloudEnvironmentRowsProps) {
if (props.connectedCloudEnvironments.length === 0) return null;
return <CloudEnvironmentRowsContent {...props} discoveryAvailable={false} />;
}

function CloudEnvironmentRowsContent(
props: CloudEnvironmentRowsProps & { readonly discoveryAvailable?: boolean },
) {
const controller = useConnectionController();
const iconColor = useThemeColor("--color-icon");
const availableCloudEnvironments =
props.showcaseAvailableEnvironments ?? controller.availableRelayEnvironments;
const discoveryAvailable = props.discoveryAvailable ?? true;
const availableCloudEnvironments = discoveryAvailable
? (props.showcaseAvailableEnvironments ?? controller.availableRelayEnvironments)
: [];
const [expandedErrorId, setExpandedErrorId] = useState<string | null>(null);
const hasCloudRows =
props.connectedCloudEnvironments.length > 0 || availableCloudEnvironments.length > 0;
Expand All @@ -89,25 +108,27 @@ function CloudEnvironmentRowsContent(props: CloudEnvironmentRowsProps) {
{showHeader ? (
<View className="flex-row items-center justify-between px-1">
<Text className="text-sm font-t3-bold uppercase text-foreground-muted">T3 Connect</Text>
<Pressable
accessibilityRole="button"
disabled={controller.relayDiscovery.isRefreshing}
onPress={() => {
void controller.refreshRelayEnvironments();
}}
className="h-9 w-9 items-center justify-center rounded-full bg-subtle active:opacity-70 disabled:opacity-50"
>
{controller.relayDiscovery.isRefreshing ? (
<ActivityIndicator color={iconColor} size="small" />
) : (
<SymbolView
name="arrow.clockwise"
size={14}
tintColor={iconColor}
type="monochrome"
/>
)}
</Pressable>
{discoveryAvailable ? (
<Pressable
accessibilityRole="button"
disabled={controller.relayDiscovery.isRefreshing}
onPress={() => {
void controller.refreshRelayEnvironments();
}}
className="h-9 w-9 items-center justify-center rounded-full bg-subtle active:opacity-70 disabled:opacity-50"
>
{controller.relayDiscovery.isRefreshing ? (
<ActivityIndicator color={iconColor} size="small" />
) : (
<SymbolView
name="arrow.clockwise"
size={14}
tintColor={iconColor}
type="monochrome"
/>
)}
</Pressable>
) : null}
</View>
) : null}

Expand Down Expand Up @@ -152,7 +173,9 @@ function CloudEnvironmentRowsContent(props: CloudEnvironmentRowsProps) {

{/* Rendered alongside any connected rows — a failed discovery must not
hide behind an otherwise-healthy list. */}
{controller.relayDiscovery.error && !controller.relayDiscovery.isRefreshing ? (
{discoveryAvailable &&
controller.relayDiscovery.error &&
!controller.relayDiscovery.isRefreshing ? (
<View collapsable={false} className="gap-3 rounded-[24px] bg-card p-5">
<Text className="text-base font-t3-bold text-foreground">
Could not load T3 Connect environments
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { useSafeAreaInsets } from "react-native-safe-area-context";

import { AppText as Text } from "../../components/AppText";
import { AndroidScreenHeader } from "../../components/AndroidScreenHeader";
import { hasCloudPublicConfig } from "../cloud/publicConfig";
import { CloudEnvironmentRows } from "../connection/CloudEnvironmentRows";
import { ConnectionEnvironmentRow } from "../connection/ConnectionEnvironmentRow";
import { splitEnvironmentSections } from "../connection/environmentSections";
Expand Down Expand Up @@ -161,18 +160,19 @@ export function SettingsEnvironmentsRouteScreen() {
</View>
)}

{hasCloudPublicConfig() || SHOWCASE_ENABLED ? (
<CloudEnvironmentRows
connectedCloudEnvironments={connectedCloudEnvironments}
onReconnectEnvironment={onReconnectEnvironment}
{...(SHOWCASE_ENABLED
? {
showcaseAvailableEnvironments: SHOWCASE_AVAILABLE_CLOUD_ENVIRONMENTS,
showcaseSignedIn: true,
}
: {})}
/>
) : null}
{/* Always mounted: already-connected relay environments must stay
visible (and removable) even when cloud config is missing or the
user is signed out — the component gates discovery itself. */}
<CloudEnvironmentRows
connectedCloudEnvironments={connectedCloudEnvironments}
onReconnectEnvironment={onReconnectEnvironment}
{...(SHOWCASE_ENABLED
? {
showcaseAvailableEnvironments: SHOWCASE_AVAILABLE_CLOUD_ENVIRONMENTS,
showcaseSignedIn: true,
}
: {})}
/>
</ScrollView>
</View>
);
Expand Down
Loading