feat: filter regions

This commit is contained in:
2025-03-24 16:10:42 +00:00
parent c0b7836ce3
commit 3c3218044b
2 changed files with 47 additions and 3 deletions

View File

@ -0,0 +1,15 @@
import classNames from "classnames";
import { ReactNode } from "react";
export function FilterButton({ children, onClick, active }: { children: ReactNode, onClick?: () => Promise<void> | void, active?: boolean }) {
return <div
className={classNames("rounded-full outline outline-1 px-4 py-1 cursor-pointer select-none",
{
"bg-neutral-800 outline-neutral-300": active,
"bg-neutral-900 outline-neutral-800 text-neutral-500": !active
}
)}
onClick={onClick}>
{children}
</div>
}

View File

@ -1,17 +1,30 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { LNVpsApi, VmTemplateResponse } from "../api"; import { LNVpsApi, VmHostRegion, VmTemplateResponse } from "../api";
import VpsCard from "../components/vps-card"; import VpsCard from "../components/vps-card";
import { ApiUrl, NostrProfile } from "../const"; import { ApiUrl, NostrProfile } from "../const";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { VpsCustomOrder } from "../components/vps-custom"; import { VpsCustomOrder } from "../components/vps-custom";
import { LatestNews } from "../components/latest-news"; import { LatestNews } from "../components/latest-news";
import { FilterButton } from "../components/button-filter";
import { dedupe } from "@snort/shared";
export default function HomePage() { export default function HomePage() {
const [offers, setOffers] = useState<VmTemplateResponse>(); const [offers, setOffers] = useState<VmTemplateResponse>();
const [region, setRegion] = useState<Array<number>>([]);
const regions = (offers?.templates.map((t) => t.region) ?? []).reduce((acc, v) => {
if (acc[v.id] === undefined) {
acc[v.id] = v;
}
return acc;
}, {} as Record<number, VmHostRegion>);
useEffect(() => { useEffect(() => {
const api = new LNVpsApi(ApiUrl, undefined); const api = new LNVpsApi(ApiUrl, undefined);
api.listOffers().then((o) => setOffers(o)); api.listOffers().then((o) => {
setOffers(o)
setRegion(dedupe(o.templates.map((z) => z.region.id)));
});
}, []); }, []);
return ( return (
@ -23,8 +36,24 @@ export default function HomePage() {
Virtual Private Server hosting with flexible plans, high uptime, and Virtual Private Server hosting with flexible plans, high uptime, and
dedicated support, tailored to your needs. dedicated support, tailored to your needs.
</div> </div>
{Object.keys(regions).length > 1 && <div className="flex gap-2 items-center">
Regions:
{Object.values(regions).map((r) => {
return <FilterButton
active={region.includes(r.id)}
onClick={() => setRegion(x => {
if (x.includes(r.id)) {
return x.filter(y => y != r.id);
} else {
return [...x, r.id];
}
})}>
{r.name}
</FilterButton>;
})}
</div>}
<div className="grid grid-cols-3 gap-2"> <div className="grid grid-cols-3 gap-2">
{offers?.templates.map((a) => <VpsCard spec={a} key={a.id} />)} {offers?.templates.filter((t) => region.includes(t.region.id)).sort((a, b) => a.cost_plan.amount - b.cost_plan.amount).map((a) => <VpsCard spec={a} key={a.id} />)}
{offers?.templates !== undefined && offers.templates.length === 0 && ( {offers?.templates !== undefined && offers.templates.length === 0 && (
<div className="text-red-500 bold text-xl uppercase"> <div className="text-red-500 bold text-xl uppercase">
No offers available No offers available