Files
tunnel-please-frontend/app/dashboard/page.tsx
bagas 7e5c12c1d1
All checks were successful
Docker Build and Push / build-and-push (push) Successful in 6m56s
renovate / renovate (push) Successful in 1m2s
feat: add dashboard page
2026-01-02 17:06:02 +07:00

558 lines
32 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client"
import { useEffect, useState } from "react"
import Link from "next/link"
import TunnelConfig, { type TunnelConfig as TunnelConfigType, type Server } from "@/components/tunnel-config"
import UserMenu from "@/components/user-menu"
import { authClient } from "@/lib/auth-client"
const defaultConfig: TunnelConfigType = {
type: "http",
serverPort: 443,
localPort: 8000,
}
type ActiveConnection = {
id: string
name: string
serverLabel: string
protocol: TunnelConfigType["type"]
localPort: number
serverPort: number
remote: string
status: "connected" | "pending" | "error"
latencyMs: number | null
dataInOut: string
startedAgo: string
}
export default function DashboardPage() {
const [selectedServer, setSelectedServer] = useState<Server | null>(null)
const [tunnelConfig, setTunnelConfig] = useState<TunnelConfigType>(defaultConfig)
const [statusMessage, setStatusMessage] = useState<string | null>(null)
const [activeConnections, setActiveConnections] = useState<ActiveConnection[]>([
{
id: "conn-1",
name: "Frontend Preview",
serverLabel: "Singapore",
protocol: "http",
localPort: 3000,
serverPort: 443,
remote: "https://sgp.tunnl.live",
status: "connected",
latencyMs: 34,
dataInOut: "1.2 GB",
startedAgo: "3h 12m",
},
{
id: "conn-2",
name: "Game TCP",
serverLabel: "Frankfurt",
protocol: "tcp",
localPort: 25565,
serverPort: 20555,
remote: "tcp://eu.tunnl.live:20555",
status: "connected",
latencyMs: 120,
dataInOut: "320 MB",
startedAgo: "54m",
},
])
type SessionResponse = Awaited<ReturnType<typeof authClient.getSession>>
const [session, setSession] = useState<SessionResponse["data"] | null>(null)
useEffect(() => {
const fetchSession = async () => {
try {
const result = await authClient.getSession()
if (result.data) {
setSession(result.data)
}
} catch (error) {
console.error("Error fetching session", error)
}
}
fetchSession()
}, [])
const handleSignOut = async () => {
try {
await authClient.signOut()
setSession(null)
} catch (error) {
console.error("Error signing out", error)
}
}
const stopConnection = (id: string) => {
setActiveConnections((prev) => prev.filter((conn) => conn.id !== id))
setStatusMessage("Connection stopped")
}
return (
<div className="flex min-h-screen flex-col bg-gray-950 text-white">
<header className="border-b border-gray-800 bg-gray-900/50 backdrop-blur-sm">
<div className="max-w-7xl mx-auto px-4 py-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" width={20} height={18}>
<path
d="M8.52 4c.296.02.574.086.863.164.09.027.18.05.27.074.066.02.066.02.132.035.29.079.578.149.871.211.332.075.66.153.989.235.492.125.984.246 1.48.355.344.078.684.16 1.027.246.11.028.215.051.325.078.402.098.8.196 1.203.297l.953.235c.023.008.05.011.074.02.16.038.32.073.48.109 1.305.285 1.305.285 1.688.52.031.015.059.034.09.054.262.168.422.37.586.637l.047.074c.238.414.187 1.015.187 1.48v1.649c0 .609-.023 1.144-.445 1.613-.39.367-.871.52-1.367.684-.059.02-.118.039-.18.062-.086.027-.168.059-.254.086-.176.059-.348.121-.523.184a8.12 8.12 0 0 1-.52.175c-.164.051-.324.114-.484.172-.102.04-.2.07-.301.102-.262.078-.52.168-.774.261-.652.231-1.308.458-1.964.68-.2.067-.399.133-.598.203a249.724 249.724 0 0 1-1.176.403c-.136.047-.273.093-.41.136-.05.02-.101.036-.156.055-.067.024-.137.047-.207.07-.04.012-.082.028-.121.04-.098.023-.098.023-.211-.016V12.69c.691-.175.691-.175.937-.23.028-.004.055-.012.086-.016.086-.02.176-.039.266-.058l.191-.04c.387-.085.774-.163 1.16-.238.356-.066.707-.144 1.059-.222.352-.078.703-.153 1.059-.215.355-.067.71-.137 1.062-.215.043-.008.086-.02.133-.027.515-.098.515-.098.918-.414.172-.278.152-.59.152-.91V9.98c.004-.09.004-.175.004-.261v-.657c.004-.039.004-.078.004-.117 0-.308-.05-.597-.203-.867-.067-.047-.067-.047-.149-.078l-.082-.04a.575.575 0 0 0-.086-.038l-.082-.04a1.447 1.447 0 0 0-.46-.093c-.036-.004-.07-.004-.106-.008-.039-.004-.074-.008-.113-.008a17.593 17.593 0 0 1-.766-.082c-.336-.043-.672-.062-1.012-.086-.261-.015-.52-.039-.777-.066-.344-.039-.687-.062-1.035-.082-.348-.023-.7-.05-1.047-.086-.332-.031-.668-.047-1-.062a4.771 4.771 0 0 1-.187-.61c-.207-.879-.653-1.832-1.356-2.41-.11-.098-.11-.098-.144-.207V4Zm0 0"
style={{
stroke: "none",
fillRule: "nonzero",
fill: "#f4f0ed",
fillOpacity: 1,
}}
/>
<path
d="M7.129 3.652c.035.016.035.016.07.035 1.043.481 1.867 1.415 2.285 2.5.27.743.27.743.196 1.083-.028-.004-.055-.004-.086-.008a49.636 49.636 0 0 0-1.864-.11c-.011-.02-.02-.043-.03-.066-.34-.82-.34-.82-.985-1.395v-.074l-.098-.035a3.2 3.2 0 0 1-.336-.14c-.62-.266-1.363-.243-1.988-.008-.79.332-1.242.941-1.586 1.722-.148.442-.129.934-.129 1.399 0 .066 0 .136-.004.207v.558c0 .2 0 .395-.004.59 0 .371-.004.738-.004 1.11 0 .421-.003.843-.003 1.265l-.012 2.598-.446.094c-.062.011-.062.011-.128.027-.86.176-.86.176-1.227.226-.09-.136-.086-.238-.086-.398V12.059c.004-.391 0-.778 0-1.164V8.53c0-.582.012-1.187.168-1.75.008-.031.016-.058.023-.09a4.958 4.958 0 0 1 1.36-2.23h.074l.04-.113h.108c.012-.024.02-.047.028-.07.058-.106.117-.137.219-.196.144-.086.144-.086.289-.176 1.195-.808 2.879-.836 4.156-.254Zm0 0"
style={{
stroke: "none",
fillRule: "nonzero",
fill: "#f2efec",
fillOpacity: 1,
}}
/>
<path
d="M12.488 7.957h.14c.13 0 .259.012.392.023a51.951 51.951 0 0 0 .996.07l.53.036.884.059c.093.007.191.011.289.02.133.007.27.019.406.026.043.004.082.008.125.008.059.004.059.004.113.012.051 0 .051 0 .102.004.082.015.082.015.195.094v.074l.113.039c.047.101.043.176.043.289 0 .043.004.086.004.133 0 .203.004.402.004.605 0 .106 0 .211.004.317 0 .156 0 .308.004.46v.145c0 .328 0 .328-.105.484a1.149 1.149 0 0 1-.504.192l-.075.012-.234.035c-.055.008-.113.015-.168.027-.36.055-.723.11-1.082.16-.176.028-.355.051-.531.078-.028.004-.059.012-.086.016-.18.027-.36.055-.54.086-.105.02-.214.035-.323.055-.055.007-.106.02-.16.027-.079.012-.153.027-.231.039-.063.012-.063.012-.133.023-.25.02-.422-.02-.613-.183-.149-.2-.156-.395-.156-.637v-.156c0-.055-.004-.11-.004-.164V8.59c.011-.203.047-.352.175-.508.149-.129.231-.125.426-.125ZM14.523 9.5a.528.528 0 0 0 0 .379c.07.094.07.094.204.125.132.008.132.008.222-.063a.594.594 0 0 0 .098-.363c-.05-.117-.05-.117-.149-.195-.164-.028-.27-.012-.375.117Zm1.114-.078c-.09.11-.078.203-.078.344.007.09.007.09.09.156.128.02.128.02.261 0 .098-.082.11-.137.13-.27-.009-.117-.009-.117-.083-.207-.113-.082-.191-.078-.32-.023Zm-2.317.156c-.043.125-.062.211-.008.34.051.074.051.074.16.16a.518.518 0 0 0 .352-.101c.086-.133.09-.247.059-.399-.055-.101-.055-.101-.149-.156-.187-.031-.289.012-.414.156Zm0 0"
style={{
stroke: "none",
fillRule: "nonzero",
fill: "#5aa680",
fillOpacity: 1,
}}
/>
<path
d="M9.68 12.77v2.808c-.293.129-.293.129-.414.172-.024.008-.051.016-.079.027-.027.008-.054.02-.082.028l-.09.03a3.392 3.392 0 0 1-.183.063c-.062.024-.125.047-.187.067-.09.031-.18.066-.274.098-.027.007-.055.019-.082.027a.613.613 0 0 1-.41.027v-2.965a7.29 7.29 0 0 1 .695-.183c.028-.008.059-.012.086-.02l.18-.035c.09-.02.18-.035.27-.055l.175-.035c.024-.004.05-.011.078-.015.11-.024.207-.04.317-.04Zm0 0"
style={{
stroke: "none",
fillRule: "nonzero",
fill: "#f4f0ee",
fillOpacity: 1,
}}
/>
<path
d="M6.703 9.59h3.133c.164.004.234.008.371.117.074.102.074.102.098.219-.024.113-.024.113-.098.195-.16.098-.289.094-.473.09H7.082c-.113 0-.23-.004-.344-.004h-.101c-.176 0-.305-.008-.446-.129-.043-.133-.043-.133-.039-.27.157-.222.305-.222.551-.218Zm0 0"
style={{
stroke: "none",
fillRule: "nonzero",
fill: "#f7f5f2",
fillOpacity: 1,
}}
/>
<path
d="M8.145 10.914c.027 0 .058 0 .09-.004h2.097c.094-.004.188 0 .281 0 .043 0 .043 0 .086-.004a.755.755 0 0 1 .446.133c.074.113.074.113.07.25-.031.133-.031.133-.09.2-.133.07-.258.066-.402.066h-.094c-.106 0-.207 0-.313-.004H8.031c-.152-.004-.281-.012-.414-.09-.054-.125-.054-.125-.074-.27.043-.12.078-.171.191-.234.137-.043.266-.047.41-.043Zm0 0"
style={{
stroke: "none",
fillRule: "nonzero",
fill: "#edeae7",
fillOpacity: 1,
}}
/>
<path
d="M8.555 8.324H9.949c.13-.004.254-.004.38-.004h.577c.16.032.215.09.313.22.004.12.004.12-.035.23-.09.093-.145.113-.27.128h-.144c-.04.004-.04.004-.079.004H9.02c-.06 0-.118 0-.176.004-.086 0-.168 0-.25-.004h-.145c-.14-.02-.183-.054-.27-.172-.015-.125-.015-.199.044-.312.105-.105.187-.09.332-.094Zm0 0"
style={{
stroke: "none",
fillRule: "nonzero",
fill: "#eeebe9",
fillOpacity: 1,
}}
/>
<path
d="M10.094 9.77c.062.011.125.023.187.039a.757.757 0 0 1 0 .23.431.431 0 0 1-.265.168c-.094.004-.188.004-.282.004H9.02c-.176 0-.348 0-.52-.004H7.074c-.117-.004-.23-.004-.344-.004H6.45c-.07-.012-.07-.012-.183-.086l3.828-.039v-.117h-.074v-.078h.074V9.77Zm0 0"
style={{
stroke: "none",
fillRule: "nonzero",
fill: "#9d9c9b",
fillOpacity: 1,
}}
/>
<path
d="M8.52 4c.335.016.648.105.972.191v.04h-.336c-.004.042-.011.085-.015.128-.024.141-.024.141-.098.22.05.023.098.05.148.073-.023.04-.046.078-.074.118-.031-.036-.058-.07-.094-.106-.039-.043-.082-.09-.12-.137l-.063-.066c-.02-.024-.04-.043-.059-.066-.02-.024-.039-.043-.058-.067a2.522 2.522 0 0 0-.145-.144C8.52 4.117 8.52 4.117 8.52 4Zm0 0"
style={{
stroke: "none",
fillRule: "nonzero",
fill: "#d2cfce",
fillOpacity: 1,
}}
/>
<path
d="M6.34 9.617c.488-.004.976-.008 1.46-.008.227 0 .454 0 .68-.004h.653c.082 0 .168 0 .25-.003H9.836c.234 0 .234 0 .355.074.028.027.028.027.051.054l-.035.079c-.05-.012-.102-.028-.152-.04v-.078c-1.223-.011-2.45-.023-3.715-.039v-.035Zm0 0"
style={{
stroke: "none",
fillRule: "nonzero",
fill: "#d9d5d6",
fillOpacity: 1,
}}
/>
<path
d="m13.707 11.324.102.012.074.012c-.2.117-.434.129-.66.164-.082.015-.164.027-.246.043l-.16.023-.145.024c-.14.015-.262 0-.402-.024l-.036-.117c.07.004.07.004.145.004.195 0 .383-.027.574-.059.035-.004.067-.011.102-.015.125-.02.246-.04.37-.07a.97.97 0 0 1 .282.003Zm0 0"
style={{
stroke: "none",
fillRule: "nonzero",
fill: "#589275",
fillOpacity: 1,
}}
/>
<path
d="M8.145 10.914h2.558c.152 0 .27.016.402.086-.14.047-.273.043-.421.043H9.473c-.196-.004-.391-.004-.586-.004H7.73c-.011.04-.027.074-.039.113a1.343 1.343 0 0 0-.074-.035c.04-.117.04-.117.113-.164.141-.039.27-.039.415-.039Zm0 0"
style={{
stroke: "none",
fillRule: "nonzero",
fill: "#c5c4c3",
fillOpacity: 1,
}}
/>
<path
d="M11.145 8.46c.035.04.035.04.074.08-.004.124-.012.202-.098.292-.121.07-.215.066-.351.066H9.387c-.125 0-.25 0-.375-.003H8.44c-.109-.012-.109-.012-.187-.086h2.742c.004-.082.004-.082-.039-.157h.148c.016-.062.028-.125.04-.191Zm0 0"
style={{
stroke: "none",
fillRule: "nonzero",
fill: "#bbbab9",
fillOpacity: 1,
}}
/>
<path
d="M7.953 13.117c.027.012.05.024.078.035-.039.016-.074.028-.113.04l.035 2.886c.102-.015.2-.027.3-.039-.1.106-.19.102-.335.113-.063-.062-.043-.144-.043-.23v-.657c0-.175 0-.35.004-.527v-1.585c.027-.012.05-.024.074-.036Zm0 0"
style={{
stroke: "none",
fillRule: "nonzero",
fill: "#999896",
fillOpacity: 1,
}}
/>
</svg>
<span className="text-xl font-bold">
<span className="text-emerald-400">tunnl</span>.live
</span>
</div>
<div className="flex items-center gap-4">
{session?.user ? (
<UserMenu
user={{
name: session.user.name ?? "User",
email: session.user.email ?? "",
image: session.user.image ?? undefined,
}}
onSignOut={handleSignOut}
/>
) : (
<>
<Link
href="/login"
className="flex items-center gap-2 bg-emerald-600 hover:bg-emerald-700 text-white px-4 py-2 rounded-lg font-medium transition-colors"
>
<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"
>
<path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4" />
<polyline points="10 17 15 12 10 7" />
<line x1="15" x2="3" y1="12" y2="12" />
</svg>
Sign In
</Link>
<div className="hidden md:flex items-center gap-2 text-sm text-gray-400">
<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"
>
<circle cx="12" cy="12" r="10" />
<path d="M12 16v-4" />
<path d="M12 8h.01" />
</svg>
<span>Sign in to save configurations & view history</span>
</div>
</>
)}
</div>
</div>
</div>
</header>
<main className="flex-1">
<div className="max-w-7xl mx-auto px-4 py-8 space-y-6">
<div className="flex flex-col gap-1">
<h1 className="text-2xl font-semibold">Active Forwarding</h1>
<p className="text-sm text-gray-400">Live tunnels for this session.</p>
</div>
{statusMessage && (
<div className="rounded-lg border border-emerald-700 bg-emerald-900/40 px-4 py-3 text-sm text-emerald-200">
{statusMessage}
</div>
)}
<div className="rounded-lg border border-gray-800 bg-gray-900 p-5">
<div className="flex items-center justify-between mb-4">
<div>
<h2 className="text-lg font-semibold">Active Connections</h2>
<p className="text-sm text-gray-400">Monitor and manage your running tunnels</p>
</div>
<Link
href="/tunnel-not-found"
className="text-sm text-emerald-400 hover:text-emerald-300"
>
View logs
</Link>
</div>
{activeConnections.length === 0 ? (
<div className="rounded-lg border border-dashed border-gray-700 bg-gray-800/60 p-6 text-center text-gray-400">
No active connections yet. Configure a tunnel to see it here.
</div>
) : (
<div className="space-y-3">
{activeConnections.map((connection) => (
<div
key={connection.id}
className="rounded-lg border border-gray-800 bg-gray-800/60 p-4 flex flex-col gap-3 md:flex-row md:items-center md:justify-between"
>
<div className="space-y-1">
<div className="flex items-center gap-2">
<span className="text-white font-medium">{connection.name}</span>
<span
className={`rounded-full px-2 py-0.5 text-xs font-semibold ${
connection.status === "connected"
? "bg-emerald-900/60 text-emerald-300 border border-emerald-700"
: connection.status === "pending"
? "bg-yellow-900/60 text-yellow-300 border border-yellow-700"
: "bg-red-900/60 text-red-300 border border-red-700"
}`}
>
{connection.status === "connected"
? "Connected"
: connection.status === "pending"
? "Reconnecting"
: "Error"}
</span>
</div>
<p className="text-sm text-gray-300">
{connection.protocol.toUpperCase()} · {connection.serverLabel}
</p>
<p className="text-xs text-gray-400">{connection.remote}</p>
<p className="text-xs text-gray-500">
Local {connection.localPort} Server {connection.serverPort} · {connection.startedAgo}
</p>
</div>
<div className="flex flex-wrap items-center gap-3 md:justify-end">
<div className="text-right">
<p className="text-sm text-gray-300">Latency</p>
<p className="text-lg font-semibold text-white">
{connection.latencyMs ? `${connection.latencyMs}ms` : "—"}
</p>
</div>
<div className="text-right">
<p className="text-sm text-gray-300">Data</p>
<p className="text-lg font-semibold text-white">{connection.dataInOut}</p>
</div>
<button
onClick={() => stopConnection(connection.id)}
className="rounded-lg border border-gray-700 px-3 py-2 text-sm text-gray-200 hover:border-red-500 hover:text-red-200 transition"
>
Stop
</button>
</div>
</div>
))}
</div>
)}
</div>
<div className="rounded-lg border border-gray-800 bg-gray-900 p-5">
<div className="flex flex-col gap-2 mb-4 sm:flex-row sm:items-center sm:justify-between">
<div>
<h2 className="text-lg font-semibold">Custom Tunnel Configuration</h2>
<p className="text-sm text-gray-400">Pick a location, test latency, and shape your tunnel exactly how you need.</p>
</div>
<Link
href="/tunnel-not-found"
className="text-sm text-emerald-400 hover:text-emerald-300"
>
View docs
</Link>
</div>
<TunnelConfig
config={tunnelConfig}
onConfigChange={setTunnelConfig}
selectedServer={selectedServer}
onServerSelect={setSelectedServer}
isAuthenticated={Boolean(session)}
userId={session?.user?.id}
/>
</div>
</div>
</main>
<footer className="border-t border-gray-800 py-6 px-4">
<div className="max-w-3xl mx-auto text-center">
<div className="flex items-center justify-center gap-2 mb-4">
<svg xmlns="http://www.w3.org/2000/svg" width={20} height={18}>
<path
d="M8.52 4c.296.02.574.086.863.164.09.027.18.05.27.074.066.02.066.02.132.035.29.079.578.149.871.211.332.075.66.153.989.235.492.125.984.246 1.48.355.344.078.684.16 1.027.246.11.028.215.051.325.078.402.098.8.196 1.203.297l.953.235c.023.008.05.011.074.02.16.038.32.073.48.109 1.305.285 1.305.285 1.688.52.031.015.059.034.09.054.262.168.422.37.586.637l.047.074c.238.414.187 1.015.187 1.48v1.649c0 .609-.023 1.144-.445 1.613-.39.367-.871.52-1.367.684-.059.02-.118.039-.18.062-.086.027-.168.059-.254.086-.176.059-.348.121-.523.184a8.12 8.12 0 0 1-.52.175c-.164.051-.324.114-.484.172-.102.04-.2.07-.301.102-.262.078-.52.168-.774.261-.652.231-1.308.458-1.964.68-.2.067-.399.133-.598.203a249.724 249.724 0 0 1-1.176.403c-.136.047-.273.093-.41.136-.05.02-.101.036-.156.055-.067.024-.137.047-.207.07-.04.012-.082.028-.121.04-.098.023-.098.023-.211-.016V12.69c.691-.175.691-.175.937-.23.028-.004.055-.012.086-.016.086-.02.176-.039.266-.058l.191-.04c.387-.085.774-.163 1.16-.238.356-.066.707-.144 1.059-.222.352-.078.703-.153 1.059-.215.355-.067.71-.137 1.062-.215.043-.008.086-.02.133-.027.515-.098.515-.098.918-.414.172-.278.152-.59.152-.91V9.98c.004-.09.004-.175.004-.261v-.657c.004-.039.004-.078.004-.117 0-.308-.05-.597-.203-.867-.067-.047-.067-.047-.149-.078l-.082-.04a.575.575 0 0 0-.086-.038l-.082-.04a1.447 1.447 0 0 0-.46-.093c-.036-.004-.07-.004-.106-.008-.039-.004-.074-.008-.113-.008a17.593 17.593 0 0 1-.766-.082c-.336-.043-.672-.062-1.012-.086-.261-.015-.52-.039-.777-.066-.344-.039-.687-.062-1.035-.082-.348-.023-.7-.05-1.047-.086-.332-.031-.668-.047-1-.062a4.771 4.771 0 0 1-.187-.61c-.207-.879-.653-1.832-1.356-2.41-.11-.098-.11-.098-.144-.207V4Zm0 0"
style={{
stroke: "none",
fillRule: "nonzero",
fill: "#f4f0ed",
fillOpacity: 1,
}}
/>
<path
d="M7.129 3.652c.035.016.035.016.07.035 1.043.481 1.867 1.415 2.285 2.5.27.743.27.743.196 1.083-.028-.004-.055-.004-.086-.008a49.636 49.636 0 0 0-1.864-.11c-.011-.02-.02-.043-.03-.066-.34-.82-.34-.82-.985-1.395v-.074l-.098-.035a3.2 3.2 0 0 1-.336-.14c-.62-.266-1.363-.243-1.988-.008-.79.332-1.242.941-1.586 1.722-.148.442-.129.934-.129 1.399 0 .066 0 .136-.004.207v.558c0 .2 0 .395-.004.59 0 .371-.004.738-.004 1.11 0 .421-.003.843-.003 1.265l-.012 2.598-.446.094c-.062.011-.062.011-.128.027-.86.176-.86.176-1.227.226-.09-.136-.086-.238-.086-.398V12.059c.004-.391 0-.778 0-1.164V8.53c0-.582.012-1.187.168-1.75.008-.031.016-.058.023-.09a4.958 4.958 0 0 1 1.36-2.23h.074l.04-.113h.108c.012-.024.02-.047.028-.07.058-.106.117-.137.219-.196.144-.086.144-.086.289-.176 1.195-.808 2.879-.836 4.156-.254Zm0 0"
style={{
stroke: "none",
fillRule: "nonzero",
fill: "#f2efec",
fillOpacity: 1,
}}
/>
<path
d="M12.488 7.957h.14c.13 0 .259.012.392.023a51.951 51.951 0 0 0 .996.07l.53.036.884.059c.093.007.191.011.289.02.133.007.27.019.406.026.043.004.082.008.125.008.059.004.059.004.113.012.051 0 .051 0 .102.004.082.015.082.015.195.094v.074l.113.039c.047.101.043.176.043.289 0 .043.004.086.004.133 0 .203.004.402.004.605 0 .106 0 .211.004.317 0 .156 0 .308.004.46v.145c0 .328 0 .328-.105.484a1.149 1.149 0 0 1-.504.192l-.075.012-.234.035c-.055.008-.113.015-.168.027-.36.055-.723.11-1.082.16-.176.028-.355.051-.531.078-.028.004-.059.012-.086.016-.18.027-.36.055-.54.086-.105.02-.214.035-.323.055-.055.007-.106.02-.16.027-.079.012-.153.027-.231.039-.063.012-.063.012-.133.023-.25.02-.422-.02-.613-.183-.149-.2-.156-.395-.156-.637v-.156c0-.055-.004-.11-.004-.164V8.59c.011-.203.047-.352.175-.508.149-.129.231-.125.426-.125ZM14.523 9.5a.528.528 0 0 0 0 .379c.07.094.07.094.204.125.132.008.132.008.222-.063a.594.594 0 0 0 .098-.363c-.05-.117-.05-.117-.149-.195-.164-.028-.27-.012-.375.117Zm1.114-.078c-.09.11-.078.203-.078.344.007.09.007.09.09.156.128.02.128.02.261 0 .098-.082.11-.137.13-.27-.009-.117-.009-.117-.083-.207-.113-.082-.191-.078-.32-.023Zm-2.317.156c-.043.125-.062.211-.008.34.051.074.051.074.16.16a.518.518 0 0 0 .352-.101c.086-.133.09-.247.059-.399-.055-.101-.055-.101-.149-.156-.187-.031-.289.012-.414.156Zm0 0"
style={{
stroke: "none",
fillRule: "nonzero",
fill: "#5aa680",
fillOpacity: 1,
}}
/>
<path
d="M9.68 12.77v2.808c-.293.129-.293.129-.414.172-.024.008-.051.016-.079.027-.027.008-.054.02-.082.028l-.09.03a3.392 3.392 0 0 1-.183.063c-.062.024-.125.047-.187.067-.09.031-.18.066-.274.098-.027.007-.055.019-.082.027a.613.613 0 0 1-.41.027v-2.965a7.29 7.29 0 0 1 .695-.183c.028-.008.059-.012.086-.02l.18-.035c.09-.02.18-.035.27-.055l.175-.035c.024-.004.05-.011.078-.015.11-.024.207-.04.317-.04Zm0 0"
style={{
stroke: "none",
fillRule: "nonzero",
fill: "#f4f0ee",
fillOpacity: 1,
}}
/>
<path
d="M6.703 9.59h3.133c.164.004.234.008.371.117.074.102.074.102.098.219-.024.113-.024.113-.098.195-.16.098-.289.094-.473.09H7.082c-.113 0-.23-.004-.344-.004h-.101c-.176 0-.305-.008-.446-.129-.043-.133-.043-.133-.039-.27.157-.222.305-.222.551-.218Zm0 0"
style={{
stroke: "none",
fillRule: "nonzero",
fill: "#f7f5f2",
fillOpacity: 1,
}}
/>
<path
d="M8.145 10.914c.027 0 .058 0 .09-.004h2.097c.094-.004.188 0 .281 0 .043 0 .043 0 .086-.004a.755.755 0 0 1 .446.133c.074.113.074.113.07.25-.031.133-.031.133-.09.2-.133.07-.258.066-.402.066h-.094c-.106 0-.207 0-.313-.004H8.031c-.152-.004-.281-.012-.414-.09-.054-.125-.054-.125-.074-.27.043-.12.078-.171.191-.234.137-.043.266-.047.41-.043Zm0 0"
style={{
stroke: "none",
fillRule: "nonzero",
fill: "#edeae7",
fillOpacity: 1,
}}
/>
<path
d="M8.555 8.324H9.949c.13-.004.254-.004.38-.004h.577c.16.032.215.09.313.22.004.12.004.12-.035.23-.09.093-.145.113-.27.128h-.144c-.04.004-.04.004-.079.004H9.02c-.06 0-.118 0-.176.004-.086 0-.168 0-.25-.004h-.145c-.14-.02-.183-.054-.27-.172-.015-.125-.015-.199.044-.312.105-.105.187-.09.332-.094Zm0 0"
style={{
stroke: "none",
fillRule: "nonzero",
fill: "#eeebe9",
fillOpacity: 1,
}}
/>
<path
d="M10.094 9.77c.062.011.125.023.187.039a.757.757 0 0 1 0 .23.431.431 0 0 1-.265.168c-.094.004-.188.004-.282.004H9.02c-.176 0-.348 0-.52-.004H7.074c-.117-.004-.23-.004-.344-.004H6.45c-.07-.012-.07-.012-.183-.086l3.828-.039v-.117h-.074v-.078h.074V9.77Zm0 0"
style={{
stroke: "none",
fillRule: "nonzero",
fill: "#9d9c9b",
fillOpacity: 1,
}}
/>
<path
d="M8.52 4c.335.016.648.105.972.191v.04h-.336c-.004.042-.011.085-.015.128-.024.141-.024.141-.098.22.05.023.098.05.148.073-.023.04-.046.078-.074.118-.031-.036-.058-.07-.094-.106-.039-.043-.082-.09-.12-.137l-.063-.066c-.02-.024-.04-.043-.059-.066-.02-.024-.039-.043-.058-.067a2.522 2.522 0 0 0-.145-.144C8.52 4.117 8.52 4.117 8.52 4Zm0 0"
style={{
stroke: "none",
fillRule: "nonzero",
fill: "#d2cfce",
fillOpacity: 1,
}}
/>
<path
d="M6.34 9.617c.488-.004.976-.008 1.46-.008.227 0 .454 0 .68-.004h.653c.082 0 .168 0 .25-.003H9.836c.234 0 .234 0 .355.074.028.027.028.027.051.054l-.035.079c-.05-.012-.102-.028-.152-.04v-.078c-1.223-.011-2.45-.023-3.715-.039v-.035Zm0 0"
style={{
stroke: "none",
fillRule: "nonzero",
fill: "#d9d5d6",
fillOpacity: 1,
}}
/>
<path
d="m13.707 11.324.102.012.074.012c-.2.117-.434.129-.66.164-.082.015-.164.027-.246.043l-.16.023-.145.024c-.14.015-.262 0-.402-.024l-.036-.117c.07.004.07.004.145.004.195 0 .383-.027.574-.059.035-.004.067-.011.102-.015.125-.02.246-.04.37-.07a.97.97 0 0 1 .282.003Zm0 0"
style={{
stroke: "none",
fillRule: "nonzero",
fill: "#589275",
fillOpacity: 1,
}}
/>
<path
d="M8.145 10.914h2.558c.152 0 .27.016.402.086-.14.047-.273.043-.421.043H9.473c-.196-.004-.391-.004-.586-.004H7.73c-.011.04-.027.074-.039.113a1.343 1.343 0 0 0-.074-.035c.04-.117.04-.117.113-.164.141-.039.27-.039.415-.039Zm0 0"
style={{
stroke: "none",
fillRule: "nonzero",
fill: "#c5c4c3",
fillOpacity: 1,
}}
/>
<path
d="M11.145 8.46c.035.04.035.04.074.08-.004.124-.012.202-.098.292-.121.07-.215.066-.351.066H9.387c-.125 0-.25 0-.375-.003H8.44c-.109-.012-.109-.012-.187-.086h2.742c.004-.082.004-.082-.039-.157h.148c.016-.062.028-.125.04-.191Zm0 0"
style={{
stroke: "none",
fillRule: "nonzero",
fill: "#bbbab9",
fillOpacity: 1,
}}
/>
<path
d="M7.953 13.117c.027.012.05.024.078.035-.039.016-.074.028-.113.04l.035 2.886c.102-.015.2-.027.3-.039-.1.106-.19.102-.335.113-.063-.062-.043-.144-.043-.23v-.657c0-.175 0-.35.004-.527v-1.585c.027-.012.05-.024.074-.036Zm0 0"
style={{
stroke: "none",
fillRule: "nonzero",
fill: "#999896",
fillOpacity: 1,
}}
/>
</svg>
<span className="text-lg font-bold">tunnl.live</span>
</div>
<div className="text-sm text-gray-500">
© {new Date().getFullYear()} tunnl.live. Made with by{" "}
<a
href="https://github.com/fossyy"
target="_blank"
rel="noopener noreferrer"
className="underline hover:text-gray-700"
>
Bagas
</a>. All rights reserved.
</div>
</div>
</footer>
</div>
)
}