huge refactor on web-api and web-app
This commit is contained in:
parent
1d299e52d4
commit
265e10a658
23 changed files with 1545 additions and 1233 deletions
|
|
@ -1,114 +1,55 @@
|
|||
import { Dialog, DialogContent, DialogHeader, DialogTitle, Button } from '@/components/ui';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { CreateExchangeRequest } from '../types';
|
||||
import { useCallback } from 'react';
|
||||
import { CreateExchangeRequest, AddExchangeDialogProps } from '../types';
|
||||
import { validateExchangeForm } from '../utils/validation';
|
||||
import { useFormValidation } from '../hooks/useFormValidation';
|
||||
|
||||
interface AddExchangeDialogProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onCreateExchange: (request: CreateExchangeRequest) => Promise<any>;
|
||||
}
|
||||
const initialFormData: CreateExchangeRequest = {
|
||||
code: '',
|
||||
name: '',
|
||||
country: '',
|
||||
currency: '',
|
||||
active: true,
|
||||
};
|
||||
|
||||
export function AddExchangeDialog({
|
||||
isOpen,
|
||||
onClose,
|
||||
onCreateExchange,
|
||||
}: AddExchangeDialogProps) {
|
||||
const [formData, setFormData] = useState<CreateExchangeRequest>({
|
||||
code: '',
|
||||
name: '',
|
||||
country: '',
|
||||
currency: '',
|
||||
active: true,
|
||||
});
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||
const {
|
||||
formData,
|
||||
errors,
|
||||
isSubmitting,
|
||||
updateField,
|
||||
handleSubmit,
|
||||
reset,
|
||||
} = useFormValidation(initialFormData, validateExchangeForm);
|
||||
|
||||
const validateForm = useCallback((): boolean => {
|
||||
const newErrors: Record<string, string> = {};
|
||||
|
||||
if (!formData.code.trim()) {
|
||||
newErrors.code = 'Exchange code is required';
|
||||
} else if (formData.code.length > 10) {
|
||||
newErrors.code = 'Exchange code must be 10 characters or less';
|
||||
}
|
||||
|
||||
if (!formData.name.trim()) {
|
||||
newErrors.name = 'Exchange name is required';
|
||||
}
|
||||
|
||||
if (!formData.country.trim()) {
|
||||
newErrors.country = 'Country is required';
|
||||
} else if (formData.country.length !== 2) {
|
||||
newErrors.country = 'Country must be exactly 2 characters (e.g., US, CA, GB)';
|
||||
}
|
||||
|
||||
if (!formData.currency.trim()) {
|
||||
newErrors.currency = 'Currency is required';
|
||||
} else if (formData.currency.length !== 3) {
|
||||
newErrors.currency = 'Currency must be exactly 3 characters (e.g., USD, EUR, CAD)';
|
||||
}
|
||||
|
||||
setErrors(newErrors);
|
||||
return Object.keys(newErrors).length === 0;
|
||||
}, [formData]);
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!validateForm()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
await onCreateExchange({
|
||||
...formData,
|
||||
code: formData.code.toUpperCase(),
|
||||
country: formData.country.toUpperCase(),
|
||||
currency: formData.currency.toUpperCase(),
|
||||
});
|
||||
|
||||
// Reset form on success
|
||||
setFormData({
|
||||
code: '',
|
||||
name: '',
|
||||
country: '',
|
||||
currency: '',
|
||||
active: true,
|
||||
});
|
||||
setErrors({});
|
||||
} catch (error) {
|
||||
console.error('Error creating exchange:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
const onSubmit = useCallback(
|
||||
async (data: CreateExchangeRequest) => {
|
||||
await onCreateExchange({
|
||||
...data,
|
||||
code: data.code.toUpperCase(),
|
||||
country: data.country.toUpperCase(),
|
||||
currency: data.currency.toUpperCase(),
|
||||
});
|
||||
},
|
||||
[formData, validateForm, onCreateExchange]
|
||||
[onCreateExchange]
|
||||
);
|
||||
|
||||
const handleFormSubmit = useCallback(
|
||||
(e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
handleSubmit(onSubmit, onClose);
|
||||
},
|
||||
[handleSubmit, onSubmit, onClose]
|
||||
);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
setFormData({
|
||||
code: '',
|
||||
name: '',
|
||||
country: '',
|
||||
currency: '',
|
||||
active: true,
|
||||
});
|
||||
setErrors({});
|
||||
reset();
|
||||
onClose();
|
||||
}, [onClose]);
|
||||
|
||||
const handleInputChange = useCallback(
|
||||
(field: keyof CreateExchangeRequest, value: string | boolean) => {
|
||||
setFormData(prev => ({ ...prev, [field]: value }));
|
||||
// Clear error when user starts typing
|
||||
if (errors[field]) {
|
||||
setErrors(prev => ({ ...prev, [field]: '' }));
|
||||
}
|
||||
},
|
||||
[errors]
|
||||
);
|
||||
}, [reset, onClose]);
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={handleClose}>
|
||||
|
|
@ -120,7 +61,7 @@ export function AddExchangeDialog({
|
|||
</p>
|
||||
</DialogHeader>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<form onSubmit={handleFormSubmit} className="space-y-4">
|
||||
{/* Exchange Code */}
|
||||
<div>
|
||||
<label htmlFor="code" className="block text-sm font-medium text-text-primary mb-1">
|
||||
|
|
@ -130,7 +71,7 @@ export function AddExchangeDialog({
|
|||
id="code"
|
||||
type="text"
|
||||
value={formData.code}
|
||||
onChange={e => handleInputChange('code', e.target.value)}
|
||||
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'
|
||||
|
|
@ -150,7 +91,7 @@ export function AddExchangeDialog({
|
|||
id="name"
|
||||
type="text"
|
||||
value={formData.name}
|
||||
onChange={e => handleInputChange('name', e.target.value)}
|
||||
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'
|
||||
|
|
@ -170,7 +111,7 @@ export function AddExchangeDialog({
|
|||
id="country"
|
||||
type="text"
|
||||
value={formData.country}
|
||||
onChange={e => handleInputChange('country', e.target.value)}
|
||||
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'
|
||||
|
|
@ -190,7 +131,7 @@ export function AddExchangeDialog({
|
|||
id="currency"
|
||||
type="text"
|
||||
value={formData.currency}
|
||||
onChange={e => handleInputChange('currency', e.target.value)}
|
||||
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'
|
||||
|
|
@ -207,7 +148,7 @@ export function AddExchangeDialog({
|
|||
<input
|
||||
type="checkbox"
|
||||
checked={formData.active}
|
||||
onChange={e => handleInputChange('active', e.target.checked)}
|
||||
onChange={e => updateField('active', e.target.checked)}
|
||||
className="rounded"
|
||||
/>
|
||||
<span className="text-sm text-text-primary">Active exchange</span>
|
||||
|
|
@ -219,11 +160,11 @@ export function AddExchangeDialog({
|
|||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex justify-end gap-2 pt-4">
|
||||
<Button type="button" variant="outline" onClick={handleClose} disabled={loading}>
|
||||
<Button type="button" variant="outline" onClick={handleClose} disabled={isSubmitting}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" disabled={loading}>
|
||||
{loading ? 'Creating...' : 'Create Exchange'}
|
||||
<Button type="submit" disabled={isSubmitting}>
|
||||
{isSubmitting ? 'Creating...' : 'Create Exchange'}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue