# Block Making

### Demonstration

{% embed url="<https://www.loom.com/share/b54d2811054c43d0bb65135823ab989c?sharedAppSource=personal_library>" %}
Block Making Demonstration
{% endembed %}

### File Structure

![](https://2634576350-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MihuyQ2hRhMOkQuQosr%2Fuploads%2F8NodXZYYSmJaKAFrhcIp%2Fimage.png?alt=media\&token=4d530ddf-54d5-44d6-9fef-f46c3e7ad5df)

This is the file structure from create-react-block. You can copy an existing block for convenience.

### Register Block in index.tsx

```tsx
import { registerBlockType } from "@wordpress/blocks";
import { __ } from "@wordpress/i18n";
import "./style.scss";
import edit from "./edit";
import save from "./save";
import { PREFIX } from "../../utils/config";

registerBlockType("gravity-platform-core/promotions", {
	title: __("Promotions", PREFIX),
	category: "gravity-platform",
	attributes: {
		formId: {
			type: "number",
			default: 1,
		},
		selectedPromotionIds: {
			type: "array",
			default: [],
		},
	},
	edit,
	save,
});
```

{% hint style="info" %}
Any block attributes that you rely on to hydrate your components should be registered in index.tsx with their type and ideally a default value.
{% endhint %}

### Create Custom Post Type -  if needed

{% code title="gravity/backend/plugins/gravity-platform-core/gravity-platform-core.php" %}

```php
function gravity_platform_core_blocks_init()
{
	register_block_type(__DIR__ . '/src/blocks/showroom');
  register_block_type(__DIR__ . '/src/blocks/promotions');

  register_post_type( 'promotions',
      array(
          'labels' => array(
              'name' => __( 'Promotions' ),
              'singular_name' => __( 'Promotion' )
          ),
          'public' => true,
          'has_archive' => true,
          'rewrite' => array('slug' => 'promotions'),
          'show_in_rest' => true,
          'show_in_graphql' => true,
          'hierarchical' => true,
          'graphql_single_name' => 'promotion',
          'graphql_plural_name' => 'promotions',
      )
  );
}
add_action('init', 'gravity_platform_core_blocks_init');
```

{% endcode %}

### Build Your Query - if using a postType for your block

You can use the GraphQl IDE in the Wordpress Backend to build your query.

![GraphQl IDE](https://2634576350-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MihuyQ2hRhMOkQuQosr%2F-Mkxg8F5p7QcBPmNapT1%2F-MkxhKyQ-tEfCv9MIO3u%2Fimage.png?alt=media\&token=79115289-fc6e-43ab-b77c-1e482ef254c9)

{% code title="gravity/frontend/lib/wp/queries/get-promotions.ts" %}

```jsx
import { gql } from "@apollo/client";

/**
 * GraphQL countries query.
 */
const GET_PROMOTIONS = gql`query GET_PROMOTIONS{
  promotions {
    nodes {
      title
      slug
      fields {
        description
        disclaimer
        endDate
        subtitle
        summary
        image {
          altText
          sourceUrl
        }
      }
    }
  }
}`;

export default GET_PROMOTIONS;
```

{% endcode %}

### Make Your Frontend Block

{% code title="frontend/components/blocks/promotions/PromotionsBlock.tsx" %}

```jsx
/**
 * Importing React is required for use in Gutenberg since WP Scripts doesn't compile components
 * as UMD modules (React is injected as a UMD module automatically in Next.js environments without an import)
 */
import React from "react";
import client from "../../../lib/wp/connector";
import GET_PROMOTIONS_QUERY from "../../../lib/wp/queries/get-promotions";
import Link from "next/link";

type Props = {
	promotions: any[];
};

export default function PromotionsBlock({ promotions }: Props) {
	return (
    <section className="text-gray-700 ">
      <div className="container items-center px-5 py-8 mx-auto lg:px-24">
        <div className="flex flex-wrap mb-12 text-left">
        {
          promotions.map( promotion => (
              <div className="w-full p-6 mx-auto lg:w-1/3">
                <div className="shadow-xl  rounded-xl bg-gray-50">
                  <img className="object-cover object-center w-full lg:h-48 md:h-36 rounded-t-xl" src={'http://' + promotion.fields.image.sourceUrl.substring(8)} alt="blog" />
                  <div className="p-4 lg:p-8 bg-gray-50">
                    <h1 className="mx-auto mb-4 text-2xl font-semibold leading-none tracking-tighter text-black lg:text-3xl title-font">{ promotion.title }</h1>
                    <h2 className="mb-8 text-xs font-semibold tracking-widest text-black uppercase title-font" dangerouslySetInnerHTML={{__html: promotion.fields.subtitle}}></h2>
                  </div>
                  <div className="px-6 py-4 bg-gray-100 rounded-b-xl">
                    <Link href={'promotions/' + promotion.slug}>
                      <a className="px-4 py-1 mr-1 text-base text-gray-500 transition duration-500 ease-in-out transform rounded-md focus:shadow-outline focus:outline-none focus:ring-2 ring-offset-current ring-offset-2 hover:text-black ">
                        Details »
                      </a>
                    </Link>
                    <a href="#" className="inline-flex items-center mt-auto font-semibold text-blue-600 lg:mb-0 hover:text-black " title="Read Full Article">  </a>
                  </div>
                </div>
              </div>
            )
          )
        }
        </div>
      </div>
    </section>
	);
}

const getPropsData = async () => {
	const { data } = await client.query({
		query: GET_PROMOTIONS_QUERY
	});

	return {
		props: {
			promotions: data?.promotions?.nodes ?? []
		}
	};
};

/**
 * Called from WP's block preview screen.
 * Separate from getServerSideProps because private/session based data might need to be mocked.
 * @returns
 */
export const getPreviewProps = async () => {
	return await getPropsData();
};

/**
 * Called by the page the block exists on within it's getServerSideProps method.
 * The result is passed to the component props.
 * @returns
 */
export const getServerSideProps = async () => {
	return await getPropsData();
};
```

{% endcode %}

When creating & registering your block the naming convention is to kabab-case rather than TitleCase or camelCase.

### Detail Page with Dynamic Routes

{% code title="gravity/frontend/pages/promotions/\[slug].tsx" %}

```jsx
import { GetServerSideProps } from "next";
import { ReactElement } from "react";
import Layout from "../../components/Layout";
import client from "../../lib/wp/connector";
import { PROMOTION_BY_SLUG_QUERY } from "../../lib/wp/queries/promotion-by-slug";
import { getSiteByDomain } from "../../lib/sites";

type Props = {
	promotion: any;
};

export default function PromotionDetail({ promotion }: Props): ReactElement {
	return (
		<Layout title={promotion.title}>
      <section className="text-gray-600 body-font">
        <div className="container mx-auto flex px-5 py-24 md:flex-row flex-col items-center">
          <div className="lg:flex-grow md:w-1/2 lg:pr-24 md:pr-16 flex flex-col md:items-start md:text-left mb-16 md:mb-0 items-center text-center">
            <h1 className="title-font sm:text-4xl text-3xl mb-4 font-medium text-gray-900">{promotion.fields.subtitle}</h1>
            <p className="mb-8 leading-relaxed" dangerouslySetInnerHTML={{__html: promotion.fields.summary}}></p>
            <p className="text-sm mt-2 text-gray-500 mb-8 w-full" dangerouslySetInnerHTML={{__html: promotion.fields.description}}></p>
          </div>
          <div className="lg:max-w-lg lg:w-full md:w-1/2 w-5/6">
            <img className="object-cover object-center w-full lg:h-48 md:h-36 rounded-t-xl" src={'http://' + promotion.fields.image.sourceUrl.substring(8)} alt={promotion.title} />
          </div>
        </div>
      </section>
		</Layout>
	);
}

export const getServerSideProps: GetServerSideProps = async ({
	req,
	res,
  resolvedUrl
}) => {
	res.setHeader(
		"Cache-Control",
		"public, s-maxage=1, stale-while-revalidate=86399" // 24 hours
	);

	// allow self-signed certs when developing locally
	if (process.env.NODE_ENV === "development") {
		process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
	}

	const site = getSiteByDomain(req.headers.host);

	if (!site) {
		return {
			notFound: true
		};
	}

	const uri = resolvedUrl;

	const { data } = await client.query({
		query: PROMOTION_BY_SLUG_QUERY,
		variables: { uri }
	});

	return {
		props: {
			promotion: data?.promotion
		}
	};
};

```

{% endcode %}

### Query Details for Single Item

{% code title="/gravity/frontend/lib/wp/queries/promotion-by-slug.ts" %}

```jsx
import { gql } from "@apollo/client";

export const PROMOTION_BY_SLUG_QUERY = gql `
  query Promotion($uri: ID!) {
    promotion(id: $uri, idType: URI) {
      id
      slug
      title
      fields {
        description
        disclaimer
        endDate
        subtitle
        summary
        image {
          altText
          sourceUrl
        }
      }
    }
  }
`
```

{% endcode %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://shaunh.gitbook.io/kairos/block-making.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
