diff --git a/src/Components/Card/Card.module.css b/src/Components/Card/Card.module.css
new file mode 100644
index 0000000..ad73e7f
--- /dev/null
+++ b/src/Components/Card/Card.module.css
@@ -0,0 +1,16 @@
+.action-area {
+ height: 100% !important;
+ display: flex !important;
+ justify-content: flex-start !important;
+ align-items: flex-start !important;
+ flex-direction: column !important;
+}
+
+.image {
+ height: 100px;
+ width: 100%;
+}
+
+.description {
+ margin-bottom: 1rem !important;
+}
\ No newline at end of file
diff --git a/src/Components/Card/Card.tsx b/src/Components/Card/Card.tsx
new file mode 100644
index 0000000..c74fc37
--- /dev/null
+++ b/src/Components/Card/Card.tsx
@@ -0,0 +1,35 @@
+import { Card as MuiCard, CardActionArea, CardContent, CardMedia, Typography } from '@mui/material';
+import Contentful from 'contentful';
+
+import { TagBlock } from 'Components/TagBlock';
+
+import styles from './Card.module.css';
+
+interface CardProps {
+ image?: Contentful.Asset;
+ title: Contentful.EntryFields.Text;
+ description?: Contentful.EntryFields.Text;
+ tags?: Contentful.TagLink[];
+ onClick: () => void;
+}
+
+export const Card = ({ onClick, image, title, description, tags }: CardProps) => {
+ return (
+
+
+ {image && }
+
+
+ {title}
+
+ {description && (
+
+ {description}
+
+ )}
+ {tags && }
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/src/Components/Card/index.ts b/src/Components/Card/index.ts
new file mode 100644
index 0000000..0dad63d
--- /dev/null
+++ b/src/Components/Card/index.ts
@@ -0,0 +1 @@
+export * from './Card';
\ No newline at end of file
diff --git a/src/Components/index.ts b/src/Components/index.ts
index 3ddfe1c..86ec3b2 100644
--- a/src/Components/index.ts
+++ b/src/Components/index.ts
@@ -1,4 +1,5 @@
export * from './AvailableFrom';
+export * from './Card';
export * from './CvDownloadButton';
export * from './ErrorAlert';
export * from './Grid';
diff --git a/src/Hooks/Api/index.ts b/src/Hooks/Api/index.ts
index d79819a..2741741 100644
--- a/src/Hooks/Api/index.ts
+++ b/src/Hooks/Api/index.ts
@@ -1,4 +1,5 @@
export * from './useExperienceApi';
export * from './useJournalApi';
export * from './usePageContentApi';
+export * from './useProjectsApi';
export * from './useSkillsApi';
\ No newline at end of file
diff --git a/src/Hooks/Api/useProjectsApi.ts b/src/Hooks/Api/useProjectsApi.ts
new file mode 100644
index 0000000..a9b4f67
--- /dev/null
+++ b/src/Hooks/Api/useProjectsApi.ts
@@ -0,0 +1,17 @@
+import { useQuery } from '@tanstack/react-query';
+
+import { useContentfulClient } from '../useContentfulClient';
+
+import { ProjectItem } from 'Types';
+
+export const useProjectsApi = () => {
+ const { fetchEntries } = useContentfulClient();
+
+ return useQuery({
+ queryKey: ['useProjectsApi'],
+ queryFn: () => fetchEntries('project', {
+ order: '-sys.createdAt',
+ }),
+ staleTime: Infinity,
+ });
+};
\ No newline at end of file
diff --git a/src/Modules/NavBar/NavBar.tsx b/src/Modules/NavBar/NavBar.tsx
index 5992653..6626d32 100644
--- a/src/Modules/NavBar/NavBar.tsx
+++ b/src/Modules/NavBar/NavBar.tsx
@@ -1,5 +1,5 @@
import { useState } from 'react';
-import { Description,DeveloperMode,FilterAlt, Home } from '@mui/icons-material';
+import { Description,DeveloperMode,FilterAlt, Home, SettingsEthernet } from '@mui/icons-material';
import { Avatar, IconButton } from '@mui/material';
import AppBar from '@mui/material/AppBar';
import Box from '@mui/material/Box';
@@ -49,8 +49,7 @@ export const NavBar = ({ location = 'header' }: NavBarProps) => {
}>Home
}>Experience
}>Journal
- {/* Projects
- History */}
+ }>Projects
>
)}
diff --git a/src/Modules/ProjectsGrid/ProjectsGrid.tsx b/src/Modules/ProjectsGrid/ProjectsGrid.tsx
new file mode 100644
index 0000000..0e53720
--- /dev/null
+++ b/src/Modules/ProjectsGrid/ProjectsGrid.tsx
@@ -0,0 +1,43 @@
+import { useNavigate } from 'react-router-dom';
+
+import { Card, ErrorAlert, Grid, LoadingSpinner, NoDataAlert } from 'Components';
+
+import { useProjectsApi } from 'Hooks';
+
+export const ProjectsGrid = () => {
+ const { data, isLoading, error } = useProjectsApi();
+ const navigate = useNavigate();
+
+ const navigateProjectItem = (slug: string) => {
+ navigate(`/projects/${slug}`);
+ };
+
+ if (isLoading) {
+ return ;
+ }
+
+ if (error) {
+ return ;
+ }
+
+ if (!isLoading && !error && !data?.items?.length) {
+ return ;
+ }
+
+ console.log(data);
+
+ return (
+
+ {data?.items?.map(project => (
+ navigateProjectItem(project.fields.slug)}
+ />
+ ))}
+
+ );
+};
\ No newline at end of file
diff --git a/src/Modules/ProjectsGrid/index.ts b/src/Modules/ProjectsGrid/index.ts
new file mode 100644
index 0000000..2bf28ab
--- /dev/null
+++ b/src/Modules/ProjectsGrid/index.ts
@@ -0,0 +1 @@
+export * from './ProjectsGrid';
\ No newline at end of file
diff --git a/src/Modules/index.ts b/src/Modules/index.ts
index 31cdc90..d4e8ec7 100644
--- a/src/Modules/index.ts
+++ b/src/Modules/index.ts
@@ -5,6 +5,7 @@ export * from './Header';
export * from './JournalGrid';
export * from './NavBar';
export * from './PageContent';
+export * from './ProjectsGrid';
export * from './SideBar';
export * from './SocialLinks';
export * from './TagDrawer';
\ No newline at end of file
diff --git a/src/Routes/Projects/ProjectsItemPage.tsx b/src/Routes/Projects/ProjectsItemPage.tsx
new file mode 100644
index 0000000..ecb5950
--- /dev/null
+++ b/src/Routes/Projects/ProjectsItemPage.tsx
@@ -0,0 +1,7 @@
+export const ProjectsItemPage = () => {
+ return (
+
+ );
+};
\ No newline at end of file
diff --git a/src/Routes/Projects/ProjectsPage.tsx b/src/Routes/Projects/ProjectsPage.tsx
new file mode 100644
index 0000000..0bb2aa0
--- /dev/null
+++ b/src/Routes/Projects/ProjectsPage.tsx
@@ -0,0 +1,34 @@
+import { useEffect } from 'react';
+import { Helmet } from 'react-helmet-async';
+import { useLocation,useOutlet } from 'react-router-dom';
+
+import { Title } from 'Components';
+import { ProjectsGrid } from 'Modules';
+
+import { useUi } from 'Context/uiContext';
+import { useAnalytics } from 'Hooks/useAnalytics/useAnalytics';
+
+export const ProjectsPage = () => {
+ const outlet = useOutlet();
+ const { pageTitle } = useUi();
+ const { pageView } = useAnalytics();
+ const { pathname } = useLocation();
+
+ useEffect(() => {
+ if (!outlet) {
+ pageView(pathname);
+ }
+ });
+
+ return (
+ <>
+
+ Projects {pageTitle}
+
+
+
+
+
+ >
+ );
+};
\ No newline at end of file
diff --git a/src/Routes/Projects/index.ts b/src/Routes/Projects/index.ts
new file mode 100644
index 0000000..11ed6ee
--- /dev/null
+++ b/src/Routes/Projects/index.ts
@@ -0,0 +1,2 @@
+export * from './ProjectsItemPage';
+export * from './ProjectsPage';
\ No newline at end of file
diff --git a/src/Routes/index.ts b/src/Routes/index.ts
index f0640a6..bb84cb6 100644
--- a/src/Routes/index.ts
+++ b/src/Routes/index.ts
@@ -2,4 +2,5 @@ export * from './Error';
export * from './Experience';
export * from './Home';
export * from './Journal';
-export * from './Root';
+export * from './Projects';
+export * from './Root';
\ No newline at end of file
diff --git a/src/Types/project.types.ts b/src/Types/project.types.ts
index 303b861..5d5ee20 100644
--- a/src/Types/project.types.ts
+++ b/src/Types/project.types.ts
@@ -1,10 +1,11 @@
import Contentful from 'contentful';
export interface ProjectItem {
- title: Contentful.EntryFields.Text;
slug: Contentful.EntryFields.Text;
+ heroImage: Contentful.Asset;
+ title: Contentful.EntryFields.Text;
+ shortDescription: Contentful.EntryFields.Text;
+ description: Contentful.EntryFields.RichText;
url: Contentful.EntryFields.Text;
repo: Contentful.EntryFields.Text;
- description: Contentful.EntryFields.RichText;
- image: Contentful.Asset;
}
\ No newline at end of file
diff --git a/src/index.tsx b/src/index.tsx
index d4611e0..a246236 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -2,7 +2,7 @@ import React from 'react';
import ReactDOM from 'react-dom/client';
import { createBrowserRouter ,RouterProvider } from 'react-router-dom';
-import { ErrorPage, ExperiencePage, JournalItemPage, JournalPage, Root } from 'Routes';
+import { ErrorPage, ExperiencePage, JournalItemPage, JournalPage, ProjectsItemPage, ProjectsPage, Root } from 'Routes';
import './index.css';
import './Themes/dracular-prism.css';
@@ -16,6 +16,9 @@ const router = createBrowserRouter([
{ path: 'journal', element: , children: [
{ path: ':slug', element: }
] },
+ { path: 'projects', element: , children: [
+ { path: ':slug', element: }
+ ] },
] },
]);