mirror of
https://github.com/dokploy/dokploy.git
synced 2026-06-14 03:19:49 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 847cf6118b | |||
| 275d7ee586 | |||
| 29aae91959 | |||
| ab6cb7349e | |||
| e36c665e02 | |||
| f3dc480b70 | |||
| 6758ef1542 | |||
| bd4ff2dbf2 | |||
| 579a2262bf |
@@ -24,6 +24,7 @@ import { Progress } from "@/components/ui/progress";
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { api } from "@/utils/api";
|
||||
import { ShowProviders } from "./show-providers";
|
||||
|
||||
const stripePromise = loadStripe(
|
||||
process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!,
|
||||
@@ -151,44 +152,25 @@ export const ShowBilling = () => {
|
||||
<Loader2 className="animate-spin" />
|
||||
</span>
|
||||
) : (
|
||||
<>
|
||||
{products?.map((product) => {
|
||||
const featured = true;
|
||||
return (
|
||||
<div key={product.id}>
|
||||
<section
|
||||
className={clsx(
|
||||
"flex flex-col rounded-3xl border-dashed border-2 px-4 max-w-sm",
|
||||
featured
|
||||
? "order-first border py-8 lg:order-none"
|
||||
: "lg:py-8",
|
||||
)}
|
||||
>
|
||||
{isAnnual && (
|
||||
<div className="mb-4 flex flex-row items-center gap-2">
|
||||
<Badge>Recommended 🚀</Badge>
|
||||
</div>
|
||||
)}
|
||||
{isAnnual ? (
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
<p className="text-2xl font-semibold tracking-tight text-primary ">
|
||||
${" "}
|
||||
{calculatePrice(
|
||||
serverQuantity,
|
||||
isAnnual,
|
||||
).toFixed(2)}{" "}
|
||||
USD
|
||||
</p>
|
||||
|
|
||||
<p className="text-base font-semibold tracking-tight text-muted-foreground">
|
||||
${" "}
|
||||
{(
|
||||
calculatePrice(serverQuantity, isAnnual) / 12
|
||||
).toFixed(2)}{" "}
|
||||
/ Month USD
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
products?.map((product) => {
|
||||
const featured = true;
|
||||
return (
|
||||
<div key={product.id}>
|
||||
<section
|
||||
className={clsx(
|
||||
"flex flex-col rounded-3xl border-dashed border-2 px-4 max-w-sm",
|
||||
featured
|
||||
? "order-first border py-8 lg:order-none"
|
||||
: "lg:py-8",
|
||||
)}
|
||||
>
|
||||
{isAnnual && (
|
||||
<div className="mb-4 flex flex-row items-center gap-2">
|
||||
<Badge>Recommended 🚀</Badge>
|
||||
</div>
|
||||
)}
|
||||
{isAnnual ? (
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
<p className="text-2xl font-semibold tracking-tight text-primary ">
|
||||
${" "}
|
||||
{calculatePrice(serverQuantity, isAnnual).toFixed(
|
||||
@@ -196,127 +178,146 @@ export const ShowBilling = () => {
|
||||
)}{" "}
|
||||
USD
|
||||
</p>
|
||||
)}
|
||||
<h3 className="mt-5 font-medium text-lg text-primary">
|
||||
{product.name}
|
||||
</h3>
|
||||
<p
|
||||
className={clsx(
|
||||
"text-sm",
|
||||
featured ? "text-white" : "text-slate-400",
|
||||
)}
|
||||
>
|
||||
{product.description}
|
||||
|
|
||||
<p className="text-base font-semibold tracking-tight text-muted-foreground">
|
||||
${" "}
|
||||
{(
|
||||
calculatePrice(serverQuantity, isAnnual) / 12
|
||||
).toFixed(2)}{" "}
|
||||
/ Month USD
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-2xl font-semibold tracking-tight text-primary ">
|
||||
${" "}
|
||||
{calculatePrice(serverQuantity, isAnnual).toFixed(
|
||||
2,
|
||||
)}{" "}
|
||||
USD
|
||||
</p>
|
||||
)}
|
||||
<h3 className="mt-5 font-medium text-lg text-primary">
|
||||
{product.name}
|
||||
</h3>
|
||||
<p
|
||||
className={clsx(
|
||||
"text-sm",
|
||||
featured ? "text-white" : "text-slate-400",
|
||||
)}
|
||||
>
|
||||
{product.description}
|
||||
</p>
|
||||
|
||||
<ul
|
||||
className={clsx(
|
||||
" mt-4 flex flex-col gap-y-2 text-sm",
|
||||
featured ? "text-white" : "text-slate-200",
|
||||
<ul
|
||||
className={clsx(
|
||||
" mt-4 flex flex-col gap-y-2 text-sm",
|
||||
featured ? "text-white" : "text-slate-200",
|
||||
)}
|
||||
>
|
||||
{[
|
||||
"All the features of Dokploy",
|
||||
"Unlimited deployments",
|
||||
"Self-hosted on your own infrastructure",
|
||||
"Full access to all deployment features",
|
||||
"Dokploy integration",
|
||||
"Backups",
|
||||
"All Incoming features",
|
||||
].map((feature) => (
|
||||
<li
|
||||
key={feature}
|
||||
className="flex text-muted-foreground"
|
||||
>
|
||||
<CheckIcon />
|
||||
<span className="ml-4">{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="flex flex-col gap-2 mt-4">
|
||||
<div className="flex items-center gap-2 justify-center">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{serverQuantity} Servers
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
disabled={serverQuantity <= 1}
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
if (serverQuantity <= 1) return;
|
||||
|
||||
setServerQuantity(serverQuantity - 1);
|
||||
}}
|
||||
>
|
||||
<MinusIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
<NumberInput
|
||||
value={serverQuantity}
|
||||
onChange={(e) => {
|
||||
setServerQuantity(
|
||||
e.target.value as unknown as number,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setServerQuantity(serverQuantity + 1);
|
||||
}}
|
||||
>
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
data?.subscriptions &&
|
||||
data?.subscriptions?.length > 0
|
||||
? "justify-between"
|
||||
: "justify-end",
|
||||
"flex flex-row items-center gap-2 mt-4",
|
||||
)}
|
||||
>
|
||||
{[
|
||||
"All the features of Dokploy",
|
||||
"Unlimited deployments",
|
||||
"Self-hosted on your own infrastructure",
|
||||
"Full access to all deployment features",
|
||||
"Dokploy integration",
|
||||
"Backups",
|
||||
"All Incoming features",
|
||||
].map((feature) => (
|
||||
<li
|
||||
key={feature}
|
||||
className="flex text-muted-foreground"
|
||||
>
|
||||
<CheckIcon />
|
||||
<span className="ml-4">{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="flex flex-col gap-2 mt-4">
|
||||
<div className="flex items-center gap-2 justify-center">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{serverQuantity} Servers
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
{admin?.user.stripeCustomerId && (
|
||||
<Button
|
||||
disabled={serverQuantity <= 1}
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
if (serverQuantity <= 1) return;
|
||||
variant="secondary"
|
||||
className="w-full"
|
||||
onClick={async () => {
|
||||
const session =
|
||||
await createCustomerPortalSession();
|
||||
|
||||
setServerQuantity(serverQuantity - 1);
|
||||
window.open(session.url);
|
||||
}}
|
||||
>
|
||||
<MinusIcon className="h-4 w-4" />
|
||||
Manage Subscription
|
||||
</Button>
|
||||
<NumberInput
|
||||
value={serverQuantity}
|
||||
onChange={(e) => {
|
||||
setServerQuantity(
|
||||
e.target.value as unknown as number,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setServerQuantity(serverQuantity + 1);
|
||||
}}
|
||||
>
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
data?.subscriptions &&
|
||||
data?.subscriptions?.length > 0
|
||||
? "justify-between"
|
||||
: "justify-end",
|
||||
"flex flex-row items-center gap-2 mt-4",
|
||||
)}
|
||||
>
|
||||
{admin?.user.stripeCustomerId && (
|
||||
{data?.subscriptions?.length === 0 && (
|
||||
<div className="justify-end w-full">
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="w-full"
|
||||
onClick={async () => {
|
||||
const session =
|
||||
await createCustomerPortalSession();
|
||||
|
||||
window.open(session.url);
|
||||
handleCheckout(product.id);
|
||||
}}
|
||||
disabled={serverQuantity < 1}
|
||||
>
|
||||
Manage Subscription
|
||||
Subscribe
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{data?.subscriptions?.length === 0 && (
|
||||
<div className="justify-end w-full">
|
||||
<Button
|
||||
className="w-full"
|
||||
onClick={async () => {
|
||||
handleCheckout(product.id);
|
||||
}}
|
||||
disabled={serverQuantity < 1}
|
||||
>
|
||||
Subscribe
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
<div className="flex flex-col gap-4 pb-10">
|
||||
<ShowProviders />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,355 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import {
|
||||
Cpu,
|
||||
EuroIcon,
|
||||
HardDrive,
|
||||
Loader2,
|
||||
MapPin,
|
||||
MemoryStick,
|
||||
Zap,
|
||||
} from "lucide-react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import * as z from "zod";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
} from "@/components/ui/form";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { api } from "@/utils/api";
|
||||
|
||||
// Function to classify servers by type
|
||||
function getServerCategory(cpuType: string) {
|
||||
if (cpuType === "shared") {
|
||||
return {
|
||||
category: "Shared CPU",
|
||||
icon: Cpu,
|
||||
badge: "shared",
|
||||
color: "bg-blue-500",
|
||||
description: "Perfect for small and medium projects",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
category: "Dedicated CPU",
|
||||
icon: Zap,
|
||||
badge: "dedicated",
|
||||
color: "bg-purple-500",
|
||||
description: "Maximum performance for demanding applications",
|
||||
};
|
||||
}
|
||||
|
||||
const formSchema = z.object({
|
||||
location: z.string().min(1, "Please select a location"),
|
||||
architecture: z.enum(["x86", "arm"], {
|
||||
required_error: "Please select an architecture",
|
||||
}),
|
||||
selectedServerId: z.string().optional(),
|
||||
});
|
||||
|
||||
type FormValues = z.infer<typeof formSchema>;
|
||||
|
||||
export const ShowHetznerProviders = () => {
|
||||
const { data: serverTypesData, isLoading: isLoadingTypes } =
|
||||
api.hetzner.serverTypes.useQuery();
|
||||
const { data: locationsData, isLoading: isLoadingLocations } =
|
||||
api.hetzner.locations.useQuery();
|
||||
|
||||
const form = useForm<FormValues>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
location: "",
|
||||
architecture: "x86",
|
||||
selectedServerId: "",
|
||||
},
|
||||
});
|
||||
|
||||
const selectedLocation = form.watch("location");
|
||||
const selectedArchitecture = form.watch("architecture");
|
||||
|
||||
if (isLoadingTypes || isLoadingLocations) {
|
||||
return (
|
||||
<div className="flex items-center justify-center p-4">
|
||||
<Loader2 className="h-6 w-6 animate-spin" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const locations = locationsData?.locations ?? [];
|
||||
const serverTypes = serverTypesData?.server_types ?? [];
|
||||
|
||||
// Filter server types by selected location AND architecture
|
||||
const filteredServerTypes = serverTypes.filter(
|
||||
(type) =>
|
||||
type.prices.some((price) => price.location === selectedLocation) &&
|
||||
type.architecture === selectedArchitecture,
|
||||
);
|
||||
|
||||
// Group by CPU type (shared/dedicated)
|
||||
const sharedServers = filteredServerTypes.filter(
|
||||
(type) => type.cpu_type === "shared",
|
||||
);
|
||||
const dedicatedServers = filteredServerTypes.filter(
|
||||
(type) => type.cpu_type === "dedicated",
|
||||
);
|
||||
|
||||
const renderServerGrid = (
|
||||
servers: typeof serverTypes,
|
||||
category: ReturnType<typeof getServerCategory>,
|
||||
) => {
|
||||
if (!servers.length) return null;
|
||||
const IconComponent = category.icon;
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<IconComponent className="h-5 w-5" />
|
||||
<h3 className="text-lg font-semibold">{category.category}</h3>
|
||||
<Badge variant="outline" className={`text-white ${category.color}`}>
|
||||
{category.badge}
|
||||
</Badge>
|
||||
<Badge variant="outline" className="text-xs text-muted-foreground">
|
||||
Sorted by price
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
{category.description}
|
||||
</p>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="selectedServerId"
|
||||
render={({ field }) => (
|
||||
<FormItem className="space-y-0">
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
onValueChange={field.onChange}
|
||||
value={field.value}
|
||||
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"
|
||||
>
|
||||
{servers.map((server) => (
|
||||
<div key={server.id} className="relative">
|
||||
<RadioGroupItem
|
||||
value={server.id.toString()}
|
||||
id={`server-${server.id}`}
|
||||
className="absolute right-4 top-4 z-10"
|
||||
/>
|
||||
<label htmlFor={`server-${server.id}`}>
|
||||
<Card
|
||||
className={`relative bg-transparent transition-all duration-200 cursor-pointer ${
|
||||
field.value === server.id.toString()
|
||||
? "border-primary bg-primary/5"
|
||||
: "hover:bg-primary/5"
|
||||
}`}
|
||||
>
|
||||
<CardHeader>
|
||||
<CardTitle>{server.name}</CardTitle>
|
||||
<CardDescription>
|
||||
{server.description}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Cpu className="h-4 w-4 text-blue-500" />
|
||||
<div>
|
||||
<strong>Cores:</strong> {server.cores}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<MemoryStick className="h-4 w-4 text-green-500" />
|
||||
<div>
|
||||
<strong>Memory:</strong> {server.memory} GB
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<HardDrive className="h-4 w-4 text-purple-500" />
|
||||
<div>
|
||||
<strong>Disk:</strong> {server.disk} GB
|
||||
</div>
|
||||
</div>
|
||||
{/* Show price for selected location */}
|
||||
{server.prices
|
||||
.filter((p) => p.location === selectedLocation)
|
||||
.map((p) => (
|
||||
<div
|
||||
key={p.location}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<EuroIcon className="h-4 w-4 text-yellow-500" />
|
||||
<div>
|
||||
<strong>Price (monthly):</strong> €
|
||||
{Number.parseFloat(
|
||||
p.price_monthly.net,
|
||||
).toFixed(2)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function onSubmit(values: FormValues) {
|
||||
console.log("Form submitted:", values);
|
||||
// Here you can handle the form submission with the selected server
|
||||
}
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||||
{/* Filters Card */}
|
||||
<Card className="bg-transparent">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-xl">
|
||||
<MapPin className="h-5 w-5" />
|
||||
Filters
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Choose a region and architecture to see location-specific pricing
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<Form {...form}>
|
||||
<div className="space-y-6">
|
||||
{/* Region Selector */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="location"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Region</FormLabel>
|
||||
<Select
|
||||
value={field.value}
|
||||
onValueChange={field.onChange}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a region" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{locations.map((loc) => (
|
||||
<SelectItem key={loc.id} value={loc.name}>
|
||||
{loc.description}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Architecture Selector */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="architecture"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Architecture</FormLabel>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value}
|
||||
className="grid grid-cols-2 gap-4"
|
||||
>
|
||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<RadioGroupItem value="x86" />
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">
|
||||
x86 (Intel/AMD)
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<RadioGroupItem value="arm" />
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">ARM</FormLabel>
|
||||
</FormItem>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</Form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Architecture Information */}
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<div className="p-4 bg-blue-900 rounded-lg border border-blue-600">
|
||||
<div className="flex items-start gap-2">
|
||||
<Cpu className="h-5 w-5 text-blue-600 mt-0.5" />
|
||||
<div className="text-sm text-blue-200">
|
||||
<strong>x86 Architecture:</strong> Traditional Intel/AMD
|
||||
processors. Most compatible with existing software and
|
||||
applications. Best choice for general-purpose workloads.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-green-900 rounded-lg border border-green-600">
|
||||
<div className="flex items-start gap-2">
|
||||
<Cpu className="h-5 w-5 text-green-600 mt-0.5" />
|
||||
<div className="text-sm text-green-200">
|
||||
<strong>ARM Architecture:</strong> Modern, energy-efficient
|
||||
processors. Excellent price-to-performance ratio. Perfect for
|
||||
cloud-native and containerized applications.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Server Types Grid */}
|
||||
{selectedLocation && (
|
||||
<>
|
||||
{renderServerGrid(sharedServers, getServerCategory("shared"))}
|
||||
{renderServerGrid(dedicatedServers, getServerCategory("dedicated"))}
|
||||
{sharedServers.length === 0 && dedicatedServers.length === 0 && (
|
||||
<p className="text-center text-muted-foreground py-10">
|
||||
No server types available for this region and architecture
|
||||
combination. <br />
|
||||
Please try a different region or architecture.
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{selectedLocation && form.watch("selectedServerId") && (
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit" className="bg-primary">
|
||||
Continue with Selected Server
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,423 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import {
|
||||
Calendar,
|
||||
Cpu,
|
||||
DollarSign,
|
||||
Globe,
|
||||
HardDrive,
|
||||
Loader2,
|
||||
MemoryStick,
|
||||
Server,
|
||||
} from "lucide-react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import * as z from "zod";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import { api } from "@/utils/api";
|
||||
|
||||
const formSchema = z.object({
|
||||
datacenter: z.string({
|
||||
required_error: "Please select a datacenter",
|
||||
}),
|
||||
plan: z.string({
|
||||
required_error: "Please select a server plan",
|
||||
}),
|
||||
billingPeriod: z.object(
|
||||
{
|
||||
unit: z.enum(["month", "year"]),
|
||||
period: z.number(),
|
||||
},
|
||||
{
|
||||
required_error: "Please select a billing period",
|
||||
},
|
||||
),
|
||||
});
|
||||
|
||||
// Format billing period
|
||||
function formatBillingPeriod(period: number, unit: string): string {
|
||||
if (unit === "month") {
|
||||
return period === 1 ? "Monthly" : `${period} months`;
|
||||
}
|
||||
if (unit === "year") {
|
||||
return period === 1 ? "Yearly" : `${period} years`;
|
||||
}
|
||||
return `${period} ${unit}`;
|
||||
}
|
||||
|
||||
// Convert price from cents to dollars
|
||||
function formatPrice(priceInCents: number): string {
|
||||
return (priceInCents / 100).toFixed(2);
|
||||
}
|
||||
|
||||
// Calculate yearly savings
|
||||
function calculateSavings(
|
||||
monthlyPriceInCents: number,
|
||||
yearlyPriceInCents: number,
|
||||
): number {
|
||||
return (monthlyPriceInCents * 12 - yearlyPriceInCents) / 100;
|
||||
}
|
||||
|
||||
type FormData = z.infer<typeof formSchema>;
|
||||
|
||||
export const ShowHostingerServers = () => {
|
||||
const { data: vpsPlans, isLoading: plansLoading } =
|
||||
api.hostinger.vpsPlans.useQuery();
|
||||
const { data: dataCenters, isLoading: centersLoading } =
|
||||
api.hostinger.dataCenters.useQuery();
|
||||
|
||||
const form = useForm<FormData>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
datacenter: "",
|
||||
plan: "",
|
||||
billingPeriod: {
|
||||
unit: "month",
|
||||
period: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const isLoading = plansLoading || centersLoading;
|
||||
|
||||
function onSubmit(data: FormData) {
|
||||
console.log(data);
|
||||
// Handle form submission here
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center p-4">
|
||||
<Loader2 className="h-6 w-6 animate-spin" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<Card className="bg-transparent">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Server className="h-5 w-5" />
|
||||
Hostinger VPS Plans
|
||||
<Badge variant="outline" className="text-xs text-muted-foreground">
|
||||
Sorted by price
|
||||
</Badge>
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
VPS plans with real pricing from Hostinger API
|
||||
<br />
|
||||
<span className="text-xs text-orange-600">
|
||||
💡 Promotional pricing applies to first billing period only
|
||||
</span>
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
||||
{/* Data Center Selection */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="datacenter"
|
||||
render={({ field }) => (
|
||||
<FormItem className="space-y-4">
|
||||
<FormLabel>
|
||||
<div className="flex items-center gap-2">
|
||||
<Globe className="h-5 w-5 text-muted-foreground" />
|
||||
<span className="text-lg font-medium">
|
||||
Select Data Center
|
||||
</span>
|
||||
</div>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value}
|
||||
className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4"
|
||||
>
|
||||
{dataCenters?.map((center) => (
|
||||
<FormItem key={center.id}>
|
||||
<FormControl>
|
||||
<RadioGroupItem
|
||||
value={center.id?.toString() || ""}
|
||||
className="peer sr-only"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel className="p-4 rounded-lg border-2 transition-all duration-200 flex flex-col items-center gap-2 peer-aria-checked:border-purple-500 peer-aria-checked:bg-purple-50 dark:peer-aria-checked:bg-purple-950 hover:border-purple-300 cursor-pointer">
|
||||
<Globe className="h-6 w-6 text-muted-foreground" />
|
||||
<span className="text-sm font-medium text-center">
|
||||
{center.city} / {center.continent}
|
||||
</span>
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Billing Period Selection */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="billingPeriod"
|
||||
render={({ field }) => (
|
||||
<FormItem className="space-y-4">
|
||||
<FormLabel>
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar className="h-5 w-5 text-muted-foreground" />
|
||||
<span className="text-lg font-medium">
|
||||
Billing Period
|
||||
</span>
|
||||
</div>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
onValueChange={(value) => {
|
||||
switch (value) {
|
||||
case "monthly":
|
||||
field.onChange({ unit: "month", period: 1 });
|
||||
break;
|
||||
case "yearly":
|
||||
field.onChange({ unit: "year", period: 1 });
|
||||
break;
|
||||
case "2years":
|
||||
field.onChange({ unit: "year", period: 2 });
|
||||
break;
|
||||
}
|
||||
}}
|
||||
defaultValue="monthly"
|
||||
className="grid w-full grid-cols-3 lg:w-[600px] gap-4"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<RadioGroupItem
|
||||
value="monthly"
|
||||
className="peer sr-only"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel className="flex items-center justify-center p-3 rounded-lg border-2 transition-all duration-200 peer-aria-checked:border-purple-500 peer-aria-checked:bg-purple-50 dark:peer-aria-checked:bg-purple-950 hover:border-purple-300 cursor-pointer">
|
||||
Monthly Billing
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<RadioGroupItem
|
||||
value="yearly"
|
||||
className="peer sr-only"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel className="flex items-center justify-center p-3 rounded-lg border-2 transition-all duration-200 peer-aria-checked:border-purple-500 peer-aria-checked:bg-purple-50 dark:peer-aria-checked:bg-purple-950 hover:border-purple-300 cursor-pointer">
|
||||
Annual Billing
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<RadioGroupItem
|
||||
value="2years"
|
||||
className="peer sr-only"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel className="flex items-center justify-center p-3 rounded-lg border-2 transition-all duration-200 peer-aria-checked:border-purple-500 peer-aria-checked:bg-purple-50 dark:peer-aria-checked:bg-purple-950 hover:border-purple-300 cursor-pointer">
|
||||
2 Year Billing
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* VPS Plans Selection */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="plan"
|
||||
render={({ field }) => (
|
||||
<FormItem className="space-y-4">
|
||||
<FormLabel>
|
||||
<div className="flex items-center gap-2">
|
||||
<Server className="h-5 w-5 text-muted-foreground" />
|
||||
<span className="text-lg font-medium">
|
||||
Select Server Plan
|
||||
</span>
|
||||
</div>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value}
|
||||
className="grid grid-cols-1 lg:grid-cols-2 gap-6"
|
||||
>
|
||||
{vpsPlans
|
||||
?.sort((a, b) => {
|
||||
const billingPeriod = form.watch("billingPeriod");
|
||||
const priceA =
|
||||
a.prices?.find(
|
||||
(p) =>
|
||||
p.period_unit === billingPeriod.unit &&
|
||||
p.period === billingPeriod.period,
|
||||
)?.price || 0;
|
||||
const priceB =
|
||||
b.prices?.find(
|
||||
(p) =>
|
||||
p.period_unit === billingPeriod.unit &&
|
||||
p.period === billingPeriod.period,
|
||||
)?.price || 0;
|
||||
return priceA - priceB;
|
||||
})
|
||||
?.map((plan) => {
|
||||
const monthlyPrice =
|
||||
plan.prices?.find(
|
||||
(p) =>
|
||||
p.period === 1 && p.period_unit === "month",
|
||||
)?.price || 0;
|
||||
|
||||
const selectedPrice = plan.prices?.find(
|
||||
(p) =>
|
||||
p.period_unit ===
|
||||
form.watch("billingPeriod.unit") &&
|
||||
p.period === form.watch("billingPeriod.period"),
|
||||
);
|
||||
|
||||
if (!selectedPrice) return null;
|
||||
|
||||
return (
|
||||
<FormItem key={plan.id}>
|
||||
<FormControl>
|
||||
<RadioGroupItem
|
||||
value={plan.id || ""}
|
||||
className="peer sr-only"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel className="w-full cursor-pointer">
|
||||
<Card
|
||||
className={`border-2 transition-all duration-200 relative bg-transparent hover:border-purple-300 hover:shadow-lg ${field.value === plan.id ? "border-purple-500 bg-purple-950/40" : ""}`}
|
||||
>
|
||||
{plan.name === "KVM 2" && (
|
||||
<div className="absolute -top-2 -right-2 bg-green-500 text-white px-2 py-1 rounded-full text-xs font-semibold">
|
||||
MOST POPULAR
|
||||
</div>
|
||||
)}
|
||||
<CardHeader>
|
||||
<CardTitle>{plan.name}</CardTitle>
|
||||
<CardDescription>
|
||||
{"High-performance VPS hosting"}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="flex flex-col items-center p-2 bg-muted rounded-lg">
|
||||
<Cpu className="h-4 w-4 mb-1 text-muted-foreground" />
|
||||
<span className="text-sm font-medium">
|
||||
{plan.metadata?.cpus || 1} vCPU
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
Cores
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center p-2 bg-muted rounded-lg">
|
||||
<MemoryStick className="h-4 w-4 mb-1 text-muted-foreground" />
|
||||
<span className="text-sm font-medium">
|
||||
{Number.parseInt(
|
||||
plan.metadata?.memory || "2048",
|
||||
) / 1024}{" "}
|
||||
GB
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
RAM
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center p-2 bg-muted rounded-lg">
|
||||
<HardDrive className="h-4 w-4 mb-1 text-muted-foreground" />
|
||||
<span className="text-sm font-medium">
|
||||
{Number.parseInt(
|
||||
plan.metadata?.disk_space ||
|
||||
"20480",
|
||||
) / 1024}{" "}
|
||||
GB
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
SSD
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-2">
|
||||
<div className="flex items-center justify-between p-3 bg-primary/5 rounded-lg">
|
||||
<div className="flex items-center">
|
||||
<Calendar className="h-4 w-4 mr-2 text-muted-foreground" />
|
||||
<span className="text-sm">
|
||||
{formatBillingPeriod(
|
||||
selectedPrice.period || 1,
|
||||
selectedPrice.period_unit ||
|
||||
"month",
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<DollarSign className="h-4 w-4 mr-1 text-primary" />
|
||||
<span className="text-lg font-semibold">
|
||||
$
|
||||
{formatPrice(
|
||||
selectedPrice.price || 0,
|
||||
)}
|
||||
</span>
|
||||
{selectedPrice.period_unit ===
|
||||
"year" && (
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="ml-2 text-xs"
|
||||
>
|
||||
Save $
|
||||
{calculateSavings(
|
||||
monthlyPrice,
|
||||
selectedPrice.price || 0,
|
||||
).toFixed(2)}
|
||||
/yr
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
);
|
||||
})}
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button type="submit" className="w-full">
|
||||
Create Server
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
import { DollarSign } from "lucide-react";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { ShowHetznerProviders } from "./show-hetzner-providers";
|
||||
import { ShowHostingerServers } from "./show-hostinger-servers";
|
||||
|
||||
export const ShowProviders = () => {
|
||||
return (
|
||||
<Card className="w-full bg-transparent border-none">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<DollarSign className="h-6 w-6 text-green-600" />
|
||||
Servers
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Manage and view available server types from Hetzner and Hostinger for
|
||||
your business. Here you can see updated pricing and specifications for
|
||||
each plan.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Tabs defaultValue="hetzner" className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="hetzner" className="flex items-center gap-2">
|
||||
🇩🇪 Hetzner Cloud
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="hostinger" className="flex items-center gap-2">
|
||||
🌍 Hostinger VPS
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="hetzner" className="mt-4">
|
||||
<ShowHetznerProviders />
|
||||
</TabsContent>
|
||||
<TabsContent value="hostinger" className="mt-4">
|
||||
<ShowHostingerServers />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
@@ -182,7 +182,8 @@
|
||||
"tsx": "^4.16.2",
|
||||
"typescript": "^5.8.3",
|
||||
"vite-tsconfig-paths": "4.3.2",
|
||||
"vitest": "^1.6.1"
|
||||
"vitest": "^1.6.1",
|
||||
"openapi-typescript": "7.8.0"
|
||||
},
|
||||
"ct3aMetadata": {
|
||||
"initVersion": "7.25.2"
|
||||
|
||||
@@ -16,6 +16,8 @@ import { gitProviderRouter } from "./routers/git-provider";
|
||||
import { giteaRouter } from "./routers/gitea";
|
||||
import { githubRouter } from "./routers/github";
|
||||
import { gitlabRouter } from "./routers/gitlab";
|
||||
import { hetznerRouter } from "./routers/hetzner";
|
||||
import { hostingerRouter } from "./routers/hostinger";
|
||||
import { mariadbRouter } from "./routers/mariadb";
|
||||
import { mongoRouter } from "./routers/mongo";
|
||||
import { mountRouter } from "./routers/mount";
|
||||
@@ -85,6 +87,8 @@ export const appRouter = createTRPCRouter({
|
||||
schedule: scheduleRouter,
|
||||
rollback: rollbackRouter,
|
||||
volumeBackups: volumeBackupsRouter,
|
||||
hetzner: hetznerRouter,
|
||||
hostinger: hostingerRouter,
|
||||
environment: environmentRouter,
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import {
|
||||
fetchHetznerLocations,
|
||||
fetchHetznerServers,
|
||||
fetchHetznerServerTypes,
|
||||
} from "@dokploy/server/index";
|
||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||
|
||||
export const hetznerRouter = createTRPCRouter({
|
||||
locations: protectedProcedure.query(async () => {
|
||||
const locations = await fetchHetznerLocations();
|
||||
return locations;
|
||||
}),
|
||||
|
||||
serverTypes: protectedProcedure.query(async () => {
|
||||
return await fetchHetznerServerTypes();
|
||||
}),
|
||||
|
||||
servers: protectedProcedure.query(async () => {
|
||||
return await fetchHetznerServers();
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import {
|
||||
fetchHostingerCatalog,
|
||||
fetchHostingerDataCenters,
|
||||
} from "@dokploy/server/index";
|
||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||
|
||||
export const hostingerRouter = createTRPCRouter({
|
||||
vpsPlans: protectedProcedure.query(async () => {
|
||||
const catalogItems = await fetchHostingerCatalog();
|
||||
return catalogItems.filter((item) => item?.name?.startsWith("KVM"));
|
||||
}),
|
||||
|
||||
dataCenters: protectedProcedure.query(async () => {
|
||||
return await fetchHostingerDataCenters();
|
||||
}),
|
||||
});
|
||||
@@ -28,6 +28,7 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"openapi-fetch": "0.14.0",
|
||||
"@ai-sdk/anthropic": "^2.0.5",
|
||||
"@ai-sdk/azure": "^2.0.16",
|
||||
"@ai-sdk/cohere": "^2.0.4",
|
||||
|
||||
@@ -21,6 +21,8 @@ export * from "./services/git-provider";
|
||||
export * from "./services/gitea";
|
||||
export * from "./services/github";
|
||||
export * from "./services/gitlab";
|
||||
export * from "./services/hetzner";
|
||||
export * from "./services/hostinger";
|
||||
export * from "./services/mariadb";
|
||||
export * from "./services/mongo";
|
||||
export * from "./services/mount";
|
||||
@@ -76,7 +78,6 @@ export * from "./utils/builders/nixpacks";
|
||||
export * from "./utils/builders/paketo";
|
||||
export * from "./utils/builders/static";
|
||||
export * from "./utils/builders/utils";
|
||||
|
||||
export * from "./utils/cluster/upload";
|
||||
export * from "./utils/databases/rebuild";
|
||||
export * from "./utils/docker/collision";
|
||||
@@ -106,7 +107,6 @@ export * from "./utils/providers/docker";
|
||||
export * from "./utils/providers/git";
|
||||
export * from "./utils/providers/gitea";
|
||||
export * from "./utils/providers/github";
|
||||
export * from "./utils/providers/github";
|
||||
export * from "./utils/providers/gitlab";
|
||||
export * from "./utils/providers/raw";
|
||||
export * from "./utils/schedules/index";
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import createClient from "openapi-fetch";
|
||||
import type { paths } from "../types/hetzner-types";
|
||||
|
||||
const HETZNER_API_URL = "https://api.hetzner.cloud/v1";
|
||||
|
||||
const hetznerApiKey = process.env.HETZNER_API_KEY;
|
||||
|
||||
const client = createClient<paths>({ baseUrl: HETZNER_API_URL });
|
||||
|
||||
export const fetchHetznerLocations = async () => {
|
||||
const { data, error } = await client.GET("/locations", {
|
||||
headers: {
|
||||
Authorization: `Bearer ${hetznerApiKey}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw new Error(`Failed to fetch Hetzner locations: ${error}`);
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
export const fetchHetznerServerTypes = async () => {
|
||||
const { data, error } = await client.GET("/server_types", {
|
||||
headers: {
|
||||
Authorization: `Bearer ${hetznerApiKey}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw new Error(`Failed to fetch Hetzner server types: ${error}`);
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export const fetchHetznerServers = async () => {
|
||||
const { data, error } = await client.GET("/servers", {
|
||||
headers: {
|
||||
Authorization: `Bearer ${hetznerApiKey}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw new Error(`Failed to fetch Hetzner servers: ${error}`);
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
import createClient from "openapi-fetch";
|
||||
import type { paths } from "../types/hostinger-types";
|
||||
|
||||
const HOSTINGER_API_URL = "https://developers.hostinger.com";
|
||||
const hostingerApiKey = process.env.HOSTINGER_API_KEY;
|
||||
|
||||
const client = createClient<paths>({ baseUrl: HOSTINGER_API_URL });
|
||||
|
||||
export const fetchHostingerCatalog = async () => {
|
||||
const { data, error } = await client.GET("/api/billing/v1/catalog", {
|
||||
headers: {
|
||||
Authorization: `Bearer ${hostingerApiKey}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw new Error(`Failed to fetch Hostinger catalog: ${error}`);
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export const fetchHostingerDataCenters = async () => {
|
||||
const { data, error } = await client.GET("/api/vps/v1/data-centers", {
|
||||
headers: {
|
||||
Authorization: `Bearer ${hostingerApiKey}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw new Error(`Failed to fetch Hostinger data centers: ${error}`);
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Generated
+160
-17
@@ -518,6 +518,9 @@ importers:
|
||||
memfs:
|
||||
specifier: ^4.17.2
|
||||
version: 4.17.2
|
||||
openapi-typescript:
|
||||
specifier: 7.8.0
|
||||
version: 7.8.0(typescript@5.8.3)
|
||||
tailwindcss:
|
||||
specifier: ^3.4.17
|
||||
version: 3.4.17
|
||||
@@ -708,6 +711,9 @@ importers:
|
||||
octokit:
|
||||
specifier: 3.1.2
|
||||
version: 3.1.2
|
||||
openapi-fetch:
|
||||
specifier: 0.14.0
|
||||
version: 0.14.0
|
||||
otpauth:
|
||||
specifier: ^9.4.0
|
||||
version: 9.4.0
|
||||
@@ -3570,6 +3576,16 @@ packages:
|
||||
peerDependencies:
|
||||
'@redis/client': ^1.0.0
|
||||
|
||||
'@redocly/ajv@8.11.2':
|
||||
resolution: {integrity: sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==}
|
||||
|
||||
'@redocly/config@0.22.2':
|
||||
resolution: {integrity: sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ==}
|
||||
|
||||
'@redocly/openapi-core@1.34.3':
|
||||
resolution: {integrity: sha512-3arRdUp1fNx55itnjKiUhO6t4Mf91TsrTIYINDNLAZPS0TPd5YpiXRctwjel0qqWoOOhjA34cZ3m4dksLDFUYg==}
|
||||
engines: {node: '>=18.17.0', npm: '>=9.5.0'}
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.41.1':
|
||||
resolution: {integrity: sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw==}
|
||||
cpu: [arm]
|
||||
@@ -4194,6 +4210,10 @@ packages:
|
||||
ansi-align@3.0.1:
|
||||
resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==}
|
||||
|
||||
ansi-colors@4.1.3:
|
||||
resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
ansi-escapes@7.0.0:
|
||||
resolution: {integrity: sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -4440,6 +4460,9 @@ packages:
|
||||
resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==}
|
||||
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
|
||||
|
||||
change-case@5.4.4:
|
||||
resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==}
|
||||
|
||||
character-entities-html4@2.1.0:
|
||||
resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==}
|
||||
|
||||
@@ -4553,6 +4576,9 @@ packages:
|
||||
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
|
||||
engines: {node: '>=12.5.0'}
|
||||
|
||||
colorette@1.4.0:
|
||||
resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==}
|
||||
|
||||
colorette@2.0.20:
|
||||
resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
|
||||
|
||||
@@ -5462,6 +5488,10 @@ packages:
|
||||
resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
index-to-position@1.1.0:
|
||||
resolution: {integrity: sha512-XPdx9Dq4t9Qk1mTMbWONJqU7boCoumEH7fRET37HX5+khDUl3J2W6PdALxhILYlIYx2amlwYcRPp28p0tSiojg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
inflation@2.1.0:
|
||||
resolution: {integrity: sha512-t54PPJHG1Pp7VQvxyVCJ9mBbjG3Hqryges9bXoOO6GExCPa+//i/d5GSuFtpx3ALLd7lgIAur6zrIlBQyJuMlQ==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
@@ -5676,6 +5706,10 @@ packages:
|
||||
js-file-download@0.4.12:
|
||||
resolution: {integrity: sha512-rML+NkoD08p5Dllpjo0ffy4jRHeY6Zsapvr/W86N7E0yuzAO6qa5X9+xog6zQNlH102J7IXljNY2FtS6Lj3ucg==}
|
||||
|
||||
js-levenshtein@1.1.6:
|
||||
resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
|
||||
@@ -6131,6 +6165,10 @@ packages:
|
||||
minimatch@3.1.2:
|
||||
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
||||
|
||||
minimatch@5.1.6:
|
||||
resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
minimatch@7.4.6:
|
||||
resolution: {integrity: sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -6382,6 +6420,9 @@ packages:
|
||||
resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
openapi-fetch@0.14.0:
|
||||
resolution: {integrity: sha512-PshIdm1NgdLvb05zp8LqRQMNSKzIlPkyMxYFxwyHR+UlKD4t2nUjkDhNxeRbhRSEd3x5EUNh2w5sJYwkhOH4fg==}
|
||||
|
||||
openapi-path-templating@2.2.1:
|
||||
resolution: {integrity: sha512-eN14VrDvl/YyGxxrkGOHkVkWEoPyhyeydOUrbvjoz8K5eIGgELASwN1eqFOJ2CTQMGCy2EntOK1KdtJ8ZMekcg==}
|
||||
engines: {node: '>=12.20.0'}
|
||||
@@ -6393,6 +6434,15 @@ packages:
|
||||
openapi-types@12.1.3:
|
||||
resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==}
|
||||
|
||||
openapi-typescript-helpers@0.0.15:
|
||||
resolution: {integrity: sha512-opyTPaunsklCBpTK8JGef6mfPhLSnyy5a0IN9vKtx3+4aExf+KxEqYwIy3hqkedXIB97u357uLMJsOnm3GVjsw==}
|
||||
|
||||
openapi-typescript@7.8.0:
|
||||
resolution: {integrity: sha512-1EeVWmDzi16A+siQlo/SwSGIT7HwaFAVjvMA7/jG5HMLSnrUOzPL7uSTRZZa4v/LCRxHTApHKtNY6glApEoiUQ==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
typescript: ^5.x
|
||||
|
||||
otpauth@9.4.0:
|
||||
resolution: {integrity: sha512-fHIfzIG5RqCkK9cmV8WU+dPQr9/ebR5QOwGZn2JAr1RQF+lmAuLL2YdtdqvmBjNmgJlYk3KZ4a0XokaEhg1Jsw==}
|
||||
|
||||
@@ -6441,6 +6491,10 @@ packages:
|
||||
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
parse-json@8.3.0:
|
||||
resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
parseley@0.12.1:
|
||||
resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==}
|
||||
|
||||
@@ -6543,6 +6597,10 @@ packages:
|
||||
resolution: {integrity: sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
pluralize@8.0.0:
|
||||
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
pngjs@5.0.0:
|
||||
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
@@ -7292,6 +7350,10 @@ packages:
|
||||
resolution: {integrity: sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
supports-color@10.0.0:
|
||||
resolution: {integrity: sha512-HRVVSbCCMbj7/kdWF9Q+bbckjBHLtHMEoJWlkmYzzdwhYMkjkOwubLM6t7NbWKjgKamGDrWL1++KrjUO1t9oAQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
supports-color@7.2.0:
|
||||
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -7478,6 +7540,10 @@ packages:
|
||||
resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
|
||||
engines: {node: '>=12.20'}
|
||||
|
||||
type-fest@4.41.0:
|
||||
resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
type-is@1.6.18:
|
||||
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@@ -7550,6 +7616,9 @@ packages:
|
||||
peerDependencies:
|
||||
browserslist: '>= 4.21.0'
|
||||
|
||||
uri-js-replace@1.0.1:
|
||||
resolution: {integrity: sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==}
|
||||
|
||||
url-parse@1.5.10:
|
||||
resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==}
|
||||
|
||||
@@ -7778,6 +7847,9 @@ packages:
|
||||
yallist@4.0.0:
|
||||
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
|
||||
|
||||
yaml-ast-parser@0.0.43:
|
||||
resolution: {integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==}
|
||||
|
||||
yaml@2.8.0:
|
||||
resolution: {integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==}
|
||||
engines: {node: '>= 14.6'}
|
||||
@@ -10613,6 +10685,29 @@ snapshots:
|
||||
dependencies:
|
||||
'@redis/client': 1.6.0
|
||||
|
||||
'@redocly/ajv@8.11.2':
|
||||
dependencies:
|
||||
fast-deep-equal: 3.1.3
|
||||
json-schema-traverse: 1.0.0
|
||||
require-from-string: 2.0.2
|
||||
uri-js-replace: 1.0.1
|
||||
|
||||
'@redocly/config@0.22.2': {}
|
||||
|
||||
'@redocly/openapi-core@1.34.3(supports-color@10.0.0)':
|
||||
dependencies:
|
||||
'@redocly/ajv': 8.11.2
|
||||
'@redocly/config': 0.22.2
|
||||
colorette: 1.4.0
|
||||
https-proxy-agent: 7.0.6(supports-color@10.0.0)
|
||||
js-levenshtein: 1.1.6
|
||||
js-yaml: 4.1.0
|
||||
minimatch: 5.1.6
|
||||
pluralize: 8.0.0
|
||||
yaml-ast-parser: 0.0.43
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.41.1':
|
||||
optional: true
|
||||
|
||||
@@ -11449,7 +11544,7 @@ snapshots:
|
||||
|
||||
agent-base@6.0.2:
|
||||
dependencies:
|
||||
debug: 4.4.1
|
||||
debug: 4.4.1(supports-color@10.0.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -11493,6 +11588,8 @@ snapshots:
|
||||
dependencies:
|
||||
string-width: 4.2.3
|
||||
|
||||
ansi-colors@4.1.3: {}
|
||||
|
||||
ansi-escapes@7.0.0:
|
||||
dependencies:
|
||||
environment: 1.1.0
|
||||
@@ -11768,6 +11865,8 @@ snapshots:
|
||||
|
||||
chalk@5.4.1: {}
|
||||
|
||||
change-case@5.4.4: {}
|
||||
|
||||
character-entities-html4@2.1.0: {}
|
||||
|
||||
character-entities-legacy@1.1.4: {}
|
||||
@@ -11891,6 +11990,8 @@ snapshots:
|
||||
color-string: 1.9.1
|
||||
optional: true
|
||||
|
||||
colorette@1.4.0: {}
|
||||
|
||||
colorette@2.0.20: {}
|
||||
|
||||
combined-stream@1.0.8:
|
||||
@@ -12063,9 +12164,11 @@ snapshots:
|
||||
|
||||
dateformat@4.6.3: {}
|
||||
|
||||
debug@4.4.1:
|
||||
debug@4.4.1(supports-color@10.0.0):
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
optionalDependencies:
|
||||
supports-color: 10.0.0
|
||||
|
||||
decamelize@1.2.0: {}
|
||||
|
||||
@@ -12137,7 +12240,7 @@ snapshots:
|
||||
|
||||
docker-modem@5.0.6:
|
||||
dependencies:
|
||||
debug: 4.4.1
|
||||
debug: 4.4.1(supports-color@10.0.0)
|
||||
readable-stream: 3.6.2
|
||||
split-ca: 1.0.1
|
||||
ssh2: 1.15.0
|
||||
@@ -12275,7 +12378,7 @@ snapshots:
|
||||
|
||||
esbuild-register@3.6.0(esbuild@0.19.12):
|
||||
dependencies:
|
||||
debug: 4.4.1
|
||||
debug: 4.4.1(supports-color@10.0.0)
|
||||
esbuild: 0.19.12
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -12522,7 +12625,7 @@ snapshots:
|
||||
gaxios@6.7.1:
|
||||
dependencies:
|
||||
extend: 3.0.2
|
||||
https-proxy-agent: 7.0.6
|
||||
https-proxy-agent: 7.0.6(supports-color@10.0.0)
|
||||
is-stream: 2.0.1
|
||||
node-fetch: 2.7.0
|
||||
uuid: 9.0.1
|
||||
@@ -12542,7 +12645,7 @@ snapshots:
|
||||
gel@2.1.0:
|
||||
dependencies:
|
||||
'@petamoriken/float16': 3.9.2
|
||||
debug: 4.4.1
|
||||
debug: 4.4.1(supports-color@10.0.0)
|
||||
env-paths: 3.0.0
|
||||
semver: 7.7.2
|
||||
shell-quote: 1.8.2
|
||||
@@ -12769,14 +12872,14 @@ snapshots:
|
||||
https-proxy-agent@5.0.1:
|
||||
dependencies:
|
||||
agent-base: 6.0.2
|
||||
debug: 4.4.1
|
||||
debug: 4.4.1(supports-color@10.0.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
https-proxy-agent@7.0.6:
|
||||
https-proxy-agent@7.0.6(supports-color@10.0.0):
|
||||
dependencies:
|
||||
agent-base: 7.1.4
|
||||
debug: 4.4.1
|
||||
debug: 4.4.1(supports-color@10.0.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -12820,6 +12923,8 @@ snapshots:
|
||||
|
||||
indent-string@5.0.0: {}
|
||||
|
||||
index-to-position@1.1.0: {}
|
||||
|
||||
inflation@2.1.0: {}
|
||||
|
||||
inflight@1.0.6:
|
||||
@@ -12851,7 +12956,7 @@ snapshots:
|
||||
canonicalize: 1.0.8
|
||||
chalk: 4.1.2
|
||||
cross-fetch: 4.1.0
|
||||
debug: 4.4.1
|
||||
debug: 4.4.1(supports-color@10.0.0)
|
||||
hash.js: 1.1.7
|
||||
json-stringify-safe: 5.0.1
|
||||
ms: 2.1.3
|
||||
@@ -12883,7 +12988,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@ioredis/commands': 1.2.0
|
||||
cluster-key-slot: 1.1.2
|
||||
debug: 4.4.1
|
||||
debug: 4.4.1(supports-color@10.0.0)
|
||||
denque: 2.1.0
|
||||
lodash.defaults: 4.2.0
|
||||
lodash.isarguments: 3.1.0
|
||||
@@ -13000,6 +13105,8 @@ snapshots:
|
||||
|
||||
js-file-download@0.4.12: {}
|
||||
|
||||
js-levenshtein@1.1.6: {}
|
||||
|
||||
js-tokens@4.0.0: {}
|
||||
|
||||
js-tokens@9.0.1: {}
|
||||
@@ -13199,7 +13306,7 @@ snapshots:
|
||||
dependencies:
|
||||
chalk: 5.4.1
|
||||
commander: 13.1.0
|
||||
debug: 4.4.1
|
||||
debug: 4.4.1(supports-color@10.0.0)
|
||||
execa: 8.0.1
|
||||
lilconfig: 3.1.3
|
||||
listr2: 8.3.3
|
||||
@@ -13550,7 +13657,7 @@ snapshots:
|
||||
micromark@4.0.2:
|
||||
dependencies:
|
||||
'@types/debug': 4.1.12
|
||||
debug: 4.4.1
|
||||
debug: 4.4.1(supports-color@10.0.0)
|
||||
decode-named-character-reference: 1.1.0
|
||||
devlop: 1.1.0
|
||||
micromark-core-commonmark: 2.0.3
|
||||
@@ -13600,6 +13707,10 @@ snapshots:
|
||||
dependencies:
|
||||
brace-expansion: 1.1.11
|
||||
|
||||
minimatch@5.1.6:
|
||||
dependencies:
|
||||
brace-expansion: 2.0.1
|
||||
|
||||
minimatch@7.4.6:
|
||||
dependencies:
|
||||
brace-expansion: 2.0.1
|
||||
@@ -13841,6 +13952,10 @@ snapshots:
|
||||
dependencies:
|
||||
mimic-function: 5.0.1
|
||||
|
||||
openapi-fetch@0.14.0:
|
||||
dependencies:
|
||||
openapi-typescript-helpers: 0.0.15
|
||||
|
||||
openapi-path-templating@2.2.1:
|
||||
dependencies:
|
||||
apg-lite: 1.0.4
|
||||
@@ -13851,6 +13966,18 @@ snapshots:
|
||||
|
||||
openapi-types@12.1.3: {}
|
||||
|
||||
openapi-typescript-helpers@0.0.15: {}
|
||||
|
||||
openapi-typescript@7.8.0(typescript@5.8.3):
|
||||
dependencies:
|
||||
'@redocly/openapi-core': 1.34.3(supports-color@10.0.0)
|
||||
ansi-colors: 4.1.3
|
||||
change-case: 5.4.4
|
||||
parse-json: 8.3.0
|
||||
supports-color: 10.0.0
|
||||
typescript: 5.8.3
|
||||
yargs-parser: 21.1.1
|
||||
|
||||
otpauth@9.4.0:
|
||||
dependencies:
|
||||
'@noble/hashes': 1.7.1
|
||||
@@ -13911,6 +14038,12 @@ snapshots:
|
||||
json-parse-even-better-errors: 2.3.1
|
||||
lines-and-columns: 1.2.4
|
||||
|
||||
parse-json@8.3.0:
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.27.1
|
||||
index-to-position: 1.1.0
|
||||
type-fest: 4.41.0
|
||||
|
||||
parseley@0.12.1:
|
||||
dependencies:
|
||||
leac: 0.6.0
|
||||
@@ -14015,6 +14148,8 @@ snapshots:
|
||||
dependencies:
|
||||
queue-lit: 1.5.2
|
||||
|
||||
pluralize@8.0.0: {}
|
||||
|
||||
pngjs@5.0.0: {}
|
||||
|
||||
postcss-import@15.1.0(postcss@8.5.3):
|
||||
@@ -14466,7 +14601,7 @@ snapshots:
|
||||
|
||||
require-in-the-middle@7.5.2:
|
||||
dependencies:
|
||||
debug: 4.4.1
|
||||
debug: 4.4.1(supports-color@10.0.0)
|
||||
module-details-from-path: 1.0.4
|
||||
resolve: 1.22.10
|
||||
transitivePeerDependencies:
|
||||
@@ -14809,6 +14944,8 @@ snapshots:
|
||||
dependencies:
|
||||
copy-anything: 3.0.5
|
||||
|
||||
supports-color@10.0.0: {}
|
||||
|
||||
supports-color@7.2.0:
|
||||
dependencies:
|
||||
has-flag: 4.0.0
|
||||
@@ -15054,6 +15191,8 @@ snapshots:
|
||||
|
||||
type-fest@2.19.0: {}
|
||||
|
||||
type-fest@4.41.0: {}
|
||||
|
||||
type-is@1.6.18:
|
||||
dependencies:
|
||||
media-typer: 0.3.0
|
||||
@@ -15129,6 +15268,8 @@ snapshots:
|
||||
escalade: 3.2.0
|
||||
picocolors: 1.1.1
|
||||
|
||||
uri-js-replace@1.0.1: {}
|
||||
|
||||
url-parse@1.5.10:
|
||||
dependencies:
|
||||
querystringify: 2.2.0
|
||||
@@ -15193,7 +15334,7 @@ snapshots:
|
||||
vite-node@1.6.1(@types/node@18.19.104):
|
||||
dependencies:
|
||||
cac: 6.7.14
|
||||
debug: 4.4.1
|
||||
debug: 4.4.1(supports-color@10.0.0)
|
||||
pathe: 1.1.2
|
||||
picocolors: 1.1.1
|
||||
vite: 5.4.19(@types/node@18.19.104)
|
||||
@@ -15210,7 +15351,7 @@ snapshots:
|
||||
|
||||
vite-tsconfig-paths@4.3.2(typescript@5.8.3)(vite@5.4.19(@types/node@18.19.104)):
|
||||
dependencies:
|
||||
debug: 4.4.1
|
||||
debug: 4.4.1(supports-color@10.0.0)
|
||||
globrex: 0.1.2
|
||||
tsconfck: 3.1.6(typescript@5.8.3)
|
||||
optionalDependencies:
|
||||
@@ -15237,7 +15378,7 @@ snapshots:
|
||||
'@vitest/utils': 1.6.1
|
||||
acorn-walk: 8.3.4
|
||||
chai: 4.5.0
|
||||
debug: 4.4.1
|
||||
debug: 4.4.1(supports-color@10.0.0)
|
||||
execa: 8.0.1
|
||||
local-pkg: 0.5.1
|
||||
magic-string: 0.30.17
|
||||
@@ -15351,6 +15492,8 @@ snapshots:
|
||||
|
||||
yallist@4.0.0: {}
|
||||
|
||||
yaml-ast-parser@0.0.43: {}
|
||||
|
||||
yaml@2.8.0: {}
|
||||
|
||||
yargs-parser@18.1.3:
|
||||
|
||||
Reference in New Issue
Block a user