├── .gitignore ├── LICENSE ├── README.md └── themes ├── card ├── components │ ├── ClientOnly.tsx │ ├── Footer.tsx │ ├── Header.tsx │ ├── Image.tsx │ ├── Link.tsx │ ├── PageTitle.tsx │ ├── Pagination.tsx │ ├── PostItem.tsx │ ├── PostList.tsx │ ├── PostListWithTag.tsx │ ├── PostTypeNav.tsx │ ├── SectionContainer.tsx │ ├── Tag.tsx │ ├── TagList.tsx │ └── social-icons │ │ ├── icons.tsx │ │ └── index.tsx ├── externals │ ├── AboutPage.tsx │ ├── BlogPage.tsx │ ├── HomePage.tsx │ ├── PostDetail.tsx │ ├── SiteLayout.tsx │ ├── TagDetailPage.tsx │ ├── TagListPage.tsx │ └── index.ts ├── files.json ├── index.ts ├── layouts │ └── SiteLayout.tsx ├── manifest.json ├── pages │ ├── AboutPage.tsx │ ├── BlogPage.tsx │ ├── HomePage.tsx │ ├── PostDetail.tsx │ ├── TagDetailPage.tsx │ └── TagListPage.tsx └── screenshots │ └── screenshot-1.png ├── garden ├── components │ ├── Header.tsx │ ├── PostItem.tsx │ ├── PostList.tsx │ ├── PostListWithTag.tsx │ ├── PostTypeNav.tsx │ ├── SectionContainer.tsx │ ├── Tag.tsx │ └── TagList.tsx ├── files.json ├── index.ts ├── layouts │ └── SiteLayout.tsx ├── manifest.json ├── pages │ ├── AboutPage.tsx │ ├── BlogPage.tsx │ ├── HomePage.tsx │ ├── PostDetail.tsx │ ├── TagDetailPage.tsx │ └── TagListPage.tsx └── screenshots │ └── screenshot-1.png ├── micro ├── components │ ├── Header.tsx │ ├── PostItem.tsx │ ├── PostList.tsx │ ├── PostListWithTag.tsx │ ├── SectionContainer.tsx │ ├── Tag.tsx │ └── TagList.tsx ├── files.json ├── index.ts ├── layouts │ └── SiteLayout.tsx ├── manifest.json ├── pages │ ├── AboutPage.tsx │ ├── BlogPage.tsx │ ├── HomePage.tsx │ ├── PostDetail.tsx │ ├── TagDetailPage.tsx │ └── TagListPage.tsx └── screenshots │ └── screenshot-1.png ├── minimal ├── components │ ├── ClientOnly.tsx │ ├── Footer.tsx │ ├── Header.tsx │ ├── Image.tsx │ ├── Link.tsx │ ├── PageTitle.tsx │ ├── Pagination.tsx │ ├── PostItem.tsx │ ├── PostList.tsx │ ├── PostListWithTag.tsx │ ├── SectionContainer.tsx │ ├── Tag.tsx │ ├── TagList.tsx │ └── social-icons │ │ ├── icons.tsx │ │ └── index.tsx ├── externals │ ├── AboutPage.tsx │ ├── BlogPage.tsx │ ├── HomePage.tsx │ ├── PostDetail.tsx │ ├── SiteLayout.tsx │ ├── TagDetailPage.tsx │ ├── TagListPage.tsx │ └── index.ts ├── files.json ├── index.ts ├── layouts │ └── SiteLayout.tsx ├── manifest.json ├── pages │ ├── AboutPage.tsx │ ├── BlogPage.tsx │ ├── HomePage.tsx │ ├── PostDetail.tsx │ ├── TagDetailPage.tsx │ └── TagListPage.tsx └── screenshots │ └── screenshot-1.png └── photo ├── components ├── Header.tsx ├── PostItem.tsx ├── PostList.tsx ├── PostListWithTag.tsx ├── SectionContainer.tsx ├── Tag.tsx └── TagList.tsx ├── files.json ├── index.ts ├── layouts └── SiteLayout.tsx ├── manifest.json ├── pages ├── AboutPage.tsx ├── BlogPage.tsx ├── HomePage.tsx ├── PostDetail.tsx ├── TagDetailPage.tsx └── TagListPage.tsx └── screenshots └── screenshot-1.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | .DS_Store 11 | 12 | # Diagnostic reports (https://nodejs.org/api/report.html) 13 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 14 | 15 | # Runtime data 16 | pids 17 | *.pid 18 | *.seed 19 | *.pid.lock 20 | 21 | # Directory for instrumented libs generated by jscoverage/JSCover 22 | lib-cov 23 | 24 | # Coverage directory used by tools like istanbul 25 | coverage 26 | *.lcov 27 | 28 | # nyc test coverage 29 | .nyc_output 30 | 31 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 32 | .grunt 33 | 34 | # Bower dependency directory (https://bower.io/) 35 | bower_components 36 | 37 | # node-waf configuration 38 | .lock-wscript 39 | 40 | # Compiled binary addons (https://nodejs.org/api/addons.html) 41 | build/Release 42 | 43 | # Dependency directories 44 | node_modules/ 45 | jspm_packages/ 46 | 47 | # Snowpack dependency directory (https://snowpack.dev/) 48 | web_modules/ 49 | 50 | # TypeScript cache 51 | *.tsbuildinfo 52 | 53 | # Optional npm cache directory 54 | .npm 55 | 56 | # Optional eslint cache 57 | .eslintcache 58 | 59 | # Optional stylelint cache 60 | .stylelintcache 61 | 62 | # Microbundle cache 63 | .rpt2_cache/ 64 | .rts2_cache_cjs/ 65 | .rts2_cache_es/ 66 | .rts2_cache_umd/ 67 | 68 | # Optional REPL history 69 | .node_repl_history 70 | 71 | # Output of 'npm pack' 72 | *.tgz 73 | 74 | # Yarn Integrity file 75 | .yarn-integrity 76 | 77 | # dotenv environment variable files 78 | .env 79 | .env.development.local 80 | .env.test.local 81 | .env.production.local 82 | .env.local 83 | 84 | # parcel-bundler cache (https://parceljs.org/) 85 | .cache 86 | .parcel-cache 87 | 88 | # Next.js build output 89 | .next 90 | out 91 | 92 | # Nuxt.js build / generate output 93 | .nuxt 94 | dist 95 | 96 | # Gatsby files 97 | .cache/ 98 | # Comment in the public line in if your project uses Gatsby and not Next.js 99 | # https://nextjs.org/blog/next-9-1#public-directory-support 100 | # public 101 | 102 | # vuepress build output 103 | .vuepress/dist 104 | 105 | # vuepress v2.x temp and cache directory 106 | .temp 107 | .cache 108 | 109 | # Docusaurus cache and generated files 110 | .docusaurus 111 | 112 | # Serverless directories 113 | .serverless/ 114 | 115 | # FuseBox cache 116 | .fusebox/ 117 | 118 | # DynamoDB Local files 119 | .dynamodb/ 120 | 121 | # TernJS port file 122 | .tern-port 123 | 124 | # Stores VSCode versions used for testing VSCode extensions 125 | .vscode-test 126 | 127 | # yarn v2 128 | .yarn/cache 129 | .yarn/unplugged 130 | .yarn/build-state.yml 131 | .yarn/install-state.gz 132 | .pnp.* 133 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 penx-dao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # themes -------------------------------------------------------------------------------- /themes/card/components/ClientOnly.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import React, { PropsWithChildren, useEffect, useState } from 'react' 4 | 5 | export function ClientOnly({ children }: PropsWithChildren) { 6 | // State / Props 7 | const [hasMounted, setHasMounted] = useState(false) 8 | 9 | // Hooks 10 | useEffect(() => { 11 | setHasMounted(true) 12 | }, []) 13 | 14 | // Render 15 | if (!hasMounted) return null 16 | 17 | return <>{children} 18 | } 19 | -------------------------------------------------------------------------------- /themes/card/components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react' 2 | import { Site } from '@penxio/types' 3 | import SocialIcon from './social-icons' 4 | 5 | interface Props { 6 | ModeToggle: () => ReactNode 7 | site: Site 8 | } 9 | 10 | export function Footer({ site, ModeToggle }: Props) { 11 | if (!site) return null 12 | const socials = site.socials 13 | return ( 14 | 46 | ) 47 | } 48 | -------------------------------------------------------------------------------- /themes/card/components/Header.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode, Suspense } from 'react' 2 | import { Profile } from '@/components/Profile/Profile' 3 | import { Airdrop } from '@/components/theme-ui/Airdrop' 4 | import { cn } from '@/lib/utils' 5 | import { Site } from '@penxio/types' 6 | import { Merienda } from 'next/font/google' 7 | import Link from 'next/link' 8 | 9 | const merienda = Merienda({ 10 | weight: ['400', '500', '600', '700'], 11 | subsets: ['latin'], 12 | display: 'swap', 13 | }) 14 | 15 | const headerNavLinks = [ 16 | { href: '/', title: 'Home' }, 17 | { href: '/posts', title: 'Blog' }, 18 | // { href: '/tags', title: 'Tags' }, 19 | { href: '/about', title: 'About' }, 20 | { href: '/membership', title: 'Membership', isMembership: true }, 21 | ] 22 | 23 | const headerNavLinksRight = [{ href: '/creator-fi', title: 'CreatorFi' }] 24 | 25 | interface Props { 26 | site: Site 27 | } 28 | 29 | export const Header = ({ site }: Props) => { 30 | return ( 31 |
32 |
33 | {headerNavLinks.map((link) => { 34 | if (link.href === '/membership' && !site.spaceId) { 35 | return null 36 | } 37 | return ( 38 | 47 | {link.title} 48 | 49 | ) 50 | })} 51 |
52 | 53 | 54 |
55 |
61 | {site.name} 62 |
63 |
64 | 65 | 66 |
67 |
68 | {headerNavLinksRight.map((link) => { 69 | if (link.href === '/creator-fi' && !site.spaceId) { 70 | return null 71 | } 72 | return ( 73 | 78 | {link.title} 79 | 80 | ) 81 | })} 82 |
83 | 84 |
85 | 86 |
87 | 88 |
89 |
90 | ) 91 | } 92 | -------------------------------------------------------------------------------- /themes/card/components/Image.tsx: -------------------------------------------------------------------------------- 1 | import NextImage, { ImageProps } from 'next/image' 2 | 3 | const basePath = process.env.BASE_PATH 4 | 5 | const Image = ({ src, ...rest }: ImageProps) => ( 6 | 7 | ) 8 | 9 | export default Image 10 | -------------------------------------------------------------------------------- /themes/card/components/Link.tsx: -------------------------------------------------------------------------------- 1 | import { AnchorHTMLAttributes } from 'react' 2 | import Link from 'next/link' 3 | import type { LinkProps } from 'next/link' 4 | 5 | const CustomLink = ({ 6 | href, 7 | ...rest 8 | }: LinkProps & AnchorHTMLAttributes) => { 9 | const isInternalLink = href && href.startsWith('/') 10 | const isAnchorLink = href && href.startsWith('#') 11 | 12 | const isProd = process.env.NODE_ENV === 'production' 13 | 14 | if (isInternalLink) { 15 | // if (isProd) href = href + '.html' 16 | return 17 | } 18 | 19 | if (isAnchorLink) { 20 | return 21 | } 22 | 23 | return ( 24 | 31 | ) 32 | } 33 | 34 | export default CustomLink 35 | -------------------------------------------------------------------------------- /themes/card/components/PageTitle.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react' 2 | import { cn } from '@/lib/utils' 3 | 4 | interface Props { 5 | children: ReactNode 6 | className?: string 7 | } 8 | 9 | export default function PageTitle({ children, className }: Props) { 10 | return ( 11 |

17 | {children} 18 |

19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /themes/card/components/Pagination.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable jsx-a11y/anchor-is-valid */ 2 | 'use client' 3 | 4 | import { usePathname } from 'next/navigation' 5 | import Link from './Link' 6 | 7 | interface PaginationProps { 8 | totalPages: number 9 | currentPage: number 10 | } 11 | 12 | export function Pagination({ totalPages, currentPage }: PaginationProps) { 13 | const pathname = usePathname()! 14 | const basePath = pathname.split('/')[1] 15 | const prevPage = currentPage - 1 > 0 16 | const nextPage = currentPage + 1 <= totalPages 17 | 18 | return ( 19 |
20 | 58 |
59 | ) 60 | } 61 | -------------------------------------------------------------------------------- /themes/card/components/PostItem.tsx: -------------------------------------------------------------------------------- 1 | import { formatDate } from '@/lib/utils' 2 | import { Post } from '@penxio/types' 3 | import Link from 'next/link' 4 | import Tag from './Tag' 5 | 6 | interface PostItemProps { 7 | post: Post 8 | } 9 | 10 | export function PostItem({ post }: PostItemProps) { 11 | const { slug, title } = post 12 | 13 | return ( 14 |
15 | 19 | {!!post?.image && ( 20 | 25 | )} 26 | 27 |
28 |
29 |
30 |
31 | {formatDate(post.updatedAt)} 32 |
33 |
34 | {post.postTags 35 | // ?.slice(0, 3) 36 | ?.map((item) => ( 37 | 38 | ))} 39 |
40 |
41 |

42 | 46 | {title} 47 | 48 |

49 |
50 | {/*
{summary}
*/} 51 |
52 |
53 | ) 54 | } 55 | -------------------------------------------------------------------------------- /themes/card/components/PostList.tsx: -------------------------------------------------------------------------------- 1 | import { Pagination } from '@/components/theme-ui/Pagination' 2 | import { Post } from '@penxio/types' 3 | import { PostItem } from './PostItem' 4 | 5 | interface PaginationProps { 6 | totalPages: number 7 | currentPage: number 8 | } 9 | interface PostListProps { 10 | posts: Post[] 11 | initialDisplayPosts?: Post[] 12 | pagination?: PaginationProps 13 | } 14 | 15 | export function PostList({ 16 | posts, 17 | initialDisplayPosts = [], 18 | pagination, 19 | }: PostListProps) { 20 | const displayPosts = 21 | initialDisplayPosts.length > 0 ? initialDisplayPosts : posts 22 | 23 | return ( 24 |
25 |
26 | {displayPosts.map((post) => { 27 | return 28 | })} 29 |
30 | {pagination && pagination.totalPages > 1 && ( 31 | 35 | )} 36 |
37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /themes/card/components/PostListWithTag.tsx: -------------------------------------------------------------------------------- 1 | import { PageTitle } from '@/components/theme-ui/PageTitle' 2 | import { Post, Tag } from '@penxio/types' 3 | import { PostList } from './PostList' 4 | import { TagList } from './TagList' 5 | 6 | interface PaginationProps { 7 | totalPages: number 8 | currentPage: number 9 | } 10 | interface PostListWithTagProps { 11 | posts: Post[] 12 | tags: Tag[] 13 | initialDisplayPosts?: Post[] 14 | pagination?: PaginationProps 15 | } 16 | 17 | export function PostListWithTag({ 18 | posts, 19 | tags = [], 20 | initialDisplayPosts = [], 21 | pagination, 22 | }: PostListWithTagProps) { 23 | const displayPosts = 24 | initialDisplayPosts.length > 0 ? initialDisplayPosts : posts 25 | 26 | return ( 27 |
28 | Tags 29 | 30 |
31 | 32 |
33 |
34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /themes/card/components/PostTypeNav.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { cn } from '@/lib/utils' 4 | import Link from 'next/link' 5 | import { useSearchParams } from 'next/navigation' 6 | 7 | interface Props { 8 | className?: string 9 | } 10 | 11 | export const PostTypeNav = ({ className }: Props) => { 12 | const param = useSearchParams()! 13 | const type = param.get('type') 14 | 15 | return ( 16 |
22 | 23 | All 24 | 25 | 29 | Articles 30 | 31 | 35 | Notes 36 | 37 | 41 | Photos 42 | 43 |
44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /themes/card/components/SectionContainer.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react' 2 | import { cn } from '@/lib/utils' 3 | 4 | interface Props { 5 | children: ReactNode 6 | className?: string 7 | } 8 | 9 | export default function SectionContainer({ children, className }: Props) { 10 | return ( 11 |
17 | {children} 18 |
19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /themes/card/components/Tag.tsx: -------------------------------------------------------------------------------- 1 | import { slug } from 'github-slugger' 2 | import Link from 'next/link' 3 | import { PostTag } from '@penxio/types' 4 | import { cn } from '@/lib/utils' 5 | 6 | interface Props { 7 | postTag: PostTag 8 | className?: string 9 | } 10 | 11 | const Tag = ({ postTag, className }: Props) => { 12 | return ( 13 | 20 | {postTag.tag.name.split(' ').join('-')} 21 | 22 | ) 23 | } 24 | 25 | export default Tag 26 | -------------------------------------------------------------------------------- /themes/card/components/TagList.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { Tag } from '@penxio/types' 4 | import { slug } from 'github-slugger' 5 | import Link from 'next/link' 6 | import { usePathname } from 'next/navigation' 7 | 8 | interface PostListWithTagProps { 9 | tags: Tag[] 10 | } 11 | 12 | export function TagList({ tags = [] }: PostListWithTagProps) { 13 | const pathname = usePathname()! 14 | 15 | return ( 16 |
    17 | {tags.map((t) => { 18 | return ( 19 |
  • 20 | {decodeURI(pathname.split('/tags/')[1]) === slug(t.name) ? ( 21 |

    #{`${t.name}`}

    22 | ) : ( 23 | 28 | #{`${t.name}`} 29 | 30 | )} 31 |
  • 32 | ) 33 | })} 34 |
35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /themes/card/components/social-icons/icons.tsx: -------------------------------------------------------------------------------- 1 | import { SVGProps } from 'react' 2 | 3 | // Icons taken from: https://simpleicons.org/ 4 | // To add a new icon, add a new function here and add it to components in social-icons/index.tsx 5 | 6 | export function Facebook(svgProps: SVGProps) { 7 | return ( 8 | 9 | Facebook 10 | 11 | 12 | ) 13 | } 14 | 15 | export function Github(svgProps: SVGProps) { 16 | return ( 17 | 18 | GitHub 19 | 20 | 21 | ) 22 | } 23 | 24 | export function Linkedin(svgProps: SVGProps) { 25 | return ( 26 | 27 | Linkedin 28 | 29 | 30 | ) 31 | } 32 | 33 | export function Mail(svgProps: SVGProps) { 34 | return ( 35 | 36 | Mail 37 | 38 | 39 | 40 | ) 41 | } 42 | 43 | export function Twitter(svgProps: SVGProps) { 44 | return ( 45 | 46 | Twitter 47 | 48 | 49 | ) 50 | } 51 | 52 | export function X(svgProps: SVGProps) { 53 | return ( 54 | 55 | X 56 | 57 | 58 | ) 59 | } 60 | 61 | export function Youtube(svgProps: SVGProps) { 62 | return ( 63 | 64 | Youtube 65 | 66 | 67 | ) 68 | } 69 | 70 | export function Mastodon(svgProps: SVGProps) { 71 | return ( 72 | 73 | Mastodon 74 | 75 | 76 | ) 77 | } 78 | 79 | export function Threads(svgProps: SVGProps) { 80 | return ( 81 | 82 | Threads 83 | 84 | 85 | ) 86 | } 87 | 88 | export function Instagram(svgProps: SVGProps) { 89 | return ( 90 | 91 | Instagram 92 | 93 | 94 | ) 95 | } 96 | -------------------------------------------------------------------------------- /themes/card/components/social-icons/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Facebook, 3 | Github, 4 | Instagram, 5 | Linkedin, 6 | Mail, 7 | Mastodon, 8 | Threads, 9 | Twitter, 10 | X, 11 | Youtube, 12 | } from './icons' 13 | 14 | const components = { 15 | mail: Mail, 16 | github: Github, 17 | facebook: Facebook, 18 | youtube: Youtube, 19 | linkedin: Linkedin, 20 | twitter: Twitter, 21 | x: X, 22 | mastodon: Mastodon, 23 | threads: Threads, 24 | instagram: Instagram, 25 | } 26 | 27 | type SocialIconProps = { 28 | kind: keyof typeof components 29 | href: string | undefined 30 | size?: number 31 | } 32 | 33 | const SocialIcon = ({ kind, href, size = 8 }: SocialIconProps) => { 34 | if ( 35 | !href || 36 | (kind === 'mail' && 37 | !/^mailto:[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(href)) 38 | ) 39 | return null 40 | 41 | const SocialSvg = components[kind] 42 | 43 | return ( 44 |
50 | {kind} 51 | 54 | 55 | ) 56 | } 57 | 58 | export default SocialIcon 59 | -------------------------------------------------------------------------------- /themes/card/externals/AboutPage.tsx: -------------------------------------------------------------------------------- 1 | import { Site } from '@penxio/types' 2 | import Image from '../components/Image' 3 | import PageTitle from '../components/PageTitle' 4 | 5 | interface Props { 6 | site: Site 7 | ContentRender: (props: { content: any[]; className?: string }) => JSX.Element 8 | } 9 | 10 | export function AboutPage({ site, ContentRender }: Props) { 11 | return ( 12 | <> 13 |
14 | About 15 |
16 |
17 | {site.logo && ( 18 | avatar 25 | )} 26 |

27 | {site.name} 28 |

29 |
{site.description}
30 | {/*
31 | 32 | 33 | 34 | 35 |
*/} 36 |
37 |
38 | 39 |
40 |
41 |
42 | 43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /themes/card/externals/BlogPage.tsx: -------------------------------------------------------------------------------- 1 | import { Post } from '@penxio/types' 2 | import PageTitle from '../components/PageTitle' 3 | import { PostList } from '../components/PostList' 4 | 5 | interface Props { 6 | posts: Post[] 7 | initialDisplayPosts: Post[] 8 | pagination: { 9 | currentPage: number 10 | totalPages: number 11 | } 12 | } 13 | 14 | export function BlogPage({ 15 | posts = [], 16 | pagination, 17 | initialDisplayPosts, 18 | }: Props) { 19 | return ( 20 |
21 | Blog 22 | 27 |
28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /themes/card/externals/HomePage.tsx: -------------------------------------------------------------------------------- 1 | import { Post, Site } from '@penxio/types' 2 | import Image from '../components/Image' 3 | import Link from '../components/Link' 4 | import { PostItem } from '../components/PostItem' 5 | 6 | const POSTS_PER_PAGE = Number(process.env.NEXT_PUBLIC_POSTS_PER_PAGE || 10) 7 | 8 | interface Props { 9 | site: Site 10 | posts: Post[] 11 | ContentRender: (props: { content: any[]; className?: string }) => JSX.Element 12 | } 13 | 14 | export function HomePage({ posts = [], site, ContentRender }: Props) { 15 | return ( 16 |
17 |
18 |
19 | {site.logo && ( 20 | avatar 27 | )} 28 |

29 | {site.name} 30 |

31 |
{site.description}
32 |
33 |
34 | 35 |
36 |
37 | 38 |
39 |
40 |

41 | Latest 42 |

43 | 44 | {posts.length > POSTS_PER_PAGE && ( 45 | 49 | All posts → 50 | 51 | )} 52 |
53 | 54 |
55 | {!posts.length && 'No posts found.'} 56 | {posts.slice(0, POSTS_PER_PAGE).map((post) => { 57 | return 58 | })} 59 |
60 |
61 |
62 | ) 63 | } 64 | -------------------------------------------------------------------------------- /themes/card/externals/PostDetail.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react' 2 | import { formatDate } from '@/lib/utils' 3 | import { Post } from '@penxio/types' 4 | import { ExternalLink } from 'lucide-react' 5 | import Image from '../components/Image' 6 | import Link from '../components/Link' 7 | import PageTitle from '../components/PageTitle' 8 | import SectionContainer from '../components/SectionContainer' 9 | 10 | interface LayoutProps { 11 | post: Post 12 | children: ReactNode 13 | className?: string 14 | next?: Post 15 | prev?: Post 16 | PostActions?: (props: { post: Post; className?: string }) => JSX.Element 17 | ContentRender?: (props: { content: any; className?: string }) => JSX.Element 18 | } 19 | 20 | export function PostDetail({ 21 | post, 22 | PostActions, 23 | className, 24 | ContentRender, 25 | next, 26 | prev, 27 | }: LayoutProps) { 28 | return ( 29 | 30 |
31 |
32 | {post.title} 33 |
34 |
35 |
36 | 37 |
38 |
·
39 |
40 | {post.readingTime.text} 41 |
42 |
43 |
44 | 45 | {PostActions && } 46 |
47 | 48 | {!!post.image && ( 49 | 56 | )} 57 | 58 |
59 |
60 | {ContentRender && } 61 |
62 | 63 | {post.cid && ( 64 |
65 | IPFS CID: 66 | {post.cid} 67 | 72 | 73 | 74 |
75 | )} 76 | 77 |
78 |
79 | {prev && prev?.slug && ( 80 |
81 | 86 | ← {prev.title} 87 | 88 |
89 | )} 90 | {next && next?.slug && ( 91 |
92 | 97 | {next.title} → 98 | 99 |
100 | )} 101 |
102 |
103 |
104 |
105 |
106 | ) 107 | } 108 | -------------------------------------------------------------------------------- /themes/card/externals/SiteLayout.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react' 2 | import { Footer } from '../components/Footer' 3 | import { Header } from '../components/Header' 4 | import SectionContainer from '../components/SectionContainer' 5 | 6 | interface Props { 7 | site: any 8 | Logo: () => ReactNode 9 | ModeToggle: () => ReactNode 10 | MobileNav: () => ReactNode 11 | ConnectButton: () => ReactNode 12 | Airdrop: () => ReactNode 13 | children: ReactNode 14 | } 15 | 16 | export function SiteLayout({ 17 | children, 18 | site, 19 | Logo, 20 | ModeToggle, 21 | MobileNav, 22 | ConnectButton, 23 | Airdrop, 24 | }: Props) { 25 | return ( 26 | 27 |
35 |
{children}
36 |