stock-bot/apps/stock/web-app/src/features/exchanges/components/AddExchangeDialog.tsx

174 lines
No EOL
6 KiB
TypeScript

import { Dialog, DialogContent, DialogHeader, DialogTitle, Button } from '@/components/ui';
import { useCallback } from 'react';
import { CreateExchangeRequest, AddExchangeDialogProps } from '../types';
import { validateExchangeForm } from '../utils/validation';
import { useFormValidation } from '../hooks/useFormValidation';
const initialFormData: CreateExchangeRequest = {
code: '',
name: '',
country: '',
currency: '',
active: true,
};
export function AddExchangeDialog({
isOpen,
onClose,
onCreateExchange,
}: AddExchangeDialogProps) {
const {
formData,
errors,
isSubmitting,
updateField,
handleSubmit,
reset,
} = useFormValidation(initialFormData, validateExchangeForm);
const onSubmit = useCallback(
async (data: CreateExchangeRequest) => {
await onCreateExchange({
...data,
code: data.code.toUpperCase(),
country: data.country.toUpperCase(),
currency: data.currency.toUpperCase(),
});
},
[onCreateExchange]
);
const handleFormSubmit = useCallback(
(e: React.FormEvent) => {
e.preventDefault();
handleSubmit(onSubmit, onClose);
},
[handleSubmit, onSubmit, onClose]
);
const handleClose = useCallback(() => {
reset();
onClose();
}, [reset, onClose]);
return (
<Dialog open={isOpen} onOpenChange={handleClose}>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle>Add New Exchange</DialogTitle>
<p className="text-sm text-text-muted">
Create a new master exchange with no provider mappings
</p>
</DialogHeader>
<form onSubmit={handleFormSubmit} className="space-y-4">
{/* Exchange Code */}
<div>
<label htmlFor="code" className="block text-sm font-medium text-text-primary mb-1">
Exchange Code *
</label>
<input
id="code"
type="text"
value={formData.code}
onChange={e => updateField('code', e.target.value)}
placeholder="e.g., NASDAQ, NYSE, TSX"
className={`w-full px-3 py-2 border rounded-md bg-surface text-text-primary focus:ring-2 focus:ring-primary-500 focus:border-primary-500 font-mono ${
errors.code ? 'border-danger' : 'border-border'
}`}
maxLength={10}
required
/>
{errors.code && <p className="text-xs text-danger mt-1">{errors.code}</p>}
</div>
{/* Exchange Name */}
<div>
<label htmlFor="name" className="block text-sm font-medium text-text-primary mb-1">
Exchange Name *
</label>
<input
id="name"
type="text"
value={formData.name}
onChange={e => updateField('name', e.target.value)}
placeholder="e.g., NASDAQ Stock Market, New York Stock Exchange"
className={`w-full px-3 py-2 border rounded-md bg-surface text-text-primary focus:ring-2 focus:ring-primary-500 focus:border-primary-500 ${
errors.name ? 'border-danger' : 'border-border'
}`}
maxLength={255}
required
/>
{errors.name && <p className="text-xs text-danger mt-1">{errors.name}</p>}
</div>
{/* Country */}
<div>
<label htmlFor="country" className="block text-sm font-medium text-text-primary mb-1">
Country Code *
</label>
<input
id="country"
type="text"
value={formData.country}
onChange={e => updateField('country', e.target.value)}
placeholder="e.g., US, CA, GB"
className={`w-full px-3 py-2 border rounded-md bg-surface text-text-primary focus:ring-2 focus:ring-primary-500 focus:border-primary-500 font-mono ${
errors.country ? 'border-danger' : 'border-border'
}`}
maxLength={2}
required
/>
{errors.country && <p className="text-xs text-danger mt-1">{errors.country}</p>}
</div>
{/* Currency */}
<div>
<label htmlFor="currency" className="block text-sm font-medium text-text-primary mb-1">
Currency Code *
</label>
<input
id="currency"
type="text"
value={formData.currency}
onChange={e => updateField('currency', e.target.value)}
placeholder="e.g., USD, EUR, CAD"
className={`w-full px-3 py-2 border rounded-md bg-surface text-text-primary focus:ring-2 focus:ring-primary-500 focus:border-primary-500 font-mono ${
errors.currency ? 'border-danger' : 'border-border'
}`}
maxLength={3}
required
/>
{errors.currency && <p className="text-xs text-danger mt-1">{errors.currency}</p>}
</div>
{/* Active Toggle */}
<div>
<label className="flex items-center gap-2">
<input
type="checkbox"
checked={formData.active}
onChange={e => updateField('active', e.target.checked)}
className="rounded"
/>
<span className="text-sm text-text-primary">Active exchange</span>
</label>
<p className="text-xs text-text-muted mt-1">
Inactive exchanges won't be used for new symbol mappings
</p>
</div>
{/* Action Buttons */}
<div className="flex justify-end gap-2 pt-4">
<Button type="button" variant="outline" onClick={handleClose} disabled={isSubmitting}>
Cancel
</Button>
<Button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Creating...' : 'Create Exchange'}
</Button>
</div>
</form>
</DialogContent>
</Dialog>
);
}