PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.5
Yatra – Travel Booking & Tour Operator Software v3.0.5
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 2.0.1 All 84 releases
yatra / resources / js / pages / account / Profile.tsx

Profile.tsx in Yatra – Travel Booking & Tour Operator Software 3.0.5, at resources/js/pages/account/Profile.tsx

450 lines 19.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import React, { useState, useEffect } from "react";
2 import {
3 User,
4 PenSquare,
5 ShieldCheck,
6 Heart,
7 CheckCircle,
8 XCircle,
9 } from "lucide-react";
10 import { __ } from "../../lib/i18n";
11 import { formatDate, currency } from "./utils";
12 import type { CustomerProfile } from "./types";
13
14 interface ProfileProps {
15 profile: CustomerProfile | null;
16 savedTrips: any[];
17 wishlistEnabled?: boolean;
18 }
19
20 const Profile: React.FC<ProfileProps> = ({
21 profile,
22 savedTrips,
23 wishlistEnabled = false,
24 }) => {
25 const [isEditing, setIsEditing] = useState(false);
26 const [formData, setFormData] = useState({
27 name: "",
28 email: "",
29 phone: "",
30 address: "",
31 city: "",
32 country: "",
33 });
34
35 const [isChangingPassword, setIsChangingPassword] = useState(false);
36 const [passwordData, setPasswordData] = useState({
37 currentPassword: "",
38 newPassword: "",
39 confirmPassword: "",
40 });
41
42 // Update formData when profile changes
43 useEffect(() => {
44 if (profile && !isEditing) {
45 setFormData({
46 name: profile.name || "",
47 email: profile.email || "",
48 phone: profile.phone || "",
49 address: profile.address || "",
50 city: profile.city || "",
51 country: profile.country || "",
52 });
53 }
54 }, [profile, isEditing]);
55
56 const handleInputChange = (field: string, value: string) => {
57 setFormData((prev) => ({ ...prev, [field]: value }));
58 };
59
60 const handleSave = () => {
61 // TODO: Implement save functionality
62 setIsEditing(false);
63 };
64
65 const handleCancel = () => {
66 setIsEditing(false);
67 if (profile) {
68 setFormData({
69 name: profile.name || "",
70 email: profile.email || "",
71 phone: profile.phone || "",
72 address: profile.address || "",
73 city: profile.city || "",
74 country: profile.country || "",
75 });
76 }
77 };
78
79 return (
80 <div className="yatra-profile-page space-y-6">
81 {/* Header */}
82 <div className="yatra-profile-header bg-white dark:bg-gray-800 border border-gray-100 dark:border-gray-700 rounded-xl shadow-sm p-6">
83 <div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
84 <div>
85 <h2 className="text-lg font-bold text-gray-900 dark:text-white flex items-center gap-3">
86 <div className="p-2 bg-yatra-soft dark:bg-yatra-surface-dark-muted rounded-lg">
87 <User className="w-6 h-6 text-yatra-primary dark:text-yatra-on-dark" />
88 </div>
89 {__("Profile", "yatra")}
90 </h2>
91 <p className="text-sm text-gray-500 dark:text-gray-400 mt-2">
92 {__(
93 "Update your contact details, address, and communication preferences.",
94 "yatra",
95 )}
96 </p>
97 </div>
98 {!isEditing && (
99 <div
100 role="button"
101 tabIndex={0}
102 onClick={() => setIsEditing(true)}
103 className="yatra-profile-edit-btn inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-yatra-primary text-white hover:bg-yatra-primary-dark transition-colors text-sm font-medium cursor-pointer"
104 >
105 <PenSquare className="w-4 h-4" /> {__("Edit Profile", "yatra")}
106 </div>
107 )}
108 </div>
109 </div>
110
111 {/* Profile Form */}
112 <div className="yatra-profile-card bg-white dark:bg-gray-800 border border-gray-100 dark:border-gray-700 rounded-xl shadow-sm p-6">
113 <div className="yatra-profile-fields space-y-6">
114 <div className="grid gap-6 md:grid-cols-2">
115 <div className="yatra-profile-field">
116 <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
117 {__("Full Name", "yatra")}{" "}
118 <span className="text-red-500">*</span>
119 </label>
120 {isEditing ? (
121 <input
122 type="text"
123 value={formData.name}
124 onChange={(e) => handleInputChange("name", e.target.value)}
125 className="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white px-4 py-2 text-sm focus:ring-2 focus:ring-yatra-primary focus:border-transparent"
126 placeholder={__("Enter your full name", "yatra")}
127 />
128 ) : (
129 <p className="text-sm font-medium text-gray-900 dark:text-white py-2">
130 {formData.name || __("Not set", "yatra")}
131 </p>
132 )}
133 </div>
134
135 <div className="yatra-profile-field">
136 <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
137 {__("Email Address", "yatra")}{" "}
138 <span className="text-red-500">*</span>
139 </label>
140 {isEditing ? (
141 <input
142 type="email"
143 value={formData.email}
144 onChange={(e) => handleInputChange("email", e.target.value)}
145 className="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white px-4 py-2 text-sm focus:ring-2 focus:ring-yatra-primary focus:border-transparent"
146 placeholder={__("Enter your email address", "yatra")}
147 />
148 ) : (
149 <p className="text-sm font-medium text-gray-900 dark:text-white py-2">
150 {formData.email || __("Not set", "yatra")}
151 </p>
152 )}
153 </div>
154
155 <div className="yatra-profile-field">
156 <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
157 {__("Phone Number", "yatra")}
158 </label>
159 {isEditing ? (
160 <input
161 type="tel"
162 value={formData.phone}
163 onChange={(e) => handleInputChange("phone", e.target.value)}
164 className="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white px-4 py-2 text-sm focus:ring-2 focus:ring-yatra-primary focus:border-transparent"
165 placeholder={__("Enter your phone number", "yatra")}
166 />
167 ) : (
168 <p className="text-sm font-medium text-gray-900 dark:text-white py-2">
169 {formData.phone || __("Not set", "yatra")}
170 </p>
171 )}
172 </div>
173
174 <div className="yatra-profile-field">
175 <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
176 {__("City", "yatra")}
177 </label>
178 {isEditing ? (
179 <input
180 type="text"
181 value={formData.city}
182 onChange={(e) => handleInputChange("city", e.target.value)}
183 className="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white px-4 py-2 text-sm focus:ring-2 focus:ring-yatra-primary focus:border-transparent"
184 placeholder={__("Enter your city", "yatra")}
185 />
186 ) : (
187 <p className="text-sm font-medium text-gray-900 dark:text-white py-2">
188 {formData.city || __("Not set", "yatra")}
189 </p>
190 )}
191 </div>
192
193 <div className="yatra-profile-field md:col-span-2">
194 <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
195 {__("Address", "yatra")}
196 </label>
197 {isEditing ? (
198 <textarea
199 value={formData.address}
200 onChange={(e) => handleInputChange("address", e.target.value)}
201 rows={3}
202 className="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white px-4 py-2 text-sm focus:ring-2 focus:ring-yatra-primary focus:border-transparent"
203 placeholder={__("Enter your address", "yatra")}
204 />
205 ) : (
206 <p className="text-sm font-medium text-gray-900 dark:text-white py-2">
207 {formData.address || __("Not set", "yatra")}
208 </p>
209 )}
210 </div>
211
212 <div className="yatra-profile-field">
213 <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
214 {__("Country", "yatra")}
215 </label>
216 {isEditing ? (
217 <input
218 type="text"
219 value={formData.country}
220 onChange={(e) => handleInputChange("country", e.target.value)}
221 className="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white px-4 py-2 text-sm focus:ring-2 focus:ring-yatra-primary focus:border-transparent"
222 placeholder={__("Enter your country", "yatra")}
223 />
224 ) : (
225 <p className="text-sm font-medium text-gray-900 dark:text-white py-2">
226 {formData.country || __("Not set", "yatra")}
227 </p>
228 )}
229 </div>
230 </div>
231
232 {isEditing && (
233 <div className="yatra-profile-actions flex flex-wrap gap-3 pt-4 border-t border-gray-200 dark:border-gray-700">
234 <button
235 type="button"
236 onClick={handleSave}
237 className="inline-flex items-center gap-2 px-6 py-2 rounded-lg bg-yatra-primary text-white hover:bg-yatra-primary-dark transition-colors text-sm font-medium"
238 >
239 <CheckCircle className="w-4 h-4" />
240 {__("Save Changes", "yatra")}
241 </button>
242 <div
243 role="button"
244 tabIndex={0}
245 onClick={handleCancel}
246 className="inline-flex items-center gap-2 px-6 py-2 rounded-lg border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors text-sm font-medium cursor-pointer"
247 >
248 <XCircle className="w-4 h-4" />
249 {__("Cancel", "yatra")}
250 </div>
251 </div>
252 )}
253 </div>
254 </div>
255
256 {/* Password Change Section */}
257 <div className="yatra-profile-password bg-white dark:bg-gray-800 border border-gray-100 dark:border-gray-700 rounded-xl shadow-sm p-6">
258 <div className="flex items-center justify-between mb-6">
259 <div>
260 <h3 className="text-base font-semibold text-gray-900 dark:text-white flex items-center gap-2">
261 <ShieldCheck className="w-5 h-5 text-yatra-primary dark:text-yatra-on-dark" />
262 {__("Change Password", "yatra")}
263 </h3>
264 <p className="text-sm text-gray-500 dark:text-gray-400 mt-1">
265 {__("Update your password to keep your account secure.", "yatra")}
266 </p>
267 </div>
268 {!isChangingPassword && (
269 <div
270 role="button"
271 tabIndex={0}
272 onClick={() => setIsChangingPassword(true)}
273 className="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-yatra-primary text-white hover:bg-yatra-primary-dark transition-colors text-sm font-medium cursor-pointer"
274 >
275 <ShieldCheck className="w-4 h-4" />
276 {__("Change Password", "yatra")}
277 </div>
278 )}
279 </div>
280
281 {isChangingPassword && (
282 <div className="space-y-4">
283 <div className="yatra-password-field">
284 <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
285 {__("Current Password", "yatra")}{" "}
286 <span className="text-red-500">*</span>
287 </label>
288 <input
289 type="password"
290 value={passwordData.currentPassword}
291 onChange={(e) =>
292 setPasswordData((prev) => ({
293 ...prev,
294 currentPassword: e.target.value,
295 }))
296 }
297 className="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white px-4 py-2 text-sm focus:ring-2 focus:ring-yatra-primary focus:border-transparent"
298 placeholder={__("Enter your current password", "yatra")}
299 />
300 </div>
301
302 <div className="yatra-password-field">
303 <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
304 {__("New Password", "yatra")}{" "}
305 <span className="text-red-500">*</span>
306 </label>
307 <input
308 type="password"
309 value={passwordData.newPassword}
310 onChange={(e) =>
311 setPasswordData((prev) => ({
312 ...prev,
313 newPassword: e.target.value,
314 }))
315 }
316 className="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white px-4 py-2 text-sm focus:ring-2 focus:ring-yatra-primary focus:border-transparent"
317 placeholder={__("Enter new password", "yatra")}
318 />
319 </div>
320
321 <div className="yatra-password-field">
322 <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
323 {__("Confirm New Password", "yatra")}{" "}
324 <span className="text-red-500">*</span>
325 </label>
326 <input
327 type="password"
328 value={passwordData.confirmPassword}
329 onChange={(e) =>
330 setPasswordData((prev) => ({
331 ...prev,
332 confirmPassword: e.target.value,
333 }))
334 }
335 className="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white px-4 py-2 text-sm focus:ring-2 focus:ring-yatra-primary focus:border-transparent"
336 placeholder={__("Confirm your new password", "yatra")}
337 />
338 {passwordData.newPassword &&
339 passwordData.confirmPassword &&
340 passwordData.newPassword !== passwordData.confirmPassword && (
341 <p className="text-xs text-red-500 mt-1">
342 {__("Passwords do not match", "yatra")}
343 </p>
344 )}
345 </div>
346
347 <div className="yatra-password-actions flex flex-wrap gap-3 pt-4 border-t border-gray-200 dark:border-gray-700">
348 <button
349 type="button"
350 onClick={() => {
351 // TODO: Implement password change functionality
352 if (
353 passwordData.newPassword !== passwordData.confirmPassword
354 ) {
355 return;
356 }
357 setIsChangingPassword(false);
358 setPasswordData({
359 currentPassword: "",
360 newPassword: "",
361 confirmPassword: "",
362 });
363 }}
364 disabled={
365 !passwordData.currentPassword ||
366 !passwordData.newPassword ||
367 !passwordData.confirmPassword ||
368 passwordData.newPassword !== passwordData.confirmPassword
369 }
370 className="inline-flex items-center gap-2 px-6 py-2 rounded-lg bg-yatra-primary text-white hover:bg-yatra-primary-dark transition-colors text-sm font-medium disabled:opacity-50 disabled:cursor-not-allowed"
371 >
372 <CheckCircle className="w-4 h-4" />
373 {__("Update Password", "yatra")}
374 </button>
375 <div
376 role="button"
377 tabIndex={0}
378 onClick={() => {
379 setIsChangingPassword(false);
380 setPasswordData({
381 currentPassword: "",
382 newPassword: "",
383 confirmPassword: "",
384 });
385 }}
386 className="inline-flex items-center gap-2 px-6 py-2 rounded-lg border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors text-sm font-medium cursor-pointer"
387 >
388 <XCircle className="w-4 h-4" />
389 {__("Cancel", "yatra")}
390 </div>
391 </div>
392 </div>
393 )}
394 </div>
395
396 {/* Additional Sections.
397 Communication Preferences card was removed: it shipped as four
398 checkboxes with `defaultChecked` and no onChange / no save
399 handler / no API persistence — purely decorative. Showing
400 fake toggles is worse than not shipping the feature because
401 customers expect a "Save" round-trip and refresh-revert.
402 Re-introduce only when there's a real preferences endpoint
403 + service-side honouring (suppress emails when toggled off). */}
404 <div className="yatra-profile-sections grid gap-6 lg:grid-cols-1">
405 {wishlistEnabled && (
406 <div className="yatra-profile-saved-trips bg-white dark:bg-gray-800 border border-gray-100 dark:border-gray-700 rounded-xl shadow-sm p-6">
407 <h3 className="text-base font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
408 <Heart className="w-5 h-5 text-red-600 dark:text-red-400" />
409 {__("Saved Trips", "yatra")}
410 </h3>
411 <div className="yatra-saved-trips-list space-y-3">
412 {savedTrips.length > 0 ? (
413 savedTrips.map((trip: any) => (
414 <div
415 key={trip.id || trip.trip_id}
416 className="yatra-saved-trip-item flex items-center justify-between border border-gray-100 dark:border-gray-700 rounded-lg p-4 hover:bg-gray-50 dark:hover:bg-gray-900/40 transition-colors"
417 >
418 <div className="flex-1 min-w-0">
419 <p className="text-sm font-medium text-gray-900 dark:text-white truncate">
420 {trip.trip_title || trip.title}
421 </p>
422 <p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
423 {formatDate(trip.next_departure)}
424 </p>
425 </div>
426 <div className="text-right ml-4 flex-shrink-0">
427 <p className="text-xs text-gray-500 dark:text-gray-400">
428 {__("From", "yatra")}
429 </p>
430 <p className="text-sm font-semibold text-gray-900 dark:text-white">
431 {currency(trip.price_from || trip.price || 0)}
432 </p>
433 </div>
434 </div>
435 ))
436 ) : (
437 <p className="text-sm text-gray-500 dark:text-gray-400 text-center py-4">
438 {__("No saved trips yet", "yatra")}
439 </p>
440 )}
441 </div>
442 </div>
443 )}
444 </div>
445 </div>
446 );
447 };
448
449 export default Profile;
450