Files
mizan/legacy/allauth/components/AuthForm.tsx
Ryth Azhur 27c30d7e50 Move allauth + auth UI to legacy/
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>
2026-04-07 03:41:22 -04:00

100 lines
2.8 KiB
TypeScript

'use client'
import { ReactNode, useState, useEffect } from 'react'
import { AuthDetails, AuthError, AuthResponse, getAuthDetails } from '../api'
import { useAuthContext } from '../contexts/AuthContext'
import { useStyles } from '../contexts/StylesContext'
interface AuthForm {
submit: () => void
authDetails: AuthDetails
fetching: boolean
response: AuthResponse | null
errors: AuthError[]
}
export default function useAuthForm(
submissionAction: () => Promise<AuthResponse>,
responseAction?: (response: AuthResponse, authDetails: AuthDetails) => void,
): AuthForm {
const auth = useAuthContext().auth
const [fetching, setFetching] = useState<boolean>(false)
const [response, setResponse] = useState<AuthResponse | null>(null)
const [errors, setErrors] = useState<AuthError[]>([])
const [authDetails, setAuthDetails] = useState<AuthDetails>(getAuthDetails(auth))
function submit() {
setFetching(true)
submissionAction()
.then((r) => {
setResponse(r)
setErrors(r.errors || [])
setFetching(false)
if (r && responseAction) {
responseAction(r, authDetails)
}
setAuthDetails(getAuthDetails(auth))
})
.catch((e) => {
console.error(e)
setFetching(false)
})
}
return { submit, authDetails, fetching, response, errors }
}
interface AuthFieldProps {
title: string
name: string
type: string
init: string
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void
authErrors: AuthError[]
placeholder?: string
children?: ReactNode
}
export function AuthField({
title,
name,
type,
init,
onChange,
authErrors,
placeholder,
children,
}: AuthFieldProps) {
const styles = useStyles()
const [mounted, setMounted] = useState(false)
const fieldErrors = authErrors.filter(err => err.param === name)
useEffect(() => {
setMounted(true)
}, [])
return (
<div className={styles.field}>
<label className={styles.fieldLabel}>{title}</label>
{mounted ? (
<input
type={type}
value={init}
onChange={onChange}
placeholder={placeholder}
className={styles.fieldInput}
autoComplete="off"
/>
) : (
<div className={styles.fieldInput} style={{ minHeight: '2.75rem' }} />
)}
{fieldErrors.map((err, i) => (
<p key={i} className={styles.fieldError}>{err.message}</p>
))}
{children}
</div>
)
}