From 423f26c7fb8fa3ee0d7e63931195e698eee527fc Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sun, 5 Feb 2023 16:26:02 -0800 Subject: [PATCH 01/10] Fix lint issues. --- pages/api/me/websites.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 pages/api/me/websites.ts diff --git a/pages/api/me/websites.ts b/pages/api/me/websites.ts new file mode 100644 index 00000000..e69de29b From 94726239aafc2db7c27641807beed23360b2b0b6 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sun, 5 Feb 2023 16:26:02 -0800 Subject: [PATCH 02/10] Added /me/websites. --- pages/api/me/websites.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 pages/api/me/websites.ts diff --git a/pages/api/me/websites.ts b/pages/api/me/websites.ts new file mode 100644 index 00000000..e69de29b From ad5e34a894e87cb8316fcde447db130dec7ae842 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Tue, 7 Feb 2023 16:29:25 -0800 Subject: [PATCH 03/10] Fixed lint issues. --- .eslintrc.json | 6 ++- components/input/RefreshButton.js | 2 +- components/pages/websites/WebsiteDetails.js | 16 +------ components/pages/websites/WebsiteMenuView.js | 2 +- lib/types.ts | 2 - pages/api/collect.ts | 8 ++-- pages/api/me/websites.ts | 31 +++++++++++++ pages/api/users/[id]/websites.ts | 47 ++++---------------- pages/api/websites/[id]/metrics.ts | 12 ++--- pages/api/websites/index.ts | 4 +- 10 files changed, 58 insertions(+), 72 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index f279e226..de639b85 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -20,7 +20,7 @@ "next" ], - "plugins": ["@typescript-eslint","prettier"], + "plugins": ["@typescript-eslint", "prettier"], "settings": { "import/resolver": { "alias": { @@ -46,7 +46,9 @@ "react/react-in-jsx-scope": "off", "react/prop-types": "off", "import/no-anonymous-default-export": "off", - "@next/next/no-img-element": "off" + "@next/next/no-img-element": "off", + "@typescript-eslint/no-empty-function": "off", + "@typescript-eslint/no-explicit-any": "off" }, "globals": { "React": "writable" diff --git a/components/input/RefreshButton.js b/components/input/RefreshButton.js index b223fecf..635a6270 100644 --- a/components/input/RefreshButton.js +++ b/components/input/RefreshButton.js @@ -1,6 +1,6 @@ import { useState, useEffect, useCallback } from 'react'; import { useIntl } from 'react-intl'; -import { Button, Icon, Tooltip } from '../react-basics'; +import { Button, Icon, Tooltip } from 'react-basics'; import useStore from 'store/queries'; import { setDateRange } from 'store/websites'; import useDateRange from 'hooks/useDateRange'; diff --git a/components/pages/websites/WebsiteDetails.js b/components/pages/websites/WebsiteDetails.js index 33cc179b..3e758599 100644 --- a/components/pages/websites/WebsiteDetails.js +++ b/components/pages/websites/WebsiteDetails.js @@ -3,13 +3,12 @@ import { Icons, Loading } from 'react-basics'; import { useIntl } from 'react-intl'; import Link from 'next/link'; import classNames from 'classnames'; -import MenuLayout from 'components/layout/MenuLayout'; import Page from 'components/layout/Page'; import WebsiteChart from 'components/metrics/WebsiteChart'; import useApi from 'hooks/useApi'; import usePageQuery from 'hooks/usePageQuery'; import { DEFAULT_ANIMATION_DURATION } from 'lib/constants'; -import { labels, messages } from 'components/messages'; +import { labels } from 'components/messages'; import styles from './WebsiteDetails.module.css'; import WebsiteTableView from './WebsiteTableView'; import WebsiteMenuView from './WebsiteMenuView'; @@ -27,19 +26,6 @@ export default function WebsiteDetails({ websiteId }) { query: { view }, } = usePageQuery(); - const BackButton = () => ( -
- } - sizes="small" - > - {formatMessage(labels.back)} - -
- ); - function handleDataLoad() { if (!chartLoaded) { setTimeout(() => setChartLoaded(true), DEFAULT_ANIMATION_DURATION); diff --git a/components/pages/websites/WebsiteMenuView.js b/components/pages/websites/WebsiteMenuView.js index f1a1b7fb..6b1f6627 100644 --- a/components/pages/websites/WebsiteMenuView.js +++ b/components/pages/websites/WebsiteMenuView.js @@ -1,4 +1,4 @@ -import { Row, Column, Menu, Item, Icon, Text, Button } from 'react-basics'; +import { Row, Column, Menu, Item, Icon, Button } from 'react-basics'; import Link from 'next/link'; import BrowsersTable from 'components/metrics/BrowsersTable'; import CountriesTable from 'components/metrics/CountriesTable'; diff --git a/lib/types.ts b/lib/types.ts index 9dc01719..8b08a343 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -39,8 +39,6 @@ export interface Share { token: string; } -export interface Empty {} - export interface WebsiteActive { x: number; } diff --git a/pages/api/collect.ts b/pages/api/collect.ts index 68c4afb2..63bbdf1b 100644 --- a/pages/api/collect.ts +++ b/pages/api/collect.ts @@ -1,12 +1,12 @@ -const { Resolver } = require('dns').promises; import isbot from 'isbot'; import ipaddr from 'ipaddr.js'; -import { createToken, unauthorized, send, badRequest, forbidden } from 'next-basics'; +import { createToken, ok, send, badRequest, forbidden } from 'next-basics'; import { savePageView, saveEvent } from 'queries'; import { useCors, useSession } from 'lib/middleware'; import { getJsonBody, getIpAddress } from 'lib/detect'; import { secret } from 'lib/crypto'; import { NextApiRequest, NextApiResponse } from 'next'; +import { Resolver } from 'dns/promises'; export interface NextApiRequestCollect extends NextApiRequest { session: { @@ -26,7 +26,7 @@ export default async (req: NextApiRequestCollect, res: NextApiResponse) => { await useCors(req, res); if (isbot(req.headers['user-agent']) && !process.env.DISABLE_BOT_CHECK) { - return unauthorized(res); + return ok(res); } const { type, payload } = getJsonBody(req); @@ -61,7 +61,7 @@ export default async (req: NextApiRequestCollect, res: NextApiResponse) => { .map(n => resolver.resolve4(n.trim()).catch(() => {})); await Promise.all(promises).then(resolvedIps => { - ips.push(...resolvedIps.filter(n => n).flatMap(n => n)); + ips.push(...resolvedIps.filter(n => n).flatMap(n => n as string[])); }); } diff --git a/pages/api/me/websites.ts b/pages/api/me/websites.ts index e69de29b..15ea2485 100644 --- a/pages/api/me/websites.ts +++ b/pages/api/me/websites.ts @@ -0,0 +1,31 @@ +import { useAuth, useCors } from 'lib/middleware'; +import { NextApiRequestQueryBody } from 'lib/types'; +import { NextApiResponse } from 'next'; +import { methodNotAllowed, ok } from 'next-basics'; +import { getUserWebsites } from 'queries'; + +export interface WebsitesRequestBody { + name: string; + domain: string; + shareId: string; +} + +export default async ( + req: NextApiRequestQueryBody, + res: NextApiResponse, +) => { + await useCors(req, res); + await useAuth(req, res); + + const { + user: { id: userId }, + } = req.auth; + + if (req.method === 'GET') { + const websites = await getUserWebsites(userId); + + return ok(res, websites); + } + + return methodNotAllowed(res); +}; diff --git a/pages/api/users/[id]/websites.ts b/pages/api/users/[id]/websites.ts index 7a6f7dc1..c8b874bb 100644 --- a/pages/api/users/[id]/websites.ts +++ b/pages/api/users/[id]/websites.ts @@ -1,63 +1,34 @@ -import { Prisma } from '@prisma/client'; -import { canCreateWebsite } from 'lib/auth'; -import { uuid } from 'lib/crypto'; import { useAuth, useCors } from 'lib/middleware'; import { NextApiRequestQueryBody } from 'lib/types'; import { NextApiResponse } from 'next'; import { methodNotAllowed, ok, unauthorized } from 'next-basics'; -import { createWebsite, getUserWebsites } from 'queries'; - -export interface WebsitesRequestQuery {} +import { getUserWebsites } from 'queries'; export interface WebsitesRequestBody { name: string; domain: string; shareId: string; - teamId?: string; } export default async ( - req: NextApiRequestQueryBody, + req: NextApiRequestQueryBody, res: NextApiResponse, ) => { await useCors(req, res); await useAuth(req, res); - const { - user: { id: userId }, - } = req.auth; - const { id } = req.query; + const { user } = req.auth; + const { id: userId } = req.query; if (req.method === 'GET') { - const websites = await getUserWebsites(id as string); + if (!user.isAdmin && user.id !== userId) { + return unauthorized(res); + } + + const websites = await getUserWebsites(userId); return ok(res, websites); } - if (req.method === 'POST') { - const { name, domain, shareId, teamId } = req.body; - - if (!(await canCreateWebsite(req.auth, teamId))) { - return unauthorized(res); - } - - const data: Prisma.WebsiteUncheckedCreateInput = { - id: uuid(), - name, - domain, - shareId, - }; - - if (teamId) { - data.teamId = teamId; - } else { - data.userId = userId; - } - - const website = await createWebsite(data); - - return ok(res, website); - } - return methodNotAllowed(res); }; diff --git a/pages/api/websites/[id]/metrics.ts b/pages/api/websites/[id]/metrics.ts index c6375f08..94358076 100644 --- a/pages/api/websites/[id]/metrics.ts +++ b/pages/api/websites/[id]/metrics.ts @@ -90,15 +90,15 @@ export default async ( }); if (type === 'language') { - let combined = {}; + const combined = {}; - for (let { x, y } of data) { - x = String(x).toLowerCase().split('-')[0]; + for (const { x, y } of data) { + const key = String(x).toLowerCase().split('-')[0]; - if (!combined[x]) { - combined[x] = { x, y }; + if (!combined[key]) { + combined[key] = { x, y }; } else { - combined[x].y += y; + combined[key].y += y; } } diff --git a/pages/api/websites/index.ts b/pages/api/websites/index.ts index b1e3204d..52c3ba35 100644 --- a/pages/api/websites/index.ts +++ b/pages/api/websites/index.ts @@ -6,8 +6,6 @@ import { NextApiResponse } from 'next'; import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { createWebsite, getUserWebsites } from 'queries'; -export interface WebsitesRequestQuery {} - export interface WebsitesRequestBody { name: string; domain: string; @@ -16,7 +14,7 @@ export interface WebsitesRequestBody { } export default async ( - req: NextApiRequestQueryBody, + req: NextApiRequestQueryBody, res: NextApiResponse, ) => { await useCors(req, res); From 87bbaa7f1dd9828c90b7864f8e411d15870837e5 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Tue, 7 Feb 2023 22:17:55 -0800 Subject: [PATCH 04/10] Fixed tooltips. --- components/input/LanguageButton.js | 5 +-- components/input/LogoutButton.js | 7 ++-- components/layout/NavBar.module.css | 9 ++++- components/pages/websites/WebsiteMenuView.js | 2 +- package.json | 3 +- yarn.lock | 41 +++----------------- 6 files changed, 19 insertions(+), 48 deletions(-) diff --git a/components/input/LanguageButton.js b/components/input/LanguageButton.js index 687f639f..f01b4716 100644 --- a/components/input/LanguageButton.js +++ b/components/input/LanguageButton.js @@ -18,14 +18,13 @@ export default function LanguageButton({ tooltipPosition = 'top' }) { return ( - + - {formatMessage(labels.language)} - +
{items.map(({ value, label }) => { diff --git a/components/input/LogoutButton.js b/components/input/LogoutButton.js index f6f2450e..31b4317d 100644 --- a/components/input/LogoutButton.js +++ b/components/input/LogoutButton.js @@ -1,4 +1,4 @@ -import { Button, Icon, Icons, PopupTrigger, Tooltip } from 'react-basics'; +import { Button, Icon, Icons, Tooltip } from 'react-basics'; import Link from 'next/link'; import { labels } from 'components/messages'; import { useIntl } from 'react-intl'; @@ -8,14 +8,13 @@ export default function LogoutButton({ tooltipPosition = 'top' }) { return ( - + - {formatMessage(labels.logout)} - + ); diff --git a/components/layout/NavBar.module.css b/components/layout/NavBar.module.css index 6610e602..1d387643 100644 --- a/components/layout/NavBar.module.css +++ b/components/layout/NavBar.module.css @@ -25,9 +25,14 @@ } .icon { - position: absolute; - right: 0; visibility: hidden; + position: absolute; + right: -10px; + border-radius: 100%; + color: var(--base50); + background: var(--base800); + height: 20px; + width: 20px; } .minimized.navbar { diff --git a/components/pages/websites/WebsiteMenuView.js b/components/pages/websites/WebsiteMenuView.js index 6b1f6627..f9717688 100644 --- a/components/pages/websites/WebsiteMenuView.js +++ b/components/pages/websites/WebsiteMenuView.js @@ -12,7 +12,7 @@ import ScreenTable from 'components/metrics/ScreenTable'; import EventsTable from 'components/metrics/EventsTable'; import usePageQuery from 'hooks/usePageQuery'; import Icons from 'components/icons'; -import { labels } from '../../messages'; +import { labels } from 'components/messages'; import { useIntl } from 'react-intl'; const views = { diff --git a/package.json b/package.json index f9543a67..384eacdf 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "@prisma/client": "4.9.0", "@tanstack/react-query": "^4.16.1", "@umami/prisma-client": "^0.2.0", - "@umami/redis-client": "^0.1.0", + "@umami/redis-client": "^0.2.0", "chalk": "^4.1.1", "chart.js": "^2.9.4", "classnames": "^2.3.1", @@ -103,7 +103,6 @@ "react-tooltip": "^4.2.21", "react-use-measure": "^2.0.4", "react-window": "^1.8.6", - "redis": "^4.6.2", "request-ip": "^3.3.0", "semver": "^7.3.6", "thenby": "^1.3.4", diff --git a/yarn.lock b/yarn.lock index c4a2b397..96e53d7f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1930,11 +1930,6 @@ resolved "https://registry.yarnpkg.com/@redis/bloom/-/bloom-1.1.0.tgz#64e310ddee72010676e14296076329e594a1f6c7" integrity sha512-9QovlxmpRtvxVbN0UBcv8WfdSMudNZZTFqCsnBszcQXqaZb/TVe30ScgGEO7u1EAIacTPAo7/oCYjYAxiHLanQ== -"@redis/bloom@1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@redis/bloom/-/bloom-1.2.0.tgz#d3fd6d3c0af3ef92f26767b56414a370c7b63b71" - integrity sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg== - "@redis/client@1.4.2": version "1.4.2" resolved "https://registry.yarnpkg.com/@redis/client/-/client-1.4.2.tgz#2a3f5e98bc33b7b979390442e6e08f96e57fabdd" @@ -1944,15 +1939,6 @@ generic-pool "3.9.0" yallist "4.0.0" -"@redis/client@1.5.3": - version "1.5.3" - resolved "https://registry.yarnpkg.com/@redis/client/-/client-1.5.3.tgz#2295ca770c9c40dcc59a96da9d05f4403c32e847" - integrity sha512-kPad3QmWyRcmFj1gnb+SkzjXBV7oPpyTJmasVA+ocgNClxqZaTJjLFReqxm9cZQiCtqZK9vrcTISNrgzQXFpLg== - dependencies: - cluster-key-slot "1.1.2" - generic-pool "3.9.0" - yallist "4.0.0" - "@redis/graph@1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@redis/graph/-/graph-1.1.0.tgz#cc2b82e5141a29ada2cce7d267a6b74baa6dd519" @@ -1968,11 +1954,6 @@ resolved "https://registry.yarnpkg.com/@redis/search/-/search-1.1.0.tgz#7abb18d431f27ceafe6bcb4dd83a3fa67e9ab4df" integrity sha512-NyFZEVnxIJEybpy+YskjgOJRNsfTYqaPbK/Buv6W2kmFNaRk85JiqjJZA5QkRmWvGbyQYwoO5QfDi2wHskKrQQ== -"@redis/search@1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@redis/search/-/search-1.1.1.tgz#f547b76b74f267831d3b368e3d7bba3a6a9e32bd" - integrity sha512-pqCXTc5e7wJJgUuJiC3hBgfoFRoPxYzwn0BEfKgejTM7M/9zP3IpUcqcjgfp8hF+LoV8rHZzcNTz7V+pEIY7LQ== - "@redis/time-series@1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@redis/time-series/-/time-series-1.0.4.tgz#af85eb080f6934580e4d3b58046026b6c2b18717" @@ -2435,10 +2416,10 @@ dependencies: debug "^4.3.4" -"@umami/redis-client@^0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@umami/redis-client/-/redis-client-0.1.0.tgz#69599b1243406a3aef83927bb8655a3ef4c0c031" - integrity sha512-XCqCdc3xA5KDOorIUvOVlz+/F/SEyL9cKWfCCGYYZix1b9yFo+5BzM0C0q7Yu/qV+1jYd+viNsBQQSM6a8sfjg== +"@umami/redis-client@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@umami/redis-client/-/redis-client-0.2.0.tgz#bdb1cd8b5c99afc5230621f19296c6d3559d68af" + integrity sha512-TONWhkuC//K2hRo3Psk7FHsuvu3XkQIYMY62/CERPtlIJz4Ac7DqsmYw4jO9/RkljA9XLl/5u+OggD4ARhMV8A== dependencies: debug "^4.3.4" redis "^4.5.1" @@ -3147,7 +3128,7 @@ cluster-key-slot@1.1.1: resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.1.tgz#10ccb9ded0729464b6d2e7d714b100a2d1259d43" integrity sha512-rwHwUfXL40Chm1r08yrhU3qpUvdVlgkKNeyeGPOxnW8/SyVDvgRaed/Uz54AqWNaTCAThlj6QAs3TZcKI0xDEw== -cluster-key-slot@1.1.2, cluster-key-slot@^1.1.0: +cluster-key-slot@^1.1.0: version "1.1.2" resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac" integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA== @@ -6932,18 +6913,6 @@ redis@^4.5.1: "@redis/search" "1.1.0" "@redis/time-series" "1.0.4" -redis@^4.6.2: - version "4.6.2" - resolved "https://registry.yarnpkg.com/redis/-/redis-4.6.2.tgz#5592db7b95c1b6652cde463e58a8029a1f7890bb" - integrity sha512-Xoh7UyU6YnT458xA8svaZAJu6ZunKeW7Z/7GXrLWGGwhVLTsDX6pr3u7ENAoV+DHBPO+9LwIu45ClwUwpIjAxw== - dependencies: - "@redis/bloom" "1.2.0" - "@redis/client" "1.5.3" - "@redis/graph" "1.1.0" - "@redis/json" "1.0.4" - "@redis/search" "1.1.1" - "@redis/time-series" "1.0.4" - redux@^4.0.0, redux@^4.0.4: version "4.2.0" resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.0.tgz#46f10d6e29b6666df758780437651eeb2b969f13" From 45c13da2625cb698e80ff8aa6611e23e54b862e3 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Wed, 8 Feb 2023 23:14:11 -0800 Subject: [PATCH 05/10] More deletes. Fixed sticky header. --- components/common/Icon.js | 28 ------ components/common/Icon.module.css | 35 -------- components/common/Link.js | 34 -------- components/common/Link.module.css | 42 --------- components/common/Menu.js | 69 --------------- components/common/Menu.module.css | 49 ----------- components/common/MenuButton.js | 87 ------------------- components/common/MenuButton.module.css | 20 ----- components/common/NavMenu.js | 46 ---------- components/common/NavMenu.module.css | 22 ----- components/helpers/StickyHeader.js | 43 ++------- components/layout/AppLayout.module.css | 4 +- components/layout/Footer.js | 7 +- components/layout/GridLayout.js | 30 ------- components/layout/GridRow.js | 18 ---- components/layout/GridRow.module.css | 21 ----- components/metrics/MetricCard.module.css | 7 +- components/metrics/MetricsTable.js | 22 ++--- components/metrics/WebsiteChart.js | 6 +- components/pages/websites/WebsiteDetails.js | 2 +- components/pages/websites/WebsiteTableView.js | 27 +++--- .../websites/WebsiteTableView.module.css} | 5 -- hooks/useSticky.js | 27 ++++++ 23 files changed, 69 insertions(+), 582 deletions(-) delete mode 100644 components/common/Icon.js delete mode 100644 components/common/Icon.module.css delete mode 100644 components/common/Link.js delete mode 100644 components/common/Link.module.css delete mode 100644 components/common/Menu.js delete mode 100644 components/common/Menu.module.css delete mode 100644 components/common/MenuButton.js delete mode 100644 components/common/MenuButton.module.css delete mode 100644 components/common/NavMenu.js delete mode 100644 components/common/NavMenu.module.css delete mode 100644 components/layout/GridLayout.js delete mode 100644 components/layout/GridRow.js delete mode 100644 components/layout/GridRow.module.css rename components/{layout/GridLayout.module.css => pages/websites/WebsiteTableView.module.css} (90%) create mode 100644 hooks/useSticky.js diff --git a/components/common/Icon.js b/components/common/Icon.js deleted file mode 100644 index 362d5376..00000000 --- a/components/common/Icon.js +++ /dev/null @@ -1,28 +0,0 @@ -import PropTypes from 'prop-types'; -import classNames from 'classnames'; -import styles from './Icon.module.css'; - -function Icon({ icon, className, size = 'medium', ...props }) { - return ( -
- {icon} -
- ); -} - -Icon.propTypes = { - className: PropTypes.string, - icon: PropTypes.node.isRequired, - size: PropTypes.oneOf(['xlarge', 'large', 'medium', 'small', 'xsmall']), -}; - -export default Icon; diff --git a/components/common/Icon.module.css b/components/common/Icon.module.css deleted file mode 100644 index 5b431668..00000000 --- a/components/common/Icon.module.css +++ /dev/null @@ -1,35 +0,0 @@ -.icon { - display: inline-flex; - justify-content: center; - align-items: center; - vertical-align: middle; -} - -.icon svg { - fill: currentColor; -} - -.xlarge > svg { - width: 48px; - height: 48px; -} - -.large > svg { - width: 24px; - height: 24px; -} - -.medium > svg { - width: 16px; - height: 16px; -} - -.small > svg { - width: 12px; - height: 12px; -} - -.xsmall > svg { - width: 10px; - height: 10px; -} diff --git a/components/common/Link.js b/components/common/Link.js deleted file mode 100644 index 3721a9a7..00000000 --- a/components/common/Link.js +++ /dev/null @@ -1,34 +0,0 @@ -import PropTypes from 'prop-types'; -import classNames from 'classnames'; -import NextLink from 'next/link'; -import { Icon } from 'react-basics'; -import styles from './Link.module.css'; - -function Link({ className, icon, children, size, iconRight, onClick, ...props }) { - return ( - - - {icon && } - {children} - - - ); -} - -Link.propTypes = { - className: PropTypes.string, - icon: PropTypes.node, - children: PropTypes.node, - size: PropTypes.oneOf(['large', 'small', 'xsmall']), - iconRight: PropTypes.bool, -}; - -export default Link; diff --git a/components/common/Link.module.css b/components/common/Link.module.css deleted file mode 100644 index 0476bd92..00000000 --- a/components/common/Link.module.css +++ /dev/null @@ -1,42 +0,0 @@ -a.link, -a.link:active, -a.link:visited { - position: relative; - color: var(--base900); - text-decoration: none; - display: inline-flex; - align-items: center; -} - -a.link span { - border-bottom: 2px solid transparent; -} - -a.link:hover span { - border-bottom: 2px solid var(--primary400); -} - -a.link.large { - font-size: var(--font-size-lg); -} - -a.link.small { - font-size: var(--font-size-sm); -} - -a.link.xsmall { - font-size: var(--font-size-xs); -} - -a.link .icon + * { - margin-left: 10px; -} - -a.link.iconRight .icon { - order: 1; - margin-left: 10px; -} - -a.link.iconRight .icon + * { - margin: 0; -} diff --git a/components/common/Menu.js b/components/common/Menu.js deleted file mode 100644 index 771ed05d..00000000 --- a/components/common/Menu.js +++ /dev/null @@ -1,69 +0,0 @@ -import PropTypes from 'prop-types'; -import classNames from 'classnames'; -import styles from './Menu.module.css'; - -function Menu({ - options = [], - selectedOption, - className, - float, - align = 'left', - optionClassName, - selectedClassName, - onSelect = () => {}, -}) { - return ( -
- {options - .filter(({ hidden }) => !hidden) - .map(option => { - const { label, value, className: customClassName, render, divider } = option; - - return render ? ( - render(option) - ) : ( -
onSelect(value, e)} - > - {label} -
- ); - })} -
- ); -} - -Menu.propTypes = { - options: PropTypes.arrayOf( - PropTypes.shape({ - label: PropTypes.node, - value: PropTypes.any, - className: PropTypes.string, - render: PropTypes.func, - divider: PropTypes.bool, - }), - ), - selectedOption: PropTypes.any, - className: PropTypes.string, - float: PropTypes.oneOf(['top', 'bottom']), - align: PropTypes.oneOf(['left', 'right']), - optionClassName: PropTypes.string, - selectedClassName: PropTypes.string, - onSelect: PropTypes.func, -}; - -export default Menu; diff --git a/components/common/Menu.module.css b/components/common/Menu.module.css deleted file mode 100644 index d9a23371..00000000 --- a/components/common/Menu.module.css +++ /dev/null @@ -1,49 +0,0 @@ -.menu { - background: var(--base50); - border: 1px solid var(--base500); - border-radius: 4px; - overflow: hidden; - z-index: 100; -} - -.option { - background: var(--base50); - padding: 4px 16px; - cursor: pointer; - white-space: nowrap; -} - -.option:hover { - background: var(--base100); -} - -.float { - position: absolute; - min-width: 100px; -} - -.top { - bottom: 100%; - margin-bottom: 5px; -} - -.bottom { - top: 100%; - margin-top: 5px; -} - -.left { - left: 0; -} - -.right { - right: 0; -} - -.divider { - border-top: 1px solid var(--base300); -} - -.selected { - font-weight: 600; -} diff --git a/components/common/MenuButton.js b/components/common/MenuButton.js deleted file mode 100644 index 69a448d0..00000000 --- a/components/common/MenuButton.js +++ /dev/null @@ -1,87 +0,0 @@ -import { useState, useRef } from 'react'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; -import Menu from 'components/common/Menu'; -import useDocumentClick from 'hooks/useDocumentClick'; -import styles from './MenuButton.module.css'; -import { Button } from 'react-basics'; - -function MenuButton({ - children, - value, - options, - buttonClassName, - buttonVariant, - menuClassName, - menuPosition = 'bottom', - menuAlign = 'right', - onSelect, - renderValue, - hideLabel, -}) { - const [showMenu, setShowMenu] = useState(false); - const ref = useRef(); - const selectedOption = options.find(e => e.value === value); - - function handleSelect(value) { - onSelect(value); - setShowMenu(false); - } - - function toggleMenu() { - setShowMenu(state => !state); - } - - useDocumentClick(e => { - if (!ref.current?.contains(e.target)) { - setShowMenu(false); - } - }); - - return ( -
- - {showMenu && ( - - )} -
- ); -} - -MenuButton.propTypes = { - icon: PropTypes.node, - value: PropTypes.any, - options: PropTypes.arrayOf( - PropTypes.shape({ - label: PropTypes.node, - value: PropTypes.any, - className: PropTypes.string, - render: PropTypes.func, - divider: PropTypes.bool, - }), - ), - buttonClassName: PropTypes.string, - menuClassName: PropTypes.string, - menuPosition: PropTypes.oneOf(['top', 'bottom']), - menuAlign: PropTypes.oneOf(['left', 'right']), - onSelect: PropTypes.func, - renderValue: PropTypes.func, -}; - -export default MenuButton; diff --git a/components/common/MenuButton.module.css b/components/common/MenuButton.module.css deleted file mode 100644 index a59ae562..00000000 --- a/components/common/MenuButton.module.css +++ /dev/null @@ -1,20 +0,0 @@ -.container { - display: flex; - position: relative; - cursor: pointer; -} - -.button { - border: 1px solid transparent; - border-radius: 4px; -} - -.text { - font-size: var(--font-size-sm); -} - -.open, -.open:hover { - background: var(--base50); - border: 1px solid var(--base500); -} diff --git a/components/common/NavMenu.js b/components/common/NavMenu.js deleted file mode 100644 index 8994daa0..00000000 --- a/components/common/NavMenu.js +++ /dev/null @@ -1,46 +0,0 @@ -import PropTypes from 'prop-types'; -import { useRouter } from 'next/router'; -import classNames from 'classnames'; -import styles from './NavMenu.module.css'; - -function NavMenu({ options = [], className, onSelect = () => {} }) { - const router = useRouter(); - - return ( -
- {options - .filter(({ hidden }) => !hidden) - .map(option => { - const { label, value, className: customClassName, render } = option; - - return render ? ( - render(option) - ) : ( -
onSelect(value, e)} - > - {label} -
- ); - })} -
- ); -} - -NavMenu.propTypes = { - options: PropTypes.arrayOf( - PropTypes.shape({ - label: PropTypes.node, - value: PropTypes.any, - className: PropTypes.string, - render: PropTypes.func, - }), - ), - className: PropTypes.string, - onSelect: PropTypes.func, -}; -export default NavMenu; diff --git a/components/common/NavMenu.module.css b/components/common/NavMenu.module.css deleted file mode 100644 index e0b01945..00000000 --- a/components/common/NavMenu.module.css +++ /dev/null @@ -1,22 +0,0 @@ -.menu { - color: var(--base800); - border: 1px solid var(--base500); - border-radius: 4px; - overflow: hidden; - z-index: 2; -} - -.option { - padding: 8px 16px; - cursor: pointer; - border-radius: 4px; -} - -.option:hover { - background: var(--base75); -} - -.selected { - color: var(--base900); - font-weight: 600; -} diff --git a/components/helpers/StickyHeader.js b/components/helpers/StickyHeader.js index 7f97b12d..f46dd3c2 100644 --- a/components/helpers/StickyHeader.js +++ b/components/helpers/StickyHeader.js @@ -1,46 +1,15 @@ -import { useState, useRef, useEffect } from 'react'; import classNames from 'classnames'; +import useSticky from 'hooks/useSticky'; -export default function StickyHeader({ - className, - stickyClassName, - stickyStyle, - children, - enabled = true, -}) { - const [sticky, setSticky] = useState(false); - const ref = useRef(); - const top = useRef(0); - - useEffect(() => { - const checkPosition = () => { - if (ref.current) { - if (!top.current) { - top.current = ref.current.offsetTop + ref.current.offsetHeight; - } - const state = window.pageYOffset > top.current; - if (sticky !== state) { - setSticky(state); - } - } - }; - - if (enabled) { - checkPosition(); - window.addEventListener('scroll', checkPosition); - } - - return () => { - window.removeEventListener('scroll', checkPosition); - }; - }, [sticky, enabled]); +export default function StickyHeader({ className, stickyClassName, stickyStyle, children }) { + const { ref, isSticky } = useSticky(); return (
{children}
diff --git a/components/layout/AppLayout.module.css b/components/layout/AppLayout.module.css index 71d61ada..7b83a24a 100644 --- a/components/layout/AppLayout.module.css +++ b/components/layout/AppLayout.module.css @@ -2,12 +2,12 @@ display: grid; grid-template-rows: 1fr; grid-template-columns: max-content 1fr; - height: 100vh; - overflow: hidden; } .nav { grid-row: 1 / 3; + height: 100vh; + position: fixed; } .body { diff --git a/components/layout/Footer.js b/components/layout/Footer.js index ec1571fb..452e070b 100644 --- a/components/layout/Footer.js +++ b/components/layout/Footer.js @@ -3,7 +3,6 @@ import { useRouter } from 'next/router'; import Script from 'next/script'; import classNames from 'classnames'; import { useIntl, defineMessages } from 'react-intl'; -import Link from 'components/common/Link'; import { CURRENT_VERSION, HOMEPAGE_URL, REPO_URL } from 'lib/constants'; import styles from './Footer.module.css'; @@ -22,15 +21,15 @@ export default function Footer({ className }) {
{formatMessage(messages.poweredBy, { name: ( - + umami - + ), })}
- {`v${CURRENT_VERSION}`} + {`v${CURRENT_VERSION}`} {!pathname.includes('/share/') &&