simplified create item. Restructured profile. Simplified availability
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import { userAPI, itemAPI, rentalAPI } from '../services/api';
|
||||
import { User, Item, Rental } from '../types';
|
||||
import { getImageUrl } from '../utils/imageUrl';
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useAuth } from "../contexts/AuthContext";
|
||||
import { userAPI, itemAPI, rentalAPI } from "../services/api";
|
||||
import { User, Item, Rental } from "../types";
|
||||
import { getImageUrl } from "../utils/imageUrl";
|
||||
import AvailabilitySettings from "../components/AvailabilitySettings";
|
||||
|
||||
const Profile: React.FC = () => {
|
||||
const { user, updateUser, logout } = useAuth();
|
||||
@@ -10,26 +11,41 @@ const Profile: React.FC = () => {
|
||||
const [editing, setEditing] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [success, setSuccess] = useState<string | null>(null);
|
||||
const [activeSection, setActiveSection] = useState<string>('overview');
|
||||
const [profileData, setProfileData] = useState<User | null>(null);
|
||||
const [formData, setFormData] = useState({
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
address1: '',
|
||||
address2: '',
|
||||
city: '',
|
||||
state: '',
|
||||
zipCode: '',
|
||||
country: '',
|
||||
profileImage: ''
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
address1: "",
|
||||
address2: "",
|
||||
city: "",
|
||||
state: "",
|
||||
zipCode: "",
|
||||
country: "",
|
||||
profileImage: "",
|
||||
});
|
||||
const [imageFile, setImageFile] = useState<File | null>(null);
|
||||
const [imagePreview, setImagePreview] = useState<string | null>(null);
|
||||
const [stats, setStats] = useState({
|
||||
itemsListed: 0,
|
||||
acceptedRentals: 0,
|
||||
totalRentals: 0
|
||||
totalRentals: 0,
|
||||
});
|
||||
const [availabilityData, setAvailabilityData] = useState({
|
||||
generalAvailableAfter: "09:00",
|
||||
generalAvailableBefore: "17:00",
|
||||
specifyTimesPerDay: false,
|
||||
weeklyTimes: {
|
||||
sunday: { availableAfter: "09:00", availableBefore: "17:00" },
|
||||
monday: { availableAfter: "09:00", availableBefore: "17:00" },
|
||||
tuesday: { availableAfter: "09:00", availableBefore: "17:00" },
|
||||
wednesday: { availableAfter: "09:00", availableBefore: "17:00" },
|
||||
thursday: { availableAfter: "09:00", availableBefore: "17:00" },
|
||||
friday: { availableAfter: "09:00", availableBefore: "17:00" },
|
||||
saturday: { availableAfter: "09:00", availableBefore: "17:00" },
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -42,23 +58,23 @@ const Profile: React.FC = () => {
|
||||
const response = await userAPI.getProfile();
|
||||
setProfileData(response.data);
|
||||
setFormData({
|
||||
firstName: response.data.firstName || '',
|
||||
lastName: response.data.lastName || '',
|
||||
email: response.data.email || '',
|
||||
phone: response.data.phone || '',
|
||||
address1: response.data.address1 || '',
|
||||
address2: response.data.address2 || '',
|
||||
city: response.data.city || '',
|
||||
state: response.data.state || '',
|
||||
zipCode: response.data.zipCode || '',
|
||||
country: response.data.country || '',
|
||||
profileImage: response.data.profileImage || ''
|
||||
firstName: response.data.firstName || "",
|
||||
lastName: response.data.lastName || "",
|
||||
email: response.data.email || "",
|
||||
phone: response.data.phone || "",
|
||||
address1: response.data.address1 || "",
|
||||
address2: response.data.address2 || "",
|
||||
city: response.data.city || "",
|
||||
state: response.data.state || "",
|
||||
zipCode: response.data.zipCode || "",
|
||||
country: response.data.country || "",
|
||||
profileImage: response.data.profileImage || "",
|
||||
});
|
||||
if (response.data.profileImage) {
|
||||
setImagePreview(getImageUrl(response.data.profileImage));
|
||||
}
|
||||
} catch (err: any) {
|
||||
setError(err.response?.data?.message || 'Failed to fetch profile');
|
||||
setError(err.response?.data?.message || "Failed to fetch profile");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -69,64 +85,71 @@ const Profile: React.FC = () => {
|
||||
// Fetch user's items
|
||||
const itemsResponse = await itemAPI.getItems();
|
||||
const allItems = itemsResponse.data.items || itemsResponse.data || [];
|
||||
const myItems = allItems.filter((item: Item) => item.ownerId === user?.id);
|
||||
|
||||
const myItems = allItems.filter(
|
||||
(item: Item) => item.ownerId === user?.id
|
||||
);
|
||||
|
||||
// Fetch rentals where user is the owner (rentals on user's items)
|
||||
const ownerRentalsResponse = await rentalAPI.getMyListings();
|
||||
const ownerRentals: Rental[] = ownerRentalsResponse.data;
|
||||
|
||||
const acceptedRentals = ownerRentals.filter(r => ['confirmed', 'active'].includes(r.status));
|
||||
|
||||
|
||||
const acceptedRentals = ownerRentals.filter((r) =>
|
||||
["confirmed", "active"].includes(r.status)
|
||||
);
|
||||
|
||||
setStats({
|
||||
itemsListed: myItems.length,
|
||||
acceptedRentals: acceptedRentals.length,
|
||||
totalRentals: ownerRentals.length
|
||||
totalRentals: ownerRentals.length,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch stats:', err);
|
||||
console.error("Failed to fetch stats:", err);
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
const handleChange = (
|
||||
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
|
||||
) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData(prev => ({ ...prev, [name]: value }));
|
||||
setFormData((prev) => ({ ...prev, [name]: value }));
|
||||
};
|
||||
|
||||
const handleImageChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) {
|
||||
setImageFile(file);
|
||||
|
||||
|
||||
// Show preview
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
setImagePreview(reader.result as string);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
|
||||
|
||||
// Upload image immediately
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('profileImage', file);
|
||||
|
||||
formData.append("profileImage", file);
|
||||
|
||||
const response = await userAPI.uploadProfileImage(formData);
|
||||
|
||||
|
||||
// Update the profileImage in formData with the new filename
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
profileImage: response.data.filename
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
profileImage: response.data.filename,
|
||||
}));
|
||||
|
||||
|
||||
// Update preview to use the uploaded image URL
|
||||
setImagePreview(getImageUrl(response.data.imageUrl));
|
||||
} catch (err: any) {
|
||||
console.error('Image upload error:', err);
|
||||
setError(err.response?.data?.error || 'Failed to upload image');
|
||||
console.error("Image upload error:", err);
|
||||
setError(err.response?.data?.error || "Failed to upload image");
|
||||
// Reset on error
|
||||
setImageFile(null);
|
||||
setImagePreview(profileData?.profileImage ?
|
||||
getImageUrl(profileData.profileImage) :
|
||||
null
|
||||
setImagePreview(
|
||||
profileData?.profileImage
|
||||
? getImageUrl(profileData.profileImage)
|
||||
: null
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -146,12 +169,17 @@ const Profile: React.FC = () => {
|
||||
updateUser(response.data); // Update the auth context
|
||||
setEditing(false);
|
||||
} catch (err: any) {
|
||||
console.error('Profile update error:', err.response?.data);
|
||||
const errorMessage = err.response?.data?.error || err.response?.data?.message || 'Failed to update profile';
|
||||
console.error("Profile update error:", err.response?.data);
|
||||
const errorMessage =
|
||||
err.response?.data?.error ||
|
||||
err.response?.data?.message ||
|
||||
"Failed to update profile";
|
||||
const errorDetails = err.response?.data?.details;
|
||||
|
||||
|
||||
if (errorDetails && Array.isArray(errorDetails)) {
|
||||
const detailMessages = errorDetails.map((d: any) => `${d.field}: ${d.message}`).join(', ');
|
||||
const detailMessages = errorDetails
|
||||
.map((d: any) => `${d.field}: ${d.message}`)
|
||||
.join(", ");
|
||||
setError(`${errorMessage} - ${detailMessages}`);
|
||||
} else {
|
||||
setError(errorMessage);
|
||||
@@ -166,25 +194,41 @@ const Profile: React.FC = () => {
|
||||
// Reset form to original data
|
||||
if (profileData) {
|
||||
setFormData({
|
||||
firstName: profileData.firstName || '',
|
||||
lastName: profileData.lastName || '',
|
||||
email: profileData.email || '',
|
||||
phone: profileData.phone || '',
|
||||
address1: profileData.address1 || '',
|
||||
address2: profileData.address2 || '',
|
||||
city: profileData.city || '',
|
||||
state: profileData.state || '',
|
||||
zipCode: profileData.zipCode || '',
|
||||
country: profileData.country || '',
|
||||
profileImage: profileData.profileImage || ''
|
||||
firstName: profileData.firstName || "",
|
||||
lastName: profileData.lastName || "",
|
||||
email: profileData.email || "",
|
||||
phone: profileData.phone || "",
|
||||
address1: profileData.address1 || "",
|
||||
address2: profileData.address2 || "",
|
||||
city: profileData.city || "",
|
||||
state: profileData.state || "",
|
||||
zipCode: profileData.zipCode || "",
|
||||
country: profileData.country || "",
|
||||
profileImage: profileData.profileImage || "",
|
||||
});
|
||||
setImagePreview(profileData.profileImage ?
|
||||
getImageUrl(profileData.profileImage) :
|
||||
null
|
||||
setImagePreview(
|
||||
profileData.profileImage ? getImageUrl(profileData.profileImage) : null
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAvailabilityChange = (field: string, value: string | boolean) => {
|
||||
setAvailabilityData(prev => ({ ...prev, [field]: value }));
|
||||
};
|
||||
|
||||
const handleWeeklyTimeChange = (day: string, field: 'availableAfter' | 'availableBefore', value: string) => {
|
||||
setAvailabilityData(prev => ({
|
||||
...prev,
|
||||
weeklyTimes: {
|
||||
...prev.weeklyTimes,
|
||||
[day]: {
|
||||
...prev.weeklyTimes[day as keyof typeof prev.weeklyTimes],
|
||||
[field]: value
|
||||
}
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="container mt-5">
|
||||
@@ -199,292 +243,366 @@ const Profile: React.FC = () => {
|
||||
|
||||
return (
|
||||
<div className="container mt-4">
|
||||
<div className="row justify-content-center">
|
||||
<div className="col-md-8">
|
||||
<h1 className="mb-4">My Profile</h1>
|
||||
<h1 className="mb-4">Profile</h1>
|
||||
|
||||
{error && (
|
||||
<div className="alert alert-danger" role="alert">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="alert alert-danger" role="alert">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{success && (
|
||||
<div className="alert alert-success" role="alert">
|
||||
{success}
|
||||
</div>
|
||||
)}
|
||||
{success && (
|
||||
<div className="alert alert-success" role="alert">
|
||||
{success}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="row">
|
||||
{/* Left Sidebar Menu */}
|
||||
<div className="col-md-3">
|
||||
<div className="card">
|
||||
<div className="card-body">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="text-center mb-4">
|
||||
<div className="position-relative d-inline-block">
|
||||
{imagePreview ? (
|
||||
<img
|
||||
src={imagePreview}
|
||||
alt="Profile"
|
||||
className="rounded-circle"
|
||||
style={{ width: '150px', height: '150px', objectFit: 'cover' }}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className="rounded-circle bg-secondary d-flex align-items-center justify-content-center"
|
||||
style={{ width: '150px', height: '150px' }}
|
||||
>
|
||||
<i className="bi bi-person-fill text-white" style={{ fontSize: '3rem' }}></i>
|
||||
<div className="list-group list-group-flush">
|
||||
<button
|
||||
className={`list-group-item list-group-item-action ${activeSection === 'overview' ? 'active' : ''}`}
|
||||
onClick={() => setActiveSection('overview')}
|
||||
>
|
||||
<i className="bi bi-person-circle me-2"></i>
|
||||
Overview
|
||||
</button>
|
||||
<button
|
||||
className={`list-group-item list-group-item-action ${activeSection === 'owner-settings' ? 'active' : ''}`}
|
||||
onClick={() => setActiveSection('owner-settings')}
|
||||
>
|
||||
<i className="bi bi-gear me-2"></i>
|
||||
Owner Settings
|
||||
</button>
|
||||
<button
|
||||
className={`list-group-item list-group-item-action ${activeSection === 'personal-info' ? 'active' : ''}`}
|
||||
onClick={() => setActiveSection('personal-info')}
|
||||
>
|
||||
<i className="bi bi-person me-2"></i>
|
||||
Personal Information
|
||||
</button>
|
||||
<button
|
||||
className={`list-group-item list-group-item-action ${activeSection === 'notifications' ? 'active' : ''}`}
|
||||
onClick={() => setActiveSection('notifications')}
|
||||
>
|
||||
<i className="bi bi-bell me-2"></i>
|
||||
Notification Settings
|
||||
</button>
|
||||
<button
|
||||
className={`list-group-item list-group-item-action ${activeSection === 'privacy' ? 'active' : ''}`}
|
||||
onClick={() => setActiveSection('privacy')}
|
||||
>
|
||||
<i className="bi bi-shield-lock me-2"></i>
|
||||
Privacy & Security
|
||||
</button>
|
||||
<button
|
||||
className={`list-group-item list-group-item-action ${activeSection === 'payment' ? 'active' : ''}`}
|
||||
onClick={() => setActiveSection('payment')}
|
||||
>
|
||||
<i className="bi bi-credit-card me-2"></i>
|
||||
Payment Methods
|
||||
</button>
|
||||
<button
|
||||
className="list-group-item list-group-item-action text-danger"
|
||||
onClick={logout}
|
||||
>
|
||||
<i className="bi bi-box-arrow-right me-2"></i>
|
||||
Log Out
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Content Area */}
|
||||
<div className="col-md-9">
|
||||
{/* Overview Section */}
|
||||
{activeSection === 'overview' && (
|
||||
<div>
|
||||
<h4 className="mb-4">Overview</h4>
|
||||
|
||||
{/* Profile Card */}
|
||||
<div className="card mb-4">
|
||||
<div className="card-body">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="text-center">
|
||||
<div className="position-relative d-inline-block mb-3">
|
||||
{imagePreview ? (
|
||||
<img
|
||||
src={imagePreview}
|
||||
alt="Profile"
|
||||
className="rounded-circle"
|
||||
style={{
|
||||
width: "120px",
|
||||
height: "120px",
|
||||
objectFit: "cover",
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className="rounded-circle bg-secondary d-flex align-items-center justify-content-center"
|
||||
style={{ width: "120px", height: "120px" }}
|
||||
>
|
||||
<i
|
||||
className="bi bi-person-fill text-white"
|
||||
style={{ fontSize: "2.5rem" }}
|
||||
></i>
|
||||
</div>
|
||||
)}
|
||||
{editing && (
|
||||
<label
|
||||
htmlFor="profileImageOverview"
|
||||
className="position-absolute bottom-0 end-0 btn btn-sm btn-primary rounded-circle"
|
||||
style={{ width: "35px", height: "35px", padding: "0" }}
|
||||
>
|
||||
<i className="bi bi-camera-fill"></i>
|
||||
<input
|
||||
type="file"
|
||||
id="profileImageOverview"
|
||||
accept="image/*"
|
||||
onChange={handleImageChange}
|
||||
className="d-none"
|
||||
/>
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{editing ? (
|
||||
<div>
|
||||
<div className="row justify-content-center mb-3">
|
||||
<div className="col-md-6">
|
||||
<input
|
||||
type="text"
|
||||
className="form-control mb-2"
|
||||
name="firstName"
|
||||
value={formData.firstName}
|
||||
onChange={handleChange}
|
||||
placeholder="First Name"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<input
|
||||
type="text"
|
||||
className="form-control mb-2"
|
||||
name="lastName"
|
||||
value={formData.lastName}
|
||||
onChange={handleChange}
|
||||
placeholder="Last Name"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="d-flex gap-2 justify-content-center">
|
||||
<button type="submit" className="btn btn-primary">
|
||||
Save Changes
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary"
|
||||
onClick={handleCancel}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<h5>
|
||||
{profileData?.firstName} {profileData?.lastName}
|
||||
</h5>
|
||||
<p className="text-muted">@{profileData?.username}</p>
|
||||
{profileData?.isVerified && (
|
||||
<span className="badge bg-success mb-3">
|
||||
<i className="bi bi-check-circle-fill"></i> Verified
|
||||
</span>
|
||||
)}
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-outline-primary"
|
||||
onClick={() => setEditing(true)}
|
||||
>
|
||||
<i className="bi bi-pencil me-2"></i>
|
||||
Edit Profile
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats Card */}
|
||||
<div className="card">
|
||||
<div className="card-body">
|
||||
<h5 className="card-title">Account Statistics</h5>
|
||||
<div className="row text-center">
|
||||
<div className="col-md-4">
|
||||
<div className="p-3">
|
||||
<h4 className="text-primary mb-1">{stats.itemsListed}</h4>
|
||||
<h6 className="text-muted">Items Listed</h6>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-4">
|
||||
<div className="p-3">
|
||||
<h4 className="text-success mb-1">{stats.acceptedRentals}</h4>
|
||||
<h6 className="text-muted">Active Rentals</h6>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-4">
|
||||
<div className="p-3">
|
||||
<h4 className="text-info mb-1">{stats.totalRentals}</h4>
|
||||
<h6 className="text-muted">Total Rentals</h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{editing && (
|
||||
<label
|
||||
htmlFor="profileImage"
|
||||
className="position-absolute bottom-0 end-0 btn btn-sm btn-primary rounded-circle"
|
||||
style={{ width: '40px', height: '40px', padding: '0' }}
|
||||
>
|
||||
<i className="bi bi-camera-fill"></i>
|
||||
<input
|
||||
type="file"
|
||||
id="profileImage"
|
||||
accept="image/*"
|
||||
onChange={handleImageChange}
|
||||
className="d-none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Personal Information Section */}
|
||||
{activeSection === 'personal-info' && (
|
||||
<div>
|
||||
<h4 className="mb-4">Personal Information</h4>
|
||||
<div className="card">
|
||||
<div className="card-body">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="mb-3">
|
||||
<label htmlFor="email" className="form-label">
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
className="form-control"
|
||||
id="email"
|
||||
name="email"
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
disabled={!editing}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<label htmlFor="phone" className="form-label">
|
||||
Phone Number
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
className="form-control"
|
||||
id="phone"
|
||||
name="phone"
|
||||
value={formData.phone}
|
||||
onChange={handleChange}
|
||||
placeholder="+1 (555) 123-4567"
|
||||
disabled={!editing}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{editing ? (
|
||||
<div className="d-flex gap-2">
|
||||
<button type="submit" className="btn btn-primary">
|
||||
Save Changes
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary"
|
||||
onClick={handleCancel}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary"
|
||||
onClick={() => setEditing(true)}
|
||||
>
|
||||
Edit Information
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<h5 className="mt-3">{profileData?.firstName} {profileData?.lastName}</h5>
|
||||
<p className="text-muted">@{profileData?.username}</p>
|
||||
{profileData?.isVerified && (
|
||||
<span className="badge bg-success">
|
||||
<i className="bi bi-check-circle-fill"></i> Verified
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="row mb-3">
|
||||
<div className="col-md-6">
|
||||
<label htmlFor="firstName" className="form-label">First Name</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
id="firstName"
|
||||
name="firstName"
|
||||
value={formData.firstName}
|
||||
onChange={handleChange}
|
||||
disabled={!editing}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<label htmlFor="lastName" className="form-label">Last Name</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
id="lastName"
|
||||
name="lastName"
|
||||
value={formData.lastName}
|
||||
onChange={handleChange}
|
||||
disabled={!editing}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<label htmlFor="email" className="form-label">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
className="form-control"
|
||||
id="email"
|
||||
name="email"
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
disabled={!editing}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<label htmlFor="phone" className="form-label">Phone Number</label>
|
||||
<input
|
||||
type="tel"
|
||||
className="form-control"
|
||||
id="phone"
|
||||
name="phone"
|
||||
value={formData.phone}
|
||||
onChange={handleChange}
|
||||
placeholder="+1 (555) 123-4567"
|
||||
disabled={!editing}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="row mb-3">
|
||||
<div className="col-md-6">
|
||||
<label htmlFor="address1" className="form-label">Address Line 1</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
id="address1"
|
||||
name="address1"
|
||||
value={formData.address1}
|
||||
onChange={handleChange}
|
||||
placeholder="123 Main Street"
|
||||
disabled={!editing}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<label htmlFor="address2" className="form-label">Address Line 2</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
id="address2"
|
||||
name="address2"
|
||||
value={formData.address2}
|
||||
onChange={handleChange}
|
||||
placeholder="Apt, Suite, Unit, etc."
|
||||
disabled={!editing}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row mb-3">
|
||||
<div className="col-md-6">
|
||||
<label htmlFor="city" className="form-label">City</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
id="city"
|
||||
name="city"
|
||||
value={formData.city}
|
||||
onChange={handleChange}
|
||||
disabled={!editing}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-md-3">
|
||||
<label htmlFor="state" className="form-label">State</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
id="state"
|
||||
name="state"
|
||||
value={formData.state}
|
||||
onChange={handleChange}
|
||||
placeholder="CA"
|
||||
disabled={!editing}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-md-3">
|
||||
<label htmlFor="zipCode" className="form-label">ZIP Code</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
id="zipCode"
|
||||
name="zipCode"
|
||||
value={formData.zipCode}
|
||||
onChange={handleChange}
|
||||
placeholder="12345"
|
||||
disabled={!editing}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<label htmlFor="country" className="form-label">Country</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
id="country"
|
||||
name="country"
|
||||
value={formData.country}
|
||||
onChange={handleChange}
|
||||
placeholder="United States"
|
||||
disabled={!editing}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{editing ? (
|
||||
<div className="d-flex gap-2">
|
||||
<button type="submit" className="btn btn-primary">
|
||||
Save Changes
|
||||
</button>
|
||||
<button type="button" className="btn btn-secondary" onClick={handleCancel}>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary"
|
||||
onClick={() => setEditing(true)}
|
||||
>
|
||||
Edit Profile
|
||||
</button>
|
||||
)}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card mt-4">
|
||||
<div className="card-body">
|
||||
<h5 className="card-title">Account Statistics</h5>
|
||||
<div className="row text-center">
|
||||
<div className="col-md-4">
|
||||
<h3 className="text-primary">{stats.itemsListed}</h3>
|
||||
<p className="text-muted">Items Listed</p>
|
||||
</div>
|
||||
<div className="col-md-4">
|
||||
<h3 className="text-success">{stats.acceptedRentals}</h3>
|
||||
<p className="text-muted">Accepted Rentals</p>
|
||||
</div>
|
||||
<div className="col-md-4">
|
||||
<h3 className="text-info">{stats.totalRentals}</h3>
|
||||
<p className="text-muted">Total Rentals</p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="card mt-4">
|
||||
<div className="card-body">
|
||||
<h5 className="card-title">Account Settings</h5>
|
||||
<div className="list-group list-group-flush">
|
||||
<a href="#" className="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<i className="bi bi-bell me-2"></i>
|
||||
Notification Settings
|
||||
{/* Owner Settings Section */}
|
||||
{activeSection === 'owner-settings' && (
|
||||
<div>
|
||||
<h4 className="mb-4">Owner Settings</h4>
|
||||
<div className="card">
|
||||
<div className="card-body">
|
||||
{/* Addresses Section */}
|
||||
<div className="mb-5">
|
||||
<h5 className="mb-3">Saved Addresses</h5>
|
||||
<p className="text-muted small mb-3">Manage addresses for your rental locations</p>
|
||||
<button className="btn btn-outline-primary">
|
||||
<i className="bi bi-plus-circle me-2"></i>
|
||||
Add New Address
|
||||
</button>
|
||||
</div>
|
||||
<i className="bi bi-chevron-right"></i>
|
||||
</a>
|
||||
<a href="#" className="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
|
||||
|
||||
{/* Availability Section */}
|
||||
<div>
|
||||
<i className="bi bi-shield-lock me-2"></i>
|
||||
Privacy & Security
|
||||
<h5 className="mb-3">Default Availability</h5>
|
||||
<p className="text-muted small mb-3">Set your general availability for all items</p>
|
||||
<AvailabilitySettings
|
||||
data={availabilityData}
|
||||
onChange={handleAvailabilityChange}
|
||||
onWeeklyTimeChange={handleWeeklyTimeChange}
|
||||
showTitle={false}
|
||||
/>
|
||||
<button className="btn btn-outline-success mt-3">
|
||||
<i className="bi bi-check2 me-2"></i>
|
||||
Save Availability
|
||||
</button>
|
||||
</div>
|
||||
<i className="bi bi-chevron-right"></i>
|
||||
</a>
|
||||
<a href="#" className="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<i className="bi bi-credit-card me-2"></i>
|
||||
Payment Methods
|
||||
</div>
|
||||
<i className="bi bi-chevron-right"></i>
|
||||
</a>
|
||||
<button
|
||||
onClick={logout}
|
||||
className="list-group-item list-group-item-action d-flex justify-content-between align-items-center text-danger border-0 w-100 text-start"
|
||||
>
|
||||
<div>
|
||||
<i className="bi bi-box-arrow-right me-2"></i>
|
||||
Log Out
|
||||
</div>
|
||||
<i className="bi bi-chevron-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Placeholder sections for other menu items */}
|
||||
{activeSection === 'notifications' && (
|
||||
<div>
|
||||
<h4 className="mb-4">Notification Settings</h4>
|
||||
<div className="card">
|
||||
<div className="card-body">
|
||||
<p className="text-muted">Notification preferences coming soon...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeSection === 'privacy' && (
|
||||
<div>
|
||||
<h4 className="mb-4">Privacy & Security</h4>
|
||||
<div className="card">
|
||||
<div className="card-body">
|
||||
<p className="text-muted">Privacy and security settings coming soon...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeSection === 'payment' && (
|
||||
<div>
|
||||
<h4 className="mb-4">Payment Methods</h4>
|
||||
<div className="card">
|
||||
<div className="card-body">
|
||||
<p className="text-muted">Payment method management coming soon...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Profile;
|
||||
export default Profile;
|
||||
|
||||
Reference in New Issue
Block a user