hãy tạo một trang reactjs với tính năng cho phép admin load nội dung help từ file .html hoặc txt, sau đó hiện lên UI cho user, nếu login bằng admin thì hiện link edit --> click vào link này thì mở ra dialog để admin sửa nội dung help trong một HtmlEditor
Browse files- components/HelpSystem.js +109 -0
components/HelpSystem.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState, useEffect } from 'react';
|
| 2 |
+
import DOMPurify from 'dompurify';
|
| 3 |
+
import { Editor } from '@tinymce/tinymce-react';
|
| 4 |
+
|
| 5 |
+
const HelpSystem = ({ isAdmin = false }) => {
|
| 6 |
+
const [helpContent, setHelpContent] = useState('');
|
| 7 |
+
const [editMode, setEditMode] = useState(false);
|
| 8 |
+
const [loading, setLoading] = useState(true);
|
| 9 |
+
const [error, setError] = useState(null);
|
| 10 |
+
|
| 11 |
+
// Load help content from API or file
|
| 12 |
+
useEffect(() => {
|
| 13 |
+
const loadHelpContent = async () => {
|
| 14 |
+
try {
|
| 15 |
+
const response = await fetch('/api/help');
|
| 16 |
+
if (!response.ok) throw new Error('Failed to load help content');
|
| 17 |
+
const data = await response.text();
|
| 18 |
+
setHelpContent(data);
|
| 19 |
+
} catch (err) {
|
| 20 |
+
setError(err.message);
|
| 21 |
+
} finally {
|
| 22 |
+
setLoading(false);
|
| 23 |
+
}
|
| 24 |
+
};
|
| 25 |
+
loadHelpContent();
|
| 26 |
+
}, []);
|
| 27 |
+
|
| 28 |
+
const handleSave = async (newContent) => {
|
| 29 |
+
try {
|
| 30 |
+
const response = await fetch('/api/help', {
|
| 31 |
+
method: 'POST',
|
| 32 |
+
headers: {
|
| 33 |
+
'Content-Type': 'application/json',
|
| 34 |
+
},
|
| 35 |
+
body: JSON.stringify({ content: newContent }),
|
| 36 |
+
});
|
| 37 |
+
|
| 38 |
+
if (!response.ok) throw new Error('Failed to save help content');
|
| 39 |
+
|
| 40 |
+
setHelpContent(newContent);
|
| 41 |
+
setEditMode(false);
|
| 42 |
+
} catch (err) {
|
| 43 |
+
setError(err.message);
|
| 44 |
+
}
|
| 45 |
+
};
|
| 46 |
+
|
| 47 |
+
if (loading) return <div className="p-4 text-center">Loading help content...</div>;
|
| 48 |
+
if (error) return <div className="p-4 text-red-500">Error: {error}</div>;
|
| 49 |
+
|
| 50 |
+
return (
|
| 51 |
+
<div className="max-w-4xl mx-auto p-6 bg-white dark:bg-gray-800 rounded-lg shadow">
|
| 52 |
+
<div className="flex justify-between items-center mb-4">
|
| 53 |
+
<h2 className="text-xl font-bold text-gray-900 dark:text-white">Help Center</h2>
|
| 54 |
+
{isAdmin && !editMode && (
|
| 55 |
+
<button
|
| 56 |
+
onClick={() => setEditMode(true)}
|
| 57 |
+
className="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400"
|
| 58 |
+
>
|
| 59 |
+
Edit Content
|
| 60 |
+
</button>
|
| 61 |
+
)}
|
| 62 |
+
</div>
|
| 63 |
+
|
| 64 |
+
{editMode ? (
|
| 65 |
+
<div className="space-y-4">
|
| 66 |
+
<Editor
|
| 67 |
+
apiKey="your-tinymce-api-key"
|
| 68 |
+
value={helpContent}
|
| 69 |
+
init={{
|
| 70 |
+
height: 500,
|
| 71 |
+
menubar: true,
|
| 72 |
+
plugins: [
|
| 73 |
+
'advlist autolink lists link image charmap print preview anchor',
|
| 74 |
+
'searchreplace visualblocks code fullscreen',
|
| 75 |
+
'insertdatetime media table paste code help wordcount'
|
| 76 |
+
],
|
| 77 |
+
toolbar:
|
| 78 |
+
'undo redo | formatselect | bold italic backcolor | \
|
| 79 |
+
alignleft aligncenter alignright alignjustify | \
|
| 80 |
+
bullist numlist outdent indent | removeformat | help'
|
| 81 |
+
}}
|
| 82 |
+
onEditorChange={(newContent) => setHelpContent(newContent)}
|
| 83 |
+
/>
|
| 84 |
+
<div className="flex space-x-3">
|
| 85 |
+
<button
|
| 86 |
+
onClick={() => handleSave(helpContent)}
|
| 87 |
+
className="px-4 py-2 bg-primary-500 text-white rounded hover:bg-primary-600"
|
| 88 |
+
>
|
| 89 |
+
Save Changes
|
| 90 |
+
</button>
|
| 91 |
+
<button
|
| 92 |
+
onClick={() => setEditMode(false)}
|
| 93 |
+
className="px-4 py-2 bg-gray-300 text-gray-700 rounded hover:bg-gray-400"
|
| 94 |
+
>
|
| 95 |
+
Cancel
|
| 96 |
+
</button>
|
| 97 |
+
</div>
|
| 98 |
+
</div>
|
| 99 |
+
) : (
|
| 100 |
+
<div
|
| 101 |
+
className="prose dark:prose-invert max-w-none"
|
| 102 |
+
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(helpContent) }}
|
| 103 |
+
/>
|
| 104 |
+
)}
|
| 105 |
+
</div>
|
| 106 |
+
);
|
| 107 |
+
};
|
| 108 |
+
|
| 109 |
+
export default HelpSystem;
|