<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Hey Rory's blog]]></title><description><![CDATA[All things Front-End development, using some of my favorite technologies like React, NextJS, Stripe, AWS, TailwindCSS and many more.
I have 5 years full time experience join me as I share my knowledge]]></description><link>https://bronz3beard.hashnode.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1672365932301/S6F8sU_KI.jpg</url><title>Hey Rory&apos;s blog</title><link>https://bronz3beard.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 15 Oct 2026 12:25:17 GMT</lastBuildDate><atom:link href="https://bronz3beard.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[[deprecated]: Google Analytics 4 & Google Tag Manager]]></title><description><![CDATA[#Full disclosure this blog does have a bit of "Product placement" in it with an open-source package I wrote. Other than that this is one way to implement client-side GA4 analytics.#

Why add google an]]></description><link>https://bronz3beard.hashnode.dev/deprecated-google-analytics-4-google-tag-manager</link><guid isPermaLink="true">https://bronz3beard.hashnode.dev/deprecated-google-analytics-4-google-tag-manager</guid><category><![CDATA[Google Analytics]]></category><category><![CDATA[ga4]]></category><category><![CDATA[google tag manager]]></category><category><![CDATA[React]]></category><category><![CDATA[CodePen]]></category><dc:creator><![CDATA[Rory]]></dc:creator><pubDate>Sun, 28 May 2023 22:54:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1685311981174/3cfb92a9-510d-4a33-9108-5d04c2038d23.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>#Full disclosure this blog does have a bit of "Product placement" in it with an open-source package I wrote. Other than that this is one way to implement client-side GA4 analytics.#</p>
</blockquote>
<h1>Why add google analytics to a web App?</h1>
<ol>
<li><p>Error tracking and analytics</p>
</li>
<li><p>Discover what works and what doesn't</p>
</li>
<li><p>Discover website usage patterns to better inform feature development or refactors</p>
</li>
<li><p>Understand the cost of features against how often they are used</p>
</li>
<li><p>Justify further spending on new features</p>
</li>
<li><p>For anything that you want to measure, you can use google analytics for this.</p>
</li>
</ol>
<hr />
<h1>Add google analytics to a web App?</h1>
<p>Let's start by adding a way for collecting data to our website, and then deciding which data we want to capture. In this scenario, we are interested to know who is clicking on the register button and who is clicking the signing in button.</p>
<p>First, we will install a dependency <a href="https://www.npmjs.com/package/react-marketing-tools">#react-marketing-tools(RMT)</a> this will provide us with a way to capture data and send it to GA4 via our GTM. Also for convenience, I recommend using this dependency <a href="https://www.npmjs.com/package/react-gtm-module">react-gtm-module</a> for adding GTM and the 👍<code>dataLayer</code> to your project, or you could do it manually if you prefer.</p>
<h2>React Marketing Tools Demo</h2>
<p>This is an example of a very simple implementation, some of this we will breakdown and explain further, just continue reading 🙃</p>
<p><a class="embed-card" href="https://codepen.io/roryfn/pen/OJBGvMG">https://codepen.io/roryfn/pen/OJBGvMG</a></p>

<p>The above demo implementation doesn't use a react <code>Context/Provider</code> so you have the option of using this package outside of react.<br />By clicking register or login and then showing <code>dataLayer</code> you can see the key/values that go to your GA4 analytics.<br />To view the <code>Context/Provider</code> implementation checkout the example in the<br /><a href="https://github.com/bronz3beard/react-marketing-tools/blob/main/README.md">GitHub repo README.md</a>.</p>
<h3>React Marketing Tools global config</h3>
<pre><code class="language-typescript">const TOKENS = { // all TOKENS are optional
  /*
    If withServerLocationInfo is true you must supply IP_INFO_TOKEN token.
  */
  IP_INFO_TOKEN: 'SOME_TOKEN',
  /* 
    If analyticsType = analyticsPlatform.GOOGLE the below tokens must be supplied.
  */
  GA4_PUBLIC_API_SECRET: 'SOME_TOKEN',
  GA4_PUBLIC_MEASUREMENT_ID: 'SOME_TOKEN',
}

const analyticsConfig = {
  appName: "my-awesome-app",
  appSessionCookieName: "AWESOME_APP",
  eventActionPrefix: {},
  globalEventActionList: {
    // this will extend the default values of globalEventActionList
    LOGIN: "LOGIN",
    REGISTER: "REGISTER",
    UNAUTHENTICATED: "UNAUTHENTICATED",
    AUTHENTICATED: "AUTHENTICATED"
  },
/*
   * If you dont add the user data keys to this includeUserKeys
   * the values will be adde to the journeyProps not the userProps
*/
  includeUserKeys: ["email"],
/*
   * showMissingUserAttributesInConsole if true will console.log in the browser console user data that does not match the includeUserKeys array
*/
  showMissingUserAttributesInConsole: false,
  TOKENS,
  withDeviceInfo: true,
  withServerLocationInfo: false
};
</code></pre>
<p>Note: <code>withDeviceInfo</code>, <code>withServerLocationInfo</code> use the following dependencies to get the respective data. These are great tools for making your life much easier!</p>
<ol>
<li><p><code>withDeviceInfo</code> - <a href="https://www.npmjs.com/package/device-detector-js">device-detector-js</a></p>
</li>
<li><p><code>withServerLocationInfo</code> - <a href="https://ipinfo.io">https://ipinfo.io</a></p>
</li>
</ol>
<h3>React Marketing Tools event config</h3>
<pre><code class="language-typescript">      const eventNameInfo = {
/*
   * eventName is optional and will be change to "REGISTER_ACCOUNT"
   * this is the event name you will add to your Tag in GTM
   * If this is not passed in the event name will be a combination of actionPrefix &amp; globalAppEvent e.g. I_REGISTER
*/
        eventName: 'register account',
/*
   * actionPrefix this will be added to the eventName
   * this helps to quickly identify the type of action in GTM &amp; GA4
   * "I_REGISTER_ACCOUNT"
*/ 
        actionPrefix: "I", // INTERACTION | J: JOURNEY
/*
   * globalAppEvent here the key/values added to "globalEventActionList" are used
*/ 
        globalAppEvent: "REGISTER",
/*
   * previousGlobalAppEvent this is helpful for building user journeys         with the data, 
   * It is not always available or needed therefore it is optional
*/ 
        previousGlobalAppEvent: "UNAUTHENTICATED"
      };
</code></pre>
<hr />
<h1>Google Tag Manager</h1>
<p>Now we set up our GTM with all the events, triggers and variables we want to collect.</p>
<p>There are any number of ways you can set up your GTM. I will show you a way that I prefer, it helps me manage a dev &amp; prod environment that way I don't pollute my prod data with any testing or dev data.</p>
<p>Below is a snapshot of things we will cover in this section and some of the terms that will be used.</p>
<ul>
<li><p><a href="https://marketingplatform.google.com/about/tag-manager/">Google Tag Manager(GTM)</a></p>
</li>
<li><p>Versions/Workspace</p>
</li>
<li><p>Live, Latest (aka production)</p>
</li>
<li><p>dev</p>
</li>
<li><p>Tags</p>
</li>
<li><p>Triggers</p>
</li>
<li><p>Variables &amp; Folders</p>
</li>
</ul>
<p>So first sign up to GTM with the Google account you want to use to manage GTM. Once we have signed in to GTM we will have access to the values below.</p>
<pre><code class="language-typescript">id: string, // GTM-ABC....
gtm_auth: string, // Qr08F...
preview: string, // env-1 - This value is what we use to define which environment we are using.
</code></pre>
<p>If you don't see these right away you can find them under <code>Admin &gt; Install Google Tag Manager</code> This is also where you will find the code needed to manually add GTM/dataLayer to your App if you do not want to use <a href="https://www.npmjs.com/package/react-gtm-module">react-gtm-module</a>.</p>
<h3>Add TagManager</h3>
<p>Now we have these values we can add GTM and the <code>dataLayer</code> to our website with <a href="https://www.npmjs.com/package/react-gtm-module">react-gtm-module</a>.</p>
<pre><code class="language-typescript">import TagManager from 'react-gtm-module'

// Usually these values can be added to your .env
const tagManagerArgs = {
   gtmId: "GTM-ABC",
   dataLayerName: "dataLayer",
   auth: "Qr08F...",
   preview: "env-1"
};

useEffect(function initiateGoogleTagManager() {
  TagManager.initialize(tagManagerArgs);
}, []);
</code></pre>
<p>If you have set up your data collection with <a href="https://www.npmjs.com/package/react-marketing-tools">#react-marketing-tools(RMT)</a> and you have completed the above initialisation of GTM, at this point you will be able to see the data in your dataLayer in the console.<br />Either use the built-in logging from RMT or just open the console type <code>dataLayer</code> and hit enter you will see something like this.</p>
<blockquote>
<p>At this point, I recommend taking a little time to understand the parts of this <code>dataLayer</code> and understand where most of the data comes from.</p>
<p><strong>TLDR;</strong> It is a combination of the <code>analyticsConfig</code> you passed to the <code>buildConfig</code> from RMT, also what you added to each <em>event data object</em> <code>trackingData</code>, in this case, the events of Register and Login.<br />Check-out the <code>handleButtonClick</code> from the above demo.</p>
<p>NOTE:</p>
<ul>
<li><p>All <code>journeyProps</code> variables will be CAPITALISED. This point is important to remember when we come to assign the <code>dataLayer</code> values to new GTM variables.</p>
</li>
<li><p>At this point none of the data that is in the <code>dataLayer</code> is going to go to GA4 via GTM because we have not configured GTM yet.</p>
</li>
</ul>
</blockquote>
<hr />
<h1>Google Tag Manager Setup</h1>
<p>We are going to start from the bottom up to setup our GTM.</p>
<ol>
<li><p>Variables &amp; Folders</p>
</li>
<li><p>Triggers</p>
</li>
<li><p>Tags</p>
</li>
</ol>
<hr />
<h3>Variables ✅</h3>
<p>We are setting up the variables based on what you might use with RMT, but this setup can be used for any implementation of GTM. The variables will be added to your Tags.</p>
<p><strong>Built-In Variables</strong></p>
<table style="min-width:50px"><colgroup><col style="min-width:25px"></col><col style="min-width:25px"></col></colgroup><tbody><tr><td><p><strong>Name</strong></p></td><td><p><strong>Type</strong></p></td></tr><tr><td><p>Event</p></td><td><p>Custom Event</p></td></tr><tr><td><p>Page Hostname</p></td><td><p>URL</p></td></tr><tr><td><p>Page Path</p></td><td><p>URL</p></td></tr><tr><td><p>Page URL</p></td><td><p>URL</p></td></tr><tr><td><p>Referrer</p></td><td><p>HTTP Referrer</p></td></tr></tbody></table>

<p><strong>User-Defined Variables</strong></p>
<p><strong>"constant"</strong> variables in this case are used as a fallback to convert any other user-defined values to an acceptable type. This is a "helper variable" in case the parsing of the data to a JSON object ever fails.<br />I decided to use these "helper variables" because it can take a lot of time to figure out why a variable is not appearing in the payload for GA4, at least with this fallback I can see the variable, and I can see it has no value <code>""</code> or a <code>"boolean"</code> value and work directly on fixing it.</p>
<table style="min-width:100px"><colgroup><col style="min-width:25px"></col><col style="min-width:25px"></col><col style="min-width:25px"></col><col style="min-width:25px"></col></colgroup><tbody><tr><td><p><strong>Name</strong></p></td><td><p><strong>Type</strong></p></td><td><p><strong>Folder</strong></p></td><td><p><strong>Value</strong></p></td></tr><tr><td><p>empty_string</p></td><td><p>Constant</p></td><td><p>common</p></td><td><p>""</p></td></tr><tr><td><p>false_string_boolean</p></td><td><p>Constant</p></td><td><p>common</p></td><td><p>"false"</p></td></tr><tr><td><p>true_string_boolean</p></td><td><p>Constant</p></td><td><p>common</p></td><td><p>"true"</p></td></tr><tr><td><p>client_id</p></td><td><p>Data Layer Variable</p></td><td><p>userProps</p></td><td><p><code>analyticsConfig</code></p></td></tr><tr><td><p>email</p></td><td><p>Data Layer Variable</p></td><td><p>userProps</p></td><td><p>event <code>data</code> &amp; <code>analyticsConfig</code></p></td></tr><tr><td><p>app</p></td><td><p>Data Layer Variable</p></td><td><p>globalVars</p></td><td><p><code>analyticsConfig</code></p></td></tr><tr><td><p>DEVICE_BROWSER</p></td><td><p>Data Layer Variable</p></td><td><p>globalVars</p></td><td><p>RMT feat <code>analyticsConfig</code></p></td></tr><tr><td><p>DEVICE_CATEGORY</p></td><td><p>Data Layer Variable</p></td><td><p>globalVars</p></td><td><p>RMT feat <code>analyticsConfig</code></p></td></tr><tr><td><p>DEVICE_OS</p></td><td><p>Data Layer Variable</p></td><td><p>globalVars</p></td><td><p>RMT feat <code>analyticsConfig</code></p></td></tr><tr><td><p>HIT_TIMESTAMP</p></td><td><p>Data Layer Variable</p></td><td><p>journeyProps</p></td><td><p>toISOString()</p></td></tr><tr><td><p>SERVER_CITY</p></td><td><p>Data Layer Variable</p></td><td><p>journeyProps</p></td><td><p>RMT feat <code>analyticsConfig</code>requires KEY</p></td></tr><tr><td><p>SERVER_COUNTRY</p></td><td><p>Data Layer Variable</p></td><td><p>journeyProps</p></td><td><p>RMT feat <code>analyticsConfig</code>requires KEY</p></td></tr><tr><td><p>SERVER_HOSTNAME</p></td><td><p>Data Layer Variable</p></td><td><p>journeyProps</p></td><td><p>RMT feat <code>analyticsConfig</code>requires KEY</p></td></tr><tr><td><p>SERVER_IP</p></td><td><p>Data Layer Variable</p></td><td><p>journeyProps</p></td><td><p>RMT feat <code>analyticsConfig</code>requires KEY</p></td></tr><tr><td><p>SERVER_LOCATION</p></td><td><p>Data Layer Variable</p></td><td><p>journeyProps</p></td><td><p>RMT feat <code>analyticsConfig</code>requires KEY</p></td></tr><tr><td><p>SERVER_POSTAL</p></td><td><p>Data Layer Variable</p></td><td><p>journeyProps</p></td><td><p>RMT feat <code>analyticsConfig</code>requires KEY</p></td></tr><tr><td><p>SERVER_REGION</p></td><td><p>Data Layer Variable</p></td><td><p>journeyProps</p></td><td><p>RMT feat <code>analyticsConfig</code>requires KEY</p></td></tr><tr><td><p>SERVER_TIMEZONE</p></td><td><p>Data Layer Variable</p></td><td><p>journeyProps</p></td><td><p>RMT feat <code>analyticsConfig</code>requires KEY</p></td></tr></tbody></table>

<p><strong>Example:</strong> You can see below we take the value from the <code>dataLayer</code> by assigning <code>userProps.client_id</code> to the GTM <code>client_id</code> variable.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1685308173537/c7ec8dac-1665-46f9-aee5-a457a22e6ab5.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3>Folders ✅</h3>
<p>This is one example of how to organise your variables, you can arrange the folders however you like.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1685303474529/fb9ca8be-aeb2-4cbc-b67c-138479c1d100.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3>Triggers ✅</h3>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1685303376023/ce3b1956-e728-47b1-b4d1-97fe27a3a24f.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1685304328217/1867b93e-2696-40e6-92e6-d11109d67a82.png" alt="" style="display:block;margin:0 auto" />

<ol>
<li><p>Give your trigger a name and add it to a folder</p>
</li>
<li><p>Add the Event Name, the Trigger event name <strong>must match</strong> the name you add to the <code>dataLayer</code>. The event name can also use regex which can be useful for event names that have a key/s to match on.<br />I will not cover regex this time. A quick simple example in this case could be <code>I_*_ACCOUNT</code> the <code>*</code> will Trigger both <code>REGISTER</code> &amp; <code>LOGIN</code>.</p>
</li>
<li><p>That is it for Triggers.</p>
</li>
</ol>
<hr />
<h3>Tags ✅</h3>
<p>This is where it all comes together! Tags house your variables and triggers consolidating the event and data that is sent to Google Analytics.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1685307307517/829fa658-778c-4548-b463-8239cfe0f2c0.png" alt="" style="display:block;margin:0 auto" />

<p>Here the <strong>Parameter Name</strong> is what we will see in Google Analytics 4, and the value is the variable reassignment as seen above in the "Variables ✅" section.</p>
<p>This is where we decide what data should be assigned to what trigger/events.<br />If we want GTM can act as a filter, this means what is sent to GTM from React Marketing Tools does not always have to be sent through to GA4.</p>
<hr />
<h3>Versions ✅</h3>
<p>The version is very important because we don't want to mix <code>dev</code> data with <code>production</code> data. You will get one environment setup automatically <code>env-1</code>.</p>
<p>To add a new env we need to go to <code>Admin &gt; Environments</code> and then click the button in the top right "New".</p>
<ol>
<li><p>Give the env a name. Optionally add a description</p>
</li>
<li><p>Enable debugging</p>
</li>
<li><p>Add a destination URL; we will add a <code>dev</code> environment, so you can add something like <code>http://localhost:3000</code></p>
</li>
</ol>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1685310390975/04509243-5a7f-4705-ac3f-4cdd54b68103.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3>Workspace ✅</h3>
<p>Here is where we can check our setup before publishing to the respective environments.</p>
<p>NOTE: "<strong>Publish to Environment</strong>", initially I will publish to production <code>env-1</code> and then make a trivial change to republish to my development environment <code>env-3</code>.</p>
<hr />
<p>That is it!<br />We have now passed data from our App, through to Google Tag Manager, then on to Google Analytics 4. From here I will leave it to you.</p>
<p>If you want to check the data is arriving go to your production site open GA4 and go to <code>Reports &gt; Real Time</code>. You should see 1 or more visitors and here you can inspect the events, variables and data that arrive.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1685313679411/b404ce00-c054-4e36-ac24-92647994b76b.png" alt="" style="display:block;margin:0 auto" />

