<?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: Abdur Rakib Rony</title>
    <description>The latest articles on DEV Community by Abdur Rakib Rony (@abdur_rakibrony_349a3f89).</description>
    <link>https://dev.to/abdur_rakibrony_349a3f89</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%2F1611019%2F0a0c0003-6d22-482a-9794-6821def7e147.png</url>
      <title>DEV Community: Abdur Rakib Rony</title>
      <link>https://dev.to/abdur_rakibrony_349a3f89</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/abdur_rakibrony_349a3f89"/>
    <language>en</language>
    <item>
      <title>Building a Modern Survey Landing Page with Next.js and Tailwind CSS 🎨</title>
      <dc:creator>Abdur Rakib Rony</dc:creator>
      <pubDate>Tue, 10 Jun 2025 03:48:25 +0000</pubDate>
      <link>https://dev.to/abdur_rakibrony_349a3f89/building-a-modern-survey-landing-page-with-nextjs-and-tailwind-css-2fm2</link>
      <guid>https://dev.to/abdur_rakibrony_349a3f89/building-a-modern-survey-landing-page-with-nextjs-and-tailwind-css-2fm2</guid>
      <description>&lt;h2&gt;
  
  
  🛠️ Tech Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Next.js 13+ with App Router&lt;/li&gt;
&lt;li&gt;React 18 with hooks
&lt;/li&gt;
&lt;li&gt;Tailwind CSS&lt;/li&gt;
&lt;li&gt;react-confetti&lt;/li&gt;
&lt;li&gt;Embedded forms integration&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ✨ Key Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;Smooth Loading Animation&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The page uses a subtle fade-in effect that makes the content feel more polished:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isLoaded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsLoaded&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;useEffect&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="nf"&gt;setIsLoaded&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="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

