Skip to content

Commit

Permalink
Add new page to show best dev tools on PH, and change john picture
Browse files Browse the repository at this point in the history
  • Loading branch information
sidiDev committed Feb 25, 2024
1 parent f858468 commit 4d0d400
Show file tree
Hide file tree
Showing 10 changed files with 462 additions and 12 deletions.
53 changes: 53 additions & 0 deletions app/api/ph-dev-tools/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import axios from 'axios';
import { NextResponse } from 'next/server';
import request from 'request';

export async function GET() {
const today = new Date();
const oneWeekAgo = new Date(today);
oneWeekAgo.setDate(today.getDate() - 7);

const PH_ACCESS_TOKEN = process.env.PH_ACCESS_TOKEN;

const config = {
headers: {
Authorization: `Bearer ${PH_ACCESS_TOKEN}`,
'Content-Type': 'application/json',
Accept: 'application/json',
},
};

const body = {
query: `query { posts(order: VOTES, topic: "developer-tools", postedAfter: "${new Date(oneWeekAgo).toISOString()}") {
edges{
cursor
node{
id
name
description
url
slug
tagline
votesCount
website
productLinks {
url
}
thumbnail {
url
}
}
}
}
}`,
};

const {
data: {
data: {
posts: { edges },
},
},
} = await axios.post('https://api.producthunt.com/v2/api/graphql', body, config);
return NextResponse.json({ posts: edges.slice(0, 10) });
}
124 changes: 124 additions & 0 deletions app/best-dev-tools-this-week-on-product-hunt/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import axios from 'axios';
import ToolName from '@/components/ui/ToolCard/Tool.Name';
import Title from '@/components/ui/ToolCard/Tool.Title';
import ToolFooter from '@/components/ui/ToolCard/Tool.Footer';
import Image from 'next/image';
import ProductHuntCard from '@/components/ui/ProductHuntCard';
import request from 'request';

