"use client"; import { useState, useEffect } from "react"; import PropTypes from "prop-types"; import { Card, Button, Input, Modal, CardSkeleton } from "@/shared/components"; import { useCopyToClipboard } from "@/shared/hooks/useCopyToClipboard"; const CLOUD_URL = process.env.NEXT_PUBLIC_CLOUD_URL; const CLOUD_ACTION_TIMEOUT_MS = 15000; export default function APIPageClient({ machineId }) { const [keys, setKeys] = useState([]); const [loading, setLoading] = useState(true); const [showAddModal, setShowAddModal] = useState(false); const [newKeyName, setNewKeyName] = useState(""); const [createdKey, setCreatedKey] = useState(null); // Cloud sync state const [cloudEnabled, setCloudEnabled] = useState(false); const [showCloudModal, setShowCloudModal] = useState(false); const [showDisableModal, setShowDisableModal] = useState(false); const [cloudSyncing, setCloudSyncing] = useState(false); const [cloudStatus, setCloudStatus] = useState(null); const [syncStep, setSyncStep] = useState(""); // "syncing" | "verifying" | "disabling" | "" const { copied, copy } = useCopyToClipboard(); useEffect(() => { fetchData(); loadCloudSettings(); }, []); const postCloudAction = async (action, timeoutMs = CLOUD_ACTION_TIMEOUT_MS) => { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeoutMs); try { const res = await fetch("/api/sync/cloud", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action }), signal: controller.signal, }); const data = await res.json().catch(() => ({})); return { ok: res.ok, status: res.status, data }; } catch (error) { if (error?.name === "AbortError") { return { ok: false, status: 408, data: { error: "Cloud request timeout" } }; } return { ok: false, status: 500, data: { error: error.message || "Cloud request failed" } }; } finally { clearTimeout(timeoutId); } }; const loadCloudSettings = async () => { try { const res = await fetch("/api/settings"); if (res.ok) { const data = await res.json(); setCloudEnabled(data.cloudEnabled || false); } } catch (error) { console.log("Error loading cloud settings:", error); } }; const fetchData = async () => { try { const keysRes = await fetch("/api/keys"); const keysData = await keysRes.json(); if (keysRes.ok) { setKeys(keysData.keys || []); } } catch (error) { console.log("Error fetching data:", error); } finally { setLoading(false); } }; const handleCloudToggle = (checked) => { if (checked) { setShowCloudModal(true); } else { setShowDisableModal(true); } }; const handleEnableCloud = async () => { setCloudSyncing(true); setSyncStep("syncing"); try { const { ok, data } = await postCloudAction("enable"); if (ok) { setSyncStep("verifying"); if (data.verified) { setCloudEnabled(true); setCloudStatus({ type: "success", message: "Cloud Proxy connected and verified!" }); setShowCloudModal(false); } else { setCloudEnabled(true); setCloudStatus({ type: "warning", message: data.verifyError || "Connected but verification failed" }); setShowCloudModal(false); } // Refresh keys list if new key was created if (data.createdKey) { await fetchData(); } } else { setCloudStatus({ type: "error", message: data.error || "Failed to enable cloud" }); } } catch (error) { setCloudStatus({ type: "error", message: error.message }); } finally { setCloudSyncing(false); setSyncStep(""); } }; const handleConfirmDisable = async () => { setCloudSyncing(true); setSyncStep("syncing"); try { // Step 1: Sync latest data from cloud await postCloudAction("sync"); setSyncStep("disabling"); // Step 2: Disable cloud const { ok, data } = await postCloudAction("disable"); if (ok) { setCloudEnabled(false); setCloudStatus({ type: "success", message: "Cloud disabled" }); setShowDisableModal(false); } else { setCloudStatus({ type: "error", message: data.error || "Failed to disable cloud" }); } } catch (error) { console.log("Error disabling cloud:", error); setCloudStatus({ type: "error", message: "Failed to disable cloud" }); } finally { setCloudSyncing(false); setSyncStep(""); } }; const handleSyncCloud = async () => { if (!cloudEnabled) return; setCloudSyncing(true); try { const { ok, data } = await postCloudAction("sync"); if (ok) { setCloudStatus({ type: "success", message: "Synced successfully" }); } else { setCloudStatus({ type: "error", message: data.error }); } } catch (error) { setCloudStatus({ type: "error", message: error.message }); } finally { setCloudSyncing(false); } }; const handleCreateKey = async () => { if (!newKeyName.trim()) return; try { const res = await fetch("/api/keys", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: newKeyName }), }); const data = await res.json(); if (res.ok) { setCreatedKey(data.key); await fetchData(); setNewKeyName(""); setShowAddModal(false); } } catch (error) { console.log("Error creating key:", error); } }; const handleDeleteKey = async (id) => { if (!confirm("Delete this API key?")) return; try { const res = await fetch(`/api/keys/${id}`, { method: "DELETE" }); if (res.ok) { setKeys(keys.filter((k) => k.id !== id)); } } catch (error) { console.log("Error deleting key:", error); } }; const [baseUrl, setBaseUrl] = useState("/v1"); const cloudEndpointNew = `${CLOUD_URL}/v1`; // Hydration fix: Only access window on client side useEffect(() => { if (typeof window !== "undefined") { setBaseUrl(`${window.location.origin}/v1`); } }, []); if (loading) { return (
); } // Use new format endpoint (machineId embedded in key) const currentEndpoint = cloudEnabled ? cloudEndpointNew : baseUrl; const cloudBenefits = [ { icon: "public", title: "Access Anywhere", desc: "No port forwarding needed" }, { icon: "group", title: "Share Endpoint", desc: "Easy team collaboration" }, { icon: "schedule", title: "Always Online", desc: "24/7 availability" }, { icon: "speed", title: "Global Edge", desc: "Fast worldwide access" }, ]; return (
{/* Endpoint Card */}

