133 lines
5.5 KiB
TypeScript
133 lines
5.5 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { TextField, Button, Box, Typography, Snackbar, Alert } from '@mui/material';
|
|
|
|
interface DesireFormProps {
|
|
onSubmit: (desires: { wants: string[], accepts: string[], noGoes: string[], afraidToAsk: string }) => void;
|
|
externalError?: string | null;
|
|
}
|
|
|
|
const DesireForm: React.FC<DesireFormProps> = ({ onSubmit, externalError }) => {
|
|
const [wants, setWants] = useState('');
|
|
const [accepts, setAccepts] = useState('');
|
|
const [noGoes, setNoGoes] = useState('');
|
|
const [afraidToAsk, setAfraidToAsk] = useState('');
|
|
const [alertOpen, setAlertOpen] = useState(false);
|
|
const [alertMessage, setAlertMessage] = useState('');
|
|
const [alertSeverity, setAlertSeverity] = useState<'error' | 'warning' | 'info' | 'success'>('error');
|
|
|
|
// Effect to handle external errors
|
|
useEffect(() => {
|
|
if (externalError) {
|
|
setAlertMessage(externalError);
|
|
setAlertSeverity('error');
|
|
setAlertOpen(true);
|
|
}
|
|
}, [externalError]);
|
|
|
|
const handleCloseAlert = (event?: React.SyntheticEvent | Event, reason?: string) => {
|
|
if (reason === 'clickaway') {
|
|
return;
|
|
}
|
|
setAlertOpen(false);
|
|
};
|
|
|
|
const handleSubmit = (event: React.FormEvent) => {
|
|
event.preventDefault();
|
|
const parsedWants = wants.split('\n').map(s => s.trim()).filter(s => s);
|
|
const parsedAccepts = accepts.split('\n').map(s => s.trim()).filter(s => s);
|
|
const parsedNoGoes = noGoes.split('\n').map(s => s.trim()).filter(s => s);
|
|
|
|
// FR-020: The system MUST require a user to enter at least one desire in at least one of the three categories
|
|
if (parsedWants.length === 0 && parsedAccepts.length === 0 && parsedNoGoes.length === 0 && afraidToAsk.length === 0) {
|
|
setAlertMessage('Please enter at least one desire in any category.');
|
|
setAlertSeverity('error');
|
|
setAlertOpen(true);
|
|
return;
|
|
}
|
|
|
|
// FR-016: System MUST validate a user's submission to prevent the same item from appearing in conflicting categories
|
|
const allItems = [...parsedWants, ...parsedAccepts, ...parsedNoGoes];
|
|
const uniqueItems = new Set(allItems);
|
|
if (allItems.length !== uniqueItems.size) {
|
|
setAlertMessage('You have conflicting desires (same item in different categories). Please resolve.');
|
|
setAlertSeverity('error');
|
|
setAlertOpen(true);
|
|
return;
|
|
}
|
|
|
|
onSubmit({
|
|
wants: parsedWants,
|
|
accepts: parsedAccepts,
|
|
noGoes: parsedNoGoes,
|
|
afraidToAsk: afraidToAsk,
|
|
});
|
|
};
|
|
|
|
return (
|
|
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 3, display: 'flex', flexDirection: 'column' }}>
|
|
<Typography variant="h6" gutterBottom>What You Want</Typography>
|
|
<TextField
|
|
multiline
|
|
rows={4}
|
|
fullWidth
|
|
value={wants}
|
|
onChange={(e) => setWants(e.target.value)}
|
|
margin="normal"
|
|
inputProps={{ maxLength: 500, 'aria-label': 'Enter items you want' }}
|
|
helperText={`Enter items you want, one per line. Max 500 characters per item. ${wants.length}/500`}
|
|
/>
|
|
|
|
<Typography variant="h6" gutterBottom sx={{ mt: 4 }}>Afraid to Ask (Private)</Typography>
|
|
<TextField
|
|
multiline
|
|
rows={4}
|
|
fullWidth
|
|
value={afraidToAsk}
|
|
onChange={(e) => setAfraidToAsk(e.target.value)}
|
|
margin="normal"
|
|
inputProps={{ maxLength: 500, 'aria-label': 'Enter sensitive ideas privately' }}
|
|
helperText={`Enter sensitive ideas privately. Max 500 characters. ${afraidToAsk.length}/500`}
|
|
/>
|
|
|
|
<Typography variant="h6" gutterBottom sx={{ mt: 4 }}>What You Accept</Typography>
|
|
<TextField
|
|
multiline
|
|
rows={4}
|
|
fullWidth
|
|
value={accepts}
|
|
onChange={(e) => setAccepts(e.target.value)}
|
|
margin="normal"
|
|
inputProps={{ maxLength: 500, 'aria-label': 'Enter items you accept' }}
|
|
helperText={`Enter items you accept, one per line. Max 500 characters per item. ${accepts.length}/500`}
|
|
/>
|
|
|
|
<Typography variant="h6" gutterBottom sx={{ mt: 4 }}>What You Do Not Want</Typography>
|
|
<TextField
|
|
multiline
|
|
rows={4}
|
|
fullWidth
|
|
value={noGoes}
|
|
onChange={(e) => setNoGoes(e.target.value)}
|
|
margin="normal"
|
|
inputProps={{ maxLength: 500, 'aria-label': 'Enter items you absolutely do not want' }}
|
|
helperText={`Enter items you absolutely do not want, one per line. Max 500 characters per item. ${noGoes.length}/500`}
|
|
/>
|
|
|
|
<Button
|
|
type="submit"
|
|
variant="contained"
|
|
sx={{ mt: 3, mb: 2, textTransform: 'none', alignSelf: 'flex-start' }}
|
|
>
|
|
Submit Desires
|
|
</Button>
|
|
<Snackbar open={alertOpen} autoHideDuration={6000} onClose={handleCloseAlert} anchorOrigin={{ vertical: 'top', horizontal: 'right' }} sx={{ mt: 8 }}>
|
|
<Alert onClose={handleCloseAlert} severity={alertSeverity} sx={{ width: '100%' }}>
|
|
{alertMessage}
|
|
</Alert>
|
|
</Snackbar>
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
export default DesireForm;
|