Skip to content
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
f4467c3
add: added the newsletter listing page, and the newsletter page, mark…
deveshru2712 Nov 16, 2025
2b2d35e
add: added routing on the newsletter click
deveshru2712 Nov 16, 2025
9fcd49d
add: added the loading skeleton, and only pro user can access the new…
deveshru2712 Nov 16, 2025
c88532d
fix: add loadingstate, and user sub to the dependecies array
deveshru2712 Nov 16, 2025
c9ec8cb
rename: update the name of some files
deveshru2712 Nov 16, 2025
b087167
fix: made newsletter pro user exclusive
deveshru2712 Nov 16, 2025
357d163
remove: removed unused package
deveshru2712 Nov 16, 2025
61228ce
Merge remote-tracking branch 'origin/main' into add/add-newsletter
deveshru2712 Nov 16, 2025
30f4fbd
fix: fix the error raised by coderrabitai
deveshru2712 Nov 16, 2025
4bb31b3
remove the aria code as it was cause too much issue
deveshru2712 Nov 16, 2025
103b124
fix: fixed type error in get all newsletter utils function
deveshru2712 Nov 16, 2025
bcf3303
fix: made some recommended changes by coderrabbitai
deveshru2712 Nov 16, 2025
2b8fd59
Merge branch 'add/add-newsletter' of https://github.com/deveshru2712/…
deveshru2712 Nov 16, 2025
147c661
fix: made some recommended changes in the filter function
deveshru2712 Nov 16, 2025
f0ac259
fix: minute fix changes let to const
deveshru2712 Nov 16, 2025
2fa2f36
fix: change data to time in .md files and renamed a file
deveshru2712 Nov 17, 2025
e76e435
Merge branch 'main' into add/add-newsletter
deveshru2712 Nov 19, 2025
d442b6e
Merge branch 'main' into add/add-newsletter
deveshru2712 Nov 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions apps/web/content/newsletters/example.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
id: "unique-newsletter-id"
title: "Your Newsletter Title Goes Here"
date: "2025-11-16"
summary: "A brief 1-2 sentence summary of what this newsletter is about. This will appear below the title."
keywords: ["keyword1", "keyword2", "keyword3"]
readTime: "5 min read"
slug: "test"
---

## Add the content here
63 changes: 63 additions & 0 deletions apps/web/content/newsletters/test-1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
id: "from-farm-to-founder"
title: "From Farm Boy to Founder: A Journey of Grit and Code"
date: "2025-10-23"
summary: "How one developer went from being a hotel receptionist with no college degree to building a startup with 10,000 users and multiple investment offersβ€”all through self-taught programming and relentless determination."
keywords:
[
"entrepreneurship",
"self-taught developer",
"startup journey",
"open source",
"career transition",
]
readTime: "4 min read"
slug: "from-farm-to-founder"
---

