Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesPull RequestsActionsSecurityInsightsSettings
✨ AI
More
Blame · Line-by-line history

useRepo.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

useRepo.tsBlame199 lines · 1 contributor
9989d86Claude1import { useCallback, useEffect, useState } from 'react';
2import { getRepo, getTree, listCommits, listBranches } from '../api/repos';
3import type { Repository, TreeEntry, CommitEntry, BranchInfo } from '../api/repos';
4
5export interface UseRepoReturn {
6 repo: Repository | null;
7 isLoading: boolean;
8 error: string | null;
9 refresh: () => void;
10}
11
12/** Fetches and caches a single repository. */
13export function useRepo(owner: string, repoName: string): UseRepoReturn {
14 const [repo, setRepo] = useState<Repository | null>(null);
15 const [isLoading, setIsLoading] = useState(true);
16 const [error, setError] = useState<string | null>(null);
17 const [tick, setTick] = useState(0);
18
19 useEffect(() => {
20 if (!owner || !repoName) return;
21 let cancelled = false;
22
23 setIsLoading(true);
24 setError(null);
25
26 getRepo(owner, repoName)
27 .then((data) => {
28 if (!cancelled) {
29 setRepo(data);
30 }
31 })
32 .catch((err) => {
33 if (!cancelled) {
34 setError(err instanceof Error ? err.message : 'Failed to load repository');
35 }
36 })
37 .finally(() => {
38 if (!cancelled) setIsLoading(false);
39 });
40
41 return () => {
42 cancelled = true;
43 };
44 }, [owner, repoName, tick]);
45
46 const refresh = useCallback(() => setTick((t) => t + 1), []);
47
48 return { repo, isLoading, error, refresh };
49}
50
51export interface UseTreeReturn {
52 entries: TreeEntry[];
53 isLoading: boolean;
54 error: string | null;
55 refresh: () => void;
56}
57
58/** Fetches the file tree for a repo at a given ref and path. */
59export function useTree(
60 owner: string,
61 repoName: string,
62 ref = 'HEAD',
63 path = '',
64): UseTreeReturn {
65 const [entries, setEntries] = useState<TreeEntry[]>([]);
66 const [isLoading, setIsLoading] = useState(true);
67 const [error, setError] = useState<string | null>(null);
68 const [tick, setTick] = useState(0);
69
70 useEffect(() => {
71 if (!owner || !repoName) return;
72 let cancelled = false;
73
74 setIsLoading(true);
75 setError(null);
76
77 getTree(owner, repoName, ref, path)
78 .then((data) => {
79 if (!cancelled) setEntries(data);
80 })
81 .catch((err) => {
82 if (!cancelled) {
83 setError(err instanceof Error ? err.message : 'Failed to load files');
84 }
85 })
86 .finally(() => {
87 if (!cancelled) setIsLoading(false);
88 });
89
90 return () => {
91 cancelled = true;
92 };
93 }, [owner, repoName, ref, path, tick]);
94
95 const refresh = useCallback(() => setTick((t) => t + 1), []);
96
97 return { entries, isLoading, error, refresh };
98}
99
100export interface UseCommitsReturn {
101 commits: CommitEntry[];
102 isLoading: boolean;
103 error: string | null;
104 loadMore: () => void;
105 hasMore: boolean;
106}
107
108/** Fetches commits for a repo with pagination. */
109export function useCommits(
110 owner: string,
111 repoName: string,
112 branch = 'HEAD',
113): UseCommitsReturn {
114 const [commits, setCommits] = useState<CommitEntry[]>([]);
115 const [page, setPage] = useState(1);
116 const [isLoading, setIsLoading] = useState(true);
117 const [error, setError] = useState<string | null>(null);
118 const [hasMore, setHasMore] = useState(true);
119
120 useEffect(() => {
121 if (!owner || !repoName) return;
122 let cancelled = false;
123
124 setIsLoading(true);
125 setError(null);
126
127 listCommits(owner, repoName, branch, page)
128 .then((data) => {
129 if (!cancelled) {
130 if (page === 1) {
131 setCommits(data);
132 } else {
133 setCommits((prev) => [...prev, ...data]);
134 }
135 setHasMore(data.length === 30);
136 }
137 })
138 .catch((err) => {
139 if (!cancelled) {
140 setError(err instanceof Error ? err.message : 'Failed to load commits');
141 }
142 })
143 .finally(() => {
144 if (!cancelled) setIsLoading(false);
145 });
146
147 return () => {
148 cancelled = true;
149 };
150 }, [owner, repoName, branch, page]);
151
152 const loadMore = useCallback(() => {
153 if (!isLoading && hasMore) {
154 setPage((p) => p + 1);
155 }
156 }, [isLoading, hasMore]);
157
158 return { commits, isLoading, error, loadMore, hasMore };
159}
160
161export interface UseBranchesReturn {
162 branches: BranchInfo[];
163 isLoading: boolean;
164 error: string | null;
165}
166
167/** Fetches branches for a repo. */
168export function useBranches(owner: string, repoName: string): UseBranchesReturn {
169 const [branches, setBranches] = useState<BranchInfo[]>([]);
170 const [isLoading, setIsLoading] = useState(true);
171 const [error, setError] = useState<string | null>(null);
172
173 useEffect(() => {
174 if (!owner || !repoName) return;
175 let cancelled = false;
176
177 setIsLoading(true);
178 setError(null);
179
180 listBranches(owner, repoName)
181 .then((data) => {
182 if (!cancelled) setBranches(data);
183 })
184 .catch((err) => {
185 if (!cancelled) {
186 setError(err instanceof Error ? err.message : 'Failed to load branches');
187 }
188 })
189 .finally(() => {
190 if (!cancelled) setIsLoading(false);
191 });
192
193 return () => {
194 cancelled = true;
195 };
196 }, [owner, repoName]);
197
198 return { branches, isLoading, error };
199}