| @@ -14,13 +14,19 @@ | ||
| 14 | 14 | Trash2, |
| 15 | 15 | Users, |
| 16 | 16 | } from "lucide-react"; |
| 17 | 17 | import { __ } from "../lib/i18n"; |
| 18 | +import { toDateValue } from "../lib/dateFormat"; | |
| 18 | 19 | import { usePermissions } from "../hooks/usePermissions"; |
| 19 | 20 | import { useToast } from "../components/ui/toast"; |
| 20 | 21 | import { apiClient } from "../lib/api-client"; |
| 21 | 22 | import { Button } from "../components/ui/button"; |
| 22 | 23 | import { Input } from "../components/ui/input"; |
| 24 | +import { getCurrencySymbol } from "../data/currencies"; | |
| 25 | +import { | |
| 26 | + normalizeCurrencyPosition, | |
| 27 | + readYatraCurrencyPositionFromWindow, | |
| 28 | +} from "../lib/currency-display"; | |
| 23 | 29 | import { Select } from "../components/ui/select"; |
| 24 | 30 | import { PageHeader } from "../components/common/PageHeader"; |
| 25 | 31 | import { |
| 26 | 32 | Card, |
| @@ -82,8 +88,28 @@ | ||
| 82 | 88 | const DiscountForm: React.FC = () => { |
| 83 | 89 | const queryClient = useQueryClient(); |
| 84 | 90 | const { can, permissions, isPro } = usePermissions(); |
| 85 | 91 | const { showToast } = useToast(); |
| 92 | + | |
| 93 | + // Currency symbol + placement for the amount fields, taken from the operator's | |
| 94 | + // settings (localized into window.yatraAdmin). Previously these inputs | |
| 95 | + // hardcoded "$" on the left, ignoring both the configured symbol and its | |
| 96 | + // position (left / left_space / right / right_space). | |
| 97 | + const currencySymbol = getCurrencySymbol( | |
| 98 | + ((window as unknown as { yatraAdmin?: { currency?: string } })?.yatraAdmin | |
| 99 | + ?.currency as string) || "USD", | |
| 100 | + ); | |
| 101 | + const currencyOnRight = (() => { | |
| 102 | + const pos = normalizeCurrencyPosition(readYatraCurrencyPositionFromWindow()); | |
| 103 | + return pos === "right" || pos === "right_space"; | |
| 104 | + })(); | |
| 105 | + const currencyPad = currencyOnRight | |
| 106 | + ? currencySymbol.length > 1 | |
| 107 | + ? "pr-10" | |
| 108 | + : "pr-7" | |
| 109 | + : currencySymbol.length > 1 | |
| 110 | + ? "pl-10" | |
| 111 | + : "pl-7"; | |
| 86 | 112 | const [showPremiumModal, setShowPremiumModal] = useState(false); |
| 87 | 113 | // Get initial discount_mode from URL at initialization time |
| 88 | 114 | const getInitialState = (): DiscountFormData => { |
| 89 | 115 | const params = new URLSearchParams(window.location.search); |
| @@ -187,9 +213,9 @@ | ||
| 187 | 213 | ); |
| 188 | 214 | throw error; |
| 189 | 215 | } |
| 190 | 216 | }, |
| 191 | - enabled: (isEditMode || isDuplicateMode) && can("yatra_view_bookings"), | |
| 217 | + enabled: (isEditMode || isDuplicateMode) && can("yatra_manage_discounts"), | |
| 192 | 218 | }); |
| 193 | 219 | |
| 194 | 220 | // Load discount data into form when editing or duplicating |
| 195 | 221 | useEffect(() => { |
| @@ -580,9 +606,9 @@ | ||
| 580 | 606 | // Valid from date can be in the past (backdating allowed) |
| 581 | 607 | // No validation needed for valid_from date |
| 582 | 608 | |
| 583 | 609 | if (formData.expiry_date) { |
| 584 | - const expiryDate = new Date(formData.expiry_date); | |
| 610 | + const expiryDate = toDateValue(formData.expiry_date); | |
| 585 | 611 | if (expiryDate < today) { |
| 586 | 612 | newErrors.expiry_date = __( |
| 587 | 613 | "Expiry date cannot be in the past", |
| 588 | 614 | "yatra", |
| @@ -591,10 +617,10 @@ | ||
| 591 | 617 | } |
| 592 | 618 | |
| 593 | 619 | // Check if expiry date is after valid from date |
| 594 | 620 | if (formData.valid_from && formData.expiry_date) { |
| 595 | - const validFrom = new Date(formData.valid_from); | |
| 596 | - const expiryDate = new Date(formData.expiry_date); | |
| 621 | + const validFrom = toDateValue(formData.valid_from); | |
| 622 | + const expiryDate = toDateValue(formData.expiry_date); | |
| 597 | 623 | if (expiryDate < validFrom) { |
| 598 | 624 | newErrors.expiry_date = __( |
| 599 | 625 | "Expiry date must be after the valid from date", |
| 600 | 626 | "yatra", |
| @@ -1046,9 +1072,9 @@ | ||
| 1046 | 1072 | </Button> |
| 1047 | 1073 | } |
| 1048 | 1074 | /> |
| 1049 | 1075 | |
| 1050 | - <ConditionalRender capability="yatra_edit_bookings"> | |
| 1076 | + <ConditionalRender capability="yatra_manage_discounts"> | |
| 1051 | 1077 | {/* Discount Information Card */} |
| 1052 | 1078 | <Card className="bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800"> |
| 1053 | 1079 | <CardContent className="p-4"> |
| 1054 | 1080 | <div className="flex items-start gap-3"> |
| @@ -1347,11 +1373,10 @@ | ||
| 1347 | 1373 | required |
| 1348 | 1374 | /> |
| 1349 | 1375 | ) : ( |
| 1350 | 1376 | <div className="relative"> |
| 1351 | - <span className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500 text-sm"> | |
| 1352 | - $ | |
| 1353 | - </span> | |
| 1377 | + <span className={`absolute ${currencyOnRight ? "right-3" : "left-3"} top-1/2 transform -translate-y-1/2 text-gray-500 text-sm`}> | |
| 1378 | + {currencySymbol} </span> | |
| 1354 | 1379 | <Input |
| 1355 | 1380 | id="amount" |
| 1356 | 1381 | name="amount" |
| 1357 | 1382 | type="number" |
| @@ -1361,9 +1386,9 @@ | ||
| 1361 | 1386 | onChange={(e) => |
| 1362 | 1387 | handleFieldChange("amount", e.target.value) |
| 1363 | 1388 | } |
| 1364 | 1389 | placeholder={__("e.g., 50", "yatra")} |
| 1365 | - className={`pl-7 ${errors.amount ? "border-red-500" : ""}`} | |
| 1390 | + className={`${currencyPad} ${errors.amount ? "border-red-500" : ""}`} | |
| 1366 | 1391 | required |
| 1367 | 1392 | /> |
| 1368 | 1393 | </div> |
| 1369 | 1394 | )} |
| @@ -1399,11 +1424,10 @@ | ||
| 1399 | 1424 | )} |
| 1400 | 1425 | className="mb-2" |
| 1401 | 1426 | /> |
| 1402 | 1427 | <div className="relative"> |
| 1403 | - <span className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500 text-sm"> | |
| 1404 | - $ | |
| 1405 | - </span> | |
| 1428 | + <span className={`absolute ${currencyOnRight ? "right-3" : "left-3"} top-1/2 transform -translate-y-1/2 text-gray-500 text-sm`}> | |
| 1429 | + {currencySymbol} </span> | |
| 1406 | 1430 | <Input |
| 1407 | 1431 | id="max_discount_amount" |
| 1408 | 1432 | type="number" |
| 1409 | 1433 | min="0" |
| @@ -1415,9 +1439,9 @@ | ||
| 1415 | 1439 | e.target.value, |
| 1416 | 1440 | ) |
| 1417 | 1441 | } |
| 1418 | 1442 | placeholder={__("e.g., 500 (optional)", "yatra")} |
| 1419 | - className={`pl-7 ${errors.max_discount_amount ? "border-red-500" : ""}`} | |
| 1443 | + className={`${currencyPad} ${errors.max_discount_amount ? "border-red-500" : ""}`} | |
| 1420 | 1444 | /> |
| 1421 | 1445 | </div> |
| 1422 | 1446 | {errors.max_discount_amount && ( |
| 1423 | 1447 | <p className="mt-1.5 text-sm text-red-600 dark:text-red-400 flex items-center gap-1"> |
| @@ -1553,11 +1577,10 @@ | ||
| 1553 | 1577 | )} |
| 1554 | 1578 | className="mb-2" |
| 1555 | 1579 | /> |
| 1556 | 1580 | <div className="relative"> |
| 1557 | - <span className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500 text-sm"> | |
| 1558 | - $ | |
| 1559 | - </span> | |
| 1581 | + <span className={`absolute ${currencyOnRight ? "right-3" : "left-3"} top-1/2 transform -translate-y-1/2 text-gray-500 text-sm`}> | |
| 1582 | + {currencySymbol} </span> | |
| 1560 | 1583 | <Input |
| 1561 | 1584 | id="min_amount" |
| 1562 | 1585 | type="number" |
| 1563 | 1586 | min="0" |
| @@ -1566,9 +1589,9 @@ | ||
| 1566 | 1589 | onChange={(e) => |
| 1567 | 1590 | handleFieldChange("min_amount", e.target.value) |
| 1568 | 1591 | } |
| 1569 | 1592 | placeholder={__("e.g., 100", "yatra")} |
| 1570 | - className={`pl-7 ${errors.min_amount ? "border-red-500" : ""}`} | |
| 1593 | + className={`${currencyPad} ${errors.min_amount ? "border-red-500" : ""}`} | |
| 1571 | 1594 | /> |
| 1572 | 1595 | </div> |
| 1573 | 1596 | {errors.min_amount && ( |
| 1574 | 1597 | <p className="mt-1.5 text-sm text-red-600 dark:text-red-400 flex items-center gap-1"> |
| @@ -1620,9 +1643,9 @@ | ||
| 1620 | 1643 | } |
| 1621 | 1644 | placeholder={__("Select expiry date", "yatra")} |
| 1622 | 1645 | minDate={ |
| 1623 | 1646 | formData.valid_from |
| 1624 | - ? new Date(formData.valid_from) | |
| 1647 | + ? toDateValue(formData.valid_from) | |
| 1625 | 1648 | : new Date() |
| 1626 | 1649 | } |
| 1627 | 1650 | error={!!errors.expiry_date} |
| 1628 | 1651 | className={errors.expiry_date ? "border-red-500" : ""} |
| @@ -1930,11 +1953,10 @@ | ||
| 1930 | 1953 | </label> |
| 1931 | 1954 | <div className="relative"> |
| 1932 | 1955 | {range.discount_type === |
| 1933 | 1956 | "fixed" && ( |
| 1934 | - <span className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500 text-sm"> | |
| 1935 | - $ | |
| 1936 | - </span> | |
| 1957 | + <span className={`absolute ${currencyOnRight ? "right-3" : "left-3"} top-1/2 transform -translate-y-1/2 text-gray-500 text-sm`}> | |
| 1958 | + {currencySymbol} </span> | |
| 1937 | 1959 | )} |
| 1938 | 1960 | <Input |
| 1939 | 1961 | type="number" |
| 1940 | 1962 | min="0" |
| @@ -1959,9 +1981,9 @@ | ||
| 1959 | 1981 | "percentage" |
| 1960 | 1982 | ? __("e.g., 10", "yatra") |
| 1961 | 1983 | : __("e.g., 50", "yatra") |
| 1962 | 1984 | } |
| 1963 | - className={`${range.discount_type === "fixed" ? "pl-7" : ""} ${errors[`group_discount_ranges_${idx}_amount`] ? "border-red-500" : ""}`} | |
| 1985 | + className={`${range.discount_type === "fixed" ? currencyPad : ""} ${errors[`group_discount_ranges_${idx}_amount`] ? "border-red-500" : ""}`} | |
| 1964 | 1986 | /> |
| 1965 | 1987 | {range.discount_type === |
| 1966 | 1988 | "percentage" && ( |
| 1967 | 1989 | <span className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-500 text-sm"> |
| @@ -2496,11 +2518,10 @@ | ||
| 2496 | 2518 | </label> |
| 2497 | 2519 | <div className="relative"> |
| 2498 | 2520 | {range.discount_type === |
| 2499 | 2521 | "fixed" && ( |
| 2500 | - <span className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500 text-sm"> | |
| 2501 | - $ | |
| 2502 | - </span> | |
| 2522 | + <span className={`absolute ${currencyOnRight ? "right-3" : "left-3"} top-1/2 transform -translate-y-1/2 text-gray-500 text-sm`}> | |
| 2523 | + {currencySymbol} </span> | |
| 2503 | 2524 | )} |
| 2504 | 2525 | <Input |
| 2505 | 2526 | type="number" |
| 2506 | 2527 | min="0" |
| @@ -2537,9 +2558,9 @@ | ||
| 2537 | 2558 | "e.g., 50", |
| 2538 | 2559 | "yatra", |
| 2539 | 2560 | ) |
| 2540 | 2561 | } |
| 2541 | - className={`${range.discount_type === "fixed" ? "pl-7" : ""} ${errors[`category_discounts_${catIdx}_ranges_${rIdx}_amount`] ? "border-red-500" : ""}`} | |
| 2562 | + className={`${range.discount_type === "fixed" ? currencyPad : ""} ${errors[`category_discounts_${catIdx}_ranges_${rIdx}_amount`] ? "border-red-500" : ""}`} | |
| 2542 | 2563 | /> |
| 2543 | 2564 | {range.discount_type === |
| 2544 | 2565 | "percentage" && ( |
| 2545 | 2566 | <span className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-500 text-sm"> |