Files
WooNooW/admin-spa/src/routes/Orders/New.tsx
Dwindi Ramadhana 396ca25be4 feat: Page Editor v1.0 - canonical schema, SSR parity, and migration
Major improvements to WooNooW Page Editor system:

Schema & Architecture:
- Canonical section schema with unified sectionSchema.ts
- Normalized feature-grid to use items (not features)
- Standardized default values across all section types
- Schema versioning with automatic migration on read

Backend (PHP):
- Enhanced PlaceholderRenderer with typed output contracts
- Added fallback behavior for empty/invalid dynamic sources
- Added caching support for post data resolution
- New SchemaMigration class for backward compatibility
- New Features class for feature flags
- Enhanced PageSSR with full style support
- Removed controller-level special-casing for related_posts

Frontend (Admin SPA):
- Updated CanvasRenderer with schema-aware transformation
- Enhanced InspectorPanel with canonical schema metadata
- Added new section renderers

Frontend (Customer SPA):
- New section components: BentoCategoryGrid, MarqueeBanner, ProductCarousel, ShoppableImage
- Updated FeatureGridSection for items prop contract

Testing:
- Add PHP tests: SchemaMigrationTest, PlaceholderRendererTest, PageSSRTest
- Add TypeScript tests: schema-integration, feature-grid-regression
- Add parity tests for React vs SSR content matching
- Add CI script: check-schema-drift.mjs
- Add VERIFICATION_CHECKLIST.md

Documentation:
- RELEASE_NOTES-v1.0.md with full release notes
- docs/PAGE_EDITOR_SECTION_SCHEMA_V1.md
- docs/PAGE_EDITOR_SSR_COVERAGE_AUDIT.md
2026-05-30 13:02:08 +07:00

89 lines
3.2 KiB
TypeScript

import React, { useEffect, useRef } from 'react';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { api } from '@/lib/api';
import { OrdersApi } from '@/lib/api/orders';
import { useNavigate } from 'react-router-dom';
import OrderForm from '@/routes/Orders/partials/OrderForm';
import { getStoreCurrency } from '@/lib/currency';
import { showErrorToast, showSuccessToast } from '@/lib/errorHandling';
import { __, sprintf } from '@/lib/i18n';
import { usePageHeader } from '@/contexts/PageHeaderContext';
import { Button } from '@/components/ui/button';
import { useFABConfig } from '@/hooks/useFABConfig';
export default function OrdersNew() {
const nav = useNavigate();
const qc = useQueryClient();
const { setPageHeader, clearPageHeader } = usePageHeader();
const formRef = useRef<HTMLFormElement>(null);
// Hide FAB on new order page
useFABConfig('none');
// Countries from Woo (allowed + default + states)
const countriesQ = useQuery({ queryKey: ['countries'], queryFn: OrdersApi.countries });
const countriesData = React.useMemo(() => {
const list = countriesQ.data?.countries ?? [];
return list.map((c: any) => ({ code: String(c.code), name: String(c.name) }));
}, [countriesQ.data]);
// Live payment & shipping methods
const payments = useQuery({ queryKey: ['payments'], queryFn: OrdersApi.payments });
const shippings = useQuery({ queryKey: ['shippings'], queryFn: OrdersApi.shippings });
const mutate = useMutation({
mutationFn: OrdersApi.create,
onSuccess: (data) => {
qc.invalidateQueries({ queryKey: ['orders'] });
showSuccessToast(__('Order created successfully'), sprintf(__('Order #%s has been created'), data.number || data.id));
nav('/orders');
},
onError: (error: any) => {
showErrorToast(error);
},
});
// Prefer global store currency injected by PHP
const { currency: storeCurrency, symbol: storeSymbol } = getStoreCurrency();
// Set page header with back button and create button
useEffect(() => {
const actions = (
<div className="flex gap-2">
<Button size="sm" variant="ghost" onClick={() => nav('/orders')}>
{__('Back')}
</Button>
<Button
size="sm"
onClick={() => formRef.current?.requestSubmit()}
disabled={mutate.isPending}
>
{mutate.isPending ? __('Creating...') : __('Create')}
</Button>
</div>
);
setPageHeader(__('New Order'), actions);
return () => clearPageHeader();
}, [mutate.isPending, setPageHeader, clearPageHeader, nav]);
return (
<div className="space-y-4">
<OrderForm
mode="create"
currency={storeCurrency || countriesQ.data?.currency || 'USD'}
currencySymbol={storeSymbol || countriesQ.data?.currency_symbol}
countries={countriesData}
states={countriesQ.data?.states || {}}
defaultCountry={countriesQ.data?.default_country}
payments={(payments.data || [])}
shippings={(shippings.data || [])}
formRef={formRef}
hideSubmitButton={true}
onSubmit={(form) => {
mutate.mutate(form as any);
}}
/>
</div>
);
}