<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Pritam Ghosh on Medium]]></title>
        <description><![CDATA[Stories by Pritam Ghosh on Medium]]></description>
        <link>https://medium.com/@pritamfinds?source=rss-f340e460bba3------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*hjqPIUBIuWMqNn4-</url>
            <title>Stories by Pritam Ghosh on Medium</title>
            <link>https://medium.com/@pritamfinds?source=rss-f340e460bba3------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Thu, 05 Nov 2026 21:37:15 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@pritamfinds/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[I Built an Anti-Hype Launch Platform for Solo Founders. Then Vercel Paused It.]]></title>
            <link>https://medium.com/@pritamfinds/i-built-an-anti-hype-launch-platform-for-solo-founders-then-vercel-paused-it-52edbabbdb89?source=rss-f340e460bba3------2</link>
            <guid isPermaLink="false">https://medium.com/p/52edbabbdb89</guid>
            <category><![CDATA[startup]]></category>
            <category><![CDATA[indiehackers]]></category>
            <category><![CDATA[nextjs]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[cloudflare]]></category>
            <dc:creator><![CDATA[Pritam Ghosh]]></dc:creator>
            <pubDate>Wed, 04 Nov 2026 12:51:06 GMT</pubDate>
            <atom:updated>2026-11-04T12:51:06.367Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*idRCYdOrTmbDX_IUUWUHgA.png" /></figure><h3>The story of building Hauntboard, hitting 1.2 million reads on the free tier, migrating to Cloudflare for $0, and reaching our first 20 products.</h3><p>A few months ago, I was talking to an indie friend who had just finished building his first SaaS app.</p><p>He had spent three months coding it late at night after his day job. He skipped weekends. He obsessed over every button and database query.</p><p>Then launch day came.</p><p>He spent 24 straight hours begging people in WhatsApp groups and Discord channels to upvote his project. He posted on Twitter every two hours. By midnight, he won a shiny badge on a popular launch site.</p><p>The next morning, his traffic dropped right back to zero.</p><p>He had spent three months building software, one day begging for votes, and made exactly $0. Two days later, his product was buried under 500 new submissions, never to be seen again.</p><p>That broke my heart. And it made me realize something painful: <strong>the way we launch indie products today is broken.</strong></p><h3>The problem with the 24-hour hype cycle</h3><p>Most launch platforms are built for teams with big Twitter followings, marketing budgets, and upvote cartels.</p><p>If you are a solo maker with 80 followers on X, you have almost no chance. You launch, nobody sees it, and you assume your product is bad.</p><p>In reality, your product wasn’t bad. <strong>It was just invisible.</strong></p><p>I wanted to fix that. So I started building <a href="https://hauntboard.com"><strong>Hauntboard</strong></a>.</p><p>The idea was simple:</p><ol><li><strong>7 days on the board, not 24 hours.</strong> Give people a full week to actually browse, click through, and test what you made.</li><li><strong>Human review.</strong> A real person looks at every single submission to filter out crypto scams and abandoned templates.</li><li><strong>Permanent dofollow links.</strong> When you launch, your page stays indexed permanently so Google and AI search engines (like Perplexity and ChatGPT) can find you for months.</li><li><strong>Public outbound clicks.</strong> No fake upvote counters. If Hauntboard sends 4 visitors to your site, the page says 4. If it sends 80, it says 80.</li></ol><p>Our motto was simple:</p><blockquote>“Don’t be a ghost. Show what you built.”</blockquote><h3>The morning the site disappeared</h3><p>I launched the first version of Hauntboard built on Next.js 16 and Convex, hosted on Vercel’s free tier.</p><p>Makers started trickling in. People submitted productivity tools, image converters, and developer utilities. Search engine bots started indexing all the launch pages.</p><p>Then came November 2.</p><p>I opened my phone to check the board, and instead of the homepage, I saw a black screen with four words:</p><blockquote><strong><em>“This page doesn’t exist. 404 DEPLOYMENT_NOT_FOUND.”</em></strong></blockquote><p>I rushed to my computer and logged into Vercel. There was a big red banner across my dashboard:</p><p><em>“Paused Nov 2 · Fast Origin Transfer · Upgrade to resume service.”</em></p><p>Because of all the pages being indexed and visited, the site had hit <strong>1.2 million ISR reads</strong> and exceeded the 10 GB bandwidth limit.</p><p>Vercel’s message was polite but firm: <em>pay up or stay offline.</em></p><h3>Why I refused to pay</h3><p>For a funded startup, a $20/month hosting bill is nothing.</p><p>But for a solo founder building a free community tool that makes $0 in revenue, that bill is a trap. Once you upgrade, you’re on a treadmill of paying hosting bills for traffic that isn’t paying you back yet.</p><p>I made a rule when I started building: <strong>never let infrastructure hold your indie project hostage.</strong></p><p>Instead of pulling out my credit card, I grabbed a cup of coffee and decided to migrate the entire application to <strong>Cloudflare Workers</strong> using OpenNext.</p><p>It wasn’t easy. Cloudflare’s edge runtime has strict compatibility rules. I had to fix node module shims, rebuild the deployment pipeline, and wire up custom domains.</p><p>By the end of the night:</p><ul><li>The site was back online.</li><li>Pages load in milliseconds across Cloudflare’s global edge.</li><li>Monthly hosting cost: <strong>$0</strong>.</li></ul><h3>20 launches and counting</h3><p>Today, Hauntboard reached a milestone I’m quietly proud of: <strong>20 indie products are live on the board.</strong></p><p>These aren’t venture-backed apps with $50,000 ad budgets. They are real tools built by solo makers:</p><ul><li>A tool that turns bank statements into spreadsheets locally without uploading private data.</li><li>A free image tool that never stores your files.</li><li>A tool that color-grades videos directly in the browser.</li></ul><p>Our total numbers right now are small: 20 launches, 9 verified outbound human visits, and 0 fake upvotes.</p><p>In the startup world, people love to brag about vanity metrics: <em>“10,000 signups in 48 hours!”</em> But behind closed doors, half of those numbers are bots, family members, or people swapping favors in group chats.</p><p>Small, honest numbers beat inflated vanity badges every single day.</p><h3>My goal: 50 before December</h3><p>My goal is to get <strong>50 indie products</strong> onto Hauntboard before December.</p><p>If you’re a solo developer, designer, or maker building something in your spare time, don’t let your work sit quietly in a private GitHub repository.</p><p>Don’t spend 24 hours begging for upvotes in Slack channels just to watch your traffic vanish the next morning.</p><p>Come put it on the board. A real person will review it, it will stay on the front page for 7 full days, and every single visit we send you will be counted in public.</p><p>You can submit it for free here:<br> 👉 <a href="https://hauntboard.com/submit"><strong>hauntboard.com/submit</strong></a></p><p>Don’t be a ghost. Show what you built.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=52edbabbdb89" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[10 Things I Learned From My Biggest Startup Failures]]></title>
            <link>https://medium.com/@pritamfinds/10-things-i-learned-from-my-biggest-startup-failures-ba0e622deca4?source=rss-f340e460bba3------2</link>
            <guid isPermaLink="false">https://medium.com/p/ba0e622deca4</guid>
            <category><![CDATA[startupş]]></category>
            <category><![CDATA[failure]]></category>
            <category><![CDATA[entrepreneurship]]></category>
            <category><![CDATA[indiehackers]]></category>
            <category><![CDATA[entrepreneur]]></category>
            <dc:creator><![CDATA[Pritam Ghosh]]></dc:creator>
            <pubDate>Wed, 14 Jan 2026 13:54:53 GMT</pubDate>
            <atom:updated>2026-01-14T13:54:53.469Z</atom:updated>
            <content:encoded><![CDATA[<p><em>Real lessons from products that flopped, launches that bombed, and decisions I regret</em></p><p>In 3 years of building products, I’ve failed more times than I’ve succeeded.</p><p>Here are the most expensive lessons, so you don’t have to pay for them too.</p><h3>Failure #1: Building for 6 Months Without Talking to Users</h3><p><strong>What happened:</strong> I spent 6 months building a “perfect” MVP. Beautiful design. Clean code. Every feature I could think of.</p><p>Launched to zero customers.</p><p><strong>The lesson:</strong> Build the smallest possible thing that solves a real problem. Then talk to users. Then iterate.</p><p>I could have saved 5 months by launching a landing page first.</p><h3>Failure #2: Competing on Price</h3><p><strong>What happened:</strong> My competitor charged $49/month. I thought I’d win by charging $19/month.</p><p>I attracted price-sensitive customers who churned immediately and complained constantly.</p><p><strong>The lesson:</strong> Competing on price is a race to the bottom. If your only value proposition is “cheaper,” you don’t have a value proposition.</p><p>Now I price premium and attract customers who value quality over cost.</p><h3>Failure #3: Saying Yes to Every Feature Request</h3><p><strong>What happened:</strong> Customer asked for a feature. I built it. Another customer asked for something different. Built that too.</p><p>Six months later, I had a Frankenstein product that did everything poorly and nothing well.</p><p><strong>The lesson:</strong> Feature requests are data, not commands. Look for patterns. One request might be noise. Ten requests for the same thing is signal.</p><h3>Failure #4: Ignoring Churn Until It Was Too Late</h3><p><strong>What happened:</strong> I was so focused on acquisition that I ignored my churn rate. I thought growth would solve everything.</p><p>It didn’t. I was filling a leaky bucket.</p><p><strong>The lesson:</strong> Churn is the silent killer. Track it religiously. Fix it before you focus on growth.</p><p>A 5% monthly churn rate sounds small. It means you lose 46% of your customers every year.</p><h3>Failure #5: The “Launch on Product Hunt” Fantasy</h3><p><strong>What happened:</strong> I thought launching on Product Hunt would make or break my product. I obsessed over the launch day, coordinated votes, wrote the perfect copy.</p><p>Got 200 upvotes. 50 signups. 2 conversions. Both churned within a month.</p><p><strong>The lesson:</strong> Product Hunt is a event, not a strategy. It’s good for a boost, but it won’t save a mediocre product or replace consistent marketing.</p><p>Build distribution before you need it.</p><h3>Failure #6: Solo Founding When I Needed Help</h3><p><strong>What happened:</strong> I thought I could do everything myself. Code. Design. Marketing. Sales. Support.</p><p>I burned out within a year.</p><p><strong>The lesson:</strong> Know your limits. Either find a cofounder who complements your skills, or outsource what you’re bad at.</p><p>The “solopreneur” fantasy is often just a recipe for exhaustion.</p><h3>Failure #7: Waiting Too Long to Charge</h3><p><strong>What happened:</strong> I offered my product for free for 8 months to “build an audience.” When I finally added pricing, users revolted.</p><p>Turns out, people who sign up for free and people who pay are different markets.</p><p><strong>The lesson:</strong> Charge from day one. Even if it’s $5. You want to attract customers who value your product enough to pay for it.</p><p>Free users teach you how to serve free users. Paying customers teach you how to build a business.</p><h3>Failure #8: Building for Myself, Not the Market</h3><p><strong>What happened:</strong> I built a tool I personally wanted. I was convinced others wanted it too.</p><p>They didn’t.</p><p><strong>The lesson:</strong> You are not your market. Validate demand before building. Talk to potential customers. Look for people actively trying to solve the problem.</p><p>If you can’t find 10 people who want to pay, reconsider the idea.</p><h3>Failure #9: Overcomplicating the Tech Stack</h3><p><strong>What happened:</strong> I used the latest frameworks, microservices, Kubernetes, the works. For an app with 12 users.</p><p>I spent more time maintaining infrastructure than building features.</p><p><strong>The lesson:</strong> Boring tech scales. Use the simplest stack that solves your problem. You can always migrate later when you have the “good problem” of too many users.</p><p>A Rails/Next.js/Django monolith will take you to $1M ARR. Microservices can wait.</p><h3>Failure #10: Not Celebrating Small Wins</h3><p><strong>What happened:</strong> I hit $100 MRR. Didn’t celebrate. Hit $500 MRR. Didn’t celebrate. Hit $1,000 MRR. Thought “still not enough.”</p><p>I was miserable at every stage because the goalpost kept moving.</p><p><strong>The lesson:</strong> Celebrate everything. First user. First dollar. First bad review (it means people are using it!). First failure.</p><p>This is why I built <a href="https://hackerbadges.com">HackerBadges</a>. To create a ritual of celebration. To make progress feel real.</p><p>And yes, we even have <a href="https://hackerbadges.com/generate/failure">Failure Badges</a>. Because surviving failure is itself an achievement.</p><h3>The Meta Lesson</h3><p>Failure is the tuition fee for success. Every failed product taught me something I couldn’t have learned any other way.</p><p>The founders who succeed aren’t the ones who avoid failure. They’re the ones who fail faster, learn quicker, and keep going.</p><p>So if you’re in the middle of a failure right now: good. You’re learning. You’re growing. You’re doing it right.</p><p><strong>Celebrate your failures with us:</strong></p><p><a href="https://hackerbadges.com/generate/failure">Create a Failure Badge →</a></p><p><em>Building HackerBadges in public. Follow: </em><a href="https://twitter.com/iPritamX"><em>@iPritamX</em></a></p><p><strong>Tags:</strong> Startups, Failure, Lessons Learned, Entrepreneurship, Indie Hackers</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ba0e622deca4" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Failure Badges Manifesto: Why Your L’s Deserve Recognition]]></title>
            <link>https://medium.com/@pritamfinds/the-failure-badges-manifesto-why-your-ls-deserve-recognition-2b07c73ca701?source=rss-f340e460bba3------2</link>
            <guid isPermaLink="false">https://medium.com/p/2b07c73ca701</guid>
            <category><![CDATA[startup]]></category>
            <category><![CDATA[entrepreneurship]]></category>
            <category><![CDATA[build-in-public]]></category>
            <category><![CDATA[indiehackers]]></category>
            <dc:creator><![CDATA[Pritam Ghosh]]></dc:creator>
            <pubDate>Mon, 12 Jan 2026 14:59:52 GMT</pubDate>
            <atom:updated>2026-01-12T14:59:52.878Z</atom:updated>
            <content:encoded><![CDATA[<p><em>A hot take on startup culture’s biggest problem</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IH-fiREYDkneWhZak6P8jQ.png" /></figure><p>I’m going to say something controversial.</p><p>The most successful founders I know are the ones who proudly display their failures.</p><p>Not hide them. Display them.</p><h3>The Toxic Positivity of Startup Twitter</h3><p>Scroll through any indie hacker timeline. What do you see?</p><ul><li>“$50K MRR! 🚀”</li><li>“Quit my job! Living the dream! 🎉”</li><li>“Just raised $2M! Let’s go! 💪”</li></ul><p>It’s an endless highlight reel. Everyone’s winning. All the time. Forever.</p><p>Here’s the problem: <strong>it’s complete bullshit</strong>.</p><p>Behind every “$50K MRR” tweet, there were probably:</p><ul><li>3 failed products</li><li>2 years of zero revenue</li><li>1 near-bankruptcy</li><li>Countless sleepless nights</li><li>A partner who almost left</li><li>A mental breakdown or two</li></ul><p>But we don’t tweet about those. Because failure is embarrassing. Because it doesn’t get likes. Because we’re performing success instead of living it.</p><h3>The Rise of Failure Culture</h3><p>Something is shifting, though. The smartest founders are starting to share their L’s publicly:</p><ul><li>Pieter Levels talking about products that flopped</li><li>Sahil Lavingia writing about Gumroad’s struggles</li><li>Indie hackers sharing “$0 MRR months”</li></ul><p>Why? Because they discovered something counterintuitive:</p><p><strong>Failures build more trust than wins.</strong></p><h3>Why Failures Work Better Than Wins</h3><h3>1. Authenticity is Rare</h3><p>Everyone claims wins. Few admit losses. When you share a failure, you immediately stand out as real.</p><h3>2. Relatability Creates Connection</h3><p>Know what’s alienating? Hearing about someone’s “$100K month” when you’re struggling to get your first customer.</p><p>Know what’s connecting? Hearing someone say “I’ve been building for 8 months and still have $0 in revenue. Here’s what I’m learning.”</p><h3>3. Vulnerability = Likability</h3><p>There’s psychological research on this. People like you more when you’re vulnerable. It’s called the <strong>Pratfall Effect</strong>. A competent person who shows weakness is more likable than a competent person who seems perfect.</p><h3>4. Memes Spread Faster Than Humble Brags</h3><p>A tweet saying “ACHIEVED $10K MRR!!!” gets likes from supportive friends.</p><p>A tweet saying “Just got my first refund. Customer said my product ‘barely functioned.’ 💀” gets engagement from everyone, because it’s funny, relatable, and real.</p><h3>Introducing Failure Badges</h3><p>This is why we built <a href="https://hackerbadges.com/generate/failure">Failure Badges</a>.</p><p>Not as a joke (though they’re funny). As a philosophy.</p><p><strong>Every failure deserves recognition.</strong></p><p>Because:</p><ul><li>Your first pivot means you’re adaptable</li><li>Your $0 MRR month means you’re still in the game</li><li>Your product shutdown means you learned what NOT to build</li><li>Your first angry customer means you have customers</li></ul><h3>The Failure Badge Categories</h3><p>We created templates for every type of startup failure:</p><h3>💸 Revenue Failures</h3><ul><li>“$0 MRR this month”</li><li>“Burned through savings”</li><li>“First refund request”</li></ul><h3>🚀 Product Failures</h3><ul><li>“Launched to crickets”</li><li>“Feature nobody wanted”</li><li>“Pivot #7”</li></ul><h3>❌ Validation Failures</h3><ul><li>“0 waitlist signups”</li><li>“Market said no”</li><li>“Built for myself only”</li></ul><h3>👋 Customer Failures</h3><ul><li>“First churn”</li><li>“Negative NPS”</li><li>“Ghost customer”</li></ul><h3>😵 Founder Failures</h3><ul><li>“Burnout month”</li><li>“Almost quit”</li><li>“Imposter syndrome peak”</li></ul><h3>💀 Epic Failures</h3><ul><li>“Shutdown complete”</li><li>“Cofounder quit”</li><li>“Domain expired”</li></ul><h3>How to Use Failure Badges</h3><h3>1. For Authenticity</h3><p>Share a failure badge alongside your wins. It makes everything more believable.</p><h3>2. For Community Building</h3><p>Start a thread: “My failures this year” with failure badges. Watch how many founders relate.</p><h3>3. For Perspective</h3><p>Looking at your failure badges reminds you how far you’ve come. Every failure you survived made you stronger.</p><h3>4. For Humor</h3><p>Let’s be honest: startup life is absurd. Might as well laugh at it.</p><h3>The Failure Hall of Fame</h3><p>On the HackerBadges homepage, we now have a “Failure Hall of Fame” section.</p><p>Right there, below the win badges.</p><p>Because we refuse to hide failures at the bottom of the page. They deserve equal billing.</p><h3>Join the Movement</h3><p>I’m calling on every founder reading this to do one thing:</p><p><strong>Share a failure this week.</strong></p><p>Not a humble brag disguised as a failure (“I’m so exhausted from all my success!”). An actual, real failure.</p><ul><li>A product that flopped</li><li>A month with no sales</li><li>A customer who churned</li><li>A launch that bombed</li></ul><p>Then create a badge for it. Display it proudly. Own your L’s.</p><p>Because the founders who embrace failure are the ones who eventually succeed.</p><p>And that’s the truth nobody wants to admit.</p><p><strong>Ready to celebrate your failures?</strong></p><p><a href="https://hackerbadges.com/generate/failure">Create Your Failure Badge →</a></p><p><em>Building HackerBadges in public. Following the journey: </em><a href="https://twitter.com/iPritamX"><em>@iPritamX</em></a></p><p><strong>Tags:</strong> Startups, Entrepreneurship, Failure, Build in Public, Indie Hackers</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2b07c73ca701" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[I Built a Tool Because I Hated How People Share Revenue Screenshots]]></title>
            <link>https://medium.com/@pritamfinds/i-built-a-tool-because-i-hated-how-people-share-revenue-screenshots-9fefb3b25e89?source=rss-f340e460bba3------2</link>
            <guid isPermaLink="false">https://medium.com/p/9fefb3b25e89</guid>
            <category><![CDATA[indiehackers]]></category>
            <category><![CDATA[startup]]></category>
            <category><![CDATA[build-in-public]]></category>
            <category><![CDATA[product-development]]></category>
            <category><![CDATA[saas]]></category>
            <dc:creator><![CDATA[Pritam Ghosh]]></dc:creator>
            <pubDate>Sat, 10 Jan 2026 14:55:10 GMT</pubDate>
            <atom:updated>2026-01-10T14:55:10.671Z</atom:updated>
            <content:encoded><![CDATA[<p><em>And accidentally learned why verification matters more than aesthetics</em></p><p>Scroll through any indie hacker timeline. You’ll see it everywhere:</p><p>“Just hit $10K MRR! 🚀💰”</p><p>Attached: A Stripe dashboard screenshot.</p><p>Or worse: Just text. No proof. Just vibes.</p><p>I’ve always found this… unsatisfying. Not because I don’t believe people, but because there has to be a better way.</p><p>So I built one.</p><h3>The Problem with Screenshots</h3><p>Let me be clear: I’m not accusing anyone of lying. Most founders sharing revenue are honest.</p><p>But screenshots have problems:</p><h3>1. They’re ugly</h3><p>Stripe’s dashboard wasn’t designed to be shared. It’s functional, not beautiful. Sharing a screenshot of analytics feels… lazy?</p><h3>2. They lack context</h3><p>“$10K MRR” means nothing without context. Is that up or down from last month? How long did it take? What’s the churn rate?</p><h3>3. They’re not verifiable</h3><p>Anyone with basic Photoshop skills can fake a dashboard. This erodes trust across the entire community.</p><h3>4. They’re temporary</h3><p>A screenshot only shows a moment in time. There’s no consistent way to track or display your journey.</p><h3>What I Built Instead</h3><p>I created <a href="https://hackerbadges.com">HackerBadges</a> — a way to create beautiful, verified achievement badges for startup milestones.</p><p>Here’s how it works:</p><ol><li><strong>Connect your payment provider</strong> (Stripe, LemonSqueezy, or Polar)</li><li><strong>We fetch your actual revenue data</strong></li><li><strong>Badges are marked “Verified”</strong> when connected to real data</li><li><strong>Share beautiful badges</strong> instead of ugly screenshots</li></ol><p>The key difference: when someone sees a verified badge, they know it’s real. It’s not a screenshot I made in Canva. It’s pulled from actual data.</p><h3>The Verification Problem</h3><p>Why does verification matter?</p><p>The indie hacker community is built on trust. We share our numbers. We celebrate each other’s wins. We learn from each other’s failures.</p><p>But that trust relies on honesty. When people fake numbers — and some do — it poisons the well. New founders see inflated success stories and feel inadequate. Real achievements get lost in the noise.</p><p>Verification is a solution. If your $10K MRR badge is connected to your actual Stripe account, there’s no question. It’s real.</p><h3>But It’s Not Just About Verification</h3><p>I’ll admit something: I initially thought verification was the whole product.</p><p>I was wrong.</p><p>After building and launching, I discovered the real value isn’t just verification. It’s <strong>ritualized celebration</strong>.</p><p>The act of creating a badge when you hit a milestone — even a small one — changes how you feel about your progress. It’s not just about proving something to others. It’s about acknowledging something to yourself.</p><p>My $38 MRR badge matters to me. Not because anyone else cares about $38. But because I took the time to recognize my own progress.</p><h3>The Features People Actually Use</h3><p>I thought people would love:</p><ul><li>Rarity tiers (Common → Legendary)</li><li>Multiple badge styles</li><li>Leaderboards</li></ul><p>And they do. But the features that get used most?</p><ol><li><strong>Basic badge creation</strong> — Just making a badge for a milestone</li><li><strong>Sharing to X/Twitter</strong> — One-click export to social</li><li><strong>Badge pages</strong> — Public pages people can link to</li></ol><p>Simple stuff. The gamification is nice, but the core utility is just: “I hit a thing, I want a badge for it.”</p><h3>What I Learned</h3><p>Building this taught me a few things:</p><h3>1. Verification is a feature, not the product</h3><p>I thought “verified badges” was the hook. Turns out, most people just want beautiful badges. Verification is a bonus.</p><h3>2. Simple beats complex</h3><p>I added rarity tiers, leaderboards, streaks… all fun stuff. But 80% of value comes from basic badge creation + sharing.</p><h3>3. Celebrate your own milestones</h3><p>Eating my own cooking here. Using the product myself reminded me why I built it. Every milestone matters.</p><h3>Try It</h3><p>If you’re an indie hacker, founder, or anyone building something:</p><ol><li>Go to <a href="https://hackerbadges.com">hackerbadges.com</a></li><li>Create a badge for whatever milestone you’ve hit lately</li><li>Share it</li></ol><p>It doesn’t have to be $100K MRR. My first badge was for $38.</p><p>Small wins matter. Celebrate them.</p><p><em>Follow the build journey </em><a href="https://twitter.com/iPritamX"><em>@iPritamX</em></a></p><p><strong>Tags:</strong> Startup, Indie Hackers, SaaS, Build in Public, Product Development</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9fefb3b25e89" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Psychology of Milestones: Why Celebrating Small Wins Matters]]></title>
            <link>https://medium.com/@pritamfinds/the-psychology-of-milestones-why-celebrating-small-wins-matters-facd1c93b043?source=rss-f340e460bba3------2</link>
            <guid isPermaLink="false">https://medium.com/p/facd1c93b043</guid>
            <category><![CDATA[motivation]]></category>
            <category><![CDATA[psychology]]></category>
            <category><![CDATA[entrepreneurship]]></category>
            <category><![CDATA[startupş]]></category>
            <category><![CDATA[indiehackers]]></category>
            <dc:creator><![CDATA[Pritam Ghosh]]></dc:creator>
            <pubDate>Sat, 10 Jan 2026 14:52:59 GMT</pubDate>
            <atom:updated>2026-01-10T14:52:59.775Z</atom:updated>
            <content:encoded><![CDATA[<p><em>Why your brain needs you to acknowledge progress, even when it feels small</em></p><p>I hit $38 in monthly recurring revenue last week.</p><p>Thirty-eight dollars. Not thirty-eight thousand. Just $38.</p><p>And I made a badge for it.</p><p>Some people might think that’s silly. “Wait until you have something real to celebrate,” they’d say. But here’s the thing: they’re wrong.</p><h3>The Science of Small Wins</h3><p>There’s a concept in psychology called the <strong>Progress Principle</strong>, studied extensively by Teresa Amabile at Harvard Business School. Her research found that of all the things that can boost emotions, motivation, and perceptions during a workday, the single most important is <strong>making progress in meaningful work</strong>.</p><p>Not finishing work. <em>Progress</em> in work.</p><p>This is huge for founders. We’re often so focused on the end goal — $10K MRR, 100K users, the big exit — that we forget to acknowledge the steps along the way. And that’s a mistake.</p><h3>Why We Skip Celebrations</h3><p>Founders are notorious for moving goalposts:</p><ul><li>“I’ll celebrate when I get my first user” → Gets first user → “I’ll celebrate when I get my first paying customer”</li><li>“I’ll celebrate at $1K MRR” → Hits $1K → “I’ll celebrate at $5K”</li><li>“I’ll celebrate when I quit my job” → Quits job → “I’ll celebrate when I’m profitable”</li></ul><p>The celebration never comes. There’s always another milestone ahead.</p><p>This isn’t just about being humble. It’s <strong>self-sabotage</strong>. You’re training your brain that no achievement is ever enough.</p><h3>The Dopamine Problem</h3><p>Your brain runs on dopamine. When you accomplish something and acknowledge it, you get a dopamine hit. That dopamine reinforces the behavior that led to the accomplishment.</p><p>But if you never acknowledge your wins:</p><ul><li>No dopamine</li><li>No positive reinforcement</li><li>No motivation to continue</li><li>Burnout</li></ul><p>It’s a vicious cycle.</p><h3>The Fix: Ritualized Celebration</h3><p>The solution isn’t to throw a party every time you get a signup. It’s to create <strong>small rituals</strong> that acknowledge progress without breaking your flow.</p><p>Here’s what works:</p><h3>1. Visual Markers</h3><p>Create something visible that marks your progress. This could be:</p><ul><li>A badge you display</li><li>A chart on your wall</li><li>A screenshot saved to a “wins” folder</li><li>A progress bar somewhere visible</li></ul><p>I built <a href="https://hackerbadges.com">HackerBadges</a> specifically for this. When I hit a milestone, I create a badge and share it. It takes 30 seconds, but it gives my brain the “yes, this matters” signal it needs.</p><h3>2. Public Acknowledgment</h3><p>Building in public isn’t just for audience growth. It’s for accountability and celebration.</p><p>When you tweet “Just hit $500 MRR 🎉”, you’re not just informing the world. You’re:</p><ul><li>Cementing the win in your memory</li><li>Getting social validation (which releases more dopamine)</li><li>Creating a record of your journey</li></ul><h3>3. Milestone Definitions</h3><p>Pre-define what counts as a milestone for you:</p><p>StageMilestonesPre-revenueFirst user, first feedback, first bug reportEarly revenueFirst dollar, $100 MRR, $500 MRRGrowth$1K, $2.5K, $5K, $10K MRRScale$25K, $50K, $100K MRR</p><p>Having clear milestones means you know when to celebrate. No ambiguity.</p><h3>4. Failure Milestones</h3><p>This one’s counterintuitive, but important: celebrate failures too.</p><p>Your first refund? Milestone. Your first pivot? Milestone. Your first $0 MRR month? Milestone.</p><p>Why? Because surviving failure is itself progress. You’re still here. You’re still building. That matters.</p><h3>The Real Reason I Built HackerBadges</h3><p>I could tell you I built <a href="https://hackerbadges.com">HackerBadges</a> because I saw a gap in the market. That’s partly true.</p><p>But the real reason is simpler: I needed it.</p><p>I was building in public, hitting small milestones, and feeling like none of it mattered. I’d screenshot my Stripe dashboard, feel a brief moment of satisfaction, then immediately think about the next goal.</p><p>The badge changed that.</p><p>When I created a visual marker of my progress — something I could look at, share, and point to — it felt real. The $38 wasn’t just a number anymore. It was an achievement.</p><h3>Try It Yourself</h3><p>You don’t need a fancy tool to start celebrating milestones. You could:</p><ol><li>Keep a “wins” journal</li><li>Screenshot every milestone</li><li>Tweet your progress (with specific numbers)</li><li>Create a visual wall of achievements</li></ol><p>Or, if you want something more structured:</p><p><a href="https://hackerbadges.com">Create your first badge</a> for whatever milestone you’ve hit lately. It doesn’t matter how small. What matters is acknowledging the progress.</p><p>Because progress, celebrated, leads to more progress.</p><p>And that’s how you build something real.</p><p><em>Building HackerBadges in public. Follow the journey </em><a href="https://twitter.com/iPritamX"><em>@iPritamX</em></a><em>.</em></p><p><strong>Tags:</strong> Entrepreneurship, Psychology, Startups, Motivation, Indie Hackers</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=facd1c93b043" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How I Built HackerBadges: Achievement Badges for Indie Hackers]]></title>
            <link>https://medium.com/@pritamfinds/how-i-built-hackerbadges-achievement-badges-for-indie-hackers-032b87cb1819?source=rss-f340e460bba3------2</link>
            <guid isPermaLink="false">https://medium.com/p/032b87cb1819</guid>
            <category><![CDATA[startup]]></category>
            <category><![CDATA[saas]]></category>
            <category><![CDATA[build-in-public]]></category>
            <category><![CDATA[entrepreneurship]]></category>
            <category><![CDATA[indiehackers]]></category>
            <dc:creator><![CDATA[Pritam Ghosh]]></dc:creator>
            <pubDate>Fri, 09 Jan 2026 12:18:12 GMT</pubDate>
            <atom:updated>2026-01-09T12:18:12.293Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1_03JPmGCGYleuw013xUcg.png" /></figure><p>Every indie hacker knows the feeling.</p><p>You hit $1K MRR. You want to celebrate. You screenshot your Stripe dashboard and post it on Twitter.</p><p>But there’s a problem: <strong>anyone can fake a screenshot.</strong></p><p>That’s why I built HackerBadges.</p><h3>The Problem</h3><p>The indie hacker community loves transparency. We share our revenue numbers, our user counts, our wins and losses.</p><p>But there’s no standard way to do it.</p><p>Some people screenshot Stripe. Others use Baremetrics. Some just… type numbers. And honestly? There’s no way to know what’s real.</p><p>I wanted something better:</p><ul><li><strong>Beautiful</strong> — Not an ugly dashboard screenshot</li><li><strong>Verified</strong> — Actually connected to real data</li><li><strong>Shareable</strong> — Works on Twitter, LinkedIn, websites</li><li><strong>Celebratory</strong> — Makes milestones feel like achievements</li></ul><p>So I built it.</p><h3>The Build</h3><p>I’m a firm believer in shipping fast and iterating.</p><p><strong>Week 1: MVP</strong></p><p>I started with the simplest possible thing: a badge generator. You type in your milestone, pick a style, and get a badge you can download.</p><p>No accounts. No payments. Just badges.</p><p>I shipped it in a week and posted on Twitter.</p><p><strong>Week 2–3: Verification</strong></p><p>The feedback was immediate: “Cool, but how do I know it’s real?”</p><p>So I added integrations:</p><ul><li>Connect your Stripe account</li><li>We fetch your actual revenue</li><li>The badge shows “Verified by Stripe” ✓</li></ul><p>Now when you share a $5K MRR badge, people know it’s legit.</p><p><strong>Week 4: Polish</strong></p><p>I added:</p><ul><li>Multiple badge styles (Neo-brutalist, Glassmorphism, Holographic)</li><li>Rarity tiers (Common, Rare, Epic, Legendary)</li><li>Milestone tracking (auto-generate badges when you hit goals)</li><li>Support for LemonSqueezy and Polar</li></ul><h3>The Tech Stack</h3><p>For those curious about how it’s built:</p><ul><li><strong>Frontend:</strong> Next.js 15 (App Router)</li><li><strong>Backend:</strong> Convex (real-time database)</li><li><strong>Auth:</strong> Convex Auth</li><li><strong>Payments:</strong> Stripe, LemonSqueezy, Polar integrations</li><li><strong>Hosting:</strong> Vercel</li><li><strong>Styling:</strong> Tailwind CSS</li></ul><p>Why Convex? I wanted real-time updates without the pain of managing sockets. When your revenue updates in Stripe, your badge can update instantly. Convex makes that trivial.</p><h3>What I Learned</h3><h3>1. Build for yourself first</h3><p>I’m an indie hacker. I wanted to celebrate my milestones. So I built the tool I wished existed.</p><p>When you’re your own target user, every decision is easier.</p><h3>2. Verification is a feature</h3><p>There are thousands of badge generators online. None of them verify anything.</p><p>That one feature — connecting to real payment data — is what makes HackerBadges different. It’s not just about looking pretty; it’s about being trustworthy.</p><h3>3. Community matters</h3><p>The indie hacker community is incredibly supportive. When I shared my progress on Twitter, people gave feedback, suggestions, and encouragement.</p><p>Build in public. It works.</p><h3>4. Fun is underrated</h3><p>Badges are inherently fun. There’s something satisfying about earning a “Legendary $100K MRR” badge with a golden glow.</p><p>Gamification works because humans like recognition. Don’t be afraid to make your product enjoyable.</p><h3>What’s Next</h3><p>I’m working on:</p><ul><li><strong>Failure Badges</strong> — Celebrate the L’s, not just the W’s (“Pivot #7”, “$0 MRR This Month”)</li><li><strong>Product Pages</strong> — A portfolio for your startup journey</li><li><strong>GitHub Stats</strong> — Badges for developers (stars, commits, contributions)</li></ul><p>The goal is to become the go-to platform for indie hackers to showcase their journey.</p><h3>Try It Yourself</h3><p>If you’re an indie hacker with milestones to celebrate:</p><ol><li>Go to <a href="https://hackerbadges.com">hackerbadges.com</a></li><li>Create a badge (free, no signup required)</li><li>Or connect your Stripe/LemonSqueezy for verified badges</li></ol><p>Every milestone deserves recognition. Big or small.</p><p><em>If you found this useful, give it a clap and follow for more indie hacker content. Building in public at </em><a href="https://twitter.com/iPritamX"><em>@iPritamX</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=032b87cb1819" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Develop a Stunning, Beautiful Portfolio with Artifact UI Template in Next JS, Tailwind CSS]]></title>
            <link>https://medium.com/@pritamfinds/develop-a-stunning-beautiful-portfolio-with-artifact-ui-template-in-next-js-tailwind-css-075072b165d6?source=rss-f340e460bba3------2</link>
            <guid isPermaLink="false">https://medium.com/p/075072b165d6</guid>
            <category><![CDATA[portfolio]]></category>
            <category><![CDATA[portfolio-website]]></category>
            <category><![CDATA[frontend-developer]]></category>
            <category><![CDATA[software-developer]]></category>
            <category><![CDATA[web-developer-portfolio]]></category>
            <dc:creator><![CDATA[Pritam Ghosh]]></dc:creator>
            <pubDate>Tue, 18 Mar 2025 04:27:06 GMT</pubDate>
            <atom:updated>2025-03-18T04:27:06.294Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lu0Msyn_YrWhPOnlPeHoDw.png" /></figure><p>A modern, responsive portfolio template built with Next.js, TypeScript, and Tailwind CSS. This template is designed to showcase your skills, projects, and professional experience with a clean and elegant UI.</p><h3>Features</h3><ul><li>Modern UI Design: Clean, minimal interface with smooth animations and transitions</li><li>Responsive Layout: Looks great on all devices from mobile to desktop</li><li>Dark/Light Mode: Built-in theme-switching capability</li><li>Section-Based Structure: Organized sections for introduction, about, projects, skills, experience, services, and contact</li><li>Custom Icons: Unique icons for each section using Lucide React</li><li>Interactive Components: Spotlight cards, project showcases, and more</li><li>SEO Optimized: Ready for search engine visibility</li><li>Fast Performance: Built on Next.js for optimal loading speeds</li></ul><h3>Tech Stack</h3><ul><li>Framework: Next.js 15 (App Router)</li><li>Language: TypeScript</li><li>Styling: Tailwind CSS v4</li><li>Icons: Lucide React, React Icons</li><li>Animations: CSS Animations, Framer Motion</li><li>Deployment: Vercel</li></ul><p>Visit <a href="https://artifactui.in/templates">https://artifactui.in/templates</a> to get the Portfolio Template and customize it to your requirements.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=075072b165d6" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Artifact UI: Building Modern Web Experiences with React, Next JS, Tailwind CSS, and Framer Motion]]></title>
            <link>https://medium.com/@pritamfinds/artifact-ui-building-modern-web-experiences-with-react-next-js-tailwind-css-and-framer-motion-d496fa76dfcd?source=rss-f340e460bba3------2</link>
            <guid isPermaLink="false">https://medium.com/p/d496fa76dfcd</guid>
            <category><![CDATA[ui-design]]></category>
            <category><![CDATA[ui]]></category>
            <category><![CDATA[reactjs]]></category>
            <category><![CDATA[components]]></category>
            <category><![CDATA[nextjs]]></category>
            <dc:creator><![CDATA[Pritam Ghosh]]></dc:creator>
            <pubDate>Wed, 12 Mar 2025 04:48:50 GMT</pubDate>
            <atom:updated>2025-03-12T05:56:45.303Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0tPH7Aq_377ad3t1hQPClg.png" /></figure><p>In today’s fast-paced digital landscape, creating visually stunning and highly interactive web experiences is no longer a luxury — it’s an expectation. Developers constantly seek tools to help them build beautiful interfaces without sacrificing performance or accessibility. Enter <strong>Artifact UI</strong>, a modern component library designed to transform how developers build websites, web applications, and user interfaces.</p><h3>What is Artifact UI?</h3><p>Artifact UI is a comprehensive UI component library built with React, Tailwind CSS, and Framer Motion. It provides developers with a collection of beautifully designed, highly interactive components that can be easily integrated into any React project. The library focuses on aesthetics and performance, offering a modern design system that works seamlessly across devices and screen sizes.<br>Unlike traditional component libraries that often prioritize functionality over visual appeal, Artifact UI places equal emphasis on both. Each component is meticulously crafted to provide a delightful user experience while maintaining excellent performance and accessibility.</p><h3>Components</h3><p>Artifact UI offers 50+ rich collections of components that go beyond basic UI elements. Here are some components that showcase the library’s capabilities:</p><ol><li>Cursor Spotlight —https://artifactui.in/docs/components/cursor-spotlight</li><li>Particle Veil — <a href="https://artifactui.in/docs/components/particle-veil">https://artifactui.in/docs/components/particle-veil</a></li><li>Icon Text — <a href="https://artifactui.in/docs/components/icon-text-substitution">https://artifactui.in/docs/components/icon-text-substitution</a></li><li>Swipe Cards — <a href="https://artifactui.in/docs/components/swipe-cards">https://artifactui.in/docs/components/swipe-cards</a></li><li>Magnetic Card — <a href="https://artifactui.in/docs/components/magnetic-card">https://artifactui.in/docs/components/magnetic-card</a></li><li>Infinite Scroll — <a href="https://artifactui.in/docs/components/infinite-scroll">https://artifactui.in/docs/components/infinite-scroll</a></li><li>Sense Card — <a href="https://artifactui.in/docs/components/sense-card">https://artifactui.in/docs/components/sense-card</a></li><li>Ghost Text — <a href="https://artifactui.in/docs/components/ghost-text">https://artifactui.in/docs/components/ghost-text</a></li><li>Starfall — <a href="https://artifactui.in/docs/components/starfall">https://artifactui.in/docs/components/starfall</a></li><li>Sphere Animation — <a href="https://artifactui.in/docs/components/sphere-animation">https://artifactui.in/docs/components/sphere-animation</a></li></ol><p>( Many more )</p><h3>Real-World Applications</h3><p>Artifact UI is versatile enough to be used in various types of projects:</p><ul><li>Marketing Websites: Create engaging landing pages with interactive components that capture visitors attention.</li><li>Web Applications: Build feature-rich applications with consistent, beautiful UI elements.</li><li>E-commerce Platforms: Showcase products with interactive cards and spotlight effects.</li><li>Portfolios and Creative Websites: Implement unique interactions and animations to showcase creative work.</li><li>Documentation Sites: Enhance user experience with sticky reveal containers and interactive elements</li></ul><h3>Conclusion</h3><p>Artifact UI represents a modern approach to UI development, combining aesthetics, performance, and developer experience into a comprehensive library. Whether you’re building a simple marketing website or a complex web application, Artifact UI provides the tools you need to create beautiful, interactive interfaces that engage users and elevate your brand.</p><p>By embracing React, Tailwind CSS, and Framer Motion, Artifact UI leverages the best of modern web technologies to help developers build the next generation of web experiences. As the web continues to evolve, tools like Artifact UI will play a crucial role in shaping how we interact with digital content.</p><p>Visit <a href="https://artifactui.in/"><strong>artifactui.in</strong></a><strong> </strong>to explore the full documentation, examples, and component showcase, and start building beautiful interfaces today.</p><p>Thank you!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d496fa76dfcd" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building a Weather Search Application with Next.js and Redux Toolkit]]></title>
            <link>https://medium.com/@pritamfinds/building-a-weather-search-application-with-next-js-and-redux-toolkit-da1c511c7011?source=rss-f340e460bba3------2</link>
            <guid isPermaLink="false">https://medium.com/p/da1c511c7011</guid>
            <category><![CDATA[tutorial]]></category>
            <category><![CDATA[nextjs-tutorial]]></category>
            <category><![CDATA[project-based-learning]]></category>
            <category><![CDATA[nextjs]]></category>
            <category><![CDATA[redux]]></category>
            <dc:creator><![CDATA[Pritam Ghosh]]></dc:creator>
            <pubDate>Tue, 27 Aug 2024 15:50:06 GMT</pubDate>
            <atom:updated>2024-08-27T15:50:06.863Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5vFQ5sByfFlAocHFZrgJgQ.png" /><figcaption>Weather Search Web Application</figcaption></figure><p>In this article, We’ll be creating a weather search application using Next.js and Redux Toolkit. This project is a practical way to learn how to build a dynamic web application with powerful state management. We’ll also use Tailwind CSS for styling, making the UI responsive and visually appealing.</p><h3>Setting Up the Project</h3><p>Let’s start by setting up our Next.js project. First, create a new Next.js application using the following command:</p><pre>npx create-next-app@latest weather-app</pre><p>After the project is created, navigate to the project directory:</p><pre>cd weather-app</pre><p>Next, we’ll install the necessary dependencies:</p><pre>npm install @reduxjs/toolkit react-redux lucide-react axios</pre><h3>Project Structure</h3><p>Our project will follow a well-organized structure, making it easy to manage and scale. Here’s how it’s set up:</p><pre>weather-search-app/<br>│<br>├── .next/                 # Next.js build output (generated, don&#39;t edit)<br>├── node_modules/          # Node.js dependencies (generated)<br>├── public/                # Static files<br>│   └── favicon.ico<br>│<br>├── src/<br>│   ├── app/<br>│   │   ├── layout.tsx     # Root layout component<br>│   │   ├── page.tsx       # Home page component<br>│   │   ├── globals.css    # Global styles (moved to the same level as layout.tsx)<br>│   │   └──weather/[location]/<br>│   │       └── page.tsx   # Dynamic route for weather details page<br>│   │<br>│   ├── components/<br>│   │   ├── AutocompleteList.tsx<br>│   │   ├── SearchBox.tsx<br>│   │   └── ui/            # ui components<br>│   │       ├── alert.tsx<br>│   │       ├── card.tsx<br>│   │     <br>│   │<br>│   ├── lib/<br>│   │   └── store/<br>│   │       ├── Provider.tsx   # Redux Provider component<br>│   │       ├── store.ts       # Redux store configuration<br>│   │       └── weatherSlice.ts  # Weather-related Redux slice<br>│   │<br>│   ├── utils/                # Utility functions and types<br>│   │   ├── api.ts            # API utility functions<br>│   │   └── types.ts          # TypeScript types<br>│<br>├── .env.local             # Local environment variables (not in version control)<br>├── .eslintrc.json         # ESLint configuration<br>├── .gitignore             # Git ignore file<br>├── next.config.js         # Next.js configuration<br>├── package.json           # Project dependencies and scripts<br>├── postcss.config.js      # PostCSS configuration (for Tailwind)<br>├── README.md              # Project documentation<br>├── tailwind.config.js     # Tailwind CSS configuration<br>└── tsconfig.json          # TypeScript configuration</pre><h4><strong>1. First, let’s start with our root layout:</strong></h4><pre>import type { Metadata } from &quot;next&quot;;<br>import { Inter } from &quot;next/font/google&quot;;<br>import &quot;./globals.css&quot;;<br>import { ReduxProvider } from &quot;@/lib/store/provider&quot;;<br><br>const inter = Inter({ subsets: [&quot;latin&quot;] });<br><br>export const metadata: Metadata = {<br>  title: &quot;Weather Search&quot;,<br>  description: &quot;Search for weather information&quot;,<br>};<br><br>export default function RootLayout({<br>  children,<br>}: Readonly&lt;{<br>  children: React.ReactNode;<br>}&gt;) {<br>  return (<br>    &lt;html lang=&quot;en&quot;&gt;<br>      &lt;body className={inter.className}&gt;<br>        &lt;ReduxProvider&gt;<br>          &lt;main className=&quot;bg-white w-full h-screen&quot;&gt;{children}&lt;/main&gt;<br>        &lt;/ReduxProvider&gt;<br>      &lt;/body&gt;<br>    &lt;/html&gt;<br>  );<br>}</pre><h4><strong>2. Now, let’s create our home page:</strong></h4><pre>import React from &#39;react&#39;<br>import AutocompleteList from &#39;@/components/AutocompleteList&#39;<br>import SearchBox from &#39;@/components/SearchBox&#39;<br>import { Sun, Cloud, CloudRain } from &#39;lucide-react&#39;<br><br>export default function Home() {<br>  return (<br>    &lt;div className=&quot;flex flex-col min-h-screen bg-gradient-to-b from-blue-100 to-white&quot;&gt;<br>      &lt;main className=&quot;flex-grow flex flex-col items-center justify-center px-4 sm:px-6 lg:px-8&quot;&gt;<br>        &lt;div className=&quot;max-w-md w-full space-y-8&quot;&gt;<br>          &lt;div className=&quot;text-center&quot;&gt;<br>            &lt;h1 className=&quot;mt-6 text-5xl font-extrabold text-gray-900&quot;&gt;<br>              Weather Search<br>            &lt;/h1&gt;<br>            &lt;p className=&quot;mt-2 text-sm text-gray-600&quot;&gt;<br>              Enter a location to get instant weather information<br>            &lt;/p&gt;<br>          &lt;/div&gt;<br>          <br>          &lt;div className=&quot;mt-8 space-y-6&quot;&gt;<br>            &lt;SearchBox /&gt;<br>            &lt;AutocompleteList /&gt;<br>          &lt;/div&gt;<br>          <br>          &lt;div className=&quot;flex justify-center space-x-4 mt-12&quot;&gt;<br>            &lt;Sun className=&quot;text-yellow-400 w-12 h-12&quot; /&gt;<br>            &lt;Cloud className=&quot;text-gray-400 w-12 h-12&quot; /&gt;<br>            &lt;CloudRain className=&quot;text-blue-400 w-12 h-12&quot; /&gt;<br>          &lt;/div&gt;<br>        &lt;/div&gt;<br>      &lt;/main&gt;<br><br>      &lt;footer className=&quot;w-full py-4 text-center text-sm text-gray-500 bg-white bg-opacity-50&quot;&gt;<br>        &lt;p&gt;© 2024 Weather Search App. All rights reserved.&lt;/p&gt;<br>        &lt;p className=&quot;mt-1&quot;&gt;Powered by WeatherUnion&lt;/p&gt;<br>      &lt;/footer&gt;<br>    &lt;/div&gt;<br>  )<br>}</pre><h4><strong>3. Let’s go to the components folder and create our components:</strong></h4><p><strong>i. SearchBox component: </strong>The SearchBox component is responsible for capturing user input. It allows users to type in the name of a location, triggering an autocomplete feature that suggests possible matches.</p><pre>&#39;use client&#39;<br><br>import React, { useState, useEffect } from &#39;react&#39;<br>import { useRouter } from &#39;next/navigation&#39;<br>import { useDispatch } from &#39;react-redux&#39;<br>import { searchLocalitiesAsync } from &#39;@/lib/store/weatherSlice&#39;<br>import { AppDispatch } from &#39;@/lib/store/store&#39;<br><br>const SearchBox: React.FC = () =&gt; {<br>  const [localSearchTerm, setLocalSearchTerm] = useState(&#39;&#39;)<br>  const router = useRouter()<br>  const dispatch = useDispatch&lt;AppDispatch&gt;()<br><br>  useEffect(() =&gt; {<br>    if (localSearchTerm) {<br>      dispatch(searchLocalitiesAsync(localSearchTerm))<br>    }<br>  }, [localSearchTerm, dispatch])<br><br>  return (<br>    &lt;form onSubmit={(e) =&gt; e.preventDefault()} className=&quot;w-full max-w-md&quot;&gt;<br>      &lt;div className=&quot;relative&quot;&gt;<br>        &lt;input<br>          type=&quot;text&quot;<br>          value={localSearchTerm}<br>          onChange={(e) =&gt; setLocalSearchTerm(e.target.value)}<br>          className=&quot;w-full px-4 py-2 border border-gray-300 rounded-full focus:outline-none focus:ring-2 focus:ring-slate-950&quot;<br>          placeholder=&quot;Search for a location...&quot;<br>        /&gt;<br>        &lt;button<br>          type=&quot;submit&quot;<br>          className=&quot;absolute right-0 top-0 mt-3 mr-4 text-gray-500 hover:text-gray-700&quot;<br>        &gt;<br>          &lt;svg className=&quot;h-5 w-5&quot; fill=&quot;none&quot; strokeLinecap=&quot;round&quot; strokeLinejoin=&quot;round&quot; strokeWidth=&quot;2&quot; viewBox=&quot;0 0 24 24&quot; stroke=&quot;currentColor&quot;&gt;<br>            &lt;path d=&quot;M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z&quot;&gt;&lt;/path&gt;<br>          &lt;/svg&gt;<br>        &lt;/button&gt;<br>      &lt;/div&gt;<br>    &lt;/form&gt;<br>  )<br>}<br><br>export default SearchBox</pre><p><strong>ii. Now, let’s create our AutocompleteList component: </strong>The AutocompleteList component displays suggestions as the user types in the SearchBox. When a user selects a suggestion, the application fetches and displays the weather data for that location.</p><pre>&#39;use client&#39;<br><br>import React from &#39;react&#39;<br>import { useSelector } from &#39;react-redux&#39;<br>import Link from &#39;next/link&#39;<br>import { RootState } from &#39;@/lib/store/store&#39;<br><br>const AutocompleteList: React.FC = () =&gt; {<br>  const { suggestions } = useSelector((state: RootState) =&gt; state.weather)<br><br>  if (suggestions.length === 0) return null<br><br>  return (<br>    &lt;ul className=&quot;mt-2 w-full max-w-md max-h-[300px] overflow-y-scroll bg-white border border-gray-300 rounded-lg shadow-lg&quot;&gt;<br>      {suggestions.map((suggestion) =&gt; (<br>        &lt;li key={suggestion.id} className=&quot;px-4 py-2 hover:bg-gray-100&quot;&gt;<br>          &lt;Link href={`/weather/${encodeURIComponent(suggestion.id)}`}&gt;<br>            {suggestion.name}<br>          &lt;/Link&gt;<br>        &lt;/li&gt;<br>      ))}<br>    &lt;/ul&gt;<br>  )<br>}<br><br>export default AutocompleteList</pre><p><strong>iii. Reuseable card component:</strong></p><pre>import React, { ReactNode } from &quot;react&quot;;<br><br>interface CardProps {<br>  children: ReactNode;<br>  className?: string;<br>}<br><br>export const Card = ({ children, className }: CardProps) =&gt; {<br>  return (<br>    &lt;div className={`bg-white shadow-md rounded-lg&quot;, ${className}`}&gt;<br>      {children}<br>    &lt;/div&gt;<br>  );<br>};<br><br>interface CardContentProps {<br>  children: ReactNode;<br>  className?: string;<br>}<br><br>export const CardContent = ({ children, className }: CardContentProps) =&gt; {<br>  return &lt;div className={`${className}`}&gt;{children}&lt;/div&gt;;<br>};<br><br>interface CardHeaderProps {<br>  children: ReactNode;<br>  className?: string;<br>}<br><br>export const CardHeader = ({ children, className }: CardHeaderProps) =&gt; {<br>  return &lt;div className={`border-b&quot;, ${className}`}&gt;{children}&lt;/div&gt;;<br>};<br><br>interface CardTitleProps {<br>  children: ReactNode;<br>  className?: string;<br>}<br><br>export const CardTitle = ({ children, className }: CardTitleProps) =&gt; {<br>  return (<br>    &lt;h2 className={`text-lg font-semibold&quot;, ${className}`}&gt;{children}&lt;/h2&gt;<br>  );<br>};</pre><p><strong>iv. Alert component:</strong></p><pre>import React, { ReactNode } from &quot;react&quot;;<br><br>interface AlertProps {<br>  children: ReactNode;<br>  variant?: &quot;default&quot; | &quot;destructive&quot;;<br>}<br><br>export const Alert = ({ children, variant = &quot;default&quot; }: AlertProps) =&gt; {<br>  return (<br>    &lt;div<br>      className={`p-4 rounded-lg flex items-start space-x-3&quot;,<br>        ${variant === &quot;destructive&quot; ? &quot;bg-red-100 text-red-700&quot; : &quot;bg-gray-100 text-gray-700&quot;}<br>      `}<br>    &gt;<br>      {children}<br>    &lt;/div&gt;<br>  );<br>};<br><br>interface AlertTitleProps {<br>  children: ReactNode;<br>  className?: string;<br>}<br><br>export const AlertTitle = ({ children, className }: AlertTitleProps) =&gt; {<br>  return (<br>    &lt;h3 className={`text-lg font-semibold&quot;, ${className}`}&gt;{children}&lt;/h3&gt;<br>  );<br>};<br><br>interface AlertDescriptionProps {<br>  children: ReactNode;<br>  className?: string;<br>}<br><br>export const AlertDescription = ({<br>  children,<br>  className,<br>}: AlertDescriptionProps) =&gt; {<br>  return (<br>    &lt;div className={`text-sm&quot;, ${className}`}&gt;{children}&lt;/div&gt;<br>  );<br>};</pre><h4><strong>4. Now Let’s do State Management with Redux Toolkit:</strong></h4><p>To manage our application’s state, we’ll use the Redux Toolkit. We start by creating a slice src/lib/store/ to manage weather-related data. The Redux store manages the global state of our application, including search terms, suggestions, and weather data.</p><p><strong>i. Redux store:</strong></p><pre>import { configureStore } from &#39;@reduxjs/toolkit&#39;<br>import weatherReducer from &#39;./weatherSlice&#39;<br><br>export const store = configureStore({<br>  reducer: {<br>    weather: weatherReducer,<br>  },<br>})<br><br>export type RootState = ReturnType&lt;typeof store.getState&gt;<br>export type AppDispatch = typeof store.dispatch</pre><p><strong>ii. Redux provider:</strong></p><pre>&#39;use client&#39;<br><br>import { Provider } from &#39;react-redux&#39;<br>import { store } from &#39;./store&#39;<br><br>export function ReduxProvider({ children }: { children: React.ReactNode }) {<br>  return &lt;Provider store={store}&gt;{children}&lt;/Provider&gt;<br>}</pre><p><strong>iii. Weather Slice:</strong></p><pre>import { createSlice, createAsyncThunk, PayloadAction } from &#39;@reduxjs/toolkit&#39;;<br>import { fetchWeatherByCoordinates, fetchWeatherByLocality, searchLocalities } from &#39;@/utils/api&#39;;<br>import { WeatherData } from &#39;@/utils/types&#39;;<br><br>interface WeatherState {<br>  searchTerm: string;<br>  suggestions: Array&lt;{ id: string; name: string }&gt;;<br>  weatherData: WeatherData | null;<br>  loading: boolean;<br>  error: string | null;<br>}<br><br>const initialState: WeatherState = {<br>  searchTerm: &#39;&#39;,<br>  suggestions: [],<br>  weatherData: null,<br>  loading: false,<br>  error: null,<br>};<br><br>export const fetchWeatherByCoordinatesAsync = createAsyncThunk(<br>  &#39;weather/fetchByCoordinates&#39;,<br>  async ({ latitude, longitude }: { latitude: number; longitude: number }) =&gt; {<br>    const response = await fetchWeatherByCoordinates(latitude, longitude);<br>    return response as WeatherData;<br>  }<br>);<br><br>export const fetchWeatherByLocalityAsync = createAsyncThunk(<br>  &#39;weather/fetchByLocality&#39;,<br>  async (localityId: string) =&gt; {<br>    const response = await fetchWeatherByLocality(localityId);<br>    return response as WeatherData;<br>  }<br>);<br><br>export const searchLocalitiesAsync = createAsyncThunk(<br>  &#39;weather/searchLocalities&#39;,<br>  async (query: string) =&gt; {<br>    const response = await searchLocalities(query);<br>    return response;<br>  }<br>);<br><br>const weatherSlice = createSlice({<br>  name: &#39;weather&#39;,<br>  initialState,<br>  reducers: {<br>    setSearchTerm(state, action: PayloadAction&lt;string&gt;) {<br>      state.searchTerm = action.payload;<br>    },<br>  },<br>  extraReducers: (builder) =&gt; {<br>    builder<br>      .addCase(fetchWeatherByCoordinatesAsync.pending, (state) =&gt; {<br>        state.loading = true;<br>        state.error = null;<br>      })<br>      .addCase(fetchWeatherByCoordinatesAsync.fulfilled, (state, action) =&gt; {<br>        state.weatherData = action.payload;<br>        state.loading = false;<br>      })<br>      .addCase(fetchWeatherByCoordinatesAsync.rejected, (state, action) =&gt; {<br>        state.loading = false;<br>        state.error = action.error.message || &#39;Failed to fetch weather data&#39;;<br>      })<br>      .addCase(fetchWeatherByLocalityAsync.pending, (state) =&gt; {<br>        state.loading = true;<br>        state.error = null;<br>      })<br>      .addCase(fetchWeatherByLocalityAsync.fulfilled, (state, action) =&gt; {<br>        state.weatherData = action.payload;<br>        state.loading = false;<br>      })<br>      .addCase(fetchWeatherByLocalityAsync.rejected, (state, action) =&gt; {<br>        state.loading = false;<br>        state.error = action.error.message || &#39;Failed to fetch weather data&#39;;<br>      })<br>      .addCase(searchLocalitiesAsync.fulfilled, (state, action) =&gt; {<br>        state.suggestions = action.payload;<br>      });<br>  },<br>});<br><br>export const { setSearchTerm } = weatherSlice.actions;<br><br>export default weatherSlice.reducer;</pre><h4>5. let’s create our API utility functions:</h4><p>We’ve created utility functions api.ts for interacting with the Weather Union API.</p><pre>import axios from &quot;axios&quot;;<br><br>const API_KEY = process.env.NEXT_PUBLIC_WEATHER_API_KEY;<br>const BASE_URL = &quot;https://www.weatherunion.com/gw/weather/external/v0&quot;;<br><br>export const fetchWeatherByCoordinates = async (<br>  latitude: number,<br>  longitude: number<br>) =&gt; {<br>  const options = {<br>    method: &quot;GET&quot;,<br>    url: `${BASE_URL}/get_weather_data`,<br>    params: { latitude, longitude },<br>    headers: { &quot;X-Zomato-Api-Key&quot;: API_KEY },<br>  };<br><br>  try {<br>    const { data } = await axios.request(options);<br>    return data;<br>  } catch (error) {<br>    console.error(&quot;Error fetching weather data:&quot;, error);<br>    throw error;<br>  }<br>};<br><br>export const fetchWeatherByLocality = async (localityId: string) =&gt; {<br>  const options = {<br>    method: &quot;GET&quot;,<br>    url: `${BASE_URL}/get_locality_weather_data`,<br>    params: { locality_id: localityId },<br>    headers: { &quot;X-Zomato-Api-Key&quot;: API_KEY },<br>  };<br><br>  try {<br>    const { data } = await axios.request(options);<br>    return data;<br>  } catch (error) {<br>    console.error(&quot;Error fetching weather data:&quot;, error);<br>    throw error;<br>  }<br>};<br><br>export const searchLocalities = async (query: string) =&gt; {<br>  return [<br>    { id: &quot;ZWL001156&quot;, name: &quot;New York&quot;, country: &quot;United States&quot; },<br>    { id: &quot;ZWL008975&quot;, name: &quot;Mumbai&quot;, country: &quot;India&quot; },<br>    { id: &quot;ZWL007486&quot;, name: &quot;Delhi&quot;, country: &quot;India&quot; },<br>    { id: &quot;ZWL005087&quot;, name: &quot;Gurgaon&quot;, country: &quot;India&quot; },<br>    { id: &quot;ZWL005375&quot;, name: &quot;Bangalore&quot;, country: &quot;India&quot; },<br>    { id: &quot;ZWL007934&quot;, name: &quot;Kolkata&quot;, country: &quot;India&quot; },<br>    { id: &quot;ZWL006743&quot;, name: &quot;Pune&quot;, country: &quot;India&quot; },<br>    { id: &quot;ZWL003283&quot;, name: &quot;Hyderabad&quot;, country: &quot;India&quot; },<br>    { id: &quot;ZWL007059&quot;, name: &quot;Chennai&quot;, country: &quot;India&quot; },<br>    { id: &quot;ZWL003455&quot;, name: &quot;Ahmedabad&quot;, country: &quot;India&quot; },<br>    { id: &quot;ZWL006687&quot;, name: &quot;Chandigarh&quot;, country: &quot;India&quot; },<br>    { id: &quot;ZWL002150&quot;, name: &quot;Goa&quot;, country: &quot;India&quot; },<br>    { id: &quot;ZWL006900&quot;, name: &quot;Bhopal&quot;, country: &quot;India&quot; },<br>    { id: &quot;ZWL002155&quot;, name: &quot;Surat&quot;, country: &quot;India&quot; },<br>    { id: &quot;ZWL008753&quot;, name: &quot;Jammu&quot;, country: &quot;India&quot; },<br>    { id: &quot;ZWL008938&quot;, name: &quot;Vadodara&quot;, country: &quot;India&quot; },<br>    { id: &quot;ZWL009668&quot;, name: &quot;Coimbatore&quot;, country: &quot;India&quot; },<br>    { id: &quot;ZWL008906&quot;, name: &quot;Bhubaneswar&quot;, country: &quot;India&quot; },<br>  ].filter((locality) =&gt;<br>    locality.name.toLowerCase().includes(query.toLowerCase())<br>  );<br>};</pre><p>Remember to create a .env.local file in your project root and add your Weather Union API key, visit <a href="https://www.weatherunion.com">https://www.weatherunion.com</a> to get the key:</p><pre>NEXT_PUBLIC_WEATHER_API_KEY=your_api_key_here</pre><h4>6. Let’s create a types.ts file for our custom types:</h4><pre>export interface WeatherData {<br>  locality_weather_data: {<br>    temperature: number,<br>    humidity: number,<br>    wind_speed: number,<br>    wind_direction: number,<br>    rain_intensity: number,<br>    rain_accumulation: number<br>  }<br>  }</pre><h4><strong>7. Finally, Let’s create our weather page:</strong></h4><p>The weather page ([location]/page.tsx) fetches and displays weather data for the selected location.</p><pre>&quot;use client&quot;;<br><br>import React, { useEffect } from &quot;react&quot;;<br>import { useSelector, useDispatch } from &quot;react-redux&quot;;<br>import { RootState, AppDispatch } from &quot;@/lib/store/store&quot;;<br>import { fetchWeatherByLocalityAsync } from &quot;@/lib/store/weatherSlice&quot;;<br>import Link from &quot;next/link&quot;;<br>import { Cloud, Droplets, Wind, Umbrella } from &quot;lucide-react&quot;;<br>import { Card, CardContent, CardHeader, CardTitle } from &quot;@/components/ui/card&quot;;<br>import { Alert, AlertDescription, AlertTitle } from &quot;@/components/ui/alert&quot;;<br><br>const WeatherPage = ({ params }: { params: { location: string } }) =&gt; {<br>  const dispatch = useDispatch&lt;AppDispatch&gt;();<br>  const { weatherData, loading, error, suggestions } = useSelector(<br>    (state: RootState) =&gt; state.weather<br>  );<br><br>  useEffect(() =&gt; {<br>    if (params.location &amp;&amp; typeof params.location === &quot;string&quot;) {<br>      dispatch(fetchWeatherByLocalityAsync(params.location));<br>    }<br>  }, [params.location, dispatch]);<br><br>  const name = suggestions.find((suggestion) =&gt; suggestion.id === params.location)?.name;<br><br>  if (!weatherData) return null;<br><br>  const getWeatherIcon = (temperature: number) =&gt; {<br>    if (temperature &gt; 30) return &quot;☀️&quot;;<br>    if (temperature &gt; 20) return &quot;🌤️&quot;;<br>    if (temperature &gt; 10) return &quot;⛅&quot;;<br>    return &quot;☁️&quot;;<br>  };<br><br>  const getWindDirection = (degrees: number) =&gt; {<br>    const directions = [&quot;N&quot;, &quot;NE&quot;, &quot;E&quot;, &quot;SE&quot;, &quot;S&quot;, &quot;SW&quot;, &quot;W&quot;, &quot;NW&quot;];<br>    return directions[Math.round(degrees / 45) % 8];<br>  };<br><br>  return (<br>    &lt;div className=&quot;container mx-auto px-4 py-8 max-w-4xl &quot;&gt;<br>      &lt;h1 className=&quot;text-4xl font-bold mb-6 text-center text-gray-900&quot;&gt;{name}&amp;apos;s Weather&lt;/h1&gt;<br>      <br>      {error ? (<br>        &lt;Alert variant=&quot;destructive&quot;&gt;<br>          &lt;AlertTitle&gt;Error&lt;/AlertTitle&gt;<br>          &lt;AlertDescription&gt;{error}&lt;/AlertDescription&gt;<br>        &lt;/Alert&gt;<br>      ) : loading ? (<br>        &lt;div className=&quot;text-center mt-8&quot;&gt;<br>          &lt;div className=&quot;animate-spin rounded-full h-20 w-20 border-b-2 border-gray-900 mx-auto&quot;&gt;&lt;/div&gt;<br>          &lt;p className=&quot;mt-4 text-lg sm:text-xl text-gray-900&quot;&gt;Loading weather data...&lt;/p&gt;<br>        &lt;/div&gt;<br>      ) : (<br>        &lt;div className=&quot;space-y-6&quot;&gt;<br>          &lt;Card className=&quot;bg-gradient-to-br from-blue-500 to-purple-600 text-white p-6 rounded-md&quot;&gt;<br>            &lt;CardContent className=&quot;pt-6&quot;&gt;<br>              &lt;div className=&quot;flex items-center justify-between&quot;&gt;<br>                &lt;div&gt;<br>                  &lt;p className=&quot;text-4xl sm:text-6xl font-bold&quot;&gt;<br>                    {weatherData?.locality_weather_data.temperature}°C<br>                  &lt;/p&gt;<br>                  &lt;p className=&quot;text-lg sm:text-xl mt-2&quot;&gt;Feels comfortable&lt;/p&gt;<br>                &lt;/div&gt;<br>                &lt;div className=&quot;text-7xl md:text-8xl&quot;&gt;<br>                  {getWeatherIcon(weatherData?.locality_weather_data.temperature)}<br>                &lt;/div&gt;<br>              &lt;/div&gt;<br>            &lt;/CardContent&gt;<br>          &lt;/Card&gt;<br><br>          &lt;div className=&quot;grid grid-cols-2 gap-4 sm:grid-cols-4&quot;&gt;<br>            &lt;Card className=&quot;p-4&quot;&gt;<br>              &lt;CardHeader className=&quot;flex flex-row items-center justify-between space-y-0 pb-2&quot;&gt;<br>                &lt;CardTitle className=&quot;text-sm font-medium&quot;&gt;Humidity&lt;/CardTitle&gt;<br>                &lt;Droplets className=&quot;h-4 w-4 text-muted-foreground&quot; /&gt;<br>              &lt;/CardHeader&gt;<br>              &lt;CardContent&gt;<br>                &lt;div className=&quot;text-xl sm:text-2xl font-bold&quot;&gt;{weatherData?.locality_weather_data.humidity}%&lt;/div&gt;<br>              &lt;/CardContent&gt;<br>            &lt;/Card&gt;<br>            &lt;Card className=&quot;p-4&quot;&gt;<br>              &lt;CardHeader className=&quot;flex flex-row items-center justify-between space-y-0 pb-2&quot;&gt;<br>                &lt;CardTitle className=&quot;text-sm font-medium&quot;&gt;Wind&lt;/CardTitle&gt;<br>                &lt;Wind className=&quot;h-4 w-4 text-muted-foreground&quot; /&gt;<br>              &lt;/CardHeader&gt;<br>              &lt;CardContent&gt;<br>                &lt;div className=&quot;text-xl sm:text-2xl font-bold&quot;&gt;{weatherData?.locality_weather_data.wind_speed} km/h&lt;/div&gt;<br>                &lt;p className=&quot;text-xs text-muted-foreground&quot;&gt;<br>                  Direction: {getWindDirection(weatherData?.locality_weather_data.wind_direction)}<br>                &lt;/p&gt;<br>              &lt;/CardContent&gt;<br>            &lt;/Card&gt;<br>            &lt;Card className=&quot;p-4&quot;&gt;<br>              &lt;CardHeader className=&quot;flex flex-row items-center justify-between space-y-0 pb-2&quot;&gt;<br>                &lt;CardTitle className=&quot;text-sm font-medium&quot;&gt;Rain Intensity&lt;/CardTitle&gt;<br>                &lt;Cloud className=&quot;h-4 w-4 text-muted-foreground&quot; /&gt;<br>              &lt;/CardHeader&gt;<br>              &lt;CardContent&gt;<br>                &lt;div className=&quot;text-xl sm:text-2xl font-bold&quot;&gt;{weatherData?.locality_weather_data.rain_intensity} mm/h&lt;/div&gt;<br>              &lt;/CardContent&gt;<br>            &lt;/Card&gt;<br>            &lt;Card className=&quot;p-4&quot;&gt;<br>              &lt;CardHeader className=&quot;flex flex-row items-center justify-between space-y-0 pb-2&quot;&gt;<br>                &lt;CardTitle className=&quot;text-sm font-medium&quot;&gt;Rain Accumulation&lt;/CardTitle&gt;<br>                &lt;Umbrella className=&quot;h-4 w-4 text-muted-foreground&quot; /&gt;<br>              &lt;/CardHeader&gt;<br>              &lt;CardContent&gt;<br>                &lt;div className=&quot;text-xl sm:text-2xl font-bold&quot;&gt;{weatherData?.locality_weather_data.rain_accumulation} mm&lt;/div&gt;<br>              &lt;/CardContent&gt;<br>            &lt;/Card&gt;<br>          &lt;/div&gt;<br><br>          &lt;Link<br>            href=&quot;/&quot;<br>            className=&quot;inline-block w-full text-center py-3 bg-slate-950 text-white rounded-lg hover:bg-gray-900 transition duration-300&quot;<br>          &gt;<br>            Back to Search<br>          &lt;/Link&gt;<br>        &lt;/div&gt;<br>      )}<br>    &lt;/div&gt;<br>  );<br>};<br><br>export default WeatherPage;</pre><p>If you are curious about globals.css and tailwind.config.js, then</p><pre>//globals.css<br>@tailwind base;<br>@tailwind components;<br>@tailwind utilities;<br><br>:root {<br>  --foreground-rgb: 0, 0, 0;<br>  --background-start-rgb: 214, 219, 220;<br>  --background-end-rgb: 255, 255, 255;<br>}<br><br>body {<br>  color: black;<br>  background: linear-gradient(<br>      to bottom,<br>      transparent,<br>      rgb(var(--background-end-rgb))<br>    )<br>    rgb(var(--background-start-rgb));<br>}<br><br><br><br>//tailwind.config.js<br>import type { Config } from &quot;tailwindcss&quot;;<br><br>const config: Config = {<br>  content: [<br>    &quot;./pages/**/*.{js,ts,jsx,tsx,mdx}&quot;,<br>    &quot;./components/**/*.{js,ts,jsx,tsx,mdx}&quot;,<br>    &quot;./app/**/*.{js,ts,jsx,tsx,mdx}&quot;,<br>  ],<br>  theme: {<br>    extend: {<br>      backgroundImage: {<br>        &quot;gradient-radial&quot;: &quot;radial-gradient(var(--tw-gradient-stops))&quot;,<br>        &quot;gradient-conic&quot;:<br>          &quot;conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))&quot;,<br>      },<br>    },<br>  },<br>  plugins: [],<br>};<br>export default config;</pre><p>By following this article, you will learn about how to build a weather search application with Next.js, Redux Toolkit, and Tailwind CSS. This project not only demonstrates how to manage state and fetch data but also how to structure a scalable web application. Feel free to experiment with the code and extend the functionality to suit your needs.</p><p>Thank you for reading this article. if you like this project, then visit GitHub and give it a star: <strong>GitHub Link</strong>: <a href="https://github.com/PrtHub/weather-app">https://github.com/PrtHub/weather-app</a></p><p>Want to connect with me:</p><p>- Twitter: [<a href="https://x.com/PritamGhosh010">https://x.com/PritamGhosh010</a>]<br>- LinkedIn: [<a href="https://www.linkedin.com/in/pritam-ghosh-dev/">https://www.linkedin.com/in/pritam-ghosh-dev</a>]<br>- GitHub: [<a href="https://github.com/PrtHub">https://github.com/PrtHub</a>]</p><p>Don’t hesitate to reach out if you have any questions or want to discuss web development further. I’m always happy to connect with fellow developers and tech enthusiasts!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=da1c511c7011" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[5 Essential Authentication Methods for MERN Applications]]></title>
            <link>https://medium.com/@pritamfinds/5-essential-authentication-methods-for-mern-applications-43c9e709a2c0?source=rss-f340e460bba3------2</link>
            <guid isPermaLink="false">https://medium.com/p/43c9e709a2c0</guid>
            <category><![CDATA[authentication]]></category>
            <category><![CDATA[mongodb]]></category>
            <category><![CDATA[mern-stack]]></category>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[react]]></category>
            <dc:creator><![CDATA[Pritam Ghosh]]></dc:creator>
            <pubDate>Mon, 12 Aug 2024 14:08:42 GMT</pubDate>
            <atom:updated>2024-08-12T14:09:23.442Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CF2WMrgYWmKNuSbM9Gsm3A.jpeg" /><figcaption>MERN Authentication</figcaption></figure><p>When building web applications with the MERN stack (MongoDB, Express, React, Node.js), handling user authentication is crucial. Authentication helps secure your app by ensuring that only authorized users can access specific resources. In this comprehensive guide, we’ll explore five key authentication methods for MERN applications, providing insights and code snippets to help you implement robust security in your projects.</p><h4>1. Token-Based Authentication (JWT)</h4><p>JSON Web Tokens (JWT) are a popular way to handle authentication. After a user logs in, the server generates a JWT and sends it to the client. The client then includes this token in the Authorization header of subsequent requests.</p><p>Backend(Node/Express):</p><pre>const jwt = require(&#39;jsonwebtoken&#39;);<br>const express = require(&#39;express&#39;);<br>const app = express();<br><br>app.post(&#39;/login&#39;, (req, res) =&gt; {<br>  // Verify user credentials<br>  const user = { id: 1, username: &#39;example&#39; };<br>  <br>  const token = jwt.sign(user, &#39;your-secret-key&#39;, { expiresIn: &#39;1h&#39; });<br>  res.json({ token });<br>});<br><br>function authenticateToken(req, res, next) {<br>  const authHeader = req.headers[&#39;authorization&#39;];<br>  const token = authHeader &amp;&amp; authHeader.split(&#39; &#39;)[1];<br><br>  if (token == null) return res.sendStatus(401);<br><br>  jwt.verify(token, &#39;your-secret-key&#39;, (err, user) =&gt; {<br>    if (err) return res.sendStatus(403);<br>    req.user = user;<br>    next();<br>  });<br>}<br><br>app.get(&#39;/protected&#39;, authenticateToken, (req, res) =&gt; {<br>  res.json({ message: &#39;Protected route accessed&#39; });<br>});</pre><p>Frontend(React):</p><pre>import axios from &#39;axios&#39;;<br><br>const login = async (username, password) =&gt; {<br>  try {<br>    const response = await axios.post(&#39;/login&#39;, { username, password });<br>    localStorage.setItem(&#39;token&#39;, response.data.token);<br>  } catch (error) {<br>    console.error(&#39;Login failed&#39;, error);<br>  }<br>};<br><br>const getProtectedData = async () =&gt; {<br>  try {<br>    const token = localStorage.getItem(&#39;token&#39;);<br>    const response = await axios.get(&#39;/protected&#39;, {<br>      headers: { Authorization: `Bearer ${token}` }<br>    });<br>    console.log(response.data);<br>  } catch (error) {<br>    console.error(&#39;Failed to get protected data&#39;, error);<br>  }<br>};</pre><h4>2. Token-Based Authentication (Bearer Tokens)</h4><p>Similar to JWT, bearer tokens are used for stateless authentication. The client sends the token in the Authorization header of the request. The server verifies the token and grants access based on its validity.</p><pre>const crypto = require(&#39;crypto&#39;);<br>const express = require(&#39;express&#39;);<br>const app = express();<br><br>const tokens = new Map();<br><br>app.post(&#39;/login&#39;, (req, res) =&gt; {<br>  // Verify user credentials<br>  const userId = 1; // Assume this is retrieved after verification<br>  const token = crypto.randomBytes(32).toString(&#39;hex&#39;);<br>  tokens.set(token, userId);<br>  res.json({ token });<br>});<br><br>function authenticateToken(req, res, next) {<br>  const token = req.headers[&#39;authorization&#39;]?.split(&#39; &#39;)[1];<br>  if (!token || !tokens.has(token)) {<br>    return res.sendStatus(401);<br>  }<br>  req.userId = tokens.get(token);<br>  next();<br>}<br><br>app.get(&#39;/protected&#39;, authenticateToken, (req, res) =&gt; {<br>  res.json({ message: `Protected route accessed by user ${req.userId}` });<br>});</pre><h4>3. Session-Based Authentication</h4><p>The server creates a session for the user and stores session data on the server side, often using sessions stored in a database or in-memory store. A session ID is sent to the client as a cookie. Session-based authentication is useful when you need to store complex session data on the server:</p><pre>const express = require(&#39;express&#39;);<br>const session = require(&#39;express-session&#39;);<br>const app = express();<br><br>app.use(session({<br>  secret: &#39;your-secret-key&#39;,<br>  resave: false,<br>  saveUninitialized: true,<br>  cookie: { secure: true }<br>}));<br><br>app.post(&#39;/login&#39;, (req, res) =&gt; {<br>  // Verify user credentials<br>  req.session.userId = 1;<br>  res.json({ message: &#39;Logged in successfully&#39; });<br>});<br><br>app.get(&#39;/logout&#39;, (req, res) =&gt; {<br>  req.session.destroy(err =&gt; {<br>    if (err) {<br>      return res.json({ message: &#39;Logout failed&#39; });<br>    }<br>    res.json({ message: &#39;Logged out successfully&#39; });<br>  });<br>});</pre><h4>4. Social Authentication</h4><p>Social authentication allows users to log in using their social media accounts (like Google, Facebook, Twitter). These services provide tokens that the application can use to verify the user’s identity. Here’s an example using Passport.js with Google OAuth:</p><pre>const express = require(&#39;express&#39;);<br>const passport = require(&#39;passport&#39;);<br>const GoogleStrategy = require(&#39;passport-google-oauth20&#39;).Strategy;<br><br>const app = express();<br><br>passport.use(new GoogleStrategy({<br>    clientID: &#39;YOUR_GOOGLE_CLIENT_ID&#39;,<br>    clientSecret: &#39;YOUR_GOOGLE_CLIENT_SECRET&#39;,<br>    callbackURL: &quot;http://localhost:3000/auth/google/callback&quot;<br>  },<br>  function(accessToken, refreshToken, profile, cb) {<br>    // Save or update user in your database<br>    return cb(null, profile);<br>  }<br>));<br><br>app.get(&#39;/auth/google&#39;,<br>  passport.authenticate(&#39;google&#39;, { scope: [&#39;profile&#39;, &#39;email&#39;] }));<br><br>app.get(&#39;/auth/google/callback&#39;, <br>  passport.authenticate(&#39;google&#39;, { failureRedirect: &#39;/login&#39; }),<br>  function(req, res) {<br>    // Successful authentication, redirect home.<br>    res.redirect(&#39;/&#39;);<br>  });</pre><h4>5. Two-Factor Authentication (2FA)</h4><p>Two-factor authentication adds an extra layer of security by requiring a second form of authentication, such as a code sent to a mobile device or generated by an authenticator app. Here’s a basic implementation using the speakeasy library for Time-based One-Time Password (TOTP):</p><pre>const speakeasy = require(&#39;speakeasy&#39;);<br>const express = require(&#39;express&#39;);<br>const app = express();<br><br>app.post(&#39;/setup-2fa&#39;, (req, res) =&gt; {<br>  const secret = speakeasy.generateSecret();<br>  // Save secret.base32 to the user&#39;s record in the database<br>  res.json({ secret: secret.base32 });<br>});<br><br>app.post(&#39;/verify-2fa&#39;, (req, res) =&gt; {<br>  const { token, secret } = req.body;<br>  const verified = speakeasy.totp.verify({<br>    secret: secret,<br>    encoding: &#39;base32&#39;,<br>    token: token<br>  });<br>  <br>  if (verified) {<br>    // Grant access<br>    res.json({ verified: true });<br>  } else {<br>    res.status(400).json({ verified: false });<br>  }<br>});</pre><p><strong>Conclusion:</strong> Selecting the right authentication method depends on your specific requirements. JWTs and Bearer tokens are great for stateless applications, session-based auth provides more flexibility with server-side storage, social authentication simplifies the login process, and 2FA adds an extra layer of security. Remember to always follow security best practices, keep your authentication mechanisms up to date, and consider combining methods for enhanced security.</p><p>Connect with me:</p><p>Thank you for reading this guide on authentication methods in MERN applications. If you found this content helpful and want to stay updated on more web development topics, feel free to connect with me on social media:</p><p>- Twitter: [<a href="https://x.com/PritamGhosh010">https://x.com/PritamGhosh010</a>]<br>- LinkedIn: [<a href="https://www.linkedin.com/in/pritam-ghosh-dev/">https://www.linkedin.com/in/pritam-ghosh-dev</a>]<br>- GitHub: [<a href="https://github.com/PrtHub">https://github.com/PrtHub</a>]</p><p>Don’t hesitate to reach out if you have any questions or want to discuss web development further. I’m always happy to connect with fellow developers and tech enthusiasts!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=43c9e709a2c0" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>