Skip to content
This repository was archived by the owner on May 29, 2023. It is now read-only.

Commit d973b16

Browse files
author
ugistelmokaitis
committed
Update[external link component]: screenshot preview and return screenshot of it
1 parent ae747ec commit d973b16

8 files changed

+203
-18
lines changed

components/externalLink.tsx

+63-7
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,69 @@
11
import type { LinkProps } from '@prismicio/react';
2+
import Image from 'next/image';
23
import Link from 'next/link';
34
import type { FC } from 'react';
5+
import { useAsync, useMountEffect } from '@react-hookz/web';
6+
import type { ScreenshotResponse } from '../pages/api/screenshot';
7+
import Placeholder from './placeholder';
48

5-
const ExternalLinkComponent: FC<LinkProps> = ({ children, href, ...props }) => (
6-
<Link href={href}>
7-
<a {...props} target="_blank" rel="noopener noreferrer" className="">
8-
{children}
9-
</a>
10-
</Link>
11-
);
9+
const excludedLinks = [
10+
'twitter.com',
11+
'instagram.com',
12+
'linkedin.com',
13+
'ugistelmokaitis.com',
14+
'discord.com',
15+
];
16+
17+
const ExternalLinkComponent: FC<LinkProps> = ({ children, href, ...props }) => {
18+
const isExcluded = excludedLinks.some((excluded) => href.includes(excluded));
19+
const [screenshot, { execute }] = useAsync(async () => {
20+
const response = await fetch('/api/screenshot', {
21+
method: 'POST',
22+
body: JSON.stringify({
23+
url: href,
24+
}),
25+
});
26+
27+
const data = (await response.json()) as ScreenshotResponse;
28+
29+
return data;
30+
});
31+
32+
useMountEffect(async () => {
33+
if (!isExcluded) {
34+
await execute();
35+
}
36+
});
37+
38+
return (
39+
<span className="group relative inline-block">
40+
{!isExcluded && !screenshot.error && !screenshot.result?.error && (
41+
<span className="bg-neutral pointer-events-none absolute left-0 bottom-full ml-[50%] flex h-[205px] w-[318px] -translate-x-2/4 -translate-y-0 rounded-lg border border-neutral-15 p-2 opacity-0 shadow-lg transition-all group-hover:-translate-y-2 group-hover:opacity-100 dark:border-neutral-50 dark:bg-neutral-80">
42+
{screenshot.result?.image ? (
43+
<Image
44+
src={`data:image/png;base64,${screenshot.result.image}`}
45+
width={300}
46+
height={187}
47+
alt=""
48+
/>
49+
) : (
50+
<Placeholder className="h-full w-full rounded-md" />
51+
)}
52+
</span>
53+
)}
54+
<Link href={href}>
55+
<a
56+
{...props}
57+
target="_blank"
58+
rel="noopener noreferrer"
59+
className="text-md inline font-normal text-neutral-100 transition-colors hover:text-neutral-100 dark:text-neutral-0 dark:hover:text-neutral-0"
60+
{...props}
61+
>
62+
{children}
63+
</a>
64+
</Link>
65+
</span>
66+
);
67+
};
1268

1369
export default ExternalLinkComponent;

components/placeholder.tsx

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import type { FC } from 'react';
2+
3+
type PlaceholderProps = {
4+
className?: string;
5+
};
6+
7+
const Placeholder: FC<PlaceholderProps> = ({ className }) => (
8+
<span
9+
className={`bg-gray-50 dark:bg-gray-800 flex items-center justify-center ${
10+
className ?? ''
11+
}`}
12+
>
13+
<svg
14+
className="text-gray-400 h-5 w-5 animate-spin"
15+
xmlns="http://www.w3.org/2000/svg"
16+
fill="none"
17+
viewBox="0 0 24 24"
18+
>
19+
<circle
20+
className="opacity-25"
21+
cx="12"
22+
cy="12"
23+
r="10"
24+
stroke="currentColor"
25+
strokeWidth="4"
26+
/>
27+
<path
28+
className="opacity-75"
29+
fill="currentColor"
30+
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
31+
/>
32+
</svg>
33+
</span>
34+
);
35+
36+
export default Placeholder;