<p>Thanks for reading, if you have any questions or comments, please leave them below. ✌️</p>
]]></content:encoded></item><item><title><![CDATA[The cool thing about Redux...]]></title><description><![CDATA[Okay so maybe there is nothing "cool" about redux, but I think it is cool how you can use vanilla redux, createReducer and createSlice all in the same project if I want or if I have to.

Why use redux?
Well for me one thing I enjoy is the "separation...]]></description><link>https://bronz3beard.hashnode.dev/the-cool-thing-about-redux</link><guid isPermaLink="true">https://bronz3beard.hashnode.dev/the-cool-thing-about-redux</guid><category><![CDATA[React]]></category><category><![CDATA[Redux]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[redux-toolkit]]></category><category><![CDATA[react redux]]></category><dc:creator><![CDATA[Rory]]></dc:creator><pubDate>Wed, 10 May 2023 01:10:29 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/ZS67i1HLllo/upload/0ef0c8f4b68f18fc0a892d586269d178.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>Okay so maybe there is nothing "cool" about redux, but I think it is cool how you can use <strong>vanilla redux</strong>, <strong>createReducer</strong> and <strong>createSlice</strong> all in the same project if I want or if I have to.</p>
</blockquote>
<h1 id="heading-why-use-redux">Why use redux?</h1>
<p>Well for me one thing I enjoy is the "separation of concerns"(SC) when managing business logic and app state.<br />SC; is a case by case requirement, if not given a good amount of thought, it will mess up a project and have the reverse effect on Developer Experience(DX).</p>
<p>Another thing that can have an undesired effect on DX is redux, so make sure you really need it for your next incomplete side project 😅🫠🤣<br />If you decide "Yes I need it" then use what I am sharing below to get the most out of it, or at least to get things moving.</p>
<hr />
<h3 id="heading-redux-folder-structure-for-some-developers-might-go-something-like-this">redux folder structure for some developers might go something like this.</h3>
<pre><code class="lang-typescript">- redux
    - helpers
        # hooks.ts
        # reduxActions.ts
    - middleware
        # logger.ts
    - reducers
        # menuReducer.ts
   # store.ts
</code></pre>
<hr />
<h1 id="heading-vanilla-redux">Vanilla Redux</h1>
<h3 id="heading-actions">Actions</h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> EVENT_MAIN_MENU_ACTIVE = <span class="hljs-string">'EVENT_MAIN_MENU_ACTIVE'</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> EVENT_MAIN_MENU_HIDE = <span class="hljs-string">'EVENT_MAIN_MENU_HIDE'</span>
</code></pre>
<h3 id="heading-reducer">Reducer</h3>
<p>Yes... The warmth of a familiar bit of code!!</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// mainMenuReducer.ts</span>
<span class="hljs-keyword">import</span> {
  EVENT_MAIN_MENU_ACTIVE,
  EVENT_MAIN_MENU_HIDE,
} <span class="hljs-keyword">from</span> <span class="hljs-string">'../../helpers/reduxActions'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> MenuStoreState = {
  menu: MenuState
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> MenuState = {
  mainMenuVisible: <span class="hljs-built_in">boolean</span>
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> menuDefaultState: MenuState = {
  mainMenuVisible: <span class="hljs-literal">false</span>,
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> (
  state: MenuState = menuDefaultState,
  action: { <span class="hljs-keyword">type</span>: <span class="hljs-built_in">string</span> },
): <span class="hljs-function"><span class="hljs-params">MenuState</span> =&gt;</span> {
  <span class="hljs-keyword">switch</span> (action.type) {
    <span class="hljs-keyword">case</span> EVENT_MAIN_MENU_ACTIVE: {
      <span class="hljs-keyword">return</span> {
        ...state,
        mainMenuVisible: <span class="hljs-literal">true</span>,
      }
    }
    <span class="hljs-keyword">case</span> EVENT_MAIN_MENU_HIDE: {
      <span class="hljs-keyword">return</span> {
        ...state,
        mainMenuVisible: <span class="hljs-literal">false</span>,
      }
    }
    <span class="hljs-keyword">default</span>: {
      <span class="hljs-keyword">return</span> {
        ...state,
      }
    }
  }
}
</code></pre>
<h3 id="heading-usage">Usage</h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useCallback, MouseEvent } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> { useDispatch, useSelector } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-redux'</span>
<span class="hljs-keyword">import</span> {
  EVENT_MAIN_MENU_ACTIVE,
  EVENT_MAIN_MENU_HIDE,
} <span class="hljs-keyword">from</span> <span class="hljs-string">'../../redux/helpers/actionTypes'</span>
<span class="hljs-keyword">import</span> { MenuStoreState } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../redux/reducers/menuReducer'</span>

<span class="hljs-keyword">const</span> useMainMenu = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> showMainMenu = useSelector&lt;MenuStoreState&gt;(
    <span class="hljs-function"><span class="hljs-params">state</span> =&gt;</span> state.menu.mainMenuVisible,
  ) <span class="hljs-keyword">as</span> <span class="hljs-built_in">boolean</span>

  <span class="hljs-keyword">const</span> dispatch = useDispatch()

  <span class="hljs-keyword">const</span> handleMainMenuClick = useCallback(
    <span class="hljs-keyword">async</span> (event: MouseEvent&lt;HTMLButtonElement&gt;) =&gt; {
      event.preventDefault()
      event.stopPropagation()

      dispatch({
        showMainMenu,
        <span class="hljs-keyword">type</span>: !showMainMenu ? EVENT_MAIN_MENU_ACTIVE : EVENT_MAIN_MENU_HIDE,
      })
    },
    [showMainMenu, dispatch],
  )
...
</code></pre>
<p>And that is it, that is the code to manage opening and closing a drawer that will contain the main menu of your website, usually on a mobile device.</p>
<p>Yes, we still need to add a <strong><em>logger.ts</em></strong> &amp; <strong><em>store.ts</em></strong>, which is coming later.</p>
<hr />
<h1 id="heading-createreducer-redux">createReducer redux</h1>
<p>Nothing like using a well-formed hook to manage app state!</p>
<h3 id="heading-reducer-initial-state-and-actions">Reducer, Initial state and Actions.</h3>
<p>All are nicely co-located.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// authedUserReducer.ts</span>
<span class="hljs-keyword">import</span> { createAction, createReducer, PayloadAction } <span class="hljs-keyword">from</span> <span class="hljs-string">'@reduxjs/toolkit'</span>

<span class="hljs-keyword">type</span> UserState = {
  user: User | <span class="hljs-literal">undefined</span>
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> initialState: UserState = {
  user: <span class="hljs-literal">undefined</span>,
}
<span class="hljs-comment">// In this example this is also known as reduxActions.ts</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> userData = createAction(<span class="hljs-string">'USER_DATA'</span>, <span class="hljs-function">(<span class="hljs-params">user: User</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> { payload: user }
})

<span class="hljs-keyword">const</span> authedUserReducer = createReducer(initialState, <span class="hljs-function"><span class="hljs-params">builder</span> =&gt;</span> {
  builder.addCase(
    userData,
    <span class="hljs-function">(<span class="hljs-params">state, action</span>) =&gt;</span> {
      <span class="hljs-comment">// payload has types inferred because of the create action!</span>
      state.user = action.payload
    },
  )
})

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> authedUserReducer
</code></pre>
<p>Something pretty cool, in the above code inside the <code>(state, action)</code> function is that the data is mutable aka; <code>state: WritableDraft&lt;UserState&gt;</code> Do what you want with the data inside this function and no need to ...spread the data or return anything that is all taken care of 👍</p>
<h3 id="heading-usage-1">Usage</h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useEffect, useReducer } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> userReducer, { userData } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../redux/reducers/userReducer'</span>

<span class="hljs-keyword">type</span> UserData = {
  user: User | <span class="hljs-literal">undefined</span>
}

<span class="hljs-keyword">const</span> useGetUserData = (): <span class="hljs-function"><span class="hljs-params">UserData</span> =&gt;</span> {
  <span class="hljs-keyword">const</span> [state, dispatch] = useReducer(
    authedUserReducer,
    authedUserReducer.getInitialState(),
  )

  useEffect(
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getUserDetailsFromAuthOrSession</span>(<span class="hljs-params"></span>) </span>{
      <span class="hljs-keyword">const</span> setUserSession = <span class="hljs-keyword">async</span> () =&gt; {
        <span class="hljs-keyword">const</span> user: User = <span class="hljs-keyword">await</span> getUser()

        dispatch(userData(user))
      }

      setUserSession()
    },
    [dispatch],
  )

  <span class="hljs-keyword">return</span> {
    user: state.user,
  }
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> useGetUserData
</code></pre>
<p>Yep that's it, nothing else to write!</p>
<hr />
<h1 id="heading-createslice-redux">createSlice redux</h1>
<p><code>createSlice</code> has quite a bit of boilerplate code, but code usage is reduced (sorry that is unintended that pun...), so it depends on where you want to write your code, more code in usage or more boilerplate pick your poison.<br /><code>createSlice</code> just like <code>createReducer</code> nicely packaged in the same place, if that's what you like, easy to see all the code and the moving parts. It is also nice to have all the reducers in one place to see how it all works together.</p>
<p>Note:: <code>pageDataReducer.ts</code> &lt;- This name is misleading but rather than create another folder for <code>slices</code> I named it as a reducer, whatever you prefer in this case.</p>
<h3 id="heading-slice">Slice</h3>
<pre><code class="lang-typescript"><span class="hljs-comment">// pageDataReducer.ts</span>
<span class="hljs-keyword">import</span> { createAsyncThunk, createSlice, PayloadAction } <span class="hljs-keyword">from</span> <span class="hljs-string">'@reduxjs/toolkit'</span>
<span class="hljs-keyword">import</span> { optimisticLoadArrayResource } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../../utils/helpers/staticAssets'</span>
<span class="hljs-keyword">import</span> { Subset } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../../utils/valueCheckers'</span>

<span class="hljs-keyword">type</span> PageState = {
  resources: Subset&lt;Resource&gt;[]
}

<span class="hljs-keyword">const</span> initialState: PageState = { resources: [] }

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> fetchPageData = createAsyncThunk(
  <span class="hljs-string">'page/fetchResources'</span>,
  <span class="hljs-keyword">async</span> (): <span class="hljs-built_in">Promise</span>&lt;Subset&lt;Resource&gt;[]&gt; =&gt; {
    <span class="hljs-comment">// Make the backend call.</span>
    <span class="hljs-comment">// <span class="hljs-doctag">NOTE:</span>: This will run when user is unauthed but only an empty array will be returned,</span>
    <span class="hljs-comment">// This is why we have the optimisticLoadArrayResource fallback here and when the thunk fetch is "pending"</span>
    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> getResources(ResourceTypes.resource)

    <span class="hljs-keyword">return</span> response ?? optimisticLoadArrayResource
  },
)

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> createResource = (
  resources: Subset&lt;Resource&gt;[],
): Subset&lt;Resource&gt; =&gt; {
<span class="hljs-comment">// This is a great place to cherry pick what you need and define a new type.</span>
  <span class="hljs-keyword">return</span> resources
}

<span class="hljs-keyword">const</span> pageDataSlice = createSlice({
  name: <span class="hljs-string">'page'</span>, <span class="hljs-comment">// &lt;- this is the name used in the store.ts</span>
  initialState,
  reducers: {
    createPageData: <span class="hljs-function">(<span class="hljs-params">state, action: PayloadAction&lt;Subset&lt;Resource&gt;&gt;</span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> newResource = createResource(action.payload)
      state.resources = [newResource, ...state.resources]
    },
    updatePageData: <span class="hljs-function">(<span class="hljs-params">state, action: PayloadAction&lt;Subset&lt;Resource&gt;&gt;</span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> newResource = createResource(action.payload)
      <span class="hljs-keyword">const</span> index = state.resources.findIndex(
        <span class="hljs-function"><span class="hljs-params">resource</span> =&gt;</span> resource.id === newResource.id,
      )
      state.resources.splice(index, <span class="hljs-number">1</span>)

      state.resources = [newResource, ...state.resources]
    },
    deletePageData: <span class="hljs-function">(<span class="hljs-params">state, action: PayloadAction&lt;ResourceId&gt;</span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> deleteId = action.payload
      <span class="hljs-keyword">const</span> newResourceList = state.resources.filter(
        <span class="hljs-function"><span class="hljs-params">resource</span> =&gt;</span> resource.id !== deleteId,
      )

      state.resources = [...newResourceList]
    },
  },
  extraReducers: <span class="hljs-function"><span class="hljs-params">builder</span> =&gt;</span> {
    builder.addCase(fetchPageData.pending, <span class="hljs-function"><span class="hljs-params">state</span> =&gt;</span> {
      state.resources = optimisticLoadArrayResource
    })
    builder.addCase(
      fetchPageData.fulfilled,
      <span class="hljs-function">(<span class="hljs-params">state, action: PayloadAction&lt;Subset&lt;Resource&gt;[]&gt;</span>) =&gt;</span> {
        <span class="hljs-keyword">const</span> resources = action.payload
        state.resources = resources
      },
    )
  },
})

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> pageDataReducer = pageDataSlice.reducer
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> { createPageData, updatePageData, deletePageData } =
  pageDataSlice.actions

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> pageDataSlice
</code></pre>
<p>What are some great things about <code>createReducer</code> &amp; <code>createSlice</code> ?</p>
<ul>
<li><p>First of all and most importantly you get <strong>inferred types</strong>!!<br />  "correctly infer the action type in the reducer based on the provided action creator. It's particularly useful for working with actions produced by <code>createAction</code> and <code>createAsyncThunk</code>"</p>
</li>
<li><p>You can add <code>extraReducers</code> these functions let you interact or work with actions outside of the slice that houses the regular <code>reducers</code>.<br />  In the above example the actions for the <code>fetchPageData</code> fetching statuses are handled with <code>extraReducers</code><br />  "<em>One of the key concepts of Redux is that each slice reducer "owns" its slice of state, and that many slice reducers can independently respond to the same action type.</em> <code>extraReducers</code> <em>allows</em> <code>createSlice</code> <em>to respond to other action types besides the types it has generated.</em>"</p>
</li>
<li><p>Co-location of code export what you want as you need it, reusability</p>
</li>
</ul>
<h3 id="heading-usage-2">Usage</h3>
<p>The good old "wrapper" hook in a hook...</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { TypedUseSelectorHook, useDispatch, useSelector } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-redux'</span>
<span class="hljs-keyword">import</span> { ApplicationDispatch, ApplicationState } <span class="hljs-keyword">from</span> <span class="hljs-string">'../store'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> useReduxUseSelector: TypedUseSelectorHook&lt;ApplicationState&gt; =
  useSelector

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> useReduxUseDispatch: <span class="hljs-function">() =&gt;</span> ApplicationDispatch = useDispatch
</code></pre>
<p>Now just dispatch your reducers in functions or as needed in any files you need to create, read, update, or delete data.</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">const</span> dispatch = useReduxUseDispatch()

  dispatch(createPageData(response))
  dispatch(fetchPageData())
  dispatch(updatePageData({ ...currentData, newData }))
  dispatch(deletePageData(payload.deleteNodeId))
</code></pre>
<p>For me, the above usage is just the same as importing and using a hook like <code>useSWR</code> or <code>reactQuery</code> throughout your code. One thing that can be done here, you can't do with <code>useSWR</code> or <code>reactQuery</code> hooks are fetching data before the app has mounted. 👇</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { fetchPageData } <span class="hljs-keyword">from</span> <span class="hljs-string">'./redux/reducers/pageDataReducer'</span>

<span class="hljs-keyword">const</span> container = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'root'</span>)

store.dispatch(fetchPageData())

<span class="hljs-comment">// eslint-disable-next-line @typescript-eslint/no-non-null-assertion</span>
<span class="hljs-keyword">const</span> root = createRoot(container!)

root.render(
  &lt;StrictMode&gt;
    &lt;Provider store={store}&gt;
      &lt;ErrorBoundary&gt;
        &lt;App /&gt;
      &lt;/ErrorBoundary&gt;
    &lt;/Provider&gt;
  &lt;/StrictMode&gt;,
)
</code></pre>
<hr />
<h1 id="heading-finally-the-logger-and-the-store">Finally the Logger and the Store.</h1>
<h3 id="heading-logger">Logger</h3>
<p>Pretty standard right not much to say here, also I am pretty sure there is a built-in option for this in the toolkit, but I am not sure, I'll leave you to find that out 🙌</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Middleware, MiddlewareAPI, Dispatch } <span class="hljs-keyword">from</span> <span class="hljs-string">'redux'</span>

<span class="hljs-keyword">const</span> loggerMiddleware: Middleware =
  <span class="hljs-function">(<span class="hljs-params">{ getState }: MiddlewareAPI</span>) =&gt;</span>
  <span class="hljs-comment">// eslint-disable-next-line consistent-return</span>
  (next: Dispatch) =&gt;
  <span class="hljs-function"><span class="hljs-params">action</span> =&gt;</span> {
    <span class="hljs-keyword">const</span> { <span class="hljs-keyword">type</span> } = action

    <span class="hljs-keyword">if</span> (process.env.NODE_ENV === <span class="hljs-string">'development'</span>) {
      <span class="hljs-built_in">console</span>.group(
        <span class="hljs-string">'🚀 ~ file: logger.js ~ line 3 ~ logger ~ action.type'</span>,
        <span class="hljs-keyword">type</span>,
      )
      <span class="hljs-built_in">console</span>.info(
        <span class="hljs-string">'🚀 ~ file: logger.ts ~ line 13 ~ logger -&gt; will dispatch'</span>,
        action,
      )

      <span class="hljs-keyword">const</span> result = next(action)
      <span class="hljs-built_in">console</span>.info(
        <span class="hljs-string">'🚀 ~ file: logger.js ~ line 8 ~ logger ~ store.getState() -&gt; state after dispatch'</span>,
        getState(),
      )
      <span class="hljs-built_in">console</span>.groupEnd()

      <span class="hljs-comment">// This will likely be the action itself, unless</span>
      <span class="hljs-comment">// a middleware further in chain changed it.</span>
      <span class="hljs-keyword">return</span> result
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-keyword">const</span> result = next(action)
      <span class="hljs-keyword">return</span> result
    }
  }

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> loggerMiddleware
</code></pre>
<h3 id="heading-store">Store</h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { configureStore } <span class="hljs-keyword">from</span> <span class="hljs-string">'@reduxjs/toolkit'</span>
<span class="hljs-keyword">import</span> { applyMiddleware } <span class="hljs-keyword">from</span> <span class="hljs-string">'redux'</span>
<span class="hljs-keyword">import</span> thunkMiddleware <span class="hljs-keyword">from</span> <span class="hljs-string">'redux-thunk'</span>
<span class="hljs-keyword">import</span> loggerMiddleware <span class="hljs-keyword">from</span> <span class="hljs-string">'./middleware/logger'</span>

<span class="hljs-keyword">import</span> { pageDataReducer } <span class="hljs-keyword">from</span> <span class="hljs-string">'./reducers/pageDataReducer'</span>
<span class="hljs-keyword">import</span> authedUserReducer <span class="hljs-keyword">from</span> <span class="hljs-string">'./reducers/authedUserReducer'</span>
<span class="hljs-keyword">import</span> menuReducer <span class="hljs-keyword">from</span> <span class="hljs-string">'./reducers/menuReducer'</span>

<span class="hljs-keyword">const</span> middlewareEnhancer = applyMiddleware(loggerMiddleware, thunkMiddleware)

<span class="hljs-keyword">const</span> store = configureStore({
  devTools: process.env.NODE_ENV !== <span class="hljs-string">'production'</span>,
  reducer: {
    menu: menuReducer, <span class="hljs-comment">// vanilla redux</span>
    page: pageDataReducer, <span class="hljs-comment">// createSlice redux</span>
    auth: userReducer, <span class="hljs-comment">// createReducer redux</span>
  },
  enhancers: [middlewareEnhancer],
})

<span class="hljs-comment">// These below types are a big part of why we have the custom hook wrappers useReduxUseSelector &amp; useReduxUseDispatch</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> ApplicationState = ReturnType&lt;<span class="hljs-keyword">typeof</span> store.getState&gt;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> ApplicationDispatch = <span class="hljs-keyword">typeof</span> store.dispatch

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> store
</code></pre>
<hr />
<h1 id="heading-signoff">Signoff</h1>
<p>That is it! This app is now using 3 different implementations of redux and you wouldn't even know without looking at the code 😅.</p>
<p>Redux is a powerful tool for managing app state in complex applications. It provides a structured approach that makes it easier to reason about.</p>
<p>Thank you for taking the time to read this post. If you have any questions or comments, please feel free to leave them below.</p>
<hr />
<h1 id="heading-solid">SOLID</h1>
<p>To help you sleep easier 😅🤯😎🫠 this code is <strong>S.O.L.I.D</strong></p>
<ol>
<li><p>Single Responsibility Principle (SRP): <strong>Each function and module has a single responsibility</strong> and does not take on additional responsibilities that would violate this principle.</p>
</li>
<li><p>Open/Closed Principle (OCP): The code is open for extension but closed for modification. It achieves this by using abstraction (e.g., <strong>asyncThunkAction</strong>) to separate different concerns, allowing the code to be extensible without modifying existing code.</p>
</li>
<li><p>Liskov Substitution Principle (LSP): The code implements the LSP by using interfaces (e.g., <strong>PayloadAction</strong>) to ensure that derived classes can be substituted for their base classes without affecting the correctness of the program.</p>
</li>
<li><p>Interface Segregation Principle (ISP): The code follows the ISP by using <strong>interfaces</strong> to define only what is needed for a particular module, preventing unnecessary coupling between modules.</p>
</li>
<li><p>Dependency Inversion Principle (DIP): The code follows the DIP by depending on abstractions (e.g., <strong>optimisticLoadArrayResource</strong>) instead of concrete implementations, allowing for greater flexibility in the implementation of the code.</p>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[Drag & Drop multiple images & upload with NextJs, AWS s3 and TailwindCSS]]></title><description><![CDATA[ℹ️ I recently did some work on this for a project I am working on, and I don't know if it was just me, but it was not the most enjoyable experience trying to figure out the correct configuration for a frontend file upload to AWS s3 bucket.I wanted to...]]></description><link>https://bronz3beard.hashnode.dev/drag-drop-multiple-images-upload-with-nextjs-aws-s3-and-tailwindcss</link><guid isPermaLink="true">https://bronz3beard.hashnode.dev/drag-drop-multiple-images-upload-with-nextjs-aws-s3-and-tailwindcss</guid><category><![CDATA[Drag & Drop]]></category><category><![CDATA[Amazon S3]]></category><category><![CDATA[File Upload]]></category><category><![CDATA[Next.js]]></category><category><![CDATA[Tailwind CSS]]></category><dc:creator><![CDATA[Rory]]></dc:creator><pubDate>Fri, 30 Dec 2022 01:48:11 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1672305758589/5aee97bd-8361-46fe-adb0-914df580ca21.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>ℹ️ I recently did some work on this for a project I am working on, and I don't know if it was just me, but it was not the most enjoyable experience trying to figure out the correct configuration for a frontend file upload to AWS s3 bucket.<br />I wanted to share my solution, hopefully, it can help others 😃.</p>
<p>I also want to acknowledge that there are plenty of dependencies available to do all this work for you around file uploads, but considering the file upload API doesn't change very often, it might be a better option to manage this yourself.</p>
</blockquote>
<h1 id="heading-aws-s3-bucket-setup">AWS s3 bucket setup 📐</h1>
<p>Setting up an account and logging in as a <code>root user</code> I will leave it up to you to figure out (I can answer questions about that setup if a comment is left about it 👍).<br />Once logged in, add <code>s3</code> to the search in the top left, beside the <code>services</code> waffle menu, the top result "S3" will be what you click on. (It is useful to click on the star)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672286349757/1a73e26f-3acc-457d-a34e-60fcd37c8ec3.png" alt class="image--center mx-auto" /></p>
<p>Now you want to create a bucket</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672286528406/c64233e4-9440-4cbf-b80c-6f0228e723d5.png" alt class="image--center mx-auto" /></p>
<p>I have left all the suggested default settings as they are, except for the <em>"Block Public Access setting for this bucket"</em> this setting I have deactivated and consented to.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672286637582/d694c738-64ed-4516-befa-37f47efa812d.png" alt class="image--center mx-auto" /></p>
<p>Now scroll down to the bottom and click <em>"Create Bucket".</em><br />That covers the bucket creation config, I am sure per your requirements, you can make the changes you need. For this example, default settings are good enough.</p>
<hr />
<h2 id="heading-bucket-permissions">Bucket Permissions 🔐</h2>
<p>You should see your bucket in the "Buckets" table list now, click on it, and now click on the <em>"Permissions"</em> tab.</p>
<p>update the following sections to what you see below;</p>
<h3 id="heading-bucket-policy">Bucket Policy</h3>
<pre><code class="lang-json">{
    <span class="hljs-attr">"Version"</span>: <span class="hljs-string">"2012-10-17"</span>,
    <span class="hljs-attr">"Statement"</span>: [
        {
            <span class="hljs-attr">"Sid"</span>: <span class="hljs-string">"PublicListGet"</span>,
            <span class="hljs-attr">"Effect"</span>: <span class="hljs-string">"Allow"</span>,
            <span class="hljs-attr">"Principal"</span>: <span class="hljs-string">"*"</span>,
            <span class="hljs-attr">"Action"</span>: [
                <span class="hljs-string">"s3:List*"</span>, <span class="hljs-comment">// could be "s3:ListBucket"</span>
                <span class="hljs-string">"s3:Get*"</span> <span class="hljs-comment">// could be "s3:GetObject"</span>
            ],
            <span class="hljs-attr">"Resource"</span>: <span class="hljs-string">"arn:aws:s3:::view-my-property-au"</span>
        },
        {
            <span class="hljs-attr">"Sid"</span>: <span class="hljs-string">"PutFiles"</span>,
            <span class="hljs-attr">"Effect"</span>: <span class="hljs-string">"Allow"</span>,
            <span class="hljs-attr">"Principal"</span>: <span class="hljs-string">"*"</span>,
            <span class="hljs-attr">"Action"</span>: [
                <span class="hljs-string">"s3:PutObject"</span>,
                <span class="hljs-string">"s3:GetObject"</span>,
                <span class="hljs-string">"s3:DeleteObject"</span>
            ],
            <span class="hljs-attr">"Resource"</span>: <span class="hljs-string">"arn:aws:s3:::view-my-property-au/*"</span>
        }
    ]
}
</code></pre>
<p>In the above policy, we have two <code>Statements</code>.</p>
<ol>
<li><p>The first is for allowing access to view our images, either a list or individual images.</p>
</li>
<li><p>The second is allowing access to some <code>CRUD</code> actions see the <code>prefix</code> for which actions we are allowing.</p>
</li>
<li><p>The <code>Resource</code> each of the actions has an effect on is slightly different, this is a small distinction to show in this case, that you cannot <code>PUT</code> files without a name or subfolder, you cannot delete all files in a bucket and you can <code>GET</code> individual files.</p>
</li>
<li><p>The <code>Bucket Policy Elements</code> each has a description that can better provide you with some context on the <code>JSON</code> structure: <a target="_blank" href="https://github.com/awsdocs/amazon-s3-developer-guide/blob/master/doc_source/access-policy-language-overview.md#policy-language-overview">Policy Language Overview</a> &amp; <a target="_blank" href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-policy-language-overview.html">Here</a></p>
</li>
</ol>
<blockquote>
<p><strong>Note</strong>: There are no <em>"</em><code>Conditions</code><em>"</em> for the Statement "<code>Actions"</code>.<br />Conditional Actions can only be applied to one action, here we have an array of actions. In the case of <code>Delete</code> &amp; <code>Put</code> it is safe to assume that these actions would only be accessible after authentication, and this authentication would only grant access to the images the authed user owns, therefore it is safe to leave these actions without conditions on the s3 bucket. (That is my assumption, happy to hear otherwise, or of a better option).</p>
</blockquote>
<hr />
<h3 id="heading-cross-origin-resource-sharing-cors">Cross-origin resource sharing (CORS)</h3>
<p>As stated in the cors section of the bucket; "The CORS configuration, written in JSON, defines a way for client web applications that are loaded in one domain to interact with resources in a different domain". This section feels pretty straightforward to me, but please leave a comment if you would like more info, or <a target="_blank" href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/enabling-cors-examples.html">see here for some examples</a></p>
<pre><code class="lang-json">[
    {
        <span class="hljs-attr">"AllowedHeaders"</span>: [
            <span class="hljs-string">"*"</span> <span class="hljs-comment">// could be defined if needed</span>
        ],
        <span class="hljs-attr">"AllowedMethods"</span>: [
            <span class="hljs-string">"HEAD"</span>,
            <span class="hljs-string">"PUT"</span>,
            <span class="hljs-string">"POST"</span>,
            <span class="hljs-string">"GET"</span>
        ],
        <span class="hljs-attr">"AllowedOrigins"</span>: [
            <span class="hljs-string">"*"</span> <span class="hljs-comment">// should be defined once in production</span>
        ],
        <span class="hljs-attr">"ExposeHeaders"</span>: [
            <span class="hljs-string">"ETag"</span>,
            <span class="hljs-string">"x-amz-meta-custom-header"</span> <span class="hljs-comment">// not always needed</span>
        ]
    }
]
</code></pre>
<hr />
<h1 id="heading-aws-s3-client-setup">AWS s3 client setup</h1>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { S3Client } <span class="hljs-keyword">from</span> <span class="hljs-string">'@aws-sdk/client-s3'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> s3Client = <span class="hljs-keyword">new</span> S3Client({
  region: process.env.REGION,
  credentials: {
    accessKeyId: process.env.ACCESS_KEY,
    secretAccessKey: process.env.SECRET_KEY,
  },
})
</code></pre>
<ul>
<li><p>Install dependency</p>
<ul>
<li><p><code>yarn add @aws-sdk/client-s3</code></p>
</li>
<li><p><code>npm install @aws-sdk/client-s3</code></p>
</li>
</ul>
</li>
<li><p>Add your keys to the <code>.env</code> file.</p>
<p>  (see below how to set up a user and get these keys)</p>
<ul>
<li><p>ACCESS_KEY</p>
</li>
<li><p>SECRET_KEY</p>
</li>
</ul>
</li>
</ul>
<hr />
<h1 id="heading-iam-user-programmatic-setup">IAM user (Programmatic) setup 🙈</h1>
<ul>
<li><p>The following steps are how you can get the above KEYs</p>
<ul>
<li><p>Add <code>iam</code> to the search in the top left, beside the <code>services</code> waffle menu, the top result "IAM" will be what you click on.</p>
</li>
<li><p>In the menu on the left click "users"</p>
</li>
<li><p>Click "Add users"</p>
</li>
<li><p>Add the User name and select "Access key - Programmatic access"</p>
<ul>
<li>More info reference: <a target="_blank" href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users.html">IAM users</a></li>
</ul>
</li>
</ul>
</li>
</ul>
<p>    <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672297747456/b44ee1d0-d5ac-4e80-9efe-39e812754e6c.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>Go to the next section, section 2</p>
</li>
<li><p>Here you can either create a group with the correct permissions or add the correct permissions direct to the user</p>
<ul>
<li><p>If you have created a group with the correct permissions select it, Or</p>
</li>
<li><p>Search for <code>AmazonS3FullAccess</code> and select it to add to the user</p>
<ul>
<li>If you have not set up a group you will only see a place to search for permissions. What you see will look similar to the image just below.</li>
</ul>
</li>
<li><p>Then go to the next optional step.</p>
</li>
</ul>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672304025452/5d16b990-871d-40eb-ac70-f6f9e3d03c16.png" alt class="image--center mx-auto" /></p>
<p>At the end of this setup, you will be presented with the <code>Access key ID</code> and the <code>Secret access key</code> as listed above, copy them into your <code>.env, .env.local...</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672304261950/159f78b5-591a-4c21-a441-3c1be692c4a5.png" alt class="image--center mx-auto" /></p>
<hr />
<h1 id="heading-dragandampdrop-upload-components">Drag&amp;Drop upload components 🫳</h1>
<h2 id="heading-uploadfile-usage">UploadFile usage</h2>
<pre><code class="lang-typescript">&lt;UploadFile&lt;ListingDetailsData&gt; <span class="hljs-comment">// add type to make UploadFile more generic</span>
  {...{
    formDataFields, <span class="hljs-comment">// some state that is used for adding files to subfolder</span>
    currentFiles, <span class="hljs-comment">// image urls</span>
    setCurrentFiles, <span class="hljs-comment">// state setter for image urls</span>
    hasNewFiles, <span class="hljs-comment">// optional boolean used for updating loader and or user feedback</span>
    setHasNewFiles, <span class="hljs-comment">// state setter for hasNewFiles boolean</span>
  }}
/&gt;
</code></pre>
<h2 id="heading-uploadfile-component">UploadFile component 🗂️</h2>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> React, { useState, Dispatch, SetStateAction } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> dynamic <span class="hljs-keyword">from</span> <span class="hljs-string">'next/dynamic'</span>
<span class="hljs-keyword">import</span> { endPoint } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../utils/endPoint'</span>
<span class="hljs-keyword">import</span> { handleUploadFiles } <span class="hljs-keyword">from</span> <span class="hljs-string">'./fileUploadFunction'</span>
<span class="hljs-keyword">import</span> imageResize <span class="hljs-keyword">from</span> <span class="hljs-string">'./imageResize'</span>
<span class="hljs-keyword">import</span> useWindowSizeTracker <span class="hljs-keyword">from</span> <span class="hljs-string">'../../hooks/useWindowSizeTracker'</span> <span class="hljs-comment">// a hook that keeps track of the window dimensions. Window size tracking could be done with TailwindCSS.</span>

<span class="hljs-keyword">const</span> FlexGridLayout = dynamic(<span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'../common/flexGridLayout'</span>)) <span class="hljs-comment">// just a wrapper for flex which can be used as is or passed some TailwindCSS class styling.</span>
<span class="hljs-keyword">const</span> DragAndDrop = dynamic(<span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./dragAndDrop'</span>))
<span class="hljs-keyword">const</span> BASE_URL = process.env.BACKEND_URL || endPoint

<span class="hljs-keyword">interface</span> UploadFileProps&lt;T&gt; {
  formDataFields: T
  currentFiles: Record&lt;<span class="hljs-built_in">string</span>, <span class="hljs-built_in">string</span>&gt;[] <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span>: should be better typed</span>
  setCurrentFiles?: Dispatch&lt;SetStateAction&lt;Record&lt;<span class="hljs-built_in">string</span>, <span class="hljs-built_in">string</span>&gt;[]&gt;&gt;
  hasNewFiles?: <span class="hljs-built_in">boolean</span>
  setHasNewFiles?: Dispatch&lt;SetStateAction&lt;<span class="hljs-built_in">boolean</span>&gt;&gt;
}

<span class="hljs-keyword">const</span> UploadFile = &lt;T,&gt;<span class="hljs-function">(<span class="hljs-params">{
  formDataFields,
  currentFiles,
  setCurrentFiles,
  // hasNewFiles,
  // setHasNewFiles,
}: UploadFileProps&lt;T&gt;</span>) =&gt;</span> {
  <span class="hljs-comment">// ** OPTIONAL STATE you can pass to DragAndDrop if needed. **</span>
  <span class="hljs-comment">// uncomment and pass to DragAndDrop if you want to use.</span>

  <span class="hljs-comment">// const [error, setError] = useState&lt;unknown&gt;(null)</span>
  <span class="hljs-comment">// const [uploadSize, setUploadSize] = useState&lt;string&gt;('0')</span>
  <span class="hljs-comment">// const [uploadFiles, setUploadFiles] = useState&lt;Record&lt;string, string&gt;&gt;({})</span>
  <span class="hljs-comment">// const [uploadProgress, setUploadProgress] = useState&lt;string&gt;('')</span>
  <span class="hljs-comment">// const [uploadRunning, setUploadRunning] = useState&lt;boolean&gt;(false)</span>

  <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span>: Maybe just do all of this with TailwindCSS</span>
  <span class="hljs-keyword">const</span> { windowSize } = useWindowSizeTracker() 

  <span class="hljs-keyword">const</span> handleDrop = <span class="hljs-function">(<span class="hljs-params">files: FileList</span>) =&gt;</span> {
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; files.length; i += <span class="hljs-number">1</span>) {
      <span class="hljs-keyword">const</span> file = files[i]
      <span class="hljs-keyword">if</span> (!file.name) {
        <span class="hljs-keyword">return</span>
      }

      <span class="hljs-keyword">const</span> fileSize = file.size
      <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span>: add a function for file size check handling</span>
      <span class="hljs-keyword">const</span> fileSizeCheck = <span class="hljs-built_in">Math</span>.round(fileSize / <span class="hljs-number">1024</span>)

      <span class="hljs-keyword">if</span> (fileSizeCheck &lt; <span class="hljs-number">1024</span>) {
        handleUploadFiles&lt;T&gt;(
          file,
          <span class="hljs-string">`<span class="hljs-subst">${BASE_URL}</span>/s3-upload`</span>,
          formDataFields,
        ).then(<span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> {
          setCurrentFiles(<span class="hljs-function"><span class="hljs-params">prevState</span> =&gt;</span> [...prevState, { imageUrl: response }])
        })
      } <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">const</span> image = imageResize&lt;T&gt;(file, formDataFields)

        setCurrentFiles(<span class="hljs-function"><span class="hljs-params">prevState</span> =&gt;</span> [...prevState, { imageUrl: image.url }])
      }
    }
  }

  <span class="hljs-keyword">return</span> (
    &lt;&gt;
      &lt;div className=<span class="hljs-string">"w-full h-full flex text-left flex-col px-4"</span>&gt;
        &lt;DragAndDrop
          {...{
            windowSize,
            handleDrop,
          }}
        &gt;
          &lt;FlexGridLayout
            childContainerClass=<span class="hljs-string">"w-12"</span>
            containerClass=<span class="hljs-string">"w-full absolute h-28 top-52 left-1/2 transform -translate-x-1/2 flex flex-wrap items-center overflow-y-scroll mb-4 space-x-2 z-50"</span>
          &gt;
            {currentFiles?.length &gt; <span class="hljs-number">0</span> &amp;&amp;
              currentFiles.map(<span class="hljs-function">(<span class="hljs-params">{ imageUrl }: Record&lt;<span class="hljs-built_in">string</span>, <span class="hljs-built_in">string</span>&gt;</span>) =&gt;</span> {
                <span class="hljs-keyword">return</span> (
                  &lt;img
                    src={imageUrl}
                    key={imageUrl}
                    alt=<span class="hljs-string">"upload-preview-image"</span>
                    className=<span class="hljs-string">"w-full object-contain rounded"</span>
                  /&gt;
                )
              })}
          &lt;/FlexGridLayout&gt;
        &lt;/DragAndDrop&gt;
      &lt;/div&gt;
    &lt;/&gt;
  )
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> UploadFile
</code></pre>
<ul>
<li><p><code>FileList</code> is an object but has some <code>iterator</code> properties like <code>length</code>. This means you will have to use the synchronous OG <code>for loop</code> 👍</p>
</li>
<li><p><code>fileSizeCheck</code> here we want to know if each image is greater than <code>1MB</code> or not if so we will resize it to be smaller.</p>
</li>
<li><p><code>handleUploadFiles&lt;T&gt;</code></p>
<ul>
<li><p><code>file</code> = image file</p>
</li>
<li><p><code>`${BASE_URL}/s3-upload` </code> = path to Nextjs API handler</p>
</li>
<li><p><code>formDataFields</code> = object with some state in it for constructing an image path in <code>s3 bucket</code>. (or whatever you need it for)</p>
</li>
<li><p><code>imageResize&lt;T&gt;</code> uses the <code>handleUploadFiles&lt;T&gt;</code> function under the hood, once you have finished resizing your image the same args are passed to the <code>handleUploadFiles&lt;T&gt;</code>.</p>
</li>
<li><p><code>handleUploadFiles&lt;T&gt;</code> expects a <code>&lt;File&gt;</code> type not a <code>base64</code>string, but you can change this if you prefer.</p>
</li>
</ul>
</li>
</ul>
<hr />
<h2 id="heading-draganddrop-component">DragAndDrop component</h2>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> React, { useState, useEffect, useRef, ReactNode } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> ActiveAnimation <span class="hljs-keyword">from</span> <span class="hljs-string">'../../activeAnimation'</span> <span class="hljs-comment">// just a loader indicating to user image is being uploaded</span>
<span class="hljs-keyword">import</span> useEventListener <span class="hljs-keyword">from</span> <span class="hljs-string">'../../../hooks/useEventListener'</span>
<span class="hljs-keyword">import</span> { WindowSizeTracker } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../../hooks/useWindowSizeTracker'</span>

<span class="hljs-keyword">type</span> DragAndDropProps = {
  children: ReactNode <span class="hljs-comment">// This could be made optional children?:</span>
  windowSize: WindowSizeTracker <span class="hljs-comment">// { width: number, height: number }</span>
  handleDrop: <span class="hljs-function">(<span class="hljs-params">fileList: FileList</span>) =&gt;</span> <span class="hljs-built_in">void</span>
  uploadSize?: <span class="hljs-built_in">string</span>
  uploadRunning?: <span class="hljs-built_in">boolean</span>
  uploadProgress?: <span class="hljs-built_in">string</span>
}

<span class="hljs-keyword">const</span> DragAndDrop = <span class="hljs-function">(<span class="hljs-params">{
  children,
  handleDrop,
  uploadProgress,
  uploadRunning,
  uploadSize,
  windowSize, 
}: DragAndDropProps</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> [dragging, setDragging] = useState&lt;<span class="hljs-built_in">boolean</span>&gt;(<span class="hljs-literal">false</span>) <span class="hljs-comment">// this state is used to provide user with feedback on dragging "safe" area.</span>

  <span class="hljs-keyword">const</span> dropRef = useRef&lt;HTMLDivElement&gt;(<span class="hljs-literal">null</span>)
  <span class="hljs-keyword">const</span> selectRef = useRef&lt;HTMLInputElement&gt;(<span class="hljs-literal">null</span>)

  <span class="hljs-keyword">const</span> handleDrag = <span class="hljs-function">(<span class="hljs-params">event: DragEvent</span>) =&gt;</span> {
    event.preventDefault()
    event.stopPropagation() <span class="hljs-comment">// stops the event from being propagated through parent and child elements. So we can do what we want!</span>
  }

  <span class="hljs-keyword">const</span> handleDragIn = <span class="hljs-function">(<span class="hljs-params">event: DragEvent</span>) =&gt;</span> {
    event.preventDefault()
    event.stopPropagation()

    <span class="hljs-comment">// conditional chaining can be used here if you prefer</span>
    <span class="hljs-comment">// event.dataTransfer?.items?.length &gt; 0</span>
    <span class="hljs-keyword">if</span> (
      event.dataTransfer !== <span class="hljs-literal">undefined</span> &amp;&amp;
      event.dataTransfer.items &amp;&amp;
      event.dataTransfer.items.length &gt; <span class="hljs-number">0</span>
    ) {
      setDragging(<span class="hljs-literal">true</span>) <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span>:consider a better name for this state</span>
    }
  }

  <span class="hljs-keyword">const</span> handleDragOut = <span class="hljs-function">(<span class="hljs-params">event: DragEvent</span>) =&gt;</span> {
    event.preventDefault()
    event.stopPropagation()

    setDragging(<span class="hljs-literal">false</span>)
  }

  <span class="hljs-keyword">const</span> handleDropItem = <span class="hljs-function">(<span class="hljs-params">event: DragEvent</span>) =&gt;</span> {
    event.preventDefault()
    event.stopPropagation()
    <span class="hljs-keyword">const</span> target = event.target <span class="hljs-keyword">as</span> HTMLInputElement


    <span class="hljs-keyword">if</span> (
      event.dataTransfer === <span class="hljs-literal">undefined</span> &amp;&amp;
      target.files &amp;&amp;
      target.files.length &gt; <span class="hljs-number">0</span>
    ) {
      <span class="hljs-keyword">const</span> fileList: FileList = target.files
      handleDrop(fileList)
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (
      event.dataTransfer !== <span class="hljs-literal">undefined</span> &amp;&amp;
      event.dataTransfer.files &amp;&amp;
      event.dataTransfer.files.length &gt; <span class="hljs-number">0</span>
    ) {
      handleDrop(event.dataTransfer.files)
    }
  }

  useEventListener(<span class="hljs-string">'dragenter'</span>, handleDragIn, dropRef)
  useEventListener(<span class="hljs-string">'dragleave'</span>, handleDragOut, dropRef)
  useEventListener(<span class="hljs-string">'dragover'</span>, handleDrag, dropRef)
  useEventListener(<span class="hljs-string">'drop'</span>, handleDropItem, dropRef)
  useEventListener(<span class="hljs-string">'change'</span>, handleDropItem, selectRef)

  <span class="hljs-keyword">return</span> (
    &lt;div className=<span class="hljs-string">"top-0 w-full h-80 flex relative"</span> ref={dropRef}&gt;
      &lt;div className=<span class="hljs-string">"h-full right-0 bottom-0 left-0 text-center absolute border-dashed border-2 border-gray-800 z-40 bg-gray-100"</span>&gt;
        {dragging &amp;&amp; !uploadProgress &amp;&amp; (
          &lt;div className=<span class="hljs-string">"top-1/2 text-theme-grey text-4xl right-0 left-0 text-center absolute"</span>&gt;
            &lt;div&gt;
              drop here
              &lt;span
                role=<span class="hljs-string">"img"</span>
                className=<span class="hljs-string">"ml-2"</span>
                aria-label=<span class="hljs-string">"Beaming Face with Smiling Eyes"</span>
              &gt;
                😁
              &lt;/span&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        )}
        {!dragging &amp;&amp; !uploadProgress ? (
          &lt;div className=<span class="hljs-string">"top-1/2 text-theme-grey text-4xl right-0 left-0 text-center absolute"</span>&gt;
            Drag and Drop
          &lt;/div&gt;
        ) : (
          uploadProgress &amp;&amp; (
            &lt;div className=<span class="hljs-string">"top-1/2 text-theme-grey text-4xl right-0 left-0 text-center absolute"</span>&gt;
              {uploadProgress}
              {uploadSize &amp;&amp; (
                &lt;div&gt;<span class="hljs-built_in">Error</span>: image/s must be below <span class="hljs-number">1</span>Mb or <span class="hljs-number">1028</span>Kb <span class="hljs-keyword">in</span> size&lt;/div&gt;
              )}
              {uploadRunning &amp;&amp; (
                &lt;ActiveAnimation active=<span class="hljs-string">""</span> backgroundColour=<span class="hljs-string">""</span> /&gt;
              )}
            &lt;/div&gt;
          )
        )}
        &lt;label
          htmlFor=<span class="hljs-string">"file-upload"</span>
          className=<span class="hljs-string">"flex flex-row items-center mt-16 mx-auto w-32 h-16 bg-contrast-colour hover:bg-gray-500 active:bg-gray-500 text-center text-white border border-1 border-gray-200 border-opacity-10 font-base rounded-lg cursor-pointer"</span>
        &gt;
          &lt;span className=<span class="hljs-string">"w-full text-center"</span>&gt;
            {windowSize.width &gt;= <span class="hljs-number">800</span> ? <span class="hljs-string">'Upload'</span> : <span class="hljs-string">'Take photo'</span>}
          &lt;/span&gt;
          &lt;input
            multiple
            <span class="hljs-keyword">type</span>=<span class="hljs-string">"file"</span>
            id=<span class="hljs-string">"file-upload"</span>
            accept=<span class="hljs-string">"image/*"</span>
            className=<span class="hljs-string">"hidden"</span>
            ref={selectRef}
          /&gt;
        &lt;/label&gt;
      &lt;/div&gt;
      {children}
    &lt;/div&gt;
  )
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> DragAndDrop
</code></pre>
<ul>
<li><p><code>dragging</code> state is used to indicate to the user they can drop the files in the "safe" area.</p>
</li>
<li><p><code>target.files</code> Vs <code>event.dataTransfer.files</code> see this <a target="_blank" href="https://stackoverflow.com/questions/44842247/event-datatransfer-files-vs-event-datatransfer-items">StackOverflow discussion</a></p>
</li>
<li><p><code>handleDrop</code> is a <code>prop</code> passed down to handle uploading to <code>s3 bucket</code></p>
</li>
<li><p><code>useEventListener</code> see <a target="_blank" href="https://blog.heyrory.com/add-custom-context-menu-to-your-website#heading-the-event-listener-hook">Add custom context menu to your website with React &amp; TypeScript</a> for an example of this hook</p>
</li>
<li><p><code>windowSize.width</code> is used to show different text when <code>DragAndDrop</code> is being used on a mobile,</p>
</li>
<li><p>all event listeners are listening on the respective <code>ref</code></p>
</li>
<li><p><code>input type file</code> takes care of uploading on <code>mobile</code> device and the native mobile image select or image capture will take over in this case</p>
<ul>
<li><p>We have limited the <code>input</code> to any <code>image/*</code> type, this is untested with Apple's image type <code>HEIC</code></p>
</li>
<li><p>Must be selected from the album, this includes capturing images with the camera</p>
<ul>
<li><code>&lt;input type="file" accept="image/*"&gt;</code></li>
</ul>
</li>
<li><p>Must be captured from the camera, this is only allowing you to capture images with the camera no photo album access.</p>
<ul>
<li><code>&lt;input type="file" accept="image/*" capture=""&gt;</code></li>
</ul>
</li>
<li><p>The <code>input</code> is <code>hidden</code> with a <code>htmlFor</code> reference on the label so we can customize the style on the browser's default "Browse..." input. The <code>selectRef</code> handles the onChange functionality for us.</p>
</li>
<li><p><code>children</code> in this case is just a thumbnail preview of all uploaded images.</p>
</li>
</ul>
</li>
</ul>
<h3 id="heading-before-dragging">Before dragging</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672295428764/c514cf55-3799-4b4a-91a0-61be63bb51e4.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-after-dragging">After dragging</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672295441860/17691853-cb03-45ed-8104-23d721d0ee24.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-after-the-image-is-uploaded-to-the-s3-bucket">After the image is uploaded to the s3 bucket</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672307176027/cf6c1840-bfa4-4f4f-baef-83ca42522d06.png" alt class="image--center mx-auto" /></p>
<hr />
<h1 id="heading-the-handleuploadfiles-function">The handleUploadFiles function</h1>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { singlePostRequest } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../pages/api/requests'</span> <span class="hljs-comment">// just a wrapper for axios.post()</span>
<span class="hljs-keyword">import</span> { convertGenericTypeToListingDetails } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../forms/addListing/helpers/listingDetailsValueChecker'</span> <span class="hljs-comment">// some type checking on generic &lt;Type&gt; used in this component</span>
<span class="hljs-keyword">import</span> {
  removeSpecialCharacters,
  replaceWhiteSpace,
} <span class="hljs-keyword">from</span> <span class="hljs-string">'../../utils/helpers/commonFunctions'</span> <span class="hljs-comment">// some regex helper functions to strip out unwanted string items</span>

<span class="hljs-keyword">type</span> FileResponse = {
  url: <span class="hljs-built_in">string</span>
  fields: Record&lt;<span class="hljs-built_in">string</span>, <span class="hljs-built_in">string</span>&gt; <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span>: better type this</span>
}

<span class="hljs-keyword">type</span> FileRequest = {
  fileType: <span class="hljs-built_in">string</span>
  fileName: <span class="hljs-built_in">string</span>
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> handleUploadFiles = <span class="hljs-keyword">async</span> &lt;T&gt;<span class="hljs-function">(<span class="hljs-params">
  file: File,
  apiUrl: <span class="hljs-built_in">string</span>,
  formDataFields?: T, // <span class="hljs-keyword">type</span> is checked before being used here
</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> fileName = <span class="hljs-built_in">encodeURIComponent</span>(file.name)
  <span class="hljs-keyword">const</span> fileType = <span class="hljs-built_in">encodeURIComponent</span>(file.type)

  <span class="hljs-keyword">const</span> details = convertGenericTypeToListingDetails(formDataFields)
  <span class="hljs-keyword">const</span> fileTitleFormatted = <span class="hljs-built_in">encodeURIComponent</span>(
    replaceWhiteSpace(removeSpecialCharacters(details.fileTitle, <span class="hljs-string">' '</span>), <span class="hljs-string">'-'</span>),
  )
  <span class="hljs-comment">// get presigned put url to send file to s3 bucket</span>
  <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span>: probably should move this presigned POST to its own little function and add better typing for it.</span>
  <span class="hljs-keyword">const</span> { data } = <span class="hljs-keyword">await</span> singlePostRequest&lt;FileRequest, FileResponse&gt;(
    <span class="hljs-string">`<span class="hljs-subst">${apiUrl}</span>?file=<span class="hljs-subst">${fileName}</span>&amp;fileType=<span class="hljs-subst">${fileType}</span>&amp;listingId=<span class="hljs-subst">${details.fileId}</span>&amp;listingTitle=<span class="hljs-subst">${fileTitleFormatted}</span>`</span>,
    {
      fileType,
      fileName,
    },
  )
  <span class="hljs-keyword">const</span> { url, fields } = data

  <span class="hljs-keyword">const</span> formData = <span class="hljs-keyword">new</span> FormData()
  <span class="hljs-comment">// <span class="hljs-doctag">NOTE:</span>: the order of which these fields are appended to the FormData object are important</span>
  <span class="hljs-comment">// the file: File must be the list attribute in the formData object.</span>
  <span class="hljs-built_in">Object</span>.entries({ ...fields, file }).forEach(<span class="hljs-function">(<span class="hljs-params">[key, value]</span>) =&gt;</span> {
    formData.append(key, value)
  })

  <span class="hljs-comment">// <span class="hljs-doctag">NOTE:</span>: this post request is successful if and only if it returns a 204 status.</span>
  <span class="hljs-keyword">const</span> uploadResponse = <span class="hljs-keyword">await</span> singlePostRequest&lt;FormData, unknown&gt;(
    <span class="hljs-string">`<span class="hljs-subst">${url}</span>`</span>,
    formData,
  )

  <span class="hljs-keyword">if</span> (uploadResponse) {
    <span class="hljs-comment">// return image url path used in your s3 bucket of image just uploaded this is used for thumbnail preview.</span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">`<span class="hljs-subst">${url}</span><span class="hljs-subst">${details.fileId}</span>-<span class="hljs-subst">${fileTitleFormatted}</span>/<span class="hljs-subst">${fileName}</span>`</span>
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span>: add a toast or some feedback here and logging here</span>
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Upload failed.'</span>)
  }
}
</code></pre>
<ul>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/PresignedUrlUploadObject.html">Get Presigned URL for uploading files from the frontend to the s3 bucket</a></p>
</li>
<li><p><code>data</code></p>
<ul>
<li><p><code>url</code> = The values used in the URL used to get the presigned URL must match, see below for how these fields are used in the upload config for presigned URL.</p>
</li>
<li><p><code>fields</code> = These fields must be sent in the payload body of the <code>post request</code> that is using the presigned URL, along with your file.</p>
<ul>
<li>I recommend <code>console.log('presigned fields -&gt;', fields)</code> the fields to get a better understanding of what this object consists of.</li>
</ul>
</li>
<li><p>The file must be the last item on the payload body object, or in this case the <code>formData</code> object</p>
<ul>
<li><code>Object.entries({ ...fields, file })</code></li>
</ul>
</li>
</ul>
</li>
</ul>
<hr />
<h1 id="heading-upload-config-for-presigned-url-api-handler">Upload config for presigned URL, API handler</h1>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { NextApiRequest, NextApiResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">'next'</span>
<span class="hljs-keyword">import</span> { createPresignedPost } <span class="hljs-keyword">from</span> <span class="hljs-string">'@aws-sdk/s3-presigned-post'</span>
<span class="hljs-keyword">import</span> { s3Client } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../../lib/awsS3Client'</span>

<span class="hljs-keyword">const</span> uploadHandler = <span class="hljs-keyword">async</span> (
  req: NextApiRequest,
  res: NextApiResponse,
): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; =&gt; {
  <span class="hljs-keyword">const</span> {
    query: { file, fileType, fileId, fileTitle },
    method,
  } = req

  <span class="hljs-keyword">switch</span> (method) {
    <span class="hljs-keyword">case</span> <span class="hljs-string">'POST'</span>:
      <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> post = <span class="hljs-keyword">await</span> createPresignedPost(s3Client, {
          Bucket: process.env.S3_BUCKET_NAME,
          <span class="hljs-comment">// Key: must match POST url for file upload</span>
          Key: <span class="hljs-string">`<span class="hljs-subst">${fileId}</span>-<span class="hljs-subst">${fileTitle}</span>/<span class="hljs-subst">${file}</span>`</span>,
          Fields: {
            ContentType: fileType <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>,
          },
          Expires: <span class="hljs-number">600</span>, <span class="hljs-comment">// seconds</span>
          Conditions: [
            { bucket: process.env.S3_BUCKET_NAME },
            [<span class="hljs-string">'starts-with'</span>, <span class="hljs-string">'$ContentType'</span>, <span class="hljs-string">'image/'</span>],
          ],
        })

        res.status(<span class="hljs-number">200</span>).json(post)
      } <span class="hljs-keyword">catch</span> (error: <span class="hljs-built_in">any</span>) {
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'🚀 ~ file: index.ts ~ line 45 ~ error'</span>, error)
        <span class="hljs-comment">// throw error for errorWrapper to handle?</span>
        <span class="hljs-comment">// send to some error logging service?</span>
        res.json(error)
        <span class="hljs-comment">// throw error</span>
      }
    <span class="hljs-keyword">default</span>:
      res.status(<span class="hljs-number">405</span>).end(<span class="hljs-string">`Method <span class="hljs-subst">${method}</span> Not Allowed`</span>)
  }
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> uploadHandler
</code></pre>
<ul>
<li><p>You will need to install <code>@aws-sdk/s3-presigned-post</code></p>
<ul>
<li><p><code>npm install @aws-sdk/s3-presigned-post</code></p>
</li>
<li><p><code>yarn add @aws-sdk/s3-presigned-post</code></p>
</li>
</ul>
</li>
<li><p><code>createPresignedPost</code> handles all the complexity of getting the presigned URL for file upload, you can see it can be configured with options passed in.</p>
</li>
<li><p><code>Key:</code> = This matches the folder and file path used in the <code>s3 bucket</code> which goes after the presigned URL.</p>
</li>
<li><p><code>Conditions:</code> = Checking the bucket is correct and matches the <code>Bucket:</code> and also check the file type is allowed.</p>
</li>
<li><p>This API handler only allows <code>POST</code> method</p>
</li>
<li><p>You will need to add the following key values to your <code>.env</code> file</p>
<ul>
<li><p><code>S3_BUCKET_NAME</code></p>
</li>
<li><p><code>BACKEND_URL</code> this most likely will be <code>/api/</code> or something very similar.</p>
</li>
</ul>
</li>
</ul>
<hr />
<p>There is a lot to go through in this blog, yell out if something doesn't quite make sense or you see some smelly code! At this point you can now drag and drop multiple image files and have them upload to a sub folder directory in your AWS s3 bucket.</p>
<p>Any questions leave me a comment 🙌 or déjame un comentario en español 🙌<br />(I am working on my espanol)</p>
]]></content:encoded></item><item><title><![CDATA[Adding custom location markers to mapbox]]></title><description><![CDATA[The first thing I want to point out is, working with mapbox is very enjoyable, the docs are great, and there are tons of resources to learn from and work with.
This is a small part of my experience working with mapbox, here is the little web app Info...]]></description><link>https://bronz3beard.hashnode.dev/adding-custom-location-markers-to-mapbox</link><guid isPermaLink="true">https://bronz3beard.hashnode.dev/adding-custom-location-markers-to-mapbox</guid><category><![CDATA[mapbox]]></category><category><![CDATA[React]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[mui]]></category><category><![CDATA[features]]></category><dc:creator><![CDATA[Rory]]></dc:creator><pubDate>Sat, 10 Dec 2022 08:39:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1670659479604/uYA1sVGLK.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>The first thing I want to point out is, working with <strong>mapbox</strong> is very enjoyable, the docs are great, and there are tons of resources to learn from and work with.</p>
<p>This is a small part of my experience working with <strong>mapbox,</strong> here is the little web app Info Meliquina I started, which also incorporates some of the example code below.</p>
<p>As this is a small app I did not use typescript, but you can find all the typings in the code and the docs 👍<code>node_modules/@types/mapbox-gl/index.d.ts</code></p>
</blockquote>
<hr />
<h2 id="heading-setup-and-initialise-mapboxgl">Setup and initialise mapboxGL</h2>
<p>We want to make our <code>mapboxGL</code> <code>globally</code> available, so at the top of our file, we add the below snippet of code, just as a placeholder for when <code>mapboxGL</code> is initialised.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> <span class="hljs-built_in">window</span>.mapboxGL === <span class="hljs-string">'undefined'</span>) {
  <span class="hljs-built_in">window</span>.mapboxGL = {}
}
</code></pre>
<p>Below you see a <code>useEffect</code> which only runs when the <code>client</code> aka <code>window</code> is ready and available.<br />This is where we can initialise our map and make it accessible throughout our app. Our <code>dependency</code> array <code>[]</code> of the <code>useEffect</code> is empty, this is because we only want to initialise our map when the app is first mounted.</p>
<pre><code class="lang-javascript">  useEffect(<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">initializeProceduralGL</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> map = mapRef.current

    <span class="hljs-keyword">if</span> (map) {
      <span class="hljs-keyword">const</span> mapboxGL = initMap(map)

      <span class="hljs-built_in">window</span>.mapboxGL = mapboxGL
    }
  }, [])
</code></pre>
<p>Here you will also see we are passing a <code>div node ref</code> to the <code>initMap</code> function.<br />This <code>div</code> is apart of the <code>return</code> statement of the <code>jsx component</code></p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">height:</span> '<span class="hljs-attr">100</span>%', <span class="hljs-attr">width:</span> '<span class="hljs-attr">100</span>%' }} <span class="hljs-attr">ref</span>=<span class="hljs-string">{mapRef}</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p>Once <code>initMap</code> is finished initialising our new mapboxGL object, it will return the new <code>mapboxGL</code> object and then this is attached to the <code>window</code> so we can access the same instance of mapboxGL <code>globally</code> in our app.</p>
<blockquote>
<p>You will also notice I am not using an <code>anonymous function</code> inside the <code>useEffect</code>, I think it is a much better developer experience to provide the function with a name;</p>
<ul>
<li><p>It will be noticed much more than a comment above <code>useEffect(() =&gt;..</code></p>
</li>
<li><p>It is nice to be able to read the function name and know, you don't need to look into the code to figure out what's happening with the side effect.</p>
</li>
<li><p>Makes the <code>useEffect</code> searchable 🙌</p>
</li>
</ul>
</blockquote>
<hr />
<h2 id="heading-the-initmap-function">The initMap function</h2>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> mapboxgl <span class="hljs-keyword">from</span> <span class="hljs-string">'mapbox-gl'</span>
<span class="hljs-keyword">import</span> { markerPointFeatures } <span class="hljs-keyword">from</span> <span class="hljs-string">'./constants'</span>
<span class="hljs-keyword">import</span> { mapBoxLoadPointImages } <span class="hljs-keyword">from</span> <span class="hljs-string">'./helpers/mapBoxLoadPointImages'</span>

<span class="hljs-keyword">const</span> MAPBOX_APIKEY = <span class="hljs-keyword">import</span>.meta.env.VITE_CUSTOM_MAPBOX_TOKEN

<span class="hljs-keyword">const</span> initMap = <span class="hljs-keyword">async</span> map =&gt; {
  mapboxgl.accessToken = MAPBOX_APIKEY

  <span class="hljs-keyword">let</span> mapboxGL = <span class="hljs-keyword">new</span> mapboxgl.Map({
    <span class="hljs-attr">container</span>: map, <span class="hljs-comment">// container ID</span>
    <span class="hljs-attr">center</span>: [longitude, latitude], <span class="hljs-comment">// starting position [lng, lat]</span>
    <span class="hljs-attr">zoom</span>: <span class="hljs-number">10</span>, <span class="hljs-comment">// starting zoom</span>
    <span class="hljs-attr">style</span>: <span class="hljs-string">'mapbox://styles/bronz3beard/your-custom-styled-map-key'</span>, <span class="hljs-comment">// style URL or style object</span>
    <span class="hljs-attr">pitch</span>: <span class="hljs-number">70</span>, <span class="hljs-comment">// camera angle</span>
    <span class="hljs-attr">bearing</span>: <span class="hljs-number">120</span>, <span class="hljs-comment">// camera direction measured clockwise as an angle from true north</span>
  })

  mapboxGL.on(<span class="hljs-string">'load'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
<span class="hljs-comment">// Add custom markers</span>
    markerPointFeatures.forEach(<span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> {
<span class="hljs-comment">// value = Feature</span>
      mapBoxLoadPointImages(
        value.source,
        value.iconImage,
        value.iconPath,
        value.features,
        mapboxGL,
      )
    })
<span class="hljs-comment">// Set the default atmosphere style</span>
    mapboxGL.setFog({
      <span class="hljs-attr">range</span>: [<span class="hljs-number">0</span>, <span class="hljs-number">20</span>],
    })
  })

  <span class="hljs-keyword">return</span> mapboxGL
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> initMap
</code></pre>
<h2 id="heading-initmap-breakdown">initMap breakdown.</h2>
<ol>
<li><p>I used <a target="_blank" href="https://vitejs.dev/guide/">vite</a> to create this app</p>
</li>
<li><p><code>MAPBOX_APIKEY</code> - <a target="_blank" href="https://docs.mapbox.com/help/getting-started/access-tokens/">how to set up a mapbox api key</a></p>
</li>
<li><p>initial <code>mapboxgl.Map</code> <a target="_blank" href="https://docs.mapbox.com/mapbox-gl-js/api/map/">object attributes</a></p>
<ul>
<li><p>Breakdown</p>
<ul>
<li><p><code>container:</code> value should be either <code>HTMLElement | string</code></p>
</li>
<li><p><code>center:</code> the order is important here <code>[longitude, latitude]</code></p>
</li>
<li><p><code>zoom:</code> The initial zoom level of the map defaults to 0 the range is <code>0-24</code></p>
</li>
<li><p><code>pitch:</code> The initial pitch (tilt) of the map defaults to 0, measured in degrees away from the plane of the screen <code>0-85</code></p>
</li>
<li><p><code>bearing:</code> camera direction measured clockwise as an angle from true north defaults to 0</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p>There is a custom-styled map being used.</p>
<ul>
<li>To learn more about how to do this, the docs have a <a target="_blank" href="https://docs.mapbox.com/help/tutorials/create-a-custom-style/">great walkthrough</a> which at the end also walks through adding your new map to your app.</li>
</ul>
</li>
<li><p><code>mapboxGL.on('load', function ()</code> this is where we are adding our custom markers to the map</p>
</li>
<li><p><code>mapboxGL.setFog</code> <a target="_blank" href="https://docs.mapbox.com/mapbox-gl-js/guides/globe/#atmosphere-styling">atmosphere styling</a></p>
</li>
</ol>
<hr />
<h2 id="heading-the-mapboxloadpointimages-function">The mapBoxLoadPointImages function</h2>
<blockquote>
<p>This is a little helper function I wrote to help a developer (me) maintain a single focus when working on the code while dividing up the responsibilities of the code. Also helps keep my files light and easier to read.</p>
</blockquote>
<p>This function accepts all the values that make up a Feature as you can see a little further down 👍</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> mapBoxLoadPointImages = <span class="hljs-function">(<span class="hljs-params">
  source,
  iconImage,
  iconPath,
  features,
  mapboxGL,
</span>) =&gt;</span> {
    mapboxGL.loadImage(iconPath, <span class="hljs-function">(<span class="hljs-params">error, image</span>) =&gt;</span> {
<span class="hljs-comment">// if the image is already on loaded remove it, this is to ensure there is alwaays a fresh feature set on the map.</span>
      <span class="hljs-keyword">if</span> (mapboxGL.hasImage(iconImage)) {
        mapboxGL.removeImage(iconImage)
      }
      <span class="hljs-keyword">if</span> (error) {
<span class="hljs-comment">// I want this to work every time or not at all, there are other ways you might want to handle an error here, logging.</span>
        <span class="hljs-keyword">throw</span> error
      }
<span class="hljs-comment">/* 
add the name of the image(iconImage) and the image itself. 
iconImage: string,
image: HTMLImageElement
       | ArrayBufferView
       | { width: number; height: number; data: Uint8Array | Uint8ClampedArray }
       | ImageData
       | ImageBitmap,
*/</span>
      mapboxGL.addImage(iconImage, image)
    })

<span class="hljs-comment">// Add a GeoJSON source with 2 points can be 3 but in this case it is just 2</span>
<span class="hljs-comment">/*
type: 'geojson' = GeoJSONSourceRaw -&gt; GeoJSONObject -&gt; Geometry -&gt; Point -&gt; {
        type: 'Point';
        coordinates: Position;
    } -&gt; Position = number[]; // [number, number]
*/</span>
    mapboxGL.addSource(source, {
      <span class="hljs-attr">type</span>: <span class="hljs-string">'geojson'</span>,
      <span class="hljs-attr">data</span>: {
        <span class="hljs-attr">type</span>: <span class="hljs-string">'FeatureCollection'</span>,
        features,
      },
    })

<span class="hljs-comment">// Add a symbol layer allowing us to hook into node attrubites and customise the values</span>
<span class="hljs-comment">// This gives the layer a chance to initialize gl resources and register event listeners. </span>
    mapboxGL.addLayer({
      <span class="hljs-attr">id</span>: source,
      <span class="hljs-attr">interactive</span>: <span class="hljs-literal">true</span>,
      <span class="hljs-attr">type</span>: <span class="hljs-string">'symbol'</span>,
      <span class="hljs-attr">source</span>: source,
      <span class="hljs-attr">layout</span>: {
        <span class="hljs-string">'icon-image'</span>: iconImage,
<span class="hljs-comment">// get the title name from the source's "title" property</span>
        <span class="hljs-string">'text-field'</span>: [<span class="hljs-string">'get'</span>, <span class="hljs-string">'title'</span>], <span class="hljs-comment">// text to use for title</span>
        <span class="hljs-string">'text-font'</span>: [<span class="hljs-string">'Open Sans Semibold'</span>, <span class="hljs-string">'Arial Unicode MS Bold'</span>],
        <span class="hljs-string">'text-offset'</span>: [<span class="hljs-number">0</span>, <span class="hljs-number">1.25</span>], <span class="hljs-comment">// text spacing and location from point icon</span>
        <span class="hljs-string">'text-anchor'</span>: <span class="hljs-string">'top'</span>, <span class="hljs-comment">// text location relative to point icon</span>
      },
    })

    <span class="hljs-keyword">if</span> (iconPath) {
<span class="hljs-comment">// update the mouse on hover over an icon</span>
      mapboxGL.on(<span class="hljs-string">'mouseover'</span>, source, <span class="hljs-function"><span class="hljs-params">event</span> =&gt;</span> {
        <span class="hljs-keyword">const</span> features = mapboxGL.queryRenderedFeatures(event.point)
      })

      mapboxGL.on(<span class="hljs-string">'mouseleave'</span>, source, <span class="hljs-function"><span class="hljs-params">event</span> =&gt;</span> {
        mapboxGL.getCanvas().style.cursor = <span class="hljs-string">''</span>
      })
    }
  }
}
</code></pre>
<h2 id="heading-mapboxloadpointimages-breakdown">mapBoxLoadPointImages breakdown</h2>
<blockquote>
<ul>
<li><p><code>loadImage</code> loads the image from an external domain,</p>
</li>
<li><p><code>addImage</code> add the image to the style as an icon,</p>
</li>
<li><p><code>addSource</code> add the data source containing one or more point features</p>
</li>
<li><p><code>addLayer</code> create a new symbol layer that uses the icons to represent the point data and instructs the client to "draw" the image for each point in the data source.</p>
</li>
</ul>
<p>The above breakdown is a summary of the docs found <a target="_blank" href="https://docs.mapbox.com/mapbox-gl-js/example/add-image/">here</a>.</p>
</blockquote>
<hr />
<h2 id="heading-markerpointfeatures">markerPointFeatures</h2>
<blockquote>
<p><code>ℹ markerPointFeatures</code> is an array of <code>features</code>to display on the map.</p>
</blockquote>
<h3 id="heading-feature-example">Feature example</h3>
<pre><code class="lang-javascript">  {
    <span class="hljs-attr">source</span>: <span class="hljs-string">'Food'</span>,
    <span class="hljs-attr">iconImage</span>: <span class="hljs-string">'food-marker'</span>,
<span class="hljs-comment">// icon png just in the public folder</span>
    <span class="hljs-attr">iconPath</span>: <span class="hljs-string">'/map-icons/food-32.png'</span>,
    <span class="hljs-attr">features</span>: [...foodFeatures],
  },
