forked from MarsX-dev/devhunt
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add new page to show best dev tools on PH, and change john picture
- Loading branch information
Showing
10 changed files
with
462 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) }); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.