import { useAtomValue } from "@effect/atom-react"; import { useNavigation, type StaticScreenProps } from "@react-navigation/native"; import { scopeThreadRef } from "@t3tools/client-runtime/environment"; import { isAtomCommandInterrupted, squashAtomCommandFailure, } from "@t3tools/client-runtime/state/runtime"; import type { EnvironmentId, ThreadId } from "@t3tools/contracts"; import { useMemo, useState } from "react"; import { Platform, ScrollView, Share, View } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { AndroidSheetHeader } from "../../components/ui/AndroidScreenHeader"; import { SymbolView } from "../../components/ui/AppSymbol"; import { AppText as Text } from "../../components/ui/AppText"; import { Button } from "../../components/ui/Button"; import * as Dialog from "../../components/ui/Dialog"; import { ScreenContent } from "../../components/ui/ScreenContent"; import { SheetHeader } from "../../components/ui/SheetHeader"; import { useDeadlinePassed } from "../../lib/clock"; import { tryCopyTextWithHaptic } from "../../lib/copyTextWithHaptic"; import { NativeStackScreenOptions } from "../../native/StackHeader"; import { environmentServerConfigsAtom, serverEnvironment } from "../../state/server"; import { useAtomCommand } from "../../state/use-atom-command"; import { useThreadGuestShare } from "./ThreadGuestShareBar"; const TITLE = "Share with guest"; const COPY_FEEDBACK_DURATION_MS = 1200; /** * Whether this environment offers "Share with guest": the user may create links there, and its * Guest links beta is on (Settings > Labs). The server refuses links while the beta is off. */ export function useCanShareWithGuest(environmentId: EnvironmentId | null): boolean { const permitted = useAtomValue(serverEnvironment.createGuestShare.permissionAtom(environmentId)); const enabled = useAtomValue( environmentServerConfigsAtom, (configs) => environmentId !== null && configs.get(environmentId)?.settings.guestSharingEnabled === true, ); return permitted && enabled; } type CreateState = | { readonly status: "idle" } | { readonly status: "creating" } | { readonly status: "created"; readonly url: string } | { readonly status: "failed"; readonly message: string }; type GuestShareTarget = { readonly environmentId: EnvironmentId; readonly threadId: ThreadId }; export function ThreadGuestShareSheet({ route }: StaticScreenProps) { const navigation = useNavigation(); const insets = useSafeAreaInsets(); const { environmentId, threadId } = route.params; const threadRef = useMemo( () => scopeThreadRef(environmentId, threadId), [environmentId, threadId], ); const activeShare = useThreadGuestShare(threadRef); const canCreate = useAtomValue(serverEnvironment.createGuestShare.permissionAtom(environmentId)); const createGuestShare = useAtomCommand(serverEnvironment.createGuestShare, { reportFailure: false, }); const [state, setState] = useState({ status: "idle" }); const [copiedAtMs, setCopiedAtMs] = useState(null); const copyFeedbackEnded = useDeadlinePassed( copiedAtMs === null ? null : copiedAtMs + COPY_FEEDBACK_DURATION_MS, ); const copied = copiedAtMs !== null && !copyFeedbackEnded; const creating = state.status === "creating"; const close = () => navigation.goBack(); const create = async () => { setState({ status: "creating" }); const result = await createGuestShare({ environmentId, input: { threadId } }); if (result._tag === "Success") { setState({ status: "created", url: result.value.url }); return; } if (isAtomCommandInterrupted(result)) { setState({ status: "idle" }); return; } const error = squashAtomCommandFailure(result); setState({ status: "failed", message: error instanceof Error ? error.message : "Could not create a guest link.", }); }; const copy = async (url: string) => { if (!(await tryCopyTextWithHaptic(url, { target: "guest link" }))) { Dialog.alert("Could not copy", "Try again."); return; } setCopiedAtMs(Date.now()); }; const share = (url: string) => { // Android ignores `url`; iOS turns a `message` URL into plain text instead of a link preview. Share.share(Platform.OS === "ios" ? { url } : { message: url }).catch((cause: unknown) => console.error("Failed to share guest link.", cause), ); }; const content = ( {state.status === "created" ? ( <> {state.url} Send it to one person. It works once and expires in 24 hours.