Add account and articles updater
This commit is contained in:
3
src/assets/icons/account/clipboard.svg
Normal file
3
src/assets/icons/account/clipboard.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19.5 21H13.5C13.1022 21 12.7206 21.158 12.4393 21.4393C12.158 21.7206 12 22.1022 12 22.5C12 22.8978 12.158 23.2794 12.4393 23.5607C12.7206 23.842 13.1022 24 13.5 24H19.5C19.8978 24 20.2794 23.842 20.5607 23.5607C20.842 23.2794 21 22.8978 21 22.5C21 22.1022 20.842 21.7206 20.5607 21.4393C20.2794 21.158 19.8978 21 19.5 21ZM25.5 6H23.73C23.4205 5.12468 22.8479 4.3665 22.0906 3.82941C21.3333 3.29232 20.4284 3.00261 19.5 3H16.5C15.5716 3.00261 14.6667 3.29232 13.9094 3.82941C13.1521 4.3665 12.5795 5.12468 12.27 6H10.5C9.30653 6 8.16193 6.47411 7.31802 7.31802C6.47411 8.16193 6 9.30653 6 10.5V28.5C6 29.6935 6.47411 30.8381 7.31802 31.682C8.16193 32.5259 9.30653 33 10.5 33H25.5C26.6935 33 27.8381 32.5259 28.682 31.682C29.5259 30.8381 30 29.6935 30 28.5V10.5C30 9.30653 29.5259 8.16193 28.682 7.31802C27.8381 6.47411 26.6935 6 25.5 6V6ZM15 7.5C15 7.10218 15.158 6.72064 15.4393 6.43934C15.7206 6.15804 16.1022 6 16.5 6H19.5C19.8978 6 20.2794 6.15804 20.5607 6.43934C20.842 6.72064 21 7.10218 21 7.5V9H15V7.5ZM27 28.5C27 28.8978 26.842 29.2794 26.5607 29.5607C26.2794 29.842 25.8978 30 25.5 30H10.5C10.1022 30 9.72064 29.842 9.43934 29.5607C9.15804 29.2794 9 28.8978 9 28.5V10.5C9 10.1022 9.15804 9.72064 9.43934 9.43934C9.72064 9.15804 10.1022 9 10.5 9H12V10.5C12 10.8978 12.158 11.2794 12.4393 11.5607C12.7206 11.842 13.1022 12 13.5 12H22.5C22.8978 12 23.2794 11.842 23.5607 11.5607C23.842 11.2794 24 10.8978 24 10.5V9H25.5C25.8978 9 26.2794 9.15804 26.5607 9.43934C26.842 9.72064 27 10.1022 27 10.5V28.5ZM22.5 15H13.5C13.1022 15 12.7206 15.158 12.4393 15.4393C12.158 15.7206 12 16.1022 12 16.5C12 16.8978 12.158 17.2794 12.4393 17.5607C12.7206 17.842 13.1022 18 13.5 18H22.5C22.8978 18 23.2794 17.842 23.5607 17.5607C23.842 17.2794 24 16.8978 24 16.5C24 16.1022 23.842 15.7206 23.5607 15.4393C23.2794 15.158 22.8978 15 22.5 15Z" fill="#00DBD9"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
3
src/assets/icons/account/cup.svg
Normal file
3
src/assets/icons/account/cup.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M31.5 6H27V4.5C27 4.10218 26.842 3.72064 26.5607 3.43934C26.2794 3.15804 25.8978 3 25.5 3H10.5C10.1022 3 9.72064 3.15804 9.43934 3.43934C9.15804 3.72064 9 4.10218 9 4.5V6H4.5C4.10218 6 3.72064 6.15804 3.43934 6.43934C3.15804 6.72064 3 7.10218 3 7.5V12C3 13.5913 3.63214 15.1174 4.75736 16.2426C5.88258 17.3679 7.4087 18 9 18H11.31C12.6668 19.5137 14.4961 20.5235 16.5 20.865V24H15C13.8065 24 12.6619 24.4741 11.818 25.318C10.9741 26.1619 10.5 27.3065 10.5 28.5V31.5C10.5 31.8978 10.658 32.2794 10.9393 32.5607C11.2206 32.842 11.6022 33 12 33H24C24.3978 33 24.7794 32.842 25.0607 32.5607C25.342 32.2794 25.5 31.8978 25.5 31.5V28.5C25.5 27.3065 25.0259 26.1619 24.182 25.318C23.3381 24.4741 22.1935 24 21 24H19.5V20.865C21.5039 20.5235 23.3332 19.5137 24.69 18H27C28.5913 18 30.1174 17.3679 31.2426 16.2426C32.3679 15.1174 33 13.5913 33 12V7.5C33 7.10218 32.842 6.72064 32.5607 6.43934C32.2794 6.15804 31.8978 6 31.5 6ZM9 15C8.20435 15 7.44129 14.6839 6.87868 14.1213C6.31607 13.5587 6 12.7956 6 12V9H9V12C9.0033 13.0226 9.18084 14.0371 9.525 15H9ZM21 27C21.3978 27 21.7794 27.158 22.0607 27.4393C22.342 27.7206 22.5 28.1022 22.5 28.5V30H13.5V28.5C13.5 28.1022 13.658 27.7206 13.9393 27.4393C14.2206 27.158 14.6022 27 15 27H21ZM24 12C24 13.5913 23.3679 15.1174 22.2426 16.2426C21.1174 17.3679 19.5913 18 18 18C16.4087 18 14.8826 17.3679 13.7574 16.2426C12.6321 15.1174 12 13.5913 12 12V6H24V12ZM30 12C30 12.7956 29.6839 13.5587 29.1213 14.1213C28.5587 14.6839 27.7956 15 27 15H26.475C26.8192 14.0371 26.9967 13.0226 27 12V9H30V12Z" fill="#00DBD9"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
5
src/assets/icons/account/index.ts
Normal file
5
src/assets/icons/account/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import Clipboard from './clipboard.svg';
|
||||
import Cup from './cup.svg';
|
||||
import OpenBook from './openbook.svg';
|
||||
|
||||
export { Clipboard, Cup, OpenBook };
|
||||
3
src/assets/icons/account/openbook.svg
Normal file
3
src/assets/icons/account/openbook.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M31.7554 3.09C30.6802 2.9048 29.5914 2.80947 28.5004 2.805C24.7779 2.80195 21.1331 3.86936 18.0004 5.88C14.8596 3.89576 11.2153 2.85455 7.50037 2.88C6.40934 2.88447 5.32058 2.9798 4.24537 3.165C3.89318 3.22572 3.57424 3.41019 3.34599 3.68519C3.11774 3.96019 2.99518 4.30765 3.00037 4.665V22.665C2.99716 22.8854 3.04256 23.1038 3.13335 23.3047C3.22413 23.5055 3.35807 23.6839 3.52563 23.8271C3.69319 23.9703 3.89026 24.0749 4.1028 24.1333C4.31535 24.1917 4.53816 24.2025 4.75537 24.165C6.9049 23.7928 9.10717 23.8546 11.2325 24.3466C13.3578 24.8387 15.3631 25.751 17.1304 27.03L17.3104 27.135H17.4754C17.6417 27.2043 17.8202 27.24 18.0004 27.24C18.1806 27.24 18.359 27.2043 18.5254 27.135H18.6904L18.8704 27.03C20.6253 25.7224 22.6249 24.7805 24.7506 24.26C26.8763 23.7396 29.0849 23.6511 31.2454 24C31.4626 24.0375 31.6854 24.0267 31.8979 23.9683C32.1105 23.9099 32.3075 23.8053 32.4751 23.6621C32.6427 23.5189 32.7766 23.3405 32.8674 23.1397C32.9582 22.9388 33.0036 22.7204 33.0004 22.5V4.5C32.9848 4.15823 32.8528 3.83207 32.6263 3.57561C32.3999 3.31915 32.0926 3.1478 31.7554 3.09ZM16.5004 23.025C13.7252 21.565 10.6361 20.8031 7.50037 20.805C7.00537 20.805 6.51037 20.805 6.00037 20.805V5.805C6.49996 5.77621 7.00079 5.77621 7.50037 5.805C10.7004 5.80146 13.8307 6.74054 16.5004 8.505V23.025ZM30.0004 20.865C29.4904 20.865 28.9954 20.865 28.5004 20.865C25.3646 20.8631 22.2755 21.625 19.5004 23.085V8.505C22.17 6.74054 25.3003 5.80146 28.5004 5.805C29 5.77621 29.5008 5.77621 30.0004 5.805V20.865ZM31.7554 27.09C30.6802 26.9048 29.5914 26.8095 28.5004 26.805C24.7779 26.8019 21.1331 27.8694 18.0004 29.88C14.8676 27.8694 11.2228 26.8019 7.50037 26.805C6.40934 26.8095 5.32058 26.9048 4.24537 27.09C4.05011 27.121 3.8629 27.1903 3.69451 27.2939C3.52612 27.3974 3.37987 27.5333 3.26417 27.6936C3.14847 27.8539 3.0656 28.0355 3.02033 28.228C2.97505 28.4204 2.96827 28.6199 3.00037 28.815C3.0766 29.2045 3.30411 29.548 3.63306 29.7701C3.96201 29.9922 4.36558 30.0749 4.75537 30C6.9049 29.6278 9.10717 29.6896 11.2325 30.1816C13.3578 30.6737 15.3631 31.586 17.1304 32.865C17.3844 33.0459 17.6885 33.1431 18.0004 33.1431C18.3122 33.1431 18.6163 33.0459 18.8704 32.865C20.6376 31.586 22.643 30.6737 24.7683 30.1816C26.8936 29.6896 29.0958 29.6278 31.2454 30C31.6352 30.0749 32.0387 29.9922 32.3677 29.7701C32.6966 29.548 32.9241 29.2045 33.0004 28.815C33.0325 28.6199 33.0257 28.4204 32.9804 28.228C32.9351 28.0355 32.8523 27.8539 32.7366 27.6936C32.6209 27.5333 32.4746 27.3974 32.3062 27.2939C32.1378 27.1903 31.9506 27.121 31.7554 27.09V27.09Z" fill="#00DBD9"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
3
src/assets/icons/input/edit.svg
Normal file
3
src/assets/icons/input/edit.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.7992 19.5516H19.7992M4.19922 19.5516L8.5652 18.6718C8.79698 18.6251 9.0098 18.511 9.17694 18.3438L18.9506 8.56474C19.4192 8.09588 19.4189 7.33589 18.9499 6.86743L16.8795 4.79936C16.4107 4.33109 15.6511 4.33141 15.1827 4.80007L5.40798 14.5801C5.24117 14.747 5.12727 14.9594 5.08052 15.1907L4.19922 19.5516Z" stroke="#EDF6F7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 506 B |
@@ -4,8 +4,10 @@ import googleLogo from './google-logo.svg';
|
||||
import upload from './upload.svg';
|
||||
import chevroneDropDownList from './chevron-drop-down.svg';
|
||||
import checkMark from './check-mark.svg';
|
||||
import Edit from './edit.svg';
|
||||
|
||||
export {
|
||||
Edit,
|
||||
eyeClosed,
|
||||
eyeOpen,
|
||||
googleLogo,
|
||||
|
||||
@@ -4,12 +4,18 @@ import Header from '../views/article/Header';
|
||||
import { useEffect } from 'react';
|
||||
import { fetchArticleById } from '../redux/slices/articles';
|
||||
import MarkdownPreview from '../views/articleeditor/MarckDownPreview';
|
||||
import { useQuery } from '../hooks/useQuery';
|
||||
|
||||
const Article = () => {
|
||||
// Получаем параметры из URL
|
||||
const { articleId } = useParams<{ articleId: string }>();
|
||||
const articleIdNumber = Number(articleId);
|
||||
|
||||
const query = useQuery();
|
||||
const back = query.get('back') ?? undefined;
|
||||
|
||||
if (!articleId || isNaN(articleIdNumber)) {
|
||||
if (back) return <Navigate to={back} replace />;
|
||||
return <Navigate to="/home" replace />;
|
||||
}
|
||||
const dispatch = useAppDispatch();
|
||||
@@ -24,7 +30,7 @@ const Article = () => {
|
||||
<div className="grid grid-cols-[1fr,250px] divide-x-[1px] divide-liquid-lighter">
|
||||
<div className="h-screen grid grid-rows-[60px,1fr] relative">
|
||||
<div className="">
|
||||
<Header articleId={articleIdNumber} />
|
||||
<Header articleId={articleIdNumber} back={back} />
|
||||
</div>
|
||||
|
||||
{status == 'loading' || !article ? (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Route, Routes, useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import Header from '../views/articleeditor/Header';
|
||||
import MarkdownEditor from '../views/articleeditor/Editor';
|
||||
import { useEffect, useState } from 'react';
|
||||
@@ -6,19 +6,42 @@ import { PrimaryButton } from '../components/button/PrimaryButton';
|
||||
import MarkdownPreview from '../views/articleeditor/MarckDownPreview';
|
||||
import { Input } from '../components/input/Input';
|
||||
import { useAppDispatch, useAppSelector } from '../redux/hooks';
|
||||
import { createArticle, setArticlesStatus } from '../redux/slices/articles';
|
||||
import {
|
||||
createArticle,
|
||||
deleteArticle,
|
||||
fetchArticleById,
|
||||
setArticlesStatus,
|
||||
updateArticle,
|
||||
} from '../redux/slices/articles';
|
||||
import { useQuery } from '../hooks/useQuery';
|
||||
import { ReverseButton } from '../components/button/ReverseButton';
|
||||
|
||||
const ArticleEditor = () => {
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [code, setCode] = useState<string>('');
|
||||
const [name, setName] = useState<string>('');
|
||||
const query = useQuery();
|
||||
const back = query.get('back') ?? undefined;
|
||||
const articleId = Number(query.get('articleId') ?? undefined);
|
||||
const article = useAppSelector((state) => state.articles.currentArticle);
|
||||
const refactor = articleId != undefined && !isNaN(articleId);
|
||||
|
||||
const [code, setCode] = useState<string>(article?.content || '');
|
||||
const [name, setName] = useState<string>(article?.name || '');
|
||||
const [tagInput, setTagInput] = useState<string>('');
|
||||
const [tags, setTags] = useState<string[]>([]);
|
||||
const [tags, setTags] = useState<string[]>(article?.tags || []);
|
||||
|
||||
const status = useAppSelector((state) => state.articles.statuses.create);
|
||||
const [activeEditor, setActiveEditor] = useState<boolean>(false);
|
||||
|
||||
const statusCreate = useAppSelector(
|
||||
(state) => state.articles.statuses.create,
|
||||
);
|
||||
const statusUpdate = useAppSelector(
|
||||
(state) => state.articles.statuses.update,
|
||||
);
|
||||
const statusDelete = useAppSelector(
|
||||
(state) => state.articles.statuses.delete,
|
||||
);
|
||||
|
||||
const addTag = () => {
|
||||
const newTag = tagInput.trim();
|
||||
@@ -33,40 +56,92 @@ const ArticleEditor = () => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (status == 'successful') {
|
||||
if (statusCreate == 'successful') {
|
||||
dispatch(setArticlesStatus({ key: 'create', status: 'idle' }));
|
||||
navigate('/home/articles');
|
||||
navigate(back ? back : '/home/articles');
|
||||
}
|
||||
}, [status]);
|
||||
}, [statusCreate]);
|
||||
|
||||
useEffect(() => {
|
||||
if (statusDelete == 'successful') {
|
||||
dispatch(setArticlesStatus({ key: 'delete', status: 'idle' }));
|
||||
navigate(back ? back : '/home/articles');
|
||||
}
|
||||
}, [statusDelete]);
|
||||
|
||||
useEffect(() => {
|
||||
if (statusUpdate == 'successful') {
|
||||
dispatch(setArticlesStatus({ key: 'update', status: 'idle' }));
|
||||
navigate(back ? back : '/home/articles');
|
||||
}
|
||||
}, [statusUpdate]);
|
||||
|
||||
useEffect(() => {
|
||||
if (articleId) {
|
||||
dispatch(fetchArticleById(articleId));
|
||||
}
|
||||
}, [articleId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (article && refactor) {
|
||||
setCode(article?.content || '');
|
||||
setName(article?.name || '');
|
||||
setTags(article?.tags || []);
|
||||
}
|
||||
}, [article]);
|
||||
|
||||
return (
|
||||
<div className="h-screen grid grid-rows-[60px,1fr]">
|
||||
<Routes>
|
||||
<Route
|
||||
path="editor"
|
||||
element={<Header backUrl="/article/create" />}
|
||||
{activeEditor ? (
|
||||
<Header
|
||||
backClick={() => {
|
||||
setActiveEditor(false);
|
||||
}}
|
||||
/>
|
||||
<Route path="*" element={<Header backUrl="/home/articles" />} />
|
||||
</Routes>
|
||||
) : (
|
||||
<Header
|
||||
backClick={() => navigate(back ? back : '/home/articles')}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Routes>
|
||||
<Route
|
||||
path="editor"
|
||||
element={
|
||||
<MarkdownEditor
|
||||
onChange={setCode}
|
||||
defaultValue={code}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="*"
|
||||
element={
|
||||
<div className="text-liquid-white">
|
||||
<div className="text-[40px] font-bold">
|
||||
Создание статьи
|
||||
{activeEditor ? (
|
||||
<MarkdownEditor onChange={setCode} defaultValue={code} />
|
||||
) : (
|
||||
<div className="text-liquid-white">
|
||||
<div className="text-[40px] font-bold">
|
||||
{refactor
|
||||
? `Редактирование статьи: \"${article?.name}\"`
|
||||
: 'Создание статьи'}
|
||||
</div>
|
||||
<div>
|
||||
{refactor ? (
|
||||
<div className="flex gap-[20px]">
|
||||
<PrimaryButton
|
||||
onClick={() => {
|
||||
dispatch(
|
||||
updateArticle({
|
||||
articleId,
|
||||
name,
|
||||
tags,
|
||||
content: code,
|
||||
}),
|
||||
);
|
||||
}}
|
||||
text="Обновить"
|
||||
className="mt-[20px]"
|
||||
disabled={statusUpdate == 'loading'}
|
||||
/>
|
||||
<ReverseButton
|
||||
onClick={() => {
|
||||
dispatch(deleteArticle(articleId));
|
||||
}}
|
||||
color="error"
|
||||
text="Удалить"
|
||||
className="mt-[20px]"
|
||||
disabled={statusDelete == 'loading'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
) : (
|
||||
<PrimaryButton
|
||||
onClick={() => {
|
||||
dispatch(
|
||||
@@ -79,78 +154,78 @@ const ArticleEditor = () => {
|
||||
}}
|
||||
text="Опубликовать"
|
||||
className="mt-[20px]"
|
||||
disabled={status == 'loading'}
|
||||
disabled={statusCreate == 'loading'}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Input
|
||||
defaultState={name}
|
||||
name="articleName"
|
||||
autocomplete="articleName"
|
||||
className="mt-[20px] max-w-[600px]"
|
||||
type="text"
|
||||
label="Название"
|
||||
onChange={(v) => {
|
||||
setName(v);
|
||||
}}
|
||||
placeholder="Новая статья"
|
||||
/>
|
||||
|
||||
{/* Блок для тегов */}
|
||||
<div className="mt-[20px] max-w-[600px]">
|
||||
<div className="grid grid-cols-[1fr,140px] items-end gap-2">
|
||||
<Input
|
||||
defaultState={name}
|
||||
name="articleName"
|
||||
autocomplete="articleName"
|
||||
name="articleTag"
|
||||
autocomplete="articleTag"
|
||||
className="mt-[20px] max-w-[600px]"
|
||||
type="text"
|
||||
label="Название"
|
||||
label="Теги"
|
||||
onChange={(v) => {
|
||||
setName(v);
|
||||
setTagInput(v);
|
||||
}}
|
||||
defaultState={tagInput}
|
||||
placeholder="arrays"
|
||||
onKeyDown={(e) => {
|
||||
console.log(e.key);
|
||||
if (e.key == 'Enter') addTag();
|
||||
}}
|
||||
placeholder="Новая статья"
|
||||
/>
|
||||
|
||||
{/* Блок для тегов */}
|
||||
<div className="mt-[20px] max-w-[600px]">
|
||||
<div className="grid grid-cols-[1fr,140px] items-end gap-2">
|
||||
<Input
|
||||
name="articleTag"
|
||||
autocomplete="articleTag"
|
||||
className="mt-[20px] max-w-[600px]"
|
||||
type="text"
|
||||
label="Теги"
|
||||
onChange={(v) => {
|
||||
setTagInput(v);
|
||||
}}
|
||||
defaultState={tagInput}
|
||||
placeholder="arrays"
|
||||
onKeyDown={(e) => {
|
||||
console.log(e.key);
|
||||
if (e.key == 'Enter') addTag();
|
||||
}}
|
||||
/>
|
||||
<PrimaryButton
|
||||
onClick={addTag}
|
||||
text="Добавить"
|
||||
className="h-[40px] w-[140px]"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-[10px] mt-2">
|
||||
{tags.map((tag) => (
|
||||
<div
|
||||
key={tag}
|
||||
className="flex items-center gap-1 bg-liquid-lighter px-3 py-1 rounded-full"
|
||||
>
|
||||
<span>{tag}</span>
|
||||
<button
|
||||
onClick={() => removeTag(tag)}
|
||||
className="text-liquid-red font-bold ml-[5px]"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PrimaryButton
|
||||
onClick={() => navigate('editor')}
|
||||
text="Редактировать текст"
|
||||
className="mt-[20px]"
|
||||
/>
|
||||
<MarkdownPreview
|
||||
content={code}
|
||||
className="bg-transparent border-liquid-lighter border-[3px] rounder-[20px] mt-[20px]"
|
||||
onClick={addTag}
|
||||
text="Добавить"
|
||||
className="h-[40px] w-[140px]"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
<div className="flex flex-wrap gap-[10px] mt-2">
|
||||
{tags.map((tag) => (
|
||||
<div
|
||||
key={tag}
|
||||
className="flex items-center gap-1 bg-liquid-lighter px-3 py-1 rounded-full"
|
||||
>
|
||||
<span>{tag}</span>
|
||||
<button
|
||||
onClick={() => removeTag(tag)}
|
||||
className="text-liquid-red font-bold ml-[5px]"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PrimaryButton
|
||||
onClick={() => setActiveEditor(true)}
|
||||
text="Редактировать текст"
|
||||
className="mt-[20px]"
|
||||
/>
|
||||
<MarkdownPreview
|
||||
content={code}
|
||||
className="bg-transparent border-liquid-lighter border-[3px] rounder-[20px] mt-[20px]"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -22,6 +22,7 @@ const Mission = () => {
|
||||
const back = query.get('back') ?? undefined;
|
||||
|
||||
if (!missionId || isNaN(missionIdNumber)) {
|
||||
if (back) return <Navigate to={back} replace />;
|
||||
return <Navigate to="/home" replace />;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
|
||||
import axios from '../../axios';
|
||||
|
||||
// 🔹 Декодирование JWT
|
||||
|
||||
@@ -4,6 +4,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
interface StorState {
|
||||
menu: {
|
||||
activePage: string;
|
||||
activeProfilePage: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -11,6 +12,7 @@ interface StorState {
|
||||
const initialState: StorState = {
|
||||
menu: {
|
||||
activePage: '',
|
||||
activeProfilePage: '',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -22,8 +24,15 @@ const storeSlice = createSlice({
|
||||
setMenuActivePage: (state, activePage: PayloadAction<string>) => {
|
||||
state.menu.activePage = activePage.payload;
|
||||
},
|
||||
setMenuActiveProfilePage: (
|
||||
state,
|
||||
activeProfilePage: PayloadAction<string>,
|
||||
) => {
|
||||
state.menu.activeProfilePage = activeProfilePage.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { setMenuActivePage } = storeSlice.actions;
|
||||
export const { setMenuActivePage, setMenuActiveProfilePage } =
|
||||
storeSlice.actions;
|
||||
export const storeReducer = storeSlice.reducer;
|
||||
|
||||
@@ -9,9 +9,10 @@ import { useNavigate } from 'react-router-dom';
|
||||
|
||||
interface HeaderProps {
|
||||
articleId: number;
|
||||
back?: string;
|
||||
}
|
||||
|
||||
const Header: React.FC<HeaderProps> = ({ articleId }) => {
|
||||
const Header: React.FC<HeaderProps> = ({ articleId, back }) => {
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<header className="w-full h-[60px] flex items-center px-4 gap-[20px]">
|
||||
@@ -29,7 +30,7 @@ const Header: React.FC<HeaderProps> = ({ articleId }) => {
|
||||
alt="back"
|
||||
className="h-[24px] w-[24px] cursor-pointer"
|
||||
onClick={() => {
|
||||
navigate('/home/articles');
|
||||
navigate(back ? back : '/home/articles');
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -39,8 +40,11 @@ const Header: React.FC<HeaderProps> = ({ articleId }) => {
|
||||
alt="back"
|
||||
className="h-[24px] w-[24px] cursor-pointer"
|
||||
onClick={() => {
|
||||
if (articleId > 1)
|
||||
navigate(`/article/${articleId - 1}`);
|
||||
if (articleId <= 1) return;
|
||||
|
||||
if (back)
|
||||
navigate(`/article/${articleId - 1}?back=${back}`);
|
||||
else navigate(`/article/${articleId - 1}`);
|
||||
}}
|
||||
/>
|
||||
<span className="text-[18px] font-bold">#{articleId}</span>
|
||||
@@ -49,7 +53,9 @@ const Header: React.FC<HeaderProps> = ({ articleId }) => {
|
||||
alt="back"
|
||||
className="h-[24px] w-[24px] cursor-pointer"
|
||||
onClick={() => {
|
||||
navigate(`/article/${articleId + 1}`);
|
||||
if (back)
|
||||
navigate(`/article/${articleId + 1}?back=${back}`);
|
||||
else navigate(`/article/${articleId + 1}`);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -4,10 +4,10 @@ import { Logo } from '../../assets/logos';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
interface HeaderProps {
|
||||
backUrl?: string;
|
||||
backClick?: () => void;
|
||||
}
|
||||
|
||||
const Header: React.FC<HeaderProps> = ({ backUrl = '/home/articles' }) => {
|
||||
const Header: React.FC<HeaderProps> = ({ backClick }) => {
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<header className="w-full h-[60px] flex items-center px-4 gap-[20px]">
|
||||
@@ -25,7 +25,7 @@ const Header: React.FC<HeaderProps> = ({ backUrl = '/home/articles' }) => {
|
||||
alt="back"
|
||||
className="h-[24px] w-[24px] cursor-pointer"
|
||||
onClick={() => {
|
||||
navigate(backUrl);
|
||||
if (backClick) backClick();
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,33 +1,47 @@
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||
import AccountMenu from './AccoutMenu';
|
||||
import RightPanel from './RightPanel';
|
||||
import MissionsBlock from './MissionsBlock';
|
||||
import ContestsBlock from './ContestsBlock';
|
||||
import ArticlesBlock from './ArticlesBlock';
|
||||
import { useAppDispatch } from '../../../redux/hooks';
|
||||
import { useEffect } from 'react';
|
||||
import { setMenuActivePage } from '../../../redux/slices/store';
|
||||
|
||||
const Account = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setMenuActivePage('account'));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="h-full w-[calc(100%+250px)] box-border grid grid-cols-[1fr,520px] relative">
|
||||
<div className="h-full w-[calc(100%+250px)] box-border grid grid-cols-[1fr,520px] relative divide-x-[1px] divide-liquid-lighter">
|
||||
<div className=" h-full min-h-0 flex flex-col">
|
||||
<div className=" h-full grid grid-rows-[80px,1fr] ">
|
||||
<div className="">
|
||||
<div className=" h-full grid grid-rows-[80px,1fr] ">
|
||||
<div className="h-full w-full">
|
||||
<AccountMenu />
|
||||
</div>
|
||||
<div className="h-full min-h-0 overflow-y-scroll medium-scrollbar flex flex-col gap-[20px] ">
|
||||
<Routes>
|
||||
<Route
|
||||
path="/home/account/missions"
|
||||
path="missions"
|
||||
element={<MissionsBlock />}
|
||||
/>
|
||||
<Route
|
||||
path="/home/account/articles"
|
||||
path="articles"
|
||||
element={<ArticlesBlock />}
|
||||
/>
|
||||
<Route
|
||||
path="/home/account/contests"
|
||||
path="contests"
|
||||
element={<ContestsBlock />}
|
||||
/>
|
||||
<Route path="*" element={<MissionsBlock />} />
|
||||
<Route
|
||||
path="*"
|
||||
element={
|
||||
<Navigate to="/home/account/missions" />
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,94 @@
|
||||
import { Openbook, Cup, Clipboard } from '../../../assets/icons/menu';
|
||||
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useAppDispatch, useAppSelector } from '../../../redux/hooks';
|
||||
import {
|
||||
setMenuActivePage,
|
||||
setMenuActiveProfilePage,
|
||||
} from '../../../redux/slices/store';
|
||||
|
||||
interface MenuItemProps {
|
||||
icon: string;
|
||||
text: string;
|
||||
href: string;
|
||||
page: string;
|
||||
profilePage: string;
|
||||
active?: boolean;
|
||||
}
|
||||
|
||||
const MenuItem: React.FC<MenuItemProps> = ({
|
||||
icon,
|
||||
text = '',
|
||||
href = '',
|
||||
active = false,
|
||||
page = '',
|
||||
profilePage = '',
|
||||
}) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
return (
|
||||
<Link
|
||||
to={href}
|
||||
className={`
|
||||
flex items-center gap-3 p-[16px] rounded-[10px] h-[40px] text-[18px] font-bold
|
||||
transition-all duration-300 text-liquid-white
|
||||
active:scale-95 hover:bg-liquid-lighter hover:ring-[1px] hover:ring-liquid-light hover:ring-inset
|
||||
${active && 'bg-liquid-lighter '}
|
||||
`}
|
||||
onClick={() => {
|
||||
dispatch(setMenuActivePage(page));
|
||||
dispatch(setMenuActiveProfilePage(profilePage));
|
||||
}}
|
||||
>
|
||||
<img src={icon} />
|
||||
<span>{text}</span>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
const AccountMenu = () => {
|
||||
return <div className="h-full w-full relative "></div>;
|
||||
const menuItems = [
|
||||
{
|
||||
text: 'Задачи',
|
||||
href: '/home/account/missions',
|
||||
icon: Clipboard,
|
||||
page: 'account',
|
||||
profilePage: 'missions',
|
||||
},
|
||||
{
|
||||
text: 'Статьи',
|
||||
href: '/home/account/articles',
|
||||
icon: Openbook,
|
||||
page: 'account',
|
||||
profilePage: 'articles',
|
||||
},
|
||||
{
|
||||
text: 'Контесты',
|
||||
href: '/home/account/contests',
|
||||
icon: Cup,
|
||||
page: 'account',
|
||||
profilePage: 'contests',
|
||||
},
|
||||
];
|
||||
|
||||
const activeProfilePage = useAppSelector(
|
||||
(state) => state.store.menu.activeProfilePage,
|
||||
);
|
||||
|
||||
console.log('active', [activeProfilePage]);
|
||||
|
||||
return (
|
||||
<div className="h-full w-full relative flex p-[20px] gap-[10px]">
|
||||
{menuItems.map((v, i) => (
|
||||
<MenuItem
|
||||
{...v}
|
||||
key={i}
|
||||
active={activeProfilePage == v.profilePage}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccountMenu;
|
||||
|
||||
@@ -1,5 +1,124 @@
|
||||
const ArticlesBlock = () => {
|
||||
return <div className="h-full w-full relative "></div>;
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { useAppDispatch, useAppSelector } from '../../../redux/hooks';
|
||||
import { setMenuActiveProfilePage } from '../../../redux/slices/store';
|
||||
import { cn } from '../../../lib/cn';
|
||||
import { ChevroneDown, Edit } from '../../../assets/icons/groups';
|
||||
import { fetchArticles } from '../../../redux/slices/articles';
|
||||
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
export interface ArticleItemProps {
|
||||
id: number;
|
||||
name: string;
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
const ArticleItem: React.FC<ArticleItemProps> = ({ id, name, tags }) => {
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'w-full relative rounded-[10px] text-liquid-white mb-[20px]',
|
||||
// type == "first" ? "bg-liquid-lighter" : "bg-liquid-background",
|
||||
'gap-[20px] px-[20px] py-[10px] box-border ',
|
||||
'border-b-[1px] border-b-liquid-lighter cursor-pointer hover:bg-liquid-lighter transition-all duration-300',
|
||||
)}
|
||||
onClick={() => {
|
||||
navigate(`/article/${id}?back=/home/account/articles`);
|
||||
}}
|
||||
>
|
||||
<div className="h-[23px] flex ">
|
||||
<div className="text-[18px] font-bold w-[60px] mr-[20px] flex items-center">
|
||||
#{id}
|
||||
</div>
|
||||
<div className="text-[18px] font-bold flex items-center">
|
||||
{name}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-[14px] flex text-liquid-light gap-[10px] mt-[10px]">
|
||||
{tags.map((v, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={cn(
|
||||
'rounded-full px-[16px] py-[8px] bg-liquid-lighter',
|
||||
v == 'Sertificated' && 'text-liquid-green',
|
||||
)}
|
||||
>
|
||||
{v}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<img
|
||||
className=" absolute right-[10px] top-[10px] h-[24px] w-[24px] hover:bg-liquid-light rounded-[5px] transition-all duration-300"
|
||||
src={Edit}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
navigate(
|
||||
`/article/create?back=/home/account/articles&articleId=${id}`,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface ArticlesBlockProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const ArticlesBlock: FC<ArticlesBlockProps> = ({ className = '' }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const articles = useAppSelector((state) => state.articles.articles);
|
||||
const [active, setActive] = useState<boolean>(true);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setMenuActiveProfilePage('articles'));
|
||||
dispatch(fetchArticles({}));
|
||||
}, []);
|
||||
return (
|
||||
<div className="h-full w-full relative p-[20px]">
|
||||
<div
|
||||
className={cn(
|
||||
' border-b-[1px] border-b-liquid-lighter rounded-[10px]',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
' h-[40px] text-[24px] font-bold flex gap-[10px] border-b-[1px] border-b-transparent items-center cursor-pointer transition-all duration-300',
|
||||
active && ' border-b-liquid-lighter',
|
||||
)}
|
||||
onClick={() => {
|
||||
setActive(!active);
|
||||
}}
|
||||
>
|
||||
<span>Мои статьи</span>
|
||||
<img
|
||||
src={ChevroneDown}
|
||||
className={cn(
|
||||
'transition-all duration-300',
|
||||
active && 'rotate-180',
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
' grid grid-flow-row grid-rows-[0fr] opacity-0 transition-all duration-300',
|
||||
active && 'grid-rows-[1fr] opacity-100',
|
||||
)}
|
||||
>
|
||||
<div className="overflow-hidden">
|
||||
<div className="grid gap-[20px] pt-[20px] pb-[20px] box-border">
|
||||
{articles.map((v, i) => (
|
||||
<ArticleItem key={i} {...v} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ArticlesBlock;
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useAppDispatch } from '../../../redux/hooks';
|
||||
import { setMenuActiveProfilePage } from '../../../redux/slices/store';
|
||||
|
||||
const ContestsBlock = () => {
|
||||
return <div className="h-full w-full relative bg-fuchsia-600"></div>;
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setMenuActiveProfilePage('contests'));
|
||||
}, []);
|
||||
return (
|
||||
<div className="h-full w-full relative flex items-center justify-center text-[60px] font-bold">
|
||||
Пока пусто :(
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContestsBlock;
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useAppDispatch } from '../../../redux/hooks';
|
||||
import { setMenuActiveProfilePage } from '../../../redux/slices/store';
|
||||
|
||||
const MissionsBlock = () => {
|
||||
return <div className="h-full w-full relative "></div>;
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setMenuActiveProfilePage('missions'));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="h-full w-full relative flex items-center justify-center text-[60px] font-bold">
|
||||
Пока пусто :(
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MissionsBlock;
|
||||
|
||||
@@ -1,15 +1,108 @@
|
||||
import { PrimaryButton } from '../../../components/button/PrimaryButton';
|
||||
import { ReverseButton } from '../../../components/button/ReverseButton';
|
||||
import { useAppDispatch, useAppSelector } from '../../../redux/hooks';
|
||||
import { logout } from '../../../redux/slices/auth';
|
||||
import { OpenBook, Clipboard, Cup } from '../../../assets/icons/account';
|
||||
import { FC } from 'react';
|
||||
|
||||
interface StatisticItemProps {
|
||||
icon: string;
|
||||
title: string;
|
||||
count?: number;
|
||||
countLastWeek?: number;
|
||||
}
|
||||
const StatisticItem: FC<StatisticItemProps> = ({
|
||||
title,
|
||||
icon,
|
||||
count = 0,
|
||||
countLastWeek = 0,
|
||||
}) => {
|
||||
return (
|
||||
<div className="h-[53px] grid grid-cols-[36px,1fr] gap-[20px] text-liquid-white">
|
||||
<img src={icon} />
|
||||
<div className="h-full flex flex-col justify-between">
|
||||
<div className="flex gap-[20px] text-[18px] font-bold leading-[23px]">
|
||||
<div>{title}</div>
|
||||
<div>{count}</div>
|
||||
</div>
|
||||
<div className="text-[16px] font-medium leading-[20px]">
|
||||
{'За 7 дней '}
|
||||
<span className="text-liquid-light">{countLastWeek}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const RightPanel = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const name = useAppSelector((state) => state.auth.username);
|
||||
const email = useAppSelector((state) => state.auth.email);
|
||||
return (
|
||||
<div className="h-full w-full relative p-[20px]">
|
||||
<div>{name}</div>
|
||||
<div>{email}</div>
|
||||
<div className="h-full w-full relative flex flex-col p-[20px] pt-[35px] gap-[20px]">
|
||||
<div className="grid grid-cols-[150px,1fr] h-[150px] gap-[20px]">
|
||||
<div className="-hfull w-full bg-[#B8B8B8] rounded-[10px]"></div>
|
||||
<div className=" relative">
|
||||
<div className="text-liquid-white text-[24px] leading-[30px] font-bold">
|
||||
{name}
|
||||
</div>
|
||||
<div className="text-liquid-light text-[18px] leading-[23px] font-medium">
|
||||
{email}
|
||||
</div>
|
||||
<div className=" absolute bottom-0 text-liquid-light text-[24px] leading-[30px] font-bold">
|
||||
Топ 50%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PrimaryButton
|
||||
onClick={() => {}}
|
||||
text="Редактировать"
|
||||
className="w-full"
|
||||
/>
|
||||
|
||||
<div className="h-[1px] w-full bg-liquid-lighter"></div>
|
||||
|
||||
<div className="text-liquid-white text-[24px] leading-[30px] font-bold">
|
||||
{'Статистика решений'}
|
||||
</div>
|
||||
|
||||
<StatisticItem
|
||||
icon={Clipboard}
|
||||
title={'Задачи'}
|
||||
count={14}
|
||||
countLastWeek={5}
|
||||
/>
|
||||
<StatisticItem
|
||||
icon={Cup}
|
||||
title={'Контесты'}
|
||||
count={8}
|
||||
countLastWeek={2}
|
||||
/>
|
||||
|
||||
<div className="text-liquid-white text-[24px] leading-[30px] font-bold">
|
||||
{'Статистика созданий'}
|
||||
</div>
|
||||
|
||||
<StatisticItem
|
||||
icon={Clipboard}
|
||||
title={'Задачи'}
|
||||
count={4}
|
||||
countLastWeek={2}
|
||||
/>
|
||||
<StatisticItem
|
||||
icon={OpenBook}
|
||||
title={'Статьи'}
|
||||
count={12}
|
||||
countLastWeek={4}
|
||||
/>
|
||||
<StatisticItem
|
||||
icon={Cup}
|
||||
title={'Контесты'}
|
||||
count={2}
|
||||
countLastWeek={0}
|
||||
/>
|
||||
|
||||
<ReverseButton
|
||||
className="absolute bottom-[20px] right-[20px]"
|
||||
onClick={() => {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { FC } from 'react';
|
||||
import { useAppDispatch } from '../../../redux/hooks';
|
||||
import MissionItem, { MissionItemProps } from './MissionItem';
|
||||
import MissionItem from './MissionItem';
|
||||
import { Contest } from '../../../redux/slices/contests';
|
||||
|
||||
export interface Article {
|
||||
|
||||
@@ -24,7 +24,7 @@ const MenuItem: React.FC<MenuItemProps> = ({
|
||||
<Link
|
||||
to={href}
|
||||
className={`
|
||||
flex items-center gap-3 p-[16px] rounded-[10px\] h-[40px] text-[18px] font-bold
|
||||
flex items-center gap-3 p-[16px] rounded-[10px] h-[40px] text-[18px] font-bold
|
||||
transition-all duration-300 text-liquid-white mt-[20px]
|
||||
active:scale-95
|
||||
${
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"root":["./src/app.tsx","./src/axios.ts","./src/main.tsx","./src/vite-env.d.ts","./src/assets/icons/auth/index.ts","./src/assets/icons/groups/index.ts","./src/assets/icons/header/index.ts","./src/assets/icons/input/index.ts","./src/assets/icons/menu/index.ts","./src/assets/icons/missions/index.ts","./src/assets/logos/index.ts","./src/components/button/primarybutton.tsx","./src/components/button/reversebutton.tsx","./src/components/button/secondarybutton.tsx","./src/components/checkbox/checkbox.tsx","./src/components/drop-down-list/dropdownlist.tsx","./src/components/input/input.tsx","./src/components/modal/modal.tsx","./src/components/switch/switch.tsx","./src/config/colors.ts","./src/hooks/useclickoutside.ts","./src/lib/cn.ts","./src/pages/articleeditor.tsx","./src/pages/home.tsx","./src/pages/mission.tsx","./src/redux/hooks.ts","./src/redux/store.ts","./src/redux/slices/auth.ts","./src/redux/slices/contests.ts","./src/redux/slices/groups.ts","./src/redux/slices/missions.ts","./src/redux/slices/store.ts","./src/redux/slices/submit.ts","./src/views/articleeditor/editor.tsx","./src/views/articleeditor/header.tsx","./src/views/articleeditor/marckdownpreview.tsx","./src/views/home/articles/articleitem.tsx","./src/views/home/articles/articles.tsx","./src/views/home/auth/login.tsx","./src/views/home/auth/register.tsx","./src/views/home/contests/contestitem.tsx","./src/views/home/contests/contests.tsx","./src/views/home/contests/contestsblock.tsx","./src/views/home/groups/group.tsx","./src/views/home/groups/groupitem.tsx","./src/views/home/groups/groups.tsx","./src/views/home/groups/groupsblock.tsx","./src/views/home/groups/modalcreate.tsx","./src/views/home/groups/modalupdate.tsx","./src/views/home/menu/menu.tsx","./src/views/home/menu/menuitem.tsx","./src/views/home/missions/missionitem.tsx","./src/views/home/missions/missions.tsx","./src/views/mission/uploadmissionform.tsx","./src/views/mission/codeeditor/codeeditor.tsx","./src/views/mission/statement/header.tsx","./src/views/mission/statement/latextcontainer.tsx","./src/views/mission/statement/missionsubmissions.tsx","./src/views/mission/statement/statement.tsx","./src/views/mission/statement/submissionitem.tsx"],"version":"5.6.2"}
|
||||
{"root":["./src/app.tsx","./src/axios.ts","./src/main.tsx","./src/vite-env.d.ts","./src/assets/icons/account/index.ts","./src/assets/icons/auth/index.ts","./src/assets/icons/groups/index.ts","./src/assets/icons/header/index.ts","./src/assets/icons/input/index.ts","./src/assets/icons/menu/index.ts","./src/assets/icons/missions/index.ts","./src/assets/logos/index.ts","./src/components/button/primarybutton.tsx","./src/components/button/reversebutton.tsx","./src/components/button/secondarybutton.tsx","./src/components/checkbox/checkbox.tsx","./src/components/drop-down-list/dropdownlist.tsx","./src/components/input/daterangeinput.tsx","./src/components/input/input.tsx","./src/components/modal/modal.tsx","./src/components/router/protectedroute.tsx","./src/components/switch/switch.tsx","./src/config/colors.ts","./src/hooks/useclickoutside.ts","./src/hooks/usequery.ts","./src/lib/cn.ts","./src/pages/article.tsx","./src/pages/articleeditor.tsx","./src/pages/home.tsx","./src/pages/mission.tsx","./src/redux/hooks.ts","./src/redux/store.ts","./src/redux/slices/account.ts","./src/redux/slices/articles.ts","./src/redux/slices/auth.ts","./src/redux/slices/contests.ts","./src/redux/slices/groups.ts","./src/redux/slices/missions.ts","./src/redux/slices/store.ts","./src/redux/slices/submit.ts","./src/views/article/header.tsx","./src/views/articleeditor/editor.tsx","./src/views/articleeditor/header.tsx","./src/views/articleeditor/marckdownpreview.tsx","./src/views/home/account/account.tsx","./src/views/home/account/accoutmenu.tsx","./src/views/home/account/articlesblock.tsx","./src/views/home/account/contestsblock.tsx","./src/views/home/account/missionsblock.tsx","./src/views/home/account/rightpanel.tsx","./src/views/home/articles/articleitem.tsx","./src/views/home/articles/articles.tsx","./src/views/home/auth/login.tsx","./src/views/home/auth/register.tsx","./src/views/home/contest/contest.tsx","./src/views/home/contest/missionitem.tsx","./src/views/home/contest/missions.tsx","./src/views/home/contest/submissions.tsx","./src/views/home/contests/contestitem.tsx","./src/views/home/contests/contests.tsx","./src/views/home/contests/contestsblock.tsx","./src/views/home/contests/modalcreate.tsx","./src/views/home/groups/group.tsx","./src/views/home/groups/groupitem.tsx","./src/views/home/groups/groups.tsx","./src/views/home/groups/groupsblock.tsx","./src/views/home/groups/modalcreate.tsx","./src/views/home/groups/modalupdate.tsx","./src/views/home/menu/menu.tsx","./src/views/home/menu/menuitem.tsx","./src/views/home/missions/missionitem.tsx","./src/views/home/missions/missions.tsx","./src/views/home/missions/modalcreate.tsx","./src/views/mission/codeeditor/codeeditor.tsx","./src/views/mission/statement/header.tsx","./src/views/mission/statement/latextcontainer.tsx","./src/views/mission/statement/missionsubmissions.tsx","./src/views/mission/statement/statement.tsx","./src/views/mission/statement/submissionitem.tsx","./src/views/mission/submission/submission.tsx"],"version":"5.6.2"}
|
||||
Reference in New Issue
Block a user