diff --git a/client/src/app/routes/constants/routes.ts b/client/src/app/routes/constants/routes.ts
index cba1d32e..5c692760 100644
--- a/client/src/app/routes/constants/routes.ts
+++ b/client/src/app/routes/constants/routes.ts
@@ -10,10 +10,13 @@ export enum ROUTES_V1 {
export enum ROUTES_HOME_V1 {
PROJECT = '/:project_id',
+ EDITOR = '/editor',
+ EDITOR_WITH_ID = '/editor/:project_id',
}
export enum ROUTES_SETTINGS_V1 {
PROFILE = '/profile',
+ MANAGE_ARTICLES = '/manage-articles',
INTEGRATIONS = '/integrations',
}
diff --git a/client/src/modules/editor/components/editor-navbar.tsx b/client/src/modules/editor/components/editor-navbar.tsx
deleted file mode 100644
index 6e561f90..00000000
--- a/client/src/modules/editor/components/editor-navbar.tsx
+++ /dev/null
@@ -1,81 +0,0 @@
-import { AppBar, Toolbar, Box } from '@mui/material';
-import { Link } from 'react-router-dom';
-import { useTheme } from '@mui/material/styles';
-import ArrowBackIcon from '@mui/icons-material/ArrowBack';
-import A2ZTypography from '../../../shared/components/atoms/typography';
-import A2ZButton from '../../../shared/components/atoms/button';
-
-const EditorNavbar = ({
- title,
- onSaveDraft,
- onPublish,
-}: {
- title?: string;
- onSaveDraft: () => void;
- onPublish: () => void;
-}) => {
- const theme = useTheme();
-
- return (
-
-
-
-
-
-
-
-
-
-
- Publish
-
-
-
- Save Draft
-
-
-
-
- );
-};
-
-export default EditorNavbar;
diff --git a/client/src/modules/editor/components/tags.tsx b/client/src/modules/editor/components/tags.tsx
deleted file mode 100644
index 8856c580..00000000
--- a/client/src/modules/editor/components/tags.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-const Tags = () => {
- return
Tags Component
;
-};
-
-export default Tags;
diff --git a/client/src/modules/editor/constants/index.ts b/client/src/modules/editor/constants/index.ts
deleted file mode 100644
index 3d60d77a..00000000
--- a/client/src/modules/editor/constants/index.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-export const characterLimit = 200;
-export const tagLimit = 10;
-
-export const defaultLightThumbnail =
- 'https://res.cloudinary.com/avdhesh-varshney/image/upload/v1761476229/OS7sL4eIr_MiI9fqvkYCw-1761476227343.jpg';
-
-export const defaultDarkThumbnail =
- 'https://res.cloudinary.com/avdhesh-varshney/image/upload/v1761476020/FHX9kToS3GiG0sgUFQ_Mo-1761476018134.jpg';
diff --git a/client/src/modules/editor/index.tsx b/client/src/modules/editor/index.tsx
deleted file mode 100644
index ebd52b18..00000000
--- a/client/src/modules/editor/index.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { Navigate } from 'react-router-dom';
-import { useAuth } from '../../shared/hooks/use-auth';
-import { EditorPageState } from './states';
-import { CircularProgress } from '@mui/material';
-import ProjectEditor from './components/project-editor';
-import PublishForm from './components/publish-form';
-import { useEditor } from './hooks';
-
-const Editor = () => {
- const { isAuthenticated, initialized } = useAuth();
- const { loading, editorPageState } = useEditor();
-
- if (!initialized || loading) return ;
- if (!isAuthenticated()) return ;
-
- return editorPageState === EditorPageState.EDITOR ? (
-
- ) : editorPageState === EditorPageState.PUBLISH ? (
-
- ) : undefined;
-};
-
-export default Editor;
diff --git a/client/src/modules/editor/components/project-editor.tsx b/client/src/modules/home/modules/editor/v1/components/project-editor.tsx
similarity index 61%
rename from client/src/modules/editor/components/project-editor.tsx
rename to client/src/modules/home/modules/editor/v1/components/project-editor.tsx
index c4677c34..9e9a8efc 100644
--- a/client/src/modules/editor/components/project-editor.tsx
+++ b/client/src/modules/home/modules/editor/v1/components/project-editor.tsx
@@ -2,14 +2,24 @@ import { Box, Divider, useTheme } from '@mui/material';
import TitleIcon from '@mui/icons-material/Title';
import LinkIcon from '@mui/icons-material/Link';
import HttpIcon from '@mui/icons-material/Http';
-import { defaultLightThumbnail, defaultDarkThumbnail } from '../constants';
-import EditorNavbar from './editor-navbar';
-import { useA2ZTheme } from '../../../shared/hooks/use-theme';
-import InputBox from '../../../shared/components/atoms/input-box';
+import ArrowBackIcon from '@mui/icons-material/ArrowBack';
+import PublishIcon from '@mui/icons-material/Publish';
+import SaveIcon from '@mui/icons-material/Save';
+import { Link } from 'react-router-dom';
+import {
+ defaultDarkThumbnail,
+ defaultLightThumbnail,
+} from '../../../../../../shared/constants';
+import Header from '../../../../../../shared/components/organisms/header';
+import { useA2ZTheme } from '../../../../../../shared/hooks/use-theme';
+import InputBox from '../../../../../../shared/components/atoms/input-box';
import A2ZTextEditor from './text-editor';
import { useProjectEditor } from '../hooks/use-project-editor';
import { useAtomValue } from 'jotai';
import { EditorContentAtom } from '../states';
+import A2ZTypography from '../../../../../../shared/components/atoms/typography';
+import A2ZButton from '../../../../../../shared/components/atoms/button';
+import { ROUTES_V1 } from '../../../../../../app/routes/constants/routes';
const ProjectEditor = () => {
const theme = useTheme();
@@ -26,10 +36,80 @@ const ProjectEditor = () => {
return (
<>
-
+
+
+
+
+
+
+ }
+ rightSideActions={[
+ {
+ key: 'publish',
+ label: 'Publish',
+ icon: ,
+ mobileIcon: ,
+ onClick: handlePublishEvent,
+ desktopNode: (
+
+ Publish
+
+ ),
+ },
+ {
+ key: 'save-draft',
+ label: 'Save Draft',
+ icon: ,
+ mobileIcon: ,
+ onClick: handleDraftProject,
+ desktopNode: (
+
+ Save Draft
+
+ ),
+ },
+ ]}
/>
{
const theme = useTheme();
@@ -156,7 +155,7 @@ const PublishForm = () => {
/>
{
}}
/>
{
{
return await uploadImage(e).then(({ data }) => {
diff --git a/client/src/modules/home/modules/editor/v1/constants/index.ts b/client/src/modules/home/modules/editor/v1/constants/index.ts
new file mode 100644
index 00000000..e1acaac6
--- /dev/null
+++ b/client/src/modules/home/modules/editor/v1/constants/index.ts
@@ -0,0 +1,2 @@
+export const CHARACTER_LIMIT = 200;
+export const TAG_LIMIT = 10;
diff --git a/client/src/modules/editor/hooks/index.ts b/client/src/modules/home/modules/editor/v1/hooks/index.ts
similarity index 79%
rename from client/src/modules/editor/hooks/index.ts
rename to client/src/modules/home/modules/editor/v1/hooks/index.ts
index c6588b06..9ed89669 100644
--- a/client/src/modules/editor/hooks/index.ts
+++ b/client/src/modules/home/modules/editor/v1/hooks/index.ts
@@ -1,17 +1,13 @@
-import { useState } from 'react';
import { useAtom } from 'jotai';
import { EditorPageAtom, EditorPageState } from '../states';
export const useEditor = () => {
- const [loading, setLoading] = useState(false);
const [editorPageState, setEditorPageState] = useAtom(EditorPageAtom);
const handlePublish = () => setEditorPageState(EditorPageState.PUBLISH);
const handleBackToEditor = () => setEditorPageState(EditorPageState.EDITOR);
return {
- loading,
- setLoading,
editorPageState,
setEditorPageState,
handlePublish,
diff --git a/client/src/modules/editor/hooks/use-project-editor.ts b/client/src/modules/home/modules/editor/v1/hooks/use-project-editor.ts
similarity index 80%
rename from client/src/modules/editor/hooks/use-project-editor.ts
rename to client/src/modules/home/modules/editor/v1/hooks/use-project-editor.ts
index ef607e97..4562bcf9 100644
--- a/client/src/modules/editor/hooks/use-project-editor.ts
+++ b/client/src/modules/home/modules/editor/v1/hooks/use-project-editor.ts
@@ -1,18 +1,22 @@
-import { uploadImage } from '../../../infra/rest/apis/media';
-import { useNotifications } from '../../../shared/hooks/use-notification';
+import { uploadImage } from '../../../../../../infra/rest/apis/media';
+import { useNotifications } from '../../../../../../shared/hooks/use-notification';
import { useAtom, useSetAtom } from 'jotai';
import {
- EditorContent,
EditorContentAtom,
EditorPageAtom,
EditorPageState,
TextEditorAtom,
+ DEFAULT_EDITOR_CONTENT,
} from '../states';
import { OutputData } from '@editorjs/editorjs';
-import { createProject } from '../../../infra/rest/apis/project';
+import { createProject } from '../../../../../../infra/rest/apis/project';
import { useNavigate, useParams } from 'react-router-dom';
import { useEditor } from '.';
-import { tagLimit } from '../constants';
+import { TAG_LIMIT } from '../constants';
+import {
+ ROUTES_SETTINGS_V1,
+ ROUTES_V1,
+} from '../../../../../../app/routes/constants/routes';
export const useProjectEditor = () => {
const { addNotification } = useNotifications();
@@ -34,9 +38,7 @@ export const useProjectEditor = () => {
message,
type: status,
});
- setEditorContent(
- prev => ({ ...prev, banner: data.upload_url }) as EditorContent
- );
+ setEditorContent(prev => ({ ...prev, banner: data.upload_url }));
}
})
.catch(err => {
@@ -47,56 +49,43 @@ export const useProjectEditor = () => {
const handleTitleChange = (e: React.ChangeEvent) => {
const textarea = e.target;
- setEditorContent(
- prev => ({ ...prev, title: textarea.value }) as EditorContent
- );
+ setEditorContent(prev => ({ ...prev, title: textarea.value }));
};
const handleDescriptionChange = (
e: React.ChangeEvent
) => {
const textarea = e.target;
- setEditorContent(
- prev => ({ ...prev, description: textarea.value }) as EditorContent
- );
+ setEditorContent(prev => ({ ...prev, description: textarea.value }));
};
const handleRepositoryURLChange = (
e: React.ChangeEvent
) => {
const input = e.target;
- setEditorContent(
- prev => ({ ...prev, repositoryURL: input.value }) as EditorContent
- );
+ setEditorContent(prev => ({ ...prev, repositoryURL: input.value }));
};
const handleLiveURLChange = (e: React.ChangeEvent) => {
const input = e.target;
- setEditorContent(
- prev => ({ ...prev, liveURL: input.value }) as EditorContent
- );
+ setEditorContent(prev => ({ ...prev, liveURL: input.value }));
};
const handleTagsAdd = (tag: string) => {
- if (editorContent?.tags && editorContent.tags.length >= tagLimit) {
+ if (editorContent?.tags && editorContent.tags.length >= TAG_LIMIT) {
return addNotification({
- message: `You can add up to ${tagLimit} tags only`,
+ message: `You can add up to ${TAG_LIMIT} tags only`,
type: 'error',
});
}
- setEditorContent(
- prev => ({ ...prev, tags: [...(prev?.tags || []), tag] }) as EditorContent
- );
+ setEditorContent(prev => ({ ...prev, tags: [...prev.tags, tag] }));
};
const handleTagsDelete = (index: number) => {
- setEditorContent(
- prev =>
- ({
- ...prev,
- tags: prev?.tags.filter((_, i) => i !== index),
- }) as EditorContent
- );
+ setEditorContent(prev => ({
+ ...prev,
+ tags: prev.tags.filter((_, i) => i !== index),
+ }));
};
const handleDraftProject = async () => {
@@ -117,9 +106,7 @@ export const useProjectEditor = () => {
textEditor.editor
?.save()
.then(async (outputData: OutputData) => {
- setEditorContent(
- prev => ({ ...prev, content: outputData }) as EditorContent
- );
+ setEditorContent(prev => ({ ...prev, content: outputData }));
const response = await createProject({
_id: project_id ?? undefined,
title: editorContent.title,
@@ -139,7 +126,7 @@ export const useProjectEditor = () => {
setTimeout(() => {
navigate('/dashboard/projects?tab=draft');
}, 500);
- setEditorContent(null);
+ setEditorContent(DEFAULT_EDITOR_CONTENT);
setTextEditor({ isReady: false });
setEditorPageState(EditorPageState.EDITOR);
}
@@ -168,9 +155,7 @@ export const useProjectEditor = () => {
?.save()
.then((outputData: OutputData) => {
if (outputData.blocks.length) {
- setEditorContent(
- prev => ({ ...prev, content: outputData }) as EditorContent
- );
+ setEditorContent(prev => ({ ...prev, content: outputData }));
handlePublish();
} else {
return addNotification({
@@ -246,9 +231,9 @@ export const useProjectEditor = () => {
type: response.status,
});
setTimeout(() => {
- navigate('/dashboard/projects?tab=published');
+ navigate(`${ROUTES_V1.SETTINGS}${ROUTES_SETTINGS_V1.MANAGE_ARTICLES}`);
}, 500);
- setEditorContent(null);
+ setEditorContent(DEFAULT_EDITOR_CONTENT);
setTextEditor({ isReady: false });
setEditorPageState(EditorPageState.EDITOR);
}
diff --git a/client/src/modules/home/modules/editor/v1/index.tsx b/client/src/modules/home/modules/editor/v1/index.tsx
new file mode 100644
index 00000000..fbd02fb1
--- /dev/null
+++ b/client/src/modules/home/modules/editor/v1/index.tsx
@@ -0,0 +1,16 @@
+import { EditorPageState } from './states';
+import ProjectEditor from './components/project-editor';
+import PublishForm from './components/publish-form';
+import { useEditor } from './hooks';
+
+const Editor = () => {
+ const { editorPageState } = useEditor();
+
+ if (editorPageState === EditorPageState.PUBLISH) {
+ return ;
+ }
+
+ return ;
+};
+
+export default Editor;
diff --git a/client/src/modules/editor/states/index.ts b/client/src/modules/home/modules/editor/v1/states/index.ts
similarity index 67%
rename from client/src/modules/editor/states/index.ts
rename to client/src/modules/home/modules/editor/v1/states/index.ts
index f2fda462..bc2718bf 100644
--- a/client/src/modules/editor/states/index.ts
+++ b/client/src/modules/home/modules/editor/v1/states/index.ts
@@ -16,10 +16,20 @@ export interface EditorContent {
repositoryURL: string;
}
+export const DEFAULT_EDITOR_CONTENT: EditorContent = {
+ title: '',
+ description: '',
+ banner: '',
+ content: { blocks: [] },
+ tags: [],
+ liveURL: '',
+ repositoryURL: '',
+};
+
export const EditorPageAtom = atom(EditorPageState.EDITOR);
export const TextEditorAtom = atom<{ isReady: boolean; editor?: EditorJS }>({
isReady: false,
});
-export const EditorContentAtom = atom(null);
+export const EditorContentAtom = atom(DEFAULT_EDITOR_CONTENT);
diff --git a/client/src/modules/home/modules/index.tsx b/client/src/modules/home/modules/index.tsx
index 6392128d..d5441ee3 100644
--- a/client/src/modules/home/modules/index.tsx
+++ b/client/src/modules/home/modules/index.tsx
@@ -2,3 +2,4 @@ import { lazy } from 'react';
export const ProjectLazyComponentV1 = lazy(() => import('./project/v1'));
export const SearchLazyComponentV1 = lazy(() => import('./search/v1'));
+export const EditorLazyComponentV1 = lazy(() => import('./editor/v1'));
diff --git a/client/src/modules/home/modules/project/v1/components/project-interaction.tsx b/client/src/modules/home/modules/project/v1/components/project-interaction.tsx
index 89f2ee30..16e22a20 100644
--- a/client/src/modules/home/modules/project/v1/components/project-interaction.tsx
+++ b/client/src/modules/home/modules/project/v1/components/project-interaction.tsx
@@ -13,6 +13,10 @@ import { likeStatus } from '../../../../../../infra/rest/apis/like';
import useProjectInteraction from '../hooks/use-project-interaction';
import { CommentsWrapperAtom } from '../../../../../../shared/components/organisms/comments-wrapper/states';
import A2ZTypography from '../../../../../../shared/components/atoms/typography';
+import {
+ ROUTES_V1,
+ ROUTES_HOME_V1,
+} from '../../../../../../app/routes/constants/routes';
// Module-level refs to prevent duplicate API calls across component instances
const fetchedProjectIdRef: { current: string | null } = { current: null };
@@ -131,7 +135,7 @@ const ProjectInteraction = () => {
{
const routes: React.ReactNode[] = [
@@ -12,6 +12,20 @@ export const homeRoutes = () => {
}
/>,
+
+ }
+ />,
+
+ }
+ />,
];
return { routes };
diff --git a/client/src/modules/home/v1/components/banner-project-card.tsx b/client/src/modules/home/v1/components/banner-project-card.tsx
index 7a76f14d..c6af638a 100644
--- a/client/src/modules/home/v1/components/banner-project-card.tsx
+++ b/client/src/modules/home/v1/components/banner-project-card.tsx
@@ -7,7 +7,7 @@ import { getDay } from '../../../../shared/utils/date';
import {
defaultDarkThumbnail,
defaultLightThumbnail,
-} from '../../../editor/constants';
+} from '../../../../shared/constants';
import { getAllProjectsResponse } from '../../../../infra/rest/apis/project/typing';
import A2ZTypography from '../../../../shared/components/atoms/typography';
import { ROUTES_V1 } from '../../../../app/routes/constants/routes';
diff --git a/client/src/modules/home/v1/index.tsx b/client/src/modules/home/v1/index.tsx
index f2f947c5..7f92c6bd 100644
--- a/client/src/modules/home/v1/index.tsx
+++ b/client/src/modules/home/v1/index.tsx
@@ -13,6 +13,10 @@ import { SearchLazyComponentV1 } from '../modules';
import useSearchV1 from '../modules/search/v1/hooks';
import SubscribeModal from './components/subscribe-modal';
import { useSubscribe } from './hooks/use-subscribe';
+import {
+ ROUTES_V1,
+ ROUTES_HOME_V1,
+} from '../../../app/routes/constants/routes';
const Home = () => {
const setProjects = useSetAtom(HomePageProjectsAtom);
@@ -49,7 +53,7 @@ const Home = () => {
),
- link: '/editor',
+ link: `${ROUTES_V1.HOME}${ROUTES_HOME_V1.EDITOR}`,
},
{
key: 'subscribe',
diff --git a/client/src/modules/manage-projects/index.tsx b/client/src/modules/manage-projects/index.tsx
deleted file mode 100644
index 39ebf739..00000000
--- a/client/src/modules/manage-projects/index.tsx
+++ /dev/null
@@ -1,160 +0,0 @@
-import { useEffect, useState } from 'react';
-import { useSearchParams } from 'react-router-dom';
-import InPageNavigation from '../../shared/components/molecules/page-navigation';
-import NoDataMessageBox from '../../shared/components/atoms/no-data-msg';
-import ManagePublishedProjectCard from './components/publish-projects';
-import ManageDraftProjectPost from './components/draft-projects';
-import useManageProjects from './hooks';
-import { Box, CircularProgress } from '@mui/material';
-import SearchIcon from '@mui/icons-material/Search';
-import InputBox from '../../shared/components/atoms/input-box';
-import A2ZTypography from '../../shared/components/atoms/typography';
-import Button from '../../shared/components/atoms/button';
-import { useSetAtom } from 'jotai';
-import { PublishedProjectsAtom, DraftProjectsAtom } from './states';
-
-const ManageProjects = () => {
- const [searchParams] = useSearchParams();
- const activeTab = searchParams.get('tab');
- const [query, setQuery] = useState('');
- const { fetchProjects, publishedProjects, draftProjects } =
- useManageProjects();
- const setPublishedProjects = useSetAtom(PublishedProjectsAtom);
- const setDraftProjects = useSetAtom(DraftProjectsAtom);
-
- useEffect(() => {
- if (publishedProjects === null) {
- fetchProjects({ page: 1, is_draft: false, query });
- }
- if (draftProjects === null) {
- fetchProjects({ page: 1, is_draft: true, query });
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [publishedProjects, draftProjects, query]);
-
- const handleSearch = (e: React.KeyboardEvent) => {
- const value = e.currentTarget.value;
- if (e.key === 'Enter' && value.length) {
- setPublishedProjects(null);
- setDraftProjects(null);
- }
- };
-
- const handleChange = (e: React.ChangeEvent) => {
- const value = e.currentTarget.value;
- setQuery(value);
- if (!value.length) {
- setPublishedProjects(null);
- setDraftProjects(null);
- }
- };
-
- const handleLoadMore = (is_draft: boolean) => {
- const currentState = is_draft ? draftProjects : publishedProjects;
- if (currentState && currentState.results.length < currentState.totalDocs) {
- fetchProjects({
- page: currentState.page + 1,
- is_draft,
- query,
- deletedDocCount: currentState.deletedDocCount || 0,
- });
- }
- };
-
- return (
-
-
-
-
- }
- sx={{ width: '100%' }}
- slotProps={{
- htmlInput: {
- onChange: handleChange,
- onKeyDown: handleSearch,
- },
- }}
- />
-
-
-
- {/* Published Projects */}
- {publishedProjects === null ? (
-
-
-
- ) : publishedProjects.results.length ? (
- <>
- {publishedProjects.results.map((project, i) => (
-
- ))}
-
- {publishedProjects.results.length < publishedProjects.totalDocs && (
-
-
-
- )}
- >
- ) : (
-
- )}
-
- {/* Draft Projects */}
- {draftProjects === null ? (
-
-
-
- ) : draftProjects.results.length ? (
- <>
- {draftProjects.results.map((project, i) => (
-
- ))}
-
- {draftProjects.results.length < draftProjects.totalDocs && (
-
-
-
- )}
- >
- ) : (
-
- )}
-
-
- );
-};
-
-export default ManageProjects;
diff --git a/client/src/modules/settings/modules/index.tsx b/client/src/modules/settings/modules/index.tsx
index a00cd769..1a8ffba3 100644
--- a/client/src/modules/settings/modules/index.tsx
+++ b/client/src/modules/settings/modules/index.tsx
@@ -7,3 +7,6 @@ export const IntegrationsLazyComponentV1 = lazy(
export const OpenAILazyComponentV1 = lazy(
() => import('./integrations/modules/openai/v1')
);
+export const ManageArticlesLazyComponentV1 = lazy(
+ () => import('./manage-articles/v1')
+);
diff --git a/client/src/modules/manage-projects/components/draft-projects.tsx b/client/src/modules/settings/modules/manage-articles/v1/components/draft-articles.tsx
similarity index 71%
rename from client/src/modules/manage-projects/components/draft-projects.tsx
rename to client/src/modules/settings/modules/manage-articles/v1/components/draft-articles.tsx
index e3f59921..c8dd9d88 100644
--- a/client/src/modules/manage-projects/components/draft-projects.tsx
+++ b/client/src/modules/settings/modules/manage-articles/v1/components/draft-articles.tsx
@@ -1,9 +1,13 @@
import { useState } from 'react';
import { Link } from 'react-router-dom';
+import {
+ ROUTES_HOME_V1,
+ ROUTES_V1,
+} from '../../../../../../app/routes/constants/routes';
import { useSetAtom } from 'jotai';
-import { deleteProjectById } from '../../../infra/rest/apis/project';
-import { useNotifications } from '../../../shared/hooks/use-notification';
-import { useAuth } from '../../../shared/hooks/use-auth';
+import { deleteProjectById } from '../../../../../../infra/rest/apis/project';
+import { useNotifications } from '../../../../../../shared/hooks/use-notification';
+import { useAuth } from '../../../../../../shared/hooks/use-auth';
import { DraftProjectsAtom, ManageProjectsPaginationState } from '../states';
import {
Box,
@@ -15,17 +19,14 @@ import {
} from '@mui/material';
import EditIcon from '@mui/icons-material/Edit';
import DeleteIcon from '@mui/icons-material/Delete';
-import { userProjectsResponse } from '../../../infra/rest/apis/project/typing';
+import { userProjectsResponse } from '../../../../../../infra/rest/apis/project/typing';
-interface ManageDraftProjectPostProps {
+interface ManageDraftArticleProps {
project: userProjectsResponse;
index: number;
}
-const ManageDraftProjectPost = ({
- project,
- index,
-}: ManageDraftProjectPostProps) => {
+const ManageDraftArticle = ({ project, index }: ManageDraftArticleProps) => {
const { _id, title, description } = project;
const setDraftProjects = useSetAtom(DraftProjectsAtom);
const { addNotification } = useNotifications();
@@ -77,10 +78,25 @@ const ManageDraftProjectPost = ({
mb: 3,
border: 1,
borderColor: 'divider',
+ borderRadius: 3,
+ bgcolor: 'background.default',
+ boxShadow: theme => theme.shadows[1],
+ transition: 'transform 0.2s ease, box-shadow 0.2s ease',
+ '&:hover': {
+ transform: 'translateY(-2px)',
+ boxShadow: theme => theme.shadows[3],
+ },
}}
>
-
-
+
+
}
- variant="outlined"
+ variant="contained"
>
Edit
@@ -151,4 +167,4 @@ const ManageDraftProjectPost = ({
);
};
-export default ManageDraftProjectPost;
+export default ManageDraftArticle;
diff --git a/client/src/modules/manage-projects/components/publish-projects.tsx b/client/src/modules/settings/modules/manage-articles/v1/components/publish-articles.tsx
similarity index 74%
rename from client/src/modules/manage-projects/components/publish-projects.tsx
rename to client/src/modules/settings/modules/manage-articles/v1/components/publish-articles.tsx
index b5e81a1e..aa77f1a2 100644
--- a/client/src/modules/manage-projects/components/publish-projects.tsx
+++ b/client/src/modules/settings/modules/manage-articles/v1/components/publish-articles.tsx
@@ -1,11 +1,14 @@
import { Link } from 'react-router-dom';
-import { getDay } from '../../../shared/utils/date';
+import { getDay } from '../../../../../../shared/utils/date';
import { useState } from 'react';
import { useSetAtom } from 'jotai';
-import { deleteProjectById } from '../../../infra/rest/apis/project';
-import { useNotifications } from '../../../shared/hooks/use-notification';
-import { useAuth } from '../../../shared/hooks/use-auth';
-import { ROUTES_V1 } from '../../../app/routes/constants/routes';
+import { deleteProjectById } from '../../../../../../infra/rest/apis/project';
+import { useNotifications } from '../../../../../../shared/hooks/use-notification';
+import { useAuth } from '../../../../../../shared/hooks/use-auth';
+import {
+ ROUTES_HOME_V1,
+ ROUTES_V1,
+} from '../../../../../../app/routes/constants/routes';
import {
PublishedProjectsAtom,
ManageProjectsPaginationState,
@@ -24,15 +27,15 @@ import EditIcon from '@mui/icons-material/Edit';
import DeleteIcon from '@mui/icons-material/Delete';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
-import { userProjectsResponse } from '../../../infra/rest/apis/project/typing';
-import { PROJECT_ACTIVITY } from '../../../infra/rest/typings';
+import { userProjectsResponse } from '../../../../../../infra/rest/apis/project/typing';
+import { PROJECT_ACTIVITY } from '../../../../../../infra/rest/typings';
-interface ManagePublishedProjectCardProps {
+interface ManagePublishedArticleProps {
project: userProjectsResponse;
index: number;
}
-const ProjectStats = ({ activity }: { activity: PROJECT_ACTIVITY }) => {
+const ArticleStats = ({ activity }: { activity: PROJECT_ACTIVITY }) => {
return (
{
);
};
-const ManagePublishedProjectCard = ({
+const ManagePublishedArticle = ({
project,
index,
-}: ManagePublishedProjectCardProps) => {
+}: ManagePublishedArticleProps) => {
const { _id, title, banner_url, publishedAt, activity } = project;
const setPublishedProjects = useSetAtom(PublishedProjectsAtom);
const { addNotification } = useNotifications();
@@ -104,13 +107,13 @@ const ManagePublishedProjectCard = ({
};
});
addNotification({
- message: 'Project deleted successfully',
+ message: 'Article deleted successfully',
type: 'success',
});
} catch (error: unknown) {
const err = error as { response?: { data?: { error?: string } } };
addNotification({
- message: err.response?.data?.error || 'Failed to delete project',
+ message: err.response?.data?.error || 'Failed to delete article',
type: 'error',
});
} finally {
@@ -124,14 +127,23 @@ const ManagePublishedProjectCard = ({
mb: 3,
border: 1,
borderColor: 'divider',
+ borderRadius: 3,
+ bgcolor: 'background.default',
+ boxShadow: theme => theme.shadows[1],
+ transition: 'transform 0.2s ease, box-shadow 0.2s ease',
+ '&:hover': {
+ transform: 'translateY(-2px)',
+ boxShadow: theme => theme.shadows[3],
+ },
}}
>
-
+
{banner_url && (
@@ -140,11 +152,11 @@ const ManagePublishedProjectCard = ({
src={banner_url}
alt={title}
sx={{
- width: { xs: '100%', xl: 112 },
- height: { xs: 200, xl: 112 },
+ width: { xs: '100%', sm: 180, md: 140 },
+ height: { xs: 180, sm: 120, md: 112 },
objectFit: 'cover',
borderRadius: 2,
- display: { xs: 'block', lg: 'none', xl: 'block' },
+ border: theme => `1px solid ${theme.palette.divider}`,
}}
/>
)}
@@ -172,16 +184,16 @@ const ManagePublishedProjectCard = ({
}
- variant="outlined"
+ variant="contained"
>
Edit
@@ -211,14 +223,14 @@ const ManagePublishedProjectCard = ({
{activity && (
-
+
)}
- {activity && }
+ {activity && }
@@ -226,4 +238,4 @@ const ManagePublishedProjectCard = ({
);
};
-export default ManagePublishedProjectCard;
+export default ManagePublishedArticle;
diff --git a/client/src/modules/manage-projects/hooks/index.ts b/client/src/modules/settings/modules/manage-articles/v1/hooks/index.ts
similarity index 57%
rename from client/src/modules/manage-projects/hooks/index.ts
rename to client/src/modules/settings/modules/manage-articles/v1/hooks/index.ts
index 1f7f35e5..31f0ef13 100644
--- a/client/src/modules/manage-projects/hooks/index.ts
+++ b/client/src/modules/settings/modules/manage-articles/v1/hooks/index.ts
@@ -1,22 +1,22 @@
-import { useCallback } from 'react';
-import { useSetAtom, useAtomValue } from 'jotai';
+import { useCallback, useState } from 'react';
+import { useAtom } from 'jotai';
import {
userProjects,
userProjectsCount,
-} from '../../../infra/rest/apis/project';
+} from '../../../../../../infra/rest/apis/project';
import {
PublishedProjectsAtom,
DraftProjectsAtom,
ManageProjectsPaginationState,
} from '../states';
-import { useAuth } from '../../../shared/hooks/use-auth';
-const useManageProjects = () => {
- const setPublishedProjects = useSetAtom(PublishedProjectsAtom);
- const setDraftProjects = useSetAtom(DraftProjectsAtom);
- const publishedProjects = useAtomValue(PublishedProjectsAtom);
- const draftProjects = useAtomValue(DraftProjectsAtom);
- const { isAuthenticated } = useAuth();
+const useManageArticles = () => {
+ const [publishedArticles, setPublishedArticles] = useAtom(
+ PublishedProjectsAtom
+ );
+ const [draftArticles, setDraftArticles] = useAtom(DraftProjectsAtom);
+
+ const [query, setQuery] = useState('');
const fetchProjects = useCallback(
async (params: {
@@ -25,8 +25,6 @@ const useManageProjects = () => {
query?: string;
deletedDocCount?: number;
}) => {
- if (!isAuthenticated()) return;
-
const { page, is_draft, query = '', deletedDocCount = 0 } = params;
try {
@@ -40,7 +38,7 @@ const useManageProjects = () => {
const newResults = projectsResponse.data || [];
if (is_draft) {
- setDraftProjects(
+ setDraftArticles(
(prevState: ManageProjectsPaginationState | null) => {
const previousResults = prevState?.results || [];
@@ -58,7 +56,7 @@ const useManageProjects = () => {
}
);
} else {
- setPublishedProjects(
+ setPublishedArticles(
(prevState: ManageProjectsPaginationState | null) => {
const previousResults = prevState?.results || [];
@@ -81,14 +79,53 @@ const useManageProjects = () => {
console.error('Error fetching projects:', error);
}
},
- [isAuthenticated, setPublishedProjects, setDraftProjects]
+ [setPublishedArticles, setDraftArticles]
);
+ const handleSearchChange = (value: string) => {
+ setQuery(value);
+ if (!value.length) {
+ setPublishedArticles(null);
+ setDraftArticles(null);
+ }
+ };
+
+ const handleSearchSubmit = () => {
+ if (query.length) {
+ setPublishedArticles(null);
+ setDraftArticles(null);
+ }
+ };
+
+ const handleSearchClear = () => {
+ setQuery('');
+ setPublishedArticles(null);
+ setDraftArticles(null);
+ };
+
+ const handleLoadMore = (is_draft: boolean) => {
+ const currentState = is_draft ? draftArticles : publishedArticles;
+ if (currentState && currentState.results.length < currentState.totalDocs) {
+ fetchProjects({
+ page: currentState.page + 1,
+ is_draft,
+ query,
+ deletedDocCount: currentState.deletedDocCount || 0,
+ });
+ }
+ };
+
return {
fetchProjects,
- publishedProjects,
- draftProjects,
+ publishedArticles,
+ draftArticles,
+ query,
+ setQuery,
+ handleSearchChange,
+ handleSearchSubmit,
+ handleSearchClear,
+ handleLoadMore,
};
};
-export default useManageProjects;
+export default useManageArticles;
diff --git a/client/src/modules/settings/modules/manage-articles/v1/index.tsx b/client/src/modules/settings/modules/manage-articles/v1/index.tsx
new file mode 100644
index 00000000..c3c5f864
--- /dev/null
+++ b/client/src/modules/settings/modules/manage-articles/v1/index.tsx
@@ -0,0 +1,129 @@
+import { useEffect } from 'react';
+import { useSearchParams } from 'react-router-dom';
+import InPageNavigation from '../../../../../shared/components/molecules/page-navigation';
+import NoDataMessageBox from '../../../../../shared/components/atoms/no-data-msg';
+import ManagePublishedArticle from './components/publish-articles';
+import ManageDraftArticle from './components/draft-articles';
+import useManageArticles from './hooks';
+import { Box, CircularProgress } from '@mui/material';
+import Button from '../../../../../shared/components/atoms/button';
+import Header from '../../../../../shared/components/organisms/header';
+
+const ManageArticles = () => {
+ const [searchParams] = useSearchParams();
+ const activeTab = searchParams.get('tab');
+ const {
+ fetchProjects,
+ publishedArticles,
+ draftArticles,
+ query,
+ handleSearchChange,
+ handleSearchSubmit,
+ handleSearchClear,
+ handleLoadMore,
+ } = useManageArticles();
+
+ useEffect(() => {
+ if (publishedArticles === null) {
+ fetchProjects({ page: 1, is_draft: false, query });
+ }
+ if (draftArticles === null) {
+ fetchProjects({ page: 1, is_draft: true, query });
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [publishedArticles, draftArticles, query]);
+
+ return (
+ <>
+
+
+
+
+ {/* Published Articles */}
+ {publishedArticles === null ? (
+
+
+
+ ) : publishedArticles.results.length ? (
+ <>
+ {publishedArticles.results.map((project, i) => (
+
+ ))}
+
+ {publishedArticles.results.length <
+ publishedArticles.totalDocs && (
+
+
+
+ )}
+ >
+ ) : (
+
+ )}
+
+ {/* Draft Articles */}
+ {draftArticles === null ? (
+
+
+
+ ) : draftArticles.results.length ? (
+ <>
+ {draftArticles.results.map((project, i) => (
+
+ ))}
+
+ {draftArticles.results.length < draftArticles.totalDocs && (
+
+
+
+ )}
+ >
+ ) : (
+
+ )}
+
+
+ >
+ );
+};
+
+export default ManageArticles;
diff --git a/client/src/modules/manage-projects/states/index.ts b/client/src/modules/settings/modules/manage-articles/v1/states/index.ts
similarity index 80%
rename from client/src/modules/manage-projects/states/index.ts
rename to client/src/modules/settings/modules/manage-articles/v1/states/index.ts
index 4338a699..e0859f82 100644
--- a/client/src/modules/manage-projects/states/index.ts
+++ b/client/src/modules/settings/modules/manage-articles/v1/states/index.ts
@@ -1,5 +1,5 @@
import { atom } from 'jotai';
-import { userProjectsResponse } from '../../../infra/rest/apis/project/typing';
+import { userProjectsResponse } from '../../../../../../infra/rest/apis/project/typing';
export interface ManageProjectsPaginationState {
results: userProjectsResponse[];
diff --git a/client/src/modules/settings/routes/index.tsx b/client/src/modules/settings/routes/index.tsx
index a23fa64d..b71b59e1 100644
--- a/client/src/modules/settings/routes/index.tsx
+++ b/client/src/modules/settings/routes/index.tsx
@@ -1,5 +1,6 @@
import AccountCircleOutlinedIcon from '@mui/icons-material/AccountCircleOutlined';
import ExtensionIcon from '@mui/icons-material/Extension';
+import ArticleIcon from '@mui/icons-material/Article';
import {
ROUTES_V1,
ROUTES_SETTINGS_V1,
@@ -12,6 +13,7 @@ import {
ProfileLazyComponentV1,
IntegrationsLazyComponentV1,
OpenAILazyComponentV1,
+ ManageArticlesLazyComponentV1,
} from '../modules';
import OpenAIIcon from '../../../shared/icons/openai';
@@ -28,6 +30,13 @@ export const settingsRoutes = ({
name: 'Your profile',
description: 'Edit your personal details',
},
+ {
+ id: 'manage-articles',
+ icon: ,
+ path: ROUTES_SETTINGS_V1.MANAGE_ARTICLES,
+ name: 'Manage Articles',
+ description: 'Manage your articles',
+ },
{
id: 'integrations',
icon: ,
@@ -45,6 +54,16 @@ export const settingsRoutes = ({
}
/>,
+
+ }
+ />,
= ({
open={isMobileMenuOpen}
onClose={handleMobileMenuClose}
>
- {actions.map(action => (
-
- ))}
+ {actions.map(action => {
+ const mobileIcon = action.mobileIcon ?? action.icon;
+ return (
+
+ );
+ })}
);
};
diff --git a/client/src/shared/components/organisms/header/index.tsx b/client/src/shared/components/organisms/header/index.tsx
index a92b4b04..a246d219 100644
--- a/client/src/shared/components/organisms/header/index.tsx
+++ b/client/src/shared/components/organisms/header/index.tsx
@@ -47,7 +47,14 @@ const Header = ({
const hasRightActions = Boolean(rightSideActions?.length);
return (
-
+ theme.zIndex.appBar,
+ bgcolor: 'background.paper',
+ }}
+ >
@@ -93,16 +99,21 @@ const Header = ({
{hasRightActions && (
{rightSideActions?.map(action => (
-
- {action.icon}
-
+
+ {action.desktopNode ? (
+ action.desktopNode
+ ) : (
+
+ {action.icon}
+
+ )}
+
))}
)}
diff --git a/client/src/shared/components/organisms/header/typings/index.ts b/client/src/shared/components/organisms/header/typings/index.ts
index 98ae5d50..85127a10 100644
--- a/client/src/shared/components/organisms/header/typings/index.ts
+++ b/client/src/shared/components/organisms/header/typings/index.ts
@@ -4,6 +4,8 @@ export interface HeaderAction {
key: string;
label: string;
icon: ReactNode;
+ desktopNode?: ReactNode;
+ mobileIcon?: ReactNode;
link?: string;
onClick?: () => void;
ariaLabel?: string;
diff --git a/client/src/shared/components/organisms/sidebar/index.tsx b/client/src/shared/components/organisms/sidebar/index.tsx
index 784a0406..dca2e209 100644
--- a/client/src/shared/components/organisms/sidebar/index.tsx
+++ b/client/src/shared/components/organisms/sidebar/index.tsx
@@ -29,7 +29,7 @@ const Sidebar = () => {
position: 'absolute',
height: '100%',
top: 0,
- zIndex: 3,
+ zIndex: theme => theme.zIndex.drawer + 1,
left: 0,
width: showExpandedView ? '230px' : `${SIDEBAR_WIDTH}px`,
minWidth: showExpandedView ? '230px' : `${SIDEBAR_WIDTH}px`,
diff --git a/client/src/shared/constants/index.ts b/client/src/shared/constants/index.ts
index 718f14d0..91913618 100644
--- a/client/src/shared/constants/index.ts
+++ b/client/src/shared/constants/index.ts
@@ -1 +1,7 @@
export const PAGE_SIZE = 10;
+
+export const defaultLightThumbnail =
+ 'https://res.cloudinary.com/avdhesh-varshney/image/upload/v1761476229/OS7sL4eIr_MiI9fqvkYCw-1761476227343.jpg';
+
+export const defaultDarkThumbnail =
+ 'https://res.cloudinary.com/avdhesh-varshney/image/upload/v1761476020/FHX9kToS3GiG0sgUFQ_Mo-1761476018134.jpg';