allauth/ (44 files) is a django-allauth React UI — a separate concern from the Mizan protocol. Moved to legacy/ pending extraction into a standalone mizan-django-allauth package. Also moved to legacy/: - client/AuthContext.tsx — generic auth state from /me endpoint - client/RouterContext.tsx — framework-agnostic router adapter - client/routing.tsx — UserRoute/StaffRoute/AnonymousRoute guards - client/nextjs.tsx — Next.js router adapter for auth These are auth UI infrastructure, not Mizan protocol. The Mizan core only needs JWT for auth header selection (jwt/ stays — MizanProvider depends on useJWT() to decide between Bearer and session auth). Cleaned up re-exports in client/react.ts and vitest aliases. 33 React tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
86 lines
2.7 KiB
TypeScript
86 lines
2.7 KiB
TypeScript
'use client'
|
|
|
|
import { ReactNode } from 'react'
|
|
import { useRouter } from '../contexts/RouterContext'
|
|
import { useStyles } from '../contexts/StylesContext'
|
|
|
|
interface FooterLink {
|
|
label: string
|
|
href?: string
|
|
onClick?: () => void
|
|
}
|
|
|
|
interface AuthCardProps {
|
|
title: string
|
|
subtitle?: string
|
|
children?: ReactNode
|
|
footerLinks?: FooterLink[]
|
|
error?: string
|
|
success?: string
|
|
loading?: boolean
|
|
loadingText?: string
|
|
}
|
|
|
|
export function AuthCard({
|
|
title,
|
|
subtitle,
|
|
children,
|
|
footerLinks,
|
|
error,
|
|
success,
|
|
loading,
|
|
loadingText = 'Loading...',
|
|
}: AuthCardProps) {
|
|
const router = useRouter()
|
|
const styles = useStyles()
|
|
|
|
const handleLinkClick = (e: React.MouseEvent, href: string) => {
|
|
e.preventDefault()
|
|
router.push(href)
|
|
}
|
|
|
|
return (
|
|
<div className={styles.container}>
|
|
<div className={styles.card}>
|
|
{loading ? (
|
|
<div className={styles.loading}>
|
|
<div className={styles.spinner} />
|
|
<p className={styles.subtitle}>{loadingText}</p>
|
|
</div>
|
|
) : (
|
|
<>
|
|
<h1 className={styles.title}>{title}</h1>
|
|
{subtitle && <p className={styles.subtitle}>{subtitle}</p>}
|
|
|
|
{error && <div className={styles.error}>{error}</div>}
|
|
{success && <div className={styles.success}>{success}</div>}
|
|
|
|
{children}
|
|
|
|
{footerLinks && footerLinks.length > 0 && (
|
|
<div className={styles.footer}>
|
|
{footerLinks.map((link, i) => (
|
|
link.onClick ? (
|
|
<button key={i} onClick={link.onClick} className={styles.link}>
|
|
{link.label}
|
|
</button>
|
|
) : link.href ? (
|
|
<a
|
|
key={i}
|
|
href={link.href}
|
|
onClick={(e) => handleLinkClick(e, link.href!)}
|
|
className={styles.link}
|
|
>
|
|
{link.label}
|
|
</a>
|
|
) : null
|
|
))}
|
|
</div>
|
|
)}
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|