Skip to content

Commit c3a6891

Browse files
authored
Merge pull request #524 from mfts/feat/geography
feat: add user agent to views
2 parents 50e153a + c30de4c commit c3a6891

File tree

18 files changed

+395
-31
lines changed

18 files changed

+395
-31
lines changed

app/(static)/blog/[slug]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Metadata } from "next";
22
import Link from "next/link";
33
import { notFound } from "next/navigation";
44

5-
import BlurImage from "@/components/blur-image";
5+
import { BlurImage } from "@/components/blur-image";
66
import { ContentBody } from "@/components/mdx/post-body";
77
import TableOfContents from "@/components/mdx/table-of-contents";
88

app/(static)/de/blog/[slug]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Metadata } from "next";
22
import Link from "next/link";
33
import { notFound } from "next/navigation";
44

5-
import BlurImage from "@/components/blur-image";
5+
import { BlurImage } from "@/components/blur-image";
66
import { ContentBody } from "@/components/mdx/post-body";
77
import TableOfContents from "@/components/mdx/table-of-contents";
88

app/(static)/help/article/[slug]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Metadata } from "next";
22
import Link from "next/link";
33
import { notFound } from "next/navigation";
44

5-
import BlurImage from "@/components/blur-image";
5+
import { BlurImage } from "@/components/blur-image";
66
import { ContentBody } from "@/components/mdx/post-body";
77
import TableOfContents from "@/components/mdx/table-of-contents";
88
import {

components/blur-image.tsx

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,32 @@ import Image, { ImageProps } from "next/image";
44

55
import { useEffect, useState } from "react";
66

7-
export default function BlurImage(props: ImageProps) {
7+
import { cn } from "@/lib/utils";
8+
9+
export function BlurImage(props: ImageProps) {
810
const [loading, setLoading] = useState(true);
911
const [src, setSrc] = useState(props.src);
1012
useEffect(() => setSrc(props.src), [props.src]); // update the `src` value when the `prop.src` value changes
1113

14+
const handleLoad = (e: React.SyntheticEvent<HTMLImageElement, Event>) => {
15+
setLoading(false);
16+
const target = e.target as HTMLImageElement;
17+
if (target.naturalWidth <= 16 && target.naturalHeight <= 16) {
18+
setSrc(`https://avatar.vercel.sh/${encodeURIComponent(props.alt)}`);
19+
}
20+
};
21+
1222
return (
1323
<Image
1424
{...props}
1525
src={src}
1626
alt={props.alt}
17-
className={`${props.className} ${loading ? "blur-[2px]" : "blur-0"}`}
18-
onLoad={async () => {
19-
setLoading(false);
20-
}}
27+
className={cn(loading ? "blur-[2px]" : "blur-0", props.className)}
28+
onLoad={handleLoad}
2129
onError={() => {
22-
setSrc(`https://avatar.vercel.sh/${props.alt}`); // if the image fails to load, use the default avatar
30+
setSrc(`https://avatar.vercel.sh/${encodeURIComponent(props.alt)}`); // if the image fails to load, use the default avatar
2331
}}
32+
unoptimized
2433
/>
2534
);
2635
}

components/shared/blur-image.tsx

Lines changed: 0 additions & 20 deletions
This file was deleted.

components/ui/devices.tsx

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Thanks to Steven Tey for the original code: https://github.com/dubinc/dub/blob/652f17677828c5a9d5d354841b0bfba5fe63c7a8/apps/web/ui/shared/icons/devices.tsx
2+
3+
export function Chrome({ className }: { className: string }) {
4+
return (
5+
<svg viewBox="0 0 100 100" className={className}>
6+
<linearGradient
7+
id="b"
8+
x1="55.41"
9+
x2="12.11"
10+
y1="96.87"
11+
y2="21.87"
12+
gradientUnits="userSpaceOnUse"
13+
>
14+
<stop offset="0" stopColor="#1e8e3e" />
15+
<stop offset="1" stopColor="#34a853" />
16+
</linearGradient>
17+
<linearGradient
18+
id="c"
19+
x1="42.7"
20+
x2="86"
21+
y1="100"
22+
y2="25.13"
23+
gradientUnits="userSpaceOnUse"
24+
>
25+
<stop offset="0" stopColor="#fcc934" />
26+
<stop offset="1" stopColor="#fbbc04" />
27+
</linearGradient>
28+
<linearGradient
29+
id="a"
30+
x1="6.7"
31+
x2="93.29"
32+
y1="31.25"
33+
y2="31.25"
34+
gradientUnits="userSpaceOnUse"
35+
>
36+
<stop offset="0" stopColor="#d93025" />
37+
<stop offset="1" stopColor="#ea4335" />
38+
</linearGradient>
39+
<path fill="url(#a)" d="M93.29 25a50 50 90 0 0-86.6 0l3 54z" />
40+
<path fill="url(#b)" d="M28.35 62.5 6.7 25A50 50 90 0 0 50 100l49-50z" />
41+
<path fill="url(#c)" d="M71.65 62.5 50 100a50 50 90 0 0 43.29-75H50z" />
42+
<path fill="#fff" d="M50 75a25 25 90 1 0 0-50 25 25 90 0 0 0 50z" />
43+
<path
44+
fill="#1a73e8"
45+
d="M50 69.8a19.8 19.8 90 1 0 0-39.6 19.8 19.8 90 0 0 0 39.6z"
46+
/>{" "}
47+
</svg>
48+
);
49+
}
50+
51+
export function Safari({ className }: { className: string }) {
52+
return (
53+
<svg className={className} width="66" height="66" viewBox="0 0 66 66">
54+
<path
55+
fill="#C6C6C6"
56+
stroke="#C6C6C6"
57+
strokeLinecap="round"
58+
strokeLinejoin="round"
59+
strokeWidth="0.5"
60+
d="M383.29373 211.97671a31.325188 31.325188 0 0 1-31.32519 31.32519 31.325188 31.325188 0 0 1-31.32518-31.32519 31.325188 31.325188 0 0 1 31.32518-31.32519 31.325188 31.325188 0 0 1 31.32519 31.32519z"
61+
paintOrder="markers stroke fill"
62+
transform="translate(-318.88562 -180.59501)"
63+
/>
64+
<path
65+
fill="#4A9DED"
66+
d="M380.83911 211.97671a28.870571 28.870571 0 0 1-28.87057 28.87057 28.870571 28.870571 0 0 1-28.87057-28.87057 28.870571 28.870571 0 0 1 28.87057-28.87057 28.870571 28.870571 0 0 1 28.87057 28.87057z"
67+
paintOrder="markers stroke fill"
68+
transform="translate(-318.88562 -180.59501)"
69+
/>
70+
<path
71+
fill="#ff5150"
72+
d="m36.3834003 34.83806178-6.60095092-6.91272438 23.41607429-15.75199774z"
73+
paintOrder="markers stroke fill"
74+
/>
75+
<path
76+
fill="#f1f1f1"
77+
d="m36.38339038 34.83805895-6.60095092-6.91272438-16.81512624 22.66471911z"
78+
paintOrder="markers stroke fill"
79+
/>
80+
<path
81+
d="m12.96732 50.59006 23.41607-15.75201 16.81513-22.66472z"
82+
opacity=".243"
83+
/>
84+
</svg>
85+
);
86+
}
87+
88+
export function Apple({ className }: { className: string }) {
89+
return (
90+
<svg
91+
viewBox="0 0 2048 2048"
92+
width="2048px"
93+
height="2048px"
94+
className={className}
95+
>
96+
<path
97+
fill="#424242"
98+
fillRule="nonzero"
99+
d="M1318.64 413.756c-14.426,44.2737 -37.767,85.3075 -65.8997,119.436l0 0.0625985c-28.3855,34.324 -66.3012,64.6713 -108.482,84.7926 -38.713,18.4665 -81.1489,28.4114 -123.377,25.1197l-12.9236 -1.00748 -1.70197 -12.8681c-5.48622,-41.4992 0.849213,-83.5099 14.1921,-122.387 15.5268,-45.241 40.6772,-86.5205 67.6642,-117.8l-0.00472441 -0.00472441c27.9272,-32.7142 65.3788,-61.1776 105.487,-81.8009 40.2437,-20.6941 83.465,-33.6343 122.803,-35.237l14.8701 -0.605906 1.62992 14.8559c4.76457,43.4481 -1.02992,86.8489 -14.2571,127.445z"
100+
/>
101+
<path
102+
fill="#424242"
103+
fillRule="nonzero"
104+
d="M1592.05 804.067c-14.2559,8.82048 -152.045,94.0808 -150.337,265.937 1.80236,207.182 177.474,279.003 187.171,282.966l0.0625985 0 0.419292 0.173622 13.7835 5.70709 -4.72087 14.1047c-0.279921,0.836221 0.0377953,-0.0531496 -0.370866,1.25906 -4.48229,14.361 -34.8685,111.708 -103.511,212.014 -31.1481,45.4985 -62.8831,90.9284 -100.352,125.971 -38.7957,36.2823 -83.1024,60.7737 -137.837,61.7906 -51.5894,0.968505 -85.3642,-13.6453 -120.474,-28.8366 -33.4784,-14.4862 -68.2949,-29.5524 -122.779,-29.5524 -57.2339,0 -93.8198,15.5858 -129.06,30.6 -33.1725,14.1319 -65.2548,27.7996 -111.474,29.6433l-0.0625985 0c-53.3693,1.98189 -99.6485,-24.0343 -140.778,-62.5678 -39.3496,-36.8646 -73.8249,-85.1398 -105.241,-130.579 -70.917,-102.399 -132.592,-251.392 -151.647,-402.892 -15.6732,-124.616 -2.57244,-251.206 57.6756,-355.753 33.6331,-58.4953 80.6398,-106.233 135.598,-139.543 54.7075,-33.1571 117.299,-52.0264 182.451,-53.0032l0 -0.0011811c57.0402,-1.03465 110.823,20.3091 157.884,38.9847 33.3059,13.2165 62.98,24.9933 85.226,24.9933 19.6536,0 48.6237,-11.4224 82.3949,-24.737 57.0367,-22.487 126.815,-49.9949 200.599,-42.6579 30.9862,1.34764 95.5265,8.76969 161.501,44.524 42.0284,22.7776 84.6579,56.9741 119.701,108.261l9.23977 13.5248 -13.8024 8.91261c-0.73819,0.477166 -0.0200788,-0.00944883 -1.25906,0.755906z"
105+
/>
106+
</svg>
107+
);
108+
}

components/user-agent-icon.tsx

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import {
2+
Gamepad2Icon,
3+
MonitorIcon,
4+
SmartphoneIcon,
5+
TabletSmartphoneIcon,
6+
TvIcon,
7+
WatchIcon,
8+
} from "lucide-react";
9+
10+
import { BlurImage } from "@/components/blur-image";
11+
import { Apple, Chrome, Safari } from "@/components/ui/devices";
12+
13+
export default function UAIcon({
14+
display,
15+
type,
16+
className,
17+
}: {
18+
display: string;
19+
type: "devices" | "browsers" | "os";
20+
className: string;
21+
}) {
22+
if (type === "devices") {
23+
switch (display) {
24+
case "Desktop":
25+
return <MonitorIcon className={className} />;
26+
case "Mobile":
27+
return <SmartphoneIcon className={className} />;
28+
case "Tablet":
29+
return <TabletSmartphoneIcon className={className} />;
30+
case "Wearable":
31+
return <WatchIcon className={className} />;
32+
case "Console":
33+
return <Gamepad2Icon className={className} />;
34+
case "Smarttv":
35+
return <TvIcon className={className} />;
36+
default:
37+
return <MonitorIcon className={className} />;
38+
}
39+
} else if (type === "browsers") {
40+
if (display === "Chrome") {
41+
return <Chrome className={className} />;
42+
} else if (display === "Safari" || display === "Mobile Safari") {
43+
return <Safari className={className} />;
44+
} else {
45+
return (
46+
<BlurImage
47+
src={`https://faisalman.github.io/ua-parser-js/images/browsers/${display.toLowerCase()}.png`}
48+
alt={display}
49+
width={20}
50+
height={20}
51+
className={className}
52+
/>
53+
);
54+
}
55+
} else if (type === "os") {
56+
if (display === "Mac OS" || display === "iOS") {
57+
return <Apple className="-mx-1 h-5 w-5" />;
58+
} else {
59+
return (
60+
<BlurImage
61+
src={`https://faisalman.github.io/ua-parser-js/images/os/${display.toLowerCase()}.png`}
62+
alt={display}
63+
width={30}
64+
height={20}
65+
className="h-4 w-5"
66+
/>
67+
);
68+
}
69+
} else {
70+
return (
71+
<BlurImage
72+
src={`https://faisalman.github.io/ua-parser-js/images/companies/default.png`}
73+
alt={display}
74+
width={20}
75+
height={20}
76+
className={className}
77+
/>
78+
);
79+
}
80+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import ErrorPage from "next/error";
2+
3+
import { useVisitorUserAgent } from "@/lib/swr/use-stats";
4+
5+
import UAIcon from "../user-agent-icon";
6+
7+
export default function VisitorUserAgent({ viewId }: { viewId: string }) {
8+
const { userAgent, error } = useVisitorUserAgent(viewId);
9+
10+
if (error && error.status === 404) {
11+
return <ErrorPage statusCode={404} />;
12+
}
13+
14+
if (!userAgent) {
15+
return <div>Loading...</div>;
16+
}
17+
18+
const { device, browser, os } = userAgent;
19+
20+
return (
21+
<div className="pb-0.5 pl-0.5 md:pb-1 md:pl-1">
22+
<div className="flex items-center">
23+
<div className="flex items-center gap-x-1 px-1">
24+
<UAIcon display={device} type="devices" className="size-4" /> {device}
25+
,
26+
</div>
27+
<div className="flex items-center gap-x-1 px-1">
28+
<UAIcon display={browser} type="browsers" className="size-4" />{" "}
29+
{browser},
30+
</div>
31+
<div className="flex items-center gap-x-1 px-1">
32+
<UAIcon display={os} type="os" className="size-4" /> {os}
33+
</div>
34+
</div>
35+
</div>
36+
);
37+
}

components/visitors/visitors-table.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import {
4646
} from "../ui/pagination";
4747
import { VisitorAvatar } from "./visitor-avatar";
4848
import VisitorChart from "./visitor-chart";
49+
import VisitorUserAgent from "./visitor-useragent";
4950

5051
export default function VisitorsTable({ numPages }: { numPages: number }) {
5152
const [currentPage, setCurrentPage] = useState<number>(1);
@@ -198,6 +199,9 @@ export default function VisitorsTable({ numPages }: { numPages: number }) {
198199
<>
199200
<TableRow className="hover:bg-transparent">
200201
<TableCell colSpan={5}>
202+
{!isFreePlan ? (
203+
<VisitorUserAgent viewId={view.id} />
204+
) : null}
201205
<VisitorChart
202206
documentId={view.documentId!}
203207
viewId={view.id}

components/web/alternatives/digifytext.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Link from "next/link";
22

3-
import BlurImage from "@/components/blur-image";
3+
import { BlurImage } from "@/components/blur-image";
44
import { Button } from "@/components/ui/button";
55

66
const DigifyAlternatives = () => {

0 commit comments

Comments
 (0)