<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" 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">
    <channel>
        <title>Ravgeet Dhillon's Blog</title>
        <link>https://www.ravgeet.in</link>
        <description>Full Stack Developer and Technical Content Writer - sharing insights on web development, programming, and technology.</description>
        <lastBuildDate>Fri, 07 Aug 2026 02:03:21 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>Nuxt.js Feed Module</generator>
        <language>en</language>
        <ttl>60</ttl>
        <category>Web Development</category>
        <category>Programming</category>
        <category>Technology</category>
        <category>AI</category>
        <atom:link href="https://www.ravgeet.in/feed.xml" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[My First Electronics Project - Building a Real-Time Power Outage Monitor with ESP32 and Slack]]></title>
            <link>https://www.ravgeet.in/blog/building-a-real-time-power-outage-monitor-with-esp32-and-slack-2kmk</link>
            <guid>https://www.ravgeet.in/blog/building-a-real-time-power-outage-monitor-with-esp32-and-slack-2kmk</guid>
            <pubDate>Wed, 18 Mar 2026 11:53:05 GMT</pubDate>
            <description><![CDATA[The moment of realization happened in Singapore. I was thousands of miles away from home, enjoying a...]]></description>
            <content:encoded><![CDATA[The moment of realization happened in Singapore. I was thousands of miles away from home, enjoying a trip, when I went to check my home CCTV footage through my phone. The screen stayed black. "Connection Failed."

The internal monologue of a developer immediately goes to the worst-case scenario: *Did the router die? Is there a break-in? Did the server crash?*

The reality was much simpler, yet equally frustrating: a power cut. The cameras ran on their internal batteries until they hit 0%, leaving me in a complete information blackout. I didn't know if the power was out for ten minutes or ten hours.

I promised myself I wouldn't leave for another trip without a "Heartbeat" from my home in Amritsar.

### The Solution

I needed a non-invasive system (no cutting 220V mains wires), resilient to inverter switchover gaps, and capable of sending instant notifications.

I chose the **ESP32** for its built-in Wi-Fi and low power consumption. By pairing it with a Slack Webhook, I created a device that "shouts" the second the grid fails.

### The Hardware Stack

To keep things modular and "plug-and-play," I went with:

*   **ESP32 DevKit V1:** The brain of the operation.
    
*   **USB-TTL Converter:** This acts as the sensor. It brings the 5V USB signal down to a safe 3.3V for the ESP32 to read.
    
*   **1000µF Capacitor:** Essential for bridging the 20ms gap during inverter switchover. This prevents the ESP32 from rebooting during the transition.
    
*   Female-to-Female jumper wires
    
*   Some USB data cables
    

### Engineering the "Flicker Filter"

One of the biggest hurdles was electrical noise. GPIO 34 on the ESP32 is an input-only pin and can act like an antenna. Without a solid ground reference, the signal "flickered" between ON and OFF.

I solved this with a two-pronged approach:

1.  **Grounding Geometry:** Moving the ground wires to the same side of the board to stabilize the reference voltage.
    
2.  **Software Debounce:** I implemented a 3-second delay in the code. The system verifies that the power is *actually* out before sending a Slack alert, preventing false alarms from minor grid fluctuations.
    

### Watch the Journey

I documented the entire process—from navigating the local electronics markets in my city to the final "Production" test where I manually tripped the MCB.

*   **Watch the Documentary:** [YouTube - Making my first Electronics project](https://youtu.be/WmGZCAdeHMY)

{% embed https://youtu.be/WmGZCAdeHMY %}
    
*   **Get the Code:** [GitHub - esp32-power-alert](https://github.com/ravgeetdhillon/esp32-power-alert)
    

### Closing Thoughts

Engineering isn't just about writing code for a Jira ticket; it's about solving the small, personal anxieties of life through technology. Now, when I’m on vacation, I’ll know exactly what’s happening at home. Not because I’m checking a camera, but because my home is talking to me.

*If you're interested in building this yourself, feel free to reach out or check out the repository!*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fuploads%2Fcovers%2F613c8b1b22b7a41dfe5fc089%2F9d526864-c838-4d42-b165-0ea8f94b7f20.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Debugging and Stopping Infinite Render Loops in React]]></title>
            <link>https://www.ravgeet.in/blog/debugging-and-stopping-infinite-render-loops-in-react-fm9</link>
            <guid>https://www.ravgeet.in/blog/debugging-and-stopping-infinite-render-loops-in-react-fm9</guid>
            <pubDate>Thu, 05 Feb 2026 11:22:28 GMT</pubDate>
            <description><![CDATA[Infinite renders are not magic bugs — they are deterministic feedback loops. Once you understand why...]]></description>
            <content:encoded><![CDATA[Infinite renders are not magic bugs — they are deterministic feedback loops. Once you understand *why* a render retriggers itself, they become easy to reproduce, debug, and prevent.

This post walks through a **step‑by‑step mental model** to stop the “Maximum update depth reached“ errors for good.

## What an Infinite Render Loop Really Is

At its core, an infinite render loop looks like this:

1. Component renders
    
2. Some reactive logic runs (effect, watcher, computed, subscription)
    
3. That logic updates the state
    
4. State update causes a re-render
    
5. Repeat forever
    

The key insight:

> **Renders don’t loop by accident — they loop because state changes on every render.**

## The Fastest Way to Reproduce the Bug

When debugging, your first goal is **reproduction**.

### Minimal reproduction checklist

Comment out everything except one state value and one reactive hook (effect/watcher). Then add a log in both render and state update.

Example:

```javascript
function Component() {
  console.log('render');

  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log('effect');
    setCount(count + 1);
  }, [count]);
}
```

If you see:

```plaintext
render → effect → render → effect → ...
```

You’ve confirmed the loop.

## The #1 Root Cause: Unstable References

Most infinite loops come from **reference instability**, not logic errors.

### Identity vs Equality

```js
{} !== {}
[] !== []
() => {} !== () => {}
```

Even if values *look* equal, **their identity changes** on every render.

### Example: Object in dependencies

```javascript
function Component({ userId }) {
  const filters = { active: true, userId };

  useEffect(() => {
    fetchData(filters);
  }, [filters]); // ❌ new object every render
}
```

This results in the effect that runs forever.

## Stabilizing References with Memoization

### Fix with memoization

```javascript
const filters = useMemo(() => ({
  active: true,
  userId
}), [userId]);
```

Now, it has the same reference, but the effect runs only when `userId` changes. A rule of thumb is that if a variable goes into the dependency array, it must be referentially stable.

## Choosing Correct Dependencies (Not Fewer)

A common anti-pattern:

```javascript
useEffect(() => {
  if (status === 'loaded') return;
  setStatus('loaded');
}, [status]);
```

This effect updates its own dependency once, then converges to a stable state instead of looping forever.

## Linting That Actually Helps

Linting is one of the **cheapest ways** to prevent infinite render loops *before* they reach runtime — especially in React and Next.js apps.

By specifying the correct React Hooks Rules, you can catch the most common causes of render loops. BY using correct linting rules, you can enable rules in your editor. For example:

* `react-hooks/rules-of-hooks`
    
* `react-hooks/exhaustive-deps`
    

This forces correct dependency lists, exposes unstable references early, and prevents stale closures disguised as "fixes".

But still, the warnings about missing dependencies are not always true. It makes sense to treat them as **design bugs**, not suggestions.

## Debugging with `why-did-you-render` + LLMs

Although we are talking about this step at the very last, if time is crucial to you, then this could be the first resort.

Sometimes you *know* a component is re-rendering too much, but you don’t know **what changed**. Multiple states or effects might be responsible for an infinite render loop.

This is where `why-did-you-render` becomes extremely powerful — especially when paired with an LLM.

### What `why-did-you-render` does

`why-did-you-render` monkey-patches React in development and logs **exact reasons** for re-renders:

* Which props changed
    
* Whether the change was by **identity** or **value**
    
* Which hooks triggered the update
    

Instead of guessing, you get concrete evidence.

### Basic setup

```plaintext
[why-did-you-render]
MyComponent re-rendered because of props changes:
  props.filters changed
    prev: { active: true, userId: 1 }
    next: { active: true, userId: 1 }
  reason: props.filters !== prev.props.filters
```

This immediately tells you:

* The values are equal, but the **reference is not**
    
* Memoization is missing
    

### Feeding logs to an LLM (Copilot / ChatGPT)

Here’s where debugging gets *much* faster.

You can save the console logs as a `.log` file and feed them to the LLM agent along with the code context that you feel might be the reason for the infinite rendering. You can use the following prompt:

> "I have attached the console logs from why-did-you-render along with the code in which the infinite loop is happening. Can you find what is causing this behaviour and how do I stabilize it?"

Because the logs already encode **identity vs equality**, the LLM can:

* Identify unstable objects/functions
    
* Suggest correct `useMemo` / `useCallback` placement
    
* Detect unnecessary props drilling
    
* Recommend architectural fixes (lifting state, memo boundaries)
    

This removes the guesswork that usually slows humans down.

### Why this works so well with LLMs

LLMs struggle with *implicit runtime behavior*.

`why-did-you-render` turns runtime behavior into **explicit text**.

Once behavior is textual:

> Debugging becomes a reasoning problem — which LLMs are good at.

Used together, they form a tight loop:

1. Reproduce the render issue
    
2. Capture `why-did-you-render` logs
    
3. Paste logs + code into an LLM
    
4. Apply fix
    
5. Verify render stability
    

## Final Thought

Infinite render loops are not a framework flaw — they are a **signal**.

They tell you that:

* Data flow is unstable
    
* Identity is misunderstood
    
* Or side effects are misplaced
    

Once you respect reference stability and dependency correctness, infinite loops disappear — permanently.]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1770024886941%2F2dbeb986-7b5e-4af3-8d1e-c73c563744bc.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Rebuilding My Static Blog with Build-Time Data and Instant Search]]></title>
            <link>https://www.ravgeet.in/blog/rebuilding-my-static-blog-with-build-time-data-and-instant-search-16db</link>
            <guid>https://www.ravgeet.in/blog/rebuilding-my-static-blog-with-build-time-data-and-instant-search-16db</guid>
            <pubDate>Wed, 04 Feb 2026 17:51:24 GMT</pubDate>
            <description><![CDATA[Static sites are supposed to be fast, simple, and reliable. But over time, my personal blog started...]]></description>
            <content:encoded><![CDATA[Static sites are supposed to be fast, simple, and reliable. But over time, my personal blog started behaving like a dynamic app - runtime API calls, pagination logic everywhere, and fragmented view counts spread across platforms.

Last week, I rebuilt the blog section of **ravgeet.in** (Nuxt.js) to fix this properly. The end result is still a static site, but now it feels *alive*: aggregated view counts, instant search and sorting, and zero runtime dependencies on external APIs.

This post walks through the thinking, architecture, and trade-offs behind that rebuild.

## The problem with my old setup

Originally, my blog worked like this:

* Blog content lived on **Hashnode** (canonical source)
    
* Some posts were also cross-posted to **Dev.to**
    
* Pages fetched blog data **at runtime** using Hashnode’s GraphQL API
    
* Pagination logic (`hasNextPage`, cursors) lived inside the UI
    

This had a few downsides:

* A static site depending on live APIs felt wrong
    
* Local development and builds were slower and flaky
    
* Adding features like search or sorting would require more APIs
    

I wanted the blog to stay static - but smarter.

## Build-time data as a contract

The core decision was simple:

> **Move all external data fetching to build time, and treat the result as immutable static data.**

Instead of fetching blogs at runtime, I introduced a build step that:

1. Fetches blogs from Hashnode
    
2. Fetches articles from Dev.to
    
3. Matches the same article across platforms
    
4. Aggregates view counts
    
5. Writes everything into a single JSON file
    

At runtime, the site only reads from that JSON.

```plaintext
Hashnode + Dev.to
        ↓
Build-time fetch &amp; normalize
        ↓
static/blogs.json
        ↓
Nuxt UI (search, sort, views)
```

This one decision simplified everything else.

## Fetching and aggregating blog data

### Hashnode: canonical content

Hashnode remains the source of truth for:

* Title, slug, content, tags
    
* Publish date
    
* Cover image
    
* Base view count
    

I fetch all posts using Hashnode’s GraphQL API with pagination handled inside a Node.js script.

### Dev.to: distribution and extra reach

Dev.to is where additional readers come from, so ignoring those views felt wrong.

Using the Dev.to API (with a personal access token), I fetch all my articles and extract:

* `slug`
    
* `canonical_url`
    
* `page_views_count`
    

### Matching articles across platforms

This is the tricky part. Articles are matched using a layered strategy:

1. **Slug match**
    
2. **Canonical URL match**
    
3. **Title match**
    

Once matched, the final view count becomes:

```plaintext
combinedViews = hashnodeViews + devtoViews
```

The output for each blog includes:

* Combined views
    
* Platform-specific views (for debugging)
    
* Dev.to URL (if matched)
    

## Writing the static data contract

All processed data is written to the `static/blogs.json` file.

This file is:

* Generated at build time
    
* Git-ignored
    
* Treated as read-only by the app
    

It also includes metadata like the last updated time and the total blog count.

This JSON file effectively replaces my entire blog API.

## Replacing runtime APIs with static services

Previously, `services/blogs.js` made live GraphQL calls. After the refactor:

* The service dynamically imports `blogs.json`
    
* `find`, `findOne`, and `search` all operate locally
    
* No Axios
    
* No pagination state
    
* No network failures
    

From the UI’s perspective, nothing changed - but under the hood, everything became predictable.

## Instant search and sorting

Once all blog data is local, search becomes trivial.

I added:

* Client-side text search (title, brief, tags)
    
* Sorting by:
    
    * Published date (recent / oldest)
        
    * View count (most / least)
        

Because the dataset is small and static:

* Search results are instant
    
* No debouncing hacks
    
* No loading states
    
* Sorting is deterministic
    

This dramatically improves discoverability without introducing a search service.

## Trade-offs and lessons learned

This approach isn’t perfect:

* Build time increases slightly
    
* The JSON file grows over time
    
* It’s not suitable for real-time analytics
    

But for a personal blog, the trade-offs are worth it.

The key takeaways from the refactor that made me realize that:

* Static doesn’t mean lifeless
    
* Build-time data pipelines are underrated
    
* One clean data contract simplifies UI, UX, and performance
    

If you’re curious, the full implementation lives in the [ravgeet.in repository](https://github.com/ravgeetdhillon/ravgeet-web).]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1769509123113%2F95179792-58e0-47aa-b33d-96fc716c0b99.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[My 7 Aspirations as a Software Engineer in 2026]]></title>
            <link>https://www.ravgeet.in/blog/my-7-aspirations-as-a-software-engineer-in-2026-2fdb</link>
            <guid>https://www.ravgeet.in/blog/my-7-aspirations-as-a-software-engineer-in-2026-2fdb</guid>
            <pubDate>Tue, 27 Jan 2026 09:30:14 GMT</pubDate>
            <description><![CDATA[2026 feels like a genuine inflection point in my software engineering journey. By February 2026, I...]]></description>
            <content:encoded><![CDATA[2026 feels like a genuine inflection point in my software engineering journey. By February 2026, I will have completed four years as a professional engineer, starting with my first full-time role at CloudAnswers in February 2022. At this stage, my aspirations are less about titles or rapid jumps and more about clarity — how I think, the kinds of problems I choose to solve, and the impact I want my work to have over time.

This post is not a checklist of goals. Instead, it’s a reflection on the *direction* I want my career to move in — as an engineer who values strong fundamentals, meaningful leverage, and sustainable, long-term growth.

## 1\. Mastering the Fundamentals

By 2026, my primary aspiration is to be **fundamentally strong**.

Not just "good at React" or "familiar with backend systems" but genuinely comfortable explaining *why* things behave the way they do:

* How JavaScript works under the hood
    
* How browsers render, schedule, and optimize work
    
* How React actually reconciles, schedules, and re-renders
    
* How data flows through a system end-to-end
    

I want to be the engineer who can debug issues calmly because I understand the system — not because I’ve memorized fixes.

## 2\. Thinking in Systems, Not Just Features

Another aspiration is to shift from **feature-level thinking** to **system-level thinking**.

Instead of asking:

> “How do I implement this requirement?”

I want my default question to be:

> “How does this decision affect the system six months from now?”

That means caring about:

* Trade-offs
    
* Maintainability
    
* Operational complexity
    
* Developer experience
    

Good engineers ship features. Great engineers design systems that *survive change*. This shift usually happens when you’re trusted to own a product end‑to‑end, responsible not just for fixing bugs or adding features, but for the long‑term health and evolution of the entire system.

## 3\. Becoming a Strong Communicator, Not Just a Coder

By 2026, I want my value to extend beyond code.

This includes:

* Explaining complex ideas clearly to other engineers
    
* Writing thoughtful PR descriptions and design docs
    
* Adding working evidence in the PRs in the form of videos and screenshots
    
* Helping juniors build correct mental models
    
* Disagreeing respectfully and productively
    

Software engineering is a team sport. Clear thinking is useless if it can’t be communicated.

## 4\. Building Leverage Through Tools and Automation

One of my strongest aspirations is to **build leverage**.

Instead of solving the same problems repeatedly, I want to:

* Automate workflows
    
* Build internal tools
    
* Create systems that scale my impact beyond my own output
    

Leverage is what separates engineers who work *hard* from engineers who work *effectively*.

## 5\. Developing Taste and Judgment

Technical skills can be learned. **Judgment takes time.**

By 2026, I want to develop a strong engineering taste:

* Knowing when *not* to over-engineer
    
* Knowing when technical debt is acceptable
    
* Choosing boring solutions when they’re the right ones
    

This kind of judgment only comes from reflection, mistakes, and intentional learning.

## 6\. Staying Curious Without Burning Out

Finally, I aspire to stay curious — but sustainable.

Not chasing every new framework, but:

* Learning deeply
    
* Picking tools intentionally
    
* Balancing ambition with health
    

Longevity matters. I want a career that compounds, not one that exhausts me early.

## 7\. Launching at Least One Production-grade App

By the end of 2026, I want to have launched **at least one app that real users can use** — not just a side project living on GitHub.

This aspiration is less about building a startup and more about **closing the loop** as an engineer. From idea to execution to feedback, I want to experience the full lifecycle:

* Identifying a real problem (even a small one)
    
* Designing a simple, opinionated solution
    
* Making trade-offs under real constraints
    
* Shipping, maintaining, and iterating based on usage
    

Building a personal app forces a different level of ownership. There is no product manager, no deadline imposed by someone else, and no ambiguity about responsibility. If something is broken, unclear, or poorly designed — it’s on me.

More importantly, it sharpens judgment. You quickly learn what *actually* matters to users, which abstractions are worth the cost, and which technical decisions age poorly once real usage begins.

## Closing Thoughts

My aspirations for 2026 are less about *where* I work and more about *how* I work and think.

If I can be a calmer problem-solver, a clearer thinker, and an engineer who builds systems that last — I’ll consider myself on the right path.

I plan to revisit this post at the end of the year to assess how closely my reality aligned with these intentions, honestly.]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1769247203368%2F313620ca-4f36-44d0-b5d0-429ab1f01b86.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Realtime Deploy Notifications in Next.js with Toasts]]></title>
            <link>https://www.ravgeet.in/blog/realtime-deploy-notifications-in-nextjs-with-toasts-17lm</link>
            <guid>https://www.ravgeet.in/blog/realtime-deploy-notifications-in-nextjs-with-toasts-17lm</guid>
            <pubDate>Sun, 25 Jan 2026 05:30:48 GMT</pubDate>
            <description><![CDATA[Ever deployed a new version of your app and wished your users got notified instantly while they’re...]]></description>
            <content:encoded><![CDATA[Ever deployed a new version of your app and wished your users got notified **instantly** while they’re still using the old one? In this post, I'll show you how I built a **live build checker** in my Next.js app that detects when a new deployment is live and gently nudges users to refresh using a toast notification.

> Use case: Helpful for apps deployed on Vercel where static pages and edge functions make it easy to serve outdated content across sessions.

## The Stack

* **Next.js 15**
    
* **React 19**
    
* **React-Bootstrap**
    
* **React-Toastify**
    
* **Vercel Edge Functions**
    
* **Supabase Auth (optional)**
    

## The Concept

When a new build is deployed, a unique timestamp (`NEXT_PUBLIC_BUILD_TIMESTAMP`) is injected into the environment. On the client, we **poll an API endpoint** every few minutes to check if the server's build timestamp has changed. If it has, we notify the user.

## Step-by-Step Implementation

### 1\. Add a build timestamp during build time

In `next.config.ts`:

```ts
const now = new Date().toISOString();

const nextConfig = {
  env: {
    NEXT_PUBLIC_BUILD_TIMESTAMP: now,
  },
};

export default nextConfig;
```

This ensures every build has a unique timestamp baked in at compile time.

### 2\. Create a `/api/build` route

This edge function returns the current server build timestamp:

```ts
import { NextRequest } from "next/server";
export const runtime = "edge";

export async function GET(request: NextRequest) {
  return new Response(
    JSON.stringify({ build: process.env.NEXT_PUBLIC_BUILD_TIMESTAMP }),
    {
      headers: { "Content-Type": "application/json" },
    }
  );
}
```

You can also add authentication here if needed, e.g., for private dashboards.

### 3\. Build a custom hook: `useLiveBuildChecker`

This hook polls the `/api/build` endpoint periodically and triggers a toast if it detects a new version:

```ts
import { useEffect, useRef } from "react";
import { toast } from "react-toastify";
import axios from "axios";

export function useLiveBuildChecker(intervalMin = 5) {
  const currentBuild = useRef(
    process.env.NEXT_PUBLIC_BUILD_TIMESTAMP
  );

  useEffect(() => {
    const checkForUpdate = async () => {
      try {
        const res = await axios.get("/api/build");
        const { build } = res.data;

        if (build &amp;&amp; build !== currentBuild.current) {
          toast.info("A new version of this page is available. Refresh to see the latest changes.", {
            autoClose: false,
            position: "bottom-right",
          });
          clearInterval(interval); // stop further polling
        }
      } catch (e) {
        console.error("Update check failed:", e);
      }
    };

    const interval = setInterval(
      checkForUpdate,
      process.env.NODE_ENV === "development" ? 5 * 1000 : intervalMin * 60 * 1000
    );

    return () => clearInterval(interval);
  }, [intervalMin]);
}
```

### 4\. Add Toast UI + Hook in Layout

Update your app layout to include the `ToastContainer` and run the hook:

```tsx
"use client";

import { ReactNode } from "react";
import { ToastContainer } from "react-toastify";
import { useLiveBuildChecker } from "@/hooks/useLiveBuildChecker";

import "react-toastify/dist/ReactToastify.css";

interface Props {
  children: ReactNode;
}

export default function AppLayout({ children }: Props) {
  useLiveBuildChecker(); // default 5-min interval

  return (
    <>
      {children}
      <ToastContainer />
    </>
  );
}
```

## Result

Whenever a new version of your app is deployed, users still active in the browser will get this neat toast:

> **"**A new version of this page is available. Refresh to see the latest changes.**"**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1769244335784/ba5fed26-3f32-4300-8a6c-53b504f6bd0f.png)

## Why This Works

This approach is fully **edge-friendly**, **stateless**, and **easy to scale**. And unlike service worker-based update detection, it works great for **SSR/ISR setups** and **custom dashboards**.

## Bonus Ideas

* You can trigger a background update via service workers.
    
* You can track how long users stay on an outdated version.
    

## Final Thoughts

Keeping users on the latest version of your app improves stability, security, and experience. With just a few lines of code, you can achieve this kind of real-time deploy awareness in any Next.js app.

If you're building a dashboard, personal tool, or even a SaaS product, this is one of those *delightfully simple yet powerful* improvements.]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1769245089949%2Fae0d75b0-a29b-414b-adb5-5e0bbc9ad823.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[How I Built a Unified Calendar Dashboard with Next.js, Vercel Edge Functions & No Database]]></title>
            <link>https://www.ravgeet.in/blog/how-i-built-a-unified-calendar-dashboard-with-nextjs-vercel-edge-functions-no-database-368k</link>
            <guid>https://www.ravgeet.in/blog/how-i-built-a-unified-calendar-dashboard-with-nextjs-vercel-edge-functions-no-database-368k</guid>
            <pubDate>Wed, 05 Nov 2025 04:43:54 GMT</pubDate>
            <description><![CDATA[Problem   I was juggling tasks across:   Company ClickUp (for team collaboration) Notion...]]></description>
            <content:encoded><![CDATA[## Problem

I was juggling tasks across:

* Company ClickUp (for team collaboration)
    
* Notion (for personal to-dos and planning)
    
* Google Calendar (from both company &amp; personal emails)
    

The chaos was real. I was missing due dates, spending too much time jumping between apps, and lacked a single place to glance at all my tasks.

## The Solution

I built a **read-only personal dashboard** that:

* Aggregates tasks/events from ClickUp, Notion, and Google Calendar
    
* Groups tasks as **Overdue**, **Due Today**, **Upcoming by Date,** and **No Due Date**
    
* Runs entirely on **Next.js + Edge Functions**
    
* Uses **no database**, just live API reads
    
* Stores my daily tasks that I need to do
    
* Is **password protected** and deployed on a Vercel subdomain
    

Here’s how it looks after multiple polishes:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ohxy2th1q6qqlxm5872p.png)

## Tech Stack

* **Frontend**: Next.js App Router + React Bootstrap + TanStack Query
    
* **API Layer**: Vercel Edge Functions
    
* **Auth**: Cookie-based with middleware protection
    
* **Hosting**: Vercel subdomain
    

## Core Features

### Unified Task View

Each task is grouped into:

* Overdue
    
* Due Today / Tomorrow
    
* Upcoming
    
* No Due Date
    

It pulls data from these 3 APIs:

```ts
const [clickup, notion, calendar] = await Promise.all([
  fetchClickupTasks(),
  fetchNotionTasks(),
  fetchCalendarEvents(),
]);
```

### Auth with Middleware

I implemented simple cookie-based authentication to protect my dashboard. The middleware runs on every request and checks for a valid auth cookie before allowing access to protected routes.

```ts
// middleware.ts
import { NextRequest, NextResponse } from "next/server";

export function middleware(request: NextRequest) {
  const auth = request.cookies.get("auth");
  const pathname = request.nextUrl.pathname;

  const publicPaths = ["/login", "/api/login", "/api/logout"];
  if (publicPaths.includes(pathname)) return NextResponse.next();
  if (auth?.value === "1") return NextResponse.next();

  if (pathname.startsWith("/api/")) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  return NextResponse.redirect(new URL("/login", request.url));
}

export const config = {
  matcher: [
    "/",
    "/dashboard",
    "/api/events",
    "/api/clickup",
    "/api/notion",
    "/api/calendar",
  ],
};
```

### Modular API Fetchers

I kept the codebase clean by separating each API integration into its own module. This makes it easier to maintain and test each data source independently.

```ts
// lib/sources/clickup.ts
export async function fetchClickupTasks() {
  const res = await fetch("https://api.clickup.com/api/v2/...");
  return await res.json();
}
```

The same goes for Notion &amp; Google Calendar.

### Server API Route Example

The backend API routes handle data aggregation from all sources. This route fetches tasks from all three platforms simultaneously and returns them as a unified JSON response.

```ts
// app/api/events/route.ts
import { getAllEvents } from "@/lib/getAllEvents";

export async function GET() {
  const { clickup, notion, calendar } = await getAllEvents();
  return Response.json({ clickup, notion, calendar });
}
```

## Frontend UI

The frontend uses TanStack Query for efficient data fetching with automatic caching and background updates. This ensures the dashboard stays responsive while keeping data fresh.

Using TanStack Query for live fetching and caching:

```ts
const { data, isLoading } = useQuery({
  queryKey: ["events"],
  queryFn: () => fetch("/api/events").then((res) => res.json()),
});
```

Then we categorize tasks by due date:

```ts
const overdue = allTasks.filter(task => isBefore(task.dueDate, today));
const dueToday = allTasks.filter(task => isToday(task.dueDate));
const upcoming = groupByDate(allTasks.filter(...));
const noDueDate = allTasks.filter(task => !task.dueDate);
```

The dashboard also includes a "Today's Work List" feature where I can curate specific tasks from across all platforms. This has become my morning ritual - selecting what I want to focus on for the day creates clarity and intention around my daily goals.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w7kggtem5iq5adb4zxf3.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dtzlmozz41s1qe4db4bf.png)

## Deployment

I deployed the app to Vercel and created a subdomain via Hostinger by:

1. Creating a subdomain DNS record
    
2. Adding the domain to Vercel
    
3. Setting env variables and password via the Vercel dashboard
    

No secrets or tasks are stored — it's 100% live.

## What’s Next?

I'm happy with the current version, but I could add the following features in the future:

* Month View toggle
    
* Desktop notifications for overdue tasks
    
* Auto-refresh every 10 mins
    
* Tauri or Expo wrapper for mobile
    

## Final Thoughts

This project helped me regain clarity over my weekly tasks. I have pinned this dashboard in my browser and open it every morning to immediately see what matters. It's fast, reliable, and mine.

If you're tired of hopping between 5 apps, build something simple that fits your brain.]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1762260694520%2Fc760b5d3-bd6e-42d1-b954-495840544610.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Building a Smart Hardware Inventory System That Actually Works]]></title>
            <link>https://www.ravgeet.in/blog/building-a-smart-hardware-inventory-system-that-actually-works-ngg</link>
            <guid>https://www.ravgeet.in/blog/building-a-smart-hardware-inventory-system-that-actually-works-ngg</guid>
            <pubDate>Mon, 13 Oct 2025 18:14:12 GMT</pubDate>
            <description><![CDATA[We've all been there. You're rushing to meet a deadline and desperately need that specific USB...]]></description>
            <content:encoded><![CDATA[We've all been there. You're rushing to meet a deadline and desperately need that specific USB drive—the one with the 32GB capacity that has your client's backup files. But as you stare at the drawer full of identical-looking pendrives, cables, and chargers, you realize you have no idea which one is which.

Last month, I finally got tired of this digital scavenger hunt and decided to build something better. Not an enterprise-grade asset management system (who has time for that?), but a lightweight, practical solution that would actually solve my real-world problem.

Here's how I built a hardware inventory system that's simple enough to maintain and smart enough to find anything instantly.

## The Problem: Hardware Chaos

My desk drawer looked like a tech graveyard. Multiple USB drives, various charging cables, adapters, and dongles—all visually identical but functionally different. The 8GB drive with personal photos looked exactly like the 64GB one with work projects. The USB-C cable that supports fast charging was indistinguishable from the data-only one.

Every time I needed something specific, I'd end up:

* Plugging in random drives to check their contents
    
* Testing cables to see what they actually do
    
* Wasting 10-15 minutes on something that should take 30 seconds
    

There had to be a better way.

## The Solution: Smart Simplicity

Instead of over-engineering this, I decided to build something that would work with tools I already use daily. My requirements were simple:

1. **Quick to update** - Adding new items shouldn't be a chore
    
2. **Accessible anywhere** - No app installations or complex logins
    
3. **Physical integration** - Must work with actual hardware, not just digital records
    
4. **Intelligent search** - Find items by description, not just exact matches
    

## Building the System: A Step-by-Step Breakdown

### Step 1: The Foundation - Google Sheets as a Database

I started with a clean Google Sheet with these columns:

* **ID**: Auto-generated unique identifier
    
* **Type**: Category like "USB Drive", "Cable", "Charger"
    
* **Description**: The magic field where I describe each item in plain English
    
* **Date Added**: Automatic timestamp
    
* **Status**: Available, In Use, Lost
    

Nothing revolutionary here, but the key was keeping it simple enough that I'd actually maintain it.

### Step 2: Smart ID Generation with Apps Script

Manual ID assignment? No thanks. I wrote a Google Apps Script function that generates short, unique IDs automatically:

```javascript
function generateUniqueID() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const existingIDs = sheet.getRange('A:A').getValues().flat();
  
  let newID;
  do {
    newID = Math.random().toString(36).substring(2, 5).toUpperCase();
  } while (existingIDs.includes(newID));
  
  return newID;
}
```

This creates memorable 3-character IDs like `XR7` or `K2M`. Short enough to write on tiny labels, unique enough to avoid conflicts.

### Step 3: Bridging Digital and Physical Worlds

Here's where it gets practical. I ordered a pack of small adhesive labels and a fine-tip permanent marker. Every time I add a new item to the sheet:

1. The system generates a unique ID
    
2. I write that ID on a physical label
    
3. I stick the label directly on the hardware
    

Now every pendrive, cable, and charger has its own "name tag." When I need something, I just check the physical tag and look it up instantly.

**Pro tip**: For items too small for labels (like tiny dongles), I use small zip-lock bags with labeled sticky notes.

### Step 4: Web Access Without the Hassle

Opening Google Sheets every time felt clunky. Instead, I:

1. Published the sheet as a web app through Apps Script
    
2. Set up a custom subdomain using Vercel
    
3. Created a clean, mobile-friendly interface
    

Now I can check my inventory from my phone while standing in front of my hardware drawer. Game-changer.

### Step 5: AI-Powered Search That Actually Understands

This is where the system gets genuinely smart. Instead of remembering exact product names or scrolling through rows, I integrated OpenAI search that understands natural language queries:

* *"Find the Kingston drive with project files"*
    
* *"Which cable charges my laptop?"*
    
* *"Show me USB drives over 16GB"*
    

The AI reads through all my descriptions and instantly returns the matching items with their IDs. It's like having a personal assistant for my hardware drawer.

## Real-World Impact: Why This Actually Works

**Before**: "I need that specific USB drive... *proceeds to test 6 different drives*"

**After**: "I need that specific USB drive" → Check AI search → "It's the one labeled K2M" → Done in 30 seconds.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5gic5ixdrcvqiy5tq73e.png)

The system works because it addresses the actual problem, not the theoretical one. I don't need enterprise features like check-out workflows or depreciation tracking. I just need to find my stuff quickly.

### The Numbers

After 3 months of use:

* **45 items** tracked (drives, cables, adapters, dongles)
    
* **Average search time**: 15 seconds (down from 10+ minutes)
    
* **Time to add new item**: 90 seconds
    

## Lessons Learned

### What Works Really Well

* **Physical labels are non-negotiable** - Digital-only systems fail in the real world
    
* **AI search is a multiplier** - Turns a simple spreadsheet into something genuinely intelligent
    
* **Simplicity scales** - Started with pendrives, now tracks everything
    

## What's Next: Future Improvements

The foundation is solid, so I'm adding features that solve real pain points:

**QR Code Integration**: Generate QR codes for each item that link directly to their details. Scan with phone → instant access.

**Capacity Analytics**: Total up storage capacity across all drives, and identify gaps in my hardware collection.

## The Bigger Picture

This tiny project reminded me why I love building solutions to real problems. Not every system needs machine learning, microservices, or a dedicated mobile app. Sometimes, Google Sheets, a bit of scripting, and some creativity are exactly the right tools.

The best productivity systems are the ones you actually use. And for keeping track of my ever-growing collection of tech accessories, this simple approach has been perfect.]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1759218185506%2F774ed865-391e-45b2-a959-e2a106cc230c.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Building a Basic AI Agent]]></title>
            <link>https://www.ravgeet.in/blog/building-an-ai-powered-function-orchestrator-when-ai-becomes-your-code-planner-5d6o</link>
            <guid>https://www.ravgeet.in/blog/building-an-ai-powered-function-orchestrator-when-ai-becomes-your-code-planner-5d6o</guid>
            <pubDate>Sat, 11 Oct 2025 13:35:15 GMT</pubDate>
            <description><![CDATA[As developers, we constantly face a dilemma: how do we make our code flexible enough to handle...]]></description>
            <content:encoded><![CDATA[As developers, we constantly face a dilemma: **how do we make our code flexible enough to handle natural human requests without hardcoding every possible scenario?**

While working on various automation projects, I kept running into the same pattern—users would ask for something in plain English, like *"Can you calculate 2+25-4^2?* or *"Process these files and send me a summary",* but my code was rigid, expecting specific formats and predefined workflows.

The breakthrough came when I realized: **What if AI handled the planning, and I just focused on building solid, reusable functions?** Instead of trying to anticipate every user request, let AI interpret the intent and dynamically orchestrate my functions.

This post walks through building a mini AI agent framework that separates **what you can do** (functions) from **how to do it** (AI planning). The result? Code that adapts to human intent rather than forcing humans to adapt to your interface.

## The Problem: Traditional Code vs. Human Intent

How many times have you written code that looks like this?

```python
def complex_math_solver(expression):
    # Parse expression
    # Apply PEMDAS rules
    # Handle edge cases
    # Return result
    pass
```

The problem? You're cramming **parsing logic**, **mathematical rules**, and **execution** into one monolithic function. What if we could separate these concerns entirely?

## The Solution: AI as a Function Orchestrator

Instead of hardcoding business logic, what if we let AI handle the **planning** while we focus on building **atomic, reusable functions**?

Here's the architecture:

### Step 1: Define Atomic Functions

First, we create simple, single-purpose functions that do one thing well. Think of these as your building blocks—each function is pure, testable, and completely independent. The key is keeping them atomic so AI can combine them in any order to solve complex problems.

```python
def sum(a, b):
    return a + b

def multiply(a, b):
    return a * b

def power(a, b):
    return a ** b

# Function registry for dynamic execution
FUNCTIONS = {
    "sum": sum,
    "multiply": multiply,
    "power": power,
}
```

### Step 2: Document Your Functions (For AI)

Next, we create clear documentation for each function. This isn't just good practice — it's essential for AI to understand what each function does and how to use it. Think of this as your function's "instruction manual" that AI reads to make smart planning decisions.

```python
DOCS = {
    "sum": {
        "description": "Add two numbers",
        "args": {
            "a": {
                "type": "number",
                "description": "a float or int number",
            },
            "b": {
                "type": "number",
                "description": "a float or int number",
            },
        },
    },
    "multiply": {
        "description": "Multiply two numbers",
        "args": {
            "a": {
                "type": "number",
                "description": "a float or int number",
            },
            "b": {
                "type": "number",
                "description": "a float or int number",
            },
        },
    },
    "power": {
        "description": "Raise a to the power of b",
        "args": {
            "a": {
                "type": "number",
                "description": "a float or int number",
            },
            "b": {
                "type": "number",
                "description": "a float or int number",
            },
        },
    },
}
```

### Step 3: Let AI Generate Execution Plans

Finally, we let AI do the heavy lifting — interpreting natural language requests and creating step-by-step execution plans. The AI uses your function documentation to understand what's possible, then figures out the optimal sequence to achieve the user's goal.

```python
def get_action_plan(user_query):
    prompt = f"""
    You are an AI planner. Convert the user's request into a step-by-step plan.

    Available functions:
    {json.dumps(DOCS, indent=2)}

    User's query: "{user_query}"

    Return a JSON plan with ordered steps.
    """

    response = openai.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}],
        temperature=0
    )
    return json.loads(response.choices[0].message.content)
```

## Real Example: "Solve 2+2\*5+4^2"

When a user sends this request to the system, it gets passed to OpenAI along with the function documentation. The AI analyzes the mathematical expression, applies PEMDAS rules, and returns a structured JSON plan that breaks down the calculation into atomic steps using the available functions.

**Input:** Natural language request  
**AI Output:** Structured execution plan

```json
{
  "steps": [
    { "function": "power", "args": { "a": 4, "b": 2 } },
    { "function": "multiply", "args": { "a": 2, "b": 5 } },
    { "function": "sum", "args": { "a": 2, "b": "<result_of_step_2>" } },
    {
      "function": "sum",
      "args": { "a": "<result_of_step_3>", "b": "<result_of_step_1>" }
    }
  ]
}
```

**Execution Output:**

```plaintext
Step 1: power(4, 2) → 16
Step 2: multiply(2, 5) → 10
Step 3: sum(2, 10) → 12
Step 4: sum(12, 16) → 28

Final Result: 28
```

## The Magic: Dynamic Plan Execution

This function takes the AI-generated plan and executes it step by step. The key insight is **result chaining**—each step can reference outputs from previous steps using placeholders like `<result_of_step_1>`. The system automatically resolves these references, creating a dynamic pipeline where complex calculations emerge from simple function compositions.

```python
def execute_plan(plan):
    results = {}
    for idx, step in enumerate(plan["steps"], start=1):
        func_name = step["function"]
        args = step["args"]

        # Replace placeholders with previous results
        for k, v in args.items():
            if isinstance(v, str) and v.startswith("<result_of_step_"):
                step_idx = int(v.split("_")[-1].replace(">", ""))
                args[k] = results[step_idx]

        # Execute function dynamically
        func = FUNCTIONS[func_name]
        result = func(**args)
        results[idx] = result

        print(f"Step {idx}: {func_name}({args}) → {result}")

    return results[len(results)]
```

## Why This Architecture Wins

### **Separation of Concerns**

* **You write:** Pure, testable functions
    
* **AI handles:** Complex planning and orchestration
    
* **System manages:** Execution flow and state
    

### **Human-in-the-Loop Safety**

```python
if "error" in plan:
    print("❌ Error in plan generation:")
    print(plan["error"]["message"])
    sys.exit(1)  # Safe exit on planning failures
```

For example, if a user asks *"Divide 10 by 0 and add 5"*, the AI can detect this is mathematically impossible and return an error response like:

```json
{
  "error": {
    "message": "Cannot divide by zero - this operation is undefined in mathematics"
  }
}
```

This prevents dangerous operations from executing and provides clear feedback to users.

### **Infinite Extensibility**

Today, it's math functions:

```python
FUNCTIONS = {
    "sum": sum,
    "multiply": multiply,
    "divide": divide
}
```

Tomorrow it could be **anything**:

```python
FUNCTIONS = {
    "read_file": read_file,
    "send_email": send_email,
    "query_database": query_db,
    "fetch_weather": weather_api,
    "analyze_sentiment": sentiment,
}
```

## Real-World Applications

### File Operations

*"Take all .txt files in /docs, extract headings, and create a summary document"*

**Required Functions:** `list_files()`, `read_file()`, `extract_headings()`, `create_document()`, `write_file()`

### API Orchestration

*"Get weather for New York, if it's raining, send a Slack message to #general"*

**Required Functions:** `fetch_weather()`, `check_condition()`, `send_slack_message()`

### Data Pipeline

*"Load sales.csv, calculate monthly averages, generate a chart, and email it to the team"*

**Required Functions:** `load_csv()`, `calculate_average()`, `group_by_month()`, `generate_chart()`, `send_email()`

### MCP Server Integration

*"Query our customer database, analyze sentiment of recent feedback, and create a dashboard"*

**Required Functions:** `query_database()`, `analyze_sentiment()`, `aggregate_data()`, `create_dashboard()`, `save_report()`

## The Bigger Picture

This isn't just a math solver — it's a **mini AI agent framework**. The system provides:

1. **Function Library:** Atomic, reusable components
    
2. **AI Planner:** Intelligent request interpretation
    
3. **Execution Engine:** Safe, traceable function orchestration
    
4. **Human Oversight:** Approval and error handling
    

## What's Next?

1. **Add more function types** (file ops, API calls)
    
2. **Implement approval workflows** (show plan before execution)
    
3. **Add function validation** (type checking, parameter validation)
    
4. **Build a web interface** (make it accessible to non-developers)
    
5. **Integrate with MCP servers** (extend to complex business logic)
    

**The bottom line:** Stop hardcoding business logic. Let AI handle the planning, you handle the implementation. The result? More flexible, maintainable, and extensible code that adapts to user intent rather than forcing users to adapt to your interface.]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1759051142066%2F7eae6181-9d78-479c-b91e-f39a41468d4a.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[The ultimate guide to Python logging]]></title>
            <link>https://www.ravgeet.in/blog/the-ultimate-guide-to-python-logging-kdc</link>
            <guid>https://www.ravgeet.in/blog/the-ultimate-guide-to-python-logging-kdc</guid>
            <pubDate>Wed, 27 Aug 2025 04:30:31 GMT</pubDate>
            <description><![CDATA[When an application runs, it performs a tremendous number of tasks, with many happening behind the...]]></description>
            <content:encoded><![CDATA[When an application runs, it performs a tremendous number of tasks, with many happening behind the scenes. Even a simple to-do application has more than you'd expect. The app will at a bare minimum have tons of tasks like user logins, creating to-dos, updating to-dos, deleting to-dos, and duplicating to-dos. The tasks an application performs can result in success or potentially result in some errors.

For anything you're running that has users, you'll need to at least consider monitoring events happening so that they can be analyzed to identify bottlenecks in the performance of the application. This is where **logging** is useful. Without logging, it's impossible to have insight or observability into what your application is actually doing.

In this article, you'll learn how to create logs in a Python application using the Python logging module. Logging can help Python developers of all experience levels develop and analyze an application's performance more quickly. Let's dig in!

Read the full blog on [Honeybadger](https://www.honeybadger.io/blog/python-logging/).

{% embed https://www.honeybadger.io/blog/python-logging/ %}

Thanks for reading 💜

---

[I publish a monthly newsletter in which](https://www.tiny.cloud/) I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come ac[ross while surfing the web.](https://www.tiny.cloud/)

[Connect with](https://www.tiny.cloud/) [me through Twitter • LinkedIn • Github or](https://www.tiny.cloud/) send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Working with Markdown in Python]]></title>
            <link>https://www.ravgeet.in/blog/working-with-markdown-in-python-2k5</link>
            <guid>https://www.ravgeet.in/blog/working-with-markdown-in-python-2k5</guid>
            <pubDate>Wed, 27 Aug 2025 04:28:57 GMT</pubDate>
            <description><![CDATA[If you use the Internet, you have surely come across the term Markdown. Markdown is a lightweight...]]></description>
            <content:encoded><![CDATA[If you use the Internet, you have surely come across the term **Markdown.** [Markdown](https://daringfireball.net/projects/markdown/) is a lightweight markup language that makes it very easy to write formatted content. It was created by John Gruber and Aaron Swartz in 2004. It uses very easy-to-remember syntax and is therefore used by many bloggers and content writers around the world. Even this blog that you are reading is written and formatted using Markdown.

Markdown is one of the most widely used formats for storing formatted [data. It](https://daringfireball.net/projects/markdown/) easily integrates with Web technologies, as it can be converted to HTML or vice versa using Markdown compilers. It allows you to write HTML entities, such as headings, lists, images, links, tables, and more without much effort or code. It is used in blogs, content management systems, Wikis, documentation, and many more places.

In this article, you'll learn how to work with Markdown in a Python ap[plicatio](https://daringfireball.net/projects/markdown/)n using different Python packages, including markdown, front matter, and markdownify.

Read the full blog on [Honeybadger](https://www.honeybadger.io/blog/python-markdown/).

{% embed https://www.honeybadger.io/blog/python-markdown/ %}

Thanks for reading 💜

---

[I publish a monthly newsletter in which](https://www.tiny.cloud/) I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come ac[ross while surfing the web.](https://www.tiny.cloud/)

[Connect with](https://www.tiny.cloud/) [me through Twitter • LinkedIn • Github or](https://www.tiny.cloud/) send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Handling undo functions in rich text editors]]></title>
            <link>https://www.ravgeet.in/blog/handling-undo-functions-in-rich-text-editors-2lgg</link>
            <guid>https://www.ravgeet.in/blog/handling-undo-functions-in-rich-text-editors-2lgg</guid>
            <pubDate>Fri, 01 Aug 2025 12:38:55 GMT</pubDate>
            <description><![CDATA[Undo and redo operations are a must-have feature in any rich text editor – they’re a user's safety...]]></description>
            <content:encoded><![CDATA[Undo and redo operations are a must-have feature in any rich text editor – they’re a user's safety net. For a great user experience (UX), users need to solve their editing problems in a rich text editor.

An undo/redo button makes your users more confident, because it’s a clear signal that if they make a mistake, they can easily undo and restore changes. For example, if a user accidentally deletes a paragraph, the undo function can restore their work – and spare them a lot of frustration.

However, implementing the undo/redo functionality is complicated. It requires an understanding of [**data structures like Stack**](https://www.geeksforgeeks.org/stack-data-structure/).

You need to know which actions need to be pushed onto the stack, as well as when to push them, and the same goes for the pop operation.

In this article, you'll find out about the complexity of creating and maintaining the undo/redo functionality, and see how the [**TinyMCE rich text editor**](https://www.tiny.cloud/tinymce/) makes it easy.

Read the full blog on [Tiny](https://www.tiny.cloud/blog/undo-function-handling/).

{% embed https://www.tiny.cloud/blog/undo-function-handling/ %}

Thanks for reading 💜

---

[I publish a monthly newsletter in which](https://www.tiny.cloud/) I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come ac[ross while surfing the web.](https://www.tiny.cloud/)

[Connect with](https://www.tiny.cloud/) [me through Twitter • LinkedIn • Github or](https://www.tiny.cloud/) send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1750429199494%2F62a8139a-dc56-4364-ac06-4587c9e57daf.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[🎬 Introducing Ravgeek: Dev Concepts in 60 Seconds]]></title>
            <link>https://www.ravgeet.in/blog/introducing-ravgeek-dev-concepts-in-60-seconds-54nj</link>
            <guid>https://www.ravgeet.in/blog/introducing-ravgeek-dev-concepts-in-60-seconds-54nj</guid>
            <pubDate>Sat, 19 Jul 2025 02:41:33 GMT</pubDate>
            <description><![CDATA[After years of writing code, debugging endlessly, and explaining APIs to teammates over coffee, I’ve...]]></description>
            <content:encoded><![CDATA[After years of writing code, debugging endlessly, and explaining APIs to teammates over coffee, I’ve finally taken the plunge into something new — **bite-sized developer explainers on YouTube**.

📺 My new channel is called [**Ravgeek**](https://www.youtube.com/@ravgeek) (“t” dropped from my name)— and it's built around a simple idea:

> **Make technical concepts simple, fun, and fast.**

Whether it’s understanding what a REST API is, how Git works, or when to use GraphQL, each video is designed to explain core ideas in **under 60 seconds** — in a way that’s accessible to beginners and still fun for experienced devs.

Here’s a video in which I explain - “What is prompt engineering”:

{% embed https://www.youtube.com/watch?v=LnG3Moja5nY %}

You’ll see:

* ⚡️ Rapid, to-the-point explanations
    
* 🎙️ Conversational storytelling (think devs talking over chai)
    
* 🎨 Animations, avatars, and a touch of humor

This has been a passion project for me — combining my love for **coding, storytelling, and design** — and I’m excited to finally share it with the world.

👉 Check out the channel: [youtube.com/@ravgeek](https://www.youtube.com/@ravgeek)  
💬 And if you like what you see, hit that subscribe button and let me know what topic you'd like me to cover next.

Let’s learn, laugh, and geek out together.]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to enable in-app Notifications using TinyMCE APIs]]></title>
            <link>https://www.ravgeet.in/blog/how-to-enable-in-app-notifications-using-tinymce-apis-39dh</link>
            <guid>https://www.ravgeet.in/blog/how-to-enable-in-app-notifications-using-tinymce-apis-39dh</guid>
            <pubDate>Thu, 17 Jul 2025 12:44:14 GMT</pubDate>
            <description><![CDATA[Notifications add value to an app by helping to build conversations between users. They can also help...]]></description>
            <content:encoded><![CDATA[Notifications add value to an app by helping to build conversations between users. They can also help make an interface less hostile by sharing important information. And it’s because they’re so useful that you’re being flooded by feature requests that are asking for notifications to be added to your app. Now (please).

However, because of the noise, it’s hard to set aside the time to figure out the necessary requirements: does it need an opt-in and opt-out feature? Audience segmentation? Personalization? 

The work involved just keeps piling up. 

The good news is that in-app notifications (the kind of notifications that give useful information on screen after a change or event), are easy to handle with a [**reliable rich text editor such as T**](https://www.tiny.cloud/)[**inyMCE**. The editor has a notifications AP](https://www.tiny.cloud/)I that can communicate vital information to the user, and is easy and quick to set up. In this article, you'll find a guide on how to set up and manage notifications in the TinyMCE rich text editor, with NotificationManager API.

Read the full blog on [Tiny](https://www.tiny.cloud/blog/enable-in-app-notifications/).

{% embed https://www.tiny.cloud/blog/enable-in-app-notifications/ %}

Thanks for reading 💜

---

[I publish a monthly newsletter in which](https://www.tiny.cloud/) I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come ac[ross while surfing the web.](https://www.tiny.cloud/)

[Connect with](https://www.tiny.cloud/) [me through Twitter • LinkedIn • Github or](https://www.tiny.cloud/) send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1750429108540%2Fee479c2d-5006-489f-bc2a-03cb8395da7a.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Automate GitHub stats reporting with scheduled pipelines]]></title>
            <link>https://www.ravgeet.in/blog/automate-github-stats-reporting-with-scheduled-pipelines-1jo3</link>
            <guid>https://www.ravgeet.in/blog/automate-github-stats-reporting-with-scheduled-pipelines-1jo3</guid>
            <pubDate>Thu, 17 Jul 2025 06:59:26 GMT</pubDate>
            <description><![CDATA[Release notes provide essential documentation when a new software version is released. For release...]]></description>
            <content:encoded><![CDATA[Release notes provide essential documentation when a new software version is released. For release notes to be most effective, dev teams must consolidate all of the work that has been done since the previous release. It is a hectic task that requires a lot of effort and time sorting through weeks or even months of software issues and pull requests.

Why not make the life of the release team easier by automating the creation of release notes? You can, using a combination of GitHub API and a [CI/CD tool](https://circleci.com/blog/what-is-a-ci-cd-pipeline/) like CircleCI. Automate the task of fetching issues and pull requests, and put them in a single place where they can be accessed easily by the release notes team.

In this tutorial, you’ll learn to use the GitHub API and CircleCI to create weekly stats for your GitHub repositories. The plan is to build an automated workflow using CircleCI [scheduled pipelines](https://circleci.com/blog/using-scheduled-pipelines/). The pipeline will fetch all the issues and pull requests made during a specified interval, save these stats in a file, and commit this file back to the repository.

Read the full blog on [CircleCI](https://circleci.com/blog/automate-github-stats/).

{% embed https://circleci.com/blog/automate-github-stats/ %}

Thanks for reading 💜

---

I publish a [monthly newsletter](https://www.ravsam.in/newsletter/) in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come across while surfing the web.

Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) or send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F25p5xraxy15k2yyxevmi.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Building a Smart Session Tracker for Your Mac's Menu Bar]]></title>
            <link>https://www.ravgeet.in/blog/building-a-smart-session-tracker-for-your-macs-menu-bar-41km</link>
            <guid>https://www.ravgeet.in/blog/building-a-smart-session-tracker-for-your-macs-menu-bar-41km</guid>
            <pubDate>Wed, 16 Jul 2025 17:07:56 GMT</pubDate>
            <description><![CDATA[Picture this: You sit down at your Mac with a coffee, planning to "quickly check a few emails." Next...]]></description>
            <content:encoded><![CDATA[Picture this: You sit down at your Mac with a coffee, planning to "quickly check a few emails." Next thing you know, it's 3 PM, your coffee has achieved room temperature, and you're wondering if you've entered some sort of time vortex. Sound familiar?

If you're nodding your head (and possibly rubbing your stiff neck), you're not alone. In our hyper-connected world, time has a sneaky way of slipping through our fingers like sand – or like that last slice of pizza when you're not paying attention.

That's why I built a session tracker that lives right in your Mac's menu bar. It's like having a gentle, persistent friend who reminds you to take breaks, tracks your work patterns, and occasionally judges your life choices (in the nicest possible way).

## What Does This Digital Time Wizard Do?

Our session tracker is basically a sophisticated time-keeping ninja that:

1. **Tracks Your Current Work Session** - It knows when you start working and keeps a running timer
    
2. **Detects Sleep/Wake Cycles** - Smartly figures out when you've been away from your computer
    
3. **Logs Daily Activity** - Keeps a record of all your work sessions throughout the day
    
4. **Sends Gentle Reminders** - Politely suggests you take a break after an hour (because your eyes and back will thank you)
    
5. **Shows Beautiful Stats** - Displays your current session time and daily totals right in your menu bar
    

Think of it as a Fitbit for your productivity, but instead of counting steps, it's counting the minutes you spend glued to your screen.

## The Magic Behind the Curtain

This isn't just any ordinary timer script – it's a surprisingly sophisticated piece of bash wizardry that handles all sorts of edge cases:

### Smart Session Detection

The script doesn't just start counting from when you run it. It's smart enough to:

* Detect when your Mac has been rebooted
    
* Figure out when you've been away (sleeping, lunch break, or that inevitable YouTube rabbit hole)
    
* Resume tracking seamlessly when you return
    

### Intelligent Time Gap Detection

Here's where it gets clever: the script monitors for gaps in activity longer than 2 minutes. If it detects you've been away, it logs your previous session and starts a new one. It's like having a personal assistant who's really good at reading between the lines.

### Cross-Reboot Persistence

Even if you restart your Mac, the script remembers your previous session and can estimate when it ended. It's like having a time-tracking elephant – it never forgets.

## Setting Up Your New Digital Productivity Buddy

Ready to get this bad boy running on your Mac? Here's how to set it up:

### Prerequisites

First, you'll need **xbar** (formerly BitBar), which is a fantastic tool that lets you put the output of any script in your Mac's menu bar:

```bash
# Install xbar using Homebrew
brew install xbar
```

If you don't have Homebrew installed, grab it from [brew.sh](http://brew.sh) first.

### Optional but Recommended: Better Notifications

For prettier notifications, install `terminal-notifier`:

```bash
brew install terminal-notifier
```

Don't worry if you skip this – the script will fall back to macOS's built-in notification system.

### Installing the Script

1. **Create the xbar plugins directory** (if it doesn't exist):
    

```bash
mkdir -p "~/Library/ApplicationSupport/xbar/plugins"
```

2. **Create the script file**:
    

```bash
nano "~/Library/Application Support/xbar/plugins/current-session.1m.sh"
```

3. **Copy and paste the following code**:
    

```bash
#!/bin/bash

# Set PATH to include common locations
export PATH="/usr/local/bin:/opt/homebrew/bin:$PATH"

# File to store the session start time
SESSION_FILE="/tmp/current_session_start"
# File to store daily activity log
DAILY_LOG_FILE="/tmp/daily_activity_$(date +%Y%m%d)"

# Get current time
CURRENT_TIME=$(date +%s)

# Check if system was recently awakened by looking at uptime vs session file age
UPTIME_SECONDS=$(sysctl -n kern.boottime | awk '{print $4}' | sed 's/,//')
BOOT_TIME=$(date -r "$UPTIME_SECONDS" +%s 2>/dev/null || echo "$CURRENT_TIME")

# If session file doesn't exist or is older than boot time, create new session
if [ ! -f "$SESSION_FILE" ]; then
  SESSION_START="$CURRENT_TIME"
  SESSION_FILE_NEEDS_UPDATE=true
else
  STORED_TIME=$(cat "$SESSION_FILE")

  # Check if the stored time is valid (numeric and reasonable)
  if ! [[ "$STORED_TIME" =~ ^[0-9]+$ ]] || [ "$STORED_TIME" -lt 1000000000 ] || [ "$STORED_TIME" -gt $((CURRENT_TIME + 86400)) ]; then
    # Invalid timestamp, start new session
    SESSION_START="$CURRENT_TIME"
    SESSION_FILE_NEEDS_UPDATE=true
  elif [ "$STORED_TIME" -lt "$BOOT_TIME" ]; then
    # Session file is from before last boot, start new session
    SESSION_START="$CURRENT_TIME"
    SESSION_FILE_NEEDS_UPDATE=true
  else
    # Check if we've been asleep (gap in timestamps)
    LAST_CHECK_FILE="/tmp/last_session_check"
    if [ -f "$LAST_CHECK_FILE" ]; then
      LAST_CHECK=$(cat "$LAST_CHECK_FILE")
      TIME_GAP=$((CURRENT_TIME - LAST_CHECK))

      # If gap is more than 2 minutes, assume we were asleep and start new session
      if [ "$TIME_GAP" -gt 120 ]; then
        SESSION_START="$CURRENT_TIME"
        SESSION_FILE_NEEDS_UPDATE=true
      else
        SESSION_START="$STORED_TIME"
        SESSION_FILE_NEEDS_UPDATE=false
      fi
    else
      SESSION_START="$STORED_TIME"
      SESSION_FILE_NEEDS_UPDATE=false
    fi
  fi
fi

# Track daily activity - check if we need to log previous session before updating files
LAST_CHECK_FILE="/tmp/last_session_check"
NEW_SESSION_STARTED=false

# Check if we're about to start a new session and need to log the previous one
if [ -f "$LAST_CHECK_FILE" ] &amp;&amp; [ -f "$SESSION_FILE" ]; then
  LAST_CHECK=$(cat "$LAST_CHECK_FILE")
  PREV_SESSION_START=$(cat "$SESSION_FILE")
  TIME_GAP=$((CURRENT_TIME - LAST_CHECK))

  # If gap detected and we have a valid previous session, log it before starting new session
  if [ "$TIME_GAP" -gt 120 ] &amp;&amp; [ "$PREV_SESSION_START" -lt "$LAST_CHECK" ]; then
    PREV_SESSION_DURATION=$((LAST_CHECK - PREV_SESSION_START))
    # Only log sessions longer than 1 minute
    if [ "$PREV_SESSION_DURATION" -gt 60 ]; then
      echo "$PREV_SESSION_START $LAST_CHECK $PREV_SESSION_DURATION" >> "$DAILY_LOG_FILE"
    fi
    NEW_SESSION_STARTED=true
  fi
elif [ ! -f "$LAST_CHECK_FILE" ] &amp;&amp; [ -f "$SESSION_FILE" ]; then
  # First run after boot - check if we should log a session from before reboot
  PREV_SESSION_START=$(cat "$SESSION_FILE")
  if [ "$PREV_SESSION_START" -lt "$BOOT_TIME" ]; then
    # Session was from before boot, try to estimate when it ended (use boot time)
    PREV_SESSION_DURATION=$((BOOT_TIME - PREV_SESSION_START))
    if [ "$PREV_SESSION_DURATION" -gt 60 ] &amp;&amp; [ "$PREV_SESSION_DURATION" -lt 86400 ]; then
      # Only log if duration seems reasonable (between 1 minute and 24 hours)
      echo "$PREV_SESSION_START $BOOT_TIME $PREV_SESSION_DURATION" >> "$DAILY_LOG_FILE"
    fi
  fi
fi

# Update session file if we're starting a new session
if [ "$SESSION_FILE_NEEDS_UPDATE" = true ]; then
  echo "$SESSION_START" > "$SESSION_FILE"
  # Reset notification tracking for new session
  rm -f "/tmp/last_rest_notification"
fi

# Update the last check time
echo "$CURRENT_TIME" > "/tmp/last_session_check"

# Calculate the session duration in seconds
SESSION_DURATION=$((CURRENT_TIME - SESSION_START))

# Check if we need to show a rest notification (every hour)
NOTIFICATION_FILE="/tmp/last_rest_notification"
if [ "$SESSION_DURATION" -gt 3600 ]; then
  # Check if we've already shown a notification for this hour
  CURRENT_HOUR=$((SESSION_DURATION / 3600))
  if [ -f "$NOTIFICATION_FILE" ]; then
    LAST_NOTIFICATION_HOUR=$(cat "$NOTIFICATION_FILE")
  else
    LAST_NOTIFICATION_HOUR=0
  fi

  # Show notification if we haven't shown one for this hour yet
  if [ "$CURRENT_HOUR" -gt "$LAST_NOTIFICATION_HOUR" ]; then
    # Check if terminal-notifier is available
    if command -v terminal-notifier >/dev/null 2>&amp;1; then
      terminal-notifier -title "Session Tracker" -subtitle "Time for a rest" -message "You've been working for ${CURRENT_HOUR} hour(s). Consider taking a break!" -sound funk -group "session-tracker"
    else
      # Fallback to osascript if terminal-notifier is not available
      osascript -e "display notification \"You've been working for ${CURRENT_HOUR} hour(s). Consider taking a break!\" with title \"Session Tracker\" subtitle \"Time for a rest\" sound name \"Glass\""
    fi
    echo "$CURRENT_HOUR" > "$NOTIFICATION_FILE"
  fi
fi

# Calculate total daily activity
TOTAL_TODAY=0
if [ -f "$DAILY_LOG_FILE" ]; then
  while read -r start_time end_time duration; do
    if [ -n "$duration" ] &amp;&amp; [ "$duration" -gt 0 ]; then
      TOTAL_TODAY=$((TOTAL_TODAY + duration))
    fi
  done < "$DAILY_LOG_FILE"
fi

# Add current session to today's total
TOTAL_TODAY=$((TOTAL_TODAY + SESSION_DURATION))

# Convert duration to human-readable format
DURATION_HOURS=$((SESSION_DURATION / 3600))
DURATION_MINUTES=$(( (SESSION_DURATION % 3600) / 60 ))
DURATION_SECONDS=$((SESSION_DURATION % 60))

# Convert total daily time to human-readable format
TOTAL_HOURS=$((TOTAL_TODAY / 3600))
TOTAL_MINUTES=$(( (TOTAL_TODAY % 3600) / 60 ))

# Format the session output
if [ $DURATION_HOURS -gt 0 ]; then
  DURATION_STRING="${DURATION_HOURS}h ${DURATION_MINUTES}m"
else
  DURATION_STRING="${DURATION_MINUTES}m"
fi

# Format the daily total output
if [ $TOTAL_HOURS -gt 0 ]; then
  TOTAL_STRING="${TOTAL_HOURS}h ${TOTAL_MINUTES}m"
else
  TOTAL_STRING="${TOTAL_MINUTES}m"
fi

# Output the session duration and daily total
# Add warning indicator if session is over 1 hour
if [ "$SESSION_DURATION" -gt 3600 ]; then
  echo "⚠️ $DURATION_STRING | size=10"
else
  echo "🕒 $DURATION_STRING | size=10"
fi
echo "---"
echo "📊 Current Session: $DURATION_STRING"
echo "📅 Today Total: $TOTAL_STRING"

# Show session breakdown if there are previous sessions
if [ -f "$DAILY_LOG_FILE" ] &amp;&amp; [ -s "$DAILY_LOG_FILE" ]; then
  SESSION_COUNT=$(wc -l < "$DAILY_LOG_FILE")
  echo "🔢 Sessions Today: $((SESSION_COUNT + 1))"
fi

# Add rest reminder in dropdown if session is over 1 hour
if [ "$SESSION_DURATION" -gt 3600 ]; then
  echo "---"
  echo "⏰ Take a break! You've been working for over an hour | color=orange"
fi
```

4. **Make it executable**:
    

```bash
chmod +x "~/Library/Application Support/xbar/plugins/current-session.1m.sh"
```

5. **Start xbar and refresh**:
    
    * Launch xbar from your Applications folder
        
    * Click the xbar icon in your menu bar and select "Refresh all"
        

## Understanding the Code: A Gentle Journey Through Bash Land

Let's break down what this script does, step by step:

### The Setup Phase

```bash
SESSION_FILE="/tmp/current_session_start"
DAILY_LOG_FILE="/tmp/daily_activity_$(date +%Y%m%d)"
```

We store our session data in temporary files. The daily log file includes the date, so each day gets its own log file. It's like having a new diary page for each day!

### The Detective Work

```bash
UPTIME_SECONDS=$(sysctl -n kern.boottime | awk '{print $4}' | sed 's/,//')
BOOT_TIME=$(date -r "$UPTIME_SECONDS" +%s 2>/dev/null || echo "$CURRENT_TIME")
```

This is where we get all CSI about when your Mac was last booted. We use this to figure out if your session file is from before a reboot.

### The Smart Session Logic

The script has several scenarios it handles:

1. **First run ever**: Creates a new session
    
2. **File exists but invalid**: Starts fresh (protects against corrupted data)
    
3. **File is from before reboot**: Starts a new session post-reboot
    
4. **Checking for sleep gaps**: If there's a gap &gt; 2 minutes, it assumes you were away
    

### The Notification System

```bash
if [ "$SESSION_DURATION" -gt 3600 ]; then
  # Time for a break notification logic
fi
```

After an hour of work, the script gently reminds you to take a break. It's like having a caring friend who's also really good at math.

## What You'll See in Action

Once everything is running, you'll see:

* **🕒 45m** in your menu bar (showing your current session time)
    
* **⚠️ 1h 23m** when you've been working for over an hour (subtle hint to take a break)
    
* A dropdown menu showing:
    
    * 📊 Current Session: 1h 23m
        
    * 📅 Today Total: 3h 45m
        
    * 🔢 Sessions Today: 4
        
    * ⏰ Take a break! reminder (if you've been working too long)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sxuaak9qobvcwjyhd0x0.png)

## The Beauty of Simplicity

What I love about this solution is that it's:

* **Lightweight**: Uses minimal system resources
    
* **Unobtrusive**: Sits quietly in your menu bar
    
* **Smart**: Handles edge cases you didn't even think about
    
* **Customizable**: Easy to modify for your specific needs
    

## Customization Ideas

Want to make it your own? Here are some ideas:

1. **Change the break reminder interval**: Modify the `3600` seconds (1 hour) to whatever works for you
    
2. **Add different notification sounds**: Change the `sound funk` to `sound glass`, `sound ping`, etc.
    
3. **Modify the time gap detection**: Change the `120` seconds gap threshold
    
4. **Add more detailed logging**: Include timestamps, session names, or project tags
    

## The Philosophical Side: Why This Matters

In our always-on world, this simple script serves a deeper purpose. It's not just about tracking time – it's about being mindful of how we spend our most precious resource. By making our work patterns visible, we can:

* **Recognize unhealthy patterns** (like those 4-hour coding binges)
    
* **Celebrate productivity** (look at all those completed sessions!)
    
* **Build better habits** (those break reminders really do help)
    
* **Understand our rhythms** (maybe you're most productive in the morning?)
    

## Wrapping Up

Building this session tracker was a fun exercise in bash scripting and practical problem-solving. It started as a simple "how long have I been working?" question and evolved into a surprisingly sophisticated time-tracking system.

The best part? It's taught me to actually take breaks. Turns out, when your computer politely suggests you step away from the screen, you're more likely to listen than when your back is screaming at you.

So go ahead, give it a try! Your future self (and your neck) will thank you. And who knows? You might just discover that you're either more or less productive than you thought. Either way, at least you'll know for sure.]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fari7oqggbggwdn0x2nf6.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[What is Git | Explained in under a minute]]></title>
            <link>https://www.ravgeet.in/blog/what-is-git-explained-in-under-a-minute-50d4</link>
            <guid>https://www.ravgeet.in/blog/what-is-git-explained-in-under-a-minute-50d4</guid>
            <pubDate>Mon, 14 Jul 2025 07:56:55 GMT</pubDate>
            <description><![CDATA[A post by Ravgeet Dhillon]]></description>
        </item>
        <item>
            <title><![CDATA[Building a Real-Time CPU Monitor for macOS with xbar]]></title>
            <link>https://www.ravgeet.in/blog/building-a-real-time-cpu-monitor-for-macos-with-xbar-4nch</link>
            <guid>https://www.ravgeet.in/blog/building-a-real-time-cpu-monitor-for-macos-with-xbar-4nch</guid>
            <pubDate>Tue, 08 Jul 2025 04:00:51 GMT</pubDate>
            <description><![CDATA[Have you ever noticed your Mac's fan spinning wildly but couldn't quickly identify which process was...]]></description>
            <content:encoded><![CDATA[Have you ever noticed your Mac's fan spinning wildly but couldn't quickly identify which process was consuming all your CPU?

As a developer who uses Visual Studio Code daily, I rely heavily on its rich ecosystem of extensions to boost my productivity. But over time, I started noticing my MacBook Air heating up, the fans spinning loudly, and my system becoming sluggish — all while I was just editing code. When I opened the Activity Monitor, I saw one or more mysterious "Code Helper (Plugin)" processes consuming 90–100% CPU, but there was no clear indication of which extension was responsible.

VS Code spawns multiple helper processes, and most of them are generically named, making it incredibly difficult to trace high CPU usage back to a specific extension. This left me guessing — was it Copilot? ESLint? Live Server? I needed a way to monitor these extensions intelligently, without sacrificing performance or productivity.

That's what led me to build a solution — a lightweight tool that monitors CPU usage in real time, maps it to the responsible extension, and alerts me before things spiral out of control. Today, I'll walk you through building a lightweight, real-time CPU monitoring tool that lives in your macOS menu bar and sends notifications when processes exceed your defined thresholds.

## What We're Building

Our CPU monitor will:

* Display CPU status directly in the menu bar
    
* Alert you when any process exceeds 80% CPU usage
    
* Send native macOS notifications for high CPU processes
    
* Provide special handling for VS Code extensions and helpers
    
* Show detailed process information (PID, name, command)
    
* Indicate when all processes are running normally
    

## Prerequisites

Before we start, you'll need:

* macOS (this guide is macOS-specific)
    
* [xbar](https://xbarapp.com/) installed (formerly BitBar)
    
* Basic familiarity with shell scripting
    

## The Architecture

Our solution uses a simple but effective approach:

1. **xbar Integration**: The script runs every 5 minutes (indicated by the `.5m.` in the filename)
    
2. **Process Monitoring**: We use `ps` to capture all running processes with their CPU usage
    
3. **Threshold Detection**: Any process using more than 80% CPU triggers an alert
    
4. **Native Notifications**: We leverage macOS's `osascript` for system notifications
    
5. **Fallback Support**: Includes support for `terminal-notifier` as a backup
    

## The Complete Script

Here's the full [`vscode-ext-monitor.5m.sh`](http://vscode-ext-monitor.5m.sh) script:

```bash
#!/bin/bash

CPU_THRESHOLD=80.0
HIGH_CPU_FOUND=0

# Menu Bar Title
echo "🖥️ CPU Monitor"

# Store process information in a temporary file to avoid subshell issues
TEMP_FILE=$(mktemp)
ps -Ao pid,%cpu,command | grep -v "ps -Ao" | grep -v grep > "$TEMP_FILE"

# Read from the temporary file
while IFS= read -r line; do
  if [ -z "$line" ]; then
    continue
  fi

  cpu=$(echo "$line" | awk '{print $2}')
  pid=$(echo "$line" | awk '{print $1}')
  command=$(echo "$line" | cut -d ' ' -f3-)

  # Skip if CPU is not a valid number or is 0.0
  if ! echo "$cpu" | grep -q '^[0-9]*\.[0-9]*$' || [ "$cpu" = "0.0" ]; then
    continue
  fi

  is_high=$(echo "$cpu > $CPU_THRESHOLD" | bc)

  if [ "$is_high" -eq 1 ]; then
    HIGH_CPU_FOUND=1
    echo "---"
    echo "⚠️ High CPU ($cpu%)"
    echo "PID: $pid"

    # Get process name from command
    process_name=$(echo "$command" | awk '{print $1}' | xargs basename 2>/dev/null || echo "Unknown")
    echo "📱 Process: $process_name"

    # Show truncated command
    echo "💻 ${command:0:60}..."

    notification_title="From CPU Monitor"

    # Special handling for different process types
    if echo "$command" | grep -q ".vscode/extensions"; then
      ext=$(echo "$command" | grep -o "/Users/[^ ]*\.vscode/extensions/[^ ]*")
      echo "🧩 VS Code Extension: $(basename "$ext")"
      notification_message="High CPU: ${cpu}% by VS Code extension $(basename "$ext")"
    elif echo "$command" | grep -q "Code Helper"; then
      echo "🔧 VS Code Helper Process"
      notification_message="High CPU: ${cpu}% by VS Code Helper"
    else
      notification_message="High CPU: ${cpu}% by $process_name"
    fi

    # Desktop notification (macOS only) - with error handling
    if command -v osascript >/dev/null 2>&amp;1; then
      osascript -e "display notification \"$notification_message\" with title \"$notification_title\"" 2>/dev/null || {
        # Fallback: try using terminal-notifier if available
        if command -v terminal-notifier >/dev/null 2>&amp;1; then
          terminal-notifier -title "$notification_title" -message "$notification_message" 2>/dev/null
        fi
      }
    fi
  fi
done < "$TEMP_FILE"

# Clean up temporary file
rm -f "$TEMP_FILE"

if [ "$HIGH_CPU_FOUND" -eq 0 ]; then
  echo "---"
  echo "✅ All processes under ${CPU_THRESHOLD}%"
fi
```

## Key Technical Decisions

### 1\. Avoiding Subshell Issues

Initially, we faced a common bash pitfall where notifications wouldn't work because the `while` loop was running in a subshell:

```bash
# This doesn't work for GUI operations
ps ... | while read line; do
  osascript -e "display notification ..."
done
```

**Solution**: We use a temporary file approach to avoid the subshell:

```bash
TEMP_FILE=$(mktemp)
ps -Ao pid,%cpu,command > "$TEMP_FILE"
while read line; do
  # Process data and send notifications
done < "$TEMP_FILE"
rm -f "$TEMP_FILE"
```

### 2\. Robust CPU Validation

We validate CPU values to ensure we're working with actual numeric data:

```bash
if ! echo "$cpu" | grep -q '^[0-9]*\.[0-9]*$' || [ "$cpu" = "0.0" ]; then
  continue
fi
```

This prevents errors from malformed process data.

### 3\. Smart Process Classification

The script intelligently categorizes processes:

* **VS Code Extensions**: Detected by `.vscode/extensions` in the command path
    
* **VS Code Helpers**: Identified by "Code Helper" in the command
    
* **General Processes**: Everything else gets generic handling
    

### 4\. Notification Reliability

We implement a two-tier notification system:

```bash
osascript -e "display notification ..." 2>/dev/null || {
  # Fallback to terminal-notifier if available
  if command -v terminal-notifier >/dev/null 2>&amp;1; then
    terminal-notifier -title "..." -message "..."
  fi
}
```

## Installation and Setup

1. **Install xbar** if you haven't already:
    
    ```bash
    brew install --cask xbar
    ```
    
2. **Create the plugin directory**:
    
    ```bash
    mkdir -p "$HOME/Library/Application Support/xbar/plugins"
    ```
    
3. **Save the script** as [`vscode-ext-monitor.5m.sh`](http://vscode-ext-monitor.5m.sh) in the plugins directory
    
4. **Make it executable**:
    
    ```bash
    chmod +x "$HOME/Library/Application Support/xbar/plugins/vscode-ext-monitor.5m.sh"
    ```
    
5. **Launch xbar** and refresh to see your new CPU monitor
    

## Customization Options

### Adjust the CPU Threshold

Change the threshold by modifying this line:

```bash
CPU_THRESHOLD=80.0  # Change to your preferred percentage
```

### Modify the Update Frequency

Rename the file to change how often it runs:

* `.`[`1m.sh`](http://1m.sh) = Every minute
    
* `.`[`30s.sh`](http://30s.sh) = Every 30 seconds
    
* `.`[`10m.sh`](http://10m.sh) = Every 10 minutes
    

### Add More Process Types

Extend the classification logic:

```bash
elif echo "$command" | grep -q "chrome"; then
  echo "🌐 Chrome Process"
  notification_message="High CPU: ${cpu}% by Chrome"
```

## Troubleshooting

### Notifications Not Appearing?

1. **Check System Preferences**: Ensure notifications are enabled for Terminal in System Preferences &gt; Notifications &amp; Focus
    
2. **Test manually**:
    
    ```bash
    osascript -e 'display notification "Test" with title "Test"'
    ```
    
3. **Install terminal-notifier as backup**:
    
    ```bash
    brew install terminal-notifier
    ```
    

### Script Not Running?

1. **Verify file permissions**:
    
    ```bash
    ls -la "$HOME/Library/Application Support/xbar/plugins/"
    ```
    
2. **Check xbar is running** and refresh the menu
    
3. **Test the script manually**:
    
    ```bash
    cd "$HOME/Library/Application Support/xbar/plugins/"
    ./vscode-ext-monitor.5m.sh
    ```
    

## What's Next?

This CPU monitor provides a solid foundation that you can extend further:

* **Memory Monitoring**: Add RAM usage alerts
    
* **Network Activity**: Monitor processes with high network usage
    
* **Historical Tracking**: Log high CPU events to a file
    
* **Kill Process Feature**: Add menu options to terminate problematic processes
    
* **Custom Thresholds**: Different thresholds for different process types
    

## Conclusion

Building system monitoring tools doesn't require complex frameworks or heavy applications. With a simple bash script and xbar, we've created a lightweight, effective CPU monitor that:

* Provides real-time visibility into system performance
    
* Sends proactive notifications before problems escalate
    
* Offers detailed process information for quick troubleshooting
    
* Runs efficiently with minimal system overhead
    

The beauty of this approach is its simplicity and customizability. You have full control over the monitoring logic, notification behavior, and display format. Plus, since it's just a bash script, you can easily modify it to suit your specific needs.

*This CPU monitor has been tested on macOS Sequoia and later. The script should work on earlier versions but may require minor adjustments for notification handling.*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1751618719871%2F6e4b910d-06b5-4166-bf21-acd18e159db7.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Getting the Most Out of GitHub Copilot Chat in VS Code]]></title>
            <link>https://www.ravgeet.in/blog/getting-the-most-out-of-github-copilot-chat-in-vs-code-106i</link>
            <guid>https://www.ravgeet.in/blog/getting-the-most-out-of-github-copilot-chat-in-vs-code-106i</guid>
            <pubDate>Fri, 04 Jul 2025 09:49:57 GMT</pubDate>
            <description><![CDATA[GitHub Copilot is already an incredible tool for autocompleting code, but if you haven’t tried...]]></description>
            <content:encoded><![CDATA[GitHub Copilot is already an incredible tool for autocompleting code, but if you haven’t tried **Copilot Chat**, you’re missing out on one of the most powerful AI developer workflows available today.

In this post, you’ll walk through:

* How to use Copilot Chat in VS Code
    
* What it’s best at
    
* Real-world prompts you can use right away
    

## What Is GitHub Copilot Chat?

Copilot Chat brings the power of ChatGPT **directly into VS Code**, allowing you to ask natural language questions about your code, request explanations, generate tests, fix bugs, refactor logic, and more — without ever leaving your editor.

It’s like having an AI pair programmer that:

* Understands your code context
    
* Works inline or in a chat panel
    
* Helps you learn, debug, and ship faster
    

## What Can You Use Copilot Chat For?

Here are some powerful use cases:

### Code Understanding &amp; Explanation

You can highlight any block of code and ask:

* “Explain what this code does”
    
* “What is the time complexity here?”
    
* “Why am I getting this TypeError?”
    

### Code Improvement &amp; Refactoring

Ask it to:

* “Refactor this function to be cleaner”
    
* “Optimize this loop”
    
* “Suggest better variable names”
    

### Test Generation

Save hours writing boilerplate with prompts like:

* “Write unit tests for this function using Jest”
    
* “Generate test cases for edge inputs”
    

### Code Conversion &amp; Migration

Let it help with transitions like:

* “Convert this JS code to TypeScript”
    
* “Rewrite this to use async/await”
    
* “Switch from useEffect to React Query”
    

### General Cleanup

Ask it to:

* “Remove unused imports”
    
* “Simplify this logic”
    
* “Make this more readable”
    

## Some Example Prompts You Can Use Right Now

| Use Case | Example Prompt |
| --- | --- |
| Understand Code | `Explain this function step by step` |
| Improve Performance | `Optimize this loop for large datasets` |
| Refactor Logic | `Make this code more readable and concise` |
| Convert Language | `Translate this from Python to JavaScript` |
| Debug Errors | `Fix the error in this fetch call` |
| Generate Tests | `Write unit tests for this React component` |
| Learn Concepts | `What is useMemo in React and when to use it?` |
| Clean Up Code | `Remove all unused variables from this file` |

> ✅ Tip: You can highlight a code block and then open Copilot Chat to get smarter, context-aware answers.

## How to Enable Copilot Chat in VS Code

1. Install the GitHub Copilot Chat extension from the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=GitHub.copilot-chat)
    
2. Make sure you’re signed in to GitHub with an active Copilot plan
    
3. Open the Copilot Chat panel or use `Cmd/Ctrl + I` to start inline chat
    
4. Highlight code and right-click → **Ask Copilot**
    

## Final Thoughts

Copilot Chat takes the promise of AI-assisted development to the next level. It’s not just about writing code faster — it’s about **thinking through problems, debugging more efficiently, and learning as you go**, all inside your editor.]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fstock%2Funsplash%2FnbZHM2uwkJs%2Fupload%2F9c2c6c473ad9dfe9cc28dc9271647775.jpeg" length="0" type="image/jpeg"/>
        </item>
        <item>
            <title><![CDATA[Generating dynamic sales quotes with Dropbox Sign]]></title>
            <link>https://www.ravgeet.in/blog/generating-dynamic-sales-quotes-with-dropbox-sign-4jom</link>
            <guid>https://www.ravgeet.in/blog/generating-dynamic-sales-quotes-with-dropbox-sign-4jom</guid>
            <pubDate>Fri, 27 Jun 2025 17:08:20 GMT</pubDate>
            <description><![CDATA[Creating and sending price quotes is a necessary part of business, but it can also be a laborious...]]></description>
            <content:encoded><![CDATA[Creating and sending price quotes is a necessary part of business, but it can also be a laborious process, especially if the sales team sends out multiple quotes daily. You can make things easier on yourself and your sales team by automating your sales quote generation. An excellent way to do that is by using the [**Dropbox Sign API**](https://sign.dropbox.com/developers)[.](https://sign.dropbox.com/developers)

[Dropbox Sig](https://sign.dropbox.com/developers)n allows you to create and send bulk templates, as well as generate dynamic documents such as sales quotes. In this tutorial, you’ll create a command-line utility for generating sales quote documents using the Dropbox Sign API. You’ll pass input data as command line arguments, creating emailed sales quote documents t[hat customers can](https://sign.dropbox.com/developers) sign digitally.

Read the full blog on [Dropbox](https://sign.dropbox.com/blog/generating-dynamic-sales-quotes).

{% embed https://sign.dropbox.com/blog/generating-dynamic-sales-quotes %}

Thanks for reading 💜

---

I publish a [monthly newsletter](https://www.ravsam.in/newsletter/) in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come across while surfing the web.

Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) or send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1750428177112%2F383145d9-50e5-4cfd-9f1b-1c031a4117e6.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[How I Use GitHub Copilot and ChatGPT Together as a Frontend Developer]]></title>
            <link>https://www.ravgeet.in/blog/how-i-use-github-copilot-and-chatgpt-together-as-a-frontend-developer-2i23</link>
            <guid>https://www.ravgeet.in/blog/how-i-use-github-copilot-and-chatgpt-together-as-a-frontend-developer-2i23</guid>
            <pubDate>Sat, 21 Jun 2025 12:24:36 GMT</pubDate>
            <description><![CDATA[As a frontend developer, I'm constantly juggling between writing clean code, shipping features fast,...]]></description>
            <content:encoded><![CDATA[As a frontend developer, I'm constantly juggling between writing clean code, shipping features fast, and keeping my sanity intact. AI tools like **GitHub Copilot** and **ChatGPT** have completely changed how I work. While each tool is powerful on its own, using them **together** has helped me work faster, think clearly, and code smartly.

Here’s how I personally use **Copilot + ChatGPT** in my daily workflow.

---

## TL;DR: My Quick Comparison

| Tool | What I Use It For |
| --- | --- |
| **GitHub Copilot** | Real-time code completion inside my IDE (VS Code) |
| **ChatGPT** | Explaining bugs, brainstorming UI, writing docs, or generating code blocks |

I see Copilot as my **coding sidekick inside the editor**, and ChatGPT as my **thinking partner outside of it**.

---

## Real Workflow: Building a React Component

### Step 1: I Ask ChatGPT to Help Plan It

When I need something like a responsive navbar with Tailwind CSS, I type:

> "Create a responsive navbar with logo, links, and hamburger menu using React and Tailwind."

ChatGPT usually gives me a great starting point with code, accessibility notes, and even file structure suggestions.

### Step 2: I Paste That Into VS Code and Let Copilot Take Over

As I begin typing:

```js
function Navbar() {
  return (
```

Copilot fills in:

```js
<nav className="bg-white shadow-md p-4 flex justify-between items-center">
  <div className="text-xl font-bold">Logo</div>
  ...
</nav>
```

It handles the obvious stuff—repetitive patterns, responsive classes, even conditional rendering—while I focus on logic.

## How I Use Them Together Every Day

### 1\. **Rapid Prototyping**

* ChatGPT helps me quickly draft UI layouts.
    
* Copilot finishes JSX, props, and common logic on the fly.
    

### 2\. **Debugging**

* When I hit an error, I paste it into ChatGPT and ask:
    
    > "What’s wrong with this error?"
    
* Then I fix the bug in VS Code with Copilot suggesting inline changes.
    

### 3\. **Writing Tests**

* I ask ChatGPT to generate tests for my React components.
    
* I then use Copilot to autocomplete the repetitive test setup or assertions.
    

### 4\. **Refactoring**

* I send messy functions to ChatGPT to break them down and improve naming.
    
* Back in VS Code, Copilot speeds up implementing the improved version.
    

### 5\. **Explaining My Code**

* When I want to document my code or explain it to my team, I ask ChatGPT:
    
    > "Explain this hook and why the useEffect dependency array matters."
    

## My Favorite Productivity Tips

### Tip 1: Comment-Driven Prompts for Copilot

```js
// Create a responsive card component with image and title
```

Copilot often gives me a complete JSX block instantly.

### Tip 2: ChatGPT for Documentation

I paste a tricky function into ChatGPT and say:

> "Write a JSDoc for this and suggest better variable names."

### Tip 3: Code Reviews Get Easier

I now use GitHub Copilot to explain my PRs or generate commit messages. Copilot helps me fix minor issues before I even push.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1750060967446/43b291bb-9d50-484b-9c46-cfc346eb6f86.png align="center")

## Bonus Tools I Like

| Tool | Why I Use It |
| --- | --- |
| **VS Code + Copilot** | For live code suggestions |
| **ChatGPT Web + API** | For deeper analysis, ideas, and code generation |
| **ChatGPT Plugins** | For testing or GitHub integrations |

## My Final Thoughts

Using both GitHub Copilot and ChatGPT hasn’t just saved me time—it’s **leveled up how I think and build**.

Copilot gives me instant coding speed in the editor, while ChatGPT helps me think clearly, plan ahead, and document better. Whether I’m building a component, squashing bugs, or writing docs, these two tools make the process smoother.

So if you're a frontend dev like me, give this duo a try. You might never want to code alone again. ✨]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fstock%2Funsplash%2FkjqTlMHLci4%2Fupload%2F60cb77f5390dac04a3a7d5c20b8ec41d.jpeg" length="0" type="image/jpeg"/>
        </item>
        <item>
            <title><![CDATA[Improve Table Speed in React by Using Web Workers for Filters]]></title>
            <link>https://www.ravgeet.in/blog/improve-table-speed-in-react-by-using-web-workers-for-filters-3db0</link>
            <guid>https://www.ravgeet.in/blog/improve-table-speed-in-react-by-using-web-workers-for-filters-3db0</guid>
            <pubDate>Sat, 21 Jun 2025 12:24:22 GMT</pubDate>
            <description><![CDATA[TL;DR: I implemented a Web Worker–powered filtering system in a React data table component to...]]></description>
            <content:encoded><![CDATA[> TL;DR: I implemented a **Web Worker–powered filtering system** in a React data table component to eliminate UI lag and improve responsiveness when working with large datasets. Here's how and why.

## The Problem: Filtering Slows the UI

Our application relies heavily on a custom `<NewTable />` component that supports:

* Nested (hierarchical) rows
    
* Column-based filtering
    
* Custom filters
    
* Server-side pagination
    

As datasets grew into the thousands of rows, filtering became noticeably sluggish. The culprit? All filtering logic ran on the **main UI thread**, blocking React’s render cycle and causing the interface to freeze temporarily.

## Goal

Move heavy data-filtering logic off the main thread using **Web Workers**, without breaking existing functionality or developer experience.

## The Solution: Asynchronous Filtering with Web Workers

We built a pipeline that:

1. **Serializes hierarchical data** into a flat structure
    
2. Sends that data to a **dedicated Web Worker**
    
3. Worker filters based on column &amp; custom filters
    
4. Sends back the result
    
5. Updates the UI reactively and shows a loader while waiting
    

## Implementation Breakdown

### 1\. Set up `search.worker.js`

We created a Web Worker dynamically using a blob:

```ts
const searchWorker = () => {
  onmessage = async (e) => {
    importScripts("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js");
    importScripts("https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.10.7/dayjs.min.js");

    // ... logic for deep filtering, date handling, and custom filters ...
    
    postMessage({ success, result });
  };
};

let code = searchWorker.toString();
code = code.substring(code.indexOf("{") + 1, code.lastIndexOf("}"));
const blob = new Blob([code], { type: "application/javascript" });
const workerScript = URL.createObjectURL(blob);

export default workerScript;
```

### 2\. New Utility Function

A new async utility offloads data filtering:

```ts
const asyncFilterDataWithColumnAndCustomFilters = async (
  data,
  columns,
  columnFilters,
  customFilters,
  setData,
  setIsSearching,
) => {
  const searchWorker = new window.Worker(searchWorkerScript);

  const convertedData = data.map(row => ({
    id: row.id,
    values: columns.map(col => col.selector?.(row) ?? row[col.id]),
    items: (row.items || []).map(item => ({
      id: item.id,
      values: columns.map(col => col.selector?.(item) ?? item[col.id]),
    })),
  }));

  searchWorker.postMessage({ data, convertedData, columns, columnFilters, customFilters });

  searchWorker.onmessage = (e) => {
    setData(e.data.result);
    setIsSearching(false);
    searchWorker.terminate();
  };

  searchWorker.onerror = (err) => {
    console.error("Worker error", err.message);
    setIsSearching(false);
    searchWorker.terminate();
  };
};
```

### 3\. Hook Update

We extended the existing table filter hook:

```ts
export const useFilterDataWithColumnAndCustomFilters = ({
  data,
  columns,
  columnFilters,
  customFilters,
  setIsSearching,
}) => {
  const [filteredData, setFilteredData] = useState({ data: [], diff: 0 });

  useEffect(() => {
    asyncFilterDataWithColumnAndCustomFilters(
      data,
      columns,
      columnFilters,
      customFilters,
      setIsSearching,
      setFilteredData
    );
  }, [data, columnFilters, customFilters]);

  return { filteredData: filteredData.data, diff: filteredData.diff };
};
```

### 4\. Loading Indicator

We updated the table’s loading prop:

```tsx
<NewTable
  isLoading={isLoading || isSearching}
  // ...
/>
```

## Bonus: What the Worker Can Handle

* `BOOLEAN`, `DATE`, `DATETIME`, and `NUMBER` types
    
* Interval filters (e.g. date ranges)
    
* Null checks: `eq: "null"` and `ne: "null"`
    
* Multi-select picklists
    
* Filters nested rows **and** their parents
    

## Benefits

* **UI never freezes** during filtering
    
* **Filters run faster**, even on large datasets
    
* **Code is modular** and easy to maintain
    
* **Great user experience** with real-time filtering feedback
    

## Key Takeaways

* Use **Web Workers** to offload expensive tasks in the browser.
    
* Normalize complex data structures before sending to the worker.
    
* Gracefully handle worker errors and show meaningful UI states.
    
* It's surprisingly easy to integrate with React.
    

## Final Thoughts

Offloading filtering to a Web Worker has been one of the most impactful performance wins for our frontend in recent times. If you're working with large tables or slow filters, **give workers a shot** — your users (and frame rate) will thank you.

**Want help integrating something similar into your React app?**  
Feel free to [connect with me](https://ravgeet.in/contact).

*I wrote this blog post for my company,* [*CloudAnswers*](https://cloudanswers.com/)*.*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fstock%2Funsplash%2FyaK5It0P0Gc%2Fupload%2F483ed162f5c50831e986593bdbaed484.jpeg" length="0" type="image/jpeg"/>
        </item>
        <item>
            <title><![CDATA[How to Build an Internal Company Wiki from Scratch]]></title>
            <link>https://www.ravgeet.in/blog/how-to-build-an-internal-company-wiki-from-scratch-3lbk</link>
            <guid>https://www.ravgeet.in/blog/how-to-build-an-internal-company-wiki-from-scratch-3lbk</guid>
            <pubDate>Fri, 12 May 2023 15:21:12 GMT</pubDate>
            <description><![CDATA[A company wiki is a knowledge hub where organization-specific information can be easily accessed by...]]></description>
            <content:encoded><![CDATA[A company wiki is a knowledge hub where organization-specific information can be easily accessed by the individuals working in an organization. Information in the hub can be related to engineering operations, hiring procedures, employee information, and other company-specific information. Creating and maintaining a company wiki can be complex, but tools like GraphQL and Hygraph (previously GraphCMS) can make it easier.

[**GraphQL**](https://graphql.org/) is a query language for APIs that allows you to request and fetch only the data that you want.

[**Hygraph**](https://hygraph.com/) is a cloud platform for creating databases, tables, and powerful GraphQL APIs. It allows you to create content on the fly with features such as text editors, workflows, and advanced roles.

In this tutorial, you’ll learn to create an internal company wiki from scratch. The backend and content management will be implemented with [**Hygraph**](https://hygraph.com/), and the frontend with [**Next.js**](https://nextjs.org/).

Read the full blog on [Hygraph](https://hygraph.com/blog/build-company-wiki).

Thanks for reading 💜

---

I publish a [monthly newsletter](https://www.ravsam.in/newsletter/) in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come across while surfing the web.

Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [GitHub](https://github.com/ravgeetdhillon) or send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1674113713549%2F310d2fab-ad57-43b0-8b8d-748028208bee.jpeg" length="0" type="image/jpeg"/>
        </item>
        <item>
            <title><![CDATA[Frontend Monitoring: A Complete Guide]]></title>
            <link>https://www.ravgeet.in/blog/frontend-monitoring-a-complete-guide-239</link>
            <guid>https://www.ravgeet.in/blog/frontend-monitoring-a-complete-guide-239</guid>
            <pubDate>Tue, 31 Jan 2023 12:10:14 GMT</pubDate>
            <description><![CDATA[Frontend monitoring is a group of techniques for measuring application layer performance,...]]></description>
            <content:encoded><![CDATA[Frontend monitoring is a group of techniques for measuring application layer performance, accessibility, uptime, and error tracking and can also be used in web analytics. In other words, these methods monitor a software application’s frontend, the layer through which a user interacts with the system’s backend.

In this article, you’ll learn about the different aspects of frontend monitoring and related tools that you can use in your own software applications. This guide is suited for developers who want to implement frontend monitoring tools in their applications.

Read the full blog on [Cronitor](https://cronitor.io/blog/frontend-monitoring).

Thanks for reading 💜

---

I publish a [monthly newsletter](https://www.ravsam.in/newsletter/) in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come across while surfing the web.

Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [GitHub](https://github.com/ravgeetdhillon) or send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Setup and Customize Tailwind in Nuxt.js]]></title>
            <link>https://www.ravgeet.in/blog/how-to-setup-and-customize-tailwind-in-nuxtjs-ao8</link>
            <guid>https://www.ravgeet.in/blog/how-to-setup-and-customize-tailwind-in-nuxtjs-ao8</guid>
            <pubDate>Fri, 02 Dec 2022 12:14:30 GMT</pubDate>
            <description><![CDATA[CSS frameworks like Bootstrap, Bulma, and Materialize are hugely popular among front-end developers....]]></description>
            <content:encoded><![CDATA[CSS frameworks like Bootstrap, Bulma, and Materialize are hugely popular among front-end developers. They are a great way to quickly style an application on the set of standard guidelines. However, they are a little difficult to customize and bloated for small applications.

Tailwind CSS is a utility-first CSS framework. This means that instead of providing you with ready-made components, Tailwind provides utility-based CSS classes that you can use to style your components. This gives you more flexibility over your design and you can build your own UI framework on top of Tailwind CSS by extending the Tailwind classes.

In this tutorial, you’ll build a portfolio landing page that will have the author’s information and a form to subscribe to a newsletter. You’ll build the front end with Nuxt.js and style it using Tailwind CSS.

Read the full blog on [Mattermost](https://mattermost.com/blog/how-to-set-up-and-customize-tailwind-in-nuxt-js/).

Thanks for reading 💜

---

I publish a [monthly newsletter](https://www.ravsam.in/newsletter/) in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come across while surfing the web.

Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [GitHub](https://github.com/ravgeetdhillon) or send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1669178218615%2FBkySHylo1.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Build a Task Assignment App with Twilio Whatsapp, Strapi, and Next.js]]></title>
            <link>https://www.ravgeet.in/blog/build-a-task-assignment-app-with-twilio-whatsapp-strapi-and-nextjs-f5d</link>
            <guid>https://www.ravgeet.in/blog/build-a-task-assignment-app-with-twilio-whatsapp-strapi-and-nextjs-f5d</guid>
            <pubDate>Tue, 29 Nov 2022 07:19:24 GMT</pubDate>
            <description><![CDATA[In a working environment, each and every individual is assigned a task. Task assignment is one of the...]]></description>
            <content:encoded><![CDATA[In a working environment, each and every individual is assigned a task. Task assignment is one of the most important aspects in the successful completion of a project. However, it is also very important to communicate the tasks assignment duties to the concerned person. Hence, you need a way to send a message to the assignee that a new task has been assigned to them.

In this tutorial, you’ll learn to create a task assignment app using Next.js, Strapi, and Twilio. You’ll learn to use Next.js for building the frontend UI, Strapi for building the backend, and Twilio for sending WhatsApp notifications.

Read the full blog on [Twilio](https://www.twilio.com/blog/build-task-assignment-app-twilio-whatsapp-strapi-next-js).

Thanks for reading 💜

---

I publish a [monthly newsletter](https://www.ravsam.in/newsletter/) in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come across while surfing the web.

Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [GitHub](https://github.com/ravgeetdhillon) or send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1669178508994%2Fa7eQuWR2t.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[REST vs SOAP: why we recommend REST APIs for A2P messaging]]></title>
            <link>https://www.ravgeet.in/blog/rest-vs-soap-why-we-recommend-rest-apis-for-a2p-messaging-2no8</link>
            <guid>https://www.ravgeet.in/blog/rest-vs-soap-why-we-recommend-rest-apis-for-a2p-messaging-2no8</guid>
            <pubDate>Mon, 07 Nov 2022 05:09:59 GMT</pubDate>
            <description><![CDATA[Many businesses use messaging APIs to simplify communications with stakeholders and customers....]]></description>
            <content:encoded><![CDATA[Many businesses use messaging APIs to simplify communications with stakeholders and customers. Business short message service (SMS) tools enable organizations to implement one-time password (OTP) verification, alerts, reminders, and other types of customer support as well as marketing campaigns. They also enable organizations to centrally manage invoicing, human resources, and other internal activities.

Though many applications continue to rely on the SOAP API standard for SMS functionality, the newer REST API standard is actually the better choice. REST offers greater flexibility and speed, while SOAP is more rigid and can be more challenging to learn. This article will explain why we recommend you should choose the REST API standard for your business SMS.

Read the full blog on [Clicksend](https://blog.clicksend.com/soap-vs-rest-api/).

Thanks for reading 💜

---

I publish a [monthly newsletter](https://www.ravsam.in/newsletter/) in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come across while surfing the web.

Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [GitHub](https://github.com/ravgeetdhillon) or send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1667738294135%2FLrSFOGFN_.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Authoring NPM Packages with Monorepos]]></title>
            <link>https://www.ravgeet.in/blog/authoring-npm-packages-with-monorepos-3794</link>
            <guid>https://www.ravgeet.in/blog/authoring-npm-packages-with-monorepos-3794</guid>
            <pubDate>Fri, 14 Oct 2022 03:49:03 GMT</pubDate>
            <description><![CDATA[Suppose that you run a software development agency and you want to enforce a common linting rule set...]]></description>
            <content:encoded><![CDATA[Suppose that you run a software development agency and you want to enforce a common linting rule set and formatting guidelines for all of your JavaScript projects. You could install ESLint and Prettier in each of your projects. However, your company manages more than a hundred different projects, with custom rules for both ESLint and Prettier. So, if you decide to add or deprecate some rules, you'll have to update the rule sets in all of those projects.

A monorepo can help solve this issue. You can put all of your configuration code in a master repository, publish it as an npm package, and then import the npm package into your projects. Next time you want to change the rules, you only need to alter the monorepo project and the change will be reflected in all of your projects, as they're dependent on the monorepo project.

If you follow the steps in this tutorial, you’ll see how you can publish NPM packages using Lerna and keep your packages’ code in a monorepo.

Read the full blog on [Fusebit](https://fusebit.io/blog/npm-packages-with-monorepos/).

Thanks for reading 💜

---

I publish a [monthly newsletter](https://www.ravsam.in/newsletter/) in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come across while surfing the web.

Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [GitHub](https://github.com/ravgeetdhillon) or send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1665551517554%2FiKzMbsBHz.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Booking Appointments with Twilio, Notion, and FastAPI]]></title>
            <link>https://www.ravgeet.in/blog/booking-appointments-with-twilio-notion-and-fastapi-lhe</link>
            <guid>https://www.ravgeet.in/blog/booking-appointments-with-twilio-notion-and-fastapi-lhe</guid>
            <pubDate>Sat, 27 Aug 2022 06:38:26 GMT</pubDate>
            <description><![CDATA[Most businesses run around the concept of appointments. Appointments allow you to schedule different...]]></description>
            <content:encoded><![CDATA[Most businesses run around the concept of appointments. Appointments allow you to schedule different events for different individuals. For example, before seeing a doctor, it might be necessary to book an appointment. Businesses can leverage the power of WhatsApp to allow their customers to book appointments easily just by sending messages. They can also get updates or check the status of their appointments through WhatsApp messages.

In this tutorial, you’ll learn to use Twilio’s WhatsApp API with the Notion API and FastAPI to create appointments and get their statuses as well. You will use Notion for storing data, Twilio for sending WhatsApp messages, and FastAPI for API and business logic.

Read the full blog on [Twilio](https://www.twilio.com/blog/booking-appointments-twilio-notion-fastapi).

Thanks for reading 💜

---

I publish a [monthly newsletter](https://www.ravsam.in/newsletter/) in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come across while surfing the web.

Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [GitHub](https://github.com/ravgeetdhillon) or send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1660973475780%2Fg34NX9cus.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Using Python Scripts to Take Screenshots]]></title>
            <link>https://www.ravgeet.in/blog/using-python-scripts-to-take-screenshots-39ll</link>
            <guid>https://www.ravgeet.in/blog/using-python-scripts-to-take-screenshots-39ll</guid>
            <pubDate>Mon, 22 Aug 2022 07:30:51 GMT</pubDate>
            <description><![CDATA[There are many reasons why developers might want to capture screenshots of web pages. You might want...]]></description>
            <content:encoded><![CDATA[There are many reasons why developers might want to capture screenshots of web pages. You might want to capture an image generated from dynamic code that you've written, collect screenshots of web pages mentioned in a dataset that you're working with, or keep software documentation up to date by automating screenshots using a CI/CD tool.

It can be surprisingly tricky to take screenshots using Python, especially when JavaScript is involved. In this tutorial, you’ll learn to take screenshots of web pages using different approaches and packages in Python. You'll also see how a tailor-made solution like Urlbox can help you easily capture screenshots of websites.

Read the full blog on [Urlbox](https://www.urlbox.io/website-screenshots-python).

Thanks for reading 💜

---

I publish a [monthly newsletter](https://www.ravsam.in/newsletter/) in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come across while surfing the web.

Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) or send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1659946071193%2FE0V89rm6k.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Logging in Python]]></title>
            <link>https://www.ravgeet.in/blog/logging-in-python-97g</link>
            <guid>https://www.ravgeet.in/blog/logging-in-python-97g</guid>
            <pubDate>Sat, 13 Aug 2022 08:53:50 GMT</pubDate>
            <description><![CDATA[When an application runs, it performs a tremendous number of tasks. A simple to-do app can have tons...]]></description>
            <content:encoded><![CDATA[When an application runs, it performs a tremendous number of tasks. A simple to-do app can have tons of tasks like - user logins, creating to-dos, updating to-dos, deleting to-dos, and duplicating to-dos. These tasks can result in success or may end up with some errors. Hence, there is a need to monitor events happening and analyze them to identify bottlenecks in the performance of the application. This is where logging is useful.

In this article, you'll learn how to create logs in a Python application using the Python logging module. Logging can help Python developers of all experience levels develop and analyze an application's performance more quickly.

Read the full blog on [Honeybadger](https://www.honeybadger.io/blog/python-logging/).

Thanks for reading 💜

---

I publish a [monthly newsletter](https://www.ravsam.in/newsletter/) in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come across while surfing the web.

Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) or send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1659769720829%2FmJn9VISdT.jpeg" length="0" type="image/jpeg"/>
        </item>
        <item>
            <title><![CDATA[Build Client Payment Reminders using Twilio, Notion, and Python]]></title>
            <link>https://www.ravgeet.in/blog/build-client-payment-reminders-using-twilio-notion-and-python-2flg</link>
            <guid>https://www.ravgeet.in/blog/build-client-payment-reminders-using-twilio-notion-and-python-2flg</guid>
            <pubDate>Wed, 10 Aug 2022 11:08:09 GMT</pubDate>
            <description><![CDATA[Running a business requires payment handling. It doesn't matter whether you are a freelancer or a big...]]></description>
            <content:encoded><![CDATA[Running a business requires payment handling. It doesn't matter whether you are a freelancer or a big corporation, sometimes clients forget to pay their pending dues. If you have a huge list of clients, it makes for a tedious experience to go through the records daily and send them reminders. To solve this issue, you can automate the entire reminder workflow.

In this tutorial, you’ll learn to use Twilio’s WhatsApp API with the Notion API and Python to send payment reminders to your clients at regular intervals. You will use Notion for storing data, Twilio for sending WhatsApp messages, and Python to implement the business logic. We will create three reminders that will be sent 7 days before, 3 days before, 1 day before, and each day after the payment is due.

Read the full blog on [Twilio](https://www.twilio.com/blog/payment-reminders-twilio-notion-python).

Thanks for reading 💜

---

I publish a [monthly newsletter](https://www.ravsam.in/newsletter/) in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come across while surfing the web.

Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) or send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1659616843498%2FHmy2eM7vp.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Build and Deploy a Nuxt3 app to Netlify]]></title>
            <link>https://www.ravgeet.in/blog/build-and-deploy-a-nuxt3-app-to-netlify-1c2k</link>
            <guid>https://www.ravgeet.in/blog/build-and-deploy-a-nuxt3-app-to-netlify-1c2k</guid>
            <pubDate>Sun, 24 Jul 2022 14:43:18 GMT</pubDate>
            <description><![CDATA[Imagine you want to build and deploy a Nuxt3 app on Netlify. Because custom scripts are not allowed...]]></description>
            <content:encoded><![CDATA[Imagine you want to build and deploy a Nuxt3 app on Netlify. Because custom scripts are not allowed on Netlify, you will not be able to perform custom tasks like automated testing before deploying the website to your Jamstack hosting platform.

That is where continuous integration/continuous deployment comes in. With a CI/CD system, you can run the kind of automated tests that create successful deployments. In this tutorial, I will lead you through building a Nuxt3 app, writing automated tests for it, and deploying it on Netlify.

Read the full blog on [CircleCI](https://circleci.com/blog/deploy-nuxt3-app-to-netlify/).

Thanks for reading 💜

---

I publish a [monthly newsletter](https://www.ravsam.in/newsletter/) in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come across while surfing the web.

Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) or send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1656743404450%2FEDqPJbZ_O.avif" length="0" type="image/avif"/>
        </item>
        <item>
            <title><![CDATA[What is End-to-End Testing?]]></title>
            <link>https://www.ravgeet.in/blog/what-is-end-to-end-testing-395p</link>
            <guid>https://www.ravgeet.in/blog/what-is-end-to-end-testing-395p</guid>
            <pubDate>Wed, 06 Jul 2022 11:01:05 GMT</pubDate>
            <description><![CDATA[End-to-end testing, also known as E2E testing, is a methodology used for ensuring that applications...]]></description>
            <content:encoded><![CDATA[End-to-end testing, also known as E2E testing, is a methodology used for ensuring that applications behave as expected and that the flow of data is maintained for all kinds of user tasks and processes. This type of testing approach starts from the end user’s perspective and simulates a real-world scenario. For example, on a sign-up form, you can expect a user to perform one or more of these actions:

- Enter a blank email and password
- Enter a valid email and password
- Enter an invalid email and password
- Click a sign-up button

You can use end-to-end testing to verify that all these actions work as a user might expect.

End-to-end testing may sound comprehensive, but there are many other testing methods that you should use with it to create a robust continuous integration practice.

Read the full blog on [CircleCI](https://circleci.com/blog/what-is-end-to-end-testing/).

Thanks for reading 💜

---

I publish a [monthly newsletter](https://www.ravsam.in/newsletter/) in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come across while surfing the web.

Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) or send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1656742896526%2FdQFF8q5P1.avif" length="0" type="image/avif"/>
        </item>
        <item>
            <title><![CDATA[Handling Undo/Redo Functions in Rich Text Editors]]></title>
            <link>https://www.ravgeet.in/blog/handling-undoredo-functions-in-rich-text-editors-idm</link>
            <guid>https://www.ravgeet.in/blog/handling-undoredo-functions-in-rich-text-editors-idm</guid>
            <pubDate>Sun, 19 Jun 2022 13:32:43 GMT</pubDate>
            <description><![CDATA[If you’ve ever written a blog or worked with a Content Management System (CMS), there’s a good chance...]]></description>
            <content:encoded><![CDATA[If you’ve ever written a blog or worked with a Content Management System (CMS), there’s a good chance you’ve heard about rich text editors – popularly known as WYSIWYG (What You See Is What You Get) editors. 

A rich text editor allows users to enter text and formatting via a GUI. It converts input into HTML behind the scenes. Why is this important? It enables non-technical users to create web-ready code. Having been on the market since the late 1990s, rich text editors have progressively evolved to support complex features like undo/redo.

Undo and redo operations are a must-have feature in any rich text editor – they’re a user's safety net. For a great user experience (UX), users need to solve their editing problems in a rich text editor.

In this article, you'll find out about the complexity of creating and maintaining the undo/redo functionality, and see how the TinyMCE rich text editor makes it easy.

Read the full blog on [Tiny.Cloud](https://www.tiny.cloud/blog/undo-function-handling/).

Thanks for reading 💜

---

I publish a [monthly newsletter](https://www.ravsam.in/newsletter/) in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come across while surfing on the web.

Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) or send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1653992846783%2F2Zyrj2Zz9.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[The Complete List of OAuth 2 Grants]]></title>
            <link>https://www.ravgeet.in/blog/the-complete-list-of-oauth-2-grants-5bpm</link>
            <guid>https://www.ravgeet.in/blog/the-complete-list-of-oauth-2-grants-5bpm</guid>
            <pubDate>Sun, 19 Jun 2022 13:29:06 GMT</pubDate>
            <description><![CDATA[Authorization is necessary to protect resources from malicious use. When the Internet Engineering...]]></description>
            <content:encoded><![CDATA[Authorization is necessary to protect resources from malicious use. When the Internet Engineering Task Force (IETF) drafted internet protocols and rules, it also planned out different methods to protect and access resources on a server. These efforts led to OAuth 1.0 and later OAuth 2.0.

The OAuth 2.0 specification is an authorization framework containing a number of methods, or grants, by which a client application can get an access token. The access token can be presented to an API endpoint, which can then examine it to determine validity and permissions levels. Each grant type is designed for a particular use case.

OAuth 2.0 focuses on the authorization. There are other protocols like OpenID Connect (OIDC) that focus on authentication. OIDC allows the software to access login and profile information about the logged-in user.

This article will go through all the different OAuth 2 grant types and explain the flow for each so that you can determine which is the best fit and safely use it in your applications.

Read the full blog on [FusionAuth](https://fusionauth.io/learn/expert-advice/oauth/complete-list-oauth-grants).

Thanks for reading 💜

---

I publish a [monthly newsletter](https://www.ravsam.in/newsletter/) in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come across while surfing on the web.

Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) or send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1653992562934%2F1sAPvwjo1.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Creating Golang CRON Jobs]]></title>
            <link>https://www.ravgeet.in/blog/creating-golang-cron-jobs-3489</link>
            <guid>https://www.ravgeet.in/blog/creating-golang-cron-jobs-3489</guid>
            <pubDate>Mon, 30 May 2022 07:52:35 GMT</pubDate>
            <description><![CDATA[Scheduled tasks allow you to run specific code at a specified interval of time and are primarily used...]]></description>
            <content:encoded><![CDATA[Scheduled tasks allow you to run specific code at a specified interval of time and are primarily used within a CI/CD system to perform a variety of operations like nightly builds, GitHub repository cleanup, newsletters, and service monitoring, among others. You can use scheduled jobs to send notifications when a process succeeds or fails and to perform batch tasks without any human involvement.

In this article, you'll learn to create CRON jobs in Golang, a statically typed, compiled programming language designed by engineers at Google. Golang contains the best features from C and Python, like memory safety, automatic garbage collection, structural typing, and concurrency, to name a few. Specifically, you'll learn to use **gocron** and **cron.v2** to schedule tasks, explore the limitations of using these packages, and then consider a serverless and easy-to-use approach to task scheduling using Airplane.

Read the full blog on [Airplane](https://www.airplane.dev/blog/creating-golang-cron-jobs).

Thanks for reading 💜

---

I publish a [monthly newsletter](https://www.ravsam.in/newsletter/) in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come across while surfing on the web.

Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) or send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuy5iagb1h5omswidcoam.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Handling Pagination in Strapi v4 with SvelteKit]]></title>
            <link>https://www.ravgeet.in/blog/handling-pagination-in-strapi-v4-with-sveltekit-2omg</link>
            <guid>https://www.ravgeet.in/blog/handling-pagination-in-strapi-v4-with-sveltekit-2omg</guid>
            <pubDate>Mon, 23 May 2022 08:54:57 GMT</pubDate>
            <description><![CDATA[If you use any kind of web or mobile application, you may have come across a data table that lets you...]]></description>
            <content:encoded><![CDATA[If you use any kind of web or mobile application, you may have come across a data table that lets you view data by breaking it up into multiple pages. In the world of software development, this is known as pagination.

Pagination is an optimization technique that is used both on the frontend and backend to enhance the performance of your applications. With pagination, you can skip to the desired page and view the results for that particular page without loading any additional data. In this tutorial, you’ll learn how to work with Strapi for the backend and implement the pagination controls UI by building the frontend in Svelte.

Read the full blog on [Strapi](https://strapi.io/blog/handling-pagination-in-strapi-v4-with-svelte-kit).

Thanks for reading 💜

---

I publish a [monthly newsletter](https://www.ravsam.in/newsletter/) in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come across while surfing on the web.

Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) or send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1652943064782%2FrDpOo5xfa.jpeg" length="0" type="image/jpeg"/>
        </item>
        <item>
            <title><![CDATA[Using Custom Controllers in Strapi to Power a Next.js App]]></title>
            <link>https://www.ravgeet.in/blog/using-custom-controllers-to-power-a-nextjs-app-3apl</link>
            <guid>https://www.ravgeet.in/blog/using-custom-controllers-to-power-a-nextjs-app-3apl</guid>
            <pubDate>Thu, 19 May 2022 08:11:31 GMT</pubDate>
            <description><![CDATA[Strapi continues to be the most popular free, open-source, headless CMS, and, recently, it released...]]></description>
            <content:encoded><![CDATA[Strapi continues to be the most popular free, open-source, headless CMS, and, recently, it released v4. Built using Node.js with support for TypeScript, Strapi allows developers to perform CRUD operations using either REST or GraphQL APIs.

The best part of Strapi is that it allows users to customize its behavior, whether for the admin panel or the core business logic of your backend. You can modify its default controllers to include your own logic. For example, you might want to send an email when a new order is created.

In this tutorial, you’ll learn how to build a messaging app with Strapi on the backend and Next.js on the frontend. For this app, you’ll customize the default controllers to set up your own business logic.

Read the full blog on [Strapi](https://strapi.io/blog/using-custom-controllers-to-power-a-next-js-app).

Thanks for reading 💜

---

I publish a [monthly newsletter](https://www.ravsam.in/newsletter/) in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come across while surfing on the web.

Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) or send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1652435500816%2FFNjtamc9K.jpeg" length="0" type="image/jpeg"/>
        </item>
        <item>
            <title><![CDATA[Build a Chrome Extension in Next.js and Notion API]]></title>
            <link>https://www.ravgeet.in/blog/build-a-chrome-extension-in-nextjs-and-notion-api-ln5</link>
            <guid>https://www.ravgeet.in/blog/build-a-chrome-extension-in-nextjs-and-notion-api-ln5</guid>
            <pubDate>Mon, 18 Apr 2022 04:49:23 GMT</pubDate>
            <description><![CDATA[Chrome extensions are a great way to customize your browsing experience. Most of the time, Chrome...]]></description>
            <content:encoded><![CDATA[Chrome extensions are a great way to customize your browsing experience. Most of the time, Chrome extensions need to be reactive and this is where building the extension in vanilla JavaScript can be a painful experience. To overcome this shortcoming, you can use a JavaScript-based front-end framework like Next.js or Nuxt.js to build your Chrome extensions.

In this tutorial, you’ll learn to build a Chrome extension using Next.js. For this tutorial, you’ll build a Chrome extension that allows you to tag and save web links to a Notion database.

Read the full blog on [Bird Eats Bug](https://birdeatsbug.com/blog/build-a-chrome-extension-in-next-js-and-notion-api).

Thanks for reading 💜

---

I publish a [monthly newsletter](https://www.ravsam.in/newsletter/) in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come across while surfing on the web.

Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) or send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1649995838315%2FPQQNm3hTr.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Building a Realtime Chat App with React, Laravel, and WebSockets]]></title>
            <link>https://www.ravgeet.in/blog/building-a-realtime-chat-app-with-react-laravel-and-websockets-4clh</link>
            <guid>https://www.ravgeet.in/blog/building-a-realtime-chat-app-with-react-laravel-and-websockets-4clh</guid>
            <pubDate>Sat, 16 Apr 2022 06:34:33 GMT</pubDate>
            <description><![CDATA[You use real-time communication every day. It is the simultaneous exchange of information between a...]]></description>
            <content:encoded><![CDATA[You use real-time communication every day. It is the simultaneous exchange of information between a sender and a receiver with almost zero latency. Internet, landlines, mobile/cell phones, instant messaging (IM), internet relay chat, videoconferencing, teleconferencing, and robotic telepresence are all examples of real-time communication systems.

In this tutorial, you’ll learn how to build a real-time public chat app using React.js, Laravel, and Ably. You’ll use React.js to build the frontend/UI and Laravel to interact with Ably Realtime APIs to facilitate real-time communication. Anyone on the internet would be able to use this app to post messages to a public chat room and talk anonymously with other connected users. By building this kind of application, you’ll learn about the relevant concepts for building applications that need real-time data transfer.

Read the full blog on [Ably](https://ably.com/blog/building-a-realtime-chat-app-with-react-laravel-and-websockets).

Thanks for reading 💜

---

I publish a [monthly newsletter](https://www.ravsam.in/newsletter/) in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come across while surfing on the web.

Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) or send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1649406701576%2F1toJt0kA1.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Starting my First Full-time role as a Software Engineer]]></title>
            <link>https://www.ravgeet.in/blog/starting-my-first-full-time-role-as-a-software-engineer-ak9</link>
            <guid>https://www.ravgeet.in/blog/starting-my-first-full-time-role-as-a-software-engineer-ak9</guid>
            <pubDate>Tue, 08 Feb 2022 04:56:31 GMT</pubDate>
            <description><![CDATA[After applying to over 50+ remote jobs and getting rejected in 3 of them, I've finally got my first...]]></description>
            <content:encoded><![CDATA[After applying to over 50+ remote jobs and getting rejected in 3 of them, I've finally got my first full-time role as a **Software Engineer**. 🎉🎉

The funny thing is I was offered a role in a company that I didn't even apply to. I want to share my experience with anyone that is looking for a job.

Here's the timeline of how it happened:

- **May 2020**: Began my freelancing career along with my Master's in Computer Science and Engineering.

- **June 2021**: I got an email from a US-based company for a Frontend Engineer freelance contract. I accepted the offer.

- **November 2021**: Since I was going to postgraduate in 6 months, I started applying for a remote full-time role.

- **November 2021 - February 2022**: Most of the time, I didn't even get a first-round interview, and in three I got rejected because of my experience even though I've worked in Open Source and made large ECommerce websites.

- **1 February 2022**: I got a call from the US-based company I was working for as a freelancer, and they asked if I wanted to continue with them as a full-time employee?

I couldn't believe it 😲. I didn't even ask them. No interviews, no technical tests, no meetings. They handed me a generous offer and I signed the legal papers on the same day.

I'm really happy how things have panned out. I'm really excited to start my career as a full-time Software Engineer at [CloudAnswers](https://cloudanswers.com/).

Thanks for reading 💜

---

I publish a [monthly newsletter](https://www.ravsam.in/newsletter/) in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come across while surfing on the web.

Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) or send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1644234973701%2FBBSL1KB6o.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Implementing Audio in a Podcast App with Strapi]]></title>
            <link>https://www.ravgeet.in/blog/implementing-audio-in-a-podcast-app-with-strapi-26fa</link>
            <guid>https://www.ravgeet.in/blog/implementing-audio-in-a-podcast-app-with-strapi-26fa</guid>
            <pubDate>Wed, 26 Jan 2022 08:29:22 GMT</pubDate>
            <description><![CDATA[Podcasts have exploded in popularity, and platforms including Google Podcasts and Spotify offer...]]></description>
            <content:encoded><![CDATA[Podcasts have exploded in popularity, and platforms including Google Podcasts and Spotify offer content creators a way to communicate their thoughts with listeners around the world. If you’d like to join them, you can create your own podcast app using Strapi and a frontend of your choice.

In this tutorial, you’ll learn to implement audio in a podcast app. You’ll build your app in Nuxt.js and manage your podcast content in the Strapi CMS.

Read the full blog on [Strapi](https://strapi.io/blog/implementing-audio-in-a-podcast-app-with-strapi).

Thanks for reading 💜

---

I publish a [monthly newsletter](https://www.ravsam.in/newsletter/) in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come across while surfing on the web.

Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) or send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1643104712881%2FWTLli67Cm.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Handling Previews in a Headless Architecture - Strapi and Next.js]]></title>
            <link>https://www.ravgeet.in/blog/handling-previews-in-a-headless-architecture-strapi-and-nextjs-8m7</link>
            <guid>https://www.ravgeet.in/blog/handling-previews-in-a-headless-architecture-strapi-and-nextjs-8m7</guid>
            <pubDate>Mon, 10 Jan 2022 09:02:29 GMT</pubDate>
            <description><![CDATA[There is an ongoing shift in content management from traditional CMS to headless CMS. A headless CMS...]]></description>
            <content:encoded><![CDATA[There is an ongoing shift in content management from traditional CMS to headless CMS. A headless CMS allows you to completely separate your content management system from the presentation layer. The content is made available via API and can be consumed in any kind of frontend, from websites to mobile apps.

Using headless CMSs has opened up a new way of building websites, known as pre-rendering. It is one of the best-known techniques in Jamstack, in which the website is compiled into a set of static assets like prebuilt HTML, CSS, and JavaScript files with the help of a static site generator (SSG). During the build time, the files are created by collecting the data from a headless CMS. These files are cached to a content delivery network (CDN) and served to a user on each request from the nearest CDN node. This improves speed and response times and reduces hosting costs.

However, content creators need to preview their content before publishing it to production, meaning they need to wait for an entire build to complete before they can view their content. To solve this problem, a preview mode allows editors to view their changes on the fly.

In this tutorial, you’ll learn to implement a preview system when working with a headless CMS like Strapi. You’ll implement the frontend in Next.js for creating content previews.

Read the full blog on [Strapi](https://strapi.io/blog/handling-previews-in-a-headless-architecture).

Thanks for reading 💜

---

I publish a [monthly newsletter](https://www.ravsam.in/newsletter/) in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come across while surfing on the web.

Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) or send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1641462626636%2FIdg9ulI4l.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Create an App Information Component in Nuxt]]></title>
            <link>https://www.ravgeet.in/blog/create-an-app-information-component-in-nuxt-1ock</link>
            <guid>https://www.ravgeet.in/blog/create-an-app-information-component-in-nuxt-1ock</guid>
            <pubDate>Thu, 23 Dec 2021 06:13:09 GMT</pubDate>
            <description><![CDATA[You must have seen multiple apps which show the app’s information like app version and last updated...]]></description>
            <content:encoded><![CDATA[You must have seen multiple apps which show the app’s information like **app version** and **last updated at time** in their footers or via a floating action button. In this tutorial, you’ll learn to create a component to show such kind of information in a Nuxt app.

Read the full blog on [RavSam](https://www.ravsam.in/blog/create-an-app-information-component-in-nuxt/).

Thanks for reading 💜

---

I publish a [monthly newsletter](https://www.ravsam.in/newsletter/) in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come across while surfing on the web.

Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) or send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1640066797991%2FbCSdHG9DA.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[React vs Vue: What is the Best Framework for your Project in 2022?]]></title>
            <link>https://www.ravgeet.in/blog/react-vs-vue-what-is-the-best-framework-for-your-project-in-2022-5d6h</link>
            <guid>https://www.ravgeet.in/blog/react-vs-vue-what-is-the-best-framework-for-your-project-in-2022-5d6h</guid>
            <pubDate>Fri, 17 Dec 2021 04:32:00 GMT</pubDate>
            <description><![CDATA[React and Vue are rising stars in the JavaScript front-end frameworks ecosystem. React is backed by...]]></description>
            <content:encoded><![CDATA[React and Vue are rising stars in the JavaScript front-end frameworks ecosystem. React is backed by Facebook, and Vue is completely a community-driven project. The choices that React developers and Vue developers make have consequences as the project size grows, but they may not realize them when they first pick up on over the other.

Both React and Vue can accomplish the same thing. However, they provide different paths to reach the final destination. In this article, you'll look at different comparisons that will help you differentiate between React and Vue and ultimately help you decide which one you should choose in your future projects.

Read the full blog on [Adeva](https://adevait.com/javascript-developers/react-vs-vue).

Thanks for reading 💜

---

I publish a [monthly newsletter](https://www.ravsam.in/newsletter/) in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come across while surfing on the web.

Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) or send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1639636323417%2FuTBBKDKgRF.jpeg" length="0" type="image/jpeg"/>
        </item>
        <item>
            <title><![CDATA[Create a Balance Reminder with Vonage Account API and Google Apps]]></title>
            <link>https://www.ravgeet.in/blog/create-a-balance-reminder-with-vonage-account-api-and-google-apps-1pl5</link>
            <guid>https://www.ravgeet.in/blog/create-a-balance-reminder-with-vonage-account-api-and-google-apps-1pl5</guid>
            <pubDate>Thu, 09 Dec 2021 05:06:28 GMT</pubDate>
            <description><![CDATA[Being a freelancer, I have helped a couple of local businesses in India implement Vonage products....]]></description>
            <content:encoded><![CDATA[Being a freelancer, I have helped a couple of local businesses in India implement Vonage products. Recently, one of my clients asked if they can get a reminder email when the Vonage balance is below a specified limit as they don't want to hamper their operations because of insufficient balance. Almost all of my clients use Google Workspace, so I decided to create an integration of Vonage and Google Apps Script to create this workflow.

Google Apps Script allows us to manage all of Google apps using one platform in the cloud. The best part is that the authentication is baked into the platforms and many businesses in the market use Google Workspace – formerly known as G-Suite.

In this blog post, you'll learn how to create custom notifications when your Vonage Account balance is below a specified limit. The post is aimed at those Vonage developers who want to manage their client base effectively by sending them reminders about the Vonage Account balance.

Read the full blog on [Vonage](https://learn.vonage.com/blog/2021/06/08/create-a-balance-reminder-with-vonage-account-api-and-google-apps/).

Thanks for reading 💜

---

I publish a [monthly newsletter](https://www.ravsam.in/newsletter/) in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come across while surfing on the web.

Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) or send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1638960379559%2FIV33WR0ez.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Build a News Aggregator App using Strapi and Nuxtjs]]></title>
            <link>https://www.ravgeet.in/blog/build-a-news-aggregator-app-using-strapi-and-nuxtjsravgeet-dhillon-1n52</link>
            <guid>https://www.ravgeet.in/blog/build-a-news-aggregator-app-using-strapi-and-nuxtjsravgeet-dhillon-1n52</guid>
            <pubDate>Mon, 06 Dec 2021 04:10:45 GMT</pubDate>
            <description><![CDATA[If you are an avid reader, you might have a News Aggregator app installed on your device. Wouldn't it...]]></description>
            <content:encoded><![CDATA[If you are an avid reader, you might have a News Aggregator app installed on your device. Wouldn't it be awesome to create your own News Aggregator app that you can control and customize according to your needs?

This tutorial aims to learn about Strapi and Nuxt.js by building a News Aggregator app with Strapi and Nuxt.js. In this app, you'll:

- Learn to set up Strapi Collection types
- Learn to set up Frontend app using Nuxt.js
- Use CRON jobs to fetch news items automatically
- Add Search capabilities
- Register subscribers

Read the full blog on [Strapi](https://strapi.io/blog/build-a-news-aggregator-app-using-strapi-and-nuxtjs).

Thanks for reading 💜

---

I publish a [monthly newsletter](https://www.ravsam.in/newsletter/) in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I across while surfing on the web.

Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) or send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1638690736178%2FAPE7q0Vtl.jpeg" length="0" type="image/jpeg"/>
        </item>
        <item>
            <title><![CDATA[Converting and Optimizing Images From the Command Line]]></title>
            <link>https://www.ravgeet.in/blog/converting-and-optimizing-images-from-the-command-line-6ap</link>
            <guid>https://www.ravgeet.in/blog/converting-and-optimizing-images-from-the-command-line-6ap</guid>
            <pubDate>Mon, 29 Nov 2021 04:54:45 GMT</pubDate>
            <description><![CDATA[Images take up to 50% of the total size of an average web page. And if images are not optimized,...]]></description>
            <content:encoded><![CDATA[Images take up to 50% of the total size of an average web page. And if images are not optimized, users end up downloading extra bytes. And if they’re downloading extra bytes, the site not only takes that much more time to load, but users are using more data, both of which can be resolved, at least in part, by optimizing the images before they are downloaded.

In this tutorial, you'll learn to write bash scripts that create and optimize images in different image formats, targeting the most common formats, including JPG, PNG, WebP, and SVG.

Read the full blog on [CSS Tricks](https://css-tricks.com/converting-and-optimizing-images-from-the-command-line/).

Thanks for reading 💜

---

I publish a [monthly newsletter](https://www.ravsam.in/newsletter/) in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I across while surfing on the web.

Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) or send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1637990799063%2FqcrTIwh7_.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Back to Basics: Conditional Logic with Python if else if]]></title>
            <link>https://www.ravgeet.in/blog/back-to-basics-conditional-logic-with-python-if-else-if-5ck7</link>
            <guid>https://www.ravgeet.in/blog/back-to-basics-conditional-logic-with-python-if-else-if-5ck7</guid>
            <pubDate>Mon, 22 Nov 2021 06:40:59 GMT</pubDate>
            <description><![CDATA[Whether you are new to Python programming or returning to it after a break, you may need to learn or...]]></description>
            <content:encoded><![CDATA[Whether you are new to Python programming or returning to it after a break, you may need to learn or re-learn about decision-making and branching statements in Python.

In this tutorial, you will learn about different if, else if, else scenarios that may arise while writing a Python program.

Read the full blog on [Adam The Automator](https://adamtheautomator.com/python-if-else-if/).

Thanks for reading 💜

---

I publish a [monthly newsletter](https://www.ravsam.in/newsletter/) in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come across while surfing on the web.

Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) or send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1637388436077%2FvCC3xlzKJ.jpeg" length="0" type="image/jpeg"/>
        </item>
        <item>
            <title><![CDATA[How to Test Your NGINX Configuration Before Screwing it Up]]></title>
            <link>https://www.ravgeet.in/blog/how-to-test-your-nginx-configuration-before-screwing-it-up-2571</link>
            <guid>https://www.ravgeet.in/blog/how-to-test-your-nginx-configuration-before-screwing-it-up-2571</guid>
            <pubDate>Fri, 19 Nov 2021 06:22:51 GMT</pubDate>
            <description><![CDATA[A little invalid change to your Nginx configuration can bring down your entire server. Before...]]></description>
            <content:encoded><![CDATA[A little invalid change to your Nginx configuration can bring down your entire server. Before performing changes to the Nginx configuration, it is a safe idea to test the changes and then reload the server.

In this tutorial, you'll learn to get started ensuring you never take production down again!

Read the full blog on [Adam The Automator](https://adamtheautomator.com/nginx-test-config/).

Thanks for reading 💜

---

I publish a [monthly newsletter](https://www.ravsam.in/newsletter/) in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I across while surfing on the web.

Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) or send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1637224148722%2F4jWFRRdha.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Using Bash Sort to Sort Files Like a Boss]]></title>
            <link>https://www.ravgeet.in/blog/using-bash-sort-to-sort-files-like-a-boss-1h2g</link>
            <guid>https://www.ravgeet.in/blog/using-bash-sort-to-sort-files-like-a-boss-1h2g</guid>
            <pubDate>Wed, 17 Nov 2021 05:14:18 GMT</pubDate>
            <description><![CDATA[Are you looking out for a way to organize your files and perform some operations on them? There are...]]></description>
            <content:encoded><![CDATA[Are you looking out for a way to organize your files and perform some operations on them? There are many instances in programming where you need to sort some data, such as a list of files. Sorting files with the Bash sort and ls commands will help you keep things organized.

In this tutorial, you will learn the fundamentals of sorting files and file contents.

I wrote this post to help you learn the fundamentals of sorting files and file contents using Shell scripting.

Read the full blog on [Adam The Automator](https://adamtheautomator.com/bash-sort/).

Thanks for reading 💜

---

I publish a [monthly newsletter](https://www.ravsam.in/newsletter/) in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I across while surfing on the web.

Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) or send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1636879979008%2F0sfUJ2ub-n.jpeg" length="0" type="image/jpeg"/>
        </item>
        <item>
            <title><![CDATA[Service Status Monitoring Using WhatsApp, Notion, and Python]]></title>
            <link>https://www.ravgeet.in/blog/service-status-monitoring-using-whatsapp-notion-and-python-905</link>
            <guid>https://www.ravgeet.in/blog/service-status-monitoring-using-whatsapp-notion-and-python-905</guid>
            <pubDate>Sat, 16 Oct 2021 05:02:39 GMT</pubDate>
            <description><![CDATA[Websites and APIs go down more often than we’d all like. Wouldn’t it be great to get a WhatsApp...]]></description>
            <content:encoded><![CDATA[Websites and APIs go down more often than we’d all like. Wouldn’t it be great to get a WhatsApp notification when your favorite or most-used services are experiencing downtime?

So, I wrote this tutorial to help you set up automated monitoring for your favorite services and receive WhatsApp notifications when the status of your services changes. You'll use Notion for the database, Twilio’s WhatsApp Business API for receiving notifications, GitHub actions for running our job on a schedule, and we’ll code everything in Python.

Read the full blog on [Twilio](https://www.twilio.com/blog/service-status-monitoring-whatsapp-notion-python).

Thanks for reading 💜

---

I publish a [monthly newsletter](https://www.ravsam.in/newsletter/) in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I across while surfing on the web.

Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) or send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1634100505372%2FkvaYGdPNv.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[How to use Linux to Recover Deleted Files]]></title>
            <link>https://www.ravgeet.in/blog/how-to-use-linux-to-recover-deleted-files-4p66</link>
            <guid>https://www.ravgeet.in/blog/how-to-use-linux-to-recover-deleted-files-4p66</guid>
            <pubDate>Thu, 14 Oct 2021 05:07:10 GMT</pubDate>
            <description><![CDATA[Have you ever accidentally deleted important files from your computer and went through these emotions...]]></description>
            <content:encoded><![CDATA[Have you ever accidentally deleted important files from your computer and went through these emotions - 🤦😨😱😤🤒?

Well, you’re not the only one. But the good news is there are plenty of ways in Linux to recover deleted files.

I wrote this post to explain exactly the same.

Read the full blog on [Adam The Automator](https://adamtheautomator.com/linux-recover-deleted-files/).

Thanks for reading 💜

---

I publish a [monthly newsletter](https://www.ravsam.in/newsletter/) in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I across while surfing on the web.

Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) or send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1634101365650%2FH_mXSiWKB.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Collect Email Signups With the Notion API]]></title>
            <link>https://www.ravgeet.in/blog/collect-email-signups-with-the-notion-api-30ao</link>
            <guid>https://www.ravgeet.in/blog/collect-email-signups-with-the-notion-api-30ao</guid>
            <pubDate>Sun, 10 Oct 2021 11:40:56 GMT</pubDate>
            <description><![CDATA[A lot of people these days are setting up their own newsletters. The first hurdle in setting up a...]]></description>
            <content:encoded><![CDATA[A lot of people these days are setting up their own newsletters. The first hurdle in setting up a newsletter is a mechanism to collect emails. But nothing is handier than setting up your own system which you can control.

So, I wrote a tutorial to help you set up your own system to collect emails for your newsletters based on the Jamstack architecture.

You'll implement an HTML form to collect emails for your newsletter, process those emails with Netlify Functions, and save them to a Notion database with the Notion API.

Read the full blog on [CSS Tricks](https://css-tricks.com/collecting-email-signups-with-the-notion-api/).

Thanks for reading 💜

---

I publish a [monthly newsletter](https://www.ravsam.in/newsletter/) in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I across while surfing on the web.

Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) or send me an [Email](mailto:ravgeetdhillon@gmail.com).

— [Ravgeet](https://www.ravgeet.in/), *Full Stack Developer and Technical Content Writer*]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1633861046438%2FIkR6tlQAv.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Progress Bar in Next.js]]></title>
            <link>https://www.ravgeet.in/blog/progress-bar-in-next-js-3gj2</link>
            <guid>https://www.ravgeet.in/blog/progress-bar-in-next-js-3gj2</guid>
            <pubDate>Thu, 29 Jul 2021 07:41:42 GMT</pubDate>
            <description><![CDATA[Display a Progress Bar on route changes in a Next.js app.   Sometimes when we transition...]]></description>
            <content:encoded><![CDATA[---
canonical_url: https://www.ravsam.in/blog/progress-bar-in-next-js/
date: 2021-07-29 07:41:42 UTC
published: true
tags: nextjs,webdevelopment,webdesign,react
title: Progress Bar in Next.js
---

#### Display a Progress Bar on route changes in a Next.js app.

Sometimes when we transition from one route to another, it takes a little time to do so due to different factors. Behind the scenes, it may be rendering a complex page component or doing an API call. In such cases, the app looks like it has frozen for some seconds and then suddenly transitions to the next route. This results in a poor UX. In such cases, it is better to add a progress bar to our application which gives our users a sense that something is loading.

In this tutorial, we learn how to implement a progress bar in a Next.js application.

#### Contents

- 1. Installing NProgress
- 2. Basic Usage
- Results

#### 1. Installing NProgress

The first step we need to do is to install [nprogress](https://www.npmjs.com/package/nprogress) npm module.

```bash
npm i --save nprogress
```

#### 2. Basic Usage

In pages/_app.js, import the following modules:

```js
import Router from 'next/router'
import NProgress from 'nprogress'
```

Now, we need to add some Router events to control the behaviour of the progress bar. We need to add the following code:

```js
Router.events.on('routeChangeStart', () => NProgress.start())
Router.events.on('routeChangeComplete', () => NProgress.done())
Router.events.on('routeChangeError', () => NProgress.done())
```

Depending upon our use case, we can remove the loading spinner that comes by default.

```js
NProgress.configure({ showSpinner: false })
```

The final code for pages/_app.js will look like this:

```js
import Router from 'next/router'
import NProgress from 'nprogress'

Router.events.on('routeChangeStart', () => NProgress.start())
Router.events.on('routeChangeComplete', () => NProgress.done())
Router.events.on('routeChangeError', () => NProgress.done())

NProgress.configure({ showSpinner: false })

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

export default MyApp
```

#### Results

We are done with the code. Let’s see how our progress bar will look like in a Next.js application.

If you enjoyed my article, follow me for more stuff.

> This article was originally published on [RavSam’s blog](https://www.ravsam.in/blog/progress-bar-in-next-js/). We publish our articles on Medium after a week.

#### 🤝 Connect with Me

I love writing for the community while working on my freelance and open source projects. Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) • [Email](mailto:ravgeetdhillon@gmail.com).

#### 💌 Get our Newsletter

We write about Nuxt, Vue, Strapi, Flutter, Jamstack, and Automation. [Subscribe to our newsletter](https://www.ravsam.in/newsletter/)

#### 🛖 About RavSam

We are helping companies and startups around the globe with Digital Product Development powered by modern Jamstack architecture. [Get in Touch with us](https://www.ravsam.in/contact-us/).

#### 📙 You might also enjoy reading

- [Setup and Customize Bootstrap in Next.js](https://www.ravsam.in/blog/setup-and-customize-bootstrap-in-nextjs/)

- [Use Humans.txt to credit your team for a project](https://www.ravsam.in/blog/use-humans-txt-to-credit-your-team-for-project/)

* * *]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.ravsam.in%2Fassets%2Fimages%2Fblogs%2Fprogress-bar-in-next-js.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Setup and Customize Bootstrap in Next.js]]></title>
            <link>https://www.ravgeet.in/blog/setup-and-customize-bootstrap-in-next-js-585k</link>
            <guid>https://www.ravgeet.in/blog/setup-and-customize-bootstrap-in-next-js-585k</guid>
            <pubDate>Fri, 09 Jul 2021 07:30:59 GMT</pubDate>
            <description><![CDATA[Learn how to improve the look and feel of the Next project by configuring the default...]]></description>
            <content:encoded><![CDATA[---
canonical_url: https://www.ravsam.in/blog/setup-and-customize-bootstrap-in-nextjs/
date: 2021-07-09 07:30:59 UTC
published: true
tags: bootstrap,next,javascript,webdesign
title: Setup and Customize Bootstrap in Next.js
---

#### Learn how to improve the look and feel of the Next project by configuring the default Bootstrap behaviour.

***

A few months back, we wrote a blog on [how to add and customize Bootstrap in Nuxt.js](https://www.ravsam.in/blog/how-to-add-customize-bootstrap-in-nuxtjs/). Today, we will learn how to set up Bootstrap in a Next.js project. We will also install [react-bootstrap](https://www.npmjs.com/package/react-bootstrap) to use Bootstrap based React components.

#### Contents

- 1. Installing Bootstrap
- 2. Creating a Custom SCSS
- 3. Configuring Next Config
- 4. Importing Bootstrap

#### 1. Installing Bootstrap

Let us get started by installing the required NPM packages. We will install [bootstrap](https://getbootstrap.com) and optionally [react-bootstrap](https://react-bootstrap.github.io/).

Since we are going to create custom _SCSS_ files, we also need to install _node-sass_.

```bash
npm install --save bootstrap react-bootstrap node-sass
```

#### 2. Creating a Custom SCSS

Let us now create a custom _scss_ file in the styles/scss directory, and name it _global.scss_. In this file, we need to import Bootstrap’s bootstrap.scss. For the sake of simplicity, let us override the default colour system provided by Bootstrap.

```scss
$theme-colors: (
  'primary': #145bea,
  'secondary': #833bec,
  'success': #1ce1ac,
  'info': #ff7d50,
  'warning': #ffbe0b,
  'danger': #ff007f,
  'light': #c0ccda,
  'dark': #001738,
);

@import '/node_modules/bootstrap/scss/bootstrap.scss';
```

#### 3. Configuring Next Config

The best part about the newer versions of Next is that they provide built-in SASS/SCSS support. All we need to tell Next is where our styles are stored by configuring the next.config.js and adding the following piece of code:

```js
const path = require('path')

module.exports = {
  
  ...

  sassOptions: {
    includePaths: [path.join(__dirname, 'styles')],
  },
}
```

#### 4. Importing Bootstrap

The final step is to import our custom Bootstrap into our project. Based on where we need to use the custom styles, we can import our global.scss. In this example, let us configure it to be used by the entire project.

In pages/_app.js file, we need to add the following code:

```js
import 'styles/scss/global.scss' // added

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

export default MyApp
```

We have done it. We have set up Bootstrap in our Next project.

#### 🤝 Connect with Me

I love writing for the community while working on my freelance and open source projects. Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) • [Email](mailto:ravgeetdhillon@gmail.com).

#### 💌 Get Newsletter

I write about Nuxt, Vue, Strapi, Flutter, Jamstack, and Automation. [Subscribe to my newsletter](https://www.ravsam.in/newsletter/).

#### 📙 You might also enjoy reading

- [How to add and customize Bootstrap in Nuxt.js](https://www.ravsam.in/blog/how-to-add-customize-bootstrap-in-nuxtjs/)

- [How to achieve a redesign of your website](https://www.ravsam.in/blog/redesigning-your-website/)
* * *]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.ravsam.in%2Fassets%2Fimages%2Fblogs%2Fsetup-and-customize-bootstrap-in-nextjs.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Script as a Task using VS Code IDE]]></title>
            <link>https://www.ravgeet.in/blog/script-as-a-task-using-vs-code-ide-530e</link>
            <guid>https://www.ravgeet.in/blog/script-as-a-task-using-vs-code-ide-530e</guid>
            <pubDate>Fri, 25 Jun 2021 06:42:13 GMT</pubDate>
            <description><![CDATA[Convert NPM, Bash scripts to VS Code tasks and run them from anywhere.   VS Code comes with...]]></description>
            <content:encoded><![CDATA[---
canonical_url: https://www.ravsam.in/blog/script-as-a-task-using-vs-code-ide/
date: 2021-06-25 06:42:13 UTC
published: true
tags: automation,vscode,bash,productivity
title: Script as a Task using VS Code IDE
---

#### Convert NPM, Bash scripts to VS Code tasks and run them from anywhere.

VS Code comes with a great feature of specifying _tasks_ and running them through **Command Palette**. There can be a variety of scripts that we need to run while developing our applications. For example, before releasing a new build, there are a lot of things that need to be done by the release team. Some of them include bumping release version, creating release notes, generating changelog and the list goes on.

In this tutorial, we will learn how to use VS Code Tasks by taking the example of pre-release commands and ensure that no step is missed along the way.

#### Contents

- Prerequisites
- 1. Writing Pre Release Script
- 2. Setting Tasks
- 3. Running Tasks
- Conclusion

#### Prerequisites

- A Local Git Repository
- VS Code Editor
- Linux Environment

#### 1. Writing Pre Release Script

The first thing we need to do is to create a script — in this case, a _bash_ script. In this script, we will define what steps we need to perform as a part of our pre-release operation.

Let us assume that before releasing, we do two operations. First, we create a .version file and add today’s date to it. Then we create an empty commit with a message - _do-production-release_.

With the steps determined, let us create a pre-release.sh in .vscode directory and add the following code:

```bash
#!/bin/sh

date > .version
git commit --allow-empty -m "do-production-release"
```

We can test run the above script by doing:

```bash
bash .vscode/pre-release.sh
```

> Make sure to give proper permissions to the script before running it.

#### 2. Setting Tasks

Now comes the most interesting part of the tutorial. VS Code allows us to specify tasks in tasks.json. The beauty of the VS Code tasks is that we can run them directly from VS Code Command Palette which is especially helpful for non-technical members of our team.

Let us create a tasks.json file in .vscode directory and add the following contents in the file:

```json
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "Pre-Release Setup",
            "type": "shell",
            "command": "bash",
            "args": ["${workspaceFolder}/.vscode/pre-release.sh"]
        }
    ]
}
```

It is important to understand what we are doing so that we can customize the workflow according to our needs.

_label_ is used to identify the script in the VS Code Command Palette.

```
"label": "Pre-Release Setup"
```

_type_ is set to _shell_ since you need to execute a shell script.

```
"type": "shell"
```

_command_ is used the specify the base command to which the arguments can be passed.

```
"command": "bash"
```

_args_ is an array that provides arguments to the _command_. ${workspaceFolder} is the internal variable provided by the VS Code. It is the absolute path to our project’s root directory.

```
"args": ["${workspaceFolder}/.vscode/pre-release.sh"]
```

#### 3. Running Tasks

Let us open the VS Code Command Palette using Ctrl + Shift + P, type Tasks: Run Task and press _Enter_.

![VS Code Command Palette to run tasks](https://cdn-images-1.medium.com/max/744/0*zsZ0YeQe1sW57bRm.png)

We will be presented with a list of tasks that we specified in the tasks.json. We will select the Pre-Release Setup and press _Enter_. We will see the task output in VS Code Integrated Terminal.

![VS Code Command Palette to select tasks](https://cdn-images-1.medium.com/max/744/0*lqTPyB8ZXuO5NB_T.png)

#### Conclusion

We now have a good overview of how we can use VS Code tasks to run our _scripts as tasks_ in a better way. We can also add more tasks like running _pre-staging release_, running _pre-dev release_ and more.

If you enjoyed my article, please clap 👏 for it.

_This article was originally published on_ [_RavSam’s blog_](https://www.ravsam.in/blog/script-as-a-task-using-vs-code-ide/)_. We publish our articles on Dev after a week._

#### 🤝 Connect with Me

I love writing for the community while working on my freelance and open source projects. Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) • [Email](mailto:ravgeetdhillon@gmail.com).

#### 💌 Get our Newsletter

We write about Nuxt, Vue, Strapi, Flutter, Jamstack, and Automation. [Subscribe to our newsletter](https://www.ravsam.in/newsletter/).

#### 🛖 About RavSam

We are helping companies and startups around the globe with Digital Product Development powered by modern Jamstack architecture. [Get in Touch with us](https://www.ravsam.in/contact-us/).

#### 📙 You might also enjoy reading

[Add Unsubscribe link in emails using Google Apps Script](https://www.ravsam.in/blog/add-unsubscribe-link-in-emails-using-google-apps-script/)

* * *]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.ravsam.in%2Fassets%2Fimages%2Fblogs%2Fscript-as-a-task-using-vs-code-ide.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Best VS Code extensions for Nuxt/Vue Projects]]></title>
            <link>https://www.ravgeet.in/blog/best-vs-code-extensions-for-nuxt-vue-projects-1jc6</link>
            <guid>https://www.ravgeet.in/blog/best-vs-code-extensions-for-nuxt-vue-projects-1jc6</guid>
            <pubDate>Thu, 27 May 2021 06:34:18 GMT</pubDate>
            <description><![CDATA[Supercharge your Nuxt/Vue App Development by using these extensions in VS Code...]]></description>
            <content:encoded><![CDATA[---
canonical_url: https://www.ravsam.in/blog/best-vs-code-extensions-for-nuxt-vue-projects/
date: 2021-05-27 06:34:18 UTC
published: true
tags: nuxt,jamstack,vue,javascript
title: Best VS Code extensions for Nuxt/Vue Projects
---

#### Supercharge your Nuxt/Vue App Development by using these extensions in VS Code Editor.

> This blog was originally published on [RavSam’s blog](https://www.ravsam.in/blog/best-vs-code-extensions-for-nuxt-vue-projects/). We publish our articles on Dev after a week.

Using the right set of tools can make us a happy and better developer. Right tools reduce friction and help us develop applications faster. In this blog, we will take a look at some of the best VS Code extensions that we can use for developing Nuxt/Vue apps. These extensions help us with things like linting, formatting, debugging, and more.

#### 1. Vetur

![Vetur Extension VS Code](https://cdn-images-1.medium.com/max/1024/0*CWlf4RE8s5csF9PZ.png)

**Vetur** is the king of all extensions we need as a Vue/Nuxt Developer. It is a Vue tooling for VS Code. It comes with Vue Language Server and other features like syntax highlighting, formatting, intellisense, debugging, and more.

[Source](https://marketplace.visualstudio.com/items?itemName=octref.vetur).

#### 2. Vue Discovery

![Vue Discovery VS Code](https://cdn-images-1.medium.com/max/680/0*cFQWqDWDKhQQqb6S.gif)

**Vue Discovery** is a great plugin that adds to the powers of Vetur. This extension discovers Vue components in our workspace and provides IntelliSense for them. It provides intellisense for components in the template section, allow us to automatically import, register and expand required props, and more.

In Short: _Vue component name completion across a project_

[Source](https://marketplace.visualstudio.com/items?itemName=Maantje.vue-discovery)

#### 3. Vue Peek

![Vue Peek Extension VS Code](https://cdn-images-1.medium.com/max/747/0*K6-WXB_SywSTGYPe.gif)

**Vue Peek** allows us to go to the definition for Vue components. It allows us to look under the hood of the Vue component declarations. It allows us to quickly jump to or peek into files that are referenced as components (from template), or as module imports (from a script).

The extension supports all the normal capabilities of symbol definition tracking and does it for CSS selectors — classes and IDs — as well.

In Short: _Peek inside Vue SFCs_

[Source](https://marketplace.visualstudio.com/items?itemName=dariofuzinato.vue-peek)

#### 4. HTML CSS Class Completion

![HTML CSS Class Completion Extension VS Code](https://cdn-images-1.medium.com/max/758/0*yCk5st__5FbNJJex.gif)

**HTML CSS Class Completion** is an amazing Visual Studio Code extension that provides CSS class name completion for the HTML class attribute based on the definitions found in our workspace or external files referenced through the link element. It is extremely handy while designing the UI of the application as it gives us quick access to the CSS classes available.

In Short: _CSS class completion in your HTML template_

[Source](https://marketplace.visualstudio.com/items?itemName=Zignd.html-css-class-completion)

#### 5. Import Cost

![Import Cost Extension VS Code](https://cdn-images-1.medium.com/max/838/0*-WRMC5ecHkIsj-mS.gif)

As a developer, our primary concern is to make sure that the application size doesn’t go beyond a certain limit. **Import Cost** extension displays the size of the imported package inline in the editor.

It currently supports

- Default importing: import Func from ‘utils’;

- Entire content importing: import \* as Utils from ‘utils’;

- Selective importing: import {Func} from ‘utils’;

- Selective importing with alias: import {orig as alias} from ‘utils’;

- Submodule importing: import Func from ‘utils/Func’;

- Require: const Func = require(‘utils’).Func;

- Supports both Javascript and Typescript

In Short: _Tells you the size of your npm imports_

[Source](https://marketplace.visualstudio.com/items?itemName=wix.vscode-import-cost)

#### 6. Internationalization

Internationalization or **i18n** allows us to write our application text in different languages for different regions.

![i18n Extension VS Code](https://cdn-images-1.medium.com/max/1024/0*RTsR9wTD5U54CHZd.gif)

i18n supports multi-root workspaces, remote development, many popular frameworks, linked locale messages, and eliminates the need to use JSON files for i18n as is done traditionally.

In Short: _i18n toolset for multilingual support, works great with vue-i18n_

[Source](https://marketplace.visualstudio.com/items?itemName=Lokalise.i18n-ally)

#### 7. Path Intellisense

![Path IntelliSense Extension VS Code](https://cdn-images-1.medium.com/max/480/0*FpND-pOVR12Nt4NQ.gif)

One of the VS Code extension that we use at RavSam is **Path Intellisense**. It provides autocompletion for file paths present in your current VS Code workspace.

In Short: _File path completion_

[Source](https://marketplace.visualstudio.com/items?itemName=christian-kohler.path-intellisense)

#### 8. SVGO

![SVGO Extenstion VS Code - 1](https://cdn-images-1.medium.com/max/640/0*mRkd2i3KZxb7TKV1.png)

**SVGO** Extension for Visual Studio Code is built on the top of [SVGO NPM module](https://github.com/svg/svgo). It allows us to minify and prettify the SVG file in place.

In Short: _Minimize SVG files in two keystrokes_

[Source](https://marketplace.visualstudio.com/items?itemName=1000ch.svgo)

#### Connect with Me

I love writing for the community while working on my freelance and open source projects. Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) • [Email](mailto:ravgeetdhillon@gmail.com).

#### About RavSam Web Solutions

We are helping companies and startups to set up Web and Mobile Apps powered by modern JAMstack architecture. Reach out to us to know more about our services, pricing, or anything else. We are always looking forward to work on great ideas. If you are looking for [an application development company](https://www.ravsam.in/), you are most welcome to get in touch with us.

#### You might also enjoy reading

- [Offline Toast notification in Nuxt/Vue app](https://www.ravsam.in/blog/offline-toast-notification-in-nuxt-vue-app/)

- [Disable Submit button if Form fields have not changed in a Nuxt/Vue app](https://www.ravsam.in/blog/disable-submit-button-if-form-fields-have-not-changed-in-a-nuxt-vue-app/)

* * *]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.ravsam.in%2Fassets%2Fimages%2Fblogs%2Fbest-vs-code-extensions-for-nuxt-vue-projects.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Automating Project Maintenance on Github]]></title>
            <link>https://www.ravgeet.in/blog/automating-project-maintenance-on-github-3iag</link>
            <guid>https://www.ravgeet.in/blog/automating-project-maintenance-on-github-3iag</guid>
            <pubDate>Tue, 11 May 2021 08:34:30 GMT</pubDate>
            <description><![CDATA[Know about the Maintenance stack we use at RavSam to keep our projects updated and...]]></description>
            <content:encoded><![CDATA[---
canonical_url: https://www.ravsam.in/blog/automating-project-maintenance-on-github/
date: 2021-05-11 08:34:30 UTC
published: true
tags: github,bots,automation
title: Automating Project Maintenance on Github
---

#### Know about the Maintenance stack we use at RavSam to keep our projects updated and secure.

> This blog was originally published on [RavSam’s blog](https://www.ravsam.in/blog/automating-project-maintenance-on-github/). We publish our articles on Medium after a week.

#### Contents

- Manual Maintenance is tough and boring
- Renovate  —  Automated Dependency Updates
- Imgbot  —  Automated Image Optimization
- RavSam Bot  —  A Github Probot App

#### Manual Maintenance is tough and boring

Most of the effort in the software business goes into the maintenance of the code that already exists. Once the software is built, many factors affect its performance over time. We need to fix bugs, address security vulnerabilities, make performance improvements, and decrease technical debt.

Managing a single piece of software is easy but as a developer, we often have to deal with more than one. And this is exactly where maintenance gets hard. The best way to handle **Maintenance debt** is to upgrade the dependencies on which our project depends regularly.

All these problems can be solved by automation. We at RavSam use [Github](http://github.com/ravsamhq) for our code handling and CI/CD purposes. There are tools like Github Apps and Github Actions that allow us to automate our software maintenance.

#### Renovate  —  Automated Dependency Updates

[Renovate](https://github.com/marketplace/renovate) is one of those packages that make our idea of automated maintenance a reality. It is a free, open-source, customizable Github app that helps us to automatically update our dependencies in software projects by receiving pull requests and that too for multiple languages.

![Dependency Updates by Renovate](https://cdn-images-1.medium.com/max/1024/0*kxVKEKvT3VfahIPZ.png)<figcaption>Dependency Updates by Renovate</figcaption>

The best part is that we can write a single config and use it for all of our projects in our Github organization. Here is a config that we use at RavSam:

```json
{
  "extends": ["config:base"],
  "labels": ["dependencies"],
  "major": {
    "enabled": false
  },
  "packageRules": [
    {
      "matchUpdateTypes": ["patch", "pin", "digest"],
      "automerge": true
    }
  ],
  "prCreation": "not-pending",
  "schedule": ["every weekend"],
  "stabilityDays": 3
}
```

We have configured Renovate to run only on weekends to prevent noise and distractions. We have enabled auto-merge when the update type is one of the following: _patch_, _pin_ or _digest_.

#### Imgbot  —  Automated Image Optimization

The performance of a Web App is often dependent on the images. Hence it is crucial to optimize images or else lose customers. Another advantage of optimized images is that it reduces the bandwidth costs for us as well as our visitors.

We love [Imgbot](https://github.com/marketplace/imgbot). It optimizes the images and creates a pull request against our default branch. Imgbot is verified by GitHub which means there is no point worrying about the security.

![Images optimized by ImgBot](https://cdn-images-1.medium.com/max/1024/0*8BzyH97kL4mfIv50.png)<figcaption>Images optimized by ImgBot</figcaption>

#### RavSam Bot  —  A Github Probot App

We have built our custom serverless Github Probot, [RavSam Bot](https://github.com/apps/ravsam-bot), our employee #1. It helps us automate various tasks like managing issues by raising their priority, assigning confirmed issues to developers, assigning reviewers to the pull requests, auto-merging them once the changes have been approved and many more things.

![Approved pull request merged by RavSam Bot](https://cdn-images-1.medium.com/max/1024/0*7ciVKnj5mrR-Cqdj.png)<figcaption>Approved pull request merged by RavSam Bot</figcaption>

Probot apps are easy to write, deploy, and share. We have deployed our app on Netlify Functions and it spends the entire day doing mundane tasks for us tirelessly.

If you loved my article, please clap 👏 for it.

#### Connect with Me

I love writing for the community while working on my freelance and open source projects. Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) • [Email](mailto:ravgeetdhillon@gmail.com).

#### About RavSam Web Solutions

We are helping startups and companies set up content management systems to manage their content delivery to customers across various products. Reach out to us to know more about our services, or anything else. We are always looking forward to work on great ideas. If you are looking for [an application development company](https://www.ravsam.in/), you are most welcome to get in touch with us.

#### You might also enjoy reading

- [Deploy a Serverless Probot/Github App on Netlify Functions](https://www.ravsam.in/blog/deploy-a-serverless-probot-github-app-on-netlify-functions/)

- [Deploy Strapi on VPS with Ubuntu, MySQL](https://www.ravsam.in/blog/deploy-strapi-on-vps-with-ubuntu-mysql/)

* * *]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.ravsam.in%2Fassets%2Fimages%2Fblogs%2Fautomating-project-maintenance-on-github.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Deploy a Serverless Probot/Github App on Netlify Functions]]></title>
            <link>https://www.ravgeet.in/blog/deploy-a-serverless-probot-github-app-on-netlify-functions-cl0</link>
            <guid>https://www.ravgeet.in/blog/deploy-a-serverless-probot-github-app-on-netlify-functions-cl0</guid>
            <pubDate>Tue, 27 Apr 2021 06:04:17 GMT</pubDate>
            <description><![CDATA[Build and Deploy a Serverless Probot or Github App on Netlify Functions to automate your...]]></description>
            <content:encoded><![CDATA[---
canonical_url: https://www.ravsam.in/blog/deploy-a-serverless-probot-github-app-on-netlify-functions/
date: 2021-04-27 06:04:17 UTC
published: true
tags: automation,githubbot,netlify,serverless
title: Deploy a Serverless Probot/Github App on Netlify Functions
---

#### Build and Deploy a Serverless Probot or Github App on Netlify Functions to automate your Github and achieve infinite scalability.

> This blog was originally published on [RavSam’s blog](https://www.ravsam.in/blog/deploy-a-serverless-probot-github-app-on-netlify-functions/). We publish our articles on Medium after a week.

Automation is love. We all love automating repetitive things. There is one such thing called Probot. [Probot](https://probot.github.io/) is one of the most popular frameworks for developing GitHub Apps using Javascript. It is easy to set up as most of the things like setting up authentication, registering webhooks, managing permissions, are all handled by Probot itself. We just need to write our code for sending responses to [different events](https://docs.github.com/en/free-pro-team@latest/developers/webhooks-and-events/webhook-events-and-payloads).

In this article, we will learn how to build and deploy a serverless bot to Netlify Functions. The advantage of using Netlify Functions is that it is _free_ for up to 125,000 requests per month which are more than enough for a small startup or organization.

#### Contents

- Prerequisites
- 1. Writing Application Logic
- 2. Deploying on Netlify Functions
- 3. Updating Webhook URL
- Results

#### Prerequisites

We can follow this [guide on how to set up Probot](https://probot.github.io/docs/development/).

#### 1. Writing Application Logic

Once our Probot is set up, we need to do some changes to our directory structure to deploy it to Netlify Functions.

Let us create a `src` directory and put our application logic in a new file, `app.js`:

```js
/**
 * This is the main entrypoint to your Probot app
 * @param {import('probot').Probot} app
 */

module.exports = (app) => {
  app.log.info('App has loaded');
  app.on('issues.opened', async (context) => {
    context.octokit.issues.createComment(
      context.issue({
        body: 'Thanks for opening this issue!',
      })
    );
  });
};
```

The above code is really simple. Whenever a new issue is opened, it creates an issue comment thanking the issue author.

Netlify Functions are AWS Lambda functions but their deployment to AWS is handled by Netlify. For deploying our Probot on Netlify, we can use AWS Lambda adapter for Probot.

```bash
npm install @probot/adapter-aws-lambda-serverless --save
```

The next thing we need to do is to create a functions directory that will be used by Netlify to deploy our serverless functions. Every _JS_ file in the functions directory is deployed as an individual function that can be accessed via <domain>/.netlify/functions/<function_name>.

In functions directory, let us create a index.js file and add the following code:

```js
const { createLambdaFunction, createProbot } = require('@probot/adapter-aws-lambda-serverless');

const app = require('../src/app');

module.exports.handler = createLambdaFunction(app, {
  probot: createProbot(),
});
```

Our code is finally done and the next step is to deploy our application to Netlify.

#### 2. Deploying on Netlify Functions

Before proceeding with setup for deployment, we need to address some issues. We need to create a configuration file, netlify.toml at the root of the project and tell some important things for Netlify to consider when deploying our bot.

Let us add the following content in netlify.toml:

```toml
[build]
command = "npm install --production"
functions = "./functions"
```

We are telling Netlify to run npm install before deploying our functions which are present in the functions directory.

To deploy on Netlify, we can use [Netlify Dev](https://dev.to/scottw/netlify-dev-3je3-temp-slug-5267792). For that, we need to install netlify-cli by doing:

```bash
npm install netlify-cli -g
```

Let us now login to our Netlify account by doing:

```bash
netlify login
```

Once we are logged in, let us connect our current directory to Netlify Functions. We can either connect to an existing one or create a new one by doing:

```bash
netlify init
```

Once our site is connected, we can build our site locally and deploy it to Netlify by doing:

```bash
netlify build
netlify deploy --prod
```

> We can also connect our Github Repository to our Netlify project or use Github Actions to deploy our bot to Netlify. {.alert alert-info}

#### 3. Updating Webhook URL

Once our Probot is deployed, we need to update the **Webhook URL** to tell Github where to send the event payloads. We can visit [https://github.com/settings/apps/<app-name>](https://github.com/settings/apps/<app-name>) and update the Webhook URL with our Netlify website URL.

#### Results

Let us test our bot by creating an issue on a repository where we installed our Github app and see whether our bot responds back or not.

![Welcome comment by Github Bot](https://cdn-images-1.medium.com/max/1024/0*wzonC87zvO4SuPoQ.png)<figcaption>Welcome comment by Github Bot</figcaption>

Awesome! We can see that our bot welcomed us with a message that we wrote earlier. There are many things to automate on Github like auto assigning users to the issues, auto assigning reviewers to a pull request, auto merging pull requests created by _dependabot alerts_ and much more.

👉 Follow me for upcoming articles.

#### Connect with Me

I love writing for the community while working on my freelance and open source projects. Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) • [Email](mailto:ravgeetdhillon@gmail.com).

#### About RavSam Web Solutions

Looking for a Web Design company to build your next project? We welcome you. Reach out to us to know more about our website development services, or anything else. We are always looking forward to work on great ideas. If you are looking for [an application development company](https://www.ravsam.in/), you are most welcome to get in touch with us.

#### You might also enjoy reading

- [Deploy a website on Netlify through Github Actions](https://www.ravsam.in/blog/deploy-a-website-on-netlify-through-github-actions/)

- [Deploy Strapi on VPS with Ubuntu, MySQL](https://www.ravsam.in/blog/deploy-strapi-on-vps-with-ubuntu-mysql/)

* * *]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.ravsam.in%2Fassets%2Fimages%2Fblogs%2Fdeploy-a-serverless-probot-github-app-on-netlify-functions.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Deploy Strapi on VPS with Ubuntu, MySQL]]></title>
            <link>https://www.ravgeet.in/blog/deploy-strapi-on-vps-with-ubuntu-mysql-23ph</link>
            <guid>https://www.ravgeet.in/blog/deploy-strapi-on-vps-with-ubuntu-mysql-23ph</guid>
            <pubDate>Fri, 16 Apr 2021 11:57:14 GMT</pubDate>
            <description><![CDATA[Learn how to set up a Strapi app on VPS, DigitalOcean, Linode with Ubuntu, MySQL.    This...]]></description>
            <content:encoded><![CDATA[---
canonical_url: https://www.ravsam.in/blog/deploy-strapi-on-vps-with-ubuntu-mysql/
date: 2021-04-16 11:57:14 UTC
published: true
tags: automation,vps,strapi
title: Deploy Strapi on VPS with Ubuntu, MySQL
---

#### Learn how to set up a Strapi app on VPS, DigitalOcean, Linode with Ubuntu, MySQL.

> This blog was originally published on [RavSam’s blog](https://www.ravsam.in/blog/deploy-strapi-on-vps-with-ubuntu-mysql/). We publish our articles on Medium after a week.

So you have built your Strapi project and the next thing you need to do is to deploy it on a production server. In this blog, we will learn about how to set up a Virtual Private Server(VPS) and then deploy our Strapi application. We can apply this guide to any kind of servers like Linode, DigitalOcean and many more.

#### Contents

- 1. Create a non-root user
- 2. Create a Public-Private key pair
- 3. SSH as a new user
- 4. Add SSH key to authorized keys
- 5. Configure Firewall
- 6. Remove Apache and Install Nginx
- 7. Install Node using NVM
- 8. Install PM2
- 9. Install Database (MariaDB/MySQL)
- 10. Create Nginx Server Blocks
- 11. Setup DNS
- 12. Install SSL
- 13. Run the app

#### Steps

We will be using **Hostinger VPS Plan 1** with **Ubuntu 20.04**. Make sure to follow step by step.

> Replace all the values in <> with your own values.

#### 1. Create a non-root user

It is a good idea to create a non-root user with _sudo_ privileges. All the commands will be run through this user. The first step is to log in as the root user.

```bash
ssh root@<VPSIPADDRESS>
```

The first thing to do on a new machine is to update the packages and remove all the older ones.

```bash
sudo apt update -y &amp;&amp; sudo apt upgrade -y &amp;&amp; sudo apt autoremove -y
```

Now our machine is up to date and we need to create a new user and log out of the session.

```bash
adduser <NEWUSER>
usermod -aG sudo <NEWUSER>
exit
```

#### 2. Create a Public-Private key pair

It is great to use Public-Private key pair to SSH into a remote server. We can create a new one using

```bash
ssh-keygen
```

or

copy the existing one’s public key onto our clipboard

```bash
xclip -selection clipboard -in ~/.ssh/hostinger_rsa.pub
```

#### 3. SSH as a new user

Let us now SSH as a new user using password authentication.

```bash
ssh <NEWUSER>@<VPSIPADDRESS>
```

Once we are logged in, we need to create a directory for the new user identified by its name.

```bash
mkdir -p <NEWUSER>
cd <NEWUSER>
```

#### 4. Add SSH key to authorized keys

Let us register our public key by adding it to our authorized keys so that we can log in using a private key.

```bash
mkdir -p ~/.ssh/
sudo echo "<COPIED_PUBLIC_KEY>" >> ~/.ssh/authorized_keys
```

#### 5. Configure Firewall

Now is the time to set up a firewall. A firewall is essential while setting up VPS to restrict unwanted traffic going out or into your VPS. Let us install _ufw_ and configure a firewall to allow SSH operations.

```bash
sudo apt install ufw -y
sudo ufw allow OpenSSH
sudo ufw enable -y
sudo ufw status
```

#### 6. Remove Apache and Install Nginx

Nginx is a much better server than Apache. It is lightweight, easy to set up and allow us to set up proxies. Before installing Nginx, we need to remove Apache which is available by default in Ubuntu 20.04.

```bash
sudo systemctl stop apache2 &amp;&amp; sudo systemctl disable apache2
sudo apt remove apache2 -y &amp;&amp; sudo rm /var/www/html/index.html
sudo apt autoremove -y
```

Let us now install Nginx.

```bash
sudo apt install nginx -y
sudo systemctl start nginx
sudo systemctl status nginx
```

Let us configure the firewall to allow HTTP and HTTPS traffic to pass through it.

```bash
sudo ufw allow 'Nginx Full'
sudo ufw enable -y
sudo ufw status
```

We can also allow either HTTPS or HTTP by using

```bash
sudo ufw allow 'Nginx HTTP'
# or 
sudo ufw allow 'Nginx HTTPS'
```

> To verify Nginx installation, visit the IP address of the VPS.  

> You can find IP address by curl -4 icanhazip.com.

#### 7. Install Node using NVM

Let us install NodeJs using NVM. The following code helps us find the current nvm version.

```bash
nvmversion=$(curl --silent "https://api.github.com/repos/nvm-sh/nvm/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
curl -o- "https://raw.githubusercontent.com/nvm-sh/nvm/$nvmversion/install.sh" | bash
```

Some NPM packages require to refer to numerous packages needed for building software in general. So we will install build-essential for the same.

```bash
sudo apt install build-essential -y
```

#### 8. Install PM2

PM2 is a Process Manager built for production-level applications. It can help us run our Strapi application when the server restarts. It can watch for file changes and restart the server automatically for us.

```bash
npm i -g pm2@latest
cd ~
pm2 startup systemd
```

> Follow the rest of the instructions as specified on the terminal and then do pm2 save.

#### 9. Install Database (MariaDB/MySQL)

MariaDB is a fork of MySQL with lots of performance gains. Let us install our database sever by doing

```bash
sudo apt install mariadb-server -y
sudo mysql_secure_installation
```

> Follow all the instructions and complete the setup.

Once our database server is installed, we need to create a non-root user with root privileges for database operations.

```bash
sudo mariadb
GRANT ALL ON *.* TO '<NEWUSER>'@'localhost' IDENTIFIED BY '<PASSWORD>`' WITH GRANT OPTION;
FLUSH PRIVILEGES;
EXIT;
```

Once this is done, we need to restart our database server.

```bash
sudo systemctl restart mariadb
```

#### 10. Create Nginx Server Blocks

It is always a good idea to server blocks rather than to change the default Nginx configuration. This helps us when we decide to host multiple websites on the same server. To create a server block, we need to do

```bash
sudo nano /etc/nginx/sites-available/<YOUR_DOMAIN>
```

Add the following config

```bash
upstream <YOUR_DOMAIN> {
  server 127.0.0.1:1337;
  keepalive 64;
}

server {
  server_name <YOUR_DOMAIN>;
  access_log /var/log/nginx/<YOUR_DOMAIN>-access.log;
  error_log /var/log/nginx/<YOUR_DOMAIN>-error.log;
  location / {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Host $http_host;
    proxy_set_header X-NginX-Proxy true;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_pass http://<YOUR_DOMAIN>;
    proxy_redirect off;
    proxy_http_version 1.1;
    proxy_cache_bypass $http_upgrade;
  }
}

server {
  listen 80;
  server_name <YOUR_DOMAIN>;
}
```

> This configuration assumes that our Strapi application will be run 127.0.0.1:1337.

Once our configuration is set up, we need to enable our website by creating a symbolic link for our configuration file.

```bash
sudo ln -s /etc/nginx/sites-available/<YOUR_DOMAIN> /etc/nginx/sites-enabled/
```

This is optional but if we are serving multiple domains or subdomains from our server, we need to edit our nginx.conf by uncommenting this line server_names_hash_bucket_size 64; using

```bash
sudo nano /etc/nginx/nginx.conf
```

Lets us quickly check that our configurations are error-free by doing

```bash
sudo nginx -t
```

The output will tell us whether an error exists. If any error comes up, we need to resolve it and then finally restart the server.

```bash
sudo systemctl restart nginx
```

#### 11. Setup DNS

In our domain provider, we need to add an **A record** to point our subdomain to the VPS as follows:

```bash
+------+-----------+----------------+------+
| Type | Name | Content | TTL |
+------+-----------+----------------+------+
| A | subdomain | «VPSIPADDRESS» | 3600 |
+------+-----------+----------------+------+
```

> The DNS propagation can take upto 24 hours. You can use this [handy tool to verify your DNS propagation](https://www.whatsmydns.net/).

#### 12. Install SSL

The final step is to issue an SSL certificate for our Strapi application. We can automate the process of issuing certificates to our domain using **certbot**. Running the following commands will help us issue a Let’s Encrypt SSL certificate for our domain.

```bash
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d <YOUR_DOMAIN>
```

> Follow all the instructions. Use the Redirect option to redirect HTTP traffic to HTTPS.

The advantage of the above commands is that the _certbot_ process runs twice a day to check if any certificates will expire within a month. It automatically renews the certificates so we don’t have to worry about certificate expiration. We can verify this by running:

```bash
sudo systemctl status certbot.timer
```

#### 13. Run the app

Now our setup is complete and it is the time that we all have been waiting for. Let us run our Strapi application using PM2.

```bash
cd ~
cd api
pm2 start ecosystem.config.js
```

We can visit our domain and check our Strapi application is running.

Follow me for upcoming articles.

#### Connect with Me

I love writing for the community while working on my freelance and open source projects. Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) • [Email](mailto:ravgeetdhillon@gmail.com).

#### About RavSam Web Solutions

We are helping startups and companies set up content management systems to manage their content delivery to customers across various products. Reach out to us to know more about our services, or anything else. We are always looking forward to work on great ideas. If you are looking for [an application development company](https://www.ravsam.in/), you are most welcome to get in touch with us.

#### You might also enjoy reading

- [Setup Github Actions for a Dart project](https://www.ravsam.in/blog/setup-github-actions-for-dart-project/)

- [Send an Email notification when Github Actions fails](https://www.ravsam.in/blog/send-email-notification-when-github-action-fails/)

* * *]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.ravsam.in%2Fassets%2Fimages%2Fblogs%2Fdeploy-strapi-on-vps-with-ubuntu-mysql.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Setup Github Actions for a Dart project]]></title>
            <link>https://www.ravgeet.in/blog/setup-github-actions-for-a-dart-project-4aeb</link>
            <guid>https://www.ravgeet.in/blog/setup-github-actions-for-a-dart-project-4aeb</guid>
            <pubDate>Thu, 08 Apr 2021 05:00:41 GMT</pubDate>
            <description><![CDATA[Format, Static Analyse, and Test a Dart project using Github Actions.    This blog was...]]></description>
            <content:encoded><![CDATA[---
canonical_url: https://www.ravsam.in/blog/setup-github-actions-for-dart-project/
date: 2021-04-08 05:00:41 UTC
published: true
tags: dart,automation,flutter,githubactions
title: Setup Github Actions for a Dart project
---

#### Format, Static Analyse, and Test a Dart project using Github Actions.

> This blog was originally published on [RavSam’s blog](https://www.ravsam.in/blog/setup-github-actions-for-dart-project/). We publish our articles on Medium after a week.

When working in a team or even as an individual, we humans often break rules. But sometimes breaking rules can result in a poor quality code which over time grows out to be messy. We can take advantage of linting and static analysis to check whether the written code adheres to our code styling rule. This can be automated using Github Actions. In this blog, we will see how we can set up Github Actions workflows for static analyzing our code before merging it with our production codebase.

#### Contents

- Prerequisites
- 1. Basic Analysis Options
- 2. Setting up Github Actions
- Results

#### Prerequisites

Before getting started, we assume that we have set up the following:

- [A Sample Dart Project](https://github.com/ravgeetdhillon/dart_shelf_server_sample)

#### 1. Basic Analysis Options

Static analysis helps us to find problems before executing a single line of code. It’s a great tool that we can integrate into our development environment to prevent bugs and ensure that code conforms to style guidelines especially when we are working with a team. analysis_options.yaml is a YAML file that we can use to specify the lint rules. Below is a basic example with minimum configuration:

```yml
# Defines a default set of lint rules enforced for
# projects at Google. For details and rationale,
# see https://github.com/dart-lang/pedantic#enabled-lints.
include: package:pedantic/analysis_options.yaml

# For lint rules and documentation, see http://dart-lang.github.io/linter/lints.
# Uncomment to specify additional rules.
linter:
  rules:
    - camel_case_types

analyzer:
  exclude:
    - path/to/excluded/files/**
```

#### 2. Setting up Github Actions

Let us now create a Github Actions workflow for doing a static analysis of our project’s source code and even run some tests. This workflow will run on each push and pull request made to the master branch. Let us create a ci.yml file in the .github/workflows/ directory and the following code:

```yml
name: CI

on:
  push:
    branches: [master]
  pull_request:
    branches: [master]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - name: Setup Repository
        uses: actions/checkout@v2
      
      - name: Setup Dart
        uses: dart-lang/setup-dart@v1

      - name: Install Pub Dependencies
        run: dart pub get

      - name: Verify Formatting
        run: dart format --output=none --set-exit-if-changed .
      - name: Analyze Project Source
        run: dart analyze

      - name: Run tests
        run: dart test
```

The above steps are pretty self-explanatory. We are using a _stable_ version of Dart. However, if we want to set up different Dart configuration, we can use **_sdk_** input with _dart-lang/setup-dart_ action.

```yml
- name: Setup Dart
  uses: dart-lang/setup-dart@v1
  with:
    sdk: 2.10.3

- name: Setup Dart
  uses: dart-lang/setup-dart@v1
  with:
    sdk: dev
```

#### Results

We can make a push directly to the master branch and check out the result for our Github Action.

![Github Actions for Dart](https://cdn-images-1.medium.com/max/1024/0*ECYLVdTHQoLeiCAz.png)<figcaption>All steps passed successfully</figcaption>

So, we can see that we have been able to set up our **lint pipeline** in less than two minutes. This power of Github Actions can help any team to achieve better developer workflow and faster releases.

Follow me for upcoming articles.

#### Connect with Me

I love writing for the community while working on my freelance and open source projects. Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) • [Email](mailto:ravgeetdhillon@gmail.com).

#### About RavSam Web Solutions

We are helping startups around the world to ship their products faster to their customers by setting up proper, error-free, and automated workflows. Reach out to us to know more about our services, or anything else. We are always looking forward to work on great ideas. If you are looking for [an application development company](https://www.ravsam.in/), you are most welcome to get in touch with us.

#### You might also enjoy reading

- [Send an Email notification when Github Actions fails](https://www.ravsam.in/blog/send-email-notification-when-github-action-fails/)

- [Deploy a website on Netlify through Github Actions](https://www.ravsam.in/blog/deploy-a-website-on-netlify-through-github-actions/)]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.ravsam.in%2Fassets%2Fimages%2Fblogs%2Fsetup-github-actions-for-dart-project.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Turn a Google Sheet into a REST API]]></title>
            <link>https://www.ravgeet.in/blog/turn-a-google-sheet-into-a-rest-api-4pnl</link>
            <guid>https://www.ravgeet.in/blog/turn-a-google-sheet-into-a-rest-api-4pnl</guid>
            <pubDate>Wed, 24 Mar 2021 08:54:13 GMT</pubDate>
            <description><![CDATA[Turn your Google Sheet into a REST API and access it in any application.    This blog was...]]></description>
            <content:encoded><![CDATA[---
canonical_url: https://www.ravsam.in/blog/turning-a-google-sheet-into-a-rest-api/
date: 2021-03-24 08:54:13 UTC
published: true
tags: restapi,googleappsscript,webapps
title: Turn a Google Sheet into a REST API
---

#### Turn your Google Sheet into a REST API and access it in any application.

> This blog was originally published on [RavSam’s blog](https://www.ravsam.in/blog/turning-a-google-sheet-into-a-rest-api/). We publish our articles on Medium after a week.

What if we can use our Google Sheets as a CMS? What if we want the data in our Google Sheet to be publicly available. This can be done easily using Google Sheets and Google Apps Script. In this blog, we will take a look at how we can convert a Google Sheet into a REST API and access it publicly from any app we want.

#### Contents

- 1. Setting up a Spreadsheet

- 2. Creating a Google Apps Script

- 3. Converting data to JSON format

- 4. Creating a Web App

- Results

#### 1. Setting up a Spreadsheet

The first task is to set up a Spreadsheet and initialize it with some data.

![Format for Google Spreadsheet](https://cdn-images-1.medium.com/max/1024/0*Na68HQ21zRGTVqbu.png)<figcaption>Google Spreadsheet with some data</figcaption>

#### 2. Creating a Google Apps Script

The first step in our journey to convert the above Google Sheet into a REST API is to be able to access the data in it. So, from _Tools_, select _Script Editor_. This will create a new Apps Script project.

Let us start by adding the following snippet of code in the Code.gs file.

```js
function json(sheetName) {
  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet()
  const sheet = spreadsheet.getSheetByName(sheetName)
  const data = sheet.getDataRange().getValues()
  const jsonData = convertToJson(data)
  return ContentService
        .createTextOutput(JSON.stringify(jsonData))
        .setMimeType(ContentService.MimeType.JSON)
}
```

The above function is really simple to understand. All we are doing is:

- Get the current active spreadsheet to which this Apps Script project is linked with

- Get our specific sheet by its name

- Get the data in that sheet

- Convert the data to JSON format

- Return the JSON response

#### 3. Converting data to JSON format

The data returned by the sheet.getDataRange().getValues() is of the following format:

```js
[
  ['name', 'age', 'role'],
  ['John', 28.0, 'Front End Engineer'],
  ['Marry', 21.0, 'Staff Engineer'],
  ['Jackson', 22.0, 'Backend Engineer']
]
```

In the above snippet, we can see that there is a custom function convertToJson that needs to be written. To convert our sheet data with headers into JSON format, let us the following code in our Apps Script.

```js
function convertToJson(data) {
  const headers = data[0]
  const raw_data = data.slice(1,)
  let json = []
  raw_data.forEach(d => {
      let object = {}
      for (let i = 0; i < headers.length; i++) {
        object[headers[i]] = d[i]
      }
      json.push(object)
  });
  return json
}
```

#### 4. Creating a Web App

To access our Google Sheet as a REST API, we need to publish our Google Apps Script as a Web App. This web app will handle **GET requests**.

Let us add the following code in our Apps Script file:

```js
function doGet(e) {
  const path = e.parameter.path
  return json(path)
}
```

Once we are done with this, the final step is to publish our Apps Script as a Web App. We can simply create a new deployment and set the _Execute As_ to **me** and _Who has access_ to **Anyone**. These settings allow our Web App to be publicly accessible.

#### Results

Let us send a GET request to our published Web App using Postman. The path for the GET request would be our Web App’s URL and query parameter **path** set to our Google Sheet’s name.

In our case, the URL is [https://script.google.com/macros/s/AKfycbw9gpHbIauF8obidyDjxe3_L9qA-Ww-e8bv6pvNNGavAv-xxxxxxxxxxxxxxxxxxxxxxx/exec?path=people](https://script.google.com/macros/s/AKfycbw9gpHbIauF8obidyDjxe3_L9qA-Ww-e8bv6pvNNGavAv-xxxxxxxxxxxxxxxxxxxxxxx/exec?path=people.)

![Google Sheet as a REST API](https://cdn-images-1.medium.com/max/772/0*6jMpSD8zJ3SiC4dg.png)<figcaption>Google Sheet as a REST API</figcaption>

Alright! We can see that we have transformed our Google Sheet into a REST API in under five minutes using the above code. We can add more sheets in our spreadsheet and access them simply using the sheet name in the path query parameter when sending a GET request.

If you loved my article, please clap 👏 for it.

#### Connect with Me

I love writing for the community while working on my freelance and open source projects. Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) • [Email](mailto:ravgeetdhillon@gmail.com).

#### About RavSam Web Solutions

We are helping companies and startups power their IT infrastructure with modern JAMstack architecture. Reach out to us to know more about our services, pricing, or anything else. We are always looking forward to work on great ideas. If you are looking for [an application development company](https://www.ravsam.in/), you are most welcome to get in touch with us.

#### You might also enjoy reading

- [Add Unsubscribe link in emails using Google Apps Script](https://www.ravsam.in/blog/add-unsubscribe-link-in-emails-using-google-apps-script/)

- [Custom Log Monitoring using Google Apps Script](https://www.ravsam.in/blog/custom-log-monitoring-service-using-google-apps-script/)

* * *]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.ravsam.in%2Fassets%2Fimages%2Fblogs%2Fturning-a-google-sheet-into-a-rest-api.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Add Unsubscribe link in emails using Google Apps Script]]></title>
            <link>https://www.ravgeet.in/blog/add-unsubscribe-link-in-emails-using-google-apps-script-19jj</link>
            <guid>https://www.ravgeet.in/blog/add-unsubscribe-link-in-emails-using-google-apps-script-19jj</guid>
            <pubDate>Sat, 20 Feb 2021 07:47:43 GMT</pubDate>
            <description><![CDATA[Provide your subscribers with an option to opt-out of mailing lists by adding unsubscribe...]]></description>
            <content:encoded><![CDATA[---
canonical_url: https://www.ravsam.in/blog/add-unsubscribe-link-in-emails-using-google-apps-script/
date: 2021-02-20 07:47:43 UTC
published: true
tags: emailmarketing,googleappsscript
title: Add Unsubscribe link in emails using Google Apps Script
---

#### Provide your subscribers with an option to opt-out of mailing lists by adding unsubscribe link using Google Apps Script.

> This blog was originally published on [RavSam’s blog](https://www.ravsam.in/blog/add-unsubscribe-link-in-emails-using-google-apps-script/). We publish our articles on Dev after a week.

When setting up our email marketing campaigns or newsletters, one thing that is often forgot is the **Unsubscribe link**. Not providing an option to unsubscribe from the mailing list can land our emails into spam. In this blog, we will look at how we can add an Unsubscribe link in our emails sent using Google Apps Script.

#### Contents

- 1. Setting up a Spreadsheet
- 2. Writing a Hash Function
- 3. Writing Email Template
- 4. Writing Unsubscribe Code
- Results

#### 1. Setting up a Spreadsheet

The first task is to set up a Spreadsheet.

- Create a new Google Spreadsheet and name the sheet as _emails_.
- Add the following fields in the top row of our spreadsheet.

![Format for Google Spreadsheet](https://cdn-images-1.medium.com/max/1024/0*dYA5yRjgXQx58dMn.png)<figcaption>Format for Google Spreadsheet</figcaption>

#### 2. Writing a Hash Function

To provide a secure way to unsubscribe, we need to create a unique token for each of our subscribers. Google Apps Script provides us with utility functions to create a hash of a string using the MD5 hashing algorithm. The following function is used to create a hash of the string provided as a parameter.

```js
function getMD5Hash(value) {
  const digest = Utilities.computeDigest(Utilities.DigestAlgorithm.MD5, value, Utilities.Charset.UTF_8);

  let hash = '';

  for (i = 0; i < digest.length; i++) {
    let byte = digest[i];
    if (byte < 0) byte += 256;
    let bStr = byte.toString(16);
    if (bStr.length == 1) bStr = '0' + bStr;
    hash += bStr;
  }

  return hash;
}
```

Since no two strings in the world have the same hash, this is the right way to provide unsubscribe tokens to our subscribers in our marketing campaigns or newsletters. However, there is a security problem here. If anyone knows the email of our subscriber, he can easily compute the hash and unsubscribe the subscriber from our email list. So, to make the hash impossible to guess, we can add some randomness to our email string. We can create a random string and append it to our original email string. The following snippet of code will help us to achieve our purpose.

```js
function getMD5Hash(value) {
  value = value + generateRandomString(8); // added this

  const digest = Utilities.computeDigest(Utilities.DigestAlgorithm.MD5, value, Utilities.Charset.UTF_8);

  let hash = '';

  for (i = 0; i < digest.length; i++) {
    let byte = digest[i];
    if (byte < 0) byte += 256;
    let bStr = byte.toString(16);
    if (bStr.length == 1) bStr = '0' + bStr;
    hash += bStr;
  }

  return hash;
}

function generateRandomString(length) {
  const randomNumber = Math.pow(36, length + 1) - Math.random() * Math.pow(36, length);
  const string = Math.round(randomNumber).toString(36).slice(1);
  return string;
}
```

![Google Spreadsheet with Unsubscribe Hashes](https://cdn-images-1.medium.com/max/1024/0*WY0XB6DY8K9fBxr8.png)<figcaption>Google Spreadsheet with Unsubscribe Hashes</figcaption>

#### 3. Writing Email Template

Let us create a basic HTML template for testing our Google Apps Script for the unsubscribing feature. Our email template contains the link for unsubscribing. We have also provided two parameters, _email_ and _unsubscribe_hash_. When the subscriber will tap this link, it will send a **GET request** to our Google Apps Script deployed as a Web App.

```html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <h1>We are testing our unsubscribe feature</h1>
    <a href="{{WEBAPP_URL}}?email={{EMAIL}}&amp;unsubscribe_hash={{TOKEN}}">Unsubscribe</a>
  </body>
</html>
```

> Make sure to replace the values in curly braces.

#### 4. Writing Unsubscribe Code

The final step to bring our workflow together is to write a code that handles our unsubscribe functionality. In our Main.gs, let us add the following code to handle the GET request as we discussed earlier:

```js
function doGet(e) {
  const email = e.parameter['email'];
  const unsubscribeHash = e.parameter['unsubscribe_hash'];
  const success = unsubscribeUser(email, unsubscribeHash);
  if (success) return ContentService.createTextOutput().append('You have unsubscribed');
  return ContentService.createTextOutput().append('Failed');
}
```

The above script is pretty self-explanatory. First of all, we retrieve the _email_ and _unsubscribe_hash_ from the query parameters and pass them to our unsubscribeUser function. Based on the output of our function, we return an appropriate response.

Let us write the code for unsubscribeUser:

```js
function unsubscribeUser(emailToUnsubscribe, unsubscribeHash) {  

  // get the active sheet which contains our emails
  let sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('emails');

  // get the data in it
  const data = sheet.getDataRange().getValues();

  // get headers
  const headers = data[0];

  // get the index of each header
  const emailIndex = headers.indexOf('email');

  const unsubscribeHashIndex = headers.indexOf('unsubscribe_hash');

  const subscribedIndex = headers.indexOf('subscribed');

  // iterate through the data, starting at index 1
  for (let i = 1; i < data.length; i++) {
    const row = data[i];
    const email = row[emailIndex];
    const hash = row[unsubscribeHashIndex];

    // if the email and unsubscribe hash match with the values in the sheet
    // then update the subscribed value to 'no'
    if (emailToUnsubscribe === email &amp;&amp; unsubscribeHash === hash) {
      sheet.getRange(i+1, subscribedIndex+1).setValue('no');
      return true;
    }
  }
}
```

In the above function, we simply iterate our Google Sheet and check for the details for every subscriber. If the subscriber’s email and unsubscribe hash match with those sent as a query parameter, we unsubscribe the subscriber by updated the value in the sheet.

#### Results

Let us send a test email to our subscriber specified in the Google Sheet.

![Email with Unsubscribe link](https://cdn-images-1.medium.com/max/1024/0*Pp_zVsKxI3KrwiPj.png)<figcaption>An email with Unsubscribe link</figcaption>

We can see that we have received our email with an option to **Unsubscribe**. Let us unsubscribe and check back our sheet.

![Google Sheet with updated data about subscriber](https://cdn-images-1.medium.com/max/1024/0*egRSwReryReQXsg4.png)<figcaption>Google Sheet with updated data about the subscriber</figcaption>

Oo Yea! We can see that the value for the subscribed field has changed to **no**. Using this workflow, we can provide our subscribers with an option to opt-out of our mailing list for newsletters or maybe marketing emails.

If you loved my article, please clap 👏 for it.

#### Connect with Me

I love writing for the community while working on my freelance and open source projects. Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) • [Email](mailto:ravgeetdhillon@gmail.com).

#### About RavSam Web Solutions

We are helping companies and startups power their IT infrastructure with modern JAMstack architecture. Reach out to us to know more about our services, pricing, or anything else. We are always looking forward to work on great ideas. If you are looking for [an application development company](https://www.ravsam.in/), you are most welcome to get in touch with us.

#### You might also enjoy reading

- [How to track Email opens with Google Apps Script](https://www.ravsam.in/blog/track-email-opens-with-google-apps-script/)

- [How to setup Email Marketing using Google Apps Script](https://www.ravsam.in/blog/setup-email-marketing-using-google-apps-script/)

* * *]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.ravsam.in%2Fassets%2Fimages%2Fblogs%2Fadd-unsubscribe-link-in-emails-using-google-apps-script.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Disable Submit button if Form fields have not changed in a Nuxt/Vue app]]></title>
            <link>https://www.ravgeet.in/blog/disable-submit-button-if-form-fields-have-not-changed-in-a-nuxt-vue-app-9hp</link>
            <guid>https://www.ravgeet.in/blog/disable-submit-button-if-form-fields-have-not-changed-in-a-nuxt-vue-app-9hp</guid>
            <pubDate>Mon, 01 Feb 2021 10:49:07 GMT</pubDate>
            <description><![CDATA[This blog was originally published on RavSam’s blog.   Forms are one of the most important aspects...]]></description>
            <content:encoded><![CDATA[---
canonical_url: https://www.ravsam.in/blog/disable-submit-button-if-form-fields-have-not-changed-in-a-nuxt-vue-app/
date: 2021-02-01 10:49:07 UTC
published: true
tags: jamstack,nuxt,javascript,webdevelopment
title: Disable Submit button if Form fields have not changed in a Nuxt/Vue app
---

> This blog was originally published on [RavSam’s blog](https://www.ravsam.in/blog/disable-submit-button-if-form-fields-have-not-changed-in-a-nuxt-vue-app/).

Forms are one of the most important aspects of any application. It is considered a good UX practice to keep the **Save/Submit** button disabled until the form contents have not changed. In this blog, we will take a look at how can we accomplish this behaviour in a Nuxt/Vue app.

##### Contents

- 1. Creating a Form Template
- 2. Writing Vuex Code
- 3. Writing Computed and Watch properties
- Results

Let us create a simple form which will help us to understand the concepts of **computed** and **watch** properties. In our index.vue in pages directory, let us add the following form template:

```vue
<template>
  <form>
    <label>Name</label>
    <input v-model='form.name' />
    <label>Age</label>
    <input v-model='form.age' />
    <button :disabled="!changed">Save</button>
  <form>
</template>
```

Let us understand the above template. We have bound our form data model to form inputs using **v-model**. In our **Save** button, we will disable it if the form fields have not changed.

#### 2. Writing Vuex Code

In this example, we will use **Vuex Store’s** state, actions and mutations to store state and fetch our form data.

```js
// initialize the state variables
export const state = () => ({
  form: {}
})

export const actions = {
  // action to setup form data
  // we can also do an api call as well
  init({ commit }) {
    const data = {
      name: 'Ravgeet',
      age: '21',
    }

    // commit mutuation for changing the state
    commit('setFormData', data)
  }
}

export const mutations = {
  // mutation to change the state
  setFormData(state, data) {
    state.form = data
  }
}
```

#### 3. Writing Computed and Watch properties

Our template and Vuex Store are set. Now is the time to implement our application logic in our template’s script. In our pages/index.vue, let us add the following code:

```vue
<script>
import _ from 'lodash'

export default {
  data() {
    return {
      changed: false, // useful for storing form change state
      form: {}, // data variable to store current form data binding
    }
  },

  computed: {
    // store the original form data
    originalForm() {
      return this.$store.state.form
    }
  },

  watch: {
    // by watching the original form data
    // create a clone of original form data
    // and assign it to the form data variable
    originalForm() {
      this.form = _.cloneDeep(this.originalForm)
    },

    // watch the changes on the form data variable
    form: {
      handler() {
        // using lodash to compare original form data and current form data
        this.changed = !_.isEqual(this.form, this.originalForm)
      },
      // useful to watch deeply nested properties on a data variable
      deep: true,
    },
  },

  created() {
    // dispatch an action to fill the store with data
    this.$store.dispatch('init')
  }
}
</script>
```

In our **computed** and **watch** properties, we need to clone and compare JS objects. **Lodash** is a great library for working with JS objects and we will install the same by doing:

```bash
$ npm install --save lodash
```

Now that we have written our code, let us understand what is happening in the above code.

- When the component is created, an action init is dispatched using a **created** hook. This action causes a mutation in the store and fills the form state variable.

- The value of the computed property, originalForm is calculated as it is dependent upon form state variable.

- As the value of originalForm is being watched using **watch** hook, the code inside it is executed. A deep clone of originalForm is made and assigned to form data variable.

- Since the value of form is being watched, we use a handler and deep property to run our business logic. We simply check whether the form and originalForm variables are equal using Lodash.

At first, it looks like something very complex is going on but once we break down the things it makes sense.

#### Results

Let us navigate to our browser and check whether we have been able to achieve our purpose of disabling the form submit button if the form fields have not changed at all.

![Tutorial to display notification when user is offline in Nuxt/Vue](https://cdn-images-1.medium.com/max/840/0*W-m1mB74EqVDwWQQ.gif)

_Voila_! We have successfully implemented our workflow. This adds to the UX of our application and saves the user from the frustration especially in long forms. If you any doubts or appreciation, let us know in the comments below.

If you loved my article, please clap 👏 for it.

#### Connect with Me

I love writing for the community while working on my freelance projects. Connect with me through [Twitter](https://twitter.com/ravgeetdhillon) • [LinkedIn](https://linkedin.com/in/ravgeetdhillon) • [Github](https://github.com/ravgeetdhillon) • [Email](mailto:ravgeetdhillon@gmail.com).

#### About RavSam Web Solutions

We are helping companies and startups to set up Web and Mobile Apps powered by modern JAMstack architecture. Reach out to us to know more about our services, pricing, or anything else. We are always looking forward to work on great ideas. If you are looking for [an application development company](https://www.ravsam.in/), you are most welcome to get in touch with us.

#### You might also enjoy reading

- [Offline Toast notification in Nuxt/Vue app](https://www.ravsam.in/blog/offline-toast-notification-in-nuxt-vue-app/)

- [5 Netlify plugins to ensure a great UX for your website](https://www.ravsam.in/blog/5-netlify-plugins-to-ensure-a-great-web-experience/)

* * *]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.ravsam.in%2Fassets%2Fimages%2Fblogs%2Fdisable-submit-button-if-form-fields-have-not-changed-in-a-nuxt-vue-app.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Offline Toast notification in Nuxt/Vue app]]></title>
            <link>https://www.ravgeet.in/blog/offline-toast-notification-in-nuxt-vue-app-14ok</link>
            <guid>https://www.ravgeet.in/blog/offline-toast-notification-in-nuxt-vue-app-14ok</guid>
            <pubDate>Sat, 23 Jan 2021 05:28:48 GMT</pubDate>
            <description><![CDATA[You can also read this article directly on RavSam’s blog. We publish our articles on Medium after a...]]></description>
            <content:encoded><![CDATA[---
canonical_url: https://www.ravsam.in/blog/offline-toast-notification-in-nuxt-vue-app/
date: 2021-01-23 05:28:48 UTC
published: true
tags: webdevelopment,webdesign,vue,jamstack
title: Offline Toast notification in Nuxt/Vue app
---

> You can also read this article directly on [RavSam’s blog](https://www.ravsam.in/blog/offline-toast-notification-in-nuxt-vue-app/). We publish our articles on Medium after a week.

We have often seen apps telling us that _“You are offline. Check your network status.”_. It is not only convenient to do so but adds to a great UX. In this blog, we will look at how can we display a toast notification in a Nuxt/Vue app whenever the user goes offline or online. This will also help us to understand how to use **computed** and **watch** properties together.

- Prerequisites

- 1. Using $nuxt helper

- 2. Writing Code

- Results

Before getting started, we need to make sure that we have correctly setup Nuxt and BootstrapVue.

#### 1. Using $nuxt helper

Nuxt provides a great way to access its helper class, $nuxt. In order to get the current network connection status, we can do two things:

```vue
<template>
  <p>$nuxt.isOffline</p>
  <p>$nuxt.isOnline</p>
</template>

<script>
export default {
  created() {
    console.log(this.$nuxt.isOffline)
    console.log(this.$nuxt.isOnline)
  }
}
</script>
```

Yes, it is as simple as that.

Now in BootstrapVue, we ca create toasts on-demand using this.$bvToast.toast(). So we can implement the notification behaviour using **computed** and **watch** properties provided by Vue.

#### 2. Writing Code

The best place to add the following piece of code is in our _layouts/default.vue_. Doing so can help us to implement a universal kind of notification behaviour.

```
<template>
  <Nuxt />
</template>

<script>
export default {
  computed: {
    connectionStatus() {
      return this.$nuxt.isOffline
    },
  },

  watch: {
    connectionStatus(offline) {
      if (offline) {
        // hide the online toast if it exists
        this.$bvToast.hide('online')

        // create a new toast for offline notification
        // that doesn't hide on its own
        this.$bvToast.toast('You are now offline', {
          id: 'offline',
          toaster: 'b-toaster-bottom-right',
          noCloseButton: true,
          solid: true,
          noAutoHide: true,
          variant: 'danger',
        })
      } else {
        // hide the offline toast if it exists
        this.$bvToast.hide('offline')

        // create a new toast for online notification
        // that auto hides after a given time
        this.$bvToast.toast('You are now online', {
          id: 'online',
          toaster: 'b-toaster-bottom-right',
          noCloseButton: true,
          solid: true,
          autoHideDelay: 5000,
          variant: 'success',
        })
      }
    },
  },
}
</script>
```

Let us go through the above code. First of all, we create a **computed** property, connectionStatus. In connectionStatus, we return the value of this.$nuxt.isOffline. Now in Vue, whenever a property, a computed is dependent upon changes, the computed property also changes. So whenever this.$nuxt.isOffline changes, connectionStatus gets a new value.

We can **watch** the value of connectionStatus and do things based on its new value. In our case, we check whether the changed value of connectionStatus is true(offline). Depending upon this we display our toast notification using BootstrapVue.

#### Results

Let us go back to our browser and check whether the above code works or not. In the Network tab in Developer Tools, let us toggle the network connection status.

![Tutorial to display notification when user is offline in Nuxt/Vue](https://cdn-images-1.medium.com/max/800/0*vejGpJ-ASbx2CWai.gif)

Hurray! Our toast notifications are working perfectly fine. So using the combined magic of **computed** and **watch** properties, we can create outstanding workflows and take our Nuxt/Vue app to next level. If you any doubts or appreciation for our team, let us know in the comments below. We would be happy to assist you.

#### About RavSam Web Solutions

We are helping companies and startups to set up Web and Mobile Apps powered by modern JAMstack architecture. Reach out to us to know more about our services, pricing, or anything else.

#### You might also enjoy reading

- [How to add and customize Bootstrap in Nuxt.js](https://www.ravsam.in/blog/how-to-add-customize-bootstrap-in-nuxtjs/)

- [5 Netlify plugins to ensure a great UX for your website](https://www.ravsam.in/blog/5-netlify-plugins-to-ensure-a-great-web-experience/)

* * *]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.ravsam.in%2Fassets%2Fimages%2Fblogs%2Foffline-toast-notification-in-nuxt-vue-app.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Dynamic Home Route in a Flutter App]]></title>
            <link>https://www.ravgeet.in/blog/dynamic-home-route-in-a-flutter-app-3ppm</link>
            <guid>https://www.ravgeet.in/blog/dynamic-home-route-in-a-flutter-app-3ppm</guid>
            <pubDate>Thu, 14 Jan 2021 06:58:19 GMT</pubDate>
            <description><![CDATA[Dynamically decide the home page to be shown to a user in a Flutter App based on some...]]></description>
            <content:encoded><![CDATA[---
title: Dynamic Home Route in a Flutter App
published: true
date: 2021-01-14 06:58:19 UTC
tags: mobileapps,flutter
canonical_url: https://www.ravsam.in/blog/dynamic-home-route-in-flutter-app/
---

#### Dynamically decide the home page to be shown to a user in a Flutter App based on some authentication logic.

> You can also read this article directly on [RavSam’s blog](https://www.ravsam.in/blog/dynamic-home-route-in-flutter-app/). We publish our articles on Medium after a week.

In any production app, the user is directed to a route based on some authentication logic whenever the app is opened. In our Flutter App, we have at least two routes, **Login** and **Dashboard**. The problem is how can we decide which route should a user be redirected to?

In this app, we will check the value of a locally stored boolean variable to dynamically decide the home route. We can use any method for writing our authentication logic, like checking the validity of the API token, but for the sake of simplicity, we will explore a simple logic.

![Flutter Dynamic Home Route](https://cdn-images-1.medium.com/max/1024/0*zPqYVaXrucQ2Ckwm.png)<figcaption>Flutter Dynamic Home Route Flowchart</figcaption>

#### Contents

- 1. Installing Dependencies
- 2. Writing Code
- Results

#### 1. Installing Dependencies

In our pubspec.yaml, let us add the following dependencies that we will be using in our Flutter application:

```yml
dependencies:
  shared_preferences: ^0.5.12+4
  async: ^2.4.2
```

> Make sure to install the latest version of the dependencies.

[Shared Preferences](https://pub.dev/packages/shared_preferences) is a simple Flutter plugin for reading and writing simple key-value pairs to the local storage. [Async](https://pub.dev/packages/async) contains the utility functions and classes related to the _dart:async_ library.

After adding these dependencies, it is now time to install them. In the terminal, let us execute the following command:

```bash
flutter pub get
```

#### 2. Writing Code

In our main.dart, let us add the following code:

```dart
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() async {
  // handle exceptions caused by making main async
  WidgetsFlutterBinding.ensureInitialized();

  // init a shared preferences variable
  SharedPreferences prefs = await SharedPreferences.getInstance();
  
  // get the locally stored boolean variable
  bool isLoggedIn = prefs.getBoolean('is_logged_in');
  
  // define the initial route based on whether the user is logged in or not
  String initialRoute = isLoggedIn ? '/' : 'login';

  // create a flutter material app as usual
  Widget app = MaterialApp(
    ...
    initialRoute: initialRoute,
  );

  // mount and run the flutter app
  runApp(app);
}
```

The code is pretty self-explanatory. All we are doing is getting the value of is\_logged\_in boolean variable, and then decide the value of the initialRoute in our Flutter Material App.

One important thing in the above code is the use of the _async-await_ pattern. We can also use then but it makes the code a little messy and that’s what we are trying to avoid here. Making our main() function asynchronous can cause some exceptions, so to solve this, we need to add WidgetsFlutterBinding.ensureInitialized().

#### Results

That’s it. We have successfully written a code that allows us to redirect the user to the **Dashboard** page if they are logged in, otherwise to the **Login** page. If you any doubts or appreciation for our team, let us know in the comments below.

#### About RavSam Web Solutions

We are helping companies and startups to migrate their Web and Mobile App to JAMstack architecture. [Reach out to us](https://www.ravsam.in) to know more about our services, pricing, or anything else.

#### You might also enjoy reading

- [Why Flutter Developer could be a $1Mn job?](https://www.ravsam.in/blog/flutter-developer-could-be-a-usd-1million-job/)
- [Top Flutter plugins to take your app to next level](https://www.ravsam.in/blog/top-flutter-plugins-to-take-your-app-to-next-level/)

* * *
 ]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to add and customize Bootstrap in Nuxt.js]]></title>
            <link>https://www.ravgeet.in/blog/how-to-add-and-customize-bootstrap-in-nuxt-js-5eh6</link>
            <guid>https://www.ravgeet.in/blog/how-to-add-and-customize-bootstrap-in-nuxt-js-5eh6</guid>
            <pubDate>Mon, 28 Dec 2020 07:07:11 GMT</pubDate>
            <description><![CDATA[Learn how to improve the look and feel of a Nuxt project by configuring the default...]]></description>
            <content:encoded><![CDATA[---
title: How to add and customize Bootstrap in Nuxt.js
published: true
date: 2020-12-28 07:07:11 UTC
tags: javascript,bootstrap,nuxt,webdesign
canonical_url: https://www.ravsam.in/blog/how-to-add-customize-bootstrap-in-nuxtjs/
---

#### Learn how to improve the look and feel of a Nuxt project by configuring the default Bootstrap behavior.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/cx4kt3rhzg0udbz99t9t.png)

> You can also read this article directly on [RavSam’s blog](https://www.ravsam.in/blog/how-to-add-customize-bootstrap-in-nuxtjs/). We publish our articles on Medium after a week.

Configuring things in any framework is always tricky especially when we are just starting. We will learn today that how can we add and customize Bootstrap in our Nuxt project. Once we go through this guide, we will get an overall idea of how to make things work in Nuxt. By learning how to setup Bootstrap, we can install Popper.js and JQuery as well which are peer dependencies for Bootstrap.

#### Installing Bootstrap

Before starting, let us install the required NPM packages. We will install [bootstrap](https://getbootstrap.com) and optionally [bootstrap-vue](https://bootstrap-vue.org) if we want to use Bootstrap Vue components.

Since we are going to create custom _SCSS_ files, we need to install some dev dependencies as well. In this case, we will install _sass-loader_ and _node-sass_.

```bash
npm install --save bootstrap bootstrap-vue
npm install --save-dev sass-loader node-sass
```

#### Creating a Custom SCSS

Let us now create a new _scss_ file in the assets/scss directory, and name it _custom.scss_. In this file, we need to import Bootstrap’s bootstrap.scss. Let us add the following styling to change the default color system of Bootstrap.

```sass
$theme-colors: (
  'primary': #145bea,
  'secondary': #833bec,
  'success': #1ce1ac,
  'info': #ff7d50,
  'warning': #ffbe0b,
  'danger': #ff007f,
  'light': #c0ccda,
  'dark': #001738,
);

@import '~/node_modules/bootstrap/scss/bootstrap.scss';
```

> We can import individual _scss_ files as well but as the project grows we need to use all the modules. It is of course a good idea to only import what is needed. So instead of worrying about increased module size, we can use [PurgeCSS](https://purgecss.com/guides/nuxt.html) plugin for Nuxt to remove unused CSS from our project when we build it for production.

#### Importing Bootstrap Vue

Using Bootstrap Vue in our project is really simple. We need to create a plugin and install it via Vue.use() to access Vue components globally in our project. Let us create a bootstrap.js file in the _plugins_ directory and add the following code:

```js
import Vue from 'vue'
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
Vue.use(BootstrapVue)
Vue.use(IconsPlugin)
```

> Importing **IconsPlugin** is optional. We can skip it in case we prefer to use FontAwesome icons or any other icon library.

#### Configuring Nuxt Config

The final step is to configure some settings in nuxt.config.js. Let us change our config to look like this:

```js
export default {
  
  ...

  // add your custom sass file
  css: ['@/assets/scss/custom.scss', ...],

  // add your plugin
  plugins: ['~/plugins/bootstrap.js', ...],

  // add bootstrap-vue module for nuxt
  modules: ['bootstrap-vue/nuxt', ...],

  // specify module rules for css and scss
  module: {
    rules: [
      {
        test: /\.s[ac]ss$/i,
        use: ['style-loader', 'css-loader', 'sass-loader'],
      },
    ],
  },
  
  // use these settings to use custom css
  bootstrapVue: {
    bootstrapCSS: false,
    icons: true,
  },

  ...
}
```

That’s it. We have set up our Nuxt project to use customize the default Bootstrap settings. We can override any Bootstrap defaults and customize the look and feel of our project to our advantage. If you any doubts, let us know in the comments below.

#### About RavSam Web Solutions

We are helping businesses migrate their Single Page Applications to Server Side Rendered apps along with Client Side Rendering using Nuxt. Reach out to us to know more about our website development services, or anything else.

#### You might also enjoy reading

- [How to achieve a redesign of your website](https://www.ravsam.in/blog/redesigning-your-website/)
- [Use Humans.txt to credit your team for a project](https://www.ravsam.in/blog/use-humans-txt-to-credit-your-team-for-project/)

* * *]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Two-minute read newsletter to help Google Summer of Code aspirants]]></title>
            <link>https://www.ravgeet.in/blog/two-minute-read-newsletter-to-help-google-summer-of-code-aspirants-2ak7</link>
            <guid>https://www.ravgeet.in/blog/two-minute-read-newsletter-to-help-google-summer-of-code-aspirants-2ak7</guid>
            <pubDate>Wed, 09 Dec 2020 12:25:59 GMT</pubDate>
            <description><![CDATA[On October 26, 2020, Google announced Google Summer of Code 2021. 🎉  Being a GSoCer with GNOME...]]></description>
            <content:encoded><![CDATA[On October 26, 2020, Google announced Google Summer of Code 2021. 🎉

Being a GSoCer with [GNOME Foundation](https://gnome.org) and now a part of their Web Team, I get a lot of requests related to GSoC preparation, how to get started, how to approach GSoC mentors, etc. especially during this part of the year.

So, I have decided to start a newsletter ✉️, Insight, where I will be sending short-form posts every 2nd day to help you prepare for the big venture. I will also bring mentors and former GSoC students to share their views and experiences with GSoC and how GSoC has helped them become who they are. You will have access to top-quality content and guidance throughout the GSoC season.

You can sign up for the newsletter 👇
https://www.ravsam.in/newsletter/sign-up/
]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ftzdhkudqbw8uhdct1zhk.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Send an Email notification when Github Actions fails]]></title>
            <link>https://www.ravgeet.in/blog/send-an-email-notification-when-github-actions-fails-19i9</link>
            <guid>https://www.ravgeet.in/blog/send-an-email-notification-when-github-actions-fails-19i9</guid>
            <pubDate>Fri, 04 Dec 2020 06:36:36 GMT</pubDate>
            <description><![CDATA[We recently published a blog on how to send a slack notification when a github action fails. We got a...]]></description>
            <content:encoded><![CDATA[---
canonical_url: https://www.ravsam.in/blog/send-an-email-notification-when-github-actions-fails/
date: 2020-12-04 06:36:36 UTC
published: true
tags: email,automation,githubactions
title: Send an Email notification when Github Actions fails
---

We recently published a blog on how to send a slack notification when a github action fails. We got a great response from the open-source community. Some of the community members asked us about how they can send an email notification when a Github Action fails. So to take in the request, today we will see how we can build a workflow that allows us to achieve this purpose.

Github has this feature natively that sends an email when a Github Action fails. It works efficiently when you are working on an individual project. However, when working in a team, we often want to notify more than one team member about the possible failure of the workflow.

- 1. Create a sample workflow

- 2. Add Send Email Action

- Results

#### 1. Create a sample workflow

Let us write a simple workflow that prints the infamous _Hello World_. Create a new file _build.yml_ in _.github/workflows_ directory and add:

```yml
name: Build

on:
  push:
    branches: main

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Hello World
        run: echo Hello, world!
```

![Github Action executed successfully](https://cdn-images-1.medium.com/max/1024/0*3s8OnPfyqAo6fSPE.png)<figcaption>Github Action executed successfully</figcaption>

#### 2. Add Send Email Action

[Dawid Dziurla](https://github.com/dawidd6/action-send-mail) has published a Github action that allows us to configure a lot of aspects related to sending the emails. We just need to add the below step to our workflow:

```yml
- name: Send mail
  if: always()
  uses: dawidd6/action-send-mail@v2
  with:
    # mail server settings
    server_address: smtp.gmail.com
    server_port: 465
    # user credentials
    username: ${{ secrets.EMAIL_USERNAME }}
    password: ${{ secrets.EMAIL_PASSWORD }}
    # email subject
    subject: ${{ github.job }} job of ${{ github.repository }} has ${{ job.status }}
    # email body as text
    body: ${{ github.job }} job in worflow ${{ github.workflow }} of ${{ github.repository }} has ${{ job.status }}
    # comma-separated string, send email to
    to: johndoe@gmail.com,doejohn@gmail.com
    # from email name
    from: John Doe
```

> Use echo "$`{{ toJson(github) }}`" to get more workflow context variables.

The if: always() directive tells the Github Actions to always run this step regardless of whether the preceding steps have been executed successfully or not. We use the workflow’s context variables to build our email subject and body. Don’t forget to add your _username_ and _password_ as Action secrets.

> Make sure to use **App-Specific** password for the above action. Learn how to [create an app-specific password for GMail](https://support.google.com/mail/answer/185833?hl=en-GB).

#### Results

Before testing the action in use, let us deliberately fail the action. All we need to do is update the _Hello World_ step’s run command to echo Hello, world! &amp;&amp; exit 1. _exit 1_ sets an exit status of 1 which tells the Github Actions that some kind of error has occurred. Let push our code and see what happens.

![Github Actions workflow failed deliberately](https://cdn-images-1.medium.com/max/1024/0*diu0XokYoCw6eutf.png)<figcaption>Github Actions workflow failed deliberately</figcaption>

From the above screenshot, we can see that the _Send mail_ step was executed even though the previous step failed. Let us check our inbox for the email about the failure.

![Email Notification sent about failed Github Action](https://cdn-images-1.medium.com/max/1024/0*3gnbohaBi4Gi2TwC.png)<figcaption>Email Notification sent about failed Github Action</figcaption>

Sweet! We can see that email notification was sent to our recipients. The subject and body were populated with the appropriate repository and workflow.

Github Actions is a great CI/CD tool. By using the right actions, we can build workflows that help in boosting team productivity at any workspace. If you any doubts or appreciation for our team, let us know in the comments below.

#### About Us

We are helping businesses around the world setup automation techniques to boost their productivity and eliminate human errors. Get in touch with us to know more about our automation software development services.

We provide Web Design, Web Development, Mobile App Development, Software Development, and Automation Services. We have been rated as one of the fastest-growing companies in India. We have been able to generate seven-figure revenue due to our customer-centric services powered by passion and skillset. We are always looking forward to work on great ideas. If you are looking for [an application development company](https://wwww.ravsam.in/services/), you are most welcome to get in touch with our team.]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.ravsam.in%2Fassets%2Fimages%2Fblogs%2Fgreat-tools-for-running-a-tech-startup.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Deploy a website on Netlify through Github Actions]]></title>
            <link>https://www.ravgeet.in/blog/deploy-a-website-on-netlify-through-github-actions-jaj</link>
            <guid>https://www.ravgeet.in/blog/deploy-a-website-on-netlify-through-github-actions-jaj</guid>
            <pubDate>Thu, 26 Nov 2020 12:32:17 GMT</pubDate>
            <description><![CDATA[Although we can connect our Github code branch directly to Netlify and deploy our website to Netlify...]]></description>
            <content:encoded><![CDATA[---
canonical_url: https://www.ravsam.in/blog/deploy-a-website-on-netlify-through-github-actions/
date: 2020-11-26 12:32:17 UTC
published: true
tags: githubactions,automation,netlify
title: Deploy a website on Netlify through Github Actions
---

Although we can connect our Github code branch directly to Netlify and deploy our website to Netlify using a build command, sometimes we want to use Github Actions for building our website and then deploy on Netlify. One of the strongest reason to do is that we get 2000 free build minutes in Github Actions as compared to 300 free build minutes on Netlify. If we are updating our website frequently, we may soon run out of these build minutes (_Of course we can buy the_ [_Pro pack on Netlify_](https://netlify.com/pricing)).

In this article, we will:

- Build our website on Github Actions

- Push the build folder to the website-build branch

- Configure Netlify to use the website-build branch to deploy our website

We will be using [Jekyll](https://jekyllrb.com) to create a website for this demo. We can use any tool to built over the website because, in the end, we will just create a build directory that we will push to another branch on our Github repository.

```bash
# create a boilerplate jekyll website
jekyll new my-awesome-site

# change the directory
cd my-awesome-site

# git add all the unstaged files
git add .

# give a good commit message
git commit -m "feat: first website commit"

# push to the origin
git push origin master
```

Alright. Now we have our website code in our Github Repo.

Since we are going to push our website build to the website-build branch, let us first create a new branch. In our terminal, we will do:

```bash
# create a new branch
git checkout --orphan website-build

# remove all files from the staging area
git rm -rf .

# create an empty commit to initialize branch
git commit --allow-empty -m "root commit"

# push branch to origin
git push origin website-build
```

Let us start by creating a new Github Action. In the terminal, we write

```bash
# switch back to master branch
git checkout master

# create a directory for github actions
mkdir -p .github/workflows

# create a workflow file for github actions
touch .github/workflows/netlify.yml
```

Let’s add the following script in our netlify.yml:

```yaml
name: Build

on:
  push:
    branches:
      - master

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v2

      - name: Set up Ruby
        uses: actions/setup-ruby@v1
        with:
          ruby-version: 2.7

      - name: Install Dependencies
        run: |
          gem install bundler
          bundle install

      - name: Create Build
        run: bundle exec jekyll build -d public

      - name: Upload artifacts
        uses: actions/upload-artifact@v1
        with:
          name: public
          path: public

  commit-build:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Clone the repoitory
        uses: actions/checkout@v2
        with:
          ref: website-build

      - name: Configure Git
        run: |
          git config --global user.email ${GITHUB_ACTOR}@gmail.com
          git config --global user.name ${GITHUB_ACTOR}

      - name: Download website build
        uses: actions/download-artifact@v1
        with:
          name: public
          path: public

      - name: Commit and Push
        run: |
          if [$(git status --porcelain=v1 2>/dev/null | wc -l) != "0"] ; then
            git add -f public
            git commit -m "gh-actions deployed a new website build"
            git push --force https://${GITHUB_ACTOR}:$@github.com/${GITHUB_REPOSITORY}.git HEAD:website-build
          fi
```

The above action contains two jobs.

In the first job, _build_, we check out our current repository, set up Ruby since we are using Jekyll, install dependencies, build the website, and add upload the _public_ directory as an artifact.

In the second job, _commit-build_, we wait for the _build_ job to finish, then check out the website-build branch, configure Git settings, download our build artifact, and finally push _public_ directory if changes are found.

```bash
# add the new files
git add .

# create a new commit with a descriptive message
git commit -m "feat: added netlify build workflow"

# push github actions workflow file to the origin
git push origin master
```

#### Configuring Netlify

The final thing we need to do is to configure Netlify. We need to change two things to make sure everything runs smoothly. First, we will change our _Branch to Deploy_ to website-build. Second, we will update our _Publish Directory_ to artifacts. Now, whenever a push is made to the website-build branch, Netlify will do its job.

![Configure Netlify settings to deploy website](https://cdn-images-1.medium.com/max/1024/0*g-0YpAA4IQ7a6HR4.png)<figcaption>Configure Netlify settings to deploy a website</figcaption>

#### Result

![Website deployed successfully on Netlify](https://cdn-images-1.medium.com/max/1024/0*QDhD4A5hhXDnhxl2.png)<figcaption>Hooray! Website deployed on Netlify</figcaption>

So that was easy. Using this workflow, we can also deploy our websites on Github Pages. There are a lot of developers complaining about issues with Jekyll plugins that are not supported by Github Pages. We can use Github Actions and commit our build to another branch and configure Github Pages to use that branch for deploying our website. If you any doubts, let us know in the comments below.

#### About Us

Looking for a Web Design company to build your next project? We welcome you. Reach out to us to know more about our website development services, or anything else.

We provide Web Design, Web Development, Mobile App Development, Software Development, and Automation Services. We have been rated as one of the fastest-growing companies in India. We have been able to generate seven-figure revenue due to our customer-centric services powered by passion and skillset. We are always looking forward to work on great ideas. If you are looking for [an application development company](https://wwww.ravsam.in/services/), you are most welcome to get in touch with our team.]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.ravsam.in%2Fassets%2Fimages%2Fblogs%2Fdeploy-a-website-on-netlify-through-github-actions.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[5 Netlify plugins to ensure a great Web Experience]]></title>
            <link>https://www.ravgeet.in/blog/5-netlify-plugins-to-ensure-a-great-web-experience-3jb2</link>
            <guid>https://www.ravgeet.in/blog/5-netlify-plugins-to-ensure-a-great-web-experience-3jb2</guid>
            <pubDate>Wed, 18 Nov 2020 08:03:56 GMT</pubDate>
            <description><![CDATA[A great web experience is a must for retaining viewers and turn them into potential leads. The key to...]]></description>
            <content:encoded><![CDATA[---
canonical_url: https://www.ravsam.in/blog/5-netlify-plugins-to-ensure-a-great-web-experience/
date: 2020-11-18 08:03:56 UTC
published: true
tags: netlify,jamstack,webdevelopment,testing
title: 5 Netlify plugins to ensure a great Web Experience
---

A great web experience is a must for retaining viewers and turn them into potential leads. The key to achieving a great web experience is making sure that your website is optimized and tested thoroughly. Testing a small website manually can be easy but a large website that is controlled by a team through a CMS requires automated tools. These days most website owners are shifting towards JAMstack. Netlify is one of the best platforms to host JAMstack websites. In this blog, we will list some of the best Netlify plugins that you can use to offer a great web experience to your viewers.

Broken links on your website can result in embarrassment and a huge dropout rate. You can manually check broken links on your website but it is better if we can automate the process. **Checklinks** helps to keep all the assets references correct and avoid 404 links to the internal pages, as well as the external pages your website links to. It can also report on inefficient redirect chains and potential mixed content warnings.

Sometimes, in the middle of the night, one of your team members can trigger a new deployment which can by chance break the website. What next? Your team members are called and you have to fix the bug at the time you were about to sleep. So to blocks deployments that happen outside of the specified deployment hours range, you can use the **Deployment Hours** Netlify plugin.

#### [3. HTML Validate](https://github.com/oliverroick/netlify-plugin-html-validate)

**HTML Validate** Netlify plugin allows you to validate your HTML website build. It is extremely important to test the validity of the HTML because not only it can break your website’s design structure but also affect the SEO as the GoogleBot won’t be able to parse your website correctly. For example,

```

<p>

  <button>Click me!</button>

  <div id="show-me">

    Lorem ipsum

  </div>

</p>

```

The validation of the above HTML will produce the following error in the terminal:

```

1:1 error Element <p> is implicitly closed by adjacent <div> no-implicit-close

2:2 error Button is missing type attribute button-type

6:4 error Unexpected close-tag, expected opening tag close-order

```

#### [4. Lighthouse](https://github.com/netlify-labs/netlify-plugin-lighthouse)

**Lighthouse** is a Netlify plugin using which you can run an automated audit of your website after every build. You can set threshold values for each of the categories tested by Lighthouse that include performance, accessibility, best practices, SEO, and PWA if required. A new website build is only deployed if all the threshold values are met.

![RavSam website performance measured by Lighthouse](https://cdn-images-1.medium.com/max/841/0*w1_XephXTIgTzKAB.png)<figcaption>Our website performance measured by Lighthouse</figcaption>

#### [5. Minify HTML](https://github.com/philhawksworth/netlify-plugin-minify-html)

When the website is built using a Static Site Generator like [Jekyll](https://jekyllrb.com/), [Hugo](https://gohugo.io/), a lot of whitespaces can creep into the HTML generated after the build. This whitespace means that the user has to download more bytes and you also waste your bandwidth. By minifying the HTML generated by your build, you can remove the redundant bytes from your website. This plugin minifies all HTML files in your publish directory, which is to be deployed by Netlify to its global CDN.

Cheers!

#### About Us

We are helping businesses around the world by migrating their WordPress websites to JAMstack websites. They have recorded an over 80% increase in conversions and are reaping the benefits of static websites that are made dynamic through the APIs. Get in touch with us to know more about our website development process.

We provide Web Design, Web Development, Mobile App Development, Software Development, and Automation Services. We have been rated as one of the fastest-growing companies in India. We have been able to generate seven-figure revenue due to our customer-centric services powered by passion and skillset. We are always looking forward to work on great ideas. If you are looking for [an application development company](https://wwww.ravsam.in/services/), you are most welcome to contact us.]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.ravsam.in%2Fassets%2Fimages%2Fblogs%2F5-netlify-plugins-to-ensure-a-great-web-experience.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Great tools for running a Tech startup]]></title>
            <link>https://www.ravgeet.in/blog/great-tools-for-running-a-tech-startup-2i2g</link>
            <guid>https://www.ravgeet.in/blog/great-tools-for-running-a-tech-startup-2i2g</guid>
            <pubDate>Tue, 10 Nov 2020 08:47:26 GMT</pubDate>
            <description><![CDATA[To run a successful tech company, you need to use the right tools to bring success to yourself and...]]></description>
            <content:encoded><![CDATA[---
canonical_url: https://www.ravsam.in/blog/great-tools-for-running-a-tech-startup/
date: 2020-11-10 08:47:26 UTC
published: true
tags: startup,tools
title: Great tools for running a Tech startup
---

To run a successful tech company, you need to use the right tools to bring success to yourself and your customers. We often get asked by our customers and developers around the world about what tools we use at RavSam. In this blog, we will talk about some of the best tools to increase team productivity within the organization.

We can achieve even more profit by connecting these tools. For example, we can create a Slack bot that creates a Typeform and send it to your customers via Send with Us. Such workflows can be designed using Zapier or Google Apps Script.

![Preview of](https://cdn-images-1.medium.com/max/1024/0*BgcuTTDL7CtNPrGu.png)

Every company does email marketing. It is a great way to reach out to your users and leads. But how do you ensure that your email template is responsive and optimized for all screens? **Send with Us** is a great service by [Dyspatch.io](https://www.dyspatch.io/sendwithus/). It allows you to build email design, templates, and AMP as well.

![Preview of](https://cdn-images-1.medium.com/max/1024/0*0rjvkaKLy3EFGGPb.png)

**Loom** is a great product that allows you to record video messages of your screen, camera, or both at the same time. It can be hugely beneficial when you have to send a report to the customer. You can record yourself explaining the report metrics which allows the customer to understand it better.

#### [3. Typeform](https://www.typeform.com/)

![Preview of](https://cdn-images-1.medium.com/max/1024/0*rObY7FJplAwPsocQ.png)

**Typeform** is the best service in the world for building forms and surveys in no time. Good looking forms help keeps the audience engaged. We get more thoughtful responses and higher completion rates. The best part is that non-developer team member s can easily create forms on their own.

#### [4. Zapier](https://www.zapier.com/)

![Preview of](https://cdn-images-1.medium.com/max/1024/0*GLIjBxuyHnEpX3NI.png)

**Zapier** allows you to connect apps and create automation workflows by sending data to form one endpoint to the other. It allows you to create a **Zap** in the form of a trigger, condition, and action.

#### [5. Intercom](https://www.intercom.com/)

![Preview of](https://cdn-images-1.medium.com/max/1024/0*txmIUAWBl44EGZQc.png)

**Intercom** is more than just a chat widget. It’s a full customer service suite that can be integrated into any digital product you want. It aims to give customers a conversational experience they’ll remember — and come back for.

#### [6. Slack](https://www.slack.com/)

![Preview of](https://cdn-images-1.medium.com/max/1024/0*wfc5YiAAvQXNiQrP.png)

There is no life without **Slack**. It is not only great for internal organization communication, but also for building **bots** , which can be used to automate processes and do other cool stuff.

#### [7. Apps Script](https://www.google.com/script/start/)

![Preview of](https://cdn-images-1.medium.com/max/1024/0*4UT3SC-oK3-c07lW.png)

**Google Apps Script** is a Javascript-based development platform that allows you to connect applications that integrate with G Suite. Authentication is baked right into the platform so that the developers don’t have to deal with access keys and tokens. We use Google Apps Script extensively in developer workflows at RavSam.

Cheers!

#### About Us

We provide Web Design, Web Development, Mobile App Development, Software Development, and Automation Services. We have been rated as one of the fastest-growing companies in India. We have been able to generate seven-figure revenue due to our customer-centric services powered by passion and skillset. We are always looking forward to work on great ideas. If you are looking for [an application development company](https://wwww.ravsam.in/services/), you are most welcome to [contact us](https://wwww.ravsam.in/contact-us/).]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.ravsam.in%2Fassets%2Fimages%2Fblogs%2Fgreat-tools-for-running-a-tech-startup.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Why Flutter Developer could be a million-dollar job?]]></title>
            <link>https://www.ravgeet.in/blog/why-flutter-developer-could-be-a-1mn-job-3819</link>
            <guid>https://www.ravgeet.in/blog/why-flutter-developer-could-be-a-1mn-job-3819</guid>
            <pubDate>Wed, 04 Nov 2020 07:03:46 GMT</pubDate>
            <description><![CDATA[Flutter is becoming a rage these days. Developers around the world are exploring this technology that...]]></description>
            <content:encoded><![CDATA[---
canonical_url: https://www.ravsam.in/blog/why-flutter-developer-could-be-a-1mn-job/
date: 2020-11-04 07:03:46 UTC
published: true
tags: flutter,appdesign,appdevelopement,jobs
title: Why Flutter Developer could be a million-dollar job?
---

[Flutter](https://www.flutter.dev) is becoming a rage these days. Developers around the world are exploring this technology that has the potential to change the world of application development. There are many cross-platform development frameworks in the market, but no one matches the essence of Flutter. In this article, we will project our future vision for the Flutter and why being a Flutter Developer would be financially rewarding.

Flutter is Google’s latest innovation for building beautiful, natively compiled applications for mobile, web, and desktop from a single codebase. It is a UI toolkit with the fast development, expressive and flexible UI, and native performance. It comes with an extremely useful feature called hot reloading which renders the UI on the device in seconds helping in decreasing the development time.

#### What skills are required to be a Flutter Developer?

To be a Flutter Developer, the first thing you need to learn is [Dart](https://dart.dev/). Dart is a programming language developed by Google. It is a beautiful language that supports Object-Oriented design patterns. It has borrowed some of the best features from other programming languages such as C++, Javascript, etc. If you are comfortable in any programming language, then you can easily understand Dart and start developing Flutter applications.

Once you are comfortable with Dart, the next step is to learn Flutter itself. **Everything in Flutter is a Widget**. It means that you can add any widgets into widgets and build widget trees, which are then rendered by the Flutter engine and painted on the screen. Once you get an idea of how to build the layouts using widgets, you will be surprised how easy it is to build the applications using Flutter.

#### Why learning Flutter could pay you $1Mn per year?

Now comes the main question. What is so special about the Flutter that it could be a job with a salary of $1Mn per year. The answer is hidden in the Flutter itself, **Write once, Build for anywhere**.

Let us take an example of a company that has a brilliant product used by millions of users around the world. Their product is available as a Web App, Android app, iOS app, Windows Desktop app, Linux Desktop app, macOS Desktop app as well. Currently, they have a Frontend Engineer who works on the Web App. In their mobile team, they have an Android developer, an iOS developer, and a tester who writes an automated test for both the apps. Coming to their desktop application ecosystem, they have four more developers working in a collaborative environment. All are very talented in what they do but implementing a new feature is very tedious as it has to be implemented in each of the applications. Also as the product functionality is increasing after every release, the maintenance is becoming a little difficult. They pay a collective of $800k to the developers in their company, plus the overhead maintenance cost.

Now let us imagine what would happen if there was a technology using which you could write the code and tests only once, and then compile it into multiple builds for different environments. Instead of hiring multiple individuals, the company could get a Flutter developer on board who would be responsible to develop this cross-platform functionality. Since we have to write the code only once, we know exactly what to test as well.

#### What’s next

The Web and Desktop apps designed in Flutter are still not up to the mark but the Google team is making Flutter better with each release. The community-backed plugin ecosystem is a huge advantage as it provides the developers a way to add functionality to their app which is not provided by the Flutter itself. If you are a student, then definitely you should learn the Flutter. You can also become [Google Developer Expert](https://developers.google.com/community/experts). Once you are done with flutter, you can upskill yourself by learning the backend development or Firebase. In this way, you could be the next Full Stack Developer with Flutter as a specialty. Cheers!

#### About Us

We provide Web Design, Web Development, Mobile App Development, Software Development, and Automation Services. We have been rated as one of the fastest-growing companies in India. We have been able to generate seven-figure revenue due to our customer-centric services powered by passion and skillset. We are always looking forward to work on great ideas. If you are looking for [an application development company](https://wwww.ravsam.in/services/), you are most welcome to [contact us](https://wwww.ravsam.in/contact-us/).]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.ravsam.in%2Fassets%2Fimages%2Fblogs%2Fflutter-developer-could-be-a-usd-1million-job.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[How to approach the API design? Any Suggestions?]]></title>
            <link>https://www.ravgeet.in/blog/how-to-approach-the-api-design-suggestions-27ci</link>
            <guid>https://www.ravgeet.in/blog/how-to-approach-the-api-design-suggestions-27ci</guid>
            <pubDate>Tue, 27 Oct 2020 09:12:58 GMT</pubDate>
            <description><![CDATA[How to approach the API design?  I am going to start a project which will have a Web App and a Mobile...]]></description>
            <content:encoded><![CDATA[How to approach the API design?

I am going to start a project which will have a Web App and a Mobile App. Both will fetch the data from an API.

Now the thing is the data stored in DB is in raw format. This data would be converted to JSON and sent over the API.

Now, I have to add some new fields to the JSON API and process the existing ones. My question is should I send this data to the client and process it here or should I process it on the server. Processing the data on the client means I have to write the code two times, one for Web App and one for Mobile App. Writing the code on the server means longer wait times.

So what is the right approach?]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Create JSON Feed for a Jekyll blog]]></title>
            <link>https://www.ravgeet.in/blog/create-json-feed-for-a-jekyll-blog-35p3</link>
            <guid>https://www.ravgeet.in/blog/create-json-feed-for-a-jekyll-blog-35p3</guid>
            <pubDate>Mon, 26 Oct 2020 12:39:13 GMT</pubDate>
            <description><![CDATA[The big difference between JSON feed and XML feed is the ability to read and write JSON. Parsing XML...]]></description>
            <content:encoded><![CDATA[---
title: Create JSON Feed for a Jekyll blog
published: true
date: 2020-10-26 12:39:13 UTC
tags: blog,webdevelopment,webdesign,jekyll
canonical_url: https://www.ravsam.in/blog/create-json-feed-for-a-jekyll-blog/
---

The big difference between JSON feed and XML feed is the ability to read and write JSON. Parsing XML is not an easy task whereas when it comes to JSON, we only have to write only a single line of code and use it any way we want. With the advent of JSON-based feed readers, it would be nice if we too have a JSON feed for our blog. In this article, we will be looking at how can use Jekyll and Liquid syntax to create a JSON feed for our blog.

#### Contents

- Prerequisites
- 1. Creating a feed.json file
- 2. Writing Liquid Code
- 3. Adding to Head
- Result

#### Prerequisites

Before proceeding, we assume that

- Our Jekyll blog is already setup.
- Our blogs live at the _blogs directory

#### 1. Creating a feed.json file

First, we will create a feed.json file at the root of our website. We will add the following front matter to it:

```yml
permalink: /blog/feed.json
```

We can specify any permalink we want. Most of the time it is either /blog/feed.json or /feed.json.

#### 2. Writing Liquid Code

Let us add the following Jekyll Liquid code to the feed.json file. This feed uses the most recent version of [JSON Feed specifications](https://jsonfeed.org/version/1.1).

```json
{
    "version": "https://jsonfeed.org/version/1.1",
    "title": "{{ 'JSON Feed for ' | append: site.author | xml_escape }}",
    "description": {{ site.description | jsonify }},
    "favicon": "{{ '/assets/images/logos/favicons/apple-touch-icon.png' | absolute_url }}",
    "language": "en",
    "home_page_url": "{{ "/" | absolute_url }}",
    "feed_url": "{{ "/blog/feed.json" | absolute_url }}",
    "user_comment": "This feed allows you to read the blogs from this site in any feed reader that supports the JSON Feed format.",
    "items": [{% for blog in site.blogs reversed %}
        {
            "id": "{{ blog.url | absolute_url }}",
            "url": "{{ blog.url | absolute_url }}",
            "language": "en",
            "title": {{ blog.title | jsonify }},
            "summary": {{ blog.description | jsonify }},
            "content_html": {{ blog.content | jsonify }},
            "date_published": "{{ blog.date | date_to_xmlschema }}",
            "date_modified": "{{ blog.last_modified_at | date_to_xmlschema }}",
            "image": "{{ blog.image.path | absolute_url }}",
            "banner_image": "{{ blog.image.path | absolute_url }}",
            "authors": [{{ blog.author | jsonify }}],
            "categories": {{ blog.categories | jsonify }},
            "tags": {{ blog.tags | jsonify }}
        }
        {% unless forloop.last %},{% endunless %}{% endfor %}
    ]
}
```

#### 3. Adding to Head

One final step is to add a reference to our JSON feed in the <head> tag of our website so that anyone looking for our blog feed can find it out here.

```html
<link rel="alternate" type="application/json" title="Feed for RavSam Web Solutions" href="https://www.ravsam.in/blog/feed.json" />
```

#### Result

That’s it. Let us execute bundle exec jekyll serve and check out our feed at [http://localhost:4000/blog/feed.json](http://localhost:4000/blog/feed.json)

![JSON Feed for a Jekyll blog](https://cdn-images-1.medium.com/max/960/0*4mlaNh_pLVV5xM2m.png)<figcaption>JSON Feed for a Jekyll blog</figcaption>

As we can see we have successfully created a JSON feed for our blog. We can submit this blog feed to a [JSON Feed Reader](https://json-feed-viewer.herokuapp.com/).

We can also create JSON feeds for **podcasts** , **microblogs** , and submit them to content aggregators. As we move into the future, we will see more and more blogs migrating to the JSON feed as it is extremely easy to consume and setup. If you any doubts or appreciation for our team, let us know in the comments below.]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[When was the last time you ran your code successfully on the first try?]]></title>
            <link>https://www.ravgeet.in/blog/when-was-the-last-time-you-ran-your-code-successfully-on-the-first-try-4i1f</link>
            <guid>https://www.ravgeet.in/blog/when-was-the-last-time-you-ran-your-code-successfully-on-the-first-try-4i1f</guid>
            <pubDate>Sat, 24 Oct 2020 12:31:33 GMT</pubDate>
            <description><![CDATA[Recently, while working on a freelance project, I wrote some Python code for about an hour with full...]]></description>
            <content:encoded><![CDATA[Recently, while working on a freelance project, I wrote some Python code for about an hour with full concentration and staying away from the terminal.

Then I ran the code.

And Boom! It executed successfully on the very first run. 

When was the last time you ran your code successfully on the first try?]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F2uxw3r5p9me2i23mzrls.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Top Flutter plugins to take your app to next level]]></title>
            <link>https://www.ravgeet.in/blog/top-flutter-plugins-to-take-your-app-to-next-level-jaf</link>
            <guid>https://www.ravgeet.in/blog/top-flutter-plugins-to-take-your-app-to-next-level-jaf</guid>
            <pubDate>Wed, 21 Oct 2020 12:36:12 GMT</pubDate>
            <description><![CDATA[Photo by Serhat Beyazkaya on Unsplash  Flutter is one of the fastest rising frameworks for developing...]]></description>
            <content:encoded><![CDATA[---
title: Top Flutter plugins to take your app to next level
published: true
date: 2020-10-21 12:36:12 UTC
tags: flutter,appdesign,appdevelopment
canonical_url: https://www.ravsam.in/blog/top-flutter-plugins-to-take-your-app-to-next-level/
---

![](https://cdn-images-1.medium.com/max/1024/1*dwOh2PxE6RGUIc9kcUdSmQ.jpeg)<figcaption>Photo by <a href="https://unsplash.com/@serhatbeyazkaya?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Serhat Beyazkaya</a> on <a href="https://unsplash.com/s/photos/stairs?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption>

Flutter is one of the fastest rising frameworks for developing cross-platform applications. At RavSam, we also use Flutter for [mobile app development](https://www.ravsam.in/services/mobile-app-development/) services. One of the biggest advantages of Flutter is its plugin ecosystem. There are tons of plugins available on [pub.dev](https://pub.dev/) build by the community that can be integrated right on the go with our Flutter apps. In this article, we will take a look at some of those plugins which can be used almost in any Flutter app and enhance the User Experience to a great extent.

#### 1. [Share](https://pub.dev/packages/share)

![Share Widget in Flutter App](https://cdn-images-1.medium.com/max/960/0*DUBShjA2MgytlT4k.png)

Share is a plugin that we can use to share the app content via other apps using the platform’s share dialog. It works on both Android and iOS and can be used in a social media application. According to its developers, a backward-compatible 1.0.0 version will be released soon as the plugin has reached a stable API.

#### 2. [Geolocator](https://pub.dev/packages/geolocator)

![Tracking Location in Flutter App](https://cdn-images-1.medium.com/max/960/0*TAqyoeMUVkqUyVxN.png)

Geolocator is one of the must-use plugins if you are building an application that shows content based on the location of the user. It provides an easy to implement API which can be used to access platform-specific location services. We can access the last know location, current location of the device, and even get continuous location updates. This plugin is of great use in Cab Booking, Food Delivery, etc.

#### 3. [URL Launcher](https://pub.dev/packages/url_launcher)

![URL Launcher Plugin to open links in Flutter App](https://cdn-images-1.medium.com/max/960/0*SU1d593jrh0Fcroo.png)

URL Launcher is one of the most famous plugins which is used on almost every production Flutter app. It is a plugin for launching different URL schemes like:

- [https://example.com](https://example.com)
- mailto:foo@bar.com
- tel:9876543210.

It supports every platform that Flutter targets such as iOS, Android, Web, Windows, macOS, and Linux.

#### 4. [Shimmer](https://pub.dev/packages/shimmer)

![Shimmer Loading Effect in Flutter App](https://cdn-images-1.medium.com/max/960/0*rElHtXzmFjTnLKPO.png)

Shimmer is a brilliant plugin to enhance the UI/UX of the app while the content is loading. It is visually appealing as it gives the user an indication that the content is being fetched from the Internet. This package provides a Widget for adding a shimmer effect in the Flutter project.

#### 5. [Connectivity](https://pub.dev/packages/connectivity)

![No Connectivity State in Flutter App](https://cdn-images-1.medium.com/max/960/0*zKoDvy35WtVlOB2v.png)

There may be sometimes when a user opens our app but he is not connected to the WiFi or Mobile Internet connection. In circumstances like these, we can simply add this plugin to our Flutter app for discovering network connectivity and deciding the UI state accordingly. This plugin provides the developers with a great way to distinguish between Mobile and WiFi connection. According to its developers, a backward-compatible 1.0.0 version will be released soon as the plugin has reached a stable API.]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Use Humans.txt to credit your team for a project]]></title>
            <link>https://www.ravgeet.in/blog/use-humans-txt-to-credit-your-team-for-a-project-1fpm</link>
            <guid>https://www.ravgeet.in/blog/use-humans-txt-to-credit-your-team-for-a-project-1fpm</guid>
            <pubDate>Thu, 15 Oct 2020 05:49:58 GMT</pubDate>
            <description><![CDATA[We all love to take credit for the work that we are proud of and there is nothing wrong with that....]]></description>
            <content:encoded><![CDATA[---
title: Use Humans.txt to credit your team for a project
published: true
date: 2020-10-15 05:49:58 UTC
tags: webdevelopment,projectmanagement,webdesign
canonical_url: https://www.ravsam.in/blogs/use-humans-txt-to-credit-your-team-for-a-project/
---

We all love to take credit for the work that we are proud of and there is nothing wrong with that. Whenever we work on the project, we put our team/company name in the **footer of the website** , or in the **about section**. However, this doesn’t take into consideration the members of the team who worked on the project. These members can be Designers, Developers, SEOs, QAs, etc. To attribute the team behind the project, there is a thing called [**humans.txt**](http://humanstxt.org/)

#### What is humans.txt?

Humans.txt is a great initiative for listing the individuals who worked behind a project. It’s an initiative for knowing the people behind a website. Basically, it is just a .txt file that contains information about individuals and their roles in a particular project.

#### Humans.txt Example

```txt
/* TEAM */
 Project Lead: John Smith
 Contact: johnsmith [at] gmail.com
 Twitter: @johnsmith
 From: New York, USA

 UI Designer: Tim Jacob
 Contact: timjacob [at] gmail.com
 Twitter: @timjacob
 From: New York, USA

 Project Lead: Jennifer Jaine
 Contact: jenniferjaine [at] gmail.com
 Twitter: @jenniferjaine
 From: Toronto, Cananda

/* SITE */
 Last update: 2020/10/05
 Language: English
 Doctype: HTML5
 IDE: VSCode
 Technologies: Jekyll, Python
```

#### Should we always humans.txt?

Humans.txt is **completely optional**. It is not related to SEO. But it is a way to tell the world about the individuals who worked hard behind the website.

#### Who to mention on humans.txt?

We can mention anyone on the humans.txt. We can attribute our designers, developers, SEOs, Project Manager, .etc. On an Open Source project, we can add all the contributors in the humans.txt by automating it through CI/CD.

#### Where should I add humans.txt?

Humans.txt lives at the **root of the website**. We can add a <link> tag in the <head> of our website.

```html
<link type="text/plain" rel="author" href="http://example.com/humans.txt">
```

We hope that you will use the humans.txt in your current or upcoming project and attribute your team for what they do. Cheers!

#### About RavSam Web Solutions

Looking for a Web Design company to build your next project? We welcome you. Reach out to us to know more about our website development services, or anything else.

We provide Web Design, Web Development, Mobile App Development, Software Development, and Automation Services. We have been rated as one of the fastest-growing companies in India. We have been able to generate seven-figure revenue due to our customer-centric services powered by passion and skillset. We are always looking forward to work on great ideas. If you are looking for [an application development company](https://wwww.ravsam.in/services/), you are most welcome to get in touch with our team.]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Custom Log Monitoring service using Google Apps Script]]></title>
            <link>https://www.ravgeet.in/blog/custom-log-monitoring-service-using-google-apps-script-44i</link>
            <guid>https://www.ravgeet.in/blog/custom-log-monitoring-service-using-google-apps-script-44i</guid>
            <pubDate>Sat, 26 Sep 2020 13:12:37 GMT</pubDate>
            <description><![CDATA[In this blog, we will talk about how can we set up our custom, serverless logging system using Google...]]></description>
            <content:encoded><![CDATA[---
title: Custom Log Monitoring service using Google Apps Script
published: true
date: 2020-09-26 13:12:37 UTC
tags: automation,webdevelopment,googleappsscript
canonical_url: https://www.ravsam.in/blog/custom-log-monitoring-service-using-google-apps-script/
---

In this blog, we will talk about how can we set up our custom, serverless logging system using Google Apps Script and Google Docs. We will use Google Apps Script to handle the HTTP requests and other business logic. We will store our logs in Google Docs.

#### Contents

1. Creating a Google Doc
2. Writing Code
3. Deploying as a Web App
4. Results

#### 1. Creating a Google Doc

First of all, we will create a new Google Doc at [https://docs.google.com/document/u/0/](https://docs.google.com/document/u/0/) in which we will store our logs. We will get its ID that we will be using in our Google Apps Script project code.

#### 2. Writing Code

First, let us create a new Google Apps Script Project by going to [https://script.google.com/home](https://script.google.com/home). Once we have created a new project, its time to write some code. Let us add the following code to our Main.gs file.

```js
function logEvent(eventString, eventType='info') {

  // get google docs to store the logs
  var body = DocumentApp.openById('google-docs-id').getBody();

  // get current time
  var time = new Date().toUTCString();

  // create a log string
  var log = time + " - " + eventString;

  // add log string to the google docs
  body.appendParagraph(log);
}
```

The above code is really simple. We have created a function _logEvent(eventString, eventType=’info’)_ which takes in two parameters eventString, eventType(which we will be discussing later). In this function, we get the body of a Google Doc in which we will store our logs. After that, we create a new string that contains the current time and the event string and append this log to the body of our Google Doc. We can try to run our function manually to see if anything happens at all.

It would be great if can **color code our logs** to identify the type of event just by looking at them. Let us modify the above code to the below code:

```js
function logEvent(eventString, eventType='info') {

  // get google docs to store the logs
  var body = DocumentApp.openById('google-docs-id').getBody();

  // get current time
  var time = new Date().toUTCString();

  // create a log string
  var log = time + " - " + eventString;

  // add log string to the google docs
  var par = body.appendParagraph(log);

  var style = {};
  style[DocumentApp.Attribute.FONT_SIZE] = 12;

  // based on the event type choose the style
  switch(eventType) {
    case 'info':
      style[DocumentApp.Attribute.FOREGROUND_COLOR] = '#0000ff';
      break;

    case 'success':
      style[DocumentApp.Attribute.FOREGROUND_COLOR] = '#06ad00';
      break;

    case 'warning':
      style[DocumentApp.Attribute.FOREGROUND_COLOR] = '#e67e00';
      break;

    case 'error':
      style[DocumentApp.Attribute.FOREGROUND_COLOR] = '#ff0000';
      break;

    default:
      style[DocumentApp.Attribute.FOREGROUND_COLOR] = '#000000'
      break;
  }

  // apply the custom style the log string
  par.setAttributes(style);
}
```

Based on the value of eventType, we apply the styling to our log string. By default, each new event is associated with the **info**  type.

#### 3. Deploying as a Web App

We are done with the core code of our script. The last step is to deploy our script as a Web App, which we can call from any type of application like React Web App or a Flutter App. Add the following function to the Main.js file.

```js
// handles the get request to the server
function doPost(e) {
  try {
    // get query parameters
    var eventString = e.parameter['event_name'];
    var eventType = e.parameter['event_type'];

    // log the event
    logEvent(eventString, eventType)

    // return json success result
    return ContentService
          .createTextOutput(JSON.stringify({"result": "success"}))
          .setMimeType(ContentService.MimeType.JSON);
    }
  }
  catch (e) {
    // return json failure result
    return ContentService
          .createTextOutput(JSON.stringify({"result": "failure"}))
          .setMimeType(ContentService.MimeType.JSON);
    }
  }
}
```

The above code handles the post request made to the Google Apps Script. Based on the script execution, a JSON response is sent back which we can check in our application. Once this is done, we can deploy the script as a Web App and use the Web App URL is our application.

#### Results

Let us how well our custom serverless logging system works.

![](https://cdn-images-1.medium.com/max/800/0*zDE9T-jP3XENlP0m.gif)

Awesome! Those color codings make the logs user friendly. This kind of custom logging system can help us to debug our applications in production mode as well. We can also configure email or slack notification functionality for a particular kind of event so that our team gets notified of any irregularities in our applications. We hope you learned something new today. If you any doubts or appreciation for our team, let us know in the comments below.]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Send Slack notification when Github Actions fails]]></title>
            <link>https://www.ravgeet.in/blog/send-slack-notification-when-github-actions-fails-1ba6</link>
            <guid>https://www.ravgeet.in/blog/send-slack-notification-when-github-actions-fails-1ba6</guid>
            <pubDate>Mon, 14 Sep 2020 08:56:38 GMT</pubDate>
            <description><![CDATA[Photo by Prateek Katyal on Unsplash  We must be sure that if you and your team use Github, then you...]]></description>
            <content:encoded><![CDATA[---
title: Send Slack notification when Github Actions fails
published: true
date: 2020-09-14 08:56:38 UTC
tags: automation,githubactions,slack
canonical_url: https://www.ravsam.in/blog/send-slack-notification-when-github-actions-fails/
---

![](https://cdn-images-1.medium.com/max/1024/1*i0AIvWAJ1Xfq0qkjBLIyLQ.jpeg)<figcaption>Photo by <a href="https://unsplash.com/@prateekkatyal?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Prateek Katyal</a> on <a href="https://unsplash.com/s/photos/notification?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption>

We must be sure that if you and your team use Github, then you must using Github Actions as well. When a Github Action fails, Github automatically sends you an email regarding the event. It works only if you are working on an individual project. However, when we are working in a team, we need a better way to monitor our Github Actions. We need to know the status of our Github Actions specifically when they fail so that our development team can act upon them as quickly as possible.

We faced this issue often at our workspace. So our team decided to publish a new Github Action that can be used effectively to notify our Slack channel whenever our Github Action fails.

#### Contents

1. Get a Webhook URL
2. Use notify-slack-action
3. Results

#### 1. Get a Webhook URL

TO send notifications to our Slack channel, we need to create a Slack App. We can follow this [easy tutorial](https://www.ravsam.in/blogs/collect-form-responses-using-google-apps-script/) that includes tips on how can we create our own Slack App and get a webhook URL. Once we have a webhook URL, we need to add it to the Github Actions secrets with the name _ACTION_MONITORING_SLACK_

#### 2. Use notify-slack-action

We assume that we already have a Github Action that fails often and we need to monitor it. Add the above the following step in the Github Action workflow:

```
- name: Report Status
  if: always()
  uses: ravsamhq/notify-slack-action@master
  with:
    status: ${{ job.status }}
    notify_when: 'failure'
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }}
```

You can find and read more about the action at [Github Marketplace](https://github.com/marketplace/actions/notify-slack-action).

That’s it. This is all we need to monitor our Github Actions workflow. If we want to monitor each run of our Github Action workflow even when it succeeds, we can simply change the notify\_when parameter value to _success,failure,warnings_.

#### Results

We will fail our Github Action deliberately to test our Github Actions monitoring.

![Failed Github Actions run](https://cdn-images-1.medium.com/max/960/0*4BIEk1QG1wSNYK0g.png)<figcaption>Failure notification received in Slack</figcaption>

![Failure notification in Slack](https://cdn-images-1.medium.com/max/960/0*vGiFqa6v1Z3sGwnF.png)<figcaption>Failure notification received in Slack</figcaption>

Alright! We can see that a notification message was sent to our Slack channel stating the commit and repository it failed in. This is extremely useful when we have multiple projects using Github Actions and we want to keep a check on our Github Actions workflow.

We strongly feel that this action will increase the productivity of any team at any workspace. If you any doubts or appreciation for our team, let us know in the comments below.]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How To Track Email Opens with Google Apps Script]]></title>
            <link>https://www.ravgeet.in/blog/how-to-track-email-opens-with-google-apps-script-2lah</link>
            <guid>https://www.ravgeet.in/blog/how-to-track-email-opens-with-google-apps-script-2lah</guid>
            <pubDate>Tue, 01 Sep 2020 12:16:04 GMT</pubDate>
            <description><![CDATA[In our last blog, we talked about how can we setup Email Marketing using Google Apps Script. We...]]></description>
            <content:encoded><![CDATA[---
title: How To Track Email Opens with Google Apps Script
published: true
date: 2020-09-01 12:16:04 UTC
tags: marketing,googleappscript,automation,marketingautomation
canonical_url: https://www.ravsam.in/blog/how-to-track-email-opens-with-google-apps-script/
---

![](https://cdn-images-1.medium.com/max/1024/1*iDe_Ct5f5WQoxcPCqTi7JQ.png)

In our last blog, we talked about how can we [setup Email Marketing using Google Apps Script](http://www.ravsam.in/blog/setup-email-marketing-using-google-apps-script/). We promised you that in our next blog we will talk about how can we track whether our emails are opened by the recipients or not. This can be implemented using **Google Apps Script** and **a tracking pixel**. Tracking the email opening is important to measure the success of our email marketing campaign but we have to make sure that we maintain the privacy of the recipients in every way possible.

#### Contents

1. Adding Status Column
2. Writing Email Tracking Code
3. Deploying as Web App
4. Adding Tracking Pixel in Email
5. Results

#### Prerequisites

Before getting started, follow all the steps we discussed in [setup Email Marketing using Google Apps Script](http://www.ravsam.in/blog/setup-email-marketing-using-google-apps-script/).

#### 1. Adding Status Column

Once you have set up the Google Sheet, add a status column in the Sheet. This column will be used to track the email openings.

![Enter the user details in Google Sheet](https://cdn-images-1.medium.com/max/960/0*Oqw8hF8rMWH6Vtb3.png)<figcaption>Enter the user details in Google Sheet</figcaption>

#### 2. Writing Email Tracking code

Now is the time to add some code to the script we wrote in the previous blog.

The workflow is really simple. We will add an <img> tag in our HTML file with width="0" and height="0". We will add our script URL along with some query parameters in the src attribute. This kind of image is known as **tracking pixel**. When the recipient will open the email, a **GET** request will be sent to the URL specified in the src attribute. We will handle this GET request in our Google Apps Script and update the Google Sheet based on the query parameters.

In the **Main.gs** , add template.email = email; after template.name = name; line in the sendEmails function:

```js
function sendEmails(mail_template='content',
                    subject='Testing my Email Marketing') {

  // get the active spreadsheet and data in it
  var id = SpreadsheetApp.getActiveSpreadsheet().getId();
  var sheet = SpreadsheetApp.openById(id).getActiveSheet();
  var data = sheet.getDataRange().getValues();

  // iterate through the data, starting at index 1
  for (var i = 1; i < data.length; i++) {
    var row = data[i];
    var email = row[0];
    var name = row[1];

    // check if we can send an email
    if (MailApp.getRemainingDailyQuota() > 0) {

      // populate the template
      var template = HtmlService.createTemplateFromFile(mail_template);
      template.name = name;
      template.email = email; // add this line
      var message = template.evaluate().getContent();

      GmailApp.sendEmail(
        email, subject, '',
        {htmlBody: message, name: 'RavSam Team'}
      );
    }
  }
}
```

Let’s add code for tracking the email opening.

```js
// handles the get request to the server
function doGet(e) {
  var method = e.parameter['method'];
  switch (method) {
    case 'track':
      var email = e.parameter['email'];
      updateEmailStatus(email);
    default:
      break;
  }
}
```

The above code will handle the **GET** request. If the value of the query parameter method is **track** , then we will get the value of the query parameter email, and pass it to the updateEmailStatus function. Let’s write the code for the updateEmailStatus function.

```js
function updateEmailStatus(emailToTrack) {

  // get the active spreadsheet and data in it
  var id = SpreadsheetApp.getActiveSpreadsheet().getId();
  var sheet = SpreadsheetApp.openById(id).getActiveSheet();
  var data = sheet.getDataRange().getValues();

  // get headers
  var headers = data[0];
  var emailOpened = headers.indexOf('status') + 1;

  // declare the variable for the correct row number
  var currentRow = 2;

  // iterate through the data, starting at index 1
  for (var i = 1; i < data.length; i++) {
    var row = data[i];
    var email = row[0];

    if (emailToTrack === email) {      
      // update the value in sheet
      sheet.getRange(currentRow, emailOpened).setValue('opened');
      break;
    }
    currentRow++;
  }
}
```

The comments in the code explain it well. We just loop over the data in the Google Sheet and compare the emails with the emailToTrack variable. Once we have found the match, the status column to the corresponding email is set to  **opened**.

#### 3. Deploying as Web App

To handle the GET request, we need to deploy our script as a Web app. To deploy as a Web app, we o to _Publish_ > _Deploy as web app…_. We will set _Who has access to the app:_ to _Anyone, even anonymous_ and click  **Update**.

#### 4. Adding Tracking Pixel in Email

In the **content.html** , we will add our tracking pixel.

```html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <!-- add this img tag -->
    <img src="https://script.google.com/macros/s/AKfycbxyhzk8JpzP1S-vXp6UVAOtQzN9qKqHLaKxiHr2cZ6mLsZ7EJcG/exec?method=track&amp;email=<?= email ?>" width="0" height="0"> 

    Hi <?= name ?>. We are testing our beta features for email marketing.
  </body>
</html>
```

The <?= name ?> and <?= email ?> are called template variables and they will be populated by the sendEmails function.

#### 4. Running the script

Alright, we have done all the necessary setup to start a successful email marketing campaign that can be tracked as well. We will execute the sendEmails function and check our inbox on behalf of users.

#### Results

Now is the time to check whether we were successful in implementing tracking email openings or not. We will open the email and check whether the Google Sheet was updated or not.

Woah! We can see that the Google Sheet was automatically updated when the recipient opened the email. This is the power of Google Apps Script. It is not widely used but there are many things that can be implemented with them. If you any doubts or appreciation for our team, let us know in the comments below.]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Setup Email Marketing using Google Apps Scripts]]></title>
            <link>https://www.ravgeet.in/blog/how-to-setup-email-marketing-using-google-apps-scripts-2f1c</link>
            <guid>https://www.ravgeet.in/blog/how-to-setup-email-marketing-using-google-apps-scripts-2f1c</guid>
            <pubDate>Fri, 28 Aug 2020 12:10:42 GMT</pubDate>
            <description><![CDATA[Recently, we have been writing a lot of stuff related to Web Design and Development, like...]]></description>
            <content:encoded><![CDATA[---
title: How to Setup Email Marketing using Google Apps Scripts
published: true
date: 2020-08-28 12:10:42 UTC
tags: googleappsscript,marketing,automation,marketingstrategies
canonical_url: https://www.ravsam.in/how-to-setup-email-marketing-using-google-apps-scripts/
---

![](https://cdn-images-1.medium.com/max/1024/1*bCuGv5CYLadYp6f-AnDLpA.png)

Recently, we have been writing a lot of stuff related to Web Design and Development, like [collecting form responses](http://www.ravsam.in/blog/collect-form-responses-using-google-apps-script/), that can be implemented using **Google Apps Scripts** and Serverless Architecture. In this blog, we will talk about **email marketing**. We will set up custom email marketing purely using Google Apps Script. The advantage is we can take control of our email marketing campaign and create our own automated workflows.

#### Contents

1. Creating a new Spreadsheet
2. Creating a new Google Apps Project
3. Writing code
4. Running the script
5. Results

#### 1. Creating a new Spreadsheet

First of all, we need a Google Sheet where we store all of our email addresses to whom we want to send the emails. Let’s [create a new spreadsheet](https://docs.google.com/spreadsheets/).

![Enter the user details in Google Sheet](https://cdn-images-1.medium.com/max/960/0*3bYykmprCVl8O_CC.png)<figcaption>Enter the user details in Google Sheet</figcaption>

#### 2. Creating a new Google Apps Project

Now is the time to connect our Google sheet to a Google Apps Script. From _Tools_, we select the _Script Editor_.

![Connect Google Sheet to Google Apps Script project](https://cdn-images-1.medium.com/max/960/0*iDxWXP2ZdsWmQQqP.png)<figcaption>Connect Google Sheet to Google Apps Script project</figcaption>

#### 3. Writing code

Finally, it is time to write some code.

a.) **Main.gs**

Add the following the code to the file:

```
function sendEmails(mail_template='content',
                    subject='Testing my Email Marketing') {

  // get the active spreadsheet and data in it
  var id = SpreadsheetApp.getActiveSpreadsheet().getId();
  var sheet = SpreadsheetApp.openById(id).getActiveSheet();
  var data = sheet.getDataRange().getValues();

  // iterate through the data, starting at index 1
  for (var i = 1; i < data.length; i++) {
    var row = data[i];
    var email = row[0];
    var name = row[1];

    // check if we can send an email
    if (MailApp.getRemainingDailyQuota() > 0) {

      // populate the template
      var template = HtmlService.createTemplateFromFile(mail_template);
      template.name = name;
      var message = template.evaluate().getContent();

      GmailApp.sendEmail(
        email, subject, '',
        {htmlBody: message, name: 'RavSam Team'}
      );
    }
  }
}
```

The comments have been included in the file for a proper description of the above function.

> Always use _GmailApp.sendEmail_ instead of _MailApp.sendEmail_. It is a more stable and reliant function.

b.) **content.html**

Since the above script uses an HTML file and populates it, we need to create an HTML template file. Add the following the code to the file:

```
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    Hi <?= name ?>. We are testing our beta features for email marketing.
  </body>
</html>
```

The <?= name ?> template variable gets auto-filled by the email marketing script.

#### 4. Running the script

We have done all the necessary setup to start a successful email marketing campaign. Before we run our code, we need to grant

![Setup the Google Apps Script Authorization](https://cdn-images-1.medium.com/max/960/0*EJPqe2zcd5_0ksbG.png)<figcaption>Authorize the Google Apps Script to send an email on your behalf</figcaption>

#### Results

Let us check our email to see if the email was received. Awesome! We can clearly see that email was delivered successfully to the user’s inbox.

![Email Delivered successfully to the inbox](https://cdn-images-1.medium.com/max/960/0*fankTTBeUG80uXK2.png)<figcaption>Email Delivered successfully to the inbox</figcaption>

We can create more beautiful and custom HTML templates and manage our email marketing campaigns around them. In our next blog, we will be talking about **how to track whether a user opens our emails or not**. If you any doubts or appreciation for our team, let us know in the comments below.]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Backup Google Apps Scripts using Github Actions]]></title>
            <link>https://www.ravgeet.in/blog/backup-google-apps-scripts-using-github-actions-7l2</link>
            <guid>https://www.ravgeet.in/blog/backup-google-apps-scripts-using-github-actions-7l2</guid>
            <pubDate>Wed, 26 Aug 2020 13:51:50 GMT</pubDate>
            <description><![CDATA[Google Apps Scripts are amazing. Without setting any servers, we can do a lot of things like...]]></description>
            <content:encoded><![CDATA[---
title: Backup Google Apps Scripts using Github Actions
published: true
date: 2020-08-26 13:51:50 UTC
tags: googleappsscript,githubactions,automation
canonical_url: https://www.ravsam.in/blog/backup-google-apps-scripts-using-github-actions/
---

![](https://cdn-images-1.medium.com/max/1024/1*ZSUBHNITOFbIvtyBWaweAA.png)

Google Apps Scripts are amazing. Without setting any servers, we can do a lot of things like [collecting form responses](https://www.ravsam.in/blog/collect-form-responses-using-google-apps-script/), email marketing campaigns, etc. But as a developer, we like our code to be on Version Control System like Github. In this blog, we will discuss how can you setup Github Actions to automatically backup your Google Apps Scripts to Github.

#### Contents

1. Prerequisites
2. Installing Clasp
3. Creating a new Repository
4. Adding Important Files
5. Setting up Github Actions
6. Adding Github Actions Secrets
7. Results

#### Prerequisites

Before getting started, we assume that you have set up the following:

- A Google Apps Script Project
- A Github Account

#### 1. Installing Clasp

[Clasp](https://github.com/google/clasp) is a Google tool to develop Apps Script projects locally. It is short for **Command-Line Apps Script Projects**. Setting up Clasp is simple. Follow this [amazing guide](https://github.com/google/clasp#readme) by Google to get your login credentials, which we will be using later while setting up Github Actions.

Once you have successfully logged in, use the following command to get the content of the credentials file.

```
cat ~/.clasprc.json
```

#### 2. Creating a new Repository

Depending upon your requirement, create a new public/private repository at [Github](https://repo.new). Once you have created a new repository, add a .gitignore file with the following content:

```
.*.json
```

This prevents our credential file to be committed back to the repository.

#### 3. Adding important files

Now it’s time to add some new files to the repository.

a.) **setup.sh**

This script will prevent us from logging in again by using our already logged in credentials from Github Actions secrets, which we will be setting up later. Add the following bash code to the file:

```
#!/bin/sh

LOGIN=$(cat <<-END
    {
        "token": {
            "access_token": "$ACCESS_TOKEN",
            "refresh_token": "$REFRESH_TOKEN",
            "scope": "https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/service.management https://www.googleapis.com/auth/script.deployments https://www.googleapis.com/auth/logging.read https://www.googleapis.com/auth/script.webapp.deploy https://www.googleapis.com/auth/userinfo.profile openid https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/script.projects https://www.googleapis.com/auth/drive.metadata.readonly",
            "token_type": "Bearer",
            "id_token": "$ID_TOKEN",
            "expiry_date": 1595752666211
        },
        "oauth2ClientSettings": {
            "clientId": "$CLIENT_ID",
            "clientSecret": "$CLIENT_SECRET",
            "redirectUri": "http://localhost"
        },
        "isLocalCreds": false
    }
END
)

echo $LOGIN > ~/.clasprc.json
```

Replace the scope key value with the value in your ~/.clasprc.json.

b.) **scripts.json**

This script will contain the **id** and **name** of the Google Apps Script projects. We can add single or multiple projects here depending upon our backup strategy. Add the following JSON object to the file:

```
[
    {
        "id": "google-apps-script-project-id",
        "name": "google-apps-script-project-name"
    },
    {
        "id": "google-apps-script-project-id",
        "name": "google-apps-script-project-name"
    }
]
```

c.) **clone.sh**

This script will use the clasp’s **clone** command to download all the scripts that we provide in the scripts.json file. This script will delete all the previous projects committed in the repository. This is an important step to reflect the deleted files in the Github. Add the following bash code to the file:

```
#!/bin/sh

# remove all the pre-existing projects
rm -r -f *

content=$(cat scripts.json)
for row in $(echo "${content}" | jq -r '.[] | @base64'); do
    _jq() {
      echo ${row} | base64 --decode | jq -r ${1}
    }

    # get name and id for project
    name=$(_jq '.name')
    id=$(_jq '.id')

    # create a project directory
    mkdir $name
    cd $name

    # clone the project using the clasp
    clasp clone $id

    # come out of the directory
    cd ..
done
```

#### 4. Setting up Github Actions

Now comes the best part of the project. We will automate the whole backup process using Github Actions. We will schedule the script to run every midnight using Cron job syntax. The workflow setups the repository, installs Node, installs Clasp, runs Clasp Setup, clones the Google Apps Scripts, checks whether new changes are present, and commits them back to the repository as required with a pre-defined commit message.

```
name: Backup

on:
  schedule:
    - cron: '0 0 * * *'

jobs:
  backup:
    runs-on: ubuntu-latest

    env:
      ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
      REFRESH_TOKEN: ${{ secrets.REFRESH_TOKEN }}
      CLIENT_ID: ${{ secrets.CLIENT_ID }}
      CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }}
      ID_TOKEN: ${{ secrets.ID_TOKEN }}
      REMOTE_BRANCH: master

    steps:
      - name: Setup repository
        uses: actions/checkout@v2

      - name: Setup Node
        uses: actions/setup-node@v1
        with:
          node-version: '12'

      - name: Install Clasp
        run: npm install -g @google/clasp

      - name: Install jq
        run: |-
          sudo apt update -y
          sudo apt install jq -y

      - name: Setup Logins
        run: bash setup.sh

      - name: Clone Scripts
        run: bash clone.sh

      - name: Update Progress
        run: |
          if [$(git status --porcelain=v1 2>/dev/null | wc -l) != "0"] ; then
            git config --global user.email ${GITHUB_ACTOR}@gmail.com
            git config --global user.name ${GITHUB_ACTOR}
            git add .
            git commit -m "github-actions: took backup"
            git push --force https://${GITHUB_ACTOR}:$@github.com/${GITHUB_REPOSITORY}.git HEAD:${REMOTE_BRANCH}
          fi
```

#### 5. Adding Github Actions Secrets

We can get our Action Secrets values from the ~/.clasprc.json and add them accordingly.

![Setting up Github Actions Secrets](https://cdn-images-1.medium.com/max/960/0*K8gn1wFsSkJVK5M_.png)<figcaption>Setting up Github Actions Secrets</figcaption>

#### Results

Hurray! We can see that the Github Action workflow completed successfully at 00:00 UTC.

![Scheduled Actions workflow completed successfully](https://cdn-images-1.medium.com/max/960/0*sZYXoYztZbrfLhUg.png)<figcaption>Scheduled Actions workflow completed successfully</figcaption>

Let us check our repository to confirm that the backup was taken successfully.

![Backup is taken successfully of Google Apps Script](https://cdn-images-1.medium.com/max/960/0*Gpdx2y52fueKciwF.png)<figcaption>Backup is taken successfully of Google Apps Script</figcaption>

We can see that the Google Apps Scripts projects were committed back to our repository with a commit message _github-action: took backup_. Using this workflow, we can stay connected with both Google Apps Script and Github. If you any doubts or appreciation for our team, let us know in the comments below.]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to achieve a redesign of your website]]></title>
            <link>https://www.ravgeet.in/blog/how-to-achieve-a-redesign-of-your-website-442p</link>
            <guid>https://www.ravgeet.in/blog/how-to-achieve-a-redesign-of-your-website-442p</guid>
            <pubDate>Tue, 18 Aug 2020 14:14:54 GMT</pubDate>
            <description><![CDATA[Over the past few months, we have been thinking about redesigning our website. Since the start of...]]></description>
            <content:encoded><![CDATA[Over the past few months, we have been thinking about **redesigning our website**. Since the start of 2018, we have been using new tools, frameworks, and updated design guidelines for our customers’ websites. But our very own website was outdated, and no one from our team liked it anymore. So we decided to put some of our time in giving a fresh look and a performance boost that it deserved.

In this blog, we will discuss about technical tools and practices we used while working on this project. We used the following points as a base for redesigning our website.

## Choosing a Static Site Generator

 Previously, we have been using WordPress for our website. At the start of 2018, we learned about the [Static Site Generators](https://www.netguru.com/blog/what-are-static-site-generators). They are secure, fast, and easy to configure. The best advantage of a Static Site Generator is its speed. It pleases your SEO team and search engine as well. It gives us the option to write pretty URLs that are easy to index and understandable for both the reader and the search engine crawler. For a non-technical customer, it can be integrated easily with the [Content Management Systems](https://www.zesty.io/mindshare/marketing-technology/what-is-a-content-management-system-cms-the-complete-guide/) made especially for the Static Site Generators.

![A banner containing Jekyll logo](https://www.ravsam.in/assets/images/resized/960/jekyll.jpeg)

Jekyll is the most popular SSG for desiging blogs

For our website, we used [Jekyll](https://jekyllrb.com/), which is a Static Site Generator written in Ruby. It is the default Static Site Generator for the [Github Pages](https://pages.github.com/) as well and has immense community support.

## Setting up a Design Framework

 Setting up a design framework is always a crucial task in the website redesign. For our [website design projects](https://www.ravsam.in/services/website-design/), we love to use a stable community-managed design framework. The reason for this is that it has been developed after years of iterations and from the contributions of the designers around the world. So we settled with the [Bootstrap](https://getbootstrap.com/). It is easy to use, customize, and helps in designing websites that not just look great but also perform well on different devices and browsers.

![A banner containing Bootstrap logo](https://www.ravsam.in/assets/images/resized/960/bootstrap.png)

We created a beautiful and maintainable website with Bootstrap v4

## Git Version Control

 Git Version Control is one of the best innovations of the century. It helps the teams around the world to develop and maintain software collaboratively and iteratively. We use Github to host the code for our website. It allows our team to create issues, pull requests, and different versions of our website. In 2019, Github introduced a new feature called Github Actions. It allows us to **create automated workflows** that get triggered when a particular event happens on the git repository.

![A banner containing Github logo](https://www.ravsam.in/assets/images/resized/960/github.png)

We use Github and Github Actions for automated deployments

We use Github Actions to test the quality of our website. The workflows run periodically to make sure that our website is free of any issues. We set up the workflows that use Lighthouse CI and send the test results back to our Slack channel. On every push to the master branch, a notification is sent to the Netlify to build a new version of the website.

## Handling Deployment

 When we started our **website redesign**, there was a new tech stack in the market, [JAMstack](https://jamstack.org/). It is a new way of **designing static websites**. It offers faster performance, higher security, and better customer experience. The pages are built at the deploy time since they are static. This helps to minimize the time to the first byte. The pre-built files are delivered from the nearest CDN to the customer rather than a single server. [Netlify](https://www.netlify.com/) is one such service that offers hosting for websites that use JAMstack. It eliminates the need to set up servers, DevOps, or costly infrastructure. It provides us with the opportunity to collect form responses from our website without setting any backend service.

![A banner containing Netlify logo](https://www.ravsam.in/assets/images/resized/960/netlify.png)

Host your websites on Netlify for free

Our website is hosted on Netlify too. Whenever a push is done to the master branch on our git repository, it triggers a build script on Netlify. The purpose of the build script is to convert our Jekyll source code into a static website. Once the website is available, it minifies the assets such as CSS, JS, images, and other optimizations to give a boost to the website speed. Once the build script finishes, the generated website is deployed by Netlify to its CDN around the world. After the **website deployment**, a Slack notification is sent by the Netlify bot to indicate that the process is complete. ]]></content:encoded>
            <enclosure url="https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.ravsam.in%2Fassets%2Fimages%2Fblogs%2Fredesign-your-website-blog-banner.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Collect form responses using Google Apps Script in Jekyll website ]]></title>
            <link>https://www.ravgeet.in/blog/collect-form-responses-using-google-apps-script-in-jekyll-website-1ej5</link>
            <guid>https://www.ravgeet.in/blog/collect-form-responses-using-google-apps-script-in-jekyll-website-1ej5</guid>
            <pubDate>Sun, 02 Aug 2020 06:30:00 GMT</pubDate>
            <description><![CDATA[Collect form responses using Google Apps Script in Jekyll website      Most of the time we...]]></description>
            <content:encoded><![CDATA[---
title: Collect form responses using Google Apps Script in Jekyll website 
published: true
date: 2020-08-02 06:30:00 UTC
tags: webdev,jekyll,googleappsscript,serverless
canonical_url: https://www.ravsam.in/blog/collect-form-responses-using-google-apps-script/
---

### Collect form responses using Google Apps Script in Jekyll website 

![](https://cdn-images-1.medium.com/max/1024/1*jjVgIcyIr0COTv5TaIrFsQ.png)

Most of the time we are designing static websites. But almost all of them have some components like forms, comments, where we want to collect the user responses. Setting up a dedicated server for backend and database is a good option, but there is a cost overhead as well. Thankfully, we can set up this entire system using a serverless architecture.

In this blog, we will talk about how can we use amazing Google Apps Scripts as backend and Google Spreadsheets for data persistence to collect the form responses from our static website. This approach can help you set up forms on Github Pages, Netlify, or any other hosting provider. As a bonus, we will also add a webhook to notify our Leads team on Slack whenever a new form is filled.

### Contents

1. Creating a Google Spreadsheet
2. Creating a Slack Bot
3. Creating a Google Apps Script Project
4. Deploying a Google Apps Script Project
5. Setting up an HTML form
6. Setting up Javascript
7. Results

### 1. Creating a Google Spreadsheet

- Create a new Google Spreadsheet and name the sheet as _Sheet1_.
- Add the following fields in the top row of your spreadsheet. Make sure you name them correctly because we will be using these names in our HTML form.

![Google Spreadsheet to collect form responses](https://cdn-images-1.medium.com/max/1024/1*JZoox-ZDmXzMx5m44tg9HA.png)<figcaption>Google Spreadsheet to collect form responses</figcaption>

### 2. Creating a Slack Bot

To notify our Leads team on the Slack, we need to create a Slack bot. Setting up a Slack bot is pretty easy.

- Go to [https://api.slack.com/apps](https://api.slack.com/apps) and click Create New App.
- We will give our app a name and choose our Development Workspace from the dropdown.
- Once we have created an app, we need to turn on the Incoming Webhook feature and create a new webhook URL.
- We will create a new webhook by clicking Add New Webhook to Workspace and choose the channel we want the notifications to be posted in. Your webhook URL should look like this https://hooks.slack.com/services/T0160Uxxxxx/B0187Nxxxxx/4AZixxswHVxxxxxxxxxxxxxx. If you have access to a terminal, you can test the webhook as well by sending a POST request using cURL.

```
curl -X POST -H 'Content-type: application/json' --data '{"text":"Hello, World!"}' https://hooks.slack.com/services/T0160Uxxxxx/B0187Nxxxxx/4AZixxswHVxxxxxxxxxxxxxx
```

![](https://cdn-images-1.medium.com/max/1024/1*gCEItE1B54CxoN-yDel0Hg.png)<figcaption>Setup name of your Slack app and development workspace</figcaption>

### 3. Creating a Google Apps Script Project

Now comes the most important and interesting part of the project. Google Apps Script is written in Javascript. So even if you have basic Javascript knowledge, setting up Google Apps will be a breeze for you.

- We will create a new project at [https://script.google.com/home](https://script.google.com/home).
- We will create a new script file from _File_ > _New_ > _Script_ and name it as _Form.gs_
- Add the following code to this script file:

```
// new property service
var SCRIPT\_PROP = PropertiesService.getScriptProperties();

function doGet(e) {
  return handleResponse(e);
}

function handleResponse(e) {
  // this prevents concurrent access overwritting data
  // we want a public lock, one that locks for all invocations
  var lock = LockService.getPublicLock();
  lock.waitLock(30000); // wait 30 seconds before conceding defeat

  try {
    // next set where we write the data - you could write to multiple/alternate destinations
    var doc = SpreadsheetApp.openById(SCRIPT\_PROP.getProperty("key"));
    var sheet = doc.getSheetByName(SHEET\_NAME);

    var headRow = 1;
    var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
    var nextRow = sheet.getLastRow() + 1; // get next row
    var row = []; 

    // loop through the header columns
    for (i in headers) {
      switch (headers[i]) {
        case "timestamp":
          row.push(new Date());
          break;
        default:
          var str = e.parameter[headers[i]];
          row.push(str.trim().substring(0, CHARACTER\_LIMIT));
          break;
      }
    }

    // add data to the spreadsheet
    sheet.getRange(nextRow, 1, 1, row.length).setValues([row]);

    // send thanks email to customer
    var emailStatus = notifyCustomer(row);

    // send notification to slack
    postToSlack(row, emailStatus);

    // return json success results
    return ContentService
          .createTextOutput(JSON.stringify({"result": "success"}))
          .setMimeType(ContentService.MimeType.JSON);
  }
  catch (e) {
    // if error then log it and return response
    Logger.log(e);
    return ContentService
          .createTextOutput(JSON.stringify({"result": "error"}))
          .setMimeType(ContentService.MimeType.JSON);
  }
  finally {
    // release lock
    lock.releaseLock();
  }
}

function setup() {
    var doc = SpreadsheetApp.getActiveSpreadsheet();
    SCRIPT\_PROP.setProperty("key", doc.getId());
}
```

> _Don’t forget to run the_ **_setup_** _function. It is important to connect your project with the Google Spreadsheet and gain the right permissions._

- We will again create a new script file from _File_ > _New_ > _Script_ and name it as _Email.gs_
- In this file, we will write the code that sends an email back to the customer on our behalf.
- Add the following code to this script file:

```
function notifyCustomer(data) {
  var name = data[1];
  var message = "Hi" + name + ". Your response has been received. We will get in touch with you shortly.";

  // check if we can send an email
  if (MailApp.getRemainingDailyQuota() > 0) {
    var email = data[2];

    // send the email on our behalf
    MailApp.sendEmail({
      to: email,
      subject: "Thanks for contacting RavSam",
      body: message
    });

    return true;
  } 
}
```

- We will again create a new script file from _File_ > _New_ > _Script_ and name it as _Slack.gs_
- In this file, we will write the code that notifies our Leads team on the form submission.
- Add the following code to this script file:

```
function postToSlack(data, emailSent) {
  var name = data[1];
  var email = data[2];
  var phone = data[3];
  var service = data[4];
  var notes = data[5];

  // check if email was sent
  if (emailSent) var emailStatus = 'Email Sent';
  else var emailStatus = 'Email Not Sent';

  // create a message format
  var payload = {
    "attachments": [{
        "text": "Lead Details",
        "fallback": "New Customer Lead has been received",
        "pretext": "New Customer Lead has been received",
        "fields": [
          {
            "title": "Full Name",
            "value": name,
            "short": true
          },
          {
            "title": "Phone",
            "value": "<tel:" + phone + "|" + phone + ">",
            "short": true
          },
          {
            "title": "Service",
            "value": service,
            "short": true
          }
          {
            "title": "Email",
            "value": emailStatus + " to <mailto:" + email + "|" + email + ">",
            "short": false
          },
          {
            "title": "Notes",
            "value": notes,
            "short": false
          },
        ],
        "mrkdwn\_in": ["text", "fields"],
        "footer": "Developed by <https://www.ravsam.in|RavSam>",
    }]
  }

  // prepare the data to be sent with POST request
  var options = {
    "method" : "post",
    "contentType" : "application/json",
    "payload" : JSON.stringify(payload)
  };

  // send a post request to our webhook URL
  return UrlFetchApp.fetch(webhookUrl, options)
}
```

- Finally, we will create a script file from _File_ > _New_ > _Script_ and name it as _Variables.gs_ to store our constant variables.
- In this file, we will store our constant variables that are referenced in the project.
- Add the following code to this script file:

```
// enter sheet name where data is to be written below
var SHEET\_NAME = 'Sheet1';

// set a max character limit for each form field
var CHARACTER\_LIMIT = 1000;

// slack bot weebhook URL
var webhookUrl = 'https://hooks.slack.com/services/T0160Uxxxxx/B0187Nxxxxx/4AZixxswHVxxxxxxxxxxxxxx';
```

So our project is ready, but there is still one last thing to do. We need to deploy our project as a Web App so that we can access it through our website’s Javascript code.

### 4. Deploying a Google Apps Script Project

We are done with code and now is the deploy our project as a **Web App**.

- We will create a script file from _Publish_ > _Deploy as Web App_…
- Make sure you set the **Who has access to the app:** to _Anyone, even anonymous_. This is important so that we can make an unauthorized call to our Web App.
- Finally, deploy the web app and copy the web app’s URL. The URL looks like this [https://script.google.com/macros/s/AKfycbxSF9Y4V4qmZLxUbcaMB0Xhmjwqxxxxxxxxxxxxxxxxxxxxxxx/exec](https://script.google.com/macros/s/AKfycbxSF9Y4V4qmZLxUbcaMB0Xhmjwqxxxxxxxxxxxxxxxxxxxxxxx/exec)

![](https://cdn-images-1.medium.com/max/1024/1*YocvPTvW-xwWTsVOcbw_jg.png)<figcaption>Deploy the Google Apps Script project as a web app</figcaption>

### 5. Setting up an HTML form

On our Jekyll website, add the following Bootstrap form:

```
<form id="contact-form" class="needs-validation" role="form" novalidate>
    <div class="row">
        <div class="col-md-6">
            <div class="form-group">
                <input type="text" name="name" class="form-control" placeholder="Full Name" required>
            </div>
        </div>
        <div class="col-md-6">
            <div class="form-group">
                <input type="email" name="email" class="form-control" placeholder="Email" required>
            </div>
        </div>
        <div class="col-md-6">
            <div class="form-group">
                <input type="tel" name="phone" class="form-control" placeholder="Mobile No." required>
            </div>
        </div>
        <div class="col-md-6">
            <div class="form-group">
                <input type="text" name="service" class="form-control" placeholder="Service" required>
            </div>
        </div>
        <div class="col-12">
            <div class="form-group">
                <textarea class="form-control rounded" rows="8" name="notes" placeholder="Any Notes" required></textarea>
            </div>
        </div>
        <div class="col-12 mt-3">
            <button class="btn btn-primary" type="submit" name="submit">Submit request -&gt;</button>
        </div>
    </div>
</form>
```

> _We need to make sure that the form fields’ names are the same as headers in the Google Spreadsheet._

### 6. Setting up Javascript

Finally, we need to add some Javascript to make AJAX call to the Google Apps Script:

```
<script src="https://www.ravsam.in/assets/jquery/dist/jquery.min.js"></script>
<script src="https://www.ravsam.in/assets/popper.js/dist/umd/popper.min.js"></script>
<script src="https://www.ravsam.in/assets/bootstrap/dist/js/bootstrap.min.js"></script>
<script>
    // for validating the forms
    (function () {
        'use strict';
        window.addEventListener(
            'load', function () {
                var formObject = $('#contact-form');
                var form = formObject[0];
                if (form != undefined) {
                    form.addEventListener(
                        'submit',
                        function (event) {
                            var submitBtn = $('button[name="submit"]')[0];
                            submitBtn.disabled = true;
                            submitBtn.innerHTML = 'Submitting request...';

                            if (form.checkValidity() === false) {
                                submitBtn.disabled = false;
                                submitBtn.innerHTML = 'Submit request -&gt;';
                                event.preventDefault();
                                event.stopPropagation();
                            }
                            else {
                                var url = 'https://script.google.com/macros/s/AKfycbxSF9Y4V4qmZLxUbcaMB0Xhmjwqxxxxxxxxxxxxxxxxxxxxxxx/exec';
                                var redirectSuccessUrl = '/thanks/';
                                var redirectFailedUrl = '/failed/';
                                var xhr = $.ajax({
                                    url: url,
                                    method: 'GET',
                                    dataType: 'json',
                                    data: formObject.serialize(),
                                    success: function (data) {
                                        submitBtn.disabled = false;
                                        submitBtn.innerHTML = 'Submit request -&gt;';
                                        $(location).attr('href', redirectSuccessUrl);
                                    },
                                    error: function (data) {
                                        submitBtn.disabled = false;
                                        submitBtn.innerHTML = 'Submit request -&gt;';
                                        $(location).attr('href', redirectFailedUrl);
                                    },
                                });
                                event.preventDefault();
                                event.stopPropagation();
                            }
                            form.classList.add('was-validated');
                        },
                        false
                    );
                }
            },
            false
        );
    })();
</script>
```

If the form submission is successful, our customer will be redirected to the **Thanks** page. However, if anything goes wrong, our customer will be redirected to a **Failed**  page.

### Results

Let us fill the form on our Jekyll website. We will add all the required details and submit the form.

![](https://cdn-images-1.medium.com/max/1024/1*LshKiG1pPrlwCHMyf7xceg.png)<figcaption>Fill out the website form</figcaption>

Hurray! We have received a notification sent by our **Customer Leads**  bot.

![](https://cdn-images-1.medium.com/max/1024/1*B2l1rVFyJ8vlfXJ14D42qg.png)<figcaption>Notification received in the Slack channel</figcaption>

Let us check our Google Spreadsheet as well and see whether the form response was recorded or not. We can see in the screenshot below that the form response has been successfully stored in the spreadsheet.

![](https://cdn-images-1.medium.com/max/1024/1*fvUfAqIH2fR9oPTIhiU9LA.png)<figcaption>Form response recorded in Google Spreadsheet</figcaption>

Using this workflow, we can get in touch with our customers as soon as possible and convert the leads into happy clients. Moreover, there is no need to set up servers and databases for collecting form responses on your website. You can use the same approach to collect comments on your blog posts as well.]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to achieve a redesign of your website — RavSam]]></title>
            <link>https://www.ravgeet.in/blog/how-to-achieve-a-redesign-of-your-website-ravsam-e8</link>
            <guid>https://www.ravgeet.in/blog/how-to-achieve-a-redesign-of-your-website-ravsam-e8</guid>
            <pubDate>Fri, 24 Jul 2020 06:40:19 GMT</pubDate>
            <description><![CDATA[How to achieve a redesign of your website   Photo by Tobias Keller on Unsplash  Over the...]]></description>
            <content:encoded><![CDATA[---
title: How to achieve a redesign of your website — RavSam
published: true
date: 2020-07-24 06:40:19 UTC
tags: uidesign,websitedesignindia,jekyll,webdesign
canonical_url: https://medium.com/@ravsamhq/how-to-achieve-a-redesign-of-your-website-ravsam-feb39f504700
---

### How to achieve a redesign of your website

![](https://cdn-images-1.medium.com/max/1024/1*EL_1p7SYkMJ4FeFy0C4iXg.jpeg)<figcaption>Photo by <a href="https://unsplash.com/@tokeller?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Tobias Keller</a> on <a href="https://unsplash.com/s/photos/architecture?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption>

Over the past few months, we have been thinking about **redesigning our website**. Since the start of 2018, we have been using new tools, frameworks, and updated design guidelines for our customers’ websites. But our very own website was outdated, and no one from our team liked it anymore. So we decided to put some of our time in giving a fresh look and a performance boost that it deserved.

In this blog, we will discuss the technical tools and practices we used while working on this project. We used the following points as a base for redesigning our website.

**Contents**

1. Choosing a Static Site Generator

2. Setting up a Design Framework

3. Git Version Control

4. Handling Deployment

**1. Choosing a Static Site Generator**

Previously, we have been using WordPress for our website. At the start of 2018, we learned about the [Static Site Generators](https://www.netguru.com/blog/what-are-static-site-generators). They are secure, fast, and easy to configure. The best advantage of a Static Site Generator is its speed. It pleases your SEO team and search engine as well. It gives us the option to write pretty URLs that are easy to index and understandable for both the reader and the search engine crawler. For a non-technical customer, it can be integrated easily with the [Content Management Systems](https://www.zesty.io/mindshare/marketing-technology/what-is-a-content-management-system-cms-the-complete-guide/) made especially for the Static Site Generators.

![A banner containing Jekyll logo](https://cdn-images-1.medium.com/max/1024/1*B3eU4xOLAB8_BPDh3pExdw.jpeg)<figcaption>Jekyll is the most popular SSG for designing blogs</figcaption>

For our website, we used [Jekyll](https://jekyllrb.com/), which is a Static Site Generator written in Ruby. It is the default Static Site Generator for the [Github Pages](https://pages.github.com/) as well and has immense community support.

**2. Setting up a Design Framework**

Setting up a design framework is always a crucial task in the website redesign. For our [website design projects](https://www.ravsam.in/services/website-design/), we love to use a stable community-managed design framework. The reason for this is that it has been developed after years of iterations and from the contributions of the designers around the world. So we settled with the [Bootstrap](https://getbootstrap.com/). It is easy to use, customize, and helps in designing websites that not just look great but also perform well on different devices and browsers.

![A banner containing Bootstrap logo](https://cdn-images-1.medium.com/max/1024/1*uDSWoyDPvZeyK3GbhuAFvA.png)<figcaption>We created a beautiful and maintainable website with Bootstrap v4</figcaption>

**3. Git Version Control**

Git Version Control is one of the best innovations of the century. It helps the teams around the world to develop and maintain software collaboratively and iteratively. We use Github to host the code for our website. It allows our team to create issues, pull requests, and different versions of our website. In 2019, Github introduced a new feature called Github Actions. It allows us to **create automated workflows** that get triggered when a particular event happens on the git repository.

![A banner containing Github logo](https://cdn-images-1.medium.com/max/1024/1*BZ_jv-xjX_FfJR5fQH_6UQ.png)<figcaption>We use Github and Github Actions for automated deployments</figcaption>

We use Github Actions to test the quality of our website. The workflows run periodically to make sure that our website is free of any issues. We set up the workflows that use Lighthouse CI and send the test results back to our Slack channel. On every push to the master branch, a notification is sent to the Netlify to build a new version of the website.

**## 4. Handling Deployment**

When we started our **website redesign** , there was a new tech stack in the market, [JAMstack](https://jamstack.org/). It is a new way of **designing static websites**. It offers faster performance, higher security, and better customer experience. The pages are built at the deploy time since they are static. This helps to minimize the time to the first byte. The pre-built files are delivered from the nearest CDN to the customer rather than a single server. [Netlify](https://www.netlify.com/) is one such service that offers hosting for websites that use JAMstack. It eliminates the need to set up servers, DevOps, or costly infrastructure. It provides us with the opportunity to collect form responses from our website without setting any backend service.

![A banner containing Netlify logo](https://cdn-images-1.medium.com/max/1024/1*VYPAwlct1pXpUf-1_-Xltw.png)<figcaption>Host your websites on Netlify for free</figcaption>

Our website is hosted on Netlify too. Whenever a push is done to the master branch on our git repository, it triggers a build script on Netlify. The purpose of the build script is to convert our Jekyll source code into a static website. Once the website is available, it minifies the assets such as CSS, JS, images, and other optimizations to give a boost to the website speed. Once the build script finishes, the generated website is deployed by Netlify to its CDN around the world. After the **website deployment** , a Slack notification is sent by the Netlify bot to indicate that the process is complete.]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Dropilio - Leveraging Twilio Whatsapp API]]></title>
            <link>https://www.ravgeet.in/blog/dropilio-app-for-twilio-and-dev-hackathon-53je</link>
            <guid>https://www.ravgeet.in/blog/dropilio-app-for-twilio-and-dev-hackathon-53je</guid>
            <pubDate>Tue, 28 Apr 2020 06:30:00 GMT</pubDate>
            <description><![CDATA[What I built   Dropilio is a REST API service for sending local files as attachments with...]]></description>
            <content:encoded><![CDATA[---
title: Dropilio - Leveraging Twilio Whatsapp API
published: true
date: 2020-04-28 06:30:00 UTC
tags: twiliohackathon, php
canonical_url: https://www.ravgeet.dev/blog/dropilio-app-for-twilio-hackathon/
---

## What I built

Dropilio is a REST API service for sending local files as attachments with Twilio Whatsapp API. This leverages the use of Twilio Whatsapp API for Desktop applications such as those built in Electron, GTK, etc which intend to send notifications with file attachments.

If you are working on a Desktop application, and you want to send a Whatsapp message along with attachments using Twilio Whatsapp API, you must include a link to that attachment as a media resource. For this, your attachment must be somewhere on the Internet. Dropilio solves this problem by uploading your attachment to your Dropbox account and then gets a temporary link that can be used by the Twilio Whatsapp API.

This project belongs to the category of **Interesting Integrations** for [Twilio and Dev hackathon](https://www.twilio.com/blog/introducing-code-exchange-community-and-hackathon).

## Branding

I asked my little brother to come up with the branding for the app. I explained the functionality to him and he came up with this.

![Dropilio App branding by Ravgeet Dhillon](https://www.ravgeet.dev/assets/img/blog/dropilio-branding.png)

## Project Usage

You can browse to the [Dropilio project](https://www.ravgeet.dev/projects/dropilio/) on my website for complete information regarding the project.

## Link to Code

You can always get the development code for the app at [https://github.com/ravgeetdhillon/dropilio](https://github.com/ravgeetdhillon/dropilio).

## How I built it

While interning as a Full Stack Developer at [Techies Infotech](https://techiesinfotech.co.in), I was granted a project to implement this sort of functionality in the native Desktop apps. So I decided to go with the Twilio API. While reading on [Dev](https://dev.to/godspowercuche/announcing-the-twilio-hackathon-on-dev-1d06-temp-slug-8558819), I came to me as a surprise that Twilio and DEV were organizing a hackathon and so, I decided to submit this project for the hackathon after taking approval from my employer.

I used Twilio SDK for PHP and PHP wrapper for Dropboxv2 API by Kunal Verma. I tested the service using Postman and made it live on Heroku.

The most important thing that I learned during the development of this project was security. I learned about how to set up a good authentication for a REST API service. I ended up developing a simple yet secure way to authenticate the requests sent to the API endpoint.

## Additional Resources

- PHP SDK for Dropbox v2 API. - [https://github.com/kunalvarma05/dropbox-php-sdk](https://github.com/kunalvarma05/dropbox-php-sdk)
- PHP SDK for Twilio API. - [https://www.twilio.com/docs/libraries/php](https://www.twilio.com/docs/libraries/php)

Lemme know if you have any doubt, appreciation or anything else that you would like to communicate to me. You can tweet me [@ravgeetdhillon](https://twitter.com/intent/tweet?screen_name=ravgeetdhillon&amp;original_referer=https://www.ravgeet.dev/blog/dropilio-app-for-twilio-hackathon/&amp;ref_src=twsrc%5Etfw). I reply to all the questions as quickly as possible. 😄 And if you liked this post, please [share](https://twitter.com/intent/tweet?text=Check%20out%20this%20amazing%20blog%20post%20by%20Ravgeet%20Dhillon%20sharing%20his%20thoughts%20on%20Dropilio%20App%20for%20Twilio%20and%20Dev%20Hackathon&amp;screen_name=ravgeetdhillon&amp;original_referer=https://www.ravgeet.dev/blog/dropilio-app-for-twilio-hackathon/&amp;ref_src=twsrc%5Etfw) it with your twitter community as well.]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Celebrating GNOME Newcomers’ contributions]]></title>
            <link>https://www.ravgeet.in/blog/celebrating-gnome-newcomers-contributions-2bg8</link>
            <guid>https://www.ravgeet.in/blog/celebrating-gnome-newcomers-contributions-2bg8</guid>
            <pubDate>Thu, 02 Jan 2020 13:00:00 GMT</pubDate>
            <description><![CDATA[A few weeks ago, I sat down to solve some issues related to the GNOME Engagement team. While going th...]]></description>
            <content:encoded><![CDATA[---
title: Celebrating GNOME Newcomers’ contributions
published: true
date: 2020-01-02 13:00:00 UTC
tags: gnome, gnome-newcomers
canonical_url: https://www.ravgeet.dev/blog/celebrating-newcomers-at-gnome/
---

A few weeks ago, I sat down to solve some issues related to the GNOME Engagement team. While going through the list, I found [this issue](https://gitlab.gnome.org/Teams/Engagement/General/issues/8) created by Umang Jain, which looked forward to celebrating the contributions made by GNOME Newcomers. It was opened in late 2017 and a lot of discussions happened during this period. So, I decided to take on this issue and solve it programmatically.

## Problem

There is no doubt that newcomers work hard to make their first contribution to a project they do not know about. So, it’s really important to recognize and celebrate their contributions when they make one.

With GNOME being a large project, there is a need for an automated system which recognizes the contributions made by the newcomers and help the GNOME Engagement team to seamlessly identify them.

The following points were listed on the issue which we need to solve, but I will only consider solving the relevant ones.

- Come up with an easy way for maintainers to indicate when a newcomer has made their first contribution
- Create/decide twitter account and a person responsible for handling that
- Create a way for the Engagement team to broadcast these achievements regularly on social media (e.g. monthly shout-out?)
- Announce the new plan to key stakeholders (maintainers), and the larger GNOME community

## Approach

Many GNOME people proposed there views and workarounds to tackle this problem. Taking the best cues out of each suggestion, I decided to use the Gitlab API. Gitlab API has all the features which can help us to take on this problem effectively.

Using Gitlab API, a list of all the users(with their first ten contributions) present on [GNOME Gitlab Instance](https://gitlab.gnome.org/), is fetched. Along with this, a list of projects is also fetched using Gitlab API. The list of users is traversed which divides the users into **Newcomers** and **Regular contributors**. This is achieved by checking when the user first contributed to a GNOME project. If the contribution was made in the last 15 days, then the contributor is categorized as a **Newcomer**. After the newcomers are identified, they are filtered based on the type of contribution made. Currently, notable contributions are related to merge requests and issues.

After going through the above procedure, a detailed report is created as a JSON file. This JSON file can be found [here](https://gitlab.gnome.org/ravgeetdhillon/newcomers-shoutout/blob/master/src/data/contributions.json).

## Scheduling scan

The above process is scheduled to run once a day using Gitlab CI. It takes about 5 hours to complete. Once the scan is completed, the result of this whole process is pushed back to the project repository for future use.

## Resources

You can find out the project [here](https://gitlab.gnome.org/ravgeetdhillon/newcomers-shoutout). You can also [open issues](https://gitlab.gnome.org/ravgeetdhillon/newcomers-shoutout/issues) and [merge requests](https://gitlab.gnome.org/ravgeetdhillon/newcomers-shoutoout/merge_requests) to make the project better.

Lemme know if you have any doubt, appreciation or anything else that you would like to communicate to me. You can tweet me [@ravgeetdhillon](https://twitter.com/intent/tweet?screen_name=ravgeetdhillon&amp;original_referer=https://www.ravgeet.dev/blog/celebrating-newcomers-at-gnome/&amp;ref_src=twsrc%5Etfw). I reply to all the questions as quickly as possible. 😄 And if you liked this post, please [share](https://twitter.com/intent/tweet?text=Check%20out%20this%20amazing%20blog%20post%20by%20Ravgeet%20Dhillon%20sharing%20his%20thoughts%20on%20Celebrating%20GNOME%20Newcomers'%20contributions&amp;screen_name=ravgeetdhillon&amp;original_referer=https://www.ravgeet.dev/blog/celebrating-newcomers-at-gnome/&amp;ref_src=twsrc%5Etfw) it with your twitter community as well.]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Developing Leaderboard for GNOME Hackers]]></title>
            <link>https://www.ravgeet.in/blog/developing-leaderboard-for-gnome-hackers-2e90</link>
            <guid>https://www.ravgeet.in/blog/developing-leaderboard-for-gnome-hackers-2e90</guid>
            <pubDate>Mon, 09 Dec 2019 15:30:00 GMT</pubDate>
            <description><![CDATA[After completing my Google Summer of Code assignment, I had an idea in my mind for a project where...]]></description>
            <content:encoded><![CDATA[---
title: Developing Leaderboard for GNOME Hackers
published: true
date: 2019-12-09 15:30:00 UTC
tags: gnome, leaderboard, python
canonical_url: https://www.ravgeet.dev/blog/developing-leaderboard-for-gnome/
---

After completing my [Google Summer of Code assignment](https://www.ravgeet.dev/blog/final-report-gsoc-2019/), I had an idea in my mind for a project where the hard-working people on GNOME, known as GNOME Hackers, could be appreciated based on the amount of work they do for the FLOSS community. In the quest for the same, I wrote a leaderboard web app, [GNOME Hackers](https://gnome-hackers.netlify.com/). It was an awesome experience and I utilized my weekends very well by learning many new things. I will give a brief of them below.

## Gitlab API

All of the GNOME groups and projects are hosted on the [Gitlab instance of GNOME](http://gitlab.gnome.org/). The most typical activities that happen on Gitlab are **commits** , **issues** and **merge requests**. These form the basis for scoring that builds up the leaderboard. The data is fetched from the [Gitlab Instance of GNOME](https://gitlab.gnome.org/) using the [Python wrapper for Gitlab API](https://github.com/python-gitlab/python-gitlab/).

## Static Website

![Landing page for GNOME Hackers :c-shadow](https://www.ravgeet.dev/assets/img/blog/gnome-hackers-main.jpg)

To create a static website, I could have used any Static Site Generator such as Jekyll. But this website required some logic such as scoring, selecting top hackers, giving them awards, etc., so I settled for Python. I used [Frozen Flask](https://pythonhosted.org/Frozen-Flask/) to freeze the website into a static website which could then be hosted on Netlify. This great library reduced the codebase and gave me the power to build the website based on [JAMstack](https://jamstack.org/).

## Scoring

For allocating points and building up the leaderboard, the script uses the following scheme, If you feel that a rule is biased against the others, you can open an [issue](https://github.com/ravgeetdhillon/gnome-hackers/issues) and we will have a conservation regarding the same.

| Event | Points |
| --- | --- |
| Each line of commit | 0.01 |
| Opened Merge Request | 5 |
| Closed Merge Request | 10 |
| Opened Issue | 1 |
| Closed Issue | 2 |

## Awards

The script gives you awards for staying on the leaderboard. You can get four types of awards:

- Gold
- Silver
- Bronze
- Top 10

For each day spent on the leaderboard, the hacker gets a **+1** for an award, which he/she is eligible for.

## GitHub Actions

Since I have a GitHub Pro pack, I get free 3000 build mins for GitHub Actions, which is an effective tool to automate the tasks. The [workflow](https://github.com/ravgeetdhillon/gnome-hackers/actions) is simple and clearly explained by the graphic below.

![Workflow for GNOME Hackers :c-shadow](https://www.ravgeet.dev/assets/img/blog/gnome-hackers-workflow.jpg)

The website builds every day at 00:00 UTC. After the workflow is executed successfully, the website build is pushed to the [`website`](https://github.com/ravgeetdhillon/gnome-hackers/tree/website) branch, which triggers a deploy script on the [Netlify](https://app.netlify.com/sites/gnome-hackers/deploys) and publishes the website accordingly.

## Personal Page

![Personal Profile page for GNOME Hackers :c-shadow](https://www.ravgeet.dev/assets/img/blog/gnome-hackers-personal-profile.jpg)

## Links

- GNOME Hackers: [https://gnome-hackers.netlify.com/](https://gnome-hackers.netlify.com/)
- GitHub Repository: [https://github.com/ravgeetdhillon/gnome-hackers](https://github.com/ravgeetdhillon/gnome-hackers)

## What’s next

If you liked by work, you can appreciate the same by [buying me a cup of coffee](https://www.buymeacoffee.com/ravgeetdhillon). Also, I was given GNOME Membership, while I was working on this project. It made me feel so happy and I want to thank [GNOME](https://gnome.org/) for all the support. For this website, I am looking forward to new ideas that I can implement on the website to make it even more interesting. If you liked my project, I would love you to [star it](https://github.com/ravgeetdhillon/gnome-hackers) as well. These little things encourage me to work further.

I am looking for an **internship** where I can implement my skills and learn the new ones as well. If you guys have any such oppurtunity for me, you can reach me through my [email](mailto:ravgeetdhillon@gmail.com).

Lemme know if you have any doubt, appreciation or anything else that you would like to communicate to me. You can tweet me [@ravgeetdhillon](https://twitter.com/intent/tweet?screen_name=ravgeetdhillon&amp;original_referer=https://www.ravgeet.dev/blog/developing-leaderboard-for-gnome/&amp;ref_src=twsrc%5Etfw). I reply to all the questions as quickly as possible. 😄 And if you liked this post, please [share](https://twitter.com/intent/tweet?text=Check%20out%20this%20amazing%20blog%20post%20by%20Ravgeet%20Dhillon%20sharing%20his%20thoughts%20on%20Developing%20Leaderboard%20for%20GNOME%20Hackers&amp;screen_name=ravgeetdhillon&amp;original_referer=https://www.ravgeet.dev/blog/developing-leaderboard-for-gnome/&amp;ref_src=twsrc%5Etfw) it with your twitter community as well.]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Fantasy Premier League with AI - First 10 Gameweeks Review]]></title>
            <link>https://www.ravgeet.in/blog/fantasy-premier-league-with-ai-first-10-gameweeks-review-2aih</link>
            <guid>https://www.ravgeet.in/blog/fantasy-premier-league-with-ai-first-10-gameweeks-review-2aih</guid>
            <pubDate>Mon, 28 Oct 2019 11:30:00 GMT</pubDate>
            <description><![CDATA[What happens when you combine your love for football and programming? I am a huge fan of the English...]]></description>
            <content:encoded><![CDATA[---
title: Fantasy Premier League with AI - First 10 Gameweeks Review
published: true
date: 2019-10-28 11:30:00 UTC
tags: datascience, ai, football
canonical_url: https://www.ravgeet.in/blog/fantasy-premier-league-with-data-science-and-ai-first-ten-gameweeks-review/
---

What happens when you combine your love for football and programming? I am a huge fan of the English Premier League and its fantasy league game which allows players to play as managers by creating their team and earn points based on the performance of their selected players in the real field.

Fantasy Premier League is the most famous fantasy game in the world with over 7 million players(that’s more than New Zealand’s population), known as managers, trying their managing skills and getting as many points as they can over 38 game weeks spanned over 10 months of football.

Now you might be asking what’s difficult about it. Just select the best players in the league and let ‘em do the rest. Things aren’t as easy as they seem. It’s a complex game of certain sets of rules that are to be followed to create a team, then there is Champions League, Europa League, International Breaks and the most dreaded one, **Pep’s Rotation**. So to be a successful manager you have to make good decisions, considering all the above factors and also be able to foresee the future and create something that will give you a chance to leap in front of others.

The big question now is what can we manage a team using Data Science and Artificial Intelligence. Certain previous managing experience says **yes**. The problem with managers is, while creating a team, we indulge in favoritism and sheep herding. What this means is we intend to choose players from our favorite teams or players who only have hype. Such players usually do not add any value to the squad.

But in data, we can trust. Hence what we do is get the data, process it and find meaningful information from it and see whether it works or not. After all, it is a game of chance and a problem is that a machine doesn’t show patience, this AI would work in collaboration with the manager to get the best results.

## Week by Week Report

### Gameweek 1

![Ravgeet Dhillon's Fantasy AI Gamweek 1 Report](https://www.ravgeet.in/assets/img/blog/ravgeet-dhillon-fpl-ai-gw1.png)

At the very start of the season, I had to get a team that could do well until the first international break. So I had to use the previous season’s data for the AI and it gave the following team. I suffered a huge blow as Allison was injured in the very first half and the same happened with Holebas as well. So this cost me at least 6 points. Other than that I guess it did pretty well, in fact, better than my expectations.

### Gameweek 2

![Ravgeet Dhillon's Fantasy AI Gamweek 2 Report](https://www.ravgeet.in/assets/img/blog/ravgeet-dhillon-fpl-ai-gw2.png)

I didn’t do any transfers here as I didn’t have any internet on the final day of the transfer deadline. And just look at the results. Disappointing! But there was a bright side as well. I got two free transfers for the next game week and so the next game week was going to be the good first test for my AI.

### Gameweek 3

![Ravgeet Dhillon's Fantasy AI Gamweek 3 Report](https://www.ravgeet.in/assets/img/blog/ravgeet-dhillon-fpl-ai-gw3.png)

Look at that beauty. I had 2 free transfers, so my AI suggested me to do two replacements, Rui Patricio in for Allison as a Goalkeeper and Teemu Pukki in for Raul Jimenez as a Forward. It was just a bad captaincy choice that cost me some points, otherwise, it was a good week for the business.

### Gameweek 4

![Ravgeet Dhillon's Fantasy AI Gamweek 4 Report](https://www.ravgeet.in/assets/img/blog/ravgeet-dhillon-fpl-ai-gw4.png)

At this point just one week before the international break, I thought of using a **Wildcard** and assembled a whole new squad. My AI did a great job but it was again my fault of handing Teemu Pukki the armband and dropped points for a bad captaincy pick.

### Gameweek 5

![Ravgeet Dhillon's Fantasy AI Gamweek 5 Report](https://www.ravgeet.in/assets/img/blog/ravgeet-dhillon-fpl-ai-gw5.png)

During the international break, I did some changes to my AI gave it a pinch more of thinking prowess. If you are interested in the technicalities of my AI, you can find a detailed blog post [here](#). I took 2 hits because I know the decisions made by AI were reasonable and I could easily recover my lost points. Just another average gameweek.

### Gameweek 6

![Ravgeet Dhillon's Fantasy AI Gamweek 6 Report](https://www.ravgeet.in/assets/img/blog/ravgeet-dhillon-fpl-ai-gw6.png)

This was the gameweek I have been waiting for. I was among 10% of managers. Just look at the bench. 19 points! Sterling didn’t play, so he was automatically substituted by Mark Nobel and gave me crucial 5 points. I think I could have made 100 points in this gameweek. My AI gave me the best set of players but I let it down by choosing a wrong captain and having points worthy players on the bench.

### Gameweek 7

![Ravgeet Dhillon's Fantasy AI Gamweek 7 Report](https://www.ravgeet.in/assets/img/blog/ravgeet-dhillon-fpl-ai-gw7.png)

My AI was asking me to remove Ashley Barnes as he was only giving me playing points. But I decided to show patience for a week as he had easy fixtures ahead. This gave me two free transfers for the next week. Otherwise, it was a good week.

### Gameweek 8

![Ravgeet Dhillon's Fantasy AI Gamweek 8 Report](https://www.ravgeet.in/assets/img/blog/ravgeet-dhillon-fpl-ai-gw8.png)

Finally, my patience ran out with Ashley Barnes, I decided to use my two free transfers and my AI suggested to bring in two Chelsea players, Tammy Abraham and Fikayo Tomori. And look at that 8 points from Abraham. That’s my AI. Intelligent!

### Gameweek 9

![Ravgeet Dhillon's Fantasy AI Gamweek 9 Report](https://www.ravgeet.in/assets/img/blog/ravgeet-dhillon-fpl-ai-gw9.png)

So this was a gameweek after the international break and I had one free transfer and asked my AI whether I should go for it. My AI referred me to sell Raheem Sterling and get in Sadio Mane. Being a Liverpool fan, I was really disappointed that we couldn’t beat Manchester United and Mane was lackluster. But what about other players? Ask any FPL manager in the world, it was the poorest gameweek in the last few. Only 18 goals scored across 10 matches. The average points reflect the pain.

### Gameweek 10

![Ravgeet Dhillon's Fantasy AI Gamweek 10 Report](https://www.ravgeet.dev/assets/img/blog/ravgeet-dhillon-fpl-ai-gw10.png)

For this one, my AI suggested me to bring in Rui Patricio in place of Tom Heaton as Aston Villa has difficult fixtures ahead. But I wish I had some Leicester City players in my squad as they ran riot against Southampton, scoring 9 goals and kept a clean sheet as well. It was a bad week at the back. Pukki party is over now and keeping Sergio Aguero is too risky due to Pep’s Rotation.

## Summary

![Ravgeet Dhillon's Fantasy AI Progress for first 10 game weeks](https://www.ravgeet.dev/assets/img/blog/ravgeet-dhillon-fantasy-ai-progress-first-10-gameweeks.png)

This above graph again shows that humans are inferior to the machine. My AI gave me the right choices but I couldn’t decide on captains and bench players. The `BEST` line shows what would have been my progress if I had made the right decisions. Well, on one side I am happy as well because my AI is something that I wrote and it’s doing well. The thing is the big buck players haven’t really fired in the last few game weeks. So should the formation go from 3-4-3 to 3-5-2 or 4-4-2? Well, that’s a big question.

## Lessons Learnt

**Patience** is an important thing in this game. **Trust the AI** , it’s guiding you towards the right path. **Data** is the secret. During the first phase, we had less data, so the suggestions were approximate. But the graph is an encouragement, as we are nearing the top-ranked manager.

## What’s next

I would be doing another blog post regarding my progress on Christmas but still, I would really love to have suggestions from you guys. Should I do blog posts more often or should I write for each game week? Also, if you guys are interested in knowing the technicalities of the AI, just mention me on twitter.

Lemme know if you have any doubt, appreciation or anything else that you would like to communicate to me. You can tweet me [@ravgeetdhillon](https://twitter.com/intent/tweet?screen_name=ravgeetdhillon&amp;original_referer=https://www.ravgeet.dev/blog/fantasy-premier-league-with-data-science-and-ai-first-ten-gameweeks-review/&amp;ref_src=twsrc%5Etfw). I reply to all the questions as quickly as possible. 😄 And if you liked this post, please [share](https://twitter.com/intent/tweet?text=Check%20out%20this%20amazing%20blog%20post%20by%20Ravgeet%20Dhillon%20sharing%20his%20thoughts%20on%20Fantasy%20Premier%20League%20with%20AI%20-%20First%2010%20Gameweeks%20Review&amp;screen_name=ravgeetdhillon&amp;original_referer=https://www.ravgeet.dev/blog/fantasy-premier-league-with-data-science-and-ai-first-ten-gameweeks-review/&amp;ref_src=twsrc%5Etfw) it with your twitter community as well.]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Final Report for Google Summer of Code 2019]]></title>
            <link>https://www.ravgeet.in/blog/final-report-for-google-summer-of-code-2019-36h5</link>
            <guid>https://www.ravgeet.in/blog/final-report-for-google-summer-of-code-2019-36h5</guid>
            <pubDate>Mon, 26 Aug 2019 00:30:00 GMT</pubDate>
            <description><![CDATA[Project   Rework the GTK Website.           Description   The ultimate goal of my project...]]></description>
            <content:encoded><![CDATA[---
title: Final Report for Google Summer of Code 2019
published: true
date: 2019-08-26 00:30:00 UTC
tags: gnome, gsoc, gtk
canonical_url: https://www.ravgeet.dev/blog/final-report-gsoc-2019/
---

## Project

Rework the GTK Website.

## Description

The ultimate goal of my project was to redesign and redevelop the GTK’s official website [https://gtk.org](https://gtk.org) by providing it a design that follows current trends and content updation that really matters to the users and developers by using modern static site generators. This website uses Gitlab CI for deployment purposes. The project is a major milestone belonging to the release of GTK 4.0.

## Project Breakdown

Create a content driven website for developers with their skills ranging from beginner to expert by establishing a Static Site Generator.

Provide the up-to-date data regarding GTK. Focus on elegance and simplicity and at the same time have a great UI/UX.

Make the content updation process so simple that even a novice can point out mistakes and solve them on his/her own by sending the pull requests.

Establish this by using modern Static Site Generator such as Jekyll.

Optimize the website for search engines.

## Tasks Completed

Redesigned the website using Bootstrap.

Implemented Jekyll as a Static Site Generator.

Implemented Gitlab APIs and GTK Blog Feed for Community page.

Implemented Gitlab CI for deployment purposes.

Completed the entire documentation for the website.

## Tasks Left

Optimize the website for SEO.

Solve all the remaining bugs that may arise in future.

Merge the code to the parent repository.

> The website on [https://ravgeetdhillon.pages.gitlab.gnome.org/gtk-web/](https://ravgeetdhillon.pages.gitlab.gnome.org/gtk-web/) will be shifted to [https://gtk.org](https://gtk.org) when the code will be merged and automatically replace the existing one.

## Others

- [Contribution within GSoC 2019 from 2019-05-06 to 2019-08-25](https://github.com/ravgeetdhillon/gtk-web/commits/master?author=ravgeetdhillon&amp;since=2019-05-06&amp;until=2019-08-25)
- [Project Link: Rework the GTK Website](https://summerofcode.withgoogle.com/projects/#6195706342146048)

Lemme know if you have any doubt, appreciation or anything else that you would like to communicate to me. You can tweet me [@ravgeetdhillon](https://twitter.com/intent/tweet?screen_name=ravgeetdhillon&amp;original_referer=https://www.ravgeet.dev/blog/final-report-gsoc-2019/&amp;ref_src=twsrc%5Etfw). I reply to all the questions as quickly as possible. 😄 And if you liked this post, please [share](https://twitter.com/intent/tweet?text=Check%20out%20this%20amazing%20blog%20post%20by%20Ravgeet%20Dhillon%20sharing%20his%20thoughts%20on%20Final%20Report%20for%20Google%20Summer%20of%20Code%202019&amp;screen_name=ravgeetdhillon&amp;original_referer=https://www.ravgeet.dev/blog/final-report-gsoc-2019/&amp;ref_src=twsrc%5Etfw) it with your twitter community as well.]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Adding pages to Jekyll site]]></title>
            <link>https://www.ravgeet.in/blog/adding-pages-to-jekyll-site-4e6g</link>
            <guid>https://www.ravgeet.in/blog/adding-pages-to-jekyll-site-4e6g</guid>
            <pubDate>Fri, 19 Jul 2019 08:30:00 GMT</pubDate>
            <description><![CDATA[This is tutorial can be used to add pages to any Jekyll site. I am assuming that you have setup your...]]></description>
            <content:encoded><![CDATA[---
title: Adding pages to Jekyll site
published: true
date: 2019-07-19 08:30:00 UTC
tags: jekyll, web-dev
canonical_url: https://www.ravgeet.dev/blog/adding-pages-to-jekyll-site/
---

This is tutorial can be used to add pages to any Jekyll site. I am assuming that you have setup your Ruby Development Environment. If not, then refer to this document here to get started easily.

Below are few easy steps that you can follow to add pages to your Jekyll site. I have also attached images to explain the process better.

_Note: I have taken the example of [GTK Website](https://ravgeetdhillon.pages.gitlab.gnome.org/gtk-web/), I’m working on. You can find it’s Gitlab instance [here](https://gitlab.gnome.org/ravgeetdhillon/gtk-web)._

### Step 1.

In `collections/_docs` directory, create a new file with name: `hello-world.md`.

![Creating a new Markdown file.](https://www.ravgeet.dev/assets/img/blog/adding-pages-to-jekyll-site-screen-1.png)

### Step 2.

I want this page to be available at the following link: [http://localhost:4000/docs/tutorials/hello-world/](http://localhost:4000/docs/tutorials/hello-world/)

Add the following front matter to the `hello-world.md`.

```
---
permalink: /docs/tutorials/:name/
---

```

![Adding the front matter to the Markdown file.](https://www.ravgeet.dev/assets/img/blog/adding-pages-to-jekyll-site-screen-2.png)

### Step 3.

Add your content in the Markdown format to the `hello-world.md.

```
This is the demo file to show the process of adding new pages to a Jekyll site.

```

![Adding the new content to the Markdown file.](https://www.ravgeet.dev/assets/img/blog/adding-pages-to-jekyll-site-screen-3.png)

### Step 4.

In `_data` directory, open the `navigation.yml` file and update the sidebar\_links array by adding the following content:

```
- title: Hello World
  name: hello-world
  section: Tutorials

```

here,

- `title` is display text on sidebar on `docs` page
- `name` is the name of the file which should be pointed to when the link is accessed
- `section` is category this page should fall under

![Updating YML file in the Jekyll Project.](https://www.ravgeet.dev/assets/img/blog/adding-pages-to-jekyll-site-screen-4.png)

### Step 5.

In case the new file is the main section page, then update the sidebar\_sections array by adding the following content:

```
- title: Tutorials
  name: tutorials

```

here,

- `title` is display text on sidebar on `docs` page
- `name` is the name of the file which should be pointed to when the link is accessed

![Updating YML file in the Jekyll Project.](https://www.ravgeet.dev/assets/img/blog/adding-pages-to-jekyll-site-screen-5.png)

### Step 6.

Save all the files and serve the website on the local server by running the following command.

```
bundle exec jekyll serve

```

![Compiling the Jekyll website.](https://www.ravgeet.dev/assets/img/blog/adding-pages-to-jekyll-site-screen-6.png)

### Step 7.

Go to [http://localhost:4000/docs/tutorials/hello-world/](http://localhost:4000/docs/tutorials/hello-world/) and the page is up and running.

![Fetching the Jekyll website on the localhost.](https://www.ravgeet.dev/assets/img/blog/adding-pages-to-jekyll-site-screen-7.png)

![Fetching the Jekyll website on the localhost.](https://www.ravgeet.dev/assets/img/blog/adding-pages-to-jekyll-site-screen-8.png)

Lemme know if you have any doubt, appreciation or anything else that you would like to communicate to me. You can tweet me [@ravgeetdhillon](https://twitter.com/intent/tweet?screen_name=ravgeetdhillon&amp;original_referer=https://www.ravgeet.dev/blog/adding-pages-to-jekyll-site/&amp;ref_src=twsrc%5Etfw). I reply to all the questions as quickly as possible. 😄 And if you liked this post, please [share](https://twitter.com/intent/tweet?text=Check%20out%20this%20amazing%20blog%20post%20by%20Ravgeet%20Dhillon%20sharing%20his%20thoughts%20on%20Adding%20pages%20to%20Jekyll%20site&amp;screen_name=ravgeetdhillon&amp;original_referer=https://www.ravgeet.dev/blog/adding-pages-to-jekyll-site/&amp;ref_src=twsrc%5Etfw) it with your twitter community as well.]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[First Two Weeks at Google Summer of Code 2019]]></title>
            <link>https://www.ravgeet.in/blog/first-two-weeks-at-google-summer-of-code-2019-240f</link>
            <guid>https://www.ravgeet.in/blog/first-two-weeks-at-google-summer-of-code-2019-240f</guid>
            <pubDate>Mon, 10 Jun 2019 06:00:00 GMT</pubDate>
            <description><![CDATA[Two weeks ago, I wasn’t sure about the technology that was to be used in this project. I was...]]></description>
            <content:encoded><![CDATA[---
title: First Two Weeks at Google Summer of Code 2019
published: true
date: 2019-06-10 06:00:00 UTC
tags: gnome, gsoc, gtk
canonical_url: https://www.ravgeet.dev/blog/first-two-weeks-at-gsoc-2019/
---

Two weeks ago, I wasn’t sure about the technology that was to be used in this project. I was completely unfamiliar with some of the tools that were to be used in this project. But I backed myself and was able to pull off the things.

Things have gone pretty good so far. I have learned a couple of new things that are going to form an important part of this project.

1. Liquid    
  - Liquid forms the basis for this website. All the conditionals and other logics are implemented with the mighty help of Liquid.
2. Pipeplines    
  - Because the website needs Continuous Integration and Deployment, Gitlab CI is a perfect tool for the same. Building efficient pipelines is going to be an important task for this website.

The landing page is the centerstage for this website and will provide routes to various other resources. I am working on some new sections and may remove/alter some of the existing ones. I looking for someone to draw some artworks/illustrations that I need on this website. If you can help with this thing, please file an issue and we will have a healthy conversation. A [wiki](https://wiki.gnome.org/Projects/GTK/WebsiteRedesign) has also been made. All the important information about the project is present here. I have forked the original project for the GTK website into my [workspace](https://gitlab.gnome.org/ravgeetdhillon/gtk-web). The website is deployed using Gitlab CI for now and can be surfed [here](https://ravgeetdhillon.pages.gitlab.gnome.org/gtk-web/).

If you have any suggestions or find an issue, please report it [here](https://gitlab.gnome.org/ravgeetdhillon/gtk-web/issues).

Lemme know if you have any doubt, appreciation or anything else that you would like to communicate to me. You can tweet me [@ravgeetdhillon](https://twitter.com/intent/tweet?screen_name=ravgeetdhillon&amp;original_referer=https://www.ravgeet.dev/blog/first-two-weeks-at-gsoc-2019/&amp;ref_src=twsrc%5Etfw). I reply to all the questions as quickly as possible. 😄 And if you liked this post, please [share](https://twitter.com/intent/tweet?text=Check%20out%20this%20amazing%20blog%20post%20by%20Ravgeet%20Dhillon%20sharing%20his%20thoughts%20on%20First%20Two%20Weeks%20at%20Google%20Summer%20of%20Code%202019&amp;screen_name=ravgeetdhillon&amp;original_referer=https://www.ravgeet.dev/blog/first-two-weeks-at-gsoc-2019/&amp;ref_src=twsrc%5Etfw) it with your twitter community as well.]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Getting Selected for Google Summer of Code 2019]]></title>
            <link>https://www.ravgeet.in/blog/getting-selected-for-google-summer-of-code-2019-a06</link>
            <guid>https://www.ravgeet.in/blog/getting-selected-for-google-summer-of-code-2019-a06</guid>
            <pubDate>Mon, 06 May 2019 06:00:00 GMT</pubDate>
            <description><![CDATA[Today is a very special day for me. In my very first try, I cracked the Google Summer of Code. I am...]]></description>
            <content:encoded><![CDATA[---
title: Getting Selected for Google Summer of Code 2019
published: true
date: 2019-05-06 06:00:00 UTC
tags: gsoc, gnome, gtk
canonical_url: https://www.ravgeet.dev/blog/getting-selected-for-gsoc/
---

Today is a very special day for me. In my very first try, I cracked the Google Summer of Code. I am very delighted to have been given an opportunity to work for the GNOME Foundation.

My task is to rebuild the [GTK website](https://gtk.org). For those interested in technicalities of the project, the current website is made in PHP which is a great web language, however not so useful for creating static websites. So my job is to build a new website from scratch which uses the concept of Content Management System. I will be using Jekyll for this purpose and the website would be deployed using Gitlab’s Continuous Integration.

It’s going to be a great challenging summer but I am really happy to be handed over this opportunity. This summer is going to enhance my knowledge about Web Designing and it’s future.

A huge thanks to all those people from GNOME, who selected me for this job. **Emmanuele Bassi** will be my mentor for this summer and I am very happy as I will learn a lot of new things from this man.

For future GSoCers, here is my [proposal](https://docs.google.com/document/d/1naeFyYH0dLJ30_KcvQes7H4tWI165Xeb5t-Qqfo67NE/edit?usp=sharing) for the project: **Rework the GTK website**. If you need any kind of help or want me to handle your next project, you can reach me at my [email](mailto:ravgeetdhillon@gmail.com) or DM me on [Instagram](https://instagram.com/ravd_ravgeet/).

Lemme know if you have any doubt, appreciation or anything else that you would like to communicate to me. You can tweet me [@ravgeetdhillon](https://twitter.com/intent/tweet?screen_name=ravgeetdhillon&amp;original_referer=https://www.ravgeet.dev/blog/getting-selected-for-gsoc/&amp;ref_src=twsrc%5Etfw). I reply to all the questions as quickly as possible. 😄 And if you liked this post, please [share](https://twitter.com/intent/tweet?text=Check%20out%20this%20amazing%20blog%20post%20by%20Ravgeet%20Dhillon%20sharing%20his%20thoughts%20on%20Getting%20Selected%20for%20Google%20Summer%20of%20Code%202019&amp;screen_name=ravgeetdhillon&amp;original_referer=https://www.ravgeet.dev/blog/getting-selected-for-gsoc/&amp;ref_src=twsrc%5Etfw) it with your twitter community as well. See you guys in the next post.]]></content:encoded>
        </item>
    </channel>
</rss>