Add errored page, make menu a burger menu for space
This commit is contained in:
parent
9c4ff25239
commit
6a7b9b3dc7
30
package-lock.json
generated
30
package-lock.json
generated
@ -9,6 +9,7 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-avatar": "^1.1.0",
|
"@radix-ui/react-avatar": "^1.1.0",
|
||||||
|
"@radix-ui/react-checkbox": "^1.1.1",
|
||||||
"@radix-ui/react-dialog": "^1.1.1",
|
"@radix-ui/react-dialog": "^1.1.1",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
||||||
"@radix-ui/react-label": "^2.1.0",
|
"@radix-ui/react-label": "^2.1.0",
|
||||||
@ -771,6 +772,35 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-checkbox": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-0i/EKJ222Afa1FE0C6pNJxDq1itzcl3HChE9DwskA4th4KRse8ojx8a1nVcOjwJdbpDLcz7uol77yYnQNMHdKw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/primitive": "1.1.0",
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.0",
|
||||||
|
"@radix-ui/react-context": "1.1.0",
|
||||||
|
"@radix-ui/react-presence": "1.1.0",
|
||||||
|
"@radix-ui/react-primitive": "2.0.0",
|
||||||
|
"@radix-ui/react-use-controllable-state": "1.1.0",
|
||||||
|
"@radix-ui/react-use-previous": "1.1.0",
|
||||||
|
"@radix-ui/react-use-size": "1.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-collection": {
|
"node_modules/@radix-ui/react-collection": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz",
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-avatar": "^1.1.0",
|
"@radix-ui/react-avatar": "^1.1.0",
|
||||||
|
"@radix-ui/react-checkbox": "^1.1.1",
|
||||||
"@radix-ui/react-dialog": "^1.1.1",
|
"@radix-ui/react-dialog": "^1.1.1",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
||||||
"@radix-ui/react-label": "^2.1.0",
|
"@radix-ui/react-label": "^2.1.0",
|
||||||
|
@ -4,6 +4,7 @@ import { Route, Switch } from "wouter";
|
|||||||
import Home from './components/pages/home';
|
import Home from './components/pages/home';
|
||||||
import Packages from './components/pages/packages';
|
import Packages from './components/pages/packages';
|
||||||
import Queue from './components/pages/queue';
|
import Queue from './components/pages/queue';
|
||||||
|
import Errored from './components/pages/errored';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
|
||||||
@ -14,6 +15,7 @@ function App() {
|
|||||||
<Switch>
|
<Switch>
|
||||||
<Route path="/queue" component={Queue} />
|
<Route path="/queue" component={Queue} />
|
||||||
<Route path="/packages" component={Packages} />
|
<Route path="/packages" component={Packages} />
|
||||||
|
<Route path="/errored" component={Errored} />
|
||||||
<Route component={Home}/>
|
<Route component={Home}/>
|
||||||
</Switch>
|
</Switch>
|
||||||
</>
|
</>
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
import { Dialog, DialogContent, DialogTrigger } from "../ui/dialog";
|
import { Dialog, DialogContent, DialogTrigger } from "../ui/dialog";
|
||||||
import { Input } from "../ui/input";
|
import { Input } from "../ui/input";
|
||||||
import { Label } from "../ui/label";
|
import { Label } from "../ui/label";
|
||||||
import { AlertCircle } from "lucide-react";
|
import { AlertCircle, Menu, X } from "lucide-react";
|
||||||
import { Alert, AlertDescription } from "../ui/alert";
|
import { Alert, AlertDescription } from "../ui/alert";
|
||||||
|
|
||||||
const Header: React.FC = () => {
|
const Header: React.FC = () => {
|
||||||
@ -21,12 +21,15 @@ const Header: React.FC = () => {
|
|||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||||
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||||
|
|
||||||
const checkLoginStatus = () => {
|
const checkLoginStatus = async () => {
|
||||||
const ptCookie = document.cookie
|
const r = await fetch("/api/isloggedin");
|
||||||
.split("; ")
|
let isLoggedIn = false;
|
||||||
.find((row) => row.startsWith("pt="));
|
if (r.status === 200) {
|
||||||
setIsLoggedIn(!!ptCookie);
|
isLoggedIn = true;
|
||||||
|
}
|
||||||
|
setIsLoggedIn(isLoggedIn);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -77,23 +80,9 @@ const Header: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="flex items-center justify-between p-4 bg-background text-foreground shadow-md">
|
<header className="flex items-center justify-between p-4 bg-background text-foreground shadow-md">
|
||||||
<nav>
|
<Button variant="ghost" onClick={() => setIsMenuOpen(true)}>
|
||||||
<ul className="flex space-x-4">
|
<Menu className="h-6 w-6" />
|
||||||
<li>
|
</Button>
|
||||||
<Link href="/queue" className="text-foreground hover:text-primary">
|
|
||||||
Queue
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Link
|
|
||||||
href="/packages"
|
|
||||||
className="text-foreground hover:text-primary"
|
|
||||||
>
|
|
||||||
Packages
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div className="flex-grow flex justify-center">
|
<div className="flex-grow flex justify-center">
|
||||||
<Link href="/">
|
<Link href="/">
|
||||||
@ -168,6 +157,54 @@ const Header: React.FC = () => {
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)}
|
)}
|
||||||
|
{/* Slide-out menu */}
|
||||||
|
<div
|
||||||
|
className={`fixed inset-y-0 left-0 w-64 bg-background shadow-lg transform ${
|
||||||
|
isMenuOpen ? "translate-x-0" : "-translate-x-full"
|
||||||
|
} transition-transform duration-300 ease-in-out z-50`}
|
||||||
|
>
|
||||||
|
<div className="p-4 relative">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() => setIsMenuOpen(false)}
|
||||||
|
className="absolute top-2 right-2 p-1"
|
||||||
|
>
|
||||||
|
<X className="h-6 w-6" />
|
||||||
|
</Button>
|
||||||
|
<nav className="mt-10">
|
||||||
|
<ul className="space-y-4">
|
||||||
|
<li>
|
||||||
|
<Link href="/" className="text-foreground hover:text-primary block">
|
||||||
|
Home
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link href="/queue" className="text-foreground hover:text-primary block">
|
||||||
|
Queue
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link href="/packages" className="text-foreground hover:text-primary block">
|
||||||
|
Packages
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link href="/errored" className="text-foreground hover:text-primary block">
|
||||||
|
Errored
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Overlay to close menu when clicking outside */}
|
||||||
|
{isMenuOpen && (
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 bg-black bg-opacity-50 z-40"
|
||||||
|
onClick={() => setIsMenuOpen(false)}
|
||||||
|
></div>
|
||||||
|
)}
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
265
src/components/pages/errored.tsx
Normal file
265
src/components/pages/errored.tsx
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
import React, { useState, useEffect, useRef, useCallback } from "react";
|
||||||
|
import { useMediaQuery } from "react-responsive";
|
||||||
|
import usePackageData from "../../hooks/usePackageData";
|
||||||
|
import { useLocation } from "wouter";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from "../ui/table";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogFooter,
|
||||||
|
} from "../ui/dialog";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
import { Input } from "../ui/input";
|
||||||
|
import { Checkbox } from "../ui/checkbox";
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
|
||||||
|
|
||||||
|
const Errored: React.FC = () => {
|
||||||
|
const { errPackages, loading, fetchErrPackages } = usePackageData();
|
||||||
|
const isMobile = useMediaQuery({ maxWidth: 767 });
|
||||||
|
const [, setPageSize] = useState(isMobile ? 100 : 250);
|
||||||
|
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
||||||
|
const [, setLocation] = useLocation();
|
||||||
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||||
|
const [, setSelectedPackage] = useState(null);
|
||||||
|
const [rebuildData, setRebuildData] = useState({
|
||||||
|
packageName: "",
|
||||||
|
version: "",
|
||||||
|
buildType: "v3",
|
||||||
|
rebuild: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const isInitialMount = useRef(true);
|
||||||
|
|
||||||
|
const checkLoginStatus = async () => {
|
||||||
|
const r = await fetch("/api/isloggedin");
|
||||||
|
let loggedIn = false;
|
||||||
|
if (r.status === 200) {
|
||||||
|
loggedIn = true;
|
||||||
|
}
|
||||||
|
setIsLoggedIn(loggedIn);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setPageSize(isMobile ? 100 : 250);
|
||||||
|
}, [isMobile]);
|
||||||
|
|
||||||
|
const loadPackages = useCallback(() => {
|
||||||
|
fetchErrPackages();
|
||||||
|
}, [fetchErrPackages]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isInitialMount.current) {
|
||||||
|
isInitialMount.current = false;
|
||||||
|
loadPackages();
|
||||||
|
checkLoginStatus();
|
||||||
|
}
|
||||||
|
}, [loadPackages]);
|
||||||
|
|
||||||
|
const handleRebuildClick = (pkg: any) => {
|
||||||
|
const firstPackage = Object.values(pkg.Packages)[0] as any;
|
||||||
|
setSelectedPackage(pkg);
|
||||||
|
setRebuildData({
|
||||||
|
packageName: pkg.Name,
|
||||||
|
version: firstPackage.NewVersion || firstPackage.Version,
|
||||||
|
buildType: "lto",
|
||||||
|
rebuild: false,
|
||||||
|
});
|
||||||
|
setIsDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDialogSubmit = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/auth/triggerbuild", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(rebuildData),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
setIsDialogOpen(false);
|
||||||
|
setLocation(`/queue?search=${rebuildData.packageName}`)
|
||||||
|
} else {
|
||||||
|
alert(data.error);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert("An error occurred while submitting the build request.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col min-h-screen">
|
||||||
|
{/* Table */}
|
||||||
|
<div className="flex-grow overflow-hidden">
|
||||||
|
<div className="container mx-0 px-0 h-full">
|
||||||
|
<div className="h-full">
|
||||||
|
<Table className="w-full">
|
||||||
|
<TableHeader className="top-0 bg-background z-10 hidden md:table-header-group">
|
||||||
|
<TableRow>
|
||||||
|
<TableHead className="w-1/2 text-left">Name</TableHead>
|
||||||
|
<TableHead className="w-1/4 text-left">Version</TableHead>
|
||||||
|
<TableHead className="w-1/4 text-left">Status</TableHead>
|
||||||
|
{isLoggedIn && <TableHead className="w-1/4 text-left">Action</TableHead>}
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{loading ? (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={isLoggedIn ? 4 : 3} className="text-center">
|
||||||
|
Loading...
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
) : Object.keys(errPackages).length === 0 ? (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={isLoggedIn ? 4 : 3} className="text-center">
|
||||||
|
No packages found.
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
) : (
|
||||||
|
errPackages.map((pkg) => {
|
||||||
|
const firstPackage = Object.values(pkg.Packages)[0];
|
||||||
|
return (
|
||||||
|
<TableRow
|
||||||
|
key={pkg.Name}
|
||||||
|
className="md:table-row border-b last:border-b-0"
|
||||||
|
>
|
||||||
|
<TableCell className="w-full md:w-1/2 py-2 md:py-4">
|
||||||
|
<div className="flex flex-col h-full md:h-auto">
|
||||||
|
<div className="flex justify-between items-start mb-2 md:mb-0">
|
||||||
|
<span className="font-medium truncate mr-2">
|
||||||
|
{pkg.Name}
|
||||||
|
</span>
|
||||||
|
<span className="text-sm md:hidden truncate">
|
||||||
|
{firstPackage.Status}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="md:hidden text-sm text-muted-foreground text-center">
|
||||||
|
<span className="truncate">
|
||||||
|
{firstPackage.Version}
|
||||||
|
</span>
|
||||||
|
{firstPackage.NewVersion && (
|
||||||
|
<span className="truncate block">
|
||||||
|
→ {firstPackage.NewVersion}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="hidden md:table-cell md:w-1/4 py-4 text-left">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="truncate">
|
||||||
|
{firstPackage.Version}
|
||||||
|
</span>
|
||||||
|
{firstPackage.NewVersion && (
|
||||||
|
<span className="text-sm text-muted-foreground truncate">
|
||||||
|
→ {firstPackage.NewVersion}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="hidden md:table-cell md:w-1/4 py-2 md:py-4 text-left">
|
||||||
|
<span className="truncate">
|
||||||
|
{firstPackage.Status}
|
||||||
|
</span>
|
||||||
|
</TableCell>
|
||||||
|
{isLoggedIn && (
|
||||||
|
<TableCell className="py-2 md:py-4 text-right">
|
||||||
|
<Button onClick={() => handleRebuildClick(pkg)}>Rebuild</Button>
|
||||||
|
</TableCell>
|
||||||
|
)}
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Rebuild Dialog */}
|
||||||
|
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Rebuild Package</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="grid gap-4 py-4">
|
||||||
|
<div className="grid grid-cols-4 items-center gap-4">
|
||||||
|
<label htmlFor="packageName" className="text-right">
|
||||||
|
Package Name
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
id="packageName"
|
||||||
|
value={rebuildData.packageName}
|
||||||
|
className="col-span-3"
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-4 items-center gap-4">
|
||||||
|
<label htmlFor="version" className="text-right">
|
||||||
|
Version
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
id="version"
|
||||||
|
value={rebuildData.version}
|
||||||
|
onChange={(e) =>
|
||||||
|
setRebuildData({ ...rebuildData, version: e.target.value })
|
||||||
|
}
|
||||||
|
className="col-span-3"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-4 items-center gap-4">
|
||||||
|
<label htmlFor="buildType" className="text-right">
|
||||||
|
Build Type
|
||||||
|
</label>
|
||||||
|
<Select
|
||||||
|
value={rebuildData.buildType}
|
||||||
|
onValueChange={(value) =>
|
||||||
|
setRebuildData({ ...rebuildData, buildType: value })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="col-span-3">
|
||||||
|
<SelectValue placeholder="Select build type" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="v3">v3</SelectItem>
|
||||||
|
<SelectItem value="lto">lto</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-4 items-center gap-4">
|
||||||
|
<label htmlFor="rebuild" className="text-right">
|
||||||
|
Rebuild
|
||||||
|
</label>
|
||||||
|
<Checkbox
|
||||||
|
id="rebuild"
|
||||||
|
checked={rebuildData.rebuild}
|
||||||
|
onCheckedChange={(checked: boolean) =>
|
||||||
|
setRebuildData({ ...rebuildData, rebuild: checked })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button onClick={handleDialogSubmit}>Submit</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Errored;
|
28
src/components/ui/checkbox.tsx
Normal file
28
src/components/ui/checkbox.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
||||||
|
import { Check } from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "../../lib/utils"
|
||||||
|
|
||||||
|
const Checkbox = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CheckboxPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<CheckboxPrimitive.Indicator
|
||||||
|
className={cn("flex items-center justify-center text-current")}
|
||||||
|
>
|
||||||
|
<Check className="h-4 w-4" />
|
||||||
|
</CheckboxPrimitive.Indicator>
|
||||||
|
</CheckboxPrimitive.Root>
|
||||||
|
))
|
||||||
|
Checkbox.displayName = CheckboxPrimitive.Root.displayName
|
||||||
|
|
||||||
|
export { Checkbox }
|
@ -34,8 +34,10 @@ const usePackageData = () => {
|
|||||||
const [stats, setStats] = useState<PackageStats | null>(null);
|
const [stats, setStats] = useState<PackageStats | null>(null);
|
||||||
const [lastUpdated, setLastUpdated] = useState<string>('');
|
const [lastUpdated, setLastUpdated] = useState<string>('');
|
||||||
const [packages, setPackages] = useState<PackageData[]>([]);
|
const [packages, setPackages] = useState<PackageData[]>([]);
|
||||||
|
const [pkg, setPackage] = useState<PackageData | null>(null);
|
||||||
const [totalCount, setTotalCount] = useState(0);
|
const [totalCount, setTotalCount] = useState(0);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [errPackages, setErrPackages] = useState<PackageData[]>([]);
|
||||||
const fetchStatsAttempted = useRef(false);
|
const fetchStatsAttempted = useRef(false);
|
||||||
|
|
||||||
const fetchStats = useCallback(async () => {
|
const fetchStats = useCallback(async () => {
|
||||||
@ -84,6 +86,34 @@ const usePackageData = () => {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const fetchPackage = useCallback(async (name: string) => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/package/${name}`);
|
||||||
|
const data: PackageData = await response.json();
|
||||||
|
setPackage(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching package:", error);
|
||||||
|
setPackage(null);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const fetchErrPackages = useCallback(async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/errpackages');
|
||||||
|
const data: PackageData[] = await response.json();
|
||||||
|
setErrPackages(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching errpackages:", error);
|
||||||
|
setErrPackages([]);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
stats,
|
stats,
|
||||||
lastUpdated,
|
lastUpdated,
|
||||||
@ -92,6 +122,10 @@ const usePackageData = () => {
|
|||||||
totalCount,
|
totalCount,
|
||||||
loading,
|
loading,
|
||||||
fetchPackages,
|
fetchPackages,
|
||||||
|
pkg,
|
||||||
|
fetchPackage,
|
||||||
|
errPackages,
|
||||||
|
fetchErrPackages,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user