PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.17
Yatra – Travel Booking & Tour Operator Software v3.0.17
3.0.17 3.0.16 3.0.15 3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 All 85 releases
← All changes | resources/js/pages/CategoryForm.tsx +87 -3 3.0.2.8 → 3.0.17 View file →
@@ -25,9 +25,29 @@
25 25 } from "../components/ui/card";
26 26 import { ConditionalRender } from "../components/ui/conditional-render";
27 27 import { IconPicker, IconPickerValue } from "../components/ui/icon-picker";
28 28 import { RichTextEditor } from "../components/ui/rich-text-editor";
29 +import {
30 + ClassificationLandingPageField,
31 + fetchPublishedPagePermalink,
32 + parseLandingPageIdFromMetadata,
33 +} from "../components/classifications/ClassificationLandingPageField";
29 34
35 +/** DB / REST may send 0|1 as number or string; never use Boolean() on string "0". */
36 +function parseTripCategoryIsFeatured(raw: unknown): boolean {
37 + if (raw === true || raw === 1) {
38 + return true;
39 + }
40 + if (raw === false || raw === 0 || raw === null || raw === undefined) {
41 + return false;
42 + }
43 + if (typeof raw === "string") {
44 + const t = raw.trim();
45 + return t === "1" || t.toLowerCase() === "true";
46 + }
47 + return Number(raw) === 1;
48 +}
49 +
30 50 interface CategoryFormData {
31 51 name: string;
32 52 slug: string;
33 53 description: string;
@@ -33,11 +53,14 @@
33 53 description: string;
34 54 icon: IconPickerValue | null;
35 55 parent_id: number | "";
36 56 status: string;
57 + /** Trip category card "Featured" badge on shortcode / Trip Category block */
58 + is_featured: boolean;
37 59 seo_title: string;
38 60 seo_description: string;
39 61 seo_keywords: string;
62 + landing_page_id: number | null;
40 63 }
41 64
42 65 interface CategoryOption {
43 66 id: number;
@@ -55,11 +78,13 @@
55 78 description: "",
56 79 icon: null,
57 80 parent_id: "",
58 81 status: "publish",
82 + is_featured: false,
59 83 seo_title: "",
60 84 seo_description: "",
61 85 seo_keywords: "",
86 + landing_page_id: null,
62 87 });
63 88 const [errors, setErrors] = useState<Record<string, string>>({});
64 89 const [isSubmitting, setIsSubmitting] = useState(false);
65 90 const [isSlugEditable, setIsSlugEditable] = useState(false);
@@ -160,11 +185,17 @@
160 185 description: categoryData.description || "",
161 186 icon: (categoryData.icon as IconPickerValue) || null,
162 187 parent_id: categoryData.parent_id ?? "",
163 188 status: categoryData.status || "publish",
189 + is_featured: parseTripCategoryIsFeatured(
190 + (categoryData as { is_featured?: unknown }).is_featured,
191 + ),
164 192 seo_title: categoryData.metadata?.seo_title || "",
165 193 seo_description: categoryData.metadata?.seo_description || "",
166 194 seo_keywords: categoryData.metadata?.seo_keywords || "",
195 + landing_page_id: parseLandingPageIdFromMetadata(
196 + categoryData.metadata as { landing_page_id?: unknown },
197 + ),
167 198 });
168 199 }
169 200 }, [categoryData, isEditMode]);
170 201
@@ -217,11 +248,11 @@
217 248 }
218 249
219 250 if (!formData.slug.trim()) {
220 251 newErrors.slug = __("Slug is required", "yatra");
221 - } else if (!/^[a-z0-9-]+$/.test(formData.slug)) {
252 + } else if (!/^[\p{L}\p{N}-]+$/u.test(formData.slug)) {
222 253 newErrors.slug = __(
223 - "Slug can only contain lowercase letters, numbers, and hyphens",
254 + "Slug can only contain letters, numbers, and hyphens",
224 255 "yatra",
225 256 );
226 257 }
227 258
@@ -245,11 +276,13 @@
245 276 description: data.description.trim(),
246 277 icon: data.icon,
247 278 parent_id: data.parent_id === "" ? null : Number(data.parent_id),
248 279 status: data.status,
280 + is_featured: data.is_featured ? 1 : 0,
249 281 seo_title: data.seo_title.trim(),
250 282 seo_description: data.seo_description.trim(),
251 283 seo_keywords: data.seo_keywords.trim(),
284 + landing_page_id: data.landing_page_id ?? null,
252 285 };
253 286
254 287 if (isEditMode && isSlugEditable) {
255 288 payload.preserve_slug = true;
@@ -392,9 +425,21 @@
392 425 </Button>
393 426 {formData.slug && (
394 427 <Button
395 428 variant="outline"
396 - onClick={() => {
429 + onClick={async () => {
430 + if (
431 + formData.landing_page_id &&
432 + window.yatraAdmin?.customLandingPagesModuleEnabled
433 + ) {
434 + const link = await fetchPublishedPagePermalink(
435 + formData.landing_page_id,
436 + );
437 + if (link) {
438 + window.open(link, "_blank", "noopener,noreferrer");
439 + return;
440 + }
441 + }
397 442 const targetUrl = buildYatraSinglePreviewUrl({
398 443 entity: "category",
399 444 slug: formData.slug || "",
400 445 bases: settings as Record<string, unknown> | null,
@@ -515,8 +560,16 @@
515 560 />
516 561 </div>
517 562 </CardContent>
518 563 </Card>
564 +
565 + <ClassificationLandingPageField
566 + selectId="yatra-category-landing-page"
567 + value={formData.landing_page_id}
568 + onChange={(id) =>
569 + setFormData((prev) => ({ ...prev, landing_page_id: id }))
570 + }
571 + />
519 572 </div>
520 573
521 574 <div className="space-y-3">
522 575 <Card>
@@ -590,8 +643,39 @@
590 643 <option value="draft">{__("Draft", "yatra")}</option>
591 644 <option value="publish">{__("Publish", "yatra")}</option>
592 645 <option value="trash">{__("Trash", "yatra")}</option>
593 646 </Select>
647 + </CardContent>
648 + </Card>
649 +
650 + <Card>
651 + <CardHeader className="pb-2">
652 + <CardTitle className="text-base">
653 + {__("Visibility", "yatra")}
654 + </CardTitle>
655 + </CardHeader>
656 + <CardContent>
657 + <label className="flex items-start gap-3 cursor-pointer">
658 + <input
659 + type="checkbox"
660 + className="mt-1 h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
661 + checked={formData.is_featured}
662 + onChange={(e) =>
663 + handleFieldChange("is_featured", e.target.checked)
664 + }
665 + />
666 + <span>
667 + <span className="block text-sm font-medium text-gray-900 dark:text-gray-100">
668 + {__("Featured category", "yatra")}
669 + </span>
670 + <span className="block text-xs text-gray-500 dark:text-gray-400 mt-0.5">
671 + {__(
672 + "Shows a “Featured” badge on Trip Category blocks and the [yatra_trip_category] shortcode cards.",
673 + "yatra",
674 + )}
675 + </span>
676 + </span>
677 + </label>
594 678 </CardContent>
595 679 </Card>
596 680
597 681 {/* SEO Settings */}