214 lines
8.3 KiB
TypeScript
214 lines
8.3 KiB
TypeScript
import { Dialog, Transition } from '@headlessui/react';
|
|
import { XMarkIcon } from '@heroicons/react/24/outline';
|
|
import React, { useState } from 'react';
|
|
import { AddSourceRequest } from '../types';
|
|
|
|
interface AddSourceDialogProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
onAddSource: (request: AddSourceRequest) => Promise<void>;
|
|
exchangeId: string;
|
|
exchangeName: string;
|
|
}
|
|
|
|
export function AddSourceDialog({
|
|
isOpen,
|
|
onClose,
|
|
onAddSource,
|
|
exchangeName,
|
|
}: AddSourceDialogProps) {
|
|
const [source, setSource] = useState('');
|
|
const [sourceCode, setSourceCode] = useState('');
|
|
const [id, setId] = useState('');
|
|
const [name, setName] = useState('');
|
|
const [code, setCode] = useState('');
|
|
const [aliases, setAliases] = useState('');
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
if (!source || !sourceCode || !id || !name || !code) return;
|
|
|
|
setLoading(true);
|
|
try {
|
|
await onAddSource({
|
|
source,
|
|
source_code: sourceCode,
|
|
mapping: {
|
|
id,
|
|
name,
|
|
code,
|
|
aliases: aliases
|
|
.split(',')
|
|
.map(a => a.trim())
|
|
.filter(Boolean),
|
|
},
|
|
});
|
|
|
|
// Reset form
|
|
setSource('');
|
|
setSourceCode('');
|
|
setId('');
|
|
setName('');
|
|
setCode('');
|
|
setAliases('');
|
|
} catch (error) {
|
|
console.error('Error adding source:', error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Transition appear show={isOpen} as={React.Fragment}>
|
|
<Dialog as="div" className="relative z-50" onClose={onClose}>
|
|
<Transition.Child
|
|
as={React.Fragment}
|
|
enter="ease-out duration-300"
|
|
enterFrom="opacity-0"
|
|
enterTo="opacity-100"
|
|
leave="ease-in duration-200"
|
|
leaveFrom="opacity-100"
|
|
leaveTo="opacity-0"
|
|
>
|
|
<div className="fixed inset-0 bg-black bg-opacity-25" />
|
|
</Transition.Child>
|
|
|
|
<div className="fixed inset-0 overflow-y-auto">
|
|
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
|
<Transition.Child
|
|
as={React.Fragment}
|
|
enter="ease-out duration-300"
|
|
enterFrom="opacity-0 scale-95"
|
|
enterTo="opacity-100 scale-100"
|
|
leave="ease-in duration-200"
|
|
leaveFrom="opacity-100 scale-100"
|
|
leaveTo="opacity-0 scale-95"
|
|
>
|
|
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-lg bg-background border border-border p-6 text-left align-middle shadow-xl transition-all">
|
|
<div className="flex items-center justify-between mb-4">
|
|
<Dialog.Title className="text-lg font-medium text-text-primary">
|
|
Add Source to {exchangeName}
|
|
</Dialog.Title>
|
|
<button
|
|
onClick={onClose}
|
|
className="text-text-muted hover:text-text-primary transition-colors"
|
|
>
|
|
<XMarkIcon className="h-5 w-5" />
|
|
</button>
|
|
</div>
|
|
|
|
<form onSubmit={handleSubmit} className="space-y-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-text-primary mb-1">
|
|
Source Provider
|
|
</label>
|
|
<select
|
|
value={source}
|
|
onChange={e => setSource(e.target.value)}
|
|
className="w-full bg-surface border border-border rounded px-3 py-2 text-text-primary focus:ring-1 focus:ring-primary-500 focus:border-primary-500"
|
|
required
|
|
>
|
|
<option value="">Select a source</option>
|
|
<option value="ib">Interactive Brokers</option>
|
|
<option value="alpaca">Alpaca</option>
|
|
<option value="polygon">Polygon</option>
|
|
<option value="yahoo">Yahoo Finance</option>
|
|
<option value="alpha_vantage">Alpha Vantage</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-text-primary mb-1">
|
|
Source Code
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={sourceCode}
|
|
onChange={e => setSourceCode(e.target.value)}
|
|
className="w-full bg-surface border border-border rounded px-3 py-2 text-text-primary focus:ring-1 focus:ring-primary-500 focus:border-primary-500"
|
|
placeholder="e.g., IB, ALP, POLY"
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-text-primary mb-1">
|
|
Source ID
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={id}
|
|
onChange={e => setId(e.target.value)}
|
|
className="w-full bg-surface border border-border rounded px-3 py-2 text-text-primary focus:ring-1 focus:ring-primary-500 focus:border-primary-500"
|
|
placeholder="e.g., NYSE, NASDAQ"
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-text-primary mb-1">
|
|
Source Name
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={name}
|
|
onChange={e => setName(e.target.value)}
|
|
className="w-full bg-surface border border-border rounded px-3 py-2 text-text-primary focus:ring-1 focus:ring-primary-500 focus:border-primary-500"
|
|
placeholder="e.g., New York Stock Exchange"
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-text-primary mb-1">
|
|
Source Code
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={code}
|
|
onChange={e => setCode(e.target.value)}
|
|
className="w-full bg-surface border border-border rounded px-3 py-2 text-text-primary focus:ring-1 focus:ring-primary-500 focus:border-primary-500"
|
|
placeholder="e.g., NYSE"
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-text-primary mb-1">
|
|
Aliases (comma-separated)
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={aliases}
|
|
onChange={e => setAliases(e.target.value)}
|
|
className="w-full bg-surface border border-border rounded px-3 py-2 text-text-primary focus:ring-1 focus:ring-primary-500 focus:border-primary-500"
|
|
placeholder="e.g., NYSE, New York, Big Board"
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex justify-end space-x-3 pt-4">
|
|
<button
|
|
type="button"
|
|
onClick={onClose}
|
|
className="px-4 py-2 border border-border text-text-secondary hover:text-text-primary hover:bg-surface-secondary rounded transition-colors"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
disabled={loading || !source || !sourceCode || !id || !name || !code}
|
|
className="px-4 py-2 bg-primary-500 text-white rounded hover:bg-primary-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
|
>
|
|
{loading ? 'Adding...' : 'Add Source'}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</Dialog.Panel>
|
|
</Transition.Child>
|
|
</div>
|
|
</div>
|
|
</Dialog>
|
|
</Transition>
|
|
);
|
|
}
|