| @@ -21,9 +21,12 @@ | ||
| 21 | 21 | Search, |
| 22 | 22 | X, |
| 23 | 23 | ArrowUpDown, |
| 24 | 24 | RefreshCw, |
| 25 | + Sparkles, | |
| 25 | 26 | } from "lucide-react"; |
| 27 | +import { CreateTripWithAiWizard } from "../components/ai/CreateTripWithAiWizard"; | |
| 28 | +import { isAiEligible, isAiModuleEnabled } from "../lib/ai-availability"; | |
| 26 | 29 | import { __ } from "../lib/i18n"; |
| 27 | 30 | import { usePermissions } from "../hooks/usePermissions"; |
| 28 | 31 | import { Button } from "../components/ui/button"; |
| 29 | 32 | import { Input } from "../components/ui/input"; |
| @@ -123,8 +126,9 @@ | ||
| 123 | 126 | const [sortBy, setSortBy] = useState("id"); |
| 124 | 127 | const [sortOrder, setSortOrder] = useState<"asc" | "desc">("desc"); |
| 125 | 128 | const [page, setPage] = useState(1); |
| 126 | 129 | const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); |
| 130 | + const [isAiWizardOpen, setIsAiWizardOpen] = useState(false); | |
| 127 | 131 | const [newTripTitle, setNewTripTitle] = useState(""); |
| 128 | 132 | const [createTripError, setCreateTripError] = useState<string | null>(null); |
| 129 | 133 | const [newTripSlug, setNewTripSlug] = useState(""); |
| 130 | 134 | const [isSlugManuallyEdited, setIsSlugManuallyEdited] = useState(false); |
| @@ -500,14 +504,14 @@ | ||
| 500 | 504 | |
| 501 | 505 | const typeMap: Record<string, { className: string; label: string }> = { |
| 502 | 506 | single_day: { |
| 503 | 507 | className: |
| 504 | - "bg-purple-100 text-purple-700 dark:bg-purple-900/20 dark:text-purple-400", | |
| 508 | + "bg-blue-100 text-blue-700 dark:bg-blue-900/20 dark:text-blue-400", | |
| 505 | 509 | label: __("Single Day", "yatra"), |
| 506 | 510 | }, |
| 507 | 511 | multi_day: { |
| 508 | 512 | className: |
| 509 | - "bg-indigo-100 text-indigo-700 dark:bg-indigo-900/20 dark:text-indigo-400", | |
| 513 | + "bg-indigo-100 text-indigo-800 dark:bg-indigo-900/20 dark:text-indigo-400", | |
| 510 | 514 | label: __("Multi-Day", "yatra"), |
| 511 | 515 | }, |
| 512 | 516 | flexible: { |
| 513 | 517 | className: |
| @@ -548,9 +552,9 @@ | ||
| 548 | 552 | } catch (error) { |
| 549 | 553 | return null; |
| 550 | 554 | } |
| 551 | 555 | }, |
| 552 | - enabled: can("manage_yatra"), | |
| 556 | + enabled: can("yatra_access_admin"), | |
| 553 | 557 | staleTime: 5 * 60 * 1000, // Cache for 5 minutes |
| 554 | 558 | }); |
| 555 | 559 | |
| 556 | 560 | // Global/default currency (fallbacks: window.yatraAdmin, settings, then USD) |
| @@ -622,12 +626,9 @@ | ||
| 622 | 626 | return; |
| 623 | 627 | } |
| 624 | 628 | if (!/^[\p{L}\p{N}-]+$/u.test(slug)) { |
| 625 | 629 | setCreateTripError( |
| 626 | - __( | |
| 627 | - "Slug can only contain letters, numbers, and hyphens", | |
| 628 | - "yatra", | |
| 629 | - ), | |
| 630 | + __("Slug can only contain letters, numbers, and hyphens", "yatra"), | |
| 630 | 631 | ); |
| 631 | 632 | return; |
| 632 | 633 | } |
| 633 | 634 | setNewTripSlug(slug); |
| @@ -1246,9 +1247,9 @@ | ||
| 1246 | 1247 | </span> |
| 1247 | 1248 | ); |
| 1248 | 1249 | } |
| 1249 | 1250 | return ( |
| 1250 | - <Badge className="text-xs bg-purple-100 text-purple-700 dark:bg-purple-900/20 dark:text-purple-400"> | |
| 1251 | + <Badge className="text-xs bg-blue-100 text-blue-700 dark:bg-blue-900/20 dark:text-blue-400"> | |
| 1251 | 1252 | {attrCount} {__("attributes", "yatra")} |
| 1252 | 1253 | </Badge> |
| 1253 | 1254 | ); |
| 1254 | 1255 | }, |
| @@ -1296,8 +1297,9 @@ | ||
| 1296 | 1297 | }, |
| 1297 | 1298 | }); |
| 1298 | 1299 | |
| 1299 | 1300 | return cols; |
| 1301 | + // eslint-disable-next-line react-hooks/exhaustive-deps | |
| 1300 | 1302 | }, [ |
| 1301 | 1303 | isPro, |
| 1302 | 1304 | visibleColumns, |
| 1303 | 1305 | summarizeDestinations, |
| @@ -1459,8 +1461,9 @@ | ||
| 1459 | 1461 | }); |
| 1460 | 1462 | } |
| 1461 | 1463 | |
| 1462 | 1464 | return actions; |
| 1465 | + // eslint-disable-next-line react-hooks/exhaustive-deps | |
| 1463 | 1466 | }, [can, queryClient, showToast]); |
| 1464 | 1467 | |
| 1465 | 1468 | return ( |
| 1466 | 1469 | <> |
| @@ -1485,14 +1488,28 @@ | ||
| 1485 | 1488 | > |
| 1486 | 1489 | {__("Refresh Data", "yatra")} |
| 1487 | 1490 | </Button> |
| 1488 | 1491 | )} |
| 1492 | + {isAiEligible() && isAiModuleEnabled() && ( | |
| 1493 | + <Button | |
| 1494 | + type="button" | |
| 1495 | + onClick={() => setIsAiWizardOpen(true)} | |
| 1496 | + className="inline-flex items-center gap-2 whitespace-nowrap bg-gradient-to-r from-blue-600 to-blue-700 text-white border-0 hover:from-blue-700 hover:to-blue-800" | |
| 1497 | + title={__( | |
| 1498 | + "Have AI draft an entire trip — details, itinerary, SEO — from a few setup questions.", | |
| 1499 | + "yatra", | |
| 1500 | + )} | |
| 1501 | + > | |
| 1502 | + <Sparkles className="h-4 w-4 shrink-0" /> | |
| 1503 | + <span>{__("Add Trip with AI", "yatra")}</span> | |
| 1504 | + </Button> | |
| 1505 | + )} | |
| 1489 | 1506 | <Button |
| 1490 | 1507 | onClick={handleCreateTrip} |
| 1491 | - className="flex items-center gap-2" | |
| 1508 | + className="inline-flex items-center gap-2 whitespace-nowrap" | |
| 1492 | 1509 | > |
| 1493 | - <Plus className="w-4 h-4" /> | |
| 1494 | - {__("Add New Trip", "yatra")} | |
| 1510 | + <Plus className="h-4 w-4 shrink-0" /> | |
| 1511 | + <span>{__("Add New Trip", "yatra")}</span> | |
| 1495 | 1512 | </Button> |
| 1496 | 1513 | </div> |
| 1497 | 1514 | } |
| 1498 | 1515 | /> |
| @@ -1810,8 +1827,15 @@ | ||
| 1810 | 1827 | </div> |
| 1811 | 1828 | )} |
| 1812 | 1829 | </div> |
| 1813 | 1830 | </Modal> |
| 1831 | + | |
| 1832 | + {/* AI: "Add Trip with AI" wizard — multi-step setup → generate → | |
| 1833 | + review → save. Drafts an entire trip from a few seed questions. */} | |
| 1834 | + <CreateTripWithAiWizard | |
| 1835 | + open={isAiWizardOpen} | |
| 1836 | + onClose={() => setIsAiWizardOpen(false)} | |
| 1837 | + /> | |
| 1814 | 1838 | |
| 1815 | 1839 | {/* Permanent Delete Confirmation (for Trash only) */} |
| 1816 | 1840 | <ConfirmationDialog |
| 1817 | 1841 | isOpen={isDeleteDialogOpen && !!tripToDelete} |