Compare commits
4 Commits
532f3014df
...
8e6af92cba
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e6af92cba | ||
|
|
42a5412582 | ||
|
|
bb16d659bd | ||
|
|
34bbf06f0c |
@@ -46,10 +46,6 @@ const AlphaInvitation = sequelize.define(
|
|||||||
defaultValue: "pending",
|
defaultValue: "pending",
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
},
|
},
|
||||||
notes: {
|
|
||||||
type: DataTypes.TEXT,
|
|
||||||
allowNull: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
indexes: [
|
indexes: [
|
||||||
|
|||||||
@@ -86,29 +86,13 @@ const Item = sequelize.define("Item", {
|
|||||||
type: DataTypes.ARRAY(DataTypes.STRING),
|
type: DataTypes.ARRAY(DataTypes.STRING),
|
||||||
defaultValue: [],
|
defaultValue: [],
|
||||||
},
|
},
|
||||||
availability: {
|
isAvailable: {
|
||||||
type: DataTypes.BOOLEAN,
|
type: DataTypes.BOOLEAN,
|
||||||
defaultValue: true,
|
defaultValue: true,
|
||||||
},
|
},
|
||||||
specifications: {
|
|
||||||
type: DataTypes.JSONB,
|
|
||||||
defaultValue: {},
|
|
||||||
},
|
|
||||||
rules: {
|
rules: {
|
||||||
type: DataTypes.TEXT,
|
type: DataTypes.TEXT,
|
||||||
},
|
},
|
||||||
minimumRentalDays: {
|
|
||||||
type: DataTypes.INTEGER,
|
|
||||||
defaultValue: 1,
|
|
||||||
},
|
|
||||||
maximumRentalDays: {
|
|
||||||
type: DataTypes.INTEGER,
|
|
||||||
},
|
|
||||||
needsTraining: {
|
|
||||||
type: DataTypes.BOOLEAN,
|
|
||||||
allowNull: false,
|
|
||||||
defaultValue: false,
|
|
||||||
},
|
|
||||||
availableAfter: {
|
availableAfter: {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
defaultValue: "09:00",
|
defaultValue: "09:00",
|
||||||
|
|||||||
@@ -64,15 +64,15 @@ const Rental = sequelize.define("Rental", {
|
|||||||
"damaged",
|
"damaged",
|
||||||
"lost"
|
"lost"
|
||||||
),
|
),
|
||||||
defaultValue: "pending",
|
allowNull: false,
|
||||||
},
|
},
|
||||||
paymentStatus: {
|
paymentStatus: {
|
||||||
type: DataTypes.ENUM("pending", "paid", "refunded", "not_required"),
|
type: DataTypes.ENUM("pending", "paid", "refunded", "not_required"),
|
||||||
defaultValue: "pending",
|
allowNull: false,
|
||||||
},
|
},
|
||||||
payoutStatus: {
|
payoutStatus: {
|
||||||
type: DataTypes.ENUM("pending", "processing", "completed", "failed"),
|
type: DataTypes.ENUM("pending", "completed", "failed"),
|
||||||
defaultValue: "pending",
|
allowNull: true,
|
||||||
},
|
},
|
||||||
payoutProcessedAt: {
|
payoutProcessedAt: {
|
||||||
type: DataTypes.DATE,
|
type: DataTypes.DATE,
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ router.get("/recommendations", authenticateToken, async (req, res) => {
|
|||||||
// For now, just return random available items as recommendations
|
// For now, just return random available items as recommendations
|
||||||
const recommendations = await Item.findAll({
|
const recommendations = await Item.findAll({
|
||||||
where: {
|
where: {
|
||||||
availability: true,
|
isAvailable: true,
|
||||||
isDeleted: false,
|
isDeleted: false,
|
||||||
},
|
},
|
||||||
limit: 10,
|
limit: 10,
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ router.post("/", authenticateToken, requireVerifiedEmail, async (req, res) => {
|
|||||||
return res.status(404).json({ error: "Item not found" });
|
return res.status(404).json({ error: "Item not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!item.availability) {
|
if (!item.isAvailable) {
|
||||||
return res.status(400).json({ error: "Item is not available" });
|
return res.status(400).json({ error: "Item is not available" });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -823,7 +823,7 @@ router.post("/:id/mark-completed", authenticateToken, async (req, res) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await rental.update({ status: "completed" });
|
await rental.update({ status: "completed", payoutStatus: "pending" });
|
||||||
|
|
||||||
const updatedRental = await Rental.findByPk(rental.id, {
|
const updatedRental = await Rental.findByPk(rental.id, {
|
||||||
include: [
|
include: [
|
||||||
@@ -1131,6 +1131,7 @@ router.post("/:id/mark-return", authenticateToken, async (req, res) => {
|
|||||||
// Item returned on time
|
// Item returned on time
|
||||||
updatedRental = await rental.update({
|
updatedRental = await rental.update({
|
||||||
status: "completed",
|
status: "completed",
|
||||||
|
payoutStatus: "pending",
|
||||||
actualReturnDateTime: actualReturnDateTime || rental.endDateTime,
|
actualReturnDateTime: actualReturnDateTime || rental.endDateTime,
|
||||||
notes: notes || null,
|
notes: notes || null,
|
||||||
});
|
});
|
||||||
@@ -1174,6 +1175,7 @@ router.post("/:id/mark-return", authenticateToken, async (req, res) => {
|
|||||||
// Item returned damaged
|
// Item returned damaged
|
||||||
const damageUpdates = {
|
const damageUpdates = {
|
||||||
status: "damaged",
|
status: "damaged",
|
||||||
|
payoutStatus: "pending",
|
||||||
actualReturnDateTime: actualReturnDateTime || rental.endDateTime,
|
actualReturnDateTime: actualReturnDateTime || rental.endDateTime,
|
||||||
notes: notes || null,
|
notes: notes || null,
|
||||||
};
|
};
|
||||||
@@ -1217,6 +1219,7 @@ router.post("/:id/mark-return", authenticateToken, async (req, res) => {
|
|||||||
// Item reported as lost
|
// Item reported as lost
|
||||||
updatedRental = await rental.update({
|
updatedRental = await rental.update({
|
||||||
status: "lost",
|
status: "lost",
|
||||||
|
payoutStatus: "pending",
|
||||||
itemLostReportedAt: new Date(),
|
itemLostReportedAt: new Date(),
|
||||||
notes: notes || null,
|
notes: notes || null,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ class LateReturnService {
|
|||||||
const updates = {
|
const updates = {
|
||||||
actualReturnDateTime: new Date(actualReturnDateTime),
|
actualReturnDateTime: new Date(actualReturnDateTime),
|
||||||
status: lateCalculation.isLate ? "returned_late" : "completed",
|
status: lateCalculation.isLate ? "returned_late" : "completed",
|
||||||
|
payoutStatus: "pending",
|
||||||
};
|
};
|
||||||
|
|
||||||
if (notes) {
|
if (notes) {
|
||||||
|
|||||||
@@ -50,11 +50,6 @@ class PayoutService {
|
|||||||
throw new Error("Invalid payout amount");
|
throw new Error("Invalid payout amount");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update status to processing
|
|
||||||
await rental.update({
|
|
||||||
payoutStatus: "processing",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create Stripe transfer
|
// Create Stripe transfer
|
||||||
const transfer = await StripeService.createTransfer({
|
const transfer = await StripeService.createTransfer({
|
||||||
amount: rental.payoutAmount,
|
amount: rental.payoutAmount,
|
||||||
|
|||||||
@@ -422,8 +422,8 @@ describe('Items Routes', () => {
|
|||||||
|
|
||||||
describe('GET /recommendations', () => {
|
describe('GET /recommendations', () => {
|
||||||
const mockRecommendations = [
|
const mockRecommendations = [
|
||||||
{ id: 1, name: 'Item 1', availability: true },
|
{ id: 1, name: 'Item 1', isAvailable: true },
|
||||||
{ id: 2, name: 'Item 2', availability: true }
|
{ id: 2, name: 'Item 2', isAvailable: true }
|
||||||
];
|
];
|
||||||
|
|
||||||
it('should get recommendations for authenticated user', async () => {
|
it('should get recommendations for authenticated user', async () => {
|
||||||
@@ -443,7 +443,7 @@ describe('Items Routes', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(mockItemFindAll).toHaveBeenCalledWith({
|
expect(mockItemFindAll).toHaveBeenCalledWith({
|
||||||
where: { availability: true },
|
where: { isAvailable: true, isDeleted: false },
|
||||||
limit: 10,
|
limit: 10,
|
||||||
order: [['createdAt', 'DESC']]
|
order: [['createdAt', 'DESC']]
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -260,7 +260,7 @@ describe('Rentals Routes', () => {
|
|||||||
id: 1,
|
id: 1,
|
||||||
name: 'Test Item',
|
name: 'Test Item',
|
||||||
ownerId: 2,
|
ownerId: 2,
|
||||||
availability: true,
|
isAvailable: true,
|
||||||
pricePerHour: 10,
|
pricePerHour: 10,
|
||||||
pricePerDay: 50,
|
pricePerDay: 50,
|
||||||
};
|
};
|
||||||
@@ -340,7 +340,7 @@ describe('Rentals Routes', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return 400 for unavailable item', async () => {
|
it('should return 400 for unavailable item', async () => {
|
||||||
Item.findByPk.mockResolvedValue({ ...mockItem, availability: false });
|
Item.findByPk.mockResolvedValue({ ...mockItem, isAvailable: false });
|
||||||
|
|
||||||
const response = await request(app)
|
const response = await request(app)
|
||||||
.post('/rentals')
|
.post('/rentals')
|
||||||
@@ -382,7 +382,7 @@ describe('Rentals Routes', () => {
|
|||||||
Item.findByPk.mockResolvedValue({
|
Item.findByPk.mockResolvedValue({
|
||||||
id: 1,
|
id: 1,
|
||||||
ownerId: 2,
|
ownerId: 2,
|
||||||
availability: true,
|
isAvailable: true,
|
||||||
pricePerHour: 0,
|
pricePerHour: 0,
|
||||||
pricePerDay: 0
|
pricePerDay: 0
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -186,11 +186,6 @@ describe('PayoutService', () => {
|
|||||||
it('should successfully process a rental payout', async () => {
|
it('should successfully process a rental payout', async () => {
|
||||||
const result = await PayoutService.processRentalPayout(mockRental);
|
const result = await PayoutService.processRentalPayout(mockRental);
|
||||||
|
|
||||||
// Verify status update to processing
|
|
||||||
expect(mockRental.update).toHaveBeenNthCalledWith(1, {
|
|
||||||
payoutStatus: 'processing'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Verify Stripe transfer creation
|
// Verify Stripe transfer creation
|
||||||
expect(mockCreateTransfer).toHaveBeenCalledWith({
|
expect(mockCreateTransfer).toHaveBeenCalledWith({
|
||||||
amount: 9500,
|
amount: 9500,
|
||||||
@@ -206,7 +201,7 @@ describe('PayoutService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Verify status update to completed
|
// Verify status update to completed
|
||||||
expect(mockRental.update).toHaveBeenNthCalledWith(2, {
|
expect(mockRental.update).toHaveBeenCalledWith({
|
||||||
payoutStatus: 'completed',
|
payoutStatus: 'completed',
|
||||||
payoutProcessedAt: expect.any(Date),
|
payoutProcessedAt: expect.any(Date),
|
||||||
stripeTransferId: 'tr_123456789'
|
stripeTransferId: 'tr_123456789'
|
||||||
@@ -260,13 +255,8 @@ describe('PayoutService', () => {
|
|||||||
await expect(PayoutService.processRentalPayout(mockRental))
|
await expect(PayoutService.processRentalPayout(mockRental))
|
||||||
.rejects.toThrow('Stripe transfer failed');
|
.rejects.toThrow('Stripe transfer failed');
|
||||||
|
|
||||||
// Verify processing status was set
|
|
||||||
expect(mockRental.update).toHaveBeenNthCalledWith(1, {
|
|
||||||
payoutStatus: 'processing'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Verify failure status was set
|
// Verify failure status was set
|
||||||
expect(mockRental.update).toHaveBeenNthCalledWith(2, {
|
expect(mockRental.update).toHaveBeenCalledWith({
|
||||||
payoutStatus: 'failed'
|
payoutStatus: 'failed'
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -296,9 +286,7 @@ describe('PayoutService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const dbError = new Error('Database completion update failed');
|
const dbError = new Error('Database completion update failed');
|
||||||
mockRental.update
|
mockRental.update.mockRejectedValueOnce(dbError);
|
||||||
.mockResolvedValueOnce(true) // processing update succeeds
|
|
||||||
.mockRejectedValueOnce(dbError); // completion update fails
|
|
||||||
|
|
||||||
await expect(PayoutService.processRentalPayout(mockRental))
|
await expect(PayoutService.processRentalPayout(mockRental))
|
||||||
.rejects.toThrow('Database completion update failed');
|
.rejects.toThrow('Database completion update failed');
|
||||||
@@ -315,16 +303,14 @@ describe('PayoutService', () => {
|
|||||||
const updateError = new Error('Update failed status failed');
|
const updateError = new Error('Update failed status failed');
|
||||||
|
|
||||||
mockCreateTransfer.mockRejectedValue(stripeError);
|
mockCreateTransfer.mockRejectedValue(stripeError);
|
||||||
mockRental.update
|
mockRental.update.mockRejectedValueOnce(updateError);
|
||||||
.mockResolvedValueOnce(true) // processing update succeeds
|
|
||||||
.mockRejectedValueOnce(updateError); // failed status update fails
|
|
||||||
|
|
||||||
// The service will throw the update error since it happens in the catch block
|
// The service will throw the update error since it happens in the catch block
|
||||||
await expect(PayoutService.processRentalPayout(mockRental))
|
await expect(PayoutService.processRentalPayout(mockRental))
|
||||||
.rejects.toThrow('Update failed status failed');
|
.rejects.toThrow('Update failed status failed');
|
||||||
|
|
||||||
// Should still attempt to update to failed status
|
// Should still attempt to update to failed status
|
||||||
expect(mockRental.update).toHaveBeenNthCalledWith(2, {
|
expect(mockRental.update).toHaveBeenCalledWith({
|
||||||
payoutStatus: 'failed'
|
payoutStatus: 'failed'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,32 +1,17 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
interface RulesFormProps {
|
interface RulesFormProps {
|
||||||
needsTraining: boolean;
|
|
||||||
rules: string;
|
rules: string;
|
||||||
onChange: (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
|
onChange: (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RulesForm: React.FC<RulesFormProps> = ({
|
const RulesForm: React.FC<RulesFormProps> = ({
|
||||||
needsTraining,
|
|
||||||
rules,
|
rules,
|
||||||
onChange
|
onChange
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="card mb-4">
|
<div className="card mb-4">
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<div className="form-check mb-3">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
className="form-check-input"
|
|
||||||
id="needsTraining"
|
|
||||||
name="needsTraining"
|
|
||||||
checked={needsTraining}
|
|
||||||
onChange={onChange}
|
|
||||||
/>
|
|
||||||
<label className="form-check-label" htmlFor="needsTraining">
|
|
||||||
Requires in-person training before rental
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<label htmlFor="rules" className="form-label">
|
<label htmlFor="rules" className="form-label">
|
||||||
Additional Rules
|
Additional Rules
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ interface ItemFormData {
|
|||||||
latitude?: number;
|
latitude?: number;
|
||||||
longitude?: number;
|
longitude?: number;
|
||||||
rules?: string;
|
rules?: string;
|
||||||
needsTraining: boolean;
|
|
||||||
generalAvailableAfter: string;
|
generalAvailableAfter: string;
|
||||||
generalAvailableBefore: string;
|
generalAvailableBefore: string;
|
||||||
specifyTimesPerDay: boolean;
|
specifyTimesPerDay: boolean;
|
||||||
@@ -63,7 +62,6 @@ const CreateItem: React.FC = () => {
|
|||||||
state: "",
|
state: "",
|
||||||
zipCode: "",
|
zipCode: "",
|
||||||
country: "US",
|
country: "US",
|
||||||
needsTraining: false,
|
|
||||||
generalAvailableAfter: "09:00",
|
generalAvailableAfter: "09:00",
|
||||||
generalAvailableBefore: "17:00",
|
generalAvailableBefore: "17:00",
|
||||||
specifyTimesPerDay: false,
|
specifyTimesPerDay: false,
|
||||||
@@ -483,7 +481,6 @@ const CreateItem: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<RulesForm
|
<RulesForm
|
||||||
needsTraining={formData.needsTraining}
|
|
||||||
rules={formData.rules || ""}
|
rules={formData.rules || ""}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -211,15 +211,15 @@ const EarningsDashboard: React.FC = () => {
|
|||||||
className={`badge ${
|
className={`badge ${
|
||||||
rental.payoutStatus === "completed"
|
rental.payoutStatus === "completed"
|
||||||
? "bg-success"
|
? "bg-success"
|
||||||
: rental.payoutStatus === "processing"
|
: rental.payoutStatus === "failed"
|
||||||
? "bg-warning"
|
? "bg-danger"
|
||||||
: "bg-secondary"
|
: "bg-secondary"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{rental.payoutStatus === "completed"
|
{rental.payoutStatus === "completed"
|
||||||
? "Paid"
|
? "Paid"
|
||||||
: rental.payoutStatus === "processing"
|
: rental.payoutStatus === "failed"
|
||||||
? "Processing"
|
? "Failed"
|
||||||
: "Pending"}
|
: "Pending"}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ interface ItemFormData {
|
|||||||
latitude?: number;
|
latitude?: number;
|
||||||
longitude?: number;
|
longitude?: number;
|
||||||
rules?: string;
|
rules?: string;
|
||||||
needsTraining: boolean;
|
|
||||||
generalAvailableAfter: string;
|
generalAvailableAfter: string;
|
||||||
generalAvailableBefore: string;
|
generalAvailableBefore: string;
|
||||||
specifyTimesPerDay: boolean;
|
specifyTimesPerDay: boolean;
|
||||||
@@ -87,7 +86,6 @@ const EditItem: React.FC = () => {
|
|||||||
zipCode: "",
|
zipCode: "",
|
||||||
country: "US",
|
country: "US",
|
||||||
rules: "",
|
rules: "",
|
||||||
needsTraining: false,
|
|
||||||
generalAvailableAfter: "09:00",
|
generalAvailableAfter: "09:00",
|
||||||
generalAvailableBefore: "17:00",
|
generalAvailableBefore: "17:00",
|
||||||
specifyTimesPerDay: false,
|
specifyTimesPerDay: false,
|
||||||
@@ -149,7 +147,6 @@ const EditItem: React.FC = () => {
|
|||||||
latitude: item.latitude,
|
latitude: item.latitude,
|
||||||
longitude: item.longitude,
|
longitude: item.longitude,
|
||||||
rules: item.rules || "",
|
rules: item.rules || "",
|
||||||
needsTraining: item.needsTraining || false,
|
|
||||||
generalAvailableAfter: item.availableAfter || "09:00",
|
generalAvailableAfter: item.availableAfter || "09:00",
|
||||||
generalAvailableBefore: item.availableBefore || "17:00",
|
generalAvailableBefore: item.availableBefore || "17:00",
|
||||||
specifyTimesPerDay: item.specifyTimesPerDay || false,
|
specifyTimesPerDay: item.specifyTimesPerDay || false,
|
||||||
@@ -547,7 +544,6 @@ const EditItem: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<RulesForm
|
<RulesForm
|
||||||
needsTraining={formData.needsTraining}
|
|
||||||
rules={formData.rules || ""}
|
rules={formData.rules || ""}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -633,7 +633,7 @@ const ItemDetail: React.FC = () => {
|
|||||||
})()}
|
})()}
|
||||||
|
|
||||||
{/* Rental Period Selection - Only show for non-owners */}
|
{/* Rental Period Selection - Only show for non-owners */}
|
||||||
{!isOwner && item.availability && !isAlreadyRenting && (
|
{!isOwner && item.isAvailable && !isAlreadyRenting && (
|
||||||
<>
|
<>
|
||||||
<hr />
|
<hr />
|
||||||
<div className="text-start">
|
<div className="text-start">
|
||||||
@@ -758,7 +758,7 @@ const ItemDetail: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Action Buttons */}
|
{/* Action Buttons */}
|
||||||
{!isOwner && item.availability && !isAlreadyRenting && (
|
{!isOwner && item.isAvailable && !isAlreadyRenting && (
|
||||||
<div className="d-grid">
|
<div className="d-grid">
|
||||||
<button
|
<button
|
||||||
className="btn btn-primary"
|
className="btn btn-primary"
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ const ItemList: React.FC = () => {
|
|||||||
// Access the items array from response.data.items
|
// Access the items array from response.data.items
|
||||||
const allItems = response.data.items || response.data || [];
|
const allItems = response.data.items || response.data || [];
|
||||||
// Filter only available items
|
// Filter only available items
|
||||||
const availableItems = allItems.filter((item: Item) => item.availability);
|
const availableItems = allItems.filter((item: Item) => item.isAvailable);
|
||||||
setItems(availableItems);
|
setItems(availableItems);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error("Error fetching items:", err);
|
console.error("Error fetching items:", err);
|
||||||
|
|||||||
@@ -110,11 +110,11 @@ const Owning: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
await api.put(`/items/${item.id}`, {
|
await api.put(`/items/${item.id}`, {
|
||||||
...item,
|
...item,
|
||||||
availability: !item.availability,
|
isAvailable: !item.isAvailable,
|
||||||
});
|
});
|
||||||
setListings(
|
setListings(
|
||||||
listings.map((i) =>
|
listings.map((i) =>
|
||||||
i.id === item.id ? { ...i, availability: !i.availability } : i
|
i.id === item.id ? { ...i, isAvailable: !i.isAvailable } : i
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
@@ -536,10 +536,10 @@ const Owning: React.FC = () => {
|
|||||||
<div className="mb-2 d-flex gap-2 flex-wrap">
|
<div className="mb-2 d-flex gap-2 flex-wrap">
|
||||||
<span
|
<span
|
||||||
className={`badge ${
|
className={`badge ${
|
||||||
item.availability ? "bg-success" : "bg-secondary"
|
item.isAvailable ? "bg-success" : "bg-secondary"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{item.availability ? "Available" : "Not Available"}
|
{item.isAvailable ? "Available" : "Not Available"}
|
||||||
</span>
|
</span>
|
||||||
{item.isDeleted && (
|
{item.isDeleted && (
|
||||||
<span className="badge bg-danger">
|
<span className="badge bg-danger">
|
||||||
@@ -620,7 +620,7 @@ const Owning: React.FC = () => {
|
|||||||
onClick={() => toggleAvailability(item)}
|
onClick={() => toggleAvailability(item)}
|
||||||
className="btn btn-sm btn-outline-info"
|
className="btn btn-sm btn-outline-info"
|
||||||
>
|
>
|
||||||
{item.availability
|
{item.isAvailable
|
||||||
? "Mark Unavailable"
|
? "Mark Unavailable"
|
||||||
: "Mark Available"}
|
: "Mark Available"}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ const RentItem: React.FC = () => {
|
|||||||
setItem(response.data);
|
setItem(response.data);
|
||||||
|
|
||||||
// Check if item is available
|
// Check if item is available
|
||||||
if (!response.data.availability) {
|
if (!response.data.isAvailable) {
|
||||||
setError("This item is not available for rent");
|
setError("This item is not available for rent");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -89,12 +89,8 @@ export interface Item {
|
|||||||
longitude?: number;
|
longitude?: number;
|
||||||
images: string[];
|
images: string[];
|
||||||
condition: "excellent" | "good" | "fair" | "poor";
|
condition: "excellent" | "good" | "fair" | "poor";
|
||||||
availability: boolean;
|
isAvailable: boolean;
|
||||||
specifications: Record<string, any>;
|
|
||||||
rules?: string;
|
rules?: string;
|
||||||
minimumRentalDays: number;
|
|
||||||
maximumRentalDays?: number;
|
|
||||||
needsTraining?: boolean;
|
|
||||||
availableAfter?: string;
|
availableAfter?: string;
|
||||||
availableBefore?: string;
|
availableBefore?: string;
|
||||||
specifyTimesPerDay?: boolean;
|
specifyTimesPerDay?: boolean;
|
||||||
@@ -151,7 +147,7 @@ export interface Rental {
|
|||||||
stripePaymentIntentId?: string;
|
stripePaymentIntentId?: string;
|
||||||
stripePaymentMethodId?: string;
|
stripePaymentMethodId?: string;
|
||||||
// Payout status tracking
|
// Payout status tracking
|
||||||
payoutStatus?: "pending" | "processing" | "completed" | "failed";
|
payoutStatus?: "pending" | "completed" | "failed" | null;
|
||||||
payoutProcessedAt?: string;
|
payoutProcessedAt?: string;
|
||||||
stripeTransferId?: string;
|
stripeTransferId?: string;
|
||||||
deliveryMethod: "pickup" | "delivery";
|
deliveryMethod: "pickup" | "delivery";
|
||||||
|
|||||||
Reference in New Issue
Block a user