All files / src/entities/project/ui/_shared ProjectModalBase.tsx

100% Statements 20/20
70.58% Branches 12/17
100% Functions 2/2
100% Lines 20/20

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109                                                                                        33x   33x 33x 33x 33x 33x     8x 8x   8x 8x 8x 4x       4x       4x 4x 4x 2x   2x 2x           4x                                                          
import React, { useState } from 'react';
import { useAppDispatch } from '@/store/hooks';
import { projectSchema } from '@/shared/schema';
import { PROJECT_MESSAGES } from '@/shared/constants';
import {
	ModalActions,
	ModalContainer,
	ModalForm,
	ModalInputs,
} from '@/entities/project/ui/modal';
import type { ErrorMessage } from '@/shared/types';
 
interface ProjectModalBaseProps<TArgs extends Record<string, unknown>> {
	title: string;
	buttonLabel: string;
	onClose: () => void;
	onSubmitAction: (
		dispatch: ReturnType<typeof useAppDispatch>,
		args: TArgs,
	) => Promise<void>;
	initialValue?: string;
	buildArgs: (name: string, width: number, height: number) => TArgs;
	disableAutoClose?: boolean;
	showInput?: boolean;
	showCanvasInputs?: boolean;
	customContent?: React.ReactNode;
	'data-testid'?: string;
}
/**
 * Base modal layout for project operations (Create / Update / Delete)
 * Handles validation, async dispatching, error feedback and visuals.
 */
export function ProjectModalBase<TArgs extends Record<string, unknown>>({
	title,
	buttonLabel,
	onClose,
	onSubmitAction,
	initialValue = '',
	buildArgs,
	showInput = true,
	showCanvasInputs = false,
	customContent,
	'data-testid': testId,
}: ProjectModalBaseProps<TArgs>) {
	const dispatch = useAppDispatch();
 
	const [name, setName] = useState(initialValue);
	const [width, setWidth] = useState(800);
	const [height, setHeight] = useState(600);
	const [loading, setLoading] = useState(false);
	const [error, setError] = useState<ErrorMessage>(null);
 
	async function handleSubmit(e: React.FormEvent) {
		e.preventDefault();
		setError(null);
 
		Eif (showInput || showCanvasInputs) {
			const validation = projectSchema.safeParse({ name, width, height });
			if (!validation.success) {
				setError(
					validation.error.issues[0]?.message ??
						PROJECT_MESSAGES.ERROR_VALIDATION,
				);
				return;
			}
		}
 
		try {
			setLoading(true);
			await onSubmitAction(dispatch, buildArgs(name, width, height));
			onClose();
		} catch (err) {
			const message = (err instanceof Error ? err.message : '') || '';
			setError(
				message.includes('exists')
					? PROJECT_MESSAGES.NAME_DUPLICATE
					: PROJECT_MESSAGES.UNEXPECTED_SERVER_ERROR,
			);
		} finally {
			setLoading(false);
		}
	}
 
	return (
		<ModalContainer data-testid={testId}>
			<ModalForm onSubmit={handleSubmit} data-testid="project-form" title={title}>
				<ModalInputs
					showInput={showInput}
					showCanvasInputs={showCanvasInputs}
					customContent={customContent}
					name={name}
					width={width}
					height={height}
					error={error}
					setName={setName}
					setWidth={setWidth}
					setHeight={setHeight}
				/>
				<ModalActions
					onClose={onClose}
					buttonLabel={buttonLabel}
					loading={loading}
					showInput={showInput}
				/>
			</ModalForm>
		</ModalContainer>
	);
}