</code></pre>
<h3 id="heading-features-example">Features example</h3>
<pre><code class="lang-javascript"><span class="hljs-comment">// foodFeatures</span>
[{
    <span class="hljs-attr">type</span>: <span class="hljs-string">'Feature'</span>,
    <span class="hljs-attr">properties</span>: {
      <span class="hljs-attr">title</span>: <span class="hljs-string">'This is the title givien to this particular marker'</span>,
      <span class="hljs-attr">locationType</span>: <span class="hljs-string">'Food'</span>,
      <span class="hljs-attr">description</span>: <span class="hljs-string">'A helpful description of the marker point'</span>,
<span class="hljs-comment">// you can add any key: value you think you might need here </span>
      ...
    },
    <span class="hljs-attr">geometry</span>: {
<span class="hljs-comment">//lng, lat</span>
      <span class="hljs-attr">coordinates</span>:  [<span class="hljs-number">-71.24808117629131</span>, <span class="hljs-number">-40.386091124703896</span>],
      <span class="hljs-attr">type</span>: <span class="hljs-string">'Point'</span>,
    },
  },
  { ... },
]
</code></pre>
<h2 id="heading-feature-breakdown">Feature breakdown</h2>
<p>Features are a simple <code>JSON object</code> defined by the <code>geometry</code> and <code>properties</code> attributes along with a <code>type</code></p>
<ul>
<li><p>The outer feature <code>object</code>, is an object I use to build out features by what source they are "feature grouping", which means in this case "Food" icons are all the same.</p>
</li>
<li><p>Everything in the features array is what <code>mapboxGl</code> needs to add an icon to a specific location on the map, along with some "metadata" that can be added to properties, you can add anything you want to properties.</p>
</li>
<li><p><code>properties:</code> "<strong><em>This can contain any JSON object</em></strong>*. Some examples of common properties, often known as metadata, include title and description*". *</p>
<ul>
<li>*source is google search top result.</li>
</ul>
</li>
</ul>
<hr />
<h2 id="heading-accessing-the-properties-metadata">Accessing the properties metadata</h2>
<blockquote>
<p>Below code snippet is a truncated view of how you can access the <code>properties</code> you add to your custom feature point markers. You have to add an event listener which is built into mapboxGL -&gt; <code>mapboxGL.on('click',</code></p>
</blockquote>
<p>You might want to access the <code>properties</code> <code>metadata</code> to display somewhere when the icon is clicked on, to log it to an analytics platform or to change it, it is really up to you what you do with it inside this <code>onclick</code> event listener.</p>
<pre><code class="lang-javascript">  useEffect(
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">listenForFeatureIconClick</span>(<span class="hljs-params"></span>) </span>{
      <span class="hljs-keyword">const</span> featureIconClick = <span class="hljs-keyword">async</span> () =&gt; {
        <span class="hljs-keyword">const</span> mapboxGL = <span class="hljs-keyword">await</span> <span class="hljs-built_in">window</span>.mapboxGL
<span class="hljs-comment">// a simple check for existance of mapboxGL and if the object has some values in it already.</span>
        <span class="hljs-keyword">if</span> (mapboxGL &amp;&amp; objectHasAttributes(mapboxGL)) {
          mapboxGL.on(<span class="hljs-string">'click'</span>, <span class="hljs-function"><span class="hljs-params">event</span> =&gt;</span> {
            <span class="hljs-keyword">const</span> features = mapboxGL.queryRenderedFeatures(event.point)

            <span class="hljs-keyword">const</span> feature = features[<span class="hljs-number">0</span>]
            ...
            const title = feature.properties.title
            ...

    }

    featureIconClick()
}, [<span class="hljs-built_in">window</span>.mapboxGL])
</code></pre>
<hr />
<p>This is just the tip of the iceberg with the kind of fun you can have building out map features. Check out <a target="_blank" href="https://www.infomeliquina.com/">Info Meliquina</a> if you see anything you like, let me know! I can write up another blog about what you see in the app.<br />I am looking to build out the app in the future and add more features to the map and overall UI 💪</p>
<p>Till next time ✌️</p>
]]></content:encoded></item><item><title><![CDATA[Scaffolding & boilerplate code generation with Plop]]></title><description><![CDATA[I write this blog knowing that right now everyone is fanning out on AI (Generative Pre-trained Transformer 3) for any automation around code, and that is great! If you would prefer to look into this topic more, I suggest starting with #openGPT on Twi...]]></description><link>https://bronz3beard.hashnode.dev/scaffolding-boilerplate-code-generation-with-plop</link><guid isPermaLink="true">https://bronz3beard.hashnode.dev/scaffolding-boilerplate-code-generation-with-plop</guid><category><![CDATA[plop]]></category><category><![CDATA[React]]></category><category><![CDATA[automation]]></category><category><![CDATA[boilerplate]]></category><dc:creator><![CDATA[Rory]]></dc:creator><pubDate>Mon, 05 Dec 2022 00:46:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1670199914348/ZsKrZA0kD.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>I write this blog knowing that right now everyone is fanning out on AI (<strong>Generative Pre-trained Transformer 3</strong>) for any automation around code, and that is great! If you would prefer to look into this topic more, I suggest starting with <strong><em>#openGPT</em></strong> on <strong>Twitter</strong> or <a target="_blank" href="https://beta.openai.com/docs/guides/code/introduction">OpenAI</a> Turn comments into code &amp; <a target="_blank" href="https://gabetao.hashnode.dev/building-a-front-end-sandbox-for-openais-codex">API example</a>.</p>
<p><strong>Plop</strong> - Looks to be quite popular for React file generation and is better suited to smaller file code generation tasks, But it can be used to generate entire apps.</p>
<p>Other considerations</p>
<ul>
<li><p><a target="_blank" href="https://yeoman.io/">Yeoman</a></p>
</li>
<li><p><a target="_blank" href="https://www.hygen.io/">Hygen</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/NitzanHen/agrippa">Agrippa</a></p>
</li>
<li><p><a target="_blank" href="https://backstage.io/docs/features/software-templates/software-templates-index">Backstage</a></p>
</li>
</ul>
<p><strong>Yeoman</strong> looks better suited to generating entire apps but can also generate files and is cross-cutting so your backend and your frontend can use the same code generator.<br /><strong>Yeoman</strong> would be my next recommendation if <strong>Plop</strong> doesn't quite fit your needs.👍</p>
<p>Let me know if you would like to know more about the above-mentioned considerations, I can write another blog about them. 🙌</p>
</blockquote>
<h1 id="heading-things-to-consider-before-deciding-on-any-automated-code-generation">Things to consider before deciding on any automated code generation.</h1>
<p>At what level do you want to set conventions and maintain code generation? App folder level or the file level?</p>
<ul>
<li><p>Generating an entire app, all or part of scaffolding is required to get developing quickly, but would also be used infrequently.</p>
</li>
<li><p>Files/folders in existing apps File code generation will have a high frequency of use for feature work and adding to existing apps, which could add the highest value.</p>
</li>
</ul>
<blockquote>
<p>Consider your current codebase, which provides the most value?</p>
<p>Does the answer lend itself to maintaining conventions &amp; patterns for app folder scaffolding, file boilerplate or maybe both?</p>
<p><strong>Frequent use is not always a great indication of the high value of auto-generating code. Through frequent implementation, consistency is higher, but if it is the same implementation over and over, we should automate it.<br />Infrequent implementation can be less consistent, meaning we should lean on a template for consistency.</strong></p>
<p><strong>My recommendation;</strong> When starting with maintaining automated code generation templates, start at the <strong>file level</strong>. It is more forgiving, quicker to implement and easier to update 💪</p>
</blockquote>
<p>The below examples only show how to generate new code, not how to update existing code.</p>
<hr />
<h1 id="heading-plop-simple-setup">Plop simple setup</h1>
<h3 id="heading-packagejson">package.json</h3>
<pre><code class="lang-json">{
    <span class="hljs-attr">"name"</span>: <span class="hljs-string">"plopCodeGen"</span>,
    <span class="hljs-attr">"version"</span>: <span class="hljs-string">"1.0.0"</span>,
    <span class="hljs-attr">"description"</span>: <span class="hljs-string">"collection of templates for generating file boilerplate or app skeletons"</span>,
    <span class="hljs-attr">"main"</span>: <span class="hljs-string">"plopfile.js"</span>,
    <span class="hljs-attr">"author"</span>: <span class="hljs-string">"me@hello.com"</span>,
    <span class="hljs-attr">"private"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"scripts"</span>: {
        <span class="hljs-attr">"new"</span>: <span class="hljs-string">"plop"</span>,
        <span class="hljs-attr">"new:app"</span>: <span class="hljs-string">"plop app"</span>,
        <span class="hljs-attr">"new:comp"</span>: <span class="hljs-string">"plop component"</span>,
        },
    <span class="hljs-attr">"devDependencies"</span>: {
        <span class="hljs-attr">"plop"</span>: <span class="hljs-string">"^3.1.1"</span>,
    }
}
</code></pre>
<h3 id="heading-plopfilejs">plopfile.js</h3>
<p>You will notice the first arg of the <code>plop.setGenerator</code>(<code>name,</code> config) matches the scripts in the <code>package.json</code></p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> generateApp <span class="hljs-keyword">from</span> <span class="hljs-string">'./templates/appTemplate'</span>
<span class="hljs-keyword">import</span> generateComponent <span class="hljs-keyword">from</span> <span class="hljs-string">'./templates/componentTemplate'</span>

<span class="hljs-built_in">module</span>.export = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">plop</span>) </span>{
    plop.setGenerator(<span class="hljs-string">'app'</span>, generateApp),
    plop.setGenerator(<span class="hljs-string">'component'</span>, generateComponent)
}
</code></pre>
<hr />
<h3 id="heading-templates">Templates</h3>
<p><strong>Example;</strong> App skeleton - simple folders &amp; files layout.</p>
<pre><code class="lang-javascript">templates/
    appTemplate/
    |   app/
    |   |   public/
    |   |   |   favicon.ico
    |   |   |   index.html.hbs
    |   |   |   manifest.json.hbs
    |   |   src/
    |   |   |   App.js.hbs
    |   |   |   index.css.hbs
    |   |   |   index.js.hbs
    |   |   |   routes.js.hbs
    |   |   test/
    |   |   package.json.hbs
    |   index.js
    |
</code></pre>
<h1 id="heading-generate-an-app-skeleton">Generate an app skeleton</h1>
<h3 id="heading-prompts-and-actions">Prompts and actions</h3>
<blockquote>
<p>In actions you will see to get our app folder structure correct we have to replicate the type: 'add', rather than use the <a target="_blank" href="https://plopjs.com/documentation/#addmany">type: addMany</a> which will add many files into one folder. There are many <a target="_blank" href="https://plopjs.com/documentation/#built-in-actions">action types</a> you can use.</p>
<p>You can also see the "name" variable has a modifier in front of it <code>pascalCase</code> you can find all <a target="_blank" href="https://plopjs.com/documentation/#case-modifiers">modifiers here</a>.</p>
</blockquote>
<h3 id="heading-templatesapptemplateindexjs">templates/appTemplate/index.js</h3>
<pre><code class="lang-javascript"><span class="hljs-built_in">module</span>.exports = {
  <span class="hljs-attr">description</span>: <span class="hljs-string">'Generates new App folder'</span>,
    <span class="hljs-attr">prompts</span>: [
      {
<span class="hljs-comment">// "input" standard inquirer prompt</span>
        <span class="hljs-attr">type</span>: <span class="hljs-string">"input"</span>,
<span class="hljs-comment">// name is the variable you can reuse in your template files and path</span>
        <span class="hljs-attr">name</span>: <span class="hljs-string">"name"</span>,
<span class="hljs-comment">// message is the prompt to the user aka. context</span>
        <span class="hljs-attr">message</span>: <span class="hljs-string">"What's the name of the new app?"</span>,
<span class="hljs-comment">// optional input validation function</span>
        <span class="hljs-attr">validate</span>: <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">value</span>) </span>{
          <span class="hljs-keyword">let</span> message = <span class="hljs-literal">true</span>
          <span class="hljs-keyword">if</span> (!<span class="hljs-regexp">/.+/</span>.test(value)) {
            message = <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Missing'</span>, <span class="hljs-string">'you must define the app name'</span>)
          } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (value.length &lt; <span class="hljs-number">4</span>) {
            <span class="hljs-comment">// send new message for user feedbaack </span>
            message = <span class="hljs-built_in">console</span>.error(
              <span class="hljs-string">'Too Short'</span>,
              <span class="hljs-string">`"<span class="hljs-subst">${value}</span>" is not descriptive enough`</span>,
            )
          }

          <span class="hljs-keyword">return</span> message
        }
      }
    ],
<span class="hljs-comment">// <span class="hljs-doctag">TODO:</span>: Make destination path an input in above prompts for user to choose where the component should live.</span>
  <span class="hljs-attr">actions</span>: <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">return</span> [
      {
        <span class="hljs-comment">// action type</span>
        <span class="hljs-attr">type</span>: <span class="hljs-string">'add'</span>,
        <span class="hljs-comment">// output path</span>
        <span class="hljs-attr">path</span>: <span class="hljs-string">'../../../apps/{{pascalCase name}}/package.json'</span>,
        <span class="hljs-comment">// input path</span>
        <span class="hljs-attr">templateFile</span>: <span class="hljs-string">'./templates/appTemplate/package.json.hbs'</span>,
        <span class="hljs-comment">// the section of the path that should be excluded when adding files to the destination folder</span>
        <span class="hljs-attr">base</span>: <span class="hljs-string">'app'</span>
      },
      {
        <span class="hljs-attr">type</span>: <span class="hljs-string">'add'</span>,
        <span class="hljs-attr">path</span>: <span class="hljs-string">'../../../apps/{{pascalCase name}}/src/App.js'</span>,
        <span class="hljs-attr">templateFile</span>: <span class="hljs-string">'./templates/appTemplate/src/App.js.hbs'</span>,
        <span class="hljs-attr">base</span>: <span class="hljs-string">'app'</span>
      },
      {
        <span class="hljs-attr">type</span>: <span class="hljs-string">'add'</span>,
        <span class="hljs-attr">path</span>: <span class="hljs-string">'../../../apps/{{pascalCase name}}/src/index.css'</span>,
        <span class="hljs-attr">templateFile</span>: <span class="hljs-string">'./templates/appTemplate/src/index.css.hbs'</span>,
        <span class="hljs-attr">base</span>: <span class="hljs-string">'app'</span>
      },
      {
        <span class="hljs-attr">type</span>: <span class="hljs-string">'add'</span>,
        <span class="hljs-attr">path</span>: <span class="hljs-string">'../../../apps/{{pascalCase name}}/src/index.js'</span>,
        <span class="hljs-attr">templateFile</span>: <span class="hljs-string">'./templates/appTemplate/src/index.js.hbs'</span>,
        <span class="hljs-attr">base</span>: <span class="hljs-string">'app'</span>
      },
      {
        <span class="hljs-attr">type</span>: <span class="hljs-string">'add'</span>,
        <span class="hljs-attr">path</span>: <span class="hljs-string">'../../../apps/{{pascalCase name}}/src/routes.js'</span>,
        <span class="hljs-attr">templateFile</span>: <span class="hljs-string">'./templates/appTemplate/src/routes.js.hbs'</span>,
        <span class="hljs-attr">base</span>: <span class="hljs-string">'app'</span>
      },
      {
        <span class="hljs-attr">type</span>: <span class="hljs-string">'add'</span>,
        <span class="hljs-attr">path</span>: <span class="hljs-string">'../../../apps/{{pascalCase name}}/public/favicon.ico'</span>,
        <span class="hljs-attr">templateFile</span>: <span class="hljs-string">'./templates/appTemplate/public/favicon.ico'</span>,
        <span class="hljs-attr">base</span>: <span class="hljs-string">'app'</span>
      },
      {
        <span class="hljs-attr">type</span>: <span class="hljs-string">'add'</span>,
        <span class="hljs-attr">path</span>: <span class="hljs-string">'../../../apps/{{pascalCase name}}/public/index.html'</span>,
        <span class="hljs-attr">templateFile</span>: <span class="hljs-string">'./templates/appTemplate/public/index.html.hbs'</span>,
        <span class="hljs-attr">base</span>: <span class="hljs-string">'app'</span>
      },
      {
        <span class="hljs-attr">type</span>: <span class="hljs-string">'add'</span>,
        <span class="hljs-attr">path</span>: <span class="hljs-string">'../../../apps/{{pascalCase name}}/public/manifest.json'</span>,
        <span class="hljs-attr">templateFile</span>: <span class="hljs-string">'./templates/appTemplate/public/manifest.json.hbs'</span>,
        <span class="hljs-attr">base</span>: <span class="hljs-string">'app'</span>
      },
    ]
  }
}
</code></pre>
<h3 id="heading-indexjshbs">index.js.hbs</h3>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { StrictMode } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>
<span class="hljs-keyword">import</span> ReactDOM <span class="hljs-keyword">from</span> <span class="hljs-string">"react-dom/client"</span>
<span class="hljs-keyword">import</span> App <span class="hljs-keyword">from</span> <span class="hljs-string">"./App"</span>
<span class="hljs-keyword">import</span> { BrowserRouter } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-router-dom"</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">'./index.css'</span>

