Updated description box and fixed title renaming
This commit is contained in:
@@ -835,6 +835,19 @@ app.put('/api/events/:slug', upload.single('wallpaper'), async (req: MulterReque
|
|||||||
return res.status(404).json({ error: 'Event not found' });
|
return res.status(404).json({ error: 'Event not found' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate new slug if title changed
|
||||||
|
let newSlug = slug;
|
||||||
|
if (title && title !== eventRows[0].title) {
|
||||||
|
newSlug = title.toLowerCase().replace(/[^a-z0-9]+/g, '-');
|
||||||
|
|
||||||
|
// Check if new slug already exists (and it's not the current event)
|
||||||
|
const existingEvent = await db.get('SELECT id FROM events WHERE slug = ? AND id != ?', [newSlug, eventRows[0].id]);
|
||||||
|
if (existingEvent) {
|
||||||
|
// Append a timestamp to make it unique
|
||||||
|
newSlug = `${newSlug}-${Date.now()}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure needed_items is properly formatted
|
// Ensure needed_items is properly formatted
|
||||||
let parsedNeededItems: string[] = [];
|
let parsedNeededItems: string[] = [];
|
||||||
try {
|
try {
|
||||||
@@ -886,9 +899,9 @@ app.put('/api/events/:slug', upload.single('wallpaper'), async (req: MulterReque
|
|||||||
wallpaperPath = req.file.filename;
|
wallpaperPath = req.file.filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the event
|
// Update the event (including the slug if it changed)
|
||||||
await db.run(
|
await db.run(
|
||||||
'UPDATE events SET title = ?, description = ?, date = ?, location = ?, needed_items = ?, rsvp_cutoff_date = ?, wallpaper = ?, max_guests_per_rsvp = ?, email_notifications_enabled = ?, email_recipients = ?, event_conclusion_email_enabled = ?, event_conclusion_message = ? WHERE slug = ?',
|
'UPDATE events SET title = ?, description = ?, date = ?, location = ?, needed_items = ?, rsvp_cutoff_date = ?, wallpaper = ?, max_guests_per_rsvp = ?, email_notifications_enabled = ?, email_recipients = ?, event_conclusion_email_enabled = ?, event_conclusion_message = ?, slug = ? WHERE slug = ?',
|
||||||
[
|
[
|
||||||
title ?? eventRows[0].title,
|
title ?? eventRows[0].title,
|
||||||
description === undefined ? eventRows[0].description : description,
|
description === undefined ? eventRows[0].description : description,
|
||||||
@@ -902,12 +915,13 @@ app.put('/api/events/:slug', upload.single('wallpaper'), async (req: MulterReque
|
|||||||
emailRecipients,
|
emailRecipients,
|
||||||
eventConclusionEmailEnabled ? 1 : 0, // Update new field
|
eventConclusionEmailEnabled ? 1 : 0, // Update new field
|
||||||
eventConclusionMessage, // Update new field
|
eventConclusionMessage, // Update new field
|
||||||
slug
|
newSlug, // Update slug if it changed
|
||||||
|
slug // WHERE clause uses the old slug
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get the updated event
|
// Get the updated event using the new slug
|
||||||
const updatedEvent = await db.get('SELECT * FROM events WHERE slug = ?', [slug]);
|
const updatedEvent = await db.get('SELECT * FROM events WHERE slug = ?', [newSlug]);
|
||||||
|
|
||||||
// Add the full path to the wallpaper and parse JSON/boolean fields
|
// Add the full path to the wallpaper and parse JSON/boolean fields
|
||||||
if (updatedEvent.wallpaper) {
|
if (updatedEvent.wallpaper) {
|
||||||
|
|||||||
@@ -16,13 +16,15 @@
|
|||||||
"@mui/material": "^5.14.2",
|
"@mui/material": "^5.14.2",
|
||||||
"@mui/icons-material": "^5.14.2",
|
"@mui/icons-material": "^5.14.2",
|
||||||
"@emotion/react": "^11.11.1",
|
"@emotion/react": "^11.11.1",
|
||||||
"@emotion/styled": "^11.11.0"
|
"@emotion/styled": "^11.11.0",
|
||||||
|
"react-quill": "^2.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^18.2.15",
|
"@types/react": "^18.2.15",
|
||||||
"@types/react-dom": "^18.2.7",
|
"@types/react-dom": "^18.2.7",
|
||||||
"@types/react-router-dom": "^5.3.3",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
"@types/axios": "^0.14.0",
|
"@types/axios": "^0.14.0",
|
||||||
|
"@types/react-quill": "^1.3.10",
|
||||||
"typescript": "^4.9.5",
|
"typescript": "^4.9.5",
|
||||||
"react-scripts": "5.0.1"
|
"react-scripts": "5.0.1"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import WallpaperIcon from '@mui/icons-material/Wallpaper';
|
|||||||
import EmailIcon from '@mui/icons-material/Email';
|
import EmailIcon from '@mui/icons-material/Email';
|
||||||
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import RichTextEditor from './RichTextEditor';
|
||||||
|
|
||||||
interface RSVP {
|
interface RSVP {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -621,11 +622,11 @@ const EventAdmin: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
// Create FormData and append all fields
|
// Create FormData and append all fields
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
|
formData.append('title', updateForm.title); // Submit the updated title
|
||||||
formData.append('description', updateForm.description);
|
formData.append('description', updateForm.description);
|
||||||
formData.append('location', updateForm.location);
|
formData.append('location', updateForm.location);
|
||||||
formData.append('date', updateForm.date);
|
formData.append('date', updateForm.date);
|
||||||
formData.append('rsvp_cutoff_date', updateForm.rsvp_cutoff_date);
|
formData.append('rsvp_cutoff_date', updateForm.rsvp_cutoff_date);
|
||||||
formData.append('title', event.title); // Keep existing title
|
|
||||||
formData.append('needed_items', JSON.stringify(event.needed_items)); // Keep existing needed items
|
formData.append('needed_items', JSON.stringify(event.needed_items)); // Keep existing needed items
|
||||||
formData.append('email_notifications_enabled', updateForm.email_notifications_enabled.toString());
|
formData.append('email_notifications_enabled', updateForm.email_notifications_enabled.toString());
|
||||||
formData.append('email_recipients', updateForm.email_recipients);
|
formData.append('email_recipients', updateForm.email_recipients);
|
||||||
@@ -644,19 +645,33 @@ const EventAdmin: React.FC = () => {
|
|||||||
'Content-Type': 'multipart/form-data',
|
'Content-Type': 'multipart/form-data',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
setEvent(prev => prev ? {
|
// Check if slug changed (title was updated)
|
||||||
...prev,
|
const newSlug = response.data.slug;
|
||||||
description: updateForm.description,
|
if (newSlug && newSlug !== slug) {
|
||||||
location: updateForm.location,
|
// Slug changed - redirect to new URL
|
||||||
date: updateForm.date,
|
setUpdateInfoDialogOpen(false);
|
||||||
rsvp_cutoff_date: updateForm.rsvp_cutoff_date,
|
navigate(`/admin/events/${newSlug}`, { replace: true });
|
||||||
wallpaper: response.data.wallpaper || prev.wallpaper,
|
// Refresh the page to load the new event data
|
||||||
email_notifications_enabled: updateForm.email_notifications_enabled,
|
window.location.href = `/admin/events/${newSlug}`;
|
||||||
email_recipients: updateForm.email_recipients
|
} else {
|
||||||
} : null);
|
// Slug didn't change - just update local state
|
||||||
|
setEvent(prev => prev ? {
|
||||||
setUpdateInfoDialogOpen(false);
|
...prev,
|
||||||
|
title: updateForm.title,
|
||||||
|
description: updateForm.description,
|
||||||
|
location: updateForm.location,
|
||||||
|
date: updateForm.date,
|
||||||
|
rsvp_cutoff_date: updateForm.rsvp_cutoff_date,
|
||||||
|
wallpaper: response.data.wallpaper || prev.wallpaper,
|
||||||
|
email_notifications_enabled: updateForm.email_notifications_enabled,
|
||||||
|
email_recipients: updateForm.email_recipients,
|
||||||
|
event_conclusion_email_enabled: updateForm.event_conclusion_email_enabled,
|
||||||
|
event_conclusion_message: updateForm.event_conclusion_message
|
||||||
|
} : null);
|
||||||
|
|
||||||
|
setUpdateInfoDialogOpen(false);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating event:', error);
|
console.error('Error updating event:', error);
|
||||||
setError('Failed to update event information');
|
setError('Failed to update event information');
|
||||||
@@ -1233,13 +1248,12 @@ const EventAdmin: React.FC = () => {
|
|||||||
onChange={(e) => setUpdateForm(prev => ({ ...prev, title: e.target.value }))}
|
onChange={(e) => setUpdateForm(prev => ({ ...prev, title: e.target.value }))}
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
<TextField
|
<RichTextEditor
|
||||||
label="Description"
|
|
||||||
value={updateForm.description}
|
value={updateForm.description}
|
||||||
onChange={(e) => setUpdateForm(prev => ({ ...prev, description: e.target.value }))}
|
onChange={(value) => setUpdateForm(prev => ({ ...prev, description: value }))}
|
||||||
fullWidth
|
label="Description"
|
||||||
multiline
|
placeholder="Enter event description with formatting..."
|
||||||
rows={3}
|
theme="light"
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
label="Location"
|
label="Location"
|
||||||
@@ -1320,20 +1334,19 @@ const EventAdmin: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{updateForm.event_conclusion_email_enabled && (
|
{updateForm.event_conclusion_email_enabled && (
|
||||||
<TextField
|
<Box sx={{ mt: 2 }}>
|
||||||
fullWidth
|
<RichTextEditor
|
||||||
label="Event conclusion message"
|
value={updateForm.event_conclusion_message}
|
||||||
value={updateForm.event_conclusion_message}
|
onChange={(value) => setUpdateForm(prev => ({
|
||||||
onChange={(e) => setUpdateForm(prev => ({
|
...prev,
|
||||||
...prev,
|
event_conclusion_message: value
|
||||||
event_conclusion_message: e.target.value
|
}))}
|
||||||
}))}
|
label="Event conclusion message"
|
||||||
variant="outlined"
|
placeholder="Enter the message to send after the event..."
|
||||||
multiline
|
theme="light"
|
||||||
rows={4}
|
helperText="This message will be sent to attendees who opted for email notifications the day after the event."
|
||||||
helperText="This message will be sent to attendees who opted for email notifications the day after the event."
|
/>
|
||||||
sx={{ mt: 2 }}
|
</Box>
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|||||||
@@ -107,9 +107,16 @@ const EventDetails: React.FC = () => {
|
|||||||
<Typography variant="h4" component="h1" gutterBottom>
|
<Typography variant="h4" component="h1" gutterBottom>
|
||||||
{event.title}
|
{event.title}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1" paragraph>
|
<Box
|
||||||
{event.description}
|
sx={{
|
||||||
</Typography>
|
'& p': { marginBottom: 1 },
|
||||||
|
'& ul, & ol': { marginLeft: 2, marginBottom: 1 },
|
||||||
|
'& li': { marginBottom: 0.5 },
|
||||||
|
'& h1, & h2, & h3': { marginTop: 2, marginBottom: 1 },
|
||||||
|
'& a': { color: 'primary.main' },
|
||||||
|
}}
|
||||||
|
dangerouslySetInnerHTML={{ __html: event.description }}
|
||||||
|
/>
|
||||||
<Box sx={{ mb: 3 }}>
|
<Box sx={{ mb: 3 }}>
|
||||||
<Typography variant="subtitle1" component="div" gutterBottom>
|
<Typography variant="subtitle1" component="div" gutterBottom>
|
||||||
<strong>Date:</strong> {new Date(event.date).toLocaleDateString()}
|
<strong>Date:</strong> {new Date(event.date).toLocaleDateString()}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import WallpaperIcon from '@mui/icons-material/Wallpaper';
|
|||||||
import DeleteIcon from '@mui/icons-material/Delete';
|
import DeleteIcon from '@mui/icons-material/Delete';
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import RichTextEditor from './RichTextEditor';
|
||||||
|
|
||||||
const DarkTextField = styled(TextField)({
|
const DarkTextField = styled(TextField)({
|
||||||
'& .MuiOutlinedInput-root': {
|
'& .MuiOutlinedInput-root': {
|
||||||
@@ -185,15 +186,12 @@ const EventForm: React.FC = () => {
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<DarkTextField
|
<RichTextEditor
|
||||||
fullWidth
|
|
||||||
label="Description"
|
|
||||||
name="description"
|
|
||||||
value={formData.description}
|
value={formData.description}
|
||||||
onChange={handleChange}
|
onChange={(value) => setFormData((prev) => ({ ...prev, description: value }))}
|
||||||
variant="outlined"
|
label="Description"
|
||||||
multiline
|
placeholder="Enter event description with formatting..."
|
||||||
rows={4}
|
theme="dark"
|
||||||
/>
|
/>
|
||||||
<DarkTextField
|
<DarkTextField
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -435,18 +433,16 @@ const EventForm: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{formData.event_conclusion_email_enabled && (
|
{formData.event_conclusion_email_enabled && (
|
||||||
<DarkTextField
|
<Box sx={{ mt: 2 }}>
|
||||||
fullWidth
|
<RichTextEditor
|
||||||
label="Event conclusion message"
|
value={formData.event_conclusion_message}
|
||||||
name="event_conclusion_message" // Corrected name prop
|
onChange={(value) => setFormData((prev) => ({ ...prev, event_conclusion_message: value }))}
|
||||||
value={formData.event_conclusion_message} // Corrected value prop
|
label="Event conclusion message"
|
||||||
onChange={handleChange}
|
placeholder="Enter the message to send after the event..."
|
||||||
variant="outlined"
|
theme="dark"
|
||||||
multiline
|
helperText="This message will be sent to attendees who opted for email notifications the day after the event."
|
||||||
rows={4}
|
/>
|
||||||
helperText="This message will be sent to attendees who opted for email notifications the day after the event."
|
</Box>
|
||||||
sx={{ mt: 2 }} // Added margin top for spacing
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
195
frontend/src/components/RichTextEditor.tsx
Normal file
195
frontend/src/components/RichTextEditor.tsx
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactQuill from 'react-quill';
|
||||||
|
import 'react-quill/dist/quill.snow.css';
|
||||||
|
import { Box, Typography, styled } from '@mui/material';
|
||||||
|
|
||||||
|
const StyledEditorContainer = styled(Box)(({ theme }) => ({
|
||||||
|
'& .quill': {
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.05)',
|
||||||
|
borderRadius: theme.shape.borderRadius,
|
||||||
|
border: '1px solid rgba(255, 255, 255, 0.23)',
|
||||||
|
'&:hover': {
|
||||||
|
borderColor: 'rgba(255, 255, 255, 0.4)',
|
||||||
|
},
|
||||||
|
'&:focus-within': {
|
||||||
|
borderColor: theme.palette.primary.main,
|
||||||
|
borderWidth: '2px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'& .ql-toolbar': {
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.08)',
|
||||||
|
borderTopLeftRadius: theme.shape.borderRadius,
|
||||||
|
borderTopRightRadius: theme.shape.borderRadius,
|
||||||
|
border: 'none',
|
||||||
|
borderBottom: '1px solid rgba(255, 255, 255, 0.23)',
|
||||||
|
},
|
||||||
|
'& .ql-container': {
|
||||||
|
borderBottomLeftRadius: theme.shape.borderRadius,
|
||||||
|
borderBottomRightRadius: theme.shape.borderRadius,
|
||||||
|
border: 'none',
|
||||||
|
fontFamily: theme.typography.fontFamily,
|
||||||
|
fontSize: '1rem',
|
||||||
|
minHeight: '150px',
|
||||||
|
},
|
||||||
|
'& .ql-editor': {
|
||||||
|
minHeight: '150px',
|
||||||
|
color: 'rgba(255, 255, 255, 0.9)',
|
||||||
|
'&.ql-blank::before': {
|
||||||
|
color: 'rgba(255, 255, 255, 0.5)',
|
||||||
|
fontStyle: 'normal',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'& .ql-stroke': {
|
||||||
|
stroke: 'rgba(255, 255, 255, 0.7) !important',
|
||||||
|
},
|
||||||
|
'& .ql-fill': {
|
||||||
|
fill: 'rgba(255, 255, 255, 0.7) !important',
|
||||||
|
},
|
||||||
|
'& .ql-picker-label': {
|
||||||
|
color: 'rgba(255, 255, 255, 0.7) !important',
|
||||||
|
},
|
||||||
|
'& .ql-picker-options': {
|
||||||
|
backgroundColor: 'rgb(30, 30, 30) !important',
|
||||||
|
border: '1px solid rgba(255, 255, 255, 0.23) !important',
|
||||||
|
},
|
||||||
|
'& .ql-picker-item': {
|
||||||
|
color: 'rgba(255, 255, 255, 0.9) !important',
|
||||||
|
'&:hover': {
|
||||||
|
color: '#90caf9 !important',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'& .ql-toolbar button:hover .ql-stroke': {
|
||||||
|
stroke: '#90caf9 !important',
|
||||||
|
},
|
||||||
|
'& .ql-toolbar button:hover .ql-fill': {
|
||||||
|
fill: '#90caf9 !important',
|
||||||
|
},
|
||||||
|
'& .ql-toolbar button.ql-active .ql-stroke': {
|
||||||
|
stroke: '#90caf9 !important',
|
||||||
|
},
|
||||||
|
'& .ql-toolbar button.ql-active .ql-fill': {
|
||||||
|
fill: '#90caf9 !important',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const LightEditorContainer = styled(Box)(({ theme }) => ({
|
||||||
|
'& .quill': {
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderRadius: theme.shape.borderRadius,
|
||||||
|
border: '1px solid rgba(0, 0, 0, 0.23)',
|
||||||
|
'&:hover': {
|
||||||
|
borderColor: 'rgba(0, 0, 0, 0.87)',
|
||||||
|
},
|
||||||
|
'&:focus-within': {
|
||||||
|
borderColor: theme.palette.primary.main,
|
||||||
|
borderWidth: '2px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'& .ql-toolbar': {
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.02)',
|
||||||
|
borderTopLeftRadius: theme.shape.borderRadius,
|
||||||
|
borderTopRightRadius: theme.shape.borderRadius,
|
||||||
|
border: 'none',
|
||||||
|
borderBottom: '1px solid rgba(0, 0, 0, 0.12)',
|
||||||
|
},
|
||||||
|
'& .ql-container': {
|
||||||
|
borderBottomLeftRadius: theme.shape.borderRadius,
|
||||||
|
borderBottomRightRadius: theme.shape.borderRadius,
|
||||||
|
border: 'none',
|
||||||
|
fontFamily: theme.typography.fontFamily,
|
||||||
|
fontSize: '1rem',
|
||||||
|
minHeight: '150px',
|
||||||
|
},
|
||||||
|
'& .ql-editor': {
|
||||||
|
minHeight: '150px',
|
||||||
|
color: 'rgba(0, 0, 0, 0.87)',
|
||||||
|
'&.ql-blank::before': {
|
||||||
|
color: 'rgba(0, 0, 0, 0.5)',
|
||||||
|
fontStyle: 'normal',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface RichTextEditorProps {
|
||||||
|
value: string;
|
||||||
|
onChange: (value: string) => void;
|
||||||
|
label?: string;
|
||||||
|
placeholder?: string;
|
||||||
|
theme?: 'dark' | 'light';
|
||||||
|
helperText?: string;
|
||||||
|
minHeight?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RichTextEditor: React.FC<RichTextEditorProps> = ({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
label,
|
||||||
|
placeholder,
|
||||||
|
theme = 'dark',
|
||||||
|
helperText,
|
||||||
|
minHeight = 150,
|
||||||
|
}) => {
|
||||||
|
const modules = {
|
||||||
|
toolbar: [
|
||||||
|
[{ 'header': [1, 2, 3, false] }],
|
||||||
|
['bold', 'italic', 'underline', 'strike'],
|
||||||
|
[{ 'list': 'ordered' }, { 'list': 'bullet' }],
|
||||||
|
[{ 'indent': '-1' }, { 'indent': '+1' }],
|
||||||
|
['link'],
|
||||||
|
['clean'],
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const formats = [
|
||||||
|
'header',
|
||||||
|
'bold', 'italic', 'underline', 'strike',
|
||||||
|
'list', 'bullet',
|
||||||
|
'indent',
|
||||||
|
'link',
|
||||||
|
];
|
||||||
|
|
||||||
|
const Container = theme === 'dark' ? StyledEditorContainer : LightEditorContainer;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ width: '100%' }}>
|
||||||
|
{label && (
|
||||||
|
<Typography
|
||||||
|
variant="subtitle2"
|
||||||
|
sx={{
|
||||||
|
mb: 1,
|
||||||
|
color: theme === 'dark' ? 'rgba(255, 255, 255, 0.7)' : 'rgba(0, 0, 0, 0.6)',
|
||||||
|
fontSize: '0.875rem',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
<Container>
|
||||||
|
<ReactQuill
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
modules={modules}
|
||||||
|
formats={formats}
|
||||||
|
placeholder={placeholder}
|
||||||
|
theme="snow"
|
||||||
|
style={{ minHeight: `${minHeight}px` }}
|
||||||
|
/>
|
||||||
|
</Container>
|
||||||
|
{helperText && (
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
sx={{
|
||||||
|
mt: 0.5,
|
||||||
|
ml: 1.5,
|
||||||
|
display: 'block',
|
||||||
|
color: theme === 'dark' ? 'rgba(255, 255, 255, 0.6)' : 'rgba(0, 0, 0, 0.6)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{helperText}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RichTextEditor;
|
||||||
Reference in New Issue
Block a user