aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNavan Chauhan <navanchauhan@gmail.com>2025-04-27 21:30:56 -0600
committerNavan Chauhan <navanchauhan@gmail.com>2025-04-27 21:30:56 -0600
commit5244aff586df93de2b9973de9c9046e4ad6163bd (patch)
tree58f4a96f44ba2e724fcf11d2cebc555989646e67
parentf52782c4dccc3f2330d9dcd14ba4e3cf0522f8cf (diff)
improve styling
-rw-r--r--client/src/App.js6
-rw-r--r--client/src/BidsPage.jsx99
-rw-r--r--client/src/MarketDataPage.jsx385
-rw-r--r--client/src/SubmitBidPage.jsx76
4 files changed, 310 insertions, 256 deletions
diff --git a/client/src/App.js b/client/src/App.js
index a83b628..7d84cb0 100644
--- a/client/src/App.js
+++ b/client/src/App.js
@@ -93,13 +93,17 @@ function App() {
</Menu.Item>
</Menu>
</div>
-
+ <div style={{ backgroundColor: 'var(--color-fill-2)', minHeight: '80vh', display: 'flex', flexDirection: 'column' }}>
<Routes>
<Route path="/" element={<Navigate to="/market-data" replace />} />
<Route path="/market-data" element={<MarketDataPage />} />
<Route path="/submit-bid" element={<SubmitBidPage />} />
<Route path="/bids" element={<BidsPage />} />
</Routes>
+ </div>
+ <footer style={{ flexShrink: 0, marginTop: 20, textAlign: 'center', fontSize: 14, color: 'var(--color-text-3)', paddingBottom: 20 }}>
+ © {new Date().getFullYear()} PolyEnergy. All rights reserved.
+ </footer>
</div>
</MarketContext.Provider>
);
diff --git a/client/src/BidsPage.jsx b/client/src/BidsPage.jsx
index 3a40f41..fad3989 100644
--- a/client/src/BidsPage.jsx
+++ b/client/src/BidsPage.jsx
@@ -1,49 +1,70 @@
import React, { useEffect, useState } from 'react';
-import { Table, Typography, Spin, Message, Card } from '@arco-design/web-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 = [
+const columns = (bids) => [
{
title: 'Timestamp',
dataIndex: 'timestamp',
+ sorter: (a, b) => new Date(a.timestamp) - new Date(b.timestamp),
render: (val) => new Date(val).toLocaleString(),
},
{
title: 'Quantity (MW)',
dataIndex: 'quantity',
+ sorter: (a, b) => a.quantity - b.quantity,
render: (val) => val.toFixed(2),
},
{
title: 'Price ($/MWh)',
dataIndex: 'price',
+ sorter: (a, b) => a.price - b.price,
render: (val) => `$${val.toFixed(2)}`,
},
{
title: 'Status',
dataIndex: 'status',
- render: (val) => {
- let color = 'gray';
- if (val === 'Success') color = 'green';
- else if (val === 'Fail') color = 'red';
- else if (val === 'Submitted') color = 'blue';
- return <Typography.Text style={{ color }}>{val}</Typography.Text>;
- }
+ 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} />
+ ),
},
{
title: 'PnL',
dataIndex: 'pnl',
+ sorter: (a, b) => (a.pnl || 0) - (b.pnl || 0),
render: (val) => {
if (val === null) return 'N/A';
- const color = val >= 0 ? 'green' : 'red';
- return <Typography.Text style={{ color }}>{val.toFixed(2)}</Typography.Text>;
- }
+ 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>
+ );
+ },
},
{
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,
- }
+ },
];
function BidsPage() {
@@ -65,24 +86,64 @@ function BidsPage() {
}, []);
return (
- <div style={{ padding: 20, backgroundColor: 'var(--color-fill-2)' }}>
- <Typography.Title heading={4}>Your Submitted Bids</Typography.Title>
- <Card style={{ marginTop: 16 }}>
+ <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 ? (
- <Spin />
+ <div style={{ textAlign: 'center', padding: 80 }}>
+ <Spin />
+ </div>
+ ) : bids.length === 0 ? (
+ <div style={{ textAlign: 'center', padding: 80 }}>
+ <Empty description="No bids found yet." />
+ </div>
) : (
<Table
- columns={columns}
+ columns={columns(bids)}
data={bids}
rowKey="id"
pagination={{
- pageSize: 10,
+ pageSize: 50,
sizeCanChange: true,
showTotal: true,
}}
+ scroll={{ x: true }}
+ border
+ style={{ transition: 'opacity 0.5s ease-in-out' }}
+ rowClassName={() => 'table-row-hover'}
/>
)}
</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;
+ }
+ `}</style>
</div>
);
}
diff --git a/client/src/MarketDataPage.jsx b/client/src/MarketDataPage.jsx
index 52ca47c..4da1969 100644
--- a/client/src/MarketDataPage.jsx
+++ b/client/src/MarketDataPage.jsx
@@ -32,254 +32,213 @@ function TimeAgo({ timestamp, prefix }) {
};
return (
- <Typography.Text type="secondary" style={{ display: 'block' }}>
+ <Typography.Text type="secondary" style={{ fontSize: 12, display: 'block', marginTop: 8 }}>
{prefix}: {formatTimeAgo()}
</Typography.Text>
);
}
-
function MarketDataPage() {
const { selectedMarket } = useContext(MarketContext);
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 [now, setNow] = useState(new Date());
-
- 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 [dayAheadData, setDayAheadData] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [fiveMinLastUpdated, setFiveMinLastUpdated] = useState(null);
+ const [dayAheadLastUpdated, setDayAheadLastUpdated] = useState(null);
+ const [now, setNow] = useState(new Date());
- 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.');
- }
- };
+ 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 initialize = async () => {
- setLoading(true);
- await Promise.all([fetchFiveMinData(), fetchDayAheadData()]);
- setLoading(false);
+ 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.');
+ }
+ };
- initVChartArcoTheme({
- defaultMode: 'light',
- isWatchingMode: true
- });
- };
+ const initialize = async () => {
+ setLoading(true);
+ await Promise.all([fetchFiveMinData(), fetchDayAheadData()]);
+ setLoading(false);
- initialize();
+ initVChartArcoTheme({
+ defaultMode: 'light',
+ isWatchingMode: true
+ });
+ };
- const fiveMinInterval = setInterval(fetchFiveMinData, 5 * 60 * 1000); // 5 minutes
- const dayAheadInterval = setInterval(fetchDayAheadData, 60 * 60 * 1000); // 1 hour
- const timer = setInterval(() => setNow(new Date()), 1000);
+ initialize();
- return () => {
- clearInterval(fiveMinInterval);
- clearInterval(dayAheadInterval);
- clearInterval(timer);
- };
- }, [selectedMarket]);
+ const fiveMinInterval = setInterval(fetchFiveMinData, 5 * 60 * 1000);
+ const dayAheadInterval = setInterval(fetchDayAheadData, 60 * 60 * 1000);
+ const timer = setInterval(() => setNow(new Date()), 1000);
- const getChartSpec = (data, title, zoom) => ({
- type: 'line',
- data: {
- values: data.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: zoom?.start ?? (title === 'fiveMin' ? 0.9 : 0.05),
- end: zoom?.end ?? 1,
- onChange: (e) => {
- if (title === 'fiveMin') {
- setFiveMinZoom({ start: e.start, end: e.end });
- } else {
- setDayAheadZoom({ start: e.start, end: e.end });
- }
- }
+ return () => {
+ clearInterval(fiveMinInterval);
+ clearInterval(dayAheadInterval);
+ clearInterval(timer);
+ };
+ }, [selectedMarket]);
+
+ const getChartSpec = (data, title, zoom) => ({
+ type: 'line',
+ data: {
+ values: data.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: zoom?.start ?? (title === 'fiveMin' ? 0.9 : 0.05),
+ end: zoom?.end ?? 1,
+ onChange: (e) => {
+ if (title === 'fiveMin') {
+ setFiveMinZoom({ start: e.start, end: e.end });
+ } else {
+ setDayAheadZoom({ start: e.start, end: e.end });
}
- ]
- });
-
+ }
+ }]
+ });
const computeKPIs = (data) => {
- if (!data.length) return {
- avgLmp: 0,
- totalEnergy: 0,
- maxCongestion: 0,
- avgLoss: 0
- };
-
+ 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
- };
+ return { avgLmp, totalEnergy, maxCongestion, avgLoss };
};
-
const fiveMinKPIs = computeKPIs(fiveMinData);
const dayAheadKPIs = computeKPIs(dayAheadData);
- const formatTimeAgo = (timestamp) => {
- 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 (
- <div style={{ padding: 20, backgroundColor: 'var(--color-fill-2)' }}>
- <Typography.Title heading={4}>Market Data for {MARKET_FULL_NAMES[selectedMarket]}</Typography.Title>
-
+ <div style={{ padding: 20, background: 'var(--color-fill-2)', animation: 'fadeIn 0.5s ease' }}>
+ <Typography.Title heading={3} style={{ textAlign: 'center', marginBottom: 20 }}>
+ Market Data for {MARKET_FULL_NAMES[selectedMarket]}
+ </Typography.Title>
{loading ? (
- <Spin />
+ <Spin style={{ display: 'block', margin: '80px auto' }} />
) : (
<>
- <TimeAgo timestamp={fiveMinLastUpdated} prefix="Real-Time Data Last Updated" />
- <TimeAgo timestamp={dayAheadLastUpdated} prefix="Day-Ahead Data Last Updated" />
- <Row gutter={24} style={{ marginTop: 20, marginBottom: 20 }}>
- <Col xs={12} md={6} style={{ marginTop: 10}}>
- <Card style={{ textAlign: 'center' }} title="Avg Real-Time LMP ($/MWh)">
- <Typography.Title heading={5}>${fiveMinKPIs.avgLmp.toFixed(2)}</Typography.Title>
- </Card>
- </Col>
- <Col xs={12} md={6} style={{ marginTop: 10}}>
- <Card style={{ textAlign: 'center' }} title="Total Real-Time Energy (MWh)">
- <Typography.Title heading={5}>{fiveMinKPIs.totalEnergy.toFixed(2)}</Typography.Title>
- </Card>
- </Col>
- <Col xs={12} md={6} style={{ marginTop: 10}}>
- <Card style={{ textAlign: 'center' }} title="Max Real-Time Congestion ($)">
- <Typography.Title heading={5}>${fiveMinKPIs.maxCongestion.toFixed(2)}</Typography.Title>
- </Card>
- </Col>
- <Col xs={12} md={6} style={{ marginTop: 10}}>
- <Card style={{ textAlign: 'center' }} title="Avg Real-Time Loss ($)">
- <Typography.Title heading={5}>${fiveMinKPIs.avgLoss.toFixed(2)}</Typography.Title>
- </Card>
- </Col>
- </Row>
- <Row gutter={24} style={{ marginTop: 20, marginBottom: 20 }}>
- <Col xs={12} md={6} style={{ marginTop: 10}}>
- <Card style={{ textAlign: 'center' }} title="Avg Day-Ahead LMP ($/MWh)">
- <Typography.Title heading={5}>${dayAheadKPIs.avgLmp.toFixed(2)}</Typography.Title>
- </Card>
- </Col>
- <Col xs={12} md={6} style={{ marginTop: 10}}>
- <Card style={{ textAlign: 'center' }} title="Total Day-Ahead Energy (MWh)">
- <Typography.Title heading={5}>{dayAheadKPIs.totalEnergy.toFixed(2)}</Typography.Title>
- </Card>
- </Col>
- <Col xs={12} md={6} style={{ marginTop: 10}}>
- <Card style={{ textAlign: 'center' }} title="Max Day-Ahead Congestion ($)">
- <Typography.Title heading={5}>${dayAheadKPIs.maxCongestion.toFixed(2)}</Typography.Title>
- </Card>
- </Col>
- <Col xs={12} md={6} style={{ marginTop: 10}}>
- <Card style={{ textAlign: 'center' }} title="Avg Day-AheadLoss ($)">
- <Typography.Title heading={5}>${dayAheadKPIs.avgLoss.toFixed(2)}</Typography.Title>
- </Card>
- </Col>
- </Row>
- <Row gutter={24} style={{ marginTop: 20 }}>
- <Col
- xs={24}
- sm={24}
- md={24}
- lg={12}
- >
- <Card
- style={{ marginBottom: 20 }}
- bodyStyle={{ padding: 0 }}
- title='Real-Time Market Data'
- >
- <div style={{ padding: 20 }}>
- <VChart spec={getChartSpec(fiveMinData, 'fiveMin', fiveMinZoom)} />
- </div>
- </Card>
- </Col>
- <Col
- xs={24}
- sm={24}
- md={24}
- lg={12}
- >
- <Card
- style={{ marginBottom: 20 }}
- bodyStyle={{ padding: 0 }}
- title='Day-Ahead Market Data'
- >
- <div style={{ padding: 20 }}>
- <VChart spec={getChartSpec(dayAheadData, 'dayAhead', dayAheadZoom)} />
- </div>
- </Card>
- </Col>
- </Row>
- </>
+ <div style={{ textAlign: 'center', marginBottom: 24 }}>
+ <TimeAgo timestamp={fiveMinLastUpdated} prefix="Real-Time Last Updated" />
+ <TimeAgo timestamp={dayAheadLastUpdated} prefix="Day-Ahead Last Updated" />
+ </div>
+
+ <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" style={{ borderRadius: 12, marginBottom: 24 }}>
+ <div style={{ padding: 10 }}>
+ <VChart spec={getChartSpec(fiveMinData, 'fiveMin', fiveMinZoom)} />
+ </div>
+ </Card>
+ </Col>
+ <Col xs={24} md={12}>
+ <Card title="Day-Ahead Market Data" style={{ borderRadius: 12, marginBottom: 24 }}>
+ <div style={{ padding: 10 }}>
+ <VChart spec={getChartSpec(dayAheadData, 'dayAhead', dayAheadZoom)} />
+ </div>
+ </Card>
+ </Col>
+ </Row>
+ </>
)}
+
+ <style>{`
+ @keyframes fadeIn {
+ from { opacity: 0; transform: translateY(10px); }
+ to { opacity: 1; transform: translateY(0); }
+ }
+ `}</style>
</div>
);
}
diff --git a/client/src/SubmitBidPage.jsx b/client/src/SubmitBidPage.jsx
index 94a0c32..7737c20 100644
--- a/client/src/SubmitBidPage.jsx
+++ b/client/src/SubmitBidPage.jsx
@@ -1,5 +1,5 @@
import React, { useState, useEffect, useContext } from 'react';
-import { Form, InputNumber, DatePicker, Button, Message, Typography, Card, Select } from '@arco-design/web-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';
@@ -27,18 +27,15 @@ function SubmitBidPage() {
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]);
@@ -56,14 +53,12 @@ function SubmitBidPage() {
quantity: values.quantity,
price: values.price,
market: selectedMarket,
- user_id: 1 // Hardcoded user id for now
+ user_id: 1
};
const res = await fetch(`${API_BASE_URL}/bids/`, {
method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
+ headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
@@ -83,24 +78,37 @@ function SubmitBidPage() {
};
return (
- <div style={{ padding: 20, backgroundColor: 'var(--color-fill-2)' }}>
- <Typography.Title heading={4}>Submit New Bid</Typography.Title>
- <Card style={{ marginTop: 16 }}>
- <Typography.Paragraph>
- <strong>Current Local Time:</strong> {localNow}
- </Typography.Paragraph>
- <Typography.Paragraph>
- <strong>Current {MARKET_FULL_NAMES[selectedMarket]} Time:</strong> {marketNow}
- </Typography.Paragraph>
+ <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
- }}
+ initialValues={{ quantity: 0, price: 0 }}
+ style={{ maxWidth: 600, margin: '0 auto' }}
>
<Form.Item
label="Bid Date and Hour"
@@ -117,7 +125,6 @@ function SubmitBidPage() {
style={{ width: '100%' }}
format="YYYY-MM-DD HH:00"
placeholder="Select date and hour"
- disabledMinutes={() => Array.from({ length: 60 }, (_, i) => i !== 0)}
timezone={MARKET_TIMEZONES[selectedMarket]}
/>
</Form.Item>
@@ -145,12 +152,35 @@ function SubmitBidPage() {
</Form.Item>
<Form.Item>
- <Button type="primary" htmlType="submit" loading={loading}>
+ <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>
);
}