<?php
/**
* The base configuration for WordPress
*
* The wp-config.php creation script uses this file during the installation.
* You don't have to use the website, you can copy this file to "wp-config.php"
* and fill in the values.
*
* This file contains the following configurations:
*
* * Database settings
* * Secret keys
* * Database table prefix
* * ABSPATH
*
* @link https://developer.wordpress.org/advanced-administration/wordpress/wp-config/
*
* @package WordPress
*/
// ** Database settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define( 'DB_NAME', 'wpclidemo' );
/** Database username */
define( 'DB_USER', 'root' );
/** Database password */
define( 'DB_PASSWORD', '' );
/** Database hostname */
define( 'DB_HOST', 'localhost' );
/** Database charset to use in creating database tables. */
define( 'DB_CHARSET', 'utf8mb4' );
/** The database collate type. Don't change this if in doubt. */
define( 'DB_COLLATE', '' );
// Any random string. This must match the .env variable in the Next.js frontend.
define( 'NEXTJS_PREVIEW_SECRET', 'preview' );
// Any random string. This must match the .env variable in the Next.js frontend.
define( 'NEXTJS_REVALIDATION_SECRET', 'revalidate' );
define('AUTH_KEY', '*N-t[-#n-3#-qv*c8K4iPf^^w*#WCu6$-(qD3cyatx^% A^QnG71Y0? O9ex}j]/');
define('SECURE_AUTH_KEY', 'F?s`7T*.m0OwP7az$Iw}!iS>D# Z5SQr+QY)DLH)klS[|UX_EIn}vs%IT_+FG');
define('LOGGED_IN_KEY', 'brdC.{e#r9x&_U>|(D02U@)]KsUQ#c3 SqIR|Y^/5A!W|9%I?;}%rLFRrB2p,vG=');
define('NONCE_KEY', '^e?>O4!YOCU|BXw`+m8NePd|QvTX~n,f8W;bUKpCU`PI|}+V;l~<j~>I>PZj7f,G');
define('AUTH_SALT', '1x;V[+Qd}DyF2ZPB}g{QX!>NF)lG8$3MrFi],YVIJ@C`OD3$&ZG9l-kGE3o~hr?|');
define('SECURE_AUTH_SALT', 'qxrb)txU(M;Y$A6?=~bl_= rJ,7EAx`_<+|WnO_<Pz~B:actx!B5k!F#@ bKQ4q7');
define('LOGGED_IN_SALT', '2vrPIByb8hZd7Z%4s~V8^=ZcyZ3TDi&I(c-X^0LxC[R|4A@=<yo|.#h=9;W!=]Cw');
define('NONCE_SALT', 'b?`&z:h)+3&w|o:<]gaS8zG^$P9f*l4FhE.YixsaE,U>EqQfn-v|Mo*465AE3+U|');
#define('GRAPHQL_JWT_AUTH_SECRET_KEY', 'wQ2gp%lGB(T0~!eV?FJg3M+tAy-R0YLF2rH_ Lou>k|7iFfGuH+0#oPTLXiG@8r-');
define('GRAPHQL_JWT_AUTH_SECRET_KEY', 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3dwY2xpZGVtby5kZXYiLCJpYXQiOjE3Mzk5OTU4MjgsIm5iZiI6MTczOTk5NTgyOCwiZXhwIjoxNzcxNTMxODI4LCJkYXRhIjp7InVzZXIiOnsiaWQiOiIxIiwidXNlcl9zZWNyZXQiOiJncmFwaHFsX2p3dF82N2I0ZTM1ZDhiODQyIn19fQ.FwrXtFD2flxFNEwDveyhByXJc7s5nL875QAz55P3TbM');
/**#@+
* Authentication unique keys and salts.
*
* Change these to different unique phrases! You can generate these using
* the {@link https://api.wordpress.org/secret-key/1.1/salt/ WordPress.org secret-key service}.
*
* You can change these at any point in time to invalidate all existing cookies.
* This will force all users to have to log in again.
*
* @since 2.6.0
*/
/**#@-*/
/**
* WordPress database table prefix.
*
* You can have multiple installations in one database if you give each
* a unique prefix. Only numbers, letters, and underscores please!
*
* At the installation time, database tables are created with the specified prefix.
* Changing this value after WordPress is installed will make your site think
* it has not been installed.
*
* @link https://developer.wordpress.org/advanced-administration/wordpress/wp-config/#table-prefix
*/
$table_prefix = 'wp_';
/**
* For developers: WordPress debugging mode.
*
* Change this to true to enable the display of notices during development.
* It is strongly recommended that plugin and theme developers use WP_DEBUG
* in their development environments.
*
* For information on other constants that can be used for debugging,
* visit the documentation.
*
* @link https://developer.wordpress.org/advanced-administration/debug/debug-wordpress/
*/
define( 'WP_DEBUG', false );
define('NEXTJS_FRONTEND_URL', 'https://wpclidemo.dev/');
// define('ALLOW_UNFILTERED_UPLOADS', true);
define('WP_DEBUG_LOG', 'C:/xampp82/htdocs/wpclidemo/index.php/wp-errors.log');
/* Add any custom values between this line and the "stop editing" line. */
/* That's all, stop editing! Happy publishing. */
/** Absolute path to the WordPress directory. */
if ( ! defined( 'ABSPATH' ) ) {
define( 'ABSPATH', __DIR__ . '/' );
}
/** Sets up WordPress vars and included files. */
require_once ABSPATH . 'wp-settings.php';
# WordPress GraphQL URL. No trailing slash.
NEXT_PUBLIC_WORDPRESS_GRAPHQL_URL="https://wpclidemo.dev/graphql"
# WordPress REST API URL. No trailing slash.
NEXT_PUBLIC_WORDPRESS_REST_API_URL="https://wpclidemo.dev/wp-json/wp/v2"
# Optional. JWT auth refresh token.
#NEXTJS_AUTH_REFRESH_TOKEN=""
# Preview Secret. Must match the constant in wp-config.php.
NEXTJS_PREVIEW_SECRET="preview"
# Revalidation Secret. Must match the constant in wp-config.php.
NEXTJS_REVALIDATION_SECRET="revalidate"
# Allow self-signed SSL certificates when working with local development environments.
NODE_TLS_REJECT_UNAUTHORIZED=0
NEXTJS_AUTH_REFRESH_TOKEN="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3dwY2xpZGVtby5kZXYiLCJpYXQiOjE3Mzk5OTU4MjgsIm5iZiI6MTczOTk5NTgyOCwiZXhwIjoxNzcxNTMxODI4LCJkYXRhIjp7InVzZXIiOnsiaWQiOiIxIiwidXNlcl9zZWNyZXQiOiJncmFwaHFsX2p3dF82N2I0ZTM1ZDhiODQyIn19fQ.FwrXtFD2flxFNEwDveyhByXJc7s5nL875QAz55P3TbM"
import {fetchGraphQL} from '@/lib/functions'
import {Menu} from '@/lib/types'
/**
* Fetch a menu by slug.
*/
export default async function getMenuBySlug(slug: string) {
const query = `
query GetMenuBySlug($slug: ID = "URI") {
menu(id: $slug, idType: SLUG) {
menuItems {
edges {
node {
uri
label
databaseId
}
}
}
}
}
`
const variables = {
slug: slug
}
const response = await fetchGraphQL(query, variables)
return response.data.menu as Menu
}
<?php
add_filter('big_image_size_threshold', '__return_false');
add_filter('intermediate_image_sizes', 'remove_default_img_sizes', 10, 1);
function remove_default_img_sizes($sizes)
{
$targets =
[
'thumbnail',
'medium',
'medium_large',
'large',
'1536x1536',
'2048x2048',
'woocommerce_thumbnail',
'woocommerce_single',
'woocommerce_gallery_thumbnail',
];
foreach ($sizes as $size_index => $size) {
if (in_array($size, $targets)) {
unset($sizes[$size_index]);
}
}
return $sizes;
}
if (! function_exists('astra_register_menu_locations')) {
/**
* Register menus
*
* @since 1.0.0
*/
function astra_register_menu_locations()
{
/**
* Primary Menus
*/
register_nav_menus(
array(
'primary' => esc_html__('Primary Menu', 'astra'),
)
);
/**
* Primary Menus
*/
register_nav_menus(
array(
'menu' => esc_html__('Menu', 'astra'),
)
);
if (true === Astra_Builder_Helper::$is_header_footer_builder_active) {
/**
* Register the Secondary & Mobile menus.
*/
register_nav_menus(
array(
'secondary_menu' => esc_html__('Secondary Menu', 'astra'),
'mobile_menu' => esc_html__('Off-Canvas Menu', 'astra'),
)
);
$component_limit = defined('ASTRA_EXT_VER') ? Astra_Builder_Helper::$component_limit : Astra_Builder_Helper::$num_of_header_menu;
for ($index = 3; $index <= $component_limit; $index++) {
if (! is_customize_preview() && ! Astra_Builder_Helper::is_component_loaded('menu-' . $index)) {
continue;
}
register_nav_menus(
array(
'menu_' . $index => esc_html__('Menu ', 'astra') . $index,
)
);
}
/**
* Register the Account menus.
*/
register_nav_menus(
array(
'loggedin_account_menu' => esc_html__('Logged In Account Menu', 'astra'),
)
);
}
/**
* Footer Menus
*/
register_nav_menus(
array(
'footer_menu' => esc_html__('Footer Menu', 'astra'),
)
);
}
}
/**
* Register a book post type.
*/
function wpdocs_codex_custom_init()
{
$labels = array(
'name' => _x('Books', 'Post type general name', 'textdomain'),
'singular_name' => _x('Book', 'Post type singular name', 'textdomain'),
'menu_name' => _x('Books', 'Admin Menu text', 'textdomain'),
'name_admin_bar' => _x('Book', 'Add New on Toolbar', 'textdomain'),
'add_new' => __('Add New', 'textdomain'),
'add_new_item' => __('Add New Book', 'textdomain'),
'new_item' => __('New Book', 'textdomain'),
'edit_item' => __('Edit Book', 'textdomain'),
'view_item' => __('View Book', 'textdomain'),
'all_items' => __('All Books', 'textdomain'),
'search_items' => __('Search Books', 'textdomain'),
'parent_item_colon' => __('Parent Books:', 'textdomain'),
'not_found' => __('No books found.', 'textdomain'),
'not_found_in_trash' => __('No books found in Trash.', 'textdomain'),
'featured_image' => _x('Book Cover Image', 'Overrides the “Featured Image” phrase for this post type. Added in 4.3', 'textdomain'),
'set_featured_image' => _x('Set cover image', 'Overrides the “Set featured image” phrase for this post type. Added in 4.3', 'textdomain'),
'remove_featured_image' => _x('Remove cover image', 'Overrides the “Remove featured image” phrase for this post type. Added in 4.3', 'textdomain'),
'use_featured_image' => _x('Use as cover image', 'Overrides the “Use as featured image” phrase for this post type. Added in 4.3', 'textdomain'),
'archives' => _x('Book archives', 'The post type archive label used in nav menus. Default “Post Archives”. Added in 4.4', 'textdomain'),
'insert_into_item' => _x('Insert into book', 'Overrides the “Insert into post”/”Insert into page” phrase (used when inserting media into a post). Added in 4.4', 'textdomain'),
'uploaded_to_this_item' => _x('Uploaded to this book', 'Overrides the “Uploaded to this post”/”Uploaded to this page” phrase (used when viewing media attached to a post). Added in 4.4', 'textdomain'),
'filter_items_list' => _x('Filter books list', 'Screen reader text for the filter links heading on the post type listing screen. Default “Filter posts list”/”Filter pages list”. Added in 4.4', 'textdomain'),
'items_list_navigation' => _x('Books list navigation', 'Screen reader text for the pagination heading on the post type listing screen. Default “Posts list navigation”/”Pages list navigation”. Added in 4.4', 'textdomain'),
'items_list' => _x('Books list', 'Screen reader text for the items list heading on the post type listing screen. Default “Posts list”/”Pages list”. Added in 4.4', 'textdomain'),
);
$args = array(
'labels' => $labels,
'public' => true,
'publicly_queryable' => true,
'show_ui' => true,
'show_in_menu' => true,
'query_var' => true,
'capability_type' => 'post',
'rewrite' => array('slug' => 'books'),
'label' => __('Books', 'textdomain'),
'has_archive' => true,
'hierarchical' => false,
'menu_position' => null,
'show_in_graphql' => true,
'graphql_single_name' => 'books',
'graphql_plural_name' => 'books',
'supports' => array('title', 'editor', 'author', 'thumbnail', 'excerpt', 'comments')
);
register_post_type('books', $args);
}
add_action('init', 'wpdocs_codex_custom_init');
add_filter('register_post_type_args', function ($args, $post_type) {
// Change this to the post type you are adding support for
if ('book' === $post_type) {
$args['show_in_graphql'] = true;
$args['graphql_single_name'] = 'book';
$args['graphql_plural_name'] = 'books'; # Don't set, and it will default to `all${graphql_single_name}`, i.e. `allDocument`.
}
return $args;
}, 10, 2);
import getAllPosts from '@/lib/queries/getAllPosts'
import getPostBySlug from '@/lib/queries/getPostBySlug'
import type {DynamicPageProps} from '@/lib/types'
import {Metadata} from 'next'
import Image from 'next/image'
import Link from 'next/link'
import {notFound} from 'next/navigation'
/**
* Generate the static routes at build time.
*
* @see https://nextjs.org/docs/app/api-reference/functions/generate-static-params
*/
export async function generateStaticParams() {
// Get all blog posts.
const posts = await getAllPosts()
// No posts? Bail...
if (!posts) {
return []
}
// Return the slugs for each post.
return posts.map((post: {slug: string}) => ({
slug: post.slug
}))
}
/**
* Generate the metadata for each static route at build time.
*
* @see https://nextjs.org/docs/app/api-reference/functions/generate-metadata#generatemetadata-function
*/
export async function generateMetadata({
params
}: DynamicPageProps): Promise<Metadata | null> {
// Get the slug from the params.
const {slug} = await params
// Get the blog post.
const post = await getPostBySlug(slug)
// No post? Bail...
if (!post) {
return {}
}
return {
title: post.seo.title,
description: post.seo.metaDesc
}
}
/**
* The blog post route.
*
* @see https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts#pages
*/
export default async function Post({params}: Readonly<DynamicPageProps>) {
// Get the slug from the params.
const {slug} = await params
// Fetch a single post from WordPress.
const post = await getPostBySlug(slug)
// No post? Bail...
if (!post) {
notFound()
}
return (
<article>
<header>
<h2 dangerouslySetInnerHTML={{__html: post.title}} />
<p className="italic">
By {post.author.node.name} on <time>{post.date}</time>
</p>
</header>
<div dangerouslySetInnerHTML={{__html: post.content}} />
<footer className="flex items-center justify-between gap-4 pb-4">
<div>
<h3>Categories</h3>
<ul className="m-0 flex list-none gap-2 p-0">
{post.categories.nodes.map((category) => (
<li className="m-0 p-0" key={category.databaseId}>
<Link href={`/blog/category/${category.name}`}>
{category.name}
</Link>
</li>
))}
</ul>
</div>
<div>
<h3>Tags</h3>
<ul className="m-0 flex list-none gap-2 p-0">
{post.tags.nodes.map((tag) => (
<li className="m-0 p-0" key={tag.databaseId}>
<Link href={`/blog/tag/${tag.name}`}>{tag.name}</Link>
</li>
))}
</ul>
</div>
</footer>
<section className="border-t-2">
<h3>Comments</h3>
{post.comments.nodes.map((comment) => (
<article key={comment.databaseId}>
<header className="flex items-center gap-2">
<Image
alt={comment.author.node.name}
className="m-0 rounded-full"
height={64}
loading="lazy"
src={comment.author.node.avatar.url}
width={64}
/>
<div className="flex flex-col gap-2">
<h4
className="m-0 p-0 leading-none"
dangerouslySetInnerHTML={{__html: comment.author.node.name}}
/>
<time className="italic">{comment.date}</time>
</div>
</header>
<div dangerouslySetInnerHTML={{__html: comment.content}} />
</article>
))}
</section>
</article>
)
}
import {fetchGraphQL} from '@/lib/functions'
import {Post} from '@/lib/types'
/**
* Fetch a single blog post by slug.
*/
export default async function getPostBySlug(slug: string) {
const query = `
query GetPost($slug: ID!) {
post(id: $slug, idType: SLUG) {
databaseId
date
modified
content(format: RENDERED)
title(format: RENDERED)
featuredImage {
node {
altText
sourceUrl
mediaDetails {
height
width
}
}
}
author {
node {
name
avatar {
url
}
}
}
tags {
nodes {
databaseId
name
}
}
categories {
nodes {
databaseId
name
}
}
seo {
metaDesc
title
}
comments(first: 30, where: {order: ASC}) {
nodes {
content(format: RENDERED)
databaseId
date
status
author {
node {
avatar {
url
}
email
name
url
}
}
}
}
}
}
`
const variables = {
slug: slug
}
const response = await fetchGraphQL(query, variables)
return response.data.post as Post
}
import getAllBooks from '@/lib/queries/getAllBooks'
import getBookBySlug from '@/lib/queries/getBookBySlug'
import type {DynamicPageProps} from '@/lib/types'
import {Metadata} from 'next'
import Link from 'next/link'
import {notFound} from 'next/navigation'
/**
* Generate the static routes at build time.
*
* @see https://nextjs.org/docs/app/api-reference/functions/generate-static-params
*/
export async function generateStaticParams() {
// Get a list of all books.
const books = await getAllBooks()
// No books? Bail...
if (!books) {
return []
}
// Return the slugs for each book.
return books.map((book: {slug: string}) => ({
slug: book.slug
}))
}
/**
* Generate the metadata for each static route at build time.
*
* @see https://nextjs.org/docs/app/api-reference/functions/generate-metadata#generatemetadata-function
*/
export async function generateMetadata({ params }: Readonly<DynamicPageProps>): Promise<Metadata | null> {
// Get the slug from the params.
const {slug} = await params
// Get the page.
const book = await getBookBySlug(slug)
// No post? Bail...
if (!book) {
return {}
}
return {
title: book.seo.title,
description: book.seo.metaDesc
}
}
/**
* A single book route.
*
* @see https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts#pages
*/
export default async function Book({params}: Readonly<DynamicPageProps>) {
// Get the slug from the params.
const {slug} = await params
// Fetch a single book from WordPress.
const book = await getBookBySlug(slug)
// No book? Bail...
if (!book) {
notFound()
}
return (
<main className="flex flex-col gap-8">
<article className="w-full">
<h1 className="font-bold" dangerouslySetInnerHTML={{__html: book.title}} />
<div dangerouslySetInnerHTML={{__html: book.content}} />
<Link className="button" href={book.bookfields.affiliateurl}>View on Amazon</Link>
</article>
</main>
)
}
import {fetchGraphQL} from '@/lib/functions'
import {Post} from '@/lib/types'
/**
* Fetch all books.
*/
export default async function getAllBooks() {
const query = `
query GetAllBooks {
books(where: {status: PUBLISH}) {
nodes {
databaseId
date
modified
title
slug
excerpt(format: RENDERED)
featuredImage {
node {
altText
sourceUrl
mediaDetails {
height
width
}
}
}
seo {
metaDesc
title
}
}
}
}
`
const response = await fetchGraphQL(query)
return response.data.books.nodes as Post[]
}
import {fetchGraphQL} from '@/lib/functions'
import {Book} from '@/lib/types'
/**
* Fetch a book by slug.
*/
export default async function getBookBySlug(slug: string) {
const query = `
query GetBookBySlug($slug: ID = "URI") {
book(idType: SLUG, id: $slug) {
databaseId
date
modified
content(format: RENDERED)
title(format: RENDERED)
bookfields {
affiliateurl
isbn
}
featuredImage {
node {
altText
sourceUrl
mediaDetails {
height
width
}
}
}
seo {
metaDesc
title
}
}
}
`
const variables = {
slug: slug
}
const response = await fetchGraphQL(query, variables)
return response.data.book as Book
}
import getAllBooks from '@/lib/queries/getAllBooks'
import getAllPosts from '@/lib/queries/getAllPosts'
import getPageBySlug from '@/lib/queries/getPageBySlug'
import type { DynamicPageProps } from '@/lib/types'
import { Page, Post } from '@/lib/types'
import Image from 'next/image'
import Link from 'next/link'
import { notFound } from 'next/navigation'
/**
* Fetches data from WordPress.
*/
async function fetchData(slug: string) {
// If the slug is 'blog', fetch all posts.
if (slug === 'blog') {
return { posts: await getAllPosts(), context: 'blog' }
}
// If the slug is 'books', fetch all books.
if (slug === 'books') {
return { posts: await getAllBooks(), context: 'books' }
}
// Otherwise, this could be a page.
const page = await getPageBySlug(slug)
// If page data exists, return it.
if (page) {
return { post: page }
}
// Otherwise, return an error.
return { error: 'No data found' }
}
/**
* Render a single page.
*/
function RenderPage({ page }: { page: Page }) {
return (
<main className="flex flex-col gap-8">
<article>
<h1 dangerouslySetInnerHTML={{ __html: page.title }} />
<div dangerouslySetInnerHTML={{ __html: page.content }} />
</article>
</main>
)
}
/**
* Render posts list.
*/
function RenderPostsList({ posts, context }: { posts: Post[]; context: string }) {
return (
<main className="flex flex-col gap-8">
<h1 className="capitalize">Latest {context}</h1>
<div className="grid grid-cols-4 gap-4">
{posts.map((post: Post) => (
<article className="p-4 shadow-md relative" key={post.databaseId}>
{post?.featuredImage ? (
<Image
src={post?.featuredImage?.node?.sourceUrl}
alt={post?.featuredImage?.node?.altText}
width={280}
height={233}
className="h-[233px]"
priority={true}
/>
) : (
<img
src={process.env.NEXT_PUBLIC_PLACEHOLDER_LARGE_IMAGE_URL as string}
alt={"Featured"}
width={280}
height={233}
className="h-[233px]"
/>
)}
<Link href={`/${context}/${post.slug}`}>
<h2 className="font-bold py-2" dangerouslySetInnerHTML={{ __html: post.title }} />
</Link>
<p className="text-sm text-gray-500 italic">
{post.commentCount} Comments
</p>
<div className="mb-2" dangerouslySetInnerHTML={{ __html: post.excerpt }} />
<Link className=" bg-sky-500 hover:bg-sky-700 px-5 py-2 text-sm leading-5 rounded-full font-semibold text-white" href={`/${context}/${post.slug}`}>
View Post
</Link>
</article>
))}
</div>
</main>
)
}
/**
* Catch-all Archive Page route.
*/
export default async function Archive({ params }: Readonly<DynamicPageProps>) {
// Get the slug from the params.
const { slug } = await params
// Fetch data from WordPress.
const data = await fetchData(slug)
// If there's an error, return a 404 page.
if (data.error) {
notFound()
}
// If this is a single page, render the page.
if (data.post) {
return <RenderPage page={data.post} />
}
// Otherwise, this must be an archive. Render the posts list.
if (data.posts && data.posts.length > 0) {
return <RenderPostsList posts={data.posts} context={data.context} />
}
// Otherwise, return a 404 page.
notFound()
}
import config from '@/lib/config'
import getCategoryBySlug from '@/lib/queries/getCategoryBySlug'
import {DynamicPageProps} from '@/lib/types'
import {Metadata} from 'next'
import Image from 'next/image'
import Link from 'next/link'
import {notFound} from 'next/navigation'
/**
* Generate the metadata for each static route at build time.
*
* @see https://nextjs.org/docs/app/api-reference/functions/generate-metadata#generatemetadata-function
*/
export async function generateMetadata({
params
}: DynamicPageProps): Promise<Metadata | null> {
// Get the slug from the params.
const {slug} = await params
return {
title: `${slug} Archives - ${config.siteName}`,
description: `The category archive for ${slug}`
}
}
/**
* The category archive route.
*
* @see https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts#pages
*/
export default async function CategoryArchive({
params
}: Readonly<DynamicPageProps>) {
// Get the slug from the params.
const {slug} = await params
// Fetch posts from WordPress.
const posts = await getCategoryBySlug(slug)
// No posts? Bail...
if (!posts) {
notFound()
}
return (
<main className="flex flex-col gap-8">
<h1 className="capitalize">Post Category: {slug}</h1>
<div className="grid grid-cols-4 gap-4">
{posts.map((post) => (
<article className="p-4 shadow-md" key={post.databaseId}>
{post?.featuredImage ? (
<Image
src={post?.featuredImage?.node?.sourceUrl}
alt={post?.featuredImage?.node?.altText}
width={280}
height={233}
className="h-[233px]"
priority={true}
/>
) : (
<img
src={process.env.NEXT_PUBLIC_PLACEHOLDER_LARGE_IMAGE_URL as string}
alt={"Featured"}
width={280}
height={233}
className="h-[233px]"
/>
)}
<Link href={`/blog/${post.slug}`}>
<h2 className="font-bold py-2" dangerouslySetInnerHTML={{__html: post.title}} />
</Link>
<p className="text-sm text-gray-500">
{post.commentCount} Comments
</p>
<div className="mb-2" dangerouslySetInnerHTML={{__html: post.excerpt}} />
<Link className="bg-sky-500 hover:bg-sky-700 px-5 py-2 text-sm leading-5 rounded-full font-semibold text-white" href={`/blog/${post.slug}`}>
View Post
</Link>
</article>
))}
</div>
</main>
)
}
import {fetchGraphQL} from '@/lib/functions'
import {Post} from '@/lib/types'
/**
* Fetch a category archive by slug.
*/
export default async function getCategoryBySlug( slug: string, limit: number = 10 ) {
const query = `
query GetCategoryBySlug($slug: String!) {
posts(where: {categoryName: $slug, status: PUBLISH}, first: ${limit}) {
nodes {
databaseId
date
excerpt(format: RENDERED)
title(format: RENDERED)
featuredImage {
node {
altText
sourceUrl
mediaDetails {
height
width
}
}
}
seo {
metaDesc
title
}
slug
}
}
}
`
const variables = {
slug: slug
}
const response = await fetchGraphQL(query, variables)
return response.data.posts.nodes as Post[]
}
import config from '@/lib/config'
import getTagBySlug from '@/lib/queries/getTagBySlug'
import type {DynamicPageProps} from '@/lib/types'
import {Metadata} from 'next'
import Image from 'next/image'
import Link from 'next/link'
import {notFound} from 'next/navigation'
/**
* Generate the metadata for each static route at build time.
*
* @see https://nextjs.org/docs/app/api-reference/functions/generate-metadata#generatemetadata-function
*/
export async function generateMetadata({
params
}: Readonly<DynamicPageProps>): Promise<Metadata | null> {
// Get the slug from the params.
const {slug} = await params
return {
title: `${slug} Archives - ${config.siteName}`,
description: `The tag archive for ${slug}`
}
}
/**
* The tag archive route.
*
* @see https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts#pages
*/
export default async function TagArchive({params}: Readonly<DynamicPageProps>) {
// Get the slug from the params.
const {slug} = await params
// Fetch posts from WordPress.
const posts = await getTagBySlug(slug)
// No posts? Bail...
if (!posts) {
notFound()
}
return (
<main className="flex flex-col gap-8">
<h1 className="capitalize">Post Tag: {slug}</h1>
<div className="grid grid-cols-4 gap-4">
{posts.map((post) => (
<article className="p-4 drop-shadow-lg border" key={post.databaseId}>
{post?.featuredImage ? (
<Image
src={post?.featuredImage?.node?.sourceUrl}
alt={post?.featuredImage?.node?.altText}
width={280}
height={233}
className="h-[233px]"
priority={true}
/>
) : (
<img
src={process.env.NEXT_PUBLIC_PLACEHOLDER_LARGE_IMAGE_URL as string}
alt={"Featured"}
width={280}
height={233}
className="h-[233px]"
/>
)}
<Link href={`/blog/${post.slug}`}>
<h2 className="font-bold py-2" dangerouslySetInnerHTML={{__html: post.title}} />
</Link>
<p className="text-sm text-gray-500">
{post.commentCount} Comments
</p>
<div className="mb-2" dangerouslySetInnerHTML={{__html: post.excerpt.substring(0, 120)}} />
<Link className="bg-sky-500 hover:bg-sky-700 px-5 py-2 text-sm leading-5 rounded-full font-semibold text-white" href={`/blog/${post.slug}`}>
View Post
</Link>
</article>
))}
</div>
</main>
)
}
import {fetchGraphQL} from '@/lib/functions'
import {Post} from '@/lib/types'
/**
* Fetch a tag archive by slug.
*/
export default async function getTagBySlug(slug: string, limit: number = 10) {
const query = `
query GetTagBySlug($slug: String!) {
posts(where: {tag: $slug, status: PUBLISH}, first: ${limit}) {
nodes {
databaseId
date
excerpt(format: RENDERED)
title(format: RENDERED)
featuredImage {
node {
altText
sourceUrl
mediaDetails {
height
width
}
}
}
seo {
metaDesc
title
}
slug
}
}
}
`
const variables = {
slug: slug
}
const response = await fetchGraphQL(query, variables)
return response.data.posts.nodes as Post[]
}