1
mirror of https://github.com/jakejarvis/jarv.is.git synced 2025-04-26 15:28:28 -04:00

add page stats typings

This commit is contained in:
Jake Jarvis 2022-06-30 09:17:12 -04:00
parent ae2cb50cbe
commit 7da0a713a4
Signed by: jake
GPG Key ID: 2B0C9CF251E69A39
9 changed files with 80 additions and 87 deletions

View File

@ -1,42 +1,36 @@
import useSWR from "swr";
import useSWRImmutable from "swr/immutable";
import commaNumber from "comma-number";
import Loading from "../Loading";
import fetcher from "../../lib/helpers/fetcher";
import type { PageStats } from "../../types";
export type HitCounterProps = {
slug: string;
className?: string;
};
const HitCounter = ({ slug, className }: HitCounterProps) => {
// start fetching repos from API immediately
const { data, error } = useSWR(
const HitCounter = ({ slug }: HitCounterProps) => {
// use immutable SWR to avoid double (or more) counting views:
// https://swr.vercel.app/docs/revalidation#disable-automatic-revalidations
const { data, error } = useSWRImmutable<PageStats>(
`/api/hits/?${new URLSearchParams({
slug,
})}`,
fetcher,
{
// avoid double (or more) counting views
revalidateOnFocus: false,
revalidateOnReconnect: false,
}
fetcher
);
// show spinning loading indicator if data isn't fetched yet
if (!data) {
return <Loading boxes={3} width={20} />;
}
// fail secretly
if (error) {
return null;
}
// show spinning loading indicator if data isn't fetched yet
if (!data) {
return <Loading boxes={3} width={20} />;
}
// we have data!
return (
<span title={`${commaNumber(data.hits)} ${data.hits === 1 ? "view" : "views"}`} className={className}>
{commaNumber(data.hits)}
</span>
<span title={`${commaNumber(data.hits)} ${data.hits === 1 ? "view" : "views"}`}>{commaNumber(data.hits)}</span>
);
};

View File

@ -56,7 +56,7 @@ const Tag = styled("span", {
export type NoteMetaProps = Pick<NoteFrontMatter, "slug" | "date" | "title" | "htmlTitle" | "tags">;
const NoteMeta = ({ slug, date, title, htmlTitle, tags = [] }: NoteMetaProps) => {
const NoteMeta = ({ slug, date, title, htmlTitle, tags }: NoteMetaProps) => {
return (
<>
<Wrapper>
@ -68,21 +68,17 @@ const NoteMeta = ({ slug, date, title, htmlTitle, tags = [] }: NoteMetaProps) =>
}}
underline={false}
>
<span>
<Icon as={DateIcon} />
</span>
<Time date={date} format="MMMM D, YYYY" />
</MetaLink>
</MetaItem>
{tags.length > 0 && (
{tags && (
<MetaItem>
<span title="Tags">
<Icon as={TagIcon} />
</span>
<TagsList>
{tags.map((tag) => (
<Tag key={tag} title={tag}>
<Tag key={tag} title={tag} aria-label={`Tagged with ${tag}`}>
{tag}
</Tag>
))}
@ -96,9 +92,7 @@ const NoteMeta = ({ slug, date, title, htmlTitle, tags = [] }: NoteMetaProps) =>
title={`Edit "${title}" on GitHub`}
underline={false}
>
<span>
<Icon as={EditIcon} />
</span>
<span>Improve This Post</span>
</MetaLink>
</MetaItem>
@ -106,12 +100,13 @@ const NoteMeta = ({ slug, date, title, htmlTitle, tags = [] }: NoteMetaProps) =>
{/* only count hits on production site */}
{process.env.NEXT_PUBLIC_VERCEL_ENV === "production" && (
<MetaItem
css={{
// fix potential layout shift when number of hits loads
css={{ minWidth: "7em", marginRight: 0 }}
minWidth: "7em",
marginRight: 0,
}}
>
<span>
<Icon as={ViewsIcon} />
</span>
<HitCounter slug={`notes/${slug}`} />
</MetaItem>
)}

View File

@ -22,7 +22,7 @@
"@fontsource/inter": "4.5.11",
"@fontsource/roboto-mono": "4.5.7",
"@giscus/react": "^2.0.6",
"@hcaptcha/react-hcaptcha": "^1.3.1",
"@hcaptcha/react-hcaptcha": "^1.4.3",
"@novnc/novnc": "github:novnc/novnc#cdfb33665195eb9a73fb00feb6ebaccd1068cd50",
"@octokit/graphql": "^4.8.0",
"@octokit/graphql-schema": "^10.74.1",
@ -93,7 +93,7 @@
"eslint": "~8.18.0",
"eslint-config-next": "12.2.0",
"eslint-config-prettier": "~8.5.0",
"eslint-plugin-prettier": "~4.1.0",
"eslint-plugin-prettier": "~4.2.1",
"lint-staged": "^13.0.3",
"prettier": "^2.7.1",
"prisma": "^4.0.0",

View File

@ -2,21 +2,7 @@ import { prisma } from "../../lib/helpers/prisma";
import { getAllNotes } from "../../lib/helpers/parse-notes";
import { logServerError } from "../../lib/helpers/sentry";
import type { NextApiRequest, NextApiResponse } from "next";
type PageStats = {
slug: string;
hits: number;
title?: string;
url?: string;
date?: string;
};
type SiteStats = {
total: {
hits: number;
};
pages: PageStats[];
};
import type { PageStats, DetailedPageStats, SiteStats } from "../../types";
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
try {
@ -56,7 +42,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
}
};
const incrementPageHits = async (slug: string): Promise<Partial<PageStats>> => {
const incrementPageHits = async (slug: string): Promise<PageStats> => {
const { hits } = await prisma.hits.upsert({
where: { slug },
create: { slug },
@ -85,7 +71,7 @@ const getSiteStats = async (): Promise<SiteStats> => {
const total = { hits: 0 };
pages.forEach((page: PageStats) => {
pages.forEach((page: DetailedPageStats) => {
// match URLs from RSS feed with db to populate some metadata
const match = notes.find((note) => `notes/${note.slug}` === page.slug);

View File

@ -4,6 +4,7 @@
import queryString from "query-string";
import { logServerError } from "../../lib/helpers/sentry";
import type { NextApiRequest, NextApiResponse } from "next";
import type { Track } from "../../types";
const { SPOTIFY_CLIENT_ID, SPOTIFY_CLIENT_SECRET, SPOTIFY_REFRESH_TOKEN } = process.env;
@ -14,14 +15,6 @@ const NOW_PLAYING_ENDPOINT = "https://api.spotify.com/v1/me/player/currently-pla
// https://developer.spotify.com/documentation/web-api/reference/#endpoint-get-users-top-artists-and-tracks
const TOP_TRACKS_ENDPOINT = "https://api.spotify.com/v1/me/top/tracks?time_range=long_term&limit=10";
type Track = {
artist: string;
title: string;
album: string;
url: URL | string;
image?: URL | string;
};
type SpotifyTrackSchema = {
name: string;
artists: Array<{

2
types/index.d.ts vendored
View File

@ -1,3 +1,5 @@
export * from "./note";
export * from "./project";
export * from "./stats";
export * from "./track";
export * from "./webpack";

15
types/stats.d.ts vendored Normal file
View File

@ -0,0 +1,15 @@
export type PageStats = {
hits: number;
};
export type DetailedPageStats = PageStats & {
slug: string;
title?: string;
url?: string;
date?: string;
};
export type SiteStats = {
total: PageStats;
pages: DetailedPageStats[];
};

7
types/track.d.ts vendored Normal file
View File

@ -0,0 +1,7 @@
export type Track = {
artist: string;
title: string;
album: string;
url: URL | string;
image?: URL | string;
};

View File

@ -1027,12 +1027,13 @@
dependencies:
giscus "^1.0.6"
"@hcaptcha/react-hcaptcha@^1.3.1":
version "1.3.1"
resolved "https://registry.yarnpkg.com/@hcaptcha/react-hcaptcha/-/react-hcaptcha-1.3.1.tgz#f6e187ad2d97e89e7fd002b44ff1fa4cd99291f6"
integrity sha512-APs/hH7VFp+6zoz6PTKiGcIjQguwRbiZDRCFUeRK7gQg7KG4tXQayGtknKmozYL6z1ypoVE8FXz/W68rnc/u3g==
"@hcaptcha/react-hcaptcha@^1.4.3":
version "1.4.3"
resolved "https://registry.yarnpkg.com/@hcaptcha/react-hcaptcha/-/react-hcaptcha-1.4.3.tgz#7dc9df288e0a9ff54d20902260a92b18bb6fb2d6"
integrity sha512-kBXP9xEH1REIsuoZWowNcSQeL9cnBkRmeJkj2Rvzy8NstkQr/qahgNXUV7YaUxqntYf2mCwFOGTZbacqfqwaQw==
dependencies:
"@babel/runtime" "^7.17.9"
"@types/react" "^18.0.0"
"@humanwhocodes/config-array@^0.9.2":
version "0.9.5"
@ -1094,9 +1095,9 @@
"@jridgewell/sourcemap-codec" "^1.4.10"
"@lit/reactive-element@^1.3.0":
version "1.3.2"
resolved "https://registry.yarnpkg.com/@lit/reactive-element/-/reactive-element-1.3.2.tgz#43e470537b6ec2c23510c07812616d5aa27a17cd"
integrity sha512-A2e18XzPMrIh35nhIdE4uoqRzoIpEU5vZYuQN4S3Ee1zkGdYC27DP12pewbw/RLgPHzaE4kx/YqxMzebOpm0dA==
version "1.3.3"
resolved "https://registry.yarnpkg.com/@lit/reactive-element/-/reactive-element-1.3.3.tgz#851de2bd28d6c3378a816a28d2505075931559c2"
integrity sha512-ukelZ49tzUqgOAEbVujl/U62JNK3wdn5kKtXVqrjKND4QvHACZOMOYaZI6/5Jd8vsg+Fq9HDwiib70FBLydOiQ==
"@mdx-js/mdx@^2.0.0":
version "2.1.2"
@ -1700,7 +1701,7 @@
dependencies:
"@types/react" "*"
"@types/react@*", "@types/react@>=16", "@types/react@^18.0.14":
"@types/react@*", "@types/react@>=16", "@types/react@^18.0.0", "@types/react@^18.0.14":
version "18.0.14"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.14.tgz#e016616ffff51dba01b04945610fe3671fdbe06d"
integrity sha512-x4gGuASSiWmo0xjDLpm5mPb52syZHJx02VKbqUKdLmKtAwIh63XClGsiTI1K6DO5q7ox4xAsQrU+Gl3+gGXF9Q==
@ -2093,9 +2094,9 @@ camelcase@^6.2.0:
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
caniuse-lite@^1.0.30001332, caniuse-lite@^1.0.30001359:
version "1.0.30001359"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001359.tgz#a1c1cbe1c2da9e689638813618b4219acbd4925e"
integrity sha512-Xln/BAsPzEuiVLgJ2/45IaqD9jShtk3Y33anKb4+yLwQzws3+v6odKfpgES/cDEaZMLzSChpIGdbOYtH9MyuHw==
version "1.0.30001361"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001361.tgz#ba2adb2527566fb96f3ac7c67698ae7fc495a28d"
integrity sha512-ybhCrjNtkFji1/Wto6SSJKkWk6kZgVQsDq5QI83SafsF6FXv2JB4df9eEdH6g8sdGgqTXrFLjAxqBGgYoU3azQ==
ccount@^2.0.0:
version "2.0.1"
@ -2489,9 +2490,9 @@ eastasianwidth@^0.2.0:
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
electron-to-chromium@^1.4.172:
version "1.4.172"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.172.tgz#87335795a3dc19e7b6dd5af291038477d81dc6b1"
integrity sha512-yDoFfTJnqBAB6hSiPvzmsBJSrjOXJtHSJoqJdI/zSIh7DYupYnIOHt/bbPw/WE31BJjNTybDdNAs21gCMnTh0Q==
version "1.4.174"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.174.tgz#ffdf57f26dd4558c5aabdb4b190c47af1c4e443b"
integrity sha512-JER+w+9MV2MBVFOXxP036bLlNOnzbYAWrWU8sNUwoOO69T3w4564WhM5H5atd8VVS8U4vpi0i0kdoYzm1NPQgQ==
emoji-regex@^8.0.0:
version "8.0.0"
@ -2685,10 +2686,10 @@ eslint-plugin-jsx-a11y@^6.5.1:
minimatch "^3.1.2"
semver "^6.3.0"
eslint-plugin-prettier@~4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.1.0.tgz#1cd4b3fadf3b3cdb30b1874b55e7f93f85eb43ad"
integrity sha512-A3AXIEfTnq3D5qDFjWJdQ9c4BLhw/TqhSR+6+SVaoPJBAWciFEuJiNQh275OnjRrAi7yssZzuWBRw66VG2g6UA==
eslint-plugin-prettier@~4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz#651cbb88b1dab98bfd42f017a12fa6b2d993f94b"
integrity sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==
dependencies:
prettier-linter-helpers "^1.0.0"
@ -3712,9 +3713,9 @@ listr2@^4.0.5:
wrap-ansi "^7.0.0"
lit-element@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-3.2.0.tgz#9c981c55dfd9a8f124dc863edb62cc529d434db7"
integrity sha512-HbE7yt2SnUtg5DCrWt028oaU4D5F4k/1cntAFHTkzY8ZIa8N0Wmu92PxSxucsQSOXlODFrICkQ5x/tEshKi13g==
version "3.2.1"
resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-3.2.1.tgz#f917d22451b848768f84164d41eb5e18903986e3"
integrity sha512-2PxyE9Yq9Jyo/YBK2anycaHcqo93YvB5D+24JxloPVqryW/BOXekne+jGsm0Ke3E5E2v7CDgkmpEmCAzYfrHCQ==
dependencies:
"@lit/reactive-element" "^1.3.0"
lit-html "^2.2.0"
@ -3727,9 +3728,9 @@ lit-html@^2.2.0:
"@types/trusted-types" "^2.0.2"
lit@^2.2.6:
version "2.2.6"
resolved "https://registry.yarnpkg.com/lit/-/lit-2.2.6.tgz#4ef223e88517c000b0c01baf2e3535e61a75a5b5"
integrity sha512-K2vkeGABfSJSfkhqHy86ujchJs3NR9nW1bEEiV+bXDkbiQ60Tv5GUausYN2mXigZn8lC1qXuc46ArQRKYmumZw==
version "2.2.7"
resolved "https://registry.yarnpkg.com/lit/-/lit-2.2.7.tgz#a563e8851db1f131912f510129dcc9a42324e838"
integrity sha512-WXYujlKFwme5ZqXOZoWuRVZQAwy7scbcVT3wCbAOHefOxyscqjywWGlF2e6nnC9E64yP9l2ZQlN8wZcRlrjUMQ==
dependencies:
"@lit/reactive-element" "^1.3.0"
lit-element "^3.2.0"