<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Isaac Dyor</title>
    <description>The latest articles on DEV Community by Isaac Dyor (@isaacdyor).</description>
    <link>https://dev.to/isaacdyor</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F888201%2F0683a4ff-a8d1-49ea-a540-644a885c624d.png</url>
      <title>DEV Community: Isaac Dyor</title>
      <link>https://dev.to/isaacdyor</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/isaacdyor"/>
    <language>en</language>
    <item>
      <title>How to programmatically post your personal blogs to Dev.to, Hashnode, and Medium with Github actions.</title>
      <dc:creator>Isaac Dyor</dc:creator>
      <pubDate>Tue, 30 Jul 2024 17:07:29 +0000</pubDate>
      <link>https://dev.to/isaacdyor/how-to-programmatically-post-your-personal-blogs-to-devto-hashnode-and-medium-with-github-actions-pp2</link>
      <guid>https://dev.to/isaacdyor/how-to-programmatically-post-your-personal-blogs-to-devto-hashnode-and-medium-with-github-actions-pp2</guid>
      <description>&lt;h2&gt;
  
  
  The reason
&lt;/h2&gt;

&lt;p&gt;This article details how you can post the blogs from your personal site to external sites like Dev.to, Hashnode, or Medium. Now why would you want to do this? I think that the main reason that this is valuable is so that you can own your own content and develop your personal brand while also improving SEO and expanding your reach.&lt;/p&gt;

&lt;p&gt;For me personally (and i have heard this sentiment before), when you read an article and it is written on someone's personal site, you are far more likely to explore the site and learn more about them. If I read an article on Dev.to, I won't check out the other articles on their page. If you would like to read more on why owning your own content is valuable I would check out this article: &lt;a href="https://yieldcode.blog/post/own-your-content/" rel="noopener noreferrer"&gt;Own your own content&lt;/a&gt;, it convinced me to start my own personal blog instead of continuing to post on Dev.to.&lt;/p&gt;

&lt;p&gt;I think the problem with just posting on your own personal website is that nobody reads it. Unless you already have a strong audience of people who are interested in what you are reading you will most likely be writing into a black hole. I think that having your writing read by people is extremely important because you can see what resonates with people, and in my opinion it is a big motivator to continue writing. I wrote a simple article on Dev.to that got like 200 likes and that definitely inspired me to keep writing.&lt;/p&gt;

&lt;p&gt;So how do you effectively balance using these external platforms to get your work in front of people while also owning your own content and developing your personal brand? I think the simplest and most effective way to do this is to automatically post the blogs from your personal sites onto these external sites so you don't have to think about it. This is really easily done with Github actions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up Github Actions
&lt;/h2&gt;

&lt;p&gt;The first step is to create a &lt;code&gt;.github&lt;/code&gt; folder in your root directory. This is where we will define our workflows and actions. Inside this folder create two new folders: &lt;code&gt;workflows&lt;/code&gt; and &lt;code&gt;actions&lt;/code&gt;. The workflow will check if there are any new files in our posts folder, and if there are it will call our parse action defined in the actions folder. The parse action calls actions for each platform we want to post to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating our workflow
&lt;/h2&gt;

