diff --git a/app/(full-page)/auth/login/page.tsx b/app/(full-page)/auth/login/page.tsx index d3dd85ca..0e618a8e 100644 --- a/app/(full-page)/auth/login/page.tsx +++ b/app/(full-page)/auth/login/page.tsx @@ -8,56 +8,110 @@ import { Password } from 'primereact/password'; import { LayoutContext } from '../../../../layout/context/layoutcontext'; import { InputText } from 'primereact/inputtext'; import { classNames } from 'primereact/utils'; +import InfoBanner from '@/app/components/InfoBanner'; + +import { useForm } from 'react-hook-form'; +import { schema } from '@/schemas/authSchema'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { Controller } from 'react-hook-form'; +import { getUser, login } from '@/services/auth'; +import FancyLinkBtn from '@/app/components/buttons/FancyLinkBtn'; +import { getToken } from '@/utils/auth'; +import { logout } from '@/utils/logout'; +import { LoginType } from '@/types/login'; const LoginPage = () => { - const [password, setPassword] = useState(''); - const [checked, setChecked] = useState(false); - const { layoutConfig } = useContext(LayoutContext); + const { layoutConfig, setUser, setMessage, setGlobalLoading } = useContext(LayoutContext); const router = useRouter(); - const containerClassName = classNames('surface-ground flex align-items-center justify-content-center min-h-screen min-w-screen overflow-hidden', { 'p-input-filled': layoutConfig.inputStyle === 'filled' }); + // const containerClassName = classNames('surface-ground flex align-items-center justify-content-center min-h-screen min-w-screen overflow-hidden', { 'p-input-filled': layoutConfig.inputStyle === 'filled' }); - return ( -
-
- Sakai logo -
-
-
- Image -
Welcome, Isabel!
- Sign in to continue -
+ const { + register, + handleSubmit, + formState: { errors }, + control + } = useForm({ + resolver: yupResolver(schema), + mode: 'onChange' + }); -
- - + const onSubmit = async (value: LoginType) => { + console.log('Данные пользователя: ', value); - - setPassword(e.target.value)} placeholder="Password" toggleMask className="w-full mb-5" inputClassName="w-full p-3 md:w-30rem"> + const user = await login(value); + console.log(user); + if (user && user.success) { + document.cookie = `access_token=${user.token.access_token}; path=/; Secure; SameSite=Strict; expires=${user.token.expires_at}`; + + const token = user.token.access_token; + if (token) { + const res = await getUser(); + try { + if (res?.success) { + console.log(res); + if (res?.user.is_working) { + window.location.href = '/course'; + } + } else { + logout({ setUser, setGlobalLoading }); + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при авторизации' } + }); // messege - Ошибка при авторизации + console.log('Ошибка при получении пользователя'); + } + } catch (error) { + logout({ setUser, setGlobalLoading }); + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при авторизации' } + }); // messege - Ошибка при авторизации + console.log('Ошибка при получении пользователя'); + } + } + } else { + console.log('Ошибка при авторизации'); + } + }; + + const onError = (errors:any) => { + console.log('Ошибки формы:', errors); + setMessage({ + state: true, + value: { severity: 'error', summary: 'Катаа', detail: 'ПОвтор' } + }); // messege - Ошибка при авторизации + }; -
-
- setChecked(e.checked ?? false)} className="mr-2"> - -
- - Forgot password? - -
- + return ( +
+ +
+
+ +
+ +
+
+
+ + + {errors.email && {errors.email.message}} +
+
+ } + /> + {errors.password && {errors.password.message}}
-
+ + +
diff --git a/app/(full-page)/pages/notfound/page.tsx b/app/(full-page)/pages/notfound/page.tsx index 52c0b4ce..c9c4dc0e 100644 --- a/app/(full-page)/pages/notfound/page.tsx +++ b/app/(full-page)/pages/notfound/page.tsx @@ -49,6 +49,6 @@ const NotFoundPage = () => {
); -}; +}; export default NotFoundPage; diff --git a/app/(main)/course/[courseTheme]/[lessons]/page.tsx b/app/(main)/course/[courseTheme]/[lessons]/page.tsx new file mode 100644 index 00000000..e0677f91 --- /dev/null +++ b/app/(main)/course/[courseTheme]/[lessons]/page.tsx @@ -0,0 +1,270 @@ +'use client'; + +import { useContext, useEffect, useState } from 'react'; +import { TabView, TabPanel } from 'primereact/tabview'; +import CKEditorWrapper from '@/app/components/CKEditorWrapper.tsx'; +import { Button } from 'primereact/button'; +import Redacting from '@/app/components/popUp/Redacting'; +import { addLesson, deleteLesson, fetchLesson, updateLesson } from '@/services/courses'; +import { getToken } from '@/utils/auth'; +import { useParams } from 'next/navigation'; +import { LayoutContext } from '@/layout/context/layoutcontext'; +import useErrorMessage from '@/hooks/useErrorMessage'; +import { getRedactor } from '@/utils/getRedactor'; +import { getConfirmOptions } from '@/utils/getConfirmOptions'; +import LessonTyping from '@/app/components/lessons/LessonTyping'; +import { TabViewChange } from '@/types/tabViewChange'; + +export default function Lesson() { + const [activeIndex, setActiveIndex] = useState(0); + const [contentShow, setContentShow] = useState(true); + const [textShow, setTextShow] = useState(false); + const [editMode, setEditMode] = useState(false); + const [editingLesson, setEditingLesson] = useState(''); + const [textValue, setTextValue] = useState<{ id: number | null }>({ id: null }); + const { setMessage } = useContext(LayoutContext); + + const showError = useErrorMessage(); + const [sentValues, setSentValues] = useState(''); + + const params = useParams(); + const courseId = params.courseTheme; + const lessonId = params.lessons; + + const handleText = (e: string) => { + setSentValues(e); + }; + + const handleFetchLesson = async () => { + // skeleton = false + + const data = await fetchLesson('text', courseId ? Number(courseId) : null, lessonId ? Number(lessonId) : null); + console.log(data); + + if (data?.success) { + if (data?.content?.content) { + setTextValue({ id: data?.content.id }); + setSentValues(data.content.content); + setTextShow(true); + } else { + setTextShow(false); + } + } else { + setTextShow(false); + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Проблема с соединением. Повторите заново' } + }); + if (data?.response?.status) { + showError(data.response.status); + } + // skeleton = false + } + }; + + const handleAddLesson = async () => { + const token = getToken('access_token'); + + const data = await addLesson('text', token, courseId ? Number(courseId) : null, lessonId ? Number(lessonId) : null, String(sentValues), null); + if (data?.success) { + handleFetchLesson(); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү Кошулдуу!', detail: '' } + }); + } else { + setEditingLesson(''); + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при при добавлении' } + }); + if (data?.response?.status) { + showError(data.response.status); + } + } + }; + + const handleDeleteLesson = async () => { + const data = await deleteLesson('text', Number(courseId), Number(lessonId), textValue.id); + if (data.success) { + handleFetchLesson(); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү өчүрүлдү!', detail: '' } + }); + } else { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при при удалении' } + }); + if (data.response.status) { + showError(data.response.status); + } + } + }; + + const editing = async () => { + setTextShow(false); + setEditMode(true); + + const token = getToken('access_token'); + const data = await fetchLesson('text', courseId ? Number(courseId) : null, lessonId ? Number(lessonId) : null); + if (data.success) { + setEditingLesson(data.content.content); + } + }; + + const clearValues = () => { + // handleFetchLesson(); пока стоит до полной реконструкции компонента + setSentValues(''); + setEditingLesson(''); + setTextShow(true); + setEditMode(false); + }; + + const handleUpdateLesson = async () => { + const token = getToken('access_token'); + const data = await updateLesson('text', token, courseId ? Number(courseId) : null, lessonId ? Number(lessonId) : null, textValue.id, sentValues); + console.log(data); + + if (data.success) { + handleFetchLesson(); + clearValues(); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү өзгөртүлдү!', detail: '' } + }); + } else { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при при изменении темы' } + }); + if (data.response.status) { + showError(data.response.status); + } + } + }; + + const handleTabChange = (e: TabViewChange) => { + if (e.index === 0) handleFetchLesson(); + setActiveIndex(e.index); + }; + + // USE EFFECTS + useEffect(() => { + console.log(sentValues); + }, [sentValues]); + + return ( +
+ {/* header section */} + handleTabChange(e)} + activeIndex={activeIndex} + className="main-bg" + pt={{ + nav: { className: 'flex flex-wrap justify-around' }, + panelContainer: { className: 'flex-1 pl-4' } + }} + > + {/* CKEDITOR */} + + {contentShow && ( +
+ {textShow ? ( +
+
+
+
+ + Текст +
+ + + {/* */} +
+
+ + xx-xx-xx +
+
+
+
+ {/* {sentValues.text} */} +
+
+ ) : ( +
+ {editMode ? ( + <> + handleText(e)} /> +
+
+ + ) : ( + <> + +
+ )} +
+ )} + + + {/* DOC */} + + {contentShow && } + + + {/* USEFUL LINKS */} + + {contentShow && } + + + {/* VIDEO */} + + {contentShow && } + {/* && ( +
+
+ +
+
+ ) */} +
+ +
+ ); +} diff --git a/app/(main)/course/[courseTheme]/page.tsx b/app/(main)/course/[courseTheme]/page.tsx new file mode 100644 index 00000000..e08d1a4c --- /dev/null +++ b/app/(main)/course/[courseTheme]/page.tsx @@ -0,0 +1,276 @@ +'use client'; + +import ConfirmModal from '@/app/components/popUp/ConfirmModal'; +import FormModal from '@/app/components/popUp/FormModal'; +import GroupSkeleton from '@/app/components/skeleton/GroupSkeleton'; +import Link from 'next/link'; +import { Button } from 'primereact/button'; +import { Column } from 'primereact/column'; +import { DataTable } from 'primereact/datatable'; +import { InputText } from 'primereact/inputtext'; +import { LayoutContext } from '@/layout/context/layoutcontext'; +import React, { useContext, useEffect, useState } from 'react'; +import { getToken } from '@/utils/auth'; +import { useParams } from 'next/navigation'; +import { addThemes, deleteTheme, fetchCourseInfo, fetchThemes, updateTheme } from '@/services/courses'; +import useErrorMessage from '@/hooks/useErrorMessage'; +import { CourseCreateType } from '@/types/courseCreateType'; +import { NotFound } from '@/app/components/NotFound'; + +export default function CourseTheme() { + const [hasThemes, setHasThemes] = useState(false); + const [themes, setThemes] = useState([]); + const [themeValue, setThemeValue] = useState({ title: '', description: '', video_url: '' }); + const [themeInfo, setThemeInfo] = useState(); + const [courseTitle, setCourseTitle] = useState(''); + const [selectedCourse, setSelectedCourse] = useState<{ id: number | null }>({ id: null }); + const [formVisible, setFormVisible] = useState(false); + const [editMode, setEditMode] = useState(false); + const [forStart, setForStart] = useState(false); + const [skeleton, setSkeleton] = useState(false); + const { setMessage } = useContext(LayoutContext); + + const { courseTheme } = useParams() as { courseTheme: string }; + + const showError = useErrorMessage(); + + const handleFetchThemes = async () => { + + const data = await fetchThemes(Number(courseTheme)); + console.log(data); + + if (data?.lessons) { + setHasThemes(false); + setThemes(data.lessons.data); + } else { + setHasThemes(true); + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Проблема с соединением. Повторите заново' } + }); // messege - Ошибка загрузки курсов + if (data?.response?.status) { + showError(data.response.status); + } + } + + console.log('he he bay '); + + }; + + const handleFetchInfo = async () => { + const data = await fetchCourseInfo(Number(courseTheme)); + + if (data.success) { + setThemeInfo(data.course); + } + }; + + const handleAddTheme = async () => { + if (themeValue.title.length < 1) { + alert('hi'); + return null; + } + + const data = await addThemes(Number(courseTheme), themeValue.title); + console.log(data); + + if (data.success) { + toggleSkeleton(); + handleFetchThemes(); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү кошулду!', detail: '' } + }); // messege - Успех! + } else { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при при добавлении' } + }); // messege - Ошибка при добавлении + if (data.response.status) { + showError(data.response.status); + } + } + }; + + const handleDeleteCourse = async (id: number) => { + + const data = await deleteTheme(id); + if (data.success) { + toggleSkeleton(); + handleFetchThemes(); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү өчүрүлдү!', detail: '' } + }); // messege - Успех! + } else { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при при удалении' } + }); // messege - Ошибка при добавлении + if (data.response.status) { + showError(data.response.status); + } + } + }; + + const handleUpdateTheme = async () => { + const data = await updateTheme(Number(courseTheme), selectedCourse.id, themeValue); + if (data.success) { + toggleSkeleton(); + handleFetchThemes(); + clearValues(); + setEditMode(false); + setSelectedCourse({ id: null }); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү өзгөртүлдү!', detail: '' } + }); // messege - Успех! + } else { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка при при изменении темы', detail: 'Заполняйте поля правильно' } + }); // messege - Ошибка при изменении курса + if (data.response.status) { + showError(data.response.status); + } + } + }; + + const clearValues = () => { + setThemeValue({ title: '', description: '', video_url: '' }); + setCourseTitle(''); + setEditMode(false); + setSelectedCourse({ id: null }); + }; + + const getConfirmOptions = (id: number) => ({ + message: 'Сиз чын эле өчүрүүнү каалайсызбы??', + header: 'Өчүрүү', + icon: 'pi pi-info-circle', + defaultFocus: 'reject', + acceptClassName: 'p-button-danger', + acceptLabel: 'Кийинки кадам', // кастомная надпись для "Yes" + rejectLabel: 'Артка', + accept: () => handleDeleteCourse(id), + reject: () => console.log('Удаление отменено') + }); + + const toggleSkeleton = () => { + setSkeleton(true); + setTimeout(() => { + setSkeleton(false); + }, 1000); + }; + + useEffect(() => { + handleFetchInfo(); + handleFetchThemes(); + }, []); + + useEffect(() => { + const title = courseTitle.trim(); + title.length > 0 ? setForStart(false) : setForStart(true); + }, [courseTitle]); + + useEffect(() => { + themes.length < 1 ? setHasThemes(true) : setHasThemes(false); + }, [themes]); + + const titleInfoClass = `${!themeInfo?.image ? 'items-center' : 'w-full'} ${themeInfo?.image ? 'w-1/2' : 'w-full'}`; + const titleImageClass = `${themeInfo?.image ? 'md:w-1/3' : ''}`; + + return ( +
+ {/* title section */} +
+
+

+ {themeInfo?.title} +

+

{themeInfo?.description}

