feat: add tunnel configuration tools
This commit is contained in:
64
app/card.tsx
64
app/card.tsx
@ -1,64 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
|
||||
export default function Card() {
|
||||
const [copied, setCopied] = useState(false)
|
||||
const command = "ssh id.tunnl.live -p 2200 -R 443:localhost:8000"
|
||||
|
||||
const copyToClipboard = () => {
|
||||
navigator.clipboard.writeText(command)
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 2000)
|
||||
}
|
||||
return (
|
||||
<div className="mb-16">
|
||||
<h2 className="text-2xl font-bold mb-6">Connect with a single command</h2>
|
||||
<div className="relative">
|
||||
<div className="bg-gray-900 rounded-lg p-4 border border-gray-800 font-mono text-sm sm:text-base overflow-x-auto">
|
||||
<pre className="whitespace-pre-wrap break-all sm:break-normal">{command}</pre>
|
||||
</div>
|
||||
<button
|
||||
onClick={copyToClipboard}
|
||||
className="absolute right-3 top-3 h-8 w-8 flex items-center justify-center rounded-md text-gray-400 hover:text-white hover:bg-gray-800"
|
||||
aria-label="Copy command"
|
||||
>
|
||||
{copied ? (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="text-emerald-400"
|
||||
>
|
||||
<path d="M20 6 9 17l-5-5" />
|
||||
</svg>
|
||||
) : (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
|
||||
<path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<p className="mt-4 text-sm text-gray-400">
|
||||
This command creates a secure tunnel from our server to your localhost:8000
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
185
app/page.tsx
185
app/page.tsx
@ -1,11 +1,23 @@
|
||||
import Card from "./card"
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import TunnelConfig, { type TunnelConfig as TunnelConfigType, type Server } from "@/components/tunnel-config"
|
||||
|
||||
const defaultConfig: TunnelConfigType = {
|
||||
type: "http",
|
||||
serverPort: 443,
|
||||
localPort: 8000,
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
const [selectedServer, setSelectedServer] = useState<Server | null>(null)
|
||||
const [tunnelConfig, setTunnelConfig] = useState<TunnelConfigType>(defaultConfig)
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col bg-gray-950 text-white">
|
||||
<main className="flex-1 flex flex-col items-center justify-center px-4">
|
||||
<div className="w-full max-w-3xl mx-auto text-center">
|
||||
<div className="mb-12">
|
||||
<main className="flex-1 flex flex-col items-center justify-center px-4 py-8">
|
||||
<div className="w-full max-w-4xl mx-auto">
|
||||
<div className="text-center mb-12">
|
||||
<h1 className="text-4xl font-bold tracking-tight sm:text-5xl md:text-6xl mb-6">
|
||||
<span className="text-emerald-400">tunnl</span>.live
|
||||
</h1>
|
||||
@ -13,85 +25,94 @@ export default function Home() {
|
||||
Expose your local services to the internet securely with our fast and reliable SSH tunneling service.
|
||||
</p>
|
||||
</div>
|
||||
<Card />
|
||||
<div className="grid gap-8 md:grid-cols-3 mb-16">
|
||||
<div className="bg-gray-900 p-6 rounded-lg border border-gray-800">
|
||||
<div className="mb-4 inline-block rounded-full bg-emerald-950 p-3">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="text-emerald-400"
|
||||
>
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20" />
|
||||
<path d="M2 12h20" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-xl font-bold mb-2">Global Network</h3>
|
||||
<p className="text-gray-400">
|
||||
We offer low-latency, high-availability servers located in Singapore for optimal performance.
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-gray-900 p-6 rounded-lg border border-gray-800">
|
||||
<div className="mb-4 inline-block rounded-full bg-emerald-950 p-3">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="text-emerald-400"
|
||||
>
|
||||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-xl font-bold mb-2">Secure by Default</h3>
|
||||
<p className="text-gray-400">
|
||||
End-to-end encryption with SSH ensures your data remains private and secure.
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-gray-900 p-6 rounded-lg border border-gray-800">
|
||||
<div className="mb-4 inline-block rounded-full bg-emerald-950 p-3">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="text-emerald-400"
|
||||
>
|
||||
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
|
||||
<polyline points="15 3 21 3 21 9" />
|
||||
<line x1="10" x2="21" y1="14" y2="3" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-xl font-bold mb-2">Easy Sharing</h3>
|
||||
<p className="text-gray-400">
|
||||
Share your local development with clients or teammates without complex setup.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center mb-8">
|
||||
<h2 className="text-2xl font-bold mb-4">100% Free Service</h2>
|
||||
<p className="text-gray-400 max-w-2xl mx-auto">
|
||||
No registration required. Just run the command and start using the tunnel immediately.
|
||||
</p>
|
||||
<TunnelConfig
|
||||
config={tunnelConfig}
|
||||
onConfigChange={setTunnelConfig}
|
||||
selectedServer={selectedServer}
|
||||
onServerSelect={setSelectedServer}
|
||||
/>
|
||||
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<div className="grid gap-8 md:grid-cols-3 mb-16">
|
||||
<div className="bg-gray-900 p-6 rounded-lg border border-gray-800">
|
||||
<div className="mb-4 inline-block rounded-full bg-emerald-950 p-3">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="text-emerald-400"
|
||||
>
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20" />
|
||||
<path d="M2 12h20" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-xl font-bold mb-2">Global Network</h3>
|
||||
<p className="text-gray-400">
|
||||
Choose from servers in US, Singapore, and Indonesia for optimal performance.
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-gray-900 p-6 rounded-lg border border-gray-800">
|
||||
<div className="mb-4 inline-block rounded-full bg-emerald-950 p-3">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="text-emerald-400"
|
||||
>
|
||||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-xl font-bold mb-2">Secure by Default</h3>
|
||||
<p className="text-gray-400">
|
||||
End-to-end encryption with SSH ensures your data remains private and secure.
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-gray-900 p-6 rounded-lg border border-gray-800">
|
||||
<div className="mb-4 inline-block rounded-full bg-emerald-950 p-3">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="text-emerald-400"
|
||||
>
|
||||
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1-2-2h6" />
|
||||
<polyline points="15 3 21 3 21 9" />
|
||||
<line x1="10" x2="21" y1="14" y2="3" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-xl font-bold mb-2">Flexible Configuration</h3>
|
||||
<p className="text-gray-400">
|
||||
Support for both HTTP/HTTPS and TCP tunneling with custom port configuration.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center mb-8">
|
||||
<h2 className="text-2xl font-bold mb-4">100% Free Service</h2>
|
||||
<p className="text-gray-400 max-w-2xl mx-auto">
|
||||
No registration required. Just run the command and start using the tunnel immediately.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
106
app/tunnel-not-found/page.tsx
Normal file
106
app/tunnel-not-found/page.tsx
Normal file
@ -0,0 +1,106 @@
|
||||
"use client"
|
||||
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
|
||||
export default function TunnelNotFound() {
|
||||
const exampleUrl = "example.com"
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col bg-gray-950 text-white">
|
||||
<main className="flex-1 flex flex-col items-center justify-center px-4 py-8">
|
||||
<div className="w-full max-w-2xl mx-auto text-center">
|
||||
{/* Mascot */}
|
||||
<div className="mb-8">
|
||||
<Image
|
||||
src="/mascot-confused.png"
|
||||
alt="Confused tunnel mascot"
|
||||
width={200}
|
||||
height={200}
|
||||
className="mx-auto"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Error Message */}
|
||||
<div className="mb-12">
|
||||
<h1 className="text-4xl font-bold mb-6">Tunnel Not Found</h1>
|
||||
<p className="text-gray-400 mb-6 text-lg">We couldn't find an active tunnel for:</p>
|
||||
<div className="bg-gray-900 rounded-lg p-4 border border-gray-800 font-mono text-emerald-400 text-xl max-w-md mx-auto mb-8">
|
||||
{exampleUrl}
|
||||
</div>
|
||||
<p className="text-gray-300 text-lg">This means no SSH tunnel is currently running for this domain.</p>
|
||||
</div>
|
||||
|
||||
{/* Instructions */}
|
||||
<div className="bg-gray-900 rounded-lg p-8 border border-gray-800 mb-8 text-left">
|
||||
<h2 className="text-xl font-bold mb-6 text-center">To create a tunnel:</h2>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-start gap-4">
|
||||
<span className="bg-emerald-600 text-white rounded-full w-8 h-8 flex items-center justify-center text-sm font-bold flex-shrink-0 mt-1">
|
||||
1
|
||||
</span>
|
||||
<div>
|
||||
<p className="text-gray-300 font-medium">Go to the main page</p>
|
||||
<p className="text-gray-400 text-sm">Configure your tunnel settings and choose a server</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-4">
|
||||
<span className="bg-emerald-600 text-white rounded-full w-8 h-8 flex items-center justify-center text-sm font-bold flex-shrink-0 mt-1">
|
||||
2
|
||||
</span>
|
||||
<div>
|
||||
<p className="text-gray-300 font-medium">Make sure your local service is running</p>
|
||||
<p className="text-gray-400 text-sm">Your application should be accessible on localhost</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-4">
|
||||
<span className="bg-emerald-600 text-white rounded-full w-8 h-8 flex items-center justify-center text-sm font-bold flex-shrink-0 mt-1">
|
||||
3
|
||||
</span>
|
||||
<div>
|
||||
<p className="text-gray-300 font-medium">Run the SSH command</p>
|
||||
<p className="text-gray-400 text-sm">Copy and paste the generated command into your terminal</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-4">
|
||||
<span className="bg-emerald-600 text-white rounded-full w-8 h-8 flex items-center justify-center text-sm font-bold flex-shrink-0 mt-1">
|
||||
4
|
||||
</span>
|
||||
<div>
|
||||
<p className="text-gray-300 font-medium">Access your tunnel</p>
|
||||
<p className="text-gray-400 text-sm">Your service will be available through the tunnel URL</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Call to Action */}
|
||||
<div className="space-y-4">
|
||||
<Link
|
||||
href="/"
|
||||
className="inline-flex items-center gap-3 bg-emerald-600 hover:bg-emerald-700 text-white px-6 py-3 rounded-lg font-medium transition-colors"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M5 12h14" />
|
||||
<path d="M12 5l7 7-7 7" />
|
||||
</svg>
|
||||
Create Your Tunnel
|
||||
</Link>
|
||||
|
||||
<p className="text-gray-400 text-sm">Need help? Check our documentation or contact support.</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
771
components/tunnel-config.tsx
Normal file
771
components/tunnel-config.tsx
Normal file
@ -0,0 +1,771 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { ComposableMap, Geographies, Geography, Marker } from "react-simple-maps"
|
||||
|
||||
export interface TunnelConfig {
|
||||
type: "http" | "tcp"
|
||||
serverPort: number
|
||||
localPort: number
|
||||
}
|
||||
|
||||
interface Server {
|
||||
id: string
|
||||
name: string
|
||||
location: string
|
||||
subdomain: string
|
||||
coordinates: [number, number]
|
||||
ping: number | null
|
||||
status: "online" | "offline" | "maintenance"
|
||||
pingStatus: "idle" | "testing" | "success" | "failed" | "timeout"
|
||||
}
|
||||
|
||||
const geoUrl = "https://cdn.jsdelivr.net/npm/world-atlas@2/countries-110m.json"
|
||||
|
||||
interface TunnelConfigProps {
|
||||
config: TunnelConfig
|
||||
onConfigChange: (config: TunnelConfig) => void
|
||||
selectedServer: Server | null
|
||||
onServerSelect: (server: Server) => void
|
||||
}
|
||||
|
||||
const fetchServers = async (): Promise<Server[]> => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000))
|
||||
|
||||
const mockServers: Server[] = [
|
||||
{
|
||||
id: "sgp",
|
||||
name: "Singapore",
|
||||
location: "Singapore",
|
||||
subdomain: "sgp.tunnl.live",
|
||||
coordinates: [103.8198, 1.3521],
|
||||
ping: null,
|
||||
status: "online",
|
||||
pingStatus: "idle",
|
||||
},
|
||||
{
|
||||
id: "id",
|
||||
name: "Indonesia",
|
||||
location: "Jakarta",
|
||||
subdomain: "id.tunnl.live",
|
||||
coordinates: [106.8456, -6.2088],
|
||||
ping: null,
|
||||
status: "online",
|
||||
pingStatus: "idle",
|
||||
},
|
||||
]
|
||||
|
||||
return mockServers.filter((server) => server.status === "online")
|
||||
}
|
||||
|
||||
const testServerPing = (
|
||||
server: Server,
|
||||
): Promise<{ server: Server; ping: number | null; status: Server["pingStatus"] }> => {
|
||||
return new Promise((resolve) => {
|
||||
const startTime = Date.now()
|
||||
const timeout = 5000
|
||||
let resolved = false
|
||||
|
||||
const pingUrl = `wss://ping.${server.subdomain}`
|
||||
|
||||
try {
|
||||
const ws = new WebSocket(pingUrl)
|
||||
|
||||
const timeoutId = setTimeout(() => {
|
||||
if (!resolved) {
|
||||
resolved = true
|
||||
ws.close()
|
||||
resolve({
|
||||
server,
|
||||
ping: null,
|
||||
status: "timeout",
|
||||
})
|
||||
}
|
||||
}, timeout)
|
||||
|
||||
ws.onopen = () => {
|
||||
console.log(`Connected to ${pingUrl}`)
|
||||
}
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
if (event.data === "pong" && !resolved) {
|
||||
resolved = true
|
||||
const ping = Date.now() - startTime
|
||||
clearTimeout(timeoutId)
|
||||
ws.close()
|
||||
|
||||
resolve({
|
||||
server,
|
||||
ping,
|
||||
status: "success",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
ws.onclose = (event) => {
|
||||
if (!resolved) {
|
||||
resolved = true
|
||||
clearTimeout(timeoutId)
|
||||
|
||||
resolve({
|
||||
server,
|
||||
ping: null,
|
||||
status: "failed",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
ws.onerror = (error) => {
|
||||
if (!resolved) {
|
||||
resolved = true
|
||||
clearTimeout(timeoutId)
|
||||
console.error(`WebSocket error for ${pingUrl}:`, error)
|
||||
|
||||
resolve({
|
||||
server,
|
||||
ping: null,
|
||||
status: "failed",
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to create WebSocket for ${pingUrl}:`, error)
|
||||
resolve({
|
||||
server,
|
||||
ping: null,
|
||||
status: "failed",
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default function TunnelConfig({ config, onConfigChange, selectedServer, onServerSelect }: TunnelConfigProps) {
|
||||
const [localConfig, setLocalConfig] = useState<TunnelConfig>(config)
|
||||
const [servers, setServers] = useState<Server[]>([])
|
||||
const [isLoadingServers, setIsLoadingServers] = useState(true)
|
||||
const [isTestingPings, setIsTestingPings] = useState(false)
|
||||
const [hasAutoTested, setHasAutoTested] = useState(false)
|
||||
const [copied, setCopied] = useState(false)
|
||||
const [serverError, setServerError] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const loadServers = async () => {
|
||||
try {
|
||||
setIsLoadingServers(true)
|
||||
setServerError(null)
|
||||
const serverData = await fetchServers()
|
||||
setServers(serverData)
|
||||
|
||||
if (serverData.length === 0) {
|
||||
setServerError("No servers are currently available. Please try again later.")
|
||||
}
|
||||
} catch (error) {
|
||||
setServerError("Failed to load servers. Please check your connection and try again.")
|
||||
} finally {
|
||||
setIsLoadingServers(false)
|
||||
}
|
||||
}
|
||||
|
||||
loadServers()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (servers.length > 0 && !isLoadingServers && !hasAutoTested) {
|
||||
const autoTestPings = async () => {
|
||||
setIsTestingPings(true)
|
||||
setHasAutoTested(true)
|
||||
|
||||
setServers((prevServers) =>
|
||||
prevServers.map((server) => ({
|
||||
...server,
|
||||
pingStatus: "testing" as const,
|
||||
})),
|
||||
)
|
||||
|
||||
try {
|
||||
const pingPromises = servers.map((server) => testServerPing(server))
|
||||
const results = await Promise.all(pingPromises)
|
||||
|
||||
const updatedServers = results.map((result) => ({
|
||||
...result.server,
|
||||
ping: result.ping,
|
||||
pingStatus: result.status,
|
||||
}))
|
||||
|
||||
setServers(updatedServers)
|
||||
|
||||
const successfulServers = updatedServers.filter((s) => s.pingStatus === "success" && s.ping !== null)
|
||||
if (successfulServers.length > 0) {
|
||||
const bestServer = successfulServers.reduce((prev, current) =>
|
||||
prev.ping! < current.ping! ? prev : current,
|
||||
)
|
||||
onServerSelect(bestServer)
|
||||
} else if (updatedServers.length > 0) {
|
||||
onServerSelect(updatedServers[0])
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error testing pings:", error)
|
||||
} finally {
|
||||
setIsTestingPings(false)
|
||||
}
|
||||
}
|
||||
|
||||
autoTestPings()
|
||||
}
|
||||
}, [servers.length, isLoadingServers, hasAutoTested, onServerSelect])
|
||||
|
||||
const updateConfig = (updates: Partial<TunnelConfig>) => {
|
||||
const newConfig = { ...localConfig, ...updates }
|
||||
|
||||
if (updates.serverPort && (updates.serverPort === 80 || updates.serverPort === 443)) {
|
||||
newConfig.type = "http"
|
||||
}
|
||||
|
||||
setLocalConfig(newConfig)
|
||||
onConfigChange(newConfig)
|
||||
}
|
||||
|
||||
const generateCommand = () => {
|
||||
if (!selectedServer) return ""
|
||||
const { serverPort, localPort } = localConfig
|
||||
return `ssh ${selectedServer.subdomain} -p 2200 -R ${serverPort}:localhost:${localPort}`
|
||||
}
|
||||
|
||||
const copyToClipboard = () => {
|
||||
const command = generateCommand()
|
||||
if (command) {
|
||||
navigator.clipboard.writeText(command)
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 2000)
|
||||
}
|
||||
}
|
||||
|
||||
const getPingColor = (server: Server) => {
|
||||
if (server.pingStatus === "testing") return "text-gray-400"
|
||||
if (server.pingStatus === "failed" || server.pingStatus === "timeout") return "text-red-400"
|
||||
if (server.pingStatus === "idle" || !server.ping) return "text-gray-400"
|
||||
if (server.ping < 50) return "text-green-400"
|
||||
if (server.ping < 100) return "text-yellow-400"
|
||||
if (server.ping < 150) return "text-orange-400"
|
||||
return "text-red-400"
|
||||
}
|
||||
|
||||
const getPingDisplay = (server: Server) => {
|
||||
if (server.pingStatus === "testing") return "Testing..."
|
||||
if (server.pingStatus === "timeout") return "Timeout"
|
||||
if (server.pingStatus === "failed") return "Failed"
|
||||
if (server.pingStatus === "idle") return "Click to test"
|
||||
if (server.ping === null) return "N/A"
|
||||
return `${server.ping}ms`
|
||||
}
|
||||
|
||||
const getPingStatus = (server: Server) => {
|
||||
if (server.pingStatus === "testing") return "Testing..."
|
||||
if (server.pingStatus === "timeout") return "Timeout (5s)"
|
||||
if (server.pingStatus === "failed") return "Connection Failed"
|
||||
if (server.pingStatus === "idle") return "Not tested"
|
||||
if (!server.ping) return "Unknown"
|
||||
if (server.ping < 50) return "Excellent"
|
||||
if (server.ping < 100) return "Good"
|
||||
if (server.ping < 150) return "Fair"
|
||||
return "Poor"
|
||||
}
|
||||
|
||||
const getMarkerColor = (server: Server) => {
|
||||
if (selectedServer?.id === server.id) return "#10b981"
|
||||
if (server.pingStatus === "failed" || server.pingStatus === "timeout") return "#ef4444"
|
||||
if (server.pingStatus === "success" && server.ping !== null) {
|
||||
if (server.ping < 50) return "#10b981"
|
||||
if (server.ping < 100) return "#eab308"
|
||||
if (server.ping < 150) return "#f97316"
|
||||
return "#ef4444"
|
||||
}
|
||||
return "#6b7280"
|
||||
}
|
||||
|
||||
const getMarkerStroke = (server: Server) => {
|
||||
if (selectedServer?.id === server.id) return "#34d399"
|
||||
if (server.pingStatus === "failed" || server.pingStatus === "timeout") return "#f87171"
|
||||
if (server.pingStatus === "success" && server.ping !== null) {
|
||||
if (server.ping < 50) return "#34d399"
|
||||
if (server.ping < 100) return "#facc15"
|
||||
if (server.ping < 150) return "#fb923c"
|
||||
return "#f87171"
|
||||
}
|
||||
return "#9ca3af"
|
||||
}
|
||||
|
||||
const testPingForServer = async (server: Server) => {
|
||||
setServers((prevServers) =>
|
||||
prevServers.map((s) => (s.id === server.id ? { ...s, pingStatus: "testing", ping: null } : s)),
|
||||
)
|
||||
|
||||
try {
|
||||
const timeoutPromise = new Promise<{ server: Server; ping: number | null; status: Server["pingStatus"] }>(
|
||||
(_, reject) => {
|
||||
setTimeout(() => reject(new Error("Timeout")), 5000)
|
||||
},
|
||||
)
|
||||
|
||||
const result = await Promise.race([testServerPing(server), timeoutPromise])
|
||||
|
||||
setServers((prevServers) =>
|
||||
prevServers.map((s) => (s.id === server.id ? { ...s, ping: result.ping, pingStatus: result.status } : s)),
|
||||
)
|
||||
} catch (error) {
|
||||
console.error("Error testing ping:", error)
|
||||
setServers((prevServers) =>
|
||||
prevServers.map((s) => (s.id === server.id ? { ...s, ping: null, pingStatus: "timeout" } : s)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const testAllPings = async () => {
|
||||
if (servers.length === 0 || isTestingPings) return
|
||||
|
||||
setIsTestingPings(true)
|
||||
|
||||
setServers((prevServers) =>
|
||||
prevServers.map((server) => ({
|
||||
...server,
|
||||
ping: null,
|
||||
pingStatus: "testing" as const,
|
||||
})),
|
||||
)
|
||||
|
||||
try {
|
||||
const pingPromises = servers.map((server) => testServerPing(server))
|
||||
const results = await Promise.all(pingPromises)
|
||||
|
||||
const updatedServers = results.map((result) => ({
|
||||
...result.server,
|
||||
ping: result.ping,
|
||||
pingStatus: result.status,
|
||||
}))
|
||||
|
||||
setServers(updatedServers)
|
||||
} catch (error) {
|
||||
console.error("Error testing pings:", error)
|
||||
} finally {
|
||||
setIsTestingPings(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-gray-900 rounded-lg border border-gray-800 p-6 mb-8">
|
||||
<h3 className="text-lg font-bold mb-6">Tunnel Configuration</h3>
|
||||
|
||||
<div className="mb-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h4 className="text-md font-medium">Choose Your Server Location</h4>
|
||||
{servers.length > 0 && hasAutoTested && (
|
||||
<button
|
||||
onClick={testAllPings}
|
||||
disabled={isTestingPings}
|
||||
className="text-sm text-emerald-400 hover:text-emerald-300 disabled:text-gray-500 disabled:cursor-not-allowed flex items-center gap-1"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={isTestingPings ? "animate-spin" : ""}
|
||||
>
|
||||
<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" />
|
||||
<path d="M21 3v5h-5" />
|
||||
<path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" />
|
||||
<path d="M3 21v-5h5" />
|
||||
</svg>
|
||||
{isTestingPings ? "Testing All..." : "Test All Pings"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isLoadingServers ? (
|
||||
<div className="bg-gray-800 rounded-lg border border-gray-700 p-8 text-center">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-emerald-400 mx-auto mb-4"></div>
|
||||
<p className="text-gray-400">Loading available servers...</p>
|
||||
</div>
|
||||
) : serverError ? (
|
||||
<div className="bg-red-950 rounded-lg border border-red-800 p-6 text-center">
|
||||
<div className="mb-4">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="48"
|
||||
height="48"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="text-red-400 mx-auto"
|
||||
>
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<line x1="15" x2="9" y1="9" y2="15" />
|
||||
<line x1="9" x2="15" y1="9" y2="15" />
|
||||
</svg>
|
||||
</div>
|
||||
<p className="text-red-400 font-medium mb-2">Server Unavailable</p>
|
||||
<p className="text-red-300 text-sm">{serverError}</p>
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
className="mt-4 px-4 py-2 bg-red-800 hover:bg-red-700 text-white rounded-lg text-sm transition-colors"
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
) : servers.length === 0 ? (
|
||||
<div className="bg-yellow-950 rounded-lg border border-yellow-800 p-6 text-center">
|
||||
<div className="mb-4">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="48"
|
||||
height="48"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="text-yellow-400 mx-auto"
|
||||
>
|
||||
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
|
||||
<line x1="12" x2="12" y1="9" y2="13" />
|
||||
<line x1="12" x2="12.01" y1="17" y2="17" />
|
||||
</svg>
|
||||
</div>
|
||||
<p className="text-yellow-400 font-medium mb-2">No Servers Available</p>
|
||||
<p className="text-yellow-300 text-sm">All servers are currently offline for maintenance.</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="bg-gray-800 rounded-lg border border-gray-700 p-3 mb-4">
|
||||
<ComposableMap
|
||||
projection="geoMercator"
|
||||
projectionConfig={{
|
||||
scale: 100,
|
||||
center: [0, 20],
|
||||
}}
|
||||
width={600}
|
||||
height={250}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "auto",
|
||||
}}
|
||||
>
|
||||
<Geographies geography={geoUrl}>
|
||||
{({ geographies }) =>
|
||||
geographies.map((geo) => (
|
||||
<Geography
|
||||
key={geo.rsmKey}
|
||||
geography={geo}
|
||||
fill="#374151"
|
||||
stroke="#4b5563"
|
||||
strokeWidth={0.5}
|
||||
style={{
|
||||
default: { outline: "none" },
|
||||
hover: { outline: "none", fill: "#4b5563" },
|
||||
pressed: { outline: "none" },
|
||||
}}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</Geographies>
|
||||
|
||||
{servers.map((server) => (
|
||||
<Marker
|
||||
key={server.id}
|
||||
coordinates={server.coordinates}
|
||||
onClick={() => {
|
||||
if (server.pingStatus === "failed" || server.pingStatus === "timeout") {
|
||||
return
|
||||
}
|
||||
onServerSelect(server)
|
||||
}}
|
||||
>
|
||||
<g>
|
||||
{selectedServer?.id === server.id && (
|
||||
<circle r="12" fill="none" stroke="#10b981" strokeWidth="2" opacity="0.6">
|
||||
<animate attributeName="r" values="6;15;6" dur="2s" repeatCount="indefinite" />
|
||||
<animate attributeName="opacity" values="0.6;0;0.6" dur="2s" repeatCount="indefinite" />
|
||||
</circle>
|
||||
)}
|
||||
<circle r="6" fill={getMarkerColor(server)} stroke={getMarkerStroke(server)} strokeWidth="2" />
|
||||
<text
|
||||
textAnchor="middle"
|
||||
y="-12"
|
||||
style={{
|
||||
fontFamily: "system-ui",
|
||||
fontSize: "10px",
|
||||
fontWeight: "bold",
|
||||
fill: "white",
|
||||
pointerEvents: "none",
|
||||
}}
|
||||
>
|
||||
{server.location}
|
||||
</text>
|
||||
</g>
|
||||
</Marker>
|
||||
))}
|
||||
</ComposableMap>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-3 md:grid-cols-3">
|
||||
{servers.map((server) => (
|
||||
<div
|
||||
key={server.id}
|
||||
onClick={() => {
|
||||
if (server.pingStatus === "failed" || server.pingStatus === "timeout") {
|
||||
return
|
||||
}
|
||||
onServerSelect(server)
|
||||
}}
|
||||
className={`p-3 rounded-lg border transition-all duration-200 ${selectedServer?.id === server.id
|
||||
? "bg-emerald-950 border-emerald-500"
|
||||
: server.pingStatus === "failed" || server.pingStatus === "timeout"
|
||||
? "bg-red-950 border-red-800 cursor-not-allowed opacity-75"
|
||||
: "bg-gray-800 border-gray-700 hover:border-gray-600 cursor-pointer"
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<h5 className="font-medium text-sm">{server.name}</h5>
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full ${selectedServer?.id === server.id
|
||||
? "bg-emerald-400"
|
||||
: server.pingStatus === "failed" || server.pingStatus === "timeout"
|
||||
? "bg-red-400"
|
||||
: server.pingStatus === "success" && server.ping !== null
|
||||
? server.ping < 50
|
||||
? "bg-green-400"
|
||||
: server.ping < 100
|
||||
? "bg-yellow-400"
|
||||
: server.ping < 150
|
||||
? "bg-orange-400"
|
||||
: "bg-red-400"
|
||||
: "bg-gray-600"
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-gray-400 mb-1">{server.location}</p>
|
||||
<p className="text-xs font-mono text-gray-500 mb-2">{server.subdomain}</p>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs">Ping:</span>
|
||||
<div className="flex items-center gap-1">
|
||||
{server.pingStatus === "testing" && (
|
||||
<div className="animate-spin rounded-full h-3 w-3 border border-gray-400 border-t-transparent"></div>
|
||||
)}
|
||||
{hasAutoTested ? (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
if (server.pingStatus !== "testing") {
|
||||
testPingForServer(server)
|
||||
}
|
||||
}}
|
||||
disabled={server.pingStatus === "testing"}
|
||||
className={`text-xs font-bold hover:underline disabled:no-underline disabled:cursor-not-allowed ${getPingColor(
|
||||
server,
|
||||
)}`}
|
||||
>
|
||||
{getPingDisplay(server)}
|
||||
</button>
|
||||
) : (
|
||||
<span className={`text-xs font-bold ${getPingColor(server)}`}>{getPingDisplay(server)}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-1">{getPingStatus(server)}</p>
|
||||
{(server.pingStatus === "failed" || server.pingStatus === "timeout") && (
|
||||
<p className="text-xs text-red-400 mt-1 font-medium">Cannot select - Server unavailable</p>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{servers.length > 0 && (
|
||||
<>
|
||||
<div className="mb-6">
|
||||
<label className="block text-sm font-medium mb-3">Forwarding Type</label>
|
||||
<div className="flex gap-4">
|
||||
<label className="flex items-center cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
name="forwardingType"
|
||||
value="http"
|
||||
checked={localConfig.type === "http"}
|
||||
onChange={() => updateConfig({ type: "http", serverPort: 443 })}
|
||||
className="sr-only"
|
||||
/>
|
||||
<div
|
||||
className={`flex items-center gap-2 px-4 py-2 rounded-lg border transition-all ${localConfig.type === "http"
|
||||
? "bg-emerald-950 border-emerald-500 text-emerald-400"
|
||||
: "bg-gray-800 border-gray-700 text-gray-300 hover:border-gray-600"
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full ${localConfig.type === "http" ? "bg-emerald-400" : "bg-gray-500"}`}
|
||||
/>
|
||||
<span className="font-medium">HTTP/HTTPS</span>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
name="forwardingType"
|
||||
value="tcp"
|
||||
checked={localConfig.type === "tcp"}
|
||||
onChange={() => updateConfig({ type: "tcp", serverPort: 8080 })}
|
||||
className="sr-only"
|
||||
/>
|
||||
<div
|
||||
className={`flex items-center gap-2 px-4 py-2 rounded-lg border transition-all ${localConfig.type === "tcp"
|
||||
? "bg-emerald-950 border-emerald-500 text-emerald-400"
|
||||
: "bg-gray-800 border-gray-700 text-gray-300 hover:border-gray-600"
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full ${localConfig.type === "tcp" ? "bg-emerald-400" : "bg-gray-500"}`}
|
||||
/>
|
||||
<span className="font-medium">TCP</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-gray-400 mt-2">
|
||||
{localConfig.type === "http"
|
||||
? "Best for web applications and APIs. Uses HTTPS (port 443) or HTTP (port 80)."
|
||||
: "For any TCP service like databases, game servers, or custom applications."}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-2 mb-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2">Server Port (Internet Access)</label>
|
||||
{localConfig.type === "http" ? (
|
||||
<select
|
||||
value={localConfig.serverPort}
|
||||
onChange={(e) => updateConfig({ serverPort: Number.parseInt(e.target.value) })}
|
||||
className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white font-mono focus:border-emerald-500 focus:outline-none"
|
||||
>
|
||||
<option value={443}>443 (HTTPS)</option>
|
||||
<option value={80}>80 (HTTP)</option>
|
||||
</select>
|
||||
) : (
|
||||
<input
|
||||
type="number"
|
||||
value={localConfig.serverPort}
|
||||
onChange={(e) => updateConfig({ serverPort: Number.parseInt(e.target.value) || 8080 })}
|
||||
className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white font-mono focus:border-emerald-500 focus:outline-none"
|
||||
placeholder="8080"
|
||||
min="1024"
|
||||
max="65535"
|
||||
/>
|
||||
)}
|
||||
<p className="text-xs text-gray-400 mt-1">
|
||||
{localConfig.type === "http" ? "Standard web ports" : "Port accessible from the internet"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2">Local Port (Your Service)</label>
|
||||
<input
|
||||
type="number"
|
||||
value={localConfig.localPort}
|
||||
onChange={(e) => updateConfig({ localPort: Number.parseInt(e.target.value) || 8000 })}
|
||||
className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white font-mono focus:border-emerald-500 focus:outline-none"
|
||||
placeholder="8000"
|
||||
min="1"
|
||||
max="65535"
|
||||
/>
|
||||
<p className="text-xs text-gray-400 mt-1">Port where your local service is running</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedServer && (
|
||||
<div className="mb-6">
|
||||
<label className="block text-sm font-medium mb-2">SSH Command</label>
|
||||
<div className="relative">
|
||||
<div className="bg-gray-800 rounded-lg p-4 border border-gray-700 font-mono text-sm overflow-x-auto">
|
||||
<pre className="whitespace-pre-wrap break-all sm:break-normal">{generateCommand()}</pre>
|
||||
</div>
|
||||
<button
|
||||
onClick={copyToClipboard}
|
||||
className="absolute right-3 top-3 h-8 w-8 flex items-center justify-center rounded-md text-gray-400 hover:text-white hover:bg-gray-700 transition-colors"
|
||||
aria-label="Copy command"
|
||||
>
|
||||
{copied ? (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="text-emerald-400"
|
||||
>
|
||||
<path d="M20 6 9 17l-5-5" />
|
||||
</svg>
|
||||
) : (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
|
||||
<path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-xs text-gray-400 mt-2">Run this command in your terminal to create the tunnel</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="p-3 bg-gray-800 rounded-lg border border-gray-700">
|
||||
<p className="text-sm text-gray-300">
|
||||
<span className="font-medium">Traffic Flow:</span> Internet →{" "}
|
||||
<span className="text-emerald-400 font-mono">
|
||||
{selectedServer ? selectedServer.location : "Server"}:{localConfig.serverPort}
|
||||
</span>{" "}
|
||||
→ <span className="text-emerald-400 font-mono">localhost:{localConfig.localPort}</span>
|
||||
</p>
|
||||
<p className="text-xs text-gray-400 mt-1">
|
||||
{localConfig.type === "http" ? (
|
||||
<>
|
||||
Your local service on port {localConfig.localPort} will be accessible via{" "}
|
||||
{localConfig.serverPort === 443 ? "HTTPS" : "HTTP"}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
TCP traffic to server port {localConfig.serverPort} will be forwarded to your localhost:
|
||||
{localConfig.localPort}
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export type { Server }
|
183
components/world-map.tsx
Normal file
183
components/world-map.tsx
Normal file
@ -0,0 +1,183 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { ComposableMap, Geographies, Geography, Marker } from "react-simple-maps"
|
||||
|
||||
interface Server {
|
||||
id: string
|
||||
name: string
|
||||
location: string
|
||||
subdomain: string
|
||||
coordinates: [number, number] // [longitude, latitude]
|
||||
ping: number | null
|
||||
}
|
||||
|
||||
const servers: Server[] = [
|
||||
{
|
||||
id: "sgp",
|
||||
name: "Singapore",
|
||||
location: "Singapore",
|
||||
subdomain: "sgp.tunnl.live",
|
||||
coordinates: [103.8198, 1.3521],
|
||||
ping: null,
|
||||
},
|
||||
{
|
||||
id: "id",
|
||||
name: "Indonesia",
|
||||
location: "Jakarta",
|
||||
subdomain: "id.tunnl.live",
|
||||
coordinates: [106.8456, -6.2088],
|
||||
ping: null,
|
||||
},
|
||||
]
|
||||
|
||||
const geoUrl = "https://cdn.jsdelivr.net/npm/world-atlas@2/countries-110m.json"
|
||||
|
||||
interface WorldMapProps {
|
||||
onServerSelect: (server: Server) => void
|
||||
selectedServer: Server
|
||||
}
|
||||
|
||||
export default function WorldMap({ onServerSelect, selectedServer }: WorldMapProps) {
|
||||
const [serverPings, setServerPings] = useState<Server[]>(servers)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
|
||||
const getPingColor = (ping: number | null) => {
|
||||
if (!ping) return "text-gray-400"
|
||||
if (ping < 50) return "text-green-400"
|
||||
if (ping < 100) return "text-yellow-400"
|
||||
if (ping < 150) return "text-orange-400"
|
||||
return "text-red-400"
|
||||
}
|
||||
|
||||
const getPingStatus = (ping: number | null) => {
|
||||
if (!ping) return "Testing..."
|
||||
if (ping < 50) return "Excellent"
|
||||
if (ping < 100) return "Good"
|
||||
if (ping < 150) return "Fair"
|
||||
return "Poor"
|
||||
}
|
||||
|
||||
const getMarkerColor = (server: Server) => {
|
||||
if (selectedServer.id === server.id) return "#10b981"
|
||||
return "#6b7280"
|
||||
}
|
||||
|
||||
const getMarkerStroke = (server: Server) => {
|
||||
if (selectedServer.id === server.id) return "#34d399"
|
||||
return "#9ca3af"
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<h3 className="text-xl font-bold text-center mb-6">Choose Your Server Location</h3>
|
||||
|
||||
<div className="relative bg-gray-900 rounded-lg border border-gray-800 p-4 mb-6 overflow-hidden">
|
||||
<ComposableMap
|
||||
projection="geoMercator"
|
||||
projectionConfig={{
|
||||
scale: 120,
|
||||
center: [0, 20],
|
||||
}}
|
||||
width={800}
|
||||
height={400}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "auto",
|
||||
}}
|
||||
>
|
||||
<Geographies geography={geoUrl}>
|
||||
{({ geographies }) =>
|
||||
geographies.map((geo) => (
|
||||
<Geography
|
||||
key={geo.rsmKey}
|
||||
geography={geo}
|
||||
fill="#374151"
|
||||
stroke="#4b5563"
|
||||
strokeWidth={0.5}
|
||||
style={{
|
||||
default: { outline: "none" },
|
||||
hover: { outline: "none", fill: "#4b5563" },
|
||||
pressed: { outline: "none" },
|
||||
}}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</Geographies>
|
||||
|
||||
{serverPings.map((server) => (
|
||||
<Marker
|
||||
key={server.id}
|
||||
coordinates={server.coordinates}
|
||||
onClick={() => onServerSelect(server)}
|
||||
>
|
||||
<g>
|
||||
{selectedServer.id === server.id && (
|
||||
<circle r="15" fill="none" stroke="#10b981" strokeWidth="2" opacity="0.6">
|
||||
<animate attributeName="r" values="8;20;8" dur="2s" repeatCount="indefinite" />
|
||||
<animate attributeName="opacity" values="0.6;0;0.6" dur="2s" repeatCount="indefinite" />
|
||||
</circle>
|
||||
)}
|
||||
|
||||
<circle
|
||||
r="8"
|
||||
fill={getMarkerColor(server)}
|
||||
stroke={getMarkerStroke(server)}
|
||||
strokeWidth="2"
|
||||
className="transition-all duration-200 hover:r-10"
|
||||
/>
|
||||
|
||||
<text
|
||||
textAnchor="middle"
|
||||
y="-15"
|
||||
style={{
|
||||
fontFamily: "system-ui",
|
||||
fontSize: "12px",
|
||||
fontWeight: "bold",
|
||||
fill: "white",
|
||||
pointerEvents: "none",
|
||||
}}
|
||||
>
|
||||
{server.location}
|
||||
</text>
|
||||
</g>
|
||||
</Marker>
|
||||
))}
|
||||
</ComposableMap>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
{serverPings.map((server) => (
|
||||
<div
|
||||
key={server.id}
|
||||
onClick={() => onServerSelect(server)}
|
||||
className={`p-4 rounded-lg border cursor-pointer transition-all duration-200 ${
|
||||
selectedServer.id === server.id
|
||||
? "bg-emerald-950 border-emerald-500"
|
||||
: "bg-gray-900 border-gray-800 hover:border-gray-700"
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h4 className="font-bold">{server.name}</h4>
|
||||
<div
|
||||
className={`w-3 h-3 rounded-full ${selectedServer.id === server.id ? "bg-emerald-400" : "bg-gray-600"}`}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-sm text-gray-400 mb-2">{server.location}</p>
|
||||
<p className="text-xs font-mono text-gray-500 mb-2">{server.subdomain}</p>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm">Ping:</span>
|
||||
{isLoading ? (
|
||||
<span className="text-sm text-gray-400">Testing...</span>
|
||||
) : (
|
||||
<span className={`text-sm font-bold ${getPingColor(server.ping)}`}>
|
||||
{server.ping}ms ({getPingStatus(server.ping)})
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
639
package-lock.json
generated
639
package-lock.json
generated
@ -8,9 +8,10 @@
|
||||
"name": "tunnlpls_frontend",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"next": "15.3.1",
|
||||
"next": "^15.5.2",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-simple-maps": "^3.0.0",
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -18,6 +19,7 @@
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"@types/react-simple-maps": "^3.0.6",
|
||||
"tailwindcss": "^4",
|
||||
"typescript": "^5"
|
||||
}
|
||||
@ -36,9 +38,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz",
|
||||
"integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz",
|
||||
"integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
@ -46,9 +48,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-arm64": {
|
||||
"version": "0.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.1.tgz",
|
||||
"integrity": "sha512-pn44xgBtgpEbZsu+lWf2KNb6OAf70X68k+yk69Ic2Xz11zHR/w24/U49XT7AeRwJ0Px+mhALhU5LPci1Aymk7A==",
|
||||
"version": "0.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.3.tgz",
|
||||
"integrity": "sha512-ryFMfvxxpQRsgZJqBd4wsttYQbCxsJksrv9Lw/v798JcQ8+w84mBWuXwl+TT0WJ/WrYOLaYpwQXi3sA9nTIaIg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -64,13 +66,13 @@
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-arm64": "1.1.0"
|
||||
"@img/sharp-libvips-darwin-arm64": "1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-x64": {
|
||||
"version": "0.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.1.tgz",
|
||||
"integrity": "sha512-VfuYgG2r8BpYiOUN+BfYeFo69nP/MIwAtSJ7/Zpxc5QF3KS22z8Pvg3FkrSFJBPNQ7mmcUcYQFBmEQp7eu1F8Q==",
|
||||
"version": "0.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.3.tgz",
|
||||
"integrity": "sha512-yHpJYynROAj12TA6qil58hmPmAwxKKC7reUqtGLzsOHfP7/rniNGTL8tjWX6L3CTV4+5P4ypcS7Pp+7OB+8ihA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -86,13 +88,13 @@
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-x64": "1.1.0"
|
||||
"@img/sharp-libvips-darwin-x64": "1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-darwin-arm64": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.1.0.tgz",
|
||||
"integrity": "sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.0.tgz",
|
||||
"integrity": "sha512-sBZmpwmxqwlqG9ueWFXtockhsxefaV6O84BMOrhtg/YqbTaRdqDE7hxraVE3y6gVM4eExmfzW4a8el9ArLeEiQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -106,9 +108,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-darwin-x64": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.1.0.tgz",
|
||||
"integrity": "sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.0.tgz",
|
||||
"integrity": "sha512-M64XVuL94OgiNHa5/m2YvEQI5q2cl9d/wk0qFTDVXcYzi43lxuiFTftMR1tOnFQovVXNZJ5TURSDK2pNe9Yzqg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -122,9 +124,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-arm": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.1.0.tgz",
|
||||
"integrity": "sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.0.tgz",
|
||||
"integrity": "sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@ -138,9 +140,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-arm64": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.1.0.tgz",
|
||||
"integrity": "sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.0.tgz",
|
||||
"integrity": "sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -154,9 +156,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-ppc64": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.1.0.tgz",
|
||||
"integrity": "sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.0.tgz",
|
||||
"integrity": "sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@ -170,9 +172,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-s390x": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.1.0.tgz",
|
||||
"integrity": "sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.0.tgz",
|
||||
"integrity": "sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@ -186,9 +188,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-x64": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.1.0.tgz",
|
||||
"integrity": "sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.0.tgz",
|
||||
"integrity": "sha512-ZW3FPWIc7K1sH9E3nxIGB3y3dZkpJlMnkk7z5tu1nSkBoCgw2nSRTFHI5pB/3CQaJM0pdzMF3paf9ckKMSE9Tg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -202,9 +204,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.1.0.tgz",
|
||||
"integrity": "sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.0.tgz",
|
||||
"integrity": "sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -218,9 +220,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.1.0.tgz",
|
||||
"integrity": "sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.0.tgz",
|
||||
"integrity": "sha512-SRYOLR7CXPgNze8akZwjoGBoN1ThNZoqpOgfnOxmWsklTGVfJiGJoC/Lod7aNMGA1jSsKWM1+HRX43OP6p9+6Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -234,9 +236,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-arm": {
|
||||
"version": "0.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.1.tgz",
|
||||
"integrity": "sha512-anKiszvACti2sGy9CirTlNyk7BjjZPiML1jt2ZkTdcvpLU1YH6CXwRAZCA2UmRXnhiIftXQ7+Oh62Ji25W72jA==",
|
||||
"version": "0.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.3.tgz",
|
||||
"integrity": "sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@ -252,13 +254,13 @@
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm": "1.1.0"
|
||||
"@img/sharp-libvips-linux-arm": "1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-arm64": {
|
||||
"version": "0.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.1.tgz",
|
||||
"integrity": "sha512-kX2c+vbvaXC6vly1RDf/IWNXxrlxLNpBVWkdpRq5Ka7OOKj6nr66etKy2IENf6FtOgklkg9ZdGpEu9kwdlcwOQ==",
|
||||
"version": "0.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.3.tgz",
|
||||
"integrity": "sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -274,13 +276,35 @@
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm64": "1.1.0"
|
||||
"@img/sharp-libvips-linux-arm64": "1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-ppc64": {
|
||||
"version": "0.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.3.tgz",
|
||||
"integrity": "sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-ppc64": "1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-s390x": {
|
||||
"version": "0.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.1.tgz",
|
||||
"integrity": "sha512-7s0KX2tI9mZI2buRipKIw2X1ufdTeaRgwmRabt5bi9chYfhur+/C1OXg3TKg/eag1W+6CCWLVmSauV1owmRPxA==",
|
||||
"version": "0.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.3.tgz",
|
||||
"integrity": "sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@ -296,13 +320,13 @@
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-s390x": "1.1.0"
|
||||
"@img/sharp-libvips-linux-s390x": "1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-x64": {
|
||||
"version": "0.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.1.tgz",
|
||||
"integrity": "sha512-wExv7SH9nmoBW3Wr2gvQopX1k8q2g5V5Iag8Zk6AVENsjwd+3adjwxtp3Dcu2QhOXr8W9NusBU6XcQUohBZ5MA==",
|
||||
"version": "0.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.3.tgz",
|
||||
"integrity": "sha512-8kYso8d806ypnSq3/Ly0QEw90V5ZoHh10yH0HnrzOCr6DKAPI6QVHvwleqMkVQ0m+fc7EH8ah0BB0QPuWY6zJQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -318,13 +342,13 @@
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-x64": "1.1.0"
|
||||
"@img/sharp-libvips-linux-x64": "1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linuxmusl-arm64": {
|
||||
"version": "0.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.1.tgz",
|
||||
"integrity": "sha512-DfvyxzHxw4WGdPiTF0SOHnm11Xv4aQexvqhRDAoD00MzHekAj9a/jADXeXYCDFH/DzYruwHbXU7uz+H+nWmSOQ==",
|
||||
"version": "0.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.3.tgz",
|
||||
"integrity": "sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -340,13 +364,13 @@
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.1.0"
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linuxmusl-x64": {
|
||||
"version": "0.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.1.tgz",
|
||||
"integrity": "sha512-pax/kTR407vNb9qaSIiWVnQplPcGU8LRIJpDT5o8PdAx5aAA7AS3X9PS8Isw1/WfqgQorPotjrZL3Pqh6C5EBg==",
|
||||
"version": "0.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.3.tgz",
|
||||
"integrity": "sha512-gCWUn9547K5bwvOn9l5XGAEjVTTRji4aPTqLzGXHvIr6bIDZKNTA34seMPgM0WmSf+RYBH411VavCejp3PkOeQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -362,20 +386,20 @@
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.1.0"
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-wasm32": {
|
||||
"version": "0.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.1.tgz",
|
||||
"integrity": "sha512-YDybQnYrLQfEpzGOQe7OKcyLUCML4YOXl428gOOzBgN6Gw0rv8dpsJ7PqTHxBnXnwXr8S1mYFSLSa727tpz0xg==",
|
||||
"version": "0.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.3.tgz",
|
||||
"integrity": "sha512-+CyRcpagHMGteySaWos8IbnXcHgfDn7pO2fiC2slJxvNq9gDipYBN42/RagzctVRKgxATmfqOSulgZv5e1RdMg==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/runtime": "^1.4.0"
|
||||
"@emnapi/runtime": "^1.4.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
@ -384,10 +408,29 @@
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-arm64": {
|
||||
"version": "0.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.3.tgz",
|
||||
"integrity": "sha512-MjnHPnbqMXNC2UgeLJtX4XqoVHHlZNd+nPt1kRPmj63wURegwBhZlApELdtxM2OIZDRv/DFtLcNhVbd1z8GYXQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-ia32": {
|
||||
"version": "0.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.1.tgz",
|
||||
"integrity": "sha512-WKf/NAZITnonBf3U1LfdjoMgNO5JYRSlhovhRhMxXVdvWYveM4kM3L8m35onYIdh75cOMCo1BexgVQcCDzyoWw==",
|
||||
"version": "0.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.3.tgz",
|
||||
"integrity": "sha512-xuCdhH44WxuXgOM714hn4amodJMZl3OEvf0GVTm0BEyMeA2to+8HEdRPShH0SLYptJY1uBw+SCFP9WVQi1Q/cw==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@ -404,9 +447,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-x64": {
|
||||
"version": "0.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.1.tgz",
|
||||
"integrity": "sha512-hw1iIAHpNE8q3uMIRCgGOeDoz9KtFNarFLQclLxr/LK1VBkj8nby18RjFvr6aP7USRYAjTZW6yisnBWMX571Tw==",
|
||||
"version": "0.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.3.tgz",
|
||||
"integrity": "sha512-OWwz05d++TxzLEv4VnsTz5CmZ6mI6S05sfQGEMrNrQcOEERbX46332IvE7pO/EUiw7jUrrS40z/M7kPyjfl04g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -423,15 +466,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/env": {
|
||||
"version": "15.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.3.1.tgz",
|
||||
"integrity": "sha512-cwK27QdzrMblHSn9DZRV+DQscHXRuJv6MydlJRpFSqJWZrTYMLzKDeyueJNN9MGd8NNiUKzDQADAf+dMLXX7YQ==",
|
||||
"version": "15.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.2.tgz",
|
||||
"integrity": "sha512-Qe06ew4zt12LeO6N7j8/nULSOe3fMXE4dM6xgpBQNvdzyK1sv5y4oAP3bq4LamrvGCZtmRYnW8URFCeX5nFgGg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@next/swc-darwin-arm64": {
|
||||
"version": "15.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.3.1.tgz",
|
||||
"integrity": "sha512-hjDw4f4/nla+6wysBL07z52Gs55Gttp5Bsk5/8AncQLJoisvTBP0pRIBK/B16/KqQyH+uN4Ww8KkcAqJODYH3w==",
|
||||
"version": "15.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.2.tgz",
|
||||
"integrity": "sha512-8bGt577BXGSd4iqFygmzIfTYizHb0LGWqH+qgIF/2EDxS5JsSdERJKA8WgwDyNBZgTIIA4D8qUtoQHmxIIquoQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -445,9 +488,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-darwin-x64": {
|
||||
"version": "15.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.3.1.tgz",
|
||||
"integrity": "sha512-q+aw+cJ2ooVYdCEqZVk+T4Ni10jF6Fo5DfpEV51OupMaV5XL6pf3GCzrk6kSSZBsMKZtVC1Zm/xaNBFpA6bJ2g==",
|
||||
"version": "15.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.2.tgz",
|
||||
"integrity": "sha512-2DjnmR6JHK4X+dgTXt5/sOCu/7yPtqpYt8s8hLkHFK3MGkka2snTv3yRMdHvuRtJVkPwCGsvBSwmoQCHatauFQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -461,9 +504,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm64-gnu": {
|
||||
"version": "15.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.3.1.tgz",
|
||||
"integrity": "sha512-wBQ+jGUI3N0QZyWmmvRHjXjTWFy8o+zPFLSOyAyGFI94oJi+kK/LIZFJXeykvgXUk1NLDAEFDZw/NVINhdk9FQ==",
|
||||
"version": "15.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.2.tgz",
|
||||
"integrity": "sha512-3j7SWDBS2Wov/L9q0mFJtEvQ5miIqfO4l7d2m9Mo06ddsgUK8gWfHGgbjdFlCp2Ek7MmMQZSxpGFqcC8zGh2AA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -477,9 +520,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm64-musl": {
|
||||
"version": "15.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.3.1.tgz",
|
||||
"integrity": "sha512-IIxXEXRti/AulO9lWRHiCpUUR8AR/ZYLPALgiIg/9ENzMzLn3l0NSxVdva7R/VDcuSEBo0eGVCe3evSIHNz0Hg==",
|
||||
"version": "15.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.2.tgz",
|
||||
"integrity": "sha512-s6N8k8dF9YGc5T01UPQ08yxsK6fUow5gG1/axWc1HVVBYQBgOjca4oUZF7s4p+kwhkB1bDSGR8QznWrFZ/Rt5g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -493,9 +536,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-x64-gnu": {
|
||||
"version": "15.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.3.1.tgz",
|
||||
"integrity": "sha512-bfI4AMhySJbyXQIKH5rmLJ5/BP7bPwuxauTvVEiJ/ADoddaA9fgyNNCcsbu9SlqfHDoZmfI6g2EjzLwbsVTr5A==",
|
||||
"version": "15.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.2.tgz",
|
||||
"integrity": "sha512-o1RV/KOODQh6dM6ZRJGZbc+MOAHww33Vbs5JC9Mp1gDk8cpEO+cYC/l7rweiEalkSm5/1WGa4zY7xrNwObN4+Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -509,9 +552,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-x64-musl": {
|
||||
"version": "15.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.3.1.tgz",
|
||||
"integrity": "sha512-FeAbR7FYMWR+Z+M5iSGytVryKHiAsc0x3Nc3J+FD5NVbD5Mqz7fTSy8CYliXinn7T26nDMbpExRUI/4ekTvoiA==",
|
||||
"version": "15.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.2.tgz",
|
||||
"integrity": "sha512-/VUnh7w8RElYZ0IV83nUcP/J4KJ6LLYliiBIri3p3aW2giF+PAVgZb6mk8jbQSB3WlTai8gEmCAr7kptFa1H6g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -525,9 +568,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-arm64-msvc": {
|
||||
"version": "15.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.3.1.tgz",
|
||||
"integrity": "sha512-yP7FueWjphQEPpJQ2oKmshk/ppOt+0/bB8JC8svPUZNy0Pi3KbPx2Llkzv1p8CoQa+D2wknINlJpHf3vtChVBw==",
|
||||
"version": "15.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.2.tgz",
|
||||
"integrity": "sha512-sMPyTvRcNKXseNQ/7qRfVRLa0VhR0esmQ29DD6pqvG71+JdVnESJaHPA8t7bc67KD5spP3+DOCNLhqlEI2ZgQg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -541,9 +584,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-x64-msvc": {
|
||||
"version": "15.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.3.1.tgz",
|
||||
"integrity": "sha512-3PMvF2zRJAifcRNni9uMk/gulWfWS+qVI/pagd+4yLF5bcXPZPPH2xlYRYOsUjmCJOXSTAC2PjRzbhsRzR2fDQ==",
|
||||
"version": "15.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.2.tgz",
|
||||
"integrity": "sha512-W5VvyZHnxG/2ukhZF/9Ikdra5fdNftxI6ybeVKYvBPDtyx7x4jPPSNduUkfH5fo3zG0JQ0bPxgy41af2JX5D4Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -556,12 +599,6 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/counter": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
||||
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@swc/helpers": {
|
||||
"version": "0.5.15",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
||||
@ -839,6 +876,58 @@
|
||||
"tailwindcss": "4.1.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-color": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-2.0.6.tgz",
|
||||
"integrity": "sha512-tbaFGDmJWHqnenvk3QGSvD3RVwr631BjKRD7Sc7VLRgrdX5mk5hTyoeBL6rXZaeoXzmZwIl1D2HPogEdt1rHBg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/d3-geo": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-2.0.7.tgz",
|
||||
"integrity": "sha512-RIXlxPdxvX+LAZFv+t78CuYpxYag4zuw9mZc+AwfB8tZpKU90rMEn2il2ADncmeZlb7nER9dDsJpRisA3lRvjA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/geojson": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-interpolate": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-2.0.5.tgz",
|
||||
"integrity": "sha512-UINE41RDaUMbulp+bxQMDnhOi51rh5lA2dG+dWZU0UY/IwQiG/u2x8TfnWYU9+xwGdXsJoAvrBYUEQl0r91atg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/d3-color": "^2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-selection": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-2.0.5.tgz",
|
||||
"integrity": "sha512-71BorcY0yXl12S7lvb01JdaN9TpeUHBDb4RRhSq8U8BEkX/nIk5p7Byho+ZRTsx5nYLMpAbY3qt5EhqFzfGJlw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/d3-zoom": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-2.0.7.tgz",
|
||||
"integrity": "sha512-JWke4E8ZyrKUQ68ESTWSK16fVb0OYnaiJ+WXJRYxKLn4aXU0o4CLYxMWBEiouUfO3TTCoyroOrGPcBG6u1aAxA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/d3-interpolate": "^2",
|
||||
"@types/d3-selection": "^2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/geojson": {
|
||||
"version": "7946.0.16",
|
||||
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
|
||||
"integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.17.32",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.32.tgz",
|
||||
@ -869,15 +958,17 @@
|
||||
"@types/react": "^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/busboy": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
||||
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
|
||||
"node_modules/@types/react-simple-maps": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-simple-maps/-/react-simple-maps-3.0.6.tgz",
|
||||
"integrity": "sha512-hR01RXt6VvsE41FxDd+Bqm1PPGdKbYjCYVtCgh38YeBPt46z3SwmWPWu2L3EdCAP6bd6VYEgztucihRw1C0Klg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"streamsearch": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.16.0"
|
||||
"@types/d3-geo": "^2",
|
||||
"@types/d3-zoom": "^2",
|
||||
"@types/geojson": "*",
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
@ -951,6 +1042,12 @@
|
||||
"simple-swizzle": "^0.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "2.20.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
@ -958,6 +1055,135 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/d3-array": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
|
||||
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"internmap": "1 - 2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-color": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
|
||||
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-dispatch": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
|
||||
"integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-drag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz",
|
||||
"integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"d3-dispatch": "1 - 3",
|
||||
"d3-selection": "3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-ease": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
|
||||
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-geo": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz",
|
||||
"integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"d3-array": "2.5.0 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-interpolate": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
|
||||
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"d3-color": "1 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-selection": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
|
||||
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-timer": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
|
||||
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-transition": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz",
|
||||
"integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"d3-color": "1 - 3",
|
||||
"d3-dispatch": "1 - 3",
|
||||
"d3-ease": "1 - 3",
|
||||
"d3-interpolate": "1 - 3",
|
||||
"d3-timer": "1 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"d3-selection": "2 - 3"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-zoom": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz",
|
||||
"integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"d3-dispatch": "1 - 3",
|
||||
"d3-drag": "2 - 3",
|
||||
"d3-interpolate": "1 - 3",
|
||||
"d3-selection": "2 - 3",
|
||||
"d3-transition": "2 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
|
||||
@ -989,6 +1215,15 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/internmap": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
|
||||
"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/is-arrayish": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
|
||||
@ -1006,6 +1241,13 @@
|
||||
"jiti": "lib/jiti-cli.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/lightningcss": {
|
||||
"version": "1.29.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.2.tgz",
|
||||
@ -1245,6 +1487,19 @@
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"loose-envify": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.11",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||
@ -1264,15 +1519,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/next": {
|
||||
"version": "15.3.1",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-15.3.1.tgz",
|
||||
"integrity": "sha512-8+dDV0xNLOgHlyBxP1GwHGVaNXsmp+2NhZEYrXr24GWLHtt27YrBPbPuHvzlhi7kZNYjeJNR93IF5zfFu5UL0g==",
|
||||
"version": "15.5.2",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-15.5.2.tgz",
|
||||
"integrity": "sha512-H8Otr7abj1glFhbGnvUt3gz++0AF1+QoCXEBmd/6aKbfdFwrn0LpA836Ed5+00va/7HQSDD+mOoVhn3tNy3e/Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@next/env": "15.3.1",
|
||||
"@swc/counter": "0.1.3",
|
||||
"@next/env": "15.5.2",
|
||||
"@swc/helpers": "0.5.15",
|
||||
"busboy": "1.6.0",
|
||||
"caniuse-lite": "^1.0.30001579",
|
||||
"postcss": "8.4.31",
|
||||
"styled-jsx": "5.1.6"
|
||||
@ -1284,19 +1537,19 @@
|
||||
"node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@next/swc-darwin-arm64": "15.3.1",
|
||||
"@next/swc-darwin-x64": "15.3.1",
|
||||
"@next/swc-linux-arm64-gnu": "15.3.1",
|
||||
"@next/swc-linux-arm64-musl": "15.3.1",
|
||||
"@next/swc-linux-x64-gnu": "15.3.1",
|
||||
"@next/swc-linux-x64-musl": "15.3.1",
|
||||
"@next/swc-win32-arm64-msvc": "15.3.1",
|
||||
"@next/swc-win32-x64-msvc": "15.3.1",
|
||||
"sharp": "^0.34.1"
|
||||
"@next/swc-darwin-arm64": "15.5.2",
|
||||
"@next/swc-darwin-x64": "15.5.2",
|
||||
"@next/swc-linux-arm64-gnu": "15.5.2",
|
||||
"@next/swc-linux-arm64-musl": "15.5.2",
|
||||
"@next/swc-linux-x64-gnu": "15.5.2",
|
||||
"@next/swc-linux-x64-musl": "15.5.2",
|
||||
"@next/swc-win32-arm64-msvc": "15.5.2",
|
||||
"@next/swc-win32-x64-msvc": "15.5.2",
|
||||
"sharp": "^0.34.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@opentelemetry/api": "^1.1.0",
|
||||
"@playwright/test": "^1.41.2",
|
||||
"@playwright/test": "^1.51.1",
|
||||
"babel-plugin-react-compiler": "*",
|
||||
"react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
|
||||
"react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
|
||||
@ -1345,6 +1598,16 @@
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
@ -1380,6 +1643,18 @@
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/prop-types": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"react-is": "^16.13.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "19.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
||||
@ -1401,6 +1676,30 @@
|
||||
"react": "^19.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/react-simple-maps": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-simple-maps/-/react-simple-maps-3.0.0.tgz",
|
||||
"integrity": "sha512-vKNFrvpPG8Vyfdjnz5Ne1N56rZlDfHXv5THNXOVZMqbX1rWZA48zQuYT03mx6PAKanqarJu/PDLgshIZAfHHqw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"d3-geo": "^2.0.2",
|
||||
"d3-selection": "^2.0.0",
|
||||
"d3-zoom": "^2.0.0",
|
||||
"topojson-client": "^3.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^16.8.0 || 17.x || 18.x",
|
||||
"react-dom": "^16.8.0 || 17.x || 18.x"
|
||||
}
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.26.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
|
||||
@ -1408,9 +1707,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
|
||||
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
|
||||
"version": "7.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
@ -1421,16 +1720,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sharp": {
|
||||
"version": "0.34.1",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.1.tgz",
|
||||
"integrity": "sha512-1j0w61+eVxu7DawFJtnfYcvSv6qPFvfTaqzTQ2BLknVhHTwGS8sc63ZBF4rzkWMBVKybo4S5OBtDdZahh2A1xg==",
|
||||
"version": "0.34.3",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.3.tgz",
|
||||
"integrity": "sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"color": "^4.2.3",
|
||||
"detect-libc": "^2.0.3",
|
||||
"semver": "^7.7.1"
|
||||
"detect-libc": "^2.0.4",
|
||||
"semver": "^7.7.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
@ -1439,26 +1738,28 @@
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-darwin-arm64": "0.34.1",
|
||||
"@img/sharp-darwin-x64": "0.34.1",
|
||||
"@img/sharp-libvips-darwin-arm64": "1.1.0",
|
||||
"@img/sharp-libvips-darwin-x64": "1.1.0",
|
||||
"@img/sharp-libvips-linux-arm": "1.1.0",
|
||||
"@img/sharp-libvips-linux-arm64": "1.1.0",
|
||||
"@img/sharp-libvips-linux-ppc64": "1.1.0",
|
||||
"@img/sharp-libvips-linux-s390x": "1.1.0",
|
||||
"@img/sharp-libvips-linux-x64": "1.1.0",
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.1.0",
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.1.0",
|
||||
"@img/sharp-linux-arm": "0.34.1",
|
||||
"@img/sharp-linux-arm64": "0.34.1",
|
||||
"@img/sharp-linux-s390x": "0.34.1",
|
||||
"@img/sharp-linux-x64": "0.34.1",
|
||||
"@img/sharp-linuxmusl-arm64": "0.34.1",
|
||||
"@img/sharp-linuxmusl-x64": "0.34.1",
|
||||
"@img/sharp-wasm32": "0.34.1",
|
||||
"@img/sharp-win32-ia32": "0.34.1",
|
||||
"@img/sharp-win32-x64": "0.34.1"
|
||||
"@img/sharp-darwin-arm64": "0.34.3",
|
||||
"@img/sharp-darwin-x64": "0.34.3",
|
||||
"@img/sharp-libvips-darwin-arm64": "1.2.0",
|
||||
"@img/sharp-libvips-darwin-x64": "1.2.0",
|
||||
"@img/sharp-libvips-linux-arm": "1.2.0",
|
||||
"@img/sharp-libvips-linux-arm64": "1.2.0",
|
||||
"@img/sharp-libvips-linux-ppc64": "1.2.0",
|
||||
"@img/sharp-libvips-linux-s390x": "1.2.0",
|
||||
"@img/sharp-libvips-linux-x64": "1.2.0",
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.2.0",
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.2.0",
|
||||
"@img/sharp-linux-arm": "0.34.3",
|
||||
"@img/sharp-linux-arm64": "0.34.3",
|
||||
"@img/sharp-linux-ppc64": "0.34.3",
|
||||
"@img/sharp-linux-s390x": "0.34.3",
|
||||
"@img/sharp-linux-x64": "0.34.3",
|
||||
"@img/sharp-linuxmusl-arm64": "0.34.3",
|
||||
"@img/sharp-linuxmusl-x64": "0.34.3",
|
||||
"@img/sharp-wasm32": "0.34.3",
|
||||
"@img/sharp-win32-arm64": "0.34.3",
|
||||
"@img/sharp-win32-ia32": "0.34.3",
|
||||
"@img/sharp-win32-x64": "0.34.3"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-swizzle": {
|
||||
@ -1480,14 +1781,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/streamsearch": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
|
||||
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/styled-jsx": {
|
||||
"version": "5.1.6",
|
||||
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
|
||||
@ -1536,6 +1829,20 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/topojson-client": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz",
|
||||
"integrity": "sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"commander": "2"
|
||||
},
|
||||
"bin": {
|
||||
"topo2geo": "bin/topo2geo",
|
||||
"topomerge": "bin/topomerge",
|
||||
"topoquantize": "bin/topoquantize"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
|
20
package.json
20
package.json
@ -2,6 +2,22 @@
|
||||
"name": "tunnlpls_frontend",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"overrides": {
|
||||
"react-simple-maps": {
|
||||
"d3-geo": "^3.1.0",
|
||||
"d3-color": "^3.1.0",
|
||||
"d3-selection": "^3.0.0",
|
||||
"d3-zoom": "^3.0.0",
|
||||
"react": "^16.8.0 || 17.x || 18.x || 19.x",
|
||||
"react-dom": "^16.8.0 || 17.x || 18.x || 19.x"
|
||||
},
|
||||
"d3-interpolate": {
|
||||
"d3-color": "^3.1.0"
|
||||
},
|
||||
"d3-transition": {
|
||||
"d3-color": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "next dev --turbopack",
|
||||
"build": "next build",
|
||||
@ -9,9 +25,10 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "15.3.1",
|
||||
"next": "^15.5.2",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-simple-maps": "^3.0.0",
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -19,6 +36,7 @@
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"@types/react-simple-maps": "^3.0.6",
|
||||
"tailwindcss": "^4",
|
||||
"typescript": "^5"
|
||||
}
|
||||
|
Reference in New Issue
Block a user