function extractHostAndPath(url: string): { host: string; path: string } {
const regex = /^(?:https?:\/\/)?([^\/?#]+)(\/[^?#]*)?/;
const matches = url.match(regex);

if (matches && matches.length >= 2) {
const host = matches[1];
const path = matches[2] || '/'; // Default to '/' if path is not provided
return { host, path };
} else {
throw new Error('Invalid URL format');
}
}

function extractLink(url: string) {
// Regular expression to match URLs with "www." or without any protocol
const regex = /^(?:https?:\/\/)?(?:www\.)?(.*)/;

// Extract the domain from the URL using regex
const matches = url.match(regex);

// If matches found, construct the link with "https://" prefix
if (matches && matches.length > 1) {
const domain = matches[1];
return `https://${domain}`;
}

// If no matches found, return the original URL
return url;
}

type Product = {
node: {
id: string;
name: string;
description: string;
slug: string;
tagline: string;
votesCount: number;
thumbnail: {
url: string;
};
productLinks: {
url: string;
};
website: string;
};
};

export const metadata = {
title: 'Best dev tools this week on Product Hunt - Dev Hunt',
metadataBase: new URL('https://devhunt.org'),
alternates: {
canonical: '/best-dev-tools-this-week-on-product-hunt',
},
};

export default async () => {
const origin = process.env.NODE_ENV == 'development' ? 'http://localhost:3001' : 'https://devhunt.org';
const {
data: { posts },
} = await axios.get(`${origin}/api/ph-dev-tools`);

// Create an array to store all the promises for the requests
const requests = posts.map((item: Product) => {
// Return a promise for each request
return new Promise((resolve, reject) => {
// Perform the request asynchronously
request({ url: item.node.website, followRedirect: false }, function (err, res, body) {
if (err) {
reject(err);
} else {
resolve(extractLink(extractHostAndPath(res.headers.location as string).host));
}
});
});
});

// Wait for all promises to resolve
const websites = (await Promise.all(requests)) || [];

return (
<section className="max-w-4xl mt-20 mx-auto px-4 md:px-8">
<div>
<h1 className="text-slate-50 text-3xl font-semibold">Best dev tools this week on Product Hunt</h1>
</div>
<ul className="mt-10 mb-12 divide-y divide-slate-800/60">
{posts?.map((tool: Product, idx: number) => (
<li key={idx} className="py-3">
<ProductHuntCard href={websites[idx] ? `${websites[idx]}/?ref=devhunt` : tool.node.website}>
{/* {console.log(tool.node)} */}
<div className="w-full flex items-center gap-x-4">
<Image
src={tool.node.thumbnail.url}
alt={tool.node.name}
width={64}
height={64}
className="rounded-full object-cover flex-none"
/>
<div className="w-full space-y-1">
<ToolName href={websites[idx] ? websites[idx] : tool.node.website}>{tool.node.name}</ToolName>
<Title className="line-clamp-2">{tool.node.tagline}</Title>
<ToolFooter>
{/* <Tags items={[tool.product_pricing_types?.title ?? 'Free', ...(tool.product_categories || []).map(c => c.name)]} /> */}
</ToolFooter>
</div>
</div>
<div className="px-4 py-1 text-center active:scale-[1.5] duration-200 rounded-md border bg-[linear-gradient(180deg,_#1E293B_0%,_rgba(30,_41,_59,_0.00)_100%)] border-slate-700 text-orange-300">
<span className="text-sm pointer-events-none">#{idx + 1}</span>
</div>
</ProductHuntCard>
</li>
))}
</ul>
</section>
);
};
3 changes: 3 additions & 0 deletions app/sitemap.xml/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ async function generateSiteMap() {
</url>
<url>
<loc>https://devhunt.org/blog</loc>
</url>
<url>
<loc>https://devhunt.org/best-dev-tools-this-week-on-product-hunt</loc>
</url>
${
tools &&
Expand Down
2 changes: 1 addition & 1 deletion components/ui/Navbar/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ export default () => {
className: 'bg-orange-500 hover:bg-orange-600 text-white text-center rounded-lg px-3 p-2 duration-150 btnshake',
},
];

const submenu = [
{ title: 'This Week', path: '/' },
{ title: 'Upcoming Tools', path: '/upcoming' },
{ title: 'Best DevTools On Product Hunt', path: '/best-dev-tools-this-week-on-product-hunt' },
];

const handleSearch = (value: string) => {
Expand Down
19 changes: 19 additions & 0 deletions components/ui/ProductHuntCard/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'use client';

import mergeTW from '@/utils/mergeTW';
import { MouseEvent, ReactNode } from 'react';

export default ({ href, className, children }: { href: string; className?: string; children?: ReactNode }) => {
const handleClick = (e: MouseEvent) => {
window.open(href, '_blank');
};
return (
<div
onClick={handleClick}
className={mergeTW(`flex items-start gap-x-4 relative py-4 rounded-2xl cursor-pointer group group/card ${className}`)}
>
{children}
<div className="absolute -z-10 -inset-2 rounded-2xl group-hover:bg-slate-800/60 opacity-0 group-hover:opacity-100 duration-150 sm:-inset-3"></div>
</div>
);
};
6 changes: 6 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const nextConfig = {
AUTH_TOKEN_PASSWORD: process.env.AUTH_TOKEN_PASSWORD,
AUTH_TOKEN_API_KEY: process.env.AUTH_TOKEN_API_KEY,
CRON_SECRET: process.env.CRON_SECRET,
PH_ACCESS_TOKEN: process.env.PH_ACCESS_TOKEN,
},
images: {
remotePatterns: [
Expand All @@ -29,6 +30,11 @@ const nextConfig = {
port: '',
pathname: '/seobot/devhunt.org/**',
},
{
protocol: 'https',
hostname: 'ph-files.imgix.net',
port: '',
},
],
},
};
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"@usermaven/sdk-js": "^1.2.2",
"axios": "^1.4.0",
"dompurify": "^3.0.3",
"follow-redirects": "^1.15.5",
"framer-motion": "^10.12.16",
"highlight.js": "^11.9.0",
"js-confetti": "^0.11.0",
Expand All @@ -38,6 +39,7 @@
"react-cool-onclickoutside": "^1.7.0",
"react-dom": "18.2.0",
"react-hook-form": "^7.44.2",
"request": "^2.88.2",
"seobot": "^1.0.7",
"tailwind-merge": "^1.12.0",
"waypoints": "^4.0.1"
Expand All @@ -50,6 +52,7 @@
"@types/node": "20.2.3",
"@types/react": "18.2.6",
"@types/react-dom": "18.2.4",
"@types/request": "^2.48.12",
"@typescript-eslint/eslint-plugin": "^5.59.7",
"autoprefixer": "10.4.14",
"better-supabase-types": "^2.7.1",
Expand Down
Loading

0 comments on commit 4d0d400

Please sign in to comment.