Flatten to three packages + extract mizan-runtime
packages/
mizan-runtime/ Framework-agnostic state engine (~150 lines)
Context registry, batched invalidation, fetch primitives
mizan-django/ Django server adapter (was packages/mizan-rpc/adapters/django/)
Codegen moved to mizan-django/generate/
mizan-react/ React adapter (was packages/mizan-csr/adapters/react/)
Removed premature abstractions: mizan-ast, mizan-schema, mizan-rpc,
mizan-csr, mizan-ssr stub packages. The actual architecture is three
concrete packages, not five abstract layers.
mizan-runtime implements the v1 spec: registerContext with params,
scoped invalidation via microtask batching, server-driven invalidation
from mutation responses, mizanFetch for context bundles, mizanCall for
mutations.
264 Django + 33 React tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useAllauthAPI } from '../../contexts/APIContext'
|
||||
import { useStyles } from '../../contexts/StylesContext'
|
||||
import { useDjangoFormCore } from 'mizan'
|
||||
import { SettingsSection, SettingsItem, SettingsList, Badge, Button } from './SettingsComponents'
|
||||
|
||||
interface Email {
|
||||
email: string
|
||||
primary: boolean
|
||||
verified: boolean
|
||||
}
|
||||
|
||||
export function EmailsSection() {
|
||||
const api = useAllauthAPI()
|
||||
const styles = useStyles()
|
||||
const [emails, setEmails] = useState<Email[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const addEmailForm = useDjangoFormCore<Record<string, unknown>>({ name: 'add_email' })
|
||||
|
||||
const fetchEmails = async () => {
|
||||
const res = await api.account.emails.list()
|
||||
if (res.status === 200 && res.data) {
|
||||
setEmails(res.data)
|
||||
}
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
useEffect(() => { fetchEmails() }, [])
|
||||
|
||||
const handleAdd = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
const result = await addEmailForm.submit()
|
||||
if (result.success) {
|
||||
addEmailForm.reset()
|
||||
fetchEmails()
|
||||
}
|
||||
}
|
||||
|
||||
const handleRemove = async (email: string) => {
|
||||
if (!confirm(`Remove ${email}?`)) return
|
||||
await api.account.emails.remove(email)
|
||||
fetchEmails()
|
||||
}
|
||||
|
||||
const handleSetPrimary = async (email: string) => {
|
||||
await api.account.emails.setPrimary(email)
|
||||
fetchEmails()
|
||||
}
|
||||
|
||||
const handleResendVerification = async (email: string) => {
|
||||
await api.account.emails.verification.dispatch(email)
|
||||
alert('Verification email sent!')
|
||||
}
|
||||
|
||||
if (loading) return null
|
||||
|
||||
return (
|
||||
<SettingsSection title="Email Addresses">
|
||||
<SettingsList>
|
||||
{emails.map(email => (
|
||||
<SettingsItem
|
||||
key={email.email}
|
||||
label={
|
||||
<>
|
||||
{email.email}
|
||||
{email.primary && <Badge variant="primary">Primary</Badge>}
|
||||
{!email.verified && <Badge variant="warning">Unverified</Badge>}
|
||||
</>
|
||||
}
|
||||
actions={
|
||||
<>
|
||||
{!email.verified && (
|
||||
<Button variant="secondary" onClick={() => handleResendVerification(email.email)}>
|
||||
Verify
|
||||
</Button>
|
||||
)}
|
||||
{!email.primary && email.verified && (
|
||||
<Button onClick={() => handleSetPrimary(email.email)}>
|
||||
Make Primary
|
||||
</Button>
|
||||
)}
|
||||
{!email.primary && (
|
||||
<Button variant="danger" onClick={() => handleRemove(email.email)}>
|
||||
Remove
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</SettingsList>
|
||||
|
||||
{!addEmailForm.loading && (
|
||||
<form onSubmit={handleAdd} className={styles.inlineForm}>
|
||||
<div className={styles.field}>
|
||||
<label className={styles.fieldLabel}>
|
||||
{addEmailForm.schema?.fields.email?.label || 'Add Email'}
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
value={(addEmailForm.data.email as string) || ''}
|
||||
onChange={(e) => addEmailForm.set('email', e.target.value)}
|
||||
onBlur={() => addEmailForm.touch('email')}
|
||||
className={styles.fieldInput}
|
||||
required
|
||||
/>
|
||||
{addEmailForm.getFieldErrors('email').map((err, i) => (
|
||||
<p key={i} className={styles.fieldError}>{err.message}</p>
|
||||
))}
|
||||
</div>
|
||||
<Button type="submit">
|
||||
{addEmailForm.schema?.submit_label || 'Add'}
|
||||
</Button>
|
||||
</form>
|
||||
)}
|
||||
</SettingsSection>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user