aboutsummaryrefslogtreecommitdiff
path: root/client/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/components')
-rw-r--r--client/src/components/BidsPage.jsx182
-rw-r--r--client/src/components/MarketDataPage.jsx320
-rw-r--r--client/src/components/SubmitBidPage.jsx188
3 files changed, 690 insertions, 0 deletions
diff --git a/client/src/components/BidsPage.jsx b/client/src/components/BidsPage.jsx
new file mode 100644
index 0000000..e0c4128
--- /dev/null
+++ b/client/src/components/BidsPage.jsx
@@ -0,0 +1,182 @@
+import React, { useEffect, useState } from 'react';
+import { Table, Typography, Spin, Message, Card, Empty, Badge } from '@arco-design/web-react';
+import { IconArrowRise, IconArrowFall } from '@arco-design/web-react/icon';
+import '@arco-design/web-react/dist/css/arco.css';
+import API_BASE_URL from './config';
+
+const columns = (bids) => [
+ {
+ title: 'Timestamp',
+ dataIndex: 'timestamp',
+ sorter: (a, b) => new Date(a.timestamp) - new Date(b.timestamp),
+ render: (val) => new Date(val).toLocaleString(),
+ width: 180,
+ },
+ {
+ title: 'Quantity (MW)',
+ dataIndex: 'quantity',
+ sorter: (a, b) => a.quantity - b.quantity,
+ render: (val) => val.toFixed(2),
+ width: 180,
+ },
+ {
+ title: 'Price ($/MWh)',
+ dataIndex: 'price',
+ sorter: (a, b) => a.price - b.price,
+ render: (val) => `$${val.toFixed(2)}`,
+ width: 180,
+ },
+ {
+ title: 'Status',
+ dataIndex: 'status',
+ sorter: (a, b) => a.status.localeCompare(b.status),
+ filters: [
+ { text: 'Submitted', value: 'Submitted' },
+ { text: 'Success', value: 'Success' },
+ { text: 'Fail', value: 'Fail' },
+ ],
+ onFilter: (value, record) => record.status === value,
+ render: (val) => (
+ <Badge status={val === 'Success' ? 'success' : val === 'Fail' ? 'error' : 'processing'} text={val} />
+ ),
+ width: 180,
+ },
+ {
+ title: 'PnL',
+ dataIndex: 'pnl',
+ sorter: (a, b) => (a.pnl || 0) - (b.pnl || 0),
+ render: (val) => {
+ if (val === null) return 'N/A';
+ const isProfit = val >= 0;
+ const color = isProfit ? 'green' : 'red';
+ return (
+ <Typography.Text style={{ color, display: 'flex', alignItems: 'center', gap: 4 }}>
+ {isProfit ? <IconArrowRise /> : <IconArrowFall />}
+ {val.toFixed(2)}
+ </Typography.Text>
+ );
+ },
+ width: 180,
+ },
+ {
+ title: 'Market',
+ dataIndex: 'market',
+ sorter: (a, b) => a.market.localeCompare(b.market),
+ filters: [
+ { text: 'ISONE', value: 'ISONE' },
+ { text: 'NYISO', value: 'NYISO' },
+ { text: 'MISO', value: 'MISO' },
+ ],
+ onFilter: (value, record) => record.market === value,
+ render: (val) => val,
+ width: 180,
+ },
+];
+
+function BidsPage() {
+ const [bids, setBids] = useState([]);
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ fetch(`${API_BASE_URL}/bids/`)
+ .then((res) => {
+ if (!res.ok) throw new Error('Failed to fetch bids');
+ return res.json();
+ })
+ .then((json) => setBids(json))
+ .catch((err) => {
+ console.error(err);
+ Message.error('Failed to load bids.');
+ })
+ .finally(() => setLoading(false));
+ }, []);
+
+ return (
+ <div style={{ padding: 20, backgroundColor: 'var(--color-fill-2)', minHeight: 'calc(100vh - 150px)', animation: 'fadeSlideIn 0.6s ease' }}>
+ <Typography.Title heading={3} style={{ textAlign: 'center', marginBottom: 20 }}>
+ Your Submitted Bids
+ </Typography.Title>
+
+ <Card
+ style={{
+ marginTop: 16,
+ borderRadius: 16,
+ boxShadow: '0 8px 24px rgba(0,0,0,0.08)',
+ background: 'linear-gradient(135deg, rgba(255,255,255,0.95) 0%, rgba(245,245,245,0.95) 100%)',
+ backdropFilter: 'blur(8px)',
+ overflow: 'hidden',
+ padding: 20
+ }}
+ >
+ {loading ? (
+ <div style={{ textAlign: 'center', padding: 80 }}>
+ <Spin />
+ </div>
+ ) : bids.length === 0 ? (
+ <div style={{ textAlign: 'center', padding: 80 }}>
+ <Empty description="No bids found yet." />
+ </div>
+ ) : (
+ <div className="responsive-table-wrapper">
+ <Table
+ columns={columns(bids)}
+ data={bids}
+ rowKey="id"
+ pagination={{
+ pageSize: 50,
+ sizeCanChange: true,
+ showTotal: true,
+ }}
+ scroll={{ x: true }}
+ border
+ style={{ transition: 'opacity 0.5s ease-in-out' }}
+ rowClassName={() => 'table-row-hover'}
+ />
+ </div>
+ )}
+ </Card>
+
+ <style>{`
+ @keyframes fadeSlideIn {
+ 0% {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ 100% {
+ opacity: 1;
+ transform: translateY(0);
+ }
+ }
+ .table-row-hover:hover {
+ background-color: var(--color-fill-3);
+ transition: background-color 0.3s ease;
+ }
+
+ responsive-table-wrapper {
+ width: 100%;
+ overflow-x: auto;
+ }
+
+ /* Prevent wrapping in header and cells */
+ .responsive-table-wrapper table th,
+ .responsive-table-wrapper table td {
+ white-space: nowrap;
+ }
+
+ /* Optional: smaller fonts on mobile */
+ @media (max-width: 768px) {
+ .responsive-table-wrapper table {
+ font-size: 12px;
+ }
+ .responsive-table-wrapper th,
+ .responsive-table-wrapper td {
+ padding: 8px;
+ }
+ }
+
+ `}</style>
+ </div>
+ );
+}
+
+export default BidsPage;
diff --git a/client/src/components/MarketDataPage.jsx b/client/src/components/MarketDataPage.jsx
new file mode 100644
index 0000000..5e028f8
--- /dev/null
+++ b/client/src/components/MarketDataPage.jsx
@@ -0,0 +1,320 @@
+import React, { useEffect, useState, useContext, useRef, useMemo } from 'react';
+import { Typography, Spin, Message, Card, Grid } from '@arco-design/web-react';
+import '@arco-design/web-react/dist/css/arco.css';
+import { initVChartArcoTheme } from '@visactor/vchart-arco-theme';
+import { VChart } from '@visactor/react-vchart';
+import API_BASE_URL from './config';
+import { MarketContext, MARKET_FULL_NAMES } from './App';
+
+// TODO:
+//
+// - [ ] Cancel Promise if market changes
+// - [ ] Fix Data Zoom being reset
+
+const { Row, Col } = Grid;
+
+function TimeAgo({ timestamp, prefix }) {
+ const [now, setNow] = useState(new Date());
+
+ useEffect(() => {
+ const timer = setInterval(() => setNow(new Date()), 1000);
+ return () => clearInterval(timer);
+ }, []);
+
+ const formatTimeAgo = () => {
+ if (!timestamp) return '';
+ const seconds = Math.floor((now - timestamp) / 1000);
+ if (seconds < 60) return `${seconds} sec ago`;
+ const minutes = Math.floor(seconds / 60);
+ if (minutes < 60) return `${minutes} min ago`;
+ const hours = Math.floor(minutes / 60);
+ return `${hours} hr ago`;
+ };
+
+ return (
+ <Typography.Text type="secondary" style={{ fontSize: 12, display: 'block', marginTop: 8 }}>
+ {prefix}: {formatTimeAgo()}
+ </Typography.Text>
+ );
+}
+
+function MarketDataPage() {
+ const { selectedMarket } = useContext(MarketContext);
+ const fiveMinChartRef = useRef(null);
+ const dayAheadChartRef = useRef(null);
+ const [fiveMinZoom, setFiveMinZoom] = useState({ start: 0.9, end: 1 });
+ const [dayAheadZoom, setDayAheadZoom] = useState({ start: 0.05, end: 1 });
+ const [fiveMinData, setFiveMinData] = useState([]);
+ const [dayAheadData, setDayAheadData] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [fiveMinLastUpdated, setFiveMinLastUpdated] = useState(null);
+ const [dayAheadLastUpdated, setDayAheadLastUpdated] = useState(null);
+ const [, setNow] = useState(new Date());
+
+ const handleZoomChange = (e, title) => {
+ if (!e?.start || !e?.end) return;
+ if (title === 'fiveMin') {
+ setFiveMinZoom({ start: e.start, end: e.end });
+ } else {
+ setDayAheadZoom({ start: e.start, end: e.end });
+ }
+ };
+
+ useEffect(() => {
+ const fetchFiveMinData = async () => {
+ try {
+ const res = await fetch(`${API_BASE_URL}/market/real-time?market=${selectedMarket}`);
+ if (!res.ok) throw new Error('Failed to fetch real-time data');
+ const json = await res.json();
+ setFiveMinData(json);
+ setFiveMinLastUpdated(new Date());
+ } catch (err) {
+ console.error(err);
+ Message.error('Failed to load real-time market data.');
+ }
+ };
+
+ const fetchDayAheadData = async () => {
+ try {
+ const res = await fetch(`${API_BASE_URL}/market/day-ahead?market=${selectedMarket}`);
+ if (!res.ok) throw new Error('Failed to fetch day-ahead data');
+ const json = await res.json();
+ setDayAheadData(json);
+ setDayAheadLastUpdated(new Date());
+ } catch (err) {
+ console.error(err);
+ Message.error('Failed to load day-ahead market data.');
+ }
+ };
+
+ const initialize = async () => {
+ setLoading(true);
+ await Promise.all([fetchFiveMinData(), fetchDayAheadData()]);
+ setLoading(false);
+
+ initVChartArcoTheme({
+ defaultMode: 'light',
+ isWatchingMode: true
+ });
+ };
+
+ initialize();
+
+ const fiveMinInterval = setInterval(fetchFiveMinData, 5 * 60 * 1000);
+ const dayAheadInterval = setInterval(fetchDayAheadData, 60 * 60 * 1000);
+ const timer = setInterval(() => setNow(new Date()), 1000);
+
+ return () => {
+ clearInterval(fiveMinInterval);
+ clearInterval(dayAheadInterval);
+ clearInterval(timer);
+ };
+ }, [selectedMarket]);
+
+ const fiveMinSpec = useMemo(() => ({
+ type: 'line',
+ data: {
+ values: fiveMinData.map(d => ({
+ timestamp: new Date(d.timestamp).toLocaleString(),
+ LMP: d.lmp,
+ Energy: d.energy,
+ Congestion: d.congestion,
+ Loss: d.loss
+ })),
+ transforms: [{
+ type: 'fold',
+ options: { key: 'name', value: 'value', fields: ['LMP', 'Energy', 'Congestion', 'Loss'] }
+ }]
+ },
+ xField: 'timestamp',
+ yField: 'value',
+ seriesField: 'name',
+ smooth: true,
+ legend: { position: 'top' },
+ tooltip: {
+ formatter: (datum) => ({
+ name: datum.name,
+ value: datum.value.toFixed(2)
+ })
+ },
+ dataZoom: [{
+ orient: 'bottom',
+ height: 20,
+ start: fiveMinZoom.start,
+ end: fiveMinZoom.end
+ }]
+ }), [fiveMinData, fiveMinZoom]); // <== DEPENDENCIES
+
+ const dayAheadSpec = useMemo(() => ({
+ type: 'line',
+ data: {
+ values: dayAheadData.map(d => ({
+ timestamp: new Date(d.timestamp).toLocaleString(),
+ LMP: d.lmp,
+ Energy: d.energy,
+ Congestion: d.congestion,
+ Loss: d.loss
+ })),
+ transforms: [{
+ type: 'fold',
+ options: { key: 'name', value: 'value', fields: ['LMP', 'Energy', 'Congestion', 'Loss'] }
+ }]
+ },
+ xField: 'timestamp',
+ yField: 'value',
+ seriesField: 'name',
+ smooth: true,
+ legend: { position: 'top' },
+ tooltip: {
+ formatter: (datum) => ({
+ name: datum.name,
+ value: datum.value.toFixed(2)
+ })
+ },
+ dataZoom: [{
+ orient: 'bottom',
+ height: 20,
+ start: dayAheadZoom.start,
+ end: dayAheadZoom.end
+ }]
+ }), [dayAheadData, dayAheadZoom]);
+
+ const computeKPIs = (data) => {
+ if (!data.length) return { avgLmp: 0, totalEnergy: 0, maxCongestion: 0, avgLoss: 0 };
+ const avgLmp = data.reduce((sum, d) => sum + d.lmp, 0) / data.length;
+ const totalEnergy = data.reduce((sum, d) => sum + d.energy, 0);
+ const maxCongestion = Math.max(...data.map(d => d.congestion));
+ const avgLoss = data.reduce((sum, d) => sum + d.loss, 0) / data.length;
+ return { avgLmp, totalEnergy, maxCongestion, avgLoss };
+ };
+
+ const fiveMinKPIs = computeKPIs(fiveMinData);
+ const dayAheadKPIs = computeKPIs(dayAheadData);
+
+ return (
+ <div className="page-container">
+ <Typography.Title heading={3} style={{ textAlign: 'center', marginBottom: 20 }} className="market-page-title" >
+ Market Data for {MARKET_FULL_NAMES[selectedMarket]}
+ </Typography.Title>
+
+ {loading ? (
+ <Spin style={{ display: 'block', margin: '80px auto' }} />
+ ) : (
+ <>
+ <Row gutter={24} style={{ marginBottom: 30 }}>
+ {[
+ { label: 'Avg Real-Time LMP ($/MWh)', value: fiveMinKPIs.avgLmp },
+ { label: 'Total Real-Time Energy (MWh)', value: fiveMinKPIs.totalEnergy },
+ { label: 'Max Real-Time Congestion ($)', value: fiveMinKPIs.maxCongestion },
+ { label: 'Avg Real-Time Loss ($)', value: fiveMinKPIs.avgLoss }
+ ].map((item, idx) => (
+ <Col xs={12} md={6} key={idx} style={{ marginBottom: 16 }}>
+ <Card
+ hoverable
+ style={{
+ textAlign: 'center',
+ borderRadius: 12,
+ boxShadow: '0 4px 12px rgba(0,0,0,0.05)'
+ }}
+ title={item.label}
+ >
+ <Typography.Title heading={5}>${item.value.toFixed(2)}</Typography.Title>
+ </Card>
+ </Col>
+ ))}
+ </Row>
+
+ <Row gutter={24} style={{ marginBottom: 30 }}>
+ {[
+ { label: 'Avg Day-Ahead LMP ($/MWh)', value: dayAheadKPIs.avgLmp },
+ { label: 'Total Day-Ahead Energy (MWh)', value: dayAheadKPIs.totalEnergy },
+ { label: 'Max Day-Ahead Congestion ($)', value: dayAheadKPIs.maxCongestion },
+ { label: 'Avg Day-Ahead Loss ($)', value: dayAheadKPIs.avgLoss }
+ ].map((item, idx) => (
+ <Col xs={12} md={6} key={idx} style={{ marginBottom: 16 }}>
+ <Card
+ hoverable
+ style={{
+ textAlign: 'center',
+ borderRadius: 12,
+ boxShadow: '0 4px 12px rgba(0,0,0,0.05)'
+ }}
+ title={item.label}
+ >
+ <Typography.Title heading={5}>${item.value.toFixed(2)}</Typography.Title>
+ </Card>
+ </Col>
+ ))}
+ </Row>
+
+ <Row gutter={24}>
+ <Col xs={24} md={12}>
+ <Card title="Real-Time Market Data" extra={<TimeAgo timestamp={fiveMinLastUpdated} prefix="Updated" />} style={{ borderRadius: 12, marginBottom: 24 }}>
+ <div style={{ padding: 10 }}>
+ <VChart
+ spec={fiveMinSpec}
+ ref={fiveMinChartRef}
+ onDataZoom={(e) => handleZoomChange(e.detail, 'fiveMin')}
+ />
+ </div>
+ </Card>
+ </Col>
+ <Col xs={24} md={12}>
+ <Card title="Day-Ahead Market Data" extra={<TimeAgo timestamp={dayAheadLastUpdated} prefix="Updated" />} style={{ borderRadius: 12, marginBottom: 24 }}>
+ <div style={{ padding: 10 }}>
+ <VChart
+ spec={dayAheadSpec}
+ ref={dayAheadChartRef}
+ onDataZoom={(e) => handleZoomChange(e.detail, 'dayAhead')}
+ />
+ </div>
+ </Card>
+ </Col>
+ </Row>
+ </>
+ )}
+
+ <style>{`
+ @keyframes fadeIn {
+ from { opacity: 0; transform: translateY(10px); }
+ to { opacity: 1; transform: translateY(0); }
+ }
+
+ .arco-card-header-title {
+ white-space: normal !important; /* allow wrapping */
+ word-break: break-word;
+ }
+
+ /* Optional: smaller text on tiny devices */
+ @media (max-width: 768px) {
+ .arco-card-header-title {
+ font-size: 14px;
+ }
+ }
+
+ .market-page-title {
+ white-space: normal;
+ word-break: keep-all;
+ overflow-wrap: break-word;
+ text-align: center;
+ }
+
+ .page-container {
+ padding: 15px;
+ background: var(--color-fill-2);
+ animation: fadeIn 0.5s ease;
+ overflow-x: hidden; /* Also good practice */
+ }
+
+ /* Responsive: remove padding on small screens */
+ @media (max-width: 768px) {
+ .page-container {
+ padding: 0;
+ }
+ }
+ `}</style>
+ </div>
+ );
+}
+
+export default MarketDataPage;
diff --git a/client/src/components/SubmitBidPage.jsx b/client/src/components/SubmitBidPage.jsx
new file mode 100644
index 0000000..7737c20
--- /dev/null
+++ b/client/src/components/SubmitBidPage.jsx
@@ -0,0 +1,188 @@
+import React, { useState, useEffect, useContext } from 'react';
+import { Form, InputNumber, DatePicker, Button, Message, Typography, Card } from '@arco-design/web-react';
+import '@arco-design/web-react/dist/css/arco.css';
+import dayjs from 'dayjs';
+import utc from 'dayjs/plugin/utc';
+import timezone from 'dayjs/plugin/timezone';
+import API_BASE_URL from './config';
+import { MarketContext, MARKET_FULL_NAMES } from './App';
+
+dayjs.extend(utc);
+dayjs.extend(timezone);
+
+const MARKET_TIMEZONES = {
+ ISONE: 'America/New_York',
+ NYISO: 'America/New_York',
+ MISO: 'America/Chicago'
+};
+
+function SubmitBidPage() {
+ const { selectedMarket } = useContext(MarketContext);
+ const [form] = Form.useForm();
+ const [loading, setLoading] = useState(false);
+ const [localNow, setLocalNow] = useState('');
+ const [marketNow, setMarketNow] = useState('');
+
+ useEffect(() => {
+ const updateTime = () => {
+ const now = dayjs();
+ const localFormatted = now.format('dddd, MMMM D, h:mm A');
+ const marketTz = MARKET_TIMEZONES[selectedMarket] || 'America/New_York';
+ const marketTime = now.tz(marketTz);
+ const marketFormatted = marketTime.format('dddd, MMMM D, h:mm A');
+ setLocalNow(localFormatted);
+ setMarketNow(marketFormatted);
+ };
+
+ updateTime();
+ const interval = setInterval(updateTime, 1000);
+ return () => clearInterval(interval);
+ }, [selectedMarket]);
+
+ const handleSubmit = async (values) => {
+ setLoading(true);
+ try {
+ const picked = dayjs(values.timestamp);
+ const adjusted = picked.minute(0).second(0).millisecond(0);
+
+ const marketTz = MARKET_TIMEZONES[selectedMarket] || 'America/New_York';
+ const timestampInMarketTz = adjusted.tz(marketTz).format();
+
+ const payload = {
+ timestamp: timestampInMarketTz,
+ quantity: values.quantity,
+ price: values.price,
+ market: selectedMarket,
+ user_id: 1
+ };
+
+ const res = await fetch(`${API_BASE_URL}/bids/`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(payload)
+ });
+
+ if (!res.ok) {
+ const error = await res.json();
+ throw new Error(error.detail || 'Failed to submit bid');
+ }
+
+ Message.success('Bid submitted successfully!');
+ form.resetFields();
+ } catch (err) {
+ console.error(err);
+ Message.error(err.message || 'Submission failed');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+ <div style={{ padding: 20, backgroundColor: 'var(--color-fill-2)', minHeight: 'calc(100vh - 150px)', animation: 'fadeSlideIn 0.6s ease' }}>
+ <Typography.Title heading={3} style={{ textAlign: 'center', marginBottom: 20 }}>
+ Submit New Bid
+ </Typography.Title>
+
+ <Card
+ style={{
+ marginTop: 16,
+ borderRadius: 16,
+ boxShadow: '0 8px 24px rgba(0,0,0,0.08)',
+ background: 'linear-gradient(135deg, rgba(255,255,255,0.95) 0%, rgba(245,245,245,0.95) 100%)',
+ backdropFilter: 'blur(8px)',
+ overflow: 'hidden',
+ padding: 32
+ }}
+ >
+ <div style={{ marginBottom: 32, padding: 16, border: '1px solid var(--color-border)', borderRadius: 8, background: 'var(--color-fill-1)' }}>
+ <Typography.Paragraph style={{ marginBottom: 8 }}>
+ <strong>Current Local Time:</strong> {localNow}
+ </Typography.Paragraph>
+ <Typography.Paragraph>
+ <strong>Current {MARKET_FULL_NAMES[selectedMarket]} Time:</strong> {marketNow}
+ </Typography.Paragraph>
+ </div>
+
+ <Form
+ form={form}
+ layout="vertical"
+ onSubmit={handleSubmit}
+ initialValues={{ quantity: 0, price: 0 }}
+ style={{ maxWidth: 600, margin: '0 auto' }}
+ >
+ <Form.Item
+ label="Bid Date and Hour"
+ field="timestamp"
+ rules={[{ required: true, message: 'Please select bid date and hour' }]}
+ >
+ <DatePicker
+ showTime={{
+ format: 'h A',
+ defaultValue: dayjs().hour(0).minute(0),
+ disabledMinutes: () => Array.from({ length: 60 }, (_, i) => i !== 0),
+ step: { minute: 60 }
+ }}
+ style={{ width: '100%' }}
+ format="YYYY-MM-DD HH:00"
+ placeholder="Select date and hour"
+ timezone={MARKET_TIMEZONES[selectedMarket]}
+ />
+ </Form.Item>
+
+ <Form.Item
+ label="Quantity (MW)"
+ field="quantity"
+ rules={[
+ { required: true, message: 'Please enter quantity' },
+ { type: 'number', min: 0.01, message: 'Quantity must be > 0' }
+ ]}
+ >
+ <InputNumber style={{ width: '100%' }} placeholder="Enter quantity" />
+ </Form.Item>
+
+ <Form.Item
+ label="Price ($/MWh)"
+ field="price"
+ rules={[
+ { required: true, message: 'Please enter price' },
+ { type: 'number', min: 0, message: 'Price must be >= 0' }
+ ]}
+ >
+ <InputNumber style={{ width: '100%' }} placeholder="Enter price" />
+ </Form.Item>
+
+ <Form.Item>
+ <Button
+ type="primary"
+ htmlType="submit"
+ loading={loading}
+ style={{
+ width: '100%',
+ transition: 'transform 0.2s ease',
+ }}
+ onMouseEnter={(e) => (e.currentTarget.style.transform = 'scale(1.02)')}
+ onMouseLeave={(e) => (e.currentTarget.style.transform = 'scale(1)')}
+ >
+ Submit Bid
+ </Button>
+ </Form.Item>
+ </Form>
+ </Card>
+
+ <style>{`
+ @keyframes fadeSlideIn {
+ 0% {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ 100% {
+ opacity: 1;
+ transform: translateY(0);
+ }
+ }
+ `}</style>
+ </div>
+ );
+}
+
+export default SubmitBidPage;