diff --git a/src/components/common/ErrorBoundary.js b/src/components/common/ErrorBoundary.tsx
similarity index 78%
rename from src/components/common/ErrorBoundary.js
rename to src/components/common/ErrorBoundary.tsx
index 32cedb39..4eb2700f 100644
--- a/src/components/common/ErrorBoundary.js
+++ b/src/components/common/ErrorBoundary.tsx
@@ -1,14 +1,19 @@
/* eslint-disable no-console */
+import { ErrorInfo, ReactNode } from 'react';
import { ErrorBoundary as Boundary } from 'react-error-boundary';
import { Button } from 'react-basics';
import useMessages from 'components/hooks/useMessages';
import styles from './ErrorBoundry.module.css';
-const logError = (error, info) => {
+const logError = (error: Error, info: ErrorInfo) => {
console.error(error, info.componentStack);
};
-export function ErrorBoundary({ children }) {
+export interface ErrorBoundaryProps {
+ children: ReactNode;
+}
+
+export function ErrorBoundary({ children }: ErrorBoundaryProps) {
const { formatMessage, messages } = useMessages();
const fallbackRender = ({ error, resetErrorBoundary }) => {
diff --git a/src/components/common/ErrorMessage.js b/src/components/common/ErrorMessage.tsx
similarity index 100%
rename from src/components/common/ErrorMessage.js
rename to src/components/common/ErrorMessage.tsx
diff --git a/src/components/common/Favicon.js b/src/components/common/Favicon.tsx
similarity index 93%
rename from src/components/common/Favicon.js
rename to src/components/common/Favicon.tsx
index 55059cc0..2bf43c77 100644
--- a/src/components/common/Favicon.js
+++ b/src/components/common/Favicon.tsx
@@ -1,6 +1,6 @@
import styles from './Favicon.module.css';
-function getHostName(url) {
+function getHostName(url: string) {
const match = url.match(/^(?:https?:\/\/)?(?:[^@\n]+@)?(?:www\.)?([^:/\n?=]+)/im);
return match && match.length > 1 ? match[1] : null;
}
diff --git a/src/components/common/FilterButtons.js b/src/components/common/FilterButtons.js
deleted file mode 100644
index f5a54fb6..00000000
--- a/src/components/common/FilterButtons.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import { ButtonGroup, Button, Flexbox } from 'react-basics';
-
-export function FilterButtons({ items, selectedKey, onSelect }) {
- return (
-
-
- {({ key, label }) => }
-
-
- );
-}
-
-export default FilterButtons;
diff --git a/src/components/common/FilterButtons.tsx b/src/components/common/FilterButtons.tsx
new file mode 100644
index 00000000..e1860c78
--- /dev/null
+++ b/src/components/common/FilterButtons.tsx
@@ -0,0 +1,20 @@
+import { Key } from 'react';
+import { ButtonGroup, Button, Flexbox } from 'react-basics';
+
+export interface FilterButtonsProps {
+ items: any[];
+ selectedKey?: Key;
+ onSelect: () => void;
+}
+
+export function FilterButtons({ items, selectedKey, onSelect }: FilterButtonsProps) {
+ return (
+
+
+ {({ key, label }) => }
+
+
+ );
+}
+
+export default FilterButtons;
diff --git a/src/components/common/FilterLink.js b/src/components/common/FilterLink.tsx
similarity index 79%
rename from src/components/common/FilterLink.js
rename to src/components/common/FilterLink.tsx
index 89648255..f91e1459 100644
--- a/src/components/common/FilterLink.js
+++ b/src/components/common/FilterLink.tsx
@@ -1,3 +1,4 @@
+import { ReactNode } from 'react';
import { Icon, Icons } from 'react-basics';
import classNames from 'classnames';
import Link from 'next/link';
@@ -6,7 +7,23 @@ import useNavigation from 'components/hooks/useNavigation';
import useMessages from 'components/hooks/useMessages';
import styles from './FilterLink.module.css';
-export function FilterLink({ id, value, label, externalUrl, children, className }) {
+export interface FilterLinkProps {
+ id: string;
+ value: string;
+ label: string;
+ externalUrl: string;
+ className: string;
+ children: ReactNode;
+}
+
+export function FilterLink({
+ id,
+ value,
+ label,
+ externalUrl,
+ children,
+ className,
+}: FilterLinkProps) {
const { formatMessage, labels } = useMessages();
const { makeUrl, query } = useNavigation();
const active = query[id] !== undefined;
diff --git a/src/components/common/HamburgerButton.js b/src/components/common/HamburgerButton.js
deleted file mode 100644
index f97006ef..00000000
--- a/src/components/common/HamburgerButton.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import { Button, Icon } from 'react-basics';
-import { useState } from 'react';
-import MobileMenu from './MobileMenu';
-import Icons from 'components/icons';
-import useMessages from 'components/hooks/useMessages';
-
-export function HamburgerButton() {
- const { formatMessage, labels } = useMessages();
- const [active, setActive] = useState(false);
- const cloudMode = Boolean(process.env.cloudMode);
-
- const menuItems = [
- {
- label: formatMessage(labels.dashboard),
- url: '/dashboard',
- },
- !cloudMode && {
- label: formatMessage(labels.settings),
- url: '/settings',
- children: [
- {
- label: formatMessage(labels.websites),
- url: '/settings/websites',
- },
- {
- label: formatMessage(labels.teams),
- url: '/settings/teams',
- },
- {
- label: formatMessage(labels.users),
- url: '/settings/users',
- },
- {
- label: formatMessage(labels.profile),
- url: '/settings/profile',
- },
- ],
- },
- cloudMode && {
- label: formatMessage(labels.profile),
- url: '/settings/profile',
- },
- !cloudMode && { label: formatMessage(labels.logout), url: '/logout' },
- ].filter(n => n);
-
- const handleClick = () => setActive(state => !state);
- const handleClose = () => setActive(false);
-
- return (
- <>
-
- {active && }
- >
- );
-}
-
-export default HamburgerButton;
diff --git a/src/components/common/HamburgerButton.tsx b/src/components/common/HamburgerButton.tsx
new file mode 100644
index 00000000..380392c8
--- /dev/null
+++ b/src/components/common/HamburgerButton.tsx
@@ -0,0 +1,22 @@
+import { Button, Icon } from 'react-basics';
+import { useState } from 'react';
+import MobileMenu from './MobileMenu';
+import Icons from 'components/icons';
+
+export function HamburgerButton({ menuItems }: { menuItems: any[] }) {
+ const [active, setActive] = useState(false);
+
+ const handleClick = () => setActive(state => !state);
+ const handleClose = () => setActive(false);
+
+ return (
+ <>
+
+ {active && }
+ >
+ );
+}
+
+export default HamburgerButton;
diff --git a/src/components/common/HoverTooltip.js b/src/components/common/HoverTooltip.tsx
similarity index 82%
rename from src/components/common/HoverTooltip.js
rename to src/components/common/HoverTooltip.tsx
index 614841df..e5e31219 100644
--- a/src/components/common/HoverTooltip.js
+++ b/src/components/common/HoverTooltip.tsx
@@ -1,8 +1,8 @@
-import { useEffect, useState } from 'react';
+import { ReactNode, useEffect, useState } from 'react';
import { Tooltip } from 'react-basics';
import styles from './HoverTooltip.module.css';
-export function HoverTooltip({ children }) {
+export function HoverTooltip({ children }: { children: ReactNode }) {
const [position, setPosition] = useState({ x: -1000, y: -1000 });
useEffect(() => {
diff --git a/src/components/common/LinkButton.js b/src/components/common/LinkButton.tsx
similarity index 69%
rename from src/components/common/LinkButton.js
rename to src/components/common/LinkButton.tsx
index a9a8562d..83d95151 100644
--- a/src/components/common/LinkButton.js
+++ b/src/components/common/LinkButton.tsx
@@ -2,8 +2,17 @@ import classNames from 'classnames';
import Link from 'next/link';
import { useLocale } from 'components/hooks';
import styles from './LinkButton.module.css';
+import { ReactNode } from 'react';
-export function LinkButton({ href, className, variant, scroll = true, children }) {
+export interface LinkButtonProps {
+ href: string;
+ className?: string;
+ variant?: string;
+ scroll?: boolean;
+ children?: ReactNode;
+}
+
+export function LinkButton({ href, className, variant, scroll = true, children }: LinkButtonProps) {
const { dir } = useLocale();
return (
diff --git a/src/components/common/MobileMenu.js b/src/components/common/MobileMenu.tsx
similarity index 74%
rename from src/components/common/MobileMenu.js
rename to src/components/common/MobileMenu.tsx
index 83a05dff..e14f0b83 100644
--- a/src/components/common/MobileMenu.js
+++ b/src/components/common/MobileMenu.tsx
@@ -4,12 +4,19 @@ import { usePathname } from 'next/navigation';
import Link from 'next/link';
import styles from './MobileMenu.module.css';
-export function MobileMenu({ items = [], onClose }) {
+export function MobileMenu({
+ items = [],
+ onClose,
+}: {
+ items: any[];
+ className?: string;
+ onClose: () => void;
+}): any {
const pathname = usePathname();
- const Items = ({ items, className }) => (
+ const Items = ({ items, className }: { items: any[]; className?: string }): any => (
- {items.map(({ label, url, children }) => {
+ {items.map(({ label, url, children }: { label: string; url: string; children: any[] }) => {
const selected = pathname.startsWith(url);
return (
diff --git a/src/components/common/Pager.js b/src/components/common/Pager.tsx
similarity index 86%
rename from src/components/common/Pager.js
rename to src/components/common/Pager.tsx
index a21d35d9..2fe7c6db 100644
--- a/src/components/common/Pager.js
+++ b/src/components/common/Pager.tsx
@@ -3,7 +3,15 @@ import { Button, Icon, Icons } from 'react-basics';
import useMessages from 'components/hooks/useMessages';
import styles from './Pager.module.css';
-export function Pager({ page, pageSize, count, onPageChange, className }) {
+export interface PagerProps {
+ page: number;
+ pageSize: number;
+ count: number;
+ onPageChange: (nextPage: number) => void;
+ className?: string;
+}
+
+export function Pager({ page, pageSize, count, onPageChange, className }: PagerProps) {
const { formatMessage, labels } = useMessages();
const maxPage = pageSize && count ? Math.ceil(count / pageSize) : 0;
const lastPage = page === maxPage;
@@ -13,7 +21,7 @@ export function Pager({ page, pageSize, count, onPageChange, className }) {
return null;
}
- const handlePageChange = value => {
+ const handlePageChange = (value: number) => {
const nextPage = page + value;
if (nextPage > 0 && nextPage <= maxPage) {
onPageChange(nextPage);
diff --git a/src/components/declarations.d.ts b/src/components/declarations.d.ts
index 31e44ff3..81533301 100644
--- a/src/components/declarations.d.ts
+++ b/src/components/declarations.d.ts
@@ -1,2 +1,3 @@
declare module '*.css';
declare module '*.svg';
+declare module '*.json';
diff --git a/src/components/hooks/index.js b/src/components/hooks/index.js
index 697d54c3..b851eeb7 100644
--- a/src/components/hooks/index.js
+++ b/src/components/hooks/index.js
@@ -13,7 +13,7 @@ export * from './useMessages';
export * from './useNavigation';
export * from './useReport';
export * from './useReports';
-export * from './useRequireLogin';
+export * from './useLogin';
export * from './useShareToken';
export * from './useSticky';
export * from './useTheme';
diff --git a/src/components/hooks/useConfig.js b/src/components/hooks/useConfig.ts
similarity index 100%
rename from src/components/hooks/useConfig.js
rename to src/components/hooks/useConfig.ts
diff --git a/src/components/hooks/useCountryNames.js b/src/components/hooks/useCountryNames.ts
similarity index 87%
rename from src/components/hooks/useCountryNames.js
rename to src/components/hooks/useCountryNames.ts
index 40611865..22f20666 100644
--- a/src/components/hooks/useCountryNames.js
+++ b/src/components/hooks/useCountryNames.ts
@@ -6,10 +6,10 @@ const countryNames = {
'en-US': enUS,
};
-export function useCountryNames(locale) {
+export function useCountryNames(locale: string) {
const [list, setList] = useState(countryNames[locale] || enUS);
- async function loadData(locale) {
+ async function loadData(locale: string) {
const { data } = await httpGet(`${process.env.basePath}/intl/country/${locale}.json`);
if (data) {
diff --git a/src/components/hooks/useDateRange.js b/src/components/hooks/useDateRange.ts
similarity index 91%
rename from src/components/hooks/useDateRange.js
rename to src/components/hooks/useDateRange.ts
index 1e1b0616..6e70a368 100644
--- a/src/components/hooks/useDateRange.js
+++ b/src/components/hooks/useDateRange.ts
@@ -6,7 +6,7 @@ import websiteStore, { setWebsiteDateRange } from 'store/websites';
import appStore, { setDateRange } from 'store/app';
import useApi from './useApi';
-export function useDateRange(websiteId) {
+export function useDateRange(websiteId: string) {
const { get } = useApi();
const { locale } = useLocale();
const websiteConfig = websiteStore(state => state[websiteId]?.dateRange);
@@ -20,7 +20,7 @@ export function useDateRange(websiteId) {
if (typeof value === 'string') {
if (value === 'all') {
- const result = await get(`/websites/${websiteId}/daterange`);
+ const result: any = await get(`/websites/${websiteId}/daterange`);
const { mindate, maxdate } = result;
const startDate = new Date(mindate);
diff --git a/src/components/hooks/useDocumentClick.js b/src/components/hooks/useDocumentClick.ts
similarity index 77%
rename from src/components/hooks/useDocumentClick.js
rename to src/components/hooks/useDocumentClick.ts
index be3d09be..eefd9366 100644
--- a/src/components/hooks/useDocumentClick.js
+++ b/src/components/hooks/useDocumentClick.ts
@@ -1,6 +1,6 @@
import { useEffect } from 'react';
-export function useDocumentClick(handler) {
+export function useDocumentClick(handler: (event: MouseEvent) => any) {
useEffect(() => {
document.addEventListener('click', handler);
diff --git a/src/components/hooks/useEscapeKey.js b/src/components/hooks/useEscapeKey.js
deleted file mode 100644
index 1a17f18f..00000000
--- a/src/components/hooks/useEscapeKey.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import { useEffect, useCallback } from 'react';
-
-export function useEscapeKey(handler) {
- const escFunction = useCallback(event => {
- if (event.keyCode === 27) {
- handler(event);
- }
- }, []);
-
- useEffect(() => {
- document.addEventListener('keydown', escFunction, false);
-
- return () => {
- document.removeEventListener('keydown', escFunction, false);
- };
- }, [escFunction]);
-
- return null;
-}
-
-export default useEscapeKey;
diff --git a/src/components/hooks/useEscapeKey.ts b/src/components/hooks/useEscapeKey.ts
new file mode 100644
index 00000000..5c3350e7
--- /dev/null
+++ b/src/components/hooks/useEscapeKey.ts
@@ -0,0 +1,21 @@
+import { useEffect, useCallback, KeyboardEvent } from 'react';
+
+export function useEscapeKey(handler: (event: KeyboardEvent) => void) {
+ const escFunction = useCallback((event: KeyboardEvent) => {
+ if (event.key === 'Escape') {
+ handler(event);
+ }
+ }, []);
+
+ useEffect(() => {
+ document.addEventListener('keydown', escFunction as any, false);
+
+ return () => {
+ document.removeEventListener('keydown', escFunction as any, false);
+ };
+ }, [escFunction]);
+
+ return null;
+}
+
+export default useEscapeKey;
diff --git a/src/components/hooks/useFilters.js b/src/components/hooks/useFilters.ts
similarity index 100%
rename from src/components/hooks/useFilters.js
rename to src/components/hooks/useFilters.ts
diff --git a/src/components/hooks/useForceUpdate.js b/src/components/hooks/useForceUpdate.ts
similarity index 100%
rename from src/components/hooks/useForceUpdate.js
rename to src/components/hooks/useForceUpdate.ts
diff --git a/src/components/hooks/useFormat.js b/src/components/hooks/useFormat.ts
similarity index 81%
rename from src/components/hooks/useFormat.js
rename to src/components/hooks/useFormat.ts
index 0e609c48..c1160162 100644
--- a/src/components/hooks/useFormat.js
+++ b/src/components/hooks/useFormat.ts
@@ -9,23 +9,23 @@ export function useFormat() {
const { locale } = useLocale();
const countryNames = useCountryNames(locale);
- const formatBrowser = value => {
+ const formatBrowser = (value: string) => {
return BROWSERS[value] || value;
};
- const formatCountry = value => {
+ const formatCountry = (value: string) => {
return countryNames[value] || value;
};
- const formatRegion = value => {
+ const formatRegion = (value: string) => {
return regions[value] ? regions[value] : value;
};
- const formatDevice = value => {
+ const formatDevice = (value: string) => {
return formatMessage(labels[value] || labels.unknown);
};
- const formatValue = (value, type) => {
+ const formatValue = (value: string, type: string) => {
switch (type) {
case 'browser':
return formatBrowser(value);
diff --git a/src/components/hooks/useLanguageNames.js b/src/components/hooks/useLanguageNames.ts
similarity index 100%
rename from src/components/hooks/useLanguageNames.js
rename to src/components/hooks/useLanguageNames.ts
diff --git a/src/components/hooks/useLocale.js b/src/components/hooks/useLocale.ts
similarity index 100%
rename from src/components/hooks/useLocale.js
rename to src/components/hooks/useLocale.ts
diff --git a/src/components/hooks/useLogin.ts b/src/components/hooks/useLogin.ts
new file mode 100644
index 00000000..a4ac9d3b
--- /dev/null
+++ b/src/components/hooks/useLogin.ts
@@ -0,0 +1,22 @@
+import useApi from 'components/hooks/useApi';
+import useUser from 'components/hooks/useUser';
+
+export function useLogin() {
+ const { get, useQuery } = useApi();
+ const { user, setUser } = useUser();
+
+ const query = useQuery({
+ queryKey: ['login'],
+ queryFn: async () => {
+ const data = await get('/auth/verify');
+
+ setUser(data);
+
+ return data;
+ },
+ });
+
+ return { user, ...query };
+}
+
+export default useLogin;
diff --git a/src/components/hooks/useMessages.js b/src/components/hooks/useMessages.js
deleted file mode 100644
index e3a6c20b..00000000
--- a/src/components/hooks/useMessages.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import { useIntl, FormattedMessage } from 'react-intl';
-import { messages, labels } from 'components/messages';
-
-export function useMessages() {
- const intl = useIntl();
-
- const getMessage = id => {
- const message = Object.values(messages).find(value => value.id === id);
-
- return message ? formatMessage(message) : id;
- };
-
- const formatMessage = (descriptor, values, opts) => {
- return descriptor ? intl.formatMessage(descriptor, values, opts) : null;
- };
-
- return { formatMessage, FormattedMessage, messages, labels, getMessage };
-}
-
-export default useMessages;
diff --git a/src/components/hooks/useMessages.ts b/src/components/hooks/useMessages.ts
new file mode 100644
index 00000000..594a3c61
--- /dev/null
+++ b/src/components/hooks/useMessages.ts
@@ -0,0 +1,30 @@
+import { useIntl, FormattedMessage, MessageDescriptor, PrimitiveType } from 'react-intl';
+import { messages, labels } from 'components/messages';
+import { FormatXMLElementFn, Options } from 'intl-messageformat';
+
+export function useMessages(): any {
+ const intl = useIntl();
+
+ const getMessage = (id: string) => {
+ const message = Object.values(messages).find(value => value.id === id);
+
+ return message ? formatMessage(message) : id;
+ };
+
+ const formatMessage = (
+ descriptor:
+ | MessageDescriptor
+ | {
+ id: string;
+ defaultMessage: string;
+ },
+ values?: Record>,
+ opts?: Options,
+ ) => {
+ return descriptor ? intl.formatMessage(descriptor, values, opts) : null;
+ };
+
+ return { formatMessage, FormattedMessage, messages, labels, getMessage };
+}
+
+export default useMessages;
diff --git a/src/components/hooks/useNavigation.js b/src/components/hooks/useNavigation.ts
similarity index 92%
rename from src/components/hooks/useNavigation.js
rename to src/components/hooks/useNavigation.ts
index 658e81ed..9f01cd80 100644
--- a/src/components/hooks/useNavigation.js
+++ b/src/components/hooks/useNavigation.ts
@@ -17,7 +17,7 @@ export function useNavigation() {
return obj;
}, [params]);
- function makeUrl(params, reset) {
+ function makeUrl(params: any, reset?: boolean) {
return reset ? pathname : buildUrl(pathname, { ...query, ...params });
}
diff --git a/src/components/hooks/useReport.js b/src/components/hooks/useReport.ts
similarity index 94%
rename from src/components/hooks/useReport.js
rename to src/components/hooks/useReport.ts
index 7c698b4e..1686e222 100644
--- a/src/components/hooks/useReport.js
+++ b/src/components/hooks/useReport.ts
@@ -18,7 +18,7 @@ export function useReport(reportId, defaultParameters) {
};
const loadReport = async id => {
- const data = await get(`/reports/${id}`);
+ const data: any = await get(`/reports/${id}`);
const { dateRange } = data?.parameters || {};
const { startDate, endDate } = dateRange || {};
@@ -40,7 +40,7 @@ export function useReport(reportId, defaultParameters) {
const data = await post(`/reports/${type}`, { ...parameters, timezone });
setReport(
- produce(state => {
+ produce((state: any) => {
state.parameters = parameters;
state.data = data;
@@ -56,7 +56,7 @@ export function useReport(reportId, defaultParameters) {
const updateReport = useCallback(
async data => {
setReport(
- produce(state => {
+ produce((state: any) => {
const { parameters, ...rest } = data;
if (parameters) {
diff --git a/src/components/hooks/useReports.js b/src/components/hooks/useReports.ts
similarity index 100%
rename from src/components/hooks/useReports.js
rename to src/components/hooks/useReports.ts
diff --git a/src/components/hooks/useRequireLogin.ts b/src/components/hooks/useRequireLogin.ts
deleted file mode 100644
index 76460a55..00000000
--- a/src/components/hooks/useRequireLogin.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { useEffect } from 'react';
-import useApi from 'components/hooks/useApi';
-import useUser from 'components/hooks/useUser';
-
-export function useRequireLogin(handler?: (data?: object) => void) {
- const { get } = useApi();
- const { user, setUser } = useUser();
-
- useEffect(() => {
- async function loadUser() {
- try {
- const data = await get('/auth/verify');
-
- setUser(typeof handler === 'function' ? handler(data) : (data as any)?.user);
- } catch {
- location.href = `${process.env.basePath || ''}/login`;
- }
- }
-
- if (!user) {
- loadUser();
- }
- }, [user]);
-
- return { user };
-}
-
-export default useRequireLogin;
diff --git a/src/components/hooks/useShareToken.js b/src/components/hooks/useShareToken.ts
similarity index 77%
rename from src/components/hooks/useShareToken.js
rename to src/components/hooks/useShareToken.ts
index 5062c73e..088f643e 100644
--- a/src/components/hooks/useShareToken.js
+++ b/src/components/hooks/useShareToken.ts
@@ -1,9 +1,9 @@
import useStore, { setShareToken } from 'store/app';
import useApi from './useApi';
-const selector = state => state.shareToken;
+const selector = (state: { shareToken: string }) => state.shareToken;
-export function useShareToken(shareId) {
+export function useShareToken(shareId: string) {
const shareToken = useStore(selector);
const { get, useQuery } = useApi();
const { isLoading, error } = useQuery(['share', shareId], async () => {
diff --git a/src/components/hooks/useSticky.js b/src/components/hooks/useSticky.ts
similarity index 76%
rename from src/components/hooks/useSticky.js
rename to src/components/hooks/useSticky.ts
index be33f6ed..459c489a 100644
--- a/src/components/hooks/useSticky.js
+++ b/src/components/hooks/useSticky.ts
@@ -5,8 +5,9 @@ export function useSticky({ enabled = true, threshold = 1 }) {
const ref = useRef(null);
useEffect(() => {
- let observer;
- const handler = ([entry]) => setIsSticky(entry.intersectionRatio < threshold);
+ let observer: IntersectionObserver | undefined;
+ const handler: IntersectionObserverCallback = ([entry]) =>
+ setIsSticky(entry.intersectionRatio < threshold);
if (enabled && ref.current) {
observer = new IntersectionObserver(handler, { threshold: [threshold] });
diff --git a/src/components/hooks/useTheme.js b/src/components/hooks/useTheme.ts
similarity index 97%
rename from src/components/hooks/useTheme.js
rename to src/components/hooks/useTheme.ts
index 7e40f601..099bf962 100644
--- a/src/components/hooks/useTheme.js
+++ b/src/components/hooks/useTheme.ts
@@ -4,7 +4,7 @@ import { getItem, setItem } from 'next-basics';
import { THEME_COLORS, THEME_CONFIG } from 'lib/constants';
import { colord } from 'colord';
-const selector = state => state.theme;
+const selector = (state: { theme: string }) => state.theme;
export function useTheme() {
const defaultTheme =
diff --git a/src/components/hooks/useTimezone.js b/src/components/hooks/useTimezone.ts
similarity index 100%
rename from src/components/hooks/useTimezone.js
rename to src/components/hooks/useTimezone.ts
diff --git a/src/components/hooks/useWebsite.js b/src/components/hooks/useWebsite.ts
similarity index 81%
rename from src/components/hooks/useWebsite.js
rename to src/components/hooks/useWebsite.ts
index 5315f0dc..7b68335a 100644
--- a/src/components/hooks/useWebsite.js
+++ b/src/components/hooks/useWebsite.ts
@@ -1,6 +1,6 @@
import useApi from './useApi';
-export function useWebsite(websiteId) {
+export function useWebsite(websiteId: string) {
const { get, useQuery } = useApi();
return useQuery(['websites', websiteId], () => get(`/websites/${websiteId}`), {
enabled: !!websiteId,
diff --git a/src/components/common/WorldMap.js b/src/components/metrics/WorldMap.js
similarity index 100%
rename from src/components/common/WorldMap.js
rename to src/components/metrics/WorldMap.js
diff --git a/src/components/common/WorldMap.module.css b/src/components/metrics/WorldMap.module.css
similarity index 100%
rename from src/components/common/WorldMap.module.css
rename to src/components/metrics/WorldMap.module.css
diff --git a/src/declaration.d.ts b/src/declaration.d.ts
new file mode 100644
index 00000000..3523e9fa
--- /dev/null
+++ b/src/declaration.d.ts
@@ -0,0 +1,2 @@
+declare module 'cors';
+declare module 'debug';
diff --git a/src/index.ts b/src/index.ts
index a6bb4c6c..de555051 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,19 +1,16 @@
export * from 'components/hooks/useApi';
export * from 'components/hooks/useConfig';
-export * from 'components/hooks/useCountryNames';
export * from 'components/hooks/useDateRange';
export * from 'components/hooks/useDocumentClick';
export * from 'components/hooks/useEscapeKey';
+export * from 'components/hooks/useFilterQuery';
export * from 'components/hooks/useFilters';
export * from 'components/hooks/useForceUpdate';
export * from 'components/hooks/useFormat';
-export * from 'components/hooks/useLanguageNames';
export * from 'components/hooks/useLocale';
export * from 'components/hooks/useMessages';
export * from 'components/hooks/useNavigation';
-export * from 'components/hooks/useReport';
-export * from 'components/hooks/useReports';
-export * from 'components/hooks/useRequireLogin';
+export * from 'components/hooks/useLogin';
export * from 'components/hooks/useShareToken';
export * from 'components/hooks/useSticky';
export * from 'components/hooks/useTheme';
@@ -21,7 +18,7 @@ export * from 'components/hooks/useTimezone';
export * from 'components/hooks/useUser';
export * from 'components/hooks/useWebsite';
-export * from './app/(main)/settings/teams/[id]/TeamWebsiteAddForm';
+export * from 'app/(main)/settings/teams/[id]/TeamWebsiteAddForm';
export * from 'app/(main)/settings/teams/[id]/TeamEditForm';
export * from 'app/(main)/settings/teams/[id]/TeamMemberRemoveButton';
export * from 'app/(main)/settings/teams/[id]/TeamMembers';
@@ -44,8 +41,22 @@ export * from 'app/(main)/settings/websites/[id]/TrackingCode';
export * from 'app/(main)/settings/websites/[id]/WebsiteDeleteForm';
export * from 'app/(main)/settings/websites/[id]/WebsiteEditForm';
export * from 'app/(main)/settings/websites/[id]/WebsiteResetForm';
+
export * from 'app/(main)/settings/websites/WebsiteAddForm';
export * from 'app/(main)/settings/websites/WebsitesHeader';
export * from 'app/(main)/settings/websites/WebsiteSettings';
-export * from './app/(main)/settings/websites/WebsitesDataTable';
+export * from 'app/(main)/settings/websites/WebsitesDataTable';
export * from 'app/(main)/settings/websites/WebsitesTable';
+
+export * from 'components/common/ConfirmDeleteForm';
+export * from 'components/common/DataTable';
+export * from 'components/common/Empty';
+export * from 'components/common/ErrorBoundary';
+export * from 'components/common/Favicon';
+export * from 'components/common/FilterButtons';
+export * from 'components/common/FilterLink';
+export * from 'components/common/HamburgerButton';
+export * from 'components/common/HoverTooltip';
+export * from 'components/common/LinkButton';
+export * from 'components/common/MobileMenu';
+export * from 'components/common/Pager';
diff --git a/src/lib/auth.ts b/src/lib/auth.ts
index 4a42d85d..1757f05e 100644
--- a/src/lib/auth.ts
+++ b/src/lib/auth.ts
@@ -1,29 +1,30 @@
import { Report } from '@prisma/client';
-import redis from '@umami/redis-client';
import debug from 'debug';
+import redis from '@umami/redis-client';
import { PERMISSIONS, ROLE_PERMISSIONS, SHARE_TOKEN_HEADER } from 'lib/constants';
import { secret } from 'lib/crypto';
import { createSecureToken, ensureArray, getRandomChars, parseToken } from 'next-basics';
import { findTeamWebsiteByUserId, getTeamUser, getTeamWebsite } from 'queries';
import { loadWebsite } from './load';
import { Auth } from './types';
+import { NextApiRequest } from 'next';
const log = debug('umami:auth');
const cloudMode = process.env.CLOUD_MODE;
-export async function setAuthKey(user, expire = 0) {
+export async function saveAuth(data: any, expire = 0) {
const authKey = `auth:${getRandomChars(32)}`;
- await redis.set(authKey, user);
+ await redis.client.set(authKey, data);
if (expire) {
- await redis.expire(authKey, expire);
+ await redis.client.expire(authKey, expire);
}
return createSecureToken({ authKey }, secret());
}
-export function getAuthToken(req) {
+export function getAuthToken(req: NextApiRequest) {
try {
return req.headers.authorization.split(' ')[1];
} catch {
@@ -31,7 +32,7 @@ export function getAuthToken(req) {
}
}
-export function parseShareToken(req) {
+export function parseShareToken(req: Request) {
try {
return parseToken(req.headers[SHARE_TOKEN_HEADER], secret());
} catch (e) {
@@ -60,11 +61,7 @@ export async function canViewWebsite({ user, shareToken }: Auth, websiteId: stri
export async function canCreateWebsite({ user, grant }: Auth) {
if (cloudMode) {
- if (grant?.find(a => a === PERMISSIONS.websiteCreate)) {
- return true;
- }
-
- return false;
+ return !!grant?.find(a => a === PERMISSIONS.websiteCreate);
}
if (user.isAdmin) {
@@ -120,11 +117,7 @@ export async function canDeleteReport(auth: Auth, report: Report) {
export async function canCreateTeam({ user, grant }: Auth) {
if (cloudMode) {
- if (grant?.find(a => a === PERMISSIONS.teamCreate)) {
- return true;
- }
-
- return false;
+ return !!grant?.find(a => a === PERMISSIONS.teamCreate);
}
if (user.isAdmin) {
diff --git a/src/lib/cache.ts b/src/lib/cache.ts
index 2b577bf2..69d749d0 100644
--- a/src/lib/cache.ts
+++ b/src/lib/cache.ts
@@ -3,67 +3,71 @@ import redis from '@umami/redis-client';
import { getSession, getUserById, getWebsiteById } from '../queries';
async function fetchWebsite(id): Promise {
- return redis.getCache(`website:${id}`, () => getWebsiteById(id), 86400);
+ return redis.client.getCache(`website:${id}`, () => getWebsiteById(id), 86400);
}
async function storeWebsite(data) {
const { id } = data;
const key = `website:${id}`;
- const obj = await redis.setCache(key, data);
- await redis.expire(key, 86400);
+ const obj = await redis.client.setCache(key, data);
+ await redis.client.expire(key, 86400);
return obj;
}
async function deleteWebsite(id) {
- return redis.deleteCache(`website:${id}`);
+ return redis.client.deleteCache(`website:${id}`);
}
async function fetchUser(id): Promise {
- return redis.getCache(`user:${id}`, () => getUserById(id, { includePassword: true }), 86400);
+ return redis.client.getCache(
+ `user:${id}`,
+ () => getUserById(id, { includePassword: true }),
+ 86400,
+ );
}
async function storeUser(data) {
const { id } = data;
const key = `user:${id}`;
- const obj = await redis.setCache(key, data);
- await redis.expire(key, 86400);
+ const obj = await redis.client.setCache(key, data);
+ await redis.client.expire(key, 86400);
return obj;
}
async function deleteUser(id) {
- return redis.deleteCache(`user:${id}`);
+ return redis.client.deleteCache(`user:${id}`);
}
async function fetchSession(id) {
- return redis.getCache(`session:${id}`, () => getSession(id), 86400);
+ return redis.client.getCache(`session:${id}`, () => getSession(id), 86400);
}
async function storeSession(data) {
const { id } = data;
const key = `session:${id}`;
- const obj = await redis.setCache(key, data);
- await redis.expire(key, 86400);
+ const obj = await redis.client.setCache(key, data);
+ await redis.client.expire(key, 86400);
return obj;
}
async function deleteSession(id) {
- return redis.deleteCache(`session:${id}`);
+ return redis.client.deleteCache(`session:${id}`);
}
async function fetchUserBlock(userId: string) {
const key = `user:block:${userId}`;
- return redis.get(key);
+ return redis.client.get(key);
}
async function incrementUserBlock(userId: string) {
const key = `user:block:${userId}`;
- return redis.incr(key);
+ return redis.client.incr(key);
}
export default {
@@ -78,5 +82,5 @@ export default {
deleteSession,
fetchUserBlock,
incrementUserBlock,
- enabled: !!redis,
+ enabled: !!redis.enabled,
};
diff --git a/src/lib/client.ts b/src/lib/client.ts
index 8c69d23d..7810c44a 100644
--- a/src/lib/client.ts
+++ b/src/lib/client.ts
@@ -5,7 +5,7 @@ export function getClientAuthToken() {
return getItem(AUTH_TOKEN);
}
-export function setClientAuthToken(token) {
+export function setClientAuthToken(token: string) {
setItem(AUTH_TOKEN, token);
}
diff --git a/src/lib/constants.ts b/src/lib/constants.ts
index 4c468c1c..0c894634 100644
--- a/src/lib/constants.ts
+++ b/src/lib/constants.ts
@@ -13,7 +13,7 @@ export const REPO_URL = 'https://github.com/umami-software/umami';
export const UPDATES_URL = 'https://api.umami.is/v1/updates';
export const TELEMETRY_PIXEL = 'https://i.umami.is/a.png';
-export const DEFAULT_LOCALE = process.env.defaultLocale ?? 'en-US';
+export const DEFAULT_LOCALE = process.env.defaultLocale || 'en-US';
export const DEFAULT_THEME = 'light';
export const DEFAULT_ANIMATION_DURATION = 300;
export const DEFAULT_DATE_RANGE = '24hour';
diff --git a/src/lib/middleware.ts b/src/lib/middleware.ts
index 5a12eb6a..5796009b 100644
--- a/src/lib/middleware.ts
+++ b/src/lib/middleware.ts
@@ -1,13 +1,14 @@
-import redis from '@umami/redis-client';
import cors from 'cors';
import debug from 'debug';
+import redis from '@umami/redis-client';
import { getAuthToken, parseShareToken } from 'lib/auth';
import { ROLES } from 'lib/constants';
-import { isUuid, secret } from 'lib/crypto';
+import { secret } from 'lib/crypto';
import { findSession } from 'lib/session';
import {
badRequest,
createMiddleware,
+ forbidden,
parseSecureToken,
tooManyRequest,
unauthorized,
@@ -38,6 +39,9 @@ export const useSession = createMiddleware(async (req, res, next) => {
if (e.message === 'Usage Limit.') {
return tooManyRequest(res, e.message);
}
+ if (e.message.startsWith('Website not found:')) {
+ return forbidden(res, e.message);
+ }
return badRequest(res, e.message);
}
@@ -47,19 +51,21 @@ export const useSession = createMiddleware(async (req, res, next) => {
export const useAuth = createMiddleware(async (req, res, next) => {
const token = getAuthToken(req);
const payload = parseSecureToken(token, secret());
- const shareToken = await parseShareToken(req);
+ const shareToken = await parseShareToken(req as any);
let user = null;
const { userId, authKey, grant } = payload || {};
- if (isUuid(userId)) {
+ if (userId) {
user = await getUserById(userId);
- } else if (redis && authKey) {
- user = await redis.get(authKey);
+ } else if (redis.enabled && authKey) {
+ const key = await redis.client.get(authKey);
+
+ user = await getUserById(key?.userId);
}
if (process.env.NODE_ENV === 'development') {
- log({ token, shareToken, payload, user, grant });
+ log('useAuth:', { token, shareToken, payload, user, grant });
}
if (!user?.id && !shareToken) {
diff --git a/src/pages/api/auth/login.ts b/src/pages/api/auth/login.ts
index 0946ae75..e1007b3c 100644
--- a/src/pages/api/auth/login.ts
+++ b/src/pages/api/auth/login.ts
@@ -1,6 +1,6 @@
import redis from '@umami/redis-client';
import debug from 'debug';
-import { setAuthKey } from 'lib/auth';
+import { saveAuth } from 'lib/auth';
import { secret } from 'lib/crypto';
import { useValidate } from 'lib/middleware';
import { NextApiRequestQueryBody, User } from 'lib/types';
@@ -52,8 +52,8 @@ export default async (
const user = await getUserByUsername(username, { includePassword: true });
if (user && checkPassword(password, user.password)) {
- if (redis) {
- const token = await setAuthKey(user);
+ if (redis.enabled) {
+ const token = await saveAuth({ userId: user.id });
return ok(res, { token, user });
}
diff --git a/src/pages/api/auth/logout.ts b/src/pages/api/auth/logout.ts
index e6222e49..715fda62 100644
--- a/src/pages/api/auth/logout.ts
+++ b/src/pages/api/auth/logout.ts
@@ -8,8 +8,8 @@ export default async (req: NextApiRequest, res: NextApiResponse) => {
await useAuth(req, res);
if (req.method === 'POST') {
- if (redis) {
- await redis.del(getAuthToken(req));
+ if (redis.enabled) {
+ await redis.client.del(getAuthToken(req));
}
return ok(res);
diff --git a/src/pages/api/auth/sso.ts b/src/pages/api/auth/sso.ts
index a7992666..7b1eef60 100644
--- a/src/pages/api/auth/sso.ts
+++ b/src/pages/api/auth/sso.ts
@@ -3,13 +3,13 @@ import { useAuth } from 'lib/middleware';
import { NextApiResponse } from 'next';
import { badRequest, ok } from 'next-basics';
import redis from '@umami/redis-client';
-import { setAuthKey } from 'lib/auth';
+import { saveAuth } from 'lib/auth';
export default async (req: NextApiRequestAuth, res: NextApiResponse) => {
await useAuth(req, res);
- if (redis && req.auth.user) {
- const token = await setAuthKey(req.auth.user, 86400);
+ if (redis.enabled && req.auth.user) {
+ const token = await saveAuth({ userId: req.auth.user.id }, 86400);
return ok(res, { user: req.auth.user, token });
}
diff --git a/src/pages/api/auth/verify.ts b/src/pages/api/auth/verify.ts
index 9eb9ea48..a302c69b 100644
--- a/src/pages/api/auth/verify.ts
+++ b/src/pages/api/auth/verify.ts
@@ -6,5 +6,5 @@ import { ok } from 'next-basics';
export default async (req: NextApiRequestAuth, res: NextApiResponse) => {
await useAuth(req, res);
- return ok(res, req.auth);
+ return ok(res, req.auth.user);
};
diff --git a/src/pages/api/send.ts b/src/pages/api/send.ts
index e8d3e386..1698d858 100644
--- a/src/pages/api/send.ts
+++ b/src/pages/api/send.ts
@@ -1,4 +1,3 @@
-import { Resolver } from 'dns/promises';
import ipaddr from 'ipaddr.js';
import isbot from 'isbot';
import { COLLECTION_TYPE, HOSTNAME_REGEX } from 'lib/constants';
@@ -74,18 +73,18 @@ export default async (req: NextApiRequestCollect, res: NextApiResponse) => {
await useCors(req, res);
if (req.method === 'POST') {
- if (isbot(req.headers['user-agent']) && !process.env.DISABLE_BOT_CHECK) {
+ if (!process.env.DISABLE_BOT_CHECK && isbot(req.headers['user-agent'])) {
return ok(res);
}
- const { type, payload } = req.body;
-
await useValidate(schema, req, res);
- if (await hasBlockedIp(req)) {
+ if (hasBlockedIp(req)) {
return forbidden(res);
}
+ const { type, payload } = req.body;
+
const { url, referrer, name: eventName, data: eventData, title: pageTitle } = payload;
await useSession(req, res);
@@ -143,28 +142,16 @@ export default async (req: NextApiRequestCollect, res: NextApiResponse) => {
return methodNotAllowed(res);
};
-async function hasBlockedIp(req: NextApiRequestCollect) {
+function hasBlockedIp(req: NextApiRequestCollect) {
const ignoreIps = process.env.IGNORE_IP;
- const ignoreHostnames = process.env.IGNORE_HOSTNAME;
- if (ignoreIps || ignoreHostnames) {
+ if (ignoreIps) {
const ips = [];
if (ignoreIps) {
ips.push(...ignoreIps.split(',').map(n => n.trim()));
}
- if (ignoreHostnames) {
- const resolver = new Resolver();
- const promises = ignoreHostnames
- .split(',')
- .map(n => resolver.resolve4(n.trim()).catch(() => {}));
-
- await Promise.all(promises).then(resolvedIps => {
- ips.push(...resolvedIps.filter(n => n).flatMap(n => n as string[]));
- });
- }
-
const clientIp = getIpAddress(req);
return ips.find(ip => {
@@ -177,8 +164,8 @@ async function hasBlockedIp(req: NextApiRequestCollect) {
if (addr.kind() === range[0].kind() && addr.match(range)) return true;
}
-
- return false;
});
}
+
+ return false;
}
diff --git a/src/queries/admin/user.ts b/src/queries/admin/user.ts
index b7319942..11f1c846 100644
--- a/src/queries/admin/user.ts
+++ b/src/queries/admin/user.ts
@@ -11,13 +11,17 @@ export interface GetUserOptions {
}
async function getUser(
- where: Prisma.UserWhereInput | Prisma.UserWhereUniqueInput,
+ where: Prisma.UserWhereUniqueInput,
options: GetUserOptions = {},
): Promise {
const { includePassword = false, showDeleted = false } = options;
- return prisma.client.user.findFirst({
- where: { ...where, ...(showDeleted ? {} : { deletedAt: null }) },
+ if (showDeleted) {
+ where.deletedAt = null;
+ }
+
+ return prisma.client.user.findUnique({
+ where,
select: {
id: true,
username: true,
@@ -28,8 +32,8 @@ async function getUser(
});
}
-export async function getUserById(userId: string, options: GetUserOptions = {}) {
- return getUser({ id: userId }, options);
+export async function getUserById(id: string, options: GetUserOptions = {}) {
+ return getUser({ id }, options);
}
export async function getUserByUsername(username: string, options: GetUserOptions = {}) {
diff --git a/tsconfig.json b/tsconfig.json
index 9b8b6033..1807e947 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,6 +1,6 @@
{
"compilerOptions": {
- "target": "es5",
+ "target": "es2021",
"outDir": "./build",
"module": "esnext",
"moduleResolution": "node",
diff --git a/yarn.lock b/yarn.lock
index 6dd8b93b..2c95f0d3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1353,12 +1353,12 @@
"@formatjs/intl-localematcher" "0.2.25"
tslib "^2.1.0"
-"@formatjs/ecma402-abstract@1.17.2":
- version "1.17.2"
- resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.17.2.tgz#d197c6e26b9fd96ff7ba3b3a0cc2f25f1f2dcac3"
- integrity sha512-k2mTh0m+IV1HRdU0xXM617tSQTi53tVR2muvYOsBeYcUgEAyxV1FOC7Qj279th3fBVQ+Dj6muvNJZcHSPNdbKg==
+"@formatjs/ecma402-abstract@1.18.0":
+ version "1.18.0"
+ resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.18.0.tgz#e2120e7101020140661b58430a7ff4262705a2f2"
+ integrity sha512-PEVLoa3zBevWSCZzPIM/lvPCi8P5l4G+NXQMc/CjEiaCWgyHieUoo0nM7Bs0n/NbuQ6JpXEolivQ9pKSBHaDlA==
dependencies:
- "@formatjs/intl-localematcher" "0.4.2"
+ "@formatjs/intl-localematcher" "0.5.2"
tslib "^2.4.0"
"@formatjs/ecma402-abstract@1.4.0":
@@ -1391,13 +1391,13 @@
"@formatjs/icu-skeleton-parser" "1.3.6"
tslib "^2.1.0"
-"@formatjs/icu-messageformat-parser@2.7.0":
- version "2.7.0"
- resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.0.tgz#9b13f2710a3b4efddfeb544480f684f27a53483b"
- integrity sha512-7uqC4C2RqOaBQtcjqXsSpGRYVn+ckjhNga5T/otFh6MgxRrCJQqvjfbrGLpX1Lcbxdm5WH3Z2WZqt1+Tm/cn/Q==
+"@formatjs/icu-messageformat-parser@2.7.3":
+ version "2.7.3"
+ resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.3.tgz#c8c95e7c9f8141bdb93bea0e92e4fcace19d3c9f"
+ integrity sha512-X/jy10V9S/vW+qlplqhMUxR8wErQ0mmIYSq4mrjpjDl9mbuGcCILcI1SUYkL5nlM4PJqpc0KOS0bFkkJNPxYRw==
dependencies:
- "@formatjs/ecma402-abstract" "1.17.2"
- "@formatjs/icu-skeleton-parser" "1.6.2"
+ "@formatjs/ecma402-abstract" "1.18.0"
+ "@formatjs/icu-skeleton-parser" "1.7.0"
tslib "^2.4.0"
"@formatjs/icu-skeleton-parser@1.3.6":
@@ -1408,30 +1408,30 @@
"@formatjs/ecma402-abstract" "1.11.4"
tslib "^2.1.0"
-"@formatjs/icu-skeleton-parser@1.6.2":
- version "1.6.2"
- resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.6.2.tgz#00303034dc08583973c8aa67b96534c49c0bad8d"
- integrity sha512-VtB9Slo4ZL6QgtDFJ8Injvscf0xiDd4bIV93SOJTBjUF4xe2nAWOoSjLEtqIG+hlIs1sNrVKAaFo3nuTI4r5ZA==
+"@formatjs/icu-skeleton-parser@1.7.0":
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.7.0.tgz#796938d6d0ba8fc75bb9edee038d1350bfee32cb"
+ integrity sha512-Cfdo/fgbZzpN/jlN/ptQVe0lRHora+8ezrEeg2RfrNjyp+YStwBy7cqDY8k5/z2LzXg6O0AdzAV91XS0zIWv+A==
dependencies:
- "@formatjs/ecma402-abstract" "1.17.2"
+ "@formatjs/ecma402-abstract" "1.18.0"
tslib "^2.4.0"
-"@formatjs/intl-displaynames@6.6.0":
- version "6.6.0"
- resolved "https://registry.yarnpkg.com/@formatjs/intl-displaynames/-/intl-displaynames-6.6.0.tgz#6f590784b1bcdc1b96d4dba158bdce350f876804"
- integrity sha512-bskUou9boZOzTqI8JdNCNkDavXf8uWWz/6NG1og/XJKpn4zsfiLdQ9EYKhVe/CfbCjlSyieJYn7/NztdoprHjw==
+"@formatjs/intl-displaynames@6.6.4":
+ version "6.6.4"
+ resolved "https://registry.yarnpkg.com/@formatjs/intl-displaynames/-/intl-displaynames-6.6.4.tgz#dd9ca9bb2d1f4b140cc8814667bc830802621674"
+ integrity sha512-ET8KQ+L9Q0K8x1SnJQa4DNssUcbATlMopWqYvGGR8yAvw5qwAQc1fv+DshCoZNIE9pbcue0IGC4kWNAkWqlFag==
dependencies:
- "@formatjs/ecma402-abstract" "1.17.2"
- "@formatjs/intl-localematcher" "0.4.2"
+ "@formatjs/ecma402-abstract" "1.18.0"
+ "@formatjs/intl-localematcher" "0.5.2"
tslib "^2.4.0"
-"@formatjs/intl-listformat@7.5.0":
- version "7.5.0"
- resolved "https://registry.yarnpkg.com/@formatjs/intl-listformat/-/intl-listformat-7.5.0.tgz#dbccf2e0f07792aa1c273702796bdad061dc27ae"
- integrity sha512-n9FsXGl1T2ZbX6wSyrzCDJHrbJR0YJ9ZNsAqUvHXfbY3nsOmGnSTf5+bkuIp1Xiywu7m1X1Pfm/Ngp/yK1H84A==
+"@formatjs/intl-listformat@7.5.3":
+ version "7.5.3"
+ resolved "https://registry.yarnpkg.com/@formatjs/intl-listformat/-/intl-listformat-7.5.3.tgz#c6f028471839cd1014760498f783fdfe011422d5"
+ integrity sha512-l7EOr0Yh1m8KagytukB90yw81uyzrM7amKFrgxXqphz4KeSIL0KPa68lPsdtZ+JmQB73GaDQRwLOwUKFZ1VZPQ==
dependencies:
- "@formatjs/ecma402-abstract" "1.17.2"
- "@formatjs/intl-localematcher" "0.4.2"
+ "@formatjs/ecma402-abstract" "1.18.0"
+ "@formatjs/intl-localematcher" "0.5.2"
tslib "^2.4.0"
"@formatjs/intl-localematcher@0.2.25":
@@ -1441,10 +1441,10 @@
dependencies:
tslib "^2.1.0"
-"@formatjs/intl-localematcher@0.4.2":
- version "0.4.2"
- resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.4.2.tgz#7e6e596dbaf2f0c5a7c22da5a01d5c55f4c37e9a"
- integrity sha512-BGdtJFmaNJy5An/Zan4OId/yR9Ih1OojFjcduX/xOvq798OgWSyDtd6Qd5jqJXwJs1ipe4Fxu9+cshic5Ox2tA==
+"@formatjs/intl-localematcher@0.5.2":
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.5.2.tgz#5fcf029fd218905575e5080fa33facdcb623d532"
+ integrity sha512-txaaE2fiBMagLrR4jYhxzFO6wEdEG4TPMqrzBAcbr4HFUYzH/YC+lg6OIzKCHm8WgDdyQevxbAAV1OgcXctuGw==
dependencies:
tslib "^2.4.0"
@@ -1456,17 +1456,17 @@
"@formatjs/ecma402-abstract" "1.4.0"
tslib "^2.0.1"
-"@formatjs/intl@2.9.4":
- version "2.9.4"
- resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-2.9.4.tgz#6f97a8e6e282086c39c8e502face5b2839f47b6f"
- integrity sha512-hY0UlbDz8jY12RkQtkzxe3OfUmsIcUcsvVYyr1TFue6oTrUHqpkmYLdQ626V3BCSLc90EZDXdvmsPfMd3hTcYQ==
+"@formatjs/intl@2.9.9":
+ version "2.9.9"
+ resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-2.9.9.tgz#866629b565e20dd7490f9e77ee41a00748913e8f"
+ integrity sha512-JI3CNgL2Zdg5lv9ncT2sYKqbAj2RGrCbdzaCckIxMPxn4QuHuOVvYUGmBAXVusBmfG/0sxLmMrnwnBioz+QKdA==
dependencies:
- "@formatjs/ecma402-abstract" "1.17.2"
+ "@formatjs/ecma402-abstract" "1.18.0"
"@formatjs/fast-memoize" "2.2.0"
- "@formatjs/icu-messageformat-parser" "2.7.0"
- "@formatjs/intl-displaynames" "6.6.0"
- "@formatjs/intl-listformat" "7.5.0"
- intl-messageformat "10.5.4"
+ "@formatjs/icu-messageformat-parser" "2.7.3"
+ "@formatjs/intl-displaynames" "6.6.4"
+ "@formatjs/intl-listformat" "7.5.3"
+ intl-messageformat "10.5.8"
tslib "^2.4.0"
"@formatjs/ts-transformer@3.9.4":
@@ -1958,6 +1958,11 @@
resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-5.4.2.tgz#ba2b7faeb227c76e423e88f962afe6a031319f3f"
integrity sha512-fqeucJ3LH0e1eyFdT0zRx+oETLancu5+n4lhiYECyEz6H2RDskPJHJYHkVc0LhkU4Uv7fuEnppKU3nVKNzMh8g==
+"@prisma/extension-read-replicas@^0.3.0":
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/@prisma/extension-read-replicas/-/extension-read-replicas-0.3.0.tgz#2842a7c928f957c1dd58a6256104797596d43426"
+ integrity sha512-F9+rSmYday6GT2qjhJtkZcBOpLO5LtpvFcMGqrBDHf+78LEdSuxfFjOxYlNuqk4B+th62yxpbhfpmB9/Mca14Q==
+
"@react-spring/animated@~9.7.3":
version "9.7.3"
resolved "https://registry.yarnpkg.com/@react-spring/animated/-/animated-9.7.3.tgz#4211b1a6d48da0ff474a125e93c0f460ff816e0f"
@@ -2393,10 +2398,12 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190"
integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==
-"@types/node@^18.11.9":
- version "18.18.6"
- resolved "https://registry.yarnpkg.com/@types/node/-/node-18.18.6.tgz#26da694f75cdb057750f49d099da5e3f3824cb3e"
- integrity sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==
+"@types/node@^20.9.0":
+ version "20.9.0"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.9.0.tgz#bfcdc230583aeb891cf51e73cfdaacdd8deae298"
+ integrity sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==
+ dependencies:
+ undici-types "~5.26.4"
"@types/normalize-package-data@^2.4.0":
version "2.4.3"
@@ -2408,10 +2415,10 @@
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.9.tgz#b6f785caa7ea1fe4414d9df42ee0ab67f23d8a6d"
integrity sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g==
-"@types/react-dom@^18.0.8":
- version "18.2.14"
- resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.14.tgz#c01ba40e5bb57fc1dc41569bb3ccdb19eab1c539"
- integrity sha512-V835xgdSVmyQmI1KLV2BEIUgqEuinxp9O4G6g3FqO/SqLac049E53aysv0oEFD2kHfejeKU+ZqL2bcFWj9gLAQ==
+"@types/react-dom@^18.2.15":
+ version "18.2.15"
+ resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.15.tgz#921af67f9ee023ac37ea84b1bc0cc40b898ea522"
+ integrity sha512-HWMdW+7r7MR5+PZqJF6YFNSCtjz1T0dsvo/f1BV6HkV+6erD/nA7wd9NM00KVG83zf2nJ7uATPO9ttdIPvi3gg==
dependencies:
"@types/react" "*"
@@ -2425,7 +2432,7 @@
hoist-non-react-statics "^3.3.0"
redux "^4.0.0"
-"@types/react@*", "@types/react@16 || 17 || 18", "@types/react@^18.0.25":
+"@types/react@*", "@types/react@16 || 17 || 18":
version "18.2.30"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.30.tgz#b84f786864fc46f18545364a54d5e1316308e59b"
integrity sha512-OfqdJnDsSo4UNw0bqAjFCuBpLYQM7wvZidz0hVxHRjrEkzRlvZL1pJVyOSY55HMiKvRNEo9DUBRuEl7FNlJ/Vg==
@@ -2434,6 +2441,15 @@
"@types/scheduler" "*"
csstype "^3.0.2"
+"@types/react@^18.2.37":
+ version "18.2.37"
+ resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.37.tgz#0f03af69e463c0f19a356c2660dbca5d19c44cae"
+ integrity sha512-RGAYMi2bhRgEXT3f4B92WTohopH6bIXw05FuGlmJEnv/omEn190+QYEIYxIAuIBdKgboYYdVved2p1AxZVQnaw==
+ dependencies:
+ "@types/prop-types" "*"
+ "@types/scheduler" "*"
+ csstype "^3.0.2"
+
"@types/resolve@1.20.2":
version "1.20.2"
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.2.tgz#97d26e00cd4a0423b4af620abecf3e6f442b7975"
@@ -2585,18 +2601,18 @@
"@typescript-eslint/types" "6.8.0"
eslint-visitor-keys "^3.4.1"
-"@umami/prisma-client@^0.3.0":
- version "0.3.0"
- resolved "https://registry.yarnpkg.com/@umami/prisma-client/-/prisma-client-0.3.0.tgz#fea35a44c76af0e4ce58288107cda3ee76fc80ba"
- integrity sha512-88y/WJX2TEZaUfP+PTretGUL6YdwZCBbhaoeC87eTF3l1aG0Lv3TsmW0lJy5rbKpVqrFJ8zrtvCMP/vt7WeIjg==
+"@umami/prisma-client@^0.5.0":
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/@umami/prisma-client/-/prisma-client-0.5.0.tgz#e2287debbf21f9344c989b9e7192491df88513bf"
+ integrity sha512-BkStMrvxYZQPwEIyy30JJPucTTsmQqb4jD8+ciSHxcBc7039cW0XyX3TL/u9ebZmANzIuNO0XiBArwjWulGIjg==
dependencies:
chalk "^4.1.2"
debug "^4.3.4"
-"@umami/redis-client@^0.16.0":
- version "0.16.0"
- resolved "https://registry.yarnpkg.com/@umami/redis-client/-/redis-client-0.16.0.tgz#0050d1f93338d88691c983f3c0cd4a62da20212b"
- integrity sha512-fE08lkMvhXbkXSdSRpG0R/9a3xIiTvwD6f+hKERFZrpfvJJlH3Uf4Jod8Ahg/+TmD03ihSQPooUT3T9Ig3dfaQ==
+"@umami/redis-client@^0.18.0":
+ version "0.18.0"
+ resolved "https://registry.yarnpkg.com/@umami/redis-client/-/redis-client-0.18.0.tgz#6a2315a878f2688dae162d93e88dfc4e097fc48e"
+ integrity sha512-uDuX5w7ydlOZWrq0h6fADG3XWOhto9fAqrUVu85FUhdijWoGlv5f8adaL8FAah5jD+/Byw2VyGQaZO4VhboEZw==
dependencies:
debug "^4.3.4"
redis "^4.5.1"
@@ -5158,14 +5174,14 @@ intl-messageformat-parser@^5.3.7:
dependencies:
"@formatjs/intl-numberformat" "^5.5.2"
-intl-messageformat@10.5.4:
- version "10.5.4"
- resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-10.5.4.tgz#7b212b083f1b354d7e282518e78057e025134af9"
- integrity sha512-z+hrFdiJ/heRYlzegrdFYqU1m/KOMOVMqNilIArj+PbsuU8TNE7v4TWdQgSoxlxbT4AcZH3Op3/Fu15QTp+W1w==
+intl-messageformat@10.5.8:
+ version "10.5.8"
+ resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-10.5.8.tgz#7184da425f360a53a5483a6194e16d666b011fc0"
+ integrity sha512-NRf0jpBWV0vd671G5b06wNofAN8tp7WWDogMZyaU8GUAsmbouyvgwmFJI7zLjfAMpm3zK+vSwRP3jzaoIcMbaA==
dependencies:
- "@formatjs/ecma402-abstract" "1.17.2"
+ "@formatjs/ecma402-abstract" "1.18.0"
"@formatjs/fast-memoize" "2.2.0"
- "@formatjs/icu-messageformat-parser" "2.7.0"
+ "@formatjs/icu-messageformat-parser" "2.7.3"
tslib "^2.4.0"
ioredis@^5.3.2:
@@ -7455,10 +7471,10 @@ rc@^1.2.7:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
-react-basics@^0.105.0:
- version "0.105.0"
- resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.105.0.tgz#94eda703b3c0728e817b6e9d086e5d1c6c68f25c"
- integrity sha512-iKYtfB0A2vsmO+X4jaX64XdmHE836w8TG2jFQ0pi5Qp0ktL0lAL9/q0IrWUjNr86hi0apg46aeJWxY+qQO+T1g==
+react-basics@^0.107.0:
+ version "0.107.0"
+ resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.107.0.tgz#e5615792cbb3e4707ba5c8f438b29d6a88cf38b3"
+ integrity sha512-jYnP1z2LTotxXWYwxOBvF26vXxSUBJB0x62YPKkEr1vmJGeg8iOLr8JGF8KE3R6E+NTqzRt6Bmdtt93mjaog4A==
dependencies:
"@react-spring/web" "^9.7.3"
classnames "^2.3.1"
@@ -7499,20 +7515,20 @@ react-hook-form@^7.34.2:
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.47.0.tgz#a42f07266bd297ddf1f914f08f4b5f9783262f31"
integrity sha512-F/TroLjTICipmHeFlMrLtNLceO2xr1jU3CyiNla5zdwsGUGu2UOxxR4UyJgLlhMwLW/Wzp4cpJ7CPfgJIeKdSg==
-react-intl@^6.4.7:
- version "6.5.0"
- resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-6.5.0.tgz#0ff04170f91e1bcbcd3301dfb2ae39a258827ec7"
- integrity sha512-ZnBYFlFUU1ivhvWBA87XJLAr9nR8yeC1/83e6AL7yiHbWH7xQE7tyMyIyw6or78EvU9Hx8Sh8LUDC4bGrNxXOA==
+react-intl@^6.5.5:
+ version "6.5.5"
+ resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-6.5.5.tgz#d2de7bfd79718a7e3d8031e2599e94e0c8638377"
+ integrity sha512-cI5UKvBh4tc1zxLIziHBYGMX3dhYWDEFlvUDVN6NfT2i96zTXz/zH2AmM8+2waqgOhwkFUzd+7kK1G9q7fiC2g==
dependencies:
- "@formatjs/ecma402-abstract" "1.17.2"
- "@formatjs/icu-messageformat-parser" "2.7.0"
- "@formatjs/intl" "2.9.4"
- "@formatjs/intl-displaynames" "6.6.0"
- "@formatjs/intl-listformat" "7.5.0"
+ "@formatjs/ecma402-abstract" "1.18.0"
+ "@formatjs/icu-messageformat-parser" "2.7.3"
+ "@formatjs/intl" "2.9.9"
+ "@formatjs/intl-displaynames" "6.6.4"
+ "@formatjs/intl-listformat" "7.5.3"
"@types/hoist-non-react-statics" "^3.3.1"
"@types/react" "16 || 17 || 18"
hoist-non-react-statics "^3.3.2"
- intl-messageformat "10.5.4"
+ intl-messageformat "10.5.8"
tslib "^2.4.0"
react-is@^16.13.1, react-is@^16.7.0:
@@ -8893,6 +8909,11 @@ undici-types@~5.25.1:
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.25.3.tgz#e044115914c85f0bcbb229f346ab739f064998c3"
integrity sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==
+undici-types@~5.26.4:
+ version "5.26.5"
+ resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
+ integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
+
unenv@^1.7.4:
version "1.7.4"
resolved "https://registry.yarnpkg.com/unenv/-/unenv-1.7.4.tgz#a0e5a78de2c7c3c4563c06ba9763c96c59db3333"