&lt;p&gt;There are a few things we need to do in our workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Define environment variables&lt;/li&gt;
&lt;li&gt;Find new files&lt;/li&gt;
&lt;li&gt;Run parse action for each new file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is how I implemented it (if your posts are not in src/content/post you should change the path to match your specific path)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;New Blog Post Action&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;src/content/post/**"&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;process-new-post&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Identify new files&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;new-files&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;NEW_FILES=$(git diff --name-only --diff-filter=A ${{ github.event.before }} ${{ github.sha }} -- src/content/post/)&lt;/span&gt;
          &lt;span class="s"&gt;echo "New files: $NEW_FILES"&lt;/span&gt;
          &lt;span class="s"&gt;echo "new_files=$NEW_FILES" &amp;gt;&amp;gt; $GITHUB_OUTPUT&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Process new files&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;steps.new-files.outputs.new_files != ''&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;HASHNODE_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.HASHNODE_API_KEY }}&lt;/span&gt;
          &lt;span class="na"&gt;HASHNODE_PUBLICATION_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.HASHNODE_PUBLICATION_ID }}&lt;/span&gt;
          &lt;span class="na"&gt;DEVTO_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DEVTO_API_KEY }}&lt;/span&gt;
          &lt;span class="na"&gt;MEDIUM_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.MEDIUM_API_KEY }}&lt;/span&gt;
          &lt;span class="na"&gt;MEDIUM_AUTHOR_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.MEDIUM_AUTHOR_ID }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;echo "Debug: Entering Process new files step"&lt;/span&gt;
          &lt;span class="s"&gt;echo "New files detected: ${{ steps.new-files.outputs.new_files }}"&lt;/span&gt;

          &lt;span class="s"&gt;IFS=$'\n'&lt;/span&gt;
          &lt;span class="s"&gt;for file in ${{ steps.new-files.outputs.new_files }}; do&lt;/span&gt;
            &lt;span class="s"&gt;echo "Processing file: $file"&lt;/span&gt;

            &lt;span class="s"&gt;node .github/actions/parse.js "$file"&lt;/span&gt;
          &lt;span class="s"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make sure this works correctly we need to define these environment variables in our project under secrets. Go to your project on Github and go to settings. Then select the dropdown for secrets and variables and select actions. There you can define your secrets and access them in your actions using normal &lt;code&gt;process.env.VARIABLE&lt;/code&gt; syntax.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parse
&lt;/h2&gt;

&lt;p&gt;Now that our action has recognized the new files we need to parse the content and metadata from the file. We do this by creating a &lt;code&gt;parse.js&lt;/code&gt; file inside our &lt;code&gt;.github/actions/&lt;/code&gt; folder. Add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// .github/actions/parse.js&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;promises&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;postToDevTo&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./devto.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;postToHashnode&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./hashnode.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;postToMedium&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./medium.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getFrontMatterAndBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;charactersBetweenGroupedHyphens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/^---&lt;/span&gt;&lt;span class="se"&gt;([\s\S]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;?)&lt;/span&gt;&lt;span class="sr"&gt;---&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;([\s\S]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;matched&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;charactersBetweenGroupedHyphens&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;matched&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;frontmatter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;markdown&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;matched&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;matched&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;metadataLines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;frontmatter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;metadataLines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;accumulator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;accumulator&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;accumulator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;frontmatter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;postToBlogs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Read the markdown file&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Extract the front matter and body from the markdown file&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;frontmatter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getFrontMatterAndBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;postToDevTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;frontmatter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;postToHashnode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;frontmatter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;postToMedium&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;frontmatter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Get the file path from command line arguments&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Please provide a file path&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;postToBlogs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can define an action for each platform we want to post to. If you want to add more platforms, like posting a link to your blog on twitter or something, you can just define another action and call it in this file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Posting to platforms
&lt;/h2&gt;

&lt;p&gt;Now we just need to follow the documentation of the APIs for the platforms we want to post to. If you have set up the rest of the project correctly, the following code should work for Dev.to, Hashnode, and Medium:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// .github/actions/devto.js&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;postToDevTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;frontMatter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Prepare the request body&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;postData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;article&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;frontMatter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;body_markdown&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;published&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;frontMatter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;canonical_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;frontMatter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;canonical_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Prepare the request headers&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myHeaders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;myHeaders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;myHeaders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;api-key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DEVTO_API_KEY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Prepare the request options&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requestOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;myHeaders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;postData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;follow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://dev.to/api/articles&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;requestOptions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Error in postToDevTo: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// .github/actions/hashnode.js&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;postToHashnode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;frontMatter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;graphqlEndpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://gql.hashnode.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HASHNODE_API_KEY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mutation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
    mutation PublishPost($input: PublishPostInput!) {
      publishPost(input: $input) {
        post {
          id
          title
          slug
          url
        }
      }
    }
  `&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;publicationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HASHNODE_PUBLICATION_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;frontMatter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;contentMarkdown&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;originalArticleURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;frontMatter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;canonical_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;graphqlEndpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;authToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`HTTP error!: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`GraphQL Error: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publishPost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Error in postToHashnode: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// .github/actions/medium.js&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;postToMedium&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;frontMatter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Prepare the request body&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;postData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;frontMatter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;contentFormat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;markdown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;frontMatter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;publishStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;canonicalUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;frontMatter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;canonical_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Prepare the request headers&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myHeaders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;myHeaders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;myHeaders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MEDIUM_API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Prepare the request options&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requestOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;myHeaders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;postData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;follow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="c1"&gt;// Make the API request using fetch&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`https://api.medium.com/v1/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MEDIUM_AUTHOR_ID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/posts`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;requestOptions&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Error in postToMedium: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when you add your new blog post to the &lt;code&gt;src/content/post&lt;/code&gt; folder this workflow will be called, which posts to these three platforms. You can check out the source code for my personal website &lt;a href="https://github.com/isaacdyor/Personal_Site" rel="noopener noreferrer"&gt;here&lt;/a&gt;, which has all of this implemented. Feel free to fork it and use it as your own if you want a base for starting your own personal site!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Creating a practice test builder with OctoAI Json mode</title>
      <dc:creator>Isaac Dyor</dc:creator>
      <pubDate>Sun, 05 May 2024 21:12:42 +0000</pubDate>
      <link>https://dev.to/isaacdyor/creating-a-practice-test-builder-with-octoai-json-mode-e5n</link>
      <guid>https://dev.to/isaacdyor/creating-a-practice-test-builder-with-octoai-json-mode-e5n</guid>
      <description>&lt;p&gt;I am currently taking calc 3 in university and I wanted to create a study tool for myself to practice. My goal was to create a tool that would give me practice questions based on my mastery of that certain topic, so I practice the things I am good at less than the topics I am struggling with. I know I could just do this manually but it seemed fun. &lt;/p&gt;

&lt;h2&gt;
  
  
  The plan
&lt;/h2&gt;

&lt;p&gt;For my class there are a lot of practice midterms that we can use to study and they are all stored as pdfs. So the first step would be to parse the pdf so I can plug it into an LLM. Then I would need the LLM to separate the test into different topics, so it could determine which parts I am good at and which parts need work. &lt;/p&gt;

&lt;p&gt;Once it separated the test into topics, it would need to choose a topic to ask me a question on. I will talk about how to do that more later, but the general idea is to preference topics you have previously scored low on. &lt;/p&gt;

&lt;p&gt;Once the LLM asks you the question you solve it and input your answer. Since the LLM might output the answer in a different form than you input, instead of auto grading you it will show you the work and it's answer and you can tell it whether you got it correct or incorrect. &lt;/p&gt;

&lt;p&gt;Once it sees how you performed on that question it will update your proficiency in that topic accordingly, and the process will restart. Now time for implementation!&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;Create a new folder and open it up in vscode. We need to create a virtual environment for dependency management. Run &lt;code&gt;python3 -m venv .venv&lt;/code&gt; if you are on mac/linux or &lt;code&gt;python -m venv .venv&lt;/code&gt; if you are on windows. Create a main.py file and restart your terminal with the new virtual environment. &lt;/p&gt;

&lt;h2&gt;
  
  
  Loading the pdf
&lt;/h2&gt;

&lt;p&gt;We are going to use PyPDF2 to read the Pdf so run &lt;code&gt;pip install PyPDF2&lt;/code&gt;. I have my pdf saved in the root of my project, so now I can add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;reader = PyPDF2.PdfReader('midterm1.pdf')
midterm = ""
for page in reader.pages:
    text = page.extract_text()
    midterm += text
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the practice test is saved as a string which I can plug into the LLM. &lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting to Octo
&lt;/h2&gt;

&lt;p&gt;We will be using OctoAI to host our model. First we need to get an API token. There are instructions on how to do that &lt;a href="https://octo.ai/docs/getting-started/how-to-create-an-octoai-access-token"&gt;here&lt;/a&gt;. Once you have your token you can set the environment variable with this command: &lt;code&gt;export OCTOAI_TOKEN=YOUR_TOKEN_HERE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next we have to install Octo so run the following command: &lt;code&gt;pip install OctoAI&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now we can connect to the model with the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import os
from octoai.client import OctoAI

OCTOAI_TOKEN = os.getenv('OCTOAI_TOKEN')

octoai = OctoAI(api_key=OCTOAI_TOKEN)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Getting topics
&lt;/h2&gt;

&lt;p&gt;We want to get a list of topics that are covered on the test. An easy way to do this is with Octo's Json mode. We can use pydantic to describe the shape we want out data to be. In our case we just want one list of topics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from pydantic import BaseModel
from typing import List

class Test(BaseModel):
    topics: List[str]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can use this schema when we query the LLM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import json
from octoai.text_gen import ChatMessage, ChatCompletionResponseFormat

completion = octoai.text_gen.create_chat_completion(
    model="llama-2-70b-chat",
    messages=[
        ChatMessage(
            role="system",
            content="Below is an instruction that describes a task. Write a response that appropriately completes the request.",
        ),
        ChatMessage(role="user", content="I will give you a practice midterm. Please give me a list of topics covered on the midterm. Each topic should be a string the array" + midterm),
    ],
    response_format=ChatCompletionResponseFormat(
        type="json_object",
        schema=Test.model_json_schema()
    ),
)

topics = json.loads(completion.choices[0].message.content)["topics"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now topics holds a lists of each topic on the test. We also want to keep track of how well you are doing on each section so I am going to create a dictionary that keeps track of your scores.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scores = {key: 0 for key in topics}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Generating questions
&lt;/h2&gt;

&lt;p&gt;Now that we have the topics we have to generate questions. The first step is deciding which topic we should ask a question on. We need to come up with some way to determine which topic to choose based off of the current proficiency scores. &lt;/p&gt;

&lt;p&gt;To do this we are basically going to calculate the difference between the score and 100 and use that as a weight for our random choice. This means that if you have a proficiency score of 100 you won't get any more questions on that topic. This is how we can implement it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import random

def get_next_topic():
    inverted_weights = [100 - score for score in scores.values()]
    return random.choices(topics, weights=inverted_weights, k=1)[0]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we know which topic we are going to ask a question for we have to actually ask the question. Since we want the question, the answer, and the work for the solution we are again going to use Json mode.&lt;/p&gt;

&lt;p&gt;Lets define the schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Problem(BaseModel):
    question: str
    work: str
    answer: str
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now we are going to start a while loop so it will keep asking questions until we decide we are done studying. Inside the while loop we are going to generate the question and solution, and some other stuff I will cover later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;running = True

while running:
    topic = get_next_topic()
    completion = octoai.text_gen.create_chat_completion(
        model="llama-2-70b-chat",
        messages=[
            ChatMessage(
                role="system",
                content="Below is an instruction that describes a task. Write a response that appropriately completes the request.",
            ),
            ChatMessage(role="user", content="Please give me a question about " + topic + " it should not be taken from the midterm but it should be heavily inspired by the midterm. You should also solve the question and provide the work in the work section and the final answer in exact form in the answer section. Do not round."),
        ],
        response_format=ChatCompletionResponseFormat(
            type="json_object",
            schema=Problem.model_json_schema()
        ),
    )

    problem = completion.choices[0].message.content
    question = json.loads(problem)["question"]
    work = json.loads(problem)["work"]
    correct_answer = json.loads(problem)["answer"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we have the question and the solution, the only thing left to do is determine whether or not the answer was correct and adjust the scores accordingly. &lt;/p&gt;

&lt;p&gt;Since its pretty hard to determine correctness programmatically (they might input the answer in a different format etc...), we are just going to ask the user if they got the answer correct.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    print("Question: " + question)
    given_answer = input("What is your answer?" + "\n")

    print("Correct Answer: " + correct_answer) 
    print("Work: " + work)

    while True:
      response = input("Did you get the answer correct? (y/n)")
      if response == "y":
          if scores[topic] &amp;lt; 100:
            scores[topic] += 10
          print("Your new score for " + topic + " is " + str(scores[topic]))
          break
      elif response == "n":
          if scores[topic] &amp;gt; 10:
              scores[topic] -= 10
          print("Your new score for " + topic + " is " + str(scores[topic]))
          break
      else:
          print("Invalid input. Please enter 'y' for yes or 'n' for no.")

    while True:
      response = input("Would you like to continue? (y/n)")
      if response == "y":
          get_next_topic()
          break
      elif response == "n":
          running = False
          break
      else:
          print("Invalid input. Please enter 'y' for yes or 'n' for no.") 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the user wants to continue it will restart the whole process with a new random topic based on the updated proficiency scores. &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This was just a simple tool I wanted to build and thought it could be cool to incorporate AI into it. It has a long way to go before it will help me ace my next test, but I like this direction of AI helping me learn about complex topics and I want to keep advancing it.&lt;/p&gt;

&lt;p&gt;A couple things I would like to add would be a better way of keeping track of proficiency. Right now I just increment or decrement by a fixed amount and it might be cool to have some more complicated algorithm to determine proficiency. I also think it would be interesting if I could take a picture of my work and it would analyze the correctness. That way if you made one small mistake it wouldn't penalize you too much, similar to the way the real tests are graded. &lt;/p&gt;

&lt;p&gt;Also this is all just in the terminal. It would be neat to have some sort of UI to interact with it. Also all of the variables are just stored on the machine so it wouldn't be able to remember your proficiency from the last session, so hooking it up to a database might be cool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Entire code
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import json
import random
import PyPDF2
import os
from octoai.client import OctoAI

from pydantic import BaseModel
from typing import List

from octoai.text_gen import ChatMessage, ChatCompletionResponseFormat

reader = PyPDF2.PdfReader('midterm1.pdf')
midterm = ""
for page in reader.pages:
    text = page.extract_text()
    midterm += text

OCTOAI_TOKEN = os.getenv('OCTOAI_TOKEN')

octoai = OctoAI(api_key=OCTOAI_TOKEN)

class Test(BaseModel):
    topics: List[str]

completion = octoai.text_gen.create_chat_completion(
    model="llama-2-70b-chat",
    messages=[
        ChatMessage(
            role="system",
            content="Below is an instruction that describes a task. Write a response that appropriately completes the request.",
        ),
        ChatMessage(role="user", content="I will give you a practice midterm. Please give me a list of topics covered on the midterm. Each topic should be a string the array" + midterm),
    ],
    response_format=ChatCompletionResponseFormat(
        type="json_object",
        schema=Test.model_json_schema()
    ),
)

topics = json.loads(completion.choices[0].message.content)["topics"]

scores = {key: 0 for key in topics}

def get_next_topic():
    inverted_weights = [100 - score for score in scores.values()]
    return random.choices(topics, weights=inverted_weights, k=1)[0]

class Problem(BaseModel):
    question: str
    work: str
    answer: str

running = True

while running:
    topic = get_next_topic()
    completion = octoai.text_gen.create_chat_completion(
        model="llama-2-70b-chat",
        messages=[
            ChatMessage(
                role="system",
                content="Below is an instruction that describes a task. Write a response that appropriately completes the request.",
            ),
            ChatMessage(role="user", content="Please give me a question about " + topic + " it should not be taken from the midterm but it should be heavily inspired by the midterm. You should also solve the question and provide the work in the work section and the final answer in exact form in the answer section. Do not round."),
        ],
        response_format=ChatCompletionResponseFormat(
            type="json_object",
            schema=Problem.model_json_schema()
        ),
    )

    problem = completion.choices[0].message.content
    question = json.loads(problem)["question"]
    work = json.loads(problem)["work"]
    correct_answer = json.loads(problem)["answer"]

    print("Question: " + question)
    given_answer = input("What is your answer?" + "\n")

    print("Correct Answer: " + correct_answer) 
    print("Work: " + work)

    while True:
      response = input("Did you get the answer correct? (y/n)")
      if response == "y":
          if scores[topic] &amp;lt; 100:
            scores[topic] += 10
          print("Your new score for " + topic + " is " + str(scores[topic]))
          break
      elif response == "n":
          if scores[topic] &amp;gt; 10:
              scores[topic] -= 10
          print("Your new score for " + topic + " is " + str(scores[topic]))
          break
      else:
          print("Invalid input. Please enter 'y' for yes or 'n' for no.")

    while True:
      response = input("Would you like to continue? (y/n)")
      if response == "y":
          get_next_topic()
          break
      elif response == "n":
          running = False
          break
      else:
          print("Invalid input. Please enter 'y' for yes or 'n' for no.") 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>ai</category>
      <category>octoai</category>
      <category>python</category>
    </item>
    <item>
      <title>T3 stack with app router and supabase</title>
      <dc:creator>Isaac Dyor</dc:creator>
      <pubDate>Sat, 06 Jan 2024 00:12:18 +0000</pubDate>
      <link>https://dev.to/isaacdyor/t3-stack-with-app-router-and-supabase-jh9</link>
      <guid>https://dev.to/isaacdyor/t3-stack-with-app-router-and-supabase-jh9</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;I am writing this blog post to document the creation of a new project of mine so I can remember specific things I did and hopefully make development quicker in the future. I also hope other people might find it useful. &lt;/p&gt;

&lt;p&gt;I am building this app with inspiration from &lt;a href="https://tx.shadcn.com/" rel="noopener noreferrer"&gt;Taxonomy&lt;/a&gt; and &lt;a href="https://acme-corp.jumr.dev/" rel="noopener noreferrer"&gt;Acme corp&lt;/a&gt; so a lot of the design comes from there.&lt;/p&gt;

&lt;p&gt;Why this stack? I have never used this exact stack before but I have used the t3 stack with Planetscale and Clerk before and really enjoyed it. I did not find a great solution for realtime, so I thought that I could use Supabase to replace Planetscale and Clerk and add realtime and storage. I also watched &lt;a href="https://www.youtube.com/watch?v=yVsaCVEfPn4&amp;amp;ab_channel=developedbyed" rel="noopener noreferrer"&gt;this video&lt;/a&gt; and &lt;a href="https://www.youtube.com/watch?v=SFn5e3vQglE&amp;amp;t=285s&amp;amp;ab_channel=Joshtriedcoding" rel="noopener noreferrer"&gt;this video&lt;/a&gt; so it seems like using trpc could be an interesting way to fetch data if I want interactivity.&lt;/p&gt;

&lt;p&gt;This blog is kind of like a case a study to see if I enjoy working with this stack. Let's get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating T3 app
&lt;/h2&gt;

&lt;p&gt;Run &lt;code&gt;npm create t3-app@latest&lt;/code&gt; to create a new t3 app. I selected the following options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Typescript: yes&lt;/li&gt;
&lt;li&gt;Tailwind: yes&lt;/li&gt;
&lt;li&gt;tRPC: yes&lt;/li&gt;
&lt;li&gt;Authentication: none&lt;/li&gt;
&lt;li&gt;Orm: prisma&lt;/li&gt;
&lt;li&gt;App router: yes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Run &lt;code&gt;npm run dev&lt;/code&gt; to start your local server. You are probably getting errors about an invalid prisma.post.findMany invocation because not everything is set up yet. For now we are just going to completely wipe the homepage so its just:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export default async function Home() {
  return &amp;lt;p&amp;gt;hello world&amp;lt;/p&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Also delete the file posts.ts file in src/server/api/routers. We will define our own routers later. &lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying our app
&lt;/h2&gt;

&lt;p&gt;We should be deploying early and deploying often, so even though we don't really have any content, lets deploy our app to vercel. &lt;/p&gt;

&lt;p&gt;The first thing we have to do is add the correct environment variables. If you open the env.mjs file you will see the schema for environment variables. The point of this file is to validate your environment variables to make sure you don't try to use an environment variable you don't actually have access to. &lt;/p&gt;

&lt;p&gt;As you can see, the schema expects you to have a database url so lets include that. I am using supabase so first create a new supabse project. In the supabase dashboard for your project go to settings and in the database section you will find connection string. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F96v25n2krqv6v4o30mo6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F96v25n2krqv6v4o30mo6.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select uri and copy paste that into your .env file. It should look like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// .env
DATABASE_URL="postgresql://postgres:[YOUR-PASSWORD]@db.lbcubcyvjdzufnvuynno.supabase.co:5432/postgres"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now that you have your environment variables set up we can deploy to vercel. First create a new github repository and push your code. Then go to vercel and press add new and import the repository you just created. Add the database url as an environment variable and press deploy. &lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up shadcn
&lt;/h2&gt;

&lt;p&gt;Run &lt;code&gt;npx shadcn-ui@latest init&lt;/code&gt; to start the shadcn initialization process. I selected the following options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Typescript: yes&lt;/li&gt;
&lt;li&gt;Style: default&lt;/li&gt;
&lt;li&gt;Base color: slate&lt;/li&gt;
&lt;li&gt;Global css file: src/styles/globals.css&lt;/li&gt;
&lt;li&gt;Css variables: yes&lt;/li&gt;
&lt;li&gt;Custom prefix: blank&lt;/li&gt;
&lt;li&gt;Tailwind config: tailwind.config.ts&lt;/li&gt;
&lt;li&gt;Component import alias: @/components&lt;/li&gt;
&lt;li&gt;Utils import alias: @/lib/utils&lt;/li&gt;
&lt;li&gt;React server components: yes&lt;/li&gt;
&lt;li&gt;Write to components.json: yes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now run &lt;code&gt;npm install next-themes&lt;/code&gt; to install next themes. Add a component called theme-provider.tsx to your components folder and include the following code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// components/theme-provider.tsx

"use client"

import * as React from "react"
import { ThemeProvider as NextThemesProvider } from "next-themes"
import { type ThemeProviderProps } from "next-themes/dist/types"

export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
  return &amp;lt;NextThemesProvider {...props}&amp;gt;{children}&amp;lt;/NextThemesProvider&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Once you have your provider setup, you want to add it to the layout.tsx so it is implemented on the entire app. Wrap the {children} with the Theme provider like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/layout.tsx
  return (
    &amp;lt;html lang="en" suppressHydrationWarning&amp;gt;
      &amp;lt;body className={`font-sans ${inter.variable}`}&amp;gt;
        &amp;lt;TRPCReactProvider cookies={cookies().toString()}&amp;gt;
          &amp;lt;ThemeProvider
            attribute="class"
            defaultTheme="dark"
            enableSystem
            disableTransitionOnChange
          &amp;gt;
            {children}
          &amp;lt;/ThemeProvider&amp;gt;
        &amp;lt;/TRPCReactProvider&amp;gt;
      &amp;lt;/body&amp;gt;
    &amp;lt;/html&amp;gt;
  );
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now go to the shadcn &lt;a href="https://ui.shadcn.com/themes" rel="noopener noreferrer"&gt;themes page&lt;/a&gt;. And select the theme you want to use and press copy code. Then add that copied code to your globals.css so it looks like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// styles/globals.css
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  :root {
    --background: 0 0% 100%;
    --foreground: 224 71.4% 4.1%;
    --card: 0 0% 100%;
    --card-foreground: 224 71.4% 4.1%;
    --popover: 0 0% 100%;
    --popover-foreground: 224 71.4% 4.1%;
    --primary: 262.1 83.3% 57.8%;
    --primary-foreground: 210 20% 98%;
    --secondary: 220 14.3% 95.9%;
    --secondary-foreground: 220.9 39.3% 11%;
    --muted: 220 14.3% 95.9%;
    --muted-foreground: 220 8.9% 46.1%;
    --accent: 220 14.3% 95.9%;
    --accent-foreground: 220.9 39.3% 11%;
    --destructive: 0 84.2% 60.2%;
    --destructive-foreground: 210 20% 98%;
    --border: 220 13% 91%;
    --input: 220 13% 91%;
    --ring: 262.1 83.3% 57.8%;
    --radius: 0.5rem;
  }

  .dark {
    --background: 224 71.4% 4.1%;
    --foreground: 210 20% 98%;
    --card: 224 71.4% 4.1%;
    --card-foreground: 210 20% 98%;
    --popover: 224 71.4% 4.1%;
    --popover-foreground: 210 20% 98%;
    --primary: 263.4 70% 50.4%;
    --primary-foreground: 210 20% 98%;
    --secondary: 215 27.9% 16.9%;
    --secondary-foreground: 210 20% 98%;
    --muted: 215 27.9% 16.9%;
    --muted-foreground: 217.9 10.6% 64.9%;
    --accent: 215 27.9% 16.9%;
    --accent-foreground: 210 20% 98%;
    --destructive: 0 62.8% 30.6%;
    --destructive-foreground: 210 20% 98%;
    --border: 215 27.9% 16.9%;
    --input: 215 27.9% 16.9%;
    --ring: 263.4 70% 50.4%;
  }
}

@layer base {
  * {
    @apply border-border;
  }
  body {
    @apply bg-background text-foreground;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Navbar
&lt;/h2&gt;

&lt;p&gt;We will be building a navbar that works for web and mobile, and on mobile we need a hamburger menu that can be opened and closed. For the icons we will be using heroicons, so run &lt;code&gt;npm install @heroicons/react&lt;/code&gt; to install it. We also need to install the button component from shadcn so run &lt;code&gt;npx shadcn-ui@latest add button&lt;/code&gt; to install it. &lt;/p&gt;

&lt;p&gt;For now we will keep the navbar basic, but in the future we can check to see if the user is logged in and render different content based on the user's status.&lt;/p&gt;

&lt;p&gt;Create a new file in &lt;code&gt;components/navbar/Navbar.tsx&lt;/code&gt;. Add the following code to the file: (Note that since we are going to be using state to open and close the hamburger menu on mobile, we need to make it a client component).&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// components/navbar/Navbar.tsx
"use client";
import Link from "next/link";
import React, { useState } from "react";
import { XMarkIcon, Bars3Icon } from "@heroicons/react/24/solid";
import { Button } from "../ui/button";

const routes: { title: string; href: string }[] = [
  { title: "Features", href: "#features" },
  { title: "Resources", href: "#resources" },
  { title: "Pricing", href: "#pricing" },
];

const Navbar: React.FC = () =&amp;gt; {
  const [menuOpen, setMenuOpen] = useState(false);

  const toggleMenu = () =&amp;gt; {
    setMenuOpen(!menuOpen);
  };

  return (
    &amp;lt;div className="flex h-16 items-center justify-between px-6 lg:px-14"&amp;gt;
      &amp;lt;div className="flex items-center"&amp;gt;
        &amp;lt;Link href={"/"} className="shrink-0"&amp;gt;
          &amp;lt;h1 className="text-accent-foreground text-2xl font-bold"&amp;gt;devlink&amp;lt;/h1&amp;gt;
        &amp;lt;/Link&amp;gt;
        &amp;lt;div className="bg-background hidden w-full justify-end gap-1 px-4 py-2 sm:flex"&amp;gt;
          {routes.map((route, index) =&amp;gt; (
            &amp;lt;Link
              key={index}
              href={route.href}
              className={`hover:text-accent-foreground text-muted-foreground inline-flex h-10 w-full items-center px-4 py-2 text-sm transition-colors sm:w-auto`}
            &amp;gt;
              {route.title}
            &amp;lt;/Link&amp;gt;
          ))}
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;

      &amp;lt;div className="hidden items-center gap-2 sm:flex"&amp;gt;
        &amp;lt;Link href={"/login"} className="w-full sm:w-auto"&amp;gt;
          &amp;lt;Button variant="secondary" size="sm" className="w-full"&amp;gt;
            Log In
          &amp;lt;/Button&amp;gt;
        &amp;lt;/Link&amp;gt;
        &amp;lt;Link href="/signup" className="w-full sm:w-auto"&amp;gt;
          &amp;lt;Button variant="default" size="sm" className="w-full"&amp;gt;
            Sign Up
          &amp;lt;/Button&amp;gt;
        &amp;lt;/Link&amp;gt;
      &amp;lt;/div&amp;gt;

      {menuOpen &amp;amp;&amp;amp; &amp;lt;MobileMenu toggleMenu={toggleMenu} /&amp;gt;}

      &amp;lt;button onClick={toggleMenu} className="sm:hidden"&amp;gt;
        {menuOpen ? (
          &amp;lt;XMarkIcon className="h-7 w-7" /&amp;gt;
        ) : (
          &amp;lt;Bars3Icon className="h-7 w-7" /&amp;gt;
        )}
      &amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

const MobileMenu: React.FC&amp;lt;{ toggleMenu: () =&amp;gt; void }&amp;gt; = ({ toggleMenu }) =&amp;gt; {
  return (
    &amp;lt;div className="absolute right-0 top-16 flex h-[calc(100vh-64px)] w-full flex-col"&amp;gt;
      &amp;lt;div className="bg-background  flex w-full grow flex-col gap-1 px-4 pb-2 sm:hidden"&amp;gt;
        {routes.map((route, index) =&amp;gt; (
          &amp;lt;Link
            key={index}
            href={route.href}
            onClick={toggleMenu}
            className={`hover:text-accent-foreground text-muted-foreground inline-flex h-10 w-full items-center text-sm transition-colors sm:w-auto`}
          &amp;gt;
            {route.title}
          &amp;lt;/Link&amp;gt;
        ))}
        &amp;lt;Link href={"/login"} className="w-full sm:w-auto"&amp;gt;
          &amp;lt;Button
            onClick={toggleMenu}
            variant="secondary"
            size="sm"
            className="w-full"
          &amp;gt;
            Log In
          &amp;lt;/Button&amp;gt;
        &amp;lt;/Link&amp;gt;
        &amp;lt;Link href="/signup" className="w-full sm:w-auto"&amp;gt;
          &amp;lt;Button
            onClick={toggleMenu}
            variant="default"
            size="sm"
            className="w-full"
          &amp;gt;
            Sign Up
          &amp;lt;/Button&amp;gt;
        &amp;lt;/Link&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div className="bg-background/60 h-screen w-full sm:hidden" /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default Navbar;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now that we have our Navbar component we need to add it to the layout.tsx to apply it to all of our pages: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/layout.tsx
  return (
    &amp;lt;html lang="en" suppressHydrationWarning&amp;gt;
      &amp;lt;body className={`font-sans ${inter.variable}`}&amp;gt;
        &amp;lt;TRPCReactProvider cookies={cookies().toString()}&amp;gt;
          &amp;lt;ThemeProvider
            attribute="class"
            defaultTheme="dark"
            enableSystem
            disableTransitionOnChange
          &amp;gt;
            &amp;lt;Navbar /&amp;gt;
            {children}
          &amp;lt;/ThemeProvider&amp;gt;
        &amp;lt;/TRPCReactProvider&amp;gt;
      &amp;lt;/body&amp;gt;
    &amp;lt;/html&amp;gt;
  );
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Adding supabase auth
&lt;/h2&gt;

&lt;p&gt;Run &lt;code&gt;npm install @supabase/ssr @supabase/supabase-js&lt;/code&gt; to install the supabase client and supabase ssr. &lt;/p&gt;

&lt;p&gt;Now we need to configure our environment variables for authentication. In api settings section of the supabase dashboard you will find your project url and your anon key. Add both of these to your .env file so it looks like this:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// .env
DATABASE_URL=url
NEXT_PUBLIC_SUPABASE_URL=your-supabase-url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now that we have added new enviornment variables we need to make sure our schema in env.js is up to date. Navigate to the env.js file and inside the client section add these two new environment variables:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// env.js
  client: {
    NEXT_PUBLIC_SUPABASE_URL: z.string(),
    NEXT_PUBLIC_SUPABASE_ANON_KEY: z.string(),
  },
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You also have to add them to the runtime env section, so it should now look like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  runtimeEnv: {
    DATABASE_URL: process.env.DATABASE_URL,
    NODE_ENV: process.env.NODE_ENV,
    NEXT_PUBLIC_SUPABASE_URL: process.env.NEXT_PUBLIC_SUPABASE_URL,
    NEXT_PUBLIC_SUPABASE_ANON_KEY: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
  },
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Supabase client&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now we are going to create some utility functions to create a supabase instance on the client, server, and in middleware. Create a new folder in the src directory called utils and add a new folder inside called supabase. Add a file called middleware.ts and add the following code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// utils/supabase/middleware.ts
import { createServerClient, type CookieOptions } from "@supabase/ssr";
import { type NextRequest, NextResponse } from "next/server";
import { env } from "@/env";

export const createClient = (request: NextRequest) =&amp;gt; {
  // Create an unmodified response
  let response = NextResponse.next({
    request: {
      headers: request.headers,
    },
  });

  const supabase = createServerClient(
    env.NEXT_PUBLIC_SUPABASE_URL,
    env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
    {
      cookies: {
        get(name: string) {
          return request.cookies.get(name)?.value;
        },
        set(name: string, value: string, options: CookieOptions) {
          // If the cookie is updated, update the cookies for the request and response
          request.cookies.set({
            name,
            value,
            ...options,
          });
          response = NextResponse.next({
            request: {
              headers: request.headers,
            },
          });
          response.cookies.set({
            name,
            value,
            ...options,
          });
        },
        remove(name: string, options: CookieOptions) {
          // If the cookie is removed, update the cookies for the request and response
          request.cookies.set({
            name,
            value: "",
            ...options,
          });
          response = NextResponse.next({
            request: {
              headers: request.headers,
            },
          });
          response.cookies.set({
            name,
            value: "",
            ...options,
          });
        },
      },
    },
  );

  return { supabase, response };
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now create a new file in the same directory called client.ts and add the following code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// utils/supabase/client.ts
import { createBrowserClient } from "@supabase/ssr";
import { env } from "@/env";

export const createClient = () =&amp;gt;
  createBrowserClient(
    env.NEXT_PUBLIC_SUPABASE_URL,
    env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
  );

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And finally add a file called server.ts and add the following code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// utils/supabase/server.ts
import { env } from "@/env";
import { createServerClient, type CookieOptions } from "@supabase/ssr";
import { cookies } from "next/headers";

export const createClient = (cookieStore: ReturnType&amp;lt;typeof cookies&amp;gt;) =&amp;gt; {
  return createServerClient(
    env.NEXT_PUBLIC_SUPABASE_URL,
    env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
    {
      cookies: {
        get(name: string) {
          return cookieStore.get(name)?.value;
        },
        set(name: string, value: string, options: CookieOptions) {
          try {
            cookieStore.set({ name, value, ...options });
          } catch (error) {
            // The `set` method was called from a Server Component.
            // This can be ignored if you have middleware refreshing
            // user sessions.
          }
        },
        remove(name: string, options: CookieOptions) {
          try {
            cookieStore.set({ name, value: "", ...options });
          } catch (error) {
            // The `delete` method was called from a Server Component.
            // This can be ignored if you have middleware refreshing
            // user sessions.
          }
        },
      },
    },
  );
};

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now whenever you need a new instance of supabase you can import it already preconfigured with your environment variables. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Middleware&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We need to add a middleware to refresh the session if it is expired. This is required for server components. This is also where we can define which routes are protected or unprotected using the matcher array. In the src directory add a new file called middleware.ts and add the following code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// middleware.ts
import { type NextRequest } from "next/server";
import { createClient } from "@/utils/supabase/middleware";

export async function middleware(request: NextRequest) {
  const { supabase, response } = createClient(request);

  await supabase.auth.getSession();

  return response;
}

export const config = {
  matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Callback route&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you send someone a confirmation email after they sign up you want them to be automatically signed in once they press the link. The way this is done is through a code exchange. We can set this up through a callback route. Create a new file at &lt;code&gt;app/(auth)/auth/callback/route.ts&lt;/code&gt; and add the following code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/(auth)/auth/callback/route.ts
import { createClient } from "@/utils/supabase/server";
import { NextResponse } from "next/server";
import { cookies } from "next/headers";

export async function GET(request: Request) {
  const requestUrl = new URL(request.url);
  const code = requestUrl.searchParams.get("code");

  if (code) {
    const cookieStore = cookies();
    const supabase = createClient(cookieStore);
    await supabase.auth.exchangeCodeForSession(code);
  }

  // URL to redirect to after sign in process completes
  return NextResponse.redirect(requestUrl.origin);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Authentication&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now we need a way for the user to login and logout. To do this we can create a new folder called (auth) in the app directory and add two new pages, a login/page.tsx and a signup/page.tsx. &lt;/p&gt;

&lt;p&gt;Before we add any code to these files we need to import the input, and form components from shadcn. The form component automatically uses react-hook-form so we can easily implement form validation. We will also need to install react-icons because hero-icons doesn't have github/google icons. &lt;em&gt;(If you only want to use react-icons that is fine too, I just like the consistency of heroicons)&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;npx shadcn-ui@latest add form&lt;/code&gt; to install the form and &lt;code&gt;npx shadcn-ui@latest add input&lt;/code&gt; for input. Run &lt;code&gt;npm install react-icons --save&lt;/code&gt; to install react-icons.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fixing navbar&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There is also a little more configuration we need to do to make everything look good. I don't want the navbar showing on the auth pages because I think it looks a lot cleaner. &lt;em&gt;(It's a little complicated for a small nitpick so you can skip this if you want)&lt;/em&gt;. The best way I know how to do this is to make a (main) folder in the app directory so I can add the navbar in the layout.tsx of that folder, rather than adding the navbar to the entire project. &lt;/p&gt;

&lt;p&gt;So first remove the Navbar from your main layout.tsx. Next create a new (main) folder and move the page.tsx into that folder. Create a new layout.tsx file in the new folder and include the following code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/(main)/layout.tsx
import React from "react";
import Navbar from "@/components/navbar/Navbar";

const RootLayout: React.FC&amp;lt;{ children: React.ReactNode }&amp;gt; = async ({
  children,
}) =&amp;gt; {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;Navbar /&amp;gt;
      {children}
    &amp;lt;/&amp;gt;
  );
};

export default RootLayout;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now the navbar will be applied to all of the files in the main folder, which will be everything other than auth. Although I don't want the entire navbar showing on the auth pages I still want the main logo to show so the user can easily navigate back to the homepage. I also want to redirect the user away from the auth pages if they are already signed in &lt;em&gt;(this may be better as a middleware but this was easy)&lt;/em&gt;. To do that create a layout.tsx in the (auth) folder and add the following code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/(auth)/layout.tsx
import React from "react";
import { createClient } from "@/utils/supabase/server";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import Link from "next/link";

const RootLayout: React.FC&amp;lt;{ children: React.ReactNode }&amp;gt; = async ({
  children,
}) =&amp;gt; {
  const supabase = createClient(cookies());

  const {
    data: { user },
  } = await supabase.auth.getUser();

  if (user) {
    redirect("/");
  }
  return (
    &amp;lt;&amp;gt;
      &amp;lt;Link href={"/"} className="absolute left-6 top-4 shrink-0 lg:left-14"&amp;gt;
        &amp;lt;h1 className="text-accent-foreground text-2xl font-bold"&amp;gt;devlink&amp;lt;/h1&amp;gt;
      &amp;lt;/Link&amp;gt;
      {children}
    &amp;lt;/&amp;gt;
  );
};

export default RootLayout;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Okay now everything should be working properly. Your app folder structure should look like this now:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
└── app/
    ├── (auth)/
    │   ├── auth/
    │   │   └── callback/
    │   │       └── route.ts
    │   ├── signup/
    │   │   └── page.tsx
    │   ├── login/
    │   │   └── page.tsx
    │   └── layout.tsx
    ├── (main)/
    │   ├── page.tsx
    │   └── layout.tsx
    ├── api/
    │   └── trpc/
    │       └── [trpc]/
    │           └── route.ts
    └── layout.tsx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Signup page&lt;/strong&gt;&lt;br&gt;
Now that we have completed that tangent we need to add the signup page. In the signup page add the following code: &lt;em&gt;(It doesn't have any functionality yet, we will add that later. This is just the markup)&lt;/em&gt;. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/(auth)/signup/page.tsx
"use client";

import Link from "next/link";
import { Button } from "@/components/ui/button";
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form";

import { Input } from "@/components/ui/input";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import React from "react";

import { FaGithub } from "react-icons/fa";
import { FcGoogle } from "react-icons/fc";

const registerSchema = z.object({
  email: z.string().email(),
  password: z.string().min(6).max(100),
});

export type SignupInput = z.infer&amp;lt;typeof registerSchema&amp;gt;;

export default function Login() {
  const form = useForm&amp;lt;SignupInput&amp;gt;({
    resolver: zodResolver(registerSchema),
    defaultValues: {
      email: "",
      password: "",
    },
  });

  const onSubmit = async (data: SignupInput) =&amp;gt; {
    console.log(data);
  };

  return (
    &amp;lt;div className="flex"&amp;gt;
      &amp;lt;div className="bg-secondary/15 hidden h-screen grow lg:block" /&amp;gt;
      &amp;lt;div className="bg-background h-screen w-full lg:w-1/2"&amp;gt;
        &amp;lt;div className="flex h-full items-center justify-center"&amp;gt;
          &amp;lt;div className="w-full max-w-md p-8"&amp;gt;
            &amp;lt;h1 className="mb-4 text-2xl font-semibold"&amp;gt;Sign up&amp;lt;/h1&amp;gt;
            &amp;lt;Form {...form}&amp;gt;
              &amp;lt;form
                onSubmit={form.handleSubmit(onSubmit)}
                className="animate-in text-muted-foreground flex w-full flex-1 flex-col justify-center gap-2"
              &amp;gt;
                &amp;lt;FormField
                  control={form.control}
                  name="email"
                  render={({ field }) =&amp;gt; (
                    &amp;lt;FormItem&amp;gt;
                      &amp;lt;FormLabel className="text-muted-foreground"&amp;gt;
                        Email Address
                      &amp;lt;/FormLabel&amp;gt;
                      &amp;lt;FormControl&amp;gt;
                        &amp;lt;Input
                          placeholder="Your email address"
                          {...field}
                          autoComplete="on"
                        /&amp;gt;
                      &amp;lt;/FormControl&amp;gt;
                      &amp;lt;FormMessage /&amp;gt;
                    &amp;lt;/FormItem&amp;gt;
                  )}
                /&amp;gt;
                &amp;lt;FormField
                  control={form.control}
                  name="password"
                  render={({ field }) =&amp;gt; (
                    &amp;lt;FormItem&amp;gt;
                      &amp;lt;FormLabel className="text-muted-foreground"&amp;gt;
                        Password
                      &amp;lt;/FormLabel&amp;gt;
                      &amp;lt;FormControl&amp;gt;
                        &amp;lt;Input
                          placeholder="Your password"
                          type="password"
                          autoComplete="on"
                          {...field}
                        /&amp;gt;
                      &amp;lt;/FormControl&amp;gt;
                      &amp;lt;FormMessage /&amp;gt;
                    &amp;lt;/FormItem&amp;gt;
                  )}
                /&amp;gt;
                &amp;lt;Button variant="default" className="my-3 w-full" type="submit"&amp;gt;
                  Sign up
                &amp;lt;/Button&amp;gt;
              &amp;lt;/form&amp;gt;
            &amp;lt;/Form&amp;gt;
            &amp;lt;div className="flex items-center gap-2 py-4"&amp;gt;
              &amp;lt;hr className="w-full" /&amp;gt;
              &amp;lt;p className="text-muted-foreground text-xs"&amp;gt;OR&amp;lt;/p&amp;gt;
              &amp;lt;hr className="w-full" /&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;Button
              variant="outline"
              className="text-muted-foreground mb-2 w-full font-normal"
            &amp;gt;
              &amp;lt;div className="flex items-center gap-2"&amp;gt;
                &amp;lt;FaGithub className="h-5 w-5" /&amp;gt;
                &amp;lt;p&amp;gt;Sign in with GitHub&amp;lt;/p&amp;gt;
              &amp;lt;/div&amp;gt;
            &amp;lt;/Button&amp;gt;
            &amp;lt;Button
              variant="outline"
              className="text-muted-foreground mb-2 w-full font-normal"
            &amp;gt;
              &amp;lt;div className="flex items-center gap-2"&amp;gt;
                &amp;lt;FcGoogle className="h-5 w-5" /&amp;gt;
                &amp;lt;p&amp;gt;Sign in with Google&amp;lt;/p&amp;gt;
              &amp;lt;/div&amp;gt;
            &amp;lt;/Button&amp;gt;
            &amp;lt;p className="text-muted-foreground py-4 text-center text-sm underline"&amp;gt;
              &amp;lt;Link href="/signup"&amp;gt;Already have an account? Sign in&amp;lt;/Link&amp;gt;
            &amp;lt;/p&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Signup functionality&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When the user submits the form it should call a server action where we can call supabase.signup. Lets build that server action.&lt;/p&gt;

&lt;p&gt;In the (auth) folder lets add a new file called actions.ts. This is where we will define the signup, login, and logout functions. Inside the actions file add the following code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/(auth)/actions.ts
"use server";

import { createClient } from "@/utils/supabase/server";
import { cookies, headers } from "next/headers";
import type { SignupInput } from "./signup/page";

const supabase = createClient(cookies());
const origin = headers().get("origin");

export const signUp = async (data: SignupInput) =&amp;gt; {
  "use server";

  const { error } = await supabase.auth.signUp({
    email: data.email,
    password: data.password,
    options: {
      emailRedirectTo: `${origin}/auth/callback`,
    },
  });

  if (error) {
    return {
      error: error.message,
    };
  }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And now we need to call this function on submit of the signup page. If there is an error we want to render the error so the user knows, and if the confirmation email has been successfully sent we also want to let the user know. To do this we are going to use state.&lt;/p&gt;

&lt;p&gt;In the top of your signup component add the following code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/(auth)/signup.tsx

...

const [error, setError] = useState&amp;lt;string | null&amp;gt;(null);
const [success, setSuccess] = useState&amp;lt;string | null&amp;gt;(null);

const onSubmit = async (data: SignupInput) =&amp;gt; {
  setSuccess("Check your email for further instructions");
  const result = await signUp(data);
  if (result?.error) {
    setSuccess(null);
    setError(result.error);
  }
};

...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now we need to conditionally render the error and success messages. Add the following code below the signup button:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/(auth)/signup.tsx

...

{success &amp;amp;&amp;amp; (
  &amp;lt;div className="bg-secondary/50 border-border mb-3 mt-1 rounded-md border p-3"&amp;gt;
    &amp;lt;p className="text-muted-foreground text-center text-sm font-medium"&amp;gt;
      {success}
    &amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
)}
{error &amp;amp;&amp;amp; (
  &amp;lt;div className="bg-destructive/10 border-destructive mb-3 mt-1 rounded-md border p-3"&amp;gt;
    &amp;lt;p className="text-destructive text-center text-sm font-medium"&amp;gt;
      {error}
    &amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
)}

...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now when you press signup you should see a message saying check your email. If you check the auth tab of the Supabase dashboard you should that there is a new user pending verification. Once you go to your email and press the link it should automatically sign you in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Logout&lt;/strong&gt;&lt;br&gt;
Now once the user has signed up they need to be able to logout. For now lets just add a button on the home page to logout, but later we can put it in a profile menu. In the &lt;code&gt;app/(main)/page.tsx&lt;/code&gt; file add the following code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/(main)/page.tsx
import { createClient } from "@/utils/supabase/server";
import Link from "next/link";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";

export default async function AuthButton() {
  const supabase = createClient(cookies());

  const {
    data: { user },
  } = await supabase.auth.getUser();

  const signOut = async () =&amp;gt; {
    "use server";

    const supabase = createClient(cookies());
    await supabase.auth.signOut();
    return redirect("/login");
  };

  return user ? (
    &amp;lt;div className="flex items-center gap-4"&amp;gt;
      Hey, {user.email}!
      &amp;lt;form action={signOut}&amp;gt;
        &amp;lt;button className="bg-btn-background hover:bg-btn-background-hover rounded-md px-4 py-2 no-underline"&amp;gt;
          Logout
        &amp;lt;/button&amp;gt;
      &amp;lt;/form&amp;gt;
    &amp;lt;/div&amp;gt;
  ) : (
    &amp;lt;Link
      href="/login"
      className="bg-btn-background hover:bg-btn-background-hover flex rounded-md px-3 py-2 no-underline"
    &amp;gt;
      Login
    &amp;lt;/Link&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;When you press the logout button it should redirect you to /login which doesn't exist, but when you navigate back to the home page you will see that you are not logged in. Now lets add the login page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Login&lt;/strong&gt;&lt;br&gt;
Although this process will be quite similar to signup I did not create a reusable component because in my opinion it complicates things. &lt;em&gt;(My keeping the pages seperate I can easily add things like password confirmation to the signup page without messing with the login page. If you want to make it a component feel free).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;app/(auth)/login/page.tsx&lt;/code&gt; file add the following code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/(auth)/login/page.tsx

"use client";

import Link from "next/link";
import { Button } from "@/components/ui/button";
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form";

import { Input } from "@/components/ui/input";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import React, { useState } from "react";

import { FaGithub } from "react-icons/fa";
import { FcGoogle } from "react-icons/fc";
import { signIn, signUp } from "../actions";

const registerSchema = z.object({
  email: z.string().email(),
  password: z.string().min(6).max(100),
});

export type LoginInput = z.infer&amp;lt;typeof registerSchema&amp;gt;;

export default function Login() {
  const form = useForm&amp;lt;LoginInput&amp;gt;({
    resolver: zodResolver(registerSchema),
    defaultValues: {
      email: "",
      password: "",
    },
  });

  const [error, setError] = useState&amp;lt;string | null&amp;gt;(null);

  const onSubmit = async (data: LoginInput) =&amp;gt; {
    const result = await signIn(data);
    if (result?.error) {
      setError(result.error);
    }
  };

  return (
    &amp;lt;div className="flex"&amp;gt;
      &amp;lt;div className="bg-secondary/15 hidden h-screen grow lg:block" /&amp;gt;
      &amp;lt;div className="bg-background h-screen w-full lg:w-1/2"&amp;gt;
        &amp;lt;div className="flex h-full items-center justify-center"&amp;gt;
          &amp;lt;div className="w-full max-w-md p-8"&amp;gt;
            &amp;lt;h1 className="mb-4 text-2xl font-semibold"&amp;gt;Sign in&amp;lt;/h1&amp;gt;
            &amp;lt;Form {...form}&amp;gt;
              &amp;lt;form
                onSubmit={form.handleSubmit(onSubmit)}
                className="animate-in text-muted-foreground flex w-full flex-1 flex-col justify-center gap-2"
              &amp;gt;
                &amp;lt;FormField
                  control={form.control}
                  name="email"
                  render={({ field }) =&amp;gt; (
                    &amp;lt;FormItem&amp;gt;
                      &amp;lt;FormLabel className="text-muted-foreground"&amp;gt;
                        Email Address
                      &amp;lt;/FormLabel&amp;gt;
                      &amp;lt;FormControl&amp;gt;
                        &amp;lt;Input
                          placeholder="Your email address"
                          {...field}
                          autoComplete="on"
                        /&amp;gt;
                      &amp;lt;/FormControl&amp;gt;
                      &amp;lt;FormMessage /&amp;gt;
                    &amp;lt;/FormItem&amp;gt;
                  )}
                /&amp;gt;
                &amp;lt;FormField
                  control={form.control}
                  name="password"
                  render={({ field }) =&amp;gt; (
                    &amp;lt;FormItem&amp;gt;
                      &amp;lt;FormLabel className="text-muted-foreground"&amp;gt;
                        Password
                      &amp;lt;/FormLabel&amp;gt;
                      &amp;lt;FormControl&amp;gt;
                        &amp;lt;Input
                          placeholder="Your password"
                          type="password"
                          autoComplete="on"
                          {...field}
                        /&amp;gt;
                      &amp;lt;/FormControl&amp;gt;
                      &amp;lt;FormMessage /&amp;gt;
                    &amp;lt;/FormItem&amp;gt;
                  )}
                /&amp;gt;
                &amp;lt;Button variant="default" className="my-3 w-full" type="submit"&amp;gt;
                  Sign in
                &amp;lt;/Button&amp;gt;
                {error &amp;amp;&amp;amp; (
                  &amp;lt;div className="bg-destructive/10 border-destructive mb-3 mt-1 rounded-md border p-3"&amp;gt;
                    &amp;lt;p className="text-destructive text-center text-sm font-medium"&amp;gt;
                      {error}
                    &amp;lt;/p&amp;gt;
                  &amp;lt;/div&amp;gt;
                )}
              &amp;lt;/form&amp;gt;
            &amp;lt;/Form&amp;gt;
            &amp;lt;div className="flex items-center gap-2 py-4"&amp;gt;
              &amp;lt;hr className="w-full" /&amp;gt;
              &amp;lt;p className="text-muted-foreground text-xs"&amp;gt;OR&amp;lt;/p&amp;gt;
              &amp;lt;hr className="w-full" /&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;Button
              variant="outline"
              className="text-muted-foreground mb-2 w-full font-normal"
            &amp;gt;
              &amp;lt;div className="flex items-center gap-2"&amp;gt;
                &amp;lt;FaGithub className="h-5 w-5" /&amp;gt;
                &amp;lt;p&amp;gt;Sign in with GitHub&amp;lt;/p&amp;gt;
              &amp;lt;/div&amp;gt;
            &amp;lt;/Button&amp;gt;
            &amp;lt;Button
              variant="outline"
              className="text-muted-foreground mb-2 w-full font-normal"
            &amp;gt;
              &amp;lt;div className="flex items-center gap-2"&amp;gt;
                &amp;lt;FcGoogle className="h-5 w-5" /&amp;gt;
                &amp;lt;p&amp;gt;Sign in with Google&amp;lt;/p&amp;gt;
              &amp;lt;/div&amp;gt;
            &amp;lt;/Button&amp;gt;
            &amp;lt;p className="text-muted-foreground py-4 text-center text-sm underline"&amp;gt;
              &amp;lt;Link href="/signup"&amp;gt;Don&amp;amp;apos;t have an account? Sign up&amp;lt;/Link&amp;gt;
            &amp;lt;/p&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We have not defined the signIn action yet so let's do that. In the &lt;code&gt;app/(auth)/actions.ts&lt;/code&gt; file add the following function below the signUp function:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/(auth)/actions.ts

...
export const signIn = async (data: LoginInput) =&amp;gt; {
  "use server";

  const { error } = await supabase.auth.signInWithPassword({
    email: data.email,
    password: data.password,
  });
  if (error) {
    return {
      error: error.message,
    };
  }
};
...


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now if you are logged out you can navigate to the &lt;code&gt;/login&lt;/code&gt; route and you should be able to login.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OAuth&lt;/strong&gt;&lt;br&gt;
The final part of supabase auth we need to implement is signing in with OAuth. Right now we have some buttons for this that don't do anything so lets change that. &lt;/p&gt;

&lt;p&gt;The first thing we need to do is set up the providers in the supabase dashboard. To do this we need to get the client id and client secret for whatever providers we are using. I am using google and github. Follow the &lt;a href="https://support.google.com/cloud/answer/6158849?hl=en" rel="noopener noreferrer"&gt;google oauth documentation&lt;/a&gt; and the &lt;a href="https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app" rel="noopener noreferrer"&gt;github oauth documentation&lt;/a&gt; to get your credentials. &lt;/p&gt;

&lt;p&gt;Once you have your credentials go to the providers section of the auth section of the supabase dashboard. Enable the providers you want to use and add your credentials. Make sure that you add the callback url as an approved redirect url for all of the providers you choose. &lt;/p&gt;

&lt;p&gt;In the components folder create a new file at &lt;code&gt;auth/OauthButton.tsx&lt;/code&gt; and add the following code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// components/auth/OauthButton.tsx

"use client";

import { Button } from "@/components/ui/button";
import { redirect, usePathname } from "next/navigation";
import { FcGoogle } from "react-icons/fc";
import React from "react";
import { Provider } from "@supabase/supabase-js";
import { FaGithub } from "react-icons/fa";
import { createClient } from "@/utils/supabase/client";

const OauthButton: React.FC&amp;lt;{ provider: Provider }&amp;gt; = ({ provider }) =&amp;gt; {
  const pathname = usePathname();
  const supabase = createClient();

  const handleLogin = async () =&amp;gt; {
    const { error } = await supabase.auth.signInWithOAuth({
      provider: provider,
      options: {
        redirectTo: `${location.origin}/auth/callback?next=${pathname}`,
      },
    });

    if (error) {
      return redirect("/login?message=Could not authenticate user");
    }
  };

  if (provider === "google") {
    return (
      &amp;lt;Button
        variant="outline"
        className="text-muted-foreground mb-2 w-full font-normal"
        onClick={() =&amp;gt; handleLogin().catch(console.error)}
      &amp;gt;
        &amp;lt;div className="flex items-center gap-2"&amp;gt;
          &amp;lt;FcGoogle className="h-5 w-5" /&amp;gt;
          &amp;lt;p&amp;gt;Sign in with Google&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/Button&amp;gt;
    );
  }

  if (provider === "github") {
    return (
      &amp;lt;Button
        variant="outline"
        className="text-muted-foreground mb-2 w-full font-normal"
        onClick={handleLogin}
      &amp;gt;
        &amp;lt;div className="flex items-center gap-2"&amp;gt;
          &amp;lt;FaGithub className="h-5 w-5" /&amp;gt;
          &amp;lt;p&amp;gt;Sign in with GitHub&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/Button&amp;gt;
    );
  }
};

export default OauthButton;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now we need to replace our old Oauth buttons with this new component. In both &lt;code&gt;signup/page.tsx&lt;/code&gt; and &lt;code&gt;login/page.tsx&lt;/code&gt; replace the old buttons with the following code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
...
&amp;lt;OauthButton provider={"google"} /&amp;gt;
&amp;lt;OauthButton provider={"github"} /&amp;gt;
...

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If you set up everything correctly you should now be able to login using oauth. &lt;/p&gt;

&lt;h2&gt;
  
  
  Adding auth to navbar
&lt;/h2&gt;

&lt;p&gt;Now that we have Supabase auth setup we should only render the signup and login buttons when there is no current session, and instead render a profile button if the user is logged in. &lt;/p&gt;

&lt;p&gt;To do this we are going to create an auth component that handles all this logic for us, and we can import it into our navbar. &lt;/p&gt;

&lt;p&gt;There are a few interesting things to note here. If we are going to be showing the current users data that means we need to query the current user. It is possible to query from the client side but generally it is better to query from a server component and pass the data as a prop to a client component. However, you cannot import a server component into a client component and since the navbar is a client component that would mean we could not import our AuthComponent if we made it a server component. &lt;/p&gt;

&lt;p&gt;The way next.js recommends you handle this is through &lt;a href="https://nextjs.org/docs/app/building-your-application/rendering/composition-patterns#supported-pattern-passing-server-components-to-client-components-as-props" rel="noopener noreferrer"&gt;passing the component as a prop&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The first step of this process is to create our server component at &lt;code&gt;components/navbar/AuthComponent.tsx&lt;/code&gt; and add the following code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// components/navbar/AuthComponent.tsx

import { createClient } from "@/utils/supabase/server";
import { cookies } from "next/headers";
import Link from "next/link";
import { Button } from "../ui/button";
import ProfileButton from "./ProfileButton";

const AuthComponent = async () =&amp;gt; {
  const supabase = createClient(cookies());

  const {
    data: { user },
  } = await supabase.auth.getUser();

  return user ? (
    &amp;lt;ProfileButton user={user} /&amp;gt;
  ) : (
    &amp;lt;div className="hidden items-center gap-2 sm:flex"&amp;gt;
      &amp;lt;Link href={"/login"} className="w-full sm:w-auto"&amp;gt;
        &amp;lt;Button variant="secondary" size="sm" className="w-full"&amp;gt;
          Log In
        &amp;lt;/Button&amp;gt;
      &amp;lt;/Link&amp;gt;
      &amp;lt;Link href="/signup" className="w-full sm:w-auto"&amp;gt;
        &amp;lt;Button variant="default" size="sm" className="w-full"&amp;gt;
          Sign Up
        &amp;lt;/Button&amp;gt;
      &amp;lt;/Link&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default AuthComponent;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;For the profile button I want to be able to open and close a profile modal which means I need state which means it needs to be a client component. Create a new component at &lt;code&gt;components/navbar/ProfileButton.tsx&lt;/code&gt; and add the following code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// components/navbar/ProfileButton.tsx

"use client";

import React, { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import Link from "next/link";
import { User } from "@supabase/supabase-js";
import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar";
import { createClient } from "@/utils/supabase/client";

const ProfileButton: React.FC&amp;lt;{ user: User }&amp;gt; = ({ user }) =&amp;gt; {
  const [menuOpen, setMenuOpen] = useState(false);

  const router = useRouter();
  const supabase = createClient();

  const signOut = async () =&amp;gt; {
    await supabase.auth.signOut();
    router.refresh();
  };

  const ref = React.useRef&amp;lt;HTMLDivElement&amp;gt;(null);

  // close the modal if we click outside of it
  useEffect(() =&amp;gt; {
    const handleOutsideClick = (event: MouseEvent) =&amp;gt; {
      if (!ref.current?.contains(event.target as Node)) {
        setMenuOpen(false);
      }
    };

    document.addEventListener("mousedown", handleOutsideClick);

    return () =&amp;gt; {
      document.removeEventListener("mousedown", handleOutsideClick);
    };
  }, [ref]);

  if (!user) return null;
  return (
    &amp;lt;div ref={ref} className="hidden sm:block"&amp;gt;
      &amp;lt;Avatar
        className="hover:cursor-pointer"
        onClick={() =&amp;gt; setMenuOpen(!menuOpen)}
      &amp;gt;
        &amp;lt;AvatarImage src="https://wallpapers.com/images/high/funny-profile-picture-7k1legjukiz1lju7.webp" /&amp;gt;
        &amp;lt;AvatarFallback&amp;gt;CN&amp;lt;/AvatarFallback&amp;gt;
      &amp;lt;/Avatar&amp;gt;
      {menuOpen &amp;amp;&amp;amp; (
        &amp;lt;div className="bg-secondary absolute right-5 top-16 z-50 flex w-72 flex-col rounded-lg bg-opacity-80 p-4"&amp;gt;
          &amp;lt;div className="flex items-center"&amp;gt;
            &amp;lt;div className="pr-4"&amp;gt;
              &amp;lt;Avatar onClick={() =&amp;gt; setMenuOpen(!menuOpen)}&amp;gt;
                &amp;lt;AvatarImage src="https://wallpapers.com/images/high/funny-profile-picture-7k1legjukiz1lju7.webp" /&amp;gt;
                &amp;lt;AvatarFallback&amp;gt;CN&amp;lt;/AvatarFallback&amp;gt;
              &amp;lt;/Avatar&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div className="flex flex-col"&amp;gt;
              &amp;lt;p className="text-xl"&amp;gt;Isaac Dyor&amp;lt;/p&amp;gt;
              &amp;lt;p className="text-md text-muted-foreground"&amp;gt;{user?.email}&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/div&amp;gt;
          &amp;lt;hr className="my-2 border-t-2 border-slate-600" /&amp;gt;
          &amp;lt;p className="text-muted-foreground py-2 text-lg"&amp;gt;
            &amp;lt;Link
              onClick={() =&amp;gt; setMenuOpen(false)}
              className="hover:text-muted-foreground/70"
              href="/profile"
            &amp;gt;
              Profile
            &amp;lt;/Link&amp;gt;
          &amp;lt;/p&amp;gt;
          &amp;lt;p className="text-muted-foreground py-2 text-lg"&amp;gt;
            &amp;lt;Link
              onClick={() =&amp;gt; setMenuOpen(false)}
              className="hover:text-muted-foreground/70"
              href="/settings"
            &amp;gt;
              Settings
            &amp;lt;/Link&amp;gt;
          &amp;lt;/p&amp;gt;

          &amp;lt;div className="text-muted-foreground py-2 text-lg"&amp;gt;
            &amp;lt;button
              className="hover:text-muted-foreground/70"
              onClick={() =&amp;gt;
                signOut().then(() =&amp;gt; {
                  setMenuOpen(false);
                  router.refresh();
                })
              }
            &amp;gt;
              Sign out
            &amp;lt;/button&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
      )}
    &amp;lt;/div&amp;gt;
  );
};

export default ProfileButton;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now that we have our Auth component set up we need to pass it as a prop to our navbar. Replace the login and signup links with children and we will pass the auth component in from the layout page. Your navbar should now contain the following code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// components/navbar/Navbar.tsx
"use client";
import Link from "next/link";
import React, { useState } from "react";
import { XMarkIcon, Bars3Icon } from "@heroicons/react/24/solid";

const routes: { title: string; href: string }[] = [
  { title: "Features", href: "#features" },
  { title: "Resources", href: "#resources" },
  { title: "Pricing", href: "#pricing" },
];

const Navbar: React.FC&amp;lt;{ children: React.ReactNode }&amp;gt; = ({ children }) =&amp;gt; {
  const [menuOpen, setMenuOpen] = useState(false);

  const toggleMenu = () =&amp;gt; {
    setMenuOpen(!menuOpen);
  };

  return (
    &amp;lt;div className="flex h-16 items-center justify-between px-6 lg:px-14"&amp;gt;
      &amp;lt;div className="flex items-center"&amp;gt;
        &amp;lt;Link href={"/"} className="shrink-0"&amp;gt;
          &amp;lt;h1 className="text-accent-foreground text-2xl font-bold"&amp;gt;devlink&amp;lt;/h1&amp;gt;
        &amp;lt;/Link&amp;gt;
        &amp;lt;div className="bg-background hidden w-full justify-end gap-1 px-4 py-2 sm:flex"&amp;gt;
          {routes.map((route, index) =&amp;gt; (
            &amp;lt;Link
              key={index}
              href={route.href}
              className={`hover:text-accent-foreground text-muted-foreground inline-flex h-10 w-full items-center px-4 py-2 text-sm transition-colors sm:w-auto`}
            &amp;gt;
              {route.title}
            &amp;lt;/Link&amp;gt;
          ))}
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;

      {children}

      {menuOpen &amp;amp;&amp;amp; &amp;lt;MobileMenu toggleMenu=

      &amp;lt;button onClick={toggleMenu} className="sm:hidden"&amp;gt;
        {menuOpen ? (
          &amp;lt;XMarkIcon className="h-7 w-7" /&amp;gt;
        ) : (
          &amp;lt;Bars3Icon className="h-7 w-7" /&amp;gt;
        )}
      &amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

const MobileMenu: React.FC&amp;lt;{
  toggleMenu: () =&amp;gt; void;
  children: React.ReactNode;
}&amp;gt; = ({ toggleMenu, children }) =&amp;gt; {
  return (
    &amp;lt;div className="absolute right-0 top-16 flex h-[calc(100vh-64px)] w-full flex-col"&amp;gt;
      &amp;lt;div className="bg-background  flex w-full grow flex-col gap-1 px-4 pb-2 sm:hidden"&amp;gt;
        {routes.map((route, index) =&amp;gt; (
          &amp;lt;Link
            key={index}
            href={route.href}
            onClick={toggleMenu}
            className={`hover:text-accent-foreground text-muted-foreground inline-flex h-10 w-full items-center text-sm transition-colors sm:w-auto`}
          &amp;gt;
            {route.title}
          &amp;lt;/Link&amp;gt;
        ))}
        {children}
      &amp;lt;/div&amp;gt;
      &amp;lt;div className="bg-background/60 h-screen w-full sm:hidden" /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default Navbar;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now in the &lt;code&gt;app/(main)/layout.tsx&lt;/code&gt; replace the &lt;code&gt;&amp;lt;Navbar /&amp;gt;&lt;/code&gt; with:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/(main)/layout.tsx

...
&amp;lt;Navbar&amp;gt;
  &amp;lt;AuthComponent /&amp;gt;
&amp;lt;/Navbar&amp;gt;
...

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And now when you are logged in you should see a profile button and when you click on it it should show your email. &lt;/p&gt;

&lt;p&gt;We are now finished with auth! Now it is time to start making mutations with trpc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mutations with trpc
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Prisma&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I want to make it so once users login they can create a profile to add more information. To do this we need to create a new profile table in our database. To do this we need to update our prisma schema. In the &lt;code&gt;schema.prisma&lt;/code&gt; file we need to set the provider to &lt;code&gt;postgresql&lt;/code&gt; since we are using supabase. We also need to add whatever columns we want for our table. Mine looks like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// prisma/prisma.schema

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

enum RoleType {
  FRONTEND
  BACKEND
  FULLSTACK
  DESIGN
}

model Profile {
  id        String   @id
  createdAt DateTime @default(now()) @map("created_at")
  email     String   @unique
  firstName String   
  lastName  String   
  role      RoleType
  skills    String[]
  bio       String
  github    String?
  linkedin  String?
  website   String?
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To push these changes to your database run &lt;code&gt;npx prisma db push&lt;/code&gt;. Now when you go to the table editor in the supabase dashboard you should see a new profile table. Prisma also has its own user interface called prisma studio. To open that up run &lt;code&gt;npx prisma studio&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Form&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now we need a way for the user to create a new profile once they login. Before we build the form we need to create a zod schema to ensure the user's inputs are valid. To do this we need to add a new folder in the lib directory called validators. Inside validators add a new file called &lt;code&gt;newProfile.ts&lt;/code&gt; and add the following code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// lib/validators/newProfile.ts

import { z } from "zod";
import { RoleType } from "@prisma/client";

export const newProfileSchema = z.object({
  firstName: z.string().min(1),
  lastName: z.string().min(1),
  role: z.nativeEnum(RoleType),
  skills: z
    .object({ name: z.string().min(1) })
    .array()
    .min(1),
  bio: z.string().min(100).max(500),
  github: z.string(),
  linkedin: z.string(),
  website: z.union([z.literal(""), z.string().trim().url()]),
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;For the form we are going to be using a couple new shadcn components so lets import those. Run the following commands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;npx shadcn-ui@latest add card&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;npx shadcn-ui@latest add select&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;npx shadcn-ui@latest add textarea&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now create a page at &lt;code&gt;app/(main)/profile/new/page.tsx&lt;/code&gt; and add the following code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/(main)/profile/new/page.tsx

"use client";

import { Button } from "@/components/ui/button";
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form";

import { Input } from "@/components/ui/input";
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";

import { Textarea } from "@/components/ui/textarea";

import { useFieldArray, useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { newProfileSchema } from "@/lib/validators/newProfile";

import { TrashIcon } from "@heroicons/react/24/outline";
import { useRouter } from "next/navigation";

export type NewProfileInput = z.infer&amp;lt;typeof newProfileSchema&amp;gt;;

export default function NewProfileForm() {
  const form = useForm&amp;lt;NewProfileInput&amp;gt;({
    resolver: zodResolver(newProfileSchema),
    defaultValues: {
      firstName: "",
      lastName: "",
      role: undefined,
      skills: [{ name: "" }],
      bio: "",
      github: "",
      linkedin: "",
      website: "",
    },
  });

  const { fields, append, remove } = useFieldArray({
    control: form.control,
    name: "skills",
  });

  const watchSkills = form.watch("skills");

  const router = useRouter();

  const onSubmit = async (data: NewProfileInput) =&amp;gt; {
    console.log(data);
  };

  return (
    &amp;lt;div className="flex w-screen justify-center p-8"&amp;gt;
      &amp;lt;Card className="border-border w-full max-w-2xl border"&amp;gt;
        &amp;lt;CardHeader&amp;gt;
          &amp;lt;CardTitle&amp;gt;Create Profile&amp;lt;/CardTitle&amp;gt;
          &amp;lt;CardDescription&amp;gt;Yabba dabba doo&amp;lt;/CardDescription&amp;gt;
        &amp;lt;/CardHeader&amp;gt;
        &amp;lt;CardContent&amp;gt;
          &amp;lt;Form {...form}&amp;gt;
            &amp;lt;form
              onSubmit={form.handleSubmit(onSubmit)}
              className="text-muted-foreground flex w-full flex-1 flex-col justify-center gap-6"
            &amp;gt;
              &amp;lt;div className="flex flex-row gap-4"&amp;gt;
                &amp;lt;FormField
                  control={form.control}
                  name="firstName"
                  render={({ field }) =&amp;gt; (
                    &amp;lt;FormItem className="w-full"&amp;gt;
                      &amp;lt;FormLabel className=""&amp;gt;First name&amp;lt;/FormLabel&amp;gt;
                      &amp;lt;FormControl&amp;gt;
                        &amp;lt;Input placeholder="Your first name" {...field} /&amp;gt;
                      &amp;lt;/FormControl&amp;gt;
                      &amp;lt;FormMessage /&amp;gt;
                    &amp;lt;/FormItem&amp;gt;
                  )}
                /&amp;gt;
                &amp;lt;FormField
                  control={form.control}
                  name="lastName"
                  render={({ field }) =&amp;gt; (
                    &amp;lt;FormItem className="w-full"&amp;gt;
                      &amp;lt;FormLabel className=""&amp;gt;Last name&amp;lt;/FormLabel&amp;gt;
                      &amp;lt;FormControl&amp;gt;
                        &amp;lt;Input placeholder="Your last name" {...field} /&amp;gt;
                      &amp;lt;/FormControl&amp;gt;
                      &amp;lt;FormMessage /&amp;gt;
                    &amp;lt;/FormItem&amp;gt;
                  )}
                /&amp;gt;
              &amp;lt;/div&amp;gt;
              &amp;lt;FormField
                control={form.control}
                name="role"
                render={({ field }) =&amp;gt; (
                  &amp;lt;FormItem&amp;gt;
                    &amp;lt;FormLabel&amp;gt;Role&amp;lt;/FormLabel&amp;gt;
                    &amp;lt;Select
                      onValueChange={field.onChange}
                      defaultValue={field.value}
                    &amp;gt;
                      &amp;lt;FormControl&amp;gt;
                        &amp;lt;SelectTrigger&amp;gt;
                          &amp;lt;SelectValue placeholder="Select your role" /&amp;gt;
                        &amp;lt;/SelectTrigger&amp;gt;
                      &amp;lt;/FormControl&amp;gt;
                      &amp;lt;SelectContent&amp;gt;
                        &amp;lt;SelectItem value="FULLSTACK"&amp;gt;Full Stack&amp;lt;/SelectItem&amp;gt;
                        &amp;lt;SelectItem value="FRONTEND"&amp;gt;Frontend&amp;lt;/SelectItem&amp;gt;
                        &amp;lt;SelectItem value="BACKEND"&amp;gt;Backend&amp;lt;/SelectItem&amp;gt;
                        &amp;lt;SelectItem value="DESIGN"&amp;gt;Design&amp;lt;/SelectItem&amp;gt;
                      &amp;lt;/SelectContent&amp;gt;
                    &amp;lt;/Select&amp;gt;
                    &amp;lt;FormMessage /&amp;gt;
                  &amp;lt;/FormItem&amp;gt;
                )}
              /&amp;gt;

              &amp;lt;FormField
                control={form.control}
                name="skills"
                render={({ field }) =&amp;gt; (
                  &amp;lt;FormItem&amp;gt;
                    &amp;lt;FormLabel&amp;gt;Skills&amp;lt;/FormLabel&amp;gt;
                    &amp;lt;div className="grid grid-cols-2 gap-2"&amp;gt;
                      {fields.map((field, index) =&amp;gt; (
                        &amp;lt;div key={field.name}&amp;gt;
                          &amp;lt;div className="group relative flex items-center"&amp;gt;
                            &amp;lt;FormControl&amp;gt;
                              &amp;lt;Input
                                placeholder="Your skill"
                                className="group-hover:pr-8"
                                {...form.register(
                                  `skills.${index}.name` as const,
                                )}
                              /&amp;gt;
                            &amp;lt;/FormControl&amp;gt;
                            {fields.length &amp;gt; 1 &amp;amp;&amp;amp; (
                              &amp;lt;TrashIcon
                                className="hover:text-muted-foreground/30 text-muted-foreground/40 invisible absolute right-1 h-6 w-6 hover:cursor-pointer group-hover:visible"
                                onClick={() =&amp;gt; remove(index)}
                              /&amp;gt;
                            )}
                          &amp;lt;/div&amp;gt;

                          {form.formState.errors.skills?.[index]?.name &amp;amp;&amp;amp; (
                            &amp;lt;p className="text-destructive text-sm font-medium"&amp;gt;
                              This can't be empty
                            &amp;lt;/p&amp;gt;
                          )}
                        &amp;lt;/div&amp;gt;
                      ))}
                    &amp;lt;/div&amp;gt;
                    &amp;lt;Button
                      type="button"
                      disabled={watchSkills.some((field) =&amp;gt; !field.name)}
                      onClick={() =&amp;gt; append({ name: "" })}
                      className="max-w-min"
                    &amp;gt;
                      Add Skill
                    &amp;lt;/Button&amp;gt;
                  &amp;lt;/FormItem&amp;gt;
                )}
              /&amp;gt;

              &amp;lt;FormField
                control={form.control}
                name="bio"
                render={({ field }) =&amp;gt; (
                  &amp;lt;FormItem&amp;gt;
                    &amp;lt;FormLabel&amp;gt;Bio&amp;lt;/FormLabel&amp;gt;
                    &amp;lt;FormControl&amp;gt;
                      &amp;lt;Textarea
                        placeholder="Tell us a little bit about yourself"
                        className="resize-none"
                        {...field}
                      /&amp;gt;
                    &amp;lt;/FormControl&amp;gt;
                    &amp;lt;FormMessage /&amp;gt;
                  &amp;lt;/FormItem&amp;gt;
                )}
              /&amp;gt;
              &amp;lt;div className="flex flex-row gap-4"&amp;gt;
                &amp;lt;FormField
                  control={form.control}
                  name="github"
                  render={({ field }) =&amp;gt; (
                    &amp;lt;FormItem className="w-full"&amp;gt;
                      &amp;lt;FormLabel&amp;gt;Github Username&amp;lt;/FormLabel&amp;gt;
                      &amp;lt;FormControl&amp;gt;
                        &amp;lt;Input placeholder="Your github username" {...field} /&amp;gt;
                      &amp;lt;/FormControl&amp;gt;
                      &amp;lt;FormMessage /&amp;gt;
                    &amp;lt;/FormItem&amp;gt;
                  )}
                /&amp;gt;
                &amp;lt;FormField
                  control={form.control}
                  name="linkedin"
                  render={({ field }) =&amp;gt; (
                    &amp;lt;FormItem className="w-full"&amp;gt;
                      &amp;lt;FormLabel&amp;gt;Linkedin Username&amp;lt;/FormLabel&amp;gt;
                      &amp;lt;FormControl&amp;gt;
                        &amp;lt;Input
                          placeholder="Your linkedin username"
                          {...field}
                        /&amp;gt;
                      &amp;lt;/FormControl&amp;gt;
                      &amp;lt;FormMessage /&amp;gt;
                    &amp;lt;/FormItem&amp;gt;
                  )}
                /&amp;gt;
              &amp;lt;/div&amp;gt;
              &amp;lt;FormField
                control={form.control}
                name="website"
                render={({ field }) =&amp;gt; (
                  &amp;lt;FormItem&amp;gt;
                    &amp;lt;FormLabel&amp;gt;Website&amp;lt;/FormLabel&amp;gt;
                    &amp;lt;FormControl&amp;gt;
                      &amp;lt;Input placeholder="Your website url" {...field} /&amp;gt;
                    &amp;lt;/FormControl&amp;gt;
                    &amp;lt;FormMessage /&amp;gt;
                  &amp;lt;/FormItem&amp;gt;
                )}
              /&amp;gt;

              &amp;lt;Button variant="default" className="my-4 w-full" type="submit"&amp;gt;
                Submit
              &amp;lt;/Button&amp;gt;
            &amp;lt;/form&amp;gt;
          &amp;lt;/Form&amp;gt;
        &amp;lt;/CardContent&amp;gt;
      &amp;lt;/Card&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Context and Private Procedure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now we have the form but we need to push this data to the database when the user submits it. To do that we need to create a new trpc procedure. Before we actually write the procedure there are a few things we need to add. &lt;/p&gt;

&lt;p&gt;The first step is to add the userId to the trpc context. This will make things a lot quicker because it means we don't have to pass the userId from the client. For example if we want to get the current user's posts, instead of passing the current user id to the server, we can just call the function with no props and it already knows who the user is. &lt;/p&gt;

&lt;p&gt;Navigate to the &lt;code&gt;server/api/trpc.ts&lt;/code&gt; file and import the supabase client:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// server/api/trpc.ts

...
import { createClient } from "@/utils/supabase/server";
import { cookies } from "next/headers";
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now inside the &lt;code&gt;createTRPCContext&lt;/code&gt; function we need to pass the userId to the context like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// server/api/trpc.ts

...
export const createTRPCContext = async (opts: { headers: Headers }) =&amp;gt; {
  const supabase = createClient(cookies());

  const {
    data: { user },
  } = await supabase.auth.getUser();

  return {
    user,
    db,
    ...opts,
  };
};
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The next step is to create a new type of procedure called private procedure, which is a procedure that can only be called by authenticated users. This is helpful because it means that we know there is a userId for our queries. If we were to use a public procedure we would have to assert that we know the user is authenticated and handle that on the client which is a lot more complicated. To do this add the following code at the bottom of the file:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// server/api/trpc.ts

...
const enforceUserIsAuthed = t.middleware(async ({ ctx, next }) =&amp;gt; {
  if (!ctx.user) {
    throw new TRPCError({
      code: "UNAUTHORIZED",
    });
  }

  return next({
    ctx: {
      user: ctx.user,
    },
  });
});

export const privateProcedure = t.procedure.use(enforceUserIsAuthed);
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Procedure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now we can get started writing the procedure. Go to &lt;code&gt;server/api/routers&lt;/code&gt; and you should see a &lt;code&gt;posts.ts&lt;/code&gt; file. Delete that file and create a new one called &lt;code&gt;profiles.ts&lt;/code&gt;. Inside that file add the following code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// server/api/routers/profiles.ts

import { z } from "zod";
import { createTRPCRouter, privateProcedure } from "@/server/api/trpc";

import { RoleType } from "@prisma/client";

export const profileRouter = createTRPCRouter({
  create: privateProcedure
    .input(
      z.object({
        firstName: z.string(),
        lastName: z.string(),
        role: z.nativeEnum(RoleType),
        skills: z.array(z.string()),
        bio: z.string(),
        github: z.string(),
        linkedin: z.string(),
        website: z.union([z.literal(""), z.string().trim().url()]),
      }),
    )
    .mutation(async ({ ctx, input }) =&amp;gt; {
      const user = await ctx.db.profile.create({
        data: {
          id: ctx.user.id,
          email: ctx.user.email!,
          firstName: input.firstName,
          lastName: input.lastName,
          role: input.role,
          skills: input.skills,
          bio: input.bio,
          github: input.github,
          linkedin: input.linkedin,
          website: input.website,
        },
      });

      return user;
    }),
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now we need to expose this router to the client. Go to &lt;code&gt;server/api/root.ts&lt;/code&gt; and add the following code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// server/api/root.ts

import { profileRouter } from "@/server/api/routers/profiles";
import { createTRPCRouter } from "@/server/api/trpc";

export const appRouter = createTRPCRouter({
  profiles: profileRouter,
});

export type AppRouter = typeof appRouter;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now we need to call the mutation on submit on form submission. Before we do that though, lets create a utility function to capitalize just the first letter of the string, so all of the names are uniform. In &lt;code&gt;lib/utils.ts&lt;/code&gt; add the following function:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// lib/utils.ts

...

export function capitalizeFirstLetter(inputString: string) {
  const lowercaseString = inputString.toLowerCase();

  const capitalizedString =
    lowercaseString.charAt(0).toUpperCase() + lowercaseString.slice(1);

  return capitalizedString;
}

...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now we can use our mutation. Add the following code to &lt;code&gt;app/(main)/profile/new/page.tsx&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/(main)/profile/new/page.tsx

const router = useRouter();

const { mutate } = api.profiles.create.useMutation({
  onSuccess: () =&amp;gt; {
    router.push("/profile");
  },
  onError: (e) =&amp;gt; {
    const errorMessage = e.data?.zodError?.fieldErrors.content;
    console.error("Error creating investment:", errorMessage);
  },
});

const onSubmit = async (data: NewProfileInput) =&amp;gt; {
  const skillsList = data.skills.map((skill) =&amp;gt; skill.name);
  mutate({
    firstName: capitalizeFirstLetter(data.firstName),
    lastName: capitalizeFirstLetter(data.lastName),
    role: data.role,
    skills: skillsList,
    bio: data.bio,
    github: data.github,
    linkedin: data.linkedin,
    website: data.website,
  });
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now when you submit the form you should be redirected to a profile page and when you check the table in supabase the new data should be there. Now that we have the data, let's make it so we can view it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Querying data
&lt;/h2&gt;

&lt;p&gt;Go back to &lt;code&gt;server/api/routers/profiles.ts&lt;/code&gt; and add the following query below your mutation:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// server/api/routers/profiles.ts

...
getCurrent: privateProcedure.query(async ({ ctx }) =&amp;gt; {
  const profile = await ctx.db.profile.findUnique({
    where: { id: ctx.user.id! },
  });
  return profile;
}),
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Create a new page in the profile folder and add the following code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/(main)/profile/page.tsx

import {
  Card,
  CardHeader,
  CardTitle,
  CardDescription,
  CardContent,
  CardFooter,
} from "@/components/ui/card";
import { capitalizeFirstLetter } from "@/lib/utils";
import { api } from "@/trpc/server";

export default async function ProfilePage() {
  const profile = await api.profiles.getCurrent.query();

  if (!profile) return null;

  return (
    &amp;lt;div className="flex w-full justify-center "&amp;gt;
      &amp;lt;Card className="mt-12 w-full max-w-xl"&amp;gt;
        &amp;lt;CardHeader&amp;gt;
          &amp;lt;CardTitle&amp;gt;
            {profile.firstName} {profile.firstName}
          &amp;lt;/CardTitle&amp;gt;
          &amp;lt;CardDescription&amp;gt;{profile.email}&amp;lt;/CardDescription&amp;gt;
        &amp;lt;/CardHeader&amp;gt;
        &amp;lt;CardContent&amp;gt;
          &amp;lt;p&amp;gt;Role: {capitalizeFirstLetter(profile.role)}&amp;lt;/p&amp;gt;
        &amp;lt;/CardContent&amp;gt;
        &amp;lt;CardContent className="flex items-center"&amp;gt;
          &amp;lt;p className="pr-1"&amp;gt;Skills:&amp;lt;/p&amp;gt;
          {profile.skills.map((skill, index) =&amp;gt; (
            &amp;lt;p key={index} className="border-border w-min rounded-full border px-2 py-0.5"&amp;gt;
              {skill}
            &amp;lt;/p&amp;gt;
          ))}
        &amp;lt;/CardContent&amp;gt;
        &amp;lt;CardContent&amp;gt;
          &amp;lt;p&amp;gt;Bio: {profile.bio}&amp;lt;/p&amp;gt;
        &amp;lt;/CardContent&amp;gt;
        &amp;lt;div className="flex"&amp;gt;
          &amp;lt;CardContent&amp;gt;
            &amp;lt;p&amp;gt;Github: {profile.github}&amp;lt;/p&amp;gt;
          &amp;lt;/CardContent&amp;gt;
          &amp;lt;CardContent&amp;gt;
            &amp;lt;p&amp;gt;Linkedin: {profile.linkedin}&amp;lt;/p&amp;gt;
          &amp;lt;/CardContent&amp;gt;
          &amp;lt;CardContent&amp;gt;
            &amp;lt;p&amp;gt;Website: {profile.website}&amp;lt;/p&amp;gt;
          &amp;lt;/CardContent&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/Card&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This isn't styled very well, its just supposed to show how you can fetch the data on the server side with trpc. Notice that we are importing the api from the server folder not the react folder. This means we cant use hooks like usequery and usemutation because we are in a server component. &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This app doesn't really do anything, it is just supposed to get you started where everything is configured properly to design a cool web application. You can add to your prisma schema and start doing more complicated queries and mutations with the same principles used for just a simple profile. &lt;/p&gt;

&lt;p&gt;All of the code for this project will be at this &lt;a href="https://github.com/isaacdyor/t3-blog" rel="noopener noreferrer"&gt;repo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

&lt;p&gt;P.S. If you are a software developer in college shoot me an email at &lt;a href="mailto:isaac@dyor.com"&gt;isaac@dyor.com&lt;/a&gt;. I am going to start building a website for college students to find other developers in their area to build projects together. If you want to help me build it or use it or provide feedback or whatever I would love to chat. &lt;/p&gt;

</description>
    </item>
    <item>
      <title>Create responsive navbar with React and Tailwind using the same markdown</title>
      <dc:creator>Isaac Dyor</dc:creator>
      <pubDate>Fri, 15 Dec 2023 08:51:56 +0000</pubDate>
      <link>https://dev.to/isaacdyor/create-responsive-navbar-with-react-and-tailwind-using-the-same-markdown-5b1i</link>
      <guid>https://dev.to/isaacdyor/create-responsive-navbar-with-react-and-tailwind-using-the-same-markdown-5b1i</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;When building a responsive navbar you need to render two different views for desktop and mobile. One approach is to hardcode the two different views and conditionally render them based on the screen size. Although this method seems simple in the long term, it makes refactoring more complicated because if you want to change one of your links you have to do it in two places. For this reason the better option is to use the same markdown and apply styles conditionally so it works with both desktop and mobile. This way if you want to change something you can do it in one place and the changes will be applied to both views. &lt;/p&gt;

&lt;h2&gt;
  
  
  Basic mobile navbar
&lt;/h2&gt;

&lt;p&gt;Tailwind has a mobile first design approach. For this reason I will start by building the mobile navbar, and later I will apply conditional styles to make it work with desktop.&lt;/p&gt;

&lt;p&gt;First we are just going to create the flexbox for the navbar with the logo. This eventually will use state for the hamburger menu so it needs to be a client component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"use client";
import Image from "next/image";
import Link from "next/link";
import Logo from "/public/logo.png";

const Navbar: React.FC = () =&amp;gt; {
  return (
    &amp;lt;div className="flex items-center justify-between p-3 border-b border-b-border"&amp;gt;
      &amp;lt;Link href={"/"} className="shrink-0 px-4"&amp;gt;
        &amp;lt;Image src={Logo} alt="Spark Royalty Logo" width={250} height={250} /&amp;gt;
      &amp;lt;/Link&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default Navbar;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frdj2tpul4zrs5q6vrkw7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frdj2tpul4zrs5q6vrkw7.png" alt="Current navbar" width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since we are on the mobile view we want to add a hamburger menu to toggle the links visibility. I am using &lt;a href="https://heroicons.com/"&gt;heroicons&lt;/a&gt;. We use some basic react state to know whether or not the hambuger is open, and we conditionally render either the hamburger or an X.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"use client";
import Image from "next/image";
import Link from "next/link";
import Logo from "/public/logo.png";
import { useState } from "react";
import { XMarkIcon, Bars3Icon } from "@heroicons/react/24/solid";

const Navbar: React.FC = () =&amp;gt; {
  const [menuOpen, setMenuOpen] = useState(false);

  const toggleMenu = () =&amp;gt; {
    setMenuOpen(!menuOpen);
  };

  return (
    &amp;lt;div className="flex items-center justify-between p-3 border-b border-b-border"&amp;gt;
      &amp;lt;Link href={"/"} className="shrink-0 px-4"&amp;gt;
        &amp;lt;Image src={Logo} alt="Spark Royalty Logo" width={250} height={250} /&amp;gt;
      &amp;lt;/Link&amp;gt;
      &amp;lt;button onClick={toggleMenu} className="md:hidden"&amp;gt;
        {menuOpen ? (
          &amp;lt;XMarkIcon className="h-7 w-7" /&amp;gt;
        ) : (
          &amp;lt;Bars3Icon className="h-7 w-7" /&amp;gt;
        )}
      &amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default Navbar;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F55yrqwqclufy5v7pdgko.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F55yrqwqclufy5v7pdgko.png" alt="Current navbar" width="800" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Right now the hamburger menu isnt actually rendering anything when it is opened, so lets add some links and a sign up and login button. I am using shadcn for my buttons and my colors. Check out my &lt;a href="https://dev.to/isaacdyor/setting-up-nextjs-project-with-prisma-200j"&gt;last post&lt;/a&gt; if you want to get that set up.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"use client";
import Image from "next/image";
import Link from "next/link";
import Logo from "/public/logo.png";
import { useState } from "react";
import { XMarkIcon, Bars3Icon } from "@heroicons/react/24/solid";
import { Button } from "./ui/button";

const Navbar: React.FC = () =&amp;gt; {
  const [menuOpen, setMenuOpen] = useState(false);

  const toggleMenu = () =&amp;gt; {
    setMenuOpen(!menuOpen);
  };

  const routes: { title: string; href: string }[] = [
    { title: "Features", href: "#features" },
    { title: "Resources", href: "#resources" },
    { title: "Pricing", href: "#pricing" },
  ];

  return (
    &amp;lt;div className="flex items-center justify-between p-3 border-b border-b-border"&amp;gt;
      &amp;lt;Link href={"/"} className="shrink-0 px-4"&amp;gt;
        &amp;lt;Image src={Logo} alt="Spark Royalty Logo" width={250} height={250} /&amp;gt;
      &amp;lt;/Link&amp;gt;
      &amp;lt;div
        className={`flex items-center grow justify-start flex-col absolute top-[71.5px]  right-0 w-full  ${
          menuOpen ? "visible" : "invisible"
        }`}
      &amp;gt;
        &amp;lt;div className="flex flex-col justify-end w-full py-2 px-4 gap-1 bg-background"&amp;gt;
          {routes.map((route, index) =&amp;gt; (
            &amp;lt;Link
              key={index}
              href={route.href}
              className={`inline-flex h-10 w-full items-center rounded-md px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground`}
            &amp;gt;
              {route.title}
            &amp;lt;/Link&amp;gt;
          ))}

          &amp;lt;Link href={"/login"} className="w-full "&amp;gt;
            &amp;lt;Button variant="secondary" className="w-full"&amp;gt;
              Log In
            &amp;lt;/Button&amp;gt;
          &amp;lt;/Link&amp;gt;
          &amp;lt;Link href="/signup" className="w-full"&amp;gt;
            &amp;lt;Button variant="default" className="w-full"&amp;gt;
              Sign Up
            &amp;lt;/Button&amp;gt;
          &amp;lt;/Link&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div className="h-full w-full bg-background/70" /&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;button onClick={toggleMenu}&amp;gt;
        {menuOpen ? (
          &amp;lt;XMarkIcon className="h-7 w-7" /&amp;gt;
        ) : (
          &amp;lt;Bars3Icon className="h-7 w-7" /&amp;gt;
        )}
      &amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default Navbar;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I create a flex column to store the flex column with all of the links as well as a div to take up the rest of the space. This div is opaque to cover up whatever is on the screen currently. Makes it look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffber83eqpzybom3yet15.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffber83eqpzybom3yet15.png" alt="Current navbar" width="800" height="1161"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Making it work for desktop
&lt;/h2&gt;

&lt;p&gt;Now that our navbar works for mobile we want to make it work for desktop. We are going to use the same markup, just give it different styles. We will do this by applying tailwind styles with the md: prefix so they will only be applied on medium screen sizes and larger. &lt;/p&gt;

&lt;p&gt;There are a couple of things we need to change:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make the content always visible so the hamburger state doesn't affect it&lt;/li&gt;
&lt;li&gt;Make it a flex row instead of flex col&lt;/li&gt;
&lt;li&gt;Make the position static instead of fixed&lt;/li&gt;
&lt;li&gt;Hide the hamburger menu&lt;/li&gt;
&lt;li&gt;Make the width of the buttons and links auto instead of full&lt;/li&gt;
&lt;li&gt;Add some styles specific to desktop, like padding between the login and signup buttons.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"use client";
import Image from "next/image";
import Link from "next/link";
import Logo from "/public/logo.png";
import { useState } from "react";
import { XMarkIcon, Bars3Icon } from "@heroicons/react/24/solid";
import { Button } from "./ui/button";

const Navbar: React.FC = () =&amp;gt; {
  const [menuOpen, setMenuOpen] = useState(false);

  const toggleMenu = () =&amp;gt; {
    setMenuOpen(!menuOpen);
  };

  const routes: { title: string; href: string }[] = [
    { title: "Features", href: "#features" },
    { title: "Resources", href: "#resources" },
    { title: "Pricing", href: "#pricing" },
  ];

  return (
    &amp;lt;div className="flex items-center justify-between p-3 border-b border-b-border"&amp;gt;
      &amp;lt;Link href={"/"} className="shrink-0 px-4"&amp;gt;
        &amp;lt;Image src={Logo} alt="Spark Royalty Logo" width={250} height={250} /&amp;gt;
      &amp;lt;/Link&amp;gt;
      &amp;lt;div
        className={`flex items-center h-[calc(100vh-71.5px)] grow justify-start flex-col absolute top-[71.5px]  right-0 w-full  md:visible md:flex-row md:justify-end md:static md:h-auto ${
          menuOpen ? "visible" : "invisible"
        }  `}
      &amp;gt;
        &amp;lt;div className="flex flex-col justify-end w-full py-2 px-4 gap-1 bg-background md:flex-row"&amp;gt;
          {routes.map((route, index) =&amp;gt; (
            &amp;lt;Link
              key={index}
              href={route.href}
              className={`inline-flex h-10 w-full items-center rounded-md px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground md:w-auto`}
            &amp;gt;
              {route.title}
            &amp;lt;/Link&amp;gt;
          ))}

          &amp;lt;Link href={"/login"} className=" w-full md:px-1 md:w-auto"&amp;gt;
            &amp;lt;Button variant="secondary" className="w-full"&amp;gt;
              Log In
            &amp;lt;/Button&amp;gt;
          &amp;lt;/Link&amp;gt;
          &amp;lt;Link href="/signup" className="w-full md:w-auto"&amp;gt;
            &amp;lt;Button variant="default" className="w-full"&amp;gt;
              Sign Up
            &amp;lt;/Button&amp;gt;
          &amp;lt;/Link&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div className="h-full w-full bg-background/70 md:hidden" /&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;button onClick={toggleMenu} className="md:hidden"&amp;gt;
        {menuOpen ? (
          &amp;lt;XMarkIcon className="h-7 w-7" /&amp;gt;
        ) : (
          &amp;lt;Bars3Icon className="h-7 w-7" /&amp;gt;
        )}
      &amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default Navbar;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fecem7z7p6fohljeano3t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fecem7z7p6fohljeano3t.png" alt="Current navbar" width="800" height="603"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you have a responsive navbar using the same markdown for each view, so if you want to add a new link or change the text, you only have to do it one place!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>react</category>
      <category>tailwindcss</category>
    </item>
    <item>
      <title>Setting up Next.js project with Prisma, Supabase, and Shadcn.</title>
      <dc:creator>Isaac Dyor</dc:creator>
      <pubDate>Wed, 13 Dec 2023 23:27:55 +0000</pubDate>
      <link>https://dev.to/isaacdyor/setting-up-nextjs-project-with-prisma-200j</link>
      <guid>https://dev.to/isaacdyor/setting-up-nextjs-project-with-prisma-200j</guid>
      <description>&lt;h2&gt;
  
  
  Setting up Next.js
&lt;/h2&gt;

&lt;p&gt;First run the following command to initialize the next js project with supabase, typescript, and tailwind: &lt;code&gt;npx create-next-app@latest&lt;/code&gt;. Select all of the default options: &lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Prisma
&lt;/h2&gt;

&lt;p&gt;Run the following command to install prisma:&lt;br&gt;
&lt;code&gt;npm install prisma --save-dev&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Once prisma is installed run the following command to initialize the schema file and the .env file:&lt;br&gt;
&lt;code&gt;npx prisma init&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;There should now be a .env file. You should add your database_url to connect prisma to your database. Should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// .env
DATABASE_URL=url
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside your schema.prisma you should add your model, i am just using some random model for now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model Post {
  id        String     @default(cuid()) @id
  title     String
  content   String?
  published Boolean @default(false)
  author    User?   @relation(fields: [authorId], references: [id])
  authorId  String?
}

model User {
  id            String       @default(cuid()) @id
  name          String?
  email         String?   @unique
  createdAt     DateTime  @default(now()) @map(name: "created_at")
  updatedAt     DateTime  @updatedAt @map(name: "updated_at")
  posts         Post[]
  @@map(name: "users")
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can run the following command to sync your database with your schema:&lt;br&gt;
&lt;code&gt;npx prisma db push&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In order to access prisma on the client side you need to install prisma client. You can do so by running the following command:&lt;br&gt;
&lt;code&gt;npm install @prisma/client&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Your client must be in sync with your schema as well and you can do this by running the following command:&lt;br&gt;
&lt;code&gt;npx prisma generate&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;When you run &lt;code&gt;npx prisma db push&lt;/code&gt; the generate command is automatically called. &lt;/p&gt;

&lt;p&gt;In order to access the prisma client you need to create an instance of it, so create a new folder in the src directory called lib, and add a new file called prisma.ts to it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// prisma.ts

import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();

export default prisma;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can import the same instance of Prisma in any file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Shadcn
&lt;/h2&gt;

&lt;p&gt;First run the following command to start setting up shadcn:&lt;br&gt;
&lt;code&gt;npx shadcn-ui@latest init&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I selected the following options:&lt;br&gt;
typescript: yes&lt;br&gt;
style: default&lt;br&gt;
base color: slate&lt;br&gt;
global css: src/app/globals.css&lt;br&gt;
css variables: yes&lt;br&gt;
tailwind config: tailwind.config.ts&lt;br&gt;
components: @/components (default)&lt;br&gt;
utils: @/lib/utils (default)&lt;br&gt;
react server components: yes&lt;br&gt;
write to components.json: yes&lt;/p&gt;

&lt;p&gt;Next run the following command to set up next themes:&lt;br&gt;
&lt;code&gt;npm install next-themes&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then add a file called theme-provider.tsx to your components library and add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// theme-provider.tsx

"use client"

import * as React from "react"
import { ThemeProvider as NextThemesProvider } from "next-themes"
import { type ThemeProviderProps } from "next-themes/dist/types"

export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
  return &amp;lt;NextThemesProvider {...props}&amp;gt;{children}&amp;lt;/NextThemesProvider&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you have your provider setup, you want to add it to the layout.tsx so it is implemented on the entire app. Wrap the {children} with the Theme provider like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// layout.tsx
  return (
    &amp;lt;html lang="en" suppressHydrationWarning&amp;gt;
      &amp;lt;body className={inter.className}&amp;gt;
        &amp;lt;ThemeProvider
          attribute="class"
          defaultTheme="system"
          enableSystem
          disableTransitionOnChange
        &amp;gt;
          {children}
        &amp;lt;/ThemeProvider&amp;gt;
      &amp;lt;/body&amp;gt;
    &amp;lt;/html&amp;gt;
  );
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now go to the shadcn &lt;a href="https://ui.shadcn.com/themes" rel="noopener noreferrer"&gt;themes page&lt;/a&gt;. And select the theme you want to use and press copy code. Then add that copied code to your globals.css so it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// globals.css
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  :root {
    --background: 0 0% 100%;
    --foreground: 224 71.4% 4.1%;
    --card: 0 0% 100%;
    --card-foreground: 224 71.4% 4.1%;
    --popover: 0 0% 100%;
    --popover-foreground: 224 71.4% 4.1%;
    --primary: 262.1 83.3% 57.8%;
    --primary-foreground: 210 20% 98%;
    --secondary: 220 14.3% 95.9%;
    --secondary-foreground: 220.9 39.3% 11%;
    --muted: 220 14.3% 95.9%;
    --muted-foreground: 220 8.9% 46.1%;
    --accent: 220 14.3% 95.9%;
    --accent-foreground: 220.9 39.3% 11%;
    --destructive: 0 84.2% 60.2%;
    --destructive-foreground: 210 20% 98%;
    --border: 220 13% 91%;
    --input: 220 13% 91%;
    --ring: 262.1 83.3% 57.8%;
    --radius: 0.5rem;
  }

  .dark {
    --background: 224 71.4% 4.1%;
    --foreground: 210 20% 98%;
    --card: 224 71.4% 4.1%;
    --card-foreground: 210 20% 98%;
    --popover: 224 71.4% 4.1%;
    --popover-foreground: 210 20% 98%;
    --primary: 263.4 70% 50.4%;
    --primary-foreground: 210 20% 98%;
    --secondary: 215 27.9% 16.9%;
    --secondary-foreground: 210 20% 98%;
    --muted: 215 27.9% 16.9%;
    --muted-foreground: 217.9 10.6% 64.9%;
    --accent: 215 27.9% 16.9%;
    --accent-foreground: 210 20% 98%;
    --destructive: 0 62.8% 30.6%;
    --destructive-foreground: 210 20% 98%;
    --border: 215 27.9% 16.9%;
    --input: 215 27.9% 16.9%;
    --ring: 263.4 70% 50.4%;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you should be able to use shadcn components and themes in your project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Supabase
&lt;/h2&gt;

&lt;p&gt;The first step is to create a new supabase project. Next, install the next.js auth helpers library:&lt;br&gt;
&lt;code&gt;npm install @supabase/auth-helpers-nextjs @supabase/supabase-js&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now you must add your supabase url and your anon key to your .env file. Your .env file should now look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// .env
DATABASE_URL=url
NEXT_PUBLIC_SUPABASE_URL=your-supabase-url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are going to use the supabase cli to generate types based on our schema. Install the cli with the following command:&lt;br&gt;
&lt;code&gt;npm install supabase --save-dev&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In order to login to supabase run &lt;code&gt;npx supabase login&lt;/code&gt; and it will automatically log you in.&lt;/p&gt;

&lt;p&gt;Now we can generate our types by running the following command:&lt;br&gt;
&lt;code&gt;npx supabase gen types typescript --project-id YOUR_PROJECT_ID &amp;gt; src/lib/database.types.ts&lt;/code&gt; which should add a new file in your lib folder with the types based on your schema. &lt;/p&gt;

&lt;p&gt;Now create a middleware.ts file in the root of your project and add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { createMiddlewareClient } from "@supabase/auth-helpers-nextjs";
import { NextResponse } from "next/server";

import type { NextRequest } from "next/server";
import type { Database } from "@/lib/database.types";

export async function middleware(req: NextRequest) {
  const res = NextResponse.next();
  const supabase = createMiddlewareClient&amp;lt;Database&amp;gt;({ req, res });
  await supabase.auth.getSession();
  return res;
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now create a new folder in the app directory called auth, then another folder within auth called callback, and finally a file called route.ts. Add the following code in that file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/auth/callback/route.ts
import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs";
import { cookies } from "next/headers";
import { NextResponse } from "next/server";

import type { NextRequest } from "next/server";
import type { Database } from "@/lib/database.types";

export async function GET(request: NextRequest) {
  const requestUrl = new URL(request.url);
  const code = requestUrl.searchParams.get("code");

  if (code) {
    const cookieStore = cookies();
    const supabase = createRouteHandlerClient&amp;lt;Database&amp;gt;({
      cookies: () =&amp;gt; cookieStore,
    });
    await supabase.auth.exchangeCodeForSession(code);
  }

  // URL to redirect to after sign in process completes
  return NextResponse.redirect(requestUrl.origin);
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that setup we can create a login page. In the app directory create a new folder called login with a page.tsx.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/login/page.tsx
"use client";

import { createClientComponentClient } from "@supabase/auth-helpers-nextjs";
import { useRouter } from "next/navigation";
import { useState } from "react";

import type { Database } from "@/lib/database.types";

export default function Login() {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const router = useRouter();
  const supabase = createClientComponentClient&amp;lt;Database&amp;gt;();

  const handleSignUp = async () =&amp;gt; {
    await supabase.auth.signUp({
      email,
      password,
      options: {
        emailRedirectTo: `${location.origin}/auth/callback`,
      },
    });
    router.refresh();
  };

  const handleSignIn = async () =&amp;gt; {
    await supabase.auth.signInWithPassword({
      email,
      password,
    });
    router.refresh();
  };

  const handleSignOut = async () =&amp;gt; {
    await supabase.auth.signOut();
    router.refresh();
  };

  return (
    &amp;lt;&amp;gt;
      &amp;lt;input
        name="email"
        onChange={(e) =&amp;gt; setEmail(e.target.value)}
        value={email}
      /&amp;gt;
      &amp;lt;input
        type="password"
        name="password"
        onChange={(e) =&amp;gt; setPassword(e.target.value)}
        value={password}
      /&amp;gt;
      &amp;lt;button onClick={handleSignUp}&amp;gt;Sign up&amp;lt;/button&amp;gt;
      &amp;lt;button onClick={handleSignIn}&amp;gt;Sign in&amp;lt;/button&amp;gt;
      &amp;lt;button onClick={handleSignOut}&amp;gt;Sign out&amp;lt;/button&amp;gt;
    &amp;lt;/&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now create a new folder within the auth directory called sign-up, and a route.ts within that file. Add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/auth/sign-up/route.ts
import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs";
import { cookies } from "next/headers";
import { NextResponse } from "next/server";

import type { Database } from "@/lib/database.types";

export async function POST(request: Request) {
  const requestUrl = new URL(request.url);
  const formData = await request.formData();
  const email = String(formData.get("email"));
  const password = String(formData.get("password"));
  const cookieStore = cookies();
  const supabase = createRouteHandlerClient&amp;lt;Database&amp;gt;({
    cookies: () =&amp;gt; cookieStore,
  });

  await supabase.auth.signUp({
    email,
    password,
    options: {
      emailRedirectTo: `${requestUrl.origin}/auth/callback`,
    },
  });

  return NextResponse.redirect(requestUrl.origin, {
    status: 301,
  });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create another folder in the same location called login.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/auth/login/route.ts
import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs";
import { cookies } from "next/headers";
import { NextResponse } from "next/server";

import type { Database } from "@/lib/database.types";

export async function POST(request: Request) {
  const requestUrl = new URL(request.url);
  const formData = await request.formData();
  const email = String(formData.get("email"));
  const password = String(formData.get("password"));
  const cookieStore = cookies();
  const supabase = createRouteHandlerClient&amp;lt;Database&amp;gt;({
    cookies: () =&amp;gt; cookieStore,
  });

  await supabase.auth.signInWithPassword({
    email,
    password,
  });

  return NextResponse.redirect(requestUrl.origin, {
    status: 301,
  });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally add a logout route in the same place.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/auth/logout/route.ts
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
import { cookies } from 'next/headers'
import { NextResponse } from 'next/server'

import type { Database } from '@/lib/database.types'

export async function POST(request: Request) {
  const requestUrl = new URL(request.url)
  const cookieStore = cookies()
  const supabase = createRouteHandlerClient&amp;lt;Database&amp;gt;({ cookies: () =&amp;gt; cookieStore })

  await supabase.auth.signOut()

  return NextResponse.redirect(`${requestUrl.origin}/login`, {
    status: 301,
  })
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now there should be basic login logout sign up functionality when you navigate to localhost &lt;a href="http://localhost:3000/login" rel="noopener noreferrer"&gt;http://localhost:3000/login&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now we have some basic boilerplate for a next js app with prisma shadcn and supabase auth setup. &lt;/p&gt;

</description>
    </item>
    <item>
      <title>Hello, World!</title>
      <dc:creator>Isaac Dyor</dc:creator>
      <pubDate>Wed, 11 Oct 2023 05:21:11 +0000</pubDate>
      <link>https://dev.to/isaacdyor/hello-world-4kg2</link>
      <guid>https://dev.to/isaacdyor/hello-world-4kg2</guid>
      <description>&lt;p&gt;Hello DEV, this is my first post&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Initializing and deploying T3 app with AWS database.</title>
      <dc:creator>Isaac Dyor</dc:creator>
      <pubDate>Wed, 26 Apr 2023 21:08:35 +0000</pubDate>
      <link>https://dev.to/isaacdyor/initializing-and-deploying-t3-app-with-aws-database-3fdj</link>
      <guid>https://dev.to/isaacdyor/initializing-and-deploying-t3-app-with-aws-database-3fdj</guid>
      <description>&lt;h2&gt;
  
  
  Initializing the Project
&lt;/h2&gt;

&lt;p&gt;This is just a document to remind me how I created this project. &lt;/p&gt;

&lt;p&gt;We will use Create T3 App to create the scaffolding for our application. &lt;/p&gt;

&lt;p&gt;Run the command &lt;code&gt;npm create t3-app@latest&lt;/code&gt; and select typescript. Give your project a name and then press a to select all 4 dependencies. &lt;/p&gt;

&lt;p&gt;Create a new github repo and then run the following commands to push the code to the repo:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git add .&lt;br&gt;
git commit -m "initial commit"&lt;br&gt;
git remote add origin https://github.com/&amp;lt;username&amp;gt;/&amp;lt;repo-name&amp;gt;.git&lt;br&gt;
git branch -M main&lt;br&gt;
git push -u origin main&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We now want to deploy our app to vercel, but we first have to set up our database. &lt;/p&gt;

&lt;h2&gt;
  
  
  Database Urls
&lt;/h2&gt;

&lt;p&gt;For development we will use a local Postgres database and for production we will use an Amazon RDS database. Make sure to make your AWS database publicly accessible. You may also have to edit inbound rules and allow all traffic from all ips so that vercel can access it.  &lt;/p&gt;

&lt;p&gt;In the .env file of your code include the local database url with the following information:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqv8q8jg6mns5y34zth2v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqv8q8jg6mns5y34zth2v.png" alt="Prisma Connection URL"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you have your url set up, go to the schema.prisma file and change the provider to postgresql:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

datasource db {
    provider = "postgresql"
    url      = env("DATABASE_URL")
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Also uncomment the &lt;code&gt;@db.Text&lt;/code&gt; in the Account model. &lt;/p&gt;

&lt;p&gt;Now go to vercel and import your github repo. Then add the connection url of your AWS database as an environment variable. &lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Nexauth
&lt;/h2&gt;

&lt;p&gt;We are not yet ready to deploy because next auth isn't set up. &lt;br&gt;
Create t3 app automatically configures sign-in with discord, but I would rather use sign in with google so I will get that set up. &lt;/p&gt;

&lt;p&gt;Get your credentials by following these steps:&lt;br&gt;
&lt;a href="https://dev.to/dera_johnson/how-to-get-google-client-id-and-client-secret-4c87"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make sure to add localhost and your vercel url to authorized urls. &lt;/p&gt;

&lt;p&gt;Once you have your client ID and Secret, add them to your .env file like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

GOOGLE_CLIENT_ID="*******"
GOOGLE_CLIENT_SECRET="******"


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next, navigate to the server directory and open the auth.ts file. Replace the discord provider with the Google Provider. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

  // auth.ts
  providers: [
    GoogleProvider({
      clientId: env.GOOGLE_CLIENT_ID,
      clientSecret: env.GOOGLE_CLIENT_SECRET,
    }),
  ],


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You might be getting a type error so go to the env.mjs file in the src directory and replace discord with google in two locations. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

  GOOGLE_CLIENT_ID: z.string(),
  GOOGLE_CLIENT_SECRET: z.string(),


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;and &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

const processEnv = {
  DATABASE_URL: process.env.DATABASE_URL,
  NODE_ENV: process.env.NODE_ENV,
  NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET,
  NEXTAUTH_URL: process.env.NEXTAUTH_URL,
  GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID,
  GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET,
};


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You can now start your server with npm run dev and you should be able to sign in with google. Make sure to use one of the users that you defined as a test user earlier. &lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying to Vercel
&lt;/h2&gt;

&lt;p&gt;We want to deploy our project as early as possible so that if there are any problems they are much easier to spot than later down the line. &lt;/p&gt;

&lt;p&gt;Now that we have all of our Next-auth environment variables set up we need to push our code to the repo and add the environment variables in vercel. &lt;/p&gt;

&lt;p&gt;Go to the vercel site and try logging in, it should work.&lt;/p&gt;

&lt;p&gt;Now you can start building up your website with all of this stuff configured. &lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>postgres</category>
      <category>prisma</category>
      <category>tailwindcss</category>
    </item>
    <item>
      <title>How to create Google Maps clone with Next.js, Prisma, and Postgres.</title>
      <dc:creator>Isaac Dyor</dc:creator>
      <pubDate>Sun, 10 Jul 2022 22:25:38 +0000</pubDate>
      <link>https://dev.to/isaacdyor/how-to-create-google-maps-clone-with-nextjs-prisma-and-postgres-305h</link>
      <guid>https://dev.to/isaacdyor/how-to-create-google-maps-clone-with-nextjs-prisma-and-postgres-305h</guid>
      <description>&lt;p&gt;This article is a documentation of my process of implementing a map on my website that displays markers at certain points stored in a Postgres database through Next.js and Prisma. &lt;/p&gt;

&lt;p&gt;To start this project I created a Next.js project with the command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npx create-next-app@latest&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Next I created a Postgres database hosted on Heroku following these steps: &lt;a href="https://dev.to/prisma/how-to-setup-a-free-postgresql-database-on-heroku-1dc1"&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Then I needed to connect my Next project to my Postgres database through Prisma. The first step was to install Prisma with the following command: &lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm install prisma --save-dev&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then I initialized the Prisma project by running&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npx prisma init&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This adds a prisma.schema file which is where you define your schema. It also creates a .env file where you can define your environment variables. In my .env file I defined my database link. You can find this by following step 4 of the link to setup a postgres database. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;DATABASE_URL="postgresql:blahblahblah"&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;Then I created my schema in the prisma.schema file. Make sure to include an address field in the schema because that is how our program will know where to place the markers. I also included other information I wanted to provide the user in the info window.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//prisma.schema
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model Location {
  id        String     @default(cuid()) @id
  title     String
  address   String?
  website   String?
  phone     String?
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Push the schema to your database&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npx prisma db push&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Install prisma client&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm install @prisma/client&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Updata your prisma client&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npx prisma generate&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Create a new directory called lib and a prisma.js file in it.&lt;/p&gt;

&lt;p&gt;In the prisma.js file you have to create an instance of the Prisma client.&lt;/p&gt;

&lt;p&gt;Then you can import your instance of the Prisma client into any file you need.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//prisma.js
const { PrismaClient } = require('@prisma/client')
const prisma = new PrismaClient()

export default prisma
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;npx prisma studio&lt;/code&gt; to open the Prisma studio, I added a few entries to play around with. &lt;/p&gt;

&lt;p&gt;Now that I have my project connected with my database I can start building the webpage. &lt;/p&gt;

&lt;p&gt;I created a new file in the pages directory called maps.js. First I imported all of the packages that we need to use. We need useState and useRef from React to manage the state. &lt;br&gt;
We also need to import a few things from the @react-google-maps/api package which is a package designed to connect the google maps api to our react application.&lt;br&gt;
We also need a few things from the react-places-autocomplete package which makes it easy for us to implement a google places api searchbar into our application. &lt;br&gt;
I also imported my prisma instance from my prisma.js file, and the script package from next/script.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, {useState, useRef} from 'react';
import {GoogleMap, useLoadScript, Marker, InfoWindow,} from "@react-google-maps/api";
import PlacesAutocomplete, {geocodeByAddress, getLatLng} from 'react-places-autocomplete'

import Script from "next/script";
import prisma from "../lib/prisma";

const libraries = ['places']
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After we have all of this imported then we can query our database for our data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const getServerSideProps = async () =&amp;gt; {
  const locations = await prisma.location.findMany();
  return { props: { locations } };
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can create a new functional component with our quereyed data as a prop.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const App = ({ locations }) =&amp;gt; {

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we are going to create some state. I created a lot of state and this can probably done in a more efficient way but it works so I will go with it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const App = ({ locations }) =&amp;gt; {

  const [center, setCenter] = useState({
    lat: 0,
    lng: 0,
  });

  const [address, setAddress] = useState("");

  const [coords, setCoords] = useState([]);

  const [mapRef, setMapRef] = useState(null);

  const [selected, setSelected] = useState(null);

  const mapRef2 = useRef();

  const options = {
    disableDefaultUI: true,
    zoomControl: true,
  }

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mapRef2 is pretty stupid but who cares. &lt;/p&gt;

&lt;p&gt;Next we need to connect to the google maps api. We do this through the useLoadScript function we imported earlier. The first step is to get a google maps api key. The instructions to do so can be found here. &lt;a href="https://developers.google.com/maps/documentation/embed/get-api-key#:~:text=Go%20to%20the%20Google%20Maps%20Platform%20%3E%20Credentials%20page.&amp;amp;text=On%20the%20Credentials%20page%2C%20click,Click%20Close." rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The second step is to create a .env.local file in the root directory. You might be able to use the .env file that Prisma created but this is the way that I did it. In the .env.local file add the following line and insert your API Key. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;NEXT_PUBLIC_MAPS_API_KEY=your-api-key&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You can then use this api key in your component with the following function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  const { isLoaded } = useLoadScript({
    googleMapsApiKey: process.env.NEXT_PUBLIC_MAPS_API_KEY,
    libraries,
  })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The libraries line at the end importants the places library. &lt;/p&gt;

&lt;p&gt;Now we need to define a few functions that will be called later on in our code. &lt;/p&gt;

&lt;p&gt;The first function takes the address that the user selects from the places autocomplete dropdown and it converts the address to latitude and longitude. It also sets the center to the new latitude and longitude.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  const handleSelect = async (value) =&amp;gt; {
    const results = await geocodeByAddress(value);
    const latLng = await getLatLng(results[0]);
    setAddress(value);
    setCenter(latLng);
  };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next function is the convertAddress function which is called onMapLoad and converts all of the addresses stored in the database to latitude and longitude points so that we can use those coordinates to display markers later on.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  const convertAddress = async (value) =&amp;gt; {
    const results = await geocodeByAddress(value.address);
    const latLng = await getLatLng(results[0]);
    const locationData = {
      title: value.title,
      address: value.address,
      website: value.website,
      phone: value.phone,
      lat: latLng.lat,
      lng: latLng.lng
    }
    setCoords(coords =&amp;gt; [...coords, locationData])
  };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next function is called when someone clicks on a marker. What this function does is set the center of the map to whatever the current center is. It gets the current center through calling getCenter() on the mapRef.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  const onCenterChanged = () =&amp;gt; {
    if (mapRef) {
      const newCenter = mapRef.getCenter();
      console.log(newCenter);
      setCenter({
        lat: mapRef.getCenter().lat(),
        lng: mapRef.getCenter().lng()
      })
    }
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next function is called when the map loads, and it initializes the map as well as converts all of our addresses into latitude and longitude as mentioned earlier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  const onCenterChanged = () =&amp;gt; {
    if (mapRef) {
      const newCenter = mapRef.getCenter();
      console.log(newCenter);
      setCenter({
        lat: mapRef.getCenter().lat(),
        lng: mapRef.getCenter().lng()
      })
    }
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final function just pans the map to a certain lat and long.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  const panTo = React.useCallback(({lat, lng}) =&amp;gt; {
    mapRef2.current.panTo({lat, lng});
  }, [])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Overall our component looks like this right now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const App = ({ locations }) =&amp;gt; {

  const [center, setCenter] = useState({
    lat: 0,
    lng: 0,
  });

  const [address, setAddress] = useState("");

  const [coords, setCoords] = useState([]);

  const [mapRef, setMapRef] = useState(null);

  const [selected, setSelected] = useState(null);

  const mapRef2 = useRef();

  const options = {
    disableDefaultUI: true,
    zoomControl: true,
  }

  const { isLoaded } = useLoadScript({
    googleMapsApiKey: process.env.NEXT_PUBLIC_MAPS_API_KEY,
    libraries,
  })

  const handleSelect = async (value) =&amp;gt; {
    const results = await geocodeByAddress(value);
    const latLng = await getLatLng(results[0]);
    setAddress(value);
    setCenter(latLng);
  };

  const convertAddress = async (value) =&amp;gt; {
    const results = await geocodeByAddress(value.address);
    const latLng = await getLatLng(results[0]);
    const locationData = {
      title: value.title,
      address: value.address,
      website: value.website,
      phone: value.phone,
      lat: latLng.lat,
      lng: latLng.lng
    }
    setCoords(coords =&amp;gt; [...coords, locationData])
  };

  const onCenterChanged = () =&amp;gt; {
    if (mapRef) {
      const newCenter = mapRef.getCenter();
      console.log(newCenter);
      setCenter({
        lat: mapRef.getCenter().lat(),
        lng: mapRef.getCenter().lng()
      })
    }
  }



  const onMapLoad = (map) =&amp;gt; {
    mapRef2.current = map
    setMapRef(map);
    {locations.map(location =&amp;gt; {
      convertAddress(location)
    })}
  }

  const panTo = React.useCallback(({lat, lng}) =&amp;gt; {
    mapRef2.current.panTo({lat, lng});
  }, [])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first thing I did was create a button that got the coordinates of the user and panned the map to those coordinates.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;button className='locate' onClick={() =&amp;gt; {
          setAddress('')
          navigator.geolocation.getCurrentPosition((position) =&amp;gt; {
            panTo({
              lat: position.coords.latitude,
              lng: position.coords.longitude,
            })
            setCenter({
              lat: position.coords.latitude,
              lng: position.coords.longitude,
            })
          }, () =&amp;gt; null);
        }}&amp;gt;Locate&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I created the map itself. Inside the map I mapped through the different coordinates that had been converted from our database, and I displayed a marker at each place. I also included an info window that displays the information of each place.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;GoogleMap
          zoom={10}
          center={{lat: center.lat, lng: center.lng}}
          mapContainerClassName='map-container'
          options={options}
          onLoad={onMapLoad}
          // onBoundsChanged={onCenterChanged}
        &amp;gt;
          {coords.map(coord =&amp;gt; {
            return(
              &amp;lt;Marker
                key={coord.lat}
                position={{ lat: parseFloat(coord.lat), lng: parseFloat(coord.lng) }}
                onClick={() =&amp;gt; {
                  onCenterChanged()
                  setSelected(coord);
                }}
              /&amp;gt;
            )
          })}
          {selected ? (
            &amp;lt;InfoWindow
              position={{ lat: selected.lat, lng: selected.lng }}
              onCloseClick={() =&amp;gt; {
                setSelected(null);
              }}
            &amp;gt;
              &amp;lt;div&amp;gt;
                &amp;lt;h2&amp;gt;
                  {selected.title}
                &amp;lt;/h2&amp;gt;
                &amp;lt;p&amp;gt;{selected.address}&amp;lt;/p&amp;gt;
              &amp;lt;/div&amp;gt;
            &amp;lt;/InfoWindow&amp;gt;
          ) : null
          }



        &amp;lt;/GoogleMap&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally I added the places autocomplete searchbox. I also loaded the google maps places api through the script tag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        &amp;lt;PlacesAutocomplete
          value={address}
          onChange={setAddress}
          onSelect={handleSelect}
        &amp;gt;
          {({ getInputProps, suggestions, getSuggestionItemProps }) =&amp;gt; (
            &amp;lt;div&amp;gt;
              &amp;lt;input {...getInputProps({ placeholder: "Type address" })} /&amp;gt;

              &amp;lt;div&amp;gt;
                {suggestions.map(suggestion =&amp;gt; {
                  const style = {
                    backgroundColor: suggestion.active ? "#41b6e6" : "#fff"
                  };

                  return (
                    &amp;lt;div {...getSuggestionItemProps(suggestion, { style })}&amp;gt;
                      {suggestion.description}
                    &amp;lt;/div&amp;gt;
                  );
                })}
              &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
          )}
        &amp;lt;/PlacesAutocomplete&amp;gt;
        &amp;lt;Script
          src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBMePTwqFO2xPCaxUYqq0Vq4JQc631jo0o&amp;amp;libraries=places"
          strategy="beforeInteractive"
        &amp;gt;&amp;lt;/Script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is pretty much it. Keep in mind that this code is far from perfect. Also this code has literally zero styling so it is very ugly. It works though which is pretty cool. All in all this is the final code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//maps.js

import React, {useState, useRef} from 'react';
import {GoogleMap, useLoadScript, Marker, InfoWindow,} from "@react-google-maps/api";
import PlacesAutocomplete, {geocodeByAddress, getLatLng} from 'react-places-autocomplete'

import Script from "next/script";
import prisma from "../lib/prisma";

const libraries = ['places']

export const getServerSideProps = async () =&amp;gt; {
  const locations = await prisma.location.findMany();
  return { props: { locations } };
}

const App = ({ locations }) =&amp;gt; {

  const [center, setCenter] = useState({
    lat: 0,
    lng: 0,
  });

  const [address, setAddress] = useState("");

  const [coords, setCoords] = useState([]);

  const [mapRef, setMapRef] = useState(null);

  const [selected, setSelected] = useState(null);

  const mapRef2 = useRef();

  const options = {
    disableDefaultUI: true,
    zoomControl: true,
  }

  const { isLoaded } = useLoadScript({
    googleMapsApiKey: process.env.NEXT_PUBLIC_MAPS_API_KEY,
    libraries,
  })

  const handleSelect = async (value) =&amp;gt; {
    const results = await geocodeByAddress(value);
    const latLng = await getLatLng(results[0]);
    setAddress(value);
    setCenter(latLng);
  };

  const convertAddress = async (value) =&amp;gt; {
    const results = await geocodeByAddress(value.address);
    const latLng = await getLatLng(results[0]);
    const locationData = {
      title: value.title,
      address: value.address,
      website: value.website,
      phone: value.phone,
      lat: latLng.lat,
      lng: latLng.lng
    }
    setCoords(coords =&amp;gt; [...coords, locationData])
  };

  const onCenterChanged = () =&amp;gt; {
    if (mapRef) {
      const newCenter = mapRef.getCenter();
      console.log(newCenter);
      setCenter({
        lat: mapRef.getCenter().lat(),
        lng: mapRef.getCenter().lng()
      })
    }
  }



  const onMapLoad = (map) =&amp;gt; {
    mapRef2.current = map
    setMapRef(map);
    {locations.map(location =&amp;gt; {
      convertAddress(location)
    })}
  }

  const panTo = React.useCallback(({lat, lng}) =&amp;gt; {
    mapRef2.current.panTo({lat, lng});
  }, [])

  if (!isLoaded) {
    return (
      &amp;lt;div&amp;gt;
        &amp;lt;p&amp;gt;Loading...&amp;lt;/p&amp;gt;
      &amp;lt;/div&amp;gt;
    )
  }

  if (isLoaded) {
    return(
      &amp;lt;div&amp;gt;
        &amp;lt;button className='locate' onClick={() =&amp;gt; {
          setAddress('')
          navigator.geolocation.getCurrentPosition((position) =&amp;gt; {
            panTo({
              lat: position.coords.latitude,
              lng: position.coords.longitude,
            })
            setCenter({
              lat: position.coords.latitude,
              lng: position.coords.longitude,
            })
          }, () =&amp;gt; null);
        }}&amp;gt;Locate&amp;lt;/button&amp;gt;

        &amp;lt;GoogleMap
          zoom={10}
          center={{lat: center.lat, lng: center.lng}}
          mapContainerClassName='map-container'
          options={options}
          onLoad={onMapLoad}
          // onBoundsChanged={onCenterChanged}
        &amp;gt;
          {coords.map(coord =&amp;gt; {
            return(
              &amp;lt;Marker
                key={coord.lat}
                position={{ lat: parseFloat(coord.lat), lng: parseFloat(coord.lng) }}
                onClick={() =&amp;gt; {
                  onCenterChanged()
                  setSelected(coord);
                }}
              /&amp;gt;
            )
          })}
          {selected ? (
            &amp;lt;InfoWindow
              position={{ lat: selected.lat, lng: selected.lng }}
              onCloseClick={() =&amp;gt; {
                setSelected(null);
              }}
            &amp;gt;
              &amp;lt;div&amp;gt;
                &amp;lt;h2&amp;gt;
                  {selected.title}
                &amp;lt;/h2&amp;gt;
                &amp;lt;p&amp;gt;{selected.address}&amp;lt;/p&amp;gt;
              &amp;lt;/div&amp;gt;
            &amp;lt;/InfoWindow&amp;gt;
          ) : null
          }

        &amp;lt;/GoogleMap&amp;gt;

        &amp;lt;PlacesAutocomplete
          value={address}
          onChange={setAddress}
          onSelect={handleSelect}
        &amp;gt;
          {({ getInputProps, suggestions, getSuggestionItemProps }) =&amp;gt; (
            &amp;lt;div&amp;gt;
              &amp;lt;input {...getInputProps({ placeholder: "Type address" })} /&amp;gt;

              &amp;lt;div&amp;gt;
                {suggestions.map(suggestion =&amp;gt; {
                  const style = {
                    backgroundColor: suggestion.active ? "#41b6e6" : "#fff"
                  };

                  return (
                    &amp;lt;div {...getSuggestionItemProps(suggestion, { style })}&amp;gt;
                      {suggestion.description}
                    &amp;lt;/div&amp;gt;
                  );
                })}
              &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
          )}
        &amp;lt;/PlacesAutocomplete&amp;gt;



        &amp;lt;Script
          src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBMePTwqFO2xPCaxUYqq0Vq4JQc631jo0o&amp;amp;libraries=places"
          strategy="beforeInteractive"
        &amp;gt;&amp;lt;/Script&amp;gt;
      &amp;lt;/div&amp;gt;
    )
  }
}


export default App;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also there is an error on line 168 because I didn't include a key. It is not breaking but you can just add a key to solve it. &lt;/p&gt;

&lt;p&gt;Booh yah. &lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>react</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Building a next app with Prisma.</title>
      <dc:creator>Isaac Dyor</dc:creator>
      <pubDate>Wed, 06 Jul 2022 20:48:48 +0000</pubDate>
      <link>https://dev.to/isaacdyor/building-a-next-app-with-prisma-4m6o</link>
      <guid>https://dev.to/isaacdyor/building-a-next-app-with-prisma-4m6o</guid>
      <description>&lt;p&gt;Create your next application&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npx create-next-app@latest&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Install Prisma&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm install prisma --save-dev&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Initialize Prisma project&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npx prisma init&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Add your postgres link to your .env file. Make sure that everything is on the same line. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;DATABASE_URL="postgresql:blahblahblah"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Create your database schema in the prisma.schema file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// schema.prisma
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model Post {
  id        String     @default(cuid()) @id
  title     String
  content   String?
  published Boolean @default(false)
  author    User?   @relation(fields: [authorId], references: [id])
  authorId  String?
}

model User {
  id            String       @default(cuid()) @id
  name          String?
  email         String?   @unique
  createdAt     DateTime  @default(now()) @map(name: "created_at")
  updatedAt     DateTime  @updatedAt @map(name: "updated_at")
  posts         Post[]
  @@map(name: "users")
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Push the schema to your database &lt;/p&gt;

&lt;p&gt;&lt;code&gt;npx prisma db push&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Install prisma client&lt;/p&gt;

&lt;p&gt;`npm install @prisma/client'&lt;/p&gt;

&lt;p&gt;Updata your prisma client&lt;/p&gt;

&lt;p&gt;`npx prisma generate'&lt;/p&gt;

&lt;p&gt;Create a new directory called lib and a prisma.js file in it. &lt;/p&gt;

&lt;p&gt;In the prisma.js file you have to create an instance of the Prisma client. &lt;/p&gt;

&lt;p&gt;Then you can import your instance of the Prisma client into any file you need. Then you create a getServerSideProps function to query the data. Then you can display those props however you want.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import prisma from '../lib/prisma';
import React from "react"

export const getServerSideProps = async () =&amp;gt; {
  const feed = await prisma.post.findMany({
    where: { published: true },
    include: {
      author: {
        select: { name: true },
      },
    },
  });
  return { props: { feed } };
}


const Blog = ({feed}) =&amp;gt; {
  console.log(feed)
  return (
    &amp;lt;div&amp;gt;
      {feed[0].title}
    &amp;lt;/div&amp;gt;
  )
}

export default Blog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My next post might be on creating a REST API with Prisma. &lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
