aboutsummaryrefslogtreecommitdiff
path: root/client/src/components/SubmitBidPage.jsx
diff options
context:
space:
mode:
authorNavan Chauhan <navanchauhan@gmail.com>2025-04-27 22:44:20 -0600
committerNavan Chauhan <navanchauhan@gmail.com>2025-04-27 22:44:20 -0600
commit37787895d56888ab44362252f21fb05c05e97250 (patch)
tree5ff7eaababa6fffd5dc950920f521c5f242010af /client/src/components/SubmitBidPage.jsx
parentf32142947b853076889801913d47b8c2c0f4f456 (diff)
reorganize
Diffstat (limited to 'client/src/components/SubmitBidPage.jsx')
-rw-r--r--client/src/components/SubmitBidPage.jsx188
1 files changed, 188 insertions, 0 deletions
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;