API Endpoint

{cloudEnabled ? "Using Cloud Proxy" : "Using Local Server"}

{cloudEnabled ? ( ) : ( )}
{/* Endpoint URL */}
{/* API Keys */}

API Keys

{keys.length === 0 ? (
vpn_key

No API keys yet

Create your first API key to get started

) : (
{keys.map((key) => (

{key.name}

{key.key}

Created {new Date(key.createdAt).toLocaleDateString()}

))}
)}
{/* Cloud Proxy Card - Hidden */} {false && (
{/* Header */}
cloud

Cloud Proxy

{cloudEnabled ? "Connected & Ready" : "Access your API from anywhere"}

{cloudEnabled ? ( ) : ( )}
{/* Benefits Grid */}
{cloudBenefits.map((benefit) => (
{benefit.icon}

{benefit.title}

{benefit.desc}

))}
)} {/* Cloud Enable Modal */} setShowCloudModal(false)} >

What you will get

  • • Access your API from anywhere in the world
  • • Share endpoint with your team easily
  • • No need to open ports or configure firewall
  • • Fast global edge network

Note

  • • Cloud will keep your auth session for 1 day. If not used, it will be automatically deleted.
  • • Cloud is currently unstable with Claude Code OAuth in some cases.
{/* Sync Progress */} {cloudSyncing && (
progress_activity

{syncStep === "syncing" && "Syncing data to cloud..."} {syncStep === "verifying" && "Verifying connection..."}

)}
{/* Add Key Modal */} { setShowAddModal(false); setNewKeyName(""); }} >
setNewKeyName(e.target.value)} placeholder="Production Key" />
{/* Created Key Modal */} setCreatedKey(null)} >

Save this key now!

This is the only time you will see this key. Store it securely.

{/* Disable Cloud Modal */} !cloudSyncing && setShowDisableModal(false)} >
warning

Warning

All auth sessions will be deleted from cloud.

{/* Sync Progress */} {cloudSyncing && (
progress_activity

{syncStep === "syncing" && "Syncing latest data..."} {syncStep === "disabling" && "Disabling cloud..."}

)}

Are you sure you want to disable cloud proxy?

); } APIPageClient.propTypes = { machineId: PropTypes.string.isRequired, };