<span class="hljs-keyword">const</span> root = ReactDOM.createRoot(<span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"root"</span>))
root.render(
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">StrictMode</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">BrowserRouter</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">App</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">BrowserRouter</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">StrictMode</span>&gt;</span></span>
)
</code></pre>
<h3 id="heading-appjshbs">App.js.hbs</h3>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> Routes <span class="hljs-keyword">from</span> <span class="hljs-string">'./routes'</span>

<span class="hljs-keyword">const</span> App = <span class="hljs-function">() =&gt;</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Routes</span> /&gt;</span></span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App
</code></pre>
<h3 id="heading-routesjshbs">routes.js.hbs</h3>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { Routes, Route } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> </span>{{pascalCase name}}AppRoutes() {
    <span class="hljs-keyword">return</span> (
      <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Routes</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Route</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"/"</span> <span class="hljs-attr">element</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">Landing</span> /&gt;</span>} 
          <span class="hljs-tag">&lt;<span class="hljs-name">Route</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"/tab-one"</span> <span class="hljs-attr">element</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">TabOne</span> /&gt;</span>} /&gt;
          <span class="hljs-tag">&lt;<span class="hljs-name">Route</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"/tab-two"</span> <span class="hljs-attr">element</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">TabTwo</span> /&gt;</span>} /&gt;
        <span class="hljs-tag">&lt;/<span class="hljs-name">Route</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Routes</span>&gt;</span></span>
    )
}
</code></pre>
<h3 id="heading-indexcsshbs">index.css.hbs</h3>
<blockquote>
<p>This is a small preview of a main.css file showing that you can add your <strong><em>CSS</em></strong> as normal you could ask in your prompts for global styling named variables if you wanted to add them here.</p>
</blockquote>
<pre><code class="lang-css"><span class="hljs-selector-tag">body</span>,
<span class="hljs-selector-tag">html</span> {
    <span class="hljs-attribute">height</span>: <span class="hljs-number">100%</span>;
    <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>;
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">0</span>;
    <span class="hljs-attribute">font-family</span>: ...
}
</code></pre>
<h3 id="heading-manifestjsonhbs">manifest.json.hbs</h3>
<blockquote>
<p>This is a small preview of a manifest file showing how you can assign the name variable to keys in the <strong><em>JSON</em></strong> file.</p>
</blockquote>
<pre><code class="lang-javascript">{
    <span class="hljs-string">"short_name"</span>: <span class="hljs-string">"{{name}}"</span>,
    <span class="hljs-string">"name"</span>: <span class="hljs-string">"The Best {{name}}"</span>,
    <span class="hljs-string">"icons"</span>: [],
    ...
}
</code></pre>
<hr />
<h1 id="heading-generate-a-file">Generate a file</h1>
<h3 id="heading-prompts-and-actions-1">Prompts and actions</h3>
<h3 id="heading-templatescomponenttemplateindexjs">templates/componentTemplate/index.js</h3>
<pre><code class="lang-javascript"><span class="hljs-built_in">module</span>.exports = {
  <span class="hljs-attr">description</span>: <span class="hljs-string">'Generates new component'</span>,
  <span class="hljs-attr">prompts</span>: [
    {
      <span class="hljs-attr">type</span>: <span class="hljs-string">'input'</span>,
      <span class="hljs-attr">name</span>: <span class="hljs-string">'name'</span>,
      <span class="hljs-attr">message</span>: <span class="hljs-string">"What's the name of the component? (make sure to suffix/prefix with ...)"</span>,
      <span class="hljs-attr">validate</span>: <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">value</span>) </span>{
        <span class="hljs-keyword">let</span> message = <span class="hljs-literal">true</span>

        <span class="hljs-keyword">if</span> (!<span class="hljs-regexp">/.+/</span>.test(value)) {
          message = <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Missing'</span>, <span class="hljs-string">'you must define a component name'</span>)
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (value.length &lt; <span class="hljs-number">4</span>) {
          message = <span class="hljs-built_in">console</span>.error(
            <span class="hljs-string">'Too Short'</span>,
            <span class="hljs-string">`"<span class="hljs-subst">${value}</span>" is not descriptive enough`</span>,
          )
        }

        <span class="hljs-keyword">return</span> message
      },
    },
    {
      <span class="hljs-attr">type</span>: <span class="hljs-string">'input'</span>,
      <span class="hljs-attr">name</span>: <span class="hljs-string">'app'</span>,
<span class="hljs-comment">// useful for multi app codebase or in not multi just add src/pages/app or remove.</span>
      <span class="hljs-attr">message</span>: <span class="hljs-string">"What's the name of the app for this component to be added?"</span>,
      <span class="hljs-attr">validate</span>: <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">value</span>) </span>{
        <span class="hljs-keyword">let</span> message = <span class="hljs-literal">true</span>

        <span class="hljs-keyword">if</span> (!<span class="hljs-regexp">/.+/</span>.test(value)) {
          message = <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Missing'</span>, <span class="hljs-string">'you must define a app name'</span>)
<span class="hljs-comment">// <span class="hljs-doctag">TODO:</span>: add some smarts, can a config file be used to check the existence of an app here?</span>
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (value.length &lt; <span class="hljs-number">4</span>) { 
          message = <span class="hljs-built_in">console</span>.error(
            <span class="hljs-string">'Too Short'</span>,
            <span class="hljs-string">`"<span class="hljs-subst">${value}</span>" is not descriptive enough`</span>,
          )
        }

        <span class="hljs-keyword">return</span> message
      }
    },
    {
      <span class="hljs-attr">type</span>: <span class="hljs-string">'input'</span>,
      <span class="hljs-attr">name</span>: <span class="hljs-string">'location'</span>,
      <span class="hljs-attr">message</span>: <span class="hljs-string">"Where inside the app src folder should this component live? (e.g. shared common folder)"</span>,
      <span class="hljs-attr">validate</span>: <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">value</span>) </span>{
        <span class="hljs-keyword">let</span> message = <span class="hljs-literal">true</span>

        <span class="hljs-keyword">if</span> (!<span class="hljs-regexp">/.+/</span>.test(value)) {
          message = <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Missing'</span>, <span class="hljs-string">'you must define a folder location name'</span>)
<span class="hljs-comment">// <span class="hljs-doctag">TODO:</span>: add some smarts, can a config file be used to check the existence of an app here?</span>
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (value.length &lt; <span class="hljs-number">4</span>) {
          message = <span class="hljs-built_in">console</span>.error(
            <span class="hljs-string">'Too Short'</span>,
            <span class="hljs-string">`"<span class="hljs-subst">${value}</span>" is not descriptive enough`</span>,
          )
        }

        <span class="hljs-keyword">return</span> message
      }
    }
  ],
<span class="hljs-comment">// <span class="hljs-doctag">TODO:</span>: make destination path an input above for user to choose where the component should live.</span>
  <span class="hljs-attr">actions</span>: <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">return</span> [
      {
        <span class="hljs-attr">type</span>: <span class="hljs-string">'add'</span>,
        <span class="hljs-attr">path</span>: <span class="hljs-string">'../../../apps/{{app}}/src/{{location}}/{{pascalCase name}}/index.js'</span>,
        <span class="hljs-attr">templateFile</span>: <span class="hljs-string">'./templates/componentTemplate/index.hbs'</span>,
      }
    ]
  }
}
</code></pre>
<h3 id="heading-indexjshbs-1">index.js.hbs</h3>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-comment">// Add your other imports for this component here...</span>

