BOC v1.0: RS256 auth, Ledger integration, Prometheus metrics, CI/CD, backup

This commit is contained in:
Bernt
2026-07-28 23:08:32 +00:00
parent 0b4f160af1
commit af874040ca
11541 changed files with 1654104 additions and 1103 deletions
+36
View File
@@ -0,0 +1,36 @@
import { cn } from '@/lib/utils'
interface BadgeProps {
children: React.ReactNode
variant?: 'default' | 'success' | 'warning' | 'danger' | 'primary'
size?: 'sm' | 'md'
className?: string
}
export function Badge({ children, variant = 'default', size = 'sm', className }: BadgeProps) {
const variants = {
default: 'bg-bg text-text-secondary',
success: 'bg-success-light text-success',
warning: 'bg-warning-light text-warning',
danger: 'bg-danger-light text-danger',
primary: 'bg-primary-light text-primary',
}
const sizes = {
sm: 'px-2 py-0.5 text-[11px]',
md: 'px-2.5 py-1 text-xs',
}
return (
<span
className={cn(
'inline-flex items-center rounded-full font-medium',
variants[variant],
sizes[size],
className
)}
>
{children}
</span>
)
}
+58
View File
@@ -0,0 +1,58 @@
import { cn } from '@/lib/utils'
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'ghost' | 'danger'
size?: 'sm' | 'md' | 'lg'
loading?: boolean
icon?: React.ReactNode
}
export function Button({
children,
variant = 'primary',
size = 'md',
loading = false,
icon,
className,
disabled,
...props
}: ButtonProps) {
const variants = {
primary: 'bg-primary text-white hover:bg-primary-hover shadow-sm',
secondary: 'bg-bg text-text-primary border border-border hover:bg-white',
ghost: 'bg-transparent text-text-secondary hover:bg-primary-light hover:text-primary',
danger: 'bg-danger text-white hover:opacity-90',
}
const sizes = {
sm: 'h-8 px-3 text-xs',
md: 'h-10 px-4 text-sm',
lg: 'h-12 px-6 text-sm',
}
return (
<button
className={cn(
'inline-flex items-center justify-center gap-2 rounded-[14px] font-medium transition-colors duration-150',
'focus:outline-none focus:ring-2 focus:ring-primary/20',
'disabled:opacity-50 disabled:cursor-not-allowed',
'active:scale-[0.98]',
variants[variant],
sizes[size],
className
)}
disabled={disabled || loading}
{...props}
>
{loading ? (
<svg className="animate-spin h-4 w-4" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" fill="none" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
</svg>
) : (
icon
)}
{children}
</button>
)
}
+54
View File
@@ -0,0 +1,54 @@
import { cn } from '@/lib/utils'
import { motion } from 'framer-motion'
interface CardProps {
children: React.ReactNode
className?: string
hover?: boolean
padding?: 'sm' | 'md' | 'lg'
}
export function Card({ children, className, hover = false, padding = 'lg' }: CardProps) {
const paddingClasses = {
sm: 'p-4',
md: 'p-5',
lg: 'p-6 md:p-8',
}
return (
<motion.div
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.18, ease: 'easeOut' }}
className={cn(
'bg-surface rounded-[20px] card-shadow',
paddingClasses[padding],
hover && 'transition-shadow duration-200 hover:shadow-hover cursor-pointer',
className
)}
>
{children}
</motion.div>
)
}
interface CardHeaderProps {
title: string
subtitle?: string
action?: React.ReactNode
className?: string
}
export function CardHeader({ title, subtitle, action, className }: CardHeaderProps) {
return (
<div className={cn('flex items-start justify-between mb-6', className)}>
<div>
<h3 className="text-base font-semibold text-text-primary">{title}</h3>
{subtitle && (
<p className="text-sm text-text-secondary mt-0.5">{subtitle}</p>
)}
</div>
{action && <div>{action}</div>}
</div>
)
}
+26
View File
@@ -0,0 +1,26 @@
import { cn } from '@/lib/utils'
interface EmptyStateProps {
icon?: React.ReactNode
title: string
description?: string
action?: React.ReactNode
className?: string
}
export function EmptyState({ icon, title, description, action, className }: EmptyStateProps) {
return (
<div className={cn('flex flex-col items-center justify-center py-16 text-center', className)}>
{icon && (
<div className="mb-4 text-text-secondary/40">
{icon}
</div>
)}
<h3 className="text-sm font-medium text-text-primary">{title}</h3>
{description && (
<p className="mt-1 text-sm text-text-secondary max-w-sm">{description}</p>
)}
{action && <div className="mt-4">{action}</div>}
</div>
)
}
+47
View File
@@ -0,0 +1,47 @@
import { cn } from '@/lib/utils'
import { forwardRef } from 'react'
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
label?: string
error?: string
icon?: React.ReactNode
}
export const Input = forwardRef<HTMLInputElement, InputProps>(
({ label, error, icon, className, ...props }, ref) => {
return (
<div className="w-full">
{label && (
<label className="block text-sm font-medium text-text-primary mb-1.5">
{label}
</label>
)}
<div className="relative">
{icon && (
<div className="absolute left-3.5 top-1/2 -translate-y-1/2 text-text-secondary">
{icon}
</div>
)}
<input
ref={ref}
className={cn(
'w-full h-11 rounded-[14px] border bg-surface text-text-primary text-sm',
'placeholder:text-text-secondary/50',
'focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary/30',
'transition-all duration-150',
icon ? 'pl-10 pr-4' : 'px-4',
error && 'border-danger focus:ring-danger/20 focus:border-danger/30',
className
)}
{...props}
/>
</div>
{error && (
<p className="mt-1.5 text-xs text-danger">{error}</p>
)}
</div>
)
}
)
Input.displayName = 'Input'
+32
View File
@@ -0,0 +1,32 @@
import { cn } from '@/lib/utils'
interface SkeletonProps {
className?: string
}
export function Skeleton({ className }: SkeletonProps) {
return (
<div
className={cn(
'animate-pulse rounded-[14px] bg-text-secondary/8',
className
)}
/>
)
}
export function SkeletonText({ lines = 1, className }: { lines?: number; className?: string }) {
return (
<div className={cn('space-y-2', className)}>
{Array.from({ length: lines }).map((_, i) => (
<Skeleton
key={i}
className={cn(
'h-4',
i === lines - 1 && lines > 1 ? 'w-3/4' : 'w-full'
)}
/>
))}
</div>
)
}
+36
View File
@@ -0,0 +1,36 @@
import { cn } from '@/lib/utils'
import { useState } from 'react'
interface SwitchProps {
checked?: boolean
onChange?: (checked: boolean) => void
className?: string
}
export function Switch({ checked = false, onChange, className }: SwitchProps) {
const [isOn, setIsOn] = useState(checked)
const toggle = () => {
const newValue = !isOn
setIsOn(newValue)
onChange?.(newValue)
}
return (
<button
onClick={toggle}
className={cn(
'w-9 h-5 rounded-full relative transition-colors duration-200',
isOn ? 'bg-success' : 'bg-text-secondary/20',
className
)}
>
<div
className={cn(
'absolute top-0.5 w-4 h-4 rounded-full bg-white shadow-sm transition-transform duration-200',
isOn ? 'translate-x-4.5' : 'translate-x-0.5'
)}
/>
</button>
)
}
+85
View File
@@ -0,0 +1,85 @@
import { cn } from '@/lib/utils'
interface TableProps {
children: React.ReactNode
className?: string
}
export function Table({ children, className }: TableProps) {
return (
<div className="w-full overflow-x-auto">
<table className={cn('w-full', className)}>
{children}
</table>
</div>
)
}
export function TableHead({ children, className }: TableProps) {
return (
<thead className={cn('border-b border-border', className)}>
{children}
</thead>
)
}
export function TableBody({ children, className }: TableProps) {
return <tbody className={className}>{children}</tbody>
}
export function TableRow({ children, className }: TableProps) {
return (
<tr className={cn('border-b border-border/50 transition-colors hover:bg-bg/50', className)}>
{children}
</tr>
)
}
interface TableCellProps {
children?: React.ReactNode
className?: string
align?: 'left' | 'center' | 'right'
colSpan?: number
}
export function TableHeader({ children, className, align = 'left', colSpan }: TableCellProps) {
const alignClass = {
left: 'text-left',
center: 'text-center',
right: 'text-right',
}
return (
<th
colSpan={colSpan}
className={cn(
'py-3 px-4 text-xs font-medium text-text-secondary uppercase tracking-wider',
alignClass[align],
className
)}
>
{children}
</th>
)
}
export function TableCell({ children, className, align = 'left', colSpan }: TableCellProps) {
const alignClass = {
left: 'text-left',
center: 'text-center',
right: 'text-right',
}
return (
<td
colSpan={colSpan}
className={cn(
'py-3.5 px-4 text-sm text-text-primary',
alignClass[align],
className
)}
>
{children}
</td>
)
}