Add directly purchased cars to My Requests page
- Add new /my-vehicles API endpoint returning both recommended and direct purchases - Add DirectPurchasedCarResponse and MyVehiclesResponse schemas - Update frontend to display directly purchased cars (from banners with 1CC) - Show separate collapsible section for direct purchases 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,12 +4,12 @@ from typing import List
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
from ..database import get_db
|
from ..database import get_db
|
||||||
from ..models import VehicleRequest, RequestVehicle, PurchasedVehicle, User, DealerInfo, SystemSettings, Car
|
from ..models import VehicleRequest, RequestVehicle, PurchasedVehicle, User, DealerInfo, SystemSettings, Car, CarView
|
||||||
from ..schemas import (
|
from ..schemas import (
|
||||||
VehicleRequestCreate, VehicleRequestResponse,
|
VehicleRequestCreate, VehicleRequestResponse,
|
||||||
RequestVehicleCreate, RequestVehicleResponse, RequestVehicleApprove,
|
RequestVehicleCreate, RequestVehicleResponse, RequestVehicleApprove,
|
||||||
PurchasedVehicleCreate, PurchasedVehicleResponse, PurchasedVehicleUpdateStatus,
|
PurchasedVehicleCreate, PurchasedVehicleResponse, PurchasedVehicleUpdateStatus,
|
||||||
VehicleRequestWithVehicles,
|
VehicleRequestWithVehicles, DirectPurchasedCarResponse, MyVehiclesResponse,
|
||||||
)
|
)
|
||||||
from .auth import get_current_user
|
from .auth import get_current_user
|
||||||
from .notification import notify_vehicle_recommended, notify_shipping_update
|
from .notification import notify_vehicle_recommended, notify_shipping_update
|
||||||
@@ -120,6 +120,97 @@ def get_my_requests(
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/my-vehicles", response_model=MyVehiclesResponse)
|
||||||
|
def get_my_vehicles(
|
||||||
|
current_user: User = Depends(get_current_user),
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""Get all vehicles accessible to user: recommended + directly purchased from banners"""
|
||||||
|
|
||||||
|
# 1. Get vehicle requests with recommended vehicles (existing logic)
|
||||||
|
requests = db.query(VehicleRequest).filter(
|
||||||
|
VehicleRequest.user_id == current_user.id
|
||||||
|
).order_by(VehicleRequest.created_at.desc()).all()
|
||||||
|
|
||||||
|
vehicle_requests = []
|
||||||
|
for req in requests:
|
||||||
|
if DEV_MODE or (req.created_at and datetime.utcnow() - req.created_at > timedelta(hours=24)):
|
||||||
|
approved_vehicles = [v for v in req.recommended_vehicles if v.is_approved]
|
||||||
|
else:
|
||||||
|
approved_vehicles = []
|
||||||
|
|
||||||
|
enriched_vehicles = []
|
||||||
|
for v in approved_vehicles:
|
||||||
|
vehicle_response = RequestVehicleResponse.model_validate(v)
|
||||||
|
if v.car_id:
|
||||||
|
car = db.query(Car).filter(Car.id == v.car_id).first()
|
||||||
|
if car:
|
||||||
|
vehicle_response.car_data = {**vehicle_response.car_data, "soldout": car.soldout}
|
||||||
|
enriched_vehicles.append(vehicle_response)
|
||||||
|
|
||||||
|
vehicle_requests.append(VehicleRequestWithVehicles(
|
||||||
|
request=VehicleRequestResponse.model_validate(req),
|
||||||
|
approved_vehicles=enriched_vehicles
|
||||||
|
))
|
||||||
|
|
||||||
|
# 2. Get directly purchased cars (from CarView - paid 1CC on banner)
|
||||||
|
car_views = db.query(CarView).filter(
|
||||||
|
CarView.user_id == current_user.id
|
||||||
|
).order_by(CarView.created_at.desc()).all()
|
||||||
|
|
||||||
|
# Get the car_ids that are already in recommended vehicles (to avoid duplicates)
|
||||||
|
recommended_car_ids = set()
|
||||||
|
for vr in vehicle_requests:
|
||||||
|
for v in vr.approved_vehicles:
|
||||||
|
if v.car_id:
|
||||||
|
recommended_car_ids.add(v.car_id)
|
||||||
|
|
||||||
|
direct_purchases = []
|
||||||
|
for cv in car_views:
|
||||||
|
# Skip if already in recommended (user got recommendation first, then it's not a "direct" purchase)
|
||||||
|
if cv.car_id in recommended_car_ids:
|
||||||
|
continue
|
||||||
|
|
||||||
|
car = db.query(Car).filter(Car.id == cv.car_id).first()
|
||||||
|
if car:
|
||||||
|
# Build car_data similar to recommended vehicles
|
||||||
|
main_image = None
|
||||||
|
if car.images:
|
||||||
|
main_img = next((img for img in car.images if img.is_main), None)
|
||||||
|
if main_img:
|
||||||
|
main_image = main_img.url
|
||||||
|
elif car.images:
|
||||||
|
main_image = car.images[0].url
|
||||||
|
|
||||||
|
car_data = {
|
||||||
|
"id": str(car.source_id) if car.source_id else str(car.id),
|
||||||
|
"car_name": car.car_name,
|
||||||
|
"maker_name": car.maker.name if car.maker else None,
|
||||||
|
"year": car.year,
|
||||||
|
"mileage": car.mileage,
|
||||||
|
"final_price": car.final_price_krw,
|
||||||
|
"fuel": car.fuel,
|
||||||
|
"transmission": car.transmission,
|
||||||
|
"color": car.color,
|
||||||
|
"main_image": main_image,
|
||||||
|
"soldout": car.soldout,
|
||||||
|
"local_car_id": car.id,
|
||||||
|
}
|
||||||
|
|
||||||
|
direct_purchases.append(DirectPurchasedCarResponse(
|
||||||
|
id=cv.id,
|
||||||
|
car_id=cv.car_id,
|
||||||
|
car_data=car_data,
|
||||||
|
cc_paid=cv.cc_paid,
|
||||||
|
purchased_at=cv.created_at
|
||||||
|
))
|
||||||
|
|
||||||
|
return MyVehiclesResponse(
|
||||||
|
vehicle_requests=vehicle_requests,
|
||||||
|
direct_purchases=direct_purchases
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# =====================
|
# =====================
|
||||||
# Purchased Vehicles (Find My Car)
|
# Purchased Vehicles (Find My Car)
|
||||||
# =====================
|
# =====================
|
||||||
|
|||||||
@@ -123,3 +123,21 @@ class VehicleRequestWithVehicles(BaseModel):
|
|||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
from_attributes = True
|
from_attributes = True
|
||||||
|
|
||||||
|
|
||||||
|
# Directly purchased car (from CarView - when user pays 1CC on banner car)
|
||||||
|
class DirectPurchasedCarResponse(BaseModel):
|
||||||
|
id: int # CarView id
|
||||||
|
car_id: int
|
||||||
|
car_data: dict # Car details for display
|
||||||
|
cc_paid: float
|
||||||
|
purchased_at: datetime
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
||||||
|
|
||||||
|
# Full response including both recommended and directly purchased cars
|
||||||
|
class MyVehiclesResponse(BaseModel):
|
||||||
|
vehicle_requests: List[VehicleRequestWithVehicles]
|
||||||
|
direct_purchases: List[DirectPurchasedCarResponse] # Cars purchased directly from banner
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import Link from 'next/link';
|
|||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { useTranslation, formatPriceWithCurrency, translateCarName } from '@/lib/i18n';
|
import { useTranslation, formatPriceWithCurrency, translateCarName } from '@/lib/i18n';
|
||||||
import { useAuthStore } from '@/lib/store';
|
import { useAuthStore } from '@/lib/store';
|
||||||
import { vehicleRequestsApi, VehicleRequestWithVehicles } from '@/lib/api';
|
import { vehicleRequestsApi, VehicleRequestWithVehicles, DirectPurchasedCar } from '@/lib/api';
|
||||||
import SidebarLayout from '@/components/SidebarLayout';
|
import SidebarLayout from '@/components/SidebarLayout';
|
||||||
|
|
||||||
export default function MyRequestPage() {
|
export default function MyRequestPage() {
|
||||||
@@ -15,9 +15,11 @@ export default function MyRequestPage() {
|
|||||||
const { user } = useAuthStore();
|
const { user } = useAuthStore();
|
||||||
|
|
||||||
const [requests, setRequests] = useState<VehicleRequestWithVehicles[]>([]);
|
const [requests, setRequests] = useState<VehicleRequestWithVehicles[]>([]);
|
||||||
|
const [directPurchases, setDirectPurchases] = useState<DirectPurchasedCar[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [expandedRequest, setExpandedRequest] = useState<number | null>(null);
|
const [expandedRequest, setExpandedRequest] = useState<number | null>(null);
|
||||||
|
const [showDirectPurchases, setShowDirectPurchases] = useState(true);
|
||||||
|
|
||||||
// Redirect if not logged in
|
// Redirect if not logged in
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -26,28 +28,29 @@ export default function MyRequestPage() {
|
|||||||
}
|
}
|
||||||
}, [user, router]);
|
}, [user, router]);
|
||||||
|
|
||||||
// Load requests
|
// Load all vehicles (recommended + directly purchased)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadRequests = async () => {
|
const loadVehicles = async () => {
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const data = await vehicleRequestsApi.getMyRequests();
|
const data = await vehicleRequestsApi.getMyVehicles();
|
||||||
setRequests(data);
|
setRequests(data.vehicle_requests);
|
||||||
|
setDirectPurchases(data.direct_purchases);
|
||||||
// Auto-expand first request if it has approved vehicles
|
// Auto-expand first request if it has approved vehicles
|
||||||
if (data.length > 0 && data[0].approved_vehicles.length > 0) {
|
if (data.vehicle_requests.length > 0 && data.vehicle_requests[0].approved_vehicles.length > 0) {
|
||||||
setExpandedRequest(data[0].request.id);
|
setExpandedRequest(data.vehicle_requests[0].request.id);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to load requests:', err);
|
console.error('Failed to load vehicles:', err);
|
||||||
setError(language === 'ko' ? '요청 목록을 불러오는데 실패했습니다.' : 'Failed to load requests.');
|
setError(language === 'ko' ? '차량 목록을 불러오는데 실패했습니다.' : 'Failed to load vehicles.');
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
loadRequests();
|
loadVehicles();
|
||||||
}, [user, language]);
|
}, [user, language]);
|
||||||
|
|
||||||
// Format date (mn uses en-US as mn-MN is not supported in most browsers)
|
// Format date (mn uses en-US as mn-MN is not supported in most browsers)
|
||||||
@@ -130,8 +133,8 @@ export default function MyRequestPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* No Requests */}
|
{/* No Vehicles at all */}
|
||||||
{!isLoading && !error && requests.length === 0 && (
|
{!isLoading && !error && requests.length === 0 && directPurchases.length === 0 && (
|
||||||
<div className="bg-white rounded-lg shadow-md p-12 text-center">
|
<div className="bg-white rounded-lg shadow-md p-12 text-center">
|
||||||
<div className="text-gray-400 mb-4">
|
<div className="text-gray-400 mb-4">
|
||||||
<svg className="w-16 h-16 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-16 h-16 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
@@ -338,6 +341,169 @@ export default function MyRequestPage() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Directly Purchased Cars Section */}
|
||||||
|
{!isLoading && !error && directPurchases.length > 0 && (
|
||||||
|
<div className="mt-8">
|
||||||
|
<div className="bg-white rounded-lg shadow-md overflow-hidden">
|
||||||
|
{/* Section Header */}
|
||||||
|
<div
|
||||||
|
className="p-6 cursor-pointer hover:bg-gray-50 transition border-b"
|
||||||
|
onClick={() => setShowDirectPurchases(!showDirectPurchases)}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="w-10 h-10 bg-blue-100 text-blue-600 rounded-full flex items-center justify-center">
|
||||||
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold text-gray-800">
|
||||||
|
{language === 'ko' ? '직접 구매한 차량' : 'Directly Purchased Cars'}
|
||||||
|
</h3>
|
||||||
|
<p className="text-sm text-gray-500">
|
||||||
|
{language === 'ko' ? '배너에서 1CC로 정보를 구매한 차량' : 'Cars purchased with 1CC from banners'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<span className="bg-blue-100 text-blue-700 px-3 py-1 rounded-full text-sm font-medium">
|
||||||
|
{directPurchases.length} {language === 'ko' ? '대' : 'cars'}
|
||||||
|
</span>
|
||||||
|
<svg
|
||||||
|
className={`w-6 h-6 text-gray-400 transition-transform ${showDirectPurchases ? 'rotate-180' : ''}`}
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Direct Purchases Grid */}
|
||||||
|
{showDirectPurchases && (
|
||||||
|
<div className="p-6 bg-gray-50">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
{directPurchases.map((purchase) => {
|
||||||
|
const carData = purchase.car_data;
|
||||||
|
const priceInfo = formatPrice(carData?.final_price);
|
||||||
|
const isSoldout = carData?.soldout === true;
|
||||||
|
const carId = purchase.car_id;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={purchase.id} className={`bg-white rounded-lg shadow-sm overflow-hidden border transition-shadow ${isSoldout ? 'opacity-75' : 'hover:shadow-md'}`}>
|
||||||
|
{/* Clickable Vehicle Card */}
|
||||||
|
<Link href={`/cars/${carId}`} className="block">
|
||||||
|
{/* Vehicle Image */}
|
||||||
|
<div className="relative h-40 bg-gray-200">
|
||||||
|
{carData?.main_image ? (
|
||||||
|
<Image
|
||||||
|
src={carData.main_image}
|
||||||
|
alt={carData.car_name || 'Vehicle'}
|
||||||
|
fill
|
||||||
|
className={`object-cover ${isSoldout ? 'grayscale' : ''}`}
|
||||||
|
unoptimized
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center text-gray-400">
|
||||||
|
<svg className="w-12 h-12" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{/* Soldout Badge */}
|
||||||
|
{isSoldout && (
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center bg-black/30">
|
||||||
|
<span className="bg-red-600 text-white px-4 py-2 rounded-lg font-bold text-lg transform -rotate-12 shadow-lg">
|
||||||
|
{language === 'ko' ? '판매완료' : 'SOLD OUT'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{/* Direct Purchase Badge */}
|
||||||
|
<div className="absolute top-2 left-2">
|
||||||
|
<span className="bg-blue-600 text-white px-2 py-1 rounded text-xs font-medium">
|
||||||
|
{purchase.cc_paid} CC
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Vehicle Info */}
|
||||||
|
<div className="p-4">
|
||||||
|
<h5 className="font-semibold text-gray-800 mb-2 line-clamp-2">
|
||||||
|
{translateCarName(carData?.car_name, language)}
|
||||||
|
</h5>
|
||||||
|
|
||||||
|
<div className="text-sm text-gray-600 space-y-1 mb-3">
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span>{t.year}</span>
|
||||||
|
<span>{carData?.year || '-'}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span>{t.mileage}</span>
|
||||||
|
<span>{carData?.mileage?.toLocaleString()} km</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span>{t.fuel}</span>
|
||||||
|
<span>{translateCarName(carData?.fuel, language) || '-'}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="border-t pt-3">
|
||||||
|
<div className="text-primary-600 font-bold text-lg">
|
||||||
|
{priceInfo.usdt}
|
||||||
|
</div>
|
||||||
|
<div className="text-gray-500 text-sm">
|
||||||
|
{priceInfo.local}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{/* Action Buttons */}
|
||||||
|
<div className="px-4 pb-4 flex gap-2">
|
||||||
|
{isSoldout ? (
|
||||||
|
<div className="flex-1 bg-gray-400 text-white text-sm py-2 px-3 rounded-lg text-center font-medium">
|
||||||
|
{language === 'ko' ? '판매완료' : 'Sold Out'}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => router.push(`/cars/${carId}?action=purchase`)}
|
||||||
|
className="flex-1 bg-primary-600 text-white text-sm py-2 px-3 rounded-lg hover:bg-primary-700 transition font-medium"
|
||||||
|
>
|
||||||
|
{language === 'ko' ? '구매하기' : 'Purchase'}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
if (user?.is_dealer) {
|
||||||
|
router.push(`/cars/${carId}?action=recommend`);
|
||||||
|
} else {
|
||||||
|
alert(language === 'ko'
|
||||||
|
? '딜러 회원만 지인 추천 기능을 사용할 수 있습니다.'
|
||||||
|
: 'Only dealer members can use the referral feature.');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="flex-1 bg-green-600 text-white text-sm py-2 px-3 rounded-lg hover:bg-green-700 transition font-medium"
|
||||||
|
>
|
||||||
|
{language === 'ko' ? '지인에게 추천' : 'Recommend'}
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</SidebarLayout>
|
</SidebarLayout>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -630,6 +630,21 @@ export interface VehicleRequestWithVehicles {
|
|||||||
approved_vehicles: RequestVehicle[];
|
approved_vehicles: RequestVehicle[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Directly purchased car (from banner with 1CC)
|
||||||
|
export interface DirectPurchasedCar {
|
||||||
|
id: number;
|
||||||
|
car_id: number;
|
||||||
|
car_data: Record<string, any>;
|
||||||
|
cc_paid: number;
|
||||||
|
purchased_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Full response including both recommended and directly purchased cars
|
||||||
|
export interface MyVehiclesResponse {
|
||||||
|
vehicle_requests: VehicleRequestWithVehicles[];
|
||||||
|
direct_purchases: DirectPurchasedCar[];
|
||||||
|
}
|
||||||
|
|
||||||
export const vehicleRequestsApi = {
|
export const vehicleRequestsApi = {
|
||||||
// User endpoints
|
// User endpoints
|
||||||
createRequest: async (requestData: {
|
createRequest: async (requestData: {
|
||||||
@@ -656,6 +671,12 @@ export const vehicleRequestsApi = {
|
|||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Get all vehicles: recommended + directly purchased from banners
|
||||||
|
getMyVehicles: async (): Promise<MyVehiclesResponse> => {
|
||||||
|
const { data } = await api.get('/vehicle-requests/my-vehicles');
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
getPurchasedVehicles: async (): Promise<PurchasedVehicle[]> => {
|
getPurchasedVehicles: async (): Promise<PurchasedVehicle[]> => {
|
||||||
const { data } = await api.get('/vehicle-requests/purchased');
|
const { data } = await api.get('/vehicle-requests/purchased');
|
||||||
return data;
|
return data;
|
||||||
|
|||||||
Reference in New Issue
Block a user