![Journey from farm to founder](https://imgs.search.brave.com/0NvT2qcazrFJeTKxSWwaaSCfq2Ak2DCGgdHPup131xQ/rs:fit:860:0:0:0/g:ce/aHR0cHM6Ly93YWxs/cGFwZXJzLmNvbS9p/bWFnZXMvZmVhdHVy/ZWQvaGFyZC13b3Jr/LXBpY3R1cmVzLTMw/YjY2amNnc203aGw2/djIuanBn)

## The Beginning: Breaking Free

I was born and raised on a farm. Despite nailing my studies, I got rejected by the air force. My parents, like many Indian parents, pushed hard for a government jobβ€”the "safe" option. At 18, I made a choice that would change everything: I left home.

My first job? A receptionist at a hotel. Not glamorous. Not prestigious. But it was _mine_.

## The Pivot: A Computer and a Dream

I bought a computer with my savings. Late nights after hotel shifts, I taught myself programming. No college degree. No connections. No safety net. Just documentation, tutorials, and an obsessive drive to learn.

Eventually, I quit the hotel job and started from absolute scratch in tech.

## The Grind: Open Source and GSoC

I dove into open source. My first Google Summer of Code application? **Rejected.**

But I didn't stop. I worked harder, contributed more, made genuine friends in the community. The second time around, I didn't just get inβ€”I excelled. Internships followed. Job offers came. I even became a **GSoC mentor** myself.

While still learning and growing, I started earning lakhs. I paid my brother's college fees. I covered my father's medical treatment. The farm boy who left home at 18 was now supporting his family.

## The Leap: Building Something Real

Despite the stable job and good income, I couldn't shake the itch to build something of my own. I started a side project. It grew to **10,000 users**. Then came **2 investment offers**.

The numbers spoke for themselves: my side project revenue crossed my salary. I left the job.

Now I'm scaling [opensox.ai](http://opensox.ai), and the journey continues.

## The Lesson: You Can Just Do Things

There's no secret formula. No hidden shortcut. No "right" background.

What worked for me:

- **Self-belief over credentials** – No college degree couldn't stop me from learning to code
- **Community over isolation** – Open source connected me with people who became mentors and friends
- **Action over permission** – I didn't wait for someone to give me a chance; I created my own
- **Persistence over perfection** – Failed GSoC? Applied again. Got rejected? Kept building.

If you're reading this from a small town, from a non-tech background, from a place where startup dreams seem impossibleβ€”know this: **You can just do things.**

You don't need permission. You don't need a fancy degree. You need a computer, internet, and the refusal to quit.

_Want to follow my journey? Check out what we're building at [opensox.ai](http://opensox.ai)_
68 changes: 68 additions & 0 deletions apps/web/content/newsletters/test-2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
id: "ugly-execution"
title: "Ugly Execution: Why Imperfect Action Beats Perfect Planning"
date: "2025-11-16"
summary: "Stop waiting for perfect conditions. Learn why taking messy, imperfect action is infinitely more valuable than endless planning and preparation."
keywords:
[
"productivity",
"mindset",
"execution",
"perfectionism",
"taking action",
"startup advice",
]
readTime: "3 min read"
slug: "ugly-execution"
---

![Taking imperfect action](https://pbs.twimg.com/media/G5aMbWTbcAEr9vp?format=jpg&name=medium)

I use the term **"ugly execution"** to define a state when you don't care about the perfectness in anything and just act.

When you understand that **life rewards action, not perfection.**

## What Ugly Execution Looks Like

When your camera sucks, but still you hit that red button, record a scrappy and embarrassing video, and click on "post" and actually post it on YouTube.

When your website's UI sucks, authentication fails, gets "build errors" randomly, and lags like government websites, but you still publish it and let people use it.

When your resume looks like a blank slate, but you still apply for the job you wanted.

## The Uncomfortable Truth

Your inner self is uncomfortable doing that, but you know that this **"chase of perfection" has the potential to kill your dreams.**

You know that if you don't act now, your net output in the future will be zero.

You know, acting, getting stuck, failing, making a stupid mistake, getting embarrassed, is **way better** than creating a mental prison for yourself by useless thinking, worrying, and planning.

## Stop Chasing Aesthetics

Don't wait for the perfect:

- Time
- Camera
- Mic
- Laptop
- Place
- Circumstances
- Website
- People

You get the point.

## The Only Rule That Matters

**JUST START WITH WHATEVER YOU HAVE**

**LET THE EXECUTION BE UGLY**

**JUST BE GOOD AT UGLY EXECUTION**

**LIFE WILL REWARD YOU EXPONENTIALLY.**

---

_The difference between successful people and dreamers? Successful people ship ugly v1s. Dreamers wait for perfect v10s that never come._
5 changes: 5 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"dompurify": "^3.3.0",
"framer-motion": "^11.15.0",
"geist": "^1.5.1",
"gray-matter": "^4.0.3",
"lucide-react": "^0.456.0",
"next": "15.5.3",
"next-auth": "^4.24.11",
Expand All @@ -36,12 +37,16 @@
"react-dom": "^18.2.0",
"react-qr-code": "^2.0.18",
"react-tweet": "^3.2.1",
"remark": "^15.0.1",
"remark-gfm": "^4.0.1",
"remark-html": "^16.0.1",
"superjson": "^2.2.5",
"tailwind-merge": "^2.5.4",
"tailwindcss-animate": "^1.0.7",
"zustand": "^5.0.1"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.19",
"@types/dompurify": "^3.2.0",
"@types/node": "^20",
"@types/react": "^18",
Expand Down
124 changes: 124 additions & 0 deletions apps/web/src/app/(main)/dashboard/newsletters/NewsletterPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
"use client";

import { Search } from "lucide-react";
import NewsletterItem from "@/components/newsletter/NewsletterItem";
import CustomDropdown from "@/components/newsletter/Dropdown";
import { useNewsletterFilters } from "@/hooks/useNewsletterFilters";

interface NewsletterData {
id: string;
title: string;
summary: string;
keywords: string[];
time: string;
readTime: string;
slug: string;
}

export default function NewsletterPage({
initialData,
}: {
initialData: NewsletterData[];
}) {
const {
search,
setSearch,
sortOrder,
setSortOrder,
selectedMonth,
setSelectedMonth,
monthOptions,
filteredAndSorted,
clearFilters,
hasActiveFilters,
resultCount,
} = useNewsletterFilters(initialData);

return (
<div className="w-full py-6 px-4 sm:px-8">
<div className="w-full">
<h1 className="text-3xl sm:text-4xl md:text-5xl font-bold tracking-tight">
Newsletter
</h1>
<p className="mt-1 text-sm sm:text-base text-gray-300">
Stay updated with our latest insights and stories
</p>
</div>

<div className="mt-8 w-full flex flex-col gap-4 sm:flex-row sm:flex-wrap sm:items-center">
<div className="relative w-full flex-1">
<Search
size={16}
className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400"
/>
<input
type="text"
placeholder="Search newsletter..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="
w-full bg-white/5 border border-white/10 rounded-lg
px-9 py-2 text-sm text-gray-200 placeholder-gray-400
focus:outline-none focus:border-purple-400
transition-all duration-200
"
/>
</div>

<div className="flex flex-row w-full sm:w-auto gap-3">
<div className="w-full sm:w-40">
<CustomDropdown
value={selectedMonth}
onChange={setSelectedMonth}
options={monthOptions}
/>
</div>
<div className="w-full sm:w-32">
<CustomDropdown
value={sortOrder}
onChange={setSortOrder}
options={[
{ label: "Newest First", value: "newest" },
{ label: "Oldest First", value: "oldest" },
]}
/>
</div>
</div>
</div>

{hasActiveFilters && (
<div className="text-sm text-gray-400 mt-2">
{resultCount} result{resultCount !== 1 ? "s" : ""} found
</div>
)}

<div className="mt-12 flex flex-col gap-5">
{filteredAndSorted.length > 0 ? (
filteredAndSorted.map((item) => (
<NewsletterItem
key={item.id}
title={item.title}
summary={item.summary}
time={item.time}
keywords={item.keywords}
readTime={item.readTime}
slug={item.slug}
/>
))
) : (
<div className="text-center py-12">
<p className="text-gray-400 text-lg">No newsletters found.</p>
{hasActiveFilters && (
<button
onClick={clearFilters}
className="mt-4 text-purple-400 hover:text-purple-300 text-sm underline"
>
Clear all filters
</button>
)}
</div>
)}
</div>
</div>
);
}
31 changes: 31 additions & 0 deletions apps/web/src/app/(main)/dashboard/newsletters/[slug]/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"use client";

import { useEffect } from "react";
import { useRouter } from "next/navigation";
import NewsletterSkeleton from "@/components/newsletter/NewsletterSkeleton";
import { useSubscription } from "@/hooks/useSubscription";

export default function NewsletterLayout({
children,
}: {
children: React.ReactNode;
}) {
const router = useRouter();
const { isLoading, isPaidUser } = useSubscription();

useEffect(() => {
if (!isLoading && !isPaidUser) {
router.replace("/pricing");
}
}, [isLoading, isPaidUser, router]);

if (isLoading) {
return <NewsletterSkeleton />;
}

if (!isPaidUser) {
return null;
}

return <>{children}</>;
}
Loading