Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

98 linesCodeBlameRaw
1import { Stack, router, useLocalSearchParams } from "expo-router";
2import { GitPullRequest, ListTodo } from "lucide-react-native";
3import { useState } from "react";
4import { ActivityIndicator, FlatList, RefreshControl, View } from "react-native";
5
6import { Checks, PullIcon } from "@/components/code";
7import { Segments } from "@/components/segments";
8import { Empty, ErrorView, Loading, Row, Screen, Text } from "@/components/ui";
9import { ItemRow } from "@/components/work";
10import { type ApiPull, useDescriptions, usePulls } from "@/lib/code";
11import { ago } from "@/lib/time";
12import { useWorkItems } from "@/lib/work";
13import { space, useColors } from "@/theme";
14
15type Tab = "pulls" | "work";
16type State = "open" | "closed";
17
18/**
19 * A repository: its pull requests, and its Work: the workspace's work
20 * items linked to it, in any role.
21 */
22export default function Repository() {
23 const { owner, repo } = useLocalSearchParams<{ owner: string; repo: string }>();
24 const [tab, setTab] = useState<Tab>("pulls");
25 const [state, setState] = useState<State>("open");
26 const description = useDescriptions(owner).data?.[`${owner}/${repo}`.toLowerCase()];
27 const pulls = usePulls(owner, repo, state);
28 const work = useWorkItems(owner, { state, repo });
29 const list = tab === "pulls" ? pulls : work;
30 const colors = useColors();
31
32 return (
33 <Screen>
34 <Stack.Screen options={{ title: repo }} />
35 <View style={{ padding: space.lg, gap: space.md }}>
36 <Text size="small" tone="muted">
37 {owner}/{repo}
38 {description ? ` · ${description}` : ""}
39 </Text>
40 <Segments
41 value={tab}
42 onChange={setTab}
43 options={[
44 { value: "pulls", label: "Pull requests" },
45 { value: "work", label: "Work" },
46 ]}
47 />
48 <Segments
49 value={state}
50 onChange={setState}
51 options={[
52 { value: "open", label: "Open" },
53 { value: "closed", label: "Closed" },
54 ]}
55 />
56 </View>
57 {list.isPending ? (
58 <Loading />
59 ) : list.error ? (
60 <ErrorView error={list.error} retry={() => list.refetch()} />
61 ) : tab === "pulls" ? (
62 <FlatList
63 data={pulls.data}
64 keyExtractor={(pull) => pull.id}
65 refreshControl={<RefreshControl refreshing={pulls.isRefetching} onRefresh={() => pulls.refetch()} tintColor={colors.accent} />}
66 ListEmptyComponent={<Empty icon={GitPullRequest} title={`No ${state} pull requests`} />}
67 renderItem={({ item }) => <PullRow pull={item} owner={owner} repo={repo} />}
68 />
69 ) : (
70 <FlatList
71 data={work.items}
72 keyExtractor={(item) => item.id}
73 refreshControl={<RefreshControl refreshing={work.isRefetching && !work.isFetchingNextPage} onRefresh={() => work.refetch()} tintColor={colors.accent} />}
74 onEndReached={() => work.hasNextPage && !work.isFetchingNextPage && work.fetchNextPage()}
75 onEndReachedThreshold={0.5}
76 ListFooterComponent={work.isFetchingNextPage ? <ActivityIndicator color={colors.accent} style={{ padding: space.lg }} /> : null}
77 ListEmptyComponent={<Empty icon={ListTodo} title={`No ${state} work items`} body={`Work items linked to ${repo} show here.`} />}
78 renderItem={({ item }) => (
79 <ItemRow item={item} onPress={() => router.push({ pathname: "/work/[number]", params: { number: String(item.number), workspace: item.workspace } })} />
80 )}
81 />
82 )}
83 </Screen>
84 );
85}
86
87function PullRow({ pull, owner, repo }: { pull: ApiPull; owner: string; repo: string }) {
88 const by = pull.author.display_username ?? pull.author.username;
89 return (
90 <Row
91 leading={<PullIcon pull={pull} />}
92 title={pull.title}
93 subtitle={`#${pull.number} · ${by}${pull.agent ? ` with ${pull.agent}` : ""}${pull.item ? ` · for #${pull.item}` : ""} · ${ago(pull.updated_at)}`}
94 trailing={<Checks status={pull.check_status} />}
95 onPress={() => router.push({ pathname: "/code/[owner]/[repo]/pull/[number]", params: { owner, repo, number: String(pull.number) } })}
96 />
97 );
98}