+
+ + {themeInfo?.created_at} + Home/theme +
+
+ +
+ +
+
+ + {/* add button*/} + +
+ )} + /> + + )} +
+ )} +
+ ); +} diff --git a/app/(main)/course/page.tsx b/app/(main)/course/page.tsx new file mode 100644 index 00000000..c10bcc03 --- /dev/null +++ b/app/(main)/course/page.tsx @@ -0,0 +1,624 @@ +'use client'; + +import FormModal from '@/app/components/popUp/FormModal'; +import { addCourse, deleteCourse, fetchCourseInfo, fetchCourses, updateCourse } from '@/services/courses'; +import { getToken } from '@/utils/auth'; +import { Button } from 'primereact/button'; +import { FileUpload, FileUploadSelectEvent } from 'primereact/fileupload'; +import { InputText } from 'primereact/inputtext'; +import { useContext, useEffect, useState } from 'react'; +import { LayoutContext } from '@/layout/context/layoutcontext'; +import { InputTextarea } from 'primereact/inputtextarea'; +import { DataTable } from 'primereact/datatable'; +import { Column } from 'primereact/column'; +import GroupSkeleton from '@/app/components/skeleton/GroupSkeleton'; +import Link from 'next/link'; +import { CourseCreateType } from '@/types/courseCreateType'; +import { CourseType } from '@/types/courseType'; +import { Paginator } from 'primereact/paginator'; +import { NotFound } from '@/app/components/NotFound'; +import Redacting from '@/app/components/popUp/Redacting'; +import { getRedactor } from '@/utils/getRedactor'; +import { getConfirmOptions } from '@/utils/getConfirmOptions'; +import useErrorMessage from '@/hooks/useErrorMessage'; +import { mainCourseType } from '@/types/mainCourseType'; +import StreamList from '@/app/components/tables/StreamList'; +import { TabPanel, TabView } from 'primereact/tabview'; +import { TabViewChange } from '@/types/tabViewChange'; +import { useMediaQuery } from '@/hooks/useMediaQuery'; + +export default function Course() { + const [courses, setCourses] = useState([]); + const [hasCourses, setHasCourses] = useState(false); + const [courseValue, setCourseValue] = useState({ title: '', description: '', video_url: '', image: '' }); + const [editMode, setEditMode] = useState(false); + const [selectedCourse, setSelectedCourse] = useState(null); + const [formVisible, setFormVisible] = useState(false); + const [forStart, setForStart] = useState(false); + const [skeleton, setSkeleton] = useState(false); + const [pagination, setPagination] = useState({ + currentPage: 1, + total: 0, + perPage: 0 + }); + const [activeIndex, setActiveIndex] = useState(0); + + const [editingLesson, setEditingLesson] = useState({ + title: '', + description: '', + video_url: '', + image: '', + created_at: '' + }); + + const shablon = [ + { + created_at: '', + id: 3, + image: '', + status: true, + title: 'course-1', + user_id: 2 + }, + { + created_at: '', + id: 4, + image: '', + status: true, + title: 'course-2', + user_id: 2 + }, + { + created_at: '', + id: 5, + image: '', + status: true, + title: 'course-3', + user_id: 2 + }, + { + created_at: '', + id: 6, + image: '', + status: true, + title: 'course-4', + user_id: 2 + }, + { + created_at: '', + id: 7, + image: '', + status: true, + title: 'course-5', + user_id: 2 + }, + { + created_at: '', + id: 8, + image: '', + status: true, + title: 'course-6', + user_id: 5 + } + ]; + + const [forStreamId, setForStreamId] = useState<{ id: number | null; title: string } | null>(null); + + const { setMessage, setGlobalLoading } = useContext(LayoutContext); + const showError = useErrorMessage(); + + const media = useMediaQuery('(max-width: 640px)'); + + const toggleSkeleton = () => { + // setSkeleton(true); + setTimeout(() => { + setSkeleton(false); + }, 1000); + }; + + const handleFetchCourse = async (page = 1) => { + const data = await fetchCourses(page, 0); + if (data?.courses) { + setHasCourses(false); + setCourses(data.courses.data); + setPagination({ + currentPage: data.courses.current_page, + total: data.courses.total, + perPage: data.courses.per_page + }); + } else { + setHasCourses(true); + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Проблема с соединением. Повторите заново' } + }); + if (data?.response?.status) { + showError(data.response.status); + } + } + }; + + const handleAddCourse = async () => { + const data = await addCourse(courseValue); + if (data?.success) { + toggleSkeleton(); + handleFetchCourse(); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү кошулду!', detail: '' } + }); + } else { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при при добавлении' } + }); + if (data?.response?.status) { + showError(data.response.status); + } + } + }; + + const handleDeleteCourse = async (id: number) => { + + const data = await deleteCourse(id); + if (data?.success) { + toggleSkeleton(); + handleFetchCourse(); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү өчүрүлдү!', detail: '' } + }); // messege - Успех! + } else { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при при удалении' } + }); // messege - Ошибка при добавлении + if (data?.response?.status) { + showError(data.response.status); + } + } + }; + + const clearValues = () => { + setCourseValue({ title: '', description: '', video_url: '', image: '' }); + setEditingLesson({ title: '', description: '', video_url: '', image: '', created_at: '' }); + setEditMode(false); + setSelectedCourse(null); + }; + + const handleUpdateCourse = async () => { + + const data = await updateCourse(selectedCourse, editingLesson); + if (data?.success) { + toggleSkeleton(); + handleFetchCourse(); + clearValues(); + setEditMode(false); + setSelectedCourse(null); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү өзгөртүлдү!', detail: '' } + }); // messege - Успех! + } else { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка при при изменении курса', detail: 'Заполняйте поля правильно' } + }); // messege - Ошибка при изменении курса + if (data?.response?.status) { + showError(data.response.status); + } + } + }; + + const onSelect = (e: FileUploadSelectEvent) => { + console.log(e.files[0]); + editMode + ? setEditingLesson((prev) => ({ + ...prev, + image: e.files[0] + })) + : setCourseValue((prev) => ({ + ...prev, + image: e.files[0] + })); + }; + + const imageBodyTemplate = (product: CourseType) => { + const image = product.image; + + if (typeof image === 'string') { + return ( +
+ Course image +
+ ); + } + + return ( +
+ Course image +
+ ); + }; + + // Ручное управление пагинацией + const handlePageChange = (page: number) => { + handleFetchCourse(page); + }; + + const edit = (rowData: number | null) => { + setEditMode(true); + setSelectedCourse(rowData); + setFormVisible(true); + }; + + const handleTabChange = (e: TabViewChange) => { + if (e.index === 0) { + // handleFetchLesson(); + } + setActiveIndex(e.index); + }; + + useEffect(() => { + // toggleLoading(); + handleFetchCourse(); + }, []); + + useEffect(() => { + const title = editMode ? editingLesson.title.trim() : courseValue.title.trim(); + if (title.length > 0) { + setForStart(false); + } else { + setForStart(true); + } + }, [courseValue.title, editingLesson.title]); + + useEffect(() => { + console.log('Курсы ', courses); + + if (courses.length < 1) { + setHasCourses(true); + } else { + if (courses.length > 0) { + // setForStreamId({ id: courses[0].id, title: courses[0].title }); + } + setHasCourses(false); + } + }, [courses]); + + useEffect(() => { + const handleShow = async () => { + const data = await fetchCourseInfo(selectedCourse); + + if (data?.success) { + setEditingLesson({ + title: data.course.title || '', + video_url: data.course.video_url || '', + description: data.course.description || '', + image: data.course.image + }); + } + }; + + if (editMode) { + handleShow(); + } + }, [editMode]); + + useEffect(() => { + console.log('Для потока ', forStreamId); + }, [forStreamId]); + + return ( +
+ {/* modal window */} + +
+
+
+ + { + editMode + ? setEditingLesson((prev) => ({ + ...prev, + title: e.target.value + })) + : setCourseValue((prev) => ({ + ...prev, + title: e.target.value + })); + }} + /> +
+
+ + { + editMode + ? setEditingLesson((prev) => ({ + ...prev, + video_url: e.target.value + })) + : setCourseValue((prev) => ({ + ...prev, + video_url: e.target.value + })); + }} + /> +
+
+ +
+
+ + { + editMode + ? setEditingLesson((prev) => ({ + ...prev, + description: e.target.value + })) + : setCourseValue((prev) => ({ + ...prev, + description: e.target.value + })); + }} + /> +
+ +
+ + + {courseValue.image || editingLesson.image ? ( +
+ {typeof editingLesson.image === 'string' && ( + <> + Сүрөт: {editingLesson.image} + + )} +
+ ) : ( + jpeg, png, jpg + )} +
+
+
+
+ +
+
+ {media ? ( + <> + handleTabChange(e)} + activeIndex={activeIndex} + // className="main-bg" + pt={{ + nav: { className: 'flex flex-wrap justify-around' }, + panelContainer: { className: 'flex-1 pl-4' } + }} + > + {/* COURSE MOBILE */} + + {/* mobile table section */} + {hasCourses ? ( + <> +
+
+ + + ) : ( +
+ {skeleton ? ( + + ) : ( +
+
+
+ + + rowIndex + 1} header="Номер" style={{ width: '20px' }}> + ( + + {rowData.title} + + )} + > + + + ( + <> + + + )} + > + + ( +
+ +
+ )} + /> +
+ handlePageChange(e.page + 1)} + template="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink" + /> +
+ )} +
+ )} +
+ + {/* STREAMS MOBILE */} + +
+ +
+
+
+ + ) : ( +
+
+ {/* info section */} +
+

Курстар

+
+ {/* table section */} + {hasCourses ? ( + + ) : ( +
+ {skeleton ? ( + + ) : ( + <> + + rowIndex + 1} header="Номер" style={{ width: '20px' }}> + ( + + {rowData.title} + + )} + > + + + + ( + <> + + + )} + > + ( +
+ +
+ )} + /> +
+ handlePageChange(e.page + 1)} + template="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink" + /> + + )} +
+ )} +
+ {/* STREAMS SECTION */} +
+ +
+
+ )} +
+
+
+ ); +} diff --git a/app/(main)/layout.tsx b/app/(main)/layout.tsx index 5cb39fdb..122640dd 100644 --- a/app/(main)/layout.tsx +++ b/app/(main)/layout.tsx @@ -19,7 +19,7 @@ export const metadata: Metadata = { ttl: 604800 }, icons: { - icon: '/favicon.ico' + // icon: '' } }; diff --git a/app/(main)/page.tsx b/app/(main)/teacher/page.tsx similarity index 99% rename from app/(main)/page.tsx rename to app/(main)/teacher/page.tsx index afe44527..a06710d5 100644 --- a/app/(main)/page.tsx +++ b/app/(main)/teacher/page.tsx @@ -6,8 +6,8 @@ import { Column } from 'primereact/column'; import { DataTable } from 'primereact/datatable'; import { Menu } from 'primereact/menu'; import React, { useContext, useEffect, useRef, useState } from 'react'; -import { ProductService } from '../../demo/service/ProductService'; -import { LayoutContext } from '../../layout/context/layoutcontext'; +import { ProductService } from '../../../demo/service/ProductService'; +import { LayoutContext } from '../../../layout/context/layoutcontext'; import Link from 'next/link'; import { Demo } from '@/types'; import { ChartData, ChartOptions } from 'chart.js'; diff --git a/app/(main)/uikit/floatlabel/page.tsx b/app/(main)/uikit/floatlabel/page.tsx index 6d196fe6..1dec3af6 100644 --- a/app/(main)/uikit/floatlabel/page.tsx +++ b/app/(main)/uikit/floatlabel/page.tsx @@ -84,7 +84,7 @@ const FloatLabelDemo = () => { id="autocomplete" value={value2} onChange={(e) => setValue2(e.value)} - suggestions={filteredCountries} + // suggestions={filteredCountries} completeMethod={searchCountry} field="name" > diff --git a/app/(main)/uikit/input/page.tsx b/app/(main)/uikit/input/page.tsx index 27fcffb4..dd0c4688 100644 --- a/app/(main)/uikit/input/page.tsx +++ b/app/(main)/uikit/input/page.tsx @@ -219,7 +219,7 @@ const InputDemo: Page = () => { cols={30} /> -
AutoComplete
+ {/*
AutoComplete
{ suggestions={autoFilteredValue} completeMethod={searchCountry} field="name" - /> + /> */}
Calendar
{ id="autocomplete" value={value2} onChange={(e) => setValue2(e.value)} - suggestions={filteredCountries} + // suggestions={filteredCountries} completeMethod={searchCountry} field="name" className="p-invalid" diff --git a/app/(main)/uikit/list/page.tsx b/app/(main)/uikit/list/page.tsx index 31e24b53..ca05aa44 100644 --- a/app/(main)/uikit/list/page.tsx +++ b/app/(main)/uikit/list/page.tsx @@ -163,7 +163,7 @@ const ListDemo = () => {
PickList
- { }} sourceStyle={{ height: '200px' }} targetStyle={{ height: '200px' }} - > + > */}
OrderList
-
{item.name}
} onChange={(e) => setOrderlistValue(e.value)}>
+ {/*
{item.name}
} onChange={(e) => setOrderlistValue(e.value)}>
*/}
diff --git a/app/(main)/uikit/tree/page.tsx b/app/(main)/uikit/tree/page.tsx index cc93deb2..8241603a 100644 --- a/app/(main)/uikit/tree/page.tsx +++ b/app/(main)/uikit/tree/page.tsx @@ -28,11 +28,11 @@ const TreeDemo = () => {
TreeTable
- setSelectedFileKeys2(e.value)}> + {/* setSelectedFileKeys2(e.value)}> - + */}
diff --git a/app/(student)/layout.tsx b/app/(student)/layout.tsx new file mode 100644 index 00000000..06c465a8 --- /dev/null +++ b/app/(student)/layout.tsx @@ -0,0 +1,15 @@ +import StudentLayout from "@/layout/StudentLayout"; + +export default function LayoutStudent({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + + return ( +
+ {/* {children} */} + {children}; +
+ ); +} \ No newline at end of file diff --git a/app/(student)/lessi/page.tsx b/app/(student)/lessi/page.tsx new file mode 100644 index 00000000..9f7a42f5 --- /dev/null +++ b/app/(student)/lessi/page.tsx @@ -0,0 +1,9 @@ +'use client'; + +import React from 'react' + +export default function Lessi() { + return ( +
baby!!
+ ) +} diff --git a/app/components/BaseLayout.tsx b/app/components/BaseLayout.tsx new file mode 100644 index 00000000..101fd3fb --- /dev/null +++ b/app/components/BaseLayout.tsx @@ -0,0 +1,19 @@ +"use client"; + +import AppTopbar from "@/layout/AppTopbar"; +import HomeClient from "./HomeClient"; +import AppFooter from "@/layout/AppFooter"; + +export default function BaseLayout() { + return ( + <> +
+ +
+ +
+ +
+ + ); +} \ No newline at end of file diff --git a/app/components/CKEditorWrapper.tsx.tsx b/app/components/CKEditorWrapper.tsx.tsx new file mode 100644 index 00000000..717d55a4 --- /dev/null +++ b/app/components/CKEditorWrapper.tsx.tsx @@ -0,0 +1,49 @@ +'use client'; +import useTypingEffect from '@/hooks/useTypingEffect'; +import { Editor } from 'primereact/editor'; +import { useEffect, useState } from 'react'; +import { EditorTextChangeEvent } from 'primereact/editor'; + +export default function CKEditorWrapper({ textValue, insideValue }: { textValue: (e: string) => void; insideValue: string }) { + const [text, setText] = useState(''); + const [toggleTyping, setToggleTyping] = useState(true); + + // const typedText = useTypingEffect( + // 'Текстти ушул жерге жазыныз', + // toggleTyping + // ); + + useEffect(() => { + // const parser = new DOMParser(); + // const doc = parser.parseFromString(text, 'text/html'); + // const imgs = doc.querySelectorAll('img'); + // console.log(imgs); + + // for (let img of imgs) { + // console.log(img.src.startsWith('data:image')); + + // // if (img.src.startsWith('data:image')) { + // // const res = await fetch('/api/upload', { + // // method: 'POST', + // // headers: { 'Content-Type': 'application/json' }, + // // body: JSON.stringify({ file: img.src }) + // // }); + + // // const data = await res.json(); + // // img.src = data.url; + // // } + // } + + if (text) textValue(text); + }, [text]); + + return ( +
+ {insideValue ? ( + setText(e.htmlValue)} className="w-[800px] h-[300px]" /> + ) : ( + setText(e.htmlValue)} className="w-[800px] h-[300px]" /> + )} +
+ ); +} diff --git a/app/components/CounterBanner.tsx b/app/components/CounterBanner.tsx new file mode 100644 index 00000000..70317ba5 --- /dev/null +++ b/app/components/CounterBanner.tsx @@ -0,0 +1,57 @@ +import React from 'react'; +import { faCircle, faChalkboard, faUserGraduate, faBookOpen, faShieldHeart } from '@fortawesome/free-solid-svg-icons'; +import MyFontAwesome from './MyFontAwesome'; +import CountUp from 'react-countup'; + +export default function CounterBanner() { + + return ( +
+
+
+
+ + +
+
+
+
+ Курстар & видеосабактар +
+
+ +
+
+ + +
+
+
+
+ Катталган студенттер +
+
+ +
+
+ + +
+
+
+
+ Окутуучулар +
+
+ +
+
+ + +
+
+
%
+ Канааттануу деңгээли +
+
+
+
+ ); +} diff --git a/app/components/HomeClient.tsx b/app/components/HomeClient.tsx new file mode 100644 index 00000000..5ecfb5eb --- /dev/null +++ b/app/components/HomeClient.tsx @@ -0,0 +1,134 @@ +'use client'; + +import AOS from 'aos'; +import 'aos/dist/aos.css'; +import { useContext, useEffect } from 'react'; +import CounterBanner from './CounterBanner'; +import Link from 'next/link'; +import VideoPlay from './VideoPlay'; +import FancyLinkBtn from './buttons/FancyLinkBtn'; +import { LayoutContext } from '@/layout/context/layoutcontext'; + +export default function HomeClient() { + const { user } = useContext(LayoutContext); + + useEffect(() => { + AOS.init(); + }, []); + + return ( +
+
+
+
+
+
+ Фото + + ЫҢГАЙЛУУ ОКУУ ҮЧҮН ОНЛАЙН МЕЙКИНДИК + +
+

+ Аралыктан окутуу порталына кош келиңиз! +

+
+ {' '} + Университеттин онлайн билим берүү жаатындагы долбоорлорун бириктирүүдөбүз: +
    +
  • ачык онлайн курстар
  • +
  • жогорку билим берүү программалары
  • +
+ {user && + + + + } +
+
+
+ +
+
+
+ {/*
+ Shape +
*/} + + Пользователь + +
+ Shape +
+ +
+ Shape +
+ + {/*
+
+ 13000 +

Студент

+
+
*/} + + {/*
+
+ Куттуктайбыз! +

Сиздин кабыл алуу ийгиликтүү аяктады

+
+
*/} + + {/*
+
+ User experience className +

Today at 12.00 PM

+
+ + Join now + +
*/} +
+
+
+
+
+ + {/* Counter Statistics */} + + + {/* Oshgu Video */} +
+

+ Видеоэкскурсия по главному зданию ОшГУ +

+
+ +
+ ); +} diff --git a/app/components/InfoBanner.tsx b/app/components/InfoBanner.tsx new file mode 100644 index 00000000..862aefc0 --- /dev/null +++ b/app/components/InfoBanner.tsx @@ -0,0 +1,10 @@ +'use client'; + +export default function InfoBanner({title}:{title:string}) { + + return ( +
+

{title}

+
+ ); +}; diff --git a/app/components/MyFontAwesome.tsx b/app/components/MyFontAwesome.tsx new file mode 100644 index 00000000..28e58167 --- /dev/null +++ b/app/components/MyFontAwesome.tsx @@ -0,0 +1,17 @@ +"use client"; // обязательно в app/ структуре + +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { IconProp } from "@fortawesome/fontawesome-svg-core"; +import { ComponentProps } from "react"; + +interface IconProps extends ComponentProps { + icon: IconProp; + className?: string; + size?: "xs" | "lg" | "sm" | "1x" | "2x" | "3x" | "4x" | "5x" | "6x" | "7x" | "8x" | "9x" | "10x"; +} + +export default function MyFontAwesome({ icon, className, size, ...props }:IconProps ) { + return ( + + ); +} \ No newline at end of file diff --git a/app/components/NotFound.tsx b/app/components/NotFound.tsx new file mode 100644 index 00000000..2320e3b1 --- /dev/null +++ b/app/components/NotFound.tsx @@ -0,0 +1,18 @@ +'use client'; + +import React from 'react'; +import Link from 'next/link'; +import FancyLinkBtn from '@/app/components/buttons/FancyLinkBtn'; + +export const NotFound = ({ titleMessage }: { titleMessage: string }) => { + return ( +
+
+

{titleMessage}

