PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.2.8
Yatra – Travel Booking & Tour Operator Software v3.0.2.8
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.2.8, at resources/js/pages/account/Profile.tsx

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