diff --git a/frontend/src/features/orders/components/ApprovalBanner.jsx b/frontend/src/features/orders/components/ApprovalBanner.jsx
index 652b6d1..481c90e 100644
--- a/frontend/src/features/orders/components/ApprovalBanner.jsx
+++ b/frontend/src/features/orders/components/ApprovalBanner.jsx
@@ -1,6 +1,6 @@
import { useState } from 'react'
import { Button } from '../../../components/Button'
-import { getErrorMessage } from '../../../utils/apiErrorMessage.js'
+import { getOrdersErrorMessage } from '../ordersErrorMessage.js'
import { formatUnitsString } from '../../../utils/format.js'
import { useApproveOrder } from '../hooks/useApproveOrder.js'
import './ApprovalBanner.css'
@@ -26,7 +26,7 @@ export function ApprovalBanner({ order, onApproved }) {
{status === 'error' && (
- {getErrorMessage(error)}
+ {getOrdersErrorMessage(error)}
)}
diff --git a/frontend/src/features/orders/components/OrderForm.css b/frontend/src/features/orders/components/OrderForm.css
index 0dfc5b3..82b1261 100644
--- a/frontend/src/features/orders/components/OrderForm.css
+++ b/frontend/src/features/orders/components/OrderForm.css
@@ -1,8 +1,25 @@
+/* One column below 768px; Security+Side and Quantity+Reference price pair up into a 2-column
+ grid at 768px and above, matching FeesPage.css/FundingPage.css's established breakpoint
+ convention in this codebase (a literal px value, since --breakpoint-md can't be read inside a
+ media query). The preview, error, and submit button each span the full row regardless of
+ column count -- pairing a number with its own row of controls reads worse than a clean divider
+ between input fields and the outcome/action below them. */
.tu-order-form {
- display: flex;
- flex-direction: column;
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
gap: var(--space-4);
- max-width: 360px;
+}
+
+@media (min-width: 768px) {
+ .tu-order-form {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+}
+
+.tu-order-form__preview,
+.tu-order-form__error,
+.tu-order-form__submit {
+ grid-column: 1 / -1;
}
.tu-order-form__preview {
@@ -37,3 +54,8 @@
color: var(--color-error);
font-size: var(--font-size-caption);
}
+
+.tu-order-form__empty {
+ grid-column: 1 / -1;
+ color: var(--color-text-secondary);
+}
diff --git a/frontend/src/features/orders/components/OrderForm.jsx b/frontend/src/features/orders/components/OrderForm.jsx
index e58964d..e5cfb6d 100644
--- a/frontend/src/features/orders/components/OrderForm.jsx
+++ b/frontend/src/features/orders/components/OrderForm.jsx
@@ -8,8 +8,8 @@ import { Select } from '../../../components/Select'
import { Skeleton } from '../../../components/Skeleton'
import { useToast } from '../../../components/Toast'
import { useSecurities } from '../../portfolio/hooks/useSecurities.js'
-import { getErrorMessage } from '../../../utils/apiErrorMessage.js'
import { formatMoney } from '../../../utils/format.js'
+import { getOrdersErrorMessage } from '../ordersErrorMessage.js'
import { usePlaceOrder } from '../hooks/usePlaceOrder.js'
import { parseQuantity } from '../parseQuantity.js'
import './OrderForm.css'
@@ -24,14 +24,19 @@ export function OrderForm() {
const [side, setSide] = useState('buy')
const [quantity, setQuantity] = useState('')
const [referencePrice, setReferencePrice] = useState('')
- const [validationMessage, setValidationMessage] = useState(null)
+ const [quantityError, setQuantityError] = useState(null)
+ const [referencePriceError, setReferencePriceError] = useState(null)
if (securitiesStatus === 'idle' || securitiesStatus === 'loading') {
return
}
if (securitiesStatus === 'error') {
- return
+ return
+ }
+
+ if (securities.length === 0) {
+ return
No tradable securities are available right now.
}
const selectedSecurity = securities.find((security) => security.security_id === securityId) || securities[0]
@@ -50,18 +55,18 @@ export function OrderForm() {
const handleSubmit = (event) => {
event.preventDefault()
- setValidationMessage(null)
+ setQuantityError(null)
+ setReferencePriceError(null)
+
const parsedQuantity = parseQuantity(quantity)
if (!parsedQuantity) {
- setValidationMessage('Enter a valid quantity greater than zero.')
+ setQuantityError('Enter a valid quantity greater than zero.')
return
}
- if (!selectedSecurity) {
- setValidationMessage('Choose a security.')
- return
- }
- if (!referencePrice || Number.isNaN(Number(referencePrice)) || Number(referencePrice) <= 0) {
- setValidationMessage('Enter a valid reference price greater than zero.')
+
+ const trimmedReferencePrice = referencePrice.trim()
+ if (!trimmedReferencePrice || Number.isNaN(Number(trimmedReferencePrice)) || Number(trimmedReferencePrice) <= 0) {
+ setReferencePriceError('Enter a valid reference price greater than zero.')
return
}
@@ -69,7 +74,7 @@ export function OrderForm() {
securityId: selectedSecurity.security_id,
side,
quantity: parsedQuantity,
- referencePrice,
+ referencePrice: trimmedReferencePrice,
})
.then((order) => {
showToast({ message: 'Order placed.', tone: 'success' })
@@ -78,18 +83,24 @@ export function OrderForm() {
.catch(() => {})
}
- const displayError = validationMessage || (status === 'error' ? getErrorMessage(error) : null)
+ const submitError = status === 'error' ? getOrdersErrorMessage(error) : null
return (
)}
- {displayError && (
+ {submitError && (