/** * Difficulty Level Form Page * Add/Edit trip difficulty level */ import React, { useState, useEffect, useMemo } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { ArrowLeft, Save, Loader2, Edit2, X } from "lucide-react"; import { __ } from "../lib/i18n"; import { usePermissions } from "../hooks/usePermissions"; import { useToast } from "../components/ui/toast"; import { apiClient } from "../lib/api-client"; import { generateSlug } from "../lib/slug"; import { Button } from "../components/ui/button"; import { Input } from "../components/ui/input"; import { Select } from "../components/ui/select"; import { PageHeader } from "../components/common/PageHeader"; import { Card, CardContent, CardHeader, CardTitle, } from "../components/ui/card"; import { ConditionalRender } from "../components/ui/conditional-render"; import { IconPicker, IconPickerValue } from "../components/ui/icon-picker"; import { RichTextEditor } from "../components/ui/rich-text-editor"; interface DifficultyLevelFormData { name: string; slug: string; description: string; icon: IconPickerValue | null; sorting: number | ""; status: string; } const DifficultyLevelForm: React.FC = () => { const queryClient = useQueryClient(); const { can } = usePermissions(); const { showToast } = useToast(); const [formData, setFormData] = useState({ name: "", slug: "", description: "", icon: null, sorting: "", status: "publish", }); const [errors, setErrors] = useState>({}); const [isSubmitting, setIsSubmitting] = useState(false); const [isSlugEditable, setIsSlugEditable] = useState(false); const action = useMemo(() => { const params = new URLSearchParams(window.location.search); return params.get("action") || "create"; }, []); const levelId = useMemo(() => { const params = new URLSearchParams(window.location.search); return params.get("id") ? parseInt(params.get("id") || "0", 10) : null; }, []); const isEditMode = action === "edit" && levelId !== null; const { data: levelData, isLoading: isLoadingLevel } = useQuery({ queryKey: ["difficulty-level", levelId], queryFn: async () => { if (!levelId) return null; try { const response = await apiClient.get(`/difficulty-levels/${levelId}`); return response; } catch (error: any) { showToast( error?.message || __("Failed to load difficulty level", "yatra"), "error", ); throw error; } }, enabled: isEditMode && can("yatra_view_trips"), }); useEffect(() => { if (levelData && isEditMode) { setFormData({ name: levelData.name || "", slug: levelData.slug || "", description: levelData.description || "", icon: (levelData.icon as IconPickerValue) || null, sorting: typeof levelData.sorting === "number" ? levelData.sorting : "", status: levelData.status || "publish", }); } }, [levelData, isEditMode]); const handleNameChange = (value: string) => { // Only auto-generate slug on create; in edit mode, keep existing slug if (!isEditMode && !isSlugEditable) { const newSlug = generateSlug(value); setFormData((prev) => ({ ...prev, name: value, slug: newSlug, })); } else { setFormData((prev) => ({ ...prev, name: value })); } if (errors.name) { setErrors((prev) => ({ ...prev, name: "" })); } }; const handleSlugChange = (value: string) => { if (isSlugEditable) { setFormData((prev) => ({ ...prev, slug: value })); if (errors.slug) { setErrors((prev) => ({ ...prev, slug: "" })); } } }; const handleToggleSlugEdit = () => { if (isSlugEditable) { const newSlug = generateSlug(formData.name); setFormData((prev) => ({ ...prev, slug: newSlug })); } setIsSlugEditable(!isSlugEditable); }; const handleFieldChange = ( field: keyof DifficultyLevelFormData, value: any, ) => { setFormData((prev) => ({ ...prev, [field]: value })); if (errors[field]) { setErrors((prev) => ({ ...prev, [field]: "" })); } }; const validateForm = (): boolean => { const newErrors: Record = {}; if (!formData.name.trim()) { newErrors.name = __("Name is required", "yatra"); } if (!formData.slug.trim()) { newErrors.slug = __("Slug is required", "yatra"); } else if (!/^[\p{L}\p{N}-]+$/u.test(formData.slug)) { newErrors.slug = __( "Slug can only contain letters, numbers, and hyphens", "yatra", ); } if (formData.sorting !== "" && Number(formData.sorting) < 0) { newErrors.sorting = __("Order must be a positive number", "yatra"); } setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const saveMutation = useMutation({ mutationFn: async (data: DifficultyLevelFormData) => { const payload: any = { name: data.name.trim(), slug: data.slug.trim(), description: data.description, icon: data.icon, sorting: data.sorting === "" ? null : Number(data.sorting), status: data.status, }; if (isEditMode && isSlugEditable) { payload.preserve_slug = true; } if (isEditMode && levelId) { return await apiClient.put(`/difficulty-levels/${levelId}`, payload); } return await apiClient.post("/difficulty-levels", payload); }, onSuccess: (response) => { queryClient.invalidateQueries({ queryKey: ["difficulty-levels"] }); queryClient.invalidateQueries({ queryKey: ["difficulty-level", levelId], }); showToast( isEditMode ? __("Difficulty level updated successfully", "yatra") : __("Difficulty level created successfully", "yatra"), "success", ); setIsSubmitting(false); if (!isEditMode) { const newId = response?.id; if (newId) { window.location.href = `${window.yatraAdmin?.siteUrl || ""}/wp-admin/admin.php?page=yatra&subpage=trips&tab=difficulty-levels&action=edit&id=${newId}`; } else { window.location.href = `${window.yatraAdmin?.siteUrl || ""}/wp-admin/admin.php?page=yatra&subpage=trips&tab=difficulty-levels`; } } }, onError: (error: any) => { const errorMessage = error?.message || __("An error occurred while saving the difficulty level", "yatra"); showToast(errorMessage, "error"); setIsSubmitting(false); }, }); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (!validateForm()) { showToast(__("Please fix the form errors", "yatra"), "warning"); return; } setIsSubmitting(true); setErrors({}); saveMutation.mutate(formData); }; const handleCancel = () => { window.location.href = `${window.yatraAdmin?.siteUrl || ""}/wp-admin/admin.php?page=yatra&subpage=trips&tab=difficulty-levels`; }; if (isEditMode && isLoadingLevel) { return (
{/* Header Skeleton */}
{/* Form Skeleton */}
{/* Main Fields */}
{[1, 2, 3].map((_, idx) => (
))}
{/* Sidebar */}
{[1, 2, 3].map((_, idx) => (
))}
); } return (
{__("Back", "yatra")} } />
{__("Basic Information", "yatra")}
handleNameChange(e.target.value)} placeholder={__("Enter difficulty level name", "yatra")} className={errors.name ? "border-red-500" : ""} required /> {errors.name && (

{errors.name}

)}
handleSlugChange(e.target.value)} placeholder={__("difficulty-slug", "yatra")} className={`pr-10 ${errors.slug ? "border-red-500" : ""} ${!isSlugEditable ? "bg-gray-50 dark:bg-gray-800 cursor-not-allowed" : ""}`} disabled={!isSlugEditable} required />
{errors.slug && (

{errors.slug}

)}

{isSlugEditable ? __( "Manually editing slug. Click X to cancel and regenerate from name.", "yatra", ) : __( "Auto-generated from name. Click edit icon to customize.", "yatra", )}

handleFieldChange("description", value) } placeholder={__( "Describe this difficulty level (supports formatting, lists, links...)", "yatra", )} helperText={__( "Use formatting, bullet lists, and links to explain this difficulty level. HTML is supported.", "yatra", )} minHeight={260} maxHeight={600} />
{__("Ordering & Status", "yatra")}
handleFieldChange( "sorting", e.target.value === "" ? "" : Number(e.target.value), ) } placeholder={__("Auto", "yatra")} className={errors.sorting ? "border-red-500" : ""} /> {errors.sorting && (

{errors.sorting}

)}

{__( "Lower numbers appear first. Leave blank to auto-assign.", "yatra", )}

{__("Difficulty Level Icon or Image", "yatra")} handleFieldChange("icon", value)} label={__("Select Icon or Upload Image", "yatra")} helpText={__( "Choose a library icon or upload a custom image for this difficulty level.", "yatra", )} allowImageUpload allowIconSelection size="md" />
); }; export default DifficultyLevelForm;