Minor UI improvements
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Button, SxProps, Theme } from '@mui/material';
|
import { Button, SxProps, Theme, Snackbar, Alert } from '@mui/material';
|
||||||
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
||||||
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
|
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
|
||||||
|
|
||||||
@@ -10,10 +10,15 @@ interface CopyLinkButtonProps {
|
|||||||
|
|
||||||
const CopyLinkButton: React.FC<CopyLinkButtonProps> = ({ linkToCopy, sx }) => {
|
const CopyLinkButton: React.FC<CopyLinkButtonProps> = ({ linkToCopy, sx }) => {
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
|
const [alertOpen, setAlertOpen] = useState(false);
|
||||||
|
const [alertMessage, setAlertMessage] = useState('');
|
||||||
|
const [alertSeverity, setAlertSeverity] = useState<'error' | 'warning' | 'info' | 'success'>('error');
|
||||||
|
|
||||||
const handleCopy = async () => {
|
const handleCopy = async () => {
|
||||||
if (!navigator.clipboard || !navigator.clipboard.writeText) {
|
if (!navigator.clipboard || !navigator.clipboard.writeText) {
|
||||||
alert('Clipboard API not supported in this browser. Please copy the link manually:' + linkToCopy);
|
setAlertMessage('Clipboard API not supported in this browser. Please copy the link manually.');
|
||||||
|
setAlertSeverity('warning');
|
||||||
|
setAlertOpen(true);
|
||||||
console.warn('Clipboard API not supported.');
|
console.warn('Clipboard API not supported.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -21,13 +26,25 @@ const CopyLinkButton: React.FC<CopyLinkButtonProps> = ({ linkToCopy, sx }) => {
|
|||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(linkToCopy);
|
await navigator.clipboard.writeText(linkToCopy);
|
||||||
setCopied(true);
|
setCopied(true);
|
||||||
|
setAlertMessage('Link copied to clipboard!');
|
||||||
|
setAlertSeverity('success');
|
||||||
|
setAlertOpen(true);
|
||||||
console.log('Link copied to clipboard:', linkToCopy);
|
console.log('Link copied to clipboard:', linkToCopy);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to copy link:', err);
|
console.error('Failed to copy link:', err);
|
||||||
alert('Failed to copy the link. Please try again or copy manually.');
|
setAlertMessage('Failed to copy the link. Please try again or copy manually.');
|
||||||
|
setAlertSeverity('error');
|
||||||
|
setAlertOpen(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCloseAlert = (event?: React.SyntheticEvent | Event, reason?: string) => {
|
||||||
|
if (reason === 'clickaway') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setAlertOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let timer: NodeJS.Timeout;
|
let timer: NodeJS.Timeout;
|
||||||
if (copied) {
|
if (copied) {
|
||||||
@@ -39,19 +56,26 @@ const CopyLinkButton: React.FC<CopyLinkButtonProps> = ({ linkToCopy, sx }) => {
|
|||||||
}, [copied]);
|
}, [copied]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<>
|
||||||
variant="outlined"
|
<Button
|
||||||
onClick={handleCopy}
|
variant="outlined"
|
||||||
startIcon={copied ? <CheckCircleOutlineIcon color="success" /> : <ContentCopyIcon />}
|
onClick={handleCopy}
|
||||||
color={copied ? "success" : "primary"}
|
startIcon={copied ? <CheckCircleOutlineIcon color="success" /> : <ContentCopyIcon />}
|
||||||
sx={{
|
color={copied ? "success" : "primary"}
|
||||||
minWidth: 120,
|
sx={{
|
||||||
textTransform: 'none',
|
minWidth: 120,
|
||||||
...(sx ? sx : {}),
|
textTransform: 'none',
|
||||||
}}
|
...(sx ? sx : {}),
|
||||||
>
|
}}
|
||||||
{copied ? 'Copied!' : 'Copy Link'}
|
>
|
||||||
</Button>
|
{copied ? 'Copied!' : 'Copy Link'}
|
||||||
|
</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>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { TextField, Button, Box, Typography } from '@mui/material';
|
import { TextField, Button, Box, Typography, Snackbar, Alert } from '@mui/material';
|
||||||
|
|
||||||
interface DesireFormProps {
|
interface DesireFormProps {
|
||||||
onSubmit: (desires: { wants: string[], accepts: string[], noGoes: string[], afraidToAsk: string }) => void;
|
onSubmit: (desires: { wants: string[], accepts: string[], noGoes: string[], afraidToAsk: string }) => void;
|
||||||
@@ -10,6 +10,16 @@ const DesireForm: React.FC<DesireFormProps> = ({ onSubmit }) => {
|
|||||||
const [accepts, setAccepts] = useState('');
|
const [accepts, setAccepts] = useState('');
|
||||||
const [noGoes, setNoGoes] = useState('');
|
const [noGoes, setNoGoes] = useState('');
|
||||||
const [afraidToAsk, setAfraidToAsk] = useState('');
|
const [afraidToAsk, setAfraidToAsk] = useState('');
|
||||||
|
const [alertOpen, setAlertOpen] = useState(false);
|
||||||
|
const [alertMessage, setAlertMessage] = useState('');
|
||||||
|
const [alertSeverity, setAlertSeverity] = useState<'error' | 'warning' | 'info' | 'success'>('error');
|
||||||
|
|
||||||
|
const handleCloseAlert = (event?: React.SyntheticEvent | Event, reason?: string) => {
|
||||||
|
if (reason === 'clickaway') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setAlertOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
const handleSubmit = (event: React.FormEvent) => {
|
const handleSubmit = (event: React.FormEvent) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -19,7 +29,9 @@ const DesireForm: React.FC<DesireFormProps> = ({ onSubmit }) => {
|
|||||||
|
|
||||||
// FR-020: The system MUST require a user to enter at least one desire in at least one of the three categories
|
// 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) {
|
if (parsedWants.length === 0 && parsedAccepts.length === 0 && parsedNoGoes.length === 0 && afraidToAsk.length === 0) {
|
||||||
alert('Please enter at least one desire in any category.');
|
setAlertMessage('Please enter at least one desire in any category.');
|
||||||
|
setAlertSeverity('error');
|
||||||
|
setAlertOpen(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,7 +39,9 @@ const DesireForm: React.FC<DesireFormProps> = ({ onSubmit }) => {
|
|||||||
const allItems = [...parsedWants, ...parsedAccepts, ...parsedNoGoes];
|
const allItems = [...parsedWants, ...parsedAccepts, ...parsedNoGoes];
|
||||||
const uniqueItems = new Set(allItems);
|
const uniqueItems = new Set(allItems);
|
||||||
if (allItems.length !== uniqueItems.size) {
|
if (allItems.length !== uniqueItems.size) {
|
||||||
alert('You have conflicting desires (same item in different categories). Please resolve.');
|
setAlertMessage('You have conflicting desires (same item in different categories). Please resolve.');
|
||||||
|
setAlertSeverity('error');
|
||||||
|
setAlertOpen(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +54,7 @@ const DesireForm: React.FC<DesireFormProps> = ({ onSubmit }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 3 }}>
|
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 3, display: 'flex', flexDirection: 'column' }}>
|
||||||
<Typography variant="h6" gutterBottom>What You Want</Typography>
|
<Typography variant="h6" gutterBottom>What You Want</Typography>
|
||||||
<TextField
|
<TextField
|
||||||
multiline
|
multiline
|
||||||
@@ -91,12 +105,16 @@ const DesireForm: React.FC<DesireFormProps> = ({ onSubmit }) => {
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
fullWidth
|
|
||||||
variant="contained"
|
variant="contained"
|
||||||
sx={{ mt: 3, mb: 2 }}
|
sx={{ mt: 3, mb: 2, textTransform: 'none', alignSelf: 'flex-start' }}
|
||||||
>
|
>
|
||||||
Submit Desires
|
Submit Desires
|
||||||
</Button>
|
</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>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ const SessionPage = () => {
|
|||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
sx={{ mt: 2 }}
|
sx={{ mt: 2, textTransform: 'none' }}
|
||||||
onClick={handleSetupSession}
|
onClick={handleSetupSession}
|
||||||
>
|
>
|
||||||
Start Session
|
Start Session
|
||||||
|
|||||||
Reference in New Issue
Block a user