access logs + audio

This commit is contained in:
hamid zarghami
2025-09-02 17:00:37 +03:30
parent 860f7c446f
commit 0e16af827f
25 changed files with 3394 additions and 83 deletions
+48 -31
View File
@@ -1,18 +1,16 @@
import { FC, useEffect, useRef, useState } from 'react'
import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Input from '../../components/Input'
import Button from '../../components/Button'
import SwitchComponent from '../../components/Switch'
import { TickCircle } from 'iconsax-react'
import "quill/dist/quill.snow.css";
// import Quill from 'quill';
import { useFormik } from 'formik'
import { BlogCategoryType, CreateBlogType } from './types/BlogTypes'
import { useCreateBlog, useGetBlogCategories } from './hooks/useBlogData'
import * as Yup from 'yup'
import CheckBoxComponent from '../../components/CheckBoxComponent'
import UploadBoxDraggble from '../../components/UploadBoxDraggble'
import Quill from 'quill'
import QuillEditor from '../../components/QuillEditor'
import { useSingleUpload } from '../service/hooks/useServiceData'
import { toast } from 'react-toastify'
import { ErrorType } from '../../helpers/types'
@@ -22,30 +20,9 @@ const CreateBlog: FC = () => {
const { t } = useTranslation('global')
const [file, setFile] = useState<File>()
const [audioFile, setAudioFile] = useState<File>()
const singleSlug = useSingleUpload()
const editorRef = useRef<HTMLDivElement>(null)
const editorRef2 = useRef<HTMLDivElement>(null)
useEffect(() => {
const quill = new Quill('#editor', {
theme: 'snow',
});
quill.on('text-change', () => {
const html = editorRef.current?.querySelector('.ql-editor')?.innerHTML || '';
formik.setFieldValue('previewContent', html);
});
const quill2 = new Quill('#editor2', {
theme: 'snow',
});
quill2.on('text-change', () => {
const html = editorRef2.current?.querySelector('.ql-editor')?.innerHTML || '';
formik.setFieldValue('content', html);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const audioUpload = useSingleUpload()
const navigate = useNavigate()
const createBlog = useCreateBlog()
@@ -57,6 +34,7 @@ const CreateBlog: FC = () => {
previewContent: '',
content: '',
imageUrl: '',
audioUrl: '',
isActive: false,
isPinned: false,
metaDescription: '',
@@ -80,6 +58,12 @@ const CreateBlog: FC = () => {
toast.error(t('errors.required'))
return
}
if (!audioFile) {
values.audioUrl = undefined
}
// آپلود تصویر
const formData = new FormData()
formData.append('file', file)
await singleSlug.mutateAsync(formData, {
@@ -88,6 +72,17 @@ const CreateBlog: FC = () => {
},
})
// آپلود فایل صوتی در صورت وجود
if (audioFile) {
const audioFormData = new FormData()
audioFormData.append('file', audioFile)
await audioUpload.mutateAsync(audioFormData, {
onSuccess(data) {
values.audioUrl = data.data.url
},
})
}
createBlog.mutate(values, {
onSuccess() {
toast.success(t('success'))
@@ -110,7 +105,7 @@ const CreateBlog: FC = () => {
<Button
className='w-[172px]'
onClick={() => formik.handleSubmit()}
isLoading={createBlog.isPending || singleSlug.isPending}
isLoading={createBlog.isPending || singleSlug.isPending || audioUpload.isPending}
>
<div className='flex gap-2 items-center'>
<TickCircle size={20} color='white' />
@@ -141,7 +136,13 @@ const CreateBlog: FC = () => {
<p className='mt-6 text-sm'>{t('blog.blog_summary')}</p>
<div className='mt-1'>
<div className='min-h-[120px]' ref={editorRef} id='editor'></div>
<QuillEditor
id="preview-editor"
value={formik.values.previewContent}
onChange={(content) => formik.setFieldValue('previewContent', content)}
placeholder={t('blog.enter_blog_summary')}
minHeight="120px"
/>
</div>
{
@@ -152,7 +153,13 @@ const CreateBlog: FC = () => {
<p className='mt-6 text-sm'>{t('blog.content')}</p>
<div className='mt-1'>
<div className='min-h-[200px]' ref={editorRef2} id='editor2'></div>
<QuillEditor
id="content-editor"
value={formik.values.content}
onChange={(content) => formik.setFieldValue('content', content)}
placeholder={t('blog.enter_blog_content')}
minHeight="200px"
/>
</div>
{
@@ -216,7 +223,7 @@ const CreateBlog: FC = () => {
checked={formik.values.categoryId === item.id}
onChange={(e) => formik.setFieldValue('categoryId', e.target.checked ? item.id : '')}
/>
<div>{item.title}</div>
<div className='text-[13px]'>{item.title}</div>
</div>
)
})
@@ -231,6 +238,16 @@ const CreateBlog: FC = () => {
/>
</div>
</div>
<div className='mt-8'>
<p className='text-sm'>{t('blog.audio_file')}</p>
<div className='mt-4'>
<UploadBoxDraggble
label={t('blog.audio_file')}
onChange={(files) => setAudioFile(files[0])}
accept="audio/*"
/>
</div>
</div>
<p className='mt-6 text-sm'>{t('blog.tags')}</p>
<div className='mt-6'>
<Input
+55 -41
View File
@@ -1,18 +1,16 @@
import { FC, useEffect, useRef, useState } from 'react'
import { FC, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Input from '../../components/Input'
import Button from '../../components/Button'
import SwitchComponent from '../../components/Switch'
import { TickCircle } from 'iconsax-react'
import "quill/dist/quill.snow.css";
// import Quill from 'quill';
import { useFormik } from 'formik'
import { BlogCategoryType, CreateBlogType } from './types/BlogTypes'
import { useGetBlogCategories, useGetBlogDetail, useUpdateBlog } from './hooks/useBlogData'
import * as Yup from 'yup'
import CheckBoxComponent from '../../components/CheckBoxComponent'
import UploadBoxDraggble from '../../components/UploadBoxDraggble'
import Quill from 'quill'
import QuillEditor from '../../components/QuillEditor'
import { useSingleUpload } from '../service/hooks/useServiceData'
import { toast } from 'react-toastify'
import { ErrorType } from '../../helpers/types'
@@ -23,31 +21,10 @@ const UpdateBlog: FC = () => {
const { id } = useParams()
const { t } = useTranslation('global')
const [file, setFile] = useState<File>()
const [audioFile, setAudioFile] = useState<File>()
const singleSlug = useSingleUpload()
const editorRef = useRef<HTMLDivElement>(null)
const editorRef2 = useRef<HTMLDivElement>(null)
const audioUpload = useSingleUpload()
const getBlogDetail = useGetBlogDetail(id || '')
useEffect(() => {
const quill = new Quill('#editor', {
theme: 'snow',
});
quill.on('text-change', () => {
const html = editorRef.current?.querySelector('.ql-editor')?.innerHTML || '';
formik.setFieldValue('previewContent', html);
});
const quill2 = new Quill('#editor2', {
theme: 'snow',
});
quill2.on('text-change', () => {
const html = editorRef2.current?.querySelector('.ql-editor')?.innerHTML || '';
formik.setFieldValue('content', html);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const navigate = useNavigate()
const updateBlog = useUpdateBlog()
@@ -59,6 +36,7 @@ const UpdateBlog: FC = () => {
previewContent: '',
content: '',
imageUrl: '',
audioUrl: '',
isActive: false,
isPinned: false,
metaDescription: '',
@@ -78,6 +56,7 @@ const UpdateBlog: FC = () => {
slug: Yup.string().required(t('errors.required')),
}),
onSubmit: async (values) => {
// آپلود تصویر در صورت انتخاب فایل جدید
if (file) {
const formData = new FormData()
formData.append('file', file)
@@ -88,6 +67,20 @@ const UpdateBlog: FC = () => {
})
}
if (!audioFile) {
values.audioUrl = undefined
}
// آپلود فایل صوتی در صورت وجود
if (audioFile) {
const audioFormData = new FormData()
audioFormData.append('file', audioFile)
await audioUpload.mutateAsync(audioFormData, {
onSuccess(data) {
values.audioUrl = data.data.url
},
})
}
updateBlog.mutate({ id: id || '', params: values }, {
onSuccess() {
@@ -105,16 +98,6 @@ const UpdateBlog: FC = () => {
if (getBlogDetail.data) {
formik.setValues(getBlogDetail.data?.data?.blog)
formik.setFieldValue('categoryId', getBlogDetail.data?.data?.blog.category.id)
const editorElement = editorRef.current?.querySelector('.ql-editor');
if (editorElement) {
editorElement.innerHTML = getBlogDetail.data?.data?.blog?.previewContent || '';
}
const editorElement2 = editorRef2.current?.querySelector('.ql-editor');
if (editorElement2) {
editorElement2.innerHTML = getBlogDetail.data?.data?.blog?.content || '';
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [getBlogDetail.data])
@@ -129,7 +112,7 @@ const UpdateBlog: FC = () => {
<Button
className='w-[172px]'
onClick={() => formik.handleSubmit()}
isLoading={updateBlog.isPending || singleSlug.isPending}
isLoading={updateBlog.isPending || singleSlug.isPending || audioUpload.isPending}
>
<div className='flex gap-2 items-center'>
<TickCircle size={20} color='white' />
@@ -160,7 +143,13 @@ const UpdateBlog: FC = () => {
<p className='mt-6 text-sm'>{t('blog.blog_summary')}</p>
<div className='mt-1'>
<div className='min-h-[120px]' ref={editorRef} id='editor'></div>
<QuillEditor
id="preview-editor-update"
value={formik.values.previewContent}
onChange={(content) => formik.setFieldValue('previewContent', content)}
placeholder={t('blog.enter_blog_summary')}
minHeight="120px"
/>
</div>
{
@@ -171,7 +160,13 @@ const UpdateBlog: FC = () => {
<p className='mt-6 text-sm'>{t('blog.content')}</p>
<div className='mt-1'>
<div className='min-h-[200px]' ref={editorRef2} id='editor2'></div>
<QuillEditor
id="content-editor-update"
value={formik.values.content}
onChange={(content) => formik.setFieldValue('content', content)}
placeholder={t('blog.enter_blog_content')}
minHeight="200px"
/>
</div>
{
@@ -235,7 +230,7 @@ const UpdateBlog: FC = () => {
checked={formik.values.categoryId === item.id}
onChange={(e) => formik.setFieldValue('categoryId', e.target.checked ? item.id : '')}
/>
<div>{item.title}</div>
<div className='text-[13px]'>{item.title}</div>
</div>
)
})
@@ -250,6 +245,25 @@ const UpdateBlog: FC = () => {
/>
</div>
</div>
<div className='mt-8'>
<p className='text-sm'>{t('blog.audio_file')}</p>
<div className='mt-4'>
<UploadBoxDraggble
label={t('blog.audio_file')}
onChange={(files) => setAudioFile(files[0])}
accept="audio/*"
/>
</div>
{formik.values.audioUrl && (
<div className='mt-2'>
<p className='text-xs text-gray-500'>{t('blog.current_audio_file')}:</p>
<audio controls className='w-full mt-1'>
<source src={formik.values.audioUrl} type="audio/mpeg" />
{t('blog.audio_not_supported')}
</audio>
</div>
)}
</div>
<p className='mt-6 text-sm'>{t('blog.tags')}</p>
<div className='mt-6'>
<Input
+1
View File
@@ -3,6 +3,7 @@ export type CreateBlogType = {
previewContent: string;
content: string;
imageUrl: string;
audioUrl?: string;
metaTitle: string;
metaDescription: string;
tags: string[];