Skip to content

Commit 18a00f0

Browse files
committed
docs: update pic
fix: add rate limiter back for newsletter
1 parent 629adcb commit 18a00f0

File tree

7 files changed

+123
-16
lines changed

7 files changed

+123
-16
lines changed

README.md

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
1-
<div align="center">
2-
3-
# UX Patterns for Devs
4-
5-
🎨 A comprehensive collection of UX patterns for developers who want to build effective, accessible, and usable UI components.
1+
<a href="https://uxpatterns.dev/">
2+
<img src="https://raw.githubusercontent.com/thedaviddias/ux-patterns-for-developers/refs/heads/main/banner.png" alt="">
3+
</br>
4+
</br>
5+
</a>
66

77
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
88
[![Contributions Welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/thedaviddias/ux-patterns-for-developers/blob/main/.github/CONTRIBUTING.md)
9-
[![GitHub Stars](https://img.shields.io/github/stars/thedaviddias/ux-patterns-for-developers?style=social)](https://github.com/thedaviddias/ux-patterns-for-developers)
10-
[![Twitter Follow](https://img.shields.io/twitter/follow/thedaviddias?style=social)](https://x.com/thedaviddias)
119

12-
[View Documentation](https://uxpatterns.dev) · [Report Bug](https://github.com/thedaviddias/ux-patterns-for-developers/issues) · [Request Pattern](https://github.com/thedaviddias/ux-patterns-for-developers/issues/new)
10+
---
1311

14-
![Screenshot of the homepage of UX Patterns for Devs](https://raw.githubusercontent.com/thedaviddias/ux-patterns-for-developers/refs/heads/main/ux-patterns-developers-2.jpeg)
12+
# UX Patterns for Devs
1513

16-
</div>
14+
🎨 A comprehensive collection of UX patterns for developers who want to build effective, accessible, and usable UI components.
1715

1816
## ✨ Features
1917

apps/gallery/app/api/newsletter/route.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1-
import { NextResponse } from "next/server";
21
import { KitProvider } from "@ux-patterns/newsletter/kit-provider";
3-
import { type KitConfig, subscribeSchema } from "@ux-patterns/newsletter/schema";
2+
import {
3+
checkRateLimit,
4+
getRateLimitKey,
5+
} from "@ux-patterns/newsletter/rate-limiter";
6+
import {
7+
type KitConfig,
8+
subscribeSchema,
9+
} from "@ux-patterns/newsletter/schema";
10+
import { NextResponse } from "next/server";
411

512
// POST handler for newsletter subscription
613
export async function POST(request: Request) {
@@ -25,6 +32,18 @@ export async function POST(request: Request) {
2532
);
2633
}
2734

35+
// Server-side rate limiting based on IP address
36+
const rateLimitKey = getRateLimitKey(request);
37+
if (!checkRateLimit(rateLimitKey)) {
38+
return NextResponse.json(
39+
{
40+
success: false,
41+
message: "Too many requests. Please try again in a minute.",
42+
},
43+
{ status: 429 },
44+
);
45+
}
46+
2847
// Initialize Kit provider
2948
const kitConfig: KitConfig = {
3049
provider: "kit",

apps/kit/app/api/newsletter/route.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1-
import { NextResponse } from "next/server";
21
import { KitProvider } from "@ux-patterns/newsletter/kit-provider";
3-
import { type KitConfig, subscribeSchema } from "@ux-patterns/newsletter/schema";
2+
import {
3+
checkRateLimit,
4+
getRateLimitKey,
5+
} from "@ux-patterns/newsletter/rate-limiter";
6+
import {
7+
type KitConfig,
8+
subscribeSchema,
9+
} from "@ux-patterns/newsletter/schema";
10+
import { NextResponse } from "next/server";
411

512
// POST handler for newsletter subscription
613
export async function POST(request: Request) {
@@ -25,6 +32,18 @@ export async function POST(request: Request) {
2532
);
2633
}
2734

35+
// Server-side rate limiting based on IP address
36+
const rateLimitKey = getRateLimitKey(request);
37+
if (!checkRateLimit(rateLimitKey)) {
38+
return NextResponse.json(
39+
{
40+
success: false,
41+
message: "Too many requests. Please try again in a minute.",
42+
},
43+
{ status: 429 },
44+
);
45+
}
46+
2847
// Initialize Kit provider
2948
const kitConfig: KitConfig = {
3049
provider: "kit",

apps/web/app/api/newsletter/route.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1-
import { NextResponse } from "next/server";
21
import { KitProvider } from "@ux-patterns/newsletter/kit-provider";
3-
import { type KitConfig, subscribeSchema } from "@ux-patterns/newsletter/schema";
2+
import {
3+
checkRateLimit,
4+
getRateLimitKey,
5+
} from "@ux-patterns/newsletter/rate-limiter";
6+
import {
7+
type KitConfig,
8+
subscribeSchema,
9+
} from "@ux-patterns/newsletter/schema";
10+
import { NextResponse } from "next/server";
411

512
// POST handler for newsletter subscription
613
export async function POST(request: Request) {
@@ -25,6 +32,18 @@ export async function POST(request: Request) {
2532
);
2633
}
2734

35+
// Server-side rate limiting based on IP address
36+
const rateLimitKey = getRateLimitKey(request);
37+
if (!checkRateLimit(rateLimitKey)) {
38+
return NextResponse.json(
39+
{
40+
success: false,
41+
message: "Too many requests. Please try again in a minute.",
42+
},
43+
{ status: 429 },
44+
);
45+
}
46+
2847
// Initialize Kit provider
2948
const kitConfig: KitConfig = {
3049
provider: "kit",

banner.png

9.49 KB
Loading
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Simple in-memory rate limiter (consider using Redis/Upstash in production)
2+
const rateLimitMap = new Map<string, { count: number; resetTime: number }>();
3+
4+
// Clean up expired entries every 5 minutes
5+
setInterval(
6+
() => {
7+
const now = Date.now();
8+
const keysToDelete: string[] = [];
9+
rateLimitMap.forEach((value, key) => {
10+
if (value.resetTime < now) {
11+
keysToDelete.push(key);
12+
}
13+
});
14+
keysToDelete.forEach((key) => {
15+
rateLimitMap.delete(key);
16+
});
17+
},
18+
5 * 60 * 1000,
19+
);
20+
21+
export function getRateLimitKey(request: Request): string {
22+
// Get client IP from headers (works with Vercel/Cloudflare)
23+
const forwardedFor = request.headers.get("x-forwarded-for");
24+
const realIp = request.headers.get("x-real-ip");
25+
const cfConnectingIp = request.headers.get("cf-connecting-ip");
26+
27+
const clientIp =
28+
forwardedFor?.split(",")[0] || realIp || cfConnectingIp || "unknown";
29+
return `newsletter:${clientIp}`;
30+
}
31+
32+
export function checkRateLimit(
33+
key: string,
34+
maxRequests: number = 3,
35+
windowMs: number = 60000,
36+
): boolean {
37+
const now = Date.now();
38+
const record = rateLimitMap.get(key);
39+
40+
if (!record || record.resetTime < now) {
41+
// Create new record or reset expired one
42+
rateLimitMap.set(key, { count: 1, resetTime: now + windowMs });
43+
return true;
44+
}
45+
46+
if (record.count >= maxRequests) {
47+
return false; // Rate limit exceeded
48+
}
49+
50+
record.count++;
51+
return true;
52+
}

ux-patterns-developers-2.jpeg

-148 KB
Binary file not shown.

0 commit comments

Comments
 (0)