diff --git a/workspaces/cms-config/src/collections/categories.ts b/workspaces/cms-config/src/collections/categories.ts index 896f0ff7afe..cedd023a6c9 100644 --- a/workspaces/cms-config/src/collections/categories.ts +++ b/workspaces/cms-config/src/collections/categories.ts @@ -24,6 +24,16 @@ export const categoriesCollectionConfig = { widget: "string", crowdin: true }, + { + name: 'parentCategory', + label: 'Parent category', + widget: 'relation', + collection: 'categories', + search_fields: ['name'], + value_field: 'id', + display_fields: ['name'], + options_length: 300 + }, { name: "show_custom_featured_post", label: "Show custom featured post", diff --git a/workspaces/cms-config/src/main.ts b/workspaces/cms-config/src/main.ts index d6667ab60b9..027a6c6a9fb 100644 --- a/workspaces/cms-config/src/main.ts +++ b/workspaces/cms-config/src/main.ts @@ -14,7 +14,9 @@ export const CMSConfig = { branch: "dev", base_url: "https://netlify-cms-auth.haim-6b2.workers.dev", preview_context: "Vercel – starknet-website", + local_backend: true, }, + local_backend: true, publish_mode: "editorial_workflow", show_preview_links: true, media_folder: "public/assets", diff --git a/workspaces/cms-data/src/categories.ts b/workspaces/cms-data/src/categories.ts index 1538c9abf7c..ea140a3a818 100644 --- a/workspaces/cms-data/src/categories.ts +++ b/workspaces/cms-data/src/categories.ts @@ -4,6 +4,7 @@ import { getFirst, getJSON } from "@starknet-io/cms-utils/src/index"; export interface Category { readonly id: string; readonly slug: string; + readonly parentCategory?: string; readonly name: string; readonly show_custom_featured_post?: boolean; readonly custom_featured_post?: string; diff --git a/workspaces/website/src/components/ArticleCard/ArticleCard.tsx b/workspaces/website/src/components/ArticleCard/ArticleCard.tsx index f8efcb9691e..e79e0e7385e 100644 --- a/workspaces/website/src/components/ArticleCard/ArticleCard.tsx +++ b/workspaces/website/src/components/ArticleCard/ArticleCard.tsx @@ -7,13 +7,16 @@ import { Icon, Flex, ChakraProps, - useBreakpointValue + useBreakpointValue, + BoxProps, + FlexProps } from "@chakra-ui/react"; import { Text } from "@ui/Typography/Text"; import { Heading } from "@ui/Typography/Heading"; -import { FiBookOpen, FiHeadphones, FiTv } from "react-icons/fi"; import { CardGradientBorder } from "@ui/Card/components/CardGradientBorder"; import { Category as DataCategory } from "@starknet-io/cms-data/src/categories"; +import { ReactNode } from "react"; +import { FiBookOpen, FiHeadphones, FiTv } from "react-icons/fi"; type RootProps = { children: React.ReactNode; @@ -43,39 +46,53 @@ const Root = ({ children, href, type = "grid", sx }: RootProps) => { ); }; -type ImageProps = { +type ImageProps = BoxProps & { + children?: ReactNode; url?: string; imageAlt?: string; type?: | "grid" | "featured"; }; -const Image = ({ url, imageAlt, type = "grid" }: ImageProps) => { +const Image = ({ children, url, imageAlt, type = "grid", ...rest }: ImageProps) => { const size = useBreakpointValue({ base: '581px', sm: '350px', md: '430px', xl: '320px' }); const featuredImageSize = useBreakpointValue({ base: '581px', sm: '350px', md: '430px', lg: '550px', xl: '606px' }); const cloudflareImage = `https://starknet.io/cdn-cgi/image/width=${type === "featured" ? featuredImageSize : size},height=auto,format=auto${url}`; const isProd = import.meta.env.VITE_ALGOLIA_INDEX === "production"; return ( - + + + {children} ); }; -type BodyProps = { +type BodyProps = FlexProps & { children: React.ReactNode; type?: | "grid" | "featured"; }; -const Body = ({ children, type = "grid" }: BodyProps) => { +const Body = ({ children, type = "grid", ...rest }: BodyProps) => { return ( - + {children} ); @@ -95,15 +112,19 @@ const Category = ({ category }: CategoryProps) => { ); }; -type ContentProps = { +type ContentProps = FlexProps & { title: string; excerpt: string; type?: | "grid" | "featured"; }; -const Content = ({ title, excerpt, type = "grid" }: ContentProps) => { +const Content = ({ title, excerpt, type = "grid", ...rest }: ContentProps) => { return ( - + { > {title} - + {excerpt} ); }; -type FooterProps = { +type FooterProps = FlexProps & { + hideIcon?: boolean; postType: string; publishedAt?: string; timeToConsume?: string; type?: | "grid" | "featured"; }; const Footer = ({ + hideIcon, postType, publishedAt = "N/A", timeToConsume = "5min read", - type = "grid" + type = "grid", + ...rest }: FooterProps) => { const renderPostTypeIcon = () => { switch (postType) { @@ -146,9 +170,14 @@ const Footer = ({ } }; return ( - + + {!hideIcon && ( + )} {publishedAt} · diff --git a/workspaces/website/src/components/Blog/BlogCard.tsx b/workspaces/website/src/components/Blog/BlogCard.tsx new file mode 100644 index 00000000000..19044a066ab --- /dev/null +++ b/workspaces/website/src/components/Blog/BlogCard.tsx @@ -0,0 +1,130 @@ +/** + * Module dependencies + */ + +import { BlogHit } from "src/pages/posts/CategoryPage"; +import { + Body, + Content, + Footer, + Image, + Root +} from "@ui/ArticleCard/ArticleCard"; + +import moment from "moment"; +import { Topic } from "@starknet-io/cms-data/src/topics"; +import { Tag } from "@chakra-ui/tag"; +import { useMemo } from "react"; +import { Box, Flex, Grid, Icon } from "@chakra-ui/react"; +import { IoPlaySharp } from "react-icons/io5"; + +/** + * `Props` type. + */ + +type Props = { + isFeatured?: boolean; + post: BlogHit; + topics: Topic[] +}; + + +/** + * Export `BlogCard` component. + */ + +export const BlogCard = ({ isFeatured, post, topics }: Props) => { + const topicNames = useMemo(() => ( + post.topic + .slice(0, 3) + .map((topic) => topics.find(({ id }) => id === topic)?.name) + .filter((topic) => !!topic) + ), [post, topics]) + + return ( + + + {(post.post_type === 'video' || post.post_type == 'audio') && ( + + + + )} + + + + {topicNames.length > 0 && ( + + {topicNames.length !== 0 && ( + + {topicNames.map((topic) => ( + + {topic} + + ))} + + )} + + )} + + + +