- {ordered.map(({ id, name, domain }, index) => {
+ {ordered.map(({ id }, index) => {
return index < limit ? (
-
+
+
+
+
+
+
+
) : null;
})}
diff --git a/components/pages/websites/WebsiteDetails.js b/components/pages/websites/WebsiteDetails.js
deleted file mode 100644
index ba80bcf8..00000000
--- a/components/pages/websites/WebsiteDetails.js
+++ /dev/null
@@ -1,47 +0,0 @@
-import { useState } from 'react';
-import { Loading } from 'react-basics';
-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 WebsiteTableView from './WebsiteTableView';
-import WebsiteMenuView from './WebsiteMenuView';
-
-export default function WebsiteDetails({ websiteId }) {
- const { get, useQuery } = useApi();
- const { data, isLoading, error } = useQuery(['websites', websiteId], () =>
- get(`/websites/${websiteId}`),
- );
- const [chartLoaded, setChartLoaded] = useState(false);
-
- const {
- query: { view },
- } = usePageQuery();
-
- function handleDataLoad() {
- if (!chartLoaded) {
- setTimeout(() => setChartLoaded(true), DEFAULT_ANIMATION_DURATION);
- }
- }
-
- return (
-
-
- {!chartLoaded && }
- {chartLoaded && (
- <>
- {!view && }
- {view && }
- >
- )}
-
- );
-}
diff --git a/components/pages/websites/WebsiteDetails.module.css b/components/pages/websites/WebsiteDetails.module.css
deleted file mode 100644
index b0632be6..00000000
--- a/components/pages/websites/WebsiteDetails.module.css
+++ /dev/null
@@ -1,31 +0,0 @@
-.chart {
- margin-bottom: 30px;
-}
-
-.view {
- border-top: 1px solid var(--base300);
-}
-
-.menu {
- font-size: var(--font-size-sm);
-}
-
-.content {
- min-height: 600px;
- padding: 20px 0;
-}
-
-.backButton {
- display: flex;
- justify-content: center;
- align-items: center;
- margin-bottom: 16px;
-}
-
-.backButton svg {
- transform: rotate(180deg);
-}
-
-.hidden {
- display: none;
-}
diff --git a/components/pages/websites/WebsiteDetailsPage.js b/components/pages/websites/WebsiteDetailsPage.js
new file mode 100644
index 00000000..e6545ae2
--- /dev/null
+++ b/components/pages/websites/WebsiteDetailsPage.js
@@ -0,0 +1,37 @@
+import { Loading } from 'react-basics';
+import Page from 'components/layout/Page';
+import WebsiteChart from 'components/pages/websites/WebsiteChart';
+import FilterTags from 'components/metrics/FilterTags';
+import usePageQuery from 'hooks/usePageQuery';
+import WebsiteTableView from './WebsiteTableView';
+import WebsiteMenuView from './WebsiteMenuView';
+import { useWebsite } from 'hooks';
+import WebsiteHeader from './WebsiteHeader';
+import { WebsiteMetricsBar } from './WebsiteMetricsBar';
+
+export default function WebsiteDetailsPage({ websiteId }) {
+ const { data: website, isLoading, error } = useWebsite(websiteId);
+
+ const {
+ query: { view, url, referrer, os, browser, device, country, region, city, title },
+ } = usePageQuery();
+
+ return (
+
+
+
+
+
+ {!website && }
+ {website && (
+ <>
+ {!view && }
+ {view && }
+ >
+ )}
+
+ );
+}
diff --git a/components/pages/websites/WebsiteEventData.js b/components/pages/websites/WebsiteEventData.js
new file mode 100644
index 00000000..4ec18cb1
--- /dev/null
+++ b/components/pages/websites/WebsiteEventData.js
@@ -0,0 +1,39 @@
+import { Flexbox } from 'react-basics';
+import EventDataTable from 'components/pages/event-data/EventDataTable';
+import EventDataValueTable from 'components/pages/event-data/EventDataValueTable';
+import { EventDataMetricsBar } from 'components/pages/event-data/EventDataMetricsBar';
+import { useDateRange, useApi, usePageQuery } from 'hooks';
+
+function useFields(websiteId, field) {
+ const [dateRange] = useDateRange(websiteId);
+ const { startDate, endDate } = dateRange;
+ const { get, useQuery } = useApi();
+ const { data, error, isLoading } = useQuery(
+ ['event-data:fields', { websiteId, startDate, endDate, field }],
+ () =>
+ get('/event-data', {
+ websiteId,
+ startAt: +startDate,
+ endAt: +endDate,
+ field,
+ }),
+ { enabled: !!(websiteId && startDate && endDate) },
+ );
+
+ return { data, error, isLoading };
+}
+
+export default function WebsiteEventData({ websiteId }) {
+ const {
+ query: { view },
+ } = usePageQuery();
+ const { data } = useFields(websiteId, view);
+
+ return (
+
+
+ {!view && }
+ {view && }
+
+ );
+}
diff --git a/components/pages/websites/WebsiteEventData.module.css b/components/pages/websites/WebsiteEventData.module.css
new file mode 100644
index 00000000..a386e82a
--- /dev/null
+++ b/components/pages/websites/WebsiteEventData.module.css
@@ -0,0 +1,4 @@
+.container {
+ display: flex;
+ flex-direction: column;
+}
diff --git a/components/pages/websites/WebsiteEventDataPage.js b/components/pages/websites/WebsiteEventDataPage.js
new file mode 100644
index 00000000..08acafb5
--- /dev/null
+++ b/components/pages/websites/WebsiteEventDataPage.js
@@ -0,0 +1,12 @@
+import Page from 'components/layout/Page';
+import WebsiteHeader from './WebsiteHeader';
+import WebsiteEventData from './WebsiteEventData';
+
+export default function WebsiteEventDataPage({ websiteId }) {
+ return (
+
+
+
+
+ );
+}
diff --git a/components/pages/websites/WebsiteHeader.js b/components/pages/websites/WebsiteHeader.js
new file mode 100644
index 00000000..8802c320
--- /dev/null
+++ b/components/pages/websites/WebsiteHeader.js
@@ -0,0 +1,78 @@
+import classNames from 'classnames';
+import { Flexbox, Row, Column, Text, Button, Icon } from 'react-basics';
+import Link from 'next/link';
+import { useRouter } from 'next/router';
+import Favicon from 'components/common/Favicon';
+import ActiveUsers from 'components/metrics/ActiveUsers';
+import styles from './WebsiteHeader.module.css';
+import Icons from 'components/icons';
+import { useMessages, useWebsite } from 'hooks';
+
+export function WebsiteHeader({ websiteId, showLinks = true, children }) {
+ const { formatMessage, labels } = useMessages();
+ const { asPath, pathname } = useRouter();
+ const { data: website } = useWebsite(websiteId);
+ const { name, domain } = website || {};
+
+ const links = [
+ {
+ label: formatMessage(labels.overview),
+ icon:
,
+ path: '',
+ },
+ {
+ label: formatMessage(labels.realtime),
+ icon:
,
+ path: '/realtime',
+ },
+ {
+ label: formatMessage(labels.reports),
+ icon:
,
+ path: '/reports',
+ },
+ {
+ label: formatMessage(labels.eventData),
+ icon:
,
+ path: '/event-data',
+ },
+ ];
+
+ return (
+
+
+
+ {name}
+
+
+
+ {showLinks && (
+
+ {links.map(({ label, icon, path }) => {
+ const query = path.indexOf('?');
+ const selected = path
+ ? asPath.endsWith(query >= 0 ? path.substring(0, query) : path)
+ : pathname === '/websites/[id]';
+
+ return (
+
+
+
+ );
+ })}
+
+ )}
+ {children}
+
+
+ );
+}
+
+export default WebsiteHeader;
diff --git a/components/metrics/WebsiteHeader.module.css b/components/pages/websites/WebsiteHeader.module.css
similarity index 88%
rename from components/metrics/WebsiteHeader.module.css
rename to components/pages/websites/WebsiteHeader.module.css
index 68fd22f8..89f78e52 100644
--- a/components/metrics/WebsiteHeader.module.css
+++ b/components/pages/websites/WebsiteHeader.module.css
@@ -15,7 +15,7 @@
height: 100px;
}
-.info {
+.actions {
display: flex;
flex-direction: row;
align-items: center;
@@ -23,3 +23,7 @@
gap: 30px;
min-height: 0;
}
+
+.selected {
+ font-weight: bold;
+}
diff --git a/components/pages/websites/WebsiteMetricsBar.js b/components/pages/websites/WebsiteMetricsBar.js
new file mode 100644
index 00000000..9114e8f4
--- /dev/null
+++ b/components/pages/websites/WebsiteMetricsBar.js
@@ -0,0 +1,138 @@
+import { useState } from 'react';
+import classNames from 'classnames';
+import { Row, Column } from 'react-basics';
+import { formatShortTime, formatNumber, formatLongNumber } from 'lib/format';
+import MetricCard from 'components/metrics/MetricCard';
+import RefreshButton from 'components/input/RefreshButton';
+import WebsiteDateFilter from 'components/input/WebsiteDateFilter';
+import MetricsBar from 'components/metrics/MetricsBar';
+import { useApi, useDateRange, usePageQuery, useMessages, useSticky } from 'hooks';
+import styles from './WebsiteMetricsBar.module.css';
+
+export function WebsiteMetricsBar({ websiteId, sticky }) {
+ const { formatMessage, labels } = useMessages();
+ const { get, useQuery } = useApi();
+ const [dateRange] = useDateRange(websiteId);
+ const { startDate, endDate, modified } = dateRange;
+ const [format, setFormat] = useState(true);
+ const { ref, isSticky } = useSticky({ enabled: sticky });
+ const {
+ query: { url, referrer, title, os, browser, device, country, region, city },
+ } = usePageQuery();
+
+ const { data, error, isLoading, isFetched } = useQuery(
+ [
+ 'websites:stats',
+ { websiteId, modified, url, referrer, title, os, browser, device, country, region, city },
+ ],
+ () =>
+ get(`/websites/${websiteId}/stats`, {
+ startAt: +startDate,
+ endAt: +endDate,
+ url,
+ referrer,
+ title,
+ os,
+ browser,
+ device,
+ country,
+ region,
+ city,
+ }),
+ );
+
+ const formatFunc = format
+ ? n => (n >= 0 ? formatLongNumber(n) : `-${formatLongNumber(Math.abs(n))}`)
+ : formatNumber;
+
+ function handleSetFormat() {
+ setFormat(state => !state);
+ }
+
+ const { pageviews, uniques, bounces, totaltime } = data || {};
+ const num = Math.min(data && uniques.value, data && bounces.value);
+ const diffs = data && {
+ pageviews: pageviews.value - pageviews.change,
+ uniques: uniques.value - uniques.change,
+ bounces: bounces.value - bounces.change,
+ totaltime: totaltime.value - totaltime.change,
+ };
+
+ return (
+
+
+
+ {!error && isFetched && (
+ <>
+
+
+ Number(n).toFixed(0) + '%'}
+ reverseColors
+ />
+
+ `${n < 0 ? '-' : ''}${formatShortTime(Math.abs(~~n), ['m', 's'], ' ')}`
+ }
+ />
+ >
+ )}
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default WebsiteMetricsBar;
diff --git a/components/metrics/WebsiteChart.module.css b/components/pages/websites/WebsiteMetricsBar.module.css
similarity index 67%
rename from components/metrics/WebsiteChart.module.css
rename to components/pages/websites/WebsiteMetricsBar.module.css
index c9334a27..52decfc6 100644
--- a/components/metrics/WebsiteChart.module.css
+++ b/components/pages/websites/WebsiteMetricsBar.module.css
@@ -1,22 +1,4 @@
.container {
- position: relative;
- display: flex;
- flex-direction: column;
- align-self: stretch;
-}
-
-.chart {
- position: relative;
- overflow: hidden;
-}
-
-.title {
- font-size: var(--font-size-lg);
- line-height: 60px;
- font-weight: 600;
-}
-
-.header {
display: flex;
justify-content: space-between;
align-items: center;
@@ -35,8 +17,10 @@
gap: 10px;
}
-.dropdown {
- min-width: 200px;
+@media only screen and (max-width: 1200px) {
+ .actions {
+ margin-top: 40px;
+ }
}
@media only screen and (min-width: 992px) {
@@ -49,9 +33,3 @@
border-bottom: 1px solid var(--base300);
}
}
-
-@media only screen and (max-width: 1200px) {
- .actions {
- margin-top: 40px;
- }
-}
diff --git a/components/pages/websites/WebsiteReportsPage.js b/components/pages/websites/WebsiteReportsPage.js
new file mode 100644
index 00000000..b6f41bac
--- /dev/null
+++ b/components/pages/websites/WebsiteReportsPage.js
@@ -0,0 +1,30 @@
+import Page from 'components/layout/Page';
+import Link from 'next/link';
+import { Button, Icon, Icons, Text, Flexbox } from 'react-basics';
+import { useMessages, useReports } from 'hooks';
+import ReportsTable from 'components/pages/reports/ReportsTable';
+import WebsiteHeader from './WebsiteHeader';
+
+export function WebsiteReportsPage({ websiteId }) {
+ const { formatMessage, labels } = useMessages();
+ const { reports, error, isLoading } = useReports(websiteId);
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default WebsiteReportsPage;
diff --git a/hooks/index.js b/hooks/index.js
index 892d52e4..6a9b3b35 100644
--- a/hooks/index.js
+++ b/hooks/index.js
@@ -18,3 +18,4 @@ export * from './useSticky';
export * from './useTheme';
export * from './useTimezone';
export * from './useUser';
+export * from './useWebsite';
diff --git a/hooks/useReports.js b/hooks/useReports.js
index 0b5e60d0..90aa5cf5 100644
--- a/hooks/useReports.js
+++ b/hooks/useReports.js
@@ -1,8 +1,8 @@
import useApi from './useApi';
-export function useReports() {
+export function useReports(websiteId) {
const { get, useQuery } = useApi();
- const { data, error, isLoading } = useQuery(['reports'], () => get(`/reports`));
+ const { data, error, isLoading } = useQuery(['reports'], () => get(`/reports`, { websiteId }));
return { reports: data, error, isLoading };
}
diff --git a/hooks/useWebsite.js b/hooks/useWebsite.js
new file mode 100644
index 00000000..5315f0dc
--- /dev/null
+++ b/hooks/useWebsite.js
@@ -0,0 +1,10 @@
+import useApi from './useApi';
+
+export function useWebsite(websiteId) {
+ const { get, useQuery } = useApi();
+ return useQuery(['websites', websiteId], () => get(`/websites/${websiteId}`), {
+ enabled: !!websiteId,
+ });
+}
+
+export default useWebsite;
diff --git a/lib/constants.ts b/lib/constants.ts
index 5772e147..209a97b6 100644
--- a/lib/constants.ts
+++ b/lib/constants.ts
@@ -166,8 +166,9 @@ export const EVENT_COLORS = [
'#ffec16',
];
-export const DOMAIN_REGEX =
- /^(localhost(:[1-9]\d{0,4})?|((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63})$/;
+export const DOMAIN_REGEX =
+ /^(localhost(:[1-9]\d{0,4})?|((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9-]+(-[a-z0-9-]+)*\.)+(xn--)?[a-z0-9-]{2,63})$/;
+
export const SHARE_ID_REGEX = /^[a-zA-Z0-9]{16}$/;
@@ -182,7 +183,7 @@ export const DESKTOP_OS = [
'BeOS',
'Chrome OS',
'Linux',
- 'macOS',
+ 'Mac OS',
'Open BSD',
'OS/2',
'QNX',
@@ -204,33 +205,34 @@ export const DESKTOP_OS = [
export const MOBILE_OS = ['Amazon OS', 'Android OS', 'BlackBerry OS', 'iOS', 'Windows Mobile'];
export const BROWSERS = {
+ android: 'Android',
aol: 'AOL',
- edge: 'Edge',
- 'edge-ios': 'Edge (iOS)',
- yandexbrowser: 'Yandex',
- kakaotalk: 'KaKaoTalk',
- samsung: 'Samsung',
- silk: 'Silk',
- miui: 'MIUI',
beaker: 'Beaker',
- 'edge-chromium': 'Edge (Chromium)',
+ bb10: 'BlackBerry 10',
chrome: 'Chrome',
'chromium-webview': 'Chrome (webview)',
- phantomjs: 'PhantomJS',
crios: 'Chrome (iOS)',
+ curl: 'Curl',
+ edge: 'Edge',
+ 'edge-chromium': 'Edge (Chromium)',
+ 'edge-ios': 'Edge (iOS)',
+ facebook: 'Facebook',
firefox: 'Firefox',
fxios: 'Firefox (iOS)',
- 'opera-mini': 'Opera Mini',
- opera: 'Opera',
ie: 'IE',
- bb10: 'BlackBerry 10',
- android: 'Android',
- ios: 'iOS',
- safari: 'Safari',
- facebook: 'Facebook',
instagram: 'Instagram',
+ ios: 'iOS',
'ios-webview': 'iOS (webview)',
+ kakaotalk: 'KaKaoTalk',
+ miui: 'MIUI',
+ opera: 'Opera',
+ 'opera-mini': 'Opera Mini',
+ phantomjs: 'PhantomJS',
+ safari: 'Safari',
+ samsung: 'Samsung',
+ silk: 'Silk',
searchbot: 'Searchbot',
+ yandexbrowser: 'Yandex',
};
export const MAP_FILE = '/datamaps.world.json';
diff --git a/package.json b/package.json
index 6d5ed0fe..e69052d7 100644
--- a/package.json
+++ b/package.json
@@ -95,7 +95,7 @@
"node-fetch": "^3.2.8",
"npm-run-all": "^4.1.5",
"react": "^18.2.0",
- "react-basics": "^0.89.0",
+ "react-basics": "^0.91.0",
"react-beautiful-dnd": "^13.1.0",
"react-dom": "^18.2.0",
"react-error-boundary": "^4.0.4",
@@ -105,7 +105,7 @@
"react-use-measure": "^2.0.4",
"react-window": "^1.8.6",
"request-ip": "^3.3.0",
- "semver": "^7.3.6",
+ "semver": "^7.5.2",
"thenby": "^1.3.4",
"timezone-support": "^2.0.2",
"uuid": "^8.3.2",
@@ -151,7 +151,7 @@
"rollup-plugin-node-externals": "^5.1.2",
"rollup-plugin-postcss": "^4.0.2",
"rollup-plugin-terser": "^7.0.2",
- "stylelint": "^14.16.1",
+ "stylelint": "^15.10.1",
"stylelint-config-css-modules": "^4.1.0",
"stylelint-config-prettier": "^9.0.3",
"stylelint-config-recommended": "^9.0.0",
diff --git a/pages/api/event-data/fields.ts b/pages/api/event-data/fields.ts
new file mode 100644
index 00000000..f94d6c54
--- /dev/null
+++ b/pages/api/event-data/fields.ts
@@ -0,0 +1,36 @@
+import { canViewWebsite } from 'lib/auth';
+import { useCors, useAuth } from 'lib/middleware';
+import { NextApiRequestQueryBody } from 'lib/types';
+import { NextApiResponse } from 'next';
+import { ok, methodNotAllowed, unauthorized } from 'next-basics';
+import { getEventDataFields } from 'queries';
+
+export interface EventDataFieldsRequestBody {
+ websiteId: string;
+ dateRange: {
+ startDate: string;
+ endDate: string;
+ };
+}
+
+export default async (
+ req: NextApiRequestQueryBody
,
+ res: NextApiResponse,
+) => {
+ await useCors(req, res);
+ await useAuth(req, res);
+
+ if (req.method === 'GET') {
+ const { websiteId, startAt, endAt } = req.query;
+
+ if (!(await canViewWebsite(req.auth, websiteId))) {
+ return unauthorized(res);
+ }
+
+ const data = await getEventDataFields(websiteId, new Date(+startAt), new Date(+endAt));
+
+ return ok(res, data);
+ }
+
+ return methodNotAllowed(res);
+};
diff --git a/pages/api/event-data/index.ts b/pages/api/event-data/index.ts
new file mode 100644
index 00000000..d683156f
--- /dev/null
+++ b/pages/api/event-data/index.ts
@@ -0,0 +1,37 @@
+import { canViewWebsite } from 'lib/auth';
+import { useCors, useAuth } from 'lib/middleware';
+import { NextApiRequestQueryBody } from 'lib/types';
+import { NextApiResponse } from 'next';
+import { ok, methodNotAllowed, unauthorized } from 'next-basics';
+import { getEventData } from 'queries';
+
+export interface EventDataRequestBody {
+ websiteId: string;
+ dateRange: {
+ startDate: string;
+ endDate: string;
+ };
+ field?: string;
+}
+
+export default async (
+ req: NextApiRequestQueryBody,
+ res: NextApiResponse,
+) => {
+ await useCors(req, res);
+ await useAuth(req, res);
+
+ if (req.method === 'GET') {
+ const { websiteId, startAt, endAt, field } = req.query;
+
+ if (!(await canViewWebsite(req.auth, websiteId))) {
+ return unauthorized(res);
+ }
+
+ const data = await getEventData(websiteId, new Date(+startAt), new Date(+endAt), field);
+
+ return ok(res, data);
+ }
+
+ return methodNotAllowed(res);
+};
diff --git a/pages/api/reports/event-data.ts b/pages/api/reports/event-data.ts
deleted file mode 100644
index e9135f81..00000000
--- a/pages/api/reports/event-data.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-import { canViewWebsite } from 'lib/auth';
-import { useCors, useAuth } from 'lib/middleware';
-import { NextApiRequestQueryBody } from 'lib/types';
-import { NextApiResponse } from 'next';
-import { ok, methodNotAllowed, unauthorized } from 'next-basics';
-import { getEventDataFields } from 'queries/analytics/eventData/getEventDataFields';
-import { getEventData } from 'queries';
-
-export interface EventDataRequestBody {
- websiteId: string;
- dateRange: {
- startDate: string;
- endDate: string;
- };
- fields: [
- {
- name: string;
- type: string;
- value: string;
- },
- ];
- filters: [
- {
- name: string;
- type: string;
- value: string;
- },
- ];
- groups: [
- {
- name: string;
- type: string;
- },
- ];
-}
-
-export default async (
- req: NextApiRequestQueryBody,
- res: NextApiResponse,
-) => {
- await useCors(req, res);
- await useAuth(req, res);
-
- if (req.method === 'GET') {
- const { websiteId, startAt, endAt } = req.query;
-
- if (!(await canViewWebsite(req.auth, websiteId))) {
- return unauthorized(res);
- }
-
- const data = await getEventDataFields(websiteId, new Date(+startAt), new Date(+endAt));
-
- return ok(res, data);
- }
-
- if (req.method === 'POST') {
- const {
- websiteId,
- dateRange: { startDate, endDate },
- ...criteria
- } = req.body;
-
- if (!(await canViewWebsite(req.auth, websiteId))) {
- return unauthorized(res);
- }
-
- const data = await getEventData(
- websiteId,
- new Date(startDate),
- new Date(endDate),
- criteria as any,
- );
-
- return ok(res, data);
- }
-
- return methodNotAllowed(res);
-};
diff --git a/pages/api/reports/index.ts b/pages/api/reports/index.ts
index 55dc4bf5..b2c5da9e 100644
--- a/pages/api/reports/index.ts
+++ b/pages/api/reports/index.ts
@@ -2,8 +2,9 @@ import { uuid } from 'lib/crypto';
import { useAuth, useCors } from 'lib/middleware';
import { NextApiRequestQueryBody } from 'lib/types';
import { NextApiResponse } from 'next';
-import { methodNotAllowed, ok } from 'next-basics';
+import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { createReport, getReports } from 'queries';
+import { canViewWebsite } from 'lib/auth';
export interface ReportRequestBody {
websiteId: string;
@@ -23,12 +24,18 @@ export default async (
await useCors(req, res);
await useAuth(req, res);
+ const { websiteId } = req.query;
+
const {
user: { id: userId },
} = req.auth;
if (req.method === 'GET') {
- const data = await getReports(userId);
+ if (!(websiteId && (await canViewWebsite(req.auth, websiteId)))) {
+ return unauthorized(res);
+ }
+
+ const data = await getReports({ websiteId });
return ok(res, data);
}
diff --git a/pages/api/websites/[id]/eventData.ts b/pages/api/websites/[id]/eventData.ts
deleted file mode 100644
index 04a6d83b..00000000
--- a/pages/api/websites/[id]/eventData.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import { canViewWebsite } from 'lib/auth';
-import { useAuth, useCors } from 'lib/middleware';
-import { NextApiRequestQueryBody, WebsiteEventDataMetric } from 'lib/types';
-import { NextApiResponse } from 'next';
-import { methodNotAllowed, ok, unauthorized } from 'next-basics';
-import { getEventData } from 'queries';
-
-export interface WebsiteEventDataRequestQuery {
- id: string;
-}
-
-export interface WebsiteEventDataRequestBody {
- startAt: string;
- endAt: string;
- eventName?: string;
- urlPath?: string;
- timeSeries?: {
- unit: string;
- timezone: string;
- };
- filters: [
- {
- eventKey?: string;
- eventValue?: string | number | boolean | Date;
- },
- ];
-}
-
-export default async (
- req: NextApiRequestQueryBody,
- res: NextApiResponse,
-) => {
- await useCors(req, res);
- await useAuth(req, res);
-
- const { id: websiteId } = req.query;
-
- if (req.method === 'GET') {
- if (!(await canViewWebsite(req.auth, websiteId))) {
- return unauthorized(res);
- }
-
- const { startAt, endAt, eventName, urlPath, filters } = req.body;
-
- const startDate = new Date(+startAt);
- const endDate = new Date(+endAt);
-
- const events = await getEventData(websiteId, {
- startDate,
- endDate,
- eventName,
- urlPath,
- filters,
- });
-
- return ok(res, events);
- }
-
- return methodNotAllowed(res);
-};
diff --git a/pages/console/[[...id]].js b/pages/console/[[...id]].js
index b4bcf254..d13d6f68 100644
--- a/pages/console/[[...id]].js
+++ b/pages/console/[[...id]].js
@@ -1,7 +1,7 @@
import AppLayout from 'components/layout/AppLayout';
import TestConsole from 'components/pages/console/TestConsole';
-export default function ConsolePage({ disabled }) {
+export default function ({ disabled }) {
if (disabled) {
return null;
}
diff --git a/pages/index.js b/pages/index.js
index 7d93cef1..bd4c74be 100644
--- a/pages/index.js
+++ b/pages/index.js
@@ -1,7 +1,7 @@
import { useEffect } from 'react';
import { useRouter } from 'next/router';
-export default function DefaultPage() {
+export default function () {
const router = useRouter();
useEffect(() => {
diff --git a/pages/login.js b/pages/login.js
index 9a1f3c45..a43f8c1f 100644
--- a/pages/login.js
+++ b/pages/login.js
@@ -1,7 +1,7 @@
import LoginLayout from 'components/pages/login/LoginLayout';
import LoginForm from 'components/pages/login/LoginForm';
-export default function LoginPage({ disabled }) {
+export default function ({ disabled }) {
if (disabled) {
return null;
}
diff --git a/pages/logout.js b/pages/logout.js
index 6ffe23e1..675f1932 100644
--- a/pages/logout.js
+++ b/pages/logout.js
@@ -4,7 +4,7 @@ import useApi from 'hooks/useApi';
import { setUser } from 'store/app';
import { removeClientAuthToken } from 'lib/client';
-export default function LogoutPage({ disabled }) {
+export default function ({ disabled }) {
const router = useRouter();
const { post } = useApi();
diff --git a/pages/realtime/[id].js b/pages/realtime/[id].js
deleted file mode 100644
index c9e82622..00000000
--- a/pages/realtime/[id].js
+++ /dev/null
@@ -1,26 +0,0 @@
-import { useRouter } from 'next/router';
-import AppLayout from 'components/layout/AppLayout';
-import RealtimeDashboard from 'components/pages/realtime/RealtimeDashboard';
-import useMessages from 'hooks/useMessages';
-import useApi from 'hooks/useApi';
-
-export default function RealtimeDetailsPage() {
- const router = useRouter();
- const { id: websiteId } = router.query;
- const { formatMessage, labels } = useMessages();
- const { get, useQuery } = useApi();
- const { data: website } = useQuery(['websites', websiteId], () => get(`/websites/${websiteId}`), {
- enabled: !!websiteId,
- });
- const title = `${formatMessage(labels.realtime)}${website?.name ? ` - ${website.name}` : ''}`;
-
- if (!websiteId) {
- return null;
- }
-
- return (
-
-
-
- );
-}
diff --git a/pages/realtime/index.js b/pages/realtime/index.js
deleted file mode 100644
index bb016173..00000000
--- a/pages/realtime/index.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import AppLayout from 'components/layout/AppLayout';
-import RealtimeHome from 'components/pages/realtime/RealtimeHome';
-import useMessages from 'hooks/useMessages';
-
-export default function RealtimePage() {
- const { formatMessage, labels } = useMessages();
- return (
-
-
-
- );
-}
diff --git a/pages/reports/[id].js b/pages/reports/[id].js
index 36a84a2e..2520e87d 100644
--- a/pages/reports/[id].js
+++ b/pages/reports/[id].js
@@ -3,7 +3,7 @@ import AppLayout from 'components/layout/AppLayout';
import ReportDetails from 'components/pages/reports/ReportDetails';
import { useApi, useMessages } from 'hooks';
-export default function ReportsPage() {
+export default function () {
const { formatMessage, labels } = useMessages();
const router = useRouter();
const { id } = router.query;
diff --git a/pages/reports/create.js b/pages/reports/create.js
index 421b9c0a..763e2c63 100644
--- a/pages/reports/create.js
+++ b/pages/reports/create.js
@@ -2,7 +2,7 @@ import AppLayout from 'components/layout/AppLayout';
import ReportTemplates from 'components/pages/reports/ReportTemplates';
import { useMessages } from 'hooks';
-export default function ReportsPage() {
+export default function () {
const { formatMessage, labels } = useMessages();
return (
diff --git a/pages/reports/event-data.js b/pages/reports/event-data.js
index b5749c96..4566b320 100644
--- a/pages/reports/event-data.js
+++ b/pages/reports/event-data.js
@@ -2,7 +2,7 @@ import AppLayout from 'components/layout/AppLayout';
import EventDataReport from 'components/pages/reports/event-data/EventDataReport';
import { useMessages } from 'hooks';
-export default function Report() {
+export default function () {
const { formatMessage, labels } = useMessages();
return (
diff --git a/pages/reports/funnel.js b/pages/reports/funnel.js
index 731ffa47..4acdef37 100644
--- a/pages/reports/funnel.js
+++ b/pages/reports/funnel.js
@@ -2,7 +2,7 @@ import AppLayout from 'components/layout/AppLayout';
import FunnelReport from 'components/pages/reports/funnel/FunnelReport';
import useMessages from 'hooks/useMessages';
-export default function Funnel() {
+export default function () {
const { formatMessage, labels } = useMessages();
return (
diff --git a/pages/reports/index.js b/pages/reports/index.js
deleted file mode 100644
index 237f31ee..00000000
--- a/pages/reports/index.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import AppLayout from 'components/layout/AppLayout';
-import useMessages from 'hooks/useMessages';
-import ReportsList from 'components/pages/reports/ReportsList';
-
-export default function ReportsPage() {
- const { formatMessage, labels } = useMessages();
-
- return (
-
-
-
- );
-}
diff --git a/pages/settings/profile/index.js b/pages/settings/profile/index.js
index aef21bb6..8827f1da 100644
--- a/pages/settings/profile/index.js
+++ b/pages/settings/profile/index.js
@@ -3,7 +3,7 @@ import SettingsLayout from 'components/layout/SettingsLayout';
import ProfileSettings from 'components/pages/settings/profile/ProfileSettings';
import useMessages from 'hooks/useMessages';
-export default function ProfilePage() {
+export default function () {
const { formatMessage, labels } = useMessages();
return (
diff --git a/pages/settings/teams/[id].js b/pages/settings/teams/[id].js
index 6eb631b1..a68ef80c 100644
--- a/pages/settings/teams/[id].js
+++ b/pages/settings/teams/[id].js
@@ -4,7 +4,7 @@ import TeamSettings from 'components/pages/settings/teams/TeamSettings';
import { useRouter } from 'next/router';
import useMessages from 'hooks/useMessages';
-export default function TeamDetailPage({ disabled }) {
+export default function ({ disabled }) {
const router = useRouter();
const { id } = router.query;
const { formatMessage, labels } = useMessages();
diff --git a/pages/settings/teams/index.js b/pages/settings/teams/index.js
index 471ddf0f..51739c31 100644
--- a/pages/settings/teams/index.js
+++ b/pages/settings/teams/index.js
@@ -3,7 +3,7 @@ import SettingsLayout from 'components/layout/SettingsLayout';
import TeamsList from 'components/pages/settings/teams/TeamsList';
import useMessages from 'hooks/useMessages';
-export default function TeamsPage({ disabled }) {
+export default function ({ disabled }) {
const { formatMessage, labels } = useMessages();
if (disabled) {
return null;
diff --git a/pages/settings/users/[id].js b/pages/settings/users/[id].js
index 94df6951..d1e53419 100644
--- a/pages/settings/users/[id].js
+++ b/pages/settings/users/[id].js
@@ -4,7 +4,7 @@ import UserSettings from 'components/pages/settings/users/UserSettings';
import { useRouter } from 'next/router';
import useMessages from 'hooks/useMessages';
-export default function TeamDetailPage({ disabled }) {
+export default function ({ disabled }) {
const router = useRouter();
const { id } = router.query;
const { formatMessage, labels } = useMessages();
diff --git a/pages/settings/users/index.js b/pages/settings/users/index.js
index 6e021a74..ee325adc 100644
--- a/pages/settings/users/index.js
+++ b/pages/settings/users/index.js
@@ -3,7 +3,7 @@ import SettingsLayout from 'components/layout/SettingsLayout';
import UsersList from 'components/pages/settings/users/UsersList';
import useMessages from 'hooks/useMessages';
-export default function UsersPage({ disabled }) {
+export default function ({ disabled }) {
const { formatMessage, labels } = useMessages();
if (disabled) {
return null;
diff --git a/pages/settings/websites/[id].js b/pages/settings/websites/[id].js
index 7b97b8f5..f828369e 100644
--- a/pages/settings/websites/[id].js
+++ b/pages/settings/websites/[id].js
@@ -4,7 +4,7 @@ import WebsiteSettings from 'components/pages/settings/websites/WebsiteSettings'
import SettingsLayout from 'components/layout/SettingsLayout';
import useMessages from 'hooks/useMessages';
-export default function WebsiteSettingsPage({ disabled }) {
+export default function ({ disabled }) {
const router = useRouter();
const { id } = router.query;
const { formatMessage, labels } = useMessages();
diff --git a/pages/settings/websites/index.js b/pages/settings/websites/index.js
index c115b081..899ad7c7 100644
--- a/pages/settings/websites/index.js
+++ b/pages/settings/websites/index.js
@@ -3,7 +3,7 @@ import SettingsLayout from 'components/layout/SettingsLayout';
import WebsitesList from 'components/pages/settings/websites/WebsitesList';
import useMessages from 'hooks/useMessages';
-export default function WebsitesPage({ disabled }) {
+export default function ({ disabled }) {
const { formatMessage, labels } = useMessages();
if (disabled) {
return null;
diff --git a/pages/share/[...id].js b/pages/share/[...id].js
index aa6caab3..1e424382 100644
--- a/pages/share/[...id].js
+++ b/pages/share/[...id].js
@@ -1,9 +1,9 @@
import { useRouter } from 'next/router';
import ShareLayout from 'components/layout/ShareLayout';
-import WebsiteDetails from 'components/pages/websites/WebsiteDetails';
+import WebsiteDetailsPage from 'components/pages/websites/WebsiteDetailsPage';
import useShareToken from 'hooks/useShareToken';
-export default function SharePage() {
+export default function () {
const router = useRouter();
const { id } = router.query;
const shareId = id?.[0];
@@ -15,7 +15,7 @@ export default function SharePage() {
return (
-
+
);
}
diff --git a/pages/sso.js b/pages/sso.js
index c3d499c3..6e635206 100644
--- a/pages/sso.js
+++ b/pages/sso.js
@@ -3,7 +3,7 @@ import { Loading } from 'react-basics';
import { useRouter } from 'next/router';
import { setClientAuthToken } from 'lib/client';
-export default function SingleSignOnPage() {
+export default function () {
const router = useRouter();
const { token, url } = router.query;
diff --git a/pages/websites/[id]/event-data.js b/pages/websites/[id]/event-data.js
new file mode 100644
index 00000000..8b44616d
--- /dev/null
+++ b/pages/websites/[id]/event-data.js
@@ -0,0 +1,20 @@
+import { useRouter } from 'next/router';
+import AppLayout from 'components/layout/AppLayout';
+import WebsiteEventDataPage from 'components/pages/websites/WebsiteEventDataPage';
+import useMessages from 'hooks/useMessages';
+
+export default function () {
+ const { formatMessage, labels } = useMessages();
+ const router = useRouter();
+ const { id } = router.query;
+
+ if (!id) {
+ return null;
+ }
+
+ return (
+
+
+
+ );
+}
diff --git a/pages/websites/[id].js b/pages/websites/[id]/index.js
similarity index 71%
rename from pages/websites/[id].js
rename to pages/websites/[id]/index.js
index d2a258e1..bec7a45f 100644
--- a/pages/websites/[id].js
+++ b/pages/websites/[id]/index.js
@@ -1,9 +1,9 @@
import { useRouter } from 'next/router';
import AppLayout from 'components/layout/AppLayout';
-import WebsiteDetails from 'components/pages/websites/WebsiteDetails';
+import WebsiteDetailsPage from 'components/pages/websites/WebsiteDetailsPage';
import useMessages from 'hooks/useMessages';
-export default function DetailsPage() {
+export default function () {
const { formatMessage, labels } = useMessages();
const router = useRouter();
const { id } = router.query;
@@ -14,7 +14,7 @@ export default function DetailsPage() {
return (
-
+
);
}
diff --git a/pages/websites/[id]/realtime.js b/pages/websites/[id]/realtime.js
new file mode 100644
index 00000000..efe486a5
--- /dev/null
+++ b/pages/websites/[id]/realtime.js
@@ -0,0 +1,18 @@
+import { useRouter } from 'next/router';
+import AppLayout from 'components/layout/AppLayout';
+import RealtimePage from 'components/pages/realtime/RealtimePage';
+
+export default function () {
+ const router = useRouter();
+ const { id: websiteId } = router.query;
+
+ if (!websiteId) {
+ return null;
+ }
+
+ return (
+
+
+
+ );
+}
diff --git a/pages/websites/[id]/reports.js b/pages/websites/[id]/reports.js
new file mode 100644
index 00000000..ccd88081
--- /dev/null
+++ b/pages/websites/[id]/reports.js
@@ -0,0 +1,18 @@
+import { useRouter } from 'next/router';
+import AppLayout from 'components/layout/AppLayout';
+import WebsiteReportsPage from 'components/pages/websites/WebsiteReportsPage';
+
+export default function () {
+ const router = useRouter();
+ const { id: websiteId } = router.query;
+
+ if (!websiteId) {
+ return null;
+ }
+
+ return (
+
+
+
+ );
+}
diff --git a/public/images/browsers/android-webview.png b/public/images/browsers/android-webview.png
new file mode 100644
index 00000000..99242297
Binary files /dev/null and b/public/images/browsers/android-webview.png differ
diff --git a/public/images/browsers/android.png b/public/images/browsers/android.png
new file mode 100644
index 00000000..6e28498d
Binary files /dev/null and b/public/images/browsers/android.png differ
diff --git a/public/images/browsers/aol.png b/public/images/browsers/aol.png
new file mode 100644
index 00000000..66dc4288
Binary files /dev/null and b/public/images/browsers/aol.png differ
diff --git a/public/images/browsers/beaker.png b/public/images/browsers/beaker.png
new file mode 100644
index 00000000..fbc997cd
Binary files /dev/null and b/public/images/browsers/beaker.png differ
diff --git a/public/images/browsers/blackberry.png b/public/images/browsers/blackberry.png
new file mode 100644
index 00000000..74f255cb
Binary files /dev/null and b/public/images/browsers/blackberry.png differ
diff --git a/public/images/browsers/brave.png b/public/images/browsers/brave.png
new file mode 100644
index 00000000..0556c120
Binary files /dev/null and b/public/images/browsers/brave.png differ
diff --git a/public/images/browsers/chrome.png b/public/images/browsers/chrome.png
new file mode 100644
index 00000000..e4e2773f
Binary files /dev/null and b/public/images/browsers/chrome.png differ
diff --git a/public/images/browsers/chromium-webview.png b/public/images/browsers/chromium-webview.png
new file mode 100644
index 00000000..a3fd998d
Binary files /dev/null and b/public/images/browsers/chromium-webview.png differ
diff --git a/public/images/browsers/crios.png b/public/images/browsers/crios.png
new file mode 100644
index 00000000..e4e2773f
Binary files /dev/null and b/public/images/browsers/crios.png differ
diff --git a/public/images/browsers/curl.png b/public/images/browsers/curl.png
new file mode 100644
index 00000000..dd221927
Binary files /dev/null and b/public/images/browsers/curl.png differ
diff --git a/public/images/browsers/edge-chromium.png b/public/images/browsers/edge-chromium.png
new file mode 100644
index 00000000..1f2b230f
Binary files /dev/null and b/public/images/browsers/edge-chromium.png differ
diff --git a/public/images/browsers/edge-ios.png b/public/images/browsers/edge-ios.png
new file mode 100644
index 00000000..1f2b230f
Binary files /dev/null and b/public/images/browsers/edge-ios.png differ
diff --git a/public/images/browsers/edge.png b/public/images/browsers/edge.png
new file mode 100644
index 00000000..3881a7e0
Binary files /dev/null and b/public/images/browsers/edge.png differ
diff --git a/public/images/browsers/facebook.png b/public/images/browsers/facebook.png
new file mode 100644
index 00000000..4dc9b267
Binary files /dev/null and b/public/images/browsers/facebook.png differ
diff --git a/public/images/browsers/firefox.png b/public/images/browsers/firefox.png
new file mode 100644
index 00000000..c118f9c1
Binary files /dev/null and b/public/images/browsers/firefox.png differ
diff --git a/public/images/browsers/fxios.png b/public/images/browsers/fxios.png
new file mode 100644
index 00000000..c118f9c1
Binary files /dev/null and b/public/images/browsers/fxios.png differ
diff --git a/public/images/browsers/ie.png b/public/images/browsers/ie.png
new file mode 100644
index 00000000..1d3bbe8f
Binary files /dev/null and b/public/images/browsers/ie.png differ
diff --git a/public/images/browsers/instagram.png b/public/images/browsers/instagram.png
new file mode 100644
index 00000000..5961a6b3
Binary files /dev/null and b/public/images/browsers/instagram.png differ
diff --git a/public/images/browsers/ios-webview.png b/public/images/browsers/ios-webview.png
new file mode 100644
index 00000000..5f2dd401
Binary files /dev/null and b/public/images/browsers/ios-webview.png differ
diff --git a/public/images/browsers/ios.png b/public/images/browsers/ios.png
new file mode 100644
index 00000000..5f2dd401
Binary files /dev/null and b/public/images/browsers/ios.png differ
diff --git a/public/images/browsers/kakaotalk.png b/public/images/browsers/kakaotalk.png
new file mode 100644
index 00000000..e932a67b
Binary files /dev/null and b/public/images/browsers/kakaotalk.png differ
diff --git a/public/images/browsers/miui.png b/public/images/browsers/miui.png
new file mode 100644
index 00000000..5f929510
Binary files /dev/null and b/public/images/browsers/miui.png differ
diff --git a/public/images/browsers/opera-mini.png b/public/images/browsers/opera-mini.png
new file mode 100644
index 00000000..d4e26712
Binary files /dev/null and b/public/images/browsers/opera-mini.png differ
diff --git a/public/images/browsers/opera.png b/public/images/browsers/opera.png
new file mode 100644
index 00000000..84e6d0fc
Binary files /dev/null and b/public/images/browsers/opera.png differ
diff --git a/public/images/browsers/safari.png b/public/images/browsers/safari.png
new file mode 100644
index 00000000..b06369aa
Binary files /dev/null and b/public/images/browsers/safari.png differ
diff --git a/public/images/browsers/samsung.png b/public/images/browsers/samsung.png
new file mode 100644
index 00000000..544e390e
Binary files /dev/null and b/public/images/browsers/samsung.png differ
diff --git a/public/images/browsers/searchbot.png b/public/images/browsers/searchbot.png
new file mode 100644
index 00000000..46a33055
Binary files /dev/null and b/public/images/browsers/searchbot.png differ
diff --git a/public/images/browsers/silk.png b/public/images/browsers/silk.png
new file mode 100644
index 00000000..6af1d726
Binary files /dev/null and b/public/images/browsers/silk.png differ
diff --git a/public/images/browsers/unknown.png b/public/images/browsers/unknown.png
new file mode 100644
index 00000000..52058026
Binary files /dev/null and b/public/images/browsers/unknown.png differ
diff --git a/public/images/browsers/yandexbrowser.png b/public/images/browsers/yandexbrowser.png
new file mode 100644
index 00000000..f703db23
Binary files /dev/null and b/public/images/browsers/yandexbrowser.png differ
diff --git a/public/images/device/desktop.png b/public/images/device/desktop.png
new file mode 100644
index 00000000..d5ede419
Binary files /dev/null and b/public/images/device/desktop.png differ
diff --git a/public/images/device/laptop.png b/public/images/device/laptop.png
new file mode 100644
index 00000000..19f66967
Binary files /dev/null and b/public/images/device/laptop.png differ
diff --git a/public/images/device/mobile.png b/public/images/device/mobile.png
new file mode 100644
index 00000000..d2190f4a
Binary files /dev/null and b/public/images/device/mobile.png differ
diff --git a/public/images/device/tablet.png b/public/images/device/tablet.png
new file mode 100644
index 00000000..5e06bcff
Binary files /dev/null and b/public/images/device/tablet.png differ
diff --git a/public/images/device/unknown.png b/public/images/device/unknown.png
new file mode 100644
index 00000000..52058026
Binary files /dev/null and b/public/images/device/unknown.png differ
diff --git a/public/images/os/amazon-os.png b/public/images/os/amazon-os.png
new file mode 100644
index 00000000..9b18cf0f
Binary files /dev/null and b/public/images/os/amazon-os.png differ
diff --git a/public/images/os/android-os.png b/public/images/os/android-os.png
new file mode 100644
index 00000000..fc6509b3
Binary files /dev/null and b/public/images/os/android-os.png differ
diff --git a/public/images/os/beos.png b/public/images/os/beos.png
new file mode 100644
index 00000000..6bc4a8a5
Binary files /dev/null and b/public/images/os/beos.png differ
diff --git a/public/images/os/blackberry-os.png b/public/images/os/blackberry-os.png
new file mode 100644
index 00000000..c77db525
Binary files /dev/null and b/public/images/os/blackberry-os.png differ
diff --git a/public/images/os/chrome-os.png b/public/images/os/chrome-os.png
new file mode 100644
index 00000000..ae008601
Binary files /dev/null and b/public/images/os/chrome-os.png differ
diff --git a/public/images/os/ios.png b/public/images/os/ios.png
new file mode 100644
index 00000000..1c129ae8
Binary files /dev/null and b/public/images/os/ios.png differ
diff --git a/public/images/os/linux.png b/public/images/os/linux.png
new file mode 100644
index 00000000..ce8fba38
Binary files /dev/null and b/public/images/os/linux.png differ
diff --git a/public/images/os/mac-os.png b/public/images/os/mac-os.png
new file mode 100644
index 00000000..1972abe7
Binary files /dev/null and b/public/images/os/mac-os.png differ
diff --git a/public/images/os/open-bsd.png b/public/images/os/open-bsd.png
new file mode 100644
index 00000000..806887e8
Binary files /dev/null and b/public/images/os/open-bsd.png differ
diff --git a/public/images/os/os-2.png b/public/images/os/os-2.png
new file mode 100644
index 00000000..5f88105d
Binary files /dev/null and b/public/images/os/os-2.png differ
diff --git a/public/images/os/qnx.png b/public/images/os/qnx.png
new file mode 100644
index 00000000..59d9a44c
Binary files /dev/null and b/public/images/os/qnx.png differ
diff --git a/public/images/os/sun-os.png b/public/images/os/sun-os.png
new file mode 100644
index 00000000..c19f0eb3
Binary files /dev/null and b/public/images/os/sun-os.png differ
diff --git a/public/images/os/unknown.png b/public/images/os/unknown.png
new file mode 100644
index 00000000..52058026
Binary files /dev/null and b/public/images/os/unknown.png differ
diff --git a/public/images/os/windows-10.png b/public/images/os/windows-10.png
new file mode 100644
index 00000000..4effcd2b
Binary files /dev/null and b/public/images/os/windows-10.png differ
diff --git a/public/images/os/windows-11.png b/public/images/os/windows-11.png
new file mode 100644
index 00000000..4effcd2b
Binary files /dev/null and b/public/images/os/windows-11.png differ
diff --git a/public/images/os/windows-2000.png b/public/images/os/windows-2000.png
new file mode 100644
index 00000000..3bccae3f
Binary files /dev/null and b/public/images/os/windows-2000.png differ
diff --git a/public/images/os/windows-3-11.png b/public/images/os/windows-3-11.png
new file mode 100644
index 00000000..3bccae3f
Binary files /dev/null and b/public/images/os/windows-3-11.png differ
diff --git a/public/images/os/windows-7.png b/public/images/os/windows-7.png
new file mode 100644
index 00000000..cd2db79e
Binary files /dev/null and b/public/images/os/windows-7.png differ
diff --git a/public/images/os/windows-8-1.png b/public/images/os/windows-8-1.png
new file mode 100644
index 00000000..3ce98aaa
Binary files /dev/null and b/public/images/os/windows-8-1.png differ
diff --git a/public/images/os/windows-8.png b/public/images/os/windows-8.png
new file mode 100644
index 00000000..3ce98aaa
Binary files /dev/null and b/public/images/os/windows-8.png differ
diff --git a/public/images/os/windows-95.png b/public/images/os/windows-95.png
new file mode 100644
index 00000000..3bccae3f
Binary files /dev/null and b/public/images/os/windows-95.png differ
diff --git a/public/images/os/windows-98.png b/public/images/os/windows-98.png
new file mode 100644
index 00000000..3bccae3f
Binary files /dev/null and b/public/images/os/windows-98.png differ
diff --git a/public/images/os/windows-me.png b/public/images/os/windows-me.png
new file mode 100644
index 00000000..cd2db79e
Binary files /dev/null and b/public/images/os/windows-me.png differ
diff --git a/public/images/os/windows-server-2003.png b/public/images/os/windows-server-2003.png
new file mode 100644
index 00000000..cd2db79e
Binary files /dev/null and b/public/images/os/windows-server-2003.png differ
diff --git a/public/images/os/windows-vista.png b/public/images/os/windows-vista.png
new file mode 100644
index 00000000..cd2db79e
Binary files /dev/null and b/public/images/os/windows-vista.png differ
diff --git a/public/images/os/windows-xp.png b/public/images/os/windows-xp.png
new file mode 100644
index 00000000..cd2db79e
Binary files /dev/null and b/public/images/os/windows-xp.png differ
diff --git a/queries/admin/report.ts b/queries/admin/report.ts
index 506902f5..6b557a7a 100644
--- a/queries/admin/report.ts
+++ b/queries/admin/report.ts
@@ -13,11 +13,9 @@ export async function getReportById(reportId: string): Promise {
});
}
-export async function getReports(userId: string): Promise {
+export async function getReports(where: Prisma.ReportWhereInput): Promise {
return prisma.client.report.findMany({
- where: {
- userId,
- },
+ where,
});
}
diff --git a/queries/analytics/eventData/getEventData.ts b/queries/analytics/eventData/getEventData.ts
index 8a3b2927..1190e191 100644
--- a/queries/analytics/eventData/getEventData.ts
+++ b/queries/analytics/eventData/getEventData.ts
@@ -1,28 +1,12 @@
+import prisma from 'lib/prisma';
import clickhouse from 'lib/clickhouse';
import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db';
import { WebsiteEventDataMetric } from 'lib/types';
import { loadWebsite } from 'lib/query';
import { DEFAULT_CREATED_AT } from 'lib/constants';
-export interface EventDataCriteria {
- fields: [{ name: string; type: string; value: string }];
- filters: [
- {
- name: string;
- type: string;
- value: [string, string];
- },
- ];
- groups: [
- {
- name: string;
- type: string;
- },
- ];
-}
-
export async function getEventData(
- ...args: [websiteId: string, startDate: Date, endDate: Date, criteria: EventDataCriteria]
+ ...args: [websiteId: string, startDate: Date, endDate: Date, field?: string]
): Promise {
return runQuery({
[PRISMA]: () => relationalQuery(...args),
@@ -30,70 +14,81 @@ export async function getEventData(
});
}
-async function relationalQuery() {
- return null;
+async function relationalQuery(websiteId: string, startDate: Date, endDate: Date, field: string) {
+ const { toUuid, rawQuery } = prisma;
+ const website = await loadWebsite(websiteId);
+ const resetDate = new Date(website?.resetAt || DEFAULT_CREATED_AT);
+
+ if (field) {
+ return rawQuery(
+ `select event_key as field,
+ string_value as value,
+ count(*) as total
+ from event_data
+ where website_id = $1${toUuid()}
+ and event_key = $2
+ and created_at >= $3
+ and created_at between $4 and $5
+ group by event_key, string_value
+ order by 3 desc, 2 desc, 1 asc
+ limit 100
+ `,
+ [websiteId, field, resetDate, startDate, endDate] as any,
+ );
+ }
+
+ return rawQuery(
+ `select
+ event_key as field,
+ count(*) as total
+ from event_data
+ where website_id = $1${toUuid()}
+ and created_at >= $2
+ and created_at between $3 and $4
+ group by event_key
+ order by 2 desc, 1 asc
+ limit 100
+ `,
+ [websiteId, resetDate, startDate, endDate] as any,
+ );
}
-async function clickhouseQuery(
- websiteId: string,
- startDate: Date,
- endDate: Date,
- criteria: EventDataCriteria,
-) {
- const { fields } = criteria;
+async function clickhouseQuery(websiteId: string, startDate: Date, endDate: Date, field: string) {
const { rawQuery, getDateFormat, getBetweenDates } = clickhouse;
const website = await loadWebsite(websiteId);
const resetDate = new Date(website?.resetAt || DEFAULT_CREATED_AT);
- const uniqueFields = fields.reduce((obj, { name, type }) => {
- if (!obj[name]) {
- obj[name] = {
- columns: ['event_key as field', `count(*) as total`, `${type}_value as value`],
- groups: ['event_key', `${type}_value`],
- };
- }
- return obj;
- }, {});
-
- const queries = Object.keys(uniqueFields).reduce((arr, key) => {
- const field = uniqueFields[key];
- const params = { websiteId, name: key };
-
- return arr.concat(
- rawQuery(
- `select
- ${field.columns.join(',')}
+ if (field) {
+ return rawQuery(
+ `select
+ event_key as field,
+ string_value as value,
+ count(*) as total
from event_data
where website_id = {websiteId:UUID}
- and event_key = {name:String}
+ and event_key = {field:String}
and created_at >= ${getDateFormat(resetDate)}
and ${getBetweenDates('created_at', startDate, endDate)}
- group by ${field.groups.join(',')}
- limit 20
+ group by event_key, string_value
+ order by 3 desc, 2 desc, 1 asc
+ limit 100
`,
- params,
- ),
+ { websiteId, field },
);
- }, []);
+ }
- const results = (await Promise.all(queries)).flatMap(n => n);
-
- const columns = results.reduce((arr, row) => {
- const keys = Object.keys(row);
- for (const key of keys) {
- if (!arr.includes(key)) {
- arr.push(key);
- }
- }
- return arr;
- }, []);
-
- return results.reduce((arr, row) => {
- return arr.concat(
- columns.reduce((obj, key) => {
- obj[key] = row[key];
- return obj;
- }, {}),
- );
- }, []);
+ return rawQuery(
+ `select
+ event_key as field,
+ count(*) as total
+ from event_data
+ where website_id = {websiteId:UUID}
+ and created_at >= ${getDateFormat(resetDate)}
+ and ${getBetweenDates('created_at', startDate, endDate)}
+ group by event_key
+ order by 2 desc, 1 asc
+ limit 100
+ `,
+ { websiteId },
+ );
}
diff --git a/yarn.lock b/yarn.lock
index fc94a0f7..7dafda26 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1072,6 +1072,21 @@
dependencies:
"@jridgewell/trace-mapping" "0.3.9"
+"@csstools/css-parser-algorithms@^2.3.0":
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.3.0.tgz#0cc3a656dc2d638370ecf6f98358973bfbd00141"
+ integrity sha512-dTKSIHHWc0zPvcS5cqGP+/TPFUJB0ekJ9dGKvMAFoNuBFhDPBt9OMGNZiIA5vTiNdGHHBeScYPXIGBMnVOahsA==
+
+"@csstools/css-tokenizer@^2.1.1":
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-2.1.1.tgz#07ae11a0a06365d7ec686549db7b729bc036528e"
+ integrity sha512-GbrTj2Z8MCTUv+52GE0RbFGM527xuXZ0Xa5g0Z+YN573uveS4G0qi6WNOMyz3yrFM/jaILTTwJ0+umx81EzqfA==
+
+"@csstools/media-query-list-parser@^2.1.2":
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.2.tgz#6ef642b728d30c1009bfbba3211c7e4c11302728"
+ integrity sha512-M8cFGGwl866o6++vIY7j1AKuq9v57cf+dGepScwCcbut9ypJNr4Cj+LLTWligYUZ0uyhEoJDKt5lvyBfh2L3ZQ==
+
"@csstools/postcss-cascade-layers@^1.1.1":
version "1.1.1"
resolved "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-1.1.1.tgz"
@@ -1183,6 +1198,11 @@
resolved "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz"
integrity sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==
+"@csstools/selector-specificity@^3.0.0":
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-3.0.0.tgz#798622546b63847e82389e473fd67f2707d82247"
+ integrity sha512-hBI9tfBtuPIi885ZsZ32IMEU/5nlZH/KOVYJCOh7gyMxaVLGmLedYqFN6Ui1LXkI8JlC8IsuC0rF0btcRZKd5g==
+
"@esbuild/android-arm64@0.17.19":
version "0.17.19"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz#bafb75234a5d3d1b690e7c2956a599345e84a2fd"
@@ -2367,7 +2387,7 @@
resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz"
integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==
-"@types/minimist@^1.2.0":
+"@types/minimist@^1.2.0", "@types/minimist@^1.2.2":
version "1.2.2"
resolved "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz"
integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==
@@ -3110,12 +3130,22 @@ camelcase-keys@^6.2.2:
map-obj "^4.0.0"
quick-lru "^4.0.1"
+camelcase-keys@^7.0.0:
+ version "7.0.2"
+ resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-7.0.2.tgz#d048d8c69448745bb0de6fc4c1c52a30dfbe7252"
+ integrity sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg==
+ dependencies:
+ camelcase "^6.3.0"
+ map-obj "^4.1.0"
+ quick-lru "^5.1.1"
+ type-fest "^1.2.1"
+
camelcase@^5.0.0, camelcase@^5.3.1:
version "5.3.1"
resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
-camelcase@^6.2.0:
+camelcase@^6.2.0, camelcase@^6.3.0:
version "6.3.0"
resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz"
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
@@ -3401,7 +3431,7 @@ cors@^2.8.5:
object-assign "^4"
vary "^1"
-cosmiconfig@^7.0.1, cosmiconfig@^7.1.0:
+cosmiconfig@^7.0.1:
version "7.1.0"
resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz"
integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==
@@ -3422,6 +3452,16 @@ cosmiconfig@^8.1.3:
parse-json "^5.0.0"
path-type "^4.0.0"
+cosmiconfig@^8.2.0:
+ version "8.2.0"
+ resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.2.0.tgz#f7d17c56a590856cd1e7cee98734dca272b0d8fd"
+ integrity sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==
+ dependencies:
+ import-fresh "^3.2.1"
+ js-yaml "^4.1.0"
+ parse-json "^5.0.0"
+ path-type "^4.0.0"
+
create-require@^1.1.0:
version "1.1.1"
resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz"
@@ -3520,7 +3560,7 @@ css-tree@^1.1.2, css-tree@^1.1.3:
mdn-data "2.0.14"
source-map "^0.6.1"
-css-tree@^2.2.1:
+css-tree@^2.2.1, css-tree@^2.3.1:
version "2.3.1"
resolved "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz"
integrity sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==
@@ -3778,6 +3818,11 @@ decamelize@^1.1.0, decamelize@^1.2.0:
resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz"
integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==
+decamelize@^5.0.0:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-5.0.1.tgz#db11a92e58c741ef339fb0a2868d8a06a9a7b1e9"
+ integrity sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==
+
decompress-response@^6.0.0:
version "6.0.0"
resolved "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz"
@@ -4470,7 +4515,7 @@ fast-equals@^3.0.1:
resolved "https://registry.npmjs.org/fast-equals/-/fast-equals-3.0.3.tgz"
integrity sha512-NCe8qxnZFARSHGztGMZOO/PC1qa5MIFB5Hp66WdzbCRAz8U8US3bx1UTgLS49efBQPcUtO9gf5oVEY8o7y/7Kg==
-fast-glob@^3.0.3, fast-glob@^3.2.12, fast-glob@^3.2.7, fast-glob@^3.2.9:
+fast-glob@^3.0.3, fast-glob@^3.2.7, fast-glob@^3.2.9:
version "3.2.12"
resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz"
integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==
@@ -4481,6 +4526,17 @@ fast-glob@^3.0.3, fast-glob@^3.2.12, fast-glob@^3.2.7, fast-glob@^3.2.9:
merge2 "^1.3.0"
micromatch "^4.0.4"
+fast-glob@^3.3.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.0.tgz#7c40cb491e1e2ed5664749e87bfb516dbe8727c0"
+ integrity sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==
+ dependencies:
+ "@nodelib/fs.stat" "^2.0.2"
+ "@nodelib/fs.walk" "^1.2.3"
+ glob-parent "^5.1.2"
+ merge2 "^1.3.0"
+ micromatch "^4.0.4"
+
fast-json-stable-stringify@^2.0.0:
version "2.1.0"
resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz"
@@ -4996,10 +5052,10 @@ hosted-git-info@^4.0.1:
dependencies:
lru-cache "^6.0.0"
-html-tags@^3.2.0:
- version "3.2.0"
- resolved "https://registry.npmjs.org/html-tags/-/html-tags-3.2.0.tgz"
- integrity sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==
+html-tags@^3.3.1:
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce"
+ integrity sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==
http-shutdown@^1.2.2:
version "1.2.2"
@@ -5040,7 +5096,7 @@ ieee754@^1.1.13:
resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
-ignore@^5.1.1, ignore@^5.2.0, ignore@^5.2.1:
+ignore@^5.1.1, ignore@^5.2.0, ignore@^5.2.4:
version "5.2.4"
resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz"
integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==
@@ -5092,6 +5148,11 @@ indent-string@^4.0.0:
resolved "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz"
integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
+indent-string@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-5.0.0.tgz#4fd2980fccaf8622d14c64d694f4cf33c81951a5"
+ integrity sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==
+
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz"
@@ -5649,10 +5710,10 @@ kleur@^3.0.3:
resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz"
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
-known-css-properties@^0.26.0:
- version "0.26.0"
- resolved "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.26.0.tgz"
- integrity sha512-5FZRzrZzNTBruuurWpvZnvP9pum+fe0HcK8z/ooo+U+Hmp4vtbyp1/QDsqmufirXy4egGzbaH/y2uCZf+6W5Kg==
+known-css-properties@^0.27.0:
+ version "0.27.0"
+ resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.27.0.tgz#82a9358dda5fe7f7bd12b5e7142c0a205393c0c5"
+ integrity sha512-uMCj6+hZYDoffuvAJjFAPz56E9uoowFHmTkqRtRq5WyC5Q6Cu/fTZKNQpX/RbzChBYLLl3lo8CjFZBAZXq9qFg==
language-subtag-registry@^0.3.20:
version "0.3.22"
@@ -5926,7 +5987,7 @@ map-obj@^1.0.0:
resolved "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz"
integrity sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==
-map-obj@^4.0.0:
+map-obj@^4.0.0, map-obj@^4.1.0:
version "4.3.0"
resolved "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz"
integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==
@@ -5969,6 +6030,24 @@ memorystream@^0.3.1:
resolved "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz"
integrity sha1-htcJCzDORV1j+64S3aUaR93K+bI=
+meow@^10.1.5:
+ version "10.1.5"
+ resolved "https://registry.yarnpkg.com/meow/-/meow-10.1.5.tgz#be52a1d87b5f5698602b0f32875ee5940904aa7f"
+ integrity sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw==
+ dependencies:
+ "@types/minimist" "^1.2.2"
+ camelcase-keys "^7.0.0"
+ decamelize "^5.0.0"
+ decamelize-keys "^1.1.0"
+ hard-rejection "^2.1.0"
+ minimist-options "4.1.0"
+ normalize-package-data "^3.0.2"
+ read-pkg-up "^8.0.0"
+ redent "^4.0.0"
+ trim-newlines "^4.0.2"
+ type-fest "^1.2.2"
+ yargs-parser "^20.2.9"
+
meow@^6.1.0:
version "6.1.1"
resolved "https://registry.npmjs.org/meow/-/meow-6.1.1.tgz"
@@ -5986,24 +6065,6 @@ meow@^6.1.0:
type-fest "^0.13.1"
yargs-parser "^18.1.3"
-meow@^9.0.0:
- version "9.0.0"
- resolved "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz"
- integrity sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==
- dependencies:
- "@types/minimist" "^1.2.0"
- camelcase-keys "^6.2.2"
- decamelize "^1.2.0"
- decamelize-keys "^1.1.0"
- hard-rejection "^2.1.0"
- minimist-options "4.1.0"
- normalize-package-data "^3.0.0"
- read-pkg-up "^7.0.1"
- redent "^3.0.0"
- trim-newlines "^3.0.0"
- type-fest "^0.18.0"
- yargs-parser "^20.2.3"
-
merge-stream@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz"
@@ -6049,7 +6110,7 @@ mimic-response@^3.1.0:
resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz"
integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==
-min-indent@^1.0.0:
+min-indent@^1.0.0, min-indent@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz"
integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
@@ -6293,9 +6354,9 @@ normalize-package-data@^2.3.2, normalize-package-data@^2.5.0:
semver "2 || 3 || 4 || 5"
validate-npm-package-license "^3.0.1"
-normalize-package-data@^3.0.0:
+normalize-package-data@^3.0.2:
version "3.0.3"
- resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz"
+ resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.3.tgz#dbcc3e2da59509a0983422884cd172eefdfa525e"
integrity sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==
dependencies:
hosted-git-info "^4.0.1"
@@ -6544,7 +6605,7 @@ parse-json@^4.0.0:
error-ex "^1.3.1"
json-parse-better-errors "^1.0.1"
-parse-json@^5.0.0:
+parse-json@^5.0.0, parse-json@^5.2.0:
version "5.2.0"
resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz"
integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==
@@ -7149,7 +7210,7 @@ postcss-selector-not@^6.0.1:
dependencies:
postcss-selector-parser "^6.0.10"
-postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.6, postcss-selector-parser@^6.0.9:
+postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.6, postcss-selector-parser@^6.0.9:
version "6.0.11"
resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz"
integrity sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==
@@ -7157,6 +7218,14 @@ postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.11, postcss-select
cssesc "^3.0.0"
util-deprecate "^1.0.2"
+postcss-selector-parser@^6.0.13:
+ version "6.0.13"
+ resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz#d05d8d76b1e8e173257ef9d60b706a8e5e99bf1b"
+ integrity sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==
+ dependencies:
+ cssesc "^3.0.0"
+ util-deprecate "^1.0.2"
+
postcss-svgo@^5.1.0:
version "5.1.0"
resolved "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz"
@@ -7186,7 +7255,7 @@ postcss@8.4.14:
picocolors "^1.0.0"
source-map-js "^1.0.2"
-postcss@^8.1.10, postcss@^8.4.19, postcss@^8.4.21:
+postcss@^8.1.10, postcss@^8.4.21:
version "8.4.24"
resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz"
integrity sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==
@@ -7195,6 +7264,15 @@ postcss@^8.1.10, postcss@^8.4.19, postcss@^8.4.21:
picocolors "^1.0.0"
source-map-js "^1.0.2"
+postcss@^8.4.24:
+ version "8.4.25"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.25.tgz#4a133f5e379eda7f61e906c3b1aaa9b81292726f"
+ integrity sha512-7taJ/8t2av0Z+sQEvNzCkpDynl0tX3uJMCODi6nT3PfASC7dYCWV9aQ+uiCf+KBD4SEFcu+GvJdGdwzQ6OSjCw==
+ dependencies:
+ nanoid "^3.3.6"
+ picocolors "^1.0.0"
+ source-map-js "^1.0.2"
+
prebuild-install@^7.1.1:
version "7.1.1"
resolved "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz"
@@ -7317,6 +7395,11 @@ quick-lru@^4.0.1:
resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz"
integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==
+quick-lru@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
+ integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
+
radix3@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/radix3/-/radix3-1.0.1.tgz"
@@ -7344,10 +7427,10 @@ rc@^1.2.7:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
-react-basics@^0.89.0:
- version "0.89.0"
- resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.89.0.tgz#672a14448818fc7f20a3f7d73d0340d2165f94f2"
- integrity sha512-nsYZCCfAjEy/fVt+5te3kQEyqA+4dEFutI9n7ol36eWmWbBJjZXCF1NgSHsosMYN2wlrpsrI7HoMTgL68FQnUg==
+react-basics@^0.91.0:
+ version "0.91.0"
+ resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.91.0.tgz#2970529a22a455ec73a1be884eb93a109c9dafc0"
+ integrity sha512-vP8LYWiFwA+eguMEuHvHct4Jl5R/2GUjWc1tMujDG0CsAAUGhx68tAJr0K3gBrWjmpJrTPVfX8SdBNKSDAjQsw==
dependencies:
classnames "^2.3.1"
date-fns "^2.29.3"
@@ -7499,6 +7582,15 @@ read-pkg-up@^7.0.1:
read-pkg "^5.2.0"
type-fest "^0.8.1"
+read-pkg-up@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-8.0.0.tgz#72f595b65e66110f43b052dd9af4de6b10534670"
+ integrity sha512-snVCqPczksT0HS2EC+SxUndvSzn6LRCwpfSvLrIfR5BKDQQZMaI6jPRC9dYvYFDRAuFEAnkwww8kBBNE/3VvzQ==
+ dependencies:
+ find-up "^5.0.0"
+ read-pkg "^6.0.0"
+ type-fest "^1.0.1"
+
read-pkg@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz"
@@ -7518,6 +7610,16 @@ read-pkg@^5.2.0:
parse-json "^5.0.0"
type-fest "^0.6.0"
+read-pkg@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-6.0.0.tgz#a67a7d6a1c2b0c3cd6aa2ea521f40c458a4a504c"
+ integrity sha512-X1Fu3dPuk/8ZLsMhEj5f4wFAF0DWoK7qhGJvgaijocXxBmSToKfbFtqbxMO7bVjNA1dmE5huAzjXj/ey86iw9Q==
+ dependencies:
+ "@types/normalize-package-data" "^2.4.0"
+ normalize-package-data "^3.0.2"
+ parse-json "^5.2.0"
+ type-fest "^1.0.1"
+
readable-stream@^3.1.1, readable-stream@^3.4.0:
version "3.6.2"
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz"
@@ -7542,6 +7644,14 @@ redent@^3.0.0:
indent-string "^4.0.0"
strip-indent "^3.0.0"
+redent@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/redent/-/redent-4.0.0.tgz#0c0ba7caabb24257ab3bb7a4fd95dd1d5c5681f9"
+ integrity sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==
+ dependencies:
+ indent-string "^5.0.0"
+ strip-indent "^4.0.0"
+
redis-errors@^1.0.0, redis-errors@^1.2.0:
version "1.2.0"
resolved "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz"
@@ -8004,11 +8114,16 @@ side-channel@^1.0.4:
get-intrinsic "^1.0.2"
object-inspect "^1.9.0"
-signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7:
+signal-exit@^3.0.2, signal-exit@^3.0.3:
version "3.0.7"
resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz"
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
+signal-exit@^4.0.1:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.0.2.tgz#ff55bb1d9ff2114c13b400688fa544ac63c36967"
+ integrity sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==
+
simple-concat@^1.0.0:
version "1.0.1"
resolved "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz"
@@ -8264,6 +8379,13 @@ strip-indent@^3.0.0:
dependencies:
min-indent "^1.0.0"
+strip-indent@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-4.0.0.tgz#b41379433dd06f5eae805e21d631e07ee670d853"
+ integrity sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==
+ dependencies:
+ min-indent "^1.0.1"
+
strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
version "3.1.1"
resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz"
@@ -8327,49 +8449,51 @@ stylelint-scss@^4.3.0:
postcss-selector-parser "^6.0.6"
postcss-value-parser "^4.1.0"
-stylelint@^14.16.1:
- version "14.16.1"
- resolved "https://registry.npmjs.org/stylelint/-/stylelint-14.16.1.tgz"
- integrity sha512-ErlzR/T3hhbV+a925/gbfc3f3Fep9/bnspMiJPorfGEmcBbXdS+oo6LrVtoUZ/w9fqD6o6k7PtUlCOsCRdjX/A==
+stylelint@^15.10.1:
+ version "15.10.1"
+ resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-15.10.1.tgz#93f189958687e330c106b010cbec0c41dcae506d"
+ integrity sha512-CYkzYrCFfA/gnOR+u9kJ1PpzwG10WLVnoxHDuBA/JiwGqdM9+yx9+ou6SE/y9YHtfv1mcLo06fdadHTOx4gBZQ==
dependencies:
- "@csstools/selector-specificity" "^2.0.2"
+ "@csstools/css-parser-algorithms" "^2.3.0"
+ "@csstools/css-tokenizer" "^2.1.1"
+ "@csstools/media-query-list-parser" "^2.1.2"
+ "@csstools/selector-specificity" "^3.0.0"
balanced-match "^2.0.0"
colord "^2.9.3"
- cosmiconfig "^7.1.0"
+ cosmiconfig "^8.2.0"
css-functions-list "^3.1.0"
+ css-tree "^2.3.1"
debug "^4.3.4"
- fast-glob "^3.2.12"
+ fast-glob "^3.3.0"
fastest-levenshtein "^1.0.16"
file-entry-cache "^6.0.1"
global-modules "^2.0.0"
globby "^11.1.0"
globjoin "^0.1.4"
- html-tags "^3.2.0"
- ignore "^5.2.1"
+ html-tags "^3.3.1"
+ ignore "^5.2.4"
import-lazy "^4.0.0"
imurmurhash "^0.1.4"
is-plain-object "^5.0.0"
- known-css-properties "^0.26.0"
+ known-css-properties "^0.27.0"
mathml-tag-names "^2.1.3"
- meow "^9.0.0"
+ meow "^10.1.5"
micromatch "^4.0.5"
normalize-path "^3.0.0"
picocolors "^1.0.0"
- postcss "^8.4.19"
- postcss-media-query-parser "^0.2.3"
+ postcss "^8.4.24"
postcss-resolve-nested-selector "^0.1.1"
postcss-safe-parser "^6.0.0"
- postcss-selector-parser "^6.0.11"
+ postcss-selector-parser "^6.0.13"
postcss-value-parser "^4.2.0"
resolve-from "^5.0.0"
string-width "^4.2.3"
strip-ansi "^6.0.1"
style-search "^0.1.0"
- supports-hyperlinks "^2.3.0"
+ supports-hyperlinks "^3.0.0"
svg-tags "^1.0.0"
table "^6.8.1"
- v8-compile-cache "^2.3.0"
- write-file-atomic "^4.0.2"
+ write-file-atomic "^5.0.1"
supports-color@8.1.1:
version "8.1.1"
@@ -8392,10 +8516,10 @@ supports-color@^7.0.0, supports-color@^7.1.0:
dependencies:
has-flag "^4.0.0"
-supports-hyperlinks@^2.3.0:
- version "2.3.0"
- resolved "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz"
- integrity sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==
+supports-hyperlinks@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-3.0.0.tgz#c711352a5c89070779b4dad54c05a2f14b15c94b"
+ integrity sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==
dependencies:
has-flag "^4.0.0"
supports-color "^7.0.0"
@@ -8582,6 +8706,11 @@ trim-newlines@^3.0.0:
resolved "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz"
integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==
+trim-newlines@^4.0.2:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-4.1.1.tgz#28c88deb50ed10c7ba6dc2474421904a00139125"
+ integrity sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ==
+
ts-node@^10.9.1:
version "10.9.1"
resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz"
@@ -8662,11 +8791,6 @@ type-fest@^0.13.1:
resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz"
integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==
-type-fest@^0.18.0:
- version "0.18.1"
- resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz"
- integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==
-
type-fest@^0.20.2:
version "0.20.2"
resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz"
@@ -8687,6 +8811,11 @@ type-fest@^0.8.1:
resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz"
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
+type-fest@^1.0.1, type-fest@^1.2.1, type-fest@^1.2.2:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1"
+ integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==
+
typed-array-length@^1.0.4:
version "1.0.4"
resolved "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz"
@@ -8858,11 +8987,6 @@ v8-compile-cache-lib@^3.0.1:
resolved "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz"
integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==
-v8-compile-cache@^2.3.0:
- version "2.3.0"
- resolved "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz"
- integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==
-
validate-npm-package-license@^3.0.1:
version "3.0.4"
resolved "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz"
@@ -8989,13 +9113,13 @@ write-file-atomic@^3.0.0:
signal-exit "^3.0.2"
typedarray-to-buffer "^3.1.5"
-write-file-atomic@^4.0.2:
- version "4.0.2"
- resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz"
- integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==
+write-file-atomic@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-5.0.1.tgz#68df4717c55c6fa4281a7860b4c2ba0a6d2b11e7"
+ integrity sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==
dependencies:
imurmurhash "^0.1.4"
- signal-exit "^3.0.7"
+ signal-exit "^4.0.1"
write-json-file@^4.3.0:
version "4.3.0"
@@ -9040,9 +9164,9 @@ yargs-parser@^18.1.3:
camelcase "^5.0.0"
decamelize "^1.2.0"
-yargs-parser@^20.2.3:
+yargs-parser@^20.2.9:
version "20.2.9"
- resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
yn@3.1.1: