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/account/utils.ts +144 -41 3.0.2.9 → 3.0.17 View file →
@@ -1,30 +1,135 @@
1 -import { __ } from "../../lib/i18n";
2 -import { getCurrencySymbol, getCurrency } from "../../data/currencies";
1 +import { __, sprintf } from "../../lib/i18n";
2 +import {
3 + applyCurrencyPosition,
4 + formatYatraMoney,
5 +} from "../../lib/currency-display";
6 +import {
7 + formatDate as formatGlobalDate,
8 + parseDate,
9 +} from "../../lib/dateFormat";
3 10
11 +function toBrowserLocaleTag(
12 + locale: string | undefined | null,
13 +): string | undefined {
14 + const raw = String(locale || "").trim();
15 + if (!raw) return undefined;
16 + // WP locales are often like "en_US"; Intl expects BCP47 like "en-US".
17 + return raw.replace(/_/g, "-");
18 +}
19 +
20 +function getAccountLocale(): string | undefined {
21 + if (typeof window === "undefined") return undefined;
22 + const raw = (window as unknown as { yatraAccountPage?: { locale?: string } })
23 + .yatraAccountPage?.locale;
24 + return toBrowserLocaleTag(raw);
25 +}
26 +
4 27 export const formatDate = (value: string | undefined | null) => {
5 28 if (!value) {
6 29 return __("N/A", "yatra");
7 30 }
8 31
9 - try {
10 - const date = new Date(value);
32 + // Render public account dates in the operator's configured global date
33 + // format (Settings → General) via the shared formatter — not a hardcoded
34 + // browser style. It also parses a date-only value (e.g. a booking's
35 + // travel_date) in LOCAL time, so it no longer rolls back a day in behind-UTC
36 + // timezones (Aug 1 → Jul 31).
37 + if (!parseDate(value)) {
38 + return __("Invalid date", "yatra");
39 + }
40 + return formatGlobalDate(value);
41 +};
11 42
12 - // Check if date is valid
13 - if (isNaN(date.getTime())) {
14 - return __("Invalid date", "yatra");
15 - }
43 +/** Leading Y-m-d from API datetime strings for stable range comparison. */
44 +function extractYmd(value: string | undefined | null): string | null {
45 + if (!value) {
46 + return null;
47 + }
48 + const m = String(value)
49 + .trim()
50 + .match(/^(\d{4}-\d{2}-\d{2})/);
51 + return m ? m[1] : null;
52 +}
16 53
17 - return date.toLocaleDateString(undefined, {
18 - year: "numeric",
19 - month: "long",
20 - day: "numeric",
21 - });
22 - } catch (error) {
23 - return __("Invalid date", "yatra");
54 +/**
55 + * Format trip window for the account UI: one localized date, or start–end when different.
56 + * translators: %1$s: trip start date, %2$s: trip end date (localized).
57 + */
58 +export const formatTravelDateRange = (
59 + travelDate?: string | null,
60 + endDate?: string | null,
61 +): string => {
62 + const startKey = extractYmd(travelDate);
63 + const endKey = extractYmd(endDate);
64 + if (!startKey) {
65 + return formatDate(travelDate);
24 66 }
67 + if (!endKey || endKey === startKey) {
68 + return formatDate(travelDate);
69 + }
70 + return sprintf(
71 + // translators: 1: start date, 2: end date. Joined with an en-dash for travel date ranges.
72 + __("%1$s – %2$s", "yatra"),
73 + formatDate(travelDate),
74 + formatDate(endDate),
75 + );
25 76 };
26 77
78 +/**
79 + * Translate a known booking/payment/ticket status to a localized label.
80 + *
81 + * The previous account-page code did `__(booking.status, booking.status)` which
82 + * mis-uses the textdomain argument: WP's i18n runtime treats the second arg as
83 + * the textdomain (defaulting to 'default'), so it never finds the translation
84 + * and silently returns the raw English key like 'confirmed'/'pending'. Worse,
85 + * because the first argument is a runtime variable, the WP-CLI i18n extractor
86 + * skips it — those strings never reach the .pot file and Loco never gets a
87 + * chance to translate them.
88 + *
89 + * Mapping each known status to a static `__('Foo', 'yatra')` call fixes both
90 + * problems at once: extractor sees the literals, translators can localize, and
91 + * unknown values fall back to a title-cased rendition of the raw key.
92 + */
93 +export const getStatusLabel = (status: string | undefined | null): string => {
94 + const raw = typeof status === "string" ? status.toLowerCase().trim() : "";
95 + switch (raw) {
96 + case "pending":
97 + return __("Pending", "yatra");
98 + case "confirmed":
99 + return __("Confirmed", "yatra");
100 + case "processing":
101 + return __("Processing", "yatra");
102 + case "completed":
103 + return __("Completed", "yatra");
104 + case "cancelled":
105 + return __("Cancelled", "yatra");
106 + case "refunded":
107 + return __("Refunded", "yatra");
108 + case "failed":
109 + return __("Failed", "yatra");
110 + case "on_hold":
111 + return __("On hold", "yatra");
112 + case "paid":
113 + return __("Paid", "yatra");
114 + case "partial":
115 + return __("Partial", "yatra");
116 + case "awaiting_response":
117 + return __("Awaiting response", "yatra");
118 + case "resolved":
119 + return __("Resolved", "yatra");
120 + case "closed":
121 + return __("Closed", "yatra");
122 + case "open":
123 + return __("Open", "yatra");
124 + case "":
125 + return __("Pending", "yatra");
126 + default:
127 + // Unknown status — title-case it so admins can see what's coming through.
128 + return raw.charAt(0).toUpperCase() + raw.slice(1).replace(/_/g, " ");
129 + }
130 +};
131 +
27 132 export const getBadge = (status: string | undefined | null) => {
28 133 const base = "px-2.5 py-0.5 rounded-full text-xs font-medium";
29 134
30 135 // Handle undefined/null/empty status
@@ -44,9 +149,9 @@
44 149 case "failed":
45 150 case "cancelled":
46 151 return `${base} bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300`;
47 152 default:
48 - return `${base} bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300`;
153 + return `${base} bg-yatra-chip-bg text-yatra-primary-darker dark:bg-yatra-surface-dark dark:text-yatra-primary-light`;
49 154 }
50 155 };
51 156
52 157 type PriceConfigWindow = Window & {
@@ -69,9 +174,11 @@
69 174 const p = (w.yatraAccountPage || {}) as Record<string, unknown>;
70 175 return {
71 176 globalCurrency: (a.currency || p.currency || "USD") as string,
72 177 currencyPosition: (a.currencyPosition ||
178 + a.currency_position ||
73 179 p.currencyPosition ||
180 + p.currency_position ||
74 181 "before") as string,
75 182 decimalPlaces: Number(a.decimalPlaces ?? p.decimalPlaces ?? 2) || 2,
76 183 thousandSeparator: (a.thousandSeparator ||
77 184 p.thousandSeparator ||
@@ -94,9 +201,9 @@
94 201 if (!price || price === 0) {
95 202 return __("Contact for pricing", "yatra");
96 203 }
97 204
98 - const formattedAmount = new Intl.NumberFormat(undefined, {
205 + const formattedAmount = new Intl.NumberFormat(getAccountLocale(), {
99 206 minimumFractionDigits: decimalPlaces,
100 207 maximumFractionDigits: decimalPlaces,
101 208 })
102 209 .format(price)
@@ -103,9 +210,9 @@
103 210 .replace(/,/g, "TEMP_THOUSAND")
104 211 .replace(/\./g, decimalSeparator)
105 212 .replace(/TEMP_THOUSAND/g, thousandSeparator);
106 213
107 - const currencySymbol = new Intl.NumberFormat(undefined, {
214 + const currencySymbol = new Intl.NumberFormat(getAccountLocale(), {
108 215 style: "currency",
109 216 currency: globalCurrency,
110 217 })
111 218 .format(0)
@@ -111,13 +218,13 @@
111 218 .format(0)
112 219 .replace(/[\d\s.,]/g, "")
113 220 .trim();
114 221
115 - if (currencyPosition === "right" || currencyPosition === "after") {
116 - return `${formattedAmount}${currencySymbol}`;
117 - }
118 -
119 - return `${currencySymbol}${formattedAmount}`;
222 + return applyCurrencyPosition(
223 + formattedAmount,
224 + currencySymbol,
225 + currencyPosition,
226 + );
120 227 };
121 228
122 229 export const formatPriceForBooking = (price: number, currency?: string) => {
123 230 const cfg = readPriceConfig();
@@ -133,9 +240,9 @@
133 240
134 241 // Always format the price, even if 0 - don't show "Contact for pricing" for bookings
135 242 const numPrice = Number(price) || 0;
136 243
137 - const formattedAmount = new Intl.NumberFormat(undefined, {
244 + const formattedAmount = new Intl.NumberFormat(getAccountLocale(), {
138 245 minimumFractionDigits: decimalPlaces,
139 246 maximumFractionDigits: decimalPlaces,
140 247 })
141 248 .format(numPrice)
@@ -142,9 +249,9 @@
142 249 .replace(/,/g, "TEMP_THOUSAND")
143 250 .replace(/\./g, decimalSeparator)
144 251 .replace(/TEMP_THOUSAND/g, thousandSeparator);
145 252
146 - const currencySymbol = new Intl.NumberFormat(undefined, {
253 + const currencySymbol = new Intl.NumberFormat(getAccountLocale(), {
147 254 style: "currency",
148 255 currency: currencyToUse,
149 256 })
150 257 .format(0)
@@ -150,26 +257,19 @@
150 257 .format(0)
151 258 .replace(/[\d\s.,]/g, "")
152 259 .trim();
153 260
154 - if (currencyPosition === "right" || currencyPosition === "after") {
155 - return `${formattedAmount}${currencySymbol}`;
156 - }
157 -
158 - return `${currencySymbol}${formattedAmount}`;
261 + return applyCurrencyPosition(
262 + formattedAmount,
263 + currencySymbol,
264 + currencyPosition,
265 + );
159 266 };
160 267
161 -export const currency = (value: number, currencyCode: string = "USD") => {
162 - const symbol = getCurrencySymbol(currencyCode);
163 - const currencyData = getCurrency(currencyCode);
164 - const decimals = currencyData?.decimalDigits ?? 2;
165 -
166 - const formatted = new Intl.NumberFormat(undefined, {
167 - minimumFractionDigits: decimals,
168 - maximumFractionDigits: decimals,
169 - }).format(value);
170 -
171 - return `${symbol}${formatted}`;
268 +export const currency = (value: number, currencyCode?: string) => {
269 + const cfg = readPriceConfig();
270 + const code = (currencyCode || cfg.globalCurrency || "USD") as string;
271 + return formatYatraMoney(Number(value) || 0, code, { zeroAsUnknown: false });
172 272 };
173 273
174 274 /** Values from `wp_localize_script(…, 'yatraAccountPage', …)` on the account page. */
175 275 export function getYatraAccountPageGlobals(): {
@@ -176,8 +276,9 @@
176 276 logoutUrl: string;
177 277 companyPhone: string;
178 278 companyName: string;
179 279 companyEmail: string;
280 + locale: string;
180 281 } {
181 282 if (typeof window === "undefined") {
182 283 return {
183 284 logoutUrl: "",
@@ -183,8 +284,9 @@
183 284 logoutUrl: "",
184 285 companyPhone: "",
185 286 companyName: "",
186 287 companyEmail: "",
288 + locale: "",
187 289 };
188 290 }
189 291 const p = (window as unknown as { yatraAccountPage?: Record<string, string> })
190 292 .yatraAccountPage;
@@ -193,8 +295,9 @@
193 295 logoutUrl: String(raw.logoutUrl || "").trim(),
194 296 companyPhone: String(raw.companyPhone || "").trim(),
195 297 companyName: String(raw.companyName || "").trim(),
196 298 companyEmail: String(raw.companyEmail || "").trim(),
299 + locale: String((raw as any).locale || "").trim(),
197 300 };
198 301 }
199 302
200 303 export function phoneToTelHref(phone: string): string {