BOC v1.0: RS256 auth, Ledger integration, Prometheus metrics, CI/CD, backup
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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'
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user