components/richTextComponents.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const richTextComponents: JSXMapSerializer = {
3333
),
3434
hyperlink: ({ children, node, key }) => (
3535
<PrismicLink key={key} href={docResolver(node.data)}>
36-
<span className="ABCWhyteEdu-Medium inline text-pm3 font-medium underline decoration-1 underline-offset-[5px] hover:text-neutral-65 hover:decoration-[0.0938rem] dark:hover:text-neutral-30 sm:text-pm2">
36+
<span className="ABCWhyteEdu-Medium inline cursor-pointer text-pm3 font-medium underline decoration-1 underline-offset-[5px] hover:text-neutral-65 hover:decoration-[0.0938rem] dark:hover:text-neutral-30 sm:text-pm2">
3737
{children}
3838
</span>
3939
</PrismicLink>

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"@sentry/nextjs": "^7.2.0",
2424
"@svgr/webpack": "^6.2.1",
2525
"@tippyjs/react": "^4.2.6",
26+
"chrome-aws-lambda": "^10.1.0",
2627
"date-fns": "^2.28.0",
2728
"embla-carousel-react": "^6.2.0",
2829
"eslint-config-prettier": "^8.5.0",
@@ -38,6 +39,7 @@
3839
"postcss-import": "^14.1.0",
3940
"postcss-preset-env": "^7.7.1",
4041
"prismic-cli": "^4.2.3",
42+
"puppeteer-core": "^15.3.2",
4143
"react": "17.0.2",
4244
"react-dom": "17.0.2",
4345
"react-hot-toast": "^2.2.0",

pages/about.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ const introComponents: JSXMapSerializer = {
7878

7979
return (
8080
<PrismicLink key={key} href={docResolver(node.data)} {...props}>
81-
<span className="ABCWhyteEdu-Medium inline text-pm3 font-medium underline decoration-1 underline-offset-[5px] hover:text-neutral-65 hover:decoration-[0.0938rem] dark:hover:text-neutral-30 sm:text-pm2">
81+
<span className="ABCWhyteEdu-Medium inline cursor-pointer text-pm3 font-medium underline decoration-1 underline-offset-[5px] hover:text-neutral-65 hover:decoration-[0.0938rem] dark:hover:text-neutral-30 sm:text-pm2">
8282
{children}
8383
</span>
8484
</PrismicLink>

pages/api/screenshot.ts

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import type { NextApiHandler } from 'next';
2+
import { createBrowser } from '../../utils/browser';
3+
4+
export type ScreenshotResponse = {
5+
error?: string;
6+
image?: string;
7+
};
8+
9+
const handler: NextApiHandler<ScreenshotResponse> = async (req, res) => {
10+
const { url } = JSON.parse(req.body as string) as { url: string };
11+
12+
if (!url) {
13+
res.status(400).json({ error: 'No URL specified' });
14+
return;
15+
}
16+
17+
const browser = await createBrowser();
18+
const page = await browser.newPage();
19+
await page.setViewport({ width: 1200, height: 750 });
20+
await page.goto(url, { waitUntil: 'networkidle0' });
21+
const image = (await page.screenshot({
22+
type: 'png',
23+
encoding: 'base64',
24+
})) as string;
25+
26+
await browser.close();
27+
28+
if (!image) {
29+
res.status(400).json({ error: 'No image found' });
30+
return;
31+
}
32+
33+
res.status(200).json({ image });
34+
};
35+
36+
export default handler;

utils/browser.ts

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { Browser } from 'puppeteer-core';
2+
import puppeteer from 'puppeteer-core';
3+
import chrome from 'chrome-aws-lambda';
4+
5+
export const createBrowser = async (): Promise<Browser> => {
6+
const browser = await puppeteer.launch(
7+
process.env.AWS_EXECUTION_ENV
8+
? {
9+
args: chrome.args,
10+
executablePath: await chrome.executablePath,
11+
headless: chrome.headless,
12+
}
13+
: {
14+
args: [],
15+
executablePath:
16+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
17+
}
18+
);
19+
20+
return browser;
21+
};

yarn.lock

+43-9
Original file line numberDiff line numberDiff line change
@@ -1496,11 +1496,7 @@
14961496
call-me-maybe "^1.0.1"
14971497
glob-to-regexp "^0.3.0"
14981498

1499-
<<<<<<< HEAD
1500-
"@next/bundle-analyzer@^12.1.6":
1501-
=======
15021499
"@next/bundle-analyzer@^12.2.2":
1503-
>>>>>>> 37fe350374dbc281048772ec2999bf6b2f82a379
15041500
version "12.2.2"
15051501
resolved "https://registry.yarnpkg.com/@next/bundle-analyzer/-/bundle-analyzer-12.2.2.tgz#c2da8bf44286cdf9cea91f8a4590a8172c6c5d43"
15061502
integrity sha512-ou8EIVzUTu9FACWbiCjwnpApix2kA5Qms7nuUELn2/FM4Ltx7ts+w5DDSQslrorJri1kFTp6nRD0Hy7FTERYcw==
@@ -3960,6 +3956,13 @@ chownr@^2.0.0:
39603956
resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
39613957
integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==
39623958

3959+
chrome-aws-lambda@^10.1.0:
3960+
version "10.1.0"
3961+
resolved "https://registry.yarnpkg.com/chrome-aws-lambda/-/chrome-aws-lambda-10.1.0.tgz#ac43b4cdfc1fbb2275c62effada560858099501e"
3962+
integrity sha512-NZQVf+J4kqG4sVhRm3WNmOfzY0OtTSm+S8rg77pwePa9RCYHzhnzRs8YvNI6L9tALIW6RpmefWiPURt3vURXcw==
3963+
dependencies:
3964+
lambdafs "^2.0.3"
3965+
39633966
ci-info@^1.5.0:
39643967
version "1.6.0"
39653968
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497"
@@ -4884,6 +4887,11 @@ detective@^5.2.1:
48844887
defined "^1.0.0"
48854888
minimist "^1.2.6"
48864889

4890+
4891+
version "0.0.1011705"
4892+
resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.1011705.tgz#2582ed29f84848df83fba488122015540a744539"
4893+
integrity sha512-OKvTvu9n3swmgYshvsyVHYX0+aPzCoYUnyXUacfQMmFtBtBKewV/gT4I9jkAbpTqtTi2E4S9MXLlvzBDUlqg0Q==
4894+
48874895
48884896
version "0.0.981744"
48894897
resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.981744.tgz#9960da0370284577d46c28979a0b32651022bacf"
@@ -7753,6 +7761,13 @@ known-css-properties@^0.25.0:
77537761
resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.25.0.tgz#6ebc4d4b412f602e5cfbeb4086bd544e34c0a776"
77547762
integrity sha512-b0/9J1O9Jcyik1GC6KC42hJ41jKwdO/Mq8Mdo5sYN+IuRTXs2YFHZC3kZSx6ueusqa95x3wLYe/ytKjbAfGixA==
77557763

7764+
lambdafs@^2.0.3:
7765+
version "2.1.1"
7766+
resolved "https://registry.yarnpkg.com/lambdafs/-/lambdafs-2.1.1.tgz#4bf8d3037b6c61bbb4a22ab05c73ee47964c25ed"
7767+
integrity sha512-x5k8JcoJWkWLvCVBzrl4pzvkEHSgSBqFjg3Dpsc4AcTMq7oUMym4cL/gRTZ6VM4mUMY+M0dIbQ+V1c1tsqqanQ==
7768+
dependencies:
7769+
tar-fs "^2.1.1"
7770+
77567771
language-subtag-registry@~0.3.2:
77577772
version "0.3.22"
77587773
resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz#2e1500861b2e457eba7e7ae86877cbd08fa1fd1d"
@@ -10155,6 +10170,24 @@ punycode@^2.1.0, punycode@^2.1.1:
1015510170
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
1015610171
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
1015710172

10173+
puppeteer-core@^15.3.2:
10174+
version "15.3.2"
10175+
resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-15.3.2.tgz#d4f1bfcaa8922fc05d4afcd9442348f68a7e0a7a"
10176+
integrity sha512-Fmca9UzXmJkRrvGBgUmrffGD2BlulUTfsVefV1+vqfNm4PnlZ/U1bfD6X8XQ0nftyyg520tmSKd81yH3Z2tszg==
10177+
dependencies:
10178+
cross-fetch "3.1.5"
10179+
debug "4.3.4"
10180+
devtools-protocol "0.0.1011705"
10181+
extract-zip "2.0.1"
10182+
https-proxy-agent "5.0.1"
10183+
pkg-dir "4.2.0"
10184+
progress "2.0.3"
10185+
proxy-from-env "1.1.0"
10186+
rimraf "3.0.2"
10187+
tar-fs "2.1.1"
10188+
unbzip2-stream "1.4.3"
10189+
ws "8.8.0"
10190+
1015810191
puppeteer@^13.5.1:
1015910192
version "13.7.0"
1016010193
resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-13.7.0.tgz#18e16f83e397cf02f7a0804c67c1603d381cfb0b"
@@ -11886,11 +11919,7 @@ table@^6.8.0:
1188611919
string-width "^4.2.3"
1188711920
strip-ansi "^6.0.1"
1188811921

11889-
<<<<<<< HEAD
11890-
tailwindcss@^3.1.3:
11891-
=======
1189211922
tailwindcss@^3.1.6:
11893-
>>>>>>> 37fe350374dbc281048772ec2999bf6b2f82a379
1189411923
version "3.1.6"
1189511924
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.1.6.tgz#bcb719357776c39e6376a8d84e9834b2b19a49f1"
1189611925
integrity sha512-7skAOY56erZAFQssT1xkpk+kWt2NrO45kORlxFPXUt3CiGsVPhH1smuH5XoDH6sGPXLyBv+zgCKA2HWBsgCytg==
@@ -11918,7 +11947,7 @@ tailwindcss@^3.1.6:
1191811947
quick-lru "^5.1.1"
1191911948
resolve "^1.22.1"
1192011949

11921-
11950+
[email protected], tar-fs@^2.1.1:
1192211951
version "2.1.1"
1192311952
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784"
1192411953
integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==
@@ -12941,6 +12970,11 @@ [email protected]:
1294112970
resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f"
1294212971
integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==
1294312972

12973+
12974+
version "8.8.0"
12975+
resolved "https://registry.yarnpkg.com/ws/-/ws-8.8.0.tgz#8e71c75e2f6348dbf8d78005107297056cb77769"
12976+
integrity sha512-JDAgSYQ1ksuwqfChJusw1LSJ8BizJ2e/vVu5Lxjq3YvNJNlROv1ui4i+c/kUUrPheBvQl4c5UbERhTwKa6QBJQ==
12977+
1294412978
ws@^7.3.1:
1294512979
version "7.5.8"
1294612980
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.8.tgz#ac2729881ab9e7cbaf8787fe3469a48c5c7f636a"

0 commit comments

Comments
 (0)