[
  {
    "path": ".gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pnp\n.pnp.js\n\n# testing\n/coverage\n\n# next.js\n/.next/\n/out/\n\n# production\n/build\n\n# misc\n.DS_Store\n\n# debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# local env files\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n.env"
  },
  {
    "path": "README.md",
    "content": "# Simple React Blog\n![simple-nextjs-blog](https://github.com/cosmicjs/simple-react-blog/assets/1950722/39a1604a-81a3-4d7d-8276-7650ca626ae1)\n\n## NOTE: this repo is now a mirror of the [Simple Next.js Blog](https://github.com/cosmicjs/simple-nextjs-blog)\n\n\n### [View Demo](https://cosmic-nextjs-blog.vercel.app/)\n\n### React + Next.js + Cosmic\nThis blog uses Next.js to create a React blog.  It uses Next.js 13 and the new `app` organization structure which takes advantage of [React Server Components](https://nextjs.org/docs/getting-started/react-essentials#server-components). It connects to the Cosmic API via the [Cosmic JavaScript SDK](https://www.npmjs.com/package/@cosmicjs/sdk).\n\n## Getting Started\n1. Log in to Cosmic and install the [Simple Next.js Blog template](https://www.cosmicjs.com/marketplace/templates/simple-nextjs-blog).\n2. Run the following commands to install the code locally.\n```\ngit clone https://github.com/cosmicjs/simple-nextjs-blog\ncd simple-nextjs-blog\n```\n#### Environment Variables\n\n1. Create an `.env.local` file to gain API access to your Cosmic Bucket. To do this, run:\n```\ncp .env.example .env.local\n```\n2. Find your API access keys at <em>Bucket Settings &gt; API Access</em> after logging into [your Cosmic dashboard](https://app.cosmicjs.com/login) and add them to the `.env.local` file. It should look something like this:\n```\nNEXT_PUBLIC_COSMIC_BUCKET_SLUG=your-bucket-slug\nNEXT_PUBLIC_COSMIC_READ_KEY=your-bucket-read-key\n```\n\n#### Run in development\nInstall all dependencies and run in development mode.\n```\nyarn\nyarn dev\n```\nOpen [http://localhost:3000](http://localhost:3000).\n\n## Deploy to Vercel\n\n<p>Use the following button to deploy to <a href=\"https://vercel.com/\" rel=\"noopener noreferrer\" target=\"_blank\">Vercel</a>. You will need to add API accesss keys as environment variables. Find these in <em>Bucket Settings &gt; API Access</em>.</p>\n<p>\n<a href=\"https://vercel.com/import/git?c=1&s=https://github.com/cosmicjs/simple-nextjs-blog&env=NEXT_PUBLIC_COSMIC_BUCKET_SLUG,NEXT_PUBLIC_COSMIC_READ_KEY\" rel=\"noopener noreferrer\" target=\"_blank\"><img src=\"https://cdn.cosmicjs.com/d3f0d5e0-c064-11ea-9a05-6f8a16b0b14c-deploy-to-vercel.svg\" style=\"width: 100px;\" class=\"fr-fic fr-dib fr-fil\"></a>\n</p>\n"
  },
  {
    "path": "app/author/[slug]/page.tsx",
    "content": "import React from 'react';\nimport PostCard from '../../../components/PostCard';\nimport { getAuthor, getAuthorPosts } from '../../../lib/cosmic';\n\nexport async function generateMetadata({ params }: { params: { id: string; slug: string } }) {\n  const author = await getAuthor({ params });\n  return {\n    title: `${author.title} posts | Simple React Blog`,\n  };\n}\n\nexport default async ({ params }: { params: { id: string; slug: string } }) => {\n  const author = await getAuthor({ params });\n  const posts = await getAuthorPosts({ authorId: author.id });\n\n  return (\n    <main className='mx-auto w-full max-w-3xl flex-col px-4 lg:px-0'>\n      <h1 className='my-4 text-4xl font-bold leading-tight tracking-tight text-zinc-700 dark:text-zinc-300'>Posts by {author.title}</h1>\n      <div className='space-y-16'>\n        {!posts && 'You must add at least one Post to your Bucket'}\n        {posts &&\n          posts.map((post) => {\n            return (\n              <div key={post.id}>\n                <PostCard post={post} />\n              </div>\n            );\n          })}\n      </div>\n    </main>\n  );\n};\n"
  },
  {
    "path": "app/layout.tsx",
    "content": "import React from 'react';\nimport '../styles/globals.css';\nimport { getGlobalData } from '../lib/cosmic';\nimport Generator from 'next/font/local';\nimport Banner from '../components/Banner';\nimport Header from '../components/Header';\nimport Footer from '../components/Footer';\n\nconst sans = Generator({\n  src: '../fonts/Generator-Variable.ttf',\n  variable: '--font-sans',\n});\n\nexport async function generateMetadata() {\n  const siteData = await getGlobalData();\n  return {\n    title: siteData.metadata.site_title,\n    description: siteData.metadata.site_tag,\n  };\n}\n\nexport default async function RootLayout({ children }: { children: React.ReactNode }) {\n  const siteData = await getGlobalData();\n\n  return (\n    <html lang='en' className={`${sans.variable} font-sans`}>\n      <body className='bg-white dark:bg-zinc-950'>\n        <Banner />\n        <Header name={siteData} />\n        {children}\n        <Footer />\n      </body>\n    </html>\n  );\n}\n"
  },
  {
    "path": "app/page.tsx",
    "content": "import React from 'react';\nimport PostCard from '../components/PostCard';\nimport { getAllPosts } from '../lib/cosmic';\n\nexport default async function Page(): Promise<JSX.Element> {\n  const posts = await getAllPosts();\n\n  return (\n    <main className='mx-auto w-full max-w-3xl flex-col space-y-16 px-4 lg:px-0'>\n      {!posts && 'You must add at least one Post to your Bucket'}\n      {posts &&\n        posts.map((post) => {\n          return (\n            <div key={post.id}>\n              <PostCard post={post} />\n            </div>\n          );\n        })}\n    </main>\n  );\n}\n"
  },
  {
    "path": "app/posts/[slug]/page.tsx",
    "content": "import React from 'react';\nimport Link from 'next/link';\nimport Image from 'next/image';\nimport ArrowLeft from '../../../components/ArrowLeft';\nimport { getPost } from '../../../lib/cosmic';\nimport { getRelatedPosts } from '../../../lib/cosmic';\nimport SuggestedPostCard from '../../../components/SuggestedPostCard';\nimport Tag from '../../../components/Tag';\nimport AuthorAvatar from '../../../components/AuthorAvatar';\nimport AuthorAttribution from '../../../components/AuthorAttribution';\n\nexport async function generateMetadata({ params }: { params: { slug: string } }) {\n  const post = await getPost({ params });\n  return {\n    title: `${post.title} | Simple Next 13 Blog`,\n  };\n}\n\nexport default async ({ params }: { params: { slug: string } }) => {\n  const post = await getPost({ params });\n  const suggestedPosts = await getRelatedPosts({ params });\n\n  return (\n    <>\n      {post && post.metadata.hero?.imgix_url && (\n        <Image className='mb-5 h-auto w-full bg-no-repeat object-cover object-center' src={`${post.metadata.hero?.imgix_url}?w=1400&auto=format`} width={2000} height={640} priority alt={post.title} />\n      )}\n      <main className='mx-auto flex flex-col justify-center'>\n        <div className='mx-auto flex w-full flex-col items-start justify-center px-4 md:flex-row'>\n          <div className='mt-4 flex justify-start pb-4 md:justify-center md:pb-0 md:pr-20'>\n            <Link href='/' className='rounded-full border border-zinc-100 bg-white p-2 text-zinc-700 shadow-md dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-300'>\n              <ArrowLeft className='h-4 w-4' />\n            </Link>\n          </div>\n          <div className='mr-20 flex w-full max-w-3xl flex-col justify-start md:w-3/4'>\n            <h2>\n              {!post && <div className='text-center'>Post Not found</div>}\n              {post && <Link href={`/posts/${post.slug}`}>{post.title}</Link>}\n            </h2>\n            {post && (\n              <>\n                <div className='flex flex-col justify-between space-y-4 pb-8 md:flex-row md:space-y-0'>\n                  <div className='flex items-center space-x-2 text-zinc-500 dark:text-zinc-400 md:space-y-0'>\n                    <AuthorAvatar post={post} />\n                    <AuthorAttribution post={post} />\n                  </div>\n                  <div className='flex select-none justify-start space-x-2 md:justify-end'>\n                    {post.metadata.categories && post.metadata.categories.map((category) => <Tag key={category.title}>{category.title}</Tag>)}\n                  </div>\n                </div>\n                <hr className='w-full border-t border-zinc-300 pb-8 dark:border-zinc-700' />\n                <div dangerouslySetInnerHTML={{ __html: post.metadata.content ?? '' }}></div>\n              </>\n            )}\n            <div className='mx-auto mt-8 w-full'>\n              <hr className='w-full border-t border-zinc-300 pb-8 dark:border-zinc-700' />\n              {suggestedPosts && (\n                <div className='flex w-full flex-col px-4 lg:px-0'>\n                  <h3 className='pb-3 text-xl font-semibold text-zinc-800 dark:text-zinc-200'>Suggested Posts</h3>\n                  <div className='flex flex-col space-x-0 space-y-4 md:flex-row md:space-x-4 md:space-y-0'>\n                    {suggestedPosts\n                      // .filter((nextPost) => nextPost?.id !== post?.id)\n                      .slice(0, 2)\n                      .map((post) => {\n                        return <SuggestedPostCard key={post.id} post={post} />;\n                      })}\n                  </div>\n                </div>\n              )}\n            </div>\n          </div>\n        </div>\n      </main>\n    </>\n  );\n};\n"
  },
  {
    "path": "components/ArrowLeft.tsx",
    "content": "export default function ArrowRight({ className }: { className?: string }): JSX.Element {\n  return (\n    <svg height={20} width={20} viewBox='0 0 30 30' fill='none' xmlns='http://www.w3.org/2000/svg'>\n      <path\n        fillRule='evenodd'\n        clipRule='evenodd'\n        d='M13.7071 6.29291C14.0976 6.68343 14.0976 7.31658 13.7071 7.7071L7.41422 14H25C25.5522 14 26 14.4477 26 15C26 15.5523 25.5522 16 25 16H7.41422L13.7071 22.2928C14.0976 22.6834 14.0976 23.3166 13.7071 23.7072C13.3166 24.0976 12.6834 24.0976 12.2929 23.7072L4.2929 15.7071C3.90236 15.3166 3.90236 14.6834 4.2929 14.2929L12.2929 6.29291C12.6834 5.90236 13.3166 5.90236 13.7071 6.29291Z'\n        fill='currentColor'\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "components/ArrowRight.tsx",
    "content": "export default function ArrowRight({ className }: { className?: string }): JSX.Element {\n  return (\n    <svg width='22' height='18' viewBox='0 0 22 18' fill='none' xmlns='http://www.w3.org/2000/svg' className={className}>\n      <path\n        fillRule='evenodd'\n        clipRule='evenodd'\n        d='M12.2929 0.292905C12.6834 -0.097635 13.3166 -0.097635 13.7071 0.292905L21.7072 8.2929C22.0976 8.68342 22.0976 9.31658 21.7072 9.7071L13.7071 17.7072C13.3166 18.0976 12.6834 18.0976 12.2929 17.7072C11.9024 17.3166 11.9024 16.6834 12.2929 16.2928L18.5858 10H1C0.44772 10 0 9.55228 0 9C0 8.44772 0.44772 8 1 8H18.5858L12.2929 1.7071C11.9024 1.31658 11.9024 0.683425 12.2929 0.292905Z'\n        fill='currentColor'\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "components/AuthorAttribution.tsx",
    "content": "import { Post } from '../lib/types';\nimport helpers from '../helpers';\n\nexport default function AuthorAttribution({ post }: { post: Post }): JSX.Element {\n  return (\n    <div className='flex space-x-1'>\n      <span>by</span>\n      <a href={`/author/${post.metadata.author?.slug}`} className='font-semibold text-green-600 dark:text-green-200'>\n        {post.metadata.author?.title}\n      </a>\n      <span>on {helpers.stringToFriendlyDate(post.metadata.published_date)}</span>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/AuthorAvatar.tsx",
    "content": "import Image from 'next/image';\nimport Link from 'next/link';\nimport { Post } from '../lib/types';\n\nexport default function AuthorAvatar({ post }: { post: Post }): JSX.Element {\n  return (\n    <Link href={`/author/${post.metadata.author?.slug}`}>\n      <Image className='h-8 w-8 rounded-full' src={`${post.metadata.author?.metadata.image?.imgix_url}?w=100&auto=format`} width={32} height={32} alt={post.title}></Image>\n    </Link>\n  );\n}\n"
  },
  {
    "path": "components/Banner.tsx",
    "content": "import React from 'react';\n\nexport default function Banner(): JSX.Element {\n  return (\n    <div className='flex justify-center space-x-1 bg-zinc-100 p-2 text-xs dark:bg-zinc-900 md:text-sm lg:text-base'>\n      <span className='text-zinc-800 dark:text-zinc-200'>The source code for this blog is</span>\n      <a href='https://github.com/cosmicjs/simple-nextjs-blog' target='_parent' className='text-green-500 underline dark:text-green-300'>\n        available on GitHub\n      </a>\n      .\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/CosmicLogo.tsx",
    "content": "export default function CosmicLogo(): JSX.Element {\n  return (\n    <svg xmlns='http://www.w3.org/2000/svg' width='26' height='26' viewBox='0 0 500 500' fill='none'>\n      <path\n        d='M198.942 172.131C210.987 161.524 222.106 150.916 234.151 141.273C224.885 134.523 216.547 128.737 208.208 122.951C198.016 131.63 187.824 141.273 177.632 150.916C187.824 154.773 195.236 162.488 198.942 172.131Z'\n        fill='#3DBFF5'\n      ></path>\n      <path\n        d='M169.345 221.419C191.641 221.479 209.763 203.179 209.822 180.545C209.88 157.91 191.853 139.512 169.557 139.452C147.261 139.392 129.139 157.693 129.081 180.327C129.022 202.962 147.049 221.359 169.345 221.419Z'\n        fill='#3DBFF5'\n      ></path>\n      <path\n        d='M205.035 408.818C147.774 440.607 98.2764 448.555 75.9542 426.699C44.8972 395.903 73.0426 315.436 140.98 233.976C130.304 230.995 121.569 223.048 116.717 213.114C17.7223 326.364 -26.9222 440.607 16.7517 483.324C52.6614 519.087 139.039 495.245 234.151 431.666C224.446 424.712 214.74 416.765 205.035 408.818Z'\n        fill='#3DBFF5'\n      ></path>\n      <path\n        d='M270.348 137.373C273.251 126.476 280.993 117.56 290.67 112.606C176.481 16.5085 60.3567 -26.0914 16.8101 16.5085C-18.9949 52.1736 4.22997 136.383 69.0661 229.508C76.8077 219.601 84.5493 210.685 92.2909 200.778C60.3567 144.308 51.6474 95.7642 73.9046 72.9782C105.839 42.2666 187.126 70.9968 270.348 137.373Z'\n        fill='#3DBFF5'\n      ></path>\n      <path\n        d='M352.707 163.934C348.939 173.869 340.461 181.818 331.041 185.792C345.171 200.695 358.358 214.605 369.662 229.508C376.256 220.566 382.85 211.624 387.56 202.682C377.198 189.766 364.952 176.85 352.707 163.934Z'\n        fill='#3DBFF5'\n      ></path>\n      <path\n        d='M322.966 204.918C345.499 204.918 363.337 186.925 363.337 163.934C363.337 140.944 345.499 122.951 322.966 122.951C300.434 122.951 282.595 140.944 282.595 163.934C282.595 185.926 300.434 204.918 322.966 204.918Z'\n        fill='#3DBFF5'\n      ></path>\n      <path\n        d='M433.365 73.5376C464.549 104.299 436.288 184.677 368.073 266.047C378.793 269.023 387.563 276.962 392.436 286.885C490.86 173.761 535.687 58.6529 491.835 15.9834C455.778 -18.7476 369.048 5.06793 274.521 68.576C284.266 75.5222 294.011 83.4607 303.756 91.3993C361.252 59.6452 410.951 51.7067 433.365 73.5376Z'\n        fill='#3DBFF5'\n      ></path>\n      <path\n        d='M316.969 319.672C303.127 333.522 288.363 346.382 274.521 358.254C283.749 365.178 292.054 371.114 301.282 377.049C314.2 366.168 326.196 354.296 339.115 341.436C328.964 337.479 321.582 329.565 316.969 319.672Z'\n        fill='#3DBFF5'\n      ></path>\n      <path\n        d='M347.411 278.575C325.114 278.512 306.99 296.81 306.929 319.445C306.868 342.079 324.893 360.479 347.189 360.541C369.485 360.604 387.609 342.306 387.671 319.672C387.732 297.037 369.707 278.637 347.411 278.575Z'\n        fill='#3DBFF5'\n      ></path>\n      <path\n        d='M177.631 314.208C163.502 299.305 150.314 285.395 139.01 270.492C132.416 279.434 125.822 288.376 121.112 297.317C132.416 310.234 143.72 323.15 155.966 336.066C159.734 326.13 167.27 318.182 177.631 314.208Z'\n        fill='#3DBFF5'\n      ></path>\n      <path\n        d='M439.835 270.492C432.12 280.363 424.404 289.248 416.688 299.119C448.516 355.387 457.196 402.771 434.048 425.476C402.22 456.077 321.203 427.45 238.257 362.297C235.363 373.156 227.647 382.041 218.002 386.976C332.777 482.731 448.516 526.166 491.918 483.718C527.604 447.193 504.456 362.297 439.835 270.492Z'\n        fill='#3DBFF5'\n      ></path>\n      <path\n        d='M226.182 336.281C226.24 313.647 208.213 295.25 185.917 295.19C163.62 295.13 145.499 313.431 145.441 336.065C145.382 358.7 163.41 377.097 185.706 377.157C208.002 377.216 226.124 358.916 226.182 336.281Z'\n        fill='#3DBFF5'\n      ></path>\n    </svg>\n  );\n}\n"
  },
  {
    "path": "components/Footer.tsx",
    "content": "import React from 'react';\nimport CosmicLogo from './CosmicLogo';\n\nexport default function Footer(): JSX.Element {\n  return (\n    <footer className='mx-auto mt-8 flex w-full max-w-3xl items-center justify-between px-4 py-4 text-xs md:text-sm lg:px-0 lg:text-base'>\n      <a href='https://www.cosmicjs.com' target='_blank' className='no-underline'>\n        <div className='flex items-center space-x-2'>\n          <CosmicLogo />\n          <span className='text-zinc-700 dark:text-zinc-300'>Proudly powered by Cosmic</span>\n        </div>\n      </a>\n      <div className='text-zinc-700 dark:text-zinc-300'>&copy;&nbsp;&nbsp;{new Date().getFullYear()} Cosmic</div>\n    </footer>\n  );\n}\n"
  },
  {
    "path": "components/Header.tsx",
    "content": "import React from 'react';\nimport SiteLogo from './SiteLogo';\nimport { GlobalData } from '../lib/types';\n\nexport default function Header({ name }: { name: GlobalData }): JSX.Element {\n  return (\n    <header className='sticky top-0 z-10 mx-auto bg-white/75 backdrop-blur-lg dark:bg-zinc-950/75'>\n      <SiteLogo siteData={name} />\n    </header>\n  );\n}\n"
  },
  {
    "path": "components/OBMLogo.tsx",
    "content": "export default function OBMLogo({ className }: { className?: string }): JSX.Element {\n  return (\n    <svg width='512' height='512' viewBox='0 0 512 512' fill='none' xmlns='http://www.w3.org/2000/svg' className={className}>\n      <path\n        fillRule='evenodd'\n        clipRule='evenodd'\n        d='M484.843 370.872C440.306 408.392 382.793 431 320 431C178.615 431 64 316.385 64 175C64 133.694 73.7826 94.6735 91.1574 60.128C135.694 22.6079 193.207 0 256 0C397.385 0 512 114.615 512 256C512 297.306 502.217 336.327 484.843 370.872ZM36.4558 124.258C13.3108 162.745 0 207.818 0 256C0 397.385 114.615 512 256 512C325.572 512 388.662 484.247 434.803 439.209C399.63 454.513 360.806 463 320 463C160.942 463 32 334.058 32 175C32 157.687 33.5277 140.73 36.4558 124.258Z'\n        fill='url(#paint0_linear_1_6)'\n      />\n      <defs>\n        <linearGradient id='paint0_linear_1_6' x1='256' y1='0' x2='256' y2='512' gradientUnits='userSpaceOnUse'>\n          <stop stopColor='#06B6D4' />\n          <stop offset='1' stopColor='#155E75' />\n        </linearGradient>\n      </defs>\n    </svg>\n  );\n}\n"
  },
  {
    "path": "components/PostCard.tsx",
    "content": "import React from 'react';\nimport Link from 'next/link';\nimport Image from 'next/image';\nimport helpers from '../helpers';\nimport ArrowRight from './ArrowRight';\nimport Tag from './Tag';\nimport { Post } from '../lib/types';\nimport AuthorAttribution from './AuthorAttribution';\nimport AuthorAvatar from './AuthorAvatar';\n\nexport default function PostCard({ post }: { post: Post }) {\n  return (\n    <div>\n      {post.metadata.hero?.imgix_url && (\n        <Link href={`/posts/${post.slug}`}>\n          <Image\n            className='mb-5 h-auto w-full rounded-xl bg-no-repeat object-cover object-center transition-transform duration-200 ease-out hover:scale-[1.02]'\n            src={`${post.metadata.hero?.imgix_url}?w=1400&h=340&auto=format`}\n            width={1400}\n            height={340}\n            priority\n            alt={post.title}\n          />\n        </Link>\n      )}\n      <h2 className='pb-3 text-xl font-semibold text-zinc-800 dark:text-zinc-200'>\n        <Link href={`/posts/${post.slug}`}>{post.title}</Link>\n      </h2>\n      <div className='flex flex-col justify-between space-y-4 md:flex-row md:space-y-0'>\n        <div className='flex items-center space-x-2 text-zinc-500 dark:text-zinc-400 md:space-y-0'>\n          <AuthorAvatar post={post} />\n          <AuthorAttribution post={post} />\n        </div>\n        <div className='flex select-none justify-start space-x-2 font-bold md:hidden md:justify-end'>\n          {post.metadata.categories && post.metadata.categories.map((category) => <Tag key={category.title}>{category.title}</Tag>)}\n        </div>\n      </div>\n      <div className='py-6 text-zinc-500 dark:text-zinc-300' dangerouslySetInnerHTML={{ __html: post.metadata.teaser ?? '' }} />\n      <div className='flex items-center justify-between font-semibold text-green-600 dark:text-green-200'>\n        <Link href={`/posts/${post.slug}`}>\n          <div className='flex items-center space-x-2'>\n            <span>Read more</span>\n            <ArrowRight className='h-4 w-4 text-inherit' />\n          </div>\n        </Link>\n        <div className='hidden select-none justify-end space-x-2 md:flex '>\n          {post.metadata.categories && post.metadata.categories.map((category) => <Tag key={category.title}>{category.title}</Tag>)}\n        </div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/SiteLogo.tsx",
    "content": "import Link from 'next/link';\nimport OBMLogo from './OBMLogo';\nimport { GlobalData } from '../lib/types';\n\nexport default function SiteLogo({ siteData }: { siteData: GlobalData }): JSX.Element {\n  return (\n    <div className='mx-auto flex w-full max-w-3xl flex-col items-baseline justify-between px-4 py-4 tracking-tighter md:flex-row lg:px-0'>\n      <h1 className='flex space-x-2'>\n        <OBMLogo className='h-8 w-8' />\n        <Link href='/' className='bg-gradient-to-r from-cyan-700 to-teal-600 bg-clip-text text-4xl font-bold text-transparent dark:from-cyan-300 dark:to-teal-200'>\n          {siteData.metadata.site_title}\n        </Link>\n      </h1>\n      <span className='hidden text-lg tracking-wide text-zinc-500 dark:text-zinc-200 md:flex top-[-7px] relative'>{siteData.metadata.site_tag}</span>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/SuggestedPostCard.tsx",
    "content": "import React from 'react';\nimport Link from 'next/link';\nimport Image from 'next/image';\nimport helpers from '../helpers';\nimport { Post } from '../lib/types';\n\nexport default function PostCard({ post }: { post: Post }) {\n  return (\n    <div>\n      {post.metadata.hero?.imgix_url && (\n        <Link href={`/posts/${post.slug}`}>\n          <Image\n            className='mb-5 rounded-xl bg-no-repeat object-cover object-center transition-transform duration-200 ease-out hover:scale-[1.02]'\n            src={`${post.metadata.hero?.imgix_url}?w=1400&auto=format`}\n            width={1400}\n            height={340}\n            alt={post.title}\n          />\n        </Link>\n      )}\n      <h2 className='pb-3 text-xl font-semibold text-zinc-800 dark:text-zinc-200'>\n        <Link href={`/posts/${post.slug}`}>{post.title}</Link>\n      </h2>\n      <div className='flex items-center space-x-2 text-zinc-500 dark:text-zinc-400'>\n        <Link href={`/author/${post.metadata.author?.slug}`}>\n          <Image className='h-8 w-8 rounded-full' src={`${post.metadata.author?.metadata.image?.imgix_url}?w=100&auto=format`} width={32} height={32} alt={post.title}></Image>\n        </Link>\n        <div>\n          <span>\n            by{' '}\n            <a href={`/author/${post.metadata.author?.slug}`} className='font-semibold text-green-600 dark:text-green-200'>\n              {post.metadata.author?.title}\n            </a>{' '}\n            on {helpers.stringToFriendlyDate(post.metadata.published_date)}\n          </span>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/Tag.tsx",
    "content": "import React from 'react';\n\nexport default function Tag({ children }: { children: React.ReactNode }): JSX.Element {\n  return (\n    <div className='flex rounded-full border border-zinc-200 bg-zinc-50 px-3 py-2 dark:border-zinc-700 dark:bg-zinc-900'>\n      <span className='pt-[3px] text-xs uppercase leading-none text-cyan-600 dark:text-cyan-300'>{children}</span>\n    </div>\n  );\n}\n"
  },
  {
    "path": "helpers.ts",
    "content": "const helpers = {\n  // @ts-ignore\n  friendlyDate: function (a) {\n    var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];\n    var days = ['Sun', 'Mon', 'Tues', 'Wed', 'Thurs', 'Fri', 'Sat'];\n    var year = a.getFullYear();\n    var month = months[a.getMonth()];\n    var day = days[a.getDay()];\n    var date = a.getDate();\n    var hour = a.getHours();\n    var min = a.getMinutes();\n    var sec = a.getSeconds();\n    var time_friendly = this.getTime(a);\n    var time = {\n      day: day,\n      date: date,\n      month: month,\n      year: year,\n      hour: hour,\n      min: min,\n      sec: sec,\n      time_friendly: time_friendly,\n    };\n    return time;\n  },\n  // @ts-ignore\n  getTime: function (date) {\n    var hours = date.getHours();\n    var minutes = date.getMinutes();\n    var ampm = hours >= 12 ? 'pm' : 'am';\n    hours = hours % 12;\n    hours = hours ? hours : 12; // the hour '0' should be '12'\n    minutes = minutes < 10 ? '0' + minutes : minutes;\n    var strTime = hours + ':' + minutes + ampm;\n    return strTime;\n  },\n  // @ts-ignore\n  stringToFriendlyDate: function (date_string) {\n    const date = helpers.friendlyDate(new Date(date_string));\n    const friendly_date = `${date.month} ${date.date}, ${date.year}`;\n    return friendly_date;\n  },\n};\n\nexport default helpers;\n"
  },
  {
    "path": "lib/cosmic.ts",
    "content": "import { createBucketClient } from '@cosmicjs/sdk';\nimport { Post } from './types';\nimport { GlobalData } from './types';\nimport { Author } from './types';\n\nconst cosmic = createBucketClient({\n  // @ts-ignore\n  bucketSlug: process.env.NEXT_PUBLIC_COSMIC_BUCKET_SLUG ?? '',\n  // @ts-ignore\n  readKey: process.env.NEXT_PUBLIC_COSMIC_READ_KEY ?? '',\n});\nexport default cosmic;\n\nexport async function getGlobalData(): Promise<GlobalData> {\n  // Get global data\n  try {\n    const data: any = await Promise.resolve(\n      cosmic.objects\n        .findOne({\n          type: 'globals',\n          slug: 'header',\n        })\n        .props('metadata.site_title,metadata.site_tag')\n        .depth(1)\n    );\n    const siteData: GlobalData = data.object;\n    return Promise.resolve(siteData);\n  } catch (error) {\n    console.log('Oof', error);\n  }\n  return Promise.resolve({} as GlobalData);\n}\n\nexport async function getAllPosts(): Promise<Post[]> {\n  try {\n    // Get all posts\n    const data: any = await Promise.resolve(\n      cosmic.objects\n        .find({\n          type: 'posts',\n        })\n        .props('id,type,slug,title,metadata,created_at')\n        .depth(1)\n    );\n    const posts: Post[] = await data.objects;\n    return Promise.resolve(posts);\n  } catch (error) {\n    console.log('Oof', error);\n  }\n  return Promise.resolve([]);\n}\n\nexport async function getPost({ params }: { params: { slug: string } }): Promise<Post> {\n  try {\n    // Get post\n    const data: any = await Promise.resolve(\n      cosmic.objects\n        .findOne({\n          type: 'posts',\n          slug: params.slug,\n        })\n        .props(['id', 'type', 'slug', 'title', 'metadata', 'created_at'])\n        .depth(1)\n    );\n    const post = await data.object;\n    return post;\n  } catch (error) {\n    console.log('Oof', error);\n  }\n  return Promise.resolve({} as Post);\n}\n\nexport async function getRelatedPosts({ params }: { params: { slug: string } }): Promise<Post[]> {\n  try {\n    // Get suggested posts\n    const data: any = await Promise.resolve(\n      cosmic.objects\n        .find({\n          type: 'posts',\n          slug: {\n            $ne: params?.slug,\n          },\n        })\n        .props(['id', 'type', 'slug', 'title', 'metadata', 'created_at'])\n        .sort('random')\n        .depth(1)\n    );\n    const suggestedPosts: Post[] = await data.objects;\n    return Promise.resolve(suggestedPosts);\n  } catch (error) {\n    console.log('Oof', error);\n  }\n  return Promise.resolve([]);\n}\n\nexport async function getAuthor({ params }: { params: { id: string; slug: string } }): Promise<Author> {\n  try {\n    const data: any = await Promise.resolve(\n      cosmic.objects\n        .findOne({\n          type: 'authors',\n          slug: params.slug,\n        })\n        .props('id,title')\n        .depth(1)\n    );\n    const author = await data.object;\n    return Promise.resolve(author);\n  } catch (error) {\n    console.log('Oof', error);\n  }\n  return Promise.resolve({} as Author);\n}\n\nexport async function getAuthorPosts({ authorId }: { authorId: string }): Promise<Post[]> {\n  try {\n    // Get Author's posts\n    const data: any = await Promise.resolve(\n      cosmic.objects\n        .find({\n          type: 'posts',\n          'metadata.author': authorId,\n        })\n        .props(['id', 'type', 'slug', 'title', 'metadata', 'created_at'])\n        .sort('random')\n        .depth(1)\n    );\n    const authorPosts: Post[] = await data.objects;\n    return Promise.resolve(authorPosts);\n  } catch (error) {\n    console.log('Oof', error);\n  }\n  return Promise.resolve([]);\n}\n"
  },
  {
    "path": "lib/types.ts",
    "content": "export interface GlobalData {\n  metadata: {\n    site_title: string;\n    site_tag: string;\n  };\n}\n\nexport interface Post {\n  id: string;\n  slug: string;\n  title: string;\n  metadata: {\n    published_date: string;\n    content: string;\n    hero?: {\n      imgix_url: string | null | undefined;\n    };\n    author?: {\n      slug: string | null | undefined;\n      title: string | null | undefined;\n      metadata: {\n        image?: {\n          imgix_url: string | null | undefined;\n        };\n      };\n    };\n    teaser: string;\n    categories: {\n      title: string;\n    }[];\n  };\n}\n\nexport interface Author {\n  id: string;\n  slug: string;\n  title: string;\n  metadata: {\n    image?: {\n      imgix_url: string | null | undefined;\n    };\n  };\n}\n"
  },
  {
    "path": "next.config.js",
    "content": "/** @type {import('next').NextConfig} */\n\nmodule.exports = {\n    swcMinify: true,\n    images: {\n        domains: ['imgix.cosmicjs.com'],\n        formats: ['image/avif', 'image/webp'],\n    },\n}"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"simple-nextjs-blog\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev --turbo\",\n    \"build\": \"next build\",\n    \"start\": \"next start\"\n  },\n  \"dependencies\": {\n    \"@cosmicjs/sdk\": \"^1.0.5\",\n    \"next\": \"^13.4.4\",\n    \"react\": \"18.1.0\",\n    \"react-dom\": \"18.1.0\",\n    \"typescript\": \"^5.0.4\"\n  },\n  \"engines\": {\n    \"node\": \">=18\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^20.2.5\",\n    \"@types/react\": \"^18.2.7\",\n    \"autoprefixer\": \"^10.4.14\",\n    \"postcss\": \"^8.4.24\",\n    \"prettier\": \"^2.8.8\",\n    \"prettier-plugin-tailwindcss\": \"^0.3.0\",\n    \"tailwindcss\": \"^3.3.2\"\n  }\n}\n"
  },
  {
    "path": "postcss.config.js",
    "content": "module.exports = {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {},\n  },\n}\n"
  },
  {
    "path": "styles/globals.css",
    "content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n@layer base {\n  h2 {\n    @apply text-zinc-700 dark:text-zinc-300 text-3xl font-bold mt-2 mb-4 leading-tight tracking-tight;\n  }\n\n  h3 {\n    @apply text-zinc-700 dark:text-zinc-300 text-xl font-semibold mt-2 mb-4 leading-snug tracking-tight;\n  }\n\n  p {\n    @apply text-zinc-600 dark:text-zinc-400 pb-4 last-of-type:pb-0 leading-relaxed tracking-normal;\n  }\n}\n"
  },
  {
    "path": "tailwind.config.js",
    "content": "/** @type {import('tailwindcss').Config} */\n\nconst defaultTheme = require('tailwindcss/defaultTheme');\n\nmodule.exports = {\n  content: ['./app/**/*.{js,ts,jsx,tsx,mdx}', './pages/**/*.{js,ts,jsx,tsx,mdx}', './components/**/*.{js,ts,jsx,tsx,mdx}'],\n  theme: {\n    extend: {\n      fontFamily: {\n        sans: ['var(--font-sans)', ...defaultTheme.fontFamily.sans],\n      },\n    },\n  },\n  plugins: [],\n};\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    /* Visit https://aka.ms/tsconfig to read more about this file */\n\n    /* Projects */\n    // \"incremental\": true,                              /* Save .tsbuildinfo files to allow for incremental compilation of projects. */\n    // \"composite\": true,                                /* Enable constraints that allow a TypeScript project to be used with project references. */\n    // \"tsBuildInfoFile\": \"./.tsbuildinfo\",              /* Specify the path to .tsbuildinfo incremental compilation file. */\n    // \"disableSourceOfProjectReferenceRedirect\": true,  /* Disable preferring source files instead of declaration files when referencing composite projects. */\n    // \"disableSolutionSearching\": true,                 /* Opt a project out of multi-project reference checking when editing. */\n    // \"disableReferencedProjectLoad\": true,             /* Reduce the number of projects loaded automatically by TypeScript. */\n\n    /* Language and Environment */\n    \"target\": \"es2016\" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,\n    // \"lib\": [],                                        /* Specify a set of bundled library declaration files that describe the target runtime environment. */\n    \"jsx\": \"preserve\" /* Specify what JSX code is generated. */,\n    // \"experimentalDecorators\": true,                   /* Enable experimental support for legacy experimental decorators. */\n    // \"emitDecoratorMetadata\": true,                    /* Emit design-type metadata for decorated declarations in source files. */\n    // \"jsxFactory\": \"\",                                 /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */\n    // \"jsxFragmentFactory\": \"\",                         /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */\n    // \"jsxImportSource\": \"\",                            /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */\n    // \"reactNamespace\": \"\",                             /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */\n    // \"noLib\": true,                                    /* Disable including any library files, including the default lib.d.ts. */\n    // \"useDefineForClassFields\": true,                  /* Emit ECMAScript-standard-compliant class fields. */\n    // \"moduleDetection\": \"auto\",                        /* Control what method is used to detect module-format JS files. */\n\n    /* Modules */\n    \"module\": \"commonjs\" /* Specify what module code is generated. */,\n    // \"rootDir\": \"./\",                                  /* Specify the root folder within your source files. */\n    // \"moduleResolution\": \"node10\",                     /* Specify how TypeScript looks up a file from a given module specifier. */\n    // \"baseUrl\": \"./\",                                  /* Specify the base directory to resolve non-relative module names. */\n    // \"paths\": {},                                      /* Specify a set of entries that re-map imports to additional lookup locations. */\n    // \"rootDirs\": [],                                   /* Allow multiple folders to be treated as one when resolving modules. */\n    // \"typeRoots\": [],                                  /* Specify multiple folders that act like './node_modules/@types'. */\n    // \"types\": [],                                      /* Specify type package names to be included without being referenced in a source file. */\n    // \"allowUmdGlobalAccess\": true,                     /* Allow accessing UMD globals from modules. */\n    // \"moduleSuffixes\": [],                             /* List of file name suffixes to search when resolving a module. */\n    // \"allowImportingTsExtensions\": true,               /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */\n    // \"resolvePackageJsonExports\": true,                /* Use the package.json 'exports' field when resolving package imports. */\n    // \"resolvePackageJsonImports\": true,                /* Use the package.json 'imports' field when resolving imports. */\n    // \"customConditions\": [],                           /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */\n    // \"resolveJsonModule\": true,                        /* Enable importing .json files. */\n    // \"allowArbitraryExtensions\": true,                 /* Enable importing files with any extension, provided a declaration file is present. */\n    // \"noResolve\": true,                                /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */\n\n    /* JavaScript Support */\n    // \"allowJs\": true,                                  /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */\n    // \"checkJs\": true,                                  /* Enable error reporting in type-checked JavaScript files. */\n    // \"maxNodeModuleJsDepth\": 1,                        /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */\n\n    /* Emit */\n    // \"declaration\": true,                              /* Generate .d.ts files from TypeScript and JavaScript files in your project. */\n    // \"declarationMap\": true,                           /* Create sourcemaps for d.ts files. */\n    // \"emitDeclarationOnly\": true,                      /* Only output d.ts files and not JavaScript files. */\n    // \"sourceMap\": true,                                /* Create source map files for emitted JavaScript files. */\n    // \"inlineSourceMap\": true,                          /* Include sourcemap files inside the emitted JavaScript. */\n    // \"outFile\": \"./\",                                  /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */\n    // \"outDir\": \"./\",                                   /* Specify an output folder for all emitted files. */\n    // \"removeComments\": true,                           /* Disable emitting comments. */\n    // \"noEmit\": true,                                   /* Disable emitting files from a compilation. */\n    // \"importHelpers\": true,                            /* Allow importing helper functions from tslib once per project, instead of including them per-file. */\n    // \"importsNotUsedAsValues\": \"remove\",               /* Specify emit/checking behavior for imports that are only used for types. */\n    // \"downlevelIteration\": true,                       /* Emit more compliant, but verbose and less performant JavaScript for iteration. */\n    // \"sourceRoot\": \"\",                                 /* Specify the root path for debuggers to find the reference source code. */\n    // \"mapRoot\": \"\",                                    /* Specify the location where debugger should locate map files instead of generated locations. */\n    // \"inlineSources\": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */\n    // \"emitBOM\": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */\n    // \"newLine\": \"crlf\",                                /* Set the newline character for emitting files. */\n    // \"stripInternal\": true,                            /* Disable emitting declarations that have '@internal' in their JSDoc comments. */\n    // \"noEmitHelpers\": true,                            /* Disable generating custom helper functions like '__extends' in compiled output. */\n    // \"noEmitOnError\": true,                            /* Disable emitting files if any type checking errors are reported. */\n    // \"preserveConstEnums\": true,                       /* Disable erasing 'const enum' declarations in generated code. */\n    // \"declarationDir\": \"./\",                           /* Specify the output directory for generated declaration files. */\n    // \"preserveValueImports\": true,                     /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */\n\n    /* Interop Constraints */\n    // \"isolatedModules\": true,                          /* Ensure that each file can be safely transpiled without relying on other imports. */\n    // \"verbatimModuleSyntax\": true,                     /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */\n    // \"allowSyntheticDefaultImports\": true,             /* Allow 'import x from y' when a module doesn't have a default export. */\n    \"esModuleInterop\": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,\n    // \"preserveSymlinks\": true,                         /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */\n    \"forceConsistentCasingInFileNames\": true /* Ensure that casing is correct in imports. */,\n\n    /* Type Checking */\n    \"strict\": true /* Enable all strict type-checking options. */,\n    // \"noImplicitAny\": true,                            /* Enable error reporting for expressions and declarations with an implied 'any' type. */\n    // \"strictNullChecks\": true,                         /* When type checking, take into account 'null' and 'undefined'. */\n    // \"strictFunctionTypes\": true,                      /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */\n    // \"strictBindCallApply\": true,                      /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */\n    // \"strictPropertyInitialization\": true,             /* Check for class properties that are declared but not set in the constructor. */\n    // \"noImplicitThis\": true,                           /* Enable error reporting when 'this' is given the type 'any'. */\n    // \"useUnknownInCatchVariables\": true,               /* Default catch clause variables as 'unknown' instead of 'any'. */\n    // \"alwaysStrict\": true,                             /* Ensure 'use strict' is always emitted. */\n    // \"noUnusedLocals\": true,                           /* Enable error reporting when local variables aren't read. */\n    // \"noUnusedParameters\": true,                       /* Raise an error when a function parameter isn't read. */\n    // \"exactOptionalPropertyTypes\": true,               /* Interpret optional property types as written, rather than adding 'undefined'. */\n    // \"noImplicitReturns\": true,                        /* Enable error reporting for codepaths that do not explicitly return in a function. */\n    // \"noFallthroughCasesInSwitch\": true,               /* Enable error reporting for fallthrough cases in switch statements. */\n    // \"noUncheckedIndexedAccess\": true,                 /* Add 'undefined' to a type when accessed using an index. */\n    // \"noImplicitOverride\": true,                       /* Ensure overriding members in derived classes are marked with an override modifier. */\n    // \"noPropertyAccessFromIndexSignature\": true,       /* Enforces using indexed accessors for keys declared using an indexed type. */\n    // \"allowUnusedLabels\": true,                        /* Disable error reporting for unused labels. */\n    // \"allowUnreachableCode\": true,                     /* Disable error reporting for unreachable code. */\n\n    /* Completeness */\n    // \"skipDefaultLibCheck\": true,                      /* Skip type checking .d.ts files that are included with TypeScript. */\n    \"skipLibCheck\": true /* Skip type checking all .d.ts files. */\n  }\n}\n"
  }
]