← back to ksankaran__checkmate-ui

Function bodies 210 total

All specs Real LLM only Function bodies
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);

    setIsLoadi
RootLayout 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
        onCli
formatDuration 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 fet
fetchProject 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("Fai
page 1 / 5next ›