&lt;span class="c1"&gt;// In JSX&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`transition-all duration-1000 ease-out &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;
  &lt;span class="nx"&gt;isLoaded&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;opacity-100 translate-y-0&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="s1"&gt;opacity-0 translate-y-5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. &lt;strong&gt;Celebratory Confetti Effect&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;showConfetti&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setShowConfetti&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;useEffect&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;confettiTimer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&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="nf"&gt;setShowConfetti&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="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setShowConfetti&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;4000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;1000&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;confettiTimer&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;h3&gt;
  
  
  3. &lt;strong&gt;Responsive Window Dimensions&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;windowDimensions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setWindowDimensions&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;useEffect&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;updateWindowDimensions&lt;/span&gt; &lt;span class="o"&gt;=&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="nf"&gt;setWindowDimensions&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHeight&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="nf"&gt;updateWindowDimensions&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resize&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updateWindowDimensions&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resize&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updateWindowDimensions&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;h2&gt;
  
  
  🎨 Design Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Color Palette
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;bg-&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;#fdf8f3&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;  &lt;span class="c"&gt;/* Warm cream background */&lt;/span&gt;
&lt;span class="nt"&gt;text-&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="err"&gt;#5&lt;/span&gt;&lt;span class="nt"&gt;e3c27&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="c"&gt;/* Rich brown for headings */&lt;/span&gt;
&lt;span class="nt"&gt;text-&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="err"&gt;#7&lt;/span&gt;&lt;span class="nt"&gt;a5b4a&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="c"&gt;/* Medium brown for subtext */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Typography
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-3xl md:text-4xl lg:text-5xl font-semibold text-[#5e3c27] mb-2 md:mb-3 leading-tight"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  Fashion That Feels Like You
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-lg md:text-xl text-[#7a5b4a] mb-6 md:mb-8"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  Where Style Meets Comfort
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  📱 Responsive Design
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"max-w-4xl mx-auto px-5 md:px-10 py-10 md:py-20 text-center"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Image&lt;/span&gt;
    &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"w-full h-auto rounded-xl shadow-lg shadow-[#5e3c27]/10"&lt;/span&gt;
    &lt;span class="c1"&gt;// ... other props&lt;/span&gt;
  &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🔗 Form Integration
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;iframe&lt;/span&gt; 
  &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"https://your-form-provider.com/embed/survey-id"&lt;/span&gt;
  &lt;span class="na"&gt;allowFullScreen&lt;/span&gt;
  &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"w-full h-[800px] md:h-[1000px] border-0 bg-white"&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Survey Form"&lt;/span&gt;
  &lt;span class="na"&gt;frameBorder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
  &lt;span class="na"&gt;marginWidth&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
  &lt;span class="na"&gt;marginHeight&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Integration Tips:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Set &lt;code&gt;allowFullScreen&lt;/code&gt; for better UX&lt;/li&gt;
&lt;li&gt;Use responsive heights (&lt;code&gt;h-[800px] md:h-[1000px]&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Remove borders for seamless integration&lt;/li&gt;
&lt;li&gt;Include &lt;code&gt;title&lt;/code&gt; attribute for accessibility&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🚀 Performance &amp;amp; SEO
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Image Optimization
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Image&lt;/span&gt; 
  &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;logoImage&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; 
  &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Company Logo"&lt;/span&gt; 
  &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; 
  &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mx-auto max-w-[200px] h-auto"&lt;/span&gt;
  &lt;span class="na"&gt;priority&lt;/span&gt; &lt;span class="c1"&gt;// For above-the-fold images&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Meta Tags
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Your Landing Page Title&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"description"&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Your page description..."&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🎯 Key Takeaways
&lt;/h2&gt;

&lt;p&gt;✅ Smooth animations enhance user experience&lt;br&gt;&lt;br&gt;
✅ Warm color palettes create emotional connection&lt;br&gt;&lt;br&gt;
✅ Responsive design works across all devices&lt;br&gt;&lt;br&gt;
✅ Next.js optimizations improve performance  &lt;/p&gt;

&lt;h2&gt;
  
  
  🔧 Component Structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SurveyLanding&lt;/span&gt; &lt;span class="o"&gt;=&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="c1"&gt;// State management&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;isLoaded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsLoaded&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&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;showConfetti&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setShowConfetti&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Effects for animations&lt;/span&gt;
  &lt;span class="nf"&gt;useEffect&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="nf"&gt;setIsLoaded&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="c1"&gt;// Confetti timer logic&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"min-h-screen bg-[#fdf8f3]"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Content with animations */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;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;



</description>
      <category>nextjs</category>
      <category>react</category>
      <category>tailwindcss</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Implementing Secure Single Sign-On Between Vue &amp; NodeJs Applications</title>
      <dc:creator>Abdur Rakib Rony</dc:creator>
      <pubDate>Thu, 20 Mar 2025 05:03:03 +0000</pubDate>
      <link>https://dev.to/abdur_rakibrony_349a3f89/implementing-secure-single-sign-on-between-laravel-applications-1553</link>
      <guid>https://dev.to/abdur_rakibrony_349a3f89/implementing-secure-single-sign-on-between-laravel-applications-1553</guid>
      <description>&lt;p&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;br&gt;
Single Sign-On (SSO) is a crucial feature in modern web applications that allows users to navigate between related systems without the friction of multiple logins. In this article, I'll walk through how to implement a secure SSO mechanism between two vue and nodejs applications using AES-256-CBC encryption.&lt;br&gt;
The implementation I'm sharing came from a real-world project where we needed to allow users to seamlessly switch between a Client Panel and a Partner/IB Panel while maintaining proper authentication and security.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Problem&lt;/strong&gt;&lt;br&gt;
Our application had two separate panels:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Client Panel:&lt;/strong&gt; Where regular users manage their accounts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Partner/IB Panel:&lt;/strong&gt; Where partners (Introducing Brokers) manage their business&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Some users had access to both panels, and we wanted to provide a smooth experience for them to switch between panels without having to log in multiple times.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution Overview&lt;/strong&gt;&lt;br&gt;
Our solution uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AES-256-CBC encryption to securely pass user identity between panels&lt;/li&gt;
&lt;li&gt;Shared encryption key stored in the database&lt;/li&gt;
&lt;li&gt;Token-based authentication to maintain separate sessions in each panel&lt;/li&gt;
&lt;li&gt;Vue.js and Vuetify for the frontend components&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The basic flow works like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User clicks "Switch to Partner Panel" in the Client Panel&lt;/li&gt;
&lt;li&gt;Client Panel encrypts the user's ID and redirects to Partner Panel with the encrypted token&lt;/li&gt;
&lt;li&gt;Partner Panel decrypts the token, verifies the user, and logs them in automatically&lt;/li&gt;
&lt;li&gt;The same process works in reverse for switching back to the Client Panel&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Database Setup&lt;/strong&gt;&lt;br&gt;
First, we need to set up the database to store our encryption key and panel URLs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add an encryption key to the &lt;code&gt;broker_settings&lt;/code&gt; table:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INSERT INTO broker_settings (name, value)
VALUES ('CUSTOM_ENCRYPTION_KEY', '66f7a1a9-675c-8010-b25a-48230661874a');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Ensure you have the panel URLs in your &lt;code&gt;general_informations&lt;/code&gt; table:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-- Make sure these columns exist
ALTER TABLE general_informations ADD COLUMN client_portal_url VARCHAR(255);
ALTER TABLE general_informations ADD COLUMN partner_portal_url VARCHAR(255);

-- Set the URLs
UPDATE general_informations 
SET client_portal_url = 'https://client.example.com',
    partner_portal_url = 'https://partner.example.com'
WHERE id = 1;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Backend Implementation&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Client Panel AuthController Methods&lt;/strong&gt;&lt;br&gt;
Let's add three methods to the Client Panel's AuthController:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;switchToPartnerPanel&lt;/strong&gt;: Generates an encrypted token and provides the URL to redirect to the Partner Panel
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async switchToPartnerPanel({ auth, response }) {
  try {
    // Ensure user is authenticated
    const user = await auth.getUser()
    if (!user) {
      return response.status(401).json({
        type: 'error',
        message: 'User not authenticated'
      })
    }

    // Check if user has IB status (partner privileges)
    if (!user.ib_status || user.ib_status !== 1) {
      return response.status(403).json({
        type: 'error',
        message: 'You do not have access to the Partner Panel'
      })
    }

    // Get encryption key from database
    const encryptionKey = await BrokerSitting.query()
      .where('name', 'CUSTOM_ENCRYPTION_KEY')
      .first()

    if (!encryptionKey || !encryptionKey.value) {
      return response.status(500).json({
        type: 'error',
        message: 'SSO configuration error'
      })
    }

    // Create a proper 32-byte key using SHA-256
    const crypto = require('crypto')
    const hash = crypto.createHash('sha256')
    hash.update(encryptionKey.value)
    const keyBuffer = hash.digest() // This gives a 32-byte buffer

    // Use the first 16 bytes of the key as the IV
    const iv = keyBuffer.slice(0, 16)

    // Get user ID to encrypt
    const userId = user.id

    // Encrypt the user ID using AES-256-CBC
    const cipher = crypto.createCipheriv('aes-256-cbc', keyBuffer, iv)
    let token = cipher.update(String(userId), 'utf8', 'base64')
    token += cipher.final('base64')

    // Get partner panel URL from database
    const partnerPortalInfo = await Database
      .table('general_informations')
      .select('partner_portal_url')
      .first()

    if (!partnerPortalInfo || !partnerPortalInfo.partner_portal_url) {
      return response.status(500).json({
        type: 'error',
        message: 'Partner portal URL not configured'
      })
    }

    // Create the SSO URL with the encrypted token
    const ssoUrl = `${partnerPortalInfo.partner_portal_url.replace(/\/$/, '')}/sso-login?token=${encodeURIComponent(token)}`

    // Return the URL to redirect to
    return response.status(200).json({
      type: 'success',
      ssoUrl
    })
  } catch (error) {
    console.error('SSO error:', error)
    return response.status(500).json({
      type: 'error',
      message: 'Internal server error',
      error: process.env.NODE_ENV === 'development' ? error.message : undefined
    })
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;handleSSOLogin&lt;/strong&gt;: Receives an encrypted token from the Partner Panel, decrypts it, and logs the user in
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async handleSSOLogin({ request, response, auth, session }) {
  try {
    const { token } = request.all()

    if (!token) {
      return response.status(400).json({
        type: 'error',
        message: 'SSO token is required'
      })
    }

    // Get encryption key from database
    const encryptionKey = await BrokerSitting.query()
      .where('name', 'CUSTOM_ENCRYPTION_KEY')
      .first()

    if (!encryptionKey || !encryptionKey.value) {
      return response.status(500).json({
        type: 'error',
        message: 'SSO configuration error'
      })
    }

    // Create a proper 32-byte key using SHA-256
    const crypto = require('crypto')
    const hash = crypto.createHash('sha256')
    hash.update(encryptionKey.value)
    const keyBuffer = hash.digest() // This gives a 32-byte buffer

    // Use the first 16 bytes of the key as the IV
    const iv = keyBuffer.slice(0, 16)

    // Decrypt the token to get user ID
    let userId
    try {
      const decipher = crypto.createDecipheriv('aes-256-cbc', keyBuffer, iv)
      userId = decipher.update(token, 'base64', 'utf8')
      userId += decipher.final('utf8')
    } catch (error) {
      return response.status(400).json({
        type: 'error',
        message: 'Invalid SSO token'
      })
    }

    // Find the user in database
    const user = await Account.query()
      .where('id', userId)
      .first()

    if (!user) {
      return response.status(404).json({
        type: 'error',
        message: 'User not found'
      })
    }

    // Check if user is active
    if (user.status === 0) {
      return response.status(403).json({
        type: 'error',
        message: 'Account is blocked'
      })
    }

    // Check if email is verified
    if (user.email_status === 0) {
      return response.status(403).json({
        type: 'error',
        message: 'Email not verified'
      })
    }

    // Log in the user
    const authToken = await auth.generate(user)

    // Record login activity
    const now = moment().format('YYYY-MM-DD HH:mm:ss')
    const userAgentString = request.header('user-agent')
    const userAgent = UserAgent.parse(userAgentString)
    const userDevice = `${userAgent.family} ${userAgent.major}`

    const sessionId = uuidv4()
    session.put('loginActivitySessionId', sessionId)

    const userActivityService = new LoginActivityService()
    await userActivityService.logLoginActivity(
      user.id,
      1, // Panel = 1 for client panel
      sessionId,
      now,
      null,
      userDevice,
      request.header('referer'),
      request.ip()
    )

    // Get logout time from settings
    const logOutTimeSetting = await BrokerSitting.query()
      .where('name', 'log_out_time_client')
      .first()

    const logOutTime = logOutTimeSetting ?
      parseInt(logOutTimeSetting.value) * 60 * 1000 :
      30 * 60 * 1000 // Default to 30 minutes

    return response.status(200).json({
      type: 'success',
      message: 'SSO login successful',
      logOutTime: logOutTime,
      client: authToken
    })
  } catch (error) {
    console.error('SSO login error:', error)
    return response.status(500).json({
      type: 'error',
      message: 'Internal server error',
      error: process.env.NODE_ENV === 'development' ? error.message : undefined
    })
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;logoutAndRedirectToPartner&lt;/strong&gt;: Logs the user out of the Client Panel and redirects to the Partner Panel's logout page
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async logoutAndRedirectToPartner({ auth, response }) {
  const loginUser = await auth.user.id
  const loginActivity = await LoginActivity.query()
    .where('user_id', loginUser)
    .where('panel', 1)
    .where('logout_time', null)
    .orderBy('id', 'desc')
    .first()

  if (loginActivity) {
    const userActivityService = new LoginActivityService()
    await userActivityService.logLogoutActivity(
      loginUser,
      1,
      loginActivity.session_id
    )
  }

  await auth.logout()

  // Get partner panel URL from database
  const partnerPortalInfo = await Database
    .table('general_informations')
    .select('partner_portal_url')
    .first()

  if (!partnerPortalInfo || !partnerPortalInfo.partner_portal_url) {
    return response.status(500).json({
      type: 'error',
      message: 'Partner portal URL not configured'
    })
  }

  return response.status(200).json({
    type: 'success',
    message: 'Logged out successfully',
    redirectUrl: `${partnerPortalInfo.partner_portal_url.replace(/\/$/, '')}/logout`
  })
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Partner Panel AuthController Methods&lt;/strong&gt;&lt;br&gt;
Now let's implement the corresponding methods in the Partner Panel's &lt;strong&gt;AuthController&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;handleSSOLogin&lt;/strong&gt;: Receives an encrypted token from the Client Panel
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async handleSSOLogin({ request, response, auth, session }) {
  try {
    const { token } = request.all();

    if (!token) {
      return response.status(400).json({
        type: 'error',
        message: 'SSO token is required'
      });
    }

    // Get encryption key from database
    const encryptionKey = await BrokerSitting.query()
      .where('name', 'CUSTOM_ENCRYPTION_KEY')
      .first();

    if (!encryptionKey || !encryptionKey.value) {
      return response.status(500).json({
        type: 'error',
        message: 'SSO configuration error'
      });
    }

    // Create a proper 32-byte key using SHA-256
    const crypto = require('crypto');
    const hash = crypto.createHash('sha256');
    hash.update(encryptionKey.value);
    const keyBuffer = hash.digest();

    // Use the first 16 bytes of the key as the IV
    const iv = keyBuffer.slice(0, 16);

    // Decrypt the token to get user ID
    let userId;
    try {
      const decipher = crypto.createDecipheriv('aes-256-cbc', keyBuffer, iv);
      userId = decipher.update(token, 'base64', 'utf8');
      userId += decipher.final('utf8');
    } catch (error) {
      console.error('Token decryption error:', error);
      return response.status(400).json({
        type: 'error',
        message: 'Invalid SSO token'
      });
    }

    // Find the user in database
    const user = await Account.query()
      .where('id', userId)
      .first();

    if (!user) {
      return response.status(404).json({
        type: 'error',
        message: 'User not found'
      });
    }

    // Verify the user has IB status
    if (!user.ib_status || user.ib_status !== 1) {
      return response.status(403).json({
        type: 'error',
        message: 'You do not have access to the Partner Panel'
      });
    }

    // Check if user is active
    if (user.status === 0) {
      return response.status(403).json({
        type: 'error',
        message: 'Account is blocked'
      });
    }

    // Log in the user
    await auth.loginViaId(user.id);

    // Record login activity
    const now = moment().format("YYYY-MM-DD HH:mm:ss");
    const userAgentString = request.header("user-agent");
    const userAgent = UserAgent.parse(userAgentString);
    const userDevice = `${userAgent.family} ${userAgent.major}`;

    const sessionId = uuidv4();
    session.put("loginActivitySessionId", sessionId);

    const userActivityService = new LoginActivityService();
    await userActivityService.logLoginActivity(
      user.id,
      2, // Panel = 2 for partner panel
      sessionId,
      now,
      null,
      userDevice,
      request.header("referer"),
      request.ip()
    );

    // Redirect to dashboard
    return response.redirect('/dashboard');
  } catch (error) {
    console.error('SSO login error:', error);
    return response.status(500).json({
      type: 'error',
      message: 'Internal server error'
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;switchToClientPanel&lt;/strong&gt;: Generates an encrypted token and provides the URL to redirect to the Client Panel
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async switchToClientPanel({ auth, response }) {
  try {
    // Ensure user is authenticated
    const user = await auth.getUser();

    if (!user) {
      return response.status(401).json({
        type: 'error',
        message: 'User not authenticated'
      });
    }

    // Get encryption key from database
    const encryptionKey = await BrokerSitting.query()
      .where('name', 'CUSTOM_ENCRYPTION_KEY')
      .first();

    if (!encryptionKey || !encryptionKey.value) {
      return response.status(500).json({
        type: 'error',
        message: 'SSO configuration error'
      });
    }

    // Create a proper 32-byte key using SHA-256
    const crypto = require('crypto');
    const hash = crypto.createHash('sha256');
    hash.update(encryptionKey.value);
    const keyBuffer = hash.digest();

    // Use the first 16 bytes of the key as the IV
    const iv = keyBuffer.slice(0, 16);

    // Get user ID to encrypt
    const userId = user.id;

    // Encrypt the user ID using AES-256-CBC
    const cipher = crypto.createCipheriv('aes-256-cbc', keyBuffer, iv);
    let token = cipher.update(String(userId), 'utf8', 'base64');
    token += cipher.final('base64');

    // Get client panel URL from database
    const clientPortalInfo = await Database
      .table('general_informations')
      .select('client_portal_url')
      .first();

    if (!clientPortalInfo || !clientPortalInfo.client_portal_url) {
      return response.status(500).json({
        type: 'error',
        message: 'Client portal URL not configured'
      });
    }

    // Create the SSO URL with the encrypted token
    const ssoUrl = `${clientPortalInfo.client_portal_url.replace(/\/$/, '')}/sso-login?token=${encodeURIComponent(token)}`;

    // Return the URL to redirect to
    return response.status(200).json({
      type: 'success',
      ssoUrl
    });
  } catch (error) {
    console.error('SSO error:', error);
    return response.status(500).json({
      type: 'error',
      message: 'Internal server error'
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;logoutAndRedirectToClient&lt;/strong&gt;: Logs the user out of the Partner Panel and redirects to the Client Panel
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async logoutAndRedirectToClient({ auth, response, session }) {
  try {
    const user = await auth.getUser();
    const userId = user.id;

    // Log logout activity
    const loginActivity = await LoginActivity.query()
      .where('user_id', userId)
      .where('panel', 2) // Partner panel
      .where('logout_time', null)
      .orderBy('id', 'desc')
      .first();

    if (loginActivity) {
      const userActivityService = new LoginActivityService();
      await userActivityService.logLogoutActivity(
        userId,
        2, // Partner panel
        loginActivity.session_id
      );
    }

    // Perform logout
    await auth.logout();
    session.clear();

    // Get client panel URL
    const clientPortalInfo = await Database
      .table('general_informations')
      .select('client_portal_url')
      .first();

    // Redirect to client panel
    if (clientPortalInfo &amp;amp;&amp;amp; clientPortalInfo.client_portal_url) {
      return response.status(200).json({
        type: 'success',
        message: 'Logged out successfully',
        redirectUrl: `${clientPortalInfo.client_portal_url.replace(/\/$/, '')}/login`
      });
    } else {
      return response.status(200).json({
        type: 'success',
        message: 'Logged out successfully'
      });
    }
  } catch (error) {
    console.error('Logout error:', error);
    return response.status(500).json({
      type: 'error',
      message: 'Internal server error'
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Setting Up Routes&lt;/strong&gt;&lt;br&gt;
Add these routes to your Client Panel's routes file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Client Panel routes
Route.get('/switch-to-partner', 'AuthController.switchToPartnerPanel').middleware(['auth'])
Route.get('/sso-login', 'AuthController.handleSSOLogin')
Route.get('/logout-to-partner', 'AuthController.logoutAndRedirectToPartner').middleware(['auth'])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And these routes to your Partner Panel's routes file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Partner Panel routes
Route.get('/sso-login', 'AuthController.handleSSOLogin')
Route.get('/switch-to-client', 'AuthController.switchToClientPanel').middleware(['auth'])
Route.get('/logout', 'AuthController.logoutAndRedirectToClient').middleware(['auth'])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Frontend Implementation&lt;/strong&gt;&lt;br&gt;
Now let's implement the UI components to trigger the panel switching.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Client Panel Vue Component&lt;/strong&gt;&lt;br&gt;
Create a component for the Client Panel that allows switching to the Partner Panel:&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;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;v-btn
      text
      :loading="isLoading"
      :disabled="isLoading"
      @click="switchToPartnerPanel"
      class="mx-2"
    &amp;gt;
      &amp;lt;v-icon left&amp;gt;mdi-swap-horizontal&amp;lt;/v-icon&amp;gt;
     Switch to Partner Panel
    &amp;lt;/v-btn&amp;gt;

    &amp;lt;v-snackbar
      v-model="showError"
      color="error"
      timeout="5000"
      bottom
    &amp;gt;
      {{ error }}
      &amp;lt;template v-slot:action="{ attrs }"&amp;gt;
        &amp;lt;v-btn
          text
          v-bind="attrs"
          @click="showError = false"
        &amp;gt;
          Close
        &amp;lt;/v-btn&amp;gt;
      &amp;lt;/template&amp;gt;
    &amp;lt;/v-snackbar&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import { mapState, mapActions } from 'vuex';

export default {
  name: 'PanelSwitcher',
  computed: {
    ...mapState('panelSwitcher', ['isLoading', 'error']),
    showError: {
      get() {
        return this.error !== null;
      },
      set(value) {
        if (!value) {
          this.$store.commit('panelSwitcher/SET_ERROR', null);
        }
      }
    }
  },
  methods: {
    ...mapActions('panelSwitcher', ['switchToPartnerPanel'])
  }
}
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Partner Panel Vue Component&lt;/strong&gt;&lt;br&gt;
Create a similar component for the Partner Panel to switch back to the Client Panel:&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;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;v-btn
      text
      :loading="isLoading"
      :disabled="isLoading"
      @click="switchToClientPanel"
      class="mx-2"
    &amp;gt;
      &amp;lt;v-icon left&amp;gt;mdi-swap-horizontal&amp;lt;/v-icon&amp;gt;
      {{ $t('clientPanel') || 'Switch to Client Panel' }}
    &amp;lt;/v-btn&amp;gt;

    &amp;lt;v-snackbar
      v-model="showError"
      color="error"
      timeout="5000"
      bottom
    &amp;gt;
      {{ error }}
      &amp;lt;template v-slot:action="{ attrs }"&amp;gt;
        &amp;lt;v-btn
          text
          v-bind="attrs"
          @click="showError = false"
        &amp;gt;
          Close
        &amp;lt;/v-btn&amp;gt;
      &amp;lt;/template&amp;gt;
    &amp;lt;/v-snackbar&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import { mapState, mapActions } from 'vuex';

export default {
  name: 'PanelSwitcher',
  computed: {
    ...mapState('panelSwitcher', ['isLoading', 'error']),
    showError: {
      get() {
        return this.error !== null;
      },
      set(value) {
        if (!value) {
          this.$store.commit('panelSwitcher/SET_ERROR', null);
        }
      }
    }
  },
  methods: {
    ...mapActions('panelSwitcher', ['switchToClientPanel'])
  }
}
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Vuex Store Module&lt;/strong&gt;&lt;br&gt;
Create a Vuex store module for each panel to handle the panel switching:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Client Panel: store/modules/panelSwitcher.js
import toast from "@/services/Notification";
import HTTP from "@/_helpers/http";

// State
const state = {
  ssoUrl: null,
  isLoading: false,
  error: null
};

// Getters
const getters = {};

// Mutations
const mutations = {
  SET_LOADING(state, isLoading) {
    state.isLoading = isLoading;
  },
  SET_ERROR(state, error) {
    state.error = error;
  },
  SET_SSO_URL(state, url) {
    state.ssoUrl = url;
  }
};

// Actions
const actions = {
  async switchToPartnerPanel({ commit }) {
    commit('SET_LOADING', true);
    commit('SET_ERROR', null);

    try {
      const response = await HTTP({
        trackProgress: true,
        loadingText: 'Preparing panel switch...'
      }).get('/switch-to-partner');

      if (response.status === 200 &amp;amp;&amp;amp; response.data.type === 'success') {
        commit('SET_SSO_URL', response.data.ssoUrl);
        // Redirect to partner panel
        window.location.href = response.data.ssoUrl;
      } else {
        commit('SET_ERROR', response.data.message || 'Failed to switch panel');
        toast.error(response.data.message || 'Failed to switch panel');
      }
    } catch (error) {
      console.error(error);
      commit('SET_ERROR', 'Connection error, please try again');
      toast.error('Connection error, please try again');
    } finally {
      commit('SET_LOADING', false);
    }
  }
};

export default {
  namespaced: true,
  state,
  getters,
  mutations,
  actions
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a similar module for the Partner Panel, but with **switchToClientPanel **instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security Considerations&lt;/strong&gt;&lt;br&gt;
This implementation includes several security measures:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Secure Key Management: The encryption key is stored in the database, not hardcoded in the application&lt;/li&gt;
&lt;li&gt;Strong Encryption: We use AES-256-CBC with a proper 32-byte key derived from SHA-256&lt;/li&gt;
&lt;li&gt;Minimal Data Transfer: Only the user ID is encrypted and passed between panels&lt;/li&gt;
&lt;li&gt;Permission Verification: Users must have the proper permissions to access each panel&lt;/li&gt;
&lt;li&gt;Activity Logging: All login and logout activities are recorded&lt;/li&gt;
&lt;li&gt;Error Handling: Comprehensive error handling prevents security issues from improper use&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>vue</category>
      <category>adonisjs</category>
      <category>sso</category>
      <category>security</category>
    </item>
    <item>
      <title>Implementing Two-Factor Authentication in Next.js 14 with NextAuth.js</title>
      <dc:creator>Abdur Rakib Rony</dc:creator>
      <pubDate>Thu, 06 Mar 2025 17:01:04 +0000</pubDate>
      <link>https://dev.to/abdur_rakibrony_349a3f89/implementing-two-factor-authentication-in-nextjs-14-with-nextauthjs-28jl</link>
      <guid>https://dev.to/abdur_rakibrony_349a3f89/implementing-two-factor-authentication-in-nextjs-14-with-nextauthjs-28jl</guid>
      <description>&lt;p&gt;Two-factor authentication (2FA) adds an essential layer of security to your web applications. In this guide, I'll walk you through implementing a complete 2FA solution in a Next.js 14 application using NextAuth.js and TOTP (Time-based One-Time Password).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Implement 2FA?&lt;/strong&gt;&lt;br&gt;
With data breaches becoming increasingly common, relying solely on passwords is no longer sufficient. 2FA adds an extra verification step, requiring users to provide a code from their mobile device in addition to their password. This significantly reduces the risk of unauthorized access, even if passwords are compromised.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A Next.js 14 application using the App Router&lt;/li&gt;
&lt;li&gt;NextAuth.js for authentication&lt;/li&gt;
&lt;li&gt;MongoDB or another database for user data storage&lt;/li&gt;
&lt;li&gt;Basic knowledge of React and Next.js&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Overview of Our Implementation&lt;/strong&gt;&lt;br&gt;
We'll build a complete 2FA system that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Allows users to enable 2FA from their account settings&lt;/li&gt;
&lt;li&gt;Generates a QR code for users to scan with authenticator apps&lt;/li&gt;
&lt;li&gt;Verifies the 2FA setup with a code from the user's authenticator app&lt;/li&gt;
&lt;li&gt;Redirects users to a verification page during login if 2FA is enabled&lt;/li&gt;
&lt;li&gt;Provides a way to disable 2FA when needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Setting Up the Database&lt;/strong&gt;&lt;br&gt;
First, we need to update our user schema to store 2FA-related information:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// models/user.js
import mongoose from "mongoose";

const userSchema = new mongoose.Schema(
  {
    // Existing user fields...
    email: String,
    firstName: String,
    lastName: String,
    // ... other fields

    // 2FA fields
    twoFactorEnabled: {
      type: Boolean,
      default: false,
    },
    twoFactorSecret: String,
  },
  { timestamps: true }
);

const User = mongoose.models.users || mongoose.model("users", userSchema);

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Installing Required Packages&lt;/strong&gt;&lt;br&gt;
We'll need a few packages to generate and verify TOTP codes:&lt;br&gt;
&lt;code&gt;npm install otplib qrcode&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Creating Utility Functions for 2FA&lt;/strong&gt;&lt;br&gt;
Next, let's create utility functions to handle 2FA operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// utils/2fa.js
import { authenticator } from 'otplib';
import QRCode from 'qrcode';

// Generate a secret for a user
export const generateTwoFactorSecret = (email) =&amp;gt; {
  const secret = authenticator.generateSecret();
  const serviceName = 'YourAppName';
  const otpAuthUrl = authenticator.keyuri(email, serviceName, secret);

  return { secret, otpAuthUrl };
};

// Generate QR code as data URL
export const generateQRCode = async (otpAuthUrl) =&amp;gt; {
  try {
    const qrCodeDataUrl = await QRCode.toDataURL(otpAuthUrl);
    return qrCodeDataUrl;
  } catch (error) {
    console.error('Error generating QR code:', error);
    throw error;
  }
};

// Verify OTP code
export const verifyToken = (token, secret) =&amp;gt; {
  try {
    return authenticator.verify({ token, secret });
  } catch (error) {
    console.error('Error verifying token:', error);
    return false;
  }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Configuring NextAuth.js&lt;/strong&gt;&lt;br&gt;
The next step is to update our NextAuth.js configuration to handle 2FA:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// lib/auth.js
import CredentialsProvider from "next-auth/providers/credentials";
import GoogleProvider from "next-auth/providers/google";
import FacebookProvider from "next-auth/providers/facebook";
import User from "@/models/user";
import { connectToDB } from "./db";

export const authOptions = {
  secret: process.env.NEXTAUTH_SECRET,
  pages: {
    signIn: "/login",
    error: "/login",
    verify: "/verify-2fa",
  },
  session: {
    strategy: "jwt",
  },
  callbacks: {
    async signIn({ user, account }) {
      if (account.provider === "credentials") {
        const email = user.email;
        await connectToDB();

        const dbUser = await User.findOne({ email });

        // Check if 2FA is enabled
        if (dbUser &amp;amp;&amp;amp; dbUser.twoFactorEnabled) {
          user.requiresTwoFactor = true;
          return true;
        }

        return true;
      }

      // Handle social logins similarly
      if (account.provider === "google" || account.provider === "facebook") {
        // Similar logic for social logins
        // ...
      }

      return true;
    },
    async jwt({ token, user, trigger, session }) {
      // When first creating the JWT
      if (user) {
        token._id = user._id;
        token.user_role = user.user_role;

        // Pass along the 2FA requirement
        if (user.requiresTwoFactor) {
          token.requiresTwoFactor = true;
        }
      }

      // Handle updates, including 2FA verification
      if (trigger === "update" &amp;amp;&amp;amp; session?.twoFactorVerified) {
        token.requiresTwoFactor = false;
      }

      return token;
    },
    async session({ session, token }) {
      // Include user details in session
      if (token?._id) {
        session.user._id = token._id;
        session.user.user_role = token.user_role;
      }

      // Expose the 2FA requirement to the client
      if (token.requiresTwoFactor) {
        session.requiresTwoFactor = true;
      }

      return session;
    },
  },
  providers: [
    // Your providers configuration
    // ...
  ],
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Creating API Endpoints for 2FA Management&lt;/strong&gt;&lt;br&gt;
We need to create several API endpoints to handle 2FA operations:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2FA Setup Endpoint&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/api/2fa/setup/route.js
import { getServerSession } from "next-auth/next";
import { NextResponse } from "next/server";
import { authOptions } from "@/lib/auth";
import { connectToDB } from "@/lib/db";
import User from "@/models/user";
import { generateTwoFactorSecret, generateQRCode } from "@/utils/2fa";

export async function POST() {
  try {
    const session = await getServerSession(authOptions);

    if (!session) {
      return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
    }

    await connectToDB();

    const user = await User.findOne({ email: session.user.email });

    if (!user) {
      return NextResponse.json({ error: "User not found" }, { status: 404 });
    }

    const { secret, otpAuthUrl } = generateTwoFactorSecret(user.email);
    const qrCodeDataUrl = await generateQRCode(otpAuthUrl);

    // Store the secret temporarily
    user.twoFactorSecret = secret;
    await user.save();

    return NextResponse.json({
      success: true,
      qrCodeDataUrl,
    });
  } catch (error) {
    console.error("Error setting up 2FA:", error);
    return NextResponse.json(
      { error: "Failed to set up 2FA" },
      { status: 500 }
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2FA Verification Endpoint&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/api/2fa/verify/route.js
import { getServerSession } from "next-auth/next";
import { NextResponse } from "next/server";
import { authOptions } from "@/lib/auth";
import { connectToDB } from "@/lib/db";
import User from "@/models/user";
import { verifyToken } from "@/utils/2fa";

export async function POST(req) {
  try {
    const session = await getServerSession(authOptions);

    if (!session) {
      return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
    }

    const { token } = await req.json();

    if (!token) {
      return NextResponse.json({ error: "Token is required" }, { status: 400 });
    }

    await connectToDB();

    const user = await User.findOne({ email: session.user.email });

    if (!user || !user.twoFactorSecret) {
      return NextResponse.json(
        { error: "2FA setup not initiated" },
        { status: 400 }
      );
    }

    const isValid = verifyToken(token, user.twoFactorSecret);

    if (!isValid) {
      return NextResponse.json({ error: "Invalid token" }, { status: 400 });
    }

    // Enable 2FA for the user
    user.twoFactorEnabled = true;
    await user.save();

    return NextResponse.json({
      success: true,
      message: "Two-factor authentication enabled",
    });
  } catch (error) {
    console.error("Error verifying 2FA token:", error);
    return NextResponse.json(
      { error: "Failed to verify token" },
      { status: 500 }
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2FA Login Verification Endpoint&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/api/auth/verify-2fa/route.js
import { getServerSession } from "next-auth/next";
import { NextResponse } from "next/server";
import { authOptions } from "@/lib/auth";
import { connectToDB } from "@/lib/db";
import User from "@/models/user";
import { verifyToken } from "@/utils/2fa";

export async function POST(req) {
  try {
    const session = await getServerSession(authOptions);

    if (!session) {
      return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
    }

    const { token } = await req.json();

    if (!token) {
      return NextResponse.json({ error: "Token is required" }, { status: 400 });
    }

    await connectToDB();

    const user = await User.findOne({ email: session.user.email });

    if (!user || !user.twoFactorSecret) {
      return NextResponse.json(
        { error: "User not found or 2FA not set up" },
        { status: 404 }
      );
    }

    const isValid = verifyToken(token, user.twoFactorSecret);

    if (!isValid) {
      return NextResponse.json({ error: "Invalid token" }, { status: 400 });
    }

    return NextResponse.json({
      success: true,
      message: "Two-factor authentication verified",
    });
  } catch (error) {
    console.error("Error verifying 2FA:", error);
    return NextResponse.json(
      { error: "Failed to verify 2FA" },
      { status: 500 }
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Creating the UI Components&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;2FA Setup Component&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// components/TwoFactorSetup.jsx
"use client";
import { useState, useEffect } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { toast } from "@/components/ui/use-toast";
import { useSession } from "next-auth/react";

const TwoFactorSetup = () =&amp;gt; {
  const { data: session } = useSession();
  const [qrCode, setQrCode] = useState("");
  const [token, setToken] = useState("");
  const [loading, setLoading] = useState(false);
  const [setupMode, setSetupMode] = useState(false);
  const [isEnabled, setIsEnabled] = useState(false);
  const [isStatusLoading, setIsStatusLoading] = useState(true);
  const [disableMode, setDisableMode] = useState(false);

  useEffect(() =&amp;gt; {
    // Fetch the current 2FA status when component mounts
    const checkTwoFactorStatus = async () =&amp;gt; {
      try {
        setIsStatusLoading(true);
        const response = await fetch("/api/2fa/status");
        const data = await response.json();

        if (response.ok) {
          setIsEnabled(data.enabled);
        }
      } catch (error) {
        console.error("Error checking 2FA status:", error);
      } finally {
        setIsStatusLoading(false);
      }
    };

    if (session) {
      checkTwoFactorStatus();
    }
  }, [session]);

  const initiateTwoFactorSetup = async () =&amp;gt; {
    try {
      setLoading(true);
      const response = await fetch("/api/2fa/setup", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
      });

      const data = await response.json();

      if (!response.ok) {
        throw new Error(data.error || "Failed to set up 2FA");
      }

      setQrCode(data.qrCodeDataUrl);
      setSetupMode(true);
    } catch (error) {
      toast({
        variant: "destructive",
        title: "Error",
        description: error.message,
      });
    } finally {
      setLoading(false);
    }
  };

  const verifyAndEnable = async () =&amp;gt; {
    // Handle verification and enabling 2FA
    // ...
  };

  // Rest of the component implementation
  // ...

  return (
    &amp;lt;div className="space-y-6"&amp;gt;
      &amp;lt;div className="border p-6 rounded-md"&amp;gt;
        &amp;lt;h3 className="text-lg font-medium mb-4"&amp;gt;Two-Factor Authentication&amp;lt;/h3&amp;gt;

        {isEnabled ? (
          // Show 2FA enabled UI
          // ...
        ) : !setupMode ? (
          // Show setup button
          // ...
        ) : (
          // Show QR code and verification form
          // ...
        )}
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2FA Verification Page&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/verify-2fa/page.jsx
"use client";
import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import { useSession } from "next-auth/react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { toast } from "@/components/ui/use-toast";

export default function VerifyTwoFactor() {
  const [token, setToken] = useState("");
  const [loading, setLoading] = useState(false);
  const [sessionChecked, setSessionChecked] = useState(false);
  const router = useRouter();
  const { data: session, status, update } = useSession();

  useEffect(() =&amp;gt; {
    // Only run this check after session is loaded
    if (status !== "loading") {
      setSessionChecked(true);

      // If user is authenticated but doesn't need 2FA, redirect them away
      if (session &amp;amp;&amp;amp; !session.requiresTwoFactor) {
        router.push("/");
      }
    }
  }, [session, status, router]);

  const handleVerification = async (e) =&amp;gt; {
    e.preventDefault();

    if (!token || token.length !== 6) {
      toast({
        variant: "destructive",
        title: "Error",
        description: "Please enter a valid 6-digit code",
      });
      return;
    }

    try {
      setLoading(true);

      const response = await fetch("/api/auth/verify-2fa", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ token }),
      });

      const data = await response.json();

      if (!response.ok) {
        throw new Error(data.error || "Failed to verify code");
      }

      // Update the session to mark 2FA as verified
      await update({
        twoFactorVerified: true,
      });

      toast({
        title: "Success",
        description: "Two-factor authentication verified",
      });

      // Redirect to the home page
      router.push("/account/profile");
    } catch (error) {
      toast({
        variant: "destructive",
        title: "Error",
        description: error.message,
      });
    } finally {
      setLoading(false);
    }
  };

  return (
    &amp;lt;div className="w-full sm:max-w-md mx-auto py-10 space-y-8 p-4 sm:p-6"&amp;gt;
      &amp;lt;div className="text-center"&amp;gt;
        &amp;lt;h1 className="text-2xl font-bold"&amp;gt;Verify Your Identity&amp;lt;/h1&amp;gt;
        &amp;lt;p className="text-sm text-gray-500 mt-2"&amp;gt;
          Enter the 6-digit code from your authenticator app
        &amp;lt;/p&amp;gt;
      &amp;lt;/div&amp;gt;

      &amp;lt;form onSubmit={handleVerification} className="space-y-6"&amp;gt;
        {/* Form implementation */}
      &amp;lt;/form&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Updating Middleware for 2FA Flow&lt;/strong&gt;&lt;br&gt;
To ensure users are properly redirected to the 2FA verification page when needed, update your middleware:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// middleware.js
import { getToken } from "next-auth/jwt";
import { NextResponse } from "next/server";

export async function middleware(req) {
  const url = req.nextUrl.clone();

  try {
    const token = await getToken({
      req,
      secret: process.env.NEXTAUTH_SECRET,
    });

    // Define protected routes
    const protectedRoutes = ["/dashboard", "/account", "/checkout"];

    // Check if 2FA is required but not verified
    const requires2FA = token?.requiresTwoFactor === true;

    // Skip middleware for the verify-2fa page and its API
    if (url.pathname === "/verify-2fa" || url.pathname === "/api/auth/verify-2fa") {
      return NextResponse.next();
    }

    // If 2FA is required, redirect to verification page
    if (token &amp;amp;&amp;amp; requires2FA &amp;amp;&amp;amp; url.pathname !== "/verify-2fa") {
      return NextResponse.redirect(new URL("/verify-2fa", req.url));
    }

    // Handle other authentication and authorization logic
    // ...

    return NextResponse.next();
  } catch (error) {
    console.error("Middleware error:", error);
    return NextResponse.redirect(new URL("/login", req.url));
  }
}

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Environment Variables&lt;/strong&gt;&lt;br&gt;
Make sure you have these environment variables set in your .env.local file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NEXTAUTH_SECRET=your_next_auth_secret
NEXTAUTH_URL=http://localhost:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Common Issues and Troubleshooting&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;Database Connection Issues&lt;/em&gt;&lt;br&gt;
If you're having trouble saving 2FA information to the database, ensure your model is correctly defined and imported. Use debugging logs to verify the data is correctly saved.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Session Not Updating&lt;/em&gt;&lt;br&gt;
If the session isn't properly updating after 2FA verification, check your client-side update logic. The useSession().update() method is the correct way to update the session client-side.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Redirection Problems&lt;/em&gt;&lt;br&gt;
If users aren't being redirected to the 2FA verification page when needed, check your middleware logic and ensure the token.requiresTwoFactor flag is properly set during login.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>authentication</category>
      <category>security</category>
      <category>nextauth</category>
    </item>
    <item>
      <title>Mastering Next.js Intercepting Routes: Unlocking Seamless UI Transitions</title>
      <dc:creator>Abdur Rakib Rony</dc:creator>
      <pubDate>Thu, 06 Feb 2025 08:09:52 +0000</pubDate>
      <link>https://dev.to/abdur_rakibrony_349a3f89/mastering-nextjs-intercepting-routes-unlocking-seamless-ui-transitions-1922</link>
      <guid>https://dev.to/abdur_rakibrony_349a3f89/mastering-nextjs-intercepting-routes-unlocking-seamless-ui-transitions-1922</guid>
      <description>&lt;p&gt;&lt;strong&gt;🚀 Introduction&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Next.js 13 introduced Intercepting Routes, a powerful feature that allows us to enhance user experience by keeping the current page visible while rendering modals, panels, or popups. This technique is perfect for cases like in-app notifications, media previews, and profile editing without full-page navigation.&lt;/p&gt;

&lt;p&gt;In this guide, we’ll explore three real-world scenarios where intercepting routes can be a game-changer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1️⃣ Scenario: In-App Notifications Panel&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Case:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Clicking on a notification should open a notification details panel without navigating away from the current page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🔧 Implementation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Notification Bell Component (&lt;code&gt;app/components/NotificationBell.tsx&lt;/code&gt;)&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 Link from "next/link";

export default function NotificationBell() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;Link href="/notifications" as="/notifications/panel"&amp;gt;
        &amp;lt;button className="p-2 bg-blue-500 text-white rounded"&amp;gt;🔔 Notifications&amp;lt;/button&amp;gt;
      &amp;lt;/Link&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notification Panel Route (&lt;code&gt;app/notifications/panel.tsx&lt;/code&gt;)&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 { useRouter } from "next/navigation";

export default function NotificationPanel() {
  const router = useRouter();

  return (
    &amp;lt;div className="fixed right-0 top-0 w-80 h-full bg-white shadow-lg p-4"&amp;gt;
      &amp;lt;h2 className="text-lg font-bold"&amp;gt;Notifications&amp;lt;/h2&amp;gt;
      &amp;lt;p&amp;gt;New messages, updates, and alerts will appear here.&amp;lt;/p&amp;gt;
      &amp;lt;button className="mt-4 px-4 py-2 bg-red-500 text-white rounded" onClick={() =&amp;gt; router.back()}&amp;gt;
        Close
      &amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ &lt;strong&gt;How It Works&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clicking the Notification Bell opens the notification panel (/notifications/panel).&lt;/li&gt;
&lt;li&gt;The main page remains visible while the panel overlays it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;2️⃣ &lt;strong&gt;Scenario: Image or Video Preview&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📝 Use Case:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Clicking on an image should open a fullscreen media preview modal instead of navigating to a new page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🔧 Implementation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Gallery Page (&lt;code&gt;app/gallery/page.tsx&lt;/code&gt;)&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 Link from "next/link";

export default function GalleryPage() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;Gallery&amp;lt;/h1&amp;gt;
      &amp;lt;ul&amp;gt;
        {["image1", "image2", "image3"].map((id) =&amp;gt; (
          &amp;lt;li key={id}&amp;gt;
            &amp;lt;Link href={`/gallery/${id}`} as={`/gallery/${id}/preview`}&amp;gt;
              &amp;lt;img src={`/images/${id}.jpg`} alt={id} className="w-32 h-32 cursor-pointer" /&amp;gt;
            &amp;lt;/Link&amp;gt;
          &amp;lt;/li&amp;gt;
        ))}
      &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Preview Modal Route (&lt;code&gt;app/gallery/[id]/preview.tsx&lt;/code&gt;)&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 { useRouter } from "next/navigation";

export default function ImagePreview({ params }: { params: { id: string } }) {
  const router = useRouter();

  return (
    &amp;lt;div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-80"&amp;gt;
      &amp;lt;img src={`/images/${params.id}.jpg`} alt={params.id} className="max-w-full max-h-full" /&amp;gt;
      &amp;lt;button className="absolute top-4 right-4 text-white text-xl" onClick={() =&amp;gt; router.back()}&amp;gt;
        ✖ Close
      &amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;✅ How It Works&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clicking an image thumbnail opens a fullscreen modal (&lt;code&gt;/gallery/image1/preview&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Pressing Close (✖) restores the gallery page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3️⃣ Scenario: Context-Based Form Editing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📝 Use Case:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Clicking Edit Profile should open a form inside a modal without leaving the profile page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🔧 Implementation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Profile Page (&lt;code&gt;app/profile/page.tsx&lt;/code&gt;)&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 Link from "next/link";

export default function ProfilePage() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;My Profile&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;Name: John Doe&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;Email: john@example.com&amp;lt;/p&amp;gt;
      &amp;lt;Link href="/profile/edit" as="/profile/edit/modal"&amp;gt;
        &amp;lt;button className="mt-4 px-4 py-2 bg-blue-500 text-white rounded"&amp;gt;Edit Profile&amp;lt;/button&amp;gt;
      &amp;lt;/Link&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Edit Profile Modal (&lt;code&gt;app/profile/edit/modal.tsx&lt;/code&gt;)&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 { useRouter } from "next/navigation";

export default function EditProfileModal() {
  const router = useRouter();

  return (
    &amp;lt;div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50"&amp;gt;
      &amp;lt;div className="bg-white p-6 rounded-lg shadow-lg w-96"&amp;gt;
        &amp;lt;h2 className="text-xl font-semibold"&amp;gt;Edit Profile&amp;lt;/h2&amp;gt;
        &amp;lt;input type="text" placeholder="Name" className="block w-full border p-2 my-2" /&amp;gt;
        &amp;lt;input type="email" placeholder="Email" className="block w-full border p-2 my-2" /&amp;gt;
        &amp;lt;button className="mt-4 px-4 py-2 bg-green-500 text-white rounded"&amp;gt;Save&amp;lt;/button&amp;gt;
        &amp;lt;button className="mt-4 px-4 py-2 bg-red-500 text-white rounded ml-2" onClick={() =&amp;gt; router.back()}&amp;gt;
          Cancel
        &amp;lt;/button&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;✅ How It Works&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clicking Edit Profile opens a form modal (&lt;code&gt;/profile/edit/modal&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Clicking Cancel restores the original profile page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;🎯 Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Intercepting routes in Next.js enhance user experience by keeping content visible while allowing seamless navigation. Whether it's notifications, media previews, or form modals, this approach ensures smooth transitions without full-page reloads.&lt;/p&gt;

&lt;p&gt;🚀 Start implementing these patterns in your Next.js projects today and build modern, dynamic web applications!&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>interceptingroutes</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Building a YouTube-Style Loading Bar in Vue 2: A Complete Guide</title>
      <dc:creator>Abdur Rakib Rony</dc:creator>
      <pubDate>Wed, 05 Feb 2025 09:52:07 +0000</pubDate>
      <link>https://dev.to/abdur_rakibrony_349a3f89/building-a-youtube-style-loading-bar-in-vue-2-a-complete-guide-hgf</link>
      <guid>https://dev.to/abdur_rakibrony_349a3f89/building-a-youtube-style-loading-bar-in-vue-2-a-complete-guide-hgf</guid>
      <description>&lt;p&gt;Have you ever noticed that sleek loading bar at the top of YouTube or Dev.to that appears when you navigate between pages? In this tutorial, we'll build a similar loading bar in Vue 2 using Vuex for state management and Axios for handling HTTP requests. Our implementation will include progress tracking for both uploads and downloads, making it perfect for modern web applications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What We'll Build&lt;/strong&gt;&lt;br&gt;
We'll create a global loading bar that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shows progress for all API requests automatically&lt;/li&gt;
&lt;li&gt;Tracks both upload and download progress&lt;/li&gt;
&lt;li&gt;Has smooth animations&lt;/li&gt;
&lt;li&gt;Can display custom loading text&lt;/li&gt;
&lt;li&gt;Manages multiple concurrent requests&lt;/li&gt;
&lt;li&gt;Uses Vuex for state management&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Basic knowledge of Vue 2&lt;/li&gt;
&lt;li&gt;Understanding of Vuex&lt;/li&gt;
&lt;li&gt;Familiarity with Axios&lt;/li&gt;
&lt;li&gt;A Vue 2 project with Vuex and Axios installed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Implementation Steps&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Setting Up the Vuex Store&lt;/strong&gt;&lt;br&gt;
First, let's create a Vuex module to manage our loading state. Create a new file &lt;code&gt;store/modules/loader.js&lt;/code&gt; then import this into your store &lt;code&gt;store/index.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const state = {
    pendingRequests: 0,
    isLoading: false,
    loadingText: '',
    progress: 0,
    showPercentage: false
};

const mutations = {
    INCREMENT_PENDING_REQUESTS(state) {
        state.pendingRequests++;
        state.isLoading = true;
    },
    DECREMENT_PENDING_REQUESTS(state) {
        state.pendingRequests--;
        state.isLoading = state.pendingRequests &amp;gt; 0;
        if (!state.isLoading) {
            state.progress = 0;
            state.loadingText = '';
            state.showPercentage = false;
        }
    },
    SET_LOADING_TEXT(state, text) {
        state.loadingText = text;
    },
    SET_PROGRESS(state, progress) {
        state.progress = Math.min(Math.max(progress, 0), 100);
        state.showPercentage = true;
    }
};

const getters = {
    isLoading: state =&amp;gt; state.isLoading,
    loadingText: state =&amp;gt; state.loadingText,
    progress: state =&amp;gt; state.progress,
    showPercentage: state =&amp;gt; state.showPercentage
};

export default {
    namespaced: true,
    state,
    mutations,
    getters
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Creating the Loading Bar Component&lt;/strong&gt;&lt;br&gt;
Create a new component &lt;code&gt;components/GlobalLoader.vue&lt;/code&gt;:&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;template&amp;gt;
  &amp;lt;div v-show="isLoading" class="progress-container"&amp;gt;
    &amp;lt;div 
      class="progress-bar"
      :style="{ 
        transform: `scaleX(${progress / 100})`,
        opacity: progress === 100 ? 0 : 1
      }"
    &amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import { mapGetters } from 'vuex'

export default {
  name: 'GlobalLoader',

  computed: {
    ...mapGetters('loader', [
      'isLoading',
      'progress',
      'showPercentage'
    ])
  }
}
&amp;lt;/script&amp;gt;

&amp;lt;style scoped&amp;gt;
.progress-container {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 3px;
  z-index: 9999;
  background-color: rgba(255, 255, 255, 0.1);
}

.progress-bar {
  width: 100%;
  height: 100%;
  background-color: #16bb93;
  transform-origin: left;
  transition: transform 0.2s ease, opacity 0.3s ease;
}
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Creating an Axios Instance with Progress Tracking&lt;/strong&gt;&lt;br&gt;
Create a new file for your HTTP client configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import axios from 'axios';
import store from '@/store';

const apiBaseUrl = process.env.VUE_APP_NODE_ENV === 'development'
  ? process.env.VUE_APP_LOCAL_BASE_URL
  : process.env.VUE_APP_BASE_URL;

export default ({ loadingText = '', trackProgress = false } = {}) =&amp;gt; {
  const instance = axios.create({
    baseURL: apiBaseUrl,
    headers: {
      Accept: '*/*',
      Authorization: `Bearer ${store.state.authentication.token}`,
      locale: 'en',
    },
  });

  // Request interceptor
  instance.interceptors.request.use(
    config =&amp;gt; {
      store.commit('loader/INCREMENT_PENDING_REQUESTS');
      if (loadingText) {
        store.commit('loader/SET_LOADING_TEXT', loadingText);
      }

      if (trackProgress) {
        // Initialize progress
        store.commit('loader/SET_PROGRESS', 0);

        // Track upload progress
        config.onUploadProgress = progressEvent =&amp;gt; {
          if (progressEvent.total) {
            const percentCompleted = Math.round(
              (progressEvent.loaded * 50) / progressEvent.total
            );
            store.commit('loader/SET_PROGRESS', percentCompleted);
          }
        };

        // Track download progress
        config.onDownloadProgress = progressEvent =&amp;gt; {
          if (progressEvent.total) {
            const percentCompleted = Math.round(
              50 + (progressEvent.loaded * 50) / progressEvent.total
            );
            store.commit('loader/SET_PROGRESS', percentCompleted);
          }
        };
      }

      return config;
    },
    error =&amp;gt; {
      store.commit('loader/DECREMENT_PENDING_REQUESTS');
      return Promise.reject(error);
    }
  );

  // Response interceptor
  instance.interceptors.response.use(
    response =&amp;gt; {
      if (trackProgress) {
        store.commit('loader/SET_PROGRESS', 100);
      }
      store.commit('loader/DECREMENT_PENDING_REQUESTS');
      return response;
    },
    error =&amp;gt; {
      store.commit('loader/DECREMENT_PENDING_REQUESTS');
      return Promise.reject(error);
    }
  );

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;How It Works&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;State Management:&lt;/strong&gt; The Vuex module tracks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Number of pending requests&lt;/li&gt;
&lt;li&gt;Loading state&lt;/li&gt;
&lt;li&gt;Progress percentage&lt;/li&gt;
&lt;li&gt;Custom loading text&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Progress Tracking:&lt;/strong&gt; We split the progress bar into two phases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First 50% for upload progress&lt;/li&gt;
&lt;li&gt;Last 50% for download progress&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Request Handling:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The request interceptor increments the pending requests counter&lt;/li&gt;
&lt;li&gt;The response interceptor decrements it&lt;/li&gt;
&lt;li&gt;When all requests complete, the loading bar disappears&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Smooth Animations:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CSS transitions create smooth progress updates&lt;/li&gt;
&lt;li&gt;The bar fades out when reaching 100%&lt;/li&gt;
&lt;li&gt;Transform-origin ensures the bar grows from left to right&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Usage Example&lt;/strong&gt;&lt;br&gt;
Here's how to use the loading bar in your API calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async login({ commit }, parameter) {
  try {
    const response = await HTTP({
      trackProgress: true,
      loadingText: 'Processing...'
    }).post('/auth/login', {
      email: parameter.email,
      password: parameter.password,
    });

    // Handle response...
  } catch (error) {
    // Handle error...
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Best Practices and Tips&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The loader handles multiple concurrent requests gracefully by tracking the number of pending requests.&lt;/li&gt;
&lt;li&gt;Use v-show instead of v-if to prevent frequent DOM updates.&lt;/li&gt;
&lt;li&gt;Split the progress bar into upload and download phases for better user feedback.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
This implementation provides a professional, YouTube-style loading experience for your Vue 2 application. It's highly customizable and can be easily integrated into any Vue project using Vuex and Axios.&lt;/p&gt;

</description>
      <category>vue</category>
      <category>loader</category>
      <category>vuetify</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building a Professional Email System with Next.js and Brevo: A Complete Guide</title>
      <dc:creator>Abdur Rakib Rony</dc:creator>
      <pubDate>Thu, 26 Dec 2024 06:23:45 +0000</pubDate>
      <link>https://dev.to/abdur_rakibrony_349a3f89/building-a-professional-email-system-with-nextjs-and-brevo-a-complete-guide-1d21</link>
      <guid>https://dev.to/abdur_rakibrony_349a3f89/building-a-professional-email-system-with-nextjs-and-brevo-a-complete-guide-1d21</guid>
      <description>&lt;p&gt;In this comprehensive guide, we'll explore how to create a robust email system using Next.js and Brevo (formerly Sendinblue). We'll build professional, responsive email templates and implement a reliable email sending system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Table of Contents&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Setting Up Brevo&lt;/li&gt;
&lt;li&gt;Project Setup&lt;/li&gt;
&lt;li&gt;Creating Email Templates&lt;/li&gt;
&lt;li&gt;Implementing the API&lt;/li&gt;
&lt;li&gt;Testing and Troubleshooting&lt;/li&gt;
&lt;li&gt;Best Practices&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;br&gt;
Email communication remains a crucial component of modern web applications. Whether it's order confirmations, password resets, or promotional content, having a reliable email system is essential. In this tutorial, we'll use Brevo's powerful email API with Next.js to create a professional email system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setting Up Brevo&lt;/strong&gt;&lt;br&gt;
First, you'll need to set up your Brevo account:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sign up at &lt;a href="https://www.brevo.com" rel="noopener noreferrer"&gt;Brevo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Navigate to SMTP &amp;amp; API section&lt;/li&gt;
&lt;li&gt;Generate an API key&lt;/li&gt;
&lt;li&gt;Add and verify your domain
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Store your API key in .env.local
BREVO_API_KEY=your_api_key_here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Project Setup&lt;/strong&gt;&lt;br&gt;
Let's start by installing the necessary dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install @sendinblue/client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Creating Email Templates&lt;/strong&gt;&lt;br&gt;
We'll create reusable email templates using a modular approach. Here's an example structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// utils/emailRenderer.js
export function renderEmailTemplate(templateName, data) {
  const sharedStyles = {
    mainContainer: "font-family: Arial, sans-serif; max-width: 600px;",
    header: "text-align: center; margin-bottom: 30px;",
    // ... more shared styles
  };

  const templates = {
    orderConfirmation: ({ orderNumber, items, total }) =&amp;gt; `
      &amp;lt;div style="${sharedStyles.mainContainer}"&amp;gt;
        &amp;lt;!-- Template content --&amp;gt;
      &amp;lt;/div&amp;gt;
    `,
    // ... more templates
  };

  return templates[templateName](data);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Implementing the API&lt;/strong&gt;&lt;br&gt;
Create an API route to handle email sending:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app/api/send-email/route.js
import { NextResponse } from "next/server";
import * as SibApiV3Sdk from "@sendinblue/client";
import { getEmailTemplate } from "@/utils/emailUtils";

export async function POST(req) {
  try {
    const { toEmail, subject, templateName, templateData } = await req.json();

    const htmlContent = getEmailTemplate(templateName, templateData);
    const client = new SibApiV3Sdk.TransactionalEmailsApi();

    client.setApiKey(
      SibApiV3Sdk.TransactionalEmailsApiApiKeys.apiKey,
      process.env.BREVO_API_KEY
    );

    const emailData = {
      sender: { email: "no-reply@yourdomain.com", name: "Your Brand" },
      to: [{ email: toEmail }],
      subject: subject,
      htmlContent: htmlContent,
      // ... additional configuration
    };

    const response = await client.sendTransacEmail(emailData);
    return NextResponse.json({ success: true, response });
  } catch (err) {
    console.error("Error sending email:", err);
    return NextResponse.json(
      { error: "An error occurred while sending the email" },
      { status: 500 }
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Testing Your Email System&lt;/strong&gt;&lt;br&gt;
Here's an example of how to test your email system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Example test payload
const testEmail = {
  toEmail: "test@example.com",
  subject: "Welcome to Our Store!",
  templateName: "orderConfirmation",
  templateData: {
    orderNumber: "ORD123",
    items: [
      {
        name: "Product 1",
        quantity: 2,
        price: 29.99
      }
    ],
    total: 59.98
  }
};

// Send test email
await fetch('/api/send-email', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(testEmail)
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
Building a professional email system with Next.js and Brevo provides a robust solution for handling all types of email communications. By following this guide, you can implement a scalable and maintainable email system for your applications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resources&lt;/strong&gt;&lt;br&gt;
Brevo API Documentation&lt;br&gt;
Next.js Documentation&lt;br&gt;
Email Template Best Practices&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>brevo</category>
      <category>emailtemplate</category>
    </item>
    <item>
      <title>Building a Full-Stack CRUD App with Next.js 14, Prisma, and PostgreSQL</title>
      <dc:creator>Abdur Rakib Rony</dc:creator>
      <pubDate>Sat, 09 Nov 2024 16:27:40 +0000</pubDate>
      <link>https://dev.to/abdur_rakibrony_349a3f89/building-a-full-stack-crud-app-with-nextjs-14-prisma-and-postgresql-b3c</link>
      <guid>https://dev.to/abdur_rakibrony_349a3f89/building-a-full-stack-crud-app-with-nextjs-14-prisma-and-postgresql-b3c</guid>
      <description>&lt;p&gt;In this tutorial, we'll build a full-stack CRUD (Create, Read, Update, Delete) application using Next.js 14, Prisma ORM, and PostgreSQL. We'll use Server Actions for data mutations and follow Next.js best practices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js installed on your machine&lt;/li&gt;
&lt;li&gt;Basic understanding of React and Next.js&lt;/li&gt;
&lt;li&gt;A code editor (VS Code recommended)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Installing PostgreSQL&lt;/strong&gt;&lt;br&gt;
Download PostgreSQL for your operating system from postgresql.org&lt;/p&gt;

&lt;p&gt;During installation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remember the password you set for the postgres user&lt;/li&gt;
&lt;li&gt;Keep the default port (5432)&lt;/li&gt;
&lt;li&gt;Install the command line tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Verify installation by opening Command Prompt/Terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;psql -U postgres
# Enter your password when prompted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a new database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE DATABASE mydb;
\q
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2: Setting Up Next.js Project&lt;/strong&gt;&lt;br&gt;
Create a new Next.js project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx create-next-app@latest user-management
cd user-management
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Choose the following options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Would you like to use TypeScript? › No&lt;/li&gt;
&lt;li&gt;Would you like to use ESLint? › Yes&lt;/li&gt;
&lt;li&gt;Would you like to use Tailwind CSS? › Yes&lt;/li&gt;
&lt;li&gt;Would you like to use src/ directory? › Yes&lt;/li&gt;
&lt;li&gt;Would you like to use App Router? › Yes&lt;/li&gt;
&lt;li&gt;Would you like to customize the default import alias? › No&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Install Prisma dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install prisma @prisma/client
npx prisma init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3: Configure Prisma&lt;/strong&gt;&lt;br&gt;
Update your .env file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DATABASE_URL="postgresql://postgres:yourpassword@localhost:5432/mydb?schema=public"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace yourpassword with your PostgreSQL password.&lt;/p&gt;

&lt;p&gt;Create your Prisma schema in prisma/schema.prisma:&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 User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  createdAt DateTime @default(now())
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx prisma db push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4: Setting Up Server Actions&lt;/strong&gt;&lt;br&gt;
Create &lt;code&gt;src/app/actions/users.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

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

import { prisma } from '@/lib/prisma'
import { revalidatePath } from 'next/cache'

export async function getUsers() {
  try {
    const users = await prisma.user.findMany()
    return { users }
  } catch (error) {
    return { error: 'Failed to fetch users' }
  }
}

export async function createUser(formData) {
  try {
    const name = formData.get('name')
    const email = formData.get('email')

    const user = await prisma.user.create({
      data: { name, email }
    })

    revalidatePath('/')
    return { user }
  } catch (error) {
    return { error: 'Failed to create user' }
  }
}

export async function updateUser(formData) {
  try {
    const id = parseInt(formData.get('id'))
    const name = formData.get('name')
    const email = formData.get('email')

    const user = await prisma.user.update({
      where: { id },
      data: { name, email }
    })

    revalidatePath('/')
    return { user }
  } catch (error) {
    return { error: 'Failed to update user' }
  }
}

export async function deleteUser(formData) {
  try {
    const id = parseInt(formData.get('id'))

    await prisma.user.delete({
      where: { id }
    })

    revalidatePath('/')
    return { success: true }
  } catch (error) {
    return { error: 'Failed to delete user' }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 5: Create Components&lt;/strong&gt;&lt;br&gt;
Create &lt;code&gt;src/app/components/UserForm.js&lt;/code&gt;:&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 { useFormStatus } from 'react-dom'
import { createUser } from '@/app/actions/users'

function SubmitButton() {
  const { pending } = useFormStatus()

  return (
    &amp;lt;button 
      type="submit"
      disabled={pending}
      className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 disabled:bg-blue-300"
    &amp;gt;
      {pending ? 'Adding...' : 'Add User'}
    &amp;lt;/button&amp;gt;
  )
}

export default function UserForm() {
  return (
    &amp;lt;form action={createUser} className="space-y-4"&amp;gt;
      &amp;lt;div&amp;gt;
        &amp;lt;label htmlFor="name" className="block mb-1"&amp;gt;Name:&amp;lt;/label&amp;gt;
        &amp;lt;input 
          type="text" 
          id="name" 
          name="name" 
          required
          className="border p-2 rounded w-full"
        /&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div&amp;gt;
        &amp;lt;label htmlFor="email" className="block mb-1"&amp;gt;Email:&amp;lt;/label&amp;gt;
        &amp;lt;input 
          type="email" 
          id="email" 
          name="email" 
          required
          className="border p-2 rounded w-full"
        /&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;SubmitButton /&amp;gt;
    &amp;lt;/form&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create &lt;code&gt;src/app/components/UserListItem.js&lt;/code&gt;:&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 { useState } from 'react'
import { updateUser, deleteUser } from '@/app/actions/users'
import { useFormStatus } from 'react-dom'

function Button({ children, ...props }) {
  const { pending } = useFormStatus()

  return (
    &amp;lt;button 
      {...props}
      disabled={pending}
      className={`px-3 py-1 rounded text-white disabled:opacity-50 ${props.className}`}
    &amp;gt;
      {pending ? 'Loading...' : children}
    &amp;lt;/button&amp;gt;
  )
}

export default function UserListItem({ user }) {
  const [isEditing, setIsEditing] = useState(false)

  if (isEditing) {
    return (
      &amp;lt;li className="p-4 bg-gray-100 rounded"&amp;gt;
        &amp;lt;form action={updateUser} className="space-y-2"&amp;gt;
          &amp;lt;input type="hidden" name="id" value={user.id} /&amp;gt;
          &amp;lt;input
            type="text"
            name="name"
            defaultValue={user.name}
            className="border p-2 rounded w-full"
            required
          /&amp;gt;
          &amp;lt;input
            type="email"
            name="email"
            defaultValue={user.email}
            className="border p-2 rounded w-full"
            required
          /&amp;gt;
          &amp;lt;div className="space-x-2"&amp;gt;
            &amp;lt;Button 
              type="submit"
              className="bg-green-500 hover:bg-green-600"
            &amp;gt;
              Save
            &amp;lt;/Button&amp;gt;
            &amp;lt;button
              type="button"
              onClick={() =&amp;gt; setIsEditing(false)}
              className="bg-gray-500 text-white px-3 py-1 rounded hover:bg-gray-600"
            &amp;gt;
              Cancel
            &amp;lt;/button&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/form&amp;gt;
      &amp;lt;/li&amp;gt;
    )
  }

  return (
    &amp;lt;li className="p-4 bg-gray-100 rounded flex justify-between items-center"&amp;gt;
      &amp;lt;div&amp;gt;
        {user.name} ({user.email})
      &amp;lt;/div&amp;gt;
      &amp;lt;div className="space-x-2"&amp;gt;
        &amp;lt;button
          onClick={() =&amp;gt; setIsEditing(true)}
          className="bg-blue-500 text-white px-3 py-1 rounded hover:bg-blue-600"
        &amp;gt;
          Edit
        &amp;lt;/button&amp;gt;
        &amp;lt;form action={deleteUser} className="inline"&amp;gt;
          &amp;lt;input type="hidden" name="id" value={user.id} /&amp;gt;
          &amp;lt;Button 
            type="submit"
            className="bg-red-500 hover:bg-red-600"
            onClick={(e) =&amp;gt; {
              if (!confirm('Are you sure?')) {
                e.preventDefault()
              }
            }}
          &amp;gt;
            Delete
          &amp;lt;/Button&amp;gt;
        &amp;lt;/form&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/li&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 6: Create Main Page&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;src/app/page.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { getUsers } from './actions/users'
import UserForm from './components/UserForm'
import UserListItem from './components/UserListItem'

export default async function Home() {
  const { users, error } = await getUsers()

  if (error) {
    return &amp;lt;div className="p-4 text-red-500"&amp;gt;Error: {error}&amp;lt;/div&amp;gt;
  }

  return (
    &amp;lt;main className="p-4 max-w-4xl mx-auto"&amp;gt;
      &amp;lt;h1 className="text-2xl font-bold mb-4"&amp;gt;Users Management&amp;lt;/h1&amp;gt;

      &amp;lt;div className="mb-8"&amp;gt;
        &amp;lt;h2 className="text-xl mb-2"&amp;gt;Current Users:&amp;lt;/h2&amp;gt;
        {users?.length &amp;gt; 0 ? (
          &amp;lt;ul className="space-y-2"&amp;gt;
            {users.map((user) =&amp;gt; (
              &amp;lt;UserListItem key={user.id} user={user} /&amp;gt;
            ))}
          &amp;lt;/ul&amp;gt;
        ) : (
          &amp;lt;p className="text-gray-500"&amp;gt;No users found.&amp;lt;/p&amp;gt;
        )}
      &amp;lt;/div&amp;gt;

      &amp;lt;div className="mt-8"&amp;gt;
        &amp;lt;h2 className="text-xl mb-2"&amp;gt;Add New User:&amp;lt;/h2&amp;gt;
        &amp;lt;UserForm /&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/main&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 7: Create Prisma Instance&lt;/strong&gt;&lt;br&gt;
Create &lt;code&gt;src/lib/prisma.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { PrismaClient } from '@prisma/client'

const globalForPrisma = global

if (!globalForPrisma.prisma) {
  globalForPrisma.prisma = new PrismaClient()
}

export const prisma = globalForPrisma.prisma
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Features Implemented&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create new users with name and email&lt;/li&gt;
&lt;li&gt;Display list of all users&lt;/li&gt;
&lt;li&gt;Edit existing users with inline form&lt;/li&gt;
&lt;li&gt;Delete users with confirmation&lt;/li&gt;
&lt;li&gt;Server-side data validation&lt;/li&gt;
&lt;li&gt;Optimistic UI updates&lt;/li&gt;
&lt;li&gt;Loading states for all actions&lt;/li&gt;
&lt;li&gt;Error handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Follow Github Code&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;https://github.com/abdur-rakib-rony/postgres-and-prisma-nextjs-crud-operation&lt;/code&gt;&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>nextjs</category>
      <category>prisma</category>
    </item>
    <item>
      <title>Implementing Afterpay in a Next.js E-commerce Application: A Complete Guide</title>
      <dc:creator>Abdur Rakib Rony</dc:creator>
      <pubDate>Sat, 09 Nov 2024 02:49:28 +0000</pubDate>
      <link>https://dev.to/abdur_rakibrony_349a3f89/implementing-afterpay-in-a-nextjs-e-commerce-application-a-complete-guide-2m24</link>
      <guid>https://dev.to/abdur_rakibrony_349a3f89/implementing-afterpay-in-a-nextjs-e-commerce-application-a-complete-guide-2m24</guid>
      <description>&lt;p&gt;As e-commerce continues to evolve, offering flexible payment options has become crucial for business success. In this guide, I'll walk you through implementing Afterpay in a Next.js e-commerce application, sharing real-world code examples and best practices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Afterpay?&lt;/strong&gt;&lt;br&gt;
Afterpay is a "buy now, pay later" service that allows customers to split their purchases into four equal installments, paid every two weeks. It's particularly popular in Australia, New Zealand, the United States, and the United Kingdom.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;&lt;br&gt;
Before we begin, make sure you have:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A Next.js application&lt;/li&gt;
&lt;li&gt;An Afterpay merchant account&lt;/li&gt;
&lt;li&gt;Basic understanding of React and API integration&lt;/li&gt;
&lt;li&gt;Axios for API calls&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Implementation Overview&lt;/strong&gt;&lt;br&gt;
Our implementation will cover:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Configuring Afterpay credentials&lt;/li&gt;
&lt;li&gt;Setting up the API client&lt;/li&gt;
&lt;li&gt;Creating the Afterpay component&lt;/li&gt;
&lt;li&gt;Handling checkout flow&lt;/li&gt;
&lt;li&gt;Managing payment confirmation&lt;/li&gt;
&lt;li&gt;Error handling and validation&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;1. Setting Up Afterpay Configuration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First, let's set up our Afterpay API client. Create a file &lt;code&gt;lib/afterpay.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

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

const AFTERPAY_BASE_URL = "https://global-api-sandbox.afterpay.com/v2";
const AFTERPAY_MERCHANT_ID = "YOUR_MERCHANT_ID";
const AFTERPAY_SECRET_KEY = "YOUR_SECRET_KEY";

// Create base64 encoded auth token
const authToken = Buffer.from(
  `${AFTERPAY_MERCHANT_ID}:${AFTERPAY_SECRET_KEY}`
).toString("base64");

// Create axios instance with default config
export const afterpayApi = axios.create({
  baseURL: AFTERPAY_BASE_URL,
  headers: {
    "Content-Type": "application/json",
    Accept: "application/json",
    Authorization: `Basic ${authToken}`,
    "User-Agent": `Merchant/${AFTERPAY_MERCHANT_ID}`,
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Creating Helper Functions&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Get Afterpay configuration
export const getAfterpayConfig = async () =&amp;gt; {
  try {
    const { data } = await afterpayApi.get("/configuration");
    return data;
  } catch (error) {
    console.error("Afterpay config error:", error.response?.data || error.message);
    throw error;
  }
};

// Format cart items for Afterpay
export const formatAfterpayItems = (cartItems) =&amp;gt; {
  return cartItems.map((item) =&amp;gt; ({
    name: item.title,
    sku: item._id.toString(),
    quantity: item.quantity,
    price: {
      amount: item.price.toString(),
      currency: "AUD",
    },
    categories: [[item.category, item.subCategory]],
    pageUrl: `/products/${item.handle}`,
    imageUrl: item.featured_image,
  }));
};

// Check if order amount is eligible for Afterpay
export const isOrderEligibleForAfterpay = (amount, config) =&amp;gt; {
  if (!config) return false;
  const min = parseFloat(config.minimumAmount.amount);
  const max = parseFloat(config.maximumAmount.amount);
  return amount &amp;gt;= min &amp;amp;&amp;amp; amount &amp;lt;= max;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Creating the Afterpay Component&lt;/strong&gt;&lt;br&gt;
Here's our React component for the Afterpay payment button:&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 React, { useEffect, useState } from "react";
import Image from "next/image";

export default function Afterpay({ paymentData }) {
  const [config, setConfig] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  // Format checkout data for Afterpay
  const formatCheckoutData = () =&amp;gt; {
    const items = formatAfterpayItems(paymentData.orderProducts);

    return {
      amount: {
        amount: paymentData.total.toString(),
        currency: "AUD",
      },
      consumer: {
        phoneNumber: paymentData.shippingAddress.phone,
        givenNames: paymentData.shippingAddress.firstName,
        surname: paymentData.shippingAddress.lastName,
        email: paymentData.shippingAddress.email,
      },
      shipping: {
        name: `${paymentData.shippingAddress.firstName} ${paymentData.shippingAddress.lastName}`,
        line1: paymentData.shippingAddress.address,
        area1: paymentData.shippingAddress.suburb,
        region: paymentData.shippingAddress.state,
        postcode: paymentData.shippingAddress.postcode,
        countryCode: "AU",
        phoneNumber: paymentData.shippingAddress.phone,
      },
      merchant: {
        redirectConfirmUrl: `${YOUR_DOMAIN}/confirm`,
        redirectCancelUrl: `${YOUR_DOMAIN}/cancel`,
        popupOriginUrl: YOUR_DOMAIN,
      },
      items,
      merchantReference: paymentData.orderId,
    };
  };

  // Handle checkout process
  const handleCheckout = async () =&amp;gt; {
    if (!config) return;

    setLoading(true);
    setError(null);

    try {
      const checkoutData = formatCheckoutData();
      const response = await fetch("/api/afterpay", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify(checkoutData),
      });

      const data = await response.json();

      if (data.error) {
        throw new Error(data.error);
      }

      if (data.redirectCheckoutUrl) {
        // Store data for confirmation
        sessionStorage.setItem("afterpayToken", data.token);
        sessionStorage.setItem("afterpayAmount", paymentData.total.toString());
        sessionStorage.setItem("afterpayOrderId", paymentData.orderId);

        // Redirect to Afterpay checkout
        window.location.href = data.redirectCheckoutUrl;
      }
    } catch (error) {
      setError("Unable to initiate Afterpay checkout. Please try again.");
    } finally {
      setLoading(false);
    }
  };

  return (
    &amp;lt;div className="w-full"&amp;gt;
      &amp;lt;button
        onClick={handleCheckout}
        disabled={loading || !isEligible}
        className="w-full border flex items-center justify-center rounded-lg p-3 px-5 bg-primary-color text-white"
      &amp;gt;
        {loading ? (
          "Processing..."
        ) : (
          &amp;lt;div className="flex items-center gap-2"&amp;gt;
            &amp;lt;span&amp;gt;Pay with&amp;lt;/span&amp;gt;
            &amp;lt;Image
              src="/afterpay.png"
              alt="Afterpay"
              width={100}
              height={20}
              className="h-5 w-auto object-contain"
            /&amp;gt;
          &amp;lt;/div&amp;gt;
        )}
      &amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Setting Up API Routes&lt;/strong&gt;&lt;br&gt;
Create an API route for handling Afterpay requests &lt;code&gt;(app/api/afterpay/route.js)&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { NextResponse } from "next/server";
import { createCheckout, getAfterpayConfig } from "@/lib/afterpay";

export async function GET() {
  try {
    const config = await getAfterpayConfig();
    return NextResponse.json(config);
  } catch (error) {
    return NextResponse.json(
      { error: error.response?.data?.message || error.message },
      { status: error.response?.status || 500 }
    );
  }
}

export async function POST(request) {
  try {
    const checkoutData = await request.json();

    // Validate required fields
    const requiredFields = ["amount", "consumer", "shipping", "merchant"];
    const missingFields = requiredFields.filter(
      (field) =&amp;gt; !checkoutData[field]
    );

    if (missingFields.length &amp;gt; 0) {
      return NextResponse.json(
        {
          error: "Missing required fields",
          details: `Missing: ${missingFields.join(", ")}`,
        },
        { status: 400 }
      );
    }

    const response = await createCheckout(checkoutData);
    return NextResponse.json(response);
  } catch (error) {
    return NextResponse.json(
      { error: error.response?.data?.message || error.message },
      { status: error.response?.status || 500 }
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. Handling Payment Confirmation&lt;/strong&gt;&lt;br&gt;
Create a confirmation page &lt;code&gt;(app/confirm/page.js)&lt;/code&gt; to handle successful payments:&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";

export default function ConfirmPage() {
  const searchParams = useSearchParams();
  const [processing, setProcessing] = useState(true);
  const [paymentDetails, setPaymentDetails] = useState(null);

  useEffect(() =&amp;gt; {
    const handleConfirmation = async () =&amp;gt; {
      try {
        const status = searchParams.get("status");
        const orderToken = searchParams.get("orderToken");

        if (status === "SUCCESS" &amp;amp;&amp;amp; orderToken) {
          const amount = sessionStorage.getItem("afterpayAmount");
          const merchantReference = sessionStorage.getItem("afterpayOrderId");

          const response = await fetch("/api/afterpay/capture", {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify({
              orderToken,
              amount,
              merchantReference,
            }),
          });

          if (!response.ok) {
            throw new Error("Payment capture failed");
          }

          const data = await response.json();
          setPaymentDetails(data);
        }
      } catch (error) {
        console.error("Confirmation error:", error);
      } finally {
        setProcessing(false);
      }
    };

    handleConfirmation();
  }, [searchParams]);

  // Render confirmation UI
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Best Practices and Tips&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Error Handling: Always implement comprehensive error handling at every step of the payment process.&lt;/li&gt;
&lt;li&gt;Validation: Validate all data before sending it to Afterpay to avoid failed API calls.&lt;/li&gt;
&lt;li&gt;Security: Never expose your Afterpay credentials in client-side code.&lt;/li&gt;
&lt;li&gt;Testing: Use Afterpay's sandbox environment extensively before going live.&lt;/li&gt;
&lt;li&gt;User Experience: Provide clear feedback during the payment process and handle edge cases gracefully.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>nextjs</category>
      <category>afterpay</category>
      <category>payment</category>
    </item>
    <item>
      <title>Implementing Hosted Checkout with ANZ Worldline Payment Solutions in Next.js</title>
      <dc:creator>Abdur Rakib Rony</dc:creator>
      <pubDate>Thu, 26 Sep 2024 20:25:40 +0000</pubDate>
      <link>https://dev.to/abdur_rakibrony_349a3f89/implementing-hosted-checkout-with-anz-worldline-payment-solutions-in-nextjs-597</link>
      <guid>https://dev.to/abdur_rakibrony_349a3f89/implementing-hosted-checkout-with-anz-worldline-payment-solutions-in-nextjs-597</guid>
      <description>&lt;p&gt;As developers, integrating secure and efficient payment solutions into our applications is crucial. In this post, we'll walk through implementing a hosted checkout solution using ANZ Worldline Payment Solutions in a Next.js application. This approach offers a seamless payment experience while maintaining high security standards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Hosted Checkout?&lt;/strong&gt;&lt;br&gt;
Hosted Checkout is a payment solution where the payment form is hosted by the payment provider. This method simplifies PCI DSS compliance as sensitive card data is handled directly by the provider's secure systems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A Next.js application&lt;br&gt;
An account with ANZ Worldline Payment Solutions&lt;br&gt;
Basic knowledge of React and API routes in Next.js&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setting Up the Project&lt;/strong&gt;&lt;br&gt;
First, let's set up our Next.js project structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
  app/
    api/
      create-hosted-checkout/
        route.js
      payment-details/
        [paymentId]/
          route.js
    checkout/
      page.jsx
    payment-result/
      page.jsx
    page.jsx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Implementing the Hosted Checkout&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create the Hosted Checkout API Route
&lt;em&gt;&lt;code&gt;In src/app/api/create-hosted-checkout/route.js:&lt;/code&gt;&lt;/em&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { NextResponse } from "next/server";
const onlinePaymentsSdk = require("onlinepayments-sdk-nodejs");

const client = onlinePaymentsSdk.init({
    host: 'payment.preprod.anzworldline-solutions.com.au',
    apiKeyId: 'YOUR_API_KEY_ID',
    secretApiKey: 'YOUR_SECRET_API_KEY',
    integrator: 'OnlinePayments',
});

const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000';

export async function POST(request) {
    const createHostedCheckoutRequest = {
        order: {
            amountOfMoney: {
                amount: 10 * 100, // 1000 cents AUD = $10
                currencyCode: 'AUD',
            },
        },
        hostedCheckoutSpecificInput: {
            returnUrl: `${baseUrl}/payment-result`,
        },
    };

    try {
        const response = await client.hostedCheckout.createHostedCheckout(
            'YOUR_MERCHANT_ID',
            createHostedCheckoutRequest,
            null
        );

        return NextResponse.json(response);
    } catch (error) {
        console.error("Error creating payment:", error);
        return NextResponse.json(
            { error: "Failed to create payment", details: error.message },
            { status: 500 }
        );
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Implement the Checkout Page
&lt;em&gt;&lt;code&gt;In src/app/checkout/page.jsx:&lt;/code&gt;&lt;/em&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"use client";
import React, { useState, useEffect } from "react";
import { CreditCard } from "lucide-react";

export default function CheckoutPage() {
    const [res, setRes] = useState(null);

    useEffect(() =&amp;gt; {
        if (res &amp;amp;&amp;amp; res.body &amp;amp;&amp;amp; res.body.redirectUrl) {
            window.location.href = res.body.redirectUrl;
        }
    }, [res]);

    const submitForm = async () =&amp;gt; {
        try {
            const response = await fetch("/api/create-hosted-checkout", {
                method: "POST",
            });
            const data = await response.json();
            setRes(data);
        } catch (error) {
            console.error("Error during checkout:", error);
        }
    };

    return (
        &amp;lt;div className="min-h-screen py-6 flex flex-col justify-center items-center container mx-auto"&amp;gt;
            &amp;lt;button
                className="border flex items-center rounded-lg p-2 bg-fuchsia-500 font-semibold text-white hover:bg-fuchsia-600 transition-colors"
                onClick={submitForm}
            &amp;gt;
                &amp;lt;CreditCard className="mr-2 h-5 w-5" /&amp;gt; Checkout
            &amp;lt;/button&amp;gt;
            {res &amp;amp;&amp;amp; (
                &amp;lt;div className="border rounded-lg p-10 mt-10 break-words max-w-full overflow-x-auto"&amp;gt;
                    &amp;lt;p&amp;gt;Redirecting to payment page...&amp;lt;/p&amp;gt;
                    &amp;lt;pre className="mt-4 text-sm"&amp;gt;
                        {JSON.stringify(res, null, 2)}
                    &amp;lt;/pre&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;3. Handle Payment Result&lt;/strong&gt;&lt;br&gt;
Create an API route to fetch payment details:&lt;br&gt;
&lt;em&gt;&lt;code&gt;In src/app/api/payment-details/[paymentId]/route.js:&lt;/code&gt;&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { NextResponse } from "next/server";
const onlinePaymentsSdk = require("onlinepayments-sdk-nodejs");

const client = onlinePaymentsSdk.init({
    host: 'payment.preprod.anzworldline-solutions.com.au',
    apiKeyId: 'YOUR_API_KEY_ID',
    secretApiKey: 'YOUR_SECRET_API_KEY',
    integrator: 'OnlinePayments',
});

export async function GET(request, { params }) {
    const hostedCheckoutId = params.paymentId;

    if (!hostedCheckoutId) {
        return NextResponse.json(
            { error: "Hosted Checkout ID is required" },
            { status: 400 }
        );
    }

    try {
        const hostedCheckoutStatus = await client.hostedCheckout.getHostedCheckout(
            'YOUR_MERCHANT_ID',
            hostedCheckoutId,
            null
        );

        return NextResponse.json(hostedCheckoutStatus);
    } catch (error) {
        console.error("Error getting payment details:", error);
        return NextResponse.json(
            { error: "Failed to get payment details", details: error.message },
            { status: 500 }
        );
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, create a page to display the payment result:&lt;br&gt;
&lt;em&gt;&lt;code&gt;In src/app/payment-result/page.jsx:&lt;/code&gt;&lt;/em&gt;&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 React, { useEffect, useState } from "react";
import { useSearchParams } from "next/navigation";

export default function PaymentResultPage() {
    const [paymentDetails, setPaymentDetails] = useState(null);
    const [error, setError] = useState(null);
    const searchParams = useSearchParams();
    const hostedCheckoutId = searchParams.get("hostedCheckoutId");

    useEffect(() =&amp;gt; {
        if (hostedCheckoutId) {
            fetch(`/api/payment-details/${hostedCheckoutId}`)
                .then((response) =&amp;gt; {
                    if (!response.ok) {
                        throw new Error('Network response was not ok');
                    }
                    return response.json();
                })
                .then((data) =&amp;gt; setPaymentDetails(data))
                .catch((error) =&amp;gt; {
                    console.error("Error fetching payment details:", error);
                    setError("Failed to fetch payment details. Please try again.");
                });
        }
    }, [hostedCheckoutId]);

    if (error) {
        return &amp;lt;div className="text-red-500"&amp;gt;{error}&amp;lt;/div&amp;gt;;
    }

    if (!paymentDetails) {
        return &amp;lt;div&amp;gt;Loading payment details...&amp;lt;/div&amp;gt;;
    }

    return (
        &amp;lt;div className="p-4"&amp;gt;
            &amp;lt;h1 className="text-2xl font-bold mb-4"&amp;gt;Payment Result&amp;lt;/h1&amp;gt;
            &amp;lt;pre className="bg-gray-100 p-4 rounded overflow-x-auto"&amp;gt;
                {JSON.stringify(paymentDetails, null, 2)}
            &amp;lt;/pre&amp;gt;
        &amp;lt;/div&amp;gt;
    );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
With these components in place, you've successfully implemented a hosted checkout solution using ANZ Worldline Payment Solutions in your Next.js application. This approach provides a secure and seamless payment experience for your users while simplifying PCI DSS compliance for your application.&lt;br&gt;
Remember to replace placeholder values like YOUR_API_KEY_ID, YOUR_SECRET_API_KEY, and YOUR_MERCHANT_ID with your actual credentials from ANZ Worldline Payment Solutions.&lt;br&gt;
By leveraging hosted checkout, you can focus on building great features for your application while leaving the complexities of payment processing to the experts.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>anzworldlinepayment</category>
      <category>react</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
