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/AccountPage.tsx +56 -1 3.0.4 → 3.0.17 View file →
@@ -2,8 +2,9 @@
2 2 import { useQuery } from "@tanstack/react-query";
3 3 import { apiClient } from "../lib/api-client";
4 4 import { API_ENDPOINTS } from "../lib/api-endpoints";
5 5 import { __ } from "../lib/i18n";
6 +import { toDateValue } from "../lib/dateFormat";
6 7 import {
7 8 LayoutDashboard,
8 9 Calendar,
9 10 CreditCard,
@@ -33,8 +34,9 @@
33 34 import Payments from "./account/Payments";
34 35 import Documents from "./account/Documents";
35 36 import Profile from "./account/Profile";
36 37 import SavedTrips from "./account/SavedTrips";
38 +import { useToast } from "../components/ui/toast";
37 39
38 40 const navigation: Array<{
39 41 id: Section;
40 42 label: string;
@@ -49,8 +51,9 @@
49 51 ];
50 52
51 53 const AccountPage: React.FC = () => {
52 54 const accountShell = useMemo(() => getYatraAccountPageGlobals(), []);
55 + const { showToast } = useToast();
53 56
54 57 const accountNavigation = React.useMemo(() => {
55 58 const wl =
56 59 typeof window !== "undefined" &&
@@ -142,8 +145,15 @@
142 145 };
143 146
144 147 const [section, setSection] = useState<Section>(getSectionFromUrl);
145 148
149 + // Cross-section booking selection. Lifted here from Bookings.tsx so
150 + // Dashboard's upcoming-trip cards can deep-link straight into the
151 + // booking detail screen — click on the dashboard card sets this id
152 + // AND switches to the "bookings" section; the Bookings component
153 + // honours `initialBookingId` on mount/update to open the detail view.
154 + const [pendingBookingId, setPendingBookingId] = useState<number | null>(null);
155 +
146 156 // Update section when URL changes
147 157 React.useEffect(() => {
148 158 const newSection = getSectionFromUrl();
149 159 setSection(newSection);
@@ -148,8 +158,42 @@
148 158 const newSection = getSectionFromUrl();
149 159 setSection(newSection);
150 160 }, [urlKey]);
151 161
162 + // Surface the result of an email-change confirmation. The confirmation link
163 + // (from WordPress-style verification email) redirects here with
164 + // ?email_change=success|error; show feedback, open the Profile tab, then
165 + // strip the param so a refresh doesn't repeat the toast.
166 + React.useEffect(() => {
167 + if (typeof window === "undefined") {
168 + return;
169 + }
170 + const params = new URLSearchParams(window.location.search);
171 + const outcome = params.get("email_change");
172 + if (outcome !== "success" && outcome !== "error") {
173 + return;
174 + }
175 + if (outcome === "success") {
176 + showToast(__("Your email address has been updated.", "yatra"), "success");
177 + setSection("profile");
178 + } else {
179 + showToast(
180 + __(
181 + "This email confirmation link is invalid or has expired.",
182 + "yatra",
183 + ),
184 + "error",
185 + );
186 + }
187 + params.delete("email_change");
188 + const query = params.toString();
189 + const cleanUrl = `${window.location.pathname}${query ? `?${query}` : ""}`;
190 + window.history.replaceState({}, "", cleanUrl);
191 + (window as any).__lastAccountSearch = window.location.search;
192 + // Run once on mount.
193 + // eslint-disable-next-line react-hooks/exhaustive-deps
194 + }, []);
195 +
152 196 // Update URL and localStorage when section changes
153 197 const handleSectionChange = (newSection: Section) => {
154 198 setSection(newSection);
155 199
@@ -167,8 +211,16 @@
167 211 window.dispatchEvent(new PopStateEvent("popstate"));
168 212 }
169 213 };
170 214
215 + // Open a specific booking from anywhere (currently used by the
216 + // Dashboard's "Upcoming Trips" cards). Switches to the bookings tab
217 + // and seeds the id so Bookings opens straight into the detail view.
218 + const handleBookingOpen = (bookingId: number) => {
219 + setPendingBookingId(bookingId);
220 + handleSectionChange("bookings");
221 + };
222 +
171 223 // State moved to individual components
172 224
173 225 const unwrapArrayResponse = (response: any): any[] => {
174 226 if (!response) return [];
@@ -388,9 +440,9 @@
388 440 const outstanding = payments
389 441 .filter((p: Payment) => p.status === "pending")
390 442 .reduce((sum: number, payment: Payment) => sum + payment.amount, 0);
391 443 const upcoming = bookings.filter(
392 - (b: Booking) => new Date(b.travel_date) > new Date(),
444 + (b: Booking) => toDateValue(b.travel_date) > new Date(),
393 445 ).length;
394 446
395 447 // Calculate from real data
396 448 const totalSpent = displayProfile?.total_spent ?? 0;
@@ -439,8 +491,9 @@
439 491 conciergeEmail={accountShell.companyEmail}
440 492 onSectionChange={(section: string) =>
441 493 handleSectionChange(section as Section)
442 494 }
495 + onBookingOpen={handleBookingOpen}
443 496 />
444 497 );
445 498 case "bookings":
446 499 return (
@@ -450,8 +503,10 @@
450 503 conciergeEmail={accountShell.companyEmail}
451 504 onSectionChange={(section: string) =>
452 505 handleSectionChange(section as Section)
453 506 }
507 + initialBookingId={pendingBookingId}
508 + onBookingIdConsumed={() => setPendingBookingId(null)}
454 509 />
455 510 );
456 511 case "payments":
457 512 return (