mirror of
https://github.com/jakejarvis/jarv.is.git
synced 2025-04-26 09:45:22 -04:00
add page stats typings
This commit is contained in:
parent
ae2cb50cbe
commit
7da0a713a4
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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>
|
||||
<Icon as={DateIcon} />
|
||||
<Time date={date} format="MMMM D, YYYY" />
|
||||
</MetaLink>
|
||||
</MetaItem>
|
||||
|
||||
{tags.length > 0 && (
|
||||
{tags && (
|
||||
<MetaItem>
|
||||
<span title="Tags">
|
||||
<Icon as={TagIcon} />
|
||||
</span>
|
||||
<Icon as={TagIcon} />
|
||||
<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>
|
||||
<Icon as={EditIcon} />
|
||||
<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
|
||||
// fix potential layout shift when number of hits loads
|
||||
css={{ minWidth: "7em", marginRight: 0 }}
|
||||
css={{
|
||||
// fix potential layout shift when number of hits loads
|
||||
minWidth: "7em",
|
||||
marginRight: 0,
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<Icon as={ViewsIcon} />
|
||||
</span>
|
||||
<Icon as={ViewsIcon} />
|
||||
<HitCounter slug={`notes/${slug}`} />
|
||||
</MetaItem>
|
||||
)}
|
||||
|
@ -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",
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
2
types/index.d.ts
vendored
@ -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
15
types/stats.d.ts
vendored
Normal 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
7
types/track.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
export type Track = {
|
||||
artist: string;
|
||||
title: string;
|
||||
album: string;
|
||||
url: URL | string;
|
||||
image?: URL | string;
|
||||
};
|
49
yarn.lock
49
yarn.lock
@ -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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user