Move desktop and e2e into examples/ directory
- desktop/ → examples/django-react-desktop-app/ - e2e/ → examples/django-react-site/ - example/ → examples/django-react-site/backend/ - Update Dockerfile.test, Makefile, playwright config, and django.config.mjs path references Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,7 +12,7 @@ COPY django/ /app/django/
|
|||||||
RUN pip install --no-cache-dir /app/django[channels] daphne
|
RUN pip install --no-cache-dir /app/django[channels] daphne
|
||||||
|
|
||||||
# Copy example app
|
# Copy example app
|
||||||
COPY example/ /app/example/
|
COPY examples/django-react-site/backend/ /app/example/
|
||||||
|
|
||||||
WORKDIR /app/example
|
WORKDIR /app/example
|
||||||
|
|
||||||
|
|||||||
2
Makefile
2
Makefile
@@ -43,4 +43,4 @@ clean:
|
|||||||
docker compose -f docker-compose.test.yml down -v --remove-orphans 2>/dev/null || true
|
docker compose -f docker-compose.test.yml down -v --remove-orphans 2>/dev/null || true
|
||||||
rm -rf django/src/mizan.egg-info django/dist django/build
|
rm -rf django/src/mizan.egg-info django/dist django/build
|
||||||
rm -rf react/dist react/node_modules
|
rm -rf react/dist react/node_modules
|
||||||
rm -f example/db.sqlite3
|
rm -f examples/django-react-site/backend/db.sqlite3
|
||||||
|
|||||||
@@ -276,7 +276,7 @@ cd react && npm test
|
|||||||
|
|
||||||
# E2E integration tests (real browser, real backend)
|
# E2E integration tests (real browser, real backend)
|
||||||
docker compose -f docker-compose.test.yml up -d
|
docker compose -f docker-compose.test.yml up -d
|
||||||
cd e2e/harness && npm install && npx mizan-generate && npx vite --port 5174 &
|
cd examples/django-react-site/harness && npm install && npx mizan-generate && npx vite --port 5174 &
|
||||||
npx playwright test
|
npx playwright test
|
||||||
|
|
||||||
# All at once
|
# All at once
|
||||||
@@ -289,8 +289,8 @@ make test-all
|
|||||||
mizan/
|
mizan/
|
||||||
django/ Python package (mizan)
|
django/ Python package (mizan)
|
||||||
react/ TypeScript package (@rythazhur/mizan)
|
react/ TypeScript package (@rythazhur/mizan)
|
||||||
example/ Integration test backend (Docker)
|
examples/
|
||||||
desktop/ PyWebView desktop test app
|
django-react-site/ E2E tests, React harness, Django backend
|
||||||
e2e/ Playwright E2E tests + React harness
|
django-react-desktop-app/ PyWebView desktop app
|
||||||
Makefile Test orchestration
|
Makefile Test orchestration
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -2,17 +2,17 @@ import path from 'path'
|
|||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
|
|
||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||||
const root = path.resolve(__dirname, '../..')
|
const root = path.resolve(__dirname, '../../..')
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
projectId: 'e2e-harness',
|
projectId: 'e2e-harness',
|
||||||
|
|
||||||
source: {
|
source: {
|
||||||
django: {
|
django: {
|
||||||
managePath: path.join(root, 'example/manage.py'),
|
managePath: path.join(root, 'examples/django-react-site/backend/manage.py'),
|
||||||
command: [path.join(root, 'django/.venv/bin/python')],
|
command: [path.join(root, 'django/.venv/bin/python')],
|
||||||
env: {
|
env: {
|
||||||
PYTHONPATH: `${path.join(root, 'django/src')}:${path.join(root, 'example')}`,
|
PYTHONPATH: `${path.join(root, 'django/src')}:${path.join(root, 'examples/django-react-site/backend')}`,
|
||||||
DJANGO_SETTINGS_MODULE: 'testapp.settings',
|
DJANGO_SETTINGS_MODULE: 'testapp.settings',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
// AUTO-GENERATED by mizan - do not edit manually
|
||||||
|
// Regenerate with: npm run schemas
|
||||||
|
|
||||||
|
import { useChannel, type ChannelSubscription } from 'mizan/channels'
|
||||||
|
|
||||||
|
import type { ChatParams, ChatReactMessage, ChatDjangoMessage, NotificationsDjangoMessage, PresenceDjangoMessage, PrivateDjangoMessage } from './generated.channels'
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Channel Hooks
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook for the chat channel.
|
||||||
|
*/
|
||||||
|
export function useChatChannel(params: ChatParams): ChannelSubscription<ChatParams, ChatDjangoMessage, ChatReactMessage> {
|
||||||
|
return useChannel('chat', params)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook for the notifications channel.
|
||||||
|
*/
|
||||||
|
export function useNotificationsChannel(): ChannelSubscription<Record<string, never>, NotificationsDjangoMessage, never> {
|
||||||
|
return useChannel('notifications', {})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook for the presence channel.
|
||||||
|
*/
|
||||||
|
export function usePresenceChannel(): ChannelSubscription<Record<string, never>, PresenceDjangoMessage, never> {
|
||||||
|
return useChannel('presence', {})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook for the private channel.
|
||||||
|
*/
|
||||||
|
export function usePrivateChannel(): ChannelSubscription<Record<string, never>, PrivateDjangoMessage, never> {
|
||||||
|
return useChannel('private', {})
|
||||||
|
}
|
||||||
@@ -0,0 +1,268 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.1.0",
|
||||||
|
"info": {
|
||||||
|
"title": "mizan Channels",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Auto-generated schema for mizan channels"
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"/channels/chat/params": {
|
||||||
|
"post": {
|
||||||
|
"operationId": "chatParams",
|
||||||
|
"summary": "Chat channel params",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/BaseModel"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ChatParams"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/channels/chat/react": {
|
||||||
|
"post": {
|
||||||
|
"operationId": "chatReactMessage",
|
||||||
|
"summary": "Chat React→Django message",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/BaseModel"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ChatReactMessage"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/channels/chat/django": {
|
||||||
|
"post": {
|
||||||
|
"operationId": "chatDjangoMessage",
|
||||||
|
"summary": "Chat Django→React message",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ChatDjangoMessage"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/channels/notifications/django": {
|
||||||
|
"post": {
|
||||||
|
"operationId": "notificationsDjangoMessage",
|
||||||
|
"summary": "Notifications Django→React message",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/NotificationsDjangoMessage"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/channels/presence/django": {
|
||||||
|
"post": {
|
||||||
|
"operationId": "presenceDjangoMessage",
|
||||||
|
"summary": "Presence Django→React message",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/PresenceDjangoMessage"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/channels/private/django": {
|
||||||
|
"post": {
|
||||||
|
"operationId": "privateDjangoMessage",
|
||||||
|
"summary": "Private Django→React message",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/PrivateDjangoMessage"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"BaseModel": {
|
||||||
|
"properties": {},
|
||||||
|
"title": "BaseModel",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"ChatParams": {
|
||||||
|
"properties": {
|
||||||
|
"room": {
|
||||||
|
"title": "Room",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"room"
|
||||||
|
],
|
||||||
|
"title": "ChatParams",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"ChatReactMessage": {
|
||||||
|
"properties": {
|
||||||
|
"text": {
|
||||||
|
"title": "Text",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"text"
|
||||||
|
],
|
||||||
|
"title": "ChatReactMessage",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"ChatDjangoMessage": {
|
||||||
|
"properties": {
|
||||||
|
"text": {
|
||||||
|
"title": "Text",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"text"
|
||||||
|
],
|
||||||
|
"title": "ChatDjangoMessage",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"NotificationsDjangoMessage": {
|
||||||
|
"properties": {
|
||||||
|
"text": {
|
||||||
|
"title": "Text",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"text"
|
||||||
|
],
|
||||||
|
"title": "NotificationsDjangoMessage",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"PresenceDjangoMessage": {
|
||||||
|
"properties": {
|
||||||
|
"value": {
|
||||||
|
"title": "Value",
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"value"
|
||||||
|
],
|
||||||
|
"title": "PresenceDjangoMessage",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"PrivateDjangoMessage": {
|
||||||
|
"properties": {
|
||||||
|
"text": {
|
||||||
|
"title": "Text",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"text"
|
||||||
|
],
|
||||||
|
"title": "PrivateDjangoMessage",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"servers": [],
|
||||||
|
"x-mizan-channels": [
|
||||||
|
{
|
||||||
|
"name": "chat",
|
||||||
|
"pascalName": "Chat",
|
||||||
|
"hasParams": true,
|
||||||
|
"hasReactMessage": true,
|
||||||
|
"hasDjangoMessage": true,
|
||||||
|
"paramsType": "ChatParams",
|
||||||
|
"reactMessageType": "ChatReactMessage",
|
||||||
|
"djangoMessageType": "ChatDjangoMessage"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "notifications",
|
||||||
|
"pascalName": "Notifications",
|
||||||
|
"hasParams": false,
|
||||||
|
"hasReactMessage": false,
|
||||||
|
"hasDjangoMessage": true,
|
||||||
|
"djangoMessageType": "NotificationsDjangoMessage"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "presence",
|
||||||
|
"pascalName": "Presence",
|
||||||
|
"hasParams": false,
|
||||||
|
"hasReactMessage": false,
|
||||||
|
"hasDjangoMessage": true,
|
||||||
|
"djangoMessageType": "PresenceDjangoMessage"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "private",
|
||||||
|
"pascalName": "Private",
|
||||||
|
"hasParams": false,
|
||||||
|
"hasReactMessage": false,
|
||||||
|
"hasDjangoMessage": true,
|
||||||
|
"djangoMessageType": "PrivateDjangoMessage"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
337
examples/django-react-site/harness/src/api/generated.channels.ts
Normal file
337
examples/django-react-site/harness/src/api/generated.channels.ts
Normal file
@@ -0,0 +1,337 @@
|
|||||||
|
// AUTO-GENERATED by mizan - do not edit manually
|
||||||
|
// Regenerate with: npm run schemas
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// OpenAPI Types (generated by openapi-typescript)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export interface paths {
|
||||||
|
"/channels/chat/params": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get?: never;
|
||||||
|
put?: never;
|
||||||
|
/** Chat channel params */
|
||||||
|
post: operations["chatParams"];
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/channels/chat/react": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get?: never;
|
||||||
|
put?: never;
|
||||||
|
/** Chat React→Django message */
|
||||||
|
post: operations["chatReactMessage"];
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/channels/chat/django": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get?: never;
|
||||||
|
put?: never;
|
||||||
|
/** Chat Django→React message */
|
||||||
|
post: operations["chatDjangoMessage"];
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/channels/notifications/django": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get?: never;
|
||||||
|
put?: never;
|
||||||
|
/** Notifications Django→React message */
|
||||||
|
post: operations["notificationsDjangoMessage"];
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/channels/presence/django": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get?: never;
|
||||||
|
put?: never;
|
||||||
|
/** Presence Django→React message */
|
||||||
|
post: operations["presenceDjangoMessage"];
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/channels/private/django": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get?: never;
|
||||||
|
put?: never;
|
||||||
|
/** Private Django→React message */
|
||||||
|
post: operations["privateDjangoMessage"];
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export type webhooks = Record<string, never>;
|
||||||
|
export interface components {
|
||||||
|
schemas: {
|
||||||
|
/** BaseModel */
|
||||||
|
BaseModel: Record<string, never>;
|
||||||
|
/** ChatParams */
|
||||||
|
ChatParams: {
|
||||||
|
/** Room */
|
||||||
|
room: string;
|
||||||
|
};
|
||||||
|
/** ChatReactMessage */
|
||||||
|
ChatReactMessage: {
|
||||||
|
/** Text */
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
/** ChatDjangoMessage */
|
||||||
|
ChatDjangoMessage: {
|
||||||
|
/** Text */
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
/** NotificationsDjangoMessage */
|
||||||
|
NotificationsDjangoMessage: {
|
||||||
|
/** Text */
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
/** PresenceDjangoMessage */
|
||||||
|
PresenceDjangoMessage: {
|
||||||
|
/** Value */
|
||||||
|
value: number;
|
||||||
|
};
|
||||||
|
/** PrivateDjangoMessage */
|
||||||
|
PrivateDjangoMessage: {
|
||||||
|
/** Text */
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: never;
|
||||||
|
parameters: never;
|
||||||
|
requestBodies: never;
|
||||||
|
headers: never;
|
||||||
|
pathItems: never;
|
||||||
|
}
|
||||||
|
export type $defs = Record<string, never>;
|
||||||
|
export interface operations {
|
||||||
|
chatParams: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["ChatParams"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["BaseModel"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
chatReactMessage: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["ChatReactMessage"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["BaseModel"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
chatDjangoMessage: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["ChatDjangoMessage"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
notificationsDjangoMessage: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["NotificationsDjangoMessage"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
presenceDjangoMessage: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["PresenceDjangoMessage"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
privateDjangoMessage: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["PrivateDjangoMessage"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Convenience Type Exports
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export type ChatParams = components["schemas"]["ChatParams"]
|
||||||
|
export type ChatReactMessage = components["schemas"]["ChatReactMessage"]
|
||||||
|
export type ChatDjangoMessage = components["schemas"]["ChatDjangoMessage"]
|
||||||
|
export type NotificationsDjangoMessage = components["schemas"]["NotificationsDjangoMessage"]
|
||||||
|
export type PresenceDjangoMessage = components["schemas"]["PresenceDjangoMessage"]
|
||||||
|
export type PrivateDjangoMessage = components["schemas"]["PrivateDjangoMessage"]
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Channel Registry
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export const CHANNELS = {
|
||||||
|
chat: {
|
||||||
|
name: 'chat',
|
||||||
|
pascalName: 'Chat',
|
||||||
|
hasParams: true,
|
||||||
|
hasReactMessage: true,
|
||||||
|
hasDjangoMessage: true,
|
||||||
|
paramsType: 'ChatParams',
|
||||||
|
reactMessageType: 'ChatReactMessage',
|
||||||
|
djangoMessageType: 'ChatDjangoMessage',
|
||||||
|
},
|
||||||
|
notifications: {
|
||||||
|
name: 'notifications',
|
||||||
|
pascalName: 'Notifications',
|
||||||
|
hasParams: false,
|
||||||
|
hasReactMessage: false,
|
||||||
|
hasDjangoMessage: true,
|
||||||
|
djangoMessageType: 'NotificationsDjangoMessage',
|
||||||
|
},
|
||||||
|
presence: {
|
||||||
|
name: 'presence',
|
||||||
|
pascalName: 'Presence',
|
||||||
|
hasParams: false,
|
||||||
|
hasReactMessage: false,
|
||||||
|
hasDjangoMessage: true,
|
||||||
|
djangoMessageType: 'PresenceDjangoMessage',
|
||||||
|
},
|
||||||
|
private: {
|
||||||
|
name: 'private',
|
||||||
|
pascalName: 'Private',
|
||||||
|
hasParams: false,
|
||||||
|
hasReactMessage: false,
|
||||||
|
hasDjangoMessage: true,
|
||||||
|
djangoMessageType: 'PrivateDjangoMessage',
|
||||||
|
},
|
||||||
|
} as const
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
// AUTO-GENERATED by mizan - do not edit manually
|
||||||
|
// Regenerate with: npm run schemas
|
||||||
|
//
|
||||||
|
// Server-side functions for SSR hydration.
|
||||||
|
// These run in Next.js server components/layouts.
|
||||||
|
|
||||||
|
import type { currentUserOutput, greetOutput } from './generated.mizan'
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Hydration Types
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/** Typed hydration data for SSR */
|
||||||
|
export interface DjangoHydration {
|
||||||
|
currentUser?: currentUserOutput
|
||||||
|
greet?: greetOutput
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// SSR Hydration Helper
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch hydration data for SSR.
|
||||||
|
*
|
||||||
|
* Call this in your server component:
|
||||||
|
* const hydration = await getDjangoHydration(client)
|
||||||
|
* return <DjangoContext hydration={hydration}>...</DjangoContext>
|
||||||
|
*/
|
||||||
|
export async function getDjangoHydration(
|
||||||
|
client: { request: (method: string, url: string, body?: unknown) => Promise<Response> }
|
||||||
|
): Promise<DjangoHydration> {
|
||||||
|
const hydration: DjangoHydration = {}
|
||||||
|
|
||||||
|
const results = await Promise.allSettled([
|
||||||
|
client.request('POST', '/api/mizan/call/', { fn: 'current_user', args: {} }),
|
||||||
|
client.request('POST', '/api/mizan/call/', { fn: 'greet', args: {} }),
|
||||||
|
])
|
||||||
|
|
||||||
|
if (results[0].status === 'fulfilled') {
|
||||||
|
const data = await (results[0] as PromiseFulfilledResult<Response>).value.json()
|
||||||
|
if (data.error) {
|
||||||
|
console.error('[getDjangoHydration] current_user failed:', data.code, data.message)
|
||||||
|
} else {
|
||||||
|
hydration.currentUser = data.data
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error('[getDjangoHydration] current_user request failed:', (results[0] as PromiseRejectedResult).reason)
|
||||||
|
}
|
||||||
|
if (results[1].status === 'fulfilled') {
|
||||||
|
const data = await (results[1] as PromiseFulfilledResult<Response>).value.json()
|
||||||
|
if (data.error) {
|
||||||
|
console.error('[getDjangoHydration] greet failed:', data.code, data.message)
|
||||||
|
} else {
|
||||||
|
hydration.greet = data.data
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error('[getDjangoHydration] greet request failed:', (results[1] as PromiseRejectedResult).reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
return hydration
|
||||||
|
}
|
||||||
257
examples/django-react-site/harness/src/api/generated.django.tsx
Normal file
257
examples/django-react-site/harness/src/api/generated.django.tsx
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
// AUTO-GENERATED by mizan - do not edit manually
|
||||||
|
// Regenerate with: npm run schemas
|
||||||
|
|
||||||
|
// This file provides typed wrappers around the mizan library.
|
||||||
|
// - DjangoContext: Typed provider wrapping mizanProvider
|
||||||
|
// - Typed hooks: useAuthStatus(), useUser(), etc.
|
||||||
|
|
||||||
|
import { type ReactNode, useCallback } from 'react'
|
||||||
|
import {
|
||||||
|
mizanProvider,
|
||||||
|
usemizan,
|
||||||
|
usemizanContext,
|
||||||
|
usemizanCall,
|
||||||
|
type mizanHydration,
|
||||||
|
type Transport,
|
||||||
|
} from 'mizan'
|
||||||
|
import { ChannelProvider, ChannelConnection } from 'mizan/channels'
|
||||||
|
import { useRef } from 'react'
|
||||||
|
|
||||||
|
import type { addEmailSchemaOutput, addEmailValidateInput, addEmailValidateOutput, addInput, addOutput, buggyFnOutput, contactSchemaInput, contactSchemaOutput, contactSubmitOutput, contactValidateInput, contactValidateOutput, currentUserOutput, echoInput, echoOutput, greetInput, greetOutput, httpOnlyEchoInput, httpOnlyEchoOutput, itemFormsetSchemaInput, itemFormsetSchemaOutput, itemFormsetSubmitInput, itemFormsetSubmitOutput, itemFormsetValidateInput, itemFormsetValidateOutput, itemSchemaInput, itemSchemaOutput, itemSubmitOutput, itemValidateInput, itemValidateOutput, jwtObtainOutput, jwtRefreshInput, jwtRefreshOutput, loginSchemaOutput, loginSubmitInput, loginSubmitOutput, loginValidateInput, loginValidateOutput, multiplyInput, multiplyOutput, notImplementedFnOutput, permissionCheckFnInput, permissionCheckFnOutput, signupSchemaOutput, signupSubmitInput, signupSubmitOutput, signupValidateInput, signupValidateOutput, staffOnlyOutput, superuserOnlyOutput, verifiedOnlyOutput, whoamiOutput, wsWhoamiOutput } from './generated.mizan'
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Hydration Types
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/** Typed hydration data for SSR */
|
||||||
|
export interface DjangoHydration {
|
||||||
|
currentUser?: currentUserOutput
|
||||||
|
greet?: greetOutput
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convert typed hydration to mizan format */
|
||||||
|
function tomizanHydration(hydration?: DjangoHydration): mizanHydration | undefined {
|
||||||
|
if (!hydration) return undefined
|
||||||
|
const result: mizanHydration = {}
|
||||||
|
if (hydration.currentUser !== undefined) result['current_user'] = hydration.currentUser
|
||||||
|
if (hydration.greet !== undefined) result['greet'] = hydration.greet
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Provider
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export interface DjangoContextProps {
|
||||||
|
children: ReactNode
|
||||||
|
/** SSR hydration data */
|
||||||
|
hydration?: DjangoHydration
|
||||||
|
/** WebSocket URL for RPC calls (default: /ws/) */
|
||||||
|
wsUrl?: string
|
||||||
|
/** Base URL for HTTP fallback (default: /api/mizan) */
|
||||||
|
baseUrl?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Typed Django context provider.
|
||||||
|
*
|
||||||
|
* Wraps mizanProvider with:
|
||||||
|
* - Typed hydration
|
||||||
|
* - Auto-fetch for registered contexts
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* <DjangoContext hydration={hydration}>
|
||||||
|
* <App />
|
||||||
|
* </DjangoContext>
|
||||||
|
*/
|
||||||
|
export function DjangoContext({
|
||||||
|
children,
|
||||||
|
hydration,
|
||||||
|
wsUrl,
|
||||||
|
baseUrl,
|
||||||
|
}: DjangoContextProps) {
|
||||||
|
const connectionRef = useRef<ChannelConnection | null>(null)
|
||||||
|
if (!connectionRef.current) {
|
||||||
|
connectionRef.current = new ChannelConnection({ url: wsUrl || '/ws/' })
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<mizanProvider
|
||||||
|
hydration={tomizanHydration(hydration)}
|
||||||
|
contexts={['current_user', 'greet']}
|
||||||
|
wsUrl={wsUrl}
|
||||||
|
baseUrl={baseUrl}
|
||||||
|
connection={connectionRef.current}
|
||||||
|
>
|
||||||
|
<ChannelProvider connection={connectionRef.current} autoConnect={true}>
|
||||||
|
{children}
|
||||||
|
</ChannelProvider>
|
||||||
|
</mizanProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Context Hooks (typed wrappers)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current_user context data.
|
||||||
|
* @throws if context not loaded yet
|
||||||
|
*/
|
||||||
|
export function useCurrentUser(): currentUserOutput {
|
||||||
|
const data = usemizanContext<currentUserOutput>('current_user')
|
||||||
|
if (data === undefined) {
|
||||||
|
throw new Error('useCurrentUser: context not loaded yet')
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get greet context data.
|
||||||
|
* @throws if context not loaded yet
|
||||||
|
*/
|
||||||
|
export function useGreet(): greetOutput {
|
||||||
|
const data = usemizanContext<greetOutput>('greet')
|
||||||
|
if (data === undefined) {
|
||||||
|
throw new Error('useGreet: context not loaded yet')
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get context refresh functions without subscribing to data changes.
|
||||||
|
* Use this in components that only need to trigger refreshes.
|
||||||
|
*/
|
||||||
|
export function useDjangoRefresh() {
|
||||||
|
const { refreshContext, refreshAllContexts } = usemizan()
|
||||||
|
return {
|
||||||
|
refreshCurrentUser: () => refreshContext('current_user'),
|
||||||
|
refreshGreet: () => refreshContext('greet'),
|
||||||
|
refreshAll: refreshAllContexts,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Function Hooks (typed wrappers)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call echo server function.
|
||||||
|
* Transport: websocket
|
||||||
|
*/
|
||||||
|
export function useEcho() {
|
||||||
|
return usemizanCall<echoInput, echoOutput>('echo', 'websocket')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call add server function.
|
||||||
|
* Transport: websocket
|
||||||
|
*/
|
||||||
|
export function useAdd() {
|
||||||
|
return usemizanCall<addInput, addOutput>('add', 'websocket')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call whoami server function.
|
||||||
|
* Transport: http
|
||||||
|
*/
|
||||||
|
export function useWhoami() {
|
||||||
|
return usemizanCall<void, whoamiOutput>('whoami', 'http')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call http_only_echo server function.
|
||||||
|
* Transport: http
|
||||||
|
*/
|
||||||
|
export function useHttpOnlyEcho() {
|
||||||
|
return usemizanCall<httpOnlyEchoInput, httpOnlyEchoOutput>('http_only_echo', 'http')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call staff_only server function.
|
||||||
|
* Transport: http
|
||||||
|
*/
|
||||||
|
export function useStaffOnly() {
|
||||||
|
return usemizanCall<void, staffOnlyOutput>('staff_only', 'http')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call superuser_only server function.
|
||||||
|
* Transport: http
|
||||||
|
*/
|
||||||
|
export function useSuperuserOnly() {
|
||||||
|
return usemizanCall<void, superuserOnlyOutput>('superuser_only', 'http')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call verified_only server function.
|
||||||
|
* Transport: http
|
||||||
|
*/
|
||||||
|
export function useVerifiedOnly() {
|
||||||
|
return usemizanCall<void, verifiedOnlyOutput>('verified_only', 'http')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call multiply server function.
|
||||||
|
* Transport: http
|
||||||
|
*/
|
||||||
|
export function useMultiply() {
|
||||||
|
return usemizanCall<multiplyInput, multiplyOutput>('multiply', 'http')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call not_implemented_fn server function.
|
||||||
|
* Transport: http
|
||||||
|
*/
|
||||||
|
export function useNotImplementedFn() {
|
||||||
|
return usemizanCall<void, notImplementedFnOutput>('not_implemented_fn', 'http')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call buggy_fn server function.
|
||||||
|
* Transport: http
|
||||||
|
*/
|
||||||
|
export function useBuggyFn() {
|
||||||
|
return usemizanCall<void, buggyFnOutput>('buggy_fn', 'http')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call permission_check_fn server function.
|
||||||
|
* Transport: http
|
||||||
|
*/
|
||||||
|
export function usePermissionCheckFn() {
|
||||||
|
return usemizanCall<permissionCheckFnInput, permissionCheckFnOutput>('permission_check_fn', 'http')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call ws_whoami server function.
|
||||||
|
* Transport: websocket
|
||||||
|
*/
|
||||||
|
export function useWsWhoami() {
|
||||||
|
return usemizanCall<void, wsWhoamiOutput>('ws_whoami', 'websocket')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call jwt_obtain server function.
|
||||||
|
* Transport: http
|
||||||
|
*/
|
||||||
|
export function useJwtObtain() {
|
||||||
|
return usemizanCall<void, jwtObtainOutput>('jwt_obtain', 'http')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call jwt_refresh server function.
|
||||||
|
* Transport: http
|
||||||
|
*/
|
||||||
|
export function useJwtRefresh() {
|
||||||
|
return usemizanCall<jwtRefreshInput, jwtRefreshOutput>('jwt_refresh', 'http')
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Re-exports from mizan library
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export { usemizan, usemizanStatus, usePush, DjangoError } from 'mizan'
|
||||||
|
export type { ConnectionStatus, PushMessage, PushListener } from 'mizan'
|
||||||
File diff suppressed because it is too large
Load Diff
2123
examples/django-react-site/harness/src/api/generated.djarea.ts
Normal file
2123
examples/django-react-site/harness/src/api/generated.djarea.ts
Normal file
File diff suppressed because it is too large
Load Diff
226
examples/django-react-site/harness/src/api/generated.forms.ts
Normal file
226
examples/django-react-site/harness/src/api/generated.forms.ts
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
// AUTO-GENERATED by mizan - do not edit manually
|
||||||
|
// Regenerate with: npm run schemas
|
||||||
|
|
||||||
|
// Typed form hooks with Zod validation.
|
||||||
|
// Zod schemas are generated from Django form field definitions.
|
||||||
|
// Client-side validation matches Django constraints (required, max_length, email, etc.)
|
||||||
|
|
||||||
|
import { z } from 'zod'
|
||||||
|
import {
|
||||||
|
useDjangoFormCore,
|
||||||
|
useDjangoFormsetCore,
|
||||||
|
type DjangoFormState,
|
||||||
|
type DjangoFormsetState,
|
||||||
|
type FormOptions,
|
||||||
|
} from 'mizan'
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Zod Schemas
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zod schema for login form
|
||||||
|
* Generated from Django form field definitions
|
||||||
|
*/
|
||||||
|
export const LoginSchema = z.object({
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zod schema for signup form
|
||||||
|
* Generated from Django form field definitions
|
||||||
|
*/
|
||||||
|
export const SignupSchema = z.object({
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zod schema for add_email form
|
||||||
|
* Generated from Django form field definitions
|
||||||
|
*/
|
||||||
|
export const AddEmailSchema = z.object({
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zod schema for contact form
|
||||||
|
* Generated from Django form field definitions
|
||||||
|
*/
|
||||||
|
export const ContactSchema = z.object({
|
||||||
|
name: z.string().max(100),
|
||||||
|
email: z.string().email('Invalid email address').max(320),
|
||||||
|
message: z.string(),
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zod schema for item form
|
||||||
|
* Generated from Django form field definitions
|
||||||
|
*/
|
||||||
|
export const ItemSchema = z.object({
|
||||||
|
label: z.string().max(50),
|
||||||
|
quantity: z.number().int().min(1),
|
||||||
|
})
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Form Data Types (inferred from Zod schemas)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/** Form data type for login, inferred from Zod schema */
|
||||||
|
export type LoginFormData = z.infer<typeof LoginSchema>
|
||||||
|
|
||||||
|
/** Form data type for signup, inferred from Zod schema */
|
||||||
|
export type SignupFormData = z.infer<typeof SignupSchema>
|
||||||
|
|
||||||
|
/** Form data type for add_email, inferred from Zod schema */
|
||||||
|
export type AddEmailFormData = z.infer<typeof AddEmailSchema>
|
||||||
|
|
||||||
|
/** Form data type for contact, inferred from Zod schema */
|
||||||
|
export type ContactFormData = z.infer<typeof ContactSchema>
|
||||||
|
|
||||||
|
/** Form data type for item, inferred from Zod schema */
|
||||||
|
export type ItemFormData = z.infer<typeof ItemSchema>
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Form Hooks
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Typed form hook for login
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - Full TypeScript inference for form fields
|
||||||
|
* - Client-side Zod validation (instant feedback)
|
||||||
|
* - Server-side Django validation (authoritative)
|
||||||
|
*/
|
||||||
|
export function useLoginForm(
|
||||||
|
options?: FormOptions
|
||||||
|
): DjangoFormState<LoginFormData> {
|
||||||
|
return useDjangoFormCore<LoginFormData>({
|
||||||
|
name: 'login',
|
||||||
|
zodSchema: LoginSchema,
|
||||||
|
options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Typed form hook for signup
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - Full TypeScript inference for form fields
|
||||||
|
* - Client-side Zod validation (instant feedback)
|
||||||
|
* - Server-side Django validation (authoritative)
|
||||||
|
*/
|
||||||
|
export function useSignupForm(
|
||||||
|
options?: FormOptions
|
||||||
|
): DjangoFormState<SignupFormData> {
|
||||||
|
return useDjangoFormCore<SignupFormData>({
|
||||||
|
name: 'signup',
|
||||||
|
zodSchema: SignupSchema,
|
||||||
|
options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Typed form hook for add_email
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - Full TypeScript inference for form fields
|
||||||
|
* - Client-side Zod validation (instant feedback)
|
||||||
|
* - Server-side Django validation (authoritative)
|
||||||
|
*/
|
||||||
|
export function useAddEmailForm(
|
||||||
|
options?: FormOptions
|
||||||
|
): DjangoFormState<AddEmailFormData> {
|
||||||
|
return useDjangoFormCore<AddEmailFormData>({
|
||||||
|
name: 'add_email',
|
||||||
|
zodSchema: AddEmailSchema,
|
||||||
|
options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Typed form hook for contact
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - Full TypeScript inference for form fields
|
||||||
|
* - Client-side Zod validation (instant feedback)
|
||||||
|
* - Server-side Django validation (authoritative)
|
||||||
|
*/
|
||||||
|
export function useContactForm(
|
||||||
|
options?: FormOptions
|
||||||
|
): DjangoFormState<ContactFormData> {
|
||||||
|
return useDjangoFormCore<ContactFormData>({
|
||||||
|
name: 'contact',
|
||||||
|
zodSchema: ContactSchema,
|
||||||
|
options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Typed form hook for item
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - Full TypeScript inference for form fields
|
||||||
|
* - Client-side Zod validation (instant feedback)
|
||||||
|
* - Server-side Django validation (authoritative)
|
||||||
|
*/
|
||||||
|
export function useItemForm(
|
||||||
|
options?: FormOptions
|
||||||
|
): DjangoFormState<ItemFormData> {
|
||||||
|
return useDjangoFormCore<ItemFormData>({
|
||||||
|
name: 'item',
|
||||||
|
zodSchema: ItemSchema,
|
||||||
|
options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Typed formset hook for item
|
||||||
|
*/
|
||||||
|
export function useItemFormset(
|
||||||
|
initialCount?: number,
|
||||||
|
liveValidation?: boolean
|
||||||
|
): DjangoFormsetState<ItemFormData> {
|
||||||
|
return useDjangoFormsetCore<ItemFormData>({
|
||||||
|
name: 'item',
|
||||||
|
zodSchema: ItemSchema,
|
||||||
|
initialCount,
|
||||||
|
liveValidation,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Form Registry
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export const DJANGO_FORMS = {
|
||||||
|
login: {
|
||||||
|
name: 'login',
|
||||||
|
schema: LoginSchema,
|
||||||
|
hook: 'useLoginForm',
|
||||||
|
hasFormset: false,
|
||||||
|
},
|
||||||
|
signup: {
|
||||||
|
name: 'signup',
|
||||||
|
schema: SignupSchema,
|
||||||
|
hook: 'useSignupForm',
|
||||||
|
hasFormset: false,
|
||||||
|
},
|
||||||
|
addEmail: {
|
||||||
|
name: 'add_email',
|
||||||
|
schema: AddEmailSchema,
|
||||||
|
hook: 'useAddEmailForm',
|
||||||
|
hasFormset: false,
|
||||||
|
},
|
||||||
|
contact: {
|
||||||
|
name: 'contact',
|
||||||
|
schema: ContactSchema,
|
||||||
|
hook: 'useContactForm',
|
||||||
|
hasFormset: false,
|
||||||
|
},
|
||||||
|
item: {
|
||||||
|
name: 'item',
|
||||||
|
schema: ItemSchema,
|
||||||
|
hook: 'useItemForm',
|
||||||
|
hasFormset: true,
|
||||||
|
},
|
||||||
|
} as const
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"status": "failed",
|
||||||
|
"failedTests": []
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { defineConfig } from '@playwright/test'
|
import { defineConfig } from '@playwright/test'
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
testDir: './e2e',
|
testDir: './examples/django-react-site',
|
||||||
timeout: 15000,
|
timeout: 15000,
|
||||||
retries: 0,
|
retries: 0,
|
||||||
reporter: 'list',
|
reporter: 'list',
|
||||||
|
|||||||
Reference in New Issue
Block a user