/** * External Dependencies */ import {useEffect, useRef, useState} from 'react'; import {FormProvider, SubmitHandler, useForm, useFormContext, useFormState} from 'react-hook-form'; import {ajvResolver} from '@givewp/admin/ajv'; import {SlotFillProvider} from '@wordpress/components'; import {useDispatch} from '@wordpress/data'; import {__} from '@wordpress/i18n'; import {PluginArea} from '@wordpress/plugins'; import apiFetch from '@wordpress/api-fetch'; import {JSONSchemaType} from 'ajv'; /** * Internal Dependencies */ import {Spinner as GiveSpinner} from '@givewp/components'; import styles from './AdminDetailsPage.module.scss'; import AdminSection, {AdminSectionField} from './AdminSection'; import DefaultPrimaryActionButton from './DefaultPrimaryActionButton'; import ErrorBoundary from './ErrorBoundary'; import {BreadcrumbSeparatorIcon, DotsIcons} from './Icons'; import NotificationPlaceholder from './Notifications'; import TabsRouter from './Tabs/Router'; import TabList from './Tabs/TabList'; import TabPanels from './Tabs/TabPanels'; import {AdminDetailsPageProps} from './types'; import {prepareDefaultValuesFromSchema} from '@givewp/admin/utils'; import './store'; /** * @since 4.4.0 */ export default function AdminDetailsPage>({ objectId, objectType, objectTypePlural, useObjectEntityRecord, shouldSaveForm, breadcrumbUrl, breadcrumbTitle, pageTitle, StatusBadge, PrimaryActionButton = DefaultPrimaryActionButton, SecondaryActionButton, ContextMenuItems, tabDefinitions, children, }: AdminDetailsPageProps) { const [resolver, setResolver] = useState({}); const [isSaving, setIsSaving] = useState(false); const [isLoading, setIsLoading] = useState(true); const [schema, setSchema] = useState | null>(null); const [showContextMenu, setShowContextMenu] = useState(false); const contextMenuButtonRef = useRef(null); const contextMenuRef = useRef(null); const dispatch = useDispatch(`givewp/admin-details-page-notifications`); exposeAdminComponentsAndHooks(); useEffect(() => { if (!objectId) { return; } apiFetch({ path: `/givewp/v3/${objectTypePlural}/${objectId}`, method: 'OPTIONS', }).then(({schema}: {schema: JSONSchemaType}) => { setSchema(schema); setResolver({ resolver: ajvResolver(schema), }); }); }, [objectId, objectTypePlural]); const {record, hasResolved, save, edit} = useObjectEntityRecord(objectId); const methods = useForm({ mode: 'onBlur', shouldFocusError: true, ...resolver, }); const {formState, handleSubmit, reset} = methods; // Close context menu when clicked outside useEffect(() => { const handleClickOutside = (e: MouseEvent) => { if (!showContextMenu) { return; } if ( e.target instanceof HTMLElement && !contextMenuButtonRef.current?.contains(e.target) && !contextMenuRef.current?.contains(e.target) ) { setShowContextMenu(false); contextMenuButtonRef.current?.blur(); } }; document.addEventListener('click', handleClickOutside); return () => { document.removeEventListener('click', handleClickOutside); }; }, [showContextMenu]); // Set default values when entity is loaded useEffect(() => { if (hasResolved && schema && record) { const preparedRecord = prepareDefaultValuesFromSchema(record, (schema as any)?.properties) as T; reset(preparedRecord); setIsLoading(false); } }, [hasResolved, !!schema, !!record]); const onSubmit: SubmitHandler = async (data) => { const shouldSave = shouldSaveForm ? shouldSaveForm(formState.isDirty, data) : formState.isDirty; if (shouldSave) { setIsSaving(true); edit(data); try { // @ts-ignore const response: T = await save(); setIsSaving(false); const preparedRecord = prepareDefaultValuesFromSchema(response, (schema as any)?.properties) as T; reset(preparedRecord); dispatch.addSnackbarNotice({ id: `save-success`, content: __(`${objectType.charAt(0).toUpperCase() + objectType.slice(1)} updated`, 'give'), }); } catch (err) { console.error('🔴 Save failed with error:', err); setIsSaving(false); dispatch.addSnackbarNotice({ id: `save-error`, type: 'error', content: __(`${objectType.charAt(0).toUpperCase() + objectType.slice(1)} update failed`, 'give'), }); } } }; if (isLoading) { return (
{__(`Loading ${objectType}...`, 'give')}
); } return (
); } const exposeAdminComponentsAndHooks = (): void => { (window as any).givewp = (window as any).givewp || {}; (window as any).givewp.admin = (window as any).givewp.admin || {}; (window as any).givewp.admin.components = (window as any).givewp.admin.components || {}; (window as any).givewp.admin.hooks = (window as any).givewp.admin.hooks || {}; Object.assign((window as any).givewp.admin, { components: { AdminSection, AdminSectionField, }, hooks: { useFormContext, useFormState, }, }); };