diff options
author | Navan Chauhan <navanchauhan@gmail.com> | 2025-04-27 21:30:56 -0600 |
---|---|---|
committer | Navan Chauhan <navanchauhan@gmail.com> | 2025-04-27 21:30:56 -0600 |
commit | 5244aff586df93de2b9973de9c9046e4ad6163bd (patch) | |
tree | 58f4a96f44ba2e724fcf11d2cebc555989646e67 | |
parent | f52782c4dccc3f2330d9dcd14ba4e3cf0522f8cf (diff) |
improve styling
-rw-r--r-- | client/src/App.js | 6 | ||||
-rw-r--r-- | client/src/BidsPage.jsx | 99 | ||||
-rw-r--r-- | client/src/MarketDataPage.jsx | 385 | ||||
-rw-r--r-- | client/src/SubmitBidPage.jsx | 76 |
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> ); } |