From 2e6c075efee4fea6217f449d447d608b1ebd1301 Mon Sep 17 00:00:00 2001 From: Lance Tan Date: Fri, 12 Apr 2024 20:00:47 -0700 Subject: [PATCH 1/2] Create bidding history page --- backend/auction/models.py | 4 +- backend/auction/urls.py | 4 + backend/auction/views.py | 161 ++++++++++++++++-- .../DownloadStatementOfAccountButton.jsx | 13 ++ .../src/components/cards/BiddedItemCard.jsx | 5 +- .../src/components/cards/SavedItemCard.jsx | 5 +- .../src/components/cards/VehicleItemCard.jsx | 5 +- frontend/src/components/cards/WonItemCard.jsx | 105 ++++++++++++ .../src/components/modals/QuickViewModal.jsx | 3 +- .../components/timers/BidHistoryCountdown.jsx | 69 ++++++++ .../timers/CurrentAuctionCountdown.jsx | 2 +- .../timers/VehicleDetailsCountdown.jsx | 67 -------- frontend/src/index.jsx | 5 +- frontend/src/pages/BidHistoryPage.jsx | 76 +++++++++ frontend/src/pages/HomePage.jsx | 77 +++++---- frontend/src/pages/ListingsPage.jsx | 88 +++++----- frontend/src/pages/VehicleDetailsPage.jsx | 4 +- .../src/providers/CurrentAuctionProvider.jsx | 38 +++++ frontend/src/routers/Routers.jsx | 3 +- frontend/src/utils/dateTime.js | 12 +- 20 files changed, 568 insertions(+), 178 deletions(-) create mode 100644 frontend/src/components/buttons/DownloadStatementOfAccountButton.jsx create mode 100644 frontend/src/components/cards/WonItemCard.jsx create mode 100644 frontend/src/components/timers/BidHistoryCountdown.jsx delete mode 100644 frontend/src/components/timers/VehicleDetailsCountdown.jsx create mode 100644 frontend/src/pages/BidHistoryPage.jsx create mode 100644 frontend/src/providers/CurrentAuctionProvider.jsx diff --git a/backend/auction/models.py b/backend/auction/models.py index 86ecf72..92fcee2 100644 --- a/backend/auction/models.py +++ b/backend/auction/models.py @@ -16,8 +16,8 @@ class Auction(MainModel): end_date = models.DateTimeField(null=False) cover_image = models.CharField(max_length=500, null=True, blank=True) is_published = models.BooleanField(default=False) - start_time = models.TimeField(null=False, default="00:00") - end_time = models.TimeField(null=False, default="23:59") + start_time = models.TimeField(null=False, default="00:00:00") + end_time = models.TimeField(null=False, default="23:59:59") def __str__(self): return ( diff --git a/backend/auction/urls.py b/backend/auction/urls.py index 7ea380c..b628646 100644 --- a/backend/auction/urls.py +++ b/backend/auction/urls.py @@ -9,12 +9,16 @@ BidderVerificationApiView, CurrentAuctionApiView, GetSavedUnitApiView, + PastAuctionsApiView, SaveUnitApiView, + UpcomingAuctionApiView, ) urlpatterns = [ path("", AuctionListApiView.as_view(), name="auction_list"), path("/current", CurrentAuctionApiView.as_view(), name="current_auction_list"), + path("/upcoming", UpcomingAuctionApiView.as_view(), name="upcoming_auction_list"), + path("/past", PastAuctionsApiView.as_view(), name="past_auction_list"), path("/", AuctionDetailApiView.as_view(), name="auction_detail"), path("//day", AuctionDayApiView.as_view(), name="auction_day"), path( diff --git a/backend/auction/views.py b/backend/auction/views.py index a5ab3f5..9f0f659 100644 --- a/backend/auction/views.py +++ b/backend/auction/views.py @@ -1,4 +1,4 @@ -from datetime import timedelta +from datetime import datetime, time, timedelta from django.contrib.contenttypes.models import ContentType from django.core.paginator import Paginator @@ -92,13 +92,13 @@ def post(self, request, *args, **kwargs): if serializer.is_valid(): auction = serializer.save() - start_date = auction.start_date.date() - end_date = auction.end_date.date() + start_datetime = datetime.combine(auction.start_date.date(), time()) + end_datetime = datetime.combine(auction.end_date.date(), time()) delta = timedelta(days=1) - current_date = start_date + current_date = start_datetime - while current_date <= end_date: - AuctionDay.objects.create(auction=auction, date=current_date) + while current_date <= end_datetime: + AuctionDay.objects.create(auction=auction, date=current_date.date()) current_date += delta return Response(serializer.data, status=status.HTTP_201_CREATED) @@ -129,25 +129,164 @@ class CurrentAuctionApiView(APIView): def get(self, request, *args, **kwargs): """ - Get the current auction + Get the current auction with counts of different types of items. """ today = timezone.now().date() current_auctions = Auction.objects.filter( start_date__lte=today, end_date__gte=today + ).prefetch_related( + Prefetch( + "days", + queryset=AuctionDay.objects.prefetch_related( + Prefetch( + "items", + queryset=AuctionItem.objects.select_related("content_type"), + ) + ), + ) ) if not current_auctions.exists(): return Response( - {"message": "No current auction found"}, - status=status.HTTP_404_NOT_FOUND, + status=status.HTTP_204_NO_CONTENT, ) current_auction = current_auctions.first() - serialized_data = AuctionSerializer(current_auction) + truck_count = 0 + equipment_count = 0 + trailer_count = 0 + + for day in current_auction.days.all(): + for item in day.items.all(): + if isinstance(item.content_object, Vehicle): + truck_count += 1 + elif isinstance(item.content_object, Equipment): + equipment_count += 1 + elif isinstance(item.content_object, Trailer): + trailer_count += 1 + + serialized_data = AuctionSerializer(current_auction).data + serialized_data.update( + { + "truck_count": truck_count, + "equipment_count": equipment_count, + "trailer_count": trailer_count, + } + ) + + return Response( + serialized_data, + status=status.HTTP_200_OK, + ) + + +class PastAuctionsApiView(APIView): + permission_classes = [AllowAny] + authentication_classes = [] + + def get(self, request, *args, **kwargs): + """ + Get all past auctions with counts of different types of items. + """ + today = timezone.now().date() + past_auctions = Auction.objects.filter(end_date__lt=today).prefetch_related( + Prefetch( + "days", + queryset=AuctionDay.objects.prefetch_related( + Prefetch( + "items", + queryset=AuctionItem.objects.select_related("content_type"), + ) + ), + ) + ) + + auctions_data = [] + for auction in past_auctions: + truck_count = 0 + equipment_count = 0 + trailer_count = 0 + + for day in auction.days.all(): + for item in day.items.all(): + if isinstance(item.content_object, Vehicle): + truck_count += 1 + elif isinstance(item.content_object, Equipment): + equipment_count += 1 + elif isinstance(item.content_object, Trailer): + trailer_count += 1 + + auction_data = AuctionSerializer(auction).data + auction_data.update( + { + "truck_count": truck_count, + "equipment_count": equipment_count, + "trailer_count": trailer_count, + } + ) + + auctions_data.append(auction_data) + + return Response(auctions_data, status=status.HTTP_200_OK) + + +class UpcomingAuctionApiView(APIView): + permission_classes = [AllowAny] + authentication_classes = [] + + def get(self, request, *args, **kwargs): + """ + Get the closest upcoming auction. + """ + today = timezone.now().date() + upcoming_auctions = ( + Auction.objects.filter(start_date__gt=today) + .order_by("start_date") + .prefetch_related( + Prefetch( + "days", + queryset=AuctionDay.objects.prefetch_related( + Prefetch( + "items", + queryset=AuctionItem.objects.select_related("content_type"), + ) + ), + ) + ) + ) + + if not upcoming_auctions.exists(): + return Response( + status=status.HTTP_204_NO_CONTENT, + ) + + closest_auction = upcoming_auctions.first() + + truck_count = 0 + equipment_count = 0 + trailer_count = 0 + + for day in closest_auction.days.all(): + for item in day.items.all(): + if isinstance(item.content_object, Vehicle): + truck_count += 1 + elif isinstance(item.content_object, Equipment): + equipment_count += 1 + elif isinstance(item.content_object, Trailer): + trailer_count += 1 + + auction_data = AuctionSerializer(closest_auction).data + auction_data.update( + { + "truck_count": truck_count, + "equipment_count": equipment_count, + "trailer_count": trailer_count, + } + ) return Response( - serialized_data.data, + auction_data, status=status.HTTP_200_OK, ) diff --git a/frontend/src/components/buttons/DownloadStatementOfAccountButton.jsx b/frontend/src/components/buttons/DownloadStatementOfAccountButton.jsx new file mode 100644 index 0000000..1975cb5 --- /dev/null +++ b/frontend/src/components/buttons/DownloadStatementOfAccountButton.jsx @@ -0,0 +1,13 @@ +import React from 'react'; + +export default function DownloadStatementOfAccountButton({ onClick }) { + return ( + + ); +} diff --git a/frontend/src/components/cards/BiddedItemCard.jsx b/frontend/src/components/cards/BiddedItemCard.jsx index 215659f..e8ef1f2 100644 --- a/frontend/src/components/cards/BiddedItemCard.jsx +++ b/frontend/src/components/cards/BiddedItemCard.jsx @@ -4,6 +4,7 @@ import AgricultureIcon from '@mui/icons-material/Agriculture'; import StarsOutlinedIcon from '@mui/icons-material/StarsOutlined'; import EmojiEventsIcon from '@mui/icons-material/EmojiEvents'; import BidNowButton from '../buttons/BidNowButton'; +import { priceToString } from '../../utils/priceUtil'; export default function BiddedItemCard({ // eslint-disable-next-line no-unused-vars @@ -81,7 +82,9 @@ export default function BiddedItemCard({

{isTopBid ? 'Your bid:' : 'Highest bid:'}

-

{`₱${price}`}

+

{`₱${priceToString( + price + )}`}

{isTopBid ? (

diff --git a/frontend/src/components/cards/SavedItemCard.jsx b/frontend/src/components/cards/SavedItemCard.jsx index 9b49d6f..0800569 100644 --- a/frontend/src/components/cards/SavedItemCard.jsx +++ b/frontend/src/components/cards/SavedItemCard.jsx @@ -4,6 +4,7 @@ import AgricultureIcon from '@mui/icons-material/Agriculture'; import StarsOutlinedIcon from '@mui/icons-material/StarsOutlined'; import BidNowButton from '../buttons/BidNowButton'; import RemoveFromListButton from '../buttons/RemoveFromListButton'; +import { priceToString } from '../../utils/priceUtil'; export default function SavedItemCard({ // eslint-disable-next-line no-unused-vars @@ -80,7 +81,9 @@ export default function SavedItemCard({

Highest bid:

-

{`₱${price}`}

+

{`₱${priceToString( + price + )}`}

{}} size="sm" /> {}} /> diff --git a/frontend/src/components/cards/VehicleItemCard.jsx b/frontend/src/components/cards/VehicleItemCard.jsx index 3843468..0c02c4f 100644 --- a/frontend/src/components/cards/VehicleItemCard.jsx +++ b/frontend/src/components/cards/VehicleItemCard.jsx @@ -7,6 +7,7 @@ import AddToListButton from '../buttons/AddToListButton'; import ViewModelButton from '../buttons/ViewModelButton'; import QuickViewButton from '../buttons/QuickViewButton'; import QuickViewModal from '../modals/QuickViewModal'; +import { priceToString } from '../../utils/priceUtil'; export default function VehicleItemCard({ vehicleId, @@ -97,7 +98,9 @@ export default function VehicleItemCard({

-

{`₱${price}`}

+

{`₱${priceToString( + price + )}`}

diff --git a/frontend/src/components/cards/WonItemCard.jsx b/frontend/src/components/cards/WonItemCard.jsx new file mode 100644 index 0000000..cd939bb --- /dev/null +++ b/frontend/src/components/cards/WonItemCard.jsx @@ -0,0 +1,105 @@ +import React from 'react'; +import { useNavigate } from 'react-router'; +import DirectionsBusIcon from '@mui/icons-material/DirectionsBus'; +import AgricultureIcon from '@mui/icons-material/Agriculture'; +import StarsOutlinedIcon from '@mui/icons-material/StarsOutlined'; +import ViewModelButton from '../buttons/ViewModelButton'; +import { priceToString } from '../../utils/priceUtil'; + +export default function VehicleItemCard({ + vehicleId, + description, + modelNumber, + engineNumber, + chassisNumber, + price, + imageUrl, +}) { + const navigate = useNavigate(); + + const handleViewClick = () => { + navigate(`/listings/${vehicleId}`); + }; + + return ( +
+
+ {description} +
+
+
+
+ {description} +
+ +
+
+
+ +
+

Model

+

{modelNumber}

+
+
+ +
+ +
+

Chassis

+

{chassisNumber}

+
+
+
+ +
+
+ +
+

Engine no.

+

{engineNumber}

+
+
+
+
+
+
+
+ +

+ Your bid +

+
+ +

{`₱${priceToString( + price + )}`}

+
+
+ +
+
+
+ ); +} diff --git a/frontend/src/components/modals/QuickViewModal.jsx b/frontend/src/components/modals/QuickViewModal.jsx index 722e763..e7a6539 100644 --- a/frontend/src/components/modals/QuickViewModal.jsx +++ b/frontend/src/components/modals/QuickViewModal.jsx @@ -11,6 +11,7 @@ import truck4 from '../../assets/truck4.png'; import truck5 from '../../assets/truck5.png'; import ViewModelButton from '../buttons/ViewModelButton'; import CloseButton from '../buttons/CloseButton'; +import { priceToString } from '../../utils/priceUtil'; export default function QuickViewModal({ isOpen, @@ -108,7 +109,7 @@ export default function QuickViewModal({ Highest bid:

- {`₱${price}`} + {`₱${priceToString(price)}`}

diff --git a/frontend/src/components/timers/BidHistoryCountdown.jsx b/frontend/src/components/timers/BidHistoryCountdown.jsx new file mode 100644 index 0000000..ddfce77 --- /dev/null +++ b/frontend/src/components/timers/BidHistoryCountdown.jsx @@ -0,0 +1,69 @@ +import React, { useState, useEffect } from 'react'; +import { + formatTime, + calculateProgress, + formatFlexibleDateRange, +} from '../../utils/dateTime'; + +// expects type Date for startDateTime and endDateTime props, recommended maxWidth around 300px +export default function BidHistoryCountdown({ + maxWidth = '60%', + startDate, + endDate, + startTime = '00:00:00', + endTime = '23:59:00', +}) { + const [timeRemaining, setTimeRemaining] = useState(0); + const [isExpired, setIsExpired] = useState(false); + + const startDateTimeString = `${startDate?.split('T')[0]}T${startTime}+08:00`; + const endDateTimeString = `${endDate?.split('T')[0]}T${endTime}+08:00`; + const startDateTime = new Date(startDateTimeString); + const endDateTime = new Date(endDateTimeString); + + useEffect(() => { + const timer = setInterval(() => { + const now = new Date(); + const endTimeMs = endDateTime.getTime(); + const remaining = Math.max(0, endTimeMs - now.getTime()); + setTimeRemaining(remaining); + setIsExpired(now >= endDateTime); + }, 1000); + + return () => clearInterval(timer); + }, [startDate, startTime, endDate, endTime]); + + let statusString = `${formatFlexibleDateRange( + startDateTime, + endDateTime + )} | ${formatTime(timeRemaining)}`; + if (isExpired) { + statusString = 'Auction has ended'; + } + + return ( +
+
+ {statusString} +
+
+
+
+
+ ); +} diff --git a/frontend/src/components/timers/CurrentAuctionCountdown.jsx b/frontend/src/components/timers/CurrentAuctionCountdown.jsx index 33b651a..7f23186 100644 --- a/frontend/src/components/timers/CurrentAuctionCountdown.jsx +++ b/frontend/src/components/timers/CurrentAuctionCountdown.jsx @@ -32,7 +32,7 @@ export default function CurrentAuctionCountdown({ if (!isActive) { statusString = 'Auction has not started'; } else if (isExpired) { - statusString = 'Auction has ended'; + statusString = "Today's auction has ended"; } return ( diff --git a/frontend/src/components/timers/VehicleDetailsCountdown.jsx b/frontend/src/components/timers/VehicleDetailsCountdown.jsx deleted file mode 100644 index 81becdf..0000000 --- a/frontend/src/components/timers/VehicleDetailsCountdown.jsx +++ /dev/null @@ -1,67 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { formatTime, calculateProgress } from '../../utils/dateTime'; - -// expects type Date for startDateTime and endDateTime props, recommended maxWidth around 300px -export default function VehicleDetailsCountdown({ - maxWidth = '60%', - startDateTime, - endDateTime, -}) { - const [timeRemaining, setTimeRemaining] = useState(0); - const [isExpired, setIsExpired] = useState(false); - const [isActive, setIsActive] = useState(true); - - useEffect(() => { - const timer = setInterval(() => { - const now = Date.now(); - if (now < startDateTime.getTime()) { - setIsActive(false); - } else { - setIsActive(true); - } - const endTimeMs = endDateTime.getTime(); - const remaining = Math.max(0, endTimeMs - now); - setTimeRemaining(remaining); - setIsExpired(now >= endTimeMs); - }, 1000); - - return () => clearInterval(timer); - }, [startDateTime, endDateTime]); - - let statusString = formatTime(timeRemaining); - if (!isActive) { - statusString = 'Auction has not started'; - } else if (isExpired) { - statusString = 'Auction has ended'; - } - - return ( -
-
-
-
-
- {statusString} -
-
- ); -} diff --git a/frontend/src/index.jsx b/frontend/src/index.jsx index 8539fee..8cbdbcc 100644 --- a/frontend/src/index.jsx +++ b/frontend/src/index.jsx @@ -6,12 +6,15 @@ import reportWebVitals from './reportWebVitals'; import router from './routers/Routers'; import { AuthProvider } from './providers/AuthProvider'; import { UserProvider } from './providers/UserProvider'; +import { CurrentAuctionProvider } from './providers/CurrentAuctionProvider'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( - + + + ); diff --git a/frontend/src/pages/BidHistoryPage.jsx b/frontend/src/pages/BidHistoryPage.jsx new file mode 100644 index 0000000..d46e409 --- /dev/null +++ b/frontend/src/pages/BidHistoryPage.jsx @@ -0,0 +1,76 @@ +import React from 'react'; +import { ScrollRestoration } from 'react-router-dom'; +import NavBar from '../components/navBars/NavBar'; +import Footer from '../components/footers/Footer'; +import DownloadStatementOfAccountButton from '../components/buttons/DownloadStatementOfAccountButton'; +import BidHistoryCountdown from '../components/timers/BidHistoryCountdown'; +import WonItemCard from '../components/cards/WonItemCard'; +import vehicleImage from '../assets/truck.png'; +import { useCurrentAuction } from '../providers/CurrentAuctionProvider'; + +export default function BidHistoryPage() { + const { currentAuction } = useCurrentAuction(); + + const handleClickStatementOfAccount = () => {}; + + const getExtendedDate = () => { + if (currentAuction && currentAuction.end_date) { + const endDate = new Date(currentAuction.end_date); + endDate.setDate(endDate.getDate() + 14); // Adds 14 days to the end date + return endDate.toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric', + }); + } + return 'a future date'; + }; + + return ( +
+ + + +
+
+
+

Won Bids

+ +
+
+ +
+
+ +

+ {`You have until ${getExtendedDate()}, to view and download your statement + of account.`} +

+ +
+ + +
+ +
+
+
+
+ ); +} diff --git a/frontend/src/pages/HomePage.jsx b/frontend/src/pages/HomePage.jsx index 92d936f..ea1bb2e 100644 --- a/frontend/src/pages/HomePage.jsx +++ b/frontend/src/pages/HomePage.jsx @@ -19,12 +19,12 @@ import sortAuctions from '../utils/auctionUtils'; import AwaitingApprovalButton from '../components/buttons/AwaitingApprovalButton'; import StartBiddingButton from '../components/buttons/StartBiddingButton'; import { useUser } from '../providers/UserProvider'; +import { useCurrentAuction } from '../providers/CurrentAuctionProvider'; export default function HomePage() { const user = useUser(); - // eslint-disable-next-line no-unused-vars - const [searchedAuctions, setSearchedAuctions] = useState([]); - const [currentAuctionList, setCurrentAuctionList] = useState([]); + const { currentAuction } = useCurrentAuction(); + const [upcomingAuctionList, setUpcomingAuctionList] = useState([]); const [pastAuctionList, setPastAuctionList] = useState([]); const [currentVerified, setCurrentVerified] = useState(null); @@ -33,35 +33,44 @@ export default function HomePage() { const navigate = useNavigate(); useEffect(() => { - async function fetchDataAsync() { - try { - const auctionsResponse = await fetchData({ - endpoint: '/v1/auctions', + const fetchUpcomingAuctions = async () => { + const response = await fetchData({ + endpoint: '/v1/auctions/upcoming', + method: 'GET', + }); + + setUpcomingAuctionList(response.data); + }; + + const fetchPastAuctions = async () => { + const response = await fetchData({ + endpoint: '/v1/auctions/past', + method: 'GET', + }); + + setPastAuctionList(response.data); + }; + + const fetchCurrentVerification = async () => { + if (user && isLoggedIn && currentAuction) { + const response = await fetchData({ + endpoint: `/v1/auctions/${currentAuction.id}/verification?bidder_id=${user.sub}`, method: 'GET', }); - const auctionList = sortAuctions(auctionsResponse.data); - - setUpcomingAuctionList(auctionList.upcoming); - setCurrentAuctionList(auctionList.current); - setPastAuctionList(auctionList.past); - - if (user && isLoggedIn && auctionList.current.length > 0) { - const response = await fetchData({ - endpoint: `/v1/auctions/${auctionList.current[0].id}/verification?bidder_id=${user.sub}`, - method: 'GET', - }); - - setCurrentVerified( - response.data.length > 0 ? response.data[0].is_verified : null - ); - } - } catch (error) { - /* empty */ + + setCurrentVerified( + response.data.length > 0 ? response.data[0].is_verified : null + ); } - } + }; - fetchDataAsync(); - }, [isLoggedIn, user]); + fetchPastAuctions(); + if (!currentAuction) { + fetchUpcomingAuctions(); + } else { + fetchCurrentVerification(); + } + }, [currentAuction, user, isLoggedIn]); const handleStartBiddingButton = async () => { navigate('/listings'); @@ -147,18 +156,18 @@ export default function HomePage() {
- {currentAuctionList.length > 0 ? ( + {currentAuction ? ( <>

Current Auction

diff --git a/frontend/src/pages/ListingsPage.jsx b/frontend/src/pages/ListingsPage.jsx index 9c08efd..5df64ab 100644 --- a/frontend/src/pages/ListingsPage.jsx +++ b/frontend/src/pages/ListingsPage.jsx @@ -17,17 +17,18 @@ import { formatListingsTodayDate, formatFlexibleDateRange, convertSGTToLocalDateObject, + getCurrentDateInSingapore, } from '../utils/dateTime'; +import { useCurrentAuction } from '../providers/CurrentAuctionProvider'; export default function ListingsPage() { + const { currentAuction } = useCurrentAuction(); const [currentPage, setCurrentPage] = useState(1); const [totalPages, setTotalPages] = useState(0); // eslint-disable-next-line no-unused-vars const [loading, setLoading] = useState(false); const [hasNextPage, setHasNextPage] = useState(null); const [hasPrevPage, setHasPrevPage] = useState(null); - - const [auction, setAuction] = useState(null); const [auctionDayId, setAuctionDayId] = useState(null); const [units, setUnits] = useState([]); const [searchTerm, setSearchTerm] = useState(''); @@ -97,17 +98,10 @@ export default function ListingsPage() { method: 'GET', }); - const currentAuctionPromise = fetchData({ - endpoint: '/v1/auctions/current', - method: 'GET', - }); - - const [brandsResponse, typesResponse, currentAuctionResponse] = - await Promise.all([ - brandsPromise, - typesPromise, - currentAuctionPromise, - ]); + const [brandsResponse, typesResponse] = await Promise.all([ + brandsPromise, + typesPromise, + ]); setBrands( brandsResponse.data.filter((brand) => brand && brand.name !== 'nan') @@ -116,15 +110,6 @@ export default function ListingsPage() { typesResponse.data.filter((type) => type && type.name !== 'nan') ); - const currentAuction = currentAuctionResponse.data; - - if (!currentAuction.id) { - setLoading(false); - return; - } - - setAuction(currentAuction); - const dateResponse = await fetchData({ endpoint: `/v1/auctions/${currentAuction.id}/day`, method: 'GET', @@ -160,7 +145,7 @@ export default function ListingsPage() { }; fetchAuctionAndVehicles(); - }, []); + }, [currentAuction]); const handleFetchVehicles = useCallback( debounce(async (url) => { @@ -180,7 +165,7 @@ export default function ListingsPage() { setLoading(false); } }, 500), - [auction?.id, currentPage] + [currentAuction, currentPage] ); const generateUrlFromFilters = (url) => { @@ -216,9 +201,9 @@ export default function ListingsPage() { if (hasNextPage) { handleFetchVehicles( generateUrlFromFilters( - `v1/auctions/${auction?.id}/days/${auctionDayId}/vehicles?page=${ - currentPage + 1 - }` + `v1/auctions/${ + currentAuction?.id + }/days/${auctionDayId}/vehicles?page=${currentPage + 1}` ) ); setCurrentPage((prev) => prev + 1); @@ -230,9 +215,9 @@ export default function ListingsPage() { if (hasPrevPage) { handleFetchVehicles( generateUrlFromFilters( - `v1/auctions/${auction?.id}/days/${auctionDayId}/vehicles?page=${ - currentPage - 1 - }` + `v1/auctions/${ + currentAuction?.id + }/days/${auctionDayId}/vehicles?page=${currentPage - 1}` ) ); setCurrentPage((prev) => prev + 1); @@ -241,10 +226,10 @@ export default function ListingsPage() { }; useEffect(() => { - if (auction && auctionDayId) { + if (currentAuction && auctionDayId) { handleFetchVehicles( generateUrlFromFilters( - `/v1/auctions/${auction?.id}/days/${auctionDayId}/vehicles?page=${currentPage}` + `/v1/auctions/${currentAuction?.id}/days/${auctionDayId}/vehicles?page=${currentPage}` ) ); } @@ -258,8 +243,8 @@ export default function ListingsPage() { ]); if ( - !auction || - new Date().getTime() < new Date(auction?.start_date).getTime() + !currentAuction || + new Date().getTime() < new Date(currentAuction?.start_date).getTime() ) { return (
@@ -283,13 +268,24 @@ export default function ListingsPage() { ); } - const startTime = convertSGTToLocalDateObject(auction.start_time); - const endTime = convertSGTToLocalDateObject(auction.end_time); - const currentDate = new Date(); - if (currentDate.getTime() > endTime.getTime()) { - startTime.setDate(startTime.getDate() + 1); - endTime.setDate(endTime.getDate() + 1); - } + const startDateTimeString = `${getCurrentDateInSingapore().toLocaleDateString( + 'fr-CA', + { + year: 'numeric', + month: '2-digit', + day: '2-digit', + } + )}T${currentAuction?.start_time}+08:00`; + const endDateTimeString = `${getCurrentDateInSingapore().toLocaleDateString( + 'fr-CA', + { + year: 'numeric', + month: '2-digit', + day: '2-digit', + } + )}T${currentAuction?.end_time}+08:00`; + const startDateTime = new Date(startDateTimeString); + const endDateTime = new Date(endDateTimeString); return (
@@ -300,21 +296,21 @@ export default function ListingsPage() {

{`Auction Listings (${formatFlexibleDateRange( - new Date(auction?.start_date), - new Date(auction?.end_date) + new Date(currentAuction?.start_date), + new Date(currentAuction?.end_date) )})`}

- {`Items for ${formatListingsTodayDate(currentDate)}`} + {`Items for ${formatListingsTodayDate(new Date())}`}

diff --git a/frontend/src/pages/VehicleDetailsPage.jsx b/frontend/src/pages/VehicleDetailsPage.jsx index 2fedaff..0ce4c94 100644 --- a/frontend/src/pages/VehicleDetailsPage.jsx +++ b/frontend/src/pages/VehicleDetailsPage.jsx @@ -6,7 +6,7 @@ import StarsOutlinedIcon from '@mui/icons-material/StarsOutlined'; import { useNavigate, useParams, ScrollRestoration } from 'react-router-dom'; import NavBar from '../components/navBars/NavBar'; import Footer from '../components/footers/Footer'; -import VehicleDetailsCountdown from '../components/timers/VehicleDetailsCountdown'; +import CurrentAuctionCountdown from '../components/timers/CurrentAuctionCountdown'; import ImageSlideshow from '../components/imageSlideshows/ImageSlideshow'; import truck from '../assets/truck.png'; import truck2 from '../assets/truck2.png'; @@ -83,7 +83,7 @@ export default function VehicleDetailsPage() { Back to available items

- { + const fetchCurrentAuction = async () => { + const response = await fetchData({ + endpoint: '/v1/auctions/current', + method: 'GET', + }); + + setCurrentAuction(response.data); + }; + + fetchCurrentAuction(); + }, []); + + const value = useMemo(() => ({ currentAuction }), [currentAuction]); + + return ( + + {children} + + ); +} + +export const useCurrentAuction = () => useContext(CurrentAuctionContext); diff --git a/frontend/src/routers/Routers.jsx b/frontend/src/routers/Routers.jsx index 0db0911..1d1ea4f 100644 --- a/frontend/src/routers/Routers.jsx +++ b/frontend/src/routers/Routers.jsx @@ -12,6 +12,7 @@ import BidderEmailVerifiedPage from '../pages/auth/bidders/BidderEmailVerifiedPa import VehicleDetailsPage from '../pages/VehicleDetailsPage'; import ProfilePage from '../pages/ProfilePage'; import NotFoundPage from '../pages/NotFoundPage'; +import BidHistoryPage from '../pages/BidHistoryPage'; const router = createBrowserRouter([ { path: '/', element: }, @@ -25,7 +26,7 @@ const router = createBrowserRouter([ { path: '/register/email', element: }, { path: '/register/verified', element: }, { path: '/profile', element: }, - { path: '/history', element:
History!!!!!
}, + { path: '/history', element: }, { path: '*', element: }, ]); diff --git a/frontend/src/utils/dateTime.js b/frontend/src/utils/dateTime.js index 3629b9f..151497e 100644 --- a/frontend/src/utils/dateTime.js +++ b/frontend/src/utils/dateTime.js @@ -84,16 +84,10 @@ function getTimeToEndDate(endDate) { } function getCurrentDateInSingapore() { + const offset = 8; const now = new Date(); - - // Convert to Singapore time by adding 8 hours to UTC (Singapore is UTC+8) - const singaporeOffset = 8 * 60; - const utcOffset = now.getTimezoneOffset(); - const singaporeTime = new Date( - now.getTime() + (singaporeOffset + utcOffset) * 60000 - ); - - singaporeTime.setHours(0, 0, 0, 0); + const utc = now.getTime() + now.getTimezoneOffset() * 60000; + const singaporeTime = new Date(utc + 3600000 * offset); return singaporeTime; } From d757023993f32ad806a3f006e0247d130a6f1a85 Mon Sep 17 00:00:00 2001 From: Lance Tan Date: Fri, 12 Apr 2024 20:02:44 -0700 Subject: [PATCH 2/2] Remove unused imports --- frontend/src/pages/HomePage.jsx | 1 - frontend/src/pages/ListingsPage.jsx | 1 - 2 files changed, 2 deletions(-) diff --git a/frontend/src/pages/HomePage.jsx b/frontend/src/pages/HomePage.jsx index ea1bb2e..024e627 100644 --- a/frontend/src/pages/HomePage.jsx +++ b/frontend/src/pages/HomePage.jsx @@ -15,7 +15,6 @@ import RegisterForAuctionButton from '../components/buttons/RegisterForAuctionBu import DefaultUpcomingAuctionCard from '../components/cards/DefaultUpcomingAuctionCard'; import { useAuth } from '../providers/AuthProvider'; import useAxios from '../hooks/useAxios'; -import sortAuctions from '../utils/auctionUtils'; import AwaitingApprovalButton from '../components/buttons/AwaitingApprovalButton'; import StartBiddingButton from '../components/buttons/StartBiddingButton'; import { useUser } from '../providers/UserProvider'; diff --git a/frontend/src/pages/ListingsPage.jsx b/frontend/src/pages/ListingsPage.jsx index 5df64ab..5f35bee 100644 --- a/frontend/src/pages/ListingsPage.jsx +++ b/frontend/src/pages/ListingsPage.jsx @@ -16,7 +16,6 @@ import useAxios from '../hooks/useAxios'; import { formatListingsTodayDate, formatFlexibleDateRange, - convertSGTToLocalDateObject, getCurrentDateInSingapore, } from '../utils/dateTime'; import { useCurrentAuction } from '../providers/CurrentAuctionProvider';