Refactor all components to use data from new API

This commit is contained in:
Bram Suurd 2024-11-06 17:25:02 +01:00
parent 1ef3c5a0eb
commit 76cf85293c
11 changed files with 207 additions and 222 deletions

View File

@ -42,10 +42,10 @@ export default function ScriptAccordion({
useEffect(() => { useEffect(() => {
if (selectedScript) { if (selectedScript) {
const category = items.find((category) => const category = items.find((category) =>
category.expand.items.some((script) => script.title === selectedScript), category.scripts.some((script) => script.name === selectedScript),
); );
if (category) { if (category) {
setExpandedItem(category.catagoryName); setExpandedItem(category.name);
handleSelected(selectedScript); handleSelected(selectedScript);
} }
} }
@ -60,81 +60,80 @@ export default function ScriptAccordion({
{items.map((category) => ( {items.map((category) => (
<AccordionItem <AccordionItem
key={category.id + ":category"} key={category.id + ":category"}
value={category.catagoryName} value={category.name}
className={cn("sm:text-md flex flex-col border-none", { className={cn("sm:text-md flex flex-col border-none", {
"rounded-lg bg-accent/30": expandedItem === category.catagoryName, "rounded-lg bg-accent/30": expandedItem === category.name,
})} })}
> >
<AccordionTrigger <AccordionTrigger
className={cn( className={cn(
"duration-250 rounded-lg transition ease-in-out hover:-translate-y-1 hover:scale-105 hover:bg-accent", "duration-250 rounded-lg transition ease-in-out hover:-translate-y-1 hover:scale-105 hover:bg-accent",
{ "": expandedItem === category.catagoryName }, { "": expandedItem === category.name },
)} )}
> >
<div className="mr-2 flex w-full items-center justify-between"> <div className="mr-2 flex w-full items-center justify-between">
<span className="pl-2">{category.catagoryName} </span> <span className="pl-2">{category.name} </span>
<span className="rounded-full bg-gray-200 px-2 py-1 text-xs text-muted-foreground hover:no-underline dark:bg-blue-800/20"> <span className="rounded-full bg-gray-200 px-2 py-1 text-xs text-muted-foreground hover:no-underline dark:bg-blue-800/20">
{category.expand.items.length} {category.scripts.length}
</span> </span>
</div>{" "} </div>{" "}
</AccordionTrigger> </AccordionTrigger>
<AccordionContent <AccordionContent
data-state={ data-state={
expandedItem === category.catagoryName ? "open" : "closed" expandedItem === category.name ? "open" : "closed"
} }
className="pt-0" className="pt-0"
> >
{category.expand.items {category.scripts
.slice() .slice()
.sort((a, b) => a.title.localeCompare(b.title)) .sort((a, b) => a.name.localeCompare(b.name))
.map((script, index) => ( .map((script, index) => (
<div key={index}> <div key={index}>
<Link <Link
href={{ href={{
pathname: "/scripts", pathname: "/scripts",
query: { id: script.title }, query: { id: script.name},
}} }}
prefetch={false} prefetch={false}
className={`flex cursor-pointer items-center justify-between gap-1 px-1 py-1 text-muted-foreground hover:rounded-lg hover:bg-accent/60 hover:dark:bg-accent/20 ${ className={`flex cursor-pointer items-center justify-between gap-1 px-1 py-1 text-muted-foreground hover:rounded-lg hover:bg-accent/60 hover:dark:bg-accent/20 ${
selectedScript === script.title selectedScript === script.name
? "rounded-lg bg-accent font-semibold dark:bg-accent/30 dark:text-white" ? "rounded-lg bg-accent font-semibold dark:bg-accent/30 dark:text-white"
: "" : ""
}`} }`}
onClick={() => handleSelected(script.title)} onClick={() => handleSelected(script.name)}
ref={(el) => { ref={(el) => {
linkRefs.current[script.title] = el; linkRefs.current[script.name] = el;
}} }}
> >
<Image <div className="flex items-center">
src={script.logo} <Image
height={16} src={script.logo || "/logo.png"}
width={16} height={16}
unoptimized width={16}
onError={(e) => unoptimized
((e.currentTarget as HTMLImageElement).src = onError={(e) =>
"/logo.png") ((e.currentTarget as HTMLImageElement).src =
} "/logo.png")
alt={script.title} }
className="mr-1 w-4 h-4 rounded-full" alt={script.name}
/> className="mr-1 w-4 h-4 rounded-full"
<span className="flex items-center gap-2"> />
{script.title} <span className="flex items-center gap-2">
{script.isMostViewed && ( {script.name}
<Star className="h-3 w-3 text-yellow-500"></Star> </span>
)} </div>
</span>
<Badge <Badge
className={cn( className={cn(
"ml-auto w-[37.69px] justify-center text-center", "ml-auto w-[37.69px] justify-center text-center",
{ {
"text-primary/75": script.item_type === "VM", "text-primary/75": script.type === "vm",
"text-yellow-500/75": script.item_type === "LXC", "text-yellow-500/75": script.type === "ct",
"border-none": script.item_type === "", "border-none": script.type === "misc",
hidden: !["VM", "LXC", ""].includes(script.item_type), hidden: !["VM", "LXC", ""].includes(script.type),
}, },
)} )}
> >
{script.item_type} {script.type}
</Badge> </Badge>
</Link> </Link>
</div> </div>

View File

@ -21,9 +21,9 @@ export function LatestScripts({ items }: { items: Category[] }) {
const latestScripts = useMemo(() => { const latestScripts = useMemo(() => {
if (!items) return []; if (!items) return [];
const scripts = items.flatMap((category) => category.expand.items || []); const scripts = items.flatMap((category) => category.scripts || []);
return scripts.sort( return scripts.sort(
(a, b) => new Date(b.created).getTime() - new Date(a.created).getTime(), (a, b) => new Date(b.date_created).getTime() - new Date(a.date_created).getTime(),
); );
}, [items]); }, [items]);
@ -70,14 +70,14 @@ export function LatestScripts({ items }: { items: Category[] }) {
<div className="min-w flex w-full flex-row flex-wrap gap-4"> <div className="min-w flex w-full flex-row flex-wrap gap-4">
{latestScripts.slice(startIndex, endIndex).map((item) => ( {latestScripts.slice(startIndex, endIndex).map((item) => (
<Card <Card
key={item.id} key={item.name}
className="min-w-[250px] flex-1 flex-grow bg-accent/30" className="min-w-[250px] flex-1 flex-grow bg-accent/30"
> >
<CardHeader> <CardHeader>
<CardTitle className="flex items-center gap-3"> <CardTitle className="flex items-center gap-3">
<div className="flex h-16 w-16 items-center justify-center rounded-lg bg-accent p-1"> <div className="flex h-16 w-16 items-center justify-center rounded-lg bg-accent p-1">
<Image <Image
src={item.logo} src={item.logo || "/logo.png"}
unoptimized unoptimized
height={64} height={64}
width={64} width={64}
@ -87,11 +87,11 @@ export function LatestScripts({ items }: { items: Category[] }) {
</div> </div>
<div className="flex flex-col"> <div className="flex flex-col">
<p className="text-lg line-clamp-1"> <p className="text-lg line-clamp-1">
{item.title} {item.item_type} {item.name} {item.type}
</p> </p>
<p className="text-sm text-muted-foreground flex items-center gap-1"> <p className="text-sm text-muted-foreground flex items-center gap-1">
<CalendarPlus className="h-4 w-4" /> <CalendarPlus className="h-4 w-4" />
{extractDate(item.created)} {extractDate(item.date_created)}
</p> </p>
</div> </div>
</CardTitle> </CardTitle>
@ -106,7 +106,7 @@ export function LatestScripts({ items }: { items: Category[] }) {
<Link <Link
href={{ href={{
pathname: "/scripts", pathname: "/scripts",
query: { id: item.title }, query: { id: item.name },
}} }}
> >
View Script View Script
@ -120,100 +120,102 @@ export function LatestScripts({ items }: { items: Category[] }) {
); );
} }
export function MostViewedScripts({ items }: { items: Category[] }) { // TODO: find way to determine what the most popular scripts are
const [page, setPage] = useState(1);
const mostViewedScripts = useMemo(() => { // export function MostViewedScripts({ items }: { items: Category[] }) {
if (!items) return []; // const [page, setPage] = useState(1);
const scripts = items.flatMap((category) => category.expand.items || []);
const mostViewedScripts = scripts
.filter((script) => script.isMostViewed)
.map((script) => ({
...script,
}));
return mostViewedScripts;
}, [items]);
const goToNextPage = () => { // const mostViewedScripts = useMemo(() => {
setPage((prevPage) => prevPage + 1); // if (!items) return [];
}; // const scripts = items.flatMap((category) => category.scripts || []);
// const mostViewedScripts = scripts
// .filter((script) => script.isMostViewed)
// .map((script) => ({
// ...script,
// }));
// return mostViewedScripts;
// }, [items]);
const goToPreviousPage = () => { // const goToNextPage = () => {
setPage((prevPage) => prevPage - 1); // setPage((prevPage) => prevPage + 1);
}; // };
const startIndex = (page - 1) * ITEMS_PER_PAGE; // const goToPreviousPage = () => {
const endIndex = page * ITEMS_PER_PAGE; // setPage((prevPage) => prevPage - 1);
// };
return ( // const startIndex = (page - 1) * ITEMS_PER_PAGE;
<div className=""> // const endIndex = page * ITEMS_PER_PAGE;
{mostViewedScripts.length > 0 && (
<> // return (
<h2 className="text-lg font-semibold">Most Viewed Scripts</h2> // <div className="">
</> // {mostViewedScripts.length > 0 && (
)} // <>
<div className="min-w flex w-full flex-row flex-wrap gap-4"> // <h2 className="text-lg font-semibold">Most Viewed Scripts</h2>
{mostViewedScripts.slice(startIndex, endIndex).map((item) => ( // </>
<Card // )}
key={item.id} // <div className="min-w flex w-full flex-row flex-wrap gap-4">
className="min-w-[250px] flex-1 flex-grow bg-accent/30" // {mostViewedScripts.slice(startIndex, endIndex).map((item) => (
> // <Card
<CardHeader> // key={item.id}
<CardTitle className="flex items-center gap-3"> // className="min-w-[250px] flex-1 flex-grow bg-accent/30"
<div className="flex max-h-16 min-h-16 min-w-16 max-w-16 items-center justify-center rounded-lg bg-accent p-1"> // >
<Image // <CardHeader>
unoptimized // <CardTitle className="flex items-center gap-3">
src={item.logo} // <div className="flex max-h-16 min-h-16 min-w-16 max-w-16 items-center justify-center rounded-lg bg-accent p-1">
height={64} // <Image
width={64} // unoptimized
alt="" // src={item.logo}
className="h-11 w-11 object-contain" // height={64}
/> // width={64}
</div> // alt=""
<div className="flex flex-col"> // className="h-11 w-11 object-contain"
<p className="line-clamp-1 text-lg"> // />
{item.title} {item.item_type} // </div>
</p> // <div className="flex flex-col">
<p className="flex items-center gap-1 text-sm text-muted-foreground"> // <p className="line-clamp-1 text-lg">
<CalendarPlus className="h-4 w-4" /> // {item.title} {item.item_type}
{extractDate(item.created)} // </p>
</p> // <p className="flex items-center gap-1 text-sm text-muted-foreground">
</div> // <CalendarPlus className="h-4 w-4" />
</CardTitle> // {extractDate(item.created)}
</CardHeader> // </p>
<CardContent> // </div>
<CardDescription className="line-clamp-3 text-card-foreground break-words"> // </CardTitle>
{item.description} // </CardHeader>
</CardDescription> // <CardContent>
</CardContent> // <CardDescription className="line-clamp-3 text-card-foreground break-words">
<CardFooter className=""> // {item.description}
<Button asChild variant="outline"> // </CardDescription>
<Link // </CardContent>
href={{ // <CardFooter className="">
pathname: "/scripts", // <Button asChild variant="outline">
query: { id: item.title }, // <Link
}} // href={{
prefetch={false} // pathname: "/scripts",
> // query: { id: item.title },
View Script // }}
</Link> // prefetch={false}
</Button> // >
</CardFooter> // View Script
</Card> // </Link>
))} // </Button>
</div> // </CardFooter>
<div className="flex justify-end gap-1 p-2"> // </Card>
{page > 1 && ( // ))}
<Button onClick={goToPreviousPage} variant="outline"> // </div>
Previous // <div className="flex justify-end gap-1 p-2">
</Button> // {page > 1 && (
)} // <Button onClick={goToPreviousPage} variant="outline">
{endIndex < mostViewedScripts.length && ( // Previous
<Button onClick={goToNextPage} variant="outline"> // </Button>
{page === 1 ? "More.." : "Next"} // )}
</Button> // {endIndex < mostViewedScripts.length && (
)} // <Button onClick={goToNextPage} variant="outline">
</div> // {page === 1 ? "More.." : "Next"}
</div> // </Button>
); // )}
} // </div>
// </div>
// );
// }

View File

@ -39,21 +39,21 @@ function ScriptItem({
<div className="flex"> <div className="flex">
<Image <Image
className="h-32 w-32 rounded-lg bg-accent/60 object-contain p-3 shadow-md" className="h-32 w-32 rounded-lg bg-accent/60 object-contain p-3 shadow-md"
src={item.logo} src={item.logo || "/logo.png"}
width={400} width={400}
onError={(e) => onError={(e) =>
((e.currentTarget as HTMLImageElement).src = "/logo.png") ((e.currentTarget as HTMLImageElement).src = "/logo.png")
} }
height={400} height={400}
alt={item.title} alt={item.name}
unoptimized unoptimized
/> />
<div className="ml-4 flex flex-col justify-between"> <div className="ml-4 flex flex-col justify-between">
<div className="flex h-full w-full flex-col justify-between"> <div className="flex h-full w-full flex-col justify-between">
<div> <div>
<h1 className="text-lg font-semibold">{item.title}</h1> <h1 className="text-lg font-semibold">{item.name}</h1>
<p className="w-full text-sm text-muted-foreground"> <p className="w-full text-sm text-muted-foreground">
Date added: {extractDate(item.created)} Date added: {extractDate(item.date_created)}
</p> </p>
</div> </div>
<div className="flex gap-5"> <div className="flex gap-5">
@ -76,7 +76,7 @@ function ScriptItem({
<div className="mt-4 rounded-lg border bg-accent/50"> <div className="mt-4 rounded-lg border bg-accent/50">
<div className="flex gap-3 px-4 py-2"> <div className="flex gap-3 px-4 py-2">
<h2 className="text-lg font-semibold"> <h2 className="text-lg font-semibold">
How to {item.item_type ? "install" : "use"} How to {item.type ? "install" : "use"}
</h2> </h2>
<Tooltips item={item} /> <Tooltips item={item} />
</div> </div>

View File

@ -5,12 +5,12 @@ import { Info } from "lucide-react";
export default function Alerts({ item }: { item: Script }) { export default function Alerts({ item }: { item: Script }) {
return ( return (
<> <>
{item.expand?.alerts?.length > 0 && {item?.notes?.length > 0 &&
item.expand.alerts.map((alert: any, index: number) => ( item.notes.map((note: any, index: number) => (
<div key={index} className="mt-4 flex flex-col gap-2"> <div key={index} className="mt-4 flex flex-col gap-2">
<p className="inline-flex items-center gap-2 rounded-lg border border-red-500/25 bg-destructive/25 p-2 pl-4 text-sm"> <p className="inline-flex items-center gap-2 rounded-lg border border-red-500/25 bg-destructive/25 p-2 pl-4 text-sm">
<Info className="h-4 min-h-4 w-4 min-w-4" /> <Info className="h-4 min-h-4 w-4 min-w-4" />
<span>{TextCopyBlock(alert.content)}</span> <span>{TextCopyBlock(note.text)}</span>
</p> </p>
</div> </div>
))} ))}

View File

@ -21,12 +21,12 @@ export default function Buttons({ item }: { item: Script }) {
}; };
const sourceUrl = useMemo(() => { const sourceUrl = useMemo(() => {
if (item.installCommand) { if (item.install_methods[0]?.script) {
const match = item.installCommand.match(pattern); const match = item.install_methods[0].script.match(pattern);
return match ? transformUrlToInstallScript(match[0]) : null; return match ? transformUrlToInstallScript(match[0]) : null;
} }
return null; return null;
}, [item.installCommand, pattern]); }, [item.install_methods, pattern]);
return ( return (
<div className="flex flex-wrap justify-end gap-2"> <div className="flex flex-wrap justify-end gap-2">
@ -49,17 +49,7 @@ export default function Buttons({ item }: { item: Script }) {
</Link> </Link>
</Button> </Button>
)} )}
{item.post_install && ( {item.install_methods[0]?.script && sourceUrl && (
<Button variant="secondary" asChild>
<Link target="_blank" href={item.post_install}>
<span className="flex items-center gap-2">
<ExternalLink className="h-4 w-4" />
Post Install
</span>
</Link>
</Button>
)}
{item.installCommand && sourceUrl && (
<Button variant="secondary" asChild> <Button variant="secondary" asChild>
<Link target="_blank" href={transformUrlToInstallScript(sourceUrl)}> <Link target="_blank" href={transformUrlToInstallScript(sourceUrl)}>
<span className="flex items-center gap-2"> <span className="flex items-center gap-2">

View File

@ -4,7 +4,7 @@ import handleCopy from "@/components/handleCopy";
import { Script } from "@/lib/types"; import { Script } from "@/lib/types";
export default function DefaultPassword({ item }: { item: Script }) { export default function DefaultPassword({ item }: { item: Script }) {
const hasDefaultLogin = item?.expand?.default_login !== undefined; const hasDefaultLogin = item.default_credentials.username != null && item.default_credentials.password != null;
return ( return (
<div> <div>
@ -17,7 +17,7 @@ export default function DefaultPassword({ item }: { item: Script }) {
<div className="flex flex-col gap-2 p-4"> <div className="flex flex-col gap-2 p-4">
<p className="mb-2 text-sm"> <p className="mb-2 text-sm">
You can use the following credentials to login to the {""} You can use the following credentials to login to the {""}
{item.title} {item.item_type}. {item.name} {item.type}.
</p> </p>
<div className="text-sm"> <div className="text-sm">
Username:{" "} Username:{" "}
@ -25,10 +25,10 @@ export default function DefaultPassword({ item }: { item: Script }) {
variant={"secondary"} variant={"secondary"}
size={"null"} size={"null"}
onClick={() => onClick={() =>
handleCopy("username", item.expand.default_login.username) handleCopy("username", item.default_credentials.username ?? "")
} }
> >
{item.expand.default_login.username} {item.default_credentials.username}
</Button> </Button>
</div> </div>
<div className="text-sm"> <div className="text-sm">
@ -37,10 +37,10 @@ export default function DefaultPassword({ item }: { item: Script }) {
variant={"secondary"} variant={"secondary"}
size={"null"} size={"null"}
onClick={() => onClick={() =>
handleCopy("password", item.expand.default_login.password) handleCopy("password", item.default_credentials.password ?? "")
} }
> >
{item.expand.default_login.password} {item.default_credentials.password}
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -1,35 +1,41 @@
import { Script } from "@/lib/types"; import { Script } from "@/lib/types";
export default function DefaultSettings({ item }: { item: Script }) { export default function DefaultSettings({ item }: { item: Script }) {
const hasAlpineScript = item?.expand?.alpine_script !== undefined; const defaultSettings = item.install_methods.find(
(method) => method.type === "default",
);
const defaultAlpineSettings = item.install_methods.find(
(method) => method.type === "alpine",
);
return ( return (
<> <>
{item.default_cpu && ( {defaultSettings && (
<div> <div>
<h2 className="text-md font-semibold">Default settings</h2> <h2 className="text-md font-semibold">Default settings</h2>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
CPU: {item.default_cpu} CPU: {defaultSettings.resources.cpu}vCPU
</p> </p>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
RAM: {item.default_ram} RAM: {defaultSettings.resources.ram}MB
</p> </p>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
HDD: {item.default_hdd} HDD: {defaultSettings.resources.hdd}GB
</p> </p>
</div> </div>
)} )}
{hasAlpineScript && ( {defaultAlpineSettings && (
<div> <div>
<h2 className="text-md font-semibold">Default Alpine settings</h2> <h2 className="text-md font-semibold">Default Alpine settings</h2>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
CPU: {item.expand.alpine_script.default_cpu} CPU: {defaultAlpineSettings?.resources.cpu}vCPU
</p> </p>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
RAM: {item.expand.alpine_script.default_ram} RAM: {defaultAlpineSettings?.resources.ram}MB
</p> </p>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
HDD: {item.expand.alpine_script.default_hdd} HDD: {defaultAlpineSettings?.resources.hdd}GB
</p> </p>
</div> </div>
)} )}

View File

@ -2,32 +2,41 @@ import CodeCopyButton from "@/components/ui/code-copy-button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Script } from "@/lib/types"; import { Script } from "@/lib/types";
const generateInstallCommand = (script: string) => {
return `bash -c "$(wget -qLO - https://github.com/community-scripts/ProxmoxVE/raw/main/${script})"`;
}
export default function InstallCommand({ item }: { item: Script }) { export default function InstallCommand({ item }: { item: Script }) {
const { title, item_type, installCommand, expand } = item; const alpineScript = item.install_methods.find(
const hasAlpineScript = expand?.alpine_script !== undefined; (method) => method.type === "alpine",
);
const defaultScript = item.install_methods.find(
(method) => method.type === "default"
);
const renderInstructions = (isAlpine = false) => ( const renderInstructions = (isAlpine = false) => (
<> <>
<p className="text-sm mt-2"> <p className="text-sm mt-2">
{isAlpine ? ( {isAlpine ? (
<> <>
As an alternative option, you can use Alpine Linux and the {title}{" "} As an alternative option, you can use Alpine Linux and the {item.name}{" "}
package to create a {title} {item_type} container with faster package to create a {item.name} {item.type} container with faster
creation time and minimal system resource usage. You are also creation time and minimal system resource usage. You are also
obliged to adhere to updates provided by the package maintainer. obliged to adhere to updates provided by the package maintainer.
</> </>
) : item_type ? ( ) : item.type ? (
<> <>
To create a new Proxmox VE {title} {item_type}, run the command To create a new Proxmox VE {item.name} {item.type}, run the command
below in the Proxmox VE Shell. below in the Proxmox VE Shell.
</> </>
) : ( ) : (
<>To use the {title} script, run the command below in the shell.</> <>To use the {item.name} script, run the command below in the shell.</>
)} )}
</p> </p>
{isAlpine && ( {isAlpine && (
<p className="mt-2 text-sm"> <p className="mt-2 text-sm">
To create a new Proxmox VE Alpine-{title} {item_type}, run the command To create a new Proxmox VE Alpine-{item.name} {item.type}, run the command
below in the Proxmox VE Shell below in the Proxmox VE Shell
</p> </p>
)} )}
@ -36,7 +45,7 @@ export default function InstallCommand({ item }: { item: Script }) {
return ( return (
<div className="p-4"> <div className="p-4">
{hasAlpineScript ? ( {alpineScript ? (
<Tabs defaultValue="default" className="mt-2 w-full max-w-4xl"> <Tabs defaultValue="default" className="mt-2 w-full max-w-4xl">
<TabsList> <TabsList>
<TabsTrigger value="default">Default</TabsTrigger> <TabsTrigger value="default">Default</TabsTrigger>
@ -44,25 +53,23 @@ export default function InstallCommand({ item }: { item: Script }) {
</TabsList> </TabsList>
<TabsContent value="default"> <TabsContent value="default">
{renderInstructions()} {renderInstructions()}
<CodeCopyButton>{installCommand}</CodeCopyButton> <CodeCopyButton>{defaultScript?.script}</CodeCopyButton>
</TabsContent> </TabsContent>
<TabsContent value="alpine"> <TabsContent value="alpine">
{expand.alpine_script && ( {renderInstructions(true)}
<> <CodeCopyButton>
{renderInstructions(true)} {generateInstallCommand(alpineScript.script)}
<CodeCopyButton> </CodeCopyButton>
{expand.alpine_script.installCommand}
</CodeCopyButton>
</>
)}
</TabsContent> </TabsContent>
</Tabs> </Tabs>
) : ( ) : defaultScript?.script ? (
<> <>
{renderInstructions()} {renderInstructions()}
{installCommand && <CodeCopyButton>{installCommand}</CodeCopyButton>} <CodeCopyButton>
{generateInstallCommand(defaultScript.script)}
</CodeCopyButton>
</> </>
)} ) : null}
</div> </div>
); );
} }

View File

@ -37,11 +37,11 @@ export default function Tooltips({ item }: { item: Script }) {
content="This script will be run in a privileged LXC" content="This script will be run in a privileged LXC"
/> />
)} )}
{item.isUpdateable && ( {item.updateable && (
<TooltipBadge <TooltipBadge
variant="success" variant="success"
label="Updateable" label="Updateable"
content={`To Update ${item.title}, run the command below (or type update) in the LXC Console.`} content={`To Update ${item.name}, run the command below (or type update) in the LXC Console.`}
/> />
)} )}
</div> </div>

View File

@ -18,7 +18,7 @@ const Sidebar = ({
<h1 className="text-xl font-bold">Categories</h1> <h1 className="text-xl font-bold">Categories</h1>
<p className="text-xs italic text-muted-foreground"> <p className="text-xs italic text-muted-foreground">
{items.reduce( {items.reduce(
(acc, category) => acc + category.expand.items.length, (acc, category) => acc + category.scripts.length,
0, 0,
)}{" "} )}{" "}
Total scripts Total scripts

View File

@ -10,7 +10,7 @@ import Sidebar from "./_components/Sidebar";
import { useQueryState } from "nuqs"; import { useQueryState } from "nuqs";
import { import {
LatestScripts, LatestScripts,
MostViewedScripts, // MostViewedScripts,
} from "./_components/ScriptInfoBlocks"; } from "./_components/ScriptInfoBlocks";
function ScriptContent() { function ScriptContent() {
@ -21,39 +21,20 @@ function ScriptContent() {
useEffect(() => { useEffect(() => {
if (selectedScript && links.length > 0) { if (selectedScript && links.length > 0) {
const script = links const script = links
.map((category) => category.expand.items) .map((category) => category.scripts)
.flat() .flat()
.find((script) => script.title === selectedScript); .find((script) => script.name === selectedScript);
setItem(script); setItem(script);
} }
}, [selectedScript, links]); }, [selectedScript, links]);
const sortCategories = (categories: Category[]): Category[] => {
return categories.sort((a: Category, b: Category) => {
if (
a.catagoryName === "Proxmox VE Tools" &&
b.catagoryName !== "Proxmox VE Tools"
) {
return -1;
} else if (
a.catagoryName !== "Proxmox VE Tools" &&
b.catagoryName === "Proxmox VE Tools"
) {
return 1;
} else {
return a.catagoryName.localeCompare(b.catagoryName);
}
});
};
useEffect(() => { useEffect(() => {
fetch( fetch(
`api/categories?_=${process.env.NEXT_PUBLIC_BUILD_TIME || Date.now()}`, `api/categories`,
) )
.then((response) => response.json()) .then((response) => response.json())
.then((categories) => { .then((categories) => {
const sortedCategories = sortCategories(categories); setLinks(categories);
setLinks(sortedCategories);
}) })
.catch((error) => console.error(error)); .catch((error) => console.error(error));
}, []); }, []);
@ -74,7 +55,7 @@ function ScriptContent() {
) : ( ) : (
<div className="flex w-full flex-col gap-5"> <div className="flex w-full flex-col gap-5">
<LatestScripts items={links} /> <LatestScripts items={links} />
<MostViewedScripts items={links} /> {/* <MostViewedScripts items={links} /> */}
</div> </div>
)} )}
</div> </div>