Function bodies 210 total
GET function · typescript · L1-L3 (3 LOC)src/app/api/health/route.ts
export async function GET() {
return Response.json({ status: "healthy" });
}ChatPage function · typescript · L32-L285 (254 LOC)src/app/chat/page.tsx
export default function ChatPage() {
const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [testSteps, setTestSteps] = useState<TestStep[]>([]);
const messagesEndRef = useRef<HTMLDivElement>(null);
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
};
useEffect(() => {
scrollToBottom();
}, [messages]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!input.trim() || isLoading) return;
const userMessage: Message = {
id: crypto.randomUUID(),
role: "user",
content: input.trim(),
timestamp: new Date(),
};
setMessages((prev) => [...prev, userMessage]);
setInput("");
setIsLoading(true);
// Simulate test execution (will be replaced with actual LangGraph integration)
await simulateTestExecution(userMessage.content);
setIsLoadiRootLayout function · typescript · L34-L62 (29 LOC)src/app/layout.tsx
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" suppressHydrationWarning>
<head>
{/* Runtime environment config - generated by docker-entrypoint.sh */}
<script src="/__ENV.js" />
</head>
<body
className={`${inter.variable} ${jetbrainsMono.variable} font-sans antialiased`}
>
<ThemeProvider
attribute="class"
defaultTheme="dark"
enableSystem
disableTransitionOnChange={false}
>
<NuqsAdapter>
<AppLayout>{children}</AppLayout>
</NuqsAdapter>
<Toaster richColors position="bottom-right" />
</ThemeProvider>
</body>
</html>
);
}HomePage function · typescript · L34-L194 (161 LOC)src/app/page.tsx
export default function HomePage() {
const [projects, setProjects] = useState<Project[]>([]);
const [stats, setStats] = useState<Stats>({
totalProjects: 0,
totalTestCases: 0,
recentRuns: 0,
passRate: 0,
});
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchProjects();
fetchStats();
}, []);
async function fetchProjects() {
try {
const res = await fetch(`${API_URL}/api/projects`);
if (res.ok) {
const data = await res.json();
setProjects(data);
}
} catch (error) {
console.error("Failed to fetch projects:", error);
} finally {
setLoading(false);
}
}
async function fetchStats() {
try {
const res = await fetch(`${API_URL}/api/projects/stats`);
if (res.ok) {
const data = await res.json();
setStats({
totalProjects: data.total_projects,
totalTestCases: data.total_test_cases,
recentRuns: data.recent_runs,
fetchProjects function · typescript · L49-L61 (13 LOC)src/app/page.tsx
async function fetchProjects() {
try {
const res = await fetch(`${API_URL}/api/projects`);
if (res.ok) {
const data = await res.json();
setProjects(data);
}
} catch (error) {
console.error("Failed to fetch projects:", error);
} finally {
setLoading(false);
}
}fetchStats function · typescript · L63-L78 (16 LOC)src/app/page.tsx
async function fetchStats() {
try {
const res = await fetch(`${API_URL}/api/projects/stats`);
if (res.ok) {
const data = await res.json();
setStats({
totalProjects: data.total_projects,
totalTestCases: data.total_test_cases,
recentRuns: data.recent_runs,
passRate: data.pass_rate,
});
}
} catch (error) {
console.error("Failed to fetch stats:", error);
}
}StatsCard function · typescript · L196-L220 (25 LOC)src/app/page.tsx
function StatsCard({
icon,
label,
value,
}: {
icon: React.ReactNode;
label: string;
value: number | string;
}) {
return (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className="p-4 rounded-lg border border-border bg-card"
>
<div className="flex items-center gap-3">
<div className="p-2 rounded-lg bg-primary/10 text-primary">{icon}</div>
<div>
<p className="text-sm text-muted-foreground">{label}</p>
<p className="text-2xl font-bold">{value}</p>
</div>
</div>
</motion.div>
);
}Want fix-PRs on findings? Install Repobility's GitHub App · github.com/apps/repobility-bot
ProjectCard function · typescript · L222-L241 (20 LOC)src/app/page.tsx
function ProjectCard({ project }: { project: Project }) {
return (
<Link href={`/projects/${project.id}`}>
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
whileHover={{ scale: 1.02 }}
className="p-6 rounded-lg border border-border bg-card hover:border-primary/50 transition-colors cursor-pointer"
>
<h3 className="font-semibold mb-2">{project.name}</h3>
<p className="text-sm text-muted-foreground mb-4 line-clamp-2">
{project.description || "No description"}
</p>
<p className="text-xs text-muted-foreground font-mono truncate">
{project.base_url}
</p>
</motion.div>
</Link>
);
}StepCard function · typescript · L243-L271 (29 LOC)src/app/page.tsx
function StepCard({
step,
icon,
title,
description,
}: {
step: number;
icon: React.ReactNode;
title: string;
description: string;
}) {
return (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: step * 0.1 }}
className="p-6 rounded-lg border border-border bg-card relative"
>
<div className="absolute -top-3 -left-3 w-8 h-8 rounded-full bg-primary text-primary-foreground flex items-center justify-center text-sm font-bold">
{step}
</div>
<div className="p-3 rounded-lg bg-primary/10 text-primary w-fit mb-4 mt-2">
{icon}
</div>
<h3 className="font-semibold mb-2">{title}</h3>
<p className="text-sm text-muted-foreground">{description}</p>
</motion.div>
);
}fetchProject function · typescript · L121-L131 (11 LOC)src/app/projects/[id]/chat/page.tsx
async function fetchProject() {
try {
const res = await fetch(`${API_URL}/api/projects/${projectId}`);
if (res.ok) {
const data = await res.json();
setProject(data);
}
} catch (error) {
console.error("Failed to fetch project:", error);
}
}fetchFixtures function · typescript · L133-L143 (11 LOC)src/app/projects/[id]/chat/page.tsx
async function fetchFixtures() {
try {
const res = await fetch(`${API_URL}/api/projects/${projectId}/fixtures`);
if (res.ok) {
const data = await res.json();
setFixtures(data);
}
} catch (error) {
console.error("Failed to fetch fixtures:", error);
}
}fmtDuration function · typescript · L72-L77 (6 LOC)src/app/projects/[id]/dashboard/page.tsx
function fmtDuration(ms: number | null): string {
if (!ms) return "—";
if (ms < 1000) return `${ms}ms`;
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
return `${(ms / 60000).toFixed(1)}m`;
}fmtDate function · typescript · L79-L86 (8 LOC)src/app/projects/[id]/dashboard/page.tsx
function fmtDate(iso: string): string {
return new Date(iso).toLocaleString(undefined, {
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
});
}shortDate function · typescript · L88-L91 (4 LOC)src/app/projects/[id]/dashboard/page.tsx
function shortDate(iso: string): string {
const d = new Date(iso);
return `${d.getMonth() + 1}/${d.getDate()}`;
}KpiCard function · typescript · L96-L126 (31 LOC)src/app/projects/[id]/dashboard/page.tsx
function KpiCard({
label,
value,
sub,
icon,
color,
}: {
label: string;
value: string | number;
sub?: string;
icon: React.ReactNode;
color: string;
}) {
return (
<div className="rounded-xl border bg-card p-5 flex items-start gap-4 shadow-sm">
<div
className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg"
style={{ background: color + "20", color }}
>
{icon}
</div>
<div>
<p className="text-xs font-medium text-muted-foreground uppercase tracking-wide">
{label}
</p>
<p className="text-2xl font-bold mt-0.5">{value}</p>
{sub && <p className="text-xs text-muted-foreground mt-0.5">{sub}</p>}
</div>
</div>
);
}Repobility · code-quality intelligence · https://repobility.com
SectionTitle function · typescript · L128-L134 (7 LOC)src/app/projects/[id]/dashboard/page.tsx
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
<h2 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide mb-3">
{children}
</h2>
);
}ChartCard function · typescript · L136-L151 (16 LOC)src/app/projects/[id]/dashboard/page.tsx
function ChartCard({
title,
children,
className,
}: {
title: string;
children: React.ReactNode;
className?: string;
}) {
return (
<div className={cn("rounded-xl border bg-card p-5 shadow-sm", className)}>
<SectionTitle>{title}</SectionTitle>
{children}
</div>
);
}StatusBadgeSmall function · typescript · L153-L163 (11 LOC)src/app/projects/[id]/dashboard/page.tsx
function StatusBadgeSmall({ status }: { status: string }) {
if (status === "passed")
return (
<Badge className="bg-green-500/15 text-green-600 border-0 text-xs">Passed</Badge>
);
if (status === "failed")
return (
<Badge className="bg-red-500/15 text-red-500 border-0 text-xs">Failed</Badge>
);
return <Badge variant="secondary" className="text-xs">{status}</Badge>;
}ProjectDashboardPage function · typescript · L168-L552 (385 LOC)src/app/projects/[id]/dashboard/page.tsx
export default function ProjectDashboardPage() {
const { id } = useParams<{ id: string }>();
const [data, setData] = useState<DashboardData | null>(null);
const [loading, setLoading] = useState(true);
const [lastRefreshed, setLastRefreshed] = useState<Date | null>(null);
const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);
const load = useCallback(async () => {
setLoading(true);
try {
const res = await fetch(
`${process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000"}/api/projects/${id}/dashboard`
);
if (!res.ok) throw new Error("Failed to fetch dashboard");
setData(await res.json());
setLastRefreshed(new Date());
} catch {
// keep stale data visible
} finally {
setLoading(false);
}
}, [id]);
useEffect(() => {
load();
}, [load]);
// Relative refresh label
const refreshLabel = lastRefreshed
? (() => {
const secs = Math.floor((Date.now() - FixturesPage function · typescript · L6-L15 (10 LOC)src/app/projects/[id]/fixtures/page.tsx
export default function FixturesPage() {
const params = useParams();
const projectId = params.id as string;
return (
<div className="container mx-auto px-6 py-8 max-w-4xl">
<FixturesTab projectId={projectId} />
</div>
);
}fetchProject function · typescript · L110-L122 (13 LOC)src/app/projects/[id]/page.tsx
async function fetchProject() {
try {
const res = await fetch(`${API_URL}/api/projects/${projectId}`);
if (res.ok) {
const data = await res.json();
setProject(data);
}
} catch (error) {
console.error("Failed to fetch project:", error);
} finally {
setLoading(false);
}
}fetchTestRuns function · typescript · L124-L136 (13 LOC)src/app/projects/[id]/page.tsx
async function fetchTestRuns() {
try {
const res = await fetch(
`${API_URL}/api/test-runs/project/${projectId}`
);
if (res.ok) {
const data = await res.json();
setTestRuns(data);
}
} catch (error) {
console.error("Failed to fetch test runs:", error);
}
}fetchTestCases function · typescript · L138-L150 (13 LOC)src/app/projects/[id]/page.tsx
async function fetchTestCases() {
try {
const res = await fetch(
`${API_URL}/api/test-cases/project/${projectId}`
);
if (res.ok) {
const data = await res.json();
setTestCases(data);
}
} catch (error) {
console.error("Failed to fetch test cases:", error);
}
}Repobility · code-quality intelligence platform · https://repobility.com
fetchBrowsers function · typescript · L152-L165 (14 LOC)src/app/projects/[id]/page.tsx
async function fetchBrowsers() {
try {
const res = await fetch(`${API_URL}/api/test-runs/browsers`);
if (res.ok) {
const data = await res.json();
setBrowsers(data.browsers || []);
if (data.default && !selectedBrowser) {
setSelectedBrowser(data.default);
}
}
} catch (error) {
console.error("Failed to fetch browsers:", error);
}
}handleDeleteProject function · typescript · L180-L197 (18 LOC)src/app/projects/[id]/page.tsx
async function handleDeleteProject() {
setIsDeleting(true);
try {
const res = await fetch(`${API_URL}/api/projects/${projectId}`, {
method: "DELETE",
});
if (res.ok) {
router.push("/");
} else {
console.error("Failed to delete project");
}
} catch (error) {
console.error("Failed to delete project:", error);
} finally {
setIsDeleting(false);
setShowDeleteDialog(false);
}
}toggleSuiteExpanded function · typescript · L380-L390 (11 LOC)src/app/projects/[id]/page.tsx
function toggleSuiteExpanded(batchId: string) {
setExpandedSuites((prev) => {
const newSet = new Set(prev);
if (newSet.has(batchId)) {
newSet.delete(batchId);
} else {
newSet.add(batchId);
}
return newSet;
});
}toggleTag function · typescript · L393-L397 (5 LOC)src/app/projects/[id]/page.tsx
function toggleTag(tag: string) {
setSelectedTags((prev) =>
prev.includes(tag) ? prev.filter((t) => t !== tag) : [...prev, tag]
);
}clearTags function · typescript · L400-L402 (3 LOC)src/app/projects/[id]/page.tsx
function clearTags() {
setSelectedTags([]);
}handleRunBatch function · typescript · L405-L507 (103 LOC)src/app/projects/[id]/page.tsx
async function handleRunBatch() {
if (runnableTestCases.length === 0) return;
setIsRunningBatch(true);
setBatchProgress({
currentTest: "",
currentIndex: 0,
totalTests: runnableTestCases.length,
completedTests: [],
skippedCount: skippedCount,
});
try {
const response = await fetch(
`${API_URL}/api/test-cases/project/${projectId}/run-batch/stream`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
test_case_ids: runnableTestCases.map((tc) => tc.id),
browser: selectedBrowser,
}),
}
);
if (!response.ok) {
throw new Error("Failed to start batch run");
}
const reader = response.body?.getReader();
const decoder = new TextDecoder();
if (!reader) {
throw new Error("No response body");
}
// Buffer for handling chunked SSE data
let buffer fetchProject function · typescript · L90-L101 (12 LOC)src/app/projects/[id]/record/page.tsx
async function fetchProject() {
try {
const res = await fetch(`${API_URL}/api/projects/${projectId}`);
if (res.ok) {
const data = await res.json();
setProject(data);
setBaseUrl(activeEnv?.base_url || data.base_url || "");
}
} catch (err) {
console.error("Failed to fetch project:", err);
}
}formatRelativeDate function · typescript · L113-L127 (15 LOC)src/app/projects/[id]/runs/page.tsx
function formatRelativeDate(dateStr: string): string {
const now = Date.now();
const then = new Date(dateStr).getTime();
const diffMs = now - then;
const diffSec = Math.floor(diffMs / 1000);
if (diffSec < 60) return "just now";
const diffMin = Math.floor(diffSec / 60);
if (diffMin < 60) return `${diffMin}m ago`;
const diffHr = Math.floor(diffMin / 60);
if (diffHr < 24) return `${diffHr}h ago`;
const diffDay = Math.floor(diffHr / 24);
if (diffDay < 30) return `${diffDay}d ago`;
return new Date(dateStr).toLocaleDateString();
}All rows scored by the Repobility analyzer (https://repobility.com)
fetchProjectPrefix function · typescript · L160-L170 (11 LOC)src/app/projects/[id]/runs/page.tsx
async function fetchProjectPrefix() {
try {
const res = await fetch(`${API_URL}/api/projects/${projectId}`);
if (res.ok) {
const data = await res.json();
setProjectPrefix(data.test_case_prefix || null);
}
} catch (error) {
console.error("Failed to fetch project:", error);
}
}fetchTestRuns function · typescript · L206-L220 (15 LOC)src/app/projects/[id]/runs/page.tsx
async function fetchTestRuns() {
try {
const res = await fetch(
`${API_URL}/api/test-runs/project/${projectId}`
);
if (res.ok) {
const data = await res.json();
setTestRuns(data);
}
} catch (error) {
console.error("Failed to fetch test runs:", error);
} finally {
setLoading(false);
}
}fetchTestCases function · typescript · L222-L234 (13 LOC)src/app/projects/[id]/runs/page.tsx
async function fetchTestCases() {
try {
const res = await fetch(
`${API_URL}/api/test-cases/project/${projectId}`
);
if (res.ok) {
const data = await res.json();
setTestCases(data);
}
} catch (error) {
console.error("Failed to fetch test cases:", error);
}
}fetchRunSteps function · typescript · L236-L255 (20 LOC)src/app/projects/[id]/runs/page.tsx
async function fetchRunSteps(runId: number) {
if (runSteps.has(runId)) return;
setLoadingSteps((prev) => new Set(prev).add(runId));
try {
const res = await fetch(`${API_URL}/api/test-runs/${runId}/steps`);
if (res.ok) {
const data = await res.json();
setRunSteps((prev) => new Map(prev).set(runId, data));
}
} catch (error) {
console.error("Failed to fetch run steps:", error);
} finally {
setLoadingSteps((prev) => {
const newSet = new Set(prev);
newSet.delete(runId);
return newSet;
});
}
}handleClearAll function · typescript · L257-L280 (24 LOC)src/app/projects/[id]/runs/page.tsx
async function handleClearAll() {
setClearingAll(true);
try {
const res = await fetch(
`${API_URL}/api/test-runs/project/${projectId}`,
{ method: "DELETE" }
);
if (res.ok) {
const data = await res.json();
setTestRuns([]);
setRunSteps(new Map());
setExpandedRuns(new Set());
setExpandedSuites(new Set());
toast.success(`Cleared ${data.deleted} run(s)`);
} else {
toast.error("Failed to clear history");
}
} catch {
toast.error("Failed to clear history");
} finally {
setClearingAll(false);
setClearAllOpen(false);
}
}handleDeleteRun function · typescript · L282-L303 (22 LOC)src/app/projects/[id]/runs/page.tsx
async function handleDeleteRun(runId: number) {
try {
const res = await fetch(`${API_URL}/api/test-runs/${runId}`, {
method: "DELETE",
});
if (res.ok) {
setTestRuns((prev) => prev.filter((r) => r.id !== runId));
setRunSteps((prev) => {
const newMap = new Map(prev);
newMap.delete(runId);
return newMap;
});
toast.success("Run deleted");
} else {
toast.error("Failed to delete run");
}
} catch {
toast.error("Failed to delete run");
} finally {
setDeletingRunId(null);
}
}toggleSuiteExpanded function · typescript · L361-L371 (11 LOC)src/app/projects/[id]/runs/page.tsx
function toggleSuiteExpanded(batchId: string) {
setExpandedSuites((prev) => {
const newSet = new Set(prev);
if (newSet.has(batchId)) {
newSet.delete(batchId);
} else {
newSet.add(batchId);
}
return newSet;
});
}toggleRunExpanded function · typescript · L373-L384 (12 LOC)src/app/projects/[id]/runs/page.tsx
function toggleRunExpanded(runId: number) {
setExpandedRuns((prev) => {
const newSet = new Set(prev);
if (newSet.has(runId)) {
newSet.delete(runId);
} else {
newSet.add(runId);
fetchRunSteps(runId);
}
return newSet;
});
}Want fix-PRs on findings? Install Repobility's GitHub App · github.com/apps/repobility-bot
RunRow function · typescript · L657-L825 (169 LOC)src/app/projects/[id]/runs/page.tsx
function RunRow({
run,
testCaseName,
isNested,
isExpanded,
isHighlighted,
steps,
isLoadingSteps,
onToggle,
onDelete,
runRef,
}: RunRowProps) {
const formatDuration = (ms: number | null) => {
if (!ms) return "-";
if (ms < 1000) return `${ms}ms`;
return `${(ms / 1000).toFixed(1)}s`;
};
const statusIcon = (status: string) => {
switch (status) {
case "passed": return <CheckCircle className="h-4 w-4 text-green-500 shrink-0" />;
case "failed": return <XCircle className="h-4 w-4 text-red-500 shrink-0" />;
case "running": return <Loader2 className="h-4 w-4 text-primary animate-spin shrink-0" />;
default: return <Clock className="h-4 w-4 text-muted-foreground shrink-0" />;
}
};
return (
<div
ref={runRef}
className={cn(
"border-t border-border",
isNested && "bg-muted/20",
isHighlighted && "ring-1 ring-inset ring-primary/50",
)}
>
{/* Row */}
<div
onCliformatDuration function · typescript · L54-L63 (10 LOC)src/app/projects/[id]/scheduled-runs/page.tsx
function formatDuration(startedAt: string | null, completedAt: string | null): string {
if (!startedAt || !completedAt) return "-";
const start = new Date(startedAt);
const end = new Date(completedAt);
const seconds = Math.floor((end.getTime() - start.getTime()) / 1000);
if (seconds < 60) return `${seconds}s`;
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
return `${minutes}m ${remainingSeconds}s`;
}formatDateTime function · typescript · L65-L68 (4 LOC)src/app/projects/[id]/scheduled-runs/page.tsx
function formatDateTime(dateString: string | null): string {
if (!dateString) return "-";
return new Date(dateString).toLocaleString();
}ScheduledRunsPage function · typescript · L70-L333 (264 LOC)src/app/projects/[id]/scheduled-runs/page.tsx
export default function ScheduledRunsPage() {
const params = useParams();
const projectId = params.id as string;
const [project, setProject] = useState<Project | null>(null);
const [scheduledRuns, setScheduledRuns] = useState<ScheduledRun[]>([]);
const [loading, setLoading] = useState(true);
const [expandedRuns, setExpandedRuns] = useState<Set<number>>(new Set());
const [runDetails, setRunDetails] = useState<Record<number, TestRun[]>>({});
const [loadingDetails, setLoadingDetails] = useState<Set<number>>(new Set());
useEffect(() => {
fetchProject();
fetchScheduledRuns();
}, [projectId]);
async function fetchProject() {
try {
const res = await fetch(`${API_URL}/api/projects/${projectId}`);
if (res.ok) {
const data = await res.json();
setProject(data);
}
} catch (error) {
console.error("Failed to fetch project:", error);
}
}
async function fetchScheduledRuns() {
try {
const res = await fetfetchProject function · typescript · L86-L96 (11 LOC)src/app/projects/[id]/scheduled-runs/page.tsx
async function fetchProject() {
try {
const res = await fetch(`${API_URL}/api/projects/${projectId}`);
if (res.ok) {
const data = await res.json();
setProject(data);
}
} catch (error) {
console.error("Failed to fetch project:", error);
}
}fetchScheduledRuns function · typescript · L98-L110 (13 LOC)src/app/projects/[id]/scheduled-runs/page.tsx
async function fetchScheduledRuns() {
try {
const res = await fetch(`${API_URL}/api/projects/${projectId}/scheduled-runs`);
if (res.ok) {
const data = await res.json();
setScheduledRuns(data);
}
} catch (error) {
console.error("Failed to fetch scheduled runs:", error);
} finally {
setLoading(false);
}
}toggleExpand function · typescript · L112-L143 (32 LOC)src/app/projects/[id]/scheduled-runs/page.tsx
async function toggleExpand(runId: number, threadId: string) {
const newExpanded = new Set(expandedRuns);
if (newExpanded.has(runId)) {
newExpanded.delete(runId);
} else {
newExpanded.add(runId);
// Load details if not already loaded
if (!runDetails[runId]) {
setLoadingDetails(prev => new Set(prev).add(runId));
try {
// Fetch test runs with this thread_id
const res = await fetch(`${API_URL}/api/test-runs/project/${projectId}?thread_id=${threadId}`);
if (res.ok) {
const data = await res.json();
setRunDetails(prev => ({ ...prev, [runId]: data }));
}
} catch (error) {
console.error("Failed to fetch run details:", error);
} finally {
setLoadingDetails(prev => {
const newSet = new Set(prev);
newSet.delete(runId);
return newSet;
});
}
}
}
setExpandedRuns(newExpanded);
parseCronToHuman function · typescript · L131-L154 (24 LOC)src/app/projects/[id]/settings/page.tsx
function parseCronToHuman(cron: string): string {
const parts = cron.split(" ");
if (parts.length !== 5) return cron;
const [minute, hour, dayOfMonth, month, dayOfWeek] = parts;
if (minute === "0" && hour === "*" && dayOfMonth === "*" && month === "*" && dayOfWeek === "*") {
return "Every hour";
}
if (minute === "0" && hour !== "*" && dayOfMonth === "*" && month === "*" && dayOfWeek === "*") {
return `Daily at ${hour}:00`;
}
if (minute === "0" && hour !== "*" && dayOfMonth === "*" && month === "*" && dayOfWeek === "MON-FRI") {
return `Weekdays at ${hour}:00`;
}
if (minute === "0" && hour !== "*" && dayOfMonth === "*" && month === "*" && dayOfWeek === "MON") {
return `Every Monday at ${hour}:00`;
}
if (minute === "0" && hour.startsWith("*/")) {
return `Every ${hour.slice(2)} hours`;
}
return cron;
}Repobility · code-quality intelligence · https://repobility.com
fetchProject function · typescript · L230-L250 (21 LOC)src/app/projects/[id]/settings/page.tsx
async function fetchProject() {
try {
const res = await fetch(`${API_URL}/api/projects/${projectId}`);
if (res.ok) {
const data = await res.json();
setProject(data);
setGeneralForm({
name: data.name || "",
description: data.description || "",
base_url: data.base_url || "",
page_load_state: data.page_load_state || "load",
test_case_prefix: data.test_case_prefix || "",
});
setGeneralDirty(false);
}
} catch (error) {
console.error("Failed to fetch project:", error);
} finally {
setLoading(false);
}
}fetchContext function · typescript · L252-L265 (14 LOC)src/app/projects/[id]/settings/page.tsx
async function fetchContext() {
try {
const res = await fetch(
`${API_URL}/api/projects/${projectId}/settings/context`
);
if (res.ok) {
const data = await res.json();
setBasePrompt(data.base_prompt || "");
setContextDirty(false);
}
} catch (error) {
console.error("Failed to fetch context:", error);
}
}handleSaveGeneral function · typescript · L267-L301 (35 LOC)src/app/projects/[id]/settings/page.tsx
async function handleSaveGeneral() {
if (!generalForm.name.trim() || !generalForm.base_url.trim()) {
alert("Name and Base URL are required");
return;
}
setSavingGeneral(true);
try {
const res = await fetch(
`${API_URL}/api/projects/${projectId}`,
{
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: generalForm.name,
description: generalForm.description || null,
base_url: generalForm.base_url,
page_load_state: generalForm.page_load_state,
test_case_prefix: generalForm.test_case_prefix || null,
}),
}
);
if (res.ok) {
const updated = await res.json();
setProject(updated);
setGeneralDirty(false);
} else {
const err = await res.json();
console.error("Failed to save project:", err);
}
} catch (error) {
console.error("Faipage 1 / 5next ›