mirror of
https://github.com/hanzoai/bootnode.git
synced 2026-10-25 02:31:30 +00:00
Names the two halves for what they are. The repo already holds both (12407e9 merged bootnode/bootnode into this history); this makes the frontend directory say so, so the layout reads the way the owner describes the system: backend and frontend separate, one repo. git mv throughout — 187 renames, history preserved. Every path reference follows: hanzo.yml build context and dockerfile, compose.yml, compose.prod.yml, start.sh, LLM.md, infra/k8s/web-deployments.yaml, infra/cloud/deploy.sh, and the Dockerfile's own COPY paths. Image and container names (bootnode-web, the -web tag suffix) are deliberately NOT renamed here: those are a deploy contract the App CR and ingress bind to on port 3001, and breaking them is a separate, staged change. Console builds green after the move; the Go tree builds. Co-authored-by: Hanzo Dev <[email protected]>
98 lines
3.1 KiB
TypeScript
98 lines
3.1 KiB
TypeScript
import { cn } from "@/lib/utils"
|
|
|
|
// The three things a surface can be when it is not showing data: loading,
|
|
// empty, or broken. One implementation each.
|
|
//
|
|
// Before this file the app hand-rolled `<div className="animate-spin
|
|
// rounded-full h-8 w-8 border-b-2 border-primary" />` in nine places, at four
|
|
// different sizes, with no shared idea of what "empty" looks like. lux.cloud
|
|
// has exactly one `.empty` block — centred, muted, 40px of air, with the
|
|
// recovery action directly under the sentence — and that is what these are.
|
|
|
|
/** The one spinner. Sized in `em`, so it matches whatever text it sits in. */
|
|
export function Spinner({ className }: { className?: string }) {
|
|
return (
|
|
<span
|
|
role="status"
|
|
aria-label="Loading"
|
|
className={cn(
|
|
"inline-block size-[1.15em] animate-spin rounded-full border-2 border-current border-t-transparent align-[-0.15em]",
|
|
className
|
|
)}
|
|
/>
|
|
)
|
|
}
|
|
|
|
/**
|
|
* A surface that is loading. `full` centres it in the viewport for a whole
|
|
* route; without it the block sits inside whatever panel owns it.
|
|
*/
|
|
export function Loading({ label, full = false }: { label?: string; full?: boolean }) {
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"flex flex-col items-center justify-center gap-3 text-muted-foreground",
|
|
full ? "min-h-screen px-6" : "px-5 py-10"
|
|
)}
|
|
>
|
|
<Spinner className="size-6 text-foreground" />
|
|
{label && <p className="text-sm">{label}</p>}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
/**
|
|
* A surface with nothing in it — because there is genuinely nothing, not
|
|
* because a request failed. Say which, and offer the action that would put
|
|
* something here.
|
|
*/
|
|
export function EmptyState({
|
|
title,
|
|
description,
|
|
action,
|
|
className,
|
|
}: {
|
|
title: string
|
|
description?: string
|
|
action?: React.ReactNode
|
|
className?: string
|
|
}) {
|
|
return (
|
|
<div className={cn("px-5 py-10 text-center", className)}>
|
|
<p className="text-sm font-medium text-foreground">{title}</p>
|
|
{description && <p className="mx-auto mt-1.5 max-w-md text-sm text-muted-foreground">{description}</p>}
|
|
{action && <div className="mt-4 flex flex-wrap justify-center gap-2">{action}</div>}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
/**
|
|
* A surface that failed. It states what broke and never renders zeros in place
|
|
* of unknown data — a "0 nodes" that is really "the API refused" is a lie the
|
|
* reader cannot detect.
|
|
*/
|
|
export function ErrorState({
|
|
title = "Something went wrong",
|
|
description,
|
|
action,
|
|
className,
|
|
}: {
|
|
title?: string
|
|
description?: string
|
|
action?: React.ReactNode
|
|
className?: string
|
|
}) {
|
|
return (
|
|
<div className={cn("px-5 py-10 text-center", className)}>
|
|
<p className="text-sm font-medium text-destructive">{title}</p>
|
|
{description && <p className="mx-auto mt-1.5 max-w-md text-sm text-muted-foreground">{description}</p>}
|
|
{action && <div className="mt-4 flex flex-wrap justify-center gap-2">{action}</div>}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
/** Placeholder for content whose SHAPE is known but whose values are not. */
|
|
export function Skeleton({ className }: { className?: string }) {
|
|
return <div aria-hidden="true" className={cn("animate-pulse rounded-md bg-foreground/[0.07]", className)} />
|
|
}
|