<span class="hljs-comment">// Add types here if you are using .ts/.tsx</span>

<span class="hljs-comment">// <span class="hljs-doctag">NOTE:</span> If you know the required props for this component you can destructure them and add them below insted of "props"</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> </span>{{pascalCase name}}(props) {
<span class="hljs-comment">// Add functions as you need them...</span>

<span class="hljs-comment">// Add as usual add your component return jsx...</span>
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">""</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">label</span>&gt;</span>HW<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  )
}
</code></pre>
<hr />
<h1 id="heading-some-pros-andamp-cons-of-using-plop">Some Pros &amp; Cons of using Plop</h1>
<h3 id="heading-pros">Pros:</h3>
<ul>
<li><p>Very quick implementation gentle learning curve.</p>
</li>
<li><p>Easy to add new code to existing apps.</p>
</li>
<li><p>Easy to make adjustments to templates to keep aligned with any new conventions or updated patterns*.</p>
</li>
<li><p>You can also import non-template files like favicon.ico...</p>
</li>
</ul>
<h3 id="heading-cons">Cons:</h3>
<ul>
<li><p>* Because of its ease of use, it is simple to make adjustments to the templates that suit the developer on the fly. Docs and an opinionated implementation guide would need to be maintained.</p>
</li>
<li><p>Plop template files cannot be dynamically downloaded and used from a template repo. They can be published as a (private) npm module, <a target="_blank" href="https://github.com/plopjs/plop/issues/20">git issue</a></p>
</li>
</ul>
<hr />
<h1 id="heading-some-final-things-to-consider">Some final things to consider</h1>
<ul>
<li><p>Is code auto-gen a complete buy-in for all devs when creating new apps/folders/files?</p>
</li>
<li><p>Does this implementation replace manual creation altogether? Should it?</p>
</li>
<li><p>Where/How should manual code creation be managed when used side by side with automated code auto-gen?</p>
</li>
<li><p>How can conventions and code patterns be established for templates?</p>
</li>
<li><p>How can existing auto-generated code be updated when the code auto-gen template changes with an evolving codebase?</p>
</li>
</ul>
<blockquote>
<p>I recommend using code auto-generation for brand new projects or files, but only after establishing some foundations &amp; opinions on how templating should be implemented and maintained.<br />It is a great way for any developer, especially freelancers or solo workers to free up some time so you can get down to the fun stuff.</p>
</blockquote>
<p>Till next time 👋</p>
<hr />
]]></content:encoded></item><item><title><![CDATA[Software Engineering 101 - My top 6 learning's so far]]></title><description><![CDATA[This is a very short blog post I wanted to share, I often like to remind myself of these things while I am solving problems and working in teams. This is an intense consolidation of 5 years experience.


Make sure you use consistent language and reme...]]></description><link>https://bronz3beard.hashnode.dev/software-engineering-101-my-top-6-learnings-so-far</link><guid isPermaLink="true">https://bronz3beard.hashnode.dev/software-engineering-101-my-top-6-learnings-so-far</guid><category><![CDATA[frontend]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[Problem Solving]]></category><category><![CDATA[101]]></category><category><![CDATA[backend]]></category><dc:creator><![CDATA[Rory]]></dc:creator><pubDate>Thu, 24 Nov 2022 05:27:29 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/2JIvboGLeho/upload/v1668931803925/cpPjNdnzh.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This is a very short blog post I wanted to share, I often like to remind myself of these things while I am solving problems and working in teams. This is an intense consolidation of 5 years experience.</p>
<blockquote>
<ol>
<li>Make sure you use consistent language and remember words matter.</li>
<li>Identify what the the deliverable feature is before you start working on it.</li>
<li>Identify and clearly communicate in the code the areas of awkwardness.</li>
<li>Make sure you understand responsibilities in/of the code and make it obvious where they are.</li>
<li>Duplication over incorrect abstraction (this one requires more thought than you might assume!).</li>
<li>Always have and show empathy for your fellow developer 🙌</li>
</ol>
</blockquote>
<p><strong>Caveat</strong>; Try your best to form opinions, it will help with all of the above, even if those opinions get old or are slightly off they will still help!</p>
<p>I want to add to the list in the future as I continue to learn and gain experience, if there are any other obvious items you would like to add or some gems I am missing, please share them in the comments below. The wider the range of values and perspectives for this list the more accurate and helpful it will be.</p>
<p>Rory</p>
]]></content:encoded></item><item><title><![CDATA[Add custom context menu to your website with React & TypeScript]]></title><description><![CDATA[By the end of this post, you will have a new custom context menu component to drop into your web app. 💪

So what is a "custom context menu"? It is the menu that appears when you use your mouse to right click on a website, or two fingers on a mac tra...]]></description><link>https://bronz3beard.hashnode.dev/add-custom-context-menu-to-your-website</link><guid isPermaLink="true">https://bronz3beard.hashnode.dev/add-custom-context-menu-to-your-website</guid><category><![CDATA[React]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Tailwind CSS]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Rory]]></dc:creator><pubDate>Sat, 19 Nov 2022 06:41:31 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1668839145867/RDseGxkQn.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>By the end of this post, you will have a new custom context menu component to drop into your web app. 💪</p>
</blockquote>
<p>So what is a "<strong>custom context menu</strong>"? It is the menu that appears when you use your mouse to right click on a website, or two fingers on a mac track pad. You will notice some website disable this functionality, on the Firefox browser you can press <code>cmd + shift + right mouse click</code> to see the context menu.</p>
<blockquote>
<p>⚠️ Before adding a custom context menu to your website, remember you are removing what is <strong>standard expected behavior for the right mouse click event</strong>, so remember to consider accessibility and check your design is providing a real solution for the user with a custom context menu.</p>
</blockquote>
<hr />
<h4 id="heading-so-what-do-we-want-to-deliver-here">So what do we want to deliver here? 💰</h4>
<ul>
<li><p>A custom menu with specific options relevant to the current website.</p>
</li>
<li><p>When the user right clicks, the custom context menu should appear with the custom menu options.</p>
</li>
<li><p>The menu needs to always be visible, no matter where the user clicks on your website.</p>
</li>
<li><p>The context menu should disappear when the user scrolls or clicks outside the menu.</p>
</li>
</ul>
<h4 id="heading-how-do-the-deliverables-translate-into-code">How do the deliverables translate into code? 🤲</h4>
<ol>
<li><p>We need event listeners to capture the user mouse events.</p>
<ul>
<li>Considering there are 3 different types of events to listen for, an event listener hook will be useful.</li>
</ul>
</li>
<li><p>We need a component to handle all the logic for showing and hiding the context menu.</p>
</li>
<li><p>We need a component for the menu options. (Not included here this is where you get creative)</p>
</li>
</ol>
<hr />
<h3 id="heading-the-event-listener-hook">The event listener hook 🎧</h3>
<p>Considering this is not the main focus of this post I am not going to go deep into this hook, but feel free to ask about it.</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">import</span> { useEffect, useRef, RefObject } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-comment">/*
  - "type" of listener.
  - "listenerCallback" to handle event.
  The keyof operator takes an object type
  and produces a string or numeric literal union of its keys

  - "target" for listener to be attached to,
  right now can be window or html element
  not hooked up to work with document.

  "options" generally goes unused can also be set to false by default,
  but unused is okay.
*/</span>

<span class="hljs-keyword">const</span> useEventListener = &lt;
  KW <span class="hljs-keyword">extends</span> keyof WindowEventMap,
  KH <span class="hljs-keyword">extends</span> keyof HTMLElementEventMap,
  T <span class="hljs-keyword">extends</span> HTMLElement
