import { type GitActionRequestInput, buildMenuItems, getGitActionDisabledReason, requiresDefaultBranchConfirmation, } from "@t3tools/client-runtime"; import type { EnvironmentId, ThreadId } from "@t3tools/contracts"; import { useLocalSearchParams, useRouter } from "expo-router"; import { SymbolView } from "expo-symbols"; import { useCallback, useEffect, useMemo } from "react"; import { Alert, Linking, Pressable, ScrollView, View } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { useThemeColor } from "../../../lib/useThemeColor"; import { AppText as Text } from "../../../components/AppText"; import { buildThreadReviewRoutePath } from "../../../lib/routes"; import { useVcsStatus } from "../../../state/use-vcs-status"; import { useThreadSelection } from "../../../state/use-thread-selection"; import { useSelectedThreadGitActions } from "../../../state/use-selected-thread-git-actions"; import { useSelectedThreadGitState } from "../../../state/use-selected-thread-git-state"; import { useSelectedThreadWorktree } from "../../../state/use-selected-thread-worktree"; import { MetaCard, SheetListRow, menuItemIconName, statusSummary } from "./gitSheetComponents"; export function GitOverviewSheet() { const router = useRouter(); const insets = useSafeAreaInsets(); const { environmentId, threadId } = useLocalSearchParams<{ environmentId: EnvironmentId; threadId: ThreadId; }>(); const { selectedThread } = useThreadSelection(); const { selectedThreadCwd, selectedThreadWorktreePath } = useSelectedThreadWorktree(); const gitState = useSelectedThreadGitState(); const gitActions = useSelectedThreadGitActions(); const iconColor = useThemeColor("--color-icon"); const borderColor = useThemeColor("--color-border"); const gitStatus = useVcsStatus({ environmentId: selectedThread?.environmentId ?? null, cwd: selectedThreadCwd, }); const currentBranchLabel = gitStatus.data?.refName ?? selectedThread?.branch ?? "Detached HEAD"; const currentWorktreePath = selectedThreadWorktreePath; const gitOperationLabel = gitState.gitOperationLabel; const busy = gitOperationLabel !== null; const isRepo = gitStatus.data?.isRepo ?? true; const hasPrimaryRemote = gitStatus.data?.hasPrimaryRemote ?? false; const isDefaultRef = gitStatus.data?.isDefaultRef ?? false; const menuItems = useMemo( () => (isRepo ? buildMenuItems(gitStatus.data, busy, hasPrimaryRemote) : []), [busy, gitStatus.data, hasPrimaryRemote, isRepo], ); const sheetMenuItems = useMemo( () => menuItems.map((item) => ({ item, disabledReason: getGitActionDisabledReason({ item, gitStatus: gitStatus.data, isBusy: busy, hasOriginRemote: hasPrimaryRemote, }), })), [busy, gitStatus.data, hasPrimaryRemote, menuItems], ); useEffect(() => { void gitActions.refreshSelectedThreadGitStatus({ quiet: true }); }, [gitActions]); const openExistingPr = useCallback(async () => { const prUrl = gitStatus.data?.pr?.state === "open" ? gitStatus.data.pr.url : null; if (!prUrl) { Alert.alert("No open PR", "This branch does not have an open pull request."); return; } try { await Linking.openURL(prUrl); } catch (error) { Alert.alert( "Unable to open PR", error instanceof Error ? error.message : "An error occurred.", ); } }, [gitStatus.data]); const runActionWithPrompt = useCallback( async (input: GitActionRequestInput) => { const confirmableAction = input.action === "push" || input.action === "create_pr" || input.action === "commit_push" || input.action === "commit_push_pr" ? input.action : null; const branchName = gitStatus.data?.refName; if ( branchName && confirmableAction && !input.featureBranch && requiresDefaultBranchConfirmation(input.action, isDefaultRef) ) { router.push({ pathname: "/threads/[environmentId]/[threadId]/git-confirm", params: { environmentId, threadId, confirmAction: confirmableAction, branchName, includesCommit: String( input.action === "commit_push" || input.action === "commit_push_pr", ), }, }); return; } router.dismiss(); await gitActions.onRunSelectedThreadGitAction(input); }, [environmentId, gitActions, gitStatus.data, isDefaultRef, router, threadId], ); const onPressMenuItem = useCallback( async (item: (typeof menuItems)[number]) => { if (item.disabled) return; if (item.kind === "open_pr") { await openExistingPr(); return; } if (item.dialogAction === "commit") { router.push({ pathname: "/threads/[environmentId]/[threadId]/git/commit", params: { environmentId, threadId }, }); return; } if (item.dialogAction === "push") { await runActionWithPrompt({ action: "push" }); return; } if (item.dialogAction === "create_pr") { await runActionWithPrompt({ action: "create_pr" }); } }, [environmentId, openExistingPr, router, runActionWithPrompt, threadId], ); return ( void gitActions.refreshSelectedThreadGitStatus()} > Branch {currentBranchLabel} {statusSummary(gitStatus.data)} {sheetMenuItems.map(({ item, disabledReason }, index) => ( {index > 0 ? ( ) : null} void onPressMenuItem(item)} /> ))} {(gitStatus.data?.behindCount ?? 0) > 0 ? ( <> void gitActions.onPullSelectedThreadBranch()} /> ) : null} router.push(buildThreadReviewRoutePath({ environmentId, threadId }))} /> router.push({ pathname: "/threads/[environmentId]/[threadId]/git/branches", params: { environmentId, threadId }, }) } /> {currentWorktreePath ? : null} ); }