From b61031f393c13765c573755b3717bcdef4ff8b73 Mon Sep 17 00:00:00 2001 From: Jerome Hardaway Date: Tue, 21 May 2024 12:55:35 -0400 Subject: [PATCH 1/6] Add media page with grid layout --- src/components/media-grid.tsx | 37 +++++++++++ src/components/ui/button/index.tsx | 26 ++++++-- src/data/media.json | 64 ++++++++++++++++++ src/data/menu.ts | 103 ++--------------------------- src/pages/media.tsx | 31 +++++++++ 5 files changed, 159 insertions(+), 102 deletions(-) create mode 100644 src/components/media-grid.tsx create mode 100644 src/data/media.json create mode 100644 src/pages/media.tsx diff --git a/src/components/media-grid.tsx b/src/components/media-grid.tsx new file mode 100644 index 000000000..a5cd6cfad --- /dev/null +++ b/src/components/media-grid.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const MediaGrid = ({ section, data }) => { + return ( +
+ {data.map((item, index) => ( +
+ {item.title} +
+
{item.title}
+

+ {item.description} +

+
+
+ {item.tags.map((tag, index) => ( + #{tag} + ))} +
+
+ ))} +
+ ); +}; + +MediaGrid.propTypes = { + section: PropTypes.string.isRequired, + data: PropTypes.arrayOf(PropTypes.shape({ + image: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + description: PropTypes.string, + tags: PropTypes.arrayOf(PropTypes.string), + })).isRequired, +}; + +export default MediaGrid; diff --git a/src/components/ui/button/index.tsx b/src/components/ui/button/index.tsx index b5811ec78..d0d60237b 100644 --- a/src/components/ui/button/index.tsx +++ b/src/components/ui/button/index.tsx @@ -14,11 +14,11 @@ interface ButtonProps { /** * Optional. Default is `contained`. */ - variant?: "contained" | "outlined" | "texted"; + variant?: "contained" | "outlined" | "texted" | "media"; /** * Optional. Default is `primary`. */ - color?: "primary" | "light"; + color?: "primary" | "light" | "media"; /** * Optional. Default is `md`. */ @@ -164,6 +164,24 @@ const Button = ({ lightHoverClass, ]; + // Media Button + const mediaClass = "tw-bg-media tw-border-media tw-text-white"; + const mediaHoverClass = + !disabled && + !active && + hover === "default" && + "hover:tw-bg-media-dark hover:tw-border-media-dark hover:tw-text-white"; + const mediaActiveClass = + !disabled && + active && + "tw-bg-media-dark tw-border-media-dark active:tw-bg-media-dark active:tw-border-media-dark"; + const mediaBtn = color === "media" && [ + mediaClass, + mediaHoverClass, + mediaActiveClass, + lightHoverClass, + ]; + // Buton Sizes const mdBtn = size === "md" && @@ -179,8 +197,8 @@ const Button = ({ const classnames = clsx( variant !== "texted" && baseClass, variant !== "texted" && baseNotFullWidthClass, - variant === "contained" && [containedPrimaryBtn, containedLightBtn], - variant === "outlined" && [outlinedPrimaryBtn, outlinedLightBtn], + variant === "contained" && [containedPrimaryBtn, containedLightBtn, mediaBtn], + variant === "outlined" && [outlinedPrimaryBtn, outlinedLightBtn, mediaBtn], !iconButton && variant !== "texted" && [mdBtn, xsBtn], roundedBtn, ellipseBtn, diff --git a/src/data/media.json b/src/data/media.json new file mode 100644 index 000000000..bfed3ded7 --- /dev/null +++ b/src/data/media.json @@ -0,0 +1,64 @@ +{ + "media": [ + { + "section": "What we have built", + "items": [ + { + "title": "Project 1", + "description": "Description of Project 1", + "link": "https://example.com/project1" + }, + { + "title": "Project 2", + "description": "Description of Project 2", + "link": "https://example.com/project2" + } + ] + }, + { + "section": "Publications", + "items": [ + { + "title": "Publication 1", + "description": "Description of Publication 1", + "link": "https://example.com/publication1" + }, + { + "title": "Publication 2", + "description": "Description of Publication 2", + "link": "https://example.com/publication2" + } + ] + }, + { + "section": "Podcasts", + "items": [ + { + "title": "Podcast 1", + "description": "Description of Podcast 1", + "link": "https://example.com/podcast1" + }, + { + "title": "Podcast 2", + "description": "Description of Podcast 2", + "link": "https://example.com/podcast2" + } + ] + }, + { + "section": "Courses", + "items": [ + { + "title": "Course 1", + "description": "Description of Course 1", + "link": "https://example.com/course1" + }, + { + "title": "Course 2", + "description": "Description of Course 2", + "link": "https://example.com/course2" + } + ] + } + ] +} diff --git a/src/data/menu.ts b/src/data/menu.ts index 9c32cb8eb..34d5703ed 100644 --- a/src/data/menu.ts +++ b/src/data/menu.ts @@ -3,98 +3,6 @@ export default [ id: 1, label: "Home", path: "/", - /* megamenu: [ - { - id: 11, - title: "Group 01", - submenu: [ - { - id: 111, - label: "submenu item 01", - path: "/", - status: "hot", - }, - { - id: 112, - label: "submenu item 02", - path: "#", - }, - { - id: 113, - label: "submenu item 03", - path: "#", - status: "hot", - }, - { - id: 114, - label: "submenu item 04", - path: "#", - }, - { - id: 115, - label: "submenu item 05", - path: "#", - }, - { - id: 116, - label: "submenu item 06", - path: "#", - }, - ], - }, - { - id: 12, - title: "Group 02", - submenu: [ - { - id: 121, - label: "submenu item 07", - path: "/", - status: "coming soon", - }, - { - id: 122, - label: "submenu item 08", - path: "/", - status: "coming soon", - }, - { - id: 123, - label: "submenu item 09", - path: "/", - status: "coming soon", - }, - { - id: 124, - label: "submenu item 10", - path: "/", - status: "coming soon", - }, - { - id: 125, - label: "submenu item 11", - path: "/", - status: "coming soon", - }, - { - id: 126, - label: "submenu item 12", - path: "/", - status: "coming soon", - }, - ], - }, - { - id: 13, - title: "Banner", - banner: { - path: "/", - image: { - src: "/images/menu/mega-menu.jpg", - }, - }, - }, - ], */ }, { id: 2, @@ -133,12 +41,6 @@ export default [ label: "Apply to be a Student", path: "/apply", }, - /* { - id: 26, - label: "Join our community", - path: "/join-our-community", - }, -*/ ], }, { @@ -166,4 +68,9 @@ export default [ label: "Donate", path: "/donate", }, + { + id: 9, + label: "Media", + path: "/media", + }, ]; diff --git a/src/pages/media.tsx b/src/pages/media.tsx new file mode 100644 index 000000000..a722d507a --- /dev/null +++ b/src/pages/media.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { Container, Row, Col } from 'react-bootstrap'; +import MediaGrid from '../components/media-grid'; + +const MediaPage = () => { + return ( + + + +

Media

+ +
+ + + + + + + + + + + + + + +
+ ); +}; + +export default MediaPage; From 25c7e21e34da643f07d1700fe5d3c4396720d01f Mon Sep 17 00:00:00 2001 From: Jerome Hardaway Date: Wed, 22 May 2024 12:42:30 -0400 Subject: [PATCH 2/6] removed grid wording --- src/pages/blogs/blog-grid-sidebar/index.tsx | 4 ++-- src/pages/blogs/blog-grid-sidebar/page/[page].tsx | 4 ++-- src/pages/blogs/blog/page/[page].tsx | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pages/blogs/blog-grid-sidebar/index.tsx b/src/pages/blogs/blog-grid-sidebar/index.tsx index 057cdeb75..16c4168c3 100644 --- a/src/pages/blogs/blog-grid-sidebar/index.tsx +++ b/src/pages/blogs/blog-grid-sidebar/index.tsx @@ -27,10 +27,10 @@ const BlogGridSidebar: PageProps = ({ }) => { return ( <> - + { return ( <> - + { return ( <> - + Date: Wed, 22 May 2024 15:04:08 -0400 Subject: [PATCH 3/6] feat: Add media page with grid layout --- src/components/media-grid.tsx | 37 --------- src/components/media-grid/index.tsx | 54 ++++++++++++ src/data/media/media-one.md | 11 +++ src/data/media/media-two.md | 11 +++ src/lib/media.ts | 122 ++++++++++++++++++++++++++++ src/pages/media.tsx | 83 ++++++++++++------- src/utils/types.ts | 14 ++++ 7 files changed, 267 insertions(+), 65 deletions(-) delete mode 100644 src/components/media-grid.tsx create mode 100644 src/components/media-grid/index.tsx create mode 100644 src/data/media/media-one.md create mode 100644 src/data/media/media-two.md create mode 100644 src/lib/media.ts diff --git a/src/components/media-grid.tsx b/src/components/media-grid.tsx deleted file mode 100644 index a5cd6cfad..000000000 --- a/src/components/media-grid.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -const MediaGrid = ({ section, data }) => { - return ( -
- {data.map((item, index) => ( -
- {item.title} -
-
{item.title}
-

- {item.description} -

-
-
- {item.tags.map((tag, index) => ( - #{tag} - ))} -
-
- ))} -
- ); -}; - -MediaGrid.propTypes = { - section: PropTypes.string.isRequired, - data: PropTypes.arrayOf(PropTypes.shape({ - image: PropTypes.string.isRequired, - title: PropTypes.string.isRequired, - description: PropTypes.string, - tags: PropTypes.arrayOf(PropTypes.string), - })).isRequired, -}; - -export default MediaGrid; diff --git a/src/components/media-grid/index.tsx b/src/components/media-grid/index.tsx new file mode 100644 index 000000000..dd9da9e29 --- /dev/null +++ b/src/components/media-grid/index.tsx @@ -0,0 +1,54 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { IMedia } from "@utils/types"; + +type MediaGridProps = { + section: string; + data: IMedia[]; +}; + +const MediaGrid: React.FC = ({ section, data }) => { + if (!Array.isArray(data)) { + return null; // or some fallback UI + } + + return ( +
+

{section}

+
+ {data.map((item, index) => ( +
+ {item.title} +
+
{item.title}
+

+ {item.description} +

+
+
+ {item.tags.map((tag, index) => ( + #{tag} + ))} +
+
+ ))} +
+
+ ); +}; + +MediaGrid.propTypes = { + section: PropTypes.string.isRequired, + data: PropTypes.arrayOf(PropTypes.shape({ + image: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + description: PropTypes.string, + tags: PropTypes.arrayOf(PropTypes.shape({ + title: PropTypes.string.isRequired, + slug: PropTypes.string.isRequired, + path: PropTypes.string.isRequired, + })), + })).isRequired, +}; + +export default MediaGrid; diff --git a/src/data/media/media-one.md b/src/data/media/media-one.md new file mode 100644 index 000000000..40b69f21c --- /dev/null +++ b/src/data/media/media-one.md @@ -0,0 +1,11 @@ +--- +title: "Media Title 1" +image: "https://dummyimage.com/640x4:3/" +description: "Description for media 1" +tags: + - "tag1" + - "tag2" +date: "2023-05-01" +--- + +Content for media item 1. diff --git a/src/data/media/media-two.md b/src/data/media/media-two.md new file mode 100644 index 000000000..86f6e80b6 --- /dev/null +++ b/src/data/media/media-two.md @@ -0,0 +1,11 @@ +--- +title: "Media Title 2" +image: "https://dummyimage.com/640x4:3/" +description: "Description for media 2" +tags: + - "tag3" + - "tag4" +date: "2023-06-15" +--- + +Content for media item 2. diff --git a/src/lib/media.ts b/src/lib/media.ts new file mode 100644 index 000000000..5a456458c --- /dev/null +++ b/src/lib/media.ts @@ -0,0 +1,122 @@ +import fs from "fs"; +import { join } from "path"; +import matter from "gray-matter"; +import { IMedia } from "@utils/types"; +import { slugify, flatDeep } from "@utils/methods"; +import { getSlugs } from "./util"; + +const mediaDirectory = join(process.cwd(), "src/data/media"); + +const makeExcerpt = (str: string, maxLength: number): string => { + if (str.length <= maxLength) { + return str; + } + let excerpt = str.substring(0, maxLength); + excerpt = excerpt.substring(0, excerpt.lastIndexOf(" ")); + return `${excerpt} ...`; +}; + +export function getMediaBySlug( + slug: string, + fields: Array | "all" = [] +): IMedia { + const realSlug = slug.replace(/\.md$/, ""); + const fullPath = join(mediaDirectory, `${realSlug}.md`); + const fileContents = fs.readFileSync(fullPath, "utf8"); + const { data, content } = matter(fileContents); + + const mediaData = data as IMedia; + + let media: IMedia; + + if (fields === "all") { + media = { + ...mediaData, + content, + tags: mediaData.tags.map((tag) => ({ + title: tag, + slug: slugify(tag), + path: `/media/tag/${slugify(tag)}`, + })), + slug: realSlug, + excerpt: makeExcerpt(content, 150), + }; + } else { + media = fields.reduce((acc: IMedia, field: keyof IMedia) => { + if (field === "slug") { + return { ...acc, slug: realSlug }; + } + if (field === "content") { + return { ...acc, [field]: content }; + } + if (field === "excerpt") { + return { ...acc, excerpt: makeExcerpt(content, 150) }; + } + if (field === "tags") { + return { + ...acc, + tags: mediaData.tags.map((tag) => ({ + title: tag, + slug: slugify(tag), + path: `/media/tag/${slugify(tag)}`, + })), + }; + } + if (typeof data[field] !== "undefined") { + return { ...acc, [field]: mediaData[field] }; + } + return acc; + }, {}); + } + + return { + ...media, + path: `/media/${realSlug}`, + }; +} + +export function getAllMedia( + fields: Array | "all" = [], + skip = 0, + limit?: number +) { + const slugs = getSlugs(mediaDirectory); + let media = slugs + .map((slug) => getMediaBySlug(slug, fields)) + .sort((item1, item2) => + new Date(item1.date).getTime() > new Date(item2.date).getTime() + ? -1 + : 1 + ); + if (limit) media = media.slice(skip, skip + limit); + return { media, count: slugs.length }; +} + +export function getTags() { + const { media } = getAllMedia(["tags"]); + const tags = flatDeep(media.map((item) => item.tags)); + const result: { title: string; slug: string; path: string }[] = []; + + tags.forEach((tag) => { + if (!result.find((t) => t.title === tag.title)) { + result.push(tag); + } + }); + + return result; +} + +export function getMediaByTag( + tag: string, + fields: Array | "all" = [], + skip = 0, + limit?: number +) { + const postFields = + fields === "all" ? "all" : ([...fields, "tags"] as Array); + const { media } = getAllMedia(postFields); + let result = media.filter((item) => item.tags.some((t) => t.slug === tag)); + const totalItems = result.length; + if (limit) result = result.slice(skip, skip + limit); + return { items: result, count: totalItems }; +} diff --git a/src/pages/media.tsx b/src/pages/media.tsx index a722d507a..4c237cafe 100644 --- a/src/pages/media.tsx +++ b/src/pages/media.tsx @@ -1,31 +1,58 @@ -import React from 'react'; -import { Container, Row, Col } from 'react-bootstrap'; -import MediaGrid from '../components/media-grid'; +import type { GetStaticProps, NextPage } from "next"; +import SEO from "@components/seo/page-seo"; +import Layout01 from "@layout/layout-01"; +import Breadcrumb from "@components/breadcrumb"; +import MediaGrid from "@components/media-grid"; +import { getAllMedia } from "../lib/media"; +import { IMedia } from "@utils/types"; -const MediaPage = () => { - return ( - - - -

Media

- -
- - - - - - - - - - - - - - -
- ); +type TProps = { + data: { + media: IMedia[]; + }; }; -export default MediaPage; +type PageProps = NextPage & { + Layout: typeof Layout01; +}; + +const Media: PageProps = ({ data: { media } }) => { + return ( + <> + + +
+
+ + + + +
+
+ + ); +}; + +Media.Layout = Layout01; + +export const getStaticProps: GetStaticProps = () => { + const { media } = getAllMedia(["title", "image", "description", "tags", "date", "content", "slug", "excerpt"]); + + return { + props: { + data: { + media, + }, + layout: { + headerShadow: true, + headerFluid: false, + footerMode: "light", + }, + }, + }; +}; + +export default Media; diff --git a/src/utils/types.ts b/src/utils/types.ts index 08d664fd8..6c62592de 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -308,3 +308,17 @@ export type ApiResponse = { error?: string; message?: string; }; + +// utils/types.ts + +export interface IMedia { + image: string; + title: string; + description?: string; + tags?: { title: string; slug: string; path: string }[]; + date: string; + content: string; + slug: string; + excerpt: string; + path: string; +} From 18e99afca95267e4f3878bb71e31368d5aa9cd03 Mon Sep 17 00:00:00 2001 From: Jerome Hardaway Date: Fri, 24 May 2024 21:04:52 -0400 Subject: [PATCH 4/6] feat: Add sortOrder and type properties to media items --- src/components/media-card/index.tsx | 70 +++++++++++++++++++++++++++++ src/components/media-grid/index.tsx | 66 +++++++++++++++------------ src/data/media/media-one.md | 4 +- src/data/media/media-three.md | 13 ++++++ src/data/media/media-two.md | 2 + src/lib/media.ts | 4 +- src/pages/media.tsx | 42 +++++++++++++---- 7 files changed, 162 insertions(+), 39 deletions(-) create mode 100644 src/components/media-card/index.tsx create mode 100644 src/data/media/media-three.md diff --git a/src/components/media-card/index.tsx b/src/components/media-card/index.tsx new file mode 100644 index 000000000..265fe3cbf --- /dev/null +++ b/src/components/media-card/index.tsx @@ -0,0 +1,70 @@ +import React, { forwardRef } from "react"; +import clsx from "clsx"; +import Anchor from "@ui/anchor"; +import { IMedia } from "@utils/types"; + +type TProps = Pick< + IMedia, + "title" | "path" | "type" | "date" | "image" | "views" +> & { + className?: string; +}; + +const MediaCard = forwardRef( + ({ title, path, type, date, image, views, className }, ref) => { + console.log('Image Source:', image?.src); // Debugging log + return ( +
+ {image?.src ? ( +
+ {image?.alt + + {title} + +
+ ) : ( +
+ )} + +
+ + {type} + +

+ + {title} + +

+
    +
  • + + {date} +
  • +
  • + + {views} views +
  • +
+
+
+ ); + } +); + +export default MediaCard; diff --git a/src/components/media-grid/index.tsx b/src/components/media-grid/index.tsx index dd9da9e29..24561bd5c 100644 --- a/src/components/media-grid/index.tsx +++ b/src/components/media-grid/index.tsx @@ -1,10 +1,19 @@ -import React from "react"; -import PropTypes from "prop-types"; -import { IMedia } from "@utils/types"; +import React from 'react'; +import PropTypes from 'prop-types'; +import MediaCard from '@components/media-card'; // Adjust the import path as needed type MediaGridProps = { section: string; - data: IMedia[]; + data: { + image: { src: string; alt?: string; width?: number; height?: number; loading?: string; }; + title: string; + description: string; + type: string; + date: string; + path: string; + views: number; + slug: string; + }[]; }; const MediaGrid: React.FC = ({ section, data }) => { @@ -13,24 +22,19 @@ const MediaGrid: React.FC = ({ section, data }) => { } return ( -
+

{section}

-
- {data.map((item, index) => ( -
- {item.title} -
-
{item.title}
-

- {item.description} -

-
-
- {item.tags.map((tag, index) => ( - #{tag} - ))} -
-
+
+ {data.map(item => ( + ))}
@@ -40,14 +44,20 @@ const MediaGrid: React.FC = ({ section, data }) => { MediaGrid.propTypes = { section: PropTypes.string.isRequired, data: PropTypes.arrayOf(PropTypes.shape({ - image: PropTypes.string.isRequired, + image: PropTypes.shape({ + src: PropTypes.string.isRequired, + alt: PropTypes.string, + width: PropTypes.number, + height: PropTypes.number, + loading: PropTypes.string, + }).isRequired, title: PropTypes.string.isRequired, - description: PropTypes.string, - tags: PropTypes.arrayOf(PropTypes.shape({ - title: PropTypes.string.isRequired, - slug: PropTypes.string.isRequired, - path: PropTypes.string.isRequired, - })), + description: PropTypes.string.isRequired, + type: PropTypes.string.isRequired, + date: PropTypes.string.isRequired, + path: PropTypes.string.isRequired, + views: PropTypes.number.isRequired, + slug: PropTypes.string.isRequired, })).isRequired, }; diff --git a/src/data/media/media-one.md b/src/data/media/media-one.md index 40b69f21c..a75fa0da8 100644 --- a/src/data/media/media-one.md +++ b/src/data/media/media-one.md @@ -6,6 +6,8 @@ tags: - "tag1" - "tag2" date: "2023-05-01" +sortOrder: 0 +type: "publication" +slug: "media-one" --- - Content for media item 1. diff --git a/src/data/media/media-three.md b/src/data/media/media-three.md new file mode 100644 index 000000000..f3cd3bf19 --- /dev/null +++ b/src/data/media/media-three.md @@ -0,0 +1,13 @@ +--- +title: "Media Title 5" +image: "https://res.cloudinary.com/vetswhocode/image/upload/v1716598792/junior-senior-card_sjcshc.jpg" +description: "Description for media " +tags: + - "tag1" + - "tag2" +date: "2023-05-01" +sortOrder: 1 +type: "publication" +slug: "media-one" +--- +Content for media item 5. diff --git a/src/data/media/media-two.md b/src/data/media/media-two.md index 86f6e80b6..d13128b06 100644 --- a/src/data/media/media-two.md +++ b/src/data/media/media-two.md @@ -6,6 +6,8 @@ tags: - "tag3" - "tag4" date: "2023-06-15" +sortOrder: 2 +type: "podcast" --- Content for media item 2. diff --git a/src/lib/media.ts b/src/lib/media.ts index 5a456458c..fda6846c7 100644 --- a/src/lib/media.ts +++ b/src/lib/media.ts @@ -33,7 +33,7 @@ export function getMediaBySlug( media = { ...mediaData, content, - tags: mediaData.tags.map((tag) => ({ + tags: mediaData.tags.map((tag: string) => ({ title: tag, slug: slugify(tag), path: `/media/tag/${slugify(tag)}`, @@ -55,7 +55,7 @@ export function getMediaBySlug( if (field === "tags") { return { ...acc, - tags: mediaData.tags.map((tag) => ({ + tags: mediaData.tags.map((tag: string) => ({ title: tag, slug: slugify(tag), path: `/media/tag/${slugify(tag)}`, diff --git a/src/pages/media.tsx b/src/pages/media.tsx index 4c237cafe..6a73935a4 100644 --- a/src/pages/media.tsx +++ b/src/pages/media.tsx @@ -17,6 +17,11 @@ type PageProps = NextPage & { }; const Media: PageProps = ({ data: { media } }) => { + const whatWeHaveBuilt = media.filter(item => item.tags.some(tag => tag.title === 'github')); + const publications = media.filter(item => item.type === 'publication'); + const podcasts = media.filter(item => item.type === 'podcast'); + const courses = media.filter(item => item.type === 'course'); + return ( <> @@ -25,12 +30,10 @@ const Media: PageProps = ({ data: { media } }) => { currentPage="Media" />
-
- - - - -
+ + + +
); @@ -39,12 +42,35 @@ const Media: PageProps = ({ data: { media } }) => { Media.Layout = Layout01; export const getStaticProps: GetStaticProps = () => { - const { media } = getAllMedia(["title", "image", "description", "tags", "date", "content", "slug", "excerpt"]); + const { media } = getAllMedia([ + "title", "image", "description", "tags", "date", "content", "slug", "excerpt", "sortOrder", "type" + ]); + + if (!Array.isArray(media)) { + return { + props: { + data: { + media: [], + }, + layout: { + headerShadow: true, + headerFluid: false, + footerMode: "light", + }, + }, + }; + } + + // Remove duplicates and sort data by sortOrder + const uniqueMedia = Array.from(new Set(media.map(item => item.slug))) + .map(slug => media.find(item => item.slug === slug)) + .filter(item => item !== undefined) // Ensure item is not undefined + .sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0)); return { props: { data: { - media, + media: uniqueMedia, }, layout: { headerShadow: true, From abe0944e8a92acc0d50214305edfb998b6b5be55 Mon Sep 17 00:00:00 2001 From: Jerome Hardaway Date: Fri, 24 May 2024 21:05:25 -0400 Subject: [PATCH 5/6] feat: Add sortOrder and type properties to media items --- src/components/media-card/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/media-card/index.tsx b/src/components/media-card/index.tsx index 265fe3cbf..8166903ac 100644 --- a/src/components/media-card/index.tsx +++ b/src/components/media-card/index.tsx @@ -67,4 +67,4 @@ const MediaCard = forwardRef( } ); -export default MediaCard; +export default MediaCard; \ No newline at end of file From a2b1cfed2d88efd7368901d423c32a73b439e03b Mon Sep 17 00:00:00 2001 From: Jerome Hardaway Date: Sun, 2 Jun 2024 20:11:43 -0400 Subject: [PATCH 6/6] feat: Update media-one image URL --- .eslintrc.json | 8 ++- .idea/.gitignore | 5 ++ .idea/inspectionProfiles/Project_Default.xml | 6 ++ .idea/modules.xml | 8 +++ .idea/vcs.xml | 6 ++ .idea/vets-who-code-app.iml | 12 ++++ qodana.yaml | 29 ++++++++ src/components/media-card/index.tsx | 76 +++++++++----------- src/containers/media-full/index.tsx | 57 +++++++++++++++ src/data/media/media-one.md | 2 +- src/pages/media.tsx | 46 +++++------- 11 files changed, 178 insertions(+), 77 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/vets-who-code-app.iml create mode 100644 qodana.yaml create mode 100644 src/containers/media-full/index.tsx diff --git a/.eslintrc.json b/.eslintrc.json index adb0c21aa..c2c8826d2 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -6,9 +6,11 @@ "airbnb/hooks", "prettier", "plugin:@typescript-eslint/recommended", - "plugin:@typescript-eslint/recommended-requiring-type-checking" + "plugin:@typescript-eslint/recommended-requiring-type-checking", + "eslint-config-airbnb", + "eslint-config-next" ], - "plugins": ["react", "@typescript-eslint", "prettier"], + "plugins": ["react", "@typescript-eslint", "prettier", "react-hooks"], "env": { "browser": true, "es2021": true, @@ -24,6 +26,8 @@ "sourceType": "module" }, "rules": { + "react-hooks/rules-of-hooks": "error", + "react-hooks/exhaustive-deps": "warn", "@typescript-eslint/no-misused-promises": [ "error", { diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 000000000..b58b603fe --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 000000000..03d9549ea --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 000000000..8bdf84ccd --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..35eb1ddfb --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vets-who-code-app.iml b/.idea/vets-who-code-app.iml new file mode 100644 index 000000000..24643cc37 --- /dev/null +++ b/.idea/vets-who-code-app.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/qodana.yaml b/qodana.yaml new file mode 100644 index 000000000..29f8f8c1f --- /dev/null +++ b/qodana.yaml @@ -0,0 +1,29 @@ +#-------------------------------------------------------------------------------# +# Qodana analysis is configured by qodana.yaml file # +# https://www.jetbrains.com/help/qodana/qodana-yaml.html # +#-------------------------------------------------------------------------------# +version: "1.0" + +#Specify inspection profile for code analysis +profile: + name: qodana.starter + +#Enable inspections +#include: +# - name: + +#Disable inspections +#exclude: +# - name: +# paths: +# - + +#Execute shell command before Qodana execution (Applied in CI/CD pipeline) +#bootstrap: sh ./prepare-qodana.sh + +#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline) +#plugins: +# - id: #(plugin id can be found at https://plugins.jetbrains.com) + +#Specify Qodana linter for analysis (Applied in CI/CD pipeline) +linter: jetbrains/qodana-js:latest diff --git a/src/components/media-card/index.tsx b/src/components/media-card/index.tsx index 8166903ac..a23811e9e 100644 --- a/src/components/media-card/index.tsx +++ b/src/components/media-card/index.tsx @@ -1,65 +1,53 @@ -import React, { forwardRef } from "react"; +import { forwardRef } from "react"; import clsx from "clsx"; import Anchor from "@ui/anchor"; import { IMedia } from "@utils/types"; type TProps = Pick< IMedia, - "title" | "path" | "type" | "date" | "image" | "views" + "image" | "path" | "title" | "description" | "type" | "date" > & { className?: string; }; const MediaCard = forwardRef( - ({ title, path, type, date, image, views, className }, ref) => { - console.log('Image Source:', image?.src); // Debugging log + ({ className, image, path, title, description, type, date }, ref) => { return ( -
- {image?.src ? ( -
- {image?.alt - - {title} - -
- ) : ( -
- )} +
+
+ {image?.src && ( +
+ {image?.alt +
+ )} + + {title} + +
-
- +
+
{type} - -

- - {title} - +

+ +

+ {title}

-
    -
  • + +

    {description}

    + +
      +
    • {date}
    • -
    • - - {views} views -
diff --git a/src/containers/media-full/index.tsx b/src/containers/media-full/index.tsx new file mode 100644 index 000000000..1f79991b6 --- /dev/null +++ b/src/containers/media-full/index.tsx @@ -0,0 +1,57 @@ +import { motion } from "framer-motion"; +import Section from "@ui/section"; +import MediaCard from "@components/media-card/index"; // Assuming there's a media-card component similar to blog-card +import Pagination from "@components/pagination/pagination-01"; +import { IMedia } from "@utils/types"; +import { scrollUpVariants } from "@utils/variants"; + +const AnimatedMediaCard = motion(MediaCard); + +type TProps = { + data: { + media: IMedia[]; + pagiData?: { + currentPage: number; + numberOfPages: number; + }; + }; +}; + +const MediaArea = ({ data: { media, pagiData } }: TProps) => { + return ( +
+

Media Section

+
+ {media.length > 0 && ( +
+ {media.map((item) => ( + + ))} +
+ )} + {pagiData && pagiData.numberOfPages > 1 && ( + + )} +
+
+ ); +}; + +export default MediaArea; \ No newline at end of file diff --git a/src/data/media/media-one.md b/src/data/media/media-one.md index a75fa0da8..fffb3ce8c 100644 --- a/src/data/media/media-one.md +++ b/src/data/media/media-one.md @@ -1,6 +1,6 @@ --- title: "Media Title 1" -image: "https://dummyimage.com/640x4:3/" +image: "https://res.cloudinary.com/vetswhocode/image/upload/v1716598792/junior-senior-card_sjcshc.jpg" description: "Description for media 1" tags: - "tag1" diff --git a/src/pages/media.tsx b/src/pages/media.tsx index 6a73935a4..de2e6dde8 100644 --- a/src/pages/media.tsx +++ b/src/pages/media.tsx @@ -2,13 +2,15 @@ import type { GetStaticProps, NextPage } from "next"; import SEO from "@components/seo/page-seo"; import Layout01 from "@layout/layout-01"; import Breadcrumb from "@components/breadcrumb"; -import MediaGrid from "@components/media-grid"; +import MediaArea from "@containers/media-full/index"; // Assuming the new component is saved here import { getAllMedia } from "../lib/media"; import { IMedia } from "@utils/types"; type TProps = { data: { media: IMedia[]; + currentPage: number; + numberOfPages: number; }; }; @@ -16,12 +18,9 @@ type PageProps = NextPage & { Layout: typeof Layout01; }; -const Media: PageProps = ({ data: { media } }) => { - const whatWeHaveBuilt = media.filter(item => item.tags.some(tag => tag.title === 'github')); - const publications = media.filter(item => item.type === 'publication'); - const podcasts = media.filter(item => item.type === 'podcast'); - const courses = media.filter(item => item.type === 'course'); +const POSTS_PER_PAGE = 9; +const Media: PageProps = ({ data: { media, currentPage, numberOfPages } }) => { return ( <> @@ -29,12 +28,12 @@ const Media: PageProps = ({ data: { media } }) => { pages={[{ path: "/", label: "home" }]} currentPage="Media" /> -
- - - - -
+ ); }; @@ -42,24 +41,9 @@ const Media: PageProps = ({ data: { media } }) => { Media.Layout = Layout01; export const getStaticProps: GetStaticProps = () => { - const { media } = getAllMedia([ + const { media, count } = getAllMedia([ "title", "image", "description", "tags", "date", "content", "slug", "excerpt", "sortOrder", "type" - ]); - - if (!Array.isArray(media)) { - return { - props: { - data: { - media: [], - }, - layout: { - headerShadow: true, - headerFluid: false, - footerMode: "light", - }, - }, - }; - } + ], 0, POSTS_PER_PAGE); // Remove duplicates and sort data by sortOrder const uniqueMedia = Array.from(new Set(media.map(item => item.slug))) @@ -71,6 +55,8 @@ export const getStaticProps: GetStaticProps = () => { props: { data: { media: uniqueMedia, + currentPage: 1, + numberOfPages: Math.ceil(count / POSTS_PER_PAGE), }, layout: { headerShadow: true, @@ -81,4 +67,4 @@ export const getStaticProps: GetStaticProps = () => { }; }; -export default Media; +export default Media; \ No newline at end of file