Compare commits

...

3 Commits

Author SHA1 Message Date
jackiettran
9c258177ae home page styling changes 2025-11-04 17:17:44 -05:00
jackiettran
6ec7858bbd added spacing 2025-11-03 15:41:37 -05:00
jackiettran
6d0beccea0 refactor mylistings and my rentals 2025-11-01 22:33:59 -04:00
18 changed files with 291 additions and 443 deletions

View File

@@ -53,7 +53,7 @@ const checkAndUpdateReviewVisibility = async (rental) => {
return rental; return rental;
}; };
router.get("/my-rentals", authenticateToken, async (req, res) => { router.get("/renting", authenticateToken, async (req, res) => {
try { try {
const rentals = await Rental.findAll({ const rentals = await Rental.findAll({
where: { renterId: req.user.id }, where: { renterId: req.user.id },
@@ -72,7 +72,7 @@ router.get("/my-rentals", authenticateToken, async (req, res) => {
res.json(rentals); res.json(rentals);
} catch (error) { } catch (error) {
const reqLogger = logger.withRequestId(req.id); const reqLogger = logger.withRequestId(req.id);
reqLogger.error("Error in my-rentals route", { reqLogger.error("Error in renting route", {
error: error.message, error: error.message,
stack: error.stack, stack: error.stack,
userId: req.user.id, userId: req.user.id,
@@ -81,7 +81,7 @@ router.get("/my-rentals", authenticateToken, async (req, res) => {
} }
}); });
router.get("/my-listings", authenticateToken, async (req, res) => { router.get("/owning", authenticateToken, async (req, res) => {
try { try {
const rentals = await Rental.findAll({ const rentals = await Rental.findAll({
where: { ownerId: req.user.id }, where: { ownerId: req.user.id },
@@ -100,7 +100,7 @@ router.get("/my-listings", authenticateToken, async (req, res) => {
res.json(rentals); res.json(rentals);
} catch (error) { } catch (error) {
const reqLogger = logger.withRequestId(req.id); const reqLogger = logger.withRequestId(req.id);
reqLogger.error("Error in my-listings route", { reqLogger.error("Error in owning route", {
error: error.message, error: error.message,
stack: error.stack, stack: error.stack,
userId: req.user.id, userId: req.user.id,

View File

@@ -442,7 +442,7 @@ class EmailService {
<p><strong>Rental Period:</strong> {{startDate}} to {{endDate}}</p> <p><strong>Rental Period:</strong> {{startDate}} to {{endDate}}</p>
{{earningsSection}} {{earningsSection}}
{{stripeSection}} {{stripeSection}}
<p><a href="{{myListingsUrl}}" class="button">View My Listings</a></p> <p><a href="{{owningUrl}}" class="button">View My Listings</a></p>
` `
), ),
@@ -697,7 +697,7 @@ class EmailService {
async sendRentalRequestEmail(rental) { async sendRentalRequestEmail(rental) {
const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000"; const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000";
const approveUrl = `${frontendUrl}/my-listings?rentalId=${rental.id}`; const approveUrl = `${frontendUrl}/owning?rentalId=${rental.id}`;
// Fetch owner details // Fetch owner details
const owner = await User.findByPk(rental.ownerId, { const owner = await User.findByPk(rental.ownerId, {
@@ -754,7 +754,7 @@ class EmailService {
async sendRentalRequestConfirmationEmail(rental) { async sendRentalRequestConfirmationEmail(rental) {
const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000"; const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000";
const viewRentalsUrl = `${frontendUrl}/my-rentals`; const viewRentalsUrl = `${frontendUrl}/renting`;
// Fetch renter details // Fetch renter details
const renter = await User.findByPk(rental.renterId, { const renter = await User.findByPk(rental.renterId, {
@@ -1539,7 +1539,7 @@ class EmailService {
paymentMessage: paymentMessage, paymentMessage: paymentMessage,
earningsSection: earningsSection, earningsSection: earningsSection,
stripeSection: stripeSection, stripeSection: stripeSection,
rentalDetailsUrl: `${frontendUrl}/my-listings?rentalId=${rental.id}`, rentalDetailsUrl: `${frontendUrl}/owning?rentalId=${rental.id}`,
}; };
const htmlContent = this.renderTemplate( const htmlContent = this.renderTemplate(
@@ -1615,7 +1615,7 @@ class EmailService {
</ul> </ul>
</div> </div>
<p style="text-align: center;"> <p style="text-align: center;">
<a href="${frontendUrl}/my-rentals?rentalId=${rental.id}&action=review" class="button">Leave a Review</a> <a href="${frontendUrl}/renting?rentalId=${rental.id}&action=review" class="button">Leave a Review</a>
</p> </p>
`; `;
} else { } else {
@@ -1749,7 +1749,7 @@ class EmailService {
returnedDate: returnedDate, returnedDate: returnedDate,
earningsSection: earningsSection, earningsSection: earningsSection,
stripeSection: stripeSection, stripeSection: stripeSection,
myListingsUrl: `${frontendUrl}/my-listings`, owningUrl: `${frontendUrl}/owning`,
}; };
const ownerHtmlContent = this.renderTemplate( const ownerHtmlContent = this.renderTemplate(

View File

@@ -355,7 +355,7 @@
<p>Have more items sitting idle? Turn them into income! List camping gear, tools, party supplies, sports equipment, or anything else that others might need.</p> <p>Have more items sitting idle? Turn them into income! List camping gear, tools, party supplies, sports equipment, or anything else that others might need.</p>
<p style="text-align: center;"> <p style="text-align: center;">
<a href="{{myListingsUrl}}" class="button">View My Listings</a> <a href="{{owningUrl}}" class="button">View My Listings</a>
</p> </p>
<p>Thank you for being an excellent host on RentAll!</p> <p>Thank you for being an excellent host on RentAll!</p>

View File

@@ -66,7 +66,7 @@ describe('Rentals Routes', () => {
jest.clearAllMocks(); jest.clearAllMocks();
}); });
describe('GET /my-rentals', () => { describe('GET /renting', () => {
it('should get rentals for authenticated user', async () => { it('should get rentals for authenticated user', async () => {
const mockRentals = [ const mockRentals = [
{ {
@@ -86,7 +86,7 @@ describe('Rentals Routes', () => {
mockRentalFindAll.mockResolvedValue(mockRentals); mockRentalFindAll.mockResolvedValue(mockRentals);
const response = await request(app) const response = await request(app)
.get('/rentals/my-rentals'); .get('/rentals/renting');
expect(response.status).toBe(200); expect(response.status).toBe(200);
expect(response.body).toEqual(mockRentals); expect(response.body).toEqual(mockRentals);
@@ -108,14 +108,14 @@ describe('Rentals Routes', () => {
mockRentalFindAll.mockRejectedValue(new Error('Database error')); mockRentalFindAll.mockRejectedValue(new Error('Database error'));
const response = await request(app) const response = await request(app)
.get('/rentals/my-rentals'); .get('/rentals/renting');
expect(response.status).toBe(500); expect(response.status).toBe(500);
expect(response.body).toEqual({ error: 'Failed to fetch rentals' }); expect(response.body).toEqual({ error: 'Failed to fetch rentals' });
}); });
}); });
describe('GET /my-listings', () => { describe('GET /owning', () => {
it('should get listings for authenticated user', async () => { it('should get listings for authenticated user', async () => {
const mockListings = [ const mockListings = [
{ {
@@ -129,7 +129,7 @@ describe('Rentals Routes', () => {
mockRentalFindAll.mockResolvedValue(mockListings); mockRentalFindAll.mockResolvedValue(mockListings);
const response = await request(app) const response = await request(app)
.get('/rentals/my-listings'); .get('/rentals/owning');
expect(response.status).toBe(200); expect(response.status).toBe(200);
expect(response.body).toEqual(mockListings); expect(response.body).toEqual(mockListings);
@@ -151,7 +151,7 @@ describe('Rentals Routes', () => {
mockRentalFindAll.mockRejectedValue(new Error('Database error')); mockRentalFindAll.mockRejectedValue(new Error('Database error'));
const response = await request(app) const response = await request(app)
.get('/rentals/my-listings'); .get('/rentals/owning');
expect(response.status).toBe(500); expect(response.status).toBe(500);
expect(response.body).toEqual({ error: 'Failed to fetch listings' }); expect(response.body).toEqual({ error: 'Failed to fetch listings' });

View File

@@ -15,8 +15,8 @@ import ItemDetail from './pages/ItemDetail';
import EditItem from './pages/EditItem'; import EditItem from './pages/EditItem';
import RentItem from './pages/RentItem'; import RentItem from './pages/RentItem';
import CreateItem from './pages/CreateItem'; import CreateItem from './pages/CreateItem';
import MyRentals from './pages/MyRentals'; import Renting from './pages/Renting';
import MyListings from './pages/MyListings'; import Owning from './pages/Owning';
import Profile from './pages/Profile'; import Profile from './pages/Profile';
import PublicProfile from './pages/PublicProfile'; import PublicProfile from './pages/PublicProfile';
import Messages from './pages/Messages'; import Messages from './pages/Messages';
@@ -117,18 +117,18 @@ const AppContent: React.FC = () => {
} }
/> />
<Route <Route
path="/my-rentals" path="/renting"
element={ element={
<PrivateRoute> <PrivateRoute>
<MyRentals /> <Renting />
</PrivateRoute> </PrivateRoute>
} }
/> />
<Route <Route
path="/my-listings" path="/owning"
element={ element={
<PrivateRoute> <PrivateRoute>
<MyListings /> <Owning />
</PrivateRoute> </PrivateRoute>
} }
/> />

View File

@@ -6,9 +6,10 @@ interface AuthButtonProps {
className?: string; className?: string;
children: React.ReactNode; children: React.ReactNode;
asLink?: boolean; asLink?: boolean;
style?: React.CSSProperties;
} }
const AuthButton: React.FC<AuthButtonProps> = ({ mode, className = '', children, asLink = false }) => { const AuthButton: React.FC<AuthButtonProps> = ({ mode, className = '', children, asLink = false, style }) => {
const { openAuthModal } = useAuth(); const { openAuthModal } = useAuth();
const handleClick = (e: React.MouseEvent) => { const handleClick = (e: React.MouseEvent) => {
@@ -22,6 +23,7 @@ const AuthButton: React.FC<AuthButtonProps> = ({ mode, className = '', children,
href="#" href="#"
onClick={handleClick} onClick={handleClick}
className={className} className={className}
style={style}
> >
{children} {children}
</a> </a>
@@ -32,6 +34,7 @@ const AuthButton: React.FC<AuthButtonProps> = ({ mode, className = '', children,
<button <button
onClick={handleClick} onClick={handleClick}
className={className} className={className}
style={style}
> >
{children} {children}
</button> </button>

View File

@@ -4,139 +4,51 @@ import { Link } from 'react-router-dom';
const Footer: React.FC = () => { const Footer: React.FC = () => {
return ( return (
<footer className="bg-dark text-white"> <footer className="bg-dark text-white">
<div className="container-fluid py-4" style={{ maxWidth: '1800px' }}> <div className="container py-4">
<div className="row"> <div className="text-center">
<div className="col-lg-3"> {/* Social Media Icons */}
<h5 className="mb-3"> <div className="mb-3">
<i className="bi bi-box-seam me-2"></i> <a href="#" className="text-white-50 me-3">
CommunityRentals.App <i className="bi bi-facebook"></i>
</h5> </a>
<p className="small text-white-50"> <a href="#" className="text-white-50 me-3">
The marketplace for renting anything, from anyone, anywhere. <i className="bi bi-twitter"></i>
</p> </a>
<a href="#" className="text-white-50 me-3">
<i className="bi bi-instagram"></i>
</a>
<a href="#" className="text-white-50">
<i className="bi bi-linkedin"></i>
</a>
</div> </div>
<div className="col-lg-2 col-md-6">
<h6 className="mb-3">FAQ</h6> {/* Essential Links */}
<ul className="list-unstyled small"> <div className="mb-3 small">
<li className="mb-2"> <Link to="/about" className="text-decoration-none text-white-50 me-2">
<Link to="/faq/renters" className="text-decoration-none text-white-50"> About
Renter FAQ </Link>
</Link> <span className="text-white-50"></span>
</li> <Link to="/contact" className="text-decoration-none text-white-50 mx-2">
<li className="mb-2"> Contact
<Link to="/faq/owners" className="text-decoration-none text-white-50"> </Link>
Owner FAQ <span className="text-white-50"></span>
</Link> <Link to="/privacy" className="text-decoration-none text-white-50 mx-2">
</li>
<li className="mb-2">
<Link to="/faq/payments" className="text-decoration-none text-white-50">
Payments
</Link>
</li>
<li className="mb-2">
<Link to="/help" className="text-decoration-none text-white-50">
Help Center
</Link>
</li>
</ul>
</div>
<div className="col-lg-2 col-md-6">
<h6 className="mb-3">For Renters</h6>
<ul className="list-unstyled small">
<li className="mb-2">
<Link to="/items" className="text-decoration-none text-white-50">
Browse Items
</Link>
</li>
<li className="mb-2">
<Link to="/how-it-works" className="text-decoration-none text-white-50">
How It Works
</Link>
</li>
<li className="mb-2">
<Link to="/safety" className="text-decoration-none text-white-50">
Safety Tips
</Link>
</li>
</ul>
</div>
<div className="col-lg-2 col-md-6">
<h6 className="mb-3">For Owners</h6>
<ul className="list-unstyled small">
<li className="mb-2">
<Link to="/create-item" className="text-decoration-none text-white-50">
List an Item
</Link>
</li>
<li className="mb-2">
<Link to="/owner-guide" className="text-decoration-none text-white-50">
Owner Guide
</Link>
</li>
<li className="mb-2">
<Link to="/insurance" className="text-decoration-none text-white-50">
Insurance
</Link>
</li>
</ul>
</div>
<div className="col-lg-2 col-md-6">
<h6 className="mb-3">Company</h6>
<ul className="list-unstyled small">
<li className="mb-2">
<Link to="/about" className="text-decoration-none text-white-50">
About Us
</Link>
</li>
<li className="mb-2">
<Link to="/contact" className="text-decoration-none text-white-50">
Contact
</Link>
</li>
<li className="mb-2">
<Link to="/careers" className="text-decoration-none text-white-50">
Careers
</Link>
</li>
</ul>
</div>
<div className="col-lg-1 col-md-6">
<h6 className="mb-3">Follow Us</h6>
<div className="d-flex gap-3">
<a href="#" className="text-white-50">
<i className="bi bi-facebook"></i>
</a>
<a href="#" className="text-white-50">
<i className="bi bi-twitter"></i>
</a>
<a href="#" className="text-white-50">
<i className="bi bi-instagram"></i>
</a>
<a href="#" className="text-white-50">
<i className="bi bi-linkedin"></i>
</a>
</div>
</div>
</div>
<hr className="my-4 bg-secondary" />
<div className="row">
<div className="col-md-6">
<p className="small text-white-50 mb-0">
© 2025 CommunityRentals.App. All rights reserved.
</p>
</div>
<div className="col-md-6 text-md-end">
<Link to="/privacy" className="text-decoration-none text-white-50 small me-3">
Privacy Policy Privacy Policy
</Link> </Link>
<Link to="/terms" className="text-decoration-none text-white-50 small"> <span className="text-white-50"></span>
<Link to="/terms" className="text-decoration-none text-white-50 ms-2">
Terms of Service Terms of Service
</Link> </Link>
</div> </div>
{/* Copyright */}
<p className="small text-white-50 mb-0">
© 2025 CommunityRentals.App. All rights reserved.
</p>
</div> </div>
</div> </div>
</footer> </footer>
); );
}; };
export default Footer; export default Footer;

View File

@@ -111,10 +111,11 @@ const Navbar: React.FC = () => {
} }
/> />
<span <span
className="input-group-text bg-white text-muted" className="input-group-text text-muted"
style={{ style={{
borderLeft: "0", borderLeft: "0",
borderRight: "1px solid #dee2e6", borderRight: "1px solid #dee2e6",
backgroundColor: "#f8f9fa",
}} }}
> >
in in
@@ -127,7 +128,6 @@ const Navbar: React.FC = () => {
onChange={(e) => onChange={(e) =>
handleSearchInputChange("location", e.target.value) handleSearchInputChange("location", e.target.value)
} }
style={{ borderLeft: "0" }}
/> />
<button className="btn btn-outline-secondary" type="submit"> <button className="btn btn-outline-secondary" type="submit">
<i className="bi bi-search"></i> <i className="bi bi-search"></i>
@@ -194,13 +194,13 @@ const Navbar: React.FC = () => {
</Link> </Link>
</li> </li>
<li> <li>
<Link className="dropdown-item" to="/my-rentals"> <Link className="dropdown-item" to="/renting">
<i className="bi bi-calendar-check me-2"></i> <i className="bi bi-calendar-check me-2"></i>
Renting Renting
</Link> </Link>
</li> </li>
<li> <li>
<Link className="dropdown-item" to="/my-listings"> <Link className="dropdown-item" to="/owning">
<i className="bi bi-list-ul me-2"></i>Owning <i className="bi bi-list-ul me-2"></i>Owning
{pendingRequestsCount > 0 && ( {pendingRequestsCount > 0 && (
<span className="badge bg-danger rounded-pill ms-2"> <span className="badge bg-danger rounded-pill ms-2">

View File

@@ -36,7 +36,7 @@ const EarningsDashboard: React.FC = () => {
const fetchEarningsData = async () => { const fetchEarningsData = async () => {
try { try {
// Get completed rentals where user is the owner // Get completed rentals where user is the owner
const response = await rentalAPI.getMyListings(); const response = await rentalAPI.getListings();
const rentals = response.data || []; const rentals = response.data || [];
// Filter for completed rentals with earnings data // Filter for completed rentals with earnings data

View File

@@ -173,7 +173,7 @@ const EditItem: React.FC = () => {
const fetchAcceptedRentals = async () => { const fetchAcceptedRentals = async () => {
try { try {
const response = await rentalAPI.getMyListings(); const response = await rentalAPI.getListings();
const rentals: Rental[] = response.data; const rentals: Rental[] = response.data;
// Filter for accepted rentals for this specific item // Filter for accepted rentals for this specific item
const itemRentals = rentals.filter( const itemRentals = rentals.filter(

View File

@@ -1,10 +1,10 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from "react";
import { Link } from 'react-router-dom'; import { Link } from "react-router-dom";
import { useAuth } from '../contexts/AuthContext'; import { useAuth } from "../contexts/AuthContext";
import { itemAPI } from '../services/api'; import { itemAPI } from "../services/api";
import { Item } from '../types'; import { Item } from "../types";
import ItemCard from '../components/ItemCard'; import ItemCard from "../components/ItemCard";
import AuthButton from '../components/AuthButton'; import AuthButton from "../components/AuthButton";
const Home: React.FC = () => { const Home: React.FC = () => {
const { user } = useAuth(); const { user } = useAuth();
@@ -17,7 +17,7 @@ const Home: React.FC = () => {
const response = await itemAPI.getItems({ limit: 8 }); const response = await itemAPI.getItems({ limit: 8 });
setFeaturedItems(response.data.items); setFeaturedItems(response.data.items);
} catch (error) { } catch (error) {
console.error('Error fetching items:', error); console.error("Error fetching items:", error);
} finally { } finally {
setLoading(false); setLoading(false);
} }
@@ -28,70 +28,56 @@ const Home: React.FC = () => {
return ( return (
<div> <div>
{/* Hero Section */}
<div className="bg-primary text-white py-3">
<div className="container-fluid" style={{ maxWidth: '1800px' }}>
<div className="row align-items-center">
<div className="col-lg-6">
<h1 className="fs-2 fw-bold mb-2">
Rent Anything, From Anyone, Anywhere
</h1>
<p className="mb-3">
Join the sharing economy. Rent items you need for a fraction of the cost,
or earn money from things you already own.
</p>
<div className="d-flex gap-3 flex-wrap">
<Link to="/items" className="btn btn-light">
Start Renting
</Link>
{user ? (
<Link to="/create-item" className="btn btn-outline-light">
List Your Items
</Link>
) : (
<AuthButton mode="signup" className="btn btn-outline-light">
Start Earning
</AuthButton>
)}
</div>
</div>
<div className="col-lg-6 text-center d-none d-lg-block">
<i className="bi bi-box-seam" style={{ fontSize: '8rem', opacity: 0.3 }}></i>
</div>
</div>
</div>
</div>
{/* Featured Items Section */} {/* Featured Items Section */}
<div className="py-4 bg-light"> <div className="py-4" style={{ backgroundColor: "#2C3E50" }}>
<div className="container-fluid" style={{ maxWidth: '1800px' }}> <div className="container-fluid" style={{ maxWidth: "1800px" }}>
<div className="d-flex justify-content-between align-items-center mb-4">
<h2 className="mb-0">Items for Rent</h2>
<Link to="/items" className="btn btn-link">
View All <i className="bi bi-arrow-right"></i>
</Link>
</div>
{loading ? ( {loading ? (
<div className="text-center py-4"> <div className="text-center py-4">
<div className="spinner-border text-primary" role="status"> <div
className="spinner-border"
role="status"
style={{ color: "#4A90E2" }}
>
<span className="visually-hidden">Loading...</span> <span className="visually-hidden">Loading...</span>
</div> </div>
</div> </div>
) : ( ) : (
<div className="row g-4"> <>
{featuredItems.map((item) => ( <div className="row g-4">
<div key={item.id} className="col-md-6 col-lg-4 col-xl-3"> {featuredItems.map((item) => (
<ItemCard item={item} variant="compact" /> <div key={item.id} className="col-md-6 col-lg-4 col-xl-3">
<ItemCard item={item} variant="compact" />
</div>
))}
</div>
{featuredItems.length > 0 && (
<div className="text-center mt-4">
<Link
to="/items"
className="btn btn-link"
style={{ color: "#4A90E2" }}
>
View All <i className="bi bi-arrow-right"></i>
</Link>
</div> </div>
))} )}
</div> </>
)} )}
{!loading && featuredItems.length === 0 && ( {!loading && featuredItems.length === 0 && (
<div className="text-center py-5"> <div className="text-center py-5">
<p className="text-muted">No items available for rent yet.</p> <p style={{ color: "rgba(255, 255, 255, 0.7)" }}>
<Link to="/create-item" className="btn btn-primary"> No items available for rent yet.
</p>
<Link
to="/create-item"
className="btn"
style={{
backgroundColor: "#4A90E2",
color: "white",
borderColor: "#4A90E2",
}}
>
Be the first to list an item! Be the first to list an item!
</Link> </Link>
</div> </div>
@@ -99,40 +85,164 @@ const Home: React.FC = () => {
</div> </div>
</div> </div>
{/* Benefits Section */}
<div className="py-5" style={{ backgroundColor: "#34495e" }}>
<div className="container-fluid" style={{ maxWidth: "1800px" }}>
<div className="row align-items-center">
<div className="col-lg-6">
<h2 className="mb-4" style={{ color: "white" }}>
Why Choose CommunityRentals.App?
</h2>
<div className="d-flex mb-3">
<i
className="bi bi-check-circle-fill me-3"
style={{ fontSize: "1.5rem", color: "#7FB069" }}
></i>
<div>
<h5 style={{ color: "white" }}>Save Money</h5>
<p style={{ color: "rgba(255, 255, 255, 0.8)" }}>
Why buy when you can rent? Access items for a fraction of
the purchase price.
</p>
</div>
</div>
<div className="d-flex mb-3">
<i
className="bi bi-check-circle-fill me-3"
style={{ fontSize: "1.5rem", color: "#7FB069" }}
></i>
<div>
<h5 style={{ color: "white" }}>Earn Extra Income</h5>
<p style={{ color: "rgba(255, 255, 255, 0.8)" }}>
Turn your unused items into a revenue stream. Your garage
could be a goldmine.
</p>
</div>
</div>
<div className="d-flex mb-3">
<i
className="bi bi-check-circle-fill me-3"
style={{ fontSize: "1.5rem", color: "#7FB069" }}
></i>
<div>
<h5 style={{ color: "white" }}>Build Community</h5>
<p style={{ color: "rgba(255, 255, 255, 0.8)" }}>
Connect with neighbors and help each other. Sharing builds
stronger communities.
</p>
</div>
</div>
<div className="d-flex">
<i
className="bi bi-check-circle-fill me-3"
style={{ fontSize: "1.5rem", color: "#7FB069" }}
></i>
<div>
<h5 style={{ color: "white" }}>Reduce Waste</h5>
<p style={{ color: "rgba(255, 255, 255, 0.8)" }}>
Share instead of everyone buying. It's better for your
wallet and the planet.
</p>
</div>
</div>
</div>
<div className="col-lg-6 text-center">
<h2 className="mb-4" style={{ color: "white" }}>
Ready to Get Started?
</h2>
<p
className="lead mb-4"
style={{ color: "rgba(255, 255, 255, 0.9)" }}
>
Join thousands of people sharing and renting in your community
</p>
<div className="d-flex gap-3 justify-content-center flex-wrap">
{user ? (
<Link
to="/create-item"
className="btn btn-lg"
style={{
backgroundColor: "#7FB069",
color: "white",
borderColor: "#7FB069",
}}
>
Start Earning
</Link>
) : (
<AuthButton
mode="signup"
className="btn btn-lg"
style={{
backgroundColor: "#7FB069",
color: "white",
borderColor: "#7FB069",
}}
>
Start Earning
</AuthButton>
)}
<Link
to="/items"
className="btn btn-lg"
style={{
backgroundColor: "#4A90E2",
color: "white",
borderColor: "#4A90E2",
}}
>
Browse Rentals
</Link>
</div>
</div>
</div>
</div>
</div>
{/* How It Works - For Renters */} {/* How It Works - For Renters */}
<div className="py-5"> <div className="py-5">
<div className="container-fluid" style={{ maxWidth: '1800px' }}> <div className="container-fluid" style={{ maxWidth: "1800px" }}>
<h2 className="text-center mb-5">For Renters: Get What You Need, When You Need It</h2> <h2 className="text-center mb-5">
For Renters: Get What You Need, When You Need It
</h2>
<div className="row g-4"> <div className="row g-4">
<div className="col-md-4 text-center"> <div className="col-md-4 text-center">
<div className="mb-3"> <div className="mb-3">
<i className="bi bi-search text-primary" style={{ fontSize: '3rem' }}></i> <i
className="bi bi-search"
style={{ fontSize: "3rem", color: "#4A90E2" }}
></i>
</div> </div>
<h4>1. Find What You Need</h4> <h4>1. Find What You Need</h4>
<p className="text-muted"> <p className="text-muted">
Browse our marketplace for tools, equipment, electronics, and more. Browse our marketplace for tools, equipment, electronics, and
Filter by location, price, and availability. more. Filter by location, price, and availability.
</p> </p>
</div> </div>
<div className="col-md-4 text-center"> <div className="col-md-4 text-center">
<div className="mb-3"> <div className="mb-3">
<i className="bi bi-calendar-check text-primary" style={{ fontSize: '3rem' }}></i> <i
className="bi bi-calendar-check"
style={{ fontSize: "3rem", color: "#4A90E2" }}
></i>
</div> </div>
<h4>2. Book Your Rental</h4> <h4>2. Book Your Rental</h4>
<p className="text-muted"> <p className="text-muted">
Select your rental dates, choose delivery or pickup, and pay securely. Select your rental dates, choose delivery or pickup, and pay
Your payment is held until the owner confirms. securely. Your payment is held until the owner confirms.
</p> </p>
</div> </div>
<div className="col-md-4 text-center"> <div className="col-md-4 text-center">
<div className="mb-3"> <div className="mb-3">
<i className="bi bi-box-arrow-right text-primary" style={{ fontSize: '3rem' }}></i> <i
className="bi bi-box-arrow-right"
style={{ fontSize: "3rem", color: "#4A90E2" }}
></i>
</div> </div>
<h4>3. Enjoy & Return</h4> <h4>3. Enjoy & Return</h4>
<p className="text-muted"> <p className="text-muted">
Use the item for your project or event, then return it as agreed. Use the item for your project or event, then return it as
Rate your experience to help the community. agreed. Rate your experience to help the community.
</p> </p>
</div> </div>
</div> </div>
@@ -140,168 +250,56 @@ const Home: React.FC = () => {
</div> </div>
{/* How It Works - For Owners */} {/* How It Works - For Owners */}
<div className="py-5 bg-light"> <div className="py-5" style={{ backgroundColor: "#F7F9FB" }}>
<div className="container-fluid" style={{ maxWidth: '1800px' }}> <div className="container-fluid" style={{ maxWidth: "1800px" }}>
<h2 className="text-center mb-5">For Owners: Turn Your Idle Items Into Income</h2> <h2 className="text-center mb-5">
For Owners: Turn Your Idle Items Into Income
</h2>
<div className="row g-4"> <div className="row g-4">
<div className="col-md-4 text-center"> <div className="col-md-4 text-center">
<div className="mb-3"> <div className="mb-3">
<i className="bi bi-camera text-success" style={{ fontSize: '3rem' }}></i> <i
className="bi bi-camera"
style={{ fontSize: "3rem", color: "#7FB069" }}
></i>
</div> </div>
<h4>1. List Your Items</h4> <h4>1. List Your Items</h4>
<p className="text-muted"> <p className="text-muted">
Take photos, set your price, and choose when your items are available. Take photos, set your price, and choose when your items are
List anything from power tools to party supplies. available. List anything from power tools to party supplies.
</p> </p>
</div> </div>
<div className="col-md-4 text-center"> <div className="col-md-4 text-center">
<div className="mb-3"> <div className="mb-3">
<i className="bi bi-bell text-success" style={{ fontSize: '3rem' }}></i> <i
className="bi bi-bell"
style={{ fontSize: "3rem", color: "#7FB069" }}
></i>
</div> </div>
<h4>2. Accept Requests</h4> <h4>2. Accept Requests</h4>
<p className="text-muted"> <p className="text-muted">
Review rental requests and accept the ones that work for you. Review rental requests and accept the ones that work for you.
Set your own rules and requirements. Set your own rules and requirements.
</p> </p>
</div> </div>
<div className="col-md-4 text-center"> <div className="col-md-4 text-center">
<div className="mb-3"> <div className="mb-3">
<i className="bi bi-cash-stack text-success" style={{ fontSize: '3rem' }}></i> <i
className="bi bi-cash-stack"
style={{ fontSize: "3rem", color: "#7FB069" }}
></i>
</div> </div>
<h4>3. Get Paid</h4> <h4>3. Get Paid</h4>
<p className="text-muted"> <p className="text-muted">
Earn money from items sitting in your garage. Earn money from items sitting in your garage. Payments are
Payments are processed securely after each rental. processed securely after each rental.
</p> </p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{/* Popular Categories */}
<div className="py-5">
<div className="container-fluid" style={{ maxWidth: '1800px' }}>
<h2 className="text-center mb-5">Popular Rental Categories</h2>
<div className="row g-3">
<div className="col-6 col-md-4 col-lg-2">
<div className="card h-100 text-center border-0 shadow-sm">
<div className="card-body">
<i className="bi bi-tools text-primary mb-2" style={{ fontSize: '2rem' }}></i>
<h6 className="mb-0">Tools</h6>
</div>
</div>
</div>
<div className="col-6 col-md-4 col-lg-2">
<div className="card h-100 text-center border-0 shadow-sm">
<div className="card-body">
<i className="bi bi-camera-fill text-primary mb-2" style={{ fontSize: '2rem' }}></i>
<h6 className="mb-0">Electronics</h6>
</div>
</div>
</div>
<div className="col-6 col-md-4 col-lg-2">
<div className="card h-100 text-center border-0 shadow-sm">
<div className="card-body">
<i className="bi bi-bicycle text-primary mb-2" style={{ fontSize: '2rem' }}></i>
<h6 className="mb-0">Sports</h6>
</div>
</div>
</div>
<div className="col-6 col-md-4 col-lg-2">
<div className="card h-100 text-center border-0 shadow-sm">
<div className="card-body">
<i className="bi bi-music-note-beamed text-primary mb-2" style={{ fontSize: '2rem' }}></i>
<h6 className="mb-0">Music</h6>
</div>
</div>
</div>
<div className="col-6 col-md-4 col-lg-2">
<div className="card h-100 text-center border-0 shadow-sm">
<div className="card-body">
<i className="bi bi-balloon text-primary mb-2" style={{ fontSize: '2rem' }}></i>
<h6 className="mb-0">Party</h6>
</div>
</div>
</div>
<div className="col-6 col-md-4 col-lg-2">
<div className="card h-100 text-center border-0 shadow-sm">
<div className="card-body">
<i className="bi bi-tree text-primary mb-2" style={{ fontSize: '2rem' }}></i>
<h6 className="mb-0">Outdoor</h6>
</div>
</div>
</div>
</div>
</div>
</div>
{/* Benefits Section */}
<div className="py-5 bg-light">
<div className="container-fluid" style={{ maxWidth: '1800px' }}>
<div className="row align-items-center">
<div className="col-lg-6">
<h2 className="mb-4">Why Choose CommunityRentals.App?</h2>
<div className="d-flex mb-3">
<i className="bi bi-check-circle-fill text-success me-3" style={{ fontSize: '1.5rem' }}></i>
<div>
<h5>Save Money</h5>
<p className="text-muted">Why buy when you can rent? Access expensive items for a fraction of the purchase price.</p>
</div>
</div>
<div className="d-flex mb-3">
<i className="bi bi-check-circle-fill text-success me-3" style={{ fontSize: '1.5rem' }}></i>
<div>
<h5>Earn Extra Income</h5>
<p className="text-muted">Turn your unused items into a revenue stream. Your garage could be a goldmine.</p>
</div>
</div>
<div className="d-flex mb-3">
<i className="bi bi-check-circle-fill text-success me-3" style={{ fontSize: '1.5rem' }}></i>
<div>
<h5>Build Community</h5>
<p className="text-muted">Connect with neighbors and help each other. Sharing builds stronger communities.</p>
</div>
</div>
<div className="d-flex">
<i className="bi bi-check-circle-fill text-success me-3" style={{ fontSize: '1.5rem' }}></i>
<div>
<h5>Reduce Waste</h5>
<p className="text-muted">Share instead of everyone buying. It's better for your wallet and the planet.</p>
</div>
</div>
</div>
<div className="col-lg-6 text-center">
<i className="bi bi-shield-check" style={{ fontSize: '15rem', color: '#e0e0e0' }}></i>
</div>
</div>
</div>
</div>
{/* CTA Section */}
<div className="bg-primary text-white py-5">
<div className="container text-center">
<h2 className="mb-4">Ready to Get Started?</h2>
<p className="lead mb-4">
Join thousands of people sharing and renting in your community
</p>
<div className="d-flex gap-3 justify-content-center flex-wrap">
<Link to="/items" className="btn btn-light btn-lg">
Browse Rentals
</Link>
{user ? (
<Link to="/create-item" className="btn btn-outline-light btn-lg">
List an Item
</Link>
) : (
<AuthButton mode="signup" className="btn btn-outline-light btn-lg">
Sign Up Free
</AuthButton>
)}
</div>
</div>
</div>
</div> </div>
); );
}; };
export default Home; export default Home;

View File

@@ -48,7 +48,7 @@ const ItemDetail: React.FC = () => {
const checkIfAlreadyRenting = async () => { const checkIfAlreadyRenting = async () => {
try { try {
const response = await rentalAPI.getMyRentals(); const response = await rentalAPI.getRentals();
const rentals: Rental[] = response.data; const rentals: Rental[] = response.data;
// Check if user has an active rental for this item // Check if user has an active rental for this item
const hasActiveRental = rentals.some( const hasActiveRental = rentals.some(

View File

@@ -10,7 +10,7 @@ import DeclineRentalModal from "../components/DeclineRentalModal";
import ConditionCheckModal from "../components/ConditionCheckModal"; import ConditionCheckModal from "../components/ConditionCheckModal";
import ReturnStatusModal from "../components/ReturnStatusModal"; import ReturnStatusModal from "../components/ReturnStatusModal";
const MyListings: React.FC = () => { const Owning: React.FC = () => {
// Helper function to format time // Helper function to format time
const formatTime = (timeString?: string) => { const formatTime = (timeString?: string) => {
if (!timeString || timeString.trim() === "") return ""; if (!timeString || timeString.trim() === "") return "";
@@ -66,12 +66,12 @@ const MyListings: React.FC = () => {
const [rentalForReturn, setRentalForReturn] = useState<Rental | null>(null); const [rentalForReturn, setRentalForReturn] = useState<Rental | null>(null);
useEffect(() => { useEffect(() => {
fetchMyListings(); fetchListings();
fetchOwnerRentals(); fetchOwnerRentals();
fetchAvailableChecks(); fetchAvailableChecks();
}, [user]); }, [user]);
const fetchMyListings = async () => { const fetchListings = async () => {
if (!user) return; if (!user) return;
try { try {
@@ -124,7 +124,7 @@ const MyListings: React.FC = () => {
const fetchOwnerRentals = async () => { const fetchOwnerRentals = async () => {
try { try {
const response = await rentalAPI.getMyListings(); const response = await rentalAPI.getListings();
setOwnerRentals(response.data); setOwnerRentals(response.data);
} catch (err: any) { } catch (err: any) {
console.error("Failed to fetch owner rentals:", err); console.error("Failed to fetch owner rentals:", err);
@@ -698,4 +698,4 @@ const MyListings: React.FC = () => {
); );
}; };
export default MyListings; export default Owning;

View File

@@ -155,7 +155,7 @@ const Profile: React.FC = () => {
); );
// Fetch rentals where user is the owner (rentals on user's items) // Fetch rentals where user is the owner (rentals on user's items)
const ownerRentalsResponse = await rentalAPI.getMyListings(); const ownerRentalsResponse = await rentalAPI.getListings();
const ownerRentals: Rental[] = ownerRentalsResponse.data; const ownerRentals: Rental[] = ownerRentalsResponse.data;
const acceptedRentals = ownerRentals.filter((r) => const acceptedRentals = ownerRentals.filter((r) =>
@@ -194,14 +194,14 @@ const Profile: React.FC = () => {
const fetchRentalHistory = async () => { const fetchRentalHistory = async () => {
try { try {
// Fetch past rentals as a renter // Fetch past rentals as a renter
const renterResponse = await rentalAPI.getMyRentals(); const renterResponse = await rentalAPI.getRentals();
const pastRenterRentals = renterResponse.data.filter((r: Rental) => const pastRenterRentals = renterResponse.data.filter((r: Rental) =>
["completed", "cancelled"].includes(r.status) ["completed", "cancelled"].includes(r.status)
); );
setPastRenterRentals(pastRenterRentals); setPastRenterRentals(pastRenterRentals);
// Fetch past rentals as an owner // Fetch past rentals as an owner
const ownerResponse = await rentalAPI.getMyListings(); const ownerResponse = await rentalAPI.getListings();
const pastOwnerRentals = ownerResponse.data.filter((r: Rental) => const pastOwnerRentals = ownerResponse.data.filter((r: Rental) =>
["completed", "cancelled"].includes(r.status) ["completed", "cancelled"].includes(r.status)
); );
@@ -608,7 +608,7 @@ const Profile: React.FC = () => {
} }
return ( return (
<div className="container mt-4"> <div className="container mt-4 mb-5">
<h1 className="mb-4">Profile</h1> <h1 className="mb-4">Profile</h1>
{error && ( {error && (
@@ -664,33 +664,6 @@ const Profile: React.FC = () => {
<i className="bi bi-person me-2"></i> <i className="bi bi-person me-2"></i>
Personal Information Personal Information
</button> </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 <button
className="list-group-item list-group-item-action text-danger" className="list-group-item list-group-item-action text-danger"
onClick={logout} onClick={logout}
@@ -804,12 +777,6 @@ const Profile: React.FC = () => {
{profileData?.firstName} {profileData?.lastName} {profileData?.firstName} {profileData?.lastName}
</h5> </h5>
<p className="text-muted">@{profileData?.username}</p> <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> <div>
<button <button
type="button" type="button"
@@ -1431,33 +1398,6 @@ const Profile: React.FC = () => {
</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>
)}
</div> </div>
</div> </div>

View File

@@ -63,7 +63,7 @@ const PublicProfile: React.FC = () => {
} }
return ( return (
<div className="container mt-4"> <div className="container mt-4 mb-5">
<div className="row justify-content-center"> <div className="row justify-content-center">
<div className="col-md-8"> <div className="col-md-8">
<div className="card"> <div className="card">
@@ -86,11 +86,6 @@ const PublicProfile: React.FC = () => {
)} )}
<h3>{user.firstName} {user.lastName}</h3> <h3>{user.firstName} {user.lastName}</h3>
<p className="text-muted">@{user.username}</p> <p className="text-muted">@{user.username}</p>
{user.isVerified && (
<span className="badge bg-success">
<i className="bi bi-check-circle-fill"></i> Verified User
</span>
)}
{currentUser && currentUser.id !== user.id && ( {currentUser && currentUser.id !== user.id && (
<button <button
className="btn btn-primary mt-3" className="btn btn-primary mt-3"

View File

@@ -223,7 +223,7 @@ const RentItem: React.FC = () => {
<div className="d-grid gap-2 d-md-block"> <div className="d-grid gap-2 d-md-block">
<button <button
className="btn btn-primary me-2" className="btn btn-primary me-2"
onClick={() => navigate("/my-rentals")} onClick={() => navigate("/renting")}
> >
View My Rentals View My Rentals
</button> </button>

View File

@@ -7,7 +7,7 @@ import ReviewItemModal from "../components/ReviewModal";
import RentalCancellationModal from "../components/RentalCancellationModal"; import RentalCancellationModal from "../components/RentalCancellationModal";
import ConditionCheckModal from "../components/ConditionCheckModal"; import ConditionCheckModal from "../components/ConditionCheckModal";
const MyRentals: React.FC = () => { const Renting: React.FC = () => {
// Helper function to format time // Helper function to format time
const formatTime = (timeString?: string) => { const formatTime = (timeString?: string) => {
if (!timeString || timeString.trim() === "") return ""; if (!timeString || timeString.trim() === "") return "";
@@ -66,7 +66,7 @@ const MyRentals: React.FC = () => {
const fetchRentals = async () => { const fetchRentals = async () => {
try { try {
const response = await rentalAPI.getMyRentals(); const response = await rentalAPI.getRentals();
setRentals(response.data); setRentals(response.data);
} catch (err: any) { } catch (err: any) {
setError(err.response?.data?.message || "Failed to fetch rentals"); setError(err.response?.data?.message || "Failed to fetch rentals");
@@ -470,4 +470,4 @@ const MyRentals: React.FC = () => {
); );
}; };
export default MyRentals; export default Renting;

View File

@@ -203,8 +203,8 @@ export const itemAPI = {
export const rentalAPI = { export const rentalAPI = {
createRental: (data: any) => api.post("/rentals", data), createRental: (data: any) => api.post("/rentals", data),
getMyRentals: () => api.get("/rentals/my-rentals"), getRentals: () => api.get("/rentals/renting"),
getMyListings: () => api.get("/rentals/my-listings"), getListings: () => api.get("/rentals/owning"),
getPendingRequestsCount: () => api.get("/rentals/pending-requests-count"), getPendingRequestsCount: () => api.get("/rentals/pending-requests-count"),
updateRentalStatus: (id: string, status: string) => updateRentalStatus: (id: string, status: string) =>
api.put(`/rentals/${id}/status`, { status }), api.put(`/rentals/${id}/status`, { status }),