Compare commits
2 Commits
347f709f72
...
5ec22c2a5b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ec22c2a5b | ||
|
|
426f974ed3 |
@@ -86,12 +86,6 @@ describe('Navbar', () => {
|
|||||||
expect(screen.getByPlaceholderText('Search items...')).toBeInTheDocument();
|
expect(screen.getByPlaceholderText('Search items...')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render location input', () => {
|
|
||||||
renderWithRouter(<Navbar />);
|
|
||||||
|
|
||||||
expect(screen.getByPlaceholderText('City or ZIP')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render search button', () => {
|
it('should render search button', () => {
|
||||||
renderWithRouter(<Navbar />);
|
renderWithRouter(<Navbar />);
|
||||||
|
|
||||||
@@ -122,36 +116,6 @@ describe('Navbar', () => {
|
|||||||
expect(mockNavigate).toHaveBeenCalledWith('/items?search=tent');
|
expect(mockNavigate).toHaveBeenCalledWith('/items?search=tent');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should append city to search URL', () => {
|
|
||||||
renderWithRouter(<Navbar />);
|
|
||||||
|
|
||||||
const searchInput = screen.getByPlaceholderText('Search items...');
|
|
||||||
const locationInput = screen.getByPlaceholderText('City or ZIP');
|
|
||||||
|
|
||||||
fireEvent.change(searchInput, { target: { value: 'kayak' } });
|
|
||||||
fireEvent.change(locationInput, { target: { value: 'Seattle' } });
|
|
||||||
|
|
||||||
const searchButton = document.querySelector('button.btn-outline-secondary') as HTMLButtonElement;
|
|
||||||
fireEvent.click(searchButton);
|
|
||||||
|
|
||||||
expect(mockNavigate).toHaveBeenCalledWith('/items?search=kayak&city=Seattle');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should append zipCode when location is a zip code', () => {
|
|
||||||
renderWithRouter(<Navbar />);
|
|
||||||
|
|
||||||
const searchInput = screen.getByPlaceholderText('Search items...');
|
|
||||||
const locationInput = screen.getByPlaceholderText('City or ZIP');
|
|
||||||
|
|
||||||
fireEvent.change(searchInput, { target: { value: 'bike' } });
|
|
||||||
fireEvent.change(locationInput, { target: { value: '98101' } });
|
|
||||||
|
|
||||||
const searchButton = document.querySelector('button.btn-outline-secondary') as HTMLButtonElement;
|
|
||||||
fireEvent.click(searchButton);
|
|
||||||
|
|
||||||
expect(mockNavigate).toHaveBeenCalledWith('/items?search=bike&zipCode=98101');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should clear search fields after search', () => {
|
it('should clear search fields after search', () => {
|
||||||
renderWithRouter(<Navbar />);
|
renderWithRouter(<Navbar />);
|
||||||
|
|
||||||
@@ -169,16 +133,38 @@ describe('Navbar', () => {
|
|||||||
it('should show login button when user is not logged in', () => {
|
it('should show login button when user is not logged in', () => {
|
||||||
renderWithRouter(<Navbar />);
|
renderWithRouter(<Navbar />);
|
||||||
|
|
||||||
expect(screen.getByRole('button', { name: 'Login or Sign Up' })).toBeInTheDocument();
|
// There are two login buttons (mobile + desktop)
|
||||||
|
const loginButtons = screen.getAllByRole('button', { name: 'Login or Sign Up' });
|
||||||
|
expect(loginButtons.length).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call openAuthModal when login button is clicked', () => {
|
it('should call openAuthModal when login button is clicked', () => {
|
||||||
renderWithRouter(<Navbar />);
|
renderWithRouter(<Navbar />);
|
||||||
|
|
||||||
fireEvent.click(screen.getByRole('button', { name: 'Login or Sign Up' }));
|
// Click the first login button (either mobile or desktop)
|
||||||
|
const loginButtons = screen.getAllByRole('button', { name: 'Login or Sign Up' });
|
||||||
|
fireEvent.click(loginButtons[0]);
|
||||||
|
|
||||||
expect(mockOpenAuthModal).toHaveBeenCalledWith('login');
|
expect(mockOpenAuthModal).toHaveBeenCalledWith('login');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should show Forum link when logged out', () => {
|
||||||
|
renderWithRouter(<Navbar />);
|
||||||
|
|
||||||
|
// There are two Forum links (mobile + desktop), both should link to /forum
|
||||||
|
const forumLinks = screen.getAllByRole('link', { name: /Forum/i });
|
||||||
|
expect(forumLinks.length).toBeGreaterThan(0);
|
||||||
|
forumLinks.forEach(link => {
|
||||||
|
expect(link).toHaveAttribute('href', '/forum');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show hamburger icon for mobile toggle when logged out', () => {
|
||||||
|
renderWithRouter(<Navbar />);
|
||||||
|
|
||||||
|
const hamburgerIcon = document.querySelector('.bi-list');
|
||||||
|
expect(hamburgerIcon).toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Logged In State', () => {
|
describe('Logged In State', () => {
|
||||||
@@ -191,10 +177,11 @@ describe('Navbar', () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show user name when logged in', () => {
|
it('should show user avatar when logged in', () => {
|
||||||
renderWithRouter(<Navbar />);
|
renderWithRouter(<Navbar />);
|
||||||
|
|
||||||
expect(screen.getByText('John')).toBeInTheDocument();
|
// Avatar displays user initials (JD for John Doe) - may appear multiple times (mobile + desktop)
|
||||||
|
expect(screen.getAllByText('JD').length).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not show login button when logged in', () => {
|
it('should not show login button when logged in', () => {
|
||||||
@@ -250,19 +237,26 @@ describe('Navbar', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Start Earning Link', () => {
|
|
||||||
it('should show Start Earning link', () => {
|
|
||||||
renderWithRouter(<Navbar />);
|
|
||||||
|
|
||||||
expect(screen.getByRole('link', { name: 'Start Earning' })).toHaveAttribute('href', '/create-item');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Mobile Navigation', () => {
|
describe('Mobile Navigation', () => {
|
||||||
it('should render mobile toggle button', () => {
|
it('should render mobile toggle button', () => {
|
||||||
renderWithRouter(<Navbar />);
|
renderWithRouter(<Navbar />);
|
||||||
|
|
||||||
expect(screen.getByLabelText('Toggle navigation')).toBeInTheDocument();
|
expect(screen.getByLabelText('Toggle navigation')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should show avatar in mobile toggle when logged in', () => {
|
||||||
|
mockUser = {
|
||||||
|
id: 'user-123',
|
||||||
|
firstName: 'John',
|
||||||
|
lastName: 'Doe',
|
||||||
|
email: 'john@example.com',
|
||||||
|
};
|
||||||
|
|
||||||
|
renderWithRouter(<Navbar />);
|
||||||
|
|
||||||
|
// Should have avatar with initials, not hamburger icon
|
||||||
|
expect(screen.getAllByText('JD').length).toBeGreaterThan(0);
|
||||||
|
expect(document.querySelector('.bi-list')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -24,6 +24,16 @@ const ConditionCheckViewerModal: React.FC<ConditionCheckViewerModalProps> = ({
|
|||||||
const [selectedImage, setSelectedImage] = useState(0);
|
const [selectedImage, setSelectedImage] = useState(0);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === "Escape") onHide();
|
||||||
|
};
|
||||||
|
if (show) {
|
||||||
|
document.addEventListener("keydown", handleKeyDown);
|
||||||
|
}
|
||||||
|
return () => document.removeEventListener("keydown", handleKeyDown);
|
||||||
|
}, [show, onHide]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchImageUrls = async () => {
|
const fetchImageUrls = async () => {
|
||||||
if (!conditionCheck?.imageFilenames?.length) return;
|
if (!conditionCheck?.imageFilenames?.length) return;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect, useCallback } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { rentalAPI } from "../services/api";
|
import { rentalAPI } from "../services/api";
|
||||||
import { Rental } from "../types";
|
import { Rental } from "../types";
|
||||||
|
|
||||||
@@ -65,38 +65,17 @@ const DeclineRentalModal: React.FC<DeclineRentalModalProps> = ({
|
|||||||
onHide();
|
onHide();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBackdropClick = useCallback(
|
|
||||||
(e: React.MouseEvent) => {
|
|
||||||
if (e.target === e.currentTarget && !processing) {
|
|
||||||
handleClose();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[handleClose, processing]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleKeyDown = useCallback(
|
|
||||||
(e: KeyboardEvent) => {
|
|
||||||
if (e.key === "Escape" && !processing) {
|
|
||||||
handleClose();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[handleClose, processing]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (show) {
|
if (show) {
|
||||||
document.addEventListener("keydown", handleKeyDown);
|
|
||||||
document.body.style.overflow = "hidden";
|
document.body.style.overflow = "hidden";
|
||||||
} else {
|
} else {
|
||||||
document.removeEventListener("keydown", handleKeyDown);
|
|
||||||
document.body.style.overflow = "unset";
|
document.body.style.overflow = "unset";
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener("keydown", handleKeyDown);
|
|
||||||
document.body.style.overflow = "unset";
|
document.body.style.overflow = "unset";
|
||||||
};
|
};
|
||||||
}, [show, handleKeyDown]);
|
}, [show]);
|
||||||
|
|
||||||
if (!show) return null;
|
if (!show) return null;
|
||||||
|
|
||||||
@@ -104,7 +83,6 @@ const DeclineRentalModal: React.FC<DeclineRentalModalProps> = ({
|
|||||||
<div
|
<div
|
||||||
className="modal d-block"
|
className="modal d-block"
|
||||||
style={{ backgroundColor: "rgba(0,0,0,0.5)" }}
|
style={{ backgroundColor: "rgba(0,0,0,0.5)" }}
|
||||||
onClick={handleBackdropClick}
|
|
||||||
>
|
>
|
||||||
<div className="modal-dialog modal-dialog-centered">
|
<div className="modal-dialog modal-dialog-centered">
|
||||||
<div className="modal-content">
|
<div className="modal-content">
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Link, useNavigate } from "react-router-dom";
|
|||||||
import { useAuth } from "../contexts/AuthContext";
|
import { useAuth } from "../contexts/AuthContext";
|
||||||
import { useSocket } from "../contexts/SocketContext";
|
import { useSocket } from "../contexts/SocketContext";
|
||||||
import { rentalAPI, messageAPI } from "../services/api";
|
import { rentalAPI, messageAPI } from "../services/api";
|
||||||
|
import Avatar from "./Avatar";
|
||||||
|
|
||||||
const Navbar: React.FC = () => {
|
const Navbar: React.FC = () => {
|
||||||
const { user, logout, openAuthModal } = useAuth();
|
const { user, logout, openAuthModal } = useAuth();
|
||||||
@@ -137,7 +138,7 @@ const Navbar: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mobile avatar toggle */}
|
{/* Mobile menu toggle */}
|
||||||
<button
|
<button
|
||||||
className="navbar-toggler border-0 p-0"
|
className="navbar-toggler border-0 p-0"
|
||||||
type="button"
|
type="button"
|
||||||
@@ -147,6 +148,7 @@ const Navbar: React.FC = () => {
|
|||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
aria-label="Toggle navigation"
|
aria-label="Toggle navigation"
|
||||||
>
|
>
|
||||||
|
{user ? (
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@@ -178,15 +180,18 @@ const Navbar: React.FC = () => {
|
|||||||
{pendingRequestsCount + unreadMessagesCount}
|
{pendingRequestsCount + unreadMessagesCount}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
<Avatar user={user} size="sm" />
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
<i
|
<i
|
||||||
className="bi bi-person-circle"
|
className="bi bi-list"
|
||||||
style={{ fontSize: "1.5rem", color: "#333" }}
|
style={{ fontSize: "1.5rem", color: "#333" }}
|
||||||
></i>
|
></i>
|
||||||
</span>
|
)}
|
||||||
</button>
|
</button>
|
||||||
<div className="collapse navbar-collapse" id="navbarNav">
|
<div className="collapse navbar-collapse" id="navbarNav">
|
||||||
<div className="d-flex align-items-center ms-auto">
|
<div className="d-flex align-items-center justify-content-center justify-content-lg-end w-100">
|
||||||
<ul className="navbar-nav flex-row">
|
<ul className="navbar-nav flex-column flex-lg-row">
|
||||||
{user ? (
|
{user ? (
|
||||||
<>
|
<>
|
||||||
<li className="nav-item dropdown">
|
<li className="nav-item dropdown">
|
||||||
@@ -210,29 +215,26 @@ const Navbar: React.FC = () => {
|
|||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
left: "-0.9em",
|
right: "-5px",
|
||||||
top: "50%",
|
top: "-5px",
|
||||||
transform: "translateY(-50%)",
|
|
||||||
backgroundColor: "#dc3545",
|
backgroundColor: "#dc3545",
|
||||||
color: "white",
|
color: "white",
|
||||||
borderRadius: "50%",
|
borderRadius: "50%",
|
||||||
width: "1.5em",
|
width: "1.2em",
|
||||||
height: "1.5em",
|
height: "1.2em",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
fontSize: "0.85em",
|
fontSize: "0.7em",
|
||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
border: "2px solid white",
|
border: "2px solid white",
|
||||||
opacity: 1,
|
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{pendingRequestsCount + unreadMessagesCount}
|
{pendingRequestsCount + unreadMessagesCount}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<i className="bi bi-person-circle me-1"></i>
|
<Avatar user={user} size="sm" />
|
||||||
{user.firstName}
|
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
<ul
|
<ul
|
||||||
@@ -298,14 +300,36 @@ const Navbar: React.FC = () => {
|
|||||||
</li>
|
</li>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<li className="nav-item">
|
<>
|
||||||
|
<li className="nav-item text-center text-lg-end">
|
||||||
|
<Link
|
||||||
|
className="nav-link d-lg-none"
|
||||||
|
to="/forum"
|
||||||
|
>
|
||||||
|
Forum
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
className="btn btn-outline-primary btn-sm text-nowrap d-none d-lg-inline-block me-2"
|
||||||
|
to="/forum"
|
||||||
|
>
|
||||||
|
Forum
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li className="nav-item text-center text-lg-end">
|
||||||
<button
|
<button
|
||||||
className="btn btn-primary btn-sm text-nowrap"
|
className="nav-link d-lg-none w-100 border-0 bg-transparent"
|
||||||
|
onClick={() => openAuthModal("login")}
|
||||||
|
>
|
||||||
|
Login or Sign Up
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn btn-primary btn-sm text-nowrap d-none d-lg-inline-block"
|
||||||
onClick={() => openAuthModal("login")}
|
onClick={() => openAuthModal("login")}
|
||||||
>
|
>
|
||||||
Login or Sign Up
|
Login or Sign Up
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect, useCallback } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { rentalAPI } from "../services/api";
|
import { rentalAPI } from "../services/api";
|
||||||
import { RefundPreview, Rental } from "../types";
|
import { RefundPreview, Rental } from "../types";
|
||||||
|
|
||||||
@@ -129,38 +129,17 @@ const RentalCancellationModal: React.FC<RentalCancellationModalProps> = ({
|
|||||||
return "success";
|
return "success";
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBackdropClick = useCallback(
|
|
||||||
(e: React.MouseEvent) => {
|
|
||||||
if (e.target === e.currentTarget) {
|
|
||||||
handleClose();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[handleClose]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleKeyDown = useCallback(
|
|
||||||
(e: KeyboardEvent) => {
|
|
||||||
if (e.key === "Escape") {
|
|
||||||
handleClose();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[handleClose]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (show) {
|
if (show) {
|
||||||
document.addEventListener("keydown", handleKeyDown);
|
|
||||||
document.body.style.overflow = "hidden";
|
document.body.style.overflow = "hidden";
|
||||||
} else {
|
} else {
|
||||||
document.removeEventListener("keydown", handleKeyDown);
|
|
||||||
document.body.style.overflow = "unset";
|
document.body.style.overflow = "unset";
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener("keydown", handleKeyDown);
|
|
||||||
document.body.style.overflow = "unset";
|
document.body.style.overflow = "unset";
|
||||||
};
|
};
|
||||||
}, [show, handleKeyDown]);
|
}, [show]);
|
||||||
|
|
||||||
if (!show) return null;
|
if (!show) return null;
|
||||||
|
|
||||||
@@ -168,7 +147,6 @@ const RentalCancellationModal: React.FC<RentalCancellationModalProps> = ({
|
|||||||
<div
|
<div
|
||||||
className="modal d-block"
|
className="modal d-block"
|
||||||
style={{ backgroundColor: "rgba(0,0,0,0.5)" }}
|
style={{ backgroundColor: "rgba(0,0,0,0.5)" }}
|
||||||
onClick={handleBackdropClick}
|
|
||||||
>
|
>
|
||||||
<div className="modal-dialog modal-lg modal-dialog-centered">
|
<div className="modal-dialog modal-lg modal-dialog-centered">
|
||||||
<div className="modal-content">
|
<div className="modal-content">
|
||||||
|
|||||||
@@ -353,38 +353,17 @@ const ReturnStatusModal: React.FC<ReturnStatusModalProps> = ({
|
|||||||
onHide();
|
onHide();
|
||||||
}, [onHide]);
|
}, [onHide]);
|
||||||
|
|
||||||
const handleBackdropClick = useCallback(
|
|
||||||
(e: React.MouseEvent) => {
|
|
||||||
if (e.target === e.currentTarget) {
|
|
||||||
handleClose();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[handleClose]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleKeyDown = useCallback(
|
|
||||||
(e: KeyboardEvent) => {
|
|
||||||
if (e.key === "Escape") {
|
|
||||||
handleClose();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[handleClose]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (show) {
|
if (show) {
|
||||||
document.addEventListener("keydown", handleKeyDown);
|
|
||||||
document.body.style.overflow = "hidden";
|
document.body.style.overflow = "hidden";
|
||||||
} else {
|
} else {
|
||||||
document.removeEventListener("keydown", handleKeyDown);
|
|
||||||
document.body.style.overflow = "unset";
|
document.body.style.overflow = "unset";
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener("keydown", handleKeyDown);
|
|
||||||
document.body.style.overflow = "unset";
|
document.body.style.overflow = "unset";
|
||||||
};
|
};
|
||||||
}, [show, handleKeyDown]);
|
}, [show]);
|
||||||
|
|
||||||
if (!show) return null;
|
if (!show) return null;
|
||||||
|
|
||||||
@@ -392,7 +371,6 @@ const ReturnStatusModal: React.FC<ReturnStatusModalProps> = ({
|
|||||||
<div
|
<div
|
||||||
className="modal d-block"
|
className="modal d-block"
|
||||||
style={{ backgroundColor: "rgba(0,0,0,0.5)" }}
|
style={{ backgroundColor: "rgba(0,0,0,0.5)" }}
|
||||||
onClick={handleBackdropClick}
|
|
||||||
>
|
>
|
||||||
<div className="modal-dialog modal-lg modal-dialog-centered">
|
<div className="modal-dialog modal-lg modal-dialog-centered">
|
||||||
<div className="modal-content">
|
<div className="modal-content">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from "react";
|
import React, { useEffect } from "react";
|
||||||
import { Rental } from "../types";
|
import { Rental } from "../types";
|
||||||
import StarRating from "./StarRating";
|
import StarRating from "./StarRating";
|
||||||
|
|
||||||
@@ -15,6 +15,16 @@ const ReviewDetailsModal: React.FC<ReviewDetailsModalProps> = ({
|
|||||||
rental,
|
rental,
|
||||||
userType,
|
userType,
|
||||||
}) => {
|
}) => {
|
||||||
|
useEffect(() => {
|
||||||
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === "Escape") onClose();
|
||||||
|
};
|
||||||
|
if (show) {
|
||||||
|
document.addEventListener("keydown", handleKeyDown);
|
||||||
|
}
|
||||||
|
return () => document.removeEventListener("keydown", handleKeyDown);
|
||||||
|
}, [show, onClose]);
|
||||||
|
|
||||||
if (!show) return null;
|
if (!show) return null;
|
||||||
|
|
||||||
const formatDateTime = (dateString: string) => {
|
const formatDateTime = (dateString: string) => {
|
||||||
@@ -29,6 +39,9 @@ const ReviewDetailsModal: React.FC<ReviewDetailsModalProps> = ({
|
|||||||
className="modal d-block"
|
className="modal d-block"
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
style={{ backgroundColor: "rgba(0,0,0,0.5)" }}
|
style={{ backgroundColor: "rgba(0,0,0,0.5)" }}
|
||||||
|
onClick={(e) => {
|
||||||
|
if (e.target === e.currentTarget) onClose();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="modal-dialog modal-dialog-centered modal-lg">
|
<div className="modal-dialog modal-dialog-centered modal-lg">
|
||||||
<div className="modal-content">
|
<div className="modal-content">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
|
|
||||||
interface SuccessModalProps {
|
interface SuccessModalProps {
|
||||||
show: boolean;
|
show: boolean;
|
||||||
@@ -13,6 +13,16 @@ const SuccessModal: React.FC<SuccessModalProps> = ({
|
|||||||
title = "Success!",
|
title = "Success!",
|
||||||
message
|
message
|
||||||
}) => {
|
}) => {
|
||||||
|
useEffect(() => {
|
||||||
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Escape') onClose();
|
||||||
|
};
|
||||||
|
if (show) {
|
||||||
|
document.addEventListener('keydown', handleKeyDown);
|
||||||
|
}
|
||||||
|
return () => document.removeEventListener('keydown', handleKeyDown);
|
||||||
|
}, [show, onClose]);
|
||||||
|
|
||||||
if (!show) return null;
|
if (!show) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -20,6 +30,9 @@ const SuccessModal: React.FC<SuccessModalProps> = ({
|
|||||||
className="modal d-block"
|
className="modal d-block"
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
style={{ backgroundColor: 'rgba(0,0,0,0.5)' }}
|
style={{ backgroundColor: 'rgba(0,0,0,0.5)' }}
|
||||||
|
onClick={(e) => {
|
||||||
|
if (e.target === e.currentTarget) onClose();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="modal-dialog modal-dialog-centered">
|
<div className="modal-dialog modal-dialog-centered">
|
||||||
<div className="modal-content">
|
<div className="modal-content">
|
||||||
|
|||||||
Reference in New Issue
Block a user