&gt;(
  <span class="hljs-keyword">type</span>: KW | KH,
  listenerCallback: <span class="hljs-function">(<span class="hljs-params">
    event: WindowEventMap[KW] | HTMLElementEventMap[KH] | Event
  </span>) =&gt;</span> <span class="hljs-built_in">void</span>,
  target?: RefObject&lt;T&gt;,
  options?: <span class="hljs-built_in">boolean</span> | EventListenerOptions | <span class="hljs-literal">undefined</span>
): <span class="hljs-function"><span class="hljs-params">void</span> =&gt;</span> {
  <span class="hljs-comment">// using ref so we dont have to add listenerCallback</span>
  <span class="hljs-comment">// to the dependency array</span>
 <span class="hljs-comment">// of the useEffect where the listener is added below.</span>
  <span class="hljs-keyword">const</span> savedHandler = useRef(listenerCallback);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-comment">// add callback to ref</span>
    savedHandler.current = listenerCallback;
  }, [listenerCallback]);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-comment">// window or element</span>
    <span class="hljs-keyword">const</span> targetActual: T | Window = target?.current || <span class="hljs-built_in">window</span>;

    <span class="hljs-comment">// check event listener is available on targetElement,</span>
    <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span>: maybe add assert here?</span>
    <span class="hljs-keyword">if</span> (!targetActual?.addEventListener) {
      <span class="hljs-keyword">return</span> <span class="hljs-literal">undefined</span>;
    }

    <span class="hljs-comment">// Create event listener that calls handler function stored in ref</span>
    <span class="hljs-keyword">const</span> eventListenerCallback: <span class="hljs-keyword">typeof</span> listenerCallback = <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span>
      savedHandler.current(event);
    <span class="hljs-comment">// on component mount listen for event</span>
    targetActual.addEventListener(<span class="hljs-keyword">type</span>, eventListenerCallback);
    <span class="hljs-comment">// on component unmount remove listener</span>
    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span>
      targetActual.removeEventListener(<span class="hljs-keyword">type</span>, eventListenerCallback);
  }, [target, <span class="hljs-keyword">type</span>]);
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> useEventListener;
</code></pre>
<hr />
<h3 id="heading-the-context-menu-component">The context menu component 🙈🙉🙊</h3>
<p>This is where the logic for showing and hiding the context menu lives &amp; the container of the menu itself.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> React, { FC, useEffect, useRef, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> ShareMenu <span class="hljs-keyword">from</span> <span class="hljs-string">'../shareMenu'</span>
<span class="hljs-keyword">import</span> useEventListener <span class="hljs-keyword">from</span> <span class="hljs-string">'../hooks/useEventListener'</span>

<span class="hljs-keyword">const</span> CustomContextMenu: FC = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> [visible, setVisible] = useState&lt;<span class="hljs-built_in">boolean</span>&gt;(<span class="hljs-literal">false</span>)
    <span class="hljs-keyword">const</span> [confirmCopy, setConfirmCopy] = useState&lt;<span class="hljs-built_in">boolean</span>&gt;(<span class="hljs-literal">false</span>)
    <span class="hljs-keyword">const</span> [clientSide, setClientSide] = useState&lt;<span class="hljs-built_in">boolean</span>&gt;(<span class="hljs-literal">false</span>)

    useEffect(<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onClientSideSetClientIsTrue</span>(<span class="hljs-params"></span>): <span class="hljs-title">void</span> </span>{
        <span class="hljs-comment">// This approach for checking client side is preferred over</span>
        <span class="hljs-comment">// typeof window !== 'undefined' as window is not available server side.</span>
        <span class="hljs-comment">// useEffect method will only be executed on the client.</span>
        setClientSide(<span class="hljs-literal">true</span>)
    }, [])

    <span class="hljs-comment">// refs provide a way to access DOM nodes or React elements created in the render method.</span>
    <span class="hljs-keyword">const</span> rootMenuRef = useRef&lt;HTMLDivElement&gt;(<span class="hljs-literal">null</span>)
    <span class="hljs-keyword">const</span> copyRef = useRef&lt;HTMLDivElement&gt;(<span class="hljs-literal">null</span>)

    <span class="hljs-keyword">const</span> handleContextMenu = (event: Event | MouseEvent): <span class="hljs-function"><span class="hljs-params">void</span> =&gt;</span> {
        event.preventDefault()

        setVisible(<span class="hljs-literal">true</span>)

        <span class="hljs-keyword">const</span> rootRef: HTMLDivElement | <span class="hljs-literal">null</span> = rootMenuRef?.current
        <span class="hljs-keyword">if</span> (!rootRef) {
            <span class="hljs-keyword">return</span>
        }

        <span class="hljs-keyword">const</span> clickX: <span class="hljs-built_in">number</span> = (event <span class="hljs-keyword">as</span> MouseEvent).clientX
        <span class="hljs-keyword">const</span> clickY: <span class="hljs-built_in">number</span> = (event <span class="hljs-keyword">as</span> MouseEvent).clientY
        <span class="hljs-keyword">const</span> screenW: <span class="hljs-built_in">number</span> = <span class="hljs-built_in">window</span>.innerWidth
        <span class="hljs-keyword">const</span> screenH: <span class="hljs-built_in">number</span> = <span class="hljs-built_in">window</span>.innerHeight
        <span class="hljs-comment">// root width and height have to be hard coded,</span>
        <span class="hljs-comment">// this is because the element in not visible making innerHeight and innerWidth === 0</span>
        <span class="hljs-keyword">const</span> rootW: <span class="hljs-built_in">number</span> = <span class="hljs-number">256</span> <span class="hljs-comment">// menu width you will see this matches the tailwindcss in the return</span>
        <span class="hljs-keyword">const</span> rootH: <span class="hljs-built_in">number</span> = <span class="hljs-number">320</span> <span class="hljs-comment">// menu height you will see this matches the tailwindcss in the return</span>


        <span class="hljs-keyword">const</span> right: <span class="hljs-built_in">boolean</span> = clickX &gt; screenW / <span class="hljs-number">2</span>
        <span class="hljs-keyword">const</span> left: <span class="hljs-built_in">boolean</span> = !right

        <span class="hljs-keyword">const</span> top: <span class="hljs-built_in">boolean</span> = screenH - clickY &gt; rootH
        <span class="hljs-keyword">const</span> bottom: <span class="hljs-built_in">boolean</span> = !top

        <span class="hljs-keyword">const</span> refStyles: CSSStyleDeclaration | <span class="hljs-literal">undefined</span> = rootRef?.style
        <span class="hljs-keyword">if</span> (refStyles === <span class="hljs-literal">undefined</span>) {
            <span class="hljs-keyword">return</span>
        }

        <span class="hljs-keyword">if</span> (refStyles !== <span class="hljs-literal">undefined</span>) {
            <span class="hljs-keyword">if</span> (left) {
                refStyles.left = <span class="hljs-string">`<span class="hljs-subst">${clickX + <span class="hljs-number">5</span>}</span>px`</span>
            }
            <span class="hljs-keyword">if</span> (right) {
                <span class="hljs-comment">// horizontal mouse location, minus the menu width - 5</span>
                refStyles.left = <span class="hljs-string">`<span class="hljs-subst">${clickX - rootW - <span class="hljs-number">5</span>}</span>px`</span>
            }
            <span class="hljs-keyword">if</span> (top) {
                refStyles.top = <span class="hljs-string">`<span class="hljs-subst">${clickY + <span class="hljs-number">5</span>}</span>px`</span>
            }
            <span class="hljs-keyword">if</span> (bottom) {
                <span class="hljs-comment">// vertical mouse location, minus the menu height - 5</span>
                refStyles.top = <span class="hljs-string">`<span class="hljs-subst">${clickY - rootH - <span class="hljs-number">5</span>}</span>px`</span>
            }
        }
    }

    <span class="hljs-keyword">const</span> handleMouseAction = (event: Event): <span class="hljs-function"><span class="hljs-params">void</span> =&gt;</span> {
        <span class="hljs-comment">// prevent event propagating to parent because</span>
        <span class="hljs-comment">// without this prevention this click will hide the menu</span>
        event.stopPropagation()

        <span class="hljs-keyword">const</span> rootRef = rootMenuRef?.current
        <span class="hljs-keyword">if</span> (!rootRef) {
            <span class="hljs-keyword">return</span>
        }

        <span class="hljs-keyword">const</span> copRef = copyRef?.current
        <span class="hljs-keyword">if</span> (!copRef) {
            <span class="hljs-keyword">return</span>
        }
        <span class="hljs-comment">/* if() logic in plain english */</span>
        <span class="hljs-comment">// If the context menu is not visible AND </span>
        <span class="hljs-comment">// the click event target is from the menu itself OR</span>
        <span class="hljs-comment">// the click event is from the menu option to copy a url THEN</span>
        <span class="hljs-comment">// show the menu OTHERWISE</span>
        <span class="hljs-comment">// hide the menu.</span>
        <span class="hljs-keyword">if</span> (
            !visible &amp;&amp;
            ((rootRef &amp;&amp; rootRef.contains(event.target <span class="hljs-keyword">as</span> HTMLDivElement)) ||
                (copRef &amp;&amp; copRef.contains(event.target <span class="hljs-keyword">as</span> HTMLDivElement)))
        ) {
            setVisible(<span class="hljs-literal">true</span>)
        } <span class="hljs-keyword">else</span> {
            setVisible(<span class="hljs-literal">false</span>)
        }
    }

    <span class="hljs-keyword">const</span> handleScrollAction = (): <span class="hljs-function"><span class="hljs-params">void</span> =&gt;</span> {
        <span class="hljs-comment">// if on scroll the menu is visible hide it OTHERWISE</span>
        <span class="hljs-comment">// do nothing</span>
        <span class="hljs-keyword">if</span> (visible) {
            setVisible(<span class="hljs-literal">false</span>)
        }
    }

    useEventListener(<span class="hljs-string">'click'</span>, handleMouseAction)
    useEventListener(<span class="hljs-string">'scroll'</span>, handleScrollAction)
    useEventListener(<span class="hljs-string">'contextmenu'</span>, handleContextMenu)

    <span class="hljs-keyword">return</span> !clientSide ? <span class="hljs-literal">null</span> : (
        &lt;div
            ref={rootMenuRef}
            className={<span class="hljs-string">`<span class="hljs-subst">${
                !visible
                    ? <span class="hljs-string">'hidden'</span>
                    : <span class="hljs-string">'flex items-center justify-center w-64 h-80 fixed p-1 rounded bg-gray-100 text-center z-50 shadow-lg shadow-black'</span>
            }</span>`</span>}
        &gt;
            {visible &amp;&amp; (
                &lt;ShareMenu
                    ref={copyRef}
                    confirmCopy={confirmCopy}
                    setConfirmCopy={setConfirmCopy}
                /&gt;
            )}
        &lt;/div&gt;
    )
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> CustomContextMenu
</code></pre>
<hr />
<h3 id="heading-what-does-all-this-gibberish-mean-lets-break-it-down">What does all this gibberish mean? Lets break it down.</h3>
<h4 id="heading-first-you-will-see-we-have-some-state-that-handles-3-different-things-which-are-all-initially-false">First you will see we have some <strong><em>state</em></strong> that handles 3 different things. Which are all initially false.</h4>
<ol>
<li><p>Menu visibility.</p>
</li>
<li><p>A confirmation for if the copy menu option was clicked.</p>
</li>
<li><p>Checking if the client is available.</p>
</li>
</ol>
<p>const [visible, setVisible] = useState(false) const [confirmCopy, setConfirmCopy] = useState(false) const [clientSide, setClientSide] = useState(false)</p>
<pre><code class="lang-typescript">
#### Then you see we have two <span class="hljs-string">``</span><span class="hljs-string">`refs</span>
</code></pre>
<ol>
<li>ref 1 holds the reference to the DOM node, which in this case is just a div.</li>
<li>ref 2 is actually forwarded to a DOM node which is inside the <em>ShareMenu</em> react element.</li>
</ol>
<blockquote>
<pre><code class="lang-typescript"><span class="hljs-comment">// refs provide a way to access DOM nodes</span>
<span class="hljs-comment">// or React elements created in the render method</span>
<span class="hljs-keyword">const</span> rootMenuRef = useRef&lt;HTMLDivElement&gt;(<span class="hljs-literal">null</span>)
<span class="hljs-keyword">const</span> copyRef = useRef&lt;HTMLDivElement&gt;(<span class="hljs-literal">null</span>)
</code></pre>
</blockquote>
<hr />
<h4 id="heading-now-we-have-the-smarts-of-the-component-the-handlecontextmenu-function">Now we have the smarts of the component the <code>handleContextMenu</code> function.</h4>
<p>There is a bit going on here so we can break this down also. This function handles if the context menu is visible &amp; what the location is for the menu.</p>
<p>Show the menu</p>
<p>setVisible(true)</p>
<pre><code class="lang-typescript">
Check <span class="hljs-keyword">if</span> the context menu <span class="hljs-string">``</span><span class="hljs-string">`
ref
`</span><span class="hljs-string">``</span> is not <span class="hljs-literal">null</span>
&gt; <span class="hljs-string">``</span><span class="hljs-string">`typescript   
const rootRef: HTMLDivElement | null = rootMenuRef?.current
if (!rootRef) {
   return
}</span>
</code></pre>
<p>These are the coordinates of the mouse when the user clicked.</p>
<ul>
<li>We need a Type Assertion for clickX/Y because in this case we know better than the compiler.</li>
</ul>
<p><em>"as" is a Type Assertion in TypeScript which tells the compiler to consider the object as another type than the type the compiler infers the object to be.</em></p>
<p>const clickX: number = (event as MouseEvent).clientX // horizontal const clickY: number = (event as MouseEvent).clientY // vertical</p>
<pre><code class="lang-typescript">
Browser <span class="hljs-built_in">window</span> width and height
&gt; <span class="hljs-string">``</span><span class="hljs-string">`typescript
const screenW: number = window.innerWidth
const screenH: number = window.innerHeight</span>
</code></pre>
<p>Root width and height have to be hard coded. This is because the <code>DOM node</code> in not visible making innerHeight and innerWidth both zero</p>
<p>const rootW: number = 256 // px // menu width const rootH: number = 320 // px // menu height</p>
<pre><code class="lang-typescript">
Here we are checking which quarter the mouse was click <span class="hljs-keyword">in</span>.
*Imagine there is a line through the middle <span class="hljs-keyword">of</span> the screen left to right &amp; top to bottom.*
&gt;<span class="hljs-string">``</span><span class="hljs-string">`typescript
const right: boolean = clickX &gt; screenW / 2
const left: boolean = !right
const top: boolean = screenH - clickY &gt; rootH
const bottom: boolean = !top</span>
</code></pre>
<p>Here is how we tell the context menu where to appear relative to the browser window and the mouse location.</p>
<p>if (refStyles !== undefined) { if (left) { refStyles.left = <code>${clickX + 5}px</code> } if (right) { refStyles.left = <code>${clickX - rootW - 5}px</code> } if (top) { refStyles.top = <code>${clickY + 5}px</code> } if (bottom) { refStyles.top = <code>${clickY - rootH - 5}px</code> } }</p>
<pre><code class="lang-typescript">

#### Next we have two functions <span class="hljs-string">``</span><span class="hljs-string">`handleMouseAction`</span><span class="hljs-string">``</span> &amp; <span class="hljs-string">``</span><span class="hljs-string">`handleScrollAction</span>
</code></pre>
<p>These functions are callback functions for their respective event listeners, both handle showing and hiding the context menu.</p>
<h4 id="heading-lastly-we-have-our-event-listeners">Lastly we have our <strong><em>event listeners</em></strong></h4>
<blockquote>
<pre><code class="lang-typescript">useEventListener(<span class="hljs-string">'click'</span>, handleMouseAction)
useEventListener(<span class="hljs-string">'scroll'</span>, handleScrollAction)
useEventListener(<span class="hljs-string">'contextmenu'</span>, handleContextMenu)
</code></pre>
</blockquote>
<h4 id="heading-finally-we-the-return-statement-for-our-context-menu-component">Finally we the <strong>return statement</strong> for our context menu component</h4>
<p>You will see that if the <strong><em>client</em></strong> is not ready we return <strong><em>null</em></strong>, when it is we will have access to show and hide our context menu based on some react <strong><em>state</em></strong> and some <strong><em>tailwindcss</em></strong> styling.</p>
<p>return !clientSide ? null : (</p>
<p>... ```</p>
<p>That's it! You now your very own custom context menu to add to and react project.</p>
<p>Rory. ✌️</p>
]]></content:encoded></item><item><title><![CDATA[Some things I learnt upgrading to NextJS 13 - pages to app what can possibly go wrong?]]></title><description><![CDATA[I have a side project  closed source in early stage development, it has been running nicely on NextJS 12 but as things go, there is a new shiny version so naturally I wanted to use that!
The following are some of my learning's migrating from old to n...]]></description><link>https://bronz3beard.hashnode.dev/some-things-i-learnt-upgrading-to-nextjs-13</link><guid isPermaLink="true">https://bronz3beard.hashnode.dev/some-things-i-learnt-upgrading-to-nextjs-13</guid><category><![CDATA[Next.js]]></category><category><![CDATA[React]]></category><category><![CDATA[stripe]]></category><category><![CDATA[prisma]]></category><category><![CDATA[Tailwind CSS]]></category><dc:creator><![CDATA[Rory]]></dc:creator><pubDate>Sun, 13 Nov 2022 02:11:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1668307124639/yGV1DNVB6.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I have a side project  <em>closed source</em> in early stage development, it has been running nicely on <em>NextJS 12</em> but as things go, there is a new shiny version so naturally I wanted to use that!</p>
<p>The following are some of my learning's migrating from old to new with <em>NextJS</em> <em>framework</em>, also included, is a collection of resources used a long the way.</p>
<p>I am generally not a great technical writer, I always end up with the <em>TLDR</em>; version, so I apologize for that now. Writing is something I am actively trying to improving on as a <em>software engineer</em>, hence this is why I am starting a blog, thanks <strong>hashnode</strong>!</p>
<p><em>This is a hindsight reflection on how things went down, so please ask any questions as I have surely left things out on my path to upgrade.</em></p>
<hr />
<h4 id="heading-jargon-translation-words-acronyms">Jargon translation - words, acronyms</h4>
<blockquote>
<ul>
<li><strong>TLDR</strong> = too long don't read</li>
<li><strong>Programming language</strong> = They are a kind of computer language</li>
<li><strong>Library</strong> = A collection of pre-written code</li>
<li><strong>Framework</strong> = abstraction which can provide generic functionality</li>
<li><strong>Javascript</strong> = ¿programming language?</li>
<li><strong>React </strong>= Javascript Library</li>
<li><strong>NextJS</strong> = react framework</li>
<li><strong>Closed Source</strong> = not available for public use</li>
<li><strong>Client-side</strong> = refers to operations that are performed by the client(essentially the web browser) </li>
<li><strong>Server-side</strong> = refers to operations that are performed by the server, (a different computer to the one you are using the app on)</li>
<li><strong>UI</strong> = user interface (the stuff you look at, aka the most important part of the app)</li>
<li><strong>Web app</strong> = an application that runs inside your web browser aka: app</li>
<li><strong>Software engineer</strong> = Someone who writes gibberish across a computer screen all day, and then analyses why what has been written is not yet as good as it could be</li>
<li><strong>Stack trace</strong> = A list of things the application was doing when an Exception(error) happened</li>
<li><strong>Component</strong> = An independent and reusable bit of code</li>
<li><strong>Use client</strong> = An instruction for when building the app that explains that this component should be used Client side</li>
<li><strong>Leaf</strong> = is a reference to the location of a leaf on a tree, usually being the most outer part of the tree, in reference to this app the trunk of the tree is everything inside the app folder.</li>
</ul>
</blockquote>
<hr />
<h4 id="heading-resources">Resources</h4>
<blockquote>
<ol>
<li><a target="_blank" href="https://nextjs.org/blog/next-13">NextJS 13 blog</a></li>
<li><a target="_blank" href="https://github.com/vercel/next.js/discussions/41745">[Feedback] App Directory in Next.js 13</a></li>
<li><a target="_blank" href="https://twitter.com/shadcn/status/1584971527820541953">Your Next.js 13 upgrade guide</a></li>
<li><a target="_blank" href="https://beta.nextjs.org/docs/routing/fundamentals">Routing</a></li>
<li><a target="_blank" href="https://twitter.com/shadcn/status/1568204494193963008">Layout component</a><ul>
<li><a target="_blank" href="https://youtu.be/6mQ3M1CUGnk">Nested layouts with routing</a></li>
</ul>
</li>
<li><a target="_blank" href="https://beta.nextjs.org/docs/data-fetching/fundamentals">Data fetching</a></li>
<li><a target="_blank" href="https://vercel.com/guides/nextjs-prisma-postgres">How to Build a Fullstack App with Next.js, Prisma, and PostgreSQL</a><ul>
<li><a target="_blank" href="https://next-auth.js.org/providers/">NextJS Auth - Providers</a></li>
</ul>
</li>
<li><a target="_blank" href="https://vercel.com/guides/getting-started-with-nextjs-typescript-stripe">Getting started with Next.js, TypeScript, and Stripe Checkout</a><ul>
<li><a target="_blank" href="https://stripe.com/docs/checkout/quickstart?client=next">Adding prebuilt stripe to NextJS</a></li>
<li><a target="_blank" href="https://stripe.com/docs/products-prices/manage-prices?dashboard-or-api=dashboard#create-product">Creating a product for stripe checkout</a></li>
</ul>
</li>
</ol>
</blockquote>
<hr />
<h4 id="heading-project">Project</h4>
 <table>
  <tr>
    <th>How it started</th>
    <th>How its going</th>
  </tr>
  <tr>
    <td><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1668299218032/EPZ6Oo_aG.png" alt="Screenshot 2022-11-13 at 10.26.37 am.png" /></td>
    <td><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1668299307365/C_W9KkCOT.png" alt="Screenshot 2022-11-13 at 10.28.04 am.png" /></td>
  </tr>
</table>

<p>Nothing outrageous there, no refactoring...
I know you all want to see what is in the app folder, so here it is.</p>
<hr />
<h4 id="heading-the-app-folder">The app folder</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1668299843315/nnm5Or69z.png" alt="Screenshot 2022-11-13 at 10.37.03 am.png" /></p>
<hr />
<h4 id="heading-learnings-so-far-potential-points-of-interest">Learning's so far, potential points of interest</h4>
<ul>
<li>I only have one <strong><em>layout.tsx</em></strong> (<em>server-side</em>), this is because I have a <strong>PageTemplate</strong> <em>component</em> that handles some general styling throughout my <em>app</em>. This could change as I get more familiar with <em>layout.tsx components</em>. <em>check out the code section below to see what <em>*PageTemplate</em></em> looks like.</li>
</ul>
<pre><code class="lang-jsx">  <span class="hljs-comment">// single flex col with marginX</span>
  <span class="hljs-keyword">return</span> (
      <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">PageTemplate</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Title</span> <span class="hljs-attr">headerType</span>=<span class="hljs-string">"h1Title"</span> <span class="hljs-attr">title</span>=<span class="hljs-string">"Add a your Details"</span> /&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">AddDetailsForm</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">PageTemplate</span>&gt;</span></span>
  )
</code></pre>
<pre><code class="lang-jsx">  <span class="hljs-comment">// tripple flex col</span>
  <span class="hljs-keyword">return</span> (
      <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">PageTemplate</span>
        <span class="hljs-attr">fixedLeftContent</span>=<span class="hljs-string">{</span>
          [&lt;<span class="hljs-attr">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-black"</span>&gt;</span>left content<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>]
        }
        fixedRightContent={
          [<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-black"</span>&gt;</span>right content<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>]
        }
      &gt;
        <span class="hljs-tag">&lt;<span class="hljs-name">Title</span>
          <span class="hljs-attr">headerType</span>=<span class="hljs-string">"h1Title"</span>
          <span class="hljs-attr">title</span>=<span class="hljs-string">"Update a your Profile"</span>
        /&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">UpdateProfileForm</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">PageTemplate</span>&gt;</span></span>
  )
</code></pre>
<ul>
<li>I only use one <strong><em>error.tsx</em></strong>(<em>client-side</em>), this is because previously I used a pretty standard <strong>errorBoundary</strong>  <em>component</em>, it handles all major <em>UI</em> failures, this code now lives inside <strong>error.tsx</strong>.
As I get more comfortable with error.tsx and needing better stack trace I am sure this will change.</li>
<li>All Pages except the <strong>list/page.tsx</strong> are <em>client-side</em>, I will need to move the <code>'use client'</code>  to the <em>leaf</em> <em>components</em> as I continue work on this <em>app</em>, this will help lift the performance of the site, and better inform the developer where <em>client-side</em> interactive <em>components</em> live.</li>
<li>For now I have offloaded payment responsibility to stripe for payment through url, the <code>pages/api</code>, handles that redirect</li>
<li>Next auth is handled through pages/api also, essentially following the above resource <a target="_blank" href="https://vercel.com/guides/nextjs-prisma-postgres">How to Build a Fullstack App with Next.js, Prisma, and PostgreSQL</a> with some tweaking here and there.</li>
<li>Prisma client and usage, essentially followed the above recourse <a target="_blank" href="https://vercel.com/guides/nextjs-prisma-postgres">How to Build a Fullstack App with Next.js, Prisma, and PostgreSQL</a> with some tweaking here and there</li>
<li>tailwindcss I didn't notice any changes for adding tailwindcss to the project.</li>
</ul>
<hr />
<h4 id="heading-pros-andamp-cons-of-upgrade">Pros &amp; Cons of upgrade</h4>
<blockquote>
<p><strong>Pros</strong></p>
<ul>
<li>Having a constant reminder to write code that is composition over abstraction, the reminder is the new folder/file convention.</li>
<li>Requesting data where it is needed in a simple way and now only one way.</li>
<li>Flexibility to implement different patterns for folder/file management outside of app folder per team or application requirements.</li>
<li>New and easier way to add fonts to the project</li>
<li>Remove <code>&lt;head&gt;</code> node noise from page, it now lives in <code>layout.tsx</code></li>
<li>Better developer experience<ul>
<li>Reduced code abstraction</li>
<li>Reduced need for context providers</li>
<li>Reduced duplication of tailwindcss utility styling throughout app</li>
<li>Better Image component implementation</li>
<li>Overall better developer experience</li>
<li>Single focus when working on UI, API, App composition, related to developer experience</li>
<li>No more custom server stuff, this was always possible, but it just feel more permanent now, your head space is either with in <code>app/components</code> or <code>api/prisma</code></li>
</ul>
</li>
</ul>
<p><strong>Cons</strong></p>
<ul>
<li><code>pages/api</code> I was very ready to get rid of pages folder, I never really liked it. I think api folder can be a first class citizen just like app.</li>
<li>Learning curve of server side and client side working in tandem, not specific to <em>NextJS</em>, just my limited knowledge so far.</li>
<li>Have to update all <code>Link</code> <em>components</em> to no longer contain <code>&lt;a&gt;</code> elements, or use a legacy version of <code>Link</code>.</li>
</ul>
</blockquote>
<hr />
<h4 id="heading-code-snippets">Code snippets</h4>
<p>I have added some code snippets to reference, hopefully adding some context to all the words above.</p>
<h3 id="heading-rootlayout">rootLayout</h3>
<pre><code class="lang-jsx">  <span class="hljs-keyword">import</span> React, { ReactNode } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
  <span class="hljs-keyword">import</span> { Iceland } <span class="hljs-keyword">from</span> <span class="hljs-string">'@next/font/google'</span>
  <span class="hljs-keyword">import</span> CustomContextMenu <span class="hljs-keyword">from</span> <span class="hljs-string">'../components/contextMenu'</span>
  <span class="hljs-keyword">import</span> BootstrapContext <span class="hljs-keyword">from</span> <span class="hljs-string">'../context/bootstrapContext'</span>
  <span class="hljs-keyword">import</span> Footer <span class="hljs-keyword">from</span> <span class="hljs-string">'../components/common/footer'</span>

  <span class="hljs-keyword">import</span> <span class="hljs-string">'../styles/globals.css'</span>

  <span class="hljs-keyword">const</span> iceland = Iceland({
    <span class="hljs-attr">weight</span>: <span class="hljs-string">'400'</span>,
    <span class="hljs-attr">style</span>: <span class="hljs-string">'normal'</span>,
    <span class="hljs-attr">subsets</span>: [<span class="hljs-string">'latin'</span>],
  })

  <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">RootLayout</span>(<span class="hljs-params">{ children }: { children: ReactNode }</span>) </span>{
    <span class="hljs-keyword">return</span> (
      <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span> <span class="hljs-attr">dir</span>=<span class="hljs-string">"ltr"</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{iceland.className}</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>The Shop<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"description"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"buy and sell merchandise online"</span> /&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/favicon.ico"</span> /&gt;</span>

          <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"manifest"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/site.webmanifest"</span> /&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">link</span>
            <span class="hljs-attr">rel</span>=<span class="hljs-string">"sitemap"</span>
            <span class="hljs-attr">type</span>=<span class="hljs-string">"application/xml"</span>
            <span class="hljs-attr">title</span>=<span class="hljs-string">"Sitemap"</span>
            <span class="hljs-attr">href</span>=<span class="hljs-string">"/sitemap.xml"</span>
          /&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"home"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/"</span> /&gt;</span>

          <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"canonical"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://www..."</span> /&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">link</span>
            <span class="hljs-attr">rel</span>=<span class="hljs-string">"apple-touch-icon"</span>
            <span class="hljs-attr">sizes</span>=<span class="hljs-string">"180x180"</span>
            <span class="hljs-attr">href</span>=<span class="hljs-string">"/apple-touch-icon.png"</span>
          /&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">link</span>
            <span class="hljs-attr">rel</span>=<span class="hljs-string">"icon"</span>
            <span class="hljs-attr">type</span>=<span class="hljs-string">"image/png"</span>
            <span class="hljs-attr">sizes</span>=<span class="hljs-string">"32x32"</span>
            <span class="hljs-attr">href</span>=<span class="hljs-string">"/favicon-32x32.png"</span>
          /&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">link</span>
            <span class="hljs-attr">rel</span>=<span class="hljs-string">"icon"</span>
            <span class="hljs-attr">type</span>=<span class="hljs-string">"image/png"</span>
            <span class="hljs-attr">sizes</span>=<span class="hljs-string">"16x16"</span>
            <span class="hljs-attr">href</span>=<span class="hljs-string">"/favicon-16x16.png"</span>
          /&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"manifest"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/site.webmanifest"</span> /&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"mask-icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/safari-pinned-tab.svg"</span> <span class="hljs-attr">color</span>=<span class="hljs-string">"#5bbad5"</span> /&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/favicon.ico"</span> /&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">body</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"font-theme"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">BootstrapContext</span>&gt;</span>{children}<span class="hljs-tag">&lt;/<span class="hljs-name">BootstrapContext</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">Footer</span> /&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">CustomContextMenu</span> /&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span>
    )
  }
</code></pre>
<hr />
<h3 id="heading-pagetemplate">PageTemplate</h3>
<pre><code class="lang-jsx">  <span class="hljs-keyword">import</span> React, {
    FC,
    Children,
    cloneElement,
    ReactNode,
    ReactElement,
  } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
  <span class="hljs-comment">// <span class="hljs-doctag">NOTE:</span>: PageContent just provides "padding: 1rem"</span>
  <span class="hljs-keyword">import</span> PageContent <span class="hljs-keyword">from</span> <span class="hljs-string">'../common/pageContent'</span>

  type PageTemplateProps = {
    <span class="hljs-attr">children</span>: ReactNode | ReactNode[]
    fixedLeftContent?: ReactNode | ReactNode[]
    fixedRightContent?: ReactNode | ReactNode[]
  }
  <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span>: maybe update hiding showing side columns based on count remove css.</span>
  <span class="hljs-keyword">const</span> PageTemplate: FC&lt;PageTemplateProps&gt; = <span class="hljs-function">(<span class="hljs-params">props: PageTemplateProps</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> { children, fixedLeftContent, fixedRightContent } = props

    <span class="hljs-keyword">const</span> fixedSideContentWidth =
      fixedRightContent &amp;&amp; fixedLeftContent
        ? <span class="hljs-string">'w-fixed lg:w-1/4 md:w-1/4 w-full flex-shrink flex-grow-0'</span>
        : <span class="hljs-string">'w-fixed lg:w-1/2 md:w-1/2 w-full flex-shrink flex-grow-0'</span>

    <span class="hljs-keyword">return</span> (
      <span class="xml"><span class="hljs-tag">&lt;&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"w-full flex flex-col sm:flex-row flex-wrap sm:flex-nowrap pb-4 flex-grow"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span>
            <span class="hljs-attr">className</span>=<span class="hljs-string">{</span>`${
              !<span class="hljs-attr">fixedLeftContent</span> || <span class="hljs-attr">Children.count</span>(<span class="hljs-attr">fixedLeftContent</span>) === <span class="hljs-string">0</span>
                ? '<span class="hljs-attr">hidden</span>'
                <span class="hljs-attr">:</span> <span class="hljs-attr">fixedSideContentWidth</span>
            }`}
          &gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"sticky w-full h-full justify-center text-center items-center"</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">PageContent</span>&gt;</span>
                {Children.count(fixedLeftContent) &gt; 0
                  ? Children.map(fixedLeftContent, (child: ReactElement) =&gt;
                      cloneElement(child),
                    )
                  : null}
              <span class="hljs-tag">&lt;/<span class="hljs-name">PageContent</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">main</span> <span class="hljs-attr">role</span>=<span class="hljs-string">"main"</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"w-auto flex-grow"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">PageContent</span>&gt;</span>
              {Children.count(children) === 1
                ? children
                : Children.map(children, (child: ReactElement) =&gt;
                    cloneElement(child),
                  )}
            <span class="hljs-tag">&lt;/<span class="hljs-name">PageContent</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span>
            <span class="hljs-attr">className</span>=<span class="hljs-string">{</span>`${
              !<span class="hljs-attr">fixedRightContent</span> || <span class="hljs-attr">Children.count</span>(<span class="hljs-attr">fixedRightContent</span>) === <span class="hljs-string">0</span>
                ? '<span class="hljs-attr">hidden</span>'
                <span class="hljs-attr">:</span> <span class="hljs-attr">fixedSideContentWidth</span>
            }`}
          &gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex sm:flex-col justify-center text-center items-center"</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">PageContent</span>&gt;</span>
                {Children.count(fixedRightContent) &gt; 0
                  ? Children.map(fixedRightContent, (child: ReactElement) =&gt;
                      cloneElement(child),
                    )
                  : null}
              <span class="hljs-tag">&lt;/<span class="hljs-name">PageContent</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;/&gt;</span></span>
    )
  }

  PageTemplate.defaultProps = {
    <span class="hljs-attr">fixedLeftContent</span>: <span class="hljs-literal">undefined</span>,
    <span class="hljs-attr">fixedRightContent</span>: <span class="hljs-literal">undefined</span>,
  }

  <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> PageTemplate
</code></pre>
<hr />
<h3 id="heading-errortsx">error.tsx</h3>
<pre><code class="lang-jsx">  <span class="hljs-string">'use client'</span>
<span class="hljs-keyword">import</span> React, { useState, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> Link <span class="hljs-keyword">from</span> <span class="hljs-string">'next/link'</span>

interface ErrorBoundaryProps {
  <span class="hljs-attr">error</span>: <span class="hljs-built_in">Error</span>
  <span class="hljs-attr">reset</span>: <span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">void</span>
}

interface <span class="hljs-built_in">Error</span> {
  <span class="hljs-attr">message</span>: string
}

<span class="hljs-keyword">const</span> __DEV__: boolean =
  !process.env.NODE_ENV || process.env.NODE_ENV === <span class="hljs-string">'development'</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">AppErrorBoundary</span>(<span class="hljs-params">{ error, reset }: ErrorBoundaryProps</span>) </span>{
  <span class="hljs-keyword">const</span> [hasError, setHasError] = useState&lt;boolean&gt;(<span class="hljs-literal">false</span>)

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (error) {
      setHasError(<span class="hljs-literal">true</span>)
      <span class="hljs-comment">// Log the error to an error reporting service</span>
      <span class="hljs-built_in">console</span>.error(error)
    }
  }, [error])

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex items-center justify-center w-screen h-screen bg-gradient-to-r from-indigo-600 to-blue-400"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"px-40 py-20 bg-white rounded-md shadow-xl"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-col items-center"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"font-bold text-blue-600 text-9xl capitalize"</span>&gt;</span>Error<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">h6</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mb-2 text-2xl font-bold text-center text-gray-800 md:text-3xl"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-red-500"</span>&gt;</span>Oops! <span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
            This wasn<span class="hljs-symbol">&amp;apos;</span>t meant to happen! 😫😢
          <span class="hljs-tag">&lt;/<span class="hljs-name">h6</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mb-8 text-center text-gray-500 md:text-lg"</span>&gt;</span>
            Sorry for any inconvenience. If you<span class="hljs-symbol">&amp;apos;</span>re seeing this often,
            please
            <span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/"</span>&gt;</span>contact us<span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">Link</span>
            <span class="hljs-attr">href</span>=<span class="hljs-string">"/"</span>
            <span class="hljs-attr">className</span>=<span class="hljs-string">"px-6 py-2 text-sm font-semibold text-blue-800 bg-blue-100"</span>
          &gt;</span>
            Take me home
          <span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>
          {__DEV__ &amp;&amp; (
            <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> reset()}&gt;Reset error boundary<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
          )}
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  )
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> AppErrorBoundary
</code></pre>
<hr />
<h3 id="heading-listpagetsx">list/page.tsx</h3>
<pre><code class="lang-jsx">  <span class="hljs-keyword">import</span> prisma <span class="hljs-keyword">from</span> <span class="hljs-string">'../../../../lib/prisma'</span>
  <span class="hljs-keyword">import</span> { MerchandiseApi } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../../../pages/api/merchandiseApi'</span>
  <span class="hljs-keyword">import</span> { formatDate } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../../../utils/helpers/commonFunctions'</span>
  <span class="hljs-keyword">import</span> CardGrid <span class="hljs-keyword">from</span> <span class="hljs-string">'../../../../components/cardGrid'</span>
  <span class="hljs-keyword">import</span> Title <span class="hljs-keyword">from</span> <span class="hljs-string">'../../../../components/common/title'</span>
  <span class="hljs-keyword">import</span> SearchBanner <span class="hljs-keyword">from</span> <span class="hljs-string">'../../../../components/searchBanner'</span>
  <span class="hljs-keyword">import</span> PageTemplate <span class="hljs-keyword">from</span> <span class="hljs-string">'../../../../components/pageTemplate'</span>

  <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getData</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> prisma.merchandise.findMany({
      <span class="hljs-attr">include</span>: {
        <span class="hljs-attr">seller</span>: { <span class="hljs-attr">include</span>: { <span class="hljs-attr">user</span>: <span class="hljs-literal">true</span> } },
        <span class="hljs-attr">shop</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">item</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">itemImages</span>: { <span class="hljs-attr">include</span>: { <span class="hljs-attr">image</span>: <span class="hljs-literal">true</span> } },
      },
    })

    <span class="hljs-keyword">const</span> merchandise: MerchandiseApi = {
      <span class="hljs-attr">merchandise</span>: data.map(<span class="hljs-function"><span class="hljs-params">item</span> =&gt;</span> {
        <span class="hljs-keyword">return</span> {
          ...item,
          <span class="hljs-attr">creationDate</span>: formatDate(item.creationDate, <span class="hljs-string">'readableShortDate'</span>),
        }
      }),
    }
    <span class="hljs-keyword">return</span> merchandise
  }

  <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">PropertiesPage</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> getData()

    <span class="hljs-keyword">return</span> (
      <span class="xml"><span class="hljs-tag">&lt;&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">SearchBanner</span>
          <span class="hljs-attr">bannerImage</span>=<span class="hljs-string">""</span>
          <span class="hljs-attr">bannerHasSearch</span>
          <span class="hljs-attr">showPropertyTypeFilters</span>
          <span class="hljs-attr">width</span>=<span class="hljs-string">"w-full mx-auto transition-width transition-slowest ease"</span>
        /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">PageTemplate</span>
          <span class="hljs-attr">fixedLeftContent</span>=<span class="hljs-string">{[</span>&lt;<span class="hljs-attr">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-black"</span>&gt;</span>left content<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>]}
          fixedRightContent={[<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-black"</span>&gt;</span>right content<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>]}
        &gt;
          <span class="hljs-tag">&lt;<span class="hljs-name">Title</span>
            <span class="hljs-attr">headerType</span>=<span class="hljs-string">"h1Title"</span>
            <span class="hljs-attr">containerClassName</span>=<span class="hljs-string">"text-center"</span>
            <span class="hljs-attr">title</span>=<span class="hljs-string">"Merchandise result page"</span>
          /&gt;</span>

          <span class="hljs-tag">&lt;<span class="hljs-name">CardGrid</span> <span class="hljs-attr">data</span>=<span class="hljs-string">{data.merchandise}</span> /&gt;</span>

        <span class="hljs-tag">&lt;/<span class="hljs-name">PageTemplate</span>&gt;</span>
      <span class="hljs-tag">&lt;/&gt;</span></span>
    )
  }
</code></pre>
<hr />
<h3 id="heading-stripe">stripe</h3>
<pre><code class="lang-ts">  <span class="hljs-keyword">import</span> { Stripe, loadStripe } <span class="hljs-keyword">from</span> <span class="hljs-string">'@stripe/stripe-js'</span>

  <span class="hljs-keyword">let</span> stripePromise: <span class="hljs-built_in">Promise</span>&lt;Stripe | <span class="hljs-literal">null</span>&gt;
  <span class="hljs-keyword">const</span> getStripe = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (!stripePromise) {
      stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!)
    }
    <span class="hljs-keyword">return</span> stripePromise
  }

  <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> getStripe
</code></pre>
<pre><code class="lang-jsx">  <span class="hljs-string">'use client'</span>
  <span class="hljs-keyword">import</span> { useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
  <span class="hljs-keyword">import</span> { loadStripe } <span class="hljs-keyword">from</span> <span class="hljs-string">'@stripe/stripe-js'</span>
  <span class="hljs-keyword">import</span> { usePathname } <span class="hljs-keyword">from</span> <span class="hljs-string">'next/navigation'</span>
  <span class="hljs-keyword">import</span> { PrimaryButton } <span class="hljs-keyword">from</span> <span class="hljs-string">'../common/button'</span>

  <span class="hljs-comment">// Make sure to call `loadStripe` outside of a component’s render to avoid</span>
  <span class="hljs-comment">// recreating the `Stripe` object on every render.</span>
  <span class="hljs-keyword">const</span> stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY)

  <span class="hljs-keyword">const</span> StripPaymentButton = <span class="hljs-function">() =&gt;</span> {
    useEffect(<span class="hljs-function">() =&gt;</span> {
      <span class="hljs-comment">// Check to see if this is a redirect back from Checkout</span>
      <span class="hljs-keyword">const</span> query = <span class="hljs-keyword">new</span> URLSearchParams(<span class="hljs-built_in">window</span>.location.search)
      <span class="hljs-keyword">if</span> (query.get(<span class="hljs-string">'success'</span>)) {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Order placed! You will receive an email confirmation.'</span>)
      }

      <span class="hljs-keyword">if</span> (query.get(<span class="hljs-string">'canceled'</span>)) {
        <span class="hljs-built_in">console</span>.log(
          <span class="hljs-string">'Order canceled -- continue to shop around and checkout when you’re ready.'</span>,
        )
      }
    }, [])

    <span class="hljs-keyword">const</span> pathname = usePathname()
    <span class="hljs-built_in">console</span>.log(
      <span class="hljs-string">'🚀 ~ file: index.tsx ~ line 21 ~ StripPaymentButton ~ pathname'</span>,
      pathname,
    )

    <span class="hljs-keyword">return</span> (
      <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">form</span>
        <span class="hljs-attr">action</span>=<span class="hljs-string">"/api/checkout_sessions"</span>
        <span class="hljs-attr">method</span>=<span class="hljs-string">"POST"</span>
        <span class="hljs-attr">className</span>=<span class="hljs-string">"relative w-full"</span>
      &gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">PrimaryButton</span>
          <span class="hljs-attr">width</span>=<span class="hljs-string">"lg:w-max md:w-max w-full"</span>
          <span class="hljs-attr">height</span>=<span class="hljs-string">"h-full"</span>
          <span class="hljs-attr">buttonPadding</span>=<span class="hljs-string">"p-1"</span>
          <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span>
          <span class="hljs-attr">role</span>=<span class="hljs-string">"link"</span>
          <span class="hljs-attr">textColour</span>=<span class="hljs-string">"text-gray-700"</span>
          <span class="hljs-attr">borderRadius</span>=<span class="hljs-string">"rounded-sm"</span>
          <span class="hljs-attr">backgroundColor</span>=<span class="hljs-string">"bg-green-300"</span>
          <span class="hljs-attr">containerClassName</span>=<span class="hljs-string">"absolute lg:right-2 right-0 lg:-top-64 md:-top-56 -top-24 lg:w-auto md:w-auto w-full"</span>
        &gt;</span>
          Purchase
        <span class="hljs-tag">&lt;/<span class="hljs-name">PrimaryButton</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span></span>
    )
  }

  <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> StripPaymentButton
</code></pre>
<p>pages/api/checkout_sessions</p>
<pre><code class="lang-jsx">  <span class="hljs-keyword">import</span> { NextApiRequest, NextApiResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">'next'</span>
  <span class="hljs-keyword">import</span> Stripe <span class="hljs-keyword">from</span> <span class="hljs-string">'stripe'</span>

  <span class="hljs-keyword">const</span> stripe = <span class="hljs-keyword">new</span> Stripe(process.env.STRIPE_SECRET_KEY!, {
    <span class="hljs-comment">// https://github.com/stripe/stripe-node#configuration</span>
    <span class="hljs-attr">apiVersion</span>: <span class="hljs-string">'2022-08-01'</span>,
  })

  <span class="hljs-keyword">const</span> stripeHandler = <span class="hljs-keyword">async</span> (
    req: NextApiRequest,
    <span class="hljs-attr">res</span>: NextApiResponse,
  ): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-keyword">void</span>&gt; =&gt; {
    <span class="hljs-keyword">if</span> (req.method === <span class="hljs-string">'POST'</span>) {
      <span class="hljs-keyword">try</span> {
        <span class="hljs-comment">// Create Checkout Sessions from body params.</span>
        <span class="hljs-keyword">const</span> session = <span class="hljs-keyword">await</span> stripe.checkout.sessions.create({
          <span class="hljs-attr">submit_type</span>: <span class="hljs-string">'pay'</span>,
          <span class="hljs-attr">payment_method_types</span>: [<span class="hljs-string">'card'</span>],
          <span class="hljs-attr">line_items</span>: [
            {
              <span class="hljs-comment">// Provide the exact Price ID (for example, pr_1234) of the product you want to sell</span>
              <span class="hljs-attr">price</span>: <span class="hljs-string">'YOUR_PRICE_CODE_FOR_THIS_ITEM,
              quantity: 1,
            },
          ],
          mode: '</span>payment<span class="hljs-string">',
          success_url: `${req.headers.origin}/?success=true`,
          cancel_url: `${req.headers.origin}/?canceled=true`,
          automatic_tax: { enabled: true },
        })
        res.redirect(303, session.url)
      } catch (err) {
        res.status(err.statusCode || 500).json(err.message)
      }
    } else {
      // TODO:: do i need '</span>OPTIONS<span class="hljs-string">' here also?
      res.setHeader('</span>Allow<span class="hljs-string">', '</span>POST<span class="hljs-string">')
      res.status(405).end('</span>Method Not Allowed<span class="hljs-string">')
    }
  }

  export default stripeHandler</span>
</code></pre>
<hr />
<h3 id="heading-pagesapiauthnextauthts">pages/api/auth/[...nextauth].ts</h3>
<pre><code class="lang-js">  <span class="hljs-keyword">const</span> handleNavToListMyProperty = useCallback(
    <span class="hljs-function">(<span class="hljs-params">event: MouseEvent&lt;HTMLButtonElement&gt;</span>) =&gt;</span> {
      event.preventDefault()
      router.push(!session ? <span class="hljs-string">'/api/auth/signin'</span> : <span class="hljs-string">'/create-user'</span>)
    },
    [session],
  )
</code></pre>
<pre><code class="lang-jsx">  <span class="hljs-keyword">import</span> { NextApiHandler } <span class="hljs-keyword">from</span> <span class="hljs-string">'next'</span>
  <span class="hljs-keyword">import</span> NextAuth <span class="hljs-keyword">from</span> <span class="hljs-string">'next-auth'</span>
  <span class="hljs-keyword">import</span> { PrismaAdapter } <span class="hljs-keyword">from</span> <span class="hljs-string">'@next-auth/prisma-adapter'</span>
  <span class="hljs-keyword">import</span> GitHubProvider <span class="hljs-keyword">from</span> <span class="hljs-string">'next-auth/providers/github'</span>
  <span class="hljs-keyword">import</span> prisma <span class="hljs-keyword">from</span> <span class="hljs-string">'../../../lib/prisma'</span>

  <span class="hljs-keyword">const</span> authHandler: NextApiHandler = <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> NextAuth(req, res, options)
  <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> authHandler

  <span class="hljs-keyword">const</span> options = {
    <span class="hljs-attr">providers</span>: [
      GitHubProvider({
        <span class="hljs-attr">clientId</span>: process.env.GITHUB_ID,
        <span class="hljs-attr">clientSecret</span>: process.env.GITHUB_SECRET,
      }),
    ],
    <span class="hljs-attr">adapter</span>: PrismaAdapter(prisma),
    <span class="hljs-attr">secret</span>: process.env.SECRET,
  }
</code></pre>
<hr />
<h3 id="heading-prisma">Prisma</h3>
<pre><code class="lang-ts">  <span class="hljs-keyword">import</span> { PrismaClient } <span class="hljs-keyword">from</span> <span class="hljs-string">'@prisma/client'</span>

  <span class="hljs-keyword">let</span> prisma: PrismaClient

  <span class="hljs-keyword">if</span> (process.env.NODE_ENV === <span class="hljs-string">'production'</span>) {
    prisma = <span class="hljs-keyword">new</span> PrismaClient()
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">global</span>.prisma) {
      <span class="hljs-built_in">global</span>.prisma = <span class="hljs-keyword">new</span> PrismaClient()
    }
    prisma = <span class="hljs-built_in">global</span>.prisma
  }

  <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> prisma
</code></pre>
<p>client-side api POST example: <code>pages/api/add-new-item</code></p>
<pre><code class="lang-ts">  <span class="hljs-keyword">const</span> newItem = <span class="hljs-keyword">await</span> fetcher(
    <span class="hljs-string">`<span class="hljs-subst">${endPoint}</span>/add-new-item`</span>,
    payload,
    <span class="hljs-string">'POST'</span>,
  )
</code></pre>
<pre><code class="lang-ts">  <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> fetcher = <span class="hljs-keyword">async</span> &lt;T&gt;(
    endpointUrl: <span class="hljs-built_in">string</span>,
    payload?: T | Record&lt;<span class="hljs-built_in">string</span>, <span class="hljs-built_in">string</span>&gt; | <span class="hljs-built_in">string</span>,
    method = <span class="hljs-string">'GET'</span>,
  ): <span class="hljs-built_in">Promise</span>&lt;T&gt; =&gt; {
    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(endpointUrl, {
      method,
      headers: {
        <span class="hljs-string">'content-type'</span>: <span class="hljs-string">'application/json;charset=UTF-8'</span>,
      },
      body: <span class="hljs-built_in">JSON</span>.stringify(payload),
    })

    <span class="hljs-keyword">const</span> responseData = <span class="hljs-keyword">await</span> response.json()

    <span class="hljs-keyword">if</span> (response.ok) {
      <span class="hljs-keyword">if</span> (responseData) {
        <span class="hljs-keyword">return</span> responseData
      } <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">Promise</span>.reject(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>())
      }
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-keyword">const</span> error = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(
        responseData.errors?.map(<span class="hljs-function">(<span class="hljs-params">e: <span class="hljs-built_in">Error</span></span>) =&gt;</span> e.message).join(<span class="hljs-string">'\n'</span>) ?? <span class="hljs-string">'unknown'</span>,
      )
      <span class="hljs-keyword">return</span> <span class="hljs-built_in">Promise</span>.reject(error)
    }
  }
</code></pre>
<pre><code class="lang-ts">  <span class="hljs-keyword">import</span> { NextApiRequest, NextApiResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">'next'</span>
  <span class="hljs-keyword">import</span> prisma <span class="hljs-keyword">from</span> <span class="hljs-string">'../../../lib/prisma'</span>
  <span class="hljs-keyword">import</span> { ItemDetails } <span class="hljs-keyword">from</span> <span class="hljs-string">'../itemDetails'</span>

  <span class="hljs-keyword">const</span> itemDetailsHandler = <span class="hljs-keyword">async</span> (
    req: NextApiRequest,
    res: NextApiResponse,
  ): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; =&gt; {
    <span class="hljs-keyword">const</span> {
      body,
      <span class="hljs-comment">// query,</span>
      method,
    } = req

    <span class="hljs-keyword">switch</span> (method) {
      <span class="hljs-keyword">case</span> <span class="hljs-string">'POST'</span>:
        <span class="hljs-keyword">const</span> newData: ItemDetails = {
          ...body,
        }

        <span class="hljs-keyword">try</span> {
          <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> prisma.item.create({
            data: {
              sometablecolumn: newData.value
              ...
            },
          })

          <span class="hljs-keyword">const</span> newItem: ItemDetails = data

          <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">200</span>).json(newItem)

          <span class="hljs-comment">// return res.send(payload);</span>
        } <span class="hljs-keyword">catch</span> (error: <span class="hljs-built_in">any</span>) {
          <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span>: dispatch error to redux here maybe?</span>
          <span class="hljs-comment">// throw error for errorWrapper to handle?</span>
          <span class="hljs-comment">// send to some error logging service?</span>
          <span class="hljs-keyword">throw</span> error
        }

      <span class="hljs-keyword">default</span>:
        res.setHeader(<span class="hljs-string">'Allow'</span>, [<span class="hljs-string">'OPTIONS'</span>])
        res.status(<span class="hljs-number">405</span>).end(<span class="hljs-string">`Method <span class="hljs-subst">${method}</span> Not Allowed`</span>)
    }
  }

  <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> itemDetailsHandler
</code></pre>
<hr />
<h3 id="heading-tailwindcss">tailwindcss</h3>
<pre><code class="lang-css">  <span class="hljs-comment">/* <span class="hljs-doctag">TODO:</span>: import any custom fonts here */</span>

  <span class="hljs-comment">/* import/add tailwind styles to project */</span>
  <span class="hljs-keyword">@import</span> <span class="hljs-string">"tailwindcss/base"</span>;
  <span class="hljs-keyword">@import</span> <span class="hljs-string">"./custom-base.css"</span>;

  <span class="hljs-keyword">@import</span> <span class="hljs-string">"tailwindcss/components"</span>;
  <span class="hljs-keyword">@import</span> <span class="hljs-string">"./custom-components.css"</span>;

  <span class="hljs-comment">/* import any npm dependency styles here under "tailwindcss/components" to make sure they are added as "custom" styling */</span>
  <span class="hljs-keyword">@import</span> <span class="hljs-string">"tailwindcss/utilities"</span>;
  <span class="hljs-comment">/* @import "custom-utilities.css"; */</span>
</code></pre>
<pre><code class="lang-json"><span class="hljs-comment">/** @type {import('tailwindcss').Config} */</span>

module.exports = {
  content: [
    './app<span class="hljs-comment">/**/</span>*.{js,ts,jsx,tsx}',
    './pages<span class="hljs-comment">/**/</span>*.{js,ts,jsx,tsx}',
    './components<span class="hljs-comment">/**/</span>*.{js,ts,jsx,tsx}',
  ],
  presets: [],
  darkMode: 'media', <span class="hljs-comment">// or 'class'</span>
  variants: {
    extend: {
      opacity: ['disabled'],
    },
  },
  theme: {
    extend: {
      transitionProperty: {
        width: 'width',
      },
      zIndex: {
        '-1': '<span class="hljs-number">-1</span>',
      },
      transformOrigin: {
        0: '<span class="hljs-number">0</span>%',
      },
      animation: {
        loader: 'loader <span class="hljs-number">0.6</span>s infinite alternate',
      },
      keyframes: {
        loader: {
          from: {
            opacity: <span class="hljs-number">1</span>,
            transform: 'translate3d(<span class="hljs-number">0</span>, -1rem, 0)',
          },
          to: {
            opacity: <span class="hljs-number">0.1</span>,
          },
        },
      },
      fontFamily: {
        script: [<span class="hljs-string">"'Nanum Brush Script', cursive"</span>],
        theme: [<span class="hljs-string">"'Chillax-Regular', sans"</span>],
      },
    },
    screens: {
      sm: '<span class="hljs-number">640</span>px',
      md: '<span class="hljs-number">768</span>px',
      lg: '<span class="hljs-number">1024</span>px',
      xl: '<span class="hljs-number">1280</span>px',
      '2xl': '<span class="hljs-number">1536</span>px',
    },
    colors: ({ colors }) =&gt; ({
      inherit: colors.inherit,
      current: colors.current,
      transparent: colors.transparent,
      black: colors.black,
      white: colors.white,
      slate: colors.slate,
      gray: colors.gray,
      zinc: colors.zinc,
      neutral: colors.neutral,
      stone: colors.stone,
      red: colors.red,
      orange: colors.orange,
      amber: colors.amber,
      yellow: colors.yellow,
      lime: colors.lime,
      green: colors.green,
      emerald: colors.emerald,
      teal: colors.teal,
      cyan: colors.cyan,
      sky: colors.sky,
      blue: colors.blue,
      indigo: colors.indigo,
      violet: colors.violet,
      purple: colors.purple,
      fuchsia: colors.fuchsia,
      pink: colors.pink,
      rose: colors.rose,



      'primary-colour': '#fb5607',
      'secondary-colour': '#ff006e',
      'tertiary-colour': '#<span class="hljs-number">3</span>a86ff',
      'secondary-contrast-colour': '#ffbe0b',
      'contrast-colour': '#<span class="hljs-number">8338</span>ec',

      'theme-black': '#<span class="hljs-number">121212</span>',
      'theme-grey': '#<span class="hljs-number">535353</span>', 
    }),
    columns: {
      auto: 'auto',
      1: '<span class="hljs-number">1</span>',
      2: '<span class="hljs-number">2</span>',
      3: '<span class="hljs-number">3</span>',
      4: '<span class="hljs-number">4</span>',
      5: '<span class="hljs-number">5</span>',
      6: '<span class="hljs-number">6</span>',
      7: '<span class="hljs-number">7</span>',
      8: '<span class="hljs-number">8</span>',
      9: '<span class="hljs-number">9</span>',
      10: '<span class="hljs-number">10</span>',
      11: '<span class="hljs-number">11</span>',
      12: '<span class="hljs-number">12</span>',
      '3xs': '<span class="hljs-number">16</span>rem',
      '2xs': '<span class="hljs-number">18</span>rem',
      xs: '<span class="hljs-number">20</span>rem',
      sm: '<span class="hljs-number">24</span>rem',
      md: '<span class="hljs-number">28</span>rem',
      lg: '<span class="hljs-number">32</span>rem',
      xl: '<span class="hljs-number">36</span>rem',
      '2xl': '<span class="hljs-number">42</span>rem',
      '3xl': '<span class="hljs-number">48</span>rem',
      '4xl': '<span class="hljs-number">56</span>rem',
      '5xl': '<span class="hljs-number">64</span>rem',
      '6xl': '<span class="hljs-number">72</span>rem',
      '7xl': '<span class="hljs-number">80</span>rem',
    },
    spacing: {
      px: '<span class="hljs-number">1</span>px',
      0: '<span class="hljs-number">0</span>px',
      0.5: '<span class="hljs-number">0.125</span>rem',
      1: '<span class="hljs-number">0.25</span>rem',
      1.5: '<span class="hljs-number">0.375</span>rem',
      2: '<span class="hljs-number">0.5</span>rem',
      2.5: '<span class="hljs-number">0.625</span>rem',
      3: '<span class="hljs-number">0.75</span>rem',
      3.5: '<span class="hljs-number">0.875</span>rem',
      4: '<span class="hljs-number">1</span>rem',
      5: '<span class="hljs-number">1.25</span>rem',
      6: '<span class="hljs-number">1.5</span>rem',
      7: '<span class="hljs-number">1.75</span>rem',
      8: '<span class="hljs-number">2</span>rem',
      9: '<span class="hljs-number">2.25</span>rem',
      10: '<span class="hljs-number">2.5</span>rem',
      11: '<span class="hljs-number">2.75</span>rem',
      12: '<span class="hljs-number">3</span>rem',
      14: '<span class="hljs-number">3.5</span>rem',
      16: '<span class="hljs-number">4</span>rem',
      20: '<span class="hljs-number">5</span>rem',
      24: '<span class="hljs-number">6</span>rem',
      28: '<span class="hljs-number">7</span>rem',
      32: '<span class="hljs-number">8</span>rem',
      36: '<span class="hljs-number">9</span>rem',
      40: '<span class="hljs-number">10</span>rem',
      44: '<span class="hljs-number">11</span>rem',
      48: '<span class="hljs-number">12</span>rem',
      52: '<span class="hljs-number">13</span>rem',
      56: '<span class="hljs-number">14</span>rem',
      60: '<span class="hljs-number">15</span>rem',
      64: '<span class="hljs-number">16</span>rem',
      72: '<span class="hljs-number">18</span>rem',
      80: '<span class="hljs-number">20</span>rem',
      96: '<span class="hljs-number">24</span>rem',
    },
    animation: {
      none: 'none',
      spin: 'spin <span class="hljs-number">1</span>s linear infinite',
      ping: 'ping <span class="hljs-number">1</span>s cubic-bezier(<span class="hljs-number">0</span>, 0, 0.2, 1) infinite',
      pulse: 'pulse <span class="hljs-number">2</span>s cubic-bezier(<span class="hljs-number">0.4</span>, 0, 0.6, 1) infinite',
      bounce: 'bounce <span class="hljs-number">1</span>s infinite',
      fade: 'fadeIn <span class="hljs-number">2</span>s ease',
    },
    aspectRatio: {
      auto: 'auto',
      square: '<span class="hljs-number">1</span> / <span class="hljs-number">1</span>',
      video: '<span class="hljs-number">16</span> / <span class="hljs-number">9</span>',
    },
    backdropBlur: ({ theme }) =&gt; theme('blur'),
    backdropBrightness: ({ theme }) =&gt; theme('brightness'),
    backdropContrast: ({ theme }) =&gt; theme('contrast'),
    backdropGrayscale: ({ theme }) =&gt; theme('grayscale'),
    backdropHueRotate: ({ theme }) =&gt; theme('hueRotate'),
    backdropInvert: ({ theme }) =&gt; theme('invert'),
    backdropOpacity: ({ theme }) =&gt; theme('opacity'),
    backdropSaturate: ({ theme }) =&gt; theme('saturate'),
    backdropSepia: ({ theme }) =&gt; theme('sepia'),
    backgroundColor: ({ theme }) =&gt; ({
      ...theme('colors'),
      'primary-colour': '#fb5607',
      'secondary-colour': '#ff006e',
      'contrast-colour': '#<span class="hljs-number">8338</span>ec',
    }),
    backgroundImage: {
      none: 'none',
      'gradient-to-t': 'linear-gradient(to top, var(--tw-gradient-stops))',
      'gradient-to-tr':
        'linear-gradient(to top right, var(--tw-gradient-stops))',
      'gradient-to-r': 'linear-gradient(to right, var(--tw-gradient-stops))',
      'gradient-to-br':
        'linear-gradient(to bottom right, var(--tw-gradient-stops))',
      'gradient-to-b': 'linear-gradient(to bottom, var(--tw-gradient-stops))',
      'gradient-to-bl':
        'linear-gradient(to bottom left, var(--tw-gradient-stops))',
      'gradient-to-l': 'linear-gradient(to left, var(--tw-gradient-stops))',
      'gradient-to-tl':
        'linear-gradient(to top left, var(--tw-gradient-stops))',
    },
    backgroundOpacity: ({ theme }) =&gt; theme('opacity'),
    backgroundPosition: {
      bottom: 'bottom',
      center: 'center',
      left: 'left',
      'left-bottom': 'left bottom',
      'left-top': 'left top',
      right: 'right',
      'right-bottom': 'right bottom',
      'right-top': 'right top',
      top: 'top',
    },
    backgroundSize: {
      auto: 'auto',
      cover: 'cover',
      contain: 'contain',
    },
    blur: {
      0: '<span class="hljs-number">0</span>',
      none: '<span class="hljs-number">0</span>',
      sm: '<span class="hljs-number">4</span>px',
      DEFAULT: '<span class="hljs-number">8</span>px',
      md: '<span class="hljs-number">12</span>px',
      lg: '<span class="hljs-number">16</span>px',
      xl: '<span class="hljs-number">24</span>px',
      '2xl': '<span class="hljs-number">40</span>px',
      '3xl': '<span class="hljs-number">64</span>px',
    },
    brightness: {
      0: '<span class="hljs-number">0</span>',
      50: '<span class="hljs-number">.5</span>',
      75: '<span class="hljs-number">.75</span>',
      90: '<span class="hljs-number">.9</span>',
      95: '<span class="hljs-number">.95</span>',
      100: '<span class="hljs-number">1</span>',
      105: '<span class="hljs-number">1.05</span>',
      110: '<span class="hljs-number">1.1</span>',
      125: '<span class="hljs-number">1.25</span>',
      150: '<span class="hljs-number">1.5</span>',
      200: '<span class="hljs-number">2</span>',
    },
    borderColor: ({ theme }) =&gt; ({
      ...theme('colors'),
      DEFAULT: theme('colors.gray<span class="hljs-number">.200</span>', 'currentColor'),
    }),
    borderOpacity: ({ theme }) =&gt; theme('opacity'),
    borderRadius: {
      none: '<span class="hljs-number">0</span>px',
      sm: '<span class="hljs-number">0.125</span>rem',
      DEFAULT: '<span class="hljs-number">0.25</span>rem',
      md: '<span class="hljs-number">0.375</span>rem',
      lg: '<span class="hljs-number">0.5</span>rem',
      xl: '<span class="hljs-number">0.75</span>rem',
      '2xl': '<span class="hljs-number">1</span>rem',
      '3xl': '<span class="hljs-number">1.5</span>rem',
      full: '<span class="hljs-number">9999</span>px',
    },
    borderWidth: {
      DEFAULT: '<span class="hljs-number">1</span>px',
      0: '<span class="hljs-number">0</span>px',
      2: '<span class="hljs-number">2</span>px',
      4: '<span class="hljs-number">4</span>px',
      8: '<span class="hljs-number">8</span>px',
    },
    boxShadow: {
      sm: '<span class="hljs-number">0</span> <span class="hljs-number">1</span>px <span class="hljs-number">2</span>px <span class="hljs-number">0</span> rgb(<span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">0</span> / <span class="hljs-number">0.05</span>)',
      DEFAULT: '<span class="hljs-number">0</span> <span class="hljs-number">1</span>px <span class="hljs-number">3</span>px <span class="hljs-number">0</span> rgb(<span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">0</span> / <span class="hljs-number">0.1</span>), 0 1px 2px -1px rgb(0 0 0 / 0.1)',
      md: '<span class="hljs-number">0</span> <span class="hljs-number">4</span>px <span class="hljs-number">6</span>px <span class="hljs-number">-1</span>px rgb(<span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">0</span> / <span class="hljs-number">0.1</span>), 0 2px 4px -2px rgb(0 0 0 / 0.1)',
      lg: '<span class="hljs-number">0</span> <span class="hljs-number">10</span>px <span class="hljs-number">15</span>px <span class="hljs-number">-3</span>px rgb(<span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">0</span> / <span class="hljs-number">0.1</span>), 0 4px 6px -4px rgb(0 0 0 / 0.1)',
      xl: '<span class="hljs-number">0</span> <span class="hljs-number">20</span>px <span class="hljs-number">25</span>px <span class="hljs-number">-5</span>px rgb(<span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">0</span> / <span class="hljs-number">0.1</span>), 0 8px 10px -6px rgb(0 0 0 / 0.1)',
      '2xl': '<span class="hljs-number">0</span> <span class="hljs-number">25</span>px <span class="hljs-number">50</span>px <span class="hljs-number">-12</span>px rgb(<span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">0</span> / <span class="hljs-number">0.25</span>)',
      inner: 'inset <span class="hljs-number">0</span> <span class="hljs-number">2</span>px <span class="hljs-number">4</span>px <span class="hljs-number">0</span> rgb(<span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">0</span> / <span class="hljs-number">0.05</span>)',
      none: 'none',
    },
    boxShadowColor: ({ theme }) =&gt; theme('colors'),
    caretColor: ({ theme }) =&gt; theme('colors'),
    accentColor: ({ theme }) =&gt; ({
      ...theme('colors'),
      auto: 'auto',
    }),
    contrast: {
      0: '<span class="hljs-number">0</span>',
      50: '<span class="hljs-number">.5</span>',
      75: '<span class="hljs-number">.75</span>',
      100: '<span class="hljs-number">1</span>',
      125: '<span class="hljs-number">1.25</span>',
      150: '<span class="hljs-number">1.5</span>',
      200: '<span class="hljs-number">2</span>',
    },
    container: {},
    content: {
      none: 'none',
    },
    cursor: {
      auto: 'auto',
      default: 'default',
      pointer: 'pointer',
      wait: 'wait',
      text: 'text',
      move: 'move',
      help: 'help',
      'not-allowed': 'not-allowed',
      none: 'none',
      'context-menu': 'context-menu',
      progress: 'progress',
      cell: 'cell',
      crosshair: 'crosshair',
      'vertical-text': 'vertical-text',
      alias: 'alias',
      copy: 'copy',
      'no-drop': 'no-drop',
      grab: 'grab',
      grabbing: 'grabbing',
      'all-scroll': 'all-scroll',
      'col-resize': 'col-resize',
      'row-resize': 'row-resize',
      'n-resize': 'n-resize',
      'e-resize': 'e-resize',
      's-resize': 's-resize',
      'w-resize': 'w-resize',
      'ne-resize': 'ne-resize',
      'nw-resize': 'nw-resize',
      'se-resize': 'se-resize',
      'sw-resize': 'sw-resize',
      'ew-resize': 'ew-resize',
      'ns-resize': 'ns-resize',
      'nesw-resize': 'nesw-resize',
      'nwse-resize': 'nwse-resize',
      'zoom-in': 'zoom-in',
      'zoom-out': 'zoom-out',
    },
    divideColor: ({ theme }) =&gt; theme('borderColor'),
    divideOpacity: ({ theme }) =&gt; theme('borderOpacity'),
    divideWidth: ({ theme }) =&gt; theme('borderWidth'),
    dropShadow: {
      sm: '<span class="hljs-number">0</span> <span class="hljs-number">1</span>px <span class="hljs-number">1</span>px rgb(<span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">0</span> / <span class="hljs-number">0.05</span>)',
      DEFAULT: ['<span class="hljs-number">0</span> <span class="hljs-number">1</span>px <span class="hljs-number">2</span>px rgb(<span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">0</span> / <span class="hljs-number">0.1</span>)', '<span class="hljs-number">0</span> <span class="hljs-number">1</span>px <span class="hljs-number">1</span>px rgb(<span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">0</span> / <span class="hljs-number">0.06</span>)'],
      md: ['<span class="hljs-number">0</span> <span class="hljs-number">4</span>px <span class="hljs-number">3</span>px rgb(<span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">0</span> / <span class="hljs-number">0.07</span>)', '<span class="hljs-number">0</span> <span class="hljs-number">2</span>px <span class="hljs-number">2</span>px rgb(<span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">0</span> / <span class="hljs-number">0.06</span>)'],
      lg: ['<span class="hljs-number">0</span> <span class="hljs-number">10</span>px <span class="hljs-number">8</span>px rgb(<span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">0</span> / <span class="hljs-number">0.04</span>)', '<span class="hljs-number">0</span> <span class="hljs-number">4</span>px <span class="hljs-number">3</span>px rgb(<span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">0</span> / <span class="hljs-number">0.1</span>)'],
      xl: ['<span class="hljs-number">0</span> <span class="hljs-number">20</span>px <span class="hljs-number">13</span>px rgb(<span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">0</span> / <span class="hljs-number">0.03</span>)', '<span class="hljs-number">0</span> <span class="hljs-number">8</span>px <span class="hljs-number">5</span>px rgb(<span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">0</span> / <span class="hljs-number">0.08</span>)'],
      '2xl': '<span class="hljs-number">0</span> <span class="hljs-number">25</span>px <span class="hljs-number">25</span>px rgb(<span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">0</span> / <span class="hljs-number">0.15</span>)',
      none: '<span class="hljs-number">0</span> <span class="hljs-number">0</span> #<span class="hljs-number">0000</span>',
    },
    fill: ({ theme }) =&gt; theme('colors'),
    grayscale: {
      0: '<span class="hljs-number">0</span>',
      DEFAULT: '<span class="hljs-number">100</span>%',
    },
    hueRotate: {
      0: '<span class="hljs-number">0</span>deg',
      15: '<span class="hljs-number">15</span>deg',
      30: '<span class="hljs-number">30</span>deg',
      60: '<span class="hljs-number">60</span>deg',
      90: '<span class="hljs-number">90</span>deg',
      180: '<span class="hljs-number">180</span>deg',
    },
    invert: {
      0: '<span class="hljs-number">0</span>',
      DEFAULT: '<span class="hljs-number">100</span>%',
    },
    flex: {
      1: '<span class="hljs-number">1</span> <span class="hljs-number">1</span> <span class="hljs-number">0</span>%',
      auto: '<span class="hljs-number">1</span> <span class="hljs-number">1</span> auto',
      initial: '<span class="hljs-number">0</span> <span class="hljs-number">1</span> auto',
      none: 'none',
    },
    flexBasis: ({ theme }) =&gt; ({
      auto: 'auto',
      ...theme('spacing'),
      '1/2': '<span class="hljs-number">50</span>%',
      '1/3': '<span class="hljs-number">33.333333</span>%',
      '2/3': '<span class="hljs-number">66.666667</span>%',
      '1/4': '<span class="hljs-number">25</span>%',
      '2/4': '<span class="hljs-number">50</span>%',
      '3/4': '<span class="hljs-number">75</span>%',
      '1/5': '<span class="hljs-number">20</span>%',
      '2/5': '<span class="hljs-number">40</span>%',
      '3/5': '<span class="hljs-number">60</span>%',
      '4/5': '<span class="hljs-number">80</span>%',
      '1/6': '<span class="hljs-number">16.666667</span>%',
      '2/6': '<span class="hljs-number">33.333333</span>%',
      '3/6': '<span class="hljs-number">50</span>%',
      '4/6': '<span class="hljs-number">66.666667</span>%',
      '5/6': '<span class="hljs-number">83.333333</span>%',
      '1/12': '<span class="hljs-number">8.333333</span>%',
      '2/12': '<span class="hljs-number">16.666667</span>%',
      '3/12': '<span class="hljs-number">25</span>%',
      '4/12': '<span class="hljs-number">33.333333</span>%',
      '5/12': '<span class="hljs-number">41.666667</span>%',
      '6/12': '<span class="hljs-number">50</span>%',
      '7/12': '<span class="hljs-number">58.333333</span>%',
      '8/12': '<span class="hljs-number">66.666667</span>%',
      '9/12': '<span class="hljs-number">75</span>%',
      '10/12': '<span class="hljs-number">83.333333</span>%',
      '11/12': '<span class="hljs-number">91.666667</span>%',
      full: '<span class="hljs-number">100</span>%',
    }),
    flexGrow: {
      0: '<span class="hljs-number">0</span>',
      DEFAULT: '<span class="hljs-number">1</span>',
    },
    flexShrink: {
      0: '<span class="hljs-number">0</span>',
      DEFAULT: '<span class="hljs-number">1</span>',
    },
    fontFamily: {
      sans: [
        'ui-sans-serif',
        'system-ui',
        '-apple-system',
        'BlinkMacSystemFont',
        '<span class="hljs-string">"Segoe UI"</span>',
        'Roboto',
        '<span class="hljs-string">"Helvetica Neue"</span>',
        'Arial',
        '<span class="hljs-string">"Noto Sans"</span>',
        'sans-serif',
        '<span class="hljs-string">"Apple Color Emoji"</span>',
        '<span class="hljs-string">"Segoe UI Emoji"</span>',
        '<span class="hljs-string">"Segoe UI Symbol"</span>',
        '<span class="hljs-string">"Noto Color Emoji"</span>',
      ],
      serif: [
        'ui-serif',
        'Georgia',
        'Cambria',
        '<span class="hljs-string">"Times New Roman"</span>',
        'Times',
        'serif',
      ],
      mono: [
        'ui-monospace',
        'SFMono-Regular',
        'Menlo',
        'Monaco',
        'Consolas',
        '<span class="hljs-string">"Liberation Mono"</span>',
        '<span class="hljs-string">"Courier New"</span>',
        'monospace',
      ],
    },
    fontSize: {
      xs: ['<span class="hljs-number">0.75</span>rem', { lineHeight: '<span class="hljs-number">1</span>rem' }],
      sm: ['<span class="hljs-number">0.875</span>rem', { lineHeight: '<span class="hljs-number">1.25</span>rem' }],
      base: ['<span class="hljs-number">1</span>rem', { lineHeight: '<span class="hljs-number">1.5</span>rem' }],
      lg: ['<span class="hljs-number">1.125</span>rem', { lineHeight: '<span class="hljs-number">1.75</span>rem' }],
      xl: ['<span class="hljs-number">1.25</span>rem', { lineHeight: '<span class="hljs-number">1.75</span>rem' }],
      '2xl': ['<span class="hljs-number">1.5</span>rem', { lineHeight: '<span class="hljs-number">2</span>rem' }],
      '3xl': ['<span class="hljs-number">1.875</span>rem', { lineHeight: '<span class="hljs-number">2.25</span>rem' }],
      '4xl': ['<span class="hljs-number">2.25</span>rem', { lineHeight: '<span class="hljs-number">2.5</span>rem' }],
      '5xl': ['<span class="hljs-number">3</span>rem', { lineHeight: '<span class="hljs-number">1</span>' }],
      '6xl': ['<span class="hljs-number">3.75</span>rem', { lineHeight: '<span class="hljs-number">1</span>' }],
      '7xl': ['<span class="hljs-number">4.5</span>rem', { lineHeight: '<span class="hljs-number">1</span>' }],
      '8xl': ['<span class="hljs-number">6</span>rem', { lineHeight: '<span class="hljs-number">1</span>' }],
      '9xl': ['<span class="hljs-number">8</span>rem', { lineHeight: '<span class="hljs-number">1</span>' }],
    },
    fontWeight: {
      thin: '<span class="hljs-number">100</span>',
      extralight: '<span class="hljs-number">200</span>',
      light: '<span class="hljs-number">300</span>',
      normal: '<span class="hljs-number">400</span>',
      medium: '<span class="hljs-number">500</span>',
      semibold: '<span class="hljs-number">600</span>',
      bold: '<span class="hljs-number">700</span>',
      extrabold: '<span class="hljs-number">800</span>',
      black: '<span class="hljs-number">900</span>',
    },
    gap: ({ theme }) =&gt; theme('spacing'),
    gradientColorStops: ({ theme }) =&gt; theme('colors'),
    gridAutoColumns: {
      auto: 'auto',
      min: 'min-content',
      max: 'max-content',
      fr: 'minmax(<span class="hljs-number">0</span>, 1fr)',
    },
    gridAutoRows: {
      auto: 'auto',
      min: 'min-content',
      max: 'max-content',
      fr: 'minmax(<span class="hljs-number">0</span>, 1fr)',
    },
    gridColumn: {
      auto: 'auto',
      'span-1': 'span <span class="hljs-number">1</span> / span <span class="hljs-number">1</span>',
      'span-2': 'span <span class="hljs-number">2</span> / span <span class="hljs-number">2</span>',
      'span-3': 'span <span class="hljs-number">3</span> / span <span class="hljs-number">3</span>',
      'span-4': 'span <span class="hljs-number">4</span> / span <span class="hljs-number">4</span>',
      'span-5': 'span <span class="hljs-number">5</span> / span <span class="hljs-number">5</span>',
      'span-6': 'span <span class="hljs-number">6</span> / span <span class="hljs-number">6</span>',
      'span-7': 'span <span class="hljs-number">7</span> / span <span class="hljs-number">7</span>',
      'span-8': 'span <span class="hljs-number">8</span> / span <span class="hljs-number">8</span>',
      'span-9': 'span <span class="hljs-number">9</span> / span <span class="hljs-number">9</span>',
      'span-10': 'span <span class="hljs-number">10</span> / span <span class="hljs-number">10</span>',
      'span-11': 'span <span class="hljs-number">11</span> / span <span class="hljs-number">11</span>',
      'span-12': 'span <span class="hljs-number">12</span> / span <span class="hljs-number">12</span>',
      'span-full': '<span class="hljs-number">1</span> / <span class="hljs-number">-1</span>',
    },
    gridColumnEnd: {
      auto: 'auto',
      1: '<span class="hljs-number">1</span>',
      2: '<span class="hljs-number">2</span>',
      3: '<span class="hljs-number">3</span>',
      4: '<span class="hljs-number">4</span>',
      5: '<span class="hljs-number">5</span>',
      6: '<span class="hljs-number">6</span>',
      7: '<span class="hljs-number">7</span>',
      8: '<span class="hljs-number">8</span>',
      9: '<span class="hljs-number">9</span>',
      10: '<span class="hljs-number">10</span>',
      11: '<span class="hljs-number">11</span>',
      12: '<span class="hljs-number">12</span>',
      13: '<span class="hljs-number">13</span>',
    },
    gridColumnStart: {
      auto: 'auto',
      1: '<span class="hljs-number">1</span>',
      2: '<span class="hljs-number">2</span>',
      3: '<span class="hljs-number">3</span>',
      4: '<span class="hljs-number">4</span>',
      5: '<span class="hljs-number">5</span>',
      6: '<span class="hljs-number">6</span>',
      7: '<span class="hljs-number">7</span>',
      8: '<span class="hljs-number">8</span>',
      9: '<span class="hljs-number">9</span>',
      10: '<span class="hljs-number">10</span>',
      11: '<span class="hljs-number">11</span>',
      12: '<span class="hljs-number">12</span>',
      13: '<span class="hljs-number">13</span>',
    },
    gridRow: {
      auto: 'auto',
      'span-1': 'span <span class="hljs-number">1</span> / span <span class="hljs-number">1</span>',
      'span-2': 'span <span class="hljs-number">2</span> / span <span class="hljs-number">2</span>',
      'span-3': 'span <span class="hljs-number">3</span> / span <span class="hljs-number">3</span>',
      'span-4': 'span <span class="hljs-number">4</span> / span <span class="hljs-number">4</span>',
      'span-5': 'span <span class="hljs-number">5</span> / span <span class="hljs-number">5</span>',
      'span-6': 'span <span class="hljs-number">6</span> / span <span class="hljs-number">6</span>',
      'span-full': '<span class="hljs-number">1</span> / <span class="hljs-number">-1</span>',
    },
    gridRowStart: {
      auto: 'auto',
      1: '<span class="hljs-number">1</span>',
      2: '<span class="hljs-number">2</span>',
      3: '<span class="hljs-number">3</span>',
      4: '<span class="hljs-number">4</span>',
      5: '<span class="hljs-number">5</span>',
      6: '<span class="hljs-number">6</span>',
      7: '<span class="hljs-number">7</span>',
    },
    gridRowEnd: {
      auto: 'auto',
      1: '<span class="hljs-number">1</span>',
      2: '<span class="hljs-number">2</span>',
      3: '<span class="hljs-number">3</span>',
      4: '<span class="hljs-number">4</span>',
      5: '<span class="hljs-number">5</span>',
      6: '<span class="hljs-number">6</span>',
      7: '<span class="hljs-number">7</span>',
    },
    gridTemplateColumns: {
      none: 'none',
      1: 'repeat(<span class="hljs-number">1</span>, minmax(0, 1fr))',
      2: 'repeat(<span class="hljs-number">2</span>, minmax(0, 1fr))',
      3: 'repeat(<span class="hljs-number">3</span>, minmax(0, 1fr))',
      4: 'repeat(<span class="hljs-number">4</span>, minmax(0, 1fr))',
      5: 'repeat(<span class="hljs-number">5</span>, minmax(0, 1fr))',
      6: 'repeat(<span class="hljs-number">6</span>, minmax(0, 1fr))',
      7: 'repeat(<span class="hljs-number">7</span>, minmax(0, 1fr))',
      8: 'repeat(<span class="hljs-number">8</span>, minmax(0, 1fr))',
      9: 'repeat(<span class="hljs-number">9</span>, minmax(0, 1fr))',
      10: 'repeat(<span class="hljs-number">10</span>, minmax(0, 1fr))',
      11: 'repeat(<span class="hljs-number">11</span>, minmax(0, 1fr))',
      12: 'repeat(<span class="hljs-number">12</span>, minmax(0, 1fr))',
    },
    gridTemplateRows: {
      none: 'none',
      1: 'repeat(<span class="hljs-number">1</span>, minmax(0, 1fr))',
      2: 'repeat(<span class="hljs-number">2</span>, minmax(0, 1fr))',
      3: 'repeat(<span class="hljs-number">3</span>, minmax(0, 1fr))',
      4: 'repeat(<span class="hljs-number">4</span>, minmax(0, 1fr))',
      5: 'repeat(<span class="hljs-number">5</span>, minmax(0, 1fr))',
      6: 'repeat(<span class="hljs-number">6</span>, minmax(0, 1fr))',
    },
    height: ({ theme }) =&gt; ({
      auto: 'auto',
      ...theme('spacing'),
      '1/2': '<span class="hljs-number">50</span>%',
      '1/3': '<span class="hljs-number">33.333333</span>%',
      '2/3': '<span class="hljs-number">66.666667</span>%',
      '1/4': '<span class="hljs-number">25</span>%',
      '2/4': '<span class="hljs-number">50</span>%',
      '3/4': '<span class="hljs-number">75</span>%',
      '1/5': '<span class="hljs-number">20</span>%',
      '2/5': '<span class="hljs-number">40</span>%',
      '3/5': '<span class="hljs-number">60</span>%',
      '4/5': '<span class="hljs-number">80</span>%',
      '1/6': '<span class="hljs-number">16.666667</span>%',
      '2/6': '<span class="hljs-number">33.333333</span>%',
      '3/6': '<span class="hljs-number">50</span>%',
      '4/6': '<span class="hljs-number">66.666667</span>%',
      '5/6': '<span class="hljs-number">83.333333</span>%',
      full: '<span class="hljs-number">100</span>%',
      screen: '<span class="hljs-number">100</span>vh',
      min: 'min-content',
      max: 'max-content',
      fit: 'fit-content',
    }),
    inset: ({ theme }) =&gt; ({
      auto: 'auto',
      ...theme('spacing'),
      '1/2': '<span class="hljs-number">50</span>%',
      '1/3': '<span class="hljs-number">33.333333</span>%',
      '2/3': '<span class="hljs-number">66.666667</span>%',
      '1/4': '<span class="hljs-number">25</span>%',
      '2/4': '<span class="hljs-number">50</span>%',
      '3/4': '<span class="hljs-number">75</span>%',
      full: '<span class="hljs-number">100</span>%',
    }),
    keyframes: {
      spin: {
        to: {
          transform: 'rotate(<span class="hljs-number">360</span>deg)',
        },
      },
      ping: {
        '75%, 100%': {
          transform: 'scale(<span class="hljs-number">2</span>)',
          opacity: '<span class="hljs-number">0</span>',
        },
      },
      pulse: {
        '50%': {
          opacity: '<span class="hljs-number">.5</span>',
        },
      },
      bounce: {
        '0%, 100%': {
          transform: 'translateY(<span class="hljs-number">-25</span>%)',
          animationTimingFunction: 'cubic-bezier(<span class="hljs-number">0.8</span>,0,1,1)',
        },
        '50%': {
          transform: 'none',
          animationTimingFunction: 'cubic-bezier(<span class="hljs-number">0</span>,0,0.2,1)',
        },
      },
      fadeIn: {
        from: {
          opacity: <span class="hljs-number">0</span>,
        },
        to: {
          opacity: <span class="hljs-number">1</span>,
        },
      },
    },
    letterSpacing: {
      tighter: '<span class="hljs-number">-0.05</span>em',
      tight: '<span class="hljs-number">-0.025</span>em',
      normal: '<span class="hljs-number">0</span>em',
      wide: '<span class="hljs-number">0.025</span>em',
      wider: '<span class="hljs-number">0.05</span>em',
      widest: '<span class="hljs-number">0.1</span>em',
    },
    lineHeight: {
      none: '<span class="hljs-number">1</span>',
      tight: '<span class="hljs-number">1.25</span>',
      snug: '<span class="hljs-number">1.375</span>',
      normal: '<span class="hljs-number">1.5</span>',
      relaxed: '<span class="hljs-number">1.625</span>',
      loose: '<span class="hljs-number">2</span>',
      3: '<span class="hljs-number">.75</span>rem',
      4: '<span class="hljs-number">1</span>rem',
      5: '<span class="hljs-number">1.25</span>rem',
      6: '<span class="hljs-number">1.5</span>rem',
      7: '<span class="hljs-number">1.75</span>rem',
      8: '<span class="hljs-number">2</span>rem',
      9: '<span class="hljs-number">2.25</span>rem',
      10: '<span class="hljs-number">2.5</span>rem',
    },
    listStyleType: {
      none: 'none',
      disc: 'disc',
      decimal: 'decimal',
    },
    margin: ({ theme }) =&gt; ({
      auto: 'auto',
      ...theme('spacing'),
    }),
    maxHeight: ({ theme }) =&gt; ({
      ...theme('spacing'),
      full: '<span class="hljs-number">100</span>%',
      screen: '<span class="hljs-number">100</span>vh',
      min: 'min-content',
      max: 'max-content',
      fit: 'fit-content',
    }),
    maxWidth: ({ theme, breakpoints }) =&gt; ({
      none: 'none',
      0: '<span class="hljs-number">0</span>rem',
      xs: '<span class="hljs-number">20</span>rem',
      sm: '<span class="hljs-number">24</span>rem',
      md: '<span class="hljs-number">28</span>rem',
      lg: '<span class="hljs-number">32</span>rem',
      xl: '<span class="hljs-number">36</span>rem',
      '2xl': '<span class="hljs-number">42</span>rem',
      '3xl': '<span class="hljs-number">48</span>rem',
      '4xl': '<span class="hljs-number">56</span>rem',
      '5xl': '<span class="hljs-number">64</span>rem',
      '6xl': '<span class="hljs-number">72</span>rem',
      '7xl': '<span class="hljs-number">80</span>rem',
      full: '<span class="hljs-number">100</span>%',
      min: 'min-content',
      max: 'max-content',
      fit: 'fit-content',
      prose: '<span class="hljs-number">65</span>ch',
      ...breakpoints(theme('screens')),
    }),
    minHeight: {
      0: '<span class="hljs-number">0</span>px',
      full: '<span class="hljs-number">100</span>%',
      screen: '<span class="hljs-number">100</span>vh',
      min: 'min-content',
      max: 'max-content',
      fit: 'fit-content',
    },
    minWidth: {
      0: '<span class="hljs-number">0</span>px',
      full: '<span class="hljs-number">100</span>%',
      min: 'min-content',
      max: 'max-content',
      fit: 'fit-content',
    },
    objectPosition: {
      bottom: 'bottom',
      center: 'center',
      left: 'left',
      'left-bottom': 'left bottom',
      'left-top': 'left top',
      right: 'right',
      'right-bottom': 'right bottom',
      'right-top': 'right top',
      top: 'top',
    },
    opacity: {
      0: '<span class="hljs-number">0</span>',
      5: '<span class="hljs-number">0.05</span>',
      10: '<span class="hljs-number">0.1</span>',
      20: '<span class="hljs-number">0.2</span>',
      25: '<span class="hljs-number">0.25</span>',
      30: '<span class="hljs-number">0.3</span>',
      40: '<span class="hljs-number">0.4</span>',
      50: '<span class="hljs-number">0.5</span>',
      60: '<span class="hljs-number">0.6</span>',
      70: '<span class="hljs-number">0.7</span>',
      75: '<span class="hljs-number">0.75</span>',
      80: '<span class="hljs-number">0.8</span>',
      90: '<span class="hljs-number">0.9</span>',
      95: '<span class="hljs-number">0.95</span>',
      100: '<span class="hljs-number">1</span>',
    },
    order: {
      first: '<span class="hljs-number">-9999</span>',
      last: '<span class="hljs-number">9999</span>',
      none: '<span class="hljs-number">0</span>',
      1: '<span class="hljs-number">1</span>',
      2: '<span class="hljs-number">2</span>',
      3: '<span class="hljs-number">3</span>',
      4: '<span class="hljs-number">4</span>',
      5: '<span class="hljs-number">5</span>',
      6: '<span class="hljs-number">6</span>',
      7: '<span class="hljs-number">7</span>',
      8: '<span class="hljs-number">8</span>',
      9: '<span class="hljs-number">9</span>',
      10: '<span class="hljs-number">10</span>',
      11: '<span class="hljs-number">11</span>',
      12: '<span class="hljs-number">12</span>',
    },
    padding: ({ theme }) =&gt; theme('spacing'),
    placeholderColor: ({ theme }) =&gt; theme('colors'),
    placeholderOpacity: ({ theme }) =&gt; theme('opacity'),
    outlineColor: ({ theme }) =&gt; theme('colors'),
    outlineOffset: {
      0: '<span class="hljs-number">0</span>px',
      1: '<span class="hljs-number">1</span>px',
      2: '<span class="hljs-number">2</span>px',
      4: '<span class="hljs-number">4</span>px',
      8: '<span class="hljs-number">8</span>px',
    },
    outlineWidth: {
      0: '<span class="hljs-number">0</span>px',
      1: '<span class="hljs-number">1</span>px',
      2: '<span class="hljs-number">2</span>px',
      4: '<span class="hljs-number">4</span>px',
      8: '<span class="hljs-number">8</span>px',
    },
    ringColor: ({ theme }) =&gt; ({
      DEFAULT: theme('colors.blue<span class="hljs-number">.500</span>', '#3b82f6'),
      ...theme('colors'),
    }),
    ringOffsetColor: ({ theme }) =&gt; theme('colors'),
    ringOffsetWidth: {
      0: '<span class="hljs-number">0</span>px',
      1: '<span class="hljs-number">1</span>px',
      2: '<span class="hljs-number">2</span>px',
      4: '<span class="hljs-number">4</span>px',
      8: '<span class="hljs-number">8</span>px',
    },
    ringOpacity: ({ theme }) =&gt; ({
      DEFAULT: '<span class="hljs-number">0.5</span>',
      ...theme('opacity'),
    }),
    ringWidth: {
      DEFAULT: '<span class="hljs-number">3</span>px',
      0: '<span class="hljs-number">0</span>px',
      1: '<span class="hljs-number">1</span>px',
      2: '<span class="hljs-number">2</span>px',
      4: '<span class="hljs-number">4</span>px',
      8: '<span class="hljs-number">8</span>px',
    },
    rotate: {
      0: '<span class="hljs-number">0</span>deg',
      1: '<span class="hljs-number">1</span>deg',
      2: '<span class="hljs-number">2</span>deg',
      3: '<span class="hljs-number">3</span>deg',
      6: '<span class="hljs-number">6</span>deg',
      12: '<span class="hljs-number">12</span>deg',
      45: '<span class="hljs-number">45</span>deg',
      90: '<span class="hljs-number">90</span>deg',
      180: '<span class="hljs-number">180</span>deg',
    },
    saturate: {
      0: '<span class="hljs-number">0</span>',
      50: '<span class="hljs-number">.5</span>',
      100: '<span class="hljs-number">1</span>',
      150: '<span class="hljs-number">1.5</span>',
      200: '<span class="hljs-number">2</span>',
    },
    scale: {
      0: '<span class="hljs-number">0</span>',
      50: '<span class="hljs-number">.5</span>',
      75: '<span class="hljs-number">.75</span>',
      90: '<span class="hljs-number">.9</span>',
      95: '<span class="hljs-number">.95</span>',
      100: '<span class="hljs-number">1</span>',
      105: '<span class="hljs-number">1.05</span>',
      110: '<span class="hljs-number">1.1</span>',
      125: '<span class="hljs-number">1.25</span>',
      150: '<span class="hljs-number">1.5</span>',
    },
    scrollMargin: ({ theme }) =&gt; ({
      ...theme('spacing'),
    }),
    scrollPadding: ({ theme }) =&gt; theme('spacing'),
    sepia: {
      0: '<span class="hljs-number">0</span>',
      DEFAULT: '<span class="hljs-number">100</span>%',
    },
    skew: {
      0: '<span class="hljs-number">0</span>deg',
      1: '<span class="hljs-number">1</span>deg',
      2: '<span class="hljs-number">2</span>deg',
      3: '<span class="hljs-number">3</span>deg',
      6: '<span class="hljs-number">6</span>deg',
      12: '<span class="hljs-number">12</span>deg',
    },
    space: ({ theme }) =&gt; ({
      ...theme('spacing'),
    }),
    stroke: ({ theme }) =&gt; theme('colors'),
    strokeWidth: {
      0: '<span class="hljs-number">0</span>',
      1: '<span class="hljs-number">1</span>',
      2: '<span class="hljs-number">2</span>',
    },
    textColor: ({ theme }) =&gt; theme('colors'),
    textDecorationColor: ({ theme }) =&gt; theme('colors'),
    textDecorationThickness: {
      auto: 'auto',
      'from-font': 'from-font',
      0: '<span class="hljs-number">0</span>px',
      1: '<span class="hljs-number">1</span>px',
      2: '<span class="hljs-number">2</span>px',
      4: '<span class="hljs-number">4</span>px',
      8: '<span class="hljs-number">8</span>px',
    },
    textUnderlineOffset: {
      auto: 'auto',
      0: '<span class="hljs-number">0</span>px',
      1: '<span class="hljs-number">1</span>px',
      2: '<span class="hljs-number">2</span>px',
      4: '<span class="hljs-number">4</span>px',
      8: '<span class="hljs-number">8</span>px',
    },
    textIndent: ({ theme }) =&gt; ({
      ...theme('spacing'),
    }),
    textOpacity: ({ theme }) =&gt; theme('opacity'),
    transformOrigin: {
      center: 'center',
      top: 'top',
      'top-right': 'top right',
      right: 'right',
      'bottom-right': 'bottom right',
      bottom: 'bottom',
      'bottom-left': 'bottom left',
      left: 'left',
      'top-left': 'top left',
    },
    transitionDelay: {
      75: '<span class="hljs-number">75</span>ms',
      100: '<span class="hljs-number">100</span>ms',
      150: '<span class="hljs-number">150</span>ms',
      200: '<span class="hljs-number">200</span>ms',
      300: '<span class="hljs-number">300</span>ms',
      500: '<span class="hljs-number">500</span>ms',
      700: '<span class="hljs-number">700</span>ms',
      1000: '<span class="hljs-number">1000</span>ms',
    },
    transitionDuration: {
      DEFAULT: '<span class="hljs-number">150</span>ms',
      75: '<span class="hljs-number">75</span>ms',
      100: '<span class="hljs-number">100</span>ms',
      150: '<span class="hljs-number">150</span>ms',
      200: '<span class="hljs-number">200</span>ms',
      300: '<span class="hljs-number">300</span>ms',
      500: '<span class="hljs-number">500</span>ms',
      700: '<span class="hljs-number">700</span>ms',
      1000: '<span class="hljs-number">1000</span>ms',
    },
    transitionProperty: {
      none: 'none',
      all: 'all',
      DEFAULT:
        'background-color, border-color, color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter',
      colors: 'background-color, border-color, color, fill, stroke',
      opacity: 'opacity',
      shadow: 'box-shadow',
      transform: 'transform',
    },
    transitionTimingFunction: {
      DEFAULT: 'cubic-bezier(<span class="hljs-number">0.4</span>, 0, 0.2, 1)',
      linear: 'linear',
      in: 'cubic-bezier(<span class="hljs-number">0.4</span>, 0, 1, 1)',
      out: 'cubic-bezier(<span class="hljs-number">0</span>, 0, 0.2, 1)',
      'in-out': 'cubic-bezier(<span class="hljs-number">0.4</span>, 0, 0.2, 1)',
    },
    translate: ({ theme }) =&gt; ({
      ...theme('spacing'),
      '1/2': '<span class="hljs-number">50</span>%',
      '1/3': '<span class="hljs-number">33.333333</span>%',
      '2/3': '<span class="hljs-number">66.666667</span>%',
      '1/4': '<span class="hljs-number">25</span>%',
      '2/4': '<span class="hljs-number">50</span>%',
      '3/4': '<span class="hljs-number">75</span>%',
      full: '<span class="hljs-number">100</span>%',
    }),
    width: ({ theme }) =&gt; ({
      auto: 'auto',
      ...theme('spacing'),
      '1/2': '<span class="hljs-number">50</span>%',
      '1/3': '<span class="hljs-number">33.333333</span>%',
      '2/3': '<span class="hljs-number">66.666667</span>%',
      '1/4': '<span class="hljs-number">25</span>%',
      '2/4': '<span class="hljs-number">50</span>%',
      '3/4': '<span class="hljs-number">75</span>%',
      '1/5': '<span class="hljs-number">20</span>%',
      '2/5': '<span class="hljs-number">40</span>%',
      '3/5': '<span class="hljs-number">60</span>%',
      '4/5': '<span class="hljs-number">80</span>%',
      '1/6': '<span class="hljs-number">16.666667</span>%',
      '2/6': '<span class="hljs-number">33.333333</span>%',
      '3/6': '<span class="hljs-number">50</span>%',
      '4/6': '<span class="hljs-number">66.666667</span>%',
      '5/6': '<span class="hljs-number">83.333333</span>%',
      '1/12': '<span class="hljs-number">8.333333</span>%',
      '2/12': '<span class="hljs-number">16.666667</span>%',
      '3/12': '<span class="hljs-number">25</span>%',
      '4/12': '<span class="hljs-number">33.333333</span>%',
      '5/12': '<span class="hljs-number">41.666667</span>%',
      '6/12': '<span class="hljs-number">50</span>%',
      '7/12': '<span class="hljs-number">58.333333</span>%',
      '8/12': '<span class="hljs-number">66.666667</span>%',
      '9/12': '<span class="hljs-number">75</span>%',
      '10/12': '<span class="hljs-number">83.333333</span>%',
      '11/12': '<span class="hljs-number">91.666667</span>%',
      full: '<span class="hljs-number">100</span>%',
      screen: '<span class="hljs-number">100</span>vw',
      min: 'min-content',
      max: 'max-content',
      fit: 'fit-content',
    }),
    willChange: {
      auto: 'auto',
      scroll: 'scroll-position',
      contents: 'contents',
      transform: 'transform',
    },
    zIndex: {
      auto: 'auto',
      0: '<span class="hljs-number">0</span>',
      10: '<span class="hljs-number">10</span>',
      20: '<span class="hljs-number">20</span>',
      30: '<span class="hljs-number">30</span>',
      40: '<span class="hljs-number">40</span>',
      50: '<span class="hljs-number">50</span>',
    },
  },
  variantOrder: [
    'first',
    'last',
    'odd',
    'even',
    'visited',
    'checked',
    'empty',
    'read-only',
    'group-hover',
    'group-focus',
    'focus-within',
    'hover',
    'focus',
    'focus-visible',
    'active',
    'disabled',
  ],
}
</code></pre>
]]></content:encoded></item></channel></rss>