+ + + +
+
+ ); +}; diff --git a/app/components/SessionManager.tsx b/app/components/SessionManager.tsx new file mode 100644 index 00000000..0a3f5add --- /dev/null +++ b/app/components/SessionManager.tsx @@ -0,0 +1,125 @@ +'use client'; + +import { getToken } from '@/utils/auth'; +import { useContext, useEffect } from 'react'; +import { LayoutContext } from '@/layout/context/layoutcontext'; +import { getUser } from '@/services/auth'; +import { logout } from '@/utils/logout'; +import { usePathname } from 'next/navigation'; + +const SessionManager = () => { + const { setMessage } = useContext(LayoutContext); + const { user, setUser } = useContext(LayoutContext); + const { setGlobalLoading } = useContext(LayoutContext); + + const pathname = usePathname(); + + useEffect(() => { + console.log('Пользователь ', user); + }, [user]); + + useEffect(() => { + console.log('Роутинг'); + setGlobalLoading(true); + setTimeout(() => { + setGlobalLoading(false); + }, 2000); + + const init = async () => { + console.log('проверяем токен...'); + const token = getToken('access_token'); + if (token) { + const res = await getUser(token); + setGlobalLoading(true); + try { + if (res?.success) { + setGlobalLoading(false); + console.log('Данные пользователя успешно пришли ', res); + + const userVisit = localStorage.getItem('userVisit'); + if (!userVisit) { + localStorage.setItem('userVisit', JSON.stringify(true)); + + setMessage({ + state: true, + value: { severity: 'success', summary: 'Успешная авторизация!', detail: '' } + }); // messege - Успех! + } + setUser(res.user); + } else { + logout({ setUser, setGlobalLoading }); + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при авторизации' } + }); // messege - Ошибка при авторизации + console.log('Ошибка при получении пользователя'); + } + } catch (error) { + logout({ setUser, setGlobalLoading }); + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при авторизации' } + }); // messege - Ошибка при авторизации + console.log('Ошибка при получении пользователя'); + } + } + }; + init(); + }, []); + + useEffect(() => { + setGlobalLoading(true); + console.log('Переход в', pathname); + const token = getToken('access_token'); + + if (!token && pathname !== '/' && pathname !== '/auth/login') { + console.log('Перенеправляю в login'); + + // logout({ setUser, setGlobalLoading }); + // window.location.href = '/auth/login'; + setTimeout(() => { + setGlobalLoading(false); + }, 1000); + + return; + } + setTimeout(() => { + setGlobalLoading(false); + }, 1000); + }, [pathname]); + + // useEffect(() => { + // const checkToken = () => { + // const token = getToken('access_token'); + + // if (!token) { + // const userVisit = localStorage.getItem('userVisit'); + // if(userVisit){ + // setMessage({ + // state: true, + // value: { severity: 'error', summary: 'Сессия завершилось', detail: 'Войдите заново' } + // }); // messege - Время сесси завершилось + // } + // logout({setUser, setGlobalLoading}); + // console.log('Токен отсутствует - завершаем сессию'); + // return false; // сигнал для остановки интервала + // } + // return true; + // }; + + // // немедленная проверка + // if (!checkToken()) return; + + // const interval = setInterval(() => { + // if (!checkToken()) { + // clearInterval(interval); + // } + // }, 5000); + + // return () => clearInterval(interval); + // }, []); + + return null; +}; + +export default SessionManager; diff --git a/app/components/VideoPlay.tsx b/app/components/VideoPlay.tsx new file mode 100644 index 00000000..fb45968c --- /dev/null +++ b/app/components/VideoPlay.tsx @@ -0,0 +1,49 @@ +'use client'; + +import { faPlay} from "@fortawesome/free-solid-svg-icons"; + +import Image from "next/image"; +import { useState } from "react"; +import { Dialog } from 'primereact/dialog'; +// import 'primereact/resources/themes/lara-light-blue/theme.css'; // или другая тема +import 'primereact/resources/primereact.min.css'; +import 'primeicons/primeicons.css'; +import MyFontAwesome from "./MyFontAwesome"; + +export default function VideoPlay() { + const [videoCall, setVideoCall] = useState(false); + + return ( +
+ {if (!videoCall) return; setVideoCall(false); }}> +
+ +
+
+
+
+
setVideoCall(true)} + > + {/* Волна */} + + + {/* Иконка-кнопка */} +
+ +
+
+
+ Логотип ОшГУ +
+
+ ) +} diff --git a/app/components/buttons/FancyLinkBtn.tsx b/app/components/buttons/FancyLinkBtn.tsx new file mode 100644 index 00000000..5781fed4 --- /dev/null +++ b/app/components/buttons/FancyLinkBtn.tsx @@ -0,0 +1,34 @@ +'use client'; + +import Link from 'next/link'; +import { useState } from 'react'; + +export default function FancyLinkBtn({ btnWidth, backround, effectBg, title } + : {btnWidth: string, backround: string, effectBg: string, title: string} +) { + const [position, setPosition] = useState(false); + + return ( +
+ +
+ ); +} diff --git a/app/components/cards/LessonCard.tsx b/app/components/cards/LessonCard.tsx new file mode 100644 index 00000000..64be884c --- /dev/null +++ b/app/components/cards/LessonCard.tsx @@ -0,0 +1,38 @@ +import Redacting from '../popUp/Redacting'; +import { getRedactor } from '@/utils/getRedactor'; +import { getConfirmOptions } from '@/utils/getConfirmOptions'; + +export default function LessonCard({ onSelected, onDelete, cardValue, cardBg, typeColor, type, lessonDate }: + { + onSelected: (id: number, type: string)=> void, + onDelete: (id: number) => void, + cardValue: {title: string, id: number, type: string}, + cardBg: string, + type: {typeValue: string, icon: string}, + typeColor: string, + lessonDate: string + }) + + { + + return ( +
+
+
+ + {type.typeValue} +
+ +
+
+
+
+ + {lessonDate} +
+
+
{cardValue.title}
+
+
+ ); +} diff --git a/app/components/cards/PrototypeCard.tsx b/app/components/cards/PrototypeCard.tsx new file mode 100644 index 00000000..22855f12 --- /dev/null +++ b/app/components/cards/PrototypeCard.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import MySkeleton from '../skeleton/MySkeleton'; + +export default function PrototypeCard({ value }: { value: string }) { + + return ( +
+
+ + +
+
+
+ +
+
{value ? value : }
+
+
+ ); +} diff --git a/app/components/lessons/LessonTyping.tsx b/app/components/lessons/LessonTyping.tsx new file mode 100644 index 00000000..a378cb6f --- /dev/null +++ b/app/components/lessons/LessonTyping.tsx @@ -0,0 +1,846 @@ +import { LayoutContext } from '@/layout/context/layoutcontext'; +import { Button } from 'primereact/button'; +import { FileUpload } from 'primereact/fileupload'; +import { InputText } from 'primereact/inputtext'; +import React, { useContext, useEffect, useState } from 'react'; +import LessonCard from '../cards/LessonCard'; +import { getToken } from '@/utils/auth'; +import { addLesson, deleteLesson, fetchLesson, fetchVideoType, updateLesson } from '@/services/courses'; +import useErrorMessage from '@/hooks/useErrorMessage'; +import FormModal from '../popUp/FormModal'; +import { useForm } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { lessonStateType } from '@/types/lessonStateType'; +import { lessonSchema } from '@/schemas/lessonSchema'; +import { lessonType } from '@/types/lessonType'; +import { NotFound } from '../NotFound'; +import { EditableLesson } from '@/types/editableLesson'; +import { Dropdown } from 'primereact/dropdown'; + +export default function LessonTyping({ mainType, courseId, lessonId }: { mainType: string; courseId: string | null; lessonId: string | null }) { + // types + interface editingType { + key: string; + file: string; + url: string; + link: string; + video_type_id?: number | null; + } + + interface videoType { + name: string; + status: boolean; + id: number; + } + + interface videoInsideType { + id: number; + is_link: boolean; + short_title: string; + title: string; + } + // validate + const { + setValue, + formState: { errors } + } = useForm({ + resolver: yupResolver(lessonSchema), + mode: 'onChange' + }); + + // doc + const [documents, setDocuments] = useState([]); + const [docValue, setDocValue] = useState({ + title: '', + description: '', + file: null, + url: '', + video_link: '' + }); + const [docShow, setDocShow] = useState(false); + + // links + const [links, setLinks] = useState([]); + const [linksValue, setLinksValue] = useState({ + title: '', + description: '', + file: null, + url: '', + video_link: '' + }); + const [linksShow, setLinksShow] = useState(false); + + // videos + const [video, setVideo] = useState([]); + const [selectedCity, setSelectedCity] = useState({ name: '', status: true, id: 1 }); + const [videoSelect, setVideoSelect] = useState([]); + const [videoTypes, setVideoTypes] = useState([]); + const [videoValue, setVideoValue] = useState({ + title: '', + description: '', + file: null, + url: '', + video_link: '' + }); + const [videoShow, setVideoShow] = useState(false); + + // auxiliary + const showError = useErrorMessage(); + const { setMessage } = useContext(LayoutContext); + const [selectId, setSelectId] = useState(null); + const [selectType, setSelectType] = useState(''); + const [visible, setVisisble] = useState(false); + const [editingLesson, setEditingLesson] = useState(null); + + // functions + const handleUpdate = () => { + if (selectType === 'doc') { + handleUpdateLesson(); + } else if (selectType === 'url') { + handleUpdateLink(); + } else if (selectType === 'video') { + handleUpdateVideo(); + } + }; + + const selectedForEditing = (id: number, type: string) => { + console.log('Открытие окна... ', id, type); + + setSelectId(id); + setSelectType(type); + editing(type, id); + setVisisble(true); + }; + + // Предоставляю данные из сервера для изменения + const editing = async (type: string, selected: number) => { + console.log(type, selected); + + const token = getToken('access_token'); + const data = await fetchLesson(type, courseId ? Number(courseId) : null, lessonId ? Number(lessonId) : null); + console.log(data); + + const lesson: editingType = + type === 'doc' + ? { key: 'documents', file: 'document', url: '', link: '', video_type_id: 1 } + : type === 'url' + ? { key: 'links', file: '', url: 'url', link: '', video_type_id: 1 } + : type === 'video' + ? { key: 'videos', file: '', url: '', link: 'link', video_type_id: 1 } + : { key: '', file: '', url: '', link: '', video_type_id: 1 }; + // console.log(lesson); + // console.log(data[lesson.key]); + + if (data.success) { + if (data[lesson.key]) { + const arr = data[lesson.key].find((item: lessonType) => item.id === selected); + console.log(arr); + + setEditingLesson({ + title: arr.title, + description: arr?.description, + document: arr[lesson.file], + url: arr[lesson.url], + video_link: arr[lesson.link], + video_type_id: arr?.video_type_id + }); + } + } + }; + + const clearValues = () => { + setLinksValue({ title: '', description: '', file: null, url: '', video_link: '' }); + setEditingLesson(null); + setSelectId(null); + setSelectType(''); + }; + + // DOC SECTION + + const docSection = () => { + return ( +
+
+ {}} + accept="application/pdf" + onSelect={(e) => + setDocValue((prev) => ({ + ...prev, + file: e.files[0] + })) + } + /> + + { + setDocValue((prev) => ({ ...prev, title: e.target.value })); + setValue('title', e.target.value, { shouldValidate: true }); + }} + /> + {errors.title?.message} + + setDocValue((prev) => ({ ...prev, description: e.target.value }))} className="w-full" /> +
+ {/*
+
+ +
+
+ {docShow ? ( + + ) : ( + documents.map((item: lessonType) => ( + <> + selectedForEditing(id, type)} + onDelete={(id: number) => handleDeleteDoc(id)} + cardValue={{ title: item?.title, id: item.id, type: 'doc' }} + cardBg={'#ddc4f51a'} + type={{ typeValue: 'Документтер', icon: 'pi pi-folder' }} + typeColor={'var(--mainColor)'} + lessonDate={'xx-xx-xx'} + /> + + )) + )} +
+
+
+ ); + }; + + // fetch document + const handleFetchDoc = async () => { + // skeleton = false + const token = getToken('access_token'); + const data = await fetchLesson('doc', courseId ? Number(courseId) : null, lessonId ? Number(lessonId) : null); + console.log(data); + + if (data?.success) { + if (data.documents) { + // Присваиваю себе. Для отображения + setDocValue({ title: '', description: '', file: null, url: '', video_link: '' }); + setDocuments(data.documents); + setDocShow(false); + } else { + setDocShow(true); + } + } else { + setDocShow(true); + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Проблема с соединением. Повторите заново' } + }); + if (data?.response?.status) { + showError(data.response.status); + } + // skeleton = false + } + }; + + // add document + const handleAddDoc = async () => { + const token = getToken('access_token'); + + const data = await addLesson('doc', token, courseId ? Number(courseId) : null, lessonId ? Number(lessonId) : null, docValue, 0); + console.log(data); + + if (data.success) { + handleFetchDoc(); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү Кошулдуу!', detail: '' } + }); + } else { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при при добавлении' } + }); + if (data.response.status) { + showError(data.response.status); + } + } + }; + + // update document + const handleUpdateLesson = async () => { + const token = getToken('access_token'); + + const data = await updateLesson('doc', token, courseId ? Number(courseId) : null, lessonId ? Number(lessonId) : null, Number(selectId), editingLesson); + console.log(data); + + if (data?.success) { + handleFetchDoc(); + clearValues(); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү өзгөртүлдү!', detail: '' } + }); + } else { + setDocValue({ title: '', description: '', file: null, url: '', video_link: '' }); + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при при изменении урока' } + }); + if (data?.response?.status) { + showError(data.response.status); + } + } + }; + + // delete document + const handleDeleteDoc = async (id: number) => { + const token = getToken('access_token'); + const data = await deleteLesson('doc', Number(courseId), Number(lessonId), id); + + if (data.success) { + handleFetchDoc(); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү өчүрүлдү!', detail: '' } + }); + } else { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при при удалении' } + }); + if (data.response.status) { + showError(data.response.status); + } + } + }; + + // LINKS SECTION + + const linkSection = () => { + return ( +
+
+ { + setLinksValue((prev) => ({ ...prev, url: e.target.value })); + setValue('usefulLink', e.target.value, { shouldValidate: true }); + }} + /> + {errors.usefulLink?.message} + + { + setLinksValue((prev) => ({ ...prev, title: e.target.value })); + setValue('title', e.target.value, { shouldValidate: true }); + }} + /> + {errors.title?.message} + + setLinksValue((prev) => ({ ...prev, description: e.target.value }))} className="w-full" /> +
+
+
+ +
+
+ {linksShow ? ( + + ) : ( + links.map((item: lessonType) => ( + <> + selectedForEditing(id, type)} + onDelete={(id: number) => handleDeleteLink(id)} + cardValue={{ title: item.title, id: item.id, type: 'url' }} + cardBg={'#7bb78112'} + type={{ typeValue: 'Шилтемелер', icon: 'pi pi-link' }} + typeColor={'var(--mainColor)'} + lessonDate={'xx-xx-xx'} + /> + + )) + )} +
+
+
+ ); + }; + + // fetch link + const handleFetchLink = async () => { + // skeleton = false + const token = getToken('access_token'); + const data = await fetchLesson('url', courseId ? Number(courseId) : null, lessonId ? Number(lessonId) : null); + console.log(data); + + if (data?.success) { + if (data.links) { + // Присваиваю себе. Для отображения + setLinksValue({ title: '', description: '', file: null, url: '', video_link: '' }); + setLinks(data.links); + setLinksShow(false); + } else { + setLinksShow(true); + } + } else { + setLinksShow(true); + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Проблема с соединением. Повторите заново' } + }); + if (data?.response?.status) { + showError(data.response.status); + } + // skeleton = false + } + }; + + // add link + const handleAddLink = async () => { + const token = getToken('access_token'); + + const data = await addLesson('url', token, courseId ? Number(courseId) : null, lessonId ? Number(lessonId) : null, linksValue, 0); + console.log(data); + + if (data.success) { + handleFetchLink(); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү Кошулдуу!', detail: '' } + }); + } else { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при при добавлении' } + }); + if (data.response.status) { + showError(data.response.status); + } + } + }; + + // delete link + const handleDeleteLink = async (id: number) => { + const data = await deleteLesson('url', Number(courseId), Number(lessonId), id); + + if (data.success) { + handleFetchLink(); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү өчүрүлдү!', detail: '' } + }); + } else { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при при удалении' } + }); + if (data.response.status) { + showError(data.response.status); + } + } + }; + + // update link + const handleUpdateLink = async () => { + const token = getToken('access_token'); + + const data = await updateLesson('url', token, courseId ? Number(courseId) : null, lessonId ? Number(lessonId) : null, Number(selectId), editingLesson); + console.log(data); + + if (data.success) { + handleFetchLink(); + clearValues(); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү өзгөртүлдү!', detail: '' } + }); + } else { + setLinksValue({ title: '', description: '', file: null, url: '', video_link: '' }); + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при при изменении урока' } + }); + if (data.response.status) { + showError(data.response.status); + } + } + }; + + // VIDEO SECTIONS + const toggleVideoType = (e: videoType) => { + setSelectedCity(e); + setVideoValue({ title: '', description: '', file: null, url: '', video_link: '' }); + }; + + const videoSection = () => { + return ( +
+
+ { + toggleVideoType(e.value); + }} + options={videoSelect} + optionLabel="name" + placeholder="" + className="w-full md:w-14rem" + /> +
+ {selectedCity?.status ? ( +
+ { + setVideoValue((prev) => ({ ...prev, video_link: e.target.value })); + setValue('usefulLink', e.target.value, { shouldValidate: true }); + }} + /> + {errors.usefulLink?.message} +
+ ) : ( + {}} + accept="video/" + onSelect={(e) => + setVideoValue((prev) => ({ + ...prev, + file: e.files[0] + })) + } + /> + )} +
+
+
+ { + setVideoValue((prev) => ({ ...prev, title: e.target.value })); + setValue('title', e.target.value, { shouldValidate: true }); + }} + /> + {errors.title?.message} + + setVideoValue((prev) => ({ ...prev, description: e.target.value }))} className="w-full" /> +
+
+
+ +
+
+ {videoShow ? ( + + ) : ( + video.map((item: lessonType) => ( + <> + selectedForEditing(id, type)} + onDelete={(id: number) => handleDeleteVideo(id)} + cardValue={{ title: item.title, id: item.id, type: 'video' }} + cardBg={'#f1b1b31a'} + type={{ typeValue: 'Видео', icon: 'pi pi-video' }} + typeColor={'var(--mainColor)'} + lessonDate={'xx-xx-xx'} + /> + + )) + )} +
+
+
+ ); + }; + + const handleVideoType = async () => { + const data = await fetchVideoType(); + + if (data) { // proverit + setVideoTypes(data); + } + }; + + // fetch video + const handleFetchVideo = async () => { + // skeleton = false + const token = getToken('access_token'); + const data = await fetchLesson('video', courseId ? Number(courseId) : null, lessonId ? Number(lessonId) : null); + // console.log(data); + + if (data?.success) { + if (data.videos) { + setVideoValue({ title: '', description: '', file: null, url: '', video_link: '' }); + setVideo(data.videos); + setVideoShow(false); + } else { + setVideoShow(true); + } + } else { + setVideoShow(true); + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Проблема с соединением. Повторите заново' } + }); + if (data?.response?.status) { + showError(data.response.status); + } + // skeleton = false + } + }; + + // add vieo + const handleAddVideo = async () => { + const token = getToken('access_token'); + + const data = await addLesson('video', token, courseId ? Number(courseId) : null, lessonId ? Number(lessonId) : null, videoValue, selectedCity?.id); + console.log(data); + + if (data.success) { + handleFetchVideo(); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү Кошулдуу!', detail: '' } + }); + } else { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при при добавлении' } + }); + if (data.response.status) { + showError(data.response.status); + } + } + }; + + // update video + const handleUpdateVideo = async () => { + const token = getToken('access_token'); + + const data = await updateLesson('video', token, courseId ? Number(courseId) : null, lessonId ? Number(lessonId) : null, Number(selectId), editingLesson); + console.log(data); + + if (data.success) { + handleFetchVideo(); + clearValues(); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү өзгөртүлдү!', detail: '' } + }); + } else { + setVideoValue({ title: '', description: '', file: null, url: '', video_link: '' }); + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при изменении урока' } + }); + if (data.response.status) { + showError(data.response.status); + } + } + }; + + // delete video + const handleDeleteVideo = async (id: number) => { + + const data = await deleteLesson('video',Number(courseId), Number(lessonId), id); + + if (data.success) { + handleFetchVideo(); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү өчүрүлдү!', detail: '' } + }); + } else { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при при удалении' } + }); + if (data.response.status) { + showError(data.response.status); + } + } + }; + + // USEEFFECTS + + useEffect(() => { + if (mainType === 'doc') handleFetchDoc(); + if (mainType === 'link') handleFetchLink(); + if (mainType === 'video') { + handleFetchVideo(); + handleVideoType(); + } + }, []); + + useEffect(() => { + console.log('selected id ', selectId); + }, [selectId]); + + useEffect(() => { + console.log('eidting ', editingLesson); + }, [editingLesson]); + + useEffect(() => { + links.length < 1 ? setLinksShow(true) : setLinksShow(false); + documents.length < 1 ? setDocShow(true) : setDocShow(false); + }, [links, documents]); + + useEffect(() => { + const forSelect = videoTypes.map((item) => { + return { name: item.title, status: item.is_link, id: item.id }; + }); + + setVideoSelect(forSelect); + setSelectedCity(forSelect[0]); + }, [videoTypes]); + + // useEffect(() => { + // console.log(videoSelect); + // }, [videoSelect]); + + // useEffect(() => { + // console.log(selectedCity); + // }, [selectedCity]); + + return ( +
+ +
+
+ {selectType === 'doc' ? ( + <> + {}} + accept="application/pdf" + onSelect={(e) => { + if (e.files[0]) setEditingLesson((prev) => prev && { ...prev, file: e.files[0] }); + }} + /> + {String(editingLesson?.document)} + { + setEditingLesson((prev) => prev && { ...prev, title: e.target.value }); + setValue('title', e.target.value, { shouldValidate: true }); + }} + /> + {errors.title?.message} + setEditingLesson((prev) => prev && { ...prev, description: e.target.value })} + className="w-full" + /> + + ) : selectType === 'url' ? ( + <> + { + setEditingLesson((prev) => prev && { ...prev, url: e.target.value }); + setValue('usefulLink', e.target.value, { shouldValidate: true }); + }} + /> + { + setEditingLesson((prev) => prev && { ...prev, title: e.target.value }); + setValue('title', e.target.value, { shouldValidate: true }); + }} + /> + {errors.title?.message} + setEditingLesson((prev) => prev && { ...prev, description: e.target.value })} className="w-full" /> + + ) : selectType === 'video' ? ( + <> + {editingLesson?.video_type_id ? ( +
+ { + setEditingLesson((prev) => prev && { ...prev, video_link: e.target.value }); + setValue('usefulLink', e.target.value, { shouldValidate: true }); + }} + /> + {errors.usefulLink?.message} +
+ ) : ( + <> + {}} + accept="video/" + onSelect={(e) => + setEditingLesson( + (prev) => + prev && { + ...prev, + file: e.files[0] + } + ) + } + /> + {String(editingLesson?.video_link)} + + )} + { + setEditingLesson((prev) => prev && ({ ...prev, title: e.target.value })); + setValue('title', e.target.value, { shouldValidate: true }); + }} + /> + {errors.title?.message} + setEditingLesson((prev) => prev && ({ ...prev, description: e.target.value }))} className="w-full" /> + + ) : ( + '' + )} +
+
+
+ + {mainType === 'doc' && docSection()} + {mainType === 'link' && linkSection()} + {mainType === 'video' && videoSection()} +
+ ); +} diff --git a/app/components/loading/GlobalLoading.tsx b/app/components/loading/GlobalLoading.tsx new file mode 100644 index 00000000..65be1f01 --- /dev/null +++ b/app/components/loading/GlobalLoading.tsx @@ -0,0 +1,27 @@ +import React, { useContext } from 'react'; +import { LayoutContext } from '@/layout/context/layoutcontext'; + +export default function GlobalLoading() { + const { globalLoading } = useContext(LayoutContext); + + if(globalLoading){ + return ( + <> +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + ); + } +} diff --git a/app/components/messages/Message.tsx b/app/components/messages/Message.tsx new file mode 100644 index 00000000..42baa0a2 --- /dev/null +++ b/app/components/messages/Message.tsx @@ -0,0 +1,32 @@ +'use client'; + +import { Toast } from 'primereact/toast'; +import { Toast as ToastRef} from 'primereact/toast'; +import { useContext, useEffect, useRef } from 'react'; +import { LayoutContext } from '@/layout/context/layoutcontext'; + +type SeverityType = 'success' | 'info' | 'warn' | 'error' | 'secondary' | 'contrast'; + +export default function Message() { + const { message } = useContext(LayoutContext); + const toast = useRef(null); + + const showError = () => { + toast.current?.show({ severity: message.value.severity as SeverityType, summary: message.value.summary, detail: message.value.detail, life: 3000 }); + }; + + useEffect(() => { + const timer = setTimeout(() => { + showError(); + }, 0); // откладываем до следующей итерации event loop + + return () => clearTimeout(timer); + }, [message]); + + return ( +
+ {/*
+ ); +} diff --git a/app/components/popUp/ConfirmModal.tsx b/app/components/popUp/ConfirmModal.tsx new file mode 100644 index 00000000..e003d3e3 --- /dev/null +++ b/app/components/popUp/ConfirmModal.tsx @@ -0,0 +1,27 @@ +'use client'; +import { confirmDialog } from 'primereact/confirmdialog'; +type ConfirmDialogOptions = { + message: string; + header?: string; + icon?: string; + defaultFocus?: 'accept' | 'reject'; + accept?: () => void; + reject?: () => void; + acceptLabel?: string; + rejectLabel?: string; + acceptClassName?: string; + rejectClassName?: string; +}; +export default function ConfirmModal({confirmVisible}: {confirmVisible: ConfirmDialogOptions}) { + const handleClick = () => { + console.log(confirmVisible); + + confirmDialog(confirmVisible); + }; + + return ( +
+ +
+ ); +} diff --git a/app/components/popUp/FormModal.tsx b/app/components/popUp/FormModal.tsx new file mode 100644 index 00000000..223ba1bc --- /dev/null +++ b/app/components/popUp/FormModal.tsx @@ -0,0 +1,54 @@ +'use client'; + +import { Button } from 'primereact/button'; +import { Dialog } from 'primereact/dialog'; +import { ReactNode, useEffect } from 'react'; + +export default function FormModal({children, title, fetchValue, clearValues, visible, setVisible, start}: + { + children: ReactNode, + title: string, + fetchValue: ()=> void, + clearValues: ()=> void, + visible: boolean, + setVisible: (params: boolean)=> void, + start: boolean + } + ){ + + const footerContent = ( +
+
+ ); + + useEffect(()=> { + console.log('visible', visible); + + },[visible]); + + return ( +
+ { + if (!visible) return; + setVisible(false); + clearValues(); + }} + footer={footerContent} + > + {children} + +
+ ); +} diff --git a/app/components/popUp/Redacting.tsx b/app/components/popUp/Redacting.tsx new file mode 100644 index 00000000..b42664ec --- /dev/null +++ b/app/components/popUp/Redacting.tsx @@ -0,0 +1,40 @@ +import { Menu } from 'primereact/menu'; +import type { Menu as MenuRef } from 'primereact/menu'; +import { MenuItem } from 'primereact/menuitem'; +import { useRef } from 'react'; + +export default function Redacting({ redactor, textSize }: {redactor: MenuItem[], textSize: string}) { + const menuLeft = useRef(null); + + return ( +
+ menuLeft.current?.toggle(event)} aria-controls="popup_menu_left" aria-haspopup /> + +
+ ); +} diff --git a/app/components/popUp/Tiered.tsx b/app/components/popUp/Tiered.tsx new file mode 100644 index 00000000..865c72ad --- /dev/null +++ b/app/components/popUp/Tiered.tsx @@ -0,0 +1,72 @@ +'use client'; + +import { useRef } from 'react'; +import { Button } from 'primereact/button'; +import { TieredMenu } from 'primereact/tieredmenu'; +import type { TieredMenu as TieredMenuRef } from 'primereact/tieredmenu'; +import { useMediaQuery } from '@/hooks/useMediaQuery'; +// import type { Menu, Menu as MenuRef } from 'primereact/menu'; + +import { MenuItem } from 'primereact/menuitem'; + +type CustomMenuItem = Omit & { + label?: string; + items?: CustomMenuItem[]; +}; + +interface TieredProps { + title: { + name: string; + font: string; + }; + items: CustomMenuItem[]; + insideColor: string; +} + +export default function Tiered({ title, items, insideColor }: TieredProps) { + // const [mobile, setMobile] = useState(false); + + const menu = useRef(null); + const media = useMediaQuery('(max-width: 1000px)'); + + const menuItems = items.map((item) => ({ + ...item, + url: item.url || undefined + })); + + const toggleMenu = (e: React.MouseEvent) => { + console.log(e); + + menu.current?.toggle(e); + // setMobile(prev => !prev); + }; + // Общий фонт который закрывает кнопку бургер меню, Close + return ( +
+
+ ); +} diff --git a/app/components/skeleton/GroupSkeleton.tsx b/app/components/skeleton/GroupSkeleton.tsx new file mode 100644 index 00000000..7a79e32c --- /dev/null +++ b/app/components/skeleton/GroupSkeleton.tsx @@ -0,0 +1,14 @@ +import React, { useState } from 'react'; +import { Skeleton } from 'primereact/skeleton'; + +export default function GroupSkeleton({ count, size }:{count:number, size:{width:string, height: string}}) { + const usingSkeleton = (i:number) => { + return ( +
+ +
+ ); + }; + + return
{Array.from({ length: count }).map((_, i) => usingSkeleton(i))}
; +} diff --git a/app/components/skeleton/MySkeleton.tsx b/app/components/skeleton/MySkeleton.tsx new file mode 100644 index 00000000..751a781a --- /dev/null +++ b/app/components/skeleton/MySkeleton.tsx @@ -0,0 +1,6 @@ +import React, { useState } from 'react'; +import { Skeleton } from 'primereact/skeleton'; + +export default function MySkeleton({ size }:{size:{width:string,height:string}}) { + return ; +} diff --git a/app/components/tables/StreamList.tsx b/app/components/tables/StreamList.tsx new file mode 100644 index 00000000..ca9b0c61 --- /dev/null +++ b/app/components/tables/StreamList.tsx @@ -0,0 +1,292 @@ +'use client'; + +import useErrorMessage from '@/hooks/useErrorMessage'; +import { LayoutContext } from '@/layout/context/layoutcontext'; +import { connectStreams, fetchStreams } from '@/services/streams'; +import { getToken } from '@/utils/auth'; +import { Button } from 'primereact/button'; +import { Column } from 'primereact/column'; +import { DataTable } from 'primereact/datatable'; +import React, { useContext, useEffect, useState } from 'react'; +import { NotFound } from '../NotFound'; + +export default function StreamList({ callIndex, courseValue, isMobile }: { callIndex: number; courseValue: { id: number | null; title: string } | null; isMobile: boolean }) { + const streamList = [ + { + created_at: '', + id: 1, + image: '', + status: true, + title: 'lorem-1 lorem-1 lorem-1lorem-1lorem-1lorem-1lorem-1', + user_id: 1 + }, + { + created_at: '', + id: 2, + image: '', + status: true, + title: 'lorem-2', + user_id: 2 + }, + { + created_at: '', + id: 3, + image: '', + status: true, + title: 'lorem-3', + user_id: 3 + }, + { + created_at: '', + id: 4, + image: '', + status: true, + title: 'lorem-4', + user_id: 4 + }, + { + created_at: '', + id: 5, + image: '', + status: true, + title: 'lorem-5', + user_id: 5 + } + ]; + + // type + interface StreamsType {} + + const [streams, setStreams] = useState([]); + const [streamValues, setStreamValues] = useState({ value: courseValue, streams: [] }); + const [displayStreams, setDisplayStreams] = useState([]); + const [hasStreams, setHasStreams] = useState(false); + + const [skeleton, setSkeleton] = useState(false); + + const { setMessage } = useContext(LayoutContext); + const showError = useErrorMessage(); + + const toggleSkeleton = () => { + setSkeleton(true); + setTimeout(() => { + setSkeleton(false); + }, 1000); + }; + + [ + { + courseId: 1, + streams: [1, 2] + } + ]; + + const handleFetchStreams = async (page = 1) => { + const token = getToken('access_token'); + const data = await fetchStreams(); + console.log(data); + + if (data?.courses) { + setHasStreams(false); + setStreams(data.courses.data); + // setPagination({ + // currentPage: data.courses.current_page, + // total: data.courses.total, + // perPage: data.courses.per_page + // }); + } else { + setHasStreams(true); + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Проблема с соединением. Повторите заново' } + }); + if (data?.response?.status) { + showError(data.response.status); + } + } + }; + + const handleConnect = async () => { + const token = getToken('access_token'); + const data = await connectStreams(courseValue); + console.log(data); + + if (data?.success) { + toggleSkeleton(); + handleFetchStreams(); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү кошулду!', detail: '' } + }); + } else { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при при связке' } + }); + if (data?.response?.status) { + showError(data.response.status); + } + } + }; + + const clearValues = () => { + // ? + }; + + const handleEdit = (e, id: number, title: string) => { + console.log(e.checked); + + const forSentStreams = { + stream_id: id, + stream_title: title + }; + + if (e.checked) { + setStreamValues( + (prev) => + prev && { + ...prev, + streams: [...prev.streams, forSentStreams] + } + ); + } else { + setStreamValues( + (prev) => + prev && { + ...prev, + streams: [...prev.streams.filter((item) => item?.stream_id !== id)] + } + ); + } + }; + + useEffect(() => { + console.log('отправляемый поток ', streamValues); + const forDisplay = streamValues.streams.map((item, idx) => { + if (idx <= 2) { + return item; + } + }); + + setDisplayStreams(forDisplay); + }, [streamValues]); + + useEffect(() => { + console.log('Id ', courseValue); + }, [courseValue]); + + useEffect(() => { + console.log('Потоки ', streams); + + if (streams.length < 1) { + // setHasStreams(true); + } else { + if (streams.length > 0) { + // setForStreamId({ id: courses[0].id, title: courses[0].title }); + } + // setHasStreams(false); + } + }, [streams]); + + return ( + <> + {callIndex === 1 && ( +
+ {/* info section */} + {!isMobile && ( +
+

Агымдар

+
+ )} + + {hasStreams ? ( + + ) : ( +
+ {isMobile && ( +
+
+ )} + +
+ {/* {skeleton ? ( + + ) : ( */} + <> + {/* */} + + rowIndex + 1} header="Номер" style={{ width: '20px' }}> + ( + // + // { +
{rowData.title}
+ ) + // } + // + } + >
+ + ( + + )} + > +
+ + {/* )} */} +
+ + {courseValue?.title && ( +
+
+ + Курстун аталышы: {courseValue?.title} +
+
+
+
+ {streamValues.streams?.length < 1 && Курска байлоо үчүн агымдарды тандаңыз} + {displayStreams.map((item) => { + return
{item?.stream_title}
; + })} +
{displayStreams.length >= 3 && '...'}
+
+
+
+ )} +
+ )} +
+ )} + + ); +} diff --git a/app/favicon.ico b/app/favicon.ico deleted file mode 100644 index a614a31e..00000000 Binary files a/app/favicon.ico and /dev/null differ diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 00000000..3c711a14 --- /dev/null +++ b/app/globals.css @@ -0,0 +1,391 @@ +@import 'primeflex/primeflex.css'; +@import 'tailwindcss/index.css'; + +:root { + --bodyFonts: "Jost", sans-serif; + --mainColor: #08A9E6; + --redColor: #EC272F; + --redBgColor: #EC272F1A; + --greenColor: rgb(46, 192, 46); + --titleColor: #21225F; + --bodyColor: #555555; + --whiteColor: #ffffff; + --borderBottomColor: #e5e7eb; + --fontSize: 16px; + --transition: 0.5s; +} + +p { + color: var(--bodyColor); + margin-bottom: 10px; +} + +p:last-child { + margin-bottom: 0; +} + +a { + display: inline-block; + transition: var(--transition); + text-decoration: none; +} +a:hover, a:focus { + text-decoration: none; +} + +h1, h2, h3, h4, h5, h6 { + color: var(--titleColor); +} + +.mainColor-hover:hover{ + color: #08A9E6; +} + +/* animate */ +.animateContent{ + animation: content linear 6s infinite; +} + +@keyframes content { + 10%{ + transform: translateX(2px); + } + 50%{ + transform: translateX(-4px); + } + 80%{ + transform: translateY(-2px); + } + 100%{ + transform: translateY(4px); + } +} + +.animateFaster{ + animation: faster linear 4s infinite forwards; +} + +@keyframes faster { + 10%{ + transform: translateX(-2px); + } + 50%{ + transform: translateX(4px); + } + 80%{ + transform: translateY(2px); + } + 100%{ + transform: translateY(-4px); + } +} + +.user-img { + position: relative; + margin-bottom: 30px; +} +.user-img img { + /* border: solid .5px ; */ + box-shadow: 0px 1px 9px 4px; + animation: border-transform 10s linear infinite alternate forwards; +} + +@keyframes border-transform { + 0%, 100% { + border-radius: 60% 40% 56% 33%/73% 82% 18% 27%; + } + 14% { + border-radius: 40% 60% 54% 46%/49% 60% 40% 51%; + } + 28% { + border-radius: 54% 46% 38% 62%/49% 70% 30% 51%; + } + 42% { + border-radius: 61% 39% 55% 45%/61% 38% 62% 39%; + } + 56% { + border-radius: 61% 39% 67% 33%/70% 50% 50% 30%; + } + 70% { + border-radius: 50% 50% 34% 66%/56% 68% 32% 44%; + } + 84% { + border-radius: 46% 54% 50% 50%/35% 61% 39% 65%; + } +} + +#preloader { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 9999; +} + +#preloader-area { + position: absolute; + left: 50%; + top: 50%; + height: 60px; + width: 60px; +} +#preloader-area .spinner { + position: absolute; + z-index: 9999; + width: 6px; + height: 90px; + margin-top: -45px; + border-radius: 10px; + background-color: var(--whiteColor); + animation: rotate40deg 0.8s infinite; + animation-direction: alternate-reverse; +} +#preloader-area .spinner:nth-child(1) { + margin-left: 0px; +} +#preloader-area .spinner:nth-child(2) { + margin-left: -14px; + animation-delay: 0.1s; +} +#preloader-area .spinner:nth-child(3) { + margin-left: -28px; + animation-delay: 0.2s; +} +#preloader-area .spinner:nth-child(4) { + margin-left: -42px; + animation-delay: 0.3s; +} +#preloader-area .spinner:nth-child(5) { + margin-left: -56px; + animation-delay: 0.4s; +} +#preloader-area .spinner:nth-child(6) { + margin-left: -70px; + animation-delay: 0.5s; +} +#preloader-area .spinner:nth-child(7) { + margin-left: -84px; + animation-delay: 0.6s; +} +#preloader-area .spinner:nth-child(8) { + margin-left: -98px; + animation-delay: 0.7s; +} + +#preloader .preloader-section { + position: fixed; + top: 0; + width: 51%; + height: 100%; + background-color: var(--mainColor); + z-index: 999; + transform: translateX(0); +} +#preloader .preloader-section.preloader-left { + left: 0; +} +#preloader .preloader-section.preloader-right { + right: 0; +} + +.p-password .p-password-toggle-icon { + top: 50% !important; + transform: translateY(-50%); +} + + +.loaded #preloader-area { + opacity: 0; + visibility: hidden; + transition: all 0.3s ease-out; +} +.loaded #preloader .preloader-section.preloader-left { + transform: translateX(-100%); + transition: all 0.8s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); +} +.loaded #preloader .preloader-section.preloader-right { + transform: translateX(100%); + transition: all 0.8s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); +} + +.loaded #preloader { + visibility: hidden; + transform: translateY(-100%); + transition: all 0.3s 1s ease-out; +} + +@keyframes rotate40deg { + 0% { + height: 5px; + margin-top: 0; + transform: rotate(40deg); + } + 100% { + height: 90px; + transform: rotate(0deg); + } +} +@keyframes ripple { + 0% { + transform: scale(1); + } + 75% { + transform: scale(1.5); + opacity: 1; + } + 100% { + transform: scale(1.75); + opacity: 0; + } +} +@keyframes moveleftbounce { + 0% { + transform: translateX(0px); + } + 50% { + transform: translateX(20px); + } + 100% { + transform: translateX(0px); + } +} +@keyframes fadeInUp { + 0% { + opacity: 0; + transform: translateY(20px); + } + 100% { + opacity: 1; + transform: translateY(0); + } +} +@keyframes fadeInDown { + 0% { + opacity: 0; + transform: translateY(-20px); + } + 100% { + opacity: 1; + transform: translateY(0); + } +} +@keyframes movebounce { + 0% { + transform: translateY(0); + } + 50% { + transform: translateY(15px); + } + 100% { + transform: translateY(0); + } +} +@keyframes rotate-in { + 0% { + transform: perspective(120px) rotateX(0deg) rotateY(0deg); + } + 50% { + transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg); + } + 100% { + transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); + } +} +@keyframes rotated360 { + 0% { + transform: rotateZ(0deg); + } + 100% { + transform: rotateZ(-360deg); + } +} + +/* primeReact components */ +.popup_menu_left{ + width: 300px; + display: none; +} + +.p-menuitem-icon{ + margin: 0; +} + +/* prototype effect */ +.shadowAnimate { + box-shadow: 1px 1px 6px 1px ; + transition: all 2s; + animation: shadowSkale 2s ease-in infinite; +} + +@keyframes shadowSkale { + 0% { + box-shadow: 1px 1px 6px 1px ; + } + 50% { + box-shadow: 1px 1px 6px 2px ; + } + 100%{ + box-shadow: 1px 1px 6px 1px ; + } +} + +.my-custom-table td, +.my-custom-table th { + /* border: 1px solid #ccc; */ + padding: 6px; +} + +.my-custom-table thead { + /* background-color: #f5f5f5; */ +} + +.my-custom-table .p-datatable-tbody > tr:nth-child(even) { + /* background-color: rgb(255, 239, 239); */ + background-color: #f5f5f5; +} + +/* удаление встроенного underline при активаности для таб (TabView) */ +.p-tabview-ink-bar { + display: none !important; +} + +.p-tabview-panels{ + padding: 10px; +} + +@media screen and (max-width: 640px) { + .p-tabview-panels{ + padding: 5px; + padding-left: 5px; + } +} + +.tab-custom-text{ + color: rgb(245, 206, 77) !important; +} + +.tab-custom-text-2{ + color: blueviolet !important; + /* color: rgb(221, 196, 245) !important; */ +} + +.tab-custom-text-3{ + color: green !important; +} + +.tab-custom-text-4{ + color: var(--redColor) !important; +} + +.poka{ + background-color: rgb(211, 232, 202) !important; + /* background-color: rgb(243, 145, 145) !important; */ +} + +.main-bg { + background-color: #fff; + border-radius: 12px; + padding: 4px 12px; + box-shadow: 0px 3px 5px rgba(0, 0, 0, 0.02), 0px 0px 2px rgba(0, 0, 0, 0.05), 0px 1px 4px rgba(0, 0, 0, 0.08); +} \ No newline at end of file diff --git a/app/layout.tsx b/app/layout.tsx index 4114afcf..1d243994 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,12 +1,16 @@ 'use client'; import { LayoutProvider } from '../layout/context/layoutcontext'; import { PrimeReactProvider } from 'primereact/api'; +import { config } from "@fortawesome/fontawesome-svg-core"; +import "@fortawesome/fontawesome-svg-core/styles.css"; // Импорт стилей +config.autoAddCss = false; import 'primereact/resources/primereact.css'; -import 'primeflex/primeflex.css'; import 'primeicons/primeicons.css'; import '../styles/layout/layout.scss'; import '../styles/demo/Demos.scss'; +import './globals.css'; + interface RootLayoutProps { children: React.ReactNode; } diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 00000000..ddd8c663 --- /dev/null +++ b/app/page.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import BaseLayout from './components/BaseLayout' + +export default function page() { + return ( +
+ +
+ ) +} diff --git a/hooks/useErrorMessage.tsx b/hooks/useErrorMessage.tsx new file mode 100644 index 00000000..e8a8b3ea --- /dev/null +++ b/hooks/useErrorMessage.tsx @@ -0,0 +1,63 @@ +'use client'; + +import { LayoutContext } from '@/layout/context/layoutcontext'; +import { useContext } from 'react'; + +export default function useErrorMessage() { + const { setMessage } = useContext(LayoutContext); + + return (code: number | null) => { + console.log('Код ошибки ', code); + + switch (code) { + case 400: + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Запрос не корректен' } + }); + break; + + case 403: + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Нет доступа' } + }); + break; + + case 409: + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Конфликт данных' } + }); + break; + + case 422: + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка валидации. Проверьте введенные данные' } + }); + break; + + case 500: + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка сервера. Попробуйте позже' } + }); + break; + + case 503: + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Сервер временно недоступен' } + }); + break; + + // default: + // setMessage({ + // state: true, + // value: { severity: 'error', summary: 'Ошибка', detail: 'Попробуйте позже' } + // }); + } + + }; +} diff --git a/hooks/useMediaQuery.tsx b/hooks/useMediaQuery.tsx new file mode 100644 index 00000000..89e61dca --- /dev/null +++ b/hooks/useMediaQuery.tsx @@ -0,0 +1,19 @@ +"use client"; + +import { useState, useEffect } from "react"; + +export function useMediaQuery(query:string) { + const [matches, setMatches] = useState(false); + + useEffect(() => { + const mediaQuery = window.matchMedia(query); + setMatches(mediaQuery.matches); + + const handler = (e:MediaQueryListEvent) => setMatches(e.matches); + mediaQuery.addEventListener("change", handler); + + return () => mediaQuery.removeEventListener("change", handler); + }, [query]); + + return matches; +} diff --git a/hooks/useTypingEffect.tsx b/hooks/useTypingEffect.tsx new file mode 100644 index 00000000..a5dac639 --- /dev/null +++ b/hooks/useTypingEffect.tsx @@ -0,0 +1,46 @@ +'use client'; + +import { useEffect, useRef, useState } from 'react'; + +export default function useTypingEffect(word: string, stop: boolean) { + const [typedText, setTypedText] = useState(''); + const stopRef = useRef(stop); + const indexRef = useRef(0); + const intervalRef = useRef(null); + + useEffect(() => { + stopRef.current = stop; + + if (!stop) { + if (intervalRef.current) clearInterval(intervalRef.current); + setTypedText(''); + indexRef.current = 0; + return; + } + + intervalRef.current = setInterval(() => { + if (indexRef.current >= word.length) { + clearInterval(intervalRef.current!); + return; + } + + setTypedText((prev) => { + const nextChar = word[indexRef.current]; + indexRef.current++; + const nextText = prev + nextChar; + + if (nextText.length > 50) { + clearInterval(intervalRef.current!); + alert('baby'); + return ''; + } + + return nextText; + }); + }, 60); + + return () => clearInterval(intervalRef.current!); + }, [stop, word]); + + return typedText +} \ No newline at end of file diff --git a/layout/AppFooter.tsx b/layout/AppFooter.tsx index 424b9987..9e41bccd 100644 --- a/layout/AppFooter.tsx +++ b/layout/AppFooter.tsx @@ -2,16 +2,62 @@ import React, { useContext } from 'react'; import { LayoutContext } from './context/layoutcontext'; +import Image from 'next/image'; +import { faChalkboard, faPhone } from '@fortawesome/free-solid-svg-icons'; +import MyFontAwesome from '@/app/components/MyFontAwesome'; +import Link from 'next/link'; const AppFooter = () => { const { layoutConfig } = useContext(LayoutContext); + // dark mode + // Logo + return ( -
- Logo - by - PrimeReact -
+
+
+
+
+ Логотип +
+ Кыргызстан, 723500, г. Ош, ул. Ленина, 331, ОшГУ Главный корпус + Общий отдел: +996 3222 7-07-12, + факс +996 3222 7-09-15, + edu@oshsu.kg +
+
+
+
+ +

+ Курстар +

+
+
+ lorem ipsum + lorem ipsum + lorem ipsum +
+
+
+
+ +

+ Байланыш +

+
+
+ lorem ipsum + lorem ipsum + lorem ipsum +
+
+
+

+ © 2025 ОшГУ | 2025 OshSU - IT Academy Ошский Государственный Университет oshsu.kg +

+
+
); }; diff --git a/layout/AppMenu.tsx b/layout/AppMenu.tsx index b581ecf2..14f85869 100644 --- a/layout/AppMenu.tsx +++ b/layout/AppMenu.tsx @@ -4,7 +4,6 @@ import React, { useContext } from 'react'; import AppMenuitem from './AppMenuitem'; import { LayoutContext } from './context/layoutcontext'; import { MenuProvider } from './context/menucontext'; -import Link from 'next/link'; import { AppMenuItem } from '@/types'; const AppMenu = () => { @@ -12,163 +11,31 @@ const AppMenu = () => { const model: AppMenuItem[] = [ { - label: 'Home', - items: [{ label: 'Dashboard', icon: 'pi pi-fw pi-home', to: '/' }] + label: 'Баракчалар', + items: [{ label: 'Башкы баракча', icon: 'pi pi-fw pi-home', to: '/' }] }, { - label: 'UI Components', + label: '', items: [ - { label: 'Form Layout', icon: 'pi pi-fw pi-id-card', to: '/uikit/formlayout' }, - { label: 'Input', icon: 'pi pi-fw pi-check-square', to: '/uikit/input' }, - { label: 'Float Label', icon: 'pi pi-fw pi-bookmark', to: '/uikit/floatlabel' }, - { label: 'Invalid State', icon: 'pi pi-fw pi-exclamation-circle', to: '/uikit/invalidstate' }, - { label: 'Button', icon: 'pi pi-fw pi-mobile', to: '/uikit/button', class: 'rotated-icon' }, - { label: 'Table', icon: 'pi pi-fw pi-table', to: '/uikit/table' }, - { label: 'List', icon: 'pi pi-fw pi-list', to: '/uikit/list' }, - { label: 'Tree', icon: 'pi pi-fw pi-share-alt', to: '/uikit/tree' }, - { label: 'Panel', icon: 'pi pi-fw pi-tablet', to: '/uikit/panel' }, - { label: 'Overlay', icon: 'pi pi-fw pi-clone', to: '/uikit/overlay' }, - { label: 'Media', icon: 'pi pi-fw pi-image', to: '/uikit/media' }, - { label: 'Menu', icon: 'pi pi-fw pi-bars', to: '/uikit/menu', preventExact: true }, - { label: 'Message', icon: 'pi pi-fw pi-comment', to: '/uikit/message' }, - { label: 'File', icon: 'pi pi-fw pi-file', to: '/uikit/file' }, - { label: 'Chart', icon: 'pi pi-fw pi-chart-bar', to: '/uikit/charts' }, - { label: 'Misc', icon: 'pi pi-fw pi-circle', to: '/uikit/misc' } + { label: 'Dashboard', icon: 'pi pi-fw pi-id-card', to: '/' }, + { label: 'Курстар', icon: 'pi pi-fw pi-id-card', to: '/course' }, + // { label: 'Input', icon: 'pi pi-fw pi-check-square', to: '/uikit/input' }, + // { label: 'Float Label', icon: 'pi pi-fw pi-bookmark', to: '/uikit/floatlabel' }, + // { label: 'Invalid State', icon: 'pi pi-fw pi-exclamation-circle', to: '/uikit/invalidstate' }, + // { label: 'Button', icon: 'pi pi-fw pi-mobile', to: '/uikit/button', class: 'rotated-icon' }, + // { label: 'Table', icon: 'pi pi-fw pi-table', to: '/uikit/table' }, + // { label: 'List', icon: 'pi pi-fw pi-list', to: '/uikit/list' }, + // { label: 'Tree', icon: 'pi pi-fw pi-share-alt', to: '/uikit/tree' }, + // { label: 'Panel', icon: 'pi pi-fw pi-tablet', to: '/uikit/panel' }, + // { label: 'Overlay', icon: 'pi pi-fw pi-clone', to: '/uikit/overlay' }, + // { label: 'Media', icon: 'pi pi-fw pi-image', to: '/uikit/media' }, + // { label: 'Menu', icon: 'pi pi-fw pi-bars', to: '/uikit/menu', preventExact: true }, + // { label: 'Message', icon: 'pi pi-fw pi-comment', to: '/uikit/message' }, + // { label: 'File', icon: 'pi pi-fw pi-file', to: '/uikit/file' }, + // { label: 'Chart', icon: 'pi pi-fw pi-chart-bar', to: '/uikit/charts' }, + // { label: 'Misc', icon: 'pi pi-fw pi-circle', to: '/uikit/misc' } ] }, - { - label: 'Prime Blocks', - items: [ - { label: 'Free Blocks', icon: 'pi pi-fw pi-eye', to: '/blocks', badge: 'NEW' }, - { label: 'All Blocks', icon: 'pi pi-fw pi-globe', url: 'https://blocks.primereact.org', target: '_blank' } - ] - }, - { - label: 'Utilities', - items: [ - { label: 'PrimeIcons', icon: 'pi pi-fw pi-prime', to: '/utilities/icons' }, - { label: 'PrimeFlex', icon: 'pi pi-fw pi-desktop', url: 'https://primeflex.org/', target: '_blank' } - ] - }, - { - label: 'Pages', - icon: 'pi pi-fw pi-briefcase', - to: '/pages', - items: [ - { - label: 'Landing', - icon: 'pi pi-fw pi-globe', - to: '/landing' - }, - { - label: 'Auth', - icon: 'pi pi-fw pi-user', - items: [ - { - label: 'Login', - icon: 'pi pi-fw pi-sign-in', - to: '/auth/login' - }, - { - label: 'Error', - icon: 'pi pi-fw pi-times-circle', - to: '/auth/error' - }, - { - label: 'Access Denied', - icon: 'pi pi-fw pi-lock', - to: '/auth/access' - } - ] - }, - { - label: 'Crud', - icon: 'pi pi-fw pi-pencil', - to: '/pages/crud' - }, - { - label: 'Timeline', - icon: 'pi pi-fw pi-calendar', - to: '/pages/timeline' - }, - { - label: 'Not Found', - icon: 'pi pi-fw pi-exclamation-circle', - to: '/pages/notfound' - }, - { - label: 'Empty', - icon: 'pi pi-fw pi-circle-off', - to: '/pages/empty' - } - ] - }, - { - label: 'Hierarchy', - items: [ - { - label: 'Submenu 1', - icon: 'pi pi-fw pi-bookmark', - items: [ - { - label: 'Submenu 1.1', - icon: 'pi pi-fw pi-bookmark', - items: [ - { label: 'Submenu 1.1.1', icon: 'pi pi-fw pi-bookmark' }, - { label: 'Submenu 1.1.2', icon: 'pi pi-fw pi-bookmark' }, - { label: 'Submenu 1.1.3', icon: 'pi pi-fw pi-bookmark' } - ] - }, - { - label: 'Submenu 1.2', - icon: 'pi pi-fw pi-bookmark', - items: [{ label: 'Submenu 1.2.1', icon: 'pi pi-fw pi-bookmark' }] - } - ] - }, - { - label: 'Submenu 2', - icon: 'pi pi-fw pi-bookmark', - items: [ - { - label: 'Submenu 2.1', - icon: 'pi pi-fw pi-bookmark', - items: [ - { label: 'Submenu 2.1.1', icon: 'pi pi-fw pi-bookmark' }, - { label: 'Submenu 2.1.2', icon: 'pi pi-fw pi-bookmark' } - ] - }, - { - label: 'Submenu 2.2', - icon: 'pi pi-fw pi-bookmark', - items: [{ label: 'Submenu 2.2.1', icon: 'pi pi-fw pi-bookmark' }] - } - ] - } - ] - }, - { - label: 'Get Started', - items: [ - { - label: 'Documentation', - icon: 'pi pi-fw pi-question', - to: '/documentation' - }, - { - label: 'Figma', - url: 'https://www.dropbox.com/scl/fi/bhfwymnk8wu0g5530ceas/sakai-2023.fig?rlkey=u0c8n6xgn44db9t4zkd1brr3l&dl=0', - icon: 'pi pi-fw pi-pencil', - target: '_blank' - }, - { - label: 'View Source', - icon: 'pi pi-fw pi-search', - url: 'https://github.com/primefaces/sakai-react', - target: '_blank' - } - ] - } ]; return ( @@ -177,10 +44,6 @@ const AppMenu = () => { {model.map((item, i) => { return !item?.seperator ? :
  • ; })} - - - Prime Blocks - ); diff --git a/layout/AppTopbar.tsx b/layout/AppTopbar.tsx index 9a2c8849..503344ad 100644 --- a/layout/AppTopbar.tsx +++ b/layout/AppTopbar.tsx @@ -1,13 +1,19 @@ /* eslint-disable @next/next/no-img-element */ import Link from 'next/link'; -import { classNames } from 'primereact/utils'; import React, { forwardRef, useContext, useImperativeHandle, useRef } from 'react'; +import Tiered from '@/app/components/popUp/Tiered'; +import FancyLinkBtn from '@/app/components/buttons/FancyLinkBtn'; +import { classNames } from 'primereact/utils'; import { AppTopbarRef } from '@/types'; import { LayoutContext } from './context/layoutcontext'; +import { useMediaQuery } from '@/hooks/useMediaQuery'; +import { usePathname } from 'next/navigation'; +import { logout } from '@/utils/logout'; const AppTopbar = forwardRef((props, ref) => { - const { layoutConfig, layoutState, onMenuToggle, showProfileSidebar } = useContext(LayoutContext); + const { layoutConfig, layoutState, onMenuToggle, showProfileSidebar, user, setUser, setGlobalLoading } = useContext(LayoutContext); + const menubuttonRef = useRef(null); const topbarmenuRef = useRef(null); const topbarmenubuttonRef = useRef(null); @@ -18,36 +24,165 @@ const AppTopbar = forwardRef((props, ref) => { topbarmenubutton: topbarmenubuttonRef.current })); + const pathName = usePathname(); + const media = useMediaQuery('(max-width: 1000px)'); + + const items = [ + { + label: 'Ачык онлайн курстар', + icon: 'pi pi-file', + items: [], + url: '/login' + }, + { + label: 'Бакалавриат', + icon: 'pi pi-file', + items: [] + }, + { + label: 'Магистратура', + icon: 'pi pi-file', + items: [] + }, + { + label: 'Кошумча билим берүү', + icon: 'pi pi-file', + items: [] + } + ]; + + const mobileMenu = [ + user + ? { + label: 'Профиль', + icon: 'pi pi-user', + items: [ + { + label: '', + template: ( +
    +
    + {user?.last_name} + {user?.name} +
    + {user?.email} +
    + ) + }, + { + label: 'Чыгуу', + icon: 'pi pi-sign-out', + items: [], + command: () => { + logout({ setUser, setGlobalLoading }); + } + } + ] + } + : { + label: 'Кирүү', + icon: 'pi pi-sign-in', + items: [], + url: '/auth/login' + }, + { + label: 'ОшМУнун сайты', + icon: '', + items: [], + url: 'https://oshsu.kg' + }, + { + label: 'Байланыш', + icon: '', + items: [] + } + ]; + + // profile + const profileItems = [ + { + label: '', + template: ( +
    +
    + {user?.last_name} + {user?.name} +
    + {user?.email} +
    + ) + + }, + { + label: 'Чыгуу', + icon: 'pi pi-sign-out', + items: [], + command: () => { + logout({ setUser, setGlobalLoading }); + } + } + ]; + return (
    - logo - SAKAI + {/* logo */} + logo +

    Цифровой кампус ОшГУ

    - - - + {pathName !== '/' ? ( + + ) : ( + '' + )}
    - - - - - +
    + {media ? ( + + ) : ( +
    + {/* + */} + + ОшМУнун сайты + + + Байланыш + +
    + )} + + {user && user ? ( +
    + +
    + ) : ( +
    + + + +
    + )} +
    ); diff --git a/layout/StudentLayout.tsx b/layout/StudentLayout.tsx new file mode 100644 index 00000000..770bf194 --- /dev/null +++ b/layout/StudentLayout.tsx @@ -0,0 +1,159 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +'use client'; + +import { useRouter } from 'next/navigation'; +import { useEventListener, useMountEffect, useUnmountEffect } from 'primereact/hooks'; +import React, { useContext, useEffect, useRef, useState } from 'react'; +import { classNames } from 'primereact/utils'; +import AppFooter from './AppFooter'; +import AppSidebar from './AppSidebar'; +import AppTopbar from './AppTopbar'; +import AppConfig from './AppConfig'; +import { LayoutContext } from './context/layoutcontext'; +import { PrimeReactContext } from 'primereact/api'; +import { ChildContainerProps, LayoutState, AppTopbarRef } from '@/types'; +import { usePathname, useSearchParams } from 'next/navigation'; + +const StudentLayout = ({ children }: ChildContainerProps) => { + const { layoutConfig, layoutState, setLayoutState, user } = useContext(LayoutContext); + const { setRipple } = useContext(PrimeReactContext); + const topbarRef = useRef(null); + const sidebarRef = useRef(null); + const [permission, setPermission] = useState(true); + const [bindMenuOutsideClickListener, unbindMenuOutsideClickListener] = useEventListener({ + type: 'click', + listener: (event) => { + const isOutsideClicked = !( + sidebarRef.current?.isSameNode(event.target as Node) || + sidebarRef.current?.contains(event.target as Node) || + topbarRef.current?.menubutton?.isSameNode(event.target as Node) || + topbarRef.current?.menubutton?.contains(event.target as Node) + ); + + if (isOutsideClicked) { + hideMenu(); + } + } + }); + + const pathname = usePathname(); + const searchParams = useSearchParams(); + useEffect(() => { + hideMenu(); + hideProfileMenu(); + }, [pathname, searchParams]); + + const [bindProfileMenuOutsideClickListener, unbindProfileMenuOutsideClickListener] = useEventListener({ + type: 'click', + listener: (event) => { + const isOutsideClicked = !( + topbarRef.current?.topbarmenu?.isSameNode(event.target as Node) || + topbarRef.current?.topbarmenu?.contains(event.target as Node) || + topbarRef.current?.topbarmenubutton?.isSameNode(event.target as Node) || + topbarRef.current?.topbarmenubutton?.contains(event.target as Node) + ); + + if (isOutsideClicked) { + hideProfileMenu(); + } + } + }); + + const hideMenu = () => { + setLayoutState((prevLayoutState: LayoutState) => ({ + ...prevLayoutState, + overlayMenuActive: false, + staticMenuMobileActive: false, + menuHoverActive: false + })); + unbindMenuOutsideClickListener(); + unblockBodyScroll(); + }; + + const hideProfileMenu = () => { + setLayoutState((prevLayoutState: LayoutState) => ({ + ...prevLayoutState, + profileSidebarVisible: false + })); + unbindProfileMenuOutsideClickListener(); + }; + + const blockBodyScroll = (): void => { + if (document.body.classList) { + document.body.classList.add('blocked-scroll'); + } else { + document.body.className += ' blocked-scroll'; + } + }; + + const unblockBodyScroll = (): void => { + if (document.body.classList) { + document.body.classList.remove('blocked-scroll'); + } else { + document.body.className = document.body.className.replace(new RegExp('(^|\\b)' + 'blocked-scroll'.split(' ').join('|') + '(\\b|$)', 'gi'), ' '); + } + }; + + useEffect(() => { + if (layoutState.overlayMenuActive || layoutState.staticMenuMobileActive) { + bindMenuOutsideClickListener(); + } + + layoutState.staticMenuMobileActive && blockBodyScroll(); + }, [layoutState.overlayMenuActive, layoutState.staticMenuMobileActive]); + + useEffect(() => { + if (layoutState.profileSidebarVisible) { + bindProfileMenuOutsideClickListener(); + } + }, [layoutState.profileSidebarVisible]); + + useUnmountEffect(() => { + unbindMenuOutsideClickListener(); + unbindProfileMenuOutsideClickListener(); + }); + + const containerClass = classNames('layout-wrapper', { + 'layout-overlay': layoutConfig.menuMode === 'overlay', + 'layout-static': layoutConfig.menuMode === 'static', + 'layout-static-inactive': layoutState.staticMenuDesktopInactive && layoutConfig.menuMode === 'static', + 'layout-overlay-active': layoutState.overlayMenuActive, + 'layout-mobile-active': layoutState.staticMenuMobileActive, + 'p-input-filled': layoutConfig.inputStyle === 'filled', + 'p-ripple-disabled': !layoutConfig.ripple + }); + + const requireRole = () => { + if(!user?.is_student){ + console.log('Не имеете доступ! student'); + setPermission(false); + // window.location.href = '/auth/login'; + } + setPermission(true); + } + + useEffect(()=> { + requireRole(); + },[user]); + + if(permission) return null; + + return ( + +
    + +
    + +
    +
    +
    {children}
    + {/* */} +
    + +
    +
    +
    + ); +}; + +export default StudentLayout; diff --git a/layout/context/layoutcontext.tsx b/layout/context/layoutcontext.tsx index 58e491dc..4e7e8d6f 100644 --- a/layout/context/layoutcontext.tsx +++ b/layout/context/layoutcontext.tsx @@ -1,6 +1,13 @@ 'use client'; -import React, { useState, createContext } from 'react'; +import React, { useState, createContext, useEffect } from 'react'; import { LayoutState, ChildContainerProps, LayoutConfig, LayoutContextProps } from '@/types'; +import SessionManager from '@/app/components/SessionManager'; +import GlobalLoading from '@/app/components/loading/GlobalLoading'; +import Message from '@/app/components/messages/Message'; +import { ConfirmDialog } from 'primereact/confirmdialog'; +import { User } from '@/types/user'; +import { MessageType } from '@/types/messageType'; + export const LayoutContext = createContext({} as LayoutContextProps); export const LayoutProvider = ({ children }: ChildContainerProps) => { @@ -22,6 +29,15 @@ export const LayoutProvider = ({ children }: ChildContainerProps) => { menuHoverActive: false }); + // 👇 Добавляем пользователя + const [user, setUser] = useState(null); + + // Глобальная загрузка + const [globalLoading, setGlobalLoading] = useState(false); + + // Сообщение об ошибке/успехе + const [message, setMessage] = useState({state:false, value:{}}); + const onMenuToggle = () => { if (isOverlay()) { setLayoutState((prevLayoutState) => ({ ...prevLayoutState, overlayMenuActive: !prevLayoutState.overlayMenuActive })); @@ -52,8 +68,20 @@ export const LayoutProvider = ({ children }: ChildContainerProps) => { layoutState, setLayoutState, onMenuToggle, - showProfileSidebar + showProfileSidebar, + user, + setUser, + globalLoading, + setGlobalLoading, + message, + setMessage }; - return {children}; + return + + + + {message.state && } + {children} + ; }; diff --git a/layout/layout.tsx b/layout/layout.tsx index 8421b2ca..e886a689 100644 --- a/layout/layout.tsx +++ b/layout/layout.tsx @@ -3,7 +3,7 @@ import { useRouter } from 'next/navigation'; import { useEventListener, useMountEffect, useUnmountEffect } from 'primereact/hooks'; -import React, { useContext, useEffect, useRef } from 'react'; +import React, { useContext, useEffect, useRef, useState } from 'react'; import { classNames } from 'primereact/utils'; import AppFooter from './AppFooter'; import AppSidebar from './AppSidebar'; @@ -15,10 +15,11 @@ import { ChildContainerProps, LayoutState, AppTopbarRef } from '@/types'; import { usePathname, useSearchParams } from 'next/navigation'; const Layout = ({ children }: ChildContainerProps) => { - const { layoutConfig, layoutState, setLayoutState } = useContext(LayoutContext); + const { layoutConfig, layoutState, setLayoutState, user } = useContext(LayoutContext); const { setRipple } = useContext(PrimeReactContext); const topbarRef = useRef(null); const sidebarRef = useRef(null); + const [permission, setPermission] = useState(false); const [bindMenuOutsideClickListener, unbindMenuOutsideClickListener] = useEventListener({ type: 'click', listener: (event) => { @@ -122,6 +123,21 @@ const Layout = ({ children }: ChildContainerProps) => { 'p-ripple-disabled': !layoutConfig.ripple }); + const requireRole = () => { + if(!user?.is_working){ + console.log('Не имеете доступ! working'); + setPermission(false); + // window.location.href = '/auth/login'; + } + setPermission(true); + } + + useEffect(()=> { + requireRole(); + },[user]); + + // if(permission) return null; + return (
    @@ -131,7 +147,7 @@ const Layout = ({ children }: ChildContainerProps) => {
    {children}
    - + {/* */}
    diff --git a/package-lock.json b/package-lock.json index 332e8ef7..62f99187 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,23 +8,39 @@ "name": "sakai-react", "version": "10.1.0", "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.7.2", + "@fortawesome/free-solid-svg-icons": "^6.7.2", + "@fortawesome/react-fontawesome": "^0.2.2", + "@hookform/resolvers": "^5.1.1", "@types/node": "20.3.1", "@types/react": "18.2.12", "@types/react-dom": "18.2.5", + "aos": "^2.3.4", + "axios": "^1.10.0", "chart.js": "4.2.1", + "countup": "^1.8.2", "next": "13.4.8", - "primeflex": "^3.3.1", - "primeicons": "^6.0.1", - "primereact": "10.2.1", + "primeflex": "^4.0.0", + "primeicons": "^7.0.0", + "primereact": "^10.9.6", + "quill": "^2.0.3", "react": "18.2.0", + "react-countup": "^6.5.3", "react-dom": "18.2.0", - "typescript": "5.1.3" + "react-hook-form": "^7.58.1", + "typescript": "5.1.3", + "yup": "^1.6.1" }, "devDependencies": { + "@tailwindcss/postcss": "^4.1.8", + "@types/aos": "^3.0.7", + "autoprefixer": "^10.4.21", "eslint": "8.43.0", "eslint-config-next": "13.4.6", + "postcss": "^8.5.4", "prettier": "^2.8.8", - "sass": "^1.63.4" + "sass": "^1.63.4", + "tailwindcss": "^4.1.8" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -36,6 +52,31 @@ "node": ">=0.10.0" } }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@babel/runtime": { "version": "7.23.6", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.6.tgz", @@ -103,6 +144,59 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz", + "integrity": "sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz", + "integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.7.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-solid-svg-icons": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.7.2.tgz", + "integrity": "sha512-GsBrnOzU8uj0LECDfD5zomZJIjrPhIlWU82AHwa2s40FKH+kcxQaBvBo3Z4TxyZHIyX8XTDxsyA33/Vx9eFuQA==", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.7.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/react-fontawesome": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz", + "integrity": "sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "@fortawesome/fontawesome-svg-core": "~1 || ~6", + "react": ">=16.3" + } + }, + "node_modules/@hookform/resolvers": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.1.1.tgz", + "integrity": "sha512-J/NVING3LMAEvexJkyTLjruSm7aOFx7QX21pzkiJfMoNG0wl5aFEjLTl7ay7IQb9EWY6AkrBy7tHL2Alijpdcg==", + "dependencies": { + "@standard-schema/utils": "^0.3.0" + }, + "peerDependencies": { + "react-hook-form": "^7.55.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.13", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", @@ -136,6 +230,66 @@ "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@kurkle/color": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", @@ -331,6 +485,11 @@ "integrity": "sha512-UY+FGM/2jjMkzQLn8pxcHGMaVLh9aEitG3zY2CiY7XHdLiz3bZOwa6oDxNqEMv7zZkV+cj5DOdz0cQ1BP5Hjgw==", "dev": true }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==" + }, "node_modules/@swc/helpers": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.1.tgz", @@ -339,6 +498,273 @@ "tslib": "^2.4.0" } }, + "node_modules/@tailwindcss/node": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.8.tgz", + "integrity": "sha512-OWwBsbC9BFAJelmnNcrKuf+bka2ZxCE2A4Ft53Tkg4uoiE67r/PMEYwCsourC26E+kmxfwE0hVzMdxqeW+xu7Q==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "enhanced-resolve": "^5.18.1", + "jiti": "^2.4.2", + "lightningcss": "1.30.1", + "magic-string": "^0.30.17", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.8" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.8.tgz", + "integrity": "sha512-d7qvv9PsM5N3VNKhwVUhpK6r4h9wtLkJ6lz9ZY9aeZgrUWk1Z8VPyqyDT9MZlem7GTGseRQHkeB1j3tC7W1P+A==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "detect-libc": "^2.0.4", + "tar": "^7.4.3" + }, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.8", + "@tailwindcss/oxide-darwin-arm64": "4.1.8", + "@tailwindcss/oxide-darwin-x64": "4.1.8", + "@tailwindcss/oxide-freebsd-x64": "4.1.8", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.8", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.8", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.8", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.8", + "@tailwindcss/oxide-linux-x64-musl": "4.1.8", + "@tailwindcss/oxide-wasm32-wasi": "4.1.8", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.8", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.8" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.8.tgz", + "integrity": "sha512-Fbz7qni62uKYceWYvUjRqhGfZKwhZDQhlrJKGtnZfuNtHFqa8wmr+Wn74CTWERiW2hn3mN5gTpOoxWKk0jRxjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.8.tgz", + "integrity": "sha512-RdRvedGsT0vwVVDztvyXhKpsU2ark/BjgG0huo4+2BluxdXo8NDgzl77qh0T1nUxmM11eXwR8jA39ibvSTbi7A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.8.tgz", + "integrity": "sha512-t6PgxjEMLp5Ovf7uMb2OFmb3kqzVTPPakWpBIFzppk4JE4ix0yEtbtSjPbU8+PZETpaYMtXvss2Sdkx8Vs4XRw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.8.tgz", + "integrity": "sha512-g8C8eGEyhHTqwPStSwZNSrOlyx0bhK/V/+zX0Y+n7DoRUzyS8eMbVshVOLJTDDC+Qn9IJnilYbIKzpB9n4aBsg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.8.tgz", + "integrity": "sha512-Jmzr3FA4S2tHhaC6yCjac3rGf7hG9R6Gf2z9i9JFcuyy0u79HfQsh/thifbYTF2ic82KJovKKkIB6Z9TdNhCXQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.8.tgz", + "integrity": "sha512-qq7jXtO1+UEtCmCeBBIRDrPFIVI4ilEQ97qgBGdwXAARrUqSn/L9fUrkb1XP/mvVtoVeR2bt/0L77xx53bPZ/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.8.tgz", + "integrity": "sha512-O6b8QesPbJCRshsNApsOIpzKt3ztG35gfX9tEf4arD7mwNinsoCKxkj8TgEE0YRjmjtO3r9FlJnT/ENd9EVefQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.8.tgz", + "integrity": "sha512-32iEXX/pXwikshNOGnERAFwFSfiltmijMIAbUhnNyjFr3tmWmMJWQKU2vNcFX0DACSXJ3ZWcSkzNbaKTdngH6g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.8.tgz", + "integrity": "sha512-s+VSSD+TfZeMEsCaFaHTaY5YNj3Dri8rST09gMvYQKwPphacRG7wbuQ5ZJMIJXN/puxPcg/nU+ucvWguPpvBDg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.8.tgz", + "integrity": "sha512-CXBPVFkpDjM67sS1psWohZ6g/2/cd+cq56vPxK4JeawelxwK4YECgl9Y9TjkE2qfF+9/s1tHHJqrC4SS6cVvSg==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@emnapi/wasi-threads": "^1.0.2", + "@napi-rs/wasm-runtime": "^0.2.10", + "@tybys/wasm-util": "^0.9.0", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.8.tgz", + "integrity": "sha512-7GmYk1n28teDHUjPlIx4Z6Z4hHEgvP5ZW2QS9ygnDAdI/myh3HTHjDqtSqgu1BpRoI4OiLx+fThAyA1JePoENA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.8.tgz", + "integrity": "sha512-fou+U20j+Jl0EHwK92spoWISON2OBnCazIc038Xj2TdweYV33ZRkS9nwqiUi2d/Wba5xg5UoHfvynnb/UB49cQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.8.tgz", + "integrity": "sha512-vB/vlf7rIky+w94aWMw34bWW1ka6g6C3xIOdICKX2GC0VcLtL6fhlLiafF0DVIwa9V6EHz8kbWMkS2s2QvvNlw==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.8", + "@tailwindcss/oxide": "4.1.8", + "postcss": "^8.4.41", + "tailwindcss": "4.1.8" + } + }, + "node_modules/@types/aos": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/aos/-/aos-3.0.7.tgz", + "integrity": "sha512-sEhyFqvKauUJZDbvAB3Pggynrq6g+2PS4XB3tmUr+mDL1gfDJnwslUC4QQ7/l8UD+LWpr3RxZVR/rHoZrLqZVg==", + "dev": true + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -561,6 +987,16 @@ "node": ">= 8" } }, + "node_modules/aos": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/aos/-/aos-2.3.4.tgz", + "integrity": "sha512-zh/ahtR2yME4I51z8IttIt4lC1Nw0ktsFtmeDzID1m9naJnWXhCoARaCgNOGXb5CLy3zm+wqmRAEgMYB5E2HUw==", + "dependencies": { + "classlist-polyfill": "^1.0.3", + "lodash.debounce": "^4.0.6", + "lodash.throttle": "^4.0.1" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -721,6 +1157,48 @@ "has-symbols": "^1.0.3" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/autoprefixer": { + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -742,6 +1220,16 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz", + "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/axobject-query": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", @@ -788,6 +1276,38 @@ "node": ">=8" } }, + "node_modules/browserslist": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", + "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001718", + "electron-to-chromium": "^1.5.160", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -813,6 +1333,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -823,9 +1355,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001572", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001572.tgz", - "integrity": "sha512-1Pbh5FLmn5y4+QhNyJE9j3/7dK44dGB83/ZMjv/qJk86TvDbjk0LosiZo0i0WB0Vx607qMX9jYrn1VLHCkN4rw==", + "version": "1.0.30001720", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001720.tgz", + "integrity": "sha512-Ec/2yV2nNPwb4DnTANEV99ZWwm3ZWfdlfkQbWSDDt+PsXEVYwlhPH8tdMaPunYTKKmz7AnHi2oNEi1GcmKCD8g==", "funding": [ { "type": "opencollective", @@ -907,6 +1439,20 @@ "node": ">= 6" } }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/classlist-polyfill": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/classlist-polyfill/-/classlist-polyfill-1.2.0.tgz", + "integrity": "sha512-GzIjNdcEtH4ieA2S8NmrSxv7DfEV5fmixQeyTmqmRmRJPGpRBaSnA2a0VrCjyT8iW8JjEdMbKzDotAJf+ajgaQ==" + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -930,12 +1476,33 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/countup": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/countup/-/countup-1.8.2.tgz", + "integrity": "sha512-1qOuy+h+dgLY4TroLb4ZC2hK7HYS2Z7mnSUvdzoYAU7EZbqOse1mKZta/KYPhDPfd9lPDyP3Tb4pH6a10RkoCw==" + }, + "node_modules/countup.js": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/countup.js/-/countup.js-2.8.2.tgz", + "integrity": "sha512-UtRoPH6udaru/MOhhZhI/GZHJKAyAxuKItD2Tr7AbrqrOPBX/uejWBBJt8q86169AMqKkE9h9/24kFWbUk/Bag==" + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -1015,6 +1582,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -1024,6 +1599,15 @@ "node": ">=6" } }, + "node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -1057,6 +1641,25 @@ "csstype": "^3.0.2" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.161", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.161.tgz", + "integrity": "sha512-hwtetwfKNZo/UlwHIVBlKZVdy7o8bIZxxKs0Mv/ROPiQQQmDgdm5a+KvKtBsxM8ZjFzTaCeLoodZ8jiBE3o9rA==", + "dev": true + }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -1064,9 +1667,9 @@ "dev": true }, "node_modules/enhanced-resolve": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", - "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", + "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", "dev": true, "dependencies": { "graceful-fs": "^4.2.4", @@ -1129,6 +1732,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-iterator-helpers": { "version": "1.0.15", "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz", @@ -1151,15 +1770,26 @@ "safe-array-concat": "^1.0.1" } }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-set-tostringtag": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", - "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", - "dev": true, + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dependencies": { - "get-intrinsic": "^1.2.2", - "has-tostringtag": "^1.0.0", - "hasown": "^2.0.0" + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -1191,6 +1821,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -1614,12 +2253,22 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==" + }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", @@ -1729,6 +2378,25 @@ "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -1738,6 +2406,34 @@ "is-callable": "^1.1.3" } }, + "node_modules/form-data": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", + "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -1762,7 +2458,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -1795,20 +2490,40 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", - "dev": true, - "dependencies": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-symbol-description": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", @@ -1925,12 +2640,11 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1990,10 +2704,9 @@ } }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "engines": { "node": ">= 0.4" }, @@ -2002,12 +2715,11 @@ } }, "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dependencies": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -2017,10 +2729,9 @@ } }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", - "dev": true, + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dependencies": { "function-bind": "^1.1.2" }, @@ -2449,6 +3160,15 @@ "set-function-name": "^2.0.1" } }, + "node_modules/jiti": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "dev": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -2508,47 +3228,275 @@ "object.values": "^1.1.6" }, "engines": { - "node": ">=4.0" + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", + "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==", + "dev": true + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", + "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", + "dev": true, + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-darwin-arm64": "1.30.1", + "lightningcss-darwin-x64": "1.30.1", + "lightningcss-freebsd-x64": "1.30.1", + "lightningcss-linux-arm-gnueabihf": "1.30.1", + "lightningcss-linux-arm64-gnu": "1.30.1", + "lightningcss-linux-arm64-musl": "1.30.1", + "lightningcss-linux-x64-gnu": "1.30.1", + "lightningcss-linux-x64-musl": "1.30.1", + "lightningcss-win32-arm64-msvc": "1.30.1", + "lightningcss-win32-x64-msvc": "1.30.1" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", + "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", + "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", + "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", + "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", + "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", + "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", + "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "json-buffer": "3.0.1" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/language-subtag-registry": { - "version": "0.3.22", - "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", - "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==", - "dev": true - }, - "node_modules/language-tags": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", - "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", + "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "language-subtag-registry": "^0.3.20" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.10" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", + "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", + "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 0.8.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, "node_modules/locate-path": { @@ -2566,12 +3514,38 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead." + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -2595,6 +3569,23 @@ "node": ">=10" } }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -2617,6 +3608,25 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -2638,6 +3648,42 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "dev": true, + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -2645,9 +3691,9 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "funding": [ { "type": "github", @@ -2717,6 +3763,35 @@ } } }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", + "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], + "dependencies": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -2726,6 +3801,15 @@ "node": ">=0.10.0" } }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -2899,6 +3983,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parchment": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/parchment/-/parchment-3.0.0.tgz", + "integrity": "sha512-HUrJFQ/StvgmXRcQ1ftY6VEZUq3jA2t9ncFN4F84J/vN0/FPpQF+8FKXb3l6fLces6q0uOHj6NJn+2xvZnxO6A==" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -2954,9 +4043,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -2971,9 +4060,10 @@ } }, "node_modules/postcss": { - "version": "8.4.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", - "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz", + "integrity": "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==", + "dev": true, "funding": [ { "type": "opencollective", @@ -2982,17 +4072,27 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "nanoid": "^3.3.4", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -3018,19 +4118,19 @@ } }, "node_modules/primeflex": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/primeflex/-/primeflex-3.3.1.tgz", - "integrity": "sha512-zaOq3YvcOYytbAmKv3zYc+0VNS9Wg5d37dfxZnveKBFPr7vEIwfV5ydrpiouTft8MVW6qNjfkaQphHSnvgQbpQ==" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/primeflex/-/primeflex-4.0.0.tgz", + "integrity": "sha512-UOEZCRjR36+sm5bUpDhS1xbA068l9VC6y1aTNVqQPtXuKIdPTqAWHRUxj3mKAoPrQ9W373ooJJMgNVXfiaw04g==" }, "node_modules/primeicons": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/primeicons/-/primeicons-6.0.1.tgz", - "integrity": "sha512-KDeO94CbWI4pKsPnYpA1FPjo79EsY9I+M8ywoPBSf9XMXoe/0crjbUK7jcQEDHuc0ZMRIZsxH3TYLv4TUtHmAA==" + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/primeicons/-/primeicons-7.0.0.tgz", + "integrity": "sha512-jK3Et9UzwzTsd6tzl2RmwrVY/b8raJ3QZLzoDACj+oTJ0oX7L9Hy+XnVwgo4QVKlKpnP/Ur13SXV/pVh4LzaDw==" }, "node_modules/primereact": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/primereact/-/primereact-10.2.1.tgz", - "integrity": "sha512-F25053E1z+fod6V7AJ1Ix/DwSPkFQotk2+Idm0XkFsN+gQEywH7DgtbvNKpUA+LEq48h2+/WVK3D0V8IAm1Npg==", + "version": "10.9.6", + "resolved": "https://registry.npmjs.org/primereact/-/primereact-10.9.6.tgz", + "integrity": "sha512-0Jjz/KzfUURSHaPTXJwjL2Dc7CDPnbO17MivyJz7T5smGAMLY5d+IqpQhV61R22G/rDmhMh3+32LCNva2M8fRw==", "dependencies": { "@types/react-transition-group": "^4.4.1", "react-transition-group": "^4.4.1" @@ -3039,9 +4139,9 @@ "node": ">=14.0.0" }, "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -3059,6 +4159,16 @@ "react-is": "^16.13.1" } }, + "node_modules/property-expr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", + "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==" + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3088,6 +4198,33 @@ } ] }, + "node_modules/quill": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/quill/-/quill-2.0.3.tgz", + "integrity": "sha512-xEYQBqfYx/sfb33VJiKnSJp8ehloavImQ2A6564GAbqG55PGw1dAWUn1MUbQB62t0azawUS2CZZhWCjO8gRvTw==", + "dependencies": { + "eventemitter3": "^5.0.1", + "lodash-es": "^4.17.21", + "parchment": "^3.0.0", + "quill-delta": "^5.1.0" + }, + "engines": { + "npm": ">=8.2.3" + } + }, + "node_modules/quill-delta": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-5.1.0.tgz", + "integrity": "sha512-X74oCeRI4/p0ucjb5Ma8adTXd9Scumz367kkMK5V/IatcX6A0vlgLgKbzXWy5nZmCGeNJm2oQX0d2Eqj+ZIlCA==", + "dependencies": { + "fast-diff": "^1.3.0", + "lodash.clonedeep": "^4.5.0", + "lodash.isequal": "^4.5.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -3099,6 +4236,17 @@ "node": ">=0.10.0" } }, + "node_modules/react-countup": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/react-countup/-/react-countup-6.5.3.tgz", + "integrity": "sha512-udnqVQitxC7QWADSPDOxVWULkLvKUWrDapn5i53HE4DPRVgs+Y5rr4bo25qEl8jSh+0l2cToJgGMx+clxPM3+w==", + "dependencies": { + "countup.js": "^2.8.0" + }, + "peerDependencies": { + "react": ">= 16.3.0" + } + }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -3111,6 +4259,21 @@ "react": "^18.2.0" } }, + "node_modules/react-hook-form": { + "version": "7.58.1", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.58.1.tgz", + "integrity": "sha512-Lml/KZYEEFfPhUVgE0RdCVpnC4yhW+PndRhbiTtdvSlQTL8IfVR+iQkBjLIvmmc6+GGoVeM11z37ktKFPAb0FA==", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -3414,9 +4577,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "engines": { "node": ">=0.10.0" } @@ -3573,6 +4736,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tailwindcss": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.8.tgz", + "integrity": "sha512-kjeW8gjdxasbmFKpVGrGd5T4i40mV5J2Rasw48QARfYeQ8YS9x02ON9SFWax3Qf616rt4Cp3nVNIj6Hd1mP3og==", + "dev": true + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -3582,12 +4751,43 @@ "node": ">=6" } }, + "node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "dev": true, + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "engines": { + "node": ">=18" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/tiny-case": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", + "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -3600,6 +4800,11 @@ "node": ">=8.0" } }, + "node_modules/toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" + }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -3754,6 +4959,36 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -3890,6 +5125,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yup": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/yup/-/yup-1.6.1.tgz", + "integrity": "sha512-JED8pB50qbA4FOkDol0bYF/p60qSEDQqBD0/qeIrUCG1KbPBIQ776fCUNb9ldbPcSTxA69g/47XTo4TqWiuXOA==", + "dependencies": { + "property-expr": "^2.0.5", + "tiny-case": "^1.0.3", + "toposort": "^2.0.2", + "type-fest": "^2.19.0" + } + }, + "node_modules/yup/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zod": { "version": "3.21.4", "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz", diff --git a/package.json b/package.json index 8071b50b..8d4703e1 100644 --- a/package.json +++ b/package.json @@ -10,22 +10,38 @@ "lint": "next lint" }, "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.7.2", + "@fortawesome/free-solid-svg-icons": "^6.7.2", + "@fortawesome/react-fontawesome": "^0.2.2", + "@hookform/resolvers": "^5.1.1", "@types/node": "20.3.1", "@types/react": "18.2.12", "@types/react-dom": "18.2.5", + "aos": "^2.3.4", + "axios": "^1.10.0", "chart.js": "4.2.1", + "countup": "^1.8.2", "next": "13.4.8", - "primeflex": "^3.3.1", - "primeicons": "^6.0.1", - "primereact": "10.2.1", + "primeflex": "^4.0.0", + "primeicons": "^7.0.0", + "primereact": "^10.9.6", + "quill": "^2.0.3", "react": "18.2.0", + "react-countup": "^6.5.3", "react-dom": "18.2.0", - "typescript": "5.1.3" + "react-hook-form": "^7.58.1", + "typescript": "5.1.3", + "yup": "^1.6.1" }, "devDependencies": { + "@tailwindcss/postcss": "^4.1.8", + "@types/aos": "^3.0.7", + "autoprefixer": "^10.4.21", "eslint": "8.43.0", "eslint-config-next": "13.4.6", + "postcss": "^8.5.4", "prettier": "^2.8.8", - "sass": "^1.63.4" + "sass": "^1.63.4", + "tailwindcss": "^4.1.8" } -} \ No newline at end of file +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 00000000..8f3250b7 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + '@tailwindcss/postcss': {}, // Используем новый плагин + autoprefixer: {}, + } +} \ No newline at end of file diff --git a/public/layout/images/home-banner-phone.png b/public/layout/images/home-banner-phone.png new file mode 100644 index 00000000..399a19dc Binary files /dev/null and b/public/layout/images/home-banner-phone.png differ diff --git a/public/layout/images/home-two.png b/public/layout/images/home-two.png new file mode 100644 index 00000000..3372a6ad Binary files /dev/null and b/public/layout/images/home-two.png differ diff --git a/public/layout/images/logo-kg.svg b/public/layout/images/logo-kg.svg new file mode 100644 index 00000000..ac33ea12 --- /dev/null +++ b/public/layout/images/logo-kg.svg @@ -0,0 +1,205 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + В + + + + + Р + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/layout/images/logo-remove-white.png b/public/layout/images/logo-remove-white.png new file mode 100644 index 00000000..3d28f7d5 Binary files /dev/null and b/public/layout/images/logo-remove-white.png differ diff --git a/public/layout/images/logo-remove.png b/public/layout/images/logo-remove.png new file mode 100644 index 00000000..929f087a Binary files /dev/null and b/public/layout/images/logo-remove.png differ diff --git a/public/layout/images/man.png b/public/layout/images/man.png new file mode 100644 index 00000000..0168ee16 Binary files /dev/null and b/public/layout/images/man.png differ diff --git a/public/layout/images/no-image.png b/public/layout/images/no-image.png new file mode 100644 index 00000000..3f257212 Binary files /dev/null and b/public/layout/images/no-image.png differ diff --git a/public/layout/images/shape.png b/public/layout/images/shape.png new file mode 100644 index 00000000..a82ce023 Binary files /dev/null and b/public/layout/images/shape.png differ diff --git a/public/layout/images/shape1.png b/public/layout/images/shape1.png new file mode 100644 index 00000000..bee24c65 Binary files /dev/null and b/public/layout/images/shape1.png differ diff --git a/public/layout/images/shape2.png b/public/layout/images/shape2.png new file mode 100644 index 00000000..f1fe1c5b Binary files /dev/null and b/public/layout/images/shape2.png differ diff --git a/public/layout/svg/red-logo.svg b/public/layout/svg/red-logo.svg new file mode 100644 index 00000000..69deed08 --- /dev/null +++ b/public/layout/svg/red-logo.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/layout/svg/white-logo.svg b/public/layout/svg/white-logo.svg new file mode 100644 index 00000000..d76c8ff6 --- /dev/null +++ b/public/layout/svg/white-logo.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/schemas/authSchema.tsx b/schemas/authSchema.tsx new file mode 100644 index 00000000..6d8159a5 --- /dev/null +++ b/schemas/authSchema.tsx @@ -0,0 +1,22 @@ +import * as yup from 'yup'; + +export const schema = yup.object().shape({ + email: yup.string() + .email('email туура эмес форматта') + .required('email талап кылынат!') + .matches( + /^[a-zA-Z0-9._%+-]+@oshsu\.kg$/, + 'email "@oshsu.kg" форматында болуш керек' + ) + .matches( + /^[a-zA-Z0-9._%+-]+@oshsu\.kg$/, // Стандартный формат email (только буквы, цифры, точки, подчеркивания и плюсы) + 'email "@oshsu.kg" форматында жана тыюу салынган символдор камтылбашы керек' + ), + password: yup.string() + .required('Сырсөз талап кылынат!') + .max(20, 'Сырсөз эң көп дегенде 20 белгиден турушу керек') + .matches( + /^[^!@#$%^&*()_+={}[\]:;"'`<>,.?/\\|]*$/, + 'Сырсөздө тыюу салынган символдор камтылбашы керек' + ), +}); \ No newline at end of file diff --git a/schemas/baseSchema.tsx b/schemas/baseSchema.tsx new file mode 100644 index 00000000..bbeacaf3 --- /dev/null +++ b/schemas/baseSchema.tsx @@ -0,0 +1,8 @@ +import * as yup from 'yup'; + +export const baseSchema = yup.object().shape({ + title: yup + .string() + .required('Талап кылынат!') + .max(20, 'Аталыштын узундугу макс 20 тамга') +}); diff --git a/schemas/lessonSchema.tsx b/schemas/lessonSchema.tsx new file mode 100644 index 00000000..7dda29f7 --- /dev/null +++ b/schemas/lessonSchema.tsx @@ -0,0 +1,13 @@ +import * as yup from 'yup'; + +export const lessonSchema = yup.object().shape({ + videoReq: yup + .string() + .required('Талап кылынат!') + .matches(/^https?:\/\/.+/, 'Видео шилтеме "http://" "https://" форматында болуш керек'), + usefulLink: yup + .string() + .required('Талап кылынат!') + .matches(/^https?:\/\/.+/, 'Шилтеме "http://" "https://" форматында болуш керек'), + title: yup.string().required('Талап кылынат!').max(20, 'Аталыштын узундугу макс 20 тамга') +}); \ No newline at end of file diff --git a/services/auth.tsx b/services/auth.tsx new file mode 100644 index 00000000..aec595d3 --- /dev/null +++ b/services/auth.tsx @@ -0,0 +1,37 @@ +import axiosInstance from '@/utils/axiosInstance'; +import { LoginType } from "@/types/login"; + +let url = ''; + +// const params = new URLSearchParams({ +// category_id: String(id), +// active: '' + param.active, +// ending: '' + param.ending, +// }); + +export const login = async (value:LoginType) => { + url = process.env.NEXT_PUBLIC_BASE_URL + '/login?'; + + try { + const res = await axiosInstance.post('/login?', value); + const data = res.data; + return data; + } catch (err) { + console.log('Ошибка при авторизации', err); + return []; + } +}; + +export const getUser = async () => { + url = process.env.NEXT_PUBLIC_BASE_URL + '/v1/user'; + + try { + const res = await axiosInstance.get(url); + + const data = res.data; + return data; + } catch (err) { + console.log('Ошибка при получении пользователя', err); + return []; + } +}; diff --git a/services/courses.tsx b/services/courses.tsx new file mode 100644 index 00000000..956f64b6 --- /dev/null +++ b/services/courses.tsx @@ -0,0 +1,334 @@ +import { CourseCreateType } from '@/types/courseCreateType'; +import { lessonStateType } from '@/types/lessonStateType'; +import axiosInstance from '@/utils/axiosInstance'; + +let url = ''; + +export const fetchCourses = async (page: number | null, limit: number | null) => { + try { + console.log('Номер запрашиваемой страницы ', page); + + const res = await axiosInstance.get(`/v1/teacher/courses?page=${Number(page)}&limit=${''}`); + const data = await res.data; + + return data; + } catch (error) { + console.error('Ошибка загрузки:', error); + } +}; + +export const addCourse = async (value: CourseCreateType) => { + const formData = new FormData(); + formData.append('title', value.title); + formData.append('description', value.description); + if (value.image instanceof File) { + formData.append('image', value.image); + } + formData.append('video_url', value.video_url); + + try { + const res = await axiosInstance.post(`/v1/teacher/courses/store`, formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }); + + console.log('Урок: ', res.data); + + return res.data; + } catch (err) { + console.log('Ошибка при добавлении курса', err); + return err; + } +}; + +export const deleteCourse = async (id: number) => { + url = process.env.NEXT_PUBLIC_BASE_URL + `/v1/teacher/courses/delete?course_id=${id}`; + + try { + const res = await axiosInstance.delete(`/v1/teacher/courses/delete?course_id=${id}`, { + headers: {'Content-Type': 'multipart/form-data'} + }); + + console.log(res.data); + return res.data; + } catch (err) { + console.log('Ошибка при удалении курса', err); + return err; + } +}; + +export const updateCourse = async (id: number | null, value: CourseCreateType) => { + console.log(value); + + url = process.env.NEXT_PUBLIC_BASE_URL + `/v1/teacher/courses/update?course_id=${id}`; + + const formData = new FormData(); + formData.append('title', value.title); + formData.append('description', value.description); + if (value.image instanceof File) { + formData.append('image', value.image); + } + formData.append('video_url', value.video_url); + + try { + const res = await axiosInstance.post(`/v1/teacher/courses/update?course_id=${id}`, formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }); + + const data = await res.data; + return data; + } catch (err) { + console.log('Ошибка при обновлении курса', err); + return err; + } +}; + +// Themes + +export const fetchCourseInfo = async (id: number | null) => { + try { + const res = await axiosInstance.get(`/v1/teacher/courses/show?course_id=${id}`); + const data = await res.data; + return data; + } catch (err) { + console.log('Ошибка при получении темы', err); + return err; + } +}; + +export const addThemes = async (id: number, value: string) => { + const formData = new FormData(); + formData.append('title', value); + + try { + const res = await axiosInstance.post(`/v1/teacher/lessons/store?course_id=${id}&title=${value}`, formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }); + + const data = await res.data; + return data; + } catch (err) { + console.log('Ошибка при добавлении темы', err); + return err; + } +}; + +export const fetchThemes = async (id: number) => { + try { + const res = await axiosInstance(`/v1/teacher/lessons?course_id=${id}`); + + const data = await res.data; + return data; + } catch (err) { + console.log('Ошибка при получении темы', err); + return err; + } +}; + +export const updateTheme = async (course_id: number | null, theme_id: number | null, value: CourseCreateType) => { + const formData = new FormData(); + formData.append('title', value.title); + + try { + const res = await axiosInstance.post(`/v1/teacher/lessons/update?course_id=${course_id}&title=${value}&lesson_id=${theme_id}`, formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }); + + const data = await res.data; + return data; + } catch (err) { + console.log('Ошибка при обновлении темы', err); + return err; + } +}; + +export const deleteTheme = async (id: number) => { + try { + const res = await axiosInstance.delete(`/v1/teacher/lessons/delete?lesson_id=${id}`); + + const data = await res.data; + return data; + } catch (err) { + console.log('Ошибка при удалении темы', err); + return err; + } +}; + +// Lessons + +export const addLesson = async ( + type: string, + token: string | null, + courseId: number | null, + lessonId: number | null, + value: lessonStateType | string, + videoType: number | null) => { + + let formData = new FormData(); + console.log(value, type, courseId, lessonId, videoType); + let headers: Record = token ? { Authorization: `Bearer ${token}` } : {}; + let url = ''; + let body: lessonStateType | string | FormData = value; + + if (type === 'text') { + url = `/v1/teacher/textcontent/store?course_id=${courseId}&lesson_id=${lessonId}&content=${value}`; + headers['Content-Type'] = 'application/json'; + body = value; + } else if(type === 'doc' && (typeof value === 'object') && value !== null){ + url = `/v1/teacher/document/store?lesson_id=${lessonId}`; + formData.append('lesson_id', String(lessonId)); + if(value.file){ + formData.append('document', value.file && value.file); + } + formData.append('title', String(value.title)); + formData.append('description', String(value?.description)); + body = formData; + } else if(type === 'url' && typeof value === 'object' && value !== null){ + url = `v1/teacher/usefullinks/store?lesson_id=${lessonId}&title=${value.title}&description=${value.description}&url=${value.url}`; + formData.append('lesson_id', String(lessonId)); + formData.append('document', String(value?.url)); + formData.append('title', String(value.title)); + formData.append('description', String(value?.description)); + body = formData; + } else if(type === 'video' && typeof value === 'object' && value !== null){ + url = `v1/teacher/video/store?lesson_id=${lessonId}&title=${value.title}&description=${value.description}&video_link=${value.video_link}&video_type_id=${videoType}&cover`; + formData.append('lesson_id', String(lessonId)); + formData.append('video_link', String(value?.video_link)); + formData.append('title', String(value?.title)); + formData.append('description', String(value?.description)); + body = formData; + } + + try { + const res = await axiosInstance.post(url, body, { + headers + }); + + const data = await res.data; + return data; + } catch (err) { + console.log('Ошибка при добавлении урока', err); + return err; + } +}; + +export const fetchLesson = async (type: string, courseId: number | null, lessonId: number | null) => { + let url = ''; + + // console.log(type, courseId, lessonId); + + if (type === 'text') { + url = `/v1/teacher/textcontent?course_id=${courseId}&lesson_id=${lessonId}`; + } else if (type === 'doc') { + url = `/v1/teacher/document?lesson_id=${lessonId}` + } else if (type === 'url') { + url = `/v1/teacher/usefullinks?lesson_id=${lessonId}` + } else if (type === 'video') { + url = `/v1/teacher/video?lesson_id=${lessonId}` + } + + try { + const res = await axiosInstance.get(url); + console.log('Урок: ', res.data); + return res.data; + } catch (err) { + console.log('Ошибка при получении урока', err); + return err; + } +}; + +export const deleteLesson = async (type:string, courseId: number | null, lesson_id: number | null, content_id: number | null) => { + let url = ''; + console.log('content id: ', content_id); + + if (type === 'text') { + url = `/v1/teacher/textcontent/delete?course_id=${courseId}&lesson_id=${lesson_id}&content_id=${content_id}`; + } else if(type === 'doc'){ + url = `/v1/teacher/document/delete?lesson_id=${lesson_id}&document_id=${content_id}`; + } else if(type === 'url'){ + url = `v1/teacher/usefullinks/delete?lesson_id=${lesson_id}&link_id=${content_id}`; + } else if(type === 'video'){ + url = `/v1/teacher/video/delete?video_id=${content_id}`; + } + + try { + const res = await axiosInstance.delete(url); + + const data = await res.data; + return data; + } catch (err) { + console.log('Ошибка при удалении урока', err); + return err; + } +}; + +export const updateLesson = async (type: string, token: string | null, course_id: number | null, lesson_id: number | null, contentId: number | null, value: any) => { + console.log(type ,contentId, value); + let headers: Record = token ? { Authorization: `Bearer ${token}` } : {}; + let formData = new FormData(); + let url = ''; + + let body = value; + if (type === 'text') { + url = `/v1/teacher/textcontent/update?course_id=${course_id}&lesson_id=${lesson_id}&content_id=${contentId}&content=${value}`; + headers['Content-Type'] = 'application/json'; + } else if(type === 'doc'){ + url = `/v1/teacher/document/update?lesson_id=${lesson_id}&document_id=${contentId}&document=${value.file}`; + formData.append('lesson_id', String(lesson_id)); + formData.append('document', value.file); + formData.append('document_id', String(contentId)); + formData.append('title', String(value.title)); + formData.append('description', String(value.description)); + body = formData; + } else if(type === 'url'){ + url = `/v1/teacher/usefullinks/update?lesson_id=${lesson_id}&title=${value.title}&description=${value.description}&url=${value.url}&link_id=${contentId}`; + formData.append('lesson_id', String(lesson_id)); + formData.append('url', value.url); + formData.append('link_id', String(contentId)); + formData.append('title', String(value.title)); + formData.append('description', String(value.description)); + body = formData; + } else if(type === 'video'){ + url = `/v1/teacher/video/update?lesson_id=${lesson_id}&title=${value.title}&description=${value.description}&video_link=${value.video_link}&video_type_id=${value.video_type_id}&video_id=${contentId}`; + + formData.append('lesson_id', String(lesson_id)); + formData.append('video_link', value.video_link); + formData.append('video_type_id', String(value.video_type_id)); + formData.append('video_id', String(contentId)); + formData.append('title', String(value.title)); + formData.append('description', String(value.description)); + body = formData; + } + + try { + const res = await axiosInstance.post(url, body, { + headers + }); + + const data = await res.data; + return data; + } catch (err) { + console.log('Ошибка при обновлении урока', err); + return err; + } +}; + +export const fetchVideoType = async () => { + try { + const res = await axiosInstance.get('/v1/open/video/types'); + console.log('Типы видео: ', res.data); + + return res.data; + } catch (err) { + console.log('Ошибка при получении типов видео', err); + return err; + } +}; + diff --git a/services/query-tests.http b/services/query-tests.http new file mode 100644 index 00000000..0c2465bd --- /dev/null +++ b/services/query-tests.http @@ -0,0 +1,5 @@ +### + +http://api.mooc.oshsu.kg/public/api/v1/open/video/types +Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FwaS5teWVkdS5vc2hzdS5rZy9wdWJsaWMvYXBpL21vb2MvbG9naW4iLCJpYXQiOjE3NTQzODYzMzAsImV4cCI6MTc1NDQxNTE5MCwibmJmIjoxNzU0Mzg2MzMwLCJqdGkiOiJjc0hhSkdqYVhsT1V1VkpPIiwic3ViIjoiNDY0NTYiLCJwcnYiOiIyM2JkNWM4OTQ5ZjYwMGFkYjM5ZTcwMWM0MDA4NzJkYjdhNTk3NmY3In0.E9dQ1RCISvf2psrA1iDf9CrYzExEKnZOgYq2XJ4cyXg +Content-Type: application/json diff --git a/services/streams.tsx b/services/streams.tsx new file mode 100644 index 00000000..1af4201f --- /dev/null +++ b/services/streams.tsx @@ -0,0 +1,36 @@ +import axiosInstance from '@/utils/axiosInstance'; + +let url = ''; + +// streams +export const fetchStreams = async () => { + try { + const res = await axiosInstance.get(`/v1/teacher/courses?page=${Number()}&limit=${''}`); + const data = await res.data; + + return data; + } catch (error) { + console.error('Ошибка загрузки:', error); + } +}; + +export const connectStreams = async (value) => { + const formData = new FormData(); + formData.append('title', value.title); + formData.append('description', value.description); + if (value.image instanceof File) { + formData.append('image', value.image); + } + formData.append('video_url', value.video_url); + + try { + const res = await axiosInstance.post(`/v1/teacher/courses/store`, formData, { + headers: { 'Content-Type': 'multipart/form-data' } + }); + + return res.data; + } catch (err) { + console.log('Ошибка при связке', err); + return err; + } +}; diff --git a/styles/layout/_button.scss b/styles/layout/_button.scss new file mode 100644 index 00000000..980c20d2 --- /dev/null +++ b/styles/layout/_button.scss @@ -0,0 +1,67 @@ +button { + outline: 0; + border: none; + + &:focus { + outline: 0; + border: 0; + } +} + +.default-btn { + text-align: center; + color: var(--whiteColor); + // font-size: var(--fontSize); + font-weight: 600; + transition: var(--transition); + display: inline-flex; + align-items: center; + justify-content: center; + position: relative; + z-index: 0; + box-shadow: none; + overflow: hidden; + white-space: nowrap; + cursor: pointer; +} + +.fancyEffect { + position: absolute; + top: 0; + bottom: 0; + left: 50%; + width: 550px; + height: 550px; + margin: auto; + border-radius: 50%; + z-index: -1; +} + +.fancyBefore { + transform-origin: top center; + transform: translateX(-50%) translateY(-5%) scale(0.4); + transition: transform 0.9s; +} + +.fancyAfter { + transition: transform 1s; + transform: translateX(-45%) translateY(0) scale(1); + transform-origin: bottom center; +} + +.p-button{ + background-color: var(--mainColor); + padding: 7px 14px; + font-weight: bold; +} + +@media screen and (max-width: '640px') { + .p-button { + padding: 5px 10px; + } +} + +.reject-button { + background-color: transparent; + color: var(--bodyColor); +} \ No newline at end of file diff --git a/styles/layout/_topbar.scss b/styles/layout/_topbar.scss index 79402366..d6a02350 100644 --- a/styles/layout/_topbar.scss +++ b/styles/layout/_topbar.scss @@ -1,6 +1,6 @@ .layout-topbar { position: fixed; - height: 5rem; + height: 6rem; z-index: 997; left: 0; top: 0; @@ -18,12 +18,12 @@ color: var(--surface-900); font-size: 1.5rem; font-weight: 500; - width: 300px; + // width: 300px; border-radius: 12px; img { - height: 2.5rem; - margin-right: .5rem; + // height: 2.5rem; + // margin-right: .5rem; } &:focus { @@ -63,7 +63,7 @@ } .layout-menu-button { - margin-left: 2rem; + // margin-left: 2rem; } .layout-topbar-menu-button { @@ -81,7 +81,7 @@ display: flex; .layout-topbar-button { - margin-left: 1rem; + // margin-left: 1rem; } } } @@ -91,35 +91,35 @@ justify-content: space-between; .layout-topbar-logo { - width: auto; + // width: auto; order: 2; } .layout-menu-button { margin-left: 0; - order: 1; + order: 3; } .layout-topbar-menu-button { display: inline-flex; margin-left: 0; - order: 3; + // order: 1; } .layout-topbar-menu { margin-left: 0; - position: absolute; - flex-direction: column; - background-color: var(--surface-overlay); - box-shadow: 0px 3px 5px rgba(0,0,0,.02), 0px 0px 2px rgba(0,0,0,.05), 0px 1px 4px rgba(0,0,0,.08); - border-radius: 12px; - padding: 1rem; - right: 2rem; - top: 5rem; - min-width: 15rem; - display: none; - -webkit-animation: scalein 0.15s linear; - animation: scalein 0.15s linear; + // position: absolute; + // flex-direction: column; + // background-color: var(--surface-overlay); + // box-shadow: 0px 3px 5px rgba(0,0,0,.02), 0px 0px 2px rgba(0,0,0,.05), 0px 1px 4px rgba(0,0,0,.08); + // border-radius: 12px; + // padding: 1rem; + // right: 2rem; + // top: 5rem; + // min-width: 15rem; + // display: none; + // -webkit-animation: scalein 0.15s linear; + // animation: scalein 0.15s linear; &.layout-topbar-menu-mobile-active { display: block diff --git a/styles/layout/_typography.scss b/styles/layout/_typography.scss index b9a0c8ff..6278de2f 100644 --- a/styles/layout/_typography.scss +++ b/styles/layout/_typography.scss @@ -3,37 +3,13 @@ h1, h2, h3, h4, h5, h6 { font-family: inherit; font-weight: 500; line-height: 1.2; - color: var(--surface-900); + // color: var(--surface-900); &:first-child { margin-top: 0; } } -h1 { - font-size: 2.5rem; -} - -h2 { - font-size: 2rem; -} - -h3 { - font-size: 1.75rem; -} - -h4 { - font-size: 1.5rem; -} - -h5 { - font-size: 1.25rem; -} - -h6 { - font-size: 1rem; -} - mark { background: #FFF8E1; padding: .25rem .4rem; diff --git a/styles/layout/forms.css b/styles/layout/forms.css new file mode 100644 index 00000000..d20cf888 --- /dev/null +++ b/styles/layout/forms.css @@ -0,0 +1,109 @@ +/* Сброс стандартного radio */ +.custom-radio input[type="radio"] { + appearance: none; /* Убираем нативный стиль */ + -webkit-appearance: none; + position: absolute; + opacity: 0; + pointer-events: none; +} + +/* Контейнер */ +.custom-radio { + display: inline-flex; + align-items: center; + cursor: pointer; + font-family: sans-serif; + font-size: 14px; + gap: 8px; + position: relative; + padding: 6px 10px; + transition: color 0.3s; +} + +/* Сам кружок */ +.radio-mark { + width: 18px; + height: 18px; + border: 2px solid var(--mainColor); + border-radius: 50%; + display: inline-block; + position: relative; + transition: all 0.3s ease; +} + +/* Внутренняя точка при выборе */ +.radio-mark::after { + content: ""; + position: absolute; + top: 50%; + left: 50%; + width: 8px; + height: 8px; + /* background-color: var(--mainColor); */ + box-shadow: inset 4px 4px 7px 9px var(--mainColor) ; + + border-radius: 50%; + transform: translate(-50%, -50%) scale(0); + transition: transform 0.2s ease; +} + +/* Когда radio выбран */ +.custom-radio input[type="radio"]:checked + .radio-mark::after { + transform: translate(-50%, -50%) scale(1); +} + +/* Фокус */ +.custom-radio input[type="radio"]:focus + .radio-mark { + /* border: solid 2px rgba(48, 169, 193, 0.3); */ + /* box-shadow: inset 4px 4px 7px 9px var(--mainColor) ; */ +} + +/* checkbox */ + +.customCheckbox { + appearance: none; + -webkit-appearance: none; + width: 18px; + height: 18px; + border: 2px solid var(--mainColor); /* emerald-500 */ + border-radius: 4px; + position: relative; + cursor: pointer; + transition: all 0.2s ease-in-out; +} + +.customCheckbox:checked { + background-color: var(--mainColor); + border-color: var(--mainColor); +} + +.customCheckbox::after { + content: ""; + position: absolute; + left: 4px; + top: 0px; + width: 5px; + height: 10px; + border: solid white; + border-width: 0 2px 2px 0; + transform: rotate(45deg) scale(0); + transition: transform 0.2s ease-in-out; +} + +.customCheckbox:checked::after { + transform: rotate(45deg) scale(1); +} + +.custom-radio-box { + border: 1px solid gray; + padding: 5px 10px; + border-radius: 5px; + cursor: pointer; + display: inline-block; +} + +.custom-radio-box.selected { + background-color: #4f46e5; /* например, синий */ + color: white; + border-color: #4f46e5; +} \ No newline at end of file diff --git a/styles/layout/layout.scss b/styles/layout/layout.scss index fef2ed48..da7e8e94 100644 --- a/styles/layout/layout.scss +++ b/styles/layout/layout.scss @@ -8,4 +8,6 @@ @import "./_footer"; @import "./_responsive"; @import "./_utils"; -@import "./_typography"; \ No newline at end of file +@import "./_typography"; +@import "./_button"; +@import "./forms.css"; \ No newline at end of file diff --git a/types/authBaseType.tsx b/types/authBaseType.tsx new file mode 100644 index 00000000..2aab9c9e --- /dev/null +++ b/types/authBaseType.tsx @@ -0,0 +1,4 @@ +export interface AuthBaseType { + token: string | null; + id: number; +} \ No newline at end of file diff --git a/types/courseCreateType.tsx b/types/courseCreateType.tsx new file mode 100644 index 00000000..af341344 --- /dev/null +++ b/types/courseCreateType.tsx @@ -0,0 +1,7 @@ +export interface CourseCreateType { + title: string; + description: string; + video_url: string; + image?: string | File; + created_at?: string | null; +} \ No newline at end of file diff --git a/types/courseType.tsx b/types/courseType.tsx new file mode 100644 index 00000000..7b339e63 --- /dev/null +++ b/types/courseType.tsx @@ -0,0 +1,10 @@ +export interface CourseType { + created_at: string; + description: string; + id: number; + image: string; + status: boolean; + title: string; + user_id: number; + video_url: string; +} diff --git a/types/editableLesson.tsx b/types/editableLesson.tsx new file mode 100644 index 00000000..118719cb --- /dev/null +++ b/types/editableLesson.tsx @@ -0,0 +1,8 @@ +export interface EditableLesson { + title: string; + description?: string; + document?: File | null; + url?: string | null; + video_link: string; + video_type_id: number | null; + } \ No newline at end of file diff --git a/types/layout.d.ts b/types/layout.d.ts index fe94e601..fd7dbba4 100644 --- a/types/layout.d.ts +++ b/types/layout.d.ts @@ -2,6 +2,8 @@ import React, { ReactElement, Dispatch, SetStateAction, HTMLAttributeAnchorTarge import { NextPage } from 'next'; import { Demo } from './demo'; import { Toast } from 'primereact/toast'; +import { User } from './user'; +import { MessageType } from './messageType'; /* Breadcrumb Types */ export interface AppBreadcrumbProps { @@ -45,6 +47,14 @@ export interface LayoutContextProps { setLayoutState: Dispatch>; onMenuToggle: () => void; showProfileSidebar: () => void; + user: User | null; + setUser: React.Dispatch>; + globalLoading: boolean; + setGlobalLoading: React.Dispatch>; + message: MessageType; + setMessage: React.Dispatch>; + // message: { state: boolean; value: MessageType }; + // setMessage: React.Dispatch>; } export interface MenuContextProps { diff --git a/types/lessonStateType.tsx b/types/lessonStateType.tsx new file mode 100644 index 00000000..f008d88c --- /dev/null +++ b/types/lessonStateType.tsx @@ -0,0 +1,7 @@ +export interface lessonStateType { + title: string; + description: string | null; + file: File | null; + url: string | null; + video_link: string | null; +} diff --git a/types/lessonType.tsx b/types/lessonType.tsx new file mode 100644 index 00000000..7fb5e877 --- /dev/null +++ b/types/lessonType.tsx @@ -0,0 +1,13 @@ +export interface lessonType { + id: number; + course_id: number | null; + created_at: string; + description: string; + lesson_id: number; + status: boolean; + title: string; + updated_at: string; + user_id: number; + document?: string; + url?: string; +} diff --git a/types/login.tsx b/types/login.tsx new file mode 100644 index 00000000..2cae126b --- /dev/null +++ b/types/login.tsx @@ -0,0 +1,4 @@ +export interface LoginType { + email: string; + password: string; +} diff --git a/types/mainCourseType.tsx b/types/mainCourseType.tsx new file mode 100644 index 00000000..9a7d15dc --- /dev/null +++ b/types/mainCourseType.tsx @@ -0,0 +1,8 @@ +export interface mainCourseType { + created_at: string; + id: number; + image: string; + status: true; + title: string; + user_id: number; +} diff --git a/types/messageType.tsx b/types/messageType.tsx new file mode 100644 index 00000000..589dbb8c --- /dev/null +++ b/types/messageType.tsx @@ -0,0 +1,8 @@ +export interface MessageType { + state: boolean; + value: { + severity?:string; + summary?:string; + detail?:string; + } +} \ No newline at end of file diff --git a/types/tabViewChange.tsx b/types/tabViewChange.tsx new file mode 100644 index 00000000..82122bb9 --- /dev/null +++ b/types/tabViewChange.tsx @@ -0,0 +1,4 @@ +export interface TabViewChange { + originalEvent: React.SyntheticEvent; + index: number; +} diff --git a/types/user.tsx b/types/user.tsx new file mode 100644 index 00000000..442e50e9 --- /dev/null +++ b/types/user.tsx @@ -0,0 +1,12 @@ +export interface User { + birth_date: null; + email: string; + father_name: string; + id: number; + is_student: boolean; + is_working: boolean; + last_name: string; + name: string; + phone: string; + pin: null; +} diff --git a/utils/auth.tsx b/utils/auth.tsx new file mode 100644 index 00000000..d4d6a629 --- /dev/null +++ b/utils/auth.tsx @@ -0,0 +1,28 @@ +'use client'; + +export function getToken(name:string) : string | null{ + if (typeof document === 'undefined') { + return null; + } + + const cookies = document.cookie ? document.cookie.split("; ") : []; + for (let cookie of cookies) { + const [key, value] = cookie.split("="); + if (key === name) return decodeURIComponent(value); + } + return null; +}; + +export function isTokenExpired() { + const testToken = getToken('access_token'); + + try { + if (!testToken) throw new Error("Токен отсутствует"); + + const { exp } = JSON.parse(atob(testToken.split(".")[1])); + return exp - Math.floor(Date.now() / 1000) <= 300; + } catch (error) { + console.log("Ошибка при разборе токена:", error); + return true; // В случае ошибки считаем, что истёк + } +} \ No newline at end of file diff --git a/utils/axiosInstance.tsx b/utils/axiosInstance.tsx new file mode 100644 index 00000000..90788f84 --- /dev/null +++ b/utils/axiosInstance.tsx @@ -0,0 +1,49 @@ +import axios from 'axios'; +import { getToken } from './auth'; + +const axiosInstance = axios.create({ + baseURL: process.env.NEXT_PUBLIC_BASE_URL, + timeout: 10000, +}); + +axiosInstance.interceptors.request.use((config) => { + const token = getToken('access_token'); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; +}); + +axiosInstance.interceptors.response.use( + (response) => { + return response; + }, + (error) => { + + const status = error.response?.status; + console.log(error); + + if (status === 401) { + console.warn('Неавторизован. Удаляю токен...'); + document.cookie = 'access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;'; + localStorage.removeItem('userVisit'); + window.location.href = '/auth/login'; + } + + if (status === 403) { + console.warn('Не имеет доступ. Перенаправляю...'); + window.location.href = '/'; + } + + if (status === 404) { + console.warn('404 - Перенаправляю...'); + // document.cookie = 'access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;'; + // localStorage.removeItem('userVisit'); + // window.location.href = '/pages/notfound'; + } + + return Promise.reject(error); + } +); + +export default axiosInstance; diff --git a/utils/getConfirmOptions.tsx b/utils/getConfirmOptions.tsx new file mode 100644 index 00000000..1e5ea5d3 --- /dev/null +++ b/utils/getConfirmOptions.tsx @@ -0,0 +1,24 @@ +type ConfirmDialogOptions = { + message: string; + header?: string; + icon?: string; + defaultFocus?: 'accept' | 'reject'; + accept?: () => void; + reject?: () => void; + acceptLabel?: string; + rejectLabel?: string; + acceptClassName?: string; + rejectClassName?: string; +}; + +export const getConfirmOptions = (id: number, onDelete: (id: number)=> void ): ConfirmDialogOptions => ({ + message: 'Сиз чын эле өчүрүүнү каалайсызбы?', + header: 'Өчүрүү', + icon: 'pi pi-info-circle', + defaultFocus: 'reject', + acceptClassName: 'p-button-danger', + acceptLabel: 'Өчүрүү', + rejectLabel: 'Артка', + rejectClassName: 'p-button-secondary reject-button', + accept: () => onDelete(id) +}); diff --git a/utils/getRedactor.tsx b/utils/getRedactor.tsx new file mode 100644 index 00000000..52ff8472 --- /dev/null +++ b/utils/getRedactor.tsx @@ -0,0 +1,40 @@ +import { confirmDialog } from 'primereact/confirmdialog'; + +type ConfirmDialogOptions = { + message: string; + header?: string; + icon?: string; + defaultFocus?: 'accept' | 'reject'; + accept?: () => void; + reject?: () => void; + acceptLabel?: string; + rejectLabel?: string; + acceptClassName?: string; + rejectClassName?: string; +}; + +interface RedactorType { + onEdit: (id: number, type: string) => void; + getConfirmOptions: (id: number, onDelete: (id: number) => void) => ConfirmDialogOptions; + onDelete: (id: number) => void; +} + +export const getRedactor = (rowData: any, handlers: RedactorType) => [ + { + label: '', + icon: 'pi pi-pencil', + command: () => { + handlers.onEdit(rowData.id, rowData?.type); + } + }, + { + label: '', + icon: 'pi pi-trash', + command: () => { + const options = handlers.getConfirmOptions(Number(rowData.id), handlers.onDelete ); + if(options){ + confirmDialog(options); + } + } + } +]; diff --git a/utils/logout.tsx b/utils/logout.tsx new file mode 100644 index 00000000..caf6a184 --- /dev/null +++ b/utils/logout.tsx @@ -0,0 +1,16 @@ +'use client'; + +import { User } from "@/types/user"; + +export const logout = ({setGlobalLoading, setUser}: {setGlobalLoading: (params: boolean)=> void, setUser: React.Dispatch>}) => { + console.log('logout'); + setGlobalLoading(true); + + setTimeout(() => { + setGlobalLoading(false); + }, 1000); + + setUser(null); + localStorage.removeItem('userVisit'); + document.cookie = 'access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;'; +};