Files
vibn-frontend/vibn-frontend/app/[workspace]/project/[projectId]/(home)/domains/page.tsx
mawkone 9092b9e549 fix(dashboard): remove Analytics, Marketing, Security, Integrations + fix build
- Deleted unused/stub routes: Security, Analytics, Marketing (+SEO/Social), Integrations
- Removed these routes from the Dashboard Sidebar menu
- Fixed Next.js build errors caused by duplicate component declarations (SectionHeader, KvRow) in overview, hosting, services, and infrastructure by relying fully on the unified dashboard-ui kit.
2026-06-13 11:13:37 -07:00

190 lines
5.0 KiB
TypeScript

"use client";
import { useParams } from "next/navigation";
import { useState } from "react";
import { Globe, ExternalLink, Copy, Check, Loader2 } from "lucide-react";
import { useAnatomy, type Anatomy } from "@/components/project/use-anatomy";
import {
THEME,
PageHeader,
Card,
EmptyState,
} from "@/components/project/dashboard-ui";
type LiveApp = Anatomy["hosting"]["live"][number];
// All public URLs for an app: its fqdn(s) (Coolify can store a comma-joined
// list) plus any attached custom domains, de-duplicated.
function urlsFor(app: LiveApp): string[] {
const out = new Set<string>();
(app.fqdn ?? "")
.split(",")
.map((s) => s.trim())
.filter(Boolean)
.forEach((u) => out.add(u));
(app.domains ?? []).forEach((d) =>
out.add(d.startsWith("http") ? d : `https://${d}`),
);
return [...out];
}
export default function DomainsPage() {
const params = useParams();
const projectId = params.projectId as string;
const { anatomy, loading } = useAnatomy(projectId, { pollMs: 8000 });
const live = anatomy?.hosting.live ?? [];
return (
<div
style={{
minHeight: "100vh",
background: THEME.canvasGradient,
fontFamily: THEME.font,
padding: "36px 48px",
}}
>
<div style={{ maxWidth: 860, margin: "0 auto" }}>
<PageHeader
title="Domains"
subtitle="Public URLs for your deployed apps. To add a custom domain, ask the AI in chat — DNS + TLS are wired automatically."
/>
{loading && !anatomy ? (
<Card>
<div
style={{
display: "flex",
alignItems: "center",
gap: 8,
color: THEME.mid,
fontSize: "0.875rem",
}}
>
<Loader2 size={15} className="animate-spin" /> Loading
</div>
</Card>
) : live.length === 0 ? (
<EmptyState
icon={<Globe size={22} />}
title="No deployed apps yet"
hint="Once you deploy an app, its URL and any custom domains will appear here."
/>
) : (
<div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
{live.map((app) => (
<DomainCard key={app.uuid} app={app} />
))}
</div>
)}
</div>
</div>
);
}
function DomainCard({ app }: { app: LiveApp }) {
const urls = urlsFor(app);
return (
<Card padding={0}>
<div
style={{
display: "flex",
alignItems: "center",
gap: 8,
padding: "14px 20px",
borderBottom: urls.length ? `1px solid ${THEME.borderSoft}` : "none",
}}
>
<span
style={{ fontSize: "0.95rem", fontWeight: 600, color: THEME.ink }}
>
{app.name}
</span>
<span style={{ fontSize: "0.78rem", color: THEME.muted }}>
{app.sourceLabel}
</span>
</div>
{urls.length === 0 ? (
<div
style={{
padding: "14px 20px",
fontSize: "0.85rem",
color: THEME.mid,
fontStyle: "italic",
}}
>
No domain assigned yet still deploying.
</div>
) : (
urls.map((url, i) => (
<DomainRow key={url} url={url} last={i === urls.length - 1} />
))
)}
</Card>
);
}
function DomainRow({ url, last }: { url: string; last: boolean }) {
const [copied, setCopied] = useState(false);
const copy = () => {
navigator.clipboard?.writeText(url).then(() => {
setCopied(true);
setTimeout(() => setCopied(false), 1500);
});
};
return (
<div
style={{
display: "flex",
alignItems: "center",
gap: 10,
padding: "12px 20px",
borderBottom: last ? "none" : `1px solid ${THEME.borderSoft}`,
}}
>
<Globe size={14} style={{ color: THEME.muted, flexShrink: 0 }} />
<a
href={url}
target="_blank"
rel="noreferrer"
style={{
fontSize: "0.85rem",
color: THEME.ink,
textDecoration: "none",
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
flex: 1,
}}
>
{url.replace(/^https?:\/\//, "")}
</a>
<a
href={url}
target="_blank"
rel="noreferrer"
title="Open"
style={{ color: THEME.muted, display: "inline-flex", flexShrink: 0 }}
>
<ExternalLink size={14} />
</a>
<button
onClick={copy}
title="Copy URL"
style={{
background: "transparent",
border: "none",
cursor: "pointer",
color: copied ? "#16a34a" : THEME.muted,
display: "inline-flex",
padding: 0,
flexShrink: 0,
}}
>
{copied ? <Check size={14} /> : <Copy size={14} />}
</button>
</div>
);
}