<?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: Victor Maina</title>
    <description>The latest articles on DEV Community by Victor Maina (@jvicmaina).</description>
    <link>https://dev.to/jvicmaina</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%2F488101%2F08138ca0-c609-49f0-9138-ccf804567a3d.jpg</url>
      <title>DEV Community: Victor Maina</title>
      <link>https://dev.to/jvicmaina</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jvicmaina"/>
    <language>en</language>
    <item>
      <title>How to Automate Image CAPTCHA Solving Using 2Captcha API with Python &amp; Selenium</title>
      <dc:creator>Victor Maina</dc:creator>
      <pubDate>Wed, 23 Jul 2025 21:33:50 +0000</pubDate>
      <link>https://dev.to/jvicmaina/how-to-automate-image-captcha-solving-using-2captcha-api-with-python-selenium-3ka5</link>
      <guid>https://dev.to/jvicmaina/how-to-automate-image-captcha-solving-using-2captcha-api-with-python-selenium-3ka5</guid>
      <description>&lt;p&gt;CAPTCHAs are a common security measure to prevent bots from accessing websites. While they serve a legitimate purpose, they can be a hurdle for automation tasks. In this guide, we'll explore how to automate solving image-based CAPTCHAs using the 2Captcha API with Python and Selenium.&lt;/p&gt;

&lt;p&gt;Prerequisites&lt;br&gt;
Before we begin, ensure you have:&lt;/p&gt;

&lt;p&gt;Python installed (3.6+ recommended)&lt;/p&gt;

&lt;p&gt;A 2Captcha API key &lt;a href="https://2captcha.com/2captcha-api" rel="noopener noreferrer"&gt;(Sign up here)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Basic knowledge of Selenium WebDriver&lt;/p&gt;

&lt;p&gt;Required Python packages:&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
&lt;code&gt;pip install selenium requests pillow python-dotenv twocaptcha-python&lt;/code&gt;&lt;br&gt;
Step 1: Setting Up the Environment&lt;br&gt;
1.1 Install Required Libraries&lt;br&gt;
bash&lt;br&gt;
&lt;code&gt;pip install selenium requests pillow python-dotenv twocaptcha&lt;/code&gt;&lt;br&gt;
1.2 Store API Key in .env&lt;br&gt;
Create a .env file:&lt;/p&gt;

&lt;p&gt;env&lt;br&gt;
&lt;code&gt;APIKEY=your_2captcha_api_key_here&lt;/code&gt;&lt;br&gt;
1.3 Initialize Selenium WebDriver&lt;br&gt;
python&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager

def create_browser(headless=False):
    options = Options()
    options.add_argument("--disable-blink-features=AutomationControlled")
    options.add_argument("--no-sandbox")
    options.add_argument("--disable-dev-shm-usage")

    if headless:
        options.add_argument("--headless=new")

    driver = webdriver.Chrome(
        service=Service(ChromeDriverManager().install()),
        options=options
    )
    return driver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 2: Extracting the CAPTCHA Image&lt;br&gt;
We need to locate the CAPTCHA image and download it for processing.&lt;/p&gt;

&lt;p&gt;python&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import base64
import requests

def get_captcha_image(driver):
    try:
        captcha_img = WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.XPATH, 
                "//img[contains(@src, 'captcha') or contains(@src, 'code')]"))
        )

        img_src = captcha_img.get_attribute('src')
        if img_src.startswith('data:image'):
            img_data = base64.b64decode(img_src.split(',')[1])
        else:
            img_data = requests.get(img_src).content

        with open('captcha.jpg', 'wb') as f:
            f.write(img_data)

        return 'captcha.jpg'
    except Exception as e:
        print(f"❌ Failed to get CAPTCHA: {str(e)}")
        return None
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 3: Solving CAPTCHA Using 2Captcha API&lt;br&gt;
Now, we'll send the CAPTCHA image to 2Captcha and retrieve the solution.&lt;/p&gt;

&lt;p&gt;python&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from twocaptcha import TwoCaptcha
from dotenv import load_dotenv
import os

load_dotenv()
API_KEY = os.getenv("APIKEY")
solver = TwoCaptcha(API_KEY)

def solve_captcha(image_path):
    try:
        result = solver.normal(image_path, numeric=0, minLen=4, maxLen=6)
        return result['code']
    except Exception as e:
        print(f"❌ CAPTCHA solving failed: {str(e)}")
        return None
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 4: Submitting the Solved CAPTCHA&lt;br&gt;
Once we have the solution, we fill the input field and submit the form.&lt;/p&gt;

&lt;p&gt;python&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def submit_captcha_solution(driver, solution):
    try:
        input_field = WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.XPATH, 
                "//input[contains(@name, 'captcha') or contains(@id, 'captcha')]"))
        )
        input_field.clear()
        input_field.send_keys(solution)

        submit_button = WebDriverWait(driver, 10).until(
            EC.element_to_be_clickable((By.XPATH, "//button[@type='submit']"))
        )
        submit_button.click()
        return True
    except Exception as e:
        print(f"❌ Failed to submit CAPTCHA: {str(e)}")
        return False
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 5: Putting It All Together&lt;br&gt;
Now, let's combine all steps into a single workflow.&lt;/p&gt;

&lt;p&gt;python&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def automate_captcha_solving(driver, url):
    driver.get(url)

    # Step 1: Get CAPTCHA image
    captcha_image = get_captcha_image(driver)
    if not captcha_image:
        return False

    # Step 2: Solve CAPTCHA
    solution = solve_captcha(captcha_image)
    if not solution:
        return False

    # Step 3: Submit solution
    return submit_captcha_solution(driver, solution)

# Example Usage
if __name__ == "__main__":
    driver = create_browser(headless=False)
    success = automate_captcha_solving(driver, "https://example.com/captcha-page")
    print("✅ CAPTCHA solved successfully!" if success else "❌ Failed to solve CAPTCHA")
    driver.quit()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Common Issues &amp;amp; Fixes&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;CAPTCHA Not Detected
Fix: Adjust the XPath to match the website’s CAPTCHA element.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;python&lt;br&gt;
&lt;code&gt;"//img[contains(@class, 'captcha-img')]"&lt;/code&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;2Captcha API Fails
Fix: Check your API key balance and ensure the image is clear.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Enhance image quality before sending:&lt;/p&gt;

&lt;p&gt;python&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from PIL import Image, ImageFilter, ImageEnhance

def enhance_image(image_path):
    img = Image.open(image_path)
    img = img.convert('L')  # Grayscale
    enhancer = ImageEnhance.Contrast(img)
    img = enhancer.enhance(2.0)  # Increase contrast
    img.save('enhanced_captcha.jpg')
    return 'enhanced_captcha.jpg'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Submit Button Not Clickable
Fix: Use JavaScript click as a fallback:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;python&lt;br&gt;
&lt;code&gt;driver.execute_script("arguments[0].click();", submit_button)&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
Conclusion&lt;br&gt;
By using 2Captcha API with Selenium, we can automate CAPTCHA solving efficiently. This method is useful for:&lt;/p&gt;

&lt;p&gt;Web scraping (where CAPTCHAs block data extraction)&lt;/p&gt;

&lt;p&gt;Automated testing (bypassing CAPTCHAs in test environments)&lt;/p&gt;

&lt;p&gt;Bot automation (for repetitive tasks requiring login)&lt;/p&gt;

&lt;p&gt;Final Thoughts&lt;br&gt;
While automation is powerful, always respect website policies and use this method ethically. Some websites may block automated CAPTCHA-solving attempts, so use it responsibly.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to Create and Publish a VS Code Extension</title>
      <dc:creator>Victor Maina</dc:creator>
      <pubDate>Sat, 19 Jul 2025 23:47:50 +0000</pubDate>
      <link>https://dev.to/jvicmaina/how-to-create-and-publish-a-vs-code-extension-jlm</link>
      <guid>https://dev.to/jvicmaina/how-to-create-and-publish-a-vs-code-extension-jlm</guid>
      <description>&lt;p&gt;📦 Step 1: Install Required Tools&lt;br&gt;
We started by installing the Yeoman generator for VS Code:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm install -g yo generator-code&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
Then we used the generator to scaffold a new extension:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;yo code&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
💡 This gives you multiple options like creating a TypeScript/JavaScript extension, web extension, or even a theme/snippet pack. We selected the basic TypeScript extension for this demo.&lt;/p&gt;

&lt;p&gt;⚙️ Step 2: Install the VSCE Publisher Tool&lt;br&gt;
VSCE (Visual Studio Code Extension) is the CLI tool used to package and publish your extension to the marketplace.&lt;/p&gt;

&lt;p&gt;We installed it using:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm install -g @vscode/vsce&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;⚠️ If you see a warning like EBADENGINE, it means your Node.js version is slightly outdated. VSCE is asking for Node &amp;gt;=20.18.1, while you have v20.17.0. Consider updating Node.js if issues persist.&lt;/p&gt;

&lt;p&gt;🔐 Step 3: Get a Personal Access Token (PAT)&lt;br&gt;
To publish to the VS Code Marketplace, you need a Personal Access Token (PAT) from your Microsoft account via Azure DevOps. Here's how:&lt;/p&gt;

&lt;p&gt;Go to: &lt;a href="https://dev.azure.com" rel="noopener noreferrer"&gt;https://dev.azure.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sign in using your Microsoft or GitHub account.&lt;/p&gt;

&lt;p&gt;In the top-right corner, click on your profile picture → "Personal access tokens".&lt;/p&gt;

&lt;p&gt;Click New Token:&lt;/p&gt;

&lt;p&gt;Organization: All accessible organizations&lt;/p&gt;

&lt;p&gt;Scopes: Select only "Marketplace" → "Manage"&lt;/p&gt;

&lt;p&gt;Expiry: You can choose 90 days or custom&lt;/p&gt;

&lt;p&gt;Save the token (copy it somewhere safe — it's shown once).&lt;/p&gt;

&lt;p&gt;👤 Step 4: Create a Publisher&lt;br&gt;
Before publishing, you need to create a publisher (your dev alias or brand name):&lt;/p&gt;

&lt;p&gt;&lt;code&gt;vsce create-publisher your-publisher-name&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
You'll be asked for:&lt;/p&gt;

&lt;p&gt;Your Personal Access Token&lt;/p&gt;

&lt;p&gt;Publisher display name&lt;/p&gt;

&lt;p&gt;This will register your publisher to the VS Code Marketplace.&lt;/p&gt;

&lt;p&gt;📦 Step 5: Package Your Extension&lt;br&gt;
Now, navigate into your extension folder and run:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;vsce package&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This will generate a .vsix file (e.g., my-extension-0.0.1.vsix) that contains your whole extension, ready for publishing or manual installation.&lt;/p&gt;

&lt;p&gt;🚀 Step 6: Publish to Marketplace&lt;br&gt;
Once packaged, you can publish directly:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;vsce publish&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Or specify a version bump manually:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;vsce publish minor&lt;br&gt;
vsce publish major&lt;br&gt;
vsce publish patch&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
🔑 You'll be prompted for your PAT again unless it's cached.&lt;/p&gt;

&lt;p&gt;🎯 Optional: Install Locally for Testing&lt;br&gt;
Before publishing, you can test your .vsix locally by running:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;code --install-extension my-extension-0.0.1.vsix&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;💰 Do People Earn from Publishing Extensions?&lt;br&gt;
Publishing alone doesn’t pay. However, you can monetize in several ways:&lt;/p&gt;

&lt;p&gt;Offer pro features via APIs or subscriptions&lt;/p&gt;

&lt;p&gt;Integrate SaaS tools (e.g., integrations with GPT, Firebase, etc.)&lt;/p&gt;

&lt;p&gt;Offer paid versions outside the marketplace&lt;/p&gt;

&lt;p&gt;Accept donations via GitHub Sponsors or Buy Me a Coffee&lt;/p&gt;

&lt;p&gt;Build a portfolio/brand that leads to freelance or dev job offers&lt;/p&gt;

&lt;p&gt;✅ Final Thoughts&lt;br&gt;
Now that your extension is published:&lt;/p&gt;

&lt;p&gt;Track downloads &amp;amp; installs via Visual Studio Marketplace&lt;/p&gt;

&lt;p&gt;Update frequently with new features and fixes&lt;/p&gt;

&lt;p&gt;Share it on GitHub, Reddit, Twitter, and developer forums&lt;/p&gt;

</description>
    </item>
    <item>
      <title>What is a Slug and Why Your Website Needs It for Better SEO</title>
      <dc:creator>Victor Maina</dc:creator>
      <pubDate>Thu, 10 Jul 2025 23:07:13 +0000</pubDate>
      <link>https://dev.to/jvicmaina/what-is-a-slug-and-why-your-website-needs-it-for-better-seo-hmc</link>
      <guid>https://dev.to/jvicmaina/what-is-a-slug-and-why-your-website-needs-it-for-better-seo-hmc</guid>
      <description>&lt;p&gt;When building modern websites  especially e-commerce stores or blogs — you'll often hear developers say, "Let's create a slug for this page." But what exactly is a slug, and why should you care?&lt;/p&gt;

&lt;p&gt;A slug is the part of a URL that comes after the domain name and identifies a specific page in a readable way.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;/hoodies&lt;/p&gt;

&lt;p&gt;/blog/why-seo-matters&lt;/p&gt;

&lt;p&gt;/product/254-streetwear-shirt&lt;/p&gt;

&lt;p&gt;Why Not Use an ID Instead?&lt;br&gt;
You could build URLs like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/product?id=12345&lt;/code&gt;&lt;br&gt;
But slugs are better because:&lt;/p&gt;

&lt;p&gt;✅ They're readable&lt;br&gt;
✅ They're memorable&lt;br&gt;
✅ They look cleaner&lt;br&gt;
✅ And... Google loves them!&lt;/p&gt;

&lt;p&gt;Why Slugs Help SEO&lt;br&gt;
Here’s why slugs are powerful for SEO (Search Engine Optimization):&lt;/p&gt;

&lt;p&gt;Keywords in Slugs Improve Ranking&lt;br&gt;
When someone searches “254 streetwear shirt”, Google checks if those words appear in your slug.&lt;/p&gt;

&lt;p&gt;Users Are More Likely to Click Clean URLs&lt;br&gt;
Which would you click?&lt;/p&gt;

&lt;p&gt;/product?id=12345&lt;/p&gt;

&lt;p&gt;/products/254-streetwear-shirt&lt;/p&gt;

&lt;p&gt;Slugs Help Build Trust&lt;br&gt;
Clean, human-readable URLs feel more legit and professional.&lt;/p&gt;

&lt;p&gt;🔧 How We Use Slugs in Code (Next.js Example)&lt;br&gt;
In our e-commerce project, we use Next.js with a dynamic route:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/app/[slug]/page.tsx&lt;/code&gt;&lt;br&gt;
This tells Next.js:&lt;/p&gt;

&lt;p&gt;"Any product or blog post that has a unique slug like /hoodies or /254-threads-cap will load this page."&lt;/p&gt;

&lt;p&gt;Then in code, we access that slug using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function Page({ params }: { params: { slug: string } }) {
  const slug = params.slug;
  // Now you can query your database for the product with that slug
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🛍 Real World Example&lt;br&gt;
If you’re selling a hoodie named "254 Threads Hoodie", your product URL would look like:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/products/254-threads-hoodie&lt;/code&gt;&lt;br&gt;
Instead of:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/products?id=76383&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That last part — like "254-streetwear-shirt" — is the slug.&lt;/p&gt;

&lt;p&gt;Slugs are more than just fancy URLs. They’re:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A big part of your user experience&lt;/li&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;li&gt;A simple but powerful SEO strategy&lt;/li&gt;
&lt;li&gt;And a best practice for modern websites and apps&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;📣 So next time you're naming a blog post or product page, think about your slug — your future self (and Google) will thank you!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Why Your Favicon Isn't Updating Across All Browsers (And How to Fix It)</title>
      <dc:creator>Victor Maina</dc:creator>
      <pubDate>Tue, 01 Jul 2025 11:51:29 +0000</pubDate>
      <link>https://dev.to/jvicmaina/why-your-favicon-isnt-updating-across-all-browsers-and-how-to-fix-it-2i3c</link>
      <guid>https://dev.to/jvicmaina/why-your-favicon-isnt-updating-across-all-browsers-and-how-to-fix-it-2i3c</guid>
      <description>&lt;p&gt;Have you ever updated your favicon in a Next.js project, only to see the new icon show up in one browser (like Edge) but not in others like Brave or Chrome?&lt;/p&gt;

&lt;p&gt;You're not alone—and there's a good reason for this.&lt;/p&gt;

&lt;p&gt;Modern browsers—especially Chromium-based ones like Brave, Chrome, and Opera—aggressively cache favicons. This means even if you've updated the favicon.ico file and restarted your dev server, the browser might still display the old one from cache.&lt;/p&gt;

&lt;p&gt;This caching behavior improves performance, but it causes confusion during development.&lt;/p&gt;

&lt;p&gt;✅ How I Fixed It (in Brave)&lt;br&gt;
Here’s what finally worked:&lt;/p&gt;

&lt;p&gt;🔁 1. Hard Refresh the Tab&lt;br&gt;
Force Brave to reload without using cache:&lt;/p&gt;

&lt;p&gt;Windows/Linux: Ctrl + Shift + R&lt;/p&gt;

&lt;p&gt;macOS: Cmd + Shift + R&lt;/p&gt;

&lt;p&gt;If that fails…&lt;/p&gt;

&lt;p&gt;🧼 2. Clear the Favicon Cache&lt;br&gt;
Brave (and Chrome) provide a hidden tool for this:&lt;/p&gt;

&lt;p&gt;Open chrome://favicon-internals in Brave&lt;/p&gt;

&lt;p&gt;Click on Clear Favicon Cache&lt;/p&gt;

&lt;p&gt;Reload your site&lt;/p&gt;

&lt;p&gt;Still not working? Try one more thing…&lt;/p&gt;

&lt;p&gt;📝 3. Rename the Favicon File&lt;br&gt;
Browsers cache based on the URL. Renaming the file tricks the browser into thinking it’s a new one.&lt;/p&gt;

&lt;p&gt;Rename your file to something like favicon-v2.ico&lt;/p&gt;

&lt;p&gt;Update your Next.js metadata:&lt;/p&gt;

&lt;p&gt;tsx&lt;br&gt;
Copy&lt;br&gt;
Edit&lt;br&gt;
export const metadata = {&lt;br&gt;
  title: "254THREADS",&lt;br&gt;
  icons: {&lt;br&gt;
    icon: "/favicon-v2.ico",&lt;br&gt;
  },&lt;br&gt;
};&lt;br&gt;
Now restart your dev server and refresh the page.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>💥 That One Time globals.css Crashed My Next.js Build</title>
      <dc:creator>Victor Maina</dc:creator>
      <pubDate>Mon, 30 Jun 2025 21:33:24 +0000</pubDate>
      <link>https://dev.to/jvicmaina/that-one-time-globalscss-crashed-my-nextjs-build-5e2m</link>
      <guid>https://dev.to/jvicmaina/that-one-time-globalscss-crashed-my-nextjs-build-5e2m</guid>
      <description>&lt;p&gt;You ever update some colors in your tailwind.config.ts and suddenly your entire Next.js app refuses to compile? Yeah. That happened to me.&lt;/p&gt;

&lt;p&gt;Error I saw:&lt;/p&gt;

&lt;p&gt;SyntaxError: Unexpected token, expected "(" (18:19)&lt;br&gt;
Import trace for requested module:&lt;br&gt;
./src/app/globals.css&lt;br&gt;
And I was like... huh? That file only has three Tailwind directives. What gives?&lt;/p&gt;

&lt;p&gt;🔍 The Culprit? My PostCSS Config&lt;br&gt;
Turns out, the issue wasn’t in the CSS at all — it was my postcss.mjs file. I had:&lt;/p&gt;

&lt;p&gt;js&lt;br&gt;
plugins: {&lt;br&gt;
  tailwindcss: {},&lt;br&gt;
}&lt;br&gt;
What I didn't have was this essential plugin:&lt;/p&gt;

&lt;p&gt;js&lt;br&gt;
autoprefixer: {}, // &amp;lt;- 👀 this bad boy is mandatory&lt;br&gt;
Without autoprefixer, Next.js quietly panics when it tries to compile CSS — and throws an error totally unrelated to the root cause. Classic.&lt;/p&gt;

&lt;p&gt;🛠️ The Fix&lt;br&gt;
Install Autoprefixer&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
npm install -D autoprefixer&lt;br&gt;
Update postcss.mjs&lt;/p&gt;

&lt;p&gt;js&lt;br&gt;
/** @type {import('postcss-load-config').Config} */&lt;br&gt;
const config = {&lt;br&gt;
  plugins: {&lt;br&gt;
    tailwindcss: {},&lt;br&gt;
    autoprefixer: {},&lt;br&gt;
  },&lt;br&gt;
};&lt;/p&gt;

&lt;p&gt;export default config;&lt;br&gt;
Clear the cache &amp;amp; restart&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
rm -rf .next&lt;br&gt;
npm run dev&lt;br&gt;
Boom 💣 No more phantom syntax errors.&lt;/p&gt;

&lt;p&gt;💡 Final Tip&lt;br&gt;
If your Next.js build suddenly explodes and you didn’t even touch JavaScript, suspect your CSS pipeline. It’s always the quiet files 😅&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Don’t Click, Just grep: The Smart Way to Find Files Fast</title>
      <dc:creator>Victor Maina</dc:creator>
      <pubDate>Mon, 16 Jun 2025 19:12:24 +0000</pubDate>
      <link>https://dev.to/jvicmaina/dont-click-just-grep-the-smart-way-to-find-files-fast-4k6g</link>
      <guid>https://dev.to/jvicmaina/dont-click-just-grep-the-smart-way-to-find-files-fast-4k6g</guid>
      <description>&lt;p&gt;How to Use grep to Find Files by Extension Like a Command-Line Ninja&lt;br&gt;
Ever found yourself swimming in a sea of downloads trying to fish out all the .jpg images? If you love clean terminals and fast results, grep has your back.&lt;/p&gt;

&lt;p&gt;Here’s how to use grep like a pro to filter files by extension straight from your terminal.&lt;/p&gt;

&lt;p&gt;📁 List .jpg Files in Downloads&lt;br&gt;
&lt;strong&gt;bash&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ls ~/Downloads | grep '\.jpg$'&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;📁 Case-Insensitive Search (JPG, jpg, JpG...)&lt;br&gt;
&lt;strong&gt;bash&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ls ~/Downloads | grep -i '\.jpg$'&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;🔁 Search Recursively Through Subfolders&lt;br&gt;
&lt;strong&gt;bash&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;find ~/Downloads -type f | grep '\.jpg$'&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;💡 Bonus: Windows Equivalent&lt;br&gt;
&lt;strong&gt;CMD:&lt;/strong&gt;&lt;br&gt;
cmd&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dir %USERPROFILE%\Downloads\*.jpg&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PowerShell:&lt;/strong&gt;&lt;br&gt;
powershell&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Get-ChildItem "$env:USERPROFILE\Downloads" -Filter *.jpg&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Getting set with Athena in simple steps</title>
      <dc:creator>Victor Maina</dc:creator>
      <pubDate>Thu, 17 Apr 2025 12:53:40 +0000</pubDate>
      <link>https://dev.to/jvicmaina/getting-set-with-athena-in-simple-steps-10oo</link>
      <guid>https://dev.to/jvicmaina/getting-set-with-athena-in-simple-steps-10oo</guid>
      <description>&lt;p&gt;Getting Started with Amazon Athena: &lt;/p&gt;

&lt;p&gt;Query Your S3 Data with SQL&lt;br&gt;
Amazon Athena makes it incredibly easy to explore and analyze data stored in Amazon S3 using standard SQL queries. Whether you're working with log files, application data, or structured datasets, Athena allows you to treat your S3 files as a database—no ETL or server setup required.&lt;/p&gt;

&lt;p&gt;🧠 What Is Amazon Athena?&lt;br&gt;
Amazon Athena is a serverless, interactive query service that lets you analyze data in Amazon S3 using standard SQL. There's no infrastructure to manage, and you only pay for the queries you run.&lt;/p&gt;

&lt;p&gt;Athena supports various file formats including:&lt;/p&gt;

&lt;p&gt;CSV&lt;/p&gt;

&lt;p&gt;JSON&lt;/p&gt;

&lt;p&gt;ORC (Optimized Row Columnar)&lt;/p&gt;

&lt;p&gt;Apache Avro&lt;/p&gt;

&lt;p&gt;Parquet&lt;/p&gt;

&lt;p&gt;It’s also optimized for performance through parallelism, making it fast and efficient for large datasets.&lt;/p&gt;

&lt;p&gt;💸 How Much Does Athena Cost?&lt;br&gt;
Athena charges you based on the amount of data scanned per query. As of writing, the cost is $5 per TB scanned, so it’s important to optimize your data (e.g., by compressing it or using columnar formats like Parquet or ORC).&lt;/p&gt;

&lt;p&gt;📦 Step 1: Prepare Your S3 Bucket&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbiz04vmb3x1s2o55pjiw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbiz04vmb3x1s2o55pjiw.png" alt="Image description" width="800" height="449"&gt;&lt;/a&gt;&lt;br&gt;
If this is your first time using Athena, you’ll need to set up an S3 bucket to store your data. Here’s how:&lt;/p&gt;

&lt;p&gt;Go to the S3 console and create a new bucket.&lt;/p&gt;

&lt;p&gt;Make sure you note the AWS region—Athena and the S3 bucket must be in the same region.&lt;/p&gt;

&lt;p&gt;Upload your dataset. For this tutorial, we’re using a sample Order data CSV file.&lt;/p&gt;

&lt;p&gt;We’ll also create a second S3 bucket to store the results of our Athena queries (this is required by Athena).&lt;/p&gt;

&lt;p&gt;🧰 Step 2: Set Up AWS Glue for Schema Discovery&lt;br&gt;
Athena needs a schema to understand your data. Instead of defining it manually, we’ll use AWS Glue to automatically crawl and catalog our dataset.&lt;/p&gt;

&lt;p&gt;Go to the AWS Glue Console.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foc380hkwuwjhikcye0zs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foc380hkwuwjhikcye0zs.png" alt="Aws glue table schema  crawler" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create a new database (e.g., order_data_db).&lt;/p&gt;

&lt;p&gt;Create a crawler:&lt;/p&gt;

&lt;p&gt;Set the source as your S3 data bucket.&lt;/p&gt;

&lt;p&gt;Choose the database you just created.&lt;/p&gt;

&lt;p&gt;Run the crawler to populate the schema.&lt;/p&gt;

&lt;p&gt;🔍 Step 3: Query Your Data in Athena&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp9id2kiwl01w6p1n2k6o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp9id2kiwl01w6p1n2k6o.png" alt="Image description" width="800" height="449"&gt;&lt;/a&gt;&lt;br&gt;
Now that the table is ready, it’s time to query your data:&lt;/p&gt;

&lt;p&gt;Open the Athena Query Editor.&lt;/p&gt;

&lt;p&gt;Make sure the correct database is selected.&lt;/p&gt;

&lt;p&gt;Run a sample SQL query like:&lt;/p&gt;

&lt;p&gt;SELECT * FROM orders LIMIT 10;&lt;br&gt;
You’ll see the results right in your browser. Athena also saves them as CSV files in your specified results bucket.&lt;/p&gt;

&lt;p&gt;📊 Bonus: Visualize Your Data with Amazon QuickSight&lt;br&gt;
Want to turn your raw queries into dashboards? You can connect Athena to Amazon QuickSight to create dynamic visualizations of your data. This is great for exploring patterns or trends in logs, sales data, and more.&lt;/p&gt;

&lt;p&gt;🧹 Step 4: Clean Up Your Resources&lt;br&gt;
Once you're done, be sure to delete any AWS resources to avoid ongoing charges:&lt;/p&gt;

&lt;p&gt;Delete the S3 buckets you created (both the data and the results buckets).&lt;/p&gt;

&lt;p&gt;Delete the AWS Glue crawler and database.&lt;/p&gt;

&lt;p&gt;Remove any saved Athena queries or saved workgroups, if applicable.&lt;/p&gt;

&lt;p&gt;Remember, Athena’s billing is based on the volume of data scanned cleanups and query optimization are key to keeping costs low.&lt;/p&gt;

&lt;p&gt;✅ Wrapping Up&lt;br&gt;
Amazon Athena is a powerful tool that simplifies querying large datasets stored in S3 using familiar SQL syntax. It’s perfect for analysts, engineers, and data enthusiasts who want fast insights without the overhead of managing infrastructure.&lt;/p&gt;

&lt;p&gt;In this tutorial, we walked through setting up S3, using AWS Glue to create a schema, querying with Athena, and finally cleaning up your resources. Now you’re ready to start analyzing your own data!&lt;/p&gt;

&lt;p&gt;Would you like me to help turn this into a blog post or Markdown file, or add code blocks/screenshots for each step?&lt;/p&gt;

&lt;p&gt;Reason&lt;/p&gt;

</description>
      <category>athena</category>
      <category>s3</category>
      <category>aws</category>
      <category>cloudpractitioner</category>
    </item>
    <item>
      <title>Resolving CORS Issues</title>
      <dc:creator>Victor Maina</dc:creator>
      <pubDate>Mon, 10 Mar 2025 19:59:16 +0000</pubDate>
      <link>https://dev.to/jvicmaina/resolving-cors-issues-lme</link>
      <guid>https://dev.to/jvicmaina/resolving-cors-issues-lme</guid>
      <description>&lt;p&gt;CORS (Cross-Origin Resource Sharing) errors occur when the frontend and backend are hosted on different domains or ports. To fix this:&lt;/p&gt;

&lt;p&gt;Install CORS Middleware:&lt;br&gt;
In your backend, install the cors package:&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
Copy&lt;br&gt;
npm install cors&lt;br&gt;
Enable CORS:&lt;br&gt;
Update your backend code to allow requests from the frontend:&lt;/p&gt;

&lt;p&gt;javascript&lt;br&gt;
Copy&lt;br&gt;
const cors = require('cors');&lt;br&gt;
const express = require('express');&lt;br&gt;
const app = express();&lt;/p&gt;

&lt;p&gt;app.use(cors({&lt;br&gt;
  origin: '&lt;a href="http://102.37.21.212:3000" rel="noopener noreferrer"&gt;http://102.37.21.212:3000&lt;/a&gt;', // Frontend URL&lt;br&gt;
  credentials: true&lt;br&gt;
}));&lt;/p&gt;

&lt;p&gt;app.post('/auth/login', (req, res) =&amp;gt; {&lt;br&gt;
  // Handle login&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;app.listen(4000, () =&amp;gt; {&lt;br&gt;
  console.log('Backend running on port 4000');&lt;br&gt;
});&lt;br&gt;
Restart the Backend:&lt;br&gt;
Restart the backend to apply the changes:&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
Copy&lt;br&gt;
pm2 restart uvotake-backend&lt;br&gt;
This ensures the backend accepts requests from the frontend.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Opening Ports in Azure
To allow external access to your app, open the necessary ports in Azure:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Go to your VM in the Azure Portal.&lt;/p&gt;

&lt;p&gt;Under Networking, add inbound port rules:&lt;/p&gt;

&lt;p&gt;Frontend: Open port 3000 for HTTP access.&lt;/p&gt;

&lt;p&gt;Backend: Open port 4000 for API access.&lt;/p&gt;

&lt;p&gt;Save the rules and test connectivity:&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
Copy&lt;br&gt;
curl http://:3000&lt;br&gt;
curl http://:4000/auth/login&lt;br&gt;
This ensures your app is accessible to users.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Using PM2 for Process Management
To keep your app running after disconnecting from the VM, use PM2:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Install PM2:&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
Copy&lt;br&gt;
sudo npm install -g pm2&lt;br&gt;
Start the App:&lt;br&gt;
Use PM2 to serve the frontend:&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
Copy&lt;br&gt;
pm2 serve build 3000 --name "uvotake-frontend"&lt;br&gt;
Save the Process:&lt;br&gt;
Ensure the app starts automatically after a reboot:&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
Copy&lt;br&gt;
pm2 save&lt;br&gt;
pm2 startup&lt;br&gt;
PM2 ensures your app runs continuously.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Fixing "Reached Heap Limit" Errors</title>
      <dc:creator>Victor Maina</dc:creator>
      <pubDate>Mon, 10 Mar 2025 19:58:38 +0000</pubDate>
      <link>https://dev.to/jvicmaina/fixing-reached-heap-limit-errors-iig</link>
      <guid>https://dev.to/jvicmaina/fixing-reached-heap-limit-errors-iig</guid>
      <description>&lt;p&gt;When building your React app, you might encounter FATAL ERROR: Reached heap limit. This happens because the VM has limited memory (e.g., 1 GiB). Here’s how to fix it:&lt;/p&gt;

&lt;p&gt;Increase Memory for Node.js:&lt;br&gt;
Update the build script in package.json:&lt;/p&gt;

&lt;p&gt;json&lt;br&gt;
Copy&lt;br&gt;
"build": "node --max-old-space-size=2048 $(which react-scripts) build"&lt;br&gt;
This allocates 2 GB of memory for the build process.&lt;/p&gt;

&lt;p&gt;Add Swap Space:&lt;br&gt;
Create a swap file to provide additional virtual memory:&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
Copy&lt;br&gt;
sudo fallocate -l 2G /swapfile&lt;br&gt;
sudo chmod 600 /swapfile&lt;br&gt;
sudo mkswap /swapfile&lt;br&gt;
sudo swapon /swapfile&lt;br&gt;
Rebuild the App:&lt;br&gt;
Run the build again:&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
Copy&lt;br&gt;
npm run build&lt;br&gt;
This resolves memory issues during the build process.&lt;/p&gt;

</description>
      <category>react</category>
      <category>node</category>
      <category>howto</category>
    </item>
    <item>
      <title>Creating a Virtual Machine (VM) on Azure</title>
      <dc:creator>Victor Maina</dc:creator>
      <pubDate>Mon, 10 Mar 2025 19:56:53 +0000</pubDate>
      <link>https://dev.to/jvicmaina/creating-a-virtual-machine-vm-on-azure-5g5b</link>
      <guid>https://dev.to/jvicmaina/creating-a-virtual-machine-vm-on-azure-5g5b</guid>
      <description>&lt;p&gt;Hosting your app starts with creating a VM on Azure. Here’s how to do it:&lt;/p&gt;

&lt;p&gt;Go to the Azure Portal and click Create a Resource.&lt;/p&gt;

&lt;p&gt;Select Virtual Machine and choose a configuration (e.g., Ubuntu 24.04, Standard B1s).&lt;/p&gt;

&lt;p&gt;Configure networking, storage, and SSH keys for secure access.&lt;/p&gt;

&lt;p&gt;Once the VM is created, note its public IP address (e.g., 102.37.21.212).&lt;/p&gt;

&lt;p&gt;Use SSH to connect to the VM:&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
Copy&lt;br&gt;
ssh -i  azureuser@&lt;br&gt;
This VM will serve as the hosting environment for your app.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Uploading Files to the VM Without Docker
To deploy your app without Docker:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Transfer Files:&lt;br&gt;
Use scp to copy your frontend and backend files to the VM:&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
Copy&lt;br&gt;
scp -r -i   azureuser@:&lt;br&gt;
Example:&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
Copy&lt;br&gt;
scp -r -i ~/keys/uvotakeV_key.pem ~/Desktop/frontend &lt;a href="mailto:azureuser@102.37.21.212"&gt;azureuser@102.37.21.212&lt;/a&gt;:/home/azureuser/frontend&lt;br&gt;
Install Dependencies:&lt;br&gt;
SSH into the VM and install dependencies:&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
Copy&lt;br&gt;
cd ~/frontend&lt;br&gt;
npm install&lt;br&gt;
Build and Serve:&lt;br&gt;
Create a production build and serve it:&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
Copy&lt;br&gt;
npm run build&lt;br&gt;
serve -s build -l 3000&lt;br&gt;
This approach avoids Docker and directly deploys your app to the VM.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Jenkins, the journey to being a devops dev</title>
      <dc:creator>Victor Maina</dc:creator>
      <pubDate>Wed, 05 Mar 2025 09:55:24 +0000</pubDate>
      <link>https://dev.to/jvicmaina/jenkins-the-journey-to-being-a-devops-dev-5cd6</link>
      <guid>https://dev.to/jvicmaina/jenkins-the-journey-to-being-a-devops-dev-5cd6</guid>
      <description>&lt;p&gt;Jenkins is an open-source automation server primarily used for Continuous Integration (CI) and Continuous Delivery (CD) in software development. It helps automate various tasks in the software development lifecycle, such as building, testing, and deploying applications. Here's how it works:&lt;/p&gt;

&lt;p&gt;Jenkins Server Jenkins runs on a server, which can be installed on a variety of operating systems (Linux, Windows, macOS). It has a web-based interface where you can configure Jenkins jobs, pipelines, and monitor the execution of these tasks.&lt;br&gt;
Jobs A job in Jenkins is a single task or a series of tasks that Jenkins automates. Jobs could be anything like building code, running tests, or deploying software. You can define jobs through the web interface or by using configuration files like Jenkinsfile (a text file that defines the pipeline stages).&lt;br&gt;
Pipelines Pipelines are a set of automated steps or stages that describe the process of building, testing, and deploying an application. A Jenkins Pipeline is typically defined in a Jenkinsfile and can be a Declarative Pipeline or a Scripted Pipeline. Pipelines define how the project is built, tested, and delivered through various environments (dev, test, production).&lt;br&gt;
Source Code Management (SCM) Jenkins can be integrated with various source control repositories like Git, Subversion, etc. Jenkins pulls the latest code from the repository whenever a new build is triggered (manually or automatically, e.g., via a webhook from GitHub).&lt;br&gt;
Build and Test Jenkins automates the build process (compiling code, creating artifacts) and testing process (running unit tests, integration tests) to ensure that the codebase is stable. After the build, Jenkins can also trigger additional steps like static code analysis or security scans.&lt;br&gt;
Triggering Jobs Jenkins jobs can be triggered in several ways: Poll SCM: Jenkins periodically checks for changes in the source code repository and starts a build if changes are detected. Webhook: Many modern version control systems like GitHub can send a webhook to Jenkins to trigger a build when code is pushed to the repository. Scheduled Builds: You can schedule builds to run at specific times, such as nightly or weekly. Manual Triggers: Developers or administrators can manually trigger jobs from the Jenkins interface.&lt;br&gt;
Agents/Nodes Jenkins can be scaled horizontally by using agents (also called slaves in earlier versions). Agents are machines that help Jenkins execute tasks. The main Jenkins server (Master) coordinates the execution of tasks, while agents can run the actual build processes. This allows for better load distribution and faster processing of builds.&lt;br&gt;
Notifications and Reporting After each build or test, Jenkins provides feedback via the user interface, email, or integration with other tools like Slack or Microsoft Teams. Jenkins can also generate build reports and display test results, code coverage, and other metrics.&lt;br&gt;
Plugins Jenkins has a robust ecosystem of plugins that extend its functionality. You can add plugins to integrate Jenkins with various tools and technologies, such as: Docker for containerized builds AWS for deployment Slack for notifications GitHub for source control Selenium for automated testing Summary Jenkins works by automating software development tasks through jobs and pipelines. It integrates with version control systems, triggers builds and tests, scales using agents, and provides feedback through reports and notifications. Through its extensive plugin ecosystem, Jenkins can be customized to support nearly any development workflow.&lt;/p&gt;

</description>
    </item>
    <item>
      <title># Using Open-Source APIs like WakaTime to Track Your Coding Stats in Your Projects</title>
      <dc:creator>Victor Maina</dc:creator>
      <pubDate>Thu, 27 Feb 2025 20:12:38 +0000</pubDate>
      <link>https://dev.to/jvicmaina/-using-open-source-apis-like-wakatime-to-track-your-coding-stats-in-your-projects-4p7i</link>
      <guid>https://dev.to/jvicmaina/-using-open-source-apis-like-wakatime-to-track-your-coding-stats-in-your-projects-4p7i</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Open-source APIs provide a great way to integrate useful data into your projects. WakaTime is one such API that helps developers track their coding activity across different projects and languages. In this article, we'll explore how to fetch coding stats from WakaTime and use them in a React application to visualize coding time using Chart.js.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Step 1: Get Your WakaTime API Key&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;To access WakaTime's API, you need an API key. Follow these steps to get it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://wakatime.com/" rel="noopener noreferrer"&gt;WakaTime&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Sign in and navigate to your &lt;strong&gt;Account Settings&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;API Key&lt;/strong&gt;, copy the key.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Step 2: Fetch Data from WakaTime API&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;WakaTime provides various endpoints, but for this example, we will use the &lt;code&gt;/users/current/stats/last_7_days&lt;/code&gt; endpoint to get coding stats for the past week.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example API Request
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_API_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     https://wakatime.com/api/v1/users/current/stats/last_7_days
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Sample Response
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stats"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"languages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JavaScript"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"total_seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;14400&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Python"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"total_seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"projects"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Portfolio Website"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"total_seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10800&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"To-Do App"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"total_seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Step 3: Fetch and Display WakaTime Stats in a React App&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Now, let’s create a &lt;code&gt;Statistics.tsx&lt;/code&gt; component in a React app to fetch and display this data using Chart.js.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install Dependencies
&lt;/h3&gt;

&lt;p&gt;Run the following command to install Axios and Chart.js:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;axios chart.js react-chartjs-2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Create &lt;code&gt;Statistics.tsx&lt;/code&gt;&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Bar&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-chartjs-2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Chart&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ChartJS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;CategoryScale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;LinearScale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;BarElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Tooltip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Legend&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chart.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Register Chart.js components&lt;/span&gt;
&lt;span class="nx"&gt;ChartJS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CategoryScale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;LinearScale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;BarElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Tooltip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Legend&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;Statistics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FC&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;chartData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setChartData&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;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="na"&gt;datasets&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setProjects&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="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;fetchData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &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="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:10000/api/stats&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Raw API Response:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&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;apiData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;apiData&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;apiData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;languages&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;apiData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;⚠️ No valid data found in API response.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nf"&gt;setChartData&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;apiData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;languages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="na"&gt;datasets&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="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hours Spent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;apiData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;languages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;total_seconds&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
              &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rgba(54, 162, 235, 0.6)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="nf"&gt;setProjects&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;project&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;timeSpent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;total_seconds&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt; hrs`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/projects/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})));&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;❌ Error fetching WakaTime data:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="nf"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="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;"p-6"&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;h2&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 font-bold"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Coding Stats (Last 7 Days)&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h2&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;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Bar&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;chartData&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;responsive&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="si"&gt;}&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;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h3&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-2xl"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Projects&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h3&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;link&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;"block p-2"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; - &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timeSpent&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;a&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;span class="p"&gt;:&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;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;No project data available.&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;span class="p"&gt;)&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;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Statistics&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Step 4: Run Your React App&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Make sure your API server is running and then start your React app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go to your browser and check the &lt;code&gt;/statistics&lt;/code&gt; route to see the visualization.&lt;/p&gt;

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

&lt;p&gt;By integrating WakaTime’s open-source API, you can track your coding activity, visualize your most-used languages, and monitor project time—all inside your own custom dashboard! 🚀&lt;/p&gt;

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