diff --git a/.eslintrc.json b/.eslintrc.json index 25e83d5a..a77ed5bd 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -19,22 +19,21 @@ "plugin:@typescript-eslint/recommended", "next" ], - "plugins": ["@typescript-eslint", "prettier"], "settings": { "import/resolver": { "alias": { "map": [ - ["assets", "./assets"], - ["components", "./components"], + ["assets", "./src/assets"], + ["components", "./src/components"], ["db", "./db"], - ["hooks", "./hooks"], - ["lang", "./lang"], - ["lib", "./lib"], + ["hooks", "./src/components/hooks"], + ["lang", "./src/lang"], + ["lib", "./src/lib"], ["public", "./public"], - ["queries", "./queries"], - ["store", "./store"], - ["styles", "./styles"] + ["queries", "./src/queries"], + ["store", "./src/store"], + ["styles", "./src/styles"] ], "extensions": [".ts", ".tsx", ".js", ".jsx", ".json"] } @@ -51,7 +50,8 @@ "@typescript-eslint/no-empty-function": "off", "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-var-requires": "off", - "@typescript-eslint/no-empty-interface": "off" + "@typescript-eslint/no-empty-interface": "off", + "@typescript-eslint/no-unused-vars": ["error", { "ignoreRestSiblings": true }] }, "globals": { "React": "writable" diff --git a/components/input/WebsiteDateFilter.js b/components/input/WebsiteDateFilter.js deleted file mode 100644 index 47e6f016..00000000 --- a/components/input/WebsiteDateFilter.js +++ /dev/null @@ -1,23 +0,0 @@ -import useDateRange from 'hooks/useDateRange'; -import DateFilter from './DateFilter'; -import styles from './WebsiteDateFilter.module.css'; - -export default function WebsiteDateFilter({ websiteId }) { - const [dateRange, setDateRange] = useDateRange(websiteId); - const { value, startDate, endDate } = dateRange; - - const handleChange = async value => { - setDateRange(value); - }; - - return ( - - ); -} diff --git a/components/input/WebsiteDateFilter.module.css b/components/input/WebsiteDateFilter.module.css deleted file mode 100644 index 13234c55..00000000 --- a/components/input/WebsiteDateFilter.module.css +++ /dev/null @@ -1,3 +0,0 @@ -.dropdown { - min-width: 200px; -} diff --git a/components/metrics/CitiesTable.js b/components/metrics/CitiesTable.js deleted file mode 100644 index 2e74780d..00000000 --- a/components/metrics/CitiesTable.js +++ /dev/null @@ -1,32 +0,0 @@ -import MetricsTable from './MetricsTable'; -import { emptyFilter } from 'lib/filters'; -import FilterLink from 'components/common/FilterLink'; -import useLocale from 'hooks/useLocale'; -import useMessages from 'hooks/useMessages'; - -export function CitiesTable({ websiteId, ...props }) { - const { locale } = useLocale(); - const { formatMessage, labels } = useMessages(); - - function renderLink({ x }) { - return ( -
- -
- ); - } - - return ( - - ); -} - -export default CitiesTable; diff --git a/components/pages/settings/users/UserWebsites.js b/components/pages/settings/users/UserWebsites.js deleted file mode 100644 index 144fae44..00000000 --- a/components/pages/settings/users/UserWebsites.js +++ /dev/null @@ -1,26 +0,0 @@ -import { Loading } from 'react-basics'; -import useApi from 'hooks/useApi'; -import WebsitesTable from 'components/pages/settings/websites/WebsitesTable'; -import useMessages from 'hooks/useMessages'; - -export function UserWebsites({ userId }) { - const { formatMessage, messages } = useMessages(); - const { get, useQuery } = useApi(); - const { data, isLoading } = useQuery(['user:websites', userId], () => - get(`/users/${userId}/websites`), - ); - const hasData = data && data.length !== 0; - - if (isLoading) { - return ; - } - - return ( -
- {hasData && } - {!hasData && formatMessage(messages.noDataAvailable)} -
- ); -} - -export default UserWebsites; diff --git a/jsconfig.json b/jsconfig.json index b639b0f8..738e8a46 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -1,5 +1,5 @@ { "compilerOptions": { - "baseUrl": "." + "baseUrl": "./src" } -} \ No newline at end of file +} diff --git a/lang/ja-JP.json b/lang/ja-JP.json deleted file mode 100644 index 0f4d5450..00000000 --- a/lang/ja-JP.json +++ /dev/null @@ -1,211 +0,0 @@ -{ - "label.access-code": "Access code", - "label.actions": "アクション", - "label.activity-log": "Activity log", - "label.add": "Add", - "label.add-description": "Add description", - "label.add-website": "Webサイトの追加", - "label.admin": "管理者", - "label.after": "After", - "label.all": "すべて表示", - "label.all-time": "All time", - "label.analytics": "Analytics", - "label.average": "Average", - "label.average-visit-time": "平均滞在時間", - "label.back": "戻る", - "label.before": "Before", - "label.bounce-rate": "直帰率", - "label.breakdown": "Breakdown", - "label.browser": "Browser", - "label.browsers": "ブラウザ", - "label.cancel": "キャンセル", - "label.change-password": "パスワード変更", - "label.cities": "Cities", - "label.city": "City", - "label.clear-all": "Clear all", - "label.confirm": "Confirm", - "label.confirm-password": "パスワード(確認)", - "label.contains": "Contains", - "label.continue": "Continue", - "label.countries": "国", - "label.country": "Country", - "label.create-report": "Create report", - "label.create-team": "Create team", - "label.create-user": "Create user", - "label.created": "Created", - "label.current-password": "現在のパスワード", - "label.custom-range": "期間を指定する", - "label.dashboard": "ダッシュボード", - "label.data": "Data", - "label.date": "Date", - "label.date-range": "範囲指定", - "label.day": "Day", - "label.default-date-range": "最初に表示する期間", - "label.delete": "削除", - "label.delete-team": "Delete team", - "label.delete-user": "Delete user", - "label.delete-website": "Webサイトの削除", - "label.description": "Description", - "label.desktop": "デスクトップ", - "label.details": "Details", - "label.device": "Device", - "label.devices": "デバイス", - "label.dismiss": "無視する", - "label.does-not-contain": "Does not contain", - "label.domain": "ドメイン", - "label.dropoff": "Dropoff", - "label.edit": "編集", - "label.edit-dashboard": "Edit dashboard", - "label.enable-share-url": "共有リンクを有効にする", - "label.event": "Event", - "label.event-data": "Event data", - "label.events": "イベント", - "label.false": "False", - "label.field": "Field", - "label.fields": "Fields", - "label.filter-combined": "パスまで", - "label.filter-raw": "すべて表示", - "label.filters": "Filters", - "label.funnel": "Funnel", - "label.greater-than": "Greater than", - "label.greater-than-equals": "Greater than or equals", - "label.insights": "Insights", - "label.is": "Is", - "label.is-not": "Is not", - "label.is-not-set": "Is not set", - "label.is-set": "Is set", - "label.join": "Join", - "label.join-team": "Join team", - "label.language": "Language", - "label.languages": "Languages", - "label.laptop": "ノートPC", - "label.last-days": "過去{x}日間", - "label.last-hours": "過去{x}時間", - "label.leave": "Leave", - "label.leave-team": "Leave team", - "label.less-than": "Less than", - "label.less-than-equals": "Less than or equals", - "label.login": "ログイン", - "label.logout": "ログアウト", - "label.max": "Max", - "label.members": "Members", - "label.min": "Min", - "label.mobile": "携帯電話", - "label.more": "さらに表示", - "label.my-websites": "My websites", - "label.name": "名前", - "label.new-password": "新しいパスワード", - "label.none": "None", - "label.os": "OS", - "label.overview": "Overview", - "label.owner": "Owner", - "label.page-of": "Page {current} of {total}", - "label.page-views": "閲覧数", - "label.pageTitle": "Page title", - "label.pages": "ページ", - "label.password": "パスワード", - "label.powered-by": "このシステムは {name} で実行されています。", - "label.profile": "プロファイル", - "label.queries": "Queries", - "label.query": "Query", - "label.query-parameters": "Query parameters", - "label.realtime": "リアルタイム", - "label.referrer": "Referrer", - "label.referrers": "リファラー", - "label.refresh": "更新", - "label.regenerate": "Regenerate", - "label.region": "Region", - "label.regions": "Regions", - "label.remove": "Remove", - "label.reports": "Reports", - "label.required": "必須", - "label.reset": "リセット", - "label.reset-website": "Reset statistics", - "label.retention": "Retention", - "label.role": "Role", - "label.run-query": "Run query", - "label.save": "保存", - "label.screens": "Screens", - "label.select-date": "Select date", - "label.select-website": "Select website", - "label.sessions": "Sessions", - "label.settings": "設定", - "label.share-url": "共有リンク", - "label.single-day": "一日のみ", - "label.sum": "Sum", - "label.tablet": "タブレット", - "label.team": "Team", - "label.team-guest": "Team guest", - "label.team-id": "Team ID", - "label.team-member": "Team member", - "label.team-name": "Team name", - "label.team-owner": "Team owner", - "label.team-websites": "Team websites", - "label.teams": "Teams", - "label.theme": "Theme", - "label.this-month": "今月", - "label.this-week": "今週", - "label.this-year": "今年", - "label.timezone": "タイムゾーン", - "label.title": "Title", - "label.today": "今日", - "label.toggle-charts": "Toggle charts", - "label.total": "Total", - "label.total-records": "Total records", - "label.tracking-code": "トラッキングコード", - "label.true": "True", - "label.type": "Type", - "label.unique": "Unique", - "label.unique-visitors": "ユニーク訪問者数", - "label.unknown": "不明", - "label.untitled": "Untitled", - "label.url": "URL", - "label.urls": "URLs", - "label.user": "User", - "label.username": "ユーザー名", - "label.users": "Users", - "label.value": "Value", - "label.view": "View", - "label.view-details": "詳細を見る", - "label.view-only": "View only", - "label.views": "閲覧数", - "label.visitors": "訪問者数", - "label.website": "Website", - "label.website-id": "Website ID", - "label.websites": "Webサイト", - "label.window": "Window", - "label.yesterday": "Yesterday", - "message.active-users": "{x}人が閲覧中です。", - "message.confirm-delete": "{target}を削除してもよろしいですか?", - "message.confirm-leave": "Are you sure you want to leave {target}?", - "message.confirm-reset": "Are your sure you want to reset {target}'s statistics?", - "message.delete-account": "To delete this account, type {confirmation} in the box below to confirm.", - "message.delete-website": "To delete this website, type {confirmation} in the box below to confirm.", - "message.delete-website-warning": "関連するすべてのデータも削除されます。", - "message.error": "問題が発生しました。", - "message.event-log": "{event} on {url}", - "message.go-to-settings": "設定する", - "message.incorrect-username-password": "ユーザー名/パスワードが正しくありません。", - "message.invalid-domain": "無効なドメイン", - "message.min-password-length": "Minimum length of {n} characters", - "message.new-version-available": "A new version of Umami {version} is available!", - "message.no-data-available": "データがありません。", - "message.no-event-data": "No event data is available.", - "message.no-match-password": "パスワードが一致しません", - "message.no-results-found": "No results were found.", - "message.no-team-websites": "This team does not have any websites.", - "message.no-teams": "You have not created any teams.", - "message.no-users": "There are no users.", - "message.no-websites-configured": "Webサイトが設定されていません。", - "message.page-not-found": "ページが見つかりません。", - "message.reset-website": "To reset this website, type {confirmation} in the box below to confirm.", - "message.reset-website-warning": "All statistics for this website will be deleted, but your tracking code will remain intact.", - "message.saved": "正常に保存されました。", - "message.share-url": "これは{target}の共有リンクです。", - "message.team-already-member": "You are already a member of the team.", - "message.team-not-found": "Team not found.", - "message.team-websites-info": "Websites can be viewed by anyone on the team.", - "message.tracking-code": "トラッキングコード", - "message.user-deleted": "User deleted.", - "message.visitor-log": "{os}({device})で{browser}を使用している{country}からの訪問者" -} diff --git a/next.config.js b/next.config.js index 2165a6e0..cc3cde7c 100644 --- a/next.config.js +++ b/next.config.js @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-var-requires */ require('dotenv').config(); +const path = require('path'); const pkg = require('./package.json'); const contentSecurityPolicy = ` @@ -58,7 +59,9 @@ if (process.env.TRACKER_SCRIPT_NAME) { const redirects = [ { source: '/settings', - destination: process.env.CLOUD_MODE ? '/settings/profile' : '/settings/websites', + destination: process.env.CLOUD_MODE + ? `${process.env.CLOUD_URL}/settings/websites` + : '/settings/websites', permanent: true, }, ]; @@ -73,6 +76,9 @@ if (process.env.CLOUD_MODE && process.env.CLOUD_URL && process.env.DISABLE_LOGIN const config = { env: { + cloudMode: process.env.CLOUD_MODE, + cloudUrl: process.env.CLOUD_URL, + configUrl: '/config', currentVersion: pkg.version, defaultLocale: process.env.DEFAULT_LOCALE, isProduction: process.env.NODE_ENV === 'production', @@ -92,6 +98,8 @@ const config = { use: ['@svgr/webpack'], }); + config.resolve.alias['public'] = path.resolve('./public'); + return config; }, async headers() { diff --git a/package.components.json b/package.components.json new file mode 100644 index 00000000..feb3fc2e --- /dev/null +++ b/package.components.json @@ -0,0 +1,23 @@ +{ + "name": "@umami/components", + "version": "0.11.0", + "description": "Umami React components.", + "author": "Mike Cao ", + "license": "MIT", + "type": "module", + "main": "./index.js", + "types": "./index.d.ts", + "peerDependencies": { + "@tanstack/react-query": "^4.33.0", + "classnames": "^2.3.1", + "colord": "^2.9.2", + "immer": "^9.0.12", + "moment-timezone": "^0.5.35", + "next": "^13.4.0", + "next-basics": "^0.36.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-intl": "^5.24.7", + "zustand": "^4.3.8" + } +} diff --git a/package.json b/package.json index e1361d20..4fda0a22 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "umami", - "version": "2.5.0", + "version": "2.6.0", "description": "A simple, fast, privacy-focused alternative to Google Analytics.", "author": "Mike Cao ", "license": "MIT", @@ -18,9 +18,10 @@ "start-env": "node scripts/start-env.js", "start-server": "node server.js", "build-app": "next build", - "build-tracker": "rollup -c rollup.tracker.config.js", + "build-components": "rollup -c rollup.components.config.mjs", + "build-tracker": "rollup -c rollup.tracker.config.mjs", "build-db": "npm-run-all copy-db-files build-db-client", - "build-lang": "npm-run-all format-lang compile-lang download-country-names download-language-names", + "build-lang": "npm-run-all format-lang compile-lang clean-lang download-country-names download-language-names", "build-geo": "node scripts/build-geo.js", "build-db-schema": "prisma db pull", "build-db-client": "prisma generate", @@ -34,6 +35,7 @@ "generate-lang": "npm-run-all extract-messages merge-messages", "format-lang": "node scripts/format-lang.js", "compile-lang": "formatjs compile-folder --ast build/messages public/intl/messages", + "clean-lang": "prettier --write ./public/intl/messages/*.json", "check-lang": "node scripts/check-lang.js", "download-country-names": "node scripts/download-country-names.js", "download-language-names": "node scripts/download-language-names.js", @@ -60,8 +62,8 @@ ], "dependencies": { "@fontsource/inter": "^4.5.15", - "@prisma/client": "5.0.0", - "@tanstack/react-query": "^4.16.1", + "@prisma/client": "5.2.0", + "@tanstack/react-query": "^4.33.0", "@umami/prisma-client": "^0.2.0", "@umami/redis-client": "^0.5.0", "chalk": "^4.1.1", @@ -89,7 +91,7 @@ "kafkajs": "^2.1.0", "maxmind": "^4.3.6", "moment-timezone": "^0.5.35", - "next": "13.3.1", + "next": "13.4.19", "next-basics": "^0.36.0", "node-fetch": "^3.2.8", "npm-run-all": "^4.1.5", @@ -115,13 +117,16 @@ "@formatjs/cli": "^4.2.29", "@netlify/plugin-nextjs": "^4.27.3", "@rollup/plugin-alias": "^5.0.0", - "@rollup/plugin-buble": "^0.21.3", - "@rollup/plugin-commonjs": "^24.1.0", + "@rollup/plugin-buble": "^1.0.2", + "@rollup/plugin-commonjs": "^25.0.4", "@rollup/plugin-json": "^6.0.0", - "@rollup/plugin-node-resolve": "^15.0.2", - "@rollup/plugin-replace": "^4.0.0", - "@svgr/rollup": "^7.0.0", + "@rollup/plugin-node-resolve": "^15.2.0", + "@rollup/plugin-replace": "^5.0.2", + "@svgr/rollup": "^8.1.0", "@svgr/webpack": "^6.2.1", + "@types/node": "^18.11.9", + "@types/react": "^18.0.25", + "@types/react-dom": "^18.0.8", "@typescript-eslint/eslint-plugin": "^5.50.0", "@typescript-eslint/parser": "^5.50.0", "cross-env": "^7.0.3", @@ -141,13 +146,14 @@ "postcss-preset-env": "7.8.3", "postcss-rtlcss": "^4.0.1", "prettier": "^2.6.2", - "prisma": "5.0.0", + "prisma": "5.2.0", "prompts": "2.4.2", - "rollup": "^2.70.1", + "rollup": "^3.28.0", + "rollup-plugin-copy": "^3.4.0", "rollup-plugin-delete": "^2.0.0", - "rollup-plugin-dts": "^5.3.0", + "rollup-plugin-dts": "^5.3.1", "rollup-plugin-esbuild": "^5.0.0", - "rollup-plugin-node-externals": "^5.1.2", + "rollup-plugin-node-externals": "^6.1.1", "rollup-plugin-postcss": "^4.0.2", "rollup-plugin-terser": "^7.0.2", "stylelint": "^15.10.1", @@ -156,6 +162,6 @@ "stylelint-config-recommended": "^9.0.0", "tar": "^6.1.2", "ts-node": "^10.9.1", - "typescript": "^4.9.5" + "typescript": "^5.1.6" } } diff --git a/pages/api/reports/insights.ts b/pages/api/reports/insights.ts deleted file mode 100644 index 09a07d2f..00000000 --- a/pages/api/reports/insights.ts +++ /dev/null @@ -1,56 +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 { getInsights } from 'queries'; - -export interface InsightsRequestBody { - websiteId: string; - dateRange: { - startDate: string; - endDate: string; - }; - fields: { name: string; type: string; value: string }[]; - filters: string[]; - groups: { name: string; type: string }[]; -} - -function convertFilters(filters) { - return filters.reduce((obj, { name, ...value }) => { - obj[name] = value; - - return obj; - }, {}); -} - -export default async ( - req: NextApiRequestQueryBody, - res: NextApiResponse, -) => { - await useCors(req, res); - await useAuth(req, res); - - if (req.method === 'POST') { - const { - websiteId, - dateRange: { startDate, endDate }, - fields, - filters, - } = req.body; - - if (!(await canViewWebsite(req.auth, websiteId))) { - return unauthorized(res); - } - - const data = await getInsights(websiteId, fields, { - ...convertFilters(filters), - startDate: new Date(startDate), - endDate: new Date(endDate), - }); - - return ok(res, data); - } - - return methodNotAllowed(res); -}; diff --git a/public/intl/messages/ja-JP.json b/public/intl/messages/ja-JP.json index bde2f3a9..63b49aa5 100644 --- a/public/intl/messages/ja-JP.json +++ b/public/intl/messages/ja-JP.json @@ -2,7 +2,7 @@ "label.access-code": [ { "type": 0, - "value": "Access code" + "value": "アクセスコード" } ], "label.actions": [ @@ -14,19 +14,19 @@ "label.activity-log": [ { "type": 0, - "value": "Activity log" + "value": "アクティビティログ" } ], "label.add": [ { "type": 0, - "value": "Add" + "value": "追加" } ], "label.add-description": [ { "type": 0, - "value": "Add description" + "value": "説明を追加" } ], "label.add-website": [ @@ -44,31 +44,31 @@ "label.after": [ { "type": 0, - "value": "After" + "value": "直後" } ], "label.all": [ { "type": 0, - "value": "すべて表示" + "value": "すべて" } ], "label.all-time": [ { "type": 0, - "value": "All time" + "value": "すべての時間帯" } ], "label.analytics": [ { "type": 0, - "value": "Analytics" + "value": "アナリティクス" } ], "label.average": [ { "type": 0, - "value": "Average" + "value": "平均" } ], "label.average-visit-time": [ @@ -86,7 +86,7 @@ "label.before": [ { "type": 0, - "value": "Before" + "value": "直前" } ], "label.bounce-rate": [ @@ -98,13 +98,13 @@ "label.breakdown": [ { "type": 0, - "value": "Breakdown" + "value": "故障" } ], "label.browser": [ { "type": 0, - "value": "Browser" + "value": "ブラウザ" } ], "label.browsers": [ @@ -122,85 +122,85 @@ "label.change-password": [ { "type": 0, - "value": "パスワード変更" + "value": "パスワードの変更" } ], "label.cities": [ { "type": 0, - "value": "Cities" + "value": "都市" } ], "label.city": [ { "type": 0, - "value": "City" + "value": "都市" } ], "label.clear-all": [ { "type": 0, - "value": "Clear all" + "value": "すべてクリア" } ], "label.confirm": [ { "type": 0, - "value": "Confirm" + "value": "確認" } ], "label.confirm-password": [ { "type": 0, - "value": "パスワード(確認)" + "value": "パスワード(確認)" } ], "label.contains": [ { "type": 0, - "value": "Contains" + "value": "コンテンツ" } ], "label.continue": [ { "type": 0, - "value": "Continue" + "value": "続ける" } ], "label.countries": [ { "type": 0, - "value": "国" + "value": "国名" } ], "label.country": [ { "type": 0, - "value": "Country" + "value": "国" } ], "label.create-report": [ { "type": 0, - "value": "Create report" + "value": "レポートの作成" } ], "label.create-team": [ { "type": 0, - "value": "Create team" + "value": "チームの作成" } ], "label.create-user": [ { "type": 0, - "value": "Create user" + "value": "ユーザーの作成" } ], "label.created": [ { "type": 0, - "value": "Created" + "value": "作成されました" } ], "label.current-password": [ @@ -212,7 +212,7 @@ "label.custom-range": [ { "type": 0, - "value": "期間を指定する" + "value": "範囲指定" } ], "label.dashboard": [ @@ -224,31 +224,31 @@ "label.data": [ { "type": 0, - "value": "Data" + "value": "データ" } ], "label.date": [ { "type": 0, - "value": "Date" + "value": "日付" } ], "label.date-range": [ { "type": 0, - "value": "範囲指定" + "value": "期間" } ], "label.day": [ { "type": 0, - "value": "Day" + "value": "日" } ], "label.default-date-range": [ { "type": 0, - "value": "最初に表示する期間" + "value": "デフォルトの期間" } ], "label.delete": [ @@ -260,13 +260,13 @@ "label.delete-team": [ { "type": 0, - "value": "Delete team" + "value": "チームの削除" } ], "label.delete-user": [ { "type": 0, - "value": "Delete user" + "value": "ユーザーの削除" } ], "label.delete-website": [ @@ -278,7 +278,7 @@ "label.description": [ { "type": 0, - "value": "Description" + "value": "説明" } ], "label.desktop": [ @@ -290,13 +290,13 @@ "label.details": [ { "type": 0, - "value": "Details" + "value": "詳細情報" } ], "label.device": [ { "type": 0, - "value": "Device" + "value": "デバイス" } ], "label.devices": [ @@ -308,13 +308,13 @@ "label.dismiss": [ { "type": 0, - "value": "無視する" + "value": "却下" } ], "label.does-not-contain": [ { "type": 0, - "value": "Does not contain" + "value": "を含まない" } ], "label.domain": [ @@ -326,7 +326,7 @@ "label.dropoff": [ { "type": 0, - "value": "Dropoff" + "value": "切り捨て" } ], "label.edit": [ @@ -338,25 +338,25 @@ "label.edit-dashboard": [ { "type": 0, - "value": "Edit dashboard" + "value": "ダッシュボードの編集" } ], "label.enable-share-url": [ { "type": 0, - "value": "共有リンクを有効にする" + "value": "共有URLを有効にする" } ], "label.event": [ { "type": 0, - "value": "Event" + "value": "イベント" } ], "label.event-data": [ { "type": 0, - "value": "Event data" + "value": "イベントデータ" } ], "label.events": [ @@ -368,109 +368,109 @@ "label.false": [ { "type": 0, - "value": "False" + "value": "偽" } ], "label.field": [ { "type": 0, - "value": "Field" + "value": "フィールド" } ], "label.fields": [ { "type": 0, - "value": "Fields" + "value": "フィールド" } ], "label.filter-combined": [ { "type": 0, - "value": "パスまで" + "value": "統合" } ], "label.filter-raw": [ { "type": 0, - "value": "すべて表示" + "value": "RAW" } ], "label.filters": [ { "type": 0, - "value": "Filters" + "value": "フィルター" } ], "label.funnel": [ { "type": 0, - "value": "Funnel" + "value": "分析" } ], "label.greater-than": [ { "type": 0, - "value": "Greater than" + "value": "超過" } ], "label.greater-than-equals": [ { "type": 0, - "value": "Greater than or equals" + "value": "以上" } ], "label.insights": [ { "type": 0, - "value": "Insights" + "value": "見通し" } ], "label.is": [ { "type": 0, - "value": "Is" + "value": "に等しい" } ], "label.is-not": [ { "type": 0, - "value": "Is not" + "value": "に等しくない" } ], "label.is-not-set": [ { "type": 0, - "value": "Is not set" + "value": "未設定" } ], "label.is-set": [ { "type": 0, - "value": "Is set" + "value": "設定済み" } ], "label.join": [ { "type": 0, - "value": "Join" + "value": "参加" } ], "label.join-team": [ { "type": 0, - "value": "Join team" + "value": "チームに参加" } ], "label.language": [ { "type": 0, - "value": "Language" + "value": "言語" } ], "label.languages": [ { "type": 0, - "value": "Languages" + "value": "言語" } ], "label.laptop": [ @@ -510,25 +510,25 @@ "label.leave": [ { "type": 0, - "value": "Leave" + "value": "離脱" } ], "label.leave-team": [ { "type": 0, - "value": "Leave team" + "value": "チームを離脱" } ], "label.less-than": [ { "type": 0, - "value": "Less than" + "value": "未満" } ], "label.less-than-equals": [ { "type": 0, - "value": "Less than or equals" + "value": "以下" } ], "label.login": [ @@ -546,19 +546,19 @@ "label.max": [ { "type": 0, - "value": "Max" + "value": "最大" } ], "label.members": [ { "type": 0, - "value": "Members" + "value": "メンバー" } ], "label.min": [ { "type": 0, - "value": "Min" + "value": "最小" } ], "label.mobile": [ @@ -570,13 +570,13 @@ "label.more": [ { "type": 0, - "value": "さらに表示" + "value": "もっと見る" } ], "label.my-websites": [ { "type": 0, - "value": "My websites" + "value": "マイWebサイト" } ], "label.name": [ @@ -594,7 +594,7 @@ "label.none": [ { "type": 0, - "value": "None" + "value": "なし" } ], "label.os": [ @@ -606,19 +606,19 @@ "label.overview": [ { "type": 0, - "value": "Overview" + "value": "概要" } ], "label.owner": [ { "type": 0, - "value": "Owner" + "value": "所有者" } ], "label.page-of": [ { "type": 0, - "value": "Page " + "value": "ページ " }, { "type": 1, @@ -626,7 +626,7 @@ }, { "type": 0, - "value": " of " + "value": "/" }, { "type": 1, @@ -642,7 +642,7 @@ "label.pageTitle": [ { "type": 0, - "value": "Page title" + "value": "ページタイトル" } ], "label.pages": [ @@ -660,39 +660,35 @@ "label.powered-by": [ { "type": 0, - "value": "このシステムは " + "value": "Powered by " }, { "type": 1, "value": "name" - }, - { - "type": 0, - "value": " で実行されています。" } ], "label.profile": [ { "type": 0, - "value": "プロファイル" + "value": "プロフィール" } ], "label.queries": [ { "type": 0, - "value": "Queries" + "value": "クエリ" } ], "label.query": [ { "type": 0, - "value": "Query" + "value": "クエリ" } ], "label.query-parameters": [ { "type": 0, - "value": "Query parameters" + "value": "クエリパラメーター" } ], "label.realtime": [ @@ -704,7 +700,7 @@ "label.referrer": [ { "type": 0, - "value": "Referrer" + "value": "リファラー" } ], "label.referrers": [ @@ -722,31 +718,31 @@ "label.regenerate": [ { "type": 0, - "value": "Regenerate" + "value": "再生成" } ], "label.region": [ { "type": 0, - "value": "Region" + "value": "地域" } ], "label.regions": [ { "type": 0, - "value": "Regions" + "value": "地域" } ], "label.remove": [ { "type": 0, - "value": "Remove" + "value": "削除" } ], "label.reports": [ { "type": 0, - "value": "Reports" + "value": "レポート" } ], "label.required": [ @@ -764,25 +760,25 @@ "label.reset-website": [ { "type": 0, - "value": "Reset statistics" + "value": "Webサイトをリセットする" } ], "label.retention": [ { "type": 0, - "value": "Retention" + "value": "保持" } ], "label.role": [ { "type": 0, - "value": "Role" + "value": "ロール" } ], "label.run-query": [ { "type": 0, - "value": "Run query" + "value": "クエリ実行" } ], "label.save": [ @@ -794,25 +790,25 @@ "label.screens": [ { "type": 0, - "value": "Screens" + "value": "画面サイズ" } ], "label.select-date": [ { "type": 0, - "value": "Select date" + "value": "日付を選択" } ], "label.select-website": [ { "type": 0, - "value": "Select website" + "value": "Webサイトを選択" } ], "label.sessions": [ { "type": 0, - "value": "Sessions" + "value": "セッション" } ], "label.settings": [ @@ -824,19 +820,19 @@ "label.share-url": [ { "type": 0, - "value": "共有リンク" + "value": "共有URL" } ], "label.single-day": [ { "type": 0, - "value": "一日のみ" + "value": "一日" } ], "label.sum": [ { "type": 0, - "value": "Sum" + "value": "合計" } ], "label.tablet": [ @@ -848,55 +844,55 @@ "label.team": [ { "type": 0, - "value": "Team" + "value": "チーム" } ], "label.team-guest": [ { "type": 0, - "value": "Team guest" + "value": "チームゲスト" } ], "label.team-id": [ { "type": 0, - "value": "Team ID" + "value": "チームID" } ], "label.team-member": [ { "type": 0, - "value": "Team member" + "value": "チームメンバー" } ], "label.team-name": [ { "type": 0, - "value": "Team name" + "value": "チーム名" } ], "label.team-owner": [ { "type": 0, - "value": "Team owner" + "value": "チーム所有者" } ], "label.team-websites": [ { "type": 0, - "value": "Team websites" + "value": "チームのWebサイト" } ], "label.teams": [ { "type": 0, - "value": "Teams" + "value": "チーム" } ], "label.theme": [ { "type": 0, - "value": "Theme" + "value": "テーマ" } ], "label.this-month": [ @@ -926,7 +922,7 @@ "label.title": [ { "type": 0, - "value": "Title" + "value": "タイトル" } ], "label.today": [ @@ -938,19 +934,19 @@ "label.toggle-charts": [ { "type": 0, - "value": "Toggle charts" + "value": "グラフを切り替える" } ], "label.total": [ { "type": 0, - "value": "Total" + "value": "累計" } ], "label.total-records": [ { "type": 0, - "value": "Total records" + "value": "総記録数" } ], "label.tracking-code": [ @@ -962,19 +958,19 @@ "label.true": [ { "type": 0, - "value": "True" + "value": "真" } ], "label.type": [ { "type": 0, - "value": "Type" + "value": "種別" } ], "label.unique": [ { "type": 0, - "value": "Unique" + "value": "ユニーク" } ], "label.unique-visitors": [ @@ -992,7 +988,7 @@ "label.untitled": [ { "type": 0, - "value": "Untitled" + "value": "無題" } ], "label.url": [ @@ -1004,13 +1000,13 @@ "label.urls": [ { "type": 0, - "value": "URLs" + "value": "URL" } ], "label.user": [ { "type": 0, - "value": "User" + "value": "ユーザー" } ], "label.username": [ @@ -1022,55 +1018,55 @@ "label.users": [ { "type": 0, - "value": "Users" + "value": "ユーザー" } ], "label.value": [ { "type": 0, - "value": "Value" + "value": "値" } ], "label.view": [ { "type": 0, - "value": "View" + "value": "表示" } ], "label.view-details": [ { "type": 0, - "value": "詳細を見る" + "value": "詳細を表示" } ], "label.view-only": [ { "type": 0, - "value": "View only" + "value": "表示のみ" } ], "label.views": [ { "type": 0, - "value": "閲覧数" + "value": "表示" } ], "label.visitors": [ { "type": 0, - "value": "訪問者数" + "value": "訪問者" } ], "label.website": [ { "type": 0, - "value": "Website" + "value": "Webサイト" } ], "label.website-id": [ { "type": 0, - "value": "Website ID" + "value": "WebサイトID" } ], "label.websites": [ @@ -1082,13 +1078,13 @@ "label.window": [ { "type": 0, - "value": "Window" + "value": "ウィンドウ" } ], "label.yesterday": [ { "type": 0, - "value": "Yesterday" + "value": "昨日" } ], "message.active-users": [ @@ -1098,7 +1094,31 @@ }, { "type": 0, - "value": "人が閲覧中です。" + "value": " " + }, + { + "offset": 0, + "options": { + "one": { + "value": [ + { + "type": 0, + "value": "アクティブな訪問者" + } + ] + }, + "other": { + "value": [ + { + "type": 0, + "value": "アクティブな訪問者" + } + ] + } + }, + "pluralType": "cardinal", + "type": 6, + "value": "x" } ], "message.confirm-delete": [ @@ -1112,37 +1132,29 @@ } ], "message.confirm-leave": [ - { - "type": 0, - "value": "Are you sure you want to leave " - }, { "type": 1, "value": "target" }, { "type": 0, - "value": "?" + "value": "から離脱してもよろしいですか?" } ], "message.confirm-reset": [ - { - "type": 0, - "value": "Are your sure you want to reset " - }, { "type": 1, "value": "target" }, { "type": 0, - "value": "'s statistics?" + "value": "をリセットしてもよろしいですか?" } ], "message.delete-account": [ { "type": 0, - "value": "To delete this account, type " + "value": "このアカウントを削除するには、下のフォームに「" }, { "type": 1, @@ -1150,13 +1162,13 @@ }, { "type": 0, - "value": " in the box below to confirm." + "value": "」と入力してください。" } ], "message.delete-website": [ { "type": 0, - "value": "To delete this website, type " + "value": "このWebサイトを削除するには、下のフォームに「" }, { "type": 1, @@ -1164,57 +1176,57 @@ }, { "type": 0, - "value": " in the box below to confirm." + "value": "」と入力してください。" } ], "message.delete-website-warning": [ { "type": 0, - "value": "関連するすべてのデータも削除されます。" + "value": "Webサイトのデータがすべて削除されます。" } ], "message.error": [ { "type": 0, - "value": "問題が発生しました。" + "value": "未知のエラーが発生しました。" } ], "message.event-log": [ { "type": 1, - "value": "event" + "value": "url" }, { "type": 0, - "value": " on " + "value": "の" }, { "type": 1, - "value": "url" + "value": "event" } ], "message.go-to-settings": [ { "type": 0, - "value": "設定する" + "value": "設定に移動する" } ], "message.incorrect-username-password": [ { "type": 0, - "value": "ユーザー名/パスワードが正しくありません。" + "value": "ユーザー名またはパスワードが間違っています。" } ], "message.invalid-domain": [ { "type": 0, - "value": "無効なドメイン" + "value": "無効なドメインです。http/httpsを含めないでください。" } ], "message.min-password-length": [ { "type": 0, - "value": "Minimum length of " + "value": "最小文字数は" }, { "type": 1, @@ -1222,13 +1234,13 @@ }, { "type": 0, - "value": " characters" + "value": "文字です" } ], "message.new-version-available": [ { "type": 0, - "value": "A new version of Umami " + "value": "Umamiの新しいバージョン" }, { "type": 1, @@ -1236,7 +1248,7 @@ }, { "type": 0, - "value": " is available!" + "value": "が利用可能です!" } ], "message.no-data-available": [ @@ -1248,37 +1260,37 @@ "message.no-event-data": [ { "type": 0, - "value": "No event data is available." + "value": "イベントデータがありません。" } ], "message.no-match-password": [ { "type": 0, - "value": "パスワードが一致しません" + "value": "パスワードが一致しません。" } ], "message.no-results-found": [ { "type": 0, - "value": "No results were found." + "value": "結果が見つかりません。" } ], "message.no-team-websites": [ { "type": 0, - "value": "This team does not have any websites." + "value": "このチームにはWebサイトがありません。" } ], "message.no-teams": [ { "type": 0, - "value": "You have not created any teams." + "value": "チームを作成していません。" } ], "message.no-users": [ { "type": 0, - "value": "There are no users." + "value": "ユーザーが存在しません。" } ], "message.no-websites-configured": [ @@ -1290,13 +1302,13 @@ "message.page-not-found": [ { "type": 0, - "value": "ページが見つかりません。" + "value": "ページが見つかりません" } ], "message.reset-website": [ { "type": 0, - "value": "To reset this website, type " + "value": "このWebサイトをリセットするには、下のフォームに「" }, { "type": 1, @@ -1304,63 +1316,69 @@ }, { "type": 0, - "value": " in the box below to confirm." + "value": "」と入力してください。" } ], "message.reset-website-warning": [ { "type": 0, - "value": "All statistics for this website will be deleted, but your tracking code will remain intact." + "value": "このWebサイトの統計情報はすべて削除されますが、設定はそのまま残ります。" } ], "message.saved": [ { "type": 0, - "value": "正常に保存されました。" + "value": "保存されました。" } ], "message.share-url": [ { "type": 0, - "value": "これは" - }, - { - "type": 1, - "value": "target" - }, - { - "type": 0, - "value": "の共有リンクです。" + "value": "あなたのWebサイトの統計情報は次のURLで公開されています:" } ], "message.team-already-member": [ { "type": 0, - "value": "You are already a member of the team." + "value": "あなたはすでにチームのメンバーです。" } ], "message.team-not-found": [ { "type": 0, - "value": "Team not found." + "value": "チームが見つかりません。" } ], "message.team-websites-info": [ { "type": 0, - "value": "Websites can be viewed by anyone on the team." + "value": "Webサイトはチーム内の誰でも見ることができます。" } ], "message.tracking-code": [ { "type": 0, - "value": "トラッキングコード" + "value": "このWebサイトの統計情報を追跡するには、HTMLの" + }, + { + "children": [ + { + "type": 0, + "value": "..." + } + ], + "type": 8, + "value": "head" + }, + { + "type": 0, + "value": "セクションに以下のコードを記述します。" } ], "message.user-deleted": [ { "type": 0, - "value": "User deleted." + "value": "ユーザーが削除されました。" } ], "message.visitor-log": [ @@ -1370,7 +1388,7 @@ }, { "type": 0, - "value": "(" + "value": "(" }, { "type": 1, @@ -1378,7 +1396,7 @@ }, { "type": 0, - "value": ")で" + "value": ")で" }, { "type": 1, diff --git a/public/intl/messages/zh-TW.json b/public/intl/messages/zh-TW.json index 43a1996d..c980c4bb 100644 --- a/public/intl/messages/zh-TW.json +++ b/public/intl/messages/zh-TW.json @@ -2,37 +2,37 @@ "label.access-code": [ { "type": 0, - "value": "Access code" + "value": "存取碼" } ], "label.actions": [ { "type": 0, - "value": "用戶行為" + "value": "行動" } ], "label.activity-log": [ { "type": 0, - "value": "Activity log" + "value": "活動日誌" } ], "label.add": [ { "type": 0, - "value": "Add" + "value": "新增" } ], "label.add-description": [ { "type": 0, - "value": "Add description" + "value": "新增描述" } ], "label.add-website": [ { "type": 0, - "value": "增加網站" + "value": "新增網站" } ], "label.admin": [ @@ -44,37 +44,37 @@ "label.after": [ { "type": 0, - "value": "After" + "value": "之後" } ], "label.all": [ { "type": 0, - "value": "所有" + "value": "全部" } ], "label.all-time": [ { "type": 0, - "value": "所有時間段" + "value": "所有時間" } ], "label.analytics": [ { "type": 0, - "value": "Analytics" + "value": "分析" } ], "label.average": [ { "type": 0, - "value": "Average" + "value": "平均" } ], "label.average-visit-time": [ { "type": 0, - "value": "平均訪問時間" + "value": "平均造訪時間" } ], "label.back": [ @@ -86,7 +86,7 @@ "label.before": [ { "type": 0, - "value": "Before" + "value": "之前" } ], "label.bounce-rate": [ @@ -98,13 +98,13 @@ "label.breakdown": [ { "type": 0, - "value": "Breakdown" + "value": "分解" } ], "label.browser": [ { "type": 0, - "value": "Browser" + "value": "瀏覽器" } ], "label.browsers": [ @@ -122,31 +122,31 @@ "label.change-password": [ { "type": 0, - "value": "更新密碼" + "value": "更改密碼" } ], "label.cities": [ { "type": 0, - "value": "Cities" + "value": "城市" } ], "label.city": [ { "type": 0, - "value": "City" + "value": "城市" } ], "label.clear-all": [ { "type": 0, - "value": "Clear all" + "value": "全部清除" } ], "label.confirm": [ { "type": 0, - "value": "Confirm" + "value": "確認" } ], "label.confirm-password": [ @@ -158,49 +158,49 @@ "label.contains": [ { "type": 0, - "value": "Contains" + "value": "包含" } ], "label.continue": [ { "type": 0, - "value": "Continue" + "value": "繼續" } ], "label.countries": [ { "type": 0, - "value": "國家/地區" + "value": "國家" } ], "label.country": [ { "type": 0, - "value": "Country" + "value": "國家" } ], "label.create-report": [ { "type": 0, - "value": "Create report" + "value": "建立報告" } ], "label.create-team": [ { "type": 0, - "value": "Create team" + "value": "建立團隊" } ], "label.create-user": [ { "type": 0, - "value": "Create user" + "value": "建立使用者" } ], "label.created": [ { "type": 0, - "value": "Created" + "value": "已建立" } ], "label.current-password": [ @@ -212,43 +212,43 @@ "label.custom-range": [ { "type": 0, - "value": "自定義時段" + "value": "自訂範圍" } ], "label.dashboard": [ { "type": 0, - "value": "管理面板" + "value": "儀表板" } ], "label.data": [ { "type": 0, - "value": "Data" + "value": "資料" } ], "label.date": [ { "type": 0, - "value": "Date" + "value": "日期" } ], "label.date-range": [ { "type": 0, - "value": "多日" + "value": "日期範圍" } ], "label.day": [ { "type": 0, - "value": "Day" + "value": "日" } ], "label.default-date-range": [ { "type": 0, - "value": "默認日期範圍" + "value": "預設日期範圍" } ], "label.delete": [ @@ -260,13 +260,13 @@ "label.delete-team": [ { "type": 0, - "value": "Delete team" + "value": "刪除團隊" } ], "label.delete-user": [ { "type": 0, - "value": "Delete user" + "value": "刪除使用者" } ], "label.delete-website": [ @@ -278,25 +278,25 @@ "label.description": [ { "type": 0, - "value": "Description" + "value": "描述" } ], "label.desktop": [ { "type": 0, - "value": "桌機" + "value": "桌上型電腦" } ], "label.details": [ { "type": 0, - "value": "Details" + "value": "詳細資訊" } ], "label.device": [ { "type": 0, - "value": "Device" + "value": "裝置" } ], "label.devices": [ @@ -314,19 +314,19 @@ "label.does-not-contain": [ { "type": 0, - "value": "Does not contain" + "value": "不包含" } ], "label.domain": [ { "type": 0, - "value": "域名" + "value": "網域" } ], "label.dropoff": [ { "type": 0, - "value": "Dropoff" + "value": "退出" } ], "label.edit": [ @@ -338,55 +338,55 @@ "label.edit-dashboard": [ { "type": 0, - "value": "編輯管理面板" + "value": "編輯儀表板" } ], "label.enable-share-url": [ { "type": 0, - "value": "啟用分享連結" + "value": "啟用分享網址" } ], "label.event": [ { "type": 0, - "value": "Event" + "value": "事件" } ], "label.event-data": [ { "type": 0, - "value": "Event data" + "value": "事件資料" } ], "label.events": [ { "type": 0, - "value": "行為類別" + "value": "事件" } ], "label.false": [ { "type": 0, - "value": "False" + "value": "否" } ], "label.field": [ { "type": 0, - "value": "Field" + "value": "欄位" } ], "label.fields": [ { "type": 0, - "value": "Fields" + "value": "欄位" } ], "label.filter-combined": [ { "type": 0, - "value": "總和" + "value": "組合" } ], "label.filter-raw": [ @@ -398,67 +398,67 @@ "label.filters": [ { "type": 0, - "value": "Filters" + "value": "篩選器" } ], "label.funnel": [ { "type": 0, - "value": "Funnel" + "value": "漏斗" } ], "label.greater-than": [ { "type": 0, - "value": "Greater than" + "value": "大於" } ], "label.greater-than-equals": [ { "type": 0, - "value": "Greater than or equals" + "value": "大於或等於" } ], "label.insights": [ { "type": 0, - "value": "Insights" + "value": "洞察" } ], "label.is": [ { "type": 0, - "value": "Is" + "value": "是" } ], "label.is-not": [ { "type": 0, - "value": "Is not" + "value": "不是" } ], "label.is-not-set": [ { "type": 0, - "value": "Is not set" + "value": "未設定" } ], "label.is-set": [ { "type": 0, - "value": "Is set" + "value": "已設定" } ], "label.join": [ { "type": 0, - "value": "Join" + "value": "加入" } ], "label.join-team": [ { "type": 0, - "value": "Join team" + "value": "加入團隊" } ], "label.language": [ @@ -476,7 +476,7 @@ "label.laptop": [ { "type": 0, - "value": "筆記本" + "value": "筆記型電腦" } ], "label.last-days": [ @@ -510,25 +510,25 @@ "label.leave": [ { "type": 0, - "value": "Leave" + "value": "離開" } ], "label.leave-team": [ { "type": 0, - "value": "Leave team" + "value": "離開團隊" } ], "label.less-than": [ { "type": 0, - "value": "Less than" + "value": "小於" } ], "label.less-than-equals": [ { "type": 0, - "value": "Less than or equals" + "value": "小於或等於" } ], "label.login": [ @@ -540,31 +540,31 @@ "label.logout": [ { "type": 0, - "value": "退出" + "value": "登出" } ], "label.max": [ { "type": 0, - "value": "Max" + "value": "最大" } ], "label.members": [ { "type": 0, - "value": "Members" + "value": "成員" } ], "label.min": [ { "type": 0, - "value": "Min" + "value": "最小" } ], "label.mobile": [ { "type": 0, - "value": "手機" + "value": "行動裝置" } ], "label.more": [ @@ -576,13 +576,13 @@ "label.my-websites": [ { "type": 0, - "value": "My websites" + "value": "我的網站" } ], "label.name": [ { "type": 0, - "value": "名字" + "value": "名稱" } ], "label.new-password": [ @@ -600,13 +600,13 @@ "label.os": [ { "type": 0, - "value": "OS" + "value": "作業系統" } ], "label.overview": [ { "type": 0, - "value": "Overview" + "value": "概覽" } ], "label.owner": [ @@ -618,7 +618,7 @@ "label.page-of": [ { "type": 0, - "value": "Page " + "value": "頁面 " }, { "type": 1, @@ -626,7 +626,7 @@ }, { "type": 0, - "value": " of " + "value": " / " }, { "type": 1, @@ -636,19 +636,19 @@ "label.page-views": [ { "type": 0, - "value": "網頁流量" + "value": "頁面瀏覽" } ], "label.pageTitle": [ { "type": 0, - "value": "Page title" + "value": "頁面標題" } ], "label.pages": [ { "type": 0, - "value": "網頁" + "value": "頁面" } ], "label.password": [ @@ -660,11 +660,15 @@ "label.powered-by": [ { "type": 0, - "value": "運行 " + "value": "由 " }, { "type": 1, "value": "name" + }, + { + "type": 0, + "value": " 提供" } ], "label.profile": [ @@ -676,13 +680,13 @@ "label.queries": [ { "type": 0, - "value": "Queries" + "value": "查詢" } ], "label.query": [ { "type": 0, - "value": "Query" + "value": "查詢" } ], "label.query-parameters": [ @@ -694,55 +698,55 @@ "label.realtime": [ { "type": 0, - "value": "實時" + "value": "即時" } ], "label.referrer": [ { "type": 0, - "value": "Referrer" + "value": "參照來源" } ], "label.referrers": [ { "type": 0, - "value": "指入域名" + "value": "參照來源" } ], "label.refresh": [ { "type": 0, - "value": "刷新" + "value": "重新整理" } ], "label.regenerate": [ { "type": 0, - "value": "Regenerate" + "value": "重新產生" } ], "label.region": [ { "type": 0, - "value": "Region" + "value": "區域" } ], "label.regions": [ { "type": 0, - "value": "Regions" + "value": "區域" } ], "label.remove": [ { "type": 0, - "value": "Remove" + "value": "移除" } ], "label.reports": [ { "type": 0, - "value": "Reports" + "value": "報告" } ], "label.required": [ @@ -754,73 +758,73 @@ "label.reset": [ { "type": 0, - "value": "重置" + "value": "重設" } ], "label.reset-website": [ { "type": 0, - "value": "重置統計數據" + "value": "重設網站" } ], "label.retention": [ { "type": 0, - "value": "Retention" + "value": "保留" } ], "label.role": [ { "type": 0, - "value": "Role" + "value": "角色" } ], "label.run-query": [ { "type": 0, - "value": "Run query" + "value": "執行查詢" } ], "label.save": [ { "type": 0, - "value": "保存" + "value": "儲存" } ], "label.screens": [ { "type": 0, - "value": "屏幕尺寸" + "value": "螢幕" } ], "label.select-date": [ { "type": 0, - "value": "Select date" + "value": "選擇日期" } ], "label.select-website": [ { "type": 0, - "value": "Select website" + "value": "選擇網站" } ], "label.sessions": [ { "type": 0, - "value": "Sessions" + "value": "工作階段" } ], "label.settings": [ { "type": 0, - "value": "設置" + "value": "設定" } ], "label.share-url": [ { "type": 0, - "value": "分享連結" + "value": "分享網址" } ], "label.single-day": [ @@ -832,7 +836,7 @@ "label.sum": [ { "type": 0, - "value": "Sum" + "value": "總和" } ], "label.tablet": [ @@ -844,49 +848,49 @@ "label.team": [ { "type": 0, - "value": "Team" + "value": "團隊" } ], "label.team-guest": [ { "type": 0, - "value": "Team guest" + "value": "團隊訪客" } ], "label.team-id": [ { "type": 0, - "value": "Team ID" + "value": "團隊 ID" } ], "label.team-member": [ { "type": 0, - "value": "Team member" + "value": "團隊成員" } ], "label.team-name": [ { "type": 0, - "value": "Team name" + "value": "團隊名稱" } ], "label.team-owner": [ { "type": 0, - "value": "Team owner" + "value": "團隊擁有者" } ], "label.team-websites": [ { "type": 0, - "value": "Team websites" + "value": "團隊網站" } ], "label.teams": [ { "type": 0, - "value": "Teams" + "value": "團隊" } ], "label.theme": [ @@ -922,7 +926,7 @@ "label.title": [ { "type": 0, - "value": "Title" + "value": "標題" } ], "label.today": [ @@ -940,13 +944,13 @@ "label.total": [ { "type": 0, - "value": "Total" + "value": "總計" } ], "label.total-records": [ { "type": 0, - "value": "Total records" + "value": "總記錄" } ], "label.tracking-code": [ @@ -958,19 +962,19 @@ "label.true": [ { "type": 0, - "value": "True" + "value": "是" } ], "label.type": [ { "type": 0, - "value": "Type" + "value": "類型" } ], "label.unique": [ { "type": 0, - "value": "Unique" + "value": "獨立" } ], "label.unique-visitors": [ @@ -988,85 +992,85 @@ "label.untitled": [ { "type": 0, - "value": "Untitled" + "value": "無標題" } ], "label.url": [ { "type": 0, - "value": "URL" + "value": "網址" } ], "label.urls": [ { "type": 0, - "value": "URLs" + "value": "網址" } ], "label.user": [ { "type": 0, - "value": "User" + "value": "使用者" } ], "label.username": [ { "type": 0, - "value": "用户名" + "value": "使用者名稱" } ], "label.users": [ { "type": 0, - "value": "Users" + "value": "使用者" } ], "label.value": [ { "type": 0, - "value": "Value" + "value": "值" } ], "label.view": [ { "type": 0, - "value": "View" + "value": "檢視" } ], "label.view-details": [ { "type": 0, - "value": "查看更多" + "value": "檢視詳細資訊" } ], "label.view-only": [ { "type": 0, - "value": "View only" + "value": "僅供檢視" } ], "label.views": [ { "type": 0, - "value": "頁面流量" + "value": "檢視" } ], "label.visitors": [ { "type": 0, - "value": "獨立訪客" + "value": "訪客" } ], "label.website": [ { "type": 0, - "value": "Website" + "value": "網站" } ], "label.website-id": [ { "type": 0, - "value": "Website ID" + "value": "網站 ID" } ], "label.websites": [ @@ -1078,19 +1082,19 @@ "label.window": [ { "type": 0, - "value": "Window" + "value": "視窗" } ], "label.yesterday": [ { "type": 0, - "value": "Yesterday" + "value": "昨天" } ], "message.active-users": [ { "type": 0, - "value": "當前線上 " + "value": "目前有 " }, { "type": 1, @@ -1098,13 +1102,13 @@ }, { "type": 0, - "value": " 人" + "value": " 個活躍的訪客" } ], "message.confirm-delete": [ { "type": 0, - "value": "你確定要刪除 " + "value": "您確定要刪除 " }, { "type": 1, @@ -1118,7 +1122,7 @@ "message.confirm-leave": [ { "type": 0, - "value": "Are you sure you want to leave " + "value": "您確定要離開 " }, { "type": 1, @@ -1126,13 +1130,13 @@ }, { "type": 0, - "value": "?" + "value": " 嗎?" } ], "message.confirm-reset": [ { "type": 0, - "value": "您確定要重置 " + "value": "您確定要重設 " }, { "type": 1, @@ -1140,13 +1144,13 @@ }, { "type": 0, - "value": " 的數據嗎?" + "value": " 嗎?" } ], "message.delete-account": [ { "type": 0, - "value": "To delete this account, type " + "value": "要刪除此帳戶,請在下方的方框中輸入 " }, { "type": 1, @@ -1154,13 +1158,13 @@ }, { "type": 0, - "value": " in the box below to confirm." + "value": " 以確認。" } ], "message.delete-website": [ { "type": 0, - "value": "To delete this website, type " + "value": "要刪除此網站,請在下方的方框中輸入 " }, { "type": 1, @@ -1168,19 +1172,19 @@ }, { "type": 0, - "value": " in the box below to confirm." + "value": " 以確認。" } ], "message.delete-website-warning": [ { "type": 0, - "value": "所有相關數據將會被刪除。" + "value": "所有網站資料將被刪除。" } ], "message.error": [ { "type": 0, - "value": "出現錯誤。" + "value": "發生錯誤。" } ], "message.event-log": [ @@ -1190,7 +1194,7 @@ }, { "type": 0, - "value": " on " + "value": " 在 " }, { "type": 1, @@ -1200,25 +1204,25 @@ "message.go-to-settings": [ { "type": 0, - "value": "去設定" + "value": "前往設定" } ], "message.incorrect-username-password": [ { "type": 0, - "value": "用户名或密碼不正確。" + "value": "使用者名稱和/或密碼不正確。" } ], "message.invalid-domain": [ { "type": 0, - "value": "無效域名" + "value": "無效的網域。請不要包含 http/https。" } ], "message.min-password-length": [ { "type": 0, - "value": "Minimum length of " + "value": "最少需要 " }, { "type": 1, @@ -1226,13 +1230,13 @@ }, { "type": 0, - "value": " characters" + "value": " 個字元" } ], "message.new-version-available": [ { "type": 0, - "value": "A new version of Umami " + "value": "Umami " }, { "type": 1, @@ -1240,67 +1244,67 @@ }, { "type": 0, - "value": " is available!" + "value": " 的新版本已經可以使用!" } ], "message.no-data-available": [ { "type": 0, - "value": "無可用數據。" + "value": "沒有可用的資料。" } ], "message.no-event-data": [ { "type": 0, - "value": "No event data is available." + "value": "沒有可用的事件資料。" } ], "message.no-match-password": [ { "type": 0, - "value": "密碼不一致" + "value": "密碼不一致。" } ], "message.no-results-found": [ { "type": 0, - "value": "No results were found." + "value": "找不到結果。" } ], "message.no-team-websites": [ { "type": 0, - "value": "This team does not have any websites." + "value": "此團隊沒有任何網站。" } ], "message.no-teams": [ { "type": 0, - "value": "You have not created any teams." + "value": "您尚未建立任何團隊。" } ], "message.no-users": [ { "type": 0, - "value": "There are no users." + "value": "沒有使用者。" } ], "message.no-websites-configured": [ { "type": 0, - "value": "目前無任何網站設定。" + "value": "您尚未設定任何網站。" } ], "message.page-not-found": [ { "type": 0, - "value": "網頁未找到。" + "value": "找不到頁面" } ], "message.reset-website": [ { "type": 0, - "value": "To reset this website, type " + "value": "要重設此網站,請在下方的方框中輸入 " }, { "type": 1, @@ -1308,69 +1312,75 @@ }, { "type": 0, - "value": " in the box below to confirm." + "value": " 以確認。" } ], "message.reset-website-warning": [ { "type": 0, - "value": "本網站的所有統計數據將被刪除,但您的跟蹤代碼將保持不變。" + "value": "此網站的所有統計將被刪除,但您的設定將保持不變。" } ], "message.saved": [ { "type": 0, - "value": "成功保存。" + "value": "已儲存。" } ], "message.share-url": [ { "type": 0, - "value": "這是 " - }, - { - "type": 1, - "value": "target" - }, - { - "type": 0, - "value": " 的分享連結。" + "value": "您的網站統計資料可以在以下網址公開檢視:" } ], "message.team-already-member": [ { "type": 0, - "value": "You are already a member of the team." + "value": "您已經是團隊的成員。" } ], "message.team-not-found": [ { "type": 0, - "value": "Team not found." + "value": "找不到團隊。" } ], "message.team-websites-info": [ { "type": 0, - "value": "Websites can be viewed by anyone on the team." + "value": "團隊的任何成員都可以檢視網站。" } ], "message.tracking-code": [ { "type": 0, - "value": "追蹤代碼" + "value": "要追蹤此網站的統計,請將以下代碼放在您的 HTML 的 " + }, + { + "children": [ + { + "type": 0, + "value": "..." + } + ], + "type": 8, + "value": "head" + }, + { + "type": 0, + "value": " 區段中。" } ], "message.user-deleted": [ { "type": 0, - "value": "User deleted." + "value": "使用者已刪除。" } ], "message.visitor-log": [ { "type": 0, - "value": "來自" + "value": "來自 " }, { "type": 1, @@ -1378,15 +1388,7 @@ }, { "type": 0, - "value": "的訪客在搭載 " - }, - { - "type": 1, - "value": "os" - }, - { - "type": 0, - "value": " 的" + "value": " 的訪客在 " }, { "type": 1, @@ -1394,7 +1396,15 @@ }, { "type": 0, - "value": "上使用 " + "value": " 上的 " + }, + { + "type": 1, + "value": "os" + }, + { + "type": 0, + "value": " 使用 " }, { "type": 1, @@ -1402,7 +1412,7 @@ }, { "type": 0, - "value": " 進行訪問。" + "value": " 瀏覽。" } ] } diff --git a/rollup.components.config.mjs b/rollup.components.config.mjs new file mode 100644 index 00000000..c4481d0e --- /dev/null +++ b/rollup.components.config.mjs @@ -0,0 +1,84 @@ +import path from 'path'; +import crypto from 'crypto'; +import resolve from '@rollup/plugin-node-resolve'; +import alias from '@rollup/plugin-alias'; +import json from '@rollup/plugin-json'; +import postcss from 'rollup-plugin-postcss'; +import copy from 'rollup-plugin-copy'; +import del from 'rollup-plugin-delete'; +import nodeExternals from 'rollup-plugin-node-externals'; +import esbuild from 'rollup-plugin-esbuild'; +import dts from 'rollup-plugin-dts'; +import svgr from '@svgr/rollup'; + +const md5 = str => crypto.createHash('md5').update(str).digest('hex'); + +const customResolver = resolve({ + extensions: ['.js', '.jsx', '.ts', '.tsx'], +}); + +const aliasConfig = { + entries: [ + { find: /^components/, replacement: path.resolve('./src/components') }, + { find: /^hooks/, replacement: path.resolve('./src/hooks') }, + { find: /^lib/, replacement: path.resolve('./src/lib') }, + { find: /^store/, replacement: path.resolve('./src/store') }, + { find: /^public/, replacement: path.resolve('./public') }, + { find: /^assets/, replacement: path.resolve('./src/assets') }, + ], + customResolver, +}; + +const jsBundle = { + input: 'src/index.ts', + output: [ + { + file: 'dist/index.js', + format: 'es', + sourcemap: true, + }, + ], + plugins: [ + del({ targets: 'dist/*', runOnce: true }), + copy({ targets: [{ src: './package.components.json', dest: 'dist', rename: 'package.json' }] }), + postcss({ + config: false, + extract: 'styles.css', + sourceMap: true, + minimize: true, + modules: { + generateScopedName: function (name, filename, css) { + const file = path.basename(filename, '.css').replace('.module', ''); + const hash = Buffer.from(md5(`${name}:${filename}:${css}`)) + .toString('base64') + .substring(0, 5); + + return `${file}-${name}--${hash}`; + }, + }, + }), + svgr({ icon: true }), + nodeExternals(), + json(), + alias(aliasConfig), + esbuild({ + target: 'es6', + jsx: 'automatic', + loaders: { + '.js': 'jsx', + }, + }), + ], +}; + +const dtsBundle = { + input: 'src/index.ts', + output: { + file: 'dist/index.d.ts', + format: 'es', + }, + plugins: [alias(aliasConfig), nodeExternals(), json(), dts()], + external: [/\.css/], +}; + +export default [jsBundle, dtsBundle]; diff --git a/rollup.tracker.config.js b/rollup.tracker.config.mjs similarity index 93% rename from rollup.tracker.config.js rename to rollup.tracker.config.mjs index f4e7223c..465e1af3 100644 --- a/rollup.tracker.config.js +++ b/rollup.tracker.config.mjs @@ -4,7 +4,7 @@ import replace from '@rollup/plugin-replace'; import { terser } from 'rollup-plugin-terser'; export default { - input: 'tracker/index.js', + input: 'src/tracker/index.js', output: { file: 'public/script.js', format: 'iife', diff --git a/scripts/check-lang.js b/scripts/check-lang.js index e5a0bf09..a1b60431 100644 --- a/scripts/check-lang.js +++ b/scripts/check-lang.js @@ -2,7 +2,7 @@ const fs = require('fs'); const path = require('path'); const chalk = require('chalk'); -const messages = require('../lang/en-US.json'); +const messages = require('../src/lang/en-US.json'); const ignore = require('../lang-ignore.json'); const dir = path.resolve(__dirname, '../lang'); diff --git a/scripts/download-country-names.js b/scripts/download-country-names.js index f56d91f9..6dbbbbdc 100644 --- a/scripts/download-country-names.js +++ b/scripts/download-country-names.js @@ -4,7 +4,7 @@ const path = require('path'); const https = require('https'); const chalk = require('chalk'); -const src = path.resolve(__dirname, '../lang'); +const src = path.resolve(__dirname, '../src/lang'); const dest = path.resolve(__dirname, '../public/intl/country'); const files = fs.readdirSync(src); diff --git a/scripts/download-language-names.js b/scripts/download-language-names.js index 5cea88cf..5478fb8d 100644 --- a/scripts/download-language-names.js +++ b/scripts/download-language-names.js @@ -4,7 +4,7 @@ const path = require('path'); const https = require('https'); const chalk = require('chalk'); -const src = path.resolve(__dirname, '../lang'); +const src = path.resolve(__dirname, '../src/lang'); const dest = path.resolve(__dirname, '../public/intl/language'); const files = fs.readdirSync(src); diff --git a/scripts/format-lang.js b/scripts/format-lang.js index 941fd672..593e296d 100644 --- a/scripts/format-lang.js +++ b/scripts/format-lang.js @@ -3,7 +3,7 @@ const path = require('path'); const del = require('del'); const prettier = require('prettier'); -const src = path.resolve(__dirname, '../lang'); +const src = path.resolve(__dirname, '../src/lang'); const dest = path.resolve(__dirname, '../build/messages'); const files = fs.readdirSync(src); @@ -17,7 +17,7 @@ async function run() { await fs.ensureDir(dest); files.forEach(file => { - const lang = require(`../lang/${file}`); + const lang = require(`../src/lang/${file}`); const keys = Object.keys(lang).sort(); const formatted = keys.reduce((obj, key) => { diff --git a/assets/add-user.svg b/src/assets/add-user.svg similarity index 100% rename from assets/add-user.svg rename to src/assets/add-user.svg diff --git a/assets/bar-chart.svg b/src/assets/bar-chart.svg similarity index 100% rename from assets/bar-chart.svg rename to src/assets/bar-chart.svg diff --git a/assets/bars.svg b/src/assets/bars.svg similarity index 100% rename from assets/bars.svg rename to src/assets/bars.svg diff --git a/assets/bolt.svg b/src/assets/bolt.svg similarity index 100% rename from assets/bolt.svg rename to src/assets/bolt.svg diff --git a/assets/calendar.svg b/src/assets/calendar.svg similarity index 100% rename from assets/calendar.svg rename to src/assets/calendar.svg diff --git a/assets/clock.svg b/src/assets/clock.svg similarity index 100% rename from assets/clock.svg rename to src/assets/clock.svg diff --git a/assets/dashboard.svg b/src/assets/dashboard.svg similarity index 100% rename from assets/dashboard.svg rename to src/assets/dashboard.svg diff --git a/assets/expand.svg b/src/assets/expand.svg similarity index 100% rename from assets/expand.svg rename to src/assets/expand.svg diff --git a/assets/eye.svg b/src/assets/eye.svg similarity index 100% rename from assets/eye.svg rename to src/assets/eye.svg diff --git a/assets/funnel.svg b/src/assets/funnel.svg similarity index 100% rename from assets/funnel.svg rename to src/assets/funnel.svg diff --git a/assets/gear.svg b/src/assets/gear.svg similarity index 100% rename from assets/gear.svg rename to src/assets/gear.svg diff --git a/assets/globe.svg b/src/assets/globe.svg similarity index 100% rename from assets/globe.svg rename to src/assets/globe.svg diff --git a/assets/lightbulb.svg b/src/assets/lightbulb.svg similarity index 100% rename from assets/lightbulb.svg rename to src/assets/lightbulb.svg diff --git a/assets/link.svg b/src/assets/link.svg similarity index 100% rename from assets/link.svg rename to src/assets/link.svg diff --git a/assets/lock.svg b/src/assets/lock.svg similarity index 100% rename from assets/lock.svg rename to src/assets/lock.svg diff --git a/assets/logo.svg b/src/assets/logo.svg similarity index 100% rename from assets/logo.svg rename to src/assets/logo.svg diff --git a/assets/magnet.svg b/src/assets/magnet.svg similarity index 100% rename from assets/magnet.svg rename to src/assets/magnet.svg diff --git a/assets/moon.svg b/src/assets/moon.svg similarity index 100% rename from assets/moon.svg rename to src/assets/moon.svg diff --git a/assets/nodes.svg b/src/assets/nodes.svg similarity index 100% rename from assets/nodes.svg rename to src/assets/nodes.svg diff --git a/assets/overview.svg b/src/assets/overview.svg similarity index 100% rename from assets/overview.svg rename to src/assets/overview.svg diff --git a/assets/profile.svg b/src/assets/profile.svg similarity index 100% rename from assets/profile.svg rename to src/assets/profile.svg diff --git a/assets/redo.svg b/src/assets/redo.svg similarity index 100% rename from assets/redo.svg rename to src/assets/redo.svg diff --git a/assets/reports.svg b/src/assets/reports.svg similarity index 100% rename from assets/reports.svg rename to src/assets/reports.svg diff --git a/assets/sun.svg b/src/assets/sun.svg similarity index 100% rename from assets/sun.svg rename to src/assets/sun.svg diff --git a/assets/user.svg b/src/assets/user.svg similarity index 100% rename from assets/user.svg rename to src/assets/user.svg diff --git a/assets/users.svg b/src/assets/users.svg similarity index 100% rename from assets/users.svg rename to src/assets/users.svg diff --git a/assets/visitor.svg b/src/assets/visitor.svg similarity index 100% rename from assets/visitor.svg rename to src/assets/visitor.svg diff --git a/assets/website.svg b/src/assets/website.svg similarity index 100% rename from assets/website.svg rename to src/assets/website.svg diff --git a/components/common/ConfirmDeleteForm.js b/src/components/common/ConfirmDeleteForm.js similarity index 84% rename from components/common/ConfirmDeleteForm.js rename to src/components/common/ConfirmDeleteForm.js index 3496a305..3d2c383d 100644 --- a/components/common/ConfirmDeleteForm.js +++ b/src/components/common/ConfirmDeleteForm.js @@ -1,6 +1,6 @@ import { useState } from 'react'; import { Button, LoadingButton, Form, FormButtons } from 'react-basics'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; export function ConfirmDeleteForm({ name, onConfirm, onClose }) { const [loading, setLoading] = useState(false); @@ -17,7 +17,7 @@ export function ConfirmDeleteForm({ name, onConfirm, onClose }) { {name} }} />

- + {formatMessage(labels.delete)} diff --git a/components/common/Empty.js b/src/components/common/Empty.js similarity index 86% rename from components/common/Empty.js rename to src/components/common/Empty.js index 95681b16..c0be761a 100644 --- a/components/common/Empty.js +++ b/src/components/common/Empty.js @@ -1,6 +1,6 @@ import classNames from 'classnames'; import styles from './Empty.module.css'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; export function Empty({ message, className }) { const { formatMessage, messages } = useMessages(); diff --git a/components/common/Empty.module.css b/src/components/common/Empty.module.css similarity index 100% rename from components/common/Empty.module.css rename to src/components/common/Empty.module.css diff --git a/components/common/EmptyPlaceholder.js b/src/components/common/EmptyPlaceholder.js similarity index 100% rename from components/common/EmptyPlaceholder.js rename to src/components/common/EmptyPlaceholder.js diff --git a/components/common/ErrorBoundary.js b/src/components/common/ErrorBoundary.js similarity index 93% rename from components/common/ErrorBoundary.js rename to src/components/common/ErrorBoundary.js index f97fd92c..32cedb39 100644 --- a/components/common/ErrorBoundary.js +++ b/src/components/common/ErrorBoundary.js @@ -1,7 +1,7 @@ /* eslint-disable no-console */ import { ErrorBoundary as Boundary } from 'react-error-boundary'; import { Button } from 'react-basics'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; import styles from './ErrorBoundry.module.css'; const logError = (error, info) => { diff --git a/components/common/ErrorBoundry.module.css b/src/components/common/ErrorBoundry.module.css similarity index 100% rename from components/common/ErrorBoundry.module.css rename to src/components/common/ErrorBoundry.module.css diff --git a/components/common/ErrorMessage.js b/src/components/common/ErrorMessage.js similarity index 88% rename from components/common/ErrorMessage.js rename to src/components/common/ErrorMessage.js index e2b22747..f8129c6b 100644 --- a/components/common/ErrorMessage.js +++ b/src/components/common/ErrorMessage.js @@ -1,6 +1,6 @@ import { Icon, Icons, Text } from 'react-basics'; import styles from './ErrorMessage.module.css'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; export function ErrorMessage() { const { formatMessage, messages } = useMessages(); diff --git a/components/common/ErrorMessage.module.css b/src/components/common/ErrorMessage.module.css similarity index 100% rename from components/common/ErrorMessage.module.css rename to src/components/common/ErrorMessage.module.css diff --git a/components/common/Favicon.js b/src/components/common/Favicon.js similarity index 100% rename from components/common/Favicon.js rename to src/components/common/Favicon.js diff --git a/components/common/Favicon.module.css b/src/components/common/Favicon.module.css similarity index 100% rename from components/common/Favicon.module.css rename to src/components/common/Favicon.module.css diff --git a/components/common/FilterButtons.js b/src/components/common/FilterButtons.js similarity index 100% rename from components/common/FilterButtons.js rename to src/components/common/FilterButtons.js diff --git a/components/common/FilterLink.js b/src/components/common/FilterLink.js similarity index 91% rename from components/common/FilterLink.js rename to src/components/common/FilterLink.js index 30cdc025..2a95e011 100644 --- a/components/common/FilterLink.js +++ b/src/components/common/FilterLink.js @@ -2,8 +2,8 @@ import { Icon, Icons } from 'react-basics'; import classNames from 'classnames'; import Link from 'next/link'; import { safeDecodeURI } from 'next-basics'; -import usePageQuery from 'hooks/usePageQuery'; -import useMessages from 'hooks/useMessages'; +import usePageQuery from 'components/hooks/usePageQuery'; +import useMessages from 'components/hooks/useMessages'; import styles from './FilterLink.module.css'; export function FilterLink({ id, value, label, externalUrl, children, className }) { diff --git a/components/common/FilterLink.module.css b/src/components/common/FilterLink.module.css similarity index 100% rename from components/common/FilterLink.module.css rename to src/components/common/FilterLink.module.css diff --git a/components/common/HamburgerButton.js b/src/components/common/HamburgerButton.js similarity index 92% rename from components/common/HamburgerButton.js rename to src/components/common/HamburgerButton.js index 48c80770..f97006ef 100644 --- a/components/common/HamburgerButton.js +++ b/src/components/common/HamburgerButton.js @@ -2,13 +2,12 @@ import { Button, Icon } from 'react-basics'; import { useState } from 'react'; import MobileMenu from './MobileMenu'; import Icons from 'components/icons'; -import useMessages from 'hooks/useMessages'; -import useConfig from 'hooks/useConfig'; +import useMessages from 'components/hooks/useMessages'; export function HamburgerButton() { const { formatMessage, labels } = useMessages(); const [active, setActive] = useState(false); - const { cloudMode } = useConfig(); + const cloudMode = Boolean(process.env.cloudMode); const menuItems = [ { diff --git a/components/common/HamburgerButton.module.css b/src/components/common/HamburgerButton.module.css similarity index 100% rename from components/common/HamburgerButton.module.css rename to src/components/common/HamburgerButton.module.css diff --git a/components/common/HoverTooltip.js b/src/components/common/HoverTooltip.js similarity index 100% rename from components/common/HoverTooltip.js rename to src/components/common/HoverTooltip.js diff --git a/components/common/HoverTooltip.module.css b/src/components/common/HoverTooltip.module.css similarity index 100% rename from components/common/HoverTooltip.module.css rename to src/components/common/HoverTooltip.module.css diff --git a/components/common/LinkButton.js b/src/components/common/LinkButton.js similarity index 77% rename from components/common/LinkButton.js rename to src/components/common/LinkButton.js index 8c050147..54c7fa63 100644 --- a/components/common/LinkButton.js +++ b/src/components/common/LinkButton.js @@ -2,7 +2,7 @@ import Link from 'next/link'; import { Icon, Icons, Text } from 'react-basics'; import styles from './LinkButton.module.css'; -export default function LinkButton({ href, icon, children }) { +export function LinkButton({ href, icon, children }) { return ( {icon || } @@ -10,3 +10,5 @@ export default function LinkButton({ href, icon, children }) { ); } + +export default LinkButton; diff --git a/components/common/LinkButton.module.css b/src/components/common/LinkButton.module.css similarity index 100% rename from components/common/LinkButton.module.css rename to src/components/common/LinkButton.module.css diff --git a/components/common/MobileMenu.js b/src/components/common/MobileMenu.js similarity index 90% rename from components/common/MobileMenu.js rename to src/components/common/MobileMenu.js index 2228a3e9..de1e9ffa 100644 --- a/components/common/MobileMenu.js +++ b/src/components/common/MobileMenu.js @@ -1,3 +1,4 @@ +import { createPortal } from 'react-dom'; import classNames from 'classnames'; import { useRouter } from 'next/router'; import Link from 'next/link'; @@ -28,10 +29,11 @@ export function MobileMenu({ items = [], onClose }) { ); - return ( + return createPortal(
-
+ , + document.body, ); } diff --git a/components/common/MobileMenu.module.css b/src/components/common/MobileMenu.module.css similarity index 100% rename from components/common/MobileMenu.module.css rename to src/components/common/MobileMenu.module.css diff --git a/components/common/Pager.js b/src/components/common/Pager.js similarity index 95% rename from components/common/Pager.js rename to src/components/common/Pager.js index aaeffbae..7a5e7ed5 100644 --- a/components/common/Pager.js +++ b/src/components/common/Pager.js @@ -1,6 +1,6 @@ import styles from './Pager.module.css'; import { Button, Flexbox, Icon, Icons } from 'react-basics'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; export function Pager({ page, pageSize, count, onPageChange }) { const { formatMessage, labels } = useMessages(); diff --git a/components/common/Pager.module.css b/src/components/common/Pager.module.css similarity index 100% rename from components/common/Pager.module.css rename to src/components/common/Pager.module.css diff --git a/components/common/SettingsTable.js b/src/components/common/SettingsTable.js similarity index 91% rename from components/common/SettingsTable.js rename to src/components/common/SettingsTable.js index e9491331..2df3b391 100644 --- a/components/common/SettingsTable.js +++ b/src/components/common/SettingsTable.js @@ -1,5 +1,5 @@ -import EmptyPlaceholder from 'components/common/EmptyPlaceholder'; -import useMessages from 'hooks/useMessages'; +import Empty from 'components/common/Empty'; +import useMessages from 'components/hooks/useMessages'; import { useState } from 'react'; import { SearchField, @@ -36,7 +36,7 @@ export function SettingsTable({ return ( <> - {showSearch && ( + {showSearch && !!value.length && ( )} {value.length === 0 && filterValue && ( - + )} {value.length > 0 && ( diff --git a/components/common/SettingsTable.module.css b/src/components/common/SettingsTable.module.css similarity index 100% rename from components/common/SettingsTable.module.css rename to src/components/common/SettingsTable.module.css diff --git a/components/common/UpdateNotice.js b/src/components/common/UpdateNotice.js similarity index 96% rename from components/common/UpdateNotice.js rename to src/components/common/UpdateNotice.js index bef6be98..e3edc70c 100644 --- a/components/common/UpdateNotice.js +++ b/src/components/common/UpdateNotice.js @@ -4,7 +4,7 @@ import { setItem } from 'next-basics'; import useStore, { checkVersion } from 'store/version'; import { REPO_URL, VERSION_CHECK } from 'lib/constants'; import styles from './UpdateNotice.module.css'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; import { useRouter } from 'next/router'; export function UpdateNotice({ user, config }) { diff --git a/components/common/UpdateNotice.module.css b/src/components/common/UpdateNotice.module.css similarity index 100% rename from components/common/UpdateNotice.module.css rename to src/components/common/UpdateNotice.module.css diff --git a/components/common/WorldMap.js b/src/components/common/WorldMap.js similarity index 94% rename from components/common/WorldMap.js rename to src/components/common/WorldMap.js index 9c91e4a4..b593099b 100644 --- a/components/common/WorldMap.js +++ b/src/components/common/WorldMap.js @@ -5,9 +5,9 @@ import classNames from 'classnames'; import { colord } from 'colord'; import HoverTooltip from 'components/common/HoverTooltip'; import { ISO_COUNTRIES, MAP_FILE } from 'lib/constants'; -import useTheme from 'hooks/useTheme'; -import useCountryNames from 'hooks/useCountryNames'; -import useLocale from 'hooks/useLocale'; +import useTheme from 'components/hooks/useTheme'; +import useCountryNames from 'components/hooks/useCountryNames'; +import useLocale from 'components/hooks/useLocale'; import { formatLongNumber } from 'lib/format'; import { percentFilter } from 'lib/filters'; import styles from './WorldMap.module.css'; diff --git a/components/common/WorldMap.module.css b/src/components/common/WorldMap.module.css similarity index 100% rename from components/common/WorldMap.module.css rename to src/components/common/WorldMap.module.css diff --git a/components/declarations.d.ts b/src/components/declarations.d.ts similarity index 100% rename from components/declarations.d.ts rename to src/components/declarations.d.ts diff --git a/hooks/index.js b/src/components/hooks/index.js similarity index 100% rename from hooks/index.js rename to src/components/hooks/index.js diff --git a/hooks/useApi.ts b/src/components/hooks/useApi.ts similarity index 100% rename from hooks/useApi.ts rename to src/components/hooks/useApi.ts diff --git a/hooks/useApiFilter.ts b/src/components/hooks/useApiFilter.ts similarity index 100% rename from hooks/useApiFilter.ts rename to src/components/hooks/useApiFilter.ts diff --git a/hooks/useConfig.js b/src/components/hooks/useConfig.js similarity index 70% rename from hooks/useConfig.js rename to src/components/hooks/useConfig.js index 2dead15a..5a516abc 100644 --- a/hooks/useConfig.js +++ b/src/components/hooks/useConfig.js @@ -1,21 +1,22 @@ import { useEffect } from 'react'; import useStore, { setConfig } from 'store/app'; -import useApi from 'hooks/useApi'; +import useApi from 'components/hooks/useApi'; let loading = false; export function useConfig() { const { config } = useStore(); const { get } = useApi(); + const configUrl = process.env.configUrl; async function loadConfig() { - const data = await get('/config'); + const data = await get(configUrl); loading = false; setConfig(data); } useEffect(() => { - if (!config && !loading) { + if (!config && !loading && configUrl) { loading = true; loadConfig(); } diff --git a/hooks/useCountryNames.js b/src/components/hooks/useCountryNames.js similarity index 100% rename from hooks/useCountryNames.js rename to src/components/hooks/useCountryNames.js diff --git a/hooks/useDateRange.js b/src/components/hooks/useDateRange.js similarity index 100% rename from hooks/useDateRange.js rename to src/components/hooks/useDateRange.js diff --git a/hooks/useDocumentClick.js b/src/components/hooks/useDocumentClick.js similarity index 100% rename from hooks/useDocumentClick.js rename to src/components/hooks/useDocumentClick.js diff --git a/hooks/useEscapeKey.js b/src/components/hooks/useEscapeKey.js similarity index 100% rename from hooks/useEscapeKey.js rename to src/components/hooks/useEscapeKey.js diff --git a/hooks/useFilters.js b/src/components/hooks/useFilters.js similarity index 97% rename from hooks/useFilters.js rename to src/components/hooks/useFilters.js index 089f2ee8..e1a9a885 100644 --- a/hooks/useFilters.js +++ b/src/components/hooks/useFilters.js @@ -1,4 +1,4 @@ -import { useMessages } from 'hooks'; +import { useMessages } from './useMessages'; import { OPERATORS } from 'lib/constants'; export function useFilters() { diff --git a/hooks/useForceUpdate.js b/src/components/hooks/useForceUpdate.js similarity index 100% rename from hooks/useForceUpdate.js rename to src/components/hooks/useForceUpdate.js diff --git a/hooks/useFormat.js b/src/components/hooks/useFormat.js similarity index 100% rename from hooks/useFormat.js rename to src/components/hooks/useFormat.js diff --git a/hooks/useLanguageNames.js b/src/components/hooks/useLanguageNames.js similarity index 100% rename from hooks/useLanguageNames.js rename to src/components/hooks/useLanguageNames.js diff --git a/hooks/useLocale.js b/src/components/hooks/useLocale.js similarity index 92% rename from hooks/useLocale.js rename to src/components/hooks/useLocale.js index 86ca9904..1374af81 100644 --- a/hooks/useLocale.js +++ b/src/components/hooks/useLocale.js @@ -4,8 +4,8 @@ import { httpGet, setItem } from 'next-basics'; import { LOCALE_CONFIG } from 'lib/constants'; import { getDateLocale, getTextDirection } from 'lib/lang'; import useStore, { setLocale } from 'store/app'; -import useForceUpdate from 'hooks/useForceUpdate'; -import enUS from 'public/intl/messages/en-US.json'; +import useForceUpdate from 'components/hooks/useForceUpdate'; +import enUS from 'public/intl/country/en-US.json'; const messages = { 'en-US': enUS, diff --git a/hooks/useMessages.js b/src/components/hooks/useMessages.js similarity index 100% rename from hooks/useMessages.js rename to src/components/hooks/useMessages.js diff --git a/hooks/usePageQuery.js b/src/components/hooks/usePageQuery.js similarity index 100% rename from hooks/usePageQuery.js rename to src/components/hooks/usePageQuery.js diff --git a/hooks/useReport.js b/src/components/hooks/useReport.js similarity index 100% rename from hooks/useReport.js rename to src/components/hooks/useReport.js diff --git a/hooks/useReports.js b/src/components/hooks/useReports.js similarity index 94% rename from hooks/useReports.js rename to src/components/hooks/useReports.js index 932fa6dc..d9292aeb 100644 --- a/hooks/useReports.js +++ b/src/components/hooks/useReports.js @@ -1,6 +1,6 @@ import { useState } from 'react'; import useApi from './useApi'; -import useApiFilter from 'hooks/useApiFilter'; +import useApiFilter from 'components/hooks/useApiFilter'; export function useReports() { const [modified, setModified] = useState(Date.now()); diff --git a/hooks/useRequireLogin.js b/src/components/hooks/useRequireLogin.ts similarity index 58% rename from hooks/useRequireLogin.js rename to src/components/hooks/useRequireLogin.ts index 3a95c988..950bb60a 100644 --- a/hooks/useRequireLogin.js +++ b/src/components/hooks/useRequireLogin.ts @@ -1,9 +1,9 @@ import { useEffect } from 'react'; import { useRouter } from 'next/router'; -import useApi from 'hooks/useApi'; -import useUser from 'hooks/useUser'; +import useApi from 'components/hooks/useApi'; +import useUser from 'components/hooks/useUser'; -export function useRequireLogin() { +export function useRequireLogin(handler: (data?: object) => void) { const router = useRouter(); const { get } = useApi(); const { user, setUser } = useUser(); @@ -11,9 +11,9 @@ export function useRequireLogin() { useEffect(() => { async function loadUser() { try { - const { user } = await get('/auth/verify'); + const data = await get('/auth/verify'); - setUser(user); + setUser(typeof handler === 'function' ? handler(data) : (data as any)?.user); } catch { await router.push('/login'); } diff --git a/hooks/useShareToken.js b/src/components/hooks/useShareToken.js similarity index 100% rename from hooks/useShareToken.js rename to src/components/hooks/useShareToken.js diff --git a/hooks/useSticky.js b/src/components/hooks/useSticky.js similarity index 100% rename from hooks/useSticky.js rename to src/components/hooks/useSticky.js diff --git a/hooks/useTheme.js b/src/components/hooks/useTheme.js similarity index 100% rename from hooks/useTheme.js rename to src/components/hooks/useTheme.js diff --git a/hooks/useTimezone.js b/src/components/hooks/useTimezone.js similarity index 100% rename from hooks/useTimezone.js rename to src/components/hooks/useTimezone.js diff --git a/hooks/useUser.js b/src/components/hooks/useUser.ts similarity index 100% rename from hooks/useUser.js rename to src/components/hooks/useUser.ts diff --git a/hooks/useWebsite.js b/src/components/hooks/useWebsite.js similarity index 100% rename from hooks/useWebsite.js rename to src/components/hooks/useWebsite.js diff --git a/hooks/useWebsiteReports.js b/src/components/hooks/useWebsiteReports.js similarity index 94% rename from hooks/useWebsiteReports.js rename to src/components/hooks/useWebsiteReports.js index 3b7ec415..c637bc76 100644 --- a/hooks/useWebsiteReports.js +++ b/src/components/hooks/useWebsiteReports.js @@ -1,6 +1,6 @@ import { useState } from 'react'; import useApi from './useApi'; -import useApiFilter from 'hooks/useApiFilter'; +import useApiFilter from 'components/hooks/useApiFilter'; export function useWebsiteReports(websiteId) { const [modified, setModified] = useState(Date.now()); diff --git a/components/icons.ts b/src/components/icons.ts similarity index 98% rename from components/icons.ts rename to src/components/icons.ts index 01d7caf5..8eb1f8b0 100644 --- a/components/icons.ts +++ b/src/components/icons.ts @@ -22,7 +22,7 @@ import User from 'assets/user.svg'; import Users from 'assets/users.svg'; import Visitor from 'assets/visitor.svg'; -const icons = { +const icons: any = { ...Icons, AddUser, Bars, diff --git a/components/input/DateFilter.js b/src/components/input/DateFilter.js similarity index 81% rename from components/input/DateFilter.js rename to src/components/input/DateFilter.js index af4b69dd..9fde27ca 100644 --- a/components/input/DateFilter.js +++ b/src/components/input/DateFilter.js @@ -2,10 +2,10 @@ import { useState } from 'react'; import { Icon, Modal, Dropdown, Item, Text, Flexbox } from 'react-basics'; import { endOfYear, isSameDay } from 'date-fns'; import DatePickerForm from 'components/metrics/DatePickerForm'; -import useLocale from 'hooks/useLocale'; +import useLocale from 'components/hooks/useLocale'; import { formatDate } from 'lib/date'; import Icons from 'components/icons'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; export function DateFilter({ value, @@ -13,6 +13,7 @@ export function DateFilter({ endDate, className, onChange, + selectedUnit, showAllTime = false, alignment = 'end', }) { @@ -66,7 +67,12 @@ export function DateFilter({ const renderValue = value => { return value.startsWith('range') ? ( - handleChange('custom')} /> + handleChange('custom')} + /> ) : ( options.find(e => e.value === value).label ); @@ -120,9 +126,11 @@ export function DateFilter({ ); } -const CustomRange = ({ startDate, endDate, onClick }) => { +const CustomRange = ({ startDate, endDate, selectedUnit, onClick }) => { const { locale } = useLocale(); + const monthFormat = +selectedUnit?.num === 1 && selectedUnit?.unit === 'month'; + function handleClick(e) { e.stopPropagation(); @@ -135,8 +143,14 @@ const CustomRange = ({ startDate, endDate, onClick }) => { - {formatDate(startDate, 'd LLL y', locale)} - {!isSameDay(startDate, endDate) && ` — ${formatDate(endDate, 'd LLL y', locale)}`} + {monthFormat ? ( + <>{formatDate(startDate, 'MMMM yyyy', locale)} + ) : ( + <> + {formatDate(startDate, 'd LLL y', locale)} + {!isSameDay(startDate, endDate) && ` — ${formatDate(endDate, 'd LLL y', locale)}`} + + )} ); diff --git a/components/input/LanguageButton.js b/src/components/input/LanguageButton.js similarity index 96% rename from components/input/LanguageButton.js rename to src/components/input/LanguageButton.js index d4c1cbc3..3c0d0cd6 100644 --- a/components/input/LanguageButton.js +++ b/src/components/input/LanguageButton.js @@ -1,7 +1,7 @@ import { Icon, Button, PopupTrigger, Popup, Text } from 'react-basics'; import classNames from 'classnames'; import { languages } from 'lib/lang'; -import useLocale from 'hooks/useLocale'; +import useLocale from 'components/hooks/useLocale'; import Icons from 'components/icons'; import styles from './LanguageButton.module.css'; diff --git a/components/input/LanguageButton.module.css b/src/components/input/LanguageButton.module.css similarity index 100% rename from components/input/LanguageButton.module.css rename to src/components/input/LanguageButton.module.css diff --git a/components/input/LogoutButton.js b/src/components/input/LogoutButton.js similarity index 84% rename from components/input/LogoutButton.js rename to src/components/input/LogoutButton.js index 4a15cd68..2b04a78a 100644 --- a/components/input/LogoutButton.js +++ b/src/components/input/LogoutButton.js @@ -1,11 +1,11 @@ import { Button, Icon, Icons, TooltipPopup } from 'react-basics'; import Link from 'next/link'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; export function LogoutButton({ tooltipPosition = 'top' }) { const { formatMessage, labels } = useMessages(); return ( - + + + + )} + + + ); +} + +export default WebsiteDateFilter; diff --git a/src/components/input/WebsiteDateFilter.module.css b/src/components/input/WebsiteDateFilter.module.css new file mode 100644 index 00000000..986f5c17 --- /dev/null +++ b/src/components/input/WebsiteDateFilter.module.css @@ -0,0 +1,14 @@ +.dropdown { + min-width: 200px; +} + +.buttons button:first-child { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.buttons button:last-child { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-left: 1px solid var(--base400) !important; +} diff --git a/components/input/WebsiteSelect.js b/src/components/input/WebsiteSelect.js similarity index 87% rename from components/input/WebsiteSelect.js rename to src/components/input/WebsiteSelect.js index ae3ceb46..1bdc4608 100644 --- a/components/input/WebsiteSelect.js +++ b/src/components/input/WebsiteSelect.js @@ -1,6 +1,6 @@ import { Dropdown, Item } from 'react-basics'; -import useApi from 'hooks/useApi'; -import useMessages from 'hooks/useMessages'; +import useApi from 'components/hooks/useApi'; +import useMessages from 'components/hooks/useMessages'; export function WebsiteSelect({ websiteId, onSelect }) { const { formatMessage, labels } = useMessages(); diff --git a/components/layout/AppLayout.js b/src/components/layout/AppLayout.js similarity index 87% rename from components/layout/AppLayout.js rename to src/components/layout/AppLayout.js index 7ab74351..41e2ec0d 100644 --- a/components/layout/AppLayout.js +++ b/src/components/layout/AppLayout.js @@ -2,14 +2,14 @@ import { Container } from 'react-basics'; import Head from 'next/head'; import NavBar from 'components/layout/NavBar'; import UpdateNotice from 'components/common/UpdateNotice'; -import { useRequireLogin, useConfig } from 'hooks'; +import { useRequireLogin, useConfig } from 'components/hooks'; import styles from './AppLayout.module.css'; export function AppLayout({ title, children }) { const { user } = useRequireLogin(); const config = useConfig(); - if (!user || !config) { + if (!user || !config || config?.uiDisabled) { return null; } diff --git a/components/layout/AppLayout.module.css b/src/components/layout/AppLayout.module.css similarity index 91% rename from components/layout/AppLayout.module.css rename to src/components/layout/AppLayout.module.css index be51f83c..bcce963f 100644 --- a/components/layout/AppLayout.module.css +++ b/src/components/layout/AppLayout.module.css @@ -10,7 +10,7 @@ width: 100vw; grid-column: 1; grid-row: 1 / 2; - z-index: 1; + z-index: var(--z-index-popup); } .body { diff --git a/components/layout/Footer.js b/src/components/layout/Footer.js similarity index 100% rename from components/layout/Footer.js rename to src/components/layout/Footer.js diff --git a/components/layout/Footer.module.css b/src/components/layout/Footer.module.css similarity index 100% rename from components/layout/Footer.module.css rename to src/components/layout/Footer.module.css diff --git a/components/layout/Grid.js b/src/components/layout/Grid.js similarity index 100% rename from components/layout/Grid.js rename to src/components/layout/Grid.js diff --git a/components/layout/Grid.module.css b/src/components/layout/Grid.module.css similarity index 100% rename from components/layout/Grid.module.css rename to src/components/layout/Grid.module.css diff --git a/components/layout/Header.js b/src/components/layout/Header.js similarity index 100% rename from components/layout/Header.js rename to src/components/layout/Header.js diff --git a/components/layout/Header.module.css b/src/components/layout/Header.module.css similarity index 100% rename from components/layout/Header.module.css rename to src/components/layout/Header.module.css diff --git a/components/layout/NavBar.js b/src/components/layout/NavBar.js similarity index 87% rename from components/layout/NavBar.js rename to src/components/layout/NavBar.js index e896b404..07627e2a 100644 --- a/components/layout/NavBar.js +++ b/src/components/layout/NavBar.js @@ -1,26 +1,24 @@ import { Icon, Text, Row, Column } from 'react-basics'; import Link from 'next/link'; +import { useRouter } from 'next/router'; import classNames from 'classnames'; import Icons from 'components/icons'; import ThemeButton from 'components/input/ThemeButton'; import LanguageButton from 'components/input/LanguageButton'; import ProfileButton from 'components/input/ProfileButton'; +import useMessages from 'components/hooks/useMessages'; +import HamburgerButton from 'components/common/HamburgerButton'; import styles from './NavBar.module.css'; -import useConfig from 'hooks/useConfig'; -import useMessages from 'hooks/useMessages'; -import { useRouter } from 'next/router'; -import HamburgerButton from '../common/HamburgerButton'; export function NavBar() { const { pathname } = useRouter(); - const { cloudMode } = useConfig(); const { formatMessage, labels } = useMessages(); const links = [ { label: formatMessage(labels.dashboard), url: '/dashboard' }, { label: formatMessage(labels.websites), url: '/websites' }, { label: formatMessage(labels.reports), url: '/reports' }, - !cloudMode && { label: formatMessage(labels.settings), url: '/settings' }, + { label: formatMessage(labels.settings), url: '/settings' }, ].filter(n => n); return ( diff --git a/components/layout/NavBar.module.css b/src/components/layout/NavBar.module.css similarity index 100% rename from components/layout/NavBar.module.css rename to src/components/layout/NavBar.module.css diff --git a/components/layout/NavGroup.js b/src/components/layout/NavGroup.js similarity index 100% rename from components/layout/NavGroup.js rename to src/components/layout/NavGroup.js diff --git a/components/layout/NavGroup.module.css b/src/components/layout/NavGroup.module.css similarity index 100% rename from components/layout/NavGroup.module.css rename to src/components/layout/NavGroup.module.css diff --git a/components/layout/Page.js b/src/components/layout/Page.js similarity index 90% rename from components/layout/Page.js rename to src/components/layout/Page.js index 30abde50..4f42aa55 100644 --- a/components/layout/Page.js +++ b/src/components/layout/Page.js @@ -1,6 +1,6 @@ import classNames from 'classnames'; import { Banner, Loading } from 'react-basics'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; import styles from './Page.module.css'; export function Page({ className, error, loading, children }) { diff --git a/components/layout/Page.module.css b/src/components/layout/Page.module.css similarity index 100% rename from components/layout/Page.module.css rename to src/components/layout/Page.module.css diff --git a/components/layout/PageHeader.js b/src/components/layout/PageHeader.js similarity index 100% rename from components/layout/PageHeader.js rename to src/components/layout/PageHeader.js diff --git a/components/layout/PageHeader.module.css b/src/components/layout/PageHeader.module.css similarity index 94% rename from components/layout/PageHeader.module.css rename to src/components/layout/PageHeader.module.css index b54ecfcf..8e615b93 100644 --- a/components/layout/PageHeader.module.css +++ b/src/components/layout/PageHeader.module.css @@ -37,10 +37,6 @@ margin-bottom: 10px; } - .title { - font-size: 18px; - } - .actions { flex-basis: 100%; order: -1; diff --git a/components/layout/ReportsLayout.js b/src/components/layout/ReportsLayout.js similarity index 83% rename from components/layout/ReportsLayout.js rename to src/components/layout/ReportsLayout.js index fd63a67e..374da263 100644 --- a/components/layout/ReportsLayout.js +++ b/src/components/layout/ReportsLayout.js @@ -1,7 +1,7 @@ import { Column, Row } from 'react-basics'; import styles from './ReportsLayout.module.css'; -export function SettingsLayout({ children, filter, header }) { +export function ReportsLayout({ children, filter, header }) { return ( <> {header} @@ -20,4 +20,4 @@ export function SettingsLayout({ children, filter, header }) { ); } -export default SettingsLayout; +export default ReportsLayout; diff --git a/components/layout/ReportsLayout.module.css b/src/components/layout/ReportsLayout.module.css similarity index 100% rename from components/layout/ReportsLayout.module.css rename to src/components/layout/ReportsLayout.module.css diff --git a/components/layout/SettingsLayout.js b/src/components/layout/SettingsLayout.js similarity index 88% rename from components/layout/SettingsLayout.js rename to src/components/layout/SettingsLayout.js index c79f0909..0f4aa5d9 100644 --- a/components/layout/SettingsLayout.js +++ b/src/components/layout/SettingsLayout.js @@ -1,16 +1,15 @@ import { Row, Column } from 'react-basics'; import { useRouter } from 'next/router'; import SideNav from './SideNav'; -import useUser from 'hooks/useUser'; -import useMessages from 'hooks/useMessages'; -import useConfig from 'hooks/useConfig'; +import useUser from 'components/hooks/useUser'; +import useMessages from 'components/hooks/useMessages'; import styles from './SettingsLayout.module.css'; export function SettingsLayout({ children }) { const { user } = useUser(); const { pathname } = useRouter(); const { formatMessage, labels } = useMessages(); - const { cloudMode } = useConfig(); + const cloudMode = Boolean(process.env.cloudMode); const items = [ { key: 'websites', label: formatMessage(labels.websites), url: '/settings/websites' }, diff --git a/components/layout/SettingsLayout.module.css b/src/components/layout/SettingsLayout.module.css similarity index 84% rename from components/layout/SettingsLayout.module.css rename to src/components/layout/SettingsLayout.module.css index 36d029f0..08ff02aa 100644 --- a/components/layout/SettingsLayout.module.css +++ b/src/components/layout/SettingsLayout.module.css @@ -13,4 +13,8 @@ .menu { display: none; } + + .content { + margin-top: 20px; + } } diff --git a/components/layout/ShareLayout.js b/src/components/layout/ShareLayout.js similarity index 100% rename from components/layout/ShareLayout.js rename to src/components/layout/ShareLayout.js diff --git a/components/layout/SideNav.js b/src/components/layout/SideNav.js similarity index 77% rename from components/layout/SideNav.js rename to src/components/layout/SideNav.js index e7e96b7d..ccb6f360 100644 --- a/components/layout/SideNav.js +++ b/src/components/layout/SideNav.js @@ -4,7 +4,13 @@ import { useRouter } from 'next/router'; import Link from 'next/link'; import styles from './SideNav.module.css'; -export function SideNav({ selectedKey, items, shallow, onSelect = () => {} }) { +export function SideNav({ + selectedKey, + items, + shallow = true, + scroll = false, + onSelect = () => {}, +}) { const { asPath } = useRouter(); return ( @@ -13,7 +19,7 @@ export function SideNav({ selectedKey, items, shallow, onSelect = () => {} }) { key={key} className={classNames(styles.item, { [styles.selected]: asPath.startsWith(url) })} > - + {label} diff --git a/components/layout/SideNav.module.css b/src/components/layout/SideNav.module.css similarity index 100% rename from components/layout/SideNav.module.css rename to src/components/layout/SideNav.module.css diff --git a/components/messages.js b/src/components/messages.js similarity index 99% rename from components/messages.js rename to src/components/messages.js index ff619945..f52ed5c5 100644 --- a/components/messages.js +++ b/src/components/messages.js @@ -140,6 +140,7 @@ export const labels = defineMessages({ description: { id: 'label.description', defaultMessage: 'Description' }, untitled: { id: 'label.untitled', defaultMessage: 'Untitled' }, type: { id: 'label.type', defaultMessage: 'Type' }, + filter: { id: 'label.filter', defaultMessage: 'Filter' }, filters: { id: 'label.filters', defaultMessage: 'Filters' }, breakdown: { id: 'label.breakdown', defaultMessage: 'Breakdown' }, true: { id: 'label.true', defaultMessage: 'True' }, diff --git a/components/metrics/ActiveUsers.js b/src/components/metrics/ActiveUsers.js similarity index 89% rename from components/metrics/ActiveUsers.js rename to src/components/metrics/ActiveUsers.js index 64051946..3074d0df 100644 --- a/components/metrics/ActiveUsers.js +++ b/src/components/metrics/ActiveUsers.js @@ -1,7 +1,7 @@ import { useMemo } from 'react'; import { StatusLight } from 'react-basics'; -import useApi from 'hooks/useApi'; -import useMessages from 'hooks/useMessages'; +import useApi from 'components/hooks/useApi'; +import useMessages from 'components/hooks/useMessages'; import styles from './ActiveUsers.module.css'; export function ActiveUsers({ websiteId, value, refetchInterval = 60000 }) { diff --git a/components/metrics/ActiveUsers.module.css b/src/components/metrics/ActiveUsers.module.css similarity index 100% rename from components/metrics/ActiveUsers.module.css rename to src/components/metrics/ActiveUsers.module.css diff --git a/components/metrics/BarChart.js b/src/components/metrics/BarChart.js similarity index 97% rename from components/metrics/BarChart.js rename to src/components/metrics/BarChart.js index c086017e..7b94c147 100644 --- a/components/metrics/BarChart.js +++ b/src/components/metrics/BarChart.js @@ -4,8 +4,8 @@ import classNames from 'classnames'; import Chart from 'chart.js/auto'; import HoverTooltip from 'components/common/HoverTooltip'; import Legend from 'components/metrics/Legend'; -import useLocale from 'hooks/useLocale'; -import useTheme from 'hooks/useTheme'; +import useLocale from 'components/hooks/useLocale'; +import useTheme from 'components/hooks/useTheme'; import { DEFAULT_ANIMATION_DURATION } from 'lib/constants'; import { renderNumberLabels } from 'lib/charts'; import styles from './BarChart.module.css'; diff --git a/components/metrics/BarChart.module.css b/src/components/metrics/BarChart.module.css similarity index 100% rename from components/metrics/BarChart.module.css rename to src/components/metrics/BarChart.module.css diff --git a/components/metrics/BrowsersTable.js b/src/components/metrics/BrowsersTable.js similarity index 89% rename from components/metrics/BrowsersTable.js rename to src/components/metrics/BrowsersTable.js index bf4d0aaa..e68b159f 100644 --- a/components/metrics/BrowsersTable.js +++ b/src/components/metrics/BrowsersTable.js @@ -1,8 +1,8 @@ import { useRouter } from 'next/router'; import FilterLink from 'components/common/FilterLink'; import MetricsTable from 'components/metrics/MetricsTable'; -import useMessages from 'hooks/useMessages'; -import useFormat from 'hooks/useFormat'; +import useMessages from 'components/hooks/useMessages'; +import useFormat from 'components/hooks/useFormat'; export function BrowsersTable({ websiteId, ...props }) { const { formatMessage, labels } = useMessages(); diff --git a/src/components/metrics/CitiesTable.js b/src/components/metrics/CitiesTable.js new file mode 100644 index 00000000..ee11e676 --- /dev/null +++ b/src/components/metrics/CitiesTable.js @@ -0,0 +1,46 @@ +import { useRouter } from 'next/router'; +import MetricsTable from './MetricsTable'; +import { emptyFilter } from 'lib/filters'; +import FilterLink from 'components/common/FilterLink'; +import useLocale from 'components/hooks/useLocale'; +import useMessages from 'components/hooks/useMessages'; +import useCountryNames from 'components/hooks/useCountryNames'; + +export function CitiesTable({ websiteId, ...props }) { + const { locale } = useLocale(); + const { formatMessage, labels } = useMessages(); + const { basePath } = useRouter(); + const countryNames = useCountryNames(locale); + + const renderLabel = (city, country) => { + const name = countryNames[country]; + return name ? `${city}, ${name}` : city; + }; + + const renderLink = ({ x: city, country }) => { + return ( + + {country && ( + {country} + )} + + ); + }; + + return ( + + ); +} + +export default CitiesTable; diff --git a/components/metrics/CountriesTable.js b/src/components/metrics/CountriesTable.js similarity index 88% rename from components/metrics/CountriesTable.js rename to src/components/metrics/CountriesTable.js index 283fdaf2..ec7ab73d 100644 --- a/components/metrics/CountriesTable.js +++ b/src/components/metrics/CountriesTable.js @@ -1,7 +1,7 @@ import { useRouter } from 'next/router'; import FilterLink from 'components/common/FilterLink'; -import useCountryNames from 'hooks/useCountryNames'; -import { useLocale, useMessages, useFormat } from 'hooks'; +import useCountryNames from 'components/hooks/useCountryNames'; +import { useLocale, useMessages, useFormat } from 'components/hooks'; import MetricsTable from './MetricsTable'; export function CountriesTable({ websiteId, ...props }) { diff --git a/components/metrics/DataTable.module.css b/src/components/metrics/DataTable.module.css similarity index 100% rename from components/metrics/DataTable.module.css rename to src/components/metrics/DataTable.module.css diff --git a/components/metrics/DatePickerForm.js b/src/components/metrics/DatePickerForm.js similarity index 96% rename from components/metrics/DatePickerForm.js rename to src/components/metrics/DatePickerForm.js index 53f027bb..fa45b64a 100644 --- a/components/metrics/DatePickerForm.js +++ b/src/components/metrics/DatePickerForm.js @@ -1,10 +1,10 @@ import { useState } from 'react'; import { Button, ButtonGroup, Calendar } from 'react-basics'; import { isAfter, isBefore, isSameDay } from 'date-fns'; -import useLocale from 'hooks/useLocale'; +import useLocale from 'components/hooks/useLocale'; import { getDateLocale } from 'lib/lang'; import { FILTER_DAY, FILTER_RANGE } from 'lib/constants'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; import styles from './DatePickerForm.module.css'; export function DatePickerForm({ diff --git a/components/metrics/DatePickerForm.module.css b/src/components/metrics/DatePickerForm.module.css similarity index 100% rename from components/metrics/DatePickerForm.module.css rename to src/components/metrics/DatePickerForm.module.css diff --git a/components/metrics/DevicesTable.js b/src/components/metrics/DevicesTable.js similarity index 90% rename from components/metrics/DevicesTable.js rename to src/components/metrics/DevicesTable.js index 98690d0a..7b591552 100644 --- a/components/metrics/DevicesTable.js +++ b/src/components/metrics/DevicesTable.js @@ -1,8 +1,8 @@ import MetricsTable from './MetricsTable'; import FilterLink from 'components/common/FilterLink'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; import { useRouter } from 'next/router'; -import { useFormat } from 'hooks'; +import { useFormat } from 'components/hooks'; export function DevicesTable({ websiteId, ...props }) { const { formatMessage, labels } = useMessages(); diff --git a/components/metrics/EventsChart.js b/src/components/metrics/EventsChart.js similarity index 98% rename from components/metrics/EventsChart.js rename to src/components/metrics/EventsChart.js index 82b8c8f7..f1964c15 100644 --- a/components/metrics/EventsChart.js +++ b/src/components/metrics/EventsChart.js @@ -3,7 +3,7 @@ import { Loading } from 'react-basics'; import { colord } from 'colord'; import BarChart from './BarChart'; import { getDateArray } from 'lib/date'; -import { useApi, useLocale, useDateRange, useTimezone, usePageQuery } from 'hooks'; +import { useApi, useLocale, useDateRange, useTimezone, usePageQuery } from 'components/hooks'; import { EVENT_COLORS } from 'lib/constants'; import { renderDateLabels, renderStatusTooltipPopup } from 'lib/charts'; diff --git a/components/metrics/EventsChart.module.css b/src/components/metrics/EventsChart.module.css similarity index 100% rename from components/metrics/EventsChart.module.css rename to src/components/metrics/EventsChart.module.css diff --git a/components/metrics/EventsTable.js b/src/components/metrics/EventsTable.js similarity index 89% rename from components/metrics/EventsTable.js rename to src/components/metrics/EventsTable.js index eb23a281..a8ae82aa 100644 --- a/components/metrics/EventsTable.js +++ b/src/components/metrics/EventsTable.js @@ -1,5 +1,5 @@ import MetricsTable from './MetricsTable'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; export function EventsTable({ websiteId, ...props }) { const { formatMessage, labels } = useMessages(); diff --git a/components/metrics/FilterTags.js b/src/components/metrics/FilterTags.js similarity index 87% rename from components/metrics/FilterTags.js rename to src/components/metrics/FilterTags.js index ad3ff60b..cb88a7db 100644 --- a/components/metrics/FilterTags.js +++ b/src/components/metrics/FilterTags.js @@ -1,8 +1,8 @@ import { safeDecodeURI } from 'next-basics'; import { Button, Icon, Icons, Text } from 'react-basics'; -import usePageQuery from 'hooks/usePageQuery'; +import usePageQuery from 'components/hooks/usePageQuery'; +import useMessages from 'components/hooks/useMessages'; import styles from './FilterTags.module.css'; -import useMessages from 'hooks/useMessages'; export function FilterTags({ params }) { const { formatMessage, labels } = useMessages(); @@ -26,6 +26,7 @@ export function FilterTags({ params }) { return (
+
{formatMessage(labels.filters)}
{Object.keys(params).map(key => { if (!params[key]) { return null; diff --git a/components/metrics/FilterTags.module.css b/src/components/metrics/FilterTags.module.css similarity index 63% rename from components/metrics/FilterTags.module.css rename to src/components/metrics/FilterTags.module.css index 1c8458ac..c228dc4e 100644 --- a/components/metrics/FilterTags.module.css +++ b/src/components/metrics/FilterTags.module.css @@ -4,19 +4,23 @@ gap: 10px; } +.label { + font-weight: 700; +} + .tag { display: flex; flex-direction: row; align-items: center; gap: 10px; font-size: var(--font-size-sm); - border: 1px solid var(--base600); + border: 1px solid var(--blue400); border-radius: var(--border-radius); - line-height: 30px; - padding: 0 8px; + padding: 8px 16px; cursor: pointer; + background: var(--blue100); } .tag:hover { - background: var(--base75); + background: var(--blue200); } diff --git a/components/metrics/LanguagesTable.js b/src/components/metrics/LanguagesTable.js similarity index 81% rename from components/metrics/LanguagesTable.js rename to src/components/metrics/LanguagesTable.js index e90a3425..7d220829 100644 --- a/components/metrics/LanguagesTable.js +++ b/src/components/metrics/LanguagesTable.js @@ -1,8 +1,8 @@ import MetricsTable from './MetricsTable'; import { percentFilter } from 'lib/filters'; -import useLanguageNames from 'hooks/useLanguageNames'; -import useLocale from 'hooks/useLocale'; -import useMessages from 'hooks/useMessages'; +import useLanguageNames from 'components/hooks/useLanguageNames'; +import useLocale from 'components/hooks/useLocale'; +import useMessages from 'components/hooks/useMessages'; export function LanguagesTable({ websiteId, onDataLoad, ...props }) { const { formatMessage, labels } = useMessages(); diff --git a/components/metrics/Legend.js b/src/components/metrics/Legend.js similarity index 89% rename from components/metrics/Legend.js rename to src/components/metrics/Legend.js index 91135acb..ce8a3381 100644 --- a/components/metrics/Legend.js +++ b/src/components/metrics/Legend.js @@ -2,8 +2,8 @@ import { useEffect } from 'react'; import { StatusLight } from 'react-basics'; import { colord } from 'colord'; import classNames from 'classnames'; -import useLocale from 'hooks/useLocale'; -import useForceUpdate from 'hooks/useForceUpdate'; +import useLocale from 'components/hooks/useLocale'; +import useForceUpdate from 'components/hooks/useForceUpdate'; import styles from './Legend.module.css'; export function Legend({ chart }) { @@ -22,7 +22,7 @@ export function Legend({ chart }) { useEffect(() => { forceUpdate(); - }, [locale]); + }, [locale, forceUpdate]); if (!chart?.legend?.legendItems.find(({ text }) => text)) { return null; diff --git a/components/metrics/Legend.module.css b/src/components/metrics/Legend.module.css similarity index 100% rename from components/metrics/Legend.module.css rename to src/components/metrics/Legend.module.css diff --git a/components/metrics/DataTable.js b/src/components/metrics/ListTable.js similarity index 94% rename from components/metrics/DataTable.js rename to src/components/metrics/ListTable.js index e2e9462d..33ca59eb 100644 --- a/components/metrics/DataTable.js +++ b/src/components/metrics/ListTable.js @@ -5,10 +5,10 @@ import { useSpring, animated, config } from 'react-spring'; import classNames from 'classnames'; import Empty from 'components/common/Empty'; import { formatNumber, formatLongNumber } from 'lib/format'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; import styles from './DataTable.module.css'; -export function DataTable({ +export function ListTable({ data = [], title, metric, @@ -94,7 +94,7 @@ const AnimatedRow = ({
`${n}%`) }} /> - {props.width.to(n => `${n.toFixed(0)}%`)} + {props.width.to(n => `${n?.toFixed?.(0)}%`)}
)} @@ -102,4 +102,4 @@ const AnimatedRow = ({ ); }; -export default DataTable; +export default ListTable; diff --git a/components/metrics/MetricCard.js b/src/components/metrics/MetricCard.js similarity index 100% rename from components/metrics/MetricCard.js rename to src/components/metrics/MetricCard.js diff --git a/components/metrics/MetricCard.module.css b/src/components/metrics/MetricCard.module.css similarity index 100% rename from components/metrics/MetricCard.module.css rename to src/components/metrics/MetricCard.module.css diff --git a/components/metrics/MetricsBar.js b/src/components/metrics/MetricsBar.js similarity index 100% rename from components/metrics/MetricsBar.js rename to src/components/metrics/MetricsBar.js diff --git a/components/metrics/MetricsBar.module.css b/src/components/metrics/MetricsBar.module.css similarity index 100% rename from components/metrics/MetricsBar.module.css rename to src/components/metrics/MetricsBar.module.css diff --git a/components/metrics/MetricsTable.js b/src/components/metrics/MetricsTable.js similarity index 80% rename from components/metrics/MetricsTable.js rename to src/components/metrics/MetricsTable.js index 50262798..6521c415 100644 --- a/components/metrics/MetricsTable.js +++ b/src/components/metrics/MetricsTable.js @@ -3,17 +3,17 @@ import { Loading, Icon, Text, Button } from 'react-basics'; import Link from 'next/link'; import firstBy from 'thenby'; import classNames from 'classnames'; -import useApi from 'hooks/useApi'; +import useApi from 'components/hooks/useApi'; import { percentFilter } from 'lib/filters'; -import useDateRange from 'hooks/useDateRange'; -import usePageQuery from 'hooks/usePageQuery'; +import useDateRange from 'components/hooks/useDateRange'; +import usePageQuery from 'components/hooks/usePageQuery'; import ErrorMessage from 'components/common/ErrorMessage'; -import DataTable from './DataTable'; +import ListTable from './ListTable'; import { DEFAULT_ANIMATION_DURATION } from 'lib/constants'; import Icons from 'components/icons'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; import styles from './MetricsTable.module.css'; -import useLocale from 'hooks/useLocale'; +import useLocale from 'components/hooks/useLocale'; export function MetricsTable({ websiteId, @@ -34,6 +34,7 @@ export function MetricsTable({ } = usePageQuery(); const { formatMessage, labels } = useMessages(); const { get, useQuery } = useApi(); + const { dir } = useLocale(); const { data, isLoading, isFetched, error } = useQuery( [ @@ -53,21 +54,18 @@ export function MetricsTable({ city, }, ], - () => - get(`/websites/${websiteId}/metrics`, { + () => { + const filters = { url, title, referrer, os, browser, device, country, region, city }; + + filters[type] = undefined; + + return get(`/websites/${websiteId}/metrics`, { type, startAt: +startDate, endAt: +endDate, - url, - title, - referrer, - os, - browser, - device, - country, - region, - city, - }), + ...filters, + }); + }, { onSuccess: onDataLoad, retryDelay: delay || DEFAULT_ANIMATION_DURATION }, ); @@ -97,14 +95,13 @@ export function MetricsTable({ return items.sort(firstBy('y', -1).thenBy('x')); } return []; - }, [data, error, dataFilter, filterOptions]); - const { dir } = useLocale(); + }, [data, error, dataFilter, filterOptions, limit]); return (
{!data && isLoading && !isFetched && } {error && } - {data && !error && } + {data && !error && }
{data && !error && limit && ( diff --git a/components/metrics/MetricsTable.module.css b/src/components/metrics/MetricsTable.module.css similarity index 100% rename from components/metrics/MetricsTable.module.css rename to src/components/metrics/MetricsTable.module.css diff --git a/components/metrics/OSTable.js b/src/components/metrics/OSTable.js similarity index 93% rename from components/metrics/OSTable.js rename to src/components/metrics/OSTable.js index e4bc3234..90135124 100644 --- a/components/metrics/OSTable.js +++ b/src/components/metrics/OSTable.js @@ -1,6 +1,6 @@ import MetricsTable from './MetricsTable'; import FilterLink from 'components/common/FilterLink'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; import { useRouter } from 'next/router'; export function OSTable({ websiteId, ...props }) { diff --git a/components/metrics/PagesTable.js b/src/components/metrics/PagesTable.js similarity index 91% rename from components/metrics/PagesTable.js rename to src/components/metrics/PagesTable.js index 47e70318..f310643e 100644 --- a/components/metrics/PagesTable.js +++ b/src/components/metrics/PagesTable.js @@ -1,8 +1,8 @@ import FilterLink from 'components/common/FilterLink'; import FilterButtons from 'components/common/FilterButtons'; import MetricsTable from './MetricsTable'; -import useMessages from 'hooks/useMessages'; -import usePageQuery from 'hooks/usePageQuery'; +import useMessages from 'components/hooks/useMessages'; +import usePageQuery from 'components/hooks/usePageQuery'; import { emptyFilter } from 'lib/filters'; export function PagesTable({ websiteId, showFilters, ...props }) { diff --git a/components/metrics/PageviewsChart.js b/src/components/metrics/PageviewsChart.js similarity index 90% rename from components/metrics/PageviewsChart.js rename to src/components/metrics/PageviewsChart.js index 1b481c48..096278f3 100644 --- a/components/metrics/PageviewsChart.js +++ b/src/components/metrics/PageviewsChart.js @@ -1,6 +1,6 @@ import { useMemo } from 'react'; import BarChart from './BarChart'; -import { useLocale, useTheme, useMessages } from 'hooks'; +import { useLocale, useTheme, useMessages } from 'components/hooks'; import { renderDateLabels, renderStatusTooltipPopup } from 'lib/charts'; export function PageviewsChart({ websiteId, data, unit, loading, ...props }) { @@ -25,7 +25,7 @@ export function PageviewsChart({ websiteId, data, unit, loading, ...props }) { ...colors.chart.views, }, ]; - }, [data, locale, colors]); + }, [data, colors, formatMessage, labels]); return ( { - return regions[x] ? `${regions[x]}, ${countryNames[x.split('-')[0]]}` : x; + const renderLabel = (code, country) => { + const region = code.includes('-') ? code : `${country}-${code}`; + return regions[region] ? `${regions[region]}, ${countryNames[country]}` : region; }; - const renderLink = ({ x: code }) => { + const renderLink = ({ x: code, country }) => { return ( - - {code} + + {code} ); }; diff --git a/components/metrics/ScreenTable.js b/src/components/metrics/ScreenTable.js similarity index 87% rename from components/metrics/ScreenTable.js rename to src/components/metrics/ScreenTable.js index f8ef5f2e..8ed5c05a 100644 --- a/components/metrics/ScreenTable.js +++ b/src/components/metrics/ScreenTable.js @@ -1,5 +1,5 @@ import MetricsTable from './MetricsTable'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; export function ScreenTable({ websiteId, ...props }) { const { formatMessage, labels } = useMessages(); diff --git a/components/pages/console/TestConsole.js b/src/components/pages/console/TestConsole.js similarity index 99% rename from components/pages/console/TestConsole.js rename to src/components/pages/console/TestConsole.js index 060314fd..71eb27b4 100644 --- a/components/pages/console/TestConsole.js +++ b/src/components/pages/console/TestConsole.js @@ -3,7 +3,7 @@ import Page from 'components/layout/Page'; import PageHeader from 'components/layout/PageHeader'; import EventsChart from 'components/metrics/EventsChart'; import WebsiteChart from 'components/pages/websites/WebsiteChart'; -import useApi from 'hooks/useApi'; +import useApi from 'components/hooks/useApi'; import Head from 'next/head'; import Link from 'next/link'; import { useRouter } from 'next/router'; diff --git a/components/pages/console/TestConsole.module.css b/src/components/pages/console/TestConsole.module.css similarity index 100% rename from components/pages/console/TestConsole.module.css rename to src/components/pages/console/TestConsole.module.css diff --git a/components/pages/dashboard/Dashboard.js b/src/components/pages/dashboard/Dashboard.js similarity index 93% rename from components/pages/dashboard/Dashboard.js rename to src/components/pages/dashboard/Dashboard.js index 601e741b..8248cc81 100644 --- a/components/pages/dashboard/Dashboard.js +++ b/src/components/pages/dashboard/Dashboard.js @@ -7,10 +7,10 @@ import WebsiteChartList from 'components/pages/websites/WebsiteChartList'; import DashboardSettingsButton from 'components/pages/dashboard/DashboardSettingsButton'; import DashboardEdit from 'components/pages/dashboard/DashboardEdit'; import EmptyPlaceholder from 'components/common/EmptyPlaceholder'; -import useApi from 'hooks/useApi'; +import useApi from 'components/hooks/useApi'; import useDashboard from 'store/dashboard'; -import useMessages from 'hooks/useMessages'; -import useLocale from 'hooks/useLocale'; +import useMessages from 'components/hooks/useMessages'; +import useLocale from 'components/hooks/useLocale'; export function Dashboard() { const { formatMessage, labels, messages } = useMessages(); diff --git a/components/pages/dashboard/DashboardEdit.js b/src/components/pages/dashboard/DashboardEdit.js similarity index 98% rename from components/pages/dashboard/DashboardEdit.js rename to src/components/pages/dashboard/DashboardEdit.js index 1810ee9b..4eb259d6 100644 --- a/components/pages/dashboard/DashboardEdit.js +++ b/src/components/pages/dashboard/DashboardEdit.js @@ -4,7 +4,7 @@ import classNames from 'classnames'; import { Button } from 'react-basics'; import { firstBy } from 'thenby'; import useDashboard, { saveDashboard } from 'store/dashboard'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; import styles from './DashboardEdit.module.css'; const dragId = 'dashboard-website-ordering'; diff --git a/components/pages/dashboard/DashboardEdit.module.css b/src/components/pages/dashboard/DashboardEdit.module.css similarity index 100% rename from components/pages/dashboard/DashboardEdit.module.css rename to src/components/pages/dashboard/DashboardEdit.module.css diff --git a/components/pages/dashboard/DashboardSettingsButton.js b/src/components/pages/dashboard/DashboardSettingsButton.js similarity index 86% rename from components/pages/dashboard/DashboardSettingsButton.js rename to src/components/pages/dashboard/DashboardSettingsButton.js index 99dab14f..eeb1f947 100644 --- a/components/pages/dashboard/DashboardSettingsButton.js +++ b/src/components/pages/dashboard/DashboardSettingsButton.js @@ -1,7 +1,7 @@ -import { TooltipPopup, Icon, Text, Flexbox, Popup, Item, Button } from 'react-basics'; +import { TooltipPopup, Icon, Text, Flexbox, Button } from 'react-basics'; import Icons from 'components/icons'; import { saveDashboard } from 'store/dashboard'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; export function DashboardSettingsButton() { const { formatMessage, labels } = useMessages(); diff --git a/components/pages/dashboard/DashboardSettingsButton.module.css b/src/components/pages/dashboard/DashboardSettingsButton.module.css similarity index 100% rename from components/pages/dashboard/DashboardSettingsButton.module.css rename to src/components/pages/dashboard/DashboardSettingsButton.module.css diff --git a/components/pages/event-data/EventDataMetricsBar.js b/src/components/pages/event-data/EventDataMetricsBar.js similarity index 94% rename from components/pages/event-data/EventDataMetricsBar.js rename to src/components/pages/event-data/EventDataMetricsBar.js index 90f065d5..5d984bea 100644 --- a/components/pages/event-data/EventDataMetricsBar.js +++ b/src/components/pages/event-data/EventDataMetricsBar.js @@ -1,7 +1,7 @@ import { Column, Row } from 'react-basics'; -import { useApi, useDateRange } from 'hooks'; +import { useApi, useDateRange } from 'components/hooks'; import MetricCard from 'components/metrics/MetricCard'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; import WebsiteDateFilter from 'components/input/WebsiteDateFilter'; import MetricsBar from 'components/metrics/MetricsBar'; import styles from './EventDataMetricsBar.module.css'; diff --git a/components/pages/event-data/EventDataMetricsBar.module.css b/src/components/pages/event-data/EventDataMetricsBar.module.css similarity index 100% rename from components/pages/event-data/EventDataMetricsBar.module.css rename to src/components/pages/event-data/EventDataMetricsBar.module.css diff --git a/components/pages/event-data/EventDataTable.js b/src/components/pages/event-data/EventDataTable.js similarity index 94% rename from components/pages/event-data/EventDataTable.js rename to src/components/pages/event-data/EventDataTable.js index b0d11b9d..c79916ce 100644 --- a/components/pages/event-data/EventDataTable.js +++ b/src/components/pages/event-data/EventDataTable.js @@ -1,6 +1,6 @@ import Link from 'next/link'; import { GridTable, GridColumn } from 'react-basics'; -import { useMessages, usePageQuery } from 'hooks'; +import { useMessages, usePageQuery } from 'components/hooks'; import Empty from 'components/common/Empty'; import { DATA_TYPES } from 'lib/constants'; diff --git a/components/pages/event-data/EventDataValueTable.js b/src/components/pages/event-data/EventDataValueTable.js similarity index 96% rename from components/pages/event-data/EventDataValueTable.js rename to src/components/pages/event-data/EventDataValueTable.js index 69ed10a7..75c11e32 100644 --- a/components/pages/event-data/EventDataValueTable.js +++ b/src/components/pages/event-data/EventDataValueTable.js @@ -1,5 +1,5 @@ import { GridTable, GridColumn, Button, Icon, Text } from 'react-basics'; -import { useMessages, usePageQuery } from 'hooks'; +import { useMessages, usePageQuery } from 'components/hooks'; import Link from 'next/link'; import Icons from 'components/icons'; import PageHeader from 'components/layout/PageHeader'; diff --git a/components/pages/login/LoginForm.js b/src/components/pages/login/LoginForm.js similarity index 94% rename from components/pages/login/LoginForm.js rename to src/components/pages/login/LoginForm.js index 67057759..797eea14 100644 --- a/components/pages/login/LoginForm.js +++ b/src/components/pages/login/LoginForm.js @@ -10,10 +10,10 @@ import { Icon, } from 'react-basics'; import { useRouter } from 'next/router'; -import useApi from 'hooks/useApi'; +import useApi from 'components/hooks/useApi'; import { setUser } from 'store/app'; import { setClientAuthToken } from 'lib/client'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; import Logo from 'assets/logo.svg'; import styles from './LoginForm.module.css'; diff --git a/components/pages/login/LoginForm.module.css b/src/components/pages/login/LoginForm.module.css similarity index 100% rename from components/pages/login/LoginForm.module.css rename to src/components/pages/login/LoginForm.module.css diff --git a/components/pages/login/LoginLayout.js b/src/components/pages/login/LoginLayout.js similarity index 86% rename from components/pages/login/LoginLayout.js rename to src/components/pages/login/LoginLayout.js index fc8ad461..d1bf47bb 100644 --- a/components/pages/login/LoginLayout.js +++ b/src/components/pages/login/LoginLayout.js @@ -1,5 +1,5 @@ import Head from 'next/head'; -import useLocale from 'hooks/useLocale'; +import useLocale from 'components/hooks/useLocale'; import styles from './LoginLayout.module.css'; export function LoginLayout({ children }) { diff --git a/components/pages/login/LoginLayout.module.css b/src/components/pages/login/LoginLayout.module.css similarity index 100% rename from components/pages/login/LoginLayout.module.css rename to src/components/pages/login/LoginLayout.module.css diff --git a/components/pages/realtime/RealtimeCountries.js b/src/components/pages/realtime/RealtimeCountries.js similarity index 78% rename from components/pages/realtime/RealtimeCountries.js rename to src/components/pages/realtime/RealtimeCountries.js index 62964eab..7a61651a 100644 --- a/components/pages/realtime/RealtimeCountries.js +++ b/src/components/pages/realtime/RealtimeCountries.js @@ -1,9 +1,9 @@ import { useCallback } from 'react'; import { useRouter } from 'next/router'; -import DataTable from 'components/metrics/DataTable'; -import useLocale from 'hooks/useLocale'; -import useCountryNames from 'hooks/useCountryNames'; -import useMessages from 'hooks/useMessages'; +import ListTable from 'components/metrics/ListTable'; +import useLocale from 'components/hooks/useLocale'; +import useCountryNames from 'components/hooks/useCountryNames'; +import useMessages from 'components/hooks/useMessages'; import classNames from 'classnames'; import styles from './RealtimeCountries.module.css'; @@ -24,7 +24,7 @@ export function RealtimeCountries({ data }) { ); return ( - __id); diff --git a/components/pages/realtime/RealtimePage.module.css b/src/components/pages/realtime/RealtimePage.module.css similarity index 100% rename from components/pages/realtime/RealtimePage.module.css rename to src/components/pages/realtime/RealtimePage.module.css diff --git a/components/pages/realtime/RealtimeUrls.js b/src/components/pages/realtime/RealtimeUrls.js similarity index 94% rename from components/pages/realtime/RealtimeUrls.js rename to src/components/pages/realtime/RealtimeUrls.js index 18d8f2f6..674858b2 100644 --- a/components/pages/realtime/RealtimeUrls.js +++ b/src/components/pages/realtime/RealtimeUrls.js @@ -2,9 +2,9 @@ import { useMemo, useState } from 'react'; import { ButtonGroup, Button, Flexbox } from 'react-basics'; import firstBy from 'thenby'; import { percentFilter } from 'lib/filters'; -import DataTable from 'components/metrics/DataTable'; +import ListTable from 'components/metrics/ListTable'; import { FILTER_PAGES, FILTER_REFERRERS } from 'lib/constants'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; export function RealtimeUrls({ websiteDomain, data = {} }) { const { formatMessage, labels } = useMessages(); @@ -82,7 +82,7 @@ export function RealtimeUrls({ websiteDomain, data = {} }) { {filter === FILTER_REFERRERS && ( - )} {filter === FILTER_PAGES && ( - - - {({ value, label }) => { - return {label}; - }} - + {allowFilterSelect && ( + + {({ value, label }) => { + return {label}; + }} + + )} {value => { return {formatValue(value, name)}; diff --git a/components/pages/reports/FieldFilterForm.module.css b/src/components/pages/reports/FieldFilterForm.module.css similarity index 100% rename from components/pages/reports/FieldFilterForm.module.css rename to src/components/pages/reports/FieldFilterForm.module.css diff --git a/components/pages/reports/FieldSelectForm.js b/src/components/pages/reports/FieldSelectForm.js similarity index 94% rename from components/pages/reports/FieldSelectForm.js rename to src/components/pages/reports/FieldSelectForm.js index 434a5ae7..e7661511 100644 --- a/components/pages/reports/FieldSelectForm.js +++ b/src/components/pages/reports/FieldSelectForm.js @@ -1,5 +1,5 @@ import { Menu, Item, Form, FormRow } from 'react-basics'; -import { useMessages } from 'hooks'; +import { useMessages } from 'components/hooks'; import styles from './FieldSelectForm.module.css'; export default function FieldSelectForm({ items, onSelect, showType = true }) { diff --git a/components/pages/reports/FieldSelectForm.module.css b/src/components/pages/reports/FieldSelectForm.module.css similarity index 100% rename from components/pages/reports/FieldSelectForm.module.css rename to src/components/pages/reports/FieldSelectForm.module.css diff --git a/components/pages/reports/FilterSelectForm.js b/src/components/pages/reports/FilterSelectForm.js similarity index 89% rename from components/pages/reports/FilterSelectForm.js rename to src/components/pages/reports/FilterSelectForm.js index 38094bca..5265c741 100644 --- a/components/pages/reports/FilterSelectForm.js +++ b/src/components/pages/reports/FilterSelectForm.js @@ -1,7 +1,7 @@ import { useState } from 'react'; import FieldSelectForm from './FieldSelectForm'; import FieldFilterForm from './FieldFilterForm'; -import { useApi } from 'hooks'; +import { useApi } from 'components/hooks'; import { Loading } from 'react-basics'; function useValues(websiteId, type) { @@ -18,7 +18,7 @@ function useValues(websiteId, type) { return { data, error, isLoading }; } -export default function FilterSelectForm({ websiteId, items, onSelect }) { +export default function FilterSelectForm({ websiteId, items, onSelect, allowFilterSelect }) { const [field, setField] = useState(); const { data, isLoading } = useValues(websiteId, field?.name); @@ -37,6 +37,7 @@ export default function FilterSelectForm({ websiteId, items, onSelect }) { type={field?.type} values={data} onSelect={onSelect} + allowFilterSelect={allowFilterSelect} /> ); } diff --git a/components/pages/reports/ParameterList.js b/src/components/pages/reports/ParameterList.js similarity index 95% rename from components/pages/reports/ParameterList.js rename to src/components/pages/reports/ParameterList.js index a097cbdc..bf77dd9d 100644 --- a/components/pages/reports/ParameterList.js +++ b/src/components/pages/reports/ParameterList.js @@ -1,7 +1,7 @@ import { Icon, TooltipPopup } from 'react-basics'; import Icons from 'components/icons'; import Empty from 'components/common/Empty'; -import { useMessages } from 'hooks'; +import { useMessages } from 'components/hooks'; import styles from './ParameterList.module.css'; export function ParameterList({ items = [], children, onRemove }) { diff --git a/components/pages/reports/ParameterList.module.css b/src/components/pages/reports/ParameterList.module.css similarity index 100% rename from components/pages/reports/ParameterList.module.css rename to src/components/pages/reports/ParameterList.module.css diff --git a/components/pages/reports/PopupForm.js b/src/components/pages/reports/PopupForm.js similarity index 100% rename from components/pages/reports/PopupForm.js rename to src/components/pages/reports/PopupForm.js diff --git a/components/pages/reports/PopupForm.module.css b/src/components/pages/reports/PopupForm.module.css similarity index 100% rename from components/pages/reports/PopupForm.module.css rename to src/components/pages/reports/PopupForm.module.css diff --git a/components/pages/reports/Report.js b/src/components/pages/reports/Report.js similarity index 92% rename from components/pages/reports/Report.js rename to src/components/pages/reports/Report.js index 685ebb9f..de17aca6 100644 --- a/components/pages/reports/Report.js +++ b/src/components/pages/reports/Report.js @@ -1,7 +1,7 @@ import { createContext } from 'react'; import Page from 'components/layout/Page'; import styles from './reports.module.css'; -import { useReport } from 'hooks'; +import { useReport } from 'components/hooks'; export const ReportContext = createContext(null); diff --git a/components/pages/reports/ReportBody.js b/src/components/pages/reports/ReportBody.js similarity index 100% rename from components/pages/reports/ReportBody.js rename to src/components/pages/reports/ReportBody.js diff --git a/components/pages/reports/ReportDetails.js b/src/components/pages/reports/ReportDetails.js similarity index 100% rename from components/pages/reports/ReportDetails.js rename to src/components/pages/reports/ReportDetails.js diff --git a/components/pages/reports/ReportHeader.js b/src/components/pages/reports/ReportHeader.js similarity index 96% rename from components/pages/reports/ReportHeader.js rename to src/components/pages/reports/ReportHeader.js index 394b1951..e81d6ece 100644 --- a/components/pages/reports/ReportHeader.js +++ b/src/components/pages/reports/ReportHeader.js @@ -2,7 +2,7 @@ import { useContext } from 'react'; import { useRouter } from 'next/router'; import { Icon, LoadingButton, InlineEditField, useToasts } from 'react-basics'; import PageHeader from 'components/layout/PageHeader'; -import { useMessages, useApi } from 'hooks'; +import { useMessages, useApi } from 'components/hooks'; import { ReportContext } from './Report'; import styles from './ReportHeader.module.css'; import reportStyles from './reports.module.css'; @@ -66,7 +66,7 @@ export function ReportHeader({ icon }) { }> diff --git a/components/pages/reports/ReportHeader.module.css b/src/components/pages/reports/ReportHeader.module.css similarity index 100% rename from components/pages/reports/ReportHeader.module.css rename to src/components/pages/reports/ReportHeader.module.css diff --git a/components/pages/reports/ReportMenu.js b/src/components/pages/reports/ReportMenu.js similarity index 100% rename from components/pages/reports/ReportMenu.js rename to src/components/pages/reports/ReportMenu.js diff --git a/components/pages/reports/ReportTemplates.js b/src/components/pages/reports/ReportTemplates.js similarity index 91% rename from components/pages/reports/ReportTemplates.js rename to src/components/pages/reports/ReportTemplates.js index 0f5e710d..2b934434 100644 --- a/components/pages/reports/ReportTemplates.js +++ b/src/components/pages/reports/ReportTemplates.js @@ -6,7 +6,7 @@ import Funnel from 'assets/funnel.svg'; import Lightbulb from 'assets/lightbulb.svg'; import Magnet from 'assets/magnet.svg'; import styles from './ReportTemplates.module.css'; -import { useMessages } from 'hooks'; +import { useMessages } from 'components/hooks'; function ReportItem({ title, description, url, icon }) { return ( @@ -30,7 +30,7 @@ function ReportItem({ title, description, url, icon }) { ); } -export function ReportTemplates() { +export function ReportTemplates({ showHeader = true }) { const { formatMessage, labels } = useMessages(); const reports = [ @@ -56,7 +56,7 @@ export function ReportTemplates() { return ( - + {showHeader && }
{reports.map(({ title, description, url, icon }) => { return ( diff --git a/components/pages/reports/ReportTemplates.module.css b/src/components/pages/reports/ReportTemplates.module.css similarity index 100% rename from components/pages/reports/ReportTemplates.module.css rename to src/components/pages/reports/ReportTemplates.module.css diff --git a/components/pages/reports/ReportsPage.js b/src/components/pages/reports/ReportsPage.js similarity index 85% rename from components/pages/reports/ReportsPage.js rename to src/components/pages/reports/ReportsPage.js index 95959832..bbb15a36 100644 --- a/components/pages/reports/ReportsPage.js +++ b/src/components/pages/reports/ReportsPage.js @@ -1,13 +1,13 @@ import EmptyPlaceholder from 'components/common/EmptyPlaceholder'; import Page from 'components/layout/Page'; import PageHeader from 'components/layout/PageHeader'; -import { useMessages, useReports } from 'hooks'; +import { useMessages, useReports } from 'components/hooks'; import Link from 'next/link'; import { Button, Icon, Icons, Text } from 'react-basics'; import ReportsTable from './ReportsTable'; export function ReportsPage() { - const { formatMessage, labels, messages } = useMessages(); + const { formatMessage, labels } = useMessages(); const { reports, error, @@ -47,9 +47,7 @@ export function ReportsPage() { showDomain={true} /> )} - {!hasData && ( - - )} + {!hasData && } ); } diff --git a/components/pages/reports/ReportsTable.js b/src/components/pages/reports/ReportsTable.js similarity index 95% rename from components/pages/reports/ReportsTable.js rename to src/components/pages/reports/ReportsTable.js index 98f5267a..4073fbec 100644 --- a/components/pages/reports/ReportsTable.js +++ b/src/components/pages/reports/ReportsTable.js @@ -1,8 +1,8 @@ import ConfirmDeleteForm from 'components/common/ConfirmDeleteForm'; import LinkButton from 'components/common/LinkButton'; import SettingsTable from 'components/common/SettingsTable'; -import { useMessages } from 'hooks'; -import useUser from 'hooks/useUser'; +import { useMessages } from 'components/hooks'; +import useUser from 'components/hooks/useUser'; import { useState } from 'react'; import { Button, Flexbox, Icon, Icons, Modal, Text } from 'react-basics'; diff --git a/components/pages/reports/event-data/EventDataParameters.js b/src/components/pages/reports/event-data/EventDataParameters.js similarity index 98% rename from components/pages/reports/event-data/EventDataParameters.js rename to src/components/pages/reports/event-data/EventDataParameters.js index 8d4dbb62..a01a2972 100644 --- a/components/pages/reports/event-data/EventDataParameters.js +++ b/src/components/pages/reports/event-data/EventDataParameters.js @@ -1,5 +1,5 @@ import { useContext, useRef } from 'react'; -import { useApi, useMessages } from 'hooks'; +import { useApi, useMessages } from 'components/hooks'; import { Form, FormRow, FormButtons, SubmitButton, PopupTrigger, Icon, Popup } from 'react-basics'; import { ReportContext } from 'components/pages/reports/Report'; import Empty from 'components/common/Empty'; diff --git a/components/pages/reports/event-data/EventDataParameters.module.css b/src/components/pages/reports/event-data/EventDataParameters.module.css similarity index 100% rename from components/pages/reports/event-data/EventDataParameters.module.css rename to src/components/pages/reports/event-data/EventDataParameters.module.css diff --git a/components/pages/reports/event-data/EventDataReport.js b/src/components/pages/reports/event-data/EventDataReport.js similarity index 100% rename from components/pages/reports/event-data/EventDataReport.js rename to src/components/pages/reports/event-data/EventDataReport.js diff --git a/components/pages/reports/event-data/EventDataTable.js b/src/components/pages/reports/event-data/EventDataTable.js similarity index 92% rename from components/pages/reports/event-data/EventDataTable.js rename to src/components/pages/reports/event-data/EventDataTable.js index 81b9cad1..b6450261 100644 --- a/components/pages/reports/event-data/EventDataTable.js +++ b/src/components/pages/reports/event-data/EventDataTable.js @@ -1,6 +1,6 @@ import { useContext } from 'react'; import { GridTable, GridColumn } from 'react-basics'; -import { useMessages } from 'hooks'; +import { useMessages } from 'components/hooks'; import { ReportContext } from '../Report'; export function EventDataTable() { diff --git a/components/pages/reports/funnel/FunnelChart.js b/src/components/pages/reports/funnel/FunnelChart.js similarity index 92% rename from components/pages/reports/funnel/FunnelChart.js rename to src/components/pages/reports/funnel/FunnelChart.js index c35afe4e..829a3008 100644 --- a/components/pages/reports/funnel/FunnelChart.js +++ b/src/components/pages/reports/funnel/FunnelChart.js @@ -1,7 +1,7 @@ import { useCallback, useContext, useMemo } from 'react'; import { Loading, StatusLight } from 'react-basics'; -import useMessages from 'hooks/useMessages'; -import useTheme from 'hooks/useTheme'; +import useMessages from 'components/hooks/useMessages'; +import useTheme from 'components/hooks/useTheme'; import BarChart from 'components/metrics/BarChart'; import { formatLongNumber } from 'lib/format'; import styles from './FunnelChart.module.css'; @@ -52,7 +52,7 @@ export function FunnelChart({ className, loading }) { ...colors.chart.visitors, }, ]; - }, [data]); + }, [data, colors, formatMessage, labels]); if (loading) { return ; diff --git a/components/pages/reports/funnel/FunnelChart.module.css b/src/components/pages/reports/funnel/FunnelChart.module.css similarity index 100% rename from components/pages/reports/funnel/FunnelChart.module.css rename to src/components/pages/reports/funnel/FunnelChart.module.css diff --git a/components/pages/reports/funnel/FunnelParameters.js b/src/components/pages/reports/funnel/FunnelParameters.js similarity index 98% rename from components/pages/reports/funnel/FunnelParameters.js rename to src/components/pages/reports/funnel/FunnelParameters.js index 03898db3..2c99a032 100644 --- a/components/pages/reports/funnel/FunnelParameters.js +++ b/src/components/pages/reports/funnel/FunnelParameters.js @@ -1,5 +1,5 @@ import { useContext, useRef } from 'react'; -import { useMessages } from 'hooks'; +import { useMessages } from 'components/hooks'; import { Icon, Form, diff --git a/components/pages/reports/funnel/FunnelReport.js b/src/components/pages/reports/funnel/FunnelReport.js similarity index 100% rename from components/pages/reports/funnel/FunnelReport.js rename to src/components/pages/reports/funnel/FunnelReport.js diff --git a/components/pages/reports/funnel/FunnelReport.module.css b/src/components/pages/reports/funnel/FunnelReport.module.css similarity index 100% rename from components/pages/reports/funnel/FunnelReport.module.css rename to src/components/pages/reports/funnel/FunnelReport.module.css diff --git a/components/pages/reports/funnel/FunnelTable.js b/src/components/pages/reports/funnel/FunnelTable.js similarity index 77% rename from components/pages/reports/funnel/FunnelTable.js rename to src/components/pages/reports/funnel/FunnelTable.js index 9ae8ab58..5ca2593c 100644 --- a/components/pages/reports/funnel/FunnelTable.js +++ b/src/components/pages/reports/funnel/FunnelTable.js @@ -1,13 +1,13 @@ import { useContext } from 'react'; -import DataTable from 'components/metrics/DataTable'; -import { useMessages } from 'hooks'; +import ListTable from 'components/metrics/ListTable'; +import { useMessages } from 'components/hooks'; import { ReportContext } from '../Report'; export function FunnelTable() { const { report } = useContext(ReportContext); const { formatMessage, labels } = useMessages(); return ( - getRandomChars(16); diff --git a/components/pages/settings/teams/TeamJoinForm.js b/src/components/pages/settings/teams/TeamJoinForm.js similarity index 91% rename from components/pages/settings/teams/TeamJoinForm.js rename to src/components/pages/settings/teams/TeamJoinForm.js index 34153aa1..23abcf00 100644 --- a/components/pages/settings/teams/TeamJoinForm.js +++ b/src/components/pages/settings/teams/TeamJoinForm.js @@ -8,8 +8,8 @@ import { Button, SubmitButton, } from 'react-basics'; -import useApi from 'hooks/useApi'; -import useMessages from 'hooks/useMessages'; +import useApi from 'components/hooks/useApi'; +import useMessages from 'components/hooks/useMessages'; export function TeamJoinForm({ onSave, onClose }) { const { formatMessage, labels, getMessage } = useMessages(); diff --git a/components/pages/settings/teams/TeamLeaveForm.js b/src/components/pages/settings/teams/TeamLeaveForm.js similarity index 90% rename from components/pages/settings/teams/TeamLeaveForm.js rename to src/components/pages/settings/teams/TeamLeaveForm.js index 954006ab..8af2932d 100644 --- a/components/pages/settings/teams/TeamLeaveForm.js +++ b/src/components/pages/settings/teams/TeamLeaveForm.js @@ -1,6 +1,6 @@ import { Button, Form, FormButtons, SubmitButton } from 'react-basics'; -import useApi from 'hooks/useApi'; -import useMessages from 'hooks/useMessages'; +import useApi from 'components/hooks/useApi'; +import useMessages from 'components/hooks/useMessages'; export function TeamLeaveForm({ teamId, userId, teamName, onSave, onClose }) { const { formatMessage, labels, messages, FormattedMessage } = useMessages(); diff --git a/components/pages/settings/teams/TeamMemberRemoveButton.js b/src/components/pages/settings/teams/TeamMemberRemoveButton.js similarity index 74% rename from components/pages/settings/teams/TeamMemberRemoveButton.js rename to src/components/pages/settings/teams/TeamMemberRemoveButton.js index 9f326476..3ec0f8b3 100644 --- a/components/pages/settings/teams/TeamMemberRemoveButton.js +++ b/src/components/pages/settings/teams/TeamMemberRemoveButton.js @@ -1,5 +1,5 @@ -import useApi from 'hooks/useApi'; -import useMessages from 'hooks/useMessages'; +import useApi from 'components/hooks/useApi'; +import useMessages from 'components/hooks/useMessages'; import { Icon, Icons, LoadingButton, Text } from 'react-basics'; export function TeamMemberRemoveButton({ teamId, userId, disabled, onSave }) { @@ -19,7 +19,11 @@ export function TeamMemberRemoveButton({ teamId, userId, disabled, onSave }) { }; return ( - handleRemoveTeamMember()} disabled={disabled} loading={isLoading}> + handleRemoveTeamMember()} + disabled={disabled} + isLoading={isLoading} + > diff --git a/components/pages/settings/teams/TeamMembers.js b/src/components/pages/settings/teams/TeamMembers.js similarity index 86% rename from components/pages/settings/teams/TeamMembers.js rename to src/components/pages/settings/teams/TeamMembers.js index 9762ef29..207ad72d 100644 --- a/components/pages/settings/teams/TeamMembers.js +++ b/src/components/pages/settings/teams/TeamMembers.js @@ -1,8 +1,8 @@ import { Loading, useToasts } from 'react-basics'; import TeamMembersTable from 'components/pages/settings/teams/TeamMembersTable'; -import useApi from 'hooks/useApi'; -import useMessages from 'hooks/useMessages'; -import useApiFilter from 'hooks/useApiFilter'; +import useApi from 'components/hooks/useApi'; +import useMessages from 'components/hooks/useMessages'; +import useApiFilter from 'components/hooks/useApiFilter'; export function TeamMembers({ teamId, readOnly }) { const { showToast } = useToasts(); @@ -33,6 +33,7 @@ export function TeamMembers({ teamId, readOnly }) { <> - - - {formatMessage(labels.teams)} - - {values?.name} - - } - /> + {formatMessage(labels.details)} {formatMessage(labels.members)} diff --git a/components/pages/settings/teams/TeamWebsiteRemoveButton.js b/src/components/pages/settings/teams/TeamWebsiteRemoveButton.js similarity index 78% rename from components/pages/settings/teams/TeamWebsiteRemoveButton.js rename to src/components/pages/settings/teams/TeamWebsiteRemoveButton.js index a752313f..c0ddf95c 100644 --- a/components/pages/settings/teams/TeamWebsiteRemoveButton.js +++ b/src/components/pages/settings/teams/TeamWebsiteRemoveButton.js @@ -1,5 +1,5 @@ -import useApi from 'hooks/useApi'; -import useMessages from 'hooks/useMessages'; +import useApi from 'components/hooks/useApi'; +import useMessages from 'components/hooks/useMessages'; import { Icon, Icons, LoadingButton, Text } from 'react-basics'; export function TeamWebsiteRemoveButton({ teamId, websiteId, onSave }) { @@ -19,7 +19,7 @@ export function TeamWebsiteRemoveButton({ teamId, websiteId, onSave }) { }; return ( - handleRemoveTeamMember()} loading={isLoading}> + handleRemoveTeamMember()} isLoading={isLoading}> diff --git a/components/pages/settings/teams/TeamWebsites.js b/src/components/pages/settings/teams/TeamWebsites.js similarity index 92% rename from components/pages/settings/teams/TeamWebsites.js rename to src/components/pages/settings/teams/TeamWebsites.js index 2ae344f5..27fe2f85 100644 --- a/components/pages/settings/teams/TeamWebsites.js +++ b/src/components/pages/settings/teams/TeamWebsites.js @@ -11,9 +11,9 @@ import { } from 'react-basics'; import TeamWebsitesTable from 'components/pages/settings/teams/TeamWebsitesTable'; import TeamAddWebsiteForm from 'components/pages/settings/teams/TeamAddWebsiteForm'; -import useApi from 'hooks/useApi'; -import useMessages from 'hooks/useMessages'; -import useApiFilter from 'hooks/useApiFilter'; +import useApi from 'components/hooks/useApi'; +import useMessages from 'components/hooks/useMessages'; +import useApiFilter from 'components/hooks/useApiFilter'; export function TeamWebsites({ teamId }) { const { showToast } = useToasts(); diff --git a/components/pages/settings/teams/TeamWebsitesTable.js b/src/components/pages/settings/teams/TeamWebsitesTable.js similarity index 82% rename from components/pages/settings/teams/TeamWebsitesTable.js rename to src/components/pages/settings/teams/TeamWebsitesTable.js index 564c8a78..5ce08f35 100644 --- a/components/pages/settings/teams/TeamWebsitesTable.js +++ b/src/components/pages/settings/teams/TeamWebsitesTable.js @@ -1,10 +1,9 @@ -import useMessages from 'hooks/useMessages'; -import useUser from 'hooks/useUser'; +import useMessages from 'components/hooks/useMessages'; +import useUser from 'components/hooks/useUser'; import Link from 'next/link'; import { Button, Icon, Icons, Text } from 'react-basics'; import TeamWebsiteRemoveButton from './TeamWebsiteRemoveButton'; import SettingsTable from 'components/common/SettingsTable'; -import useConfig from 'hooks/useConfig'; export function TeamWebsitesTable({ data = [], @@ -13,9 +12,9 @@ export function TeamWebsitesTable({ onFilterChange, onPageChange, onPageSizeChange, + openExternal = false, }) { const { formatMessage, labels } = useMessages(); - const { openExternal } = useConfig(); const { user } = useUser(); const columns = [ @@ -55,11 +54,7 @@ export function TeamWebsitesTable({ {canRemove && ( - + )} ); diff --git a/components/pages/settings/teams/TeamsList.js b/src/components/pages/settings/teams/TeamsList.js similarity index 92% rename from components/pages/settings/teams/TeamsList.js rename to src/components/pages/settings/teams/TeamsList.js index 061100f6..76a87b0c 100644 --- a/components/pages/settings/teams/TeamsList.js +++ b/src/components/pages/settings/teams/TeamsList.js @@ -4,16 +4,16 @@ import Page from 'components/layout/Page'; import PageHeader from 'components/layout/PageHeader'; import TeamAddForm from 'components/pages/settings/teams/TeamAddForm'; import TeamsTable from 'components/pages/settings/teams/TeamsTable'; -import useApi from 'hooks/useApi'; -import useMessages from 'hooks/useMessages'; -import useUser from 'hooks/useUser'; +import useApi from 'components/hooks/useApi'; +import useMessages from 'components/hooks/useMessages'; +import useUser from 'components/hooks/useUser'; import { ROLES } from 'lib/constants'; import { useState } from 'react'; import { Button, Flexbox, Icon, Modal, ModalTrigger, Text, useToasts } from 'react-basics'; import TeamJoinForm from './TeamJoinForm'; -import useApiFilter from 'hooks/useApiFilter'; +import useApiFilter from 'components/hooks/useApiFilter'; -export default function TeamsList() { +export function TeamsList() { const { user } = useUser(); const { formatMessage, labels, messages } = useMessages(); const { filter, page, pageSize, handleFilterChange, handlePageChange, handlePageSizeChange } = @@ -114,3 +114,5 @@ export default function TeamsList() { ); } + +export default TeamsList; diff --git a/components/pages/settings/teams/TeamsTable.js b/src/components/pages/settings/teams/TeamsTable.js similarity index 95% rename from components/pages/settings/teams/TeamsTable.js rename to src/components/pages/settings/teams/TeamsTable.js index e35fb839..e1710783 100644 --- a/components/pages/settings/teams/TeamsTable.js +++ b/src/components/pages/settings/teams/TeamsTable.js @@ -1,7 +1,7 @@ import SettingsTable from 'components/common/SettingsTable'; -import useLocale from 'hooks/useLocale'; -import useMessages from 'hooks/useMessages'; -import useUser from 'hooks/useUser'; +import useLocale from 'components/hooks/useLocale'; +import useMessages from 'components/hooks/useMessages'; +import useUser from 'components/hooks/useUser'; import { ROLES } from 'lib/constants'; import Link from 'next/link'; import { Button, Icon, Icons, Modal, ModalTrigger, Text } from 'react-basics'; diff --git a/components/pages/settings/teams/WebsiteTags.js b/src/components/pages/settings/teams/WebsiteTags.js similarity index 100% rename from components/pages/settings/teams/WebsiteTags.js rename to src/components/pages/settings/teams/WebsiteTags.js diff --git a/components/pages/settings/teams/WebsiteTags.module.css b/src/components/pages/settings/teams/WebsiteTags.module.css similarity index 100% rename from components/pages/settings/teams/WebsiteTags.module.css rename to src/components/pages/settings/teams/WebsiteTags.module.css diff --git a/components/pages/settings/users/UserAddButton.js b/src/components/pages/settings/users/UserAddButton.js similarity index 92% rename from components/pages/settings/users/UserAddButton.js rename to src/components/pages/settings/users/UserAddButton.js index a461f39d..8b691362 100644 --- a/components/pages/settings/users/UserAddButton.js +++ b/src/components/pages/settings/users/UserAddButton.js @@ -1,6 +1,6 @@ import { Button, Icon, Text, Modal, Icons, ModalTrigger } from 'react-basics'; import UserAddForm from './UserAddForm'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; export function UserAddButton({ onSave }) { const { formatMessage, labels } = useMessages(); diff --git a/components/pages/settings/users/UserAddForm.js b/src/components/pages/settings/users/UserAddForm.js similarity index 95% rename from components/pages/settings/users/UserAddForm.js rename to src/components/pages/settings/users/UserAddForm.js index bb408749..38c1bedd 100644 --- a/components/pages/settings/users/UserAddForm.js +++ b/src/components/pages/settings/users/UserAddForm.js @@ -10,9 +10,9 @@ import { SubmitButton, Button, } from 'react-basics'; -import useApi from 'hooks/useApi'; +import useApi from 'components/hooks/useApi'; import { ROLES } from 'lib/constants'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; export function UserAddForm({ onSave, onClose }) { const { post, useMutation } = useApi(); diff --git a/components/pages/settings/users/UserDeleteForm.js b/src/components/pages/settings/users/UserDeleteForm.js similarity index 91% rename from components/pages/settings/users/UserDeleteForm.js rename to src/components/pages/settings/users/UserDeleteForm.js index bd412e44..5a47fdc1 100644 --- a/components/pages/settings/users/UserDeleteForm.js +++ b/src/components/pages/settings/users/UserDeleteForm.js @@ -1,7 +1,7 @@ import { useMutation } from '@tanstack/react-query'; import { Button, Form, FormButtons, SubmitButton } from 'react-basics'; -import useApi from 'hooks/useApi'; -import useMessages from 'hooks/useMessages'; +import useApi from 'components/hooks/useApi'; +import useMessages from 'components/hooks/useMessages'; export function UserDeleteForm({ userId, username, onSave, onClose }) { const { formatMessage, FormattedMessage, labels, messages } = useMessages(); diff --git a/components/pages/settings/users/UserEditForm.js b/src/components/pages/settings/users/UserEditForm.js similarity index 95% rename from components/pages/settings/users/UserEditForm.js rename to src/components/pages/settings/users/UserEditForm.js index 887531e8..157c9ad6 100644 --- a/components/pages/settings/users/UserEditForm.js +++ b/src/components/pages/settings/users/UserEditForm.js @@ -9,9 +9,9 @@ import { SubmitButton, PasswordField, } from 'react-basics'; -import useApi from 'hooks/useApi'; +import useApi from 'components/hooks/useApi'; import { ROLES } from 'lib/constants'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; export function UserEditForm({ userId, data, onSave }) { const { formatMessage, labels, messages } = useMessages(); diff --git a/components/pages/settings/users/UserSettings.js b/src/components/pages/settings/users/UserSettings.js similarity index 74% rename from components/pages/settings/users/UserSettings.js rename to src/components/pages/settings/users/UserSettings.js index d703e964..5fadf1a1 100644 --- a/components/pages/settings/users/UserSettings.js +++ b/src/components/pages/settings/users/UserSettings.js @@ -1,12 +1,11 @@ import { useEffect, useState } from 'react'; -import { Breadcrumbs, Item, Tabs, useToasts } from 'react-basics'; -import Link from 'next/link'; -import UserEditForm from 'components/pages/settings/users//UserEditForm'; +import { Item, Tabs, useToasts } from 'react-basics'; +import UserEditForm from 'components/pages/settings/users/UserEditForm'; import Page from 'components/layout/Page'; import PageHeader from 'components/layout/PageHeader'; -import useApi from 'hooks/useApi'; +import useApi from 'components/hooks/useApi'; import UserWebsites from './UserWebsites'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; export function UserSettings({ userId }) { const { formatMessage, labels, messages } = useMessages(); @@ -44,16 +43,7 @@ export function UserSettings({ userId }) { return ( - - - {formatMessage(labels.users)} - - {values?.username} - - } - /> + {formatMessage(labels.details)} {formatMessage(labels.websites)} diff --git a/src/components/pages/settings/users/UserWebsites.js b/src/components/pages/settings/users/UserWebsites.js new file mode 100644 index 00000000..14127275 --- /dev/null +++ b/src/components/pages/settings/users/UserWebsites.js @@ -0,0 +1,36 @@ +import Page from 'components/layout/Page'; +import useApi from 'components/hooks/useApi'; +import WebsitesTable from 'components/pages/settings/websites/WebsitesTable'; +import useApiFilter from 'components/hooks/useApiFilter'; + +export function UserWebsites({ userId }) { + const { filter, page, pageSize, handleFilterChange, handlePageChange, handlePageSizeChange } = + useApiFilter(); + const { get, useQuery } = useApi(); + const { data, isLoading, error } = useQuery( + ['user:websites', userId, filter, page, pageSize], + () => + get(`/users/${userId}/websites`, { + filter, + page, + pageSize, + }), + ); + const hasData = data && data.length !== 0; + + return ( + + {hasData && ( + + )} + + ); +} + +export default UserWebsites; diff --git a/components/pages/settings/users/UsersList.js b/src/components/pages/settings/users/UsersList.js similarity index 90% rename from components/pages/settings/users/UsersList.js rename to src/components/pages/settings/users/UsersList.js index 614aabef..0bc8612e 100644 --- a/components/pages/settings/users/UsersList.js +++ b/src/components/pages/settings/users/UsersList.js @@ -4,10 +4,10 @@ import PageHeader from 'components/layout/PageHeader'; import EmptyPlaceholder from 'components/common/EmptyPlaceholder'; import UsersTable from './UsersTable'; import UserAddButton from './UserAddButton'; -import useApi from 'hooks/useApi'; -import useUser from 'hooks/useUser'; -import useMessages from 'hooks/useMessages'; -import useApiFilter from 'hooks/useApiFilter'; +import useApi from 'components/hooks/useApi'; +import useUser from 'components/hooks/useUser'; +import useMessages from 'components/hooks/useMessages'; +import useApiFilter from 'components/hooks/useApiFilter'; export function UsersList() { const { formatMessage, labels, messages } = useMessages(); diff --git a/components/pages/settings/users/UsersTable.js b/src/components/pages/settings/users/UsersTable.js similarity index 93% rename from components/pages/settings/users/UsersTable.js rename to src/components/pages/settings/users/UsersTable.js index f4c9dd77..1a93710d 100644 --- a/components/pages/settings/users/UsersTable.js +++ b/src/components/pages/settings/users/UsersTable.js @@ -1,12 +1,12 @@ import { Button, Text, Icon, Icons, ModalTrigger, Modal } from 'react-basics'; import { formatDistance } from 'date-fns'; import Link from 'next/link'; -import useUser from 'hooks/useUser'; +import useUser from 'components/hooks/useUser'; import UserDeleteForm from './UserDeleteForm'; import { ROLES } from 'lib/constants'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; import SettingsTable from 'components/common/SettingsTable'; -import useLocale from 'hooks/useLocale'; +import useLocale from 'components/hooks/useLocale'; export function UsersTable({ data = { data: [] }, @@ -54,7 +54,7 @@ export function UsersTable({ onPageSizeChange={onPageSizeChange} filterValue={filterValue} > - {(row, keys, rowIndex) => { + {row => { return ( <> diff --git a/components/pages/settings/websites/ShareUrl.js b/src/components/pages/settings/websites/ShareUrl.js similarity index 94% rename from components/pages/settings/websites/ShareUrl.js rename to src/components/pages/settings/websites/ShareUrl.js index 7f0b352c..a7b7fc2f 100644 --- a/components/pages/settings/websites/ShareUrl.js +++ b/src/components/pages/settings/websites/ShareUrl.js @@ -11,8 +11,8 @@ import { import { useEffect, useMemo, useRef, useState } from 'react'; import { getRandomChars } from 'next-basics'; import { useRouter } from 'next/router'; -import useApi from 'hooks/useApi'; -import useMessages from 'hooks/useMessages'; +import useApi from 'components/hooks/useApi'; +import useMessages from 'components/hooks/useMessages'; const generateId = () => getRandomChars(16); @@ -31,7 +31,7 @@ export function ShareUrl({ websiteId, data, onSave }) { `${process.env.analyticsUrl || location.origin}${basePath}/share/${id}/${encodeURIComponent( name, )}`, - [id, name], + [id, name, basePath], ); const handleSubmit = async data => { diff --git a/components/pages/settings/websites/TrackingCode.js b/src/components/pages/settings/websites/TrackingCode.js similarity index 53% rename from components/pages/settings/websites/TrackingCode.js rename to src/components/pages/settings/websites/TrackingCode.js index c847ed0d..d22f0d59 100644 --- a/components/pages/settings/websites/TrackingCode.js +++ b/src/components/pages/settings/websites/TrackingCode.js @@ -1,15 +1,19 @@ import { TextArea } from 'react-basics'; -import useMessages from 'hooks/useMessages'; -import useConfig from 'hooks/useConfig'; +import useMessages from 'components/hooks/useMessages'; +import useConfig from 'components/hooks/useConfig'; +import { useRouter } from 'next/router'; export function TrackingCode({ websiteId }) { const { formatMessage, messages } = useMessages(); - const { basePath, trackerScriptName } = useConfig(); + const { basePath } = useRouter(); + const config = useConfig(); + + const trackerScriptName = + config?.trackerScriptName?.split(',')?.map(n => n.trim())?.[0] || 'script.js'; + const url = trackerScriptName?.startsWith('http') ? trackerScriptName - : `${location.origin}${basePath}/${ - trackerScriptName?.split(',')?.map(n => n.trim())?.[0] || 'script.js' - }`; + : `${process.env.analyticsUrl || location.origin}${basePath}/${trackerScriptName}`; const code = ``; diff --git a/components/pages/settings/websites/WebsiteAddForm.js b/src/components/pages/settings/websites/WebsiteAddForm.js similarity index 93% rename from components/pages/settings/websites/WebsiteAddForm.js rename to src/components/pages/settings/websites/WebsiteAddForm.js index 77b850b4..371343ba 100644 --- a/components/pages/settings/websites/WebsiteAddForm.js +++ b/src/components/pages/settings/websites/WebsiteAddForm.js @@ -7,9 +7,9 @@ import { Button, SubmitButton, } from 'react-basics'; -import useApi from 'hooks/useApi'; +import useApi from 'components/hooks/useApi'; import { DOMAIN_REGEX } from 'lib/constants'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; export function WebsiteAddForm({ onSave, onClose }) { const { formatMessage, labels, messages } = useMessages(); diff --git a/components/pages/settings/websites/WebsiteData.js b/src/components/pages/settings/websites/WebsiteData.js similarity index 96% rename from components/pages/settings/websites/WebsiteData.js rename to src/components/pages/settings/websites/WebsiteData.js index a2bc6bfa..08d6702e 100644 --- a/components/pages/settings/websites/WebsiteData.js +++ b/src/components/pages/settings/websites/WebsiteData.js @@ -1,7 +1,7 @@ import { Button, Modal, ModalTrigger, ActionForm } from 'react-basics'; import WebsiteDeleteForm from 'components/pages/settings/websites/WebsiteDeleteForm'; import WebsiteResetForm from 'components/pages/settings/websites/WebsiteResetForm'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; export function WebsiteData({ websiteId, onSave }) { const { formatMessage, labels, messages } = useMessages(); diff --git a/components/pages/settings/websites/WebsiteDeleteForm.js b/src/components/pages/settings/websites/WebsiteDeleteForm.js similarity index 92% rename from components/pages/settings/websites/WebsiteDeleteForm.js rename to src/components/pages/settings/websites/WebsiteDeleteForm.js index c9e302fc..1548bddb 100644 --- a/components/pages/settings/websites/WebsiteDeleteForm.js +++ b/src/components/pages/settings/websites/WebsiteDeleteForm.js @@ -7,8 +7,8 @@ import { SubmitButton, TextField, } from 'react-basics'; -import useApi from 'hooks/useApi'; -import useMessages from 'hooks/useMessages'; +import useApi from 'components/hooks/useApi'; +import useMessages from 'components/hooks/useMessages'; const CONFIRM_VALUE = 'DELETE'; diff --git a/components/pages/settings/websites/WebsiteEditForm.js b/src/components/pages/settings/websites/WebsiteEditForm.js similarity index 94% rename from components/pages/settings/websites/WebsiteEditForm.js rename to src/components/pages/settings/websites/WebsiteEditForm.js index 89c62889..18ad0ac9 100644 --- a/components/pages/settings/websites/WebsiteEditForm.js +++ b/src/components/pages/settings/websites/WebsiteEditForm.js @@ -1,8 +1,8 @@ import { SubmitButton, Form, FormInput, FormRow, FormButtons, TextField } from 'react-basics'; import { useRef } from 'react'; -import useApi from 'hooks/useApi'; +import useApi from 'components/hooks/useApi'; import { DOMAIN_REGEX } from 'lib/constants'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; export function WebsiteEditForm({ websiteId, data, onSave }) { const { formatMessage, labels, messages } = useMessages(); diff --git a/components/pages/settings/websites/WebsiteResetForm.js b/src/components/pages/settings/websites/WebsiteResetForm.js similarity index 92% rename from components/pages/settings/websites/WebsiteResetForm.js rename to src/components/pages/settings/websites/WebsiteResetForm.js index 5fc0acf7..9886429b 100644 --- a/components/pages/settings/websites/WebsiteResetForm.js +++ b/src/components/pages/settings/websites/WebsiteResetForm.js @@ -7,8 +7,8 @@ import { SubmitButton, TextField, } from 'react-basics'; -import useApi from 'hooks/useApi'; -import useMessages from 'hooks/useMessages'; +import useApi from 'components/hooks/useApi'; +import useMessages from 'components/hooks/useMessages'; const CONFIRM_VALUE = 'RESET'; diff --git a/components/pages/settings/websites/WebsiteSettings.js b/src/components/pages/settings/websites/WebsiteSettings.js similarity index 81% rename from components/pages/settings/websites/WebsiteSettings.js rename to src/components/pages/settings/websites/WebsiteSettings.js index 5a96fa8c..f73e0a87 100644 --- a/components/pages/settings/websites/WebsiteSettings.js +++ b/src/components/pages/settings/websites/WebsiteSettings.js @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import { Breadcrumbs, Item, Tabs, useToasts, Button, Text, Icon, Icons } from 'react-basics'; +import { Item, Tabs, useToasts, Button, Text, Icon, Icons } from 'react-basics'; import { useRouter } from 'next/router'; import Link from 'next/link'; import Page from 'components/layout/Page'; @@ -8,14 +8,12 @@ import WebsiteEditForm from 'components/pages/settings/websites/WebsiteEditForm' import WebsiteData from 'components/pages/settings/websites/WebsiteData'; import TrackingCode from 'components/pages/settings/websites/TrackingCode'; import ShareUrl from 'components/pages/settings/websites/ShareUrl'; -import useApi from 'hooks/useApi'; -import useMessages from 'hooks/useMessages'; -import useConfig from 'hooks/useConfig'; +import useApi from 'components/hooks/useApi'; +import useMessages from 'components/hooks/useMessages'; -export function WebsiteSettings({ websiteId }) { +export function WebsiteSettings({ websiteId, openExternal = false }) { const router = useRouter(); const { formatMessage, labels, messages } = useMessages(); - const { openExternal } = useConfig(); const { get, useQuery } = useApi(); const { showToast } = useToasts(); const { data, isLoading } = useQuery( @@ -51,16 +49,7 @@ export function WebsiteSettings({ websiteId }) { return ( - - - {formatMessage(labels.websites)} - - {values?.name} - - } - > + - + {showCharts && }
) : null; diff --git a/components/pages/websites/WebsiteDetailsPage.js b/src/components/pages/websites/WebsiteDetailsPage.js similarity index 93% rename from components/pages/websites/WebsiteDetailsPage.js rename to src/components/pages/websites/WebsiteDetailsPage.js index 9e0519b2..222d94d9 100644 --- a/components/pages/websites/WebsiteDetailsPage.js +++ b/src/components/pages/websites/WebsiteDetailsPage.js @@ -3,10 +3,10 @@ import { useRouter } from 'next/router'; 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 usePageQuery from 'components/hooks/usePageQuery'; import WebsiteTableView from './WebsiteTableView'; import WebsiteMenuView from './WebsiteMenuView'; -import { useWebsite } from 'hooks'; +import { useWebsite } from 'components/hooks'; import WebsiteHeader from './WebsiteHeader'; import { WebsiteMetricsBar } from './WebsiteMetricsBar'; @@ -22,12 +22,12 @@ export default function WebsiteDetailsPage({ websiteId }) { return ( - - + + {!website && } {website && ( <> diff --git a/components/pages/websites/WebsiteEventData.js b/src/components/pages/websites/WebsiteEventData.js similarity index 79% rename from components/pages/websites/WebsiteEventData.js rename to src/components/pages/websites/WebsiteEventData.js index 7f9a6829..d38ca1ad 100644 --- a/components/pages/websites/WebsiteEventData.js +++ b/src/components/pages/websites/WebsiteEventData.js @@ -1,8 +1,8 @@ -import { Flexbox } from 'react-basics'; +import { Flexbox, Loading } 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'; +import { useDateRange, useApi, usePageQuery } from 'components/hooks'; import styles from './WebsiteEventData.module.css'; function useData(websiteId, event) { @@ -28,13 +28,14 @@ export default function WebsiteEventData({ websiteId }) { const { query: { event }, } = usePageQuery(); - const { data } = useData(websiteId, event); + const { data, isLoading } = useData(websiteId, event); return ( {!event && } - {event && } + {isLoading && } + {event && data && } ); } diff --git a/components/pages/websites/WebsiteEventData.module.css b/src/components/pages/websites/WebsiteEventData.module.css similarity index 100% rename from components/pages/websites/WebsiteEventData.module.css rename to src/components/pages/websites/WebsiteEventData.module.css diff --git a/components/pages/websites/WebsiteEventDataPage.js b/src/components/pages/websites/WebsiteEventDataPage.js similarity index 100% rename from components/pages/websites/WebsiteEventDataPage.js rename to src/components/pages/websites/WebsiteEventDataPage.js diff --git a/components/pages/websites/WebsiteHeader.js b/src/components/pages/websites/WebsiteHeader.js similarity index 94% rename from components/pages/websites/WebsiteHeader.js rename to src/components/pages/websites/WebsiteHeader.js index 0790397f..fb4e0986 100644 --- a/components/pages/websites/WebsiteHeader.js +++ b/src/components/pages/websites/WebsiteHeader.js @@ -1,11 +1,11 @@ import classNames from 'classnames'; -import { Flexbox, Row, Column, Text, Button, Icon } from 'react-basics'; +import { 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 Icons from 'components/icons'; -import { useMessages, useWebsite } from 'hooks'; +import { useMessages, useWebsite } from 'components/hooks'; import styles from './WebsiteHeader.module.css'; export function WebsiteHeader({ websiteId, showLinks = true, children }) { diff --git a/components/pages/websites/WebsiteHeader.module.css b/src/components/pages/websites/WebsiteHeader.module.css similarity index 100% rename from components/pages/websites/WebsiteHeader.module.css rename to src/components/pages/websites/WebsiteHeader.module.css diff --git a/components/pages/websites/WebsiteList.module.css b/src/components/pages/websites/WebsiteList.module.css similarity index 100% rename from components/pages/websites/WebsiteList.module.css rename to src/components/pages/websites/WebsiteList.module.css diff --git a/components/pages/websites/WebsiteMenuView.js b/src/components/pages/websites/WebsiteMenuView.js similarity index 96% rename from components/pages/websites/WebsiteMenuView.js rename to src/components/pages/websites/WebsiteMenuView.js index 39adb188..8c74d615 100644 --- a/components/pages/websites/WebsiteMenuView.js +++ b/src/components/pages/websites/WebsiteMenuView.js @@ -15,10 +15,10 @@ import ScreenTable from 'components/metrics/ScreenTable'; import EventsTable from 'components/metrics/EventsTable'; import Icons from 'components/icons'; import SideNav from 'components/layout/SideNav'; -import usePageQuery from 'hooks/usePageQuery'; -import useMessages from 'hooks/useMessages'; +import usePageQuery from 'components/hooks/usePageQuery'; +import useMessages from 'components/hooks/useMessages'; import styles from './WebsiteMenuView.module.css'; -import useLocale from 'hooks/useLocale'; +import useLocale from 'components/hooks/useLocale'; const views = { url: PagesTable, diff --git a/components/pages/websites/WebsiteMenuView.module.css b/src/components/pages/websites/WebsiteMenuView.module.css similarity index 100% rename from components/pages/websites/WebsiteMenuView.module.css rename to src/components/pages/websites/WebsiteMenuView.module.css diff --git a/components/pages/websites/WebsiteMetricsBar.js b/src/components/pages/websites/WebsiteMetricsBar.js similarity index 66% rename from components/pages/websites/WebsiteMetricsBar.js rename to src/components/pages/websites/WebsiteMetricsBar.js index 3683310c..7ba4a801 100644 --- a/components/pages/websites/WebsiteMetricsBar.js +++ b/src/components/pages/websites/WebsiteMetricsBar.js @@ -1,20 +1,24 @@ import classNames from 'classnames'; -import { Row, Column } from 'react-basics'; -import { formatShortTime } from 'lib/format'; -import MetricCard from 'components/metrics/MetricCard'; -import RefreshButton from 'components/input/RefreshButton'; +import { useApi, useDateRange, useMessages, usePageQuery, useSticky } from 'components/hooks'; import WebsiteDateFilter from 'components/input/WebsiteDateFilter'; +import MetricCard from 'components/metrics/MetricCard'; import MetricsBar from 'components/metrics/MetricsBar'; -import { useApi, useDateRange, usePageQuery, useMessages, useSticky } from 'hooks'; +import FilterSelectForm from 'components/pages/reports/FilterSelectForm'; +import PopupForm from 'components/pages/reports/PopupForm'; +import { formatShortTime } from 'lib/format'; +import { Button, Column, Icon, Icons, Popup, PopupTrigger, Row } from 'react-basics'; import styles from './WebsiteMetricsBar.module.css'; -export function WebsiteMetricsBar({ websiteId, sticky }) { +export function WebsiteMetricsBar({ websiteId, showFilter = true, sticky }) { const { formatMessage, labels } = useMessages(); + const { get, useQuery } = useApi(); const [dateRange] = useDateRange(websiteId); const { startDate, endDate, modified } = dateRange; const { ref, isSticky } = useSticky({ enabled: sticky }); const { + resolveUrl, + router, query: { url, referrer, title, os, browser, device, country, region, city }, } = usePageQuery(); @@ -39,6 +43,17 @@ export function WebsiteMetricsBar({ websiteId, sticky }) { }), ); + const fieldOptions = [ + { name: 'url', type: 'string', label: formatMessage(labels.url) }, + { name: 'referrer', type: 'string', label: formatMessage(labels.referrer) }, + { name: 'browser', type: 'string', label: formatMessage(labels.browser) }, + { name: 'os', type: 'string', label: formatMessage(labels.os) }, + { name: 'device', type: 'string', label: formatMessage(labels.device) }, + { name: 'country', type: 'string', label: formatMessage(labels.country) }, + { name: 'region', type: 'string', label: formatMessage(labels.region) }, + { name: 'city', type: 'string', label: formatMessage(labels.city) }, + ]; + const { pageviews, uniques, bounces, totaltime } = data || {}; const num = Math.min(data && uniques.value, data && bounces.value); const diffs = data && { @@ -48,6 +63,40 @@ export function WebsiteMetricsBar({ websiteId, sticky }) { totaltime: totaltime.value - totaltime.change, }; + const handleAddFilter = ({ name, value }) => { + router.push(resolveUrl({ [name]: value })); + }; + + const WebsiteFilterButton = () => { + return ( + + + + {close => { + return ( + + { + handleAddFilter(value); + close(); + }} + allowFilterSelect={false} + /> + + ); + }} + + + ); + }; + return (
- + {showFilter && }
diff --git a/components/pages/websites/WebsiteMetricsBar.module.css b/src/components/pages/websites/WebsiteMetricsBar.module.css similarity index 100% rename from components/pages/websites/WebsiteMetricsBar.module.css rename to src/components/pages/websites/WebsiteMetricsBar.module.css diff --git a/components/pages/websites/WebsiteReportsPage.js b/src/components/pages/websites/WebsiteReportsPage.js similarity index 83% rename from components/pages/websites/WebsiteReportsPage.js rename to src/components/pages/websites/WebsiteReportsPage.js index b04c50d1..40631839 100644 --- a/components/pages/websites/WebsiteReportsPage.js +++ b/src/components/pages/websites/WebsiteReportsPage.js @@ -1,13 +1,13 @@ -import EmptyPlaceholder from 'components/common/EmptyPlaceholder'; import Page from 'components/layout/Page'; +import Empty from 'components/common/Empty'; import ReportsTable from 'components/pages/reports/ReportsTable'; -import { useMessages, useWebsiteReports } from 'hooks'; +import { useMessages, useWebsiteReports } from 'components/hooks'; import Link from 'next/link'; import { Button, Flexbox, Icon, Icons, Text } from 'react-basics'; import WebsiteHeader from './WebsiteHeader'; export function WebsiteReportsPage({ websiteId }) { - const { formatMessage, labels, messages } = useMessages(); + const { formatMessage, labels } = useMessages(); const { reports, error, @@ -48,7 +48,7 @@ export function WebsiteReportsPage({ websiteId }) { filterValue={filter} /> )} - {!hasData && } + {!hasData && }
); } diff --git a/components/pages/websites/WebsiteTableView.js b/src/components/pages/websites/WebsiteTableView.js similarity index 100% rename from components/pages/websites/WebsiteTableView.js rename to src/components/pages/websites/WebsiteTableView.js diff --git a/components/pages/websites/WebsiteTableView.module.css b/src/components/pages/websites/WebsiteTableView.module.css similarity index 100% rename from components/pages/websites/WebsiteTableView.module.css rename to src/components/pages/websites/WebsiteTableView.module.css diff --git a/components/pages/websites/WebsitesPage.js b/src/components/pages/websites/WebsitesPage.js similarity index 93% rename from components/pages/websites/WebsitesPage.js rename to src/components/pages/websites/WebsitesPage.js index fafc62af..61c29448 100644 --- a/components/pages/websites/WebsitesPage.js +++ b/src/components/pages/websites/WebsitesPage.js @@ -2,9 +2,8 @@ import Page from 'components/layout/Page'; import PageHeader from 'components/layout/PageHeader'; import WebsiteAddForm from 'components/pages/settings/websites/WebsiteAddForm'; import WebsiteList from 'components/pages/settings/websites/WebsitesList'; -import { useMessages } from 'hooks'; -import useUser from 'hooks/useUser'; -import useConfig from 'hooks/useConfig'; +import { useMessages } from 'components/hooks'; +import useUser from 'components/hooks/useUser'; import { ROLES } from 'lib/constants'; import { useState } from 'react'; import { @@ -24,8 +23,8 @@ export function WebsitesPage() { const [tab, setTab] = useState('my-websites'); const [fetch, setFetch] = useState(1); const { user } = useUser(); - const { cloudMode } = useConfig(); const { showToast } = useToasts(); + const cloudMode = Boolean(process.env.cloudMode); const handleSave = async () => { setFetch(fetch + 1); diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 00000000..f2ef13ca --- /dev/null +++ b/src/index.ts @@ -0,0 +1,117 @@ +/* +export * from 'components/common/ConfirmDeleteForm'; +export * from 'components/common/Empty'; +export * from 'components/common/EmptyPlaceholder'; +export * from 'components/common/ErrorBoundary'; +export * from 'components/common/ErrorMessage'; +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'; +export * from 'components/common/SettingsTable'; +export * from 'components/common/UpdateNotice'; +export * from 'components/common/WorldMap'; + +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/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/usePageQuery'; +export * from 'components/hooks/useReport'; +export * from 'components/hooks/useReports'; +export * from 'components/hooks/useRequireLogin'; +export * from 'components/hooks/useShareToken'; +export * from 'components/hooks/useSticky'; +export * from 'components/hooks/useTheme'; +export * from 'components/hooks/useTimezone'; +export * from 'components/hooks/useUser'; +export * from 'components/hooks/useWebsite'; +export * from 'components/hooks/useWebsiteReports'; + +export * from 'components/input/DateFilter'; +export * from 'components/input/LanguageButton'; +export * from 'components/input/LogoutButton'; +export * from 'components/input/MonthSelect'; +export * from 'components/input/ProfileButton'; +export * from 'components/input/RefreshButton'; +export * from 'components/input/SettingsButton'; +export * from 'components/input/ThemeButton'; +export * from 'components/input/WebsiteDateFilter'; +export * from 'components/input/WebsiteSelect'; + +export * from 'components/layout/AppLayout'; +export * from 'components/layout/Footer'; +export * from 'components/layout/Grid'; +export * from 'components/layout/Header'; +export * from 'components/layout/NavBar'; +export * from 'components/layout/NavGroup'; +export * from 'components/layout/Page'; +export * from 'components/layout/PageHeader'; +export * from 'components/layout/ReportsLayout'; +export * from 'components/layout/SettingsLayout'; +export * from 'components/layout/ShareLayout'; +export * from 'components/layout/SideNav'; +*/ + +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/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/usePageQuery'; +export * from 'components/hooks/useReport'; +export * from 'components/hooks/useReports'; +export * from 'components/hooks/useRequireLogin'; +export * from 'components/hooks/useShareToken'; +export * from 'components/hooks/useSticky'; +export * from 'components/hooks/useTheme'; +export * from 'components/hooks/useTimezone'; +export * from 'components/hooks/useUser'; +export * from 'components/hooks/useWebsite'; +export * from 'components/hooks/useWebsiteReports'; + +export * from 'components/pages/settings/teams/TeamAddForm'; +export * from 'components/pages/settings/teams/TeamAddWebsiteForm'; +export * from 'components/pages/settings/teams/TeamDeleteForm'; +export * from 'components/pages/settings/teams/TeamEditForm'; +export * from 'components/pages/settings/teams/TeamJoinForm'; +export * from 'components/pages/settings/teams/TeamLeaveForm'; +export * from 'components/pages/settings/teams/TeamMemberRemoveButton'; +export * from 'components/pages/settings/teams/TeamMembers'; +export * from 'components/pages/settings/teams/TeamMembersTable'; +export * from 'components/pages/settings/teams/TeamSettings'; +export * from 'components/pages/settings/teams/TeamsList'; +export * from 'components/pages/settings/teams/TeamsTable'; +export * from 'components/pages/settings/teams/TeamWebsiteRemoveButton'; +export * from 'components/pages/settings/teams/TeamWebsites'; +export * from 'components/pages/settings/teams/TeamWebsitesTable'; +export * from 'components/pages/settings/teams/WebsiteTags'; + +export * from 'components/pages/settings/websites/ShareUrl'; +export * from 'components/pages/settings/websites/TrackingCode'; +export * from 'components/pages/settings/websites/WebsiteAddForm'; +export * from 'components/pages/settings/websites/WebsiteDeleteForm'; +export * from 'components/pages/settings/websites/WebsiteEditForm'; +export * from 'components/pages/settings/websites/WebsiteResetForm'; +export * from 'components/pages/settings/websites/WebsiteSettings'; +export * from 'components/pages/settings/websites/WebsitesList'; +export * from 'components/pages/settings/websites/WebsitesTable'; diff --git a/lang/am-ET.json b/src/lang/am-ET.json similarity index 100% rename from lang/am-ET.json rename to src/lang/am-ET.json diff --git a/lang/ar-SA.json b/src/lang/ar-SA.json similarity index 100% rename from lang/ar-SA.json rename to src/lang/ar-SA.json diff --git a/lang/be-BY.json b/src/lang/be-BY.json similarity index 100% rename from lang/be-BY.json rename to src/lang/be-BY.json diff --git a/lang/bn-BD.json b/src/lang/bn-BD.json similarity index 100% rename from lang/bn-BD.json rename to src/lang/bn-BD.json diff --git a/lang/ca-ES.json b/src/lang/ca-ES.json similarity index 100% rename from lang/ca-ES.json rename to src/lang/ca-ES.json diff --git a/lang/cs-CZ.json b/src/lang/cs-CZ.json similarity index 100% rename from lang/cs-CZ.json rename to src/lang/cs-CZ.json diff --git a/lang/da-DK.json b/src/lang/da-DK.json similarity index 100% rename from lang/da-DK.json rename to src/lang/da-DK.json diff --git a/lang/de-CH.json b/src/lang/de-CH.json similarity index 100% rename from lang/de-CH.json rename to src/lang/de-CH.json diff --git a/lang/de-DE.json b/src/lang/de-DE.json similarity index 100% rename from lang/de-DE.json rename to src/lang/de-DE.json diff --git a/lang/el-GR.json b/src/lang/el-GR.json similarity index 100% rename from lang/el-GR.json rename to src/lang/el-GR.json diff --git a/lang/en-GB.json b/src/lang/en-GB.json similarity index 100% rename from lang/en-GB.json rename to src/lang/en-GB.json diff --git a/lang/en-US.json b/src/lang/en-US.json similarity index 100% rename from lang/en-US.json rename to src/lang/en-US.json diff --git a/lang/es-ES.json b/src/lang/es-ES.json similarity index 100% rename from lang/es-ES.json rename to src/lang/es-ES.json diff --git a/lang/es-MX.json b/src/lang/es-MX.json similarity index 100% rename from lang/es-MX.json rename to src/lang/es-MX.json diff --git a/lang/fa-IR.json b/src/lang/fa-IR.json similarity index 100% rename from lang/fa-IR.json rename to src/lang/fa-IR.json diff --git a/lang/fi-FI.json b/src/lang/fi-FI.json similarity index 100% rename from lang/fi-FI.json rename to src/lang/fi-FI.json diff --git a/lang/fo-FO.json b/src/lang/fo-FO.json similarity index 100% rename from lang/fo-FO.json rename to src/lang/fo-FO.json diff --git a/lang/fr-FR.json b/src/lang/fr-FR.json similarity index 100% rename from lang/fr-FR.json rename to src/lang/fr-FR.json diff --git a/lang/ga-ES.json b/src/lang/ga-ES.json similarity index 100% rename from lang/ga-ES.json rename to src/lang/ga-ES.json diff --git a/lang/he-IL.json b/src/lang/he-IL.json similarity index 100% rename from lang/he-IL.json rename to src/lang/he-IL.json diff --git a/lang/hi-IN.json b/src/lang/hi-IN.json similarity index 100% rename from lang/hi-IN.json rename to src/lang/hi-IN.json diff --git a/lang/hr-HR.json b/src/lang/hr-HR.json similarity index 100% rename from lang/hr-HR.json rename to src/lang/hr-HR.json diff --git a/lang/hu-HU.json b/src/lang/hu-HU.json similarity index 100% rename from lang/hu-HU.json rename to src/lang/hu-HU.json diff --git a/lang/id-ID.json b/src/lang/id-ID.json similarity index 100% rename from lang/id-ID.json rename to src/lang/id-ID.json diff --git a/lang/it-IT.json b/src/lang/it-IT.json similarity index 100% rename from lang/it-IT.json rename to src/lang/it-IT.json diff --git a/src/lang/ja-JP.json b/src/lang/ja-JP.json new file mode 100644 index 00000000..770f6f07 --- /dev/null +++ b/src/lang/ja-JP.json @@ -0,0 +1,211 @@ +{ + "label.access-code": "アクセスコード", + "label.actions": "アクション", + "label.activity-log": "アクティビティログ", + "label.add": "追加", + "label.add-description": "説明を追加", + "label.add-website": "Webサイトの追加", + "label.admin": "管理者", + "label.after": "直後", + "label.all": "すべて", + "label.all-time": "すべての時間帯", + "label.analytics": "アナリティクス", + "label.average": "平均", + "label.average-visit-time": "平均滞在時間", + "label.back": "戻る", + "label.before": "直前", + "label.bounce-rate": "直帰率", + "label.breakdown": "故障", + "label.browser": "ブラウザ", + "label.browsers": "ブラウザ", + "label.cancel": "キャンセル", + "label.change-password": "パスワードの変更", + "label.cities": "都市", + "label.city": "都市", + "label.clear-all": "すべてクリア", + "label.confirm": "確認", + "label.confirm-password": "パスワード(確認)", + "label.contains": "コンテンツ", + "label.continue": "続ける", + "label.countries": "国名", + "label.country": "国", + "label.create-report": "レポートの作成", + "label.create-team": "チームの作成", + "label.create-user": "ユーザーの作成", + "label.created": "作成されました", + "label.current-password": "現在のパスワード", + "label.custom-range": "範囲指定", + "label.dashboard": "ダッシュボード", + "label.data": "データ", + "label.date": "日付", + "label.date-range": "期間", + "label.day": "日", + "label.default-date-range": "デフォルトの期間", + "label.delete": "削除", + "label.delete-team": "チームの削除", + "label.delete-user": "ユーザーの削除", + "label.delete-website": "Webサイトの削除", + "label.description": "説明", + "label.desktop": "デスクトップ", + "label.details": "詳細情報", + "label.device": "デバイス", + "label.devices": "デバイス", + "label.dismiss": "却下", + "label.does-not-contain": "を含まない", + "label.domain": "ドメイン", + "label.dropoff": "切り捨て", + "label.edit": "編集", + "label.edit-dashboard": "ダッシュボードの編集", + "label.enable-share-url": "共有URLを有効にする", + "label.event": "イベント", + "label.event-data": "イベントデータ", + "label.events": "イベント", + "label.false": "偽", + "label.field": "フィールド", + "label.fields": "フィールド", + "label.filter-combined": "統合", + "label.filter-raw": "RAW", + "label.filters": "フィルター", + "label.funnel": "分析", + "label.greater-than": "超過", + "label.greater-than-equals": "以上", + "label.insights": "見通し", + "label.is": "に等しい", + "label.is-not": "に等しくない", + "label.is-not-set": "未設定", + "label.is-set": "設定済み", + "label.join": "参加", + "label.join-team": "チームに参加", + "label.language": "言語", + "label.languages": "言語", + "label.laptop": "ノートPC", + "label.last-days": "過去{x}日間", + "label.last-hours": "過去{x}時間", + "label.leave": "離脱", + "label.leave-team": "チームを離脱", + "label.less-than": "未満", + "label.less-than-equals": "以下", + "label.login": "ログイン", + "label.logout": "ログアウト", + "label.max": "最大", + "label.members": "メンバー", + "label.min": "最小", + "label.mobile": "携帯電話", + "label.more": "もっと見る", + "label.my-websites": "マイWebサイト", + "label.name": "名前", + "label.new-password": "新しいパスワード", + "label.none": "なし", + "label.os": "OS", + "label.overview": "概要", + "label.owner": "所有者", + "label.page-of": "ページ {current}/{total}", + "label.page-views": "閲覧数", + "label.pageTitle": "ページタイトル", + "label.pages": "ページ", + "label.password": "パスワード", + "label.powered-by": "Powered by {name}", + "label.profile": "プロフィール", + "label.queries": "クエリ", + "label.query": "クエリ", + "label.query-parameters": "クエリパラメーター", + "label.realtime": "リアルタイム", + "label.referrer": "リファラー", + "label.referrers": "リファラー", + "label.refresh": "更新", + "label.regenerate": "再生成", + "label.region": "地域", + "label.regions": "地域", + "label.remove": "削除", + "label.reports": "レポート", + "label.required": "必須", + "label.reset": "リセット", + "label.reset-website": "Webサイトをリセットする", + "label.retention": "保持", + "label.role": "ロール", + "label.run-query": "クエリ実行", + "label.save": "保存", + "label.screens": "画面サイズ", + "label.select-date": "日付を選択", + "label.select-website": "Webサイトを選択", + "label.sessions": "セッション", + "label.settings": "設定", + "label.share-url": "共有URL", + "label.single-day": "一日", + "label.sum": "合計", + "label.tablet": "タブレット", + "label.team": "チーム", + "label.team-guest": "チームゲスト", + "label.team-id": "チームID", + "label.team-member": "チームメンバー", + "label.team-name": "チーム名", + "label.team-owner": "チーム所有者", + "label.team-websites": "チームのWebサイト", + "label.teams": "チーム", + "label.theme": "テーマ", + "label.this-month": "今月", + "label.this-week": "今週", + "label.this-year": "今年", + "label.timezone": "タイムゾーン", + "label.title": "タイトル", + "label.today": "今日", + "label.toggle-charts": "グラフを切り替える", + "label.total": "累計", + "label.total-records": "総記録数", + "label.tracking-code": "トラッキングコード", + "label.true": "真", + "label.type": "種別", + "label.unique": "ユニーク", + "label.unique-visitors": "ユニーク訪問者数", + "label.unknown": "不明", + "label.untitled": "無題", + "label.url": "URL", + "label.urls": "URL", + "label.user": "ユーザー", + "label.username": "ユーザー名", + "label.users": "ユーザー", + "label.value": "値", + "label.view": "表示", + "label.view-details": "詳細を表示", + "label.view-only": "表示のみ", + "label.views": "表示", + "label.visitors": "訪問者", + "label.website": "Webサイト", + "label.website-id": "WebサイトID", + "label.websites": "Webサイト", + "label.window": "ウィンドウ", + "label.yesterday": "昨日", + "message.active-users": "{x} {x, plural, one {アクティブな訪問者} other {アクティブな訪問者}}", + "message.confirm-delete": "{target}を削除してもよろしいですか?", + "message.confirm-leave": "{target}から離脱してもよろしいですか?", + "message.confirm-reset": "{target}をリセットしてもよろしいですか?", + "message.delete-account": "このアカウントを削除するには、下のフォームに「{confirmation}」と入力してください。", + "message.delete-website": "このWebサイトを削除するには、下のフォームに「{confirmation}」と入力してください。", + "message.delete-website-warning": "Webサイトのデータがすべて削除されます。", + "message.error": "未知のエラーが発生しました。", + "message.event-log": "{url}の{event}", + "message.go-to-settings": "設定に移動する", + "message.incorrect-username-password": "ユーザー名またはパスワードが間違っています。", + "message.invalid-domain": "無効なドメインです。http/httpsを含めないでください。", + "message.min-password-length": "最小文字数は{n}文字です", + "message.new-version-available": "Umamiの新しいバージョン{version}が利用可能です!", + "message.no-data-available": "データがありません。", + "message.no-event-data": "イベントデータがありません。", + "message.no-match-password": "パスワードが一致しません。", + "message.no-results-found": "結果が見つかりません。", + "message.no-team-websites": "このチームにはWebサイトがありません。", + "message.no-teams": "チームを作成していません。", + "message.no-users": "ユーザーが存在しません。", + "message.no-websites-configured": "Webサイトが設定されていません。", + "message.page-not-found": "ページが見つかりません", + "message.reset-website": "このWebサイトをリセットするには、下のフォームに「{confirmation}」と入力してください。", + "message.reset-website-warning": "このWebサイトの統計情報はすべて削除されますが、設定はそのまま残ります。", + "message.saved": "保存されました。", + "message.share-url": "あなたのWebサイトの統計情報は次のURLで公開されています:", + "message.team-already-member": "あなたはすでにチームのメンバーです。", + "message.team-not-found": "チームが見つかりません。", + "message.team-websites-info": "Webサイトはチーム内の誰でも見ることができます。", + "message.tracking-code": "このWebサイトの統計情報を追跡するには、HTMLの...セクションに以下のコードを記述します。", + "message.user-deleted": "ユーザーが削除されました。", + "message.visitor-log": "{os}({device})で{browser}を使用している{country}からの訪問者" +} diff --git a/lang/km-KH.json b/src/lang/km-KH.json similarity index 100% rename from lang/km-KH.json rename to src/lang/km-KH.json diff --git a/lang/ko-KR.json b/src/lang/ko-KR.json similarity index 100% rename from lang/ko-KR.json rename to src/lang/ko-KR.json diff --git a/lang/lt-LT.json b/src/lang/lt-LT.json similarity index 100% rename from lang/lt-LT.json rename to src/lang/lt-LT.json diff --git a/lang/mn-MN.json b/src/lang/mn-MN.json similarity index 100% rename from lang/mn-MN.json rename to src/lang/mn-MN.json diff --git a/lang/ms-MY.json b/src/lang/ms-MY.json similarity index 100% rename from lang/ms-MY.json rename to src/lang/ms-MY.json diff --git a/lang/my-MM.json b/src/lang/my-MM.json similarity index 100% rename from lang/my-MM.json rename to src/lang/my-MM.json diff --git a/lang/nb-NO.json b/src/lang/nb-NO.json similarity index 100% rename from lang/nb-NO.json rename to src/lang/nb-NO.json diff --git a/lang/nl-NL.json b/src/lang/nl-NL.json similarity index 100% rename from lang/nl-NL.json rename to src/lang/nl-NL.json diff --git a/lang/pl-PL.json b/src/lang/pl-PL.json similarity index 100% rename from lang/pl-PL.json rename to src/lang/pl-PL.json diff --git a/lang/pt-BR.json b/src/lang/pt-BR.json similarity index 100% rename from lang/pt-BR.json rename to src/lang/pt-BR.json diff --git a/lang/pt-PT.json b/src/lang/pt-PT.json similarity index 100% rename from lang/pt-PT.json rename to src/lang/pt-PT.json diff --git a/lang/ro-RO.json b/src/lang/ro-RO.json similarity index 100% rename from lang/ro-RO.json rename to src/lang/ro-RO.json diff --git a/lang/ru-RU.json b/src/lang/ru-RU.json similarity index 100% rename from lang/ru-RU.json rename to src/lang/ru-RU.json diff --git a/lang/si-LK.json b/src/lang/si-LK.json similarity index 100% rename from lang/si-LK.json rename to src/lang/si-LK.json diff --git a/lang/sk-SK.json b/src/lang/sk-SK.json similarity index 100% rename from lang/sk-SK.json rename to src/lang/sk-SK.json diff --git a/lang/sl-SI.json b/src/lang/sl-SI.json similarity index 100% rename from lang/sl-SI.json rename to src/lang/sl-SI.json diff --git a/lang/sv-SE.json b/src/lang/sv-SE.json similarity index 100% rename from lang/sv-SE.json rename to src/lang/sv-SE.json diff --git a/lang/ta-IN.json b/src/lang/ta-IN.json similarity index 100% rename from lang/ta-IN.json rename to src/lang/ta-IN.json diff --git a/lang/th-TH.json b/src/lang/th-TH.json similarity index 100% rename from lang/th-TH.json rename to src/lang/th-TH.json diff --git a/lang/tr-TR.json b/src/lang/tr-TR.json similarity index 100% rename from lang/tr-TR.json rename to src/lang/tr-TR.json diff --git a/lang/uk-UA.json b/src/lang/uk-UA.json similarity index 100% rename from lang/uk-UA.json rename to src/lang/uk-UA.json diff --git a/lang/ur-PK.json b/src/lang/ur-PK.json similarity index 100% rename from lang/ur-PK.json rename to src/lang/ur-PK.json diff --git a/lang/vi-VN.json b/src/lang/vi-VN.json similarity index 100% rename from lang/vi-VN.json rename to src/lang/vi-VN.json diff --git a/lang/zh-CN.json b/src/lang/zh-CN.json similarity index 100% rename from lang/zh-CN.json rename to src/lang/zh-CN.json diff --git a/lang/zh-TW.json b/src/lang/zh-TW.json similarity index 100% rename from lang/zh-TW.json rename to src/lang/zh-TW.json diff --git a/lib/auth.ts b/src/lib/auth.ts similarity index 90% rename from lib/auth.ts rename to src/lib/auth.ts index 10f7fbca..4a42d85d 100644 --- a/lib/auth.ts +++ b/src/lib/auth.ts @@ -4,11 +4,12 @@ import debug from 'debug'; import { PERMISSIONS, ROLE_PERMISSIONS, SHARE_TOKEN_HEADER } from 'lib/constants'; import { secret } from 'lib/crypto'; import { createSecureToken, ensureArray, getRandomChars, parseToken } from 'next-basics'; -import { getTeamUser, getTeamWebsite, findTeamWebsiteByUserId } from 'queries'; +import { findTeamWebsiteByUserId, getTeamUser, getTeamWebsite } from 'queries'; import { loadWebsite } from './load'; import { Auth } from './types'; const log = debug('umami:auth'); +const cloudMode = process.env.CLOUD_MODE; export async function setAuthKey(user, expire = 0) { const authKey = `auth:${getRandomChars(32)}`; @@ -57,7 +58,15 @@ export async function canViewWebsite({ user, shareToken }: Auth, websiteId: stri return !!(await findTeamWebsiteByUserId(websiteId, user.id)); } -export async function canCreateWebsite({ user }: Auth) { +export async function canCreateWebsite({ user, grant }: Auth) { + if (cloudMode) { + if (grant?.find(a => a === PERMISSIONS.websiteCreate)) { + return true; + } + + return false; + } + if (user.isAdmin) { return true; } @@ -109,7 +118,15 @@ export async function canDeleteReport(auth: Auth, report: Report) { return canUpdateReport(auth, report); } -export async function canCreateTeam({ user }: Auth) { +export async function canCreateTeam({ user, grant }: Auth) { + if (cloudMode) { + if (grant?.find(a => a === PERMISSIONS.teamCreate)) { + return true; + } + + return false; + } + if (user.isAdmin) { return true; } diff --git a/lib/cache.ts b/src/lib/cache.ts similarity index 72% rename from lib/cache.ts rename to src/lib/cache.ts index bc46c23d..c54eda2e 100644 --- a/lib/cache.ts +++ b/src/lib/cache.ts @@ -2,17 +2,20 @@ import { User, Website } from '@prisma/client'; import redis from '@umami/redis-client'; import { getSession, getUserById, getWebsiteById } from '../queries'; -const { fetchObject, storeObject, deleteObject } = redis; +const { fetchObject, storeObject, deleteObject, expire } = redis; async function fetchWebsite(id): Promise { - return fetchObject(`website:${id}`, () => getWebsiteById(id)); + return fetchObject(`website:${id}`, () => getWebsiteById(id), 86400); } async function storeWebsite(data) { const { id } = data; const key = `website:${id}`; - return storeObject(key, data); + const obj = await storeObject(key, data); + await expire(key, 86400); + + return obj; } async function deleteWebsite(id) { @@ -20,14 +23,17 @@ async function deleteWebsite(id) { } async function fetchUser(id): Promise { - return fetchObject(`user:${id}`, () => getUserById(id, { includePassword: true })); + return fetchObject(`user:${id}`, () => getUserById(id, { includePassword: true }), 86400); } async function storeUser(data) { const { id } = data; const key = `user:${id}`; - return storeObject(key, data); + const obj = await storeObject(key, data); + await expire(key, 86400); + + return obj; } async function deleteUser(id) { @@ -35,14 +41,17 @@ async function deleteUser(id) { } async function fetchSession(id) { - return fetchObject(`session:${id}`, () => getSession(id)); + return fetchObject(`session:${id}`, () => getSession(id), 86400); } async function storeSession(data) { const { id } = data; const key = `session:${id}`; - return storeObject(key, data); + const obj = await storeObject(key, data); + await expire(key, 86400); + + return obj; } async function deleteSession(id) { diff --git a/lib/charts.js b/src/lib/charts.js similarity index 100% rename from lib/charts.js rename to src/lib/charts.js diff --git a/lib/clickhouse.ts b/src/lib/clickhouse.ts similarity index 100% rename from lib/clickhouse.ts rename to src/lib/clickhouse.ts diff --git a/lib/client.ts b/src/lib/client.ts similarity index 100% rename from lib/client.ts rename to src/lib/client.ts diff --git a/lib/constants.ts b/src/lib/constants.ts similarity index 100% rename from lib/constants.ts rename to src/lib/constants.ts diff --git a/lib/crypto.js b/src/lib/crypto.js similarity index 100% rename from lib/crypto.js rename to src/lib/crypto.js diff --git a/lib/data.ts b/src/lib/data.ts similarity index 100% rename from lib/data.ts rename to src/lib/data.ts diff --git a/lib/date.js b/src/lib/date.ts similarity index 79% rename from lib/date.js rename to src/lib/date.ts index 49bff897..14f0e13c 100644 --- a/lib/date.js +++ b/src/lib/date.ts @@ -29,9 +29,19 @@ import { max, min, isDate, + subWeeks, } from 'date-fns'; import { getDateLocale } from 'lib/lang'; +export const TIME_UNIT = { + minute: 'minute', + hour: 'hour', + day: 'day', + week: 'week', + month: 'month', + year: 'year', +}; + const dateFuncs = { minute: [differenceInMinutes, addMinutes, startOfMinute], hour: [differenceInHours, addHours, startOfHour], @@ -81,6 +91,7 @@ export function parseDateRange(value, locale = 'en-US') { if (!match) return null; const { num, unit } = match.groups; + const selectedUnit = { num, unit }; if (+num === 1) { switch (unit) { @@ -90,6 +101,7 @@ export function parseDateRange(value, locale = 'en-US') { endDate: endOfDay(now), unit: 'hour', value, + selectedUnit, }; case 'week': return { @@ -97,6 +109,7 @@ export function parseDateRange(value, locale = 'en-US') { endDate: endOfWeek(now, { locale: dateLocale }), unit: 'day', value, + selectedUnit, }; case 'month': return { @@ -104,6 +117,7 @@ export function parseDateRange(value, locale = 'en-US') { endDate: endOfMonth(now), unit: 'day', value, + selectedUnit, }; case 'year': return { @@ -111,6 +125,7 @@ export function parseDateRange(value, locale = 'en-US') { endDate: endOfYear(now), unit: 'month', value, + selectedUnit, }; } } @@ -123,6 +138,7 @@ export function parseDateRange(value, locale = 'en-US') { endDate: subDays(endOfDay(now), 1), unit: 'hour', value, + selectedUnit, }; case 'week': return { @@ -130,6 +146,7 @@ export function parseDateRange(value, locale = 'en-US') { endDate: subDays(endOfWeek(now, { locale: dateLocale }), 1), unit: 'day', value, + selectedUnit, }; case 'month': return { @@ -137,6 +154,7 @@ export function parseDateRange(value, locale = 'en-US') { endDate: subMonths(endOfMonth(now), 1), unit: 'day', value, + selectedUnit, }; case 'year': return { @@ -144,6 +162,7 @@ export function parseDateRange(value, locale = 'en-US') { endDate: subYears(endOfYear(now), 1), unit: 'month', value, + selectedUnit, }; } } @@ -155,6 +174,7 @@ export function parseDateRange(value, locale = 'en-US') { endDate: endOfDay(now), unit, value, + selectedUnit, }; case 'hour': return { @@ -162,6 +182,53 @@ export function parseDateRange(value, locale = 'en-US') { endDate: endOfHour(now), unit, value, + selectedUnit, + }; + } +} + +export function incrementDateRange(value, increment) { + const { startDate, endDate, selectedUnit } = value; + + const { num, unit } = selectedUnit; + + const sub = num * increment; + + switch (unit) { + case 'hour': + return { + ...value, + startDate: subHours(startDate, sub), + endDate: subHours(endDate, sub), + value: 'range', + }; + case 'day': + return { + ...value, + startDate: subDays(startDate, sub), + endDate: subDays(endDate, sub), + value: 'range', + }; + case 'week': + return { + ...value, + startDate: subWeeks(startDate, sub), + endDate: subWeeks(endDate, sub), + value: 'range', + }; + case 'month': + return { + ...value, + startDate: subMonths(startDate, sub), + endDate: subMonths(endDate, sub), + value: 'range', + }; + case 'year': + return { + ...value, + startDate: subYears(startDate, sub), + endDate: subYears(endDate, sub), + value: 'range', }; } } @@ -237,7 +304,7 @@ export function getDateLength(startDate, endDate, unit) { return diff(endDate, startDate) + 1; } -export const customFormats = { +export const CUSTOM_FORMATS = { 'en-US': { p: 'ha', pp: 'h:mm:ss', @@ -252,7 +319,7 @@ export const customFormats = { export function formatDate(date, str, locale = 'en-US') { return format( typeof date === 'string' ? new Date(date) : date, - customFormats?.[locale]?.[str] || str, + CUSTOM_FORMATS?.[locale]?.[str] || str, { locale: getDateLocale(locale), }, diff --git a/lib/db.js b/src/lib/db.js similarity index 100% rename from lib/db.js rename to src/lib/db.js diff --git a/lib/detect.ts b/src/lib/detect.ts similarity index 80% rename from lib/detect.ts rename to src/lib/detect.ts index 43dac649..86f812bd 100644 --- a/lib/detect.ts +++ b/src/lib/detect.ts @@ -65,19 +65,27 @@ export async function getLocation(ip, req) { // Cloudflare headers if (req.headers['cf-ipcountry']) { + const country = safeDecodeURIComponent(req.headers['cf-ipcountry']); + const subdivision1 = safeDecodeURIComponent(req.headers['cf-region-code']); + const city = safeDecodeURIComponent(req.headers['cf-ipcity']); + return { - country: safeDecodeURIComponent(req.headers['cf-ipcountry']), - subdivision1: safeDecodeURIComponent(req.headers['cf-region-code']), - city: safeDecodeURIComponent(req.headers['cf-ipcity']), + country, + subdivision1: subdivision1.includes('-') ? subdivision1 : `${country}-${subdivision1}`, + city, }; } // Vercel headers if (req.headers['x-vercel-ip-country']) { + const country = safeDecodeURIComponent(req.headers['x-vercel-ip-country']); + const subdivision1 = safeDecodeURIComponent(req.headers['x-vercel-ip-country-region']); + const city = safeDecodeURIComponent(req.headers['x-vercel-ip-city']); + return { - country: safeDecodeURIComponent(req.headers['x-vercel-ip-country']), - subdivision1: safeDecodeURIComponent(req.headers['x-vercel-ip-country-region']), - city: safeDecodeURIComponent(req.headers['x-vercel-ip-city']), + country, + subdivision1: subdivision1.includes('-') ? subdivision1 : `${country}-${subdivision1}`, + city, }; } diff --git a/lib/filters.js b/src/lib/filters.js similarity index 100% rename from lib/filters.js rename to src/lib/filters.js diff --git a/lib/format.js b/src/lib/format.js similarity index 100% rename from lib/format.js rename to src/lib/format.js diff --git a/lib/kafka.ts b/src/lib/kafka.ts similarity index 100% rename from lib/kafka.ts rename to src/lib/kafka.ts diff --git a/lib/lang.js b/src/lib/lang.js similarity index 100% rename from lib/lang.js rename to src/lib/lang.js diff --git a/lib/load.ts b/src/lib/load.ts similarity index 100% rename from lib/load.ts rename to src/lib/load.ts diff --git a/lib/middleware.ts b/src/lib/middleware.ts similarity index 74% rename from lib/middleware.ts rename to src/lib/middleware.ts index 414cab23..edf3e929 100644 --- a/lib/middleware.ts +++ b/src/lib/middleware.ts @@ -1,19 +1,20 @@ +import redis from '@umami/redis-client'; +import cors from 'cors'; +import debug from 'debug'; +import { getAuthToken, parseShareToken } from 'lib/auth'; +import { ROLES } from 'lib/constants'; +import { isUuid, secret } from 'lib/crypto'; +import { findSession } from 'lib/session'; import { - createMiddleware, - unauthorized, badRequest, + createMiddleware, parseSecureToken, tooManyRequest, + unauthorized, } from 'next-basics'; -import debug from 'debug'; -import cors from 'cors'; -import redis from '@umami/redis-client'; -import { findSession } from 'lib/session'; -import { getAuthToken, parseShareToken } from 'lib/auth'; -import { secret, isUuid } from 'lib/crypto'; -import { ROLES } from 'lib/constants'; -import { getUserById } from '../queries'; import { NextApiRequestCollect } from 'pages/api/send'; +import { getUserById } from '../queries'; +import { NextApiRequestQueryBody } from './types'; const log = debug('umami:middleware'); @@ -50,7 +51,7 @@ export const useAuth = createMiddleware(async (req, res, next) => { const shareToken = await parseShareToken(req); let user = null; - const { userId, authKey } = payload || {}; + const { userId, authKey, grant } = payload || {}; if (isUuid(userId)) { user = await getUserById(userId); @@ -59,7 +60,7 @@ export const useAuth = createMiddleware(async (req, res, next) => { } if (process.env.NODE_ENV === 'development') { - log({ token, shareToken, payload, user }); + log({ token, shareToken, payload, user, grant }); } if (!user?.id && !shareToken) { @@ -71,7 +72,25 @@ export const useAuth = createMiddleware(async (req, res, next) => { user.isAdmin = user.role === ROLES.admin; } - (req as any).auth = { user, token, shareToken, authKey }; + (req as any).auth = { + user, + grant, + token, + shareToken, + authKey, + }; + + next(); +}); + +export const useValidate = createMiddleware(async (req: any, res, next) => { + try { + const { yup } = req as NextApiRequestQueryBody; + + yup[req.method] && yup[req.method].validateSync({ ...req.query, ...req.body }); + } catch (e: any) { + return badRequest(res, e.message); + } next(); }); diff --git a/lib/prisma.ts b/src/lib/prisma.ts similarity index 98% rename from lib/prisma.ts rename to src/lib/prisma.ts index 12bafa51..a9832c28 100644 --- a/lib/prisma.ts +++ b/src/lib/prisma.ts @@ -185,7 +185,7 @@ function getPageFilters(filters: SearchFilter): [ orderBy: string; }, ] { - const { pageSize = 10, page = 1, orderBy } = filters; + const { pageSize = 10, page = 1, orderBy } = filters || {}; return [ { diff --git a/lib/query.ts b/src/lib/query.ts similarity index 100% rename from lib/query.ts rename to src/lib/query.ts diff --git a/lib/session.ts b/src/lib/session.ts similarity index 75% rename from lib/session.ts rename to src/lib/session.ts index 5eb7398a..85c173c5 100644 --- a/lib/session.ts +++ b/src/lib/session.ts @@ -1,12 +1,27 @@ -import { secret, uuid, isUuid } from 'lib/crypto'; +import { isUuid, secret, uuid } from 'lib/crypto'; import { getClientInfo, getJsonBody } from 'lib/detect'; import { parseToken } from 'next-basics'; import { CollectRequestBody, NextApiRequestCollect } from 'pages/api/send'; import { createSession } from 'queries'; import cache from './cache'; +import clickhouse from './clickhouse'; import { loadSession, loadWebsite } from './load'; -export async function findSession(req: NextApiRequestCollect) { +export async function findSession(req: NextApiRequestCollect): Promise<{ + id: any; + websiteId: string; + hostname: string; + browser: string; + os: any; + device: string; + screen: string; + language: string; + country: any; + subdivision1: any; + subdivision2: any; + city: any; + ownerId: string; +}> { const { payload } = getJsonBody(req); if (!payload) { @@ -53,6 +68,25 @@ export async function findSession(req: NextApiRequestCollect) { const sessionId = uuid(websiteId, hostname, ip, userAgent); + // Clickhouse does not require session lookup + if (clickhouse.enabled) { + return { + id: sessionId, + websiteId, + hostname, + browser, + os: os as any, + device, + screen, + language, + country, + subdivision1, + subdivision2, + city, + ownerId: website.userId, + }; + } + // Find session let session = await loadSession(sessionId); diff --git a/lib/sql.ts b/src/lib/sql.ts similarity index 100% rename from lib/sql.ts rename to src/lib/sql.ts diff --git a/lib/types.ts b/src/lib/types.ts similarity index 87% rename from lib/types.ts rename to src/lib/types.ts index 3f3ac533..3685753e 100644 --- a/lib/types.ts +++ b/src/lib/types.ts @@ -4,20 +4,29 @@ import { DATA_TYPE, EVENT_TYPE, KAFKA_TOPIC, + PERMISSIONS, REPORT_FILTER_TYPES, + REPORT_TYPES, ROLES, TEAM_FILTER_TYPES, USER_FILTER_TYPES, WEBSITE_FILTER_TYPES, } from './constants'; +import * as yup from 'yup'; +import { TIME_UNIT } from './date'; type ObjectValues = T[keyof T]; +export type TimeUnit = ObjectValues; +export type Permission = ObjectValues; + export type CollectionType = ObjectValues; export type Role = ObjectValues; export type EventType = ObjectValues; export type DynamicDataType = ObjectValues; export type KafkaTopic = ObjectValues; +export type ReportType = ObjectValues; + export type ReportSearchFilterType = ObjectValues; export type UserSearchFilterType = ObjectValues; export type WebsiteSearchFilterType = ObjectValues; @@ -47,8 +56,8 @@ export interface ReportSearchFilter extends SearchFilter export interface SearchFilter { filter?: string; filterType?: T; - pageSize?: number; - page?: number; + pageSize: number; + page: number; orderBy?: string; } @@ -71,16 +80,25 @@ export interface Auth { role: string; isAdmin: boolean; }; + grant?: Permission[]; shareToken?: { websiteId: string; }; } +export interface YupRequest { + GET?: yup.ObjectSchema; + POST?: yup.ObjectSchema; + PUT?: yup.ObjectSchema; + DELETE?: yup.ObjectSchema; +} + export interface NextApiRequestQueryBody extends NextApiRequest { auth?: Auth; query: TQuery & { [key: string]: string | string[] }; body: TBody; headers: any; + yup: YupRequest; } export interface NextApiRequestAuth extends NextApiRequest { @@ -168,8 +186,9 @@ export interface RealtimeUpdate { export interface DateRange { startDate: Date; endDate: Date; - unit: string; value: string; + unit?: TimeUnit; + selectedUnit?: TimeUnit; } export interface QueryFilters { diff --git a/src/lib/yup.ts b/src/lib/yup.ts new file mode 100644 index 00000000..a9d21028 --- /dev/null +++ b/src/lib/yup.ts @@ -0,0 +1,19 @@ +import * as yup from 'yup'; + +export function getDateRangeValidation() { + return { + startAt: yup.number().integer().required(), + endAt: yup.number().integer().moreThan(yup.ref('startAt')).required(), + }; +} + +// ex: /funnel|insights|retention/i +export function getFilterValidation(matchRegex) { + return { + filter: yup.string(), + filterType: yup.string().matches(matchRegex), + pageSize: yup.number().integer().positive().max(200), + page: yup.number().integer().positive(), + orderBy: yup.string(), + }; +} diff --git a/pages/404.js b/src/pages/404.js similarity index 90% rename from pages/404.js rename to src/pages/404.js index 4a5f4bd9..8fa13a9c 100644 --- a/pages/404.js +++ b/src/pages/404.js @@ -1,6 +1,6 @@ import { Row, Column, Flexbox } from 'react-basics'; import AppLayout from 'components/layout/AppLayout'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; export default function Custom404() { const { formatMessage, labels } = useMessages(); diff --git a/pages/_app.js b/src/pages/_app.js similarity index 86% rename from pages/_app.js rename to src/pages/_app.js index 8d549773..7022772c 100644 --- a/pages/_app.js +++ b/src/pages/_app.js @@ -5,8 +5,7 @@ import Head from 'next/head'; import Script from 'next/script'; import { useRouter } from 'next/router'; import ErrorBoundary from 'components/common/ErrorBoundary'; -import useLocale from 'hooks/useLocale'; -import useConfig from 'hooks/useConfig'; +import useLocale from 'components/hooks/useLocale'; import '@fontsource/inter/400.css'; import '@fontsource/inter/700.css'; import 'react-basics/dist/styles.css'; @@ -27,22 +26,10 @@ const client = new QueryClient({ export default function App({ Component, pageProps }) { const { locale, messages } = useLocale(); const { basePath, pathname } = useRouter(); - const config = useConfig(); - - const Wrapper = ({ children }) => {children}; - - if (config?.uiDisabled) { - return null; - } return ( - null} - > + null}> diff --git a/pages/api/auth/login.ts b/src/pages/api/auth/login.ts similarity index 85% rename from pages/api/auth/login.ts rename to src/pages/api/auth/login.ts index b9a2be00..47521084 100644 --- a/pages/api/auth/login.ts +++ b/src/pages/api/auth/login.ts @@ -1,19 +1,20 @@ +import redis from '@umami/redis-client'; import debug from 'debug'; +import { setAuthKey } from 'lib/auth'; +import { secret } from 'lib/crypto'; +import { useValidate } from 'lib/middleware'; +import { NextApiRequestQueryBody, User } from 'lib/types'; import { NextApiResponse } from 'next'; import { - ok, - unauthorized, - badRequest, checkPassword, createSecureToken, - methodNotAllowed, forbidden, + methodNotAllowed, + ok, + unauthorized, } from 'next-basics'; -import redis from '@umami/redis-client'; import { getUserByUsername } from 'queries'; -import { secret } from 'lib/crypto'; -import { NextApiRequestQueryBody, User } from 'lib/types'; -import { setAuthKey } from 'lib/auth'; +import * as yup from 'yup'; const log = debug('umami:auth'); @@ -27,6 +28,13 @@ export interface LoginResponse { user: User; } +const schema = { + POST: yup.object().shape({ + username: yup.string().required(), + password: yup.string().required(), + }), +}; + export default async ( req: NextApiRequestQueryBody, res: NextApiResponse, @@ -35,13 +43,12 @@ export default async ( return forbidden(res); } + req.yup = schema; + await useValidate(req, res); + if (req.method === 'POST') { const { username, password } = req.body; - if (!username || !password) { - return badRequest(res); - } - const user = await getUserByUsername(username, { includePassword: true }); if (user && checkPassword(password, user.password)) { diff --git a/pages/api/auth/logout.ts b/src/pages/api/auth/logout.ts similarity index 100% rename from pages/api/auth/logout.ts rename to src/pages/api/auth/logout.ts diff --git a/pages/api/auth/sso.ts b/src/pages/api/auth/sso.ts similarity index 100% rename from pages/api/auth/sso.ts rename to src/pages/api/auth/sso.ts diff --git a/pages/api/auth/verify.ts b/src/pages/api/auth/verify.ts similarity index 100% rename from pages/api/auth/verify.ts rename to src/pages/api/auth/verify.ts diff --git a/pages/api/config.ts b/src/pages/api/config.ts similarity index 81% rename from pages/api/config.ts rename to src/pages/api/config.ts index bccfd048..adba894a 100644 --- a/pages/api/config.ts +++ b/src/pages/api/config.ts @@ -2,21 +2,19 @@ import { NextApiRequest, NextApiResponse } from 'next'; import { ok, methodNotAllowed } from 'next-basics'; export interface ConfigResponse { - basePath: string; - trackerScriptName: string; - updatesDisabled: boolean; telemetryDisabled: boolean; - cloudMode: boolean; + trackerScriptName: string; + uiDisabled: boolean; + updatesDisabled: boolean; } export default async (req: NextApiRequest, res: NextApiResponse) => { if (req.method === 'GET') { return ok(res, { - basePath: process.env.BASE_PATH || '', - trackerScriptName: process.env.TRACKER_SCRIPT_NAME, - updatesDisabled: !!process.env.DISABLE_UPDATES, telemetryDisabled: !!process.env.DISABLE_TELEMETRY, - cloudMode: !!process.env.CLOUD_MODE, + trackerScriptName: process.env.TRACKER_SCRIPT_NAME, + uiDisabled: !!process.env.DISABLE_UI, + updatesDisabled: !!process.env.DISABLE_UPDATES, }); } diff --git a/pages/api/event-data/events.ts b/src/pages/api/event-data/events.ts similarity index 56% rename from pages/api/event-data/events.ts rename to src/pages/api/event-data/events.ts index 9f8f964b..1d1d3787 100644 --- a/pages/api/event-data/events.ts +++ b/src/pages/api/event-data/events.ts @@ -1,26 +1,37 @@ import { canViewWebsite } from 'lib/auth'; -import { useCors, useAuth } from 'lib/middleware'; +import { useAuth, useCors, useValidate } from 'lib/middleware'; import { NextApiRequestQueryBody } from 'lib/types'; import { NextApiResponse } from 'next'; -import { ok, methodNotAllowed, unauthorized } from 'next-basics'; +import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { getEventDataEvents } from 'queries'; +import * as yup from 'yup'; -export interface EventDataEventsRequestQuery { +export interface EventDataFieldsRequestQuery { websiteId: string; - dateRange: { - startDate: string; - endDate: string; - }; + startAt: string; + endAt: string; event?: string; } +const schema = { + GET: yup.object().shape({ + websiteId: yup.string().uuid().required(), + startAt: yup.number().integer().required(), + endAt: yup.number().integer().moreThan(yup.ref('startAt')).required(), + event: yup.string(), + }), +}; + export default async ( - req: NextApiRequestQueryBody, + req: NextApiRequestQueryBody, res: NextApiResponse, ) => { await useCors(req, res); await useAuth(req, res); + req.yup = schema; + await useValidate(req, res); + if (req.method === 'GET') { const { websiteId, startAt, endAt, event } = req.query; diff --git a/pages/api/event-data/fields.ts b/src/pages/api/event-data/fields.ts similarity index 63% rename from pages/api/event-data/fields.ts rename to src/pages/api/event-data/fields.ts index b6a73133..1cd24fe6 100644 --- a/pages/api/event-data/fields.ts +++ b/src/pages/api/event-data/fields.ts @@ -1,19 +1,27 @@ import { canViewWebsite } from 'lib/auth'; -import { useCors, useAuth } from 'lib/middleware'; +import { useAuth, useCors, useValidate } from 'lib/middleware'; import { NextApiRequestQueryBody } from 'lib/types'; import { NextApiResponse } from 'next'; -import { ok, methodNotAllowed, unauthorized } from 'next-basics'; +import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { getEventDataFields } from 'queries'; +import * as yup from 'yup'; export interface EventDataFieldsRequestQuery { websiteId: string; - dateRange: { - startDate: string; - endDate: string; - }; + startAt: string; + endAt: string; field?: string; } +const schema = { + GET: yup.object().shape({ + websiteId: yup.string().uuid().required(), + startAt: yup.number().integer().required(), + endAt: yup.number().integer().moreThan(yup.ref('startAt')).required(), + field: yup.string(), + }), +}; + export default async ( req: NextApiRequestQueryBody, res: NextApiResponse, @@ -21,6 +29,9 @@ export default async ( await useCors(req, res); await useAuth(req, res); + req.yup = schema; + await useValidate(req, res); + if (req.method === 'GET') { const { websiteId, startAt, endAt, field } = req.query; diff --git a/pages/api/event-data/stats.ts b/src/pages/api/event-data/stats.ts similarity index 60% rename from pages/api/event-data/stats.ts rename to src/pages/api/event-data/stats.ts index 4ba843be..7f694bc6 100644 --- a/pages/api/event-data/stats.ts +++ b/src/pages/api/event-data/stats.ts @@ -1,18 +1,25 @@ import { canViewWebsite } from 'lib/auth'; -import { useCors, useAuth } from 'lib/middleware'; +import { useAuth, useCors, useValidate } from 'lib/middleware'; import { NextApiRequestQueryBody } from 'lib/types'; import { NextApiResponse } from 'next'; -import { ok, methodNotAllowed, unauthorized } from 'next-basics'; -import { getEventDataStats } from 'queries'; +import { methodNotAllowed, ok, unauthorized } from 'next-basics'; +import { getEventDataStats } from 'queries/index'; +import * as yup from 'yup'; export interface EventDataStatsRequestQuery { websiteId: string; - dateRange: { - startDate: string; - endDate: string; - }; + startAt: string; + endAt: string; } +const schema = { + GET: yup.object().shape({ + websiteId: yup.string().uuid().required(), + startAt: yup.number().integer().required(), + endAt: yup.number().integer().moreThan(yup.ref('startAt')).required(), + }), +}; + export default async ( req: NextApiRequestQueryBody, res: NextApiResponse, @@ -20,6 +27,9 @@ export default async ( await useCors(req, res); await useAuth(req, res); + req.yup = schema; + await useValidate(req, res); + if (req.method === 'GET') { const { websiteId, startAt, endAt } = req.query; diff --git a/pages/api/heartbeat.ts b/src/pages/api/heartbeat.ts similarity index 100% rename from pages/api/heartbeat.ts rename to src/pages/api/heartbeat.ts diff --git a/pages/api/me/index.ts b/src/pages/api/me/index.ts similarity index 100% rename from pages/api/me/index.ts rename to src/pages/api/me/index.ts diff --git a/pages/api/me/password.ts b/src/pages/api/me/password.ts similarity index 77% rename from pages/api/me/password.ts rename to src/pages/api/me/password.ts index f9f60fc5..6f49a182 100644 --- a/pages/api/me/password.ts +++ b/src/pages/api/me/password.ts @@ -1,15 +1,16 @@ +import { useAuth, useValidate } from 'lib/middleware'; import { NextApiRequestQueryBody, User } from 'lib/types'; -import { useAuth } from 'lib/middleware'; import { NextApiResponse } from 'next'; import { badRequest, checkPassword, + forbidden, hashPassword, methodNotAllowed, - forbidden, ok, } from 'next-basics'; import { getUserById, updateUser } from 'queries'; +import * as yup from 'yup'; export interface UserPasswordRequestQuery { id: string; @@ -20,6 +21,14 @@ export interface UserPasswordRequestBody { newPassword: string; } +const schema = { + POST: yup.object().shape({ + id: yup.string().uuid().required(), + currentPassword: yup.string().required(), + newPassword: yup.string().min(8).required(), + }), +}; + export default async ( req: NextApiRequestQueryBody, res: NextApiResponse, @@ -30,6 +39,9 @@ export default async ( await useAuth(req, res); + req.yup = schema; + await useValidate(req, res); + const { currentPassword, newPassword } = req.body; const { id } = req.auth.user; diff --git a/pages/api/me/teams.ts b/src/pages/api/me/teams.ts similarity index 60% rename from pages/api/me/teams.ts rename to src/pages/api/me/teams.ts index d323043b..d394ef07 100644 --- a/pages/api/me/teams.ts +++ b/src/pages/api/me/teams.ts @@ -1,10 +1,20 @@ -import { useCors } from 'lib/middleware'; +import { useCors, useValidate } from 'lib/middleware'; import { NextApiRequestQueryBody, SearchFilter, TeamSearchFilterType } from 'lib/types'; +import { getFilterValidation } from 'lib/yup'; import { NextApiResponse } from 'next'; import { methodNotAllowed } from 'next-basics'; import userTeams from 'pages/api/users/[id]/teams'; +import * as yup from 'yup'; -export interface MyTeamsRequestQuery extends SearchFilter {} +export interface MyTeamsRequestQuery extends SearchFilter { + id: string; +} + +const schema = { + GET: yup.object().shape({ + ...getFilterValidation(/All|Name|Owner/i), + }), +}; export default async ( req: NextApiRequestQueryBody, @@ -12,7 +22,12 @@ export default async ( ) => { await useCors(req, res); + req.yup = schema; + await useValidate(req, res); + if (req.method === 'GET') { + req.query.id = req.auth.user.id; + return userTeams(req, res); } diff --git a/pages/api/me/websites.ts b/src/pages/api/me/websites.ts similarity index 64% rename from pages/api/me/websites.ts rename to src/pages/api/me/websites.ts index 238d1b6e..d4a803a0 100644 --- a/pages/api/me/websites.ts +++ b/src/pages/api/me/websites.ts @@ -1,11 +1,20 @@ -import { useAuth, useCors } from 'lib/middleware'; +import { useAuth, useCors, useValidate } from 'lib/middleware'; import { NextApiRequestQueryBody, SearchFilter, WebsiteSearchFilterType } from 'lib/types'; +import { getFilterValidation } from 'lib/yup'; import { NextApiResponse } from 'next'; import { methodNotAllowed } from 'next-basics'; - import userWebsites from 'pages/api/users/[id]/websites'; +import * as yup from 'yup'; -export interface MyWebsitesRequestQuery extends SearchFilter {} +export interface MyWebsitesRequestQuery extends SearchFilter { + id: string; +} + +const schema = { + GET: yup.object().shape({ + ...getFilterValidation(/All|Name|Domain/i), + }), +}; export default async ( req: NextApiRequestQueryBody, @@ -14,6 +23,9 @@ export default async ( await useCors(req, res); await useAuth(req, res); + req.yup = schema; + await useValidate(req, res); + if (req.method === 'GET') { req.query.id = req.auth.user.id; diff --git a/pages/api/realtime/[id].ts b/src/pages/api/realtime/[id].ts similarity index 77% rename from pages/api/realtime/[id].ts rename to src/pages/api/realtime/[id].ts index e78599c6..5b1e1e05 100644 --- a/pages/api/realtime/[id].ts +++ b/src/pages/api/realtime/[id].ts @@ -1,22 +1,32 @@ import { subMinutes } from 'date-fns'; import { canViewWebsite } from 'lib/auth'; -import { useAuth } from 'lib/middleware'; +import { useAuth, useValidate } from 'lib/middleware'; import { NextApiRequestQueryBody, RealtimeInit } from 'lib/types'; import { NextApiResponse } from 'next'; import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { getRealtimeData } from 'queries'; - +import * as yup from 'yup'; export interface RealtimeRequestQuery { id: string; startAt: number; } +const schema = { + GET: yup.object().shape({ + id: yup.string().uuid().required(), + startAt: yup.number().integer().required(), + }), +}; + export default async ( req: NextApiRequestQueryBody, res: NextApiResponse, ) => { await useAuth(req, res); + req.yup = schema; + await useValidate(req, res); + if (req.method === 'GET') { const { id: websiteId, startAt } = req.query; diff --git a/pages/api/reports/[id].ts b/src/pages/api/reports/[id].ts similarity index 61% rename from pages/api/reports/[id].ts rename to src/pages/api/reports/[id].ts index 85bc302c..eb4199bc 100644 --- a/pages/api/reports/[id].ts +++ b/src/pages/api/reports/[id].ts @@ -1,9 +1,10 @@ -import { canUpdateReport, canViewReport, canDeleteReport } from 'lib/auth'; -import { useAuth, useCors } from 'lib/middleware'; -import { NextApiRequestQueryBody } from 'lib/types'; +import { canDeleteReport, canUpdateReport, canViewReport } from 'lib/auth'; +import { useAuth, useCors, useValidate } from 'lib/middleware'; +import { NextApiRequestQueryBody, ReportType, YupRequest } from 'lib/types'; import { NextApiResponse } from 'next'; import { methodNotAllowed, ok, unauthorized } from 'next-basics'; -import { getReportById, updateReport, deleteReport } from 'queries'; +import { deleteReport, getReportById, updateReport } from 'queries'; +import * as yup from 'yup'; export interface ReportRequestQuery { id: string; @@ -11,12 +12,34 @@ export interface ReportRequestQuery { export interface ReportRequestBody { websiteId: string; - type: string; + type: ReportType; name: string; description: string; parameters: string; } +const schema: YupRequest = { + GET: yup.object().shape({ + id: yup.string().uuid().required(), + }), + POST: yup.object().shape({ + id: yup.string().uuid().required(), + websiteId: yup.string().uuid().required(), + type: yup + .string() + .matches(/funnel|insights|retention/i) + .required(), + name: yup.string().max(200).required(), + description: yup.string().max(500), + parameters: yup + .object() + .test('len', 'Must not exceed 6000 characters.', val => JSON.stringify(val).length < 6000), + }), + DELETE: yup.object().shape({ + id: yup.string().uuid().required(), + }), +}; + export default async ( req: NextApiRequestQueryBody, res: NextApiResponse, @@ -24,6 +47,9 @@ export default async ( await useCors(req, res); await useAuth(req, res); + req.yup = schema; + await useValidate(req, res); + const { id: reportId } = req.query; const { user: { id: userId }, diff --git a/pages/api/reports/funnel.ts b/src/pages/api/reports/funnel.ts similarity index 65% rename from pages/api/reports/funnel.ts rename to src/pages/api/reports/funnel.ts index 33882e03..a51817bf 100644 --- a/pages/api/reports/funnel.ts +++ b/src/pages/api/reports/funnel.ts @@ -1,9 +1,10 @@ import { canViewWebsite } from 'lib/auth'; -import { useCors, useAuth } from 'lib/middleware'; +import { useAuth, useCors, useValidate } from 'lib/middleware'; import { NextApiRequestQueryBody } from 'lib/types'; import { NextApiResponse } from 'next'; -import { ok, methodNotAllowed, unauthorized } from 'next-basics'; +import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { getFunnel } from 'queries'; +import * as yup from 'yup'; export interface FunnelRequestBody { websiteId: string; @@ -22,6 +23,21 @@ export interface FunnelResponse { endAt: number; } +const schema = { + POST: yup.object().shape({ + websiteId: yup.string().uuid().required(), + urls: yup.array().min(2).of(yup.string()).required(), + window: yup.number().positive().required(), + dateRange: yup + .object() + .shape({ + startDate: yup.date().required(), + endDate: yup.date().required(), + }) + .required(), + }), +}; + export default async ( req: NextApiRequestQueryBody, res: NextApiResponse, @@ -29,6 +45,9 @@ export default async ( await useCors(req, res); await useAuth(req, res); + req.yup = schema; + await useValidate(req, res); + if (req.method === 'POST') { const { websiteId, diff --git a/pages/api/reports/index.ts b/src/pages/api/reports/index.ts similarity index 58% rename from pages/api/reports/index.ts rename to src/pages/api/reports/index.ts index 762f297c..e62a1cc5 100644 --- a/pages/api/reports/index.ts +++ b/src/pages/api/reports/index.ts @@ -1,10 +1,11 @@ -import { canViewWebsite } from 'lib/auth'; import { uuid } from 'lib/crypto'; -import { useAuth, useCors } from 'lib/middleware'; +import { useAuth, useCors, useValidate } from 'lib/middleware'; import { NextApiRequestQueryBody, ReportSearchFilterType, SearchFilter } from 'lib/types'; +import { getFilterValidation } from 'lib/yup'; import { NextApiResponse } from 'next'; -import { methodNotAllowed, ok, unauthorized } from 'next-basics'; -import { createReport, getReportsByUserId, getReportsByWebsiteId } from 'queries'; +import { methodNotAllowed, ok } from 'next-basics'; +import { createReport, getReportsByUserId } from 'queries'; +import * as yup from 'yup'; export interface ReportsRequestQuery extends SearchFilter {} @@ -14,11 +15,28 @@ export interface ReportRequestBody { type: string; description: string; parameters: { - window: string; - urls: string[]; + [key: string]: any; }; } +const schema = { + GET: yup.object().shape({ + ...getFilterValidation(/All|Name|Description|Type|Username|Website Name|Website Domain/i), + }), + POST: yup.object().shape({ + websiteId: yup.string().uuid().required(), + name: yup.string().max(200).required(), + type: yup + .string() + .matches(/funnel|insights|retention/i) + .required(), + description: yup.string().max(500), + parameters: yup + .object() + .test('len', 'Must not exceed 6000 characters.', val => JSON.stringify(val).length < 6000), + }), +}; + export default async ( req: NextApiRequestQueryBody, res: NextApiResponse, @@ -26,6 +44,9 @@ export default async ( await useCors(req, res); await useAuth(req, res); + req.yup = schema; + await useValidate(req, res); + const { user: { id: userId }, } = req.auth; diff --git a/src/pages/api/reports/insights.ts b/src/pages/api/reports/insights.ts new file mode 100644 index 00000000..4d17c922 --- /dev/null +++ b/src/pages/api/reports/insights.ts @@ -0,0 +1,98 @@ +import { canViewWebsite } from 'lib/auth'; +import { useAuth, useCors, useValidate } from 'lib/middleware'; +import { NextApiRequestQueryBody } from 'lib/types'; +import { NextApiResponse } from 'next'; +import { methodNotAllowed, ok, unauthorized } from 'next-basics'; +import { getInsights } from 'queries'; +import * as yup from 'yup'; + +export interface InsightsRequestBody { + websiteId: string; + dateRange: { + startDate: string; + endDate: string; + }; + fields: { name: string; type: string; label: string }[]; + filters: { name: string; type: string; filter: string; value: string }[]; + groups: { name: string; type: string }[]; +} + +const schema = { + POST: yup.object().shape({ + websiteId: yup.string().uuid().required(), + dateRange: yup + .object() + .shape({ + startDate: yup.date().required(), + endDate: yup.date().required(), + }) + .required(), + fields: yup + .array() + .of( + yup.object().shape({ + name: yup.string().required(), + type: yup.string().required(), + label: yup.string().required(), + }), + ) + .min(1) + .required(), + filters: yup.array().of( + yup.object().shape({ + name: yup.string().required(), + type: yup.string().required(), + filter: yup.string().required(), + value: yup.string().required(), + }), + ), + groups: yup.array().of( + yup.object().shape({ + name: yup.string().required(), + type: yup.string().required(), + }), + ), + }), +}; + +function convertFilters(filters) { + return filters.reduce((obj, { name, ...value }) => { + obj[name] = value; + + return obj; + }, {}); +} + +export default async ( + req: NextApiRequestQueryBody, + res: NextApiResponse, +) => { + await useCors(req, res); + await useAuth(req, res); + + req.yup = schema; + await useValidate(req, res); + + if (req.method === 'POST') { + const { + websiteId, + dateRange: { startDate, endDate }, + fields, + filters, + } = req.body; + + if (!(await canViewWebsite(req.auth, websiteId))) { + return unauthorized(res); + } + + const data = await getInsights(websiteId, fields, { + ...convertFilters(filters), + startDate: new Date(startDate), + endDate: new Date(endDate), + }); + + return ok(res, data); + } + + return methodNotAllowed(res); +}; diff --git a/pages/api/reports/retention.ts b/src/pages/api/reports/retention.ts similarity index 59% rename from pages/api/reports/retention.ts rename to src/pages/api/reports/retention.ts index 40b3266b..4006ab12 100644 --- a/pages/api/reports/retention.ts +++ b/src/pages/api/reports/retention.ts @@ -1,33 +1,43 @@ import { canViewWebsite } from 'lib/auth'; -import { useCors, useAuth } from 'lib/middleware'; +import { useAuth, useCors, useValidate } from 'lib/middleware'; import { NextApiRequestQueryBody } from 'lib/types'; import { NextApiResponse } from 'next'; -import { ok, methodNotAllowed, unauthorized } from 'next-basics'; +import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { getRetention } from 'queries'; +import * as yup from 'yup'; export interface RetentionRequestBody { websiteId: string; - dateRange: { window; startDate: string; endDate: string }; - timezone: string; + dateRange: { startDate: string; endDate: string }; } -export interface RetentionResponse { - startAt: number; - endAt: number; -} +const schema = { + POST: yup.object().shape({ + websiteId: yup.string().uuid().required(), + dateRange: yup + .object() + .shape({ + startDate: yup.date().required(), + endDate: yup.date().required(), + }) + .required(), + }), +}; export default async ( req: NextApiRequestQueryBody, - res: NextApiResponse, + res: NextApiResponse, ) => { await useCors(req, res); await useAuth(req, res); + req.yup = schema; + await useValidate(req, res); + if (req.method === 'POST') { const { websiteId, dateRange: { startDate, endDate }, - timezone, } = req.body; if (!(await canViewWebsite(req.auth, websiteId))) { @@ -37,7 +47,6 @@ export default async ( const data = await getRetention(websiteId, { startDate: new Date(startDate), endDate: new Date(endDate), - timezone, }); return ok(res, data); diff --git a/pages/api/scripts/telemetry.js b/src/pages/api/scripts/telemetry.js similarity index 100% rename from pages/api/scripts/telemetry.js rename to src/pages/api/scripts/telemetry.js diff --git a/pages/api/send.ts b/src/pages/api/send.ts similarity index 78% rename from pages/api/send.ts rename to src/pages/api/send.ts index f90ded77..a379f261 100644 --- a/pages/api/send.ts +++ b/src/pages/api/send.ts @@ -1,14 +1,15 @@ -import isbot from 'isbot'; -import ipaddr from 'ipaddr.js'; -import { createToken, ok, send, badRequest, forbidden } from 'next-basics'; -import { saveEvent, saveSessionData } from 'queries'; -import { useCors, useSession } from 'lib/middleware'; -import { getJsonBody, getIpAddress } from 'lib/detect'; -import { secret } from 'lib/crypto'; -import { NextApiRequest, NextApiResponse } from 'next'; import { Resolver } from 'dns/promises'; -import { CollectionType } from 'lib/types'; -import { COLLECTION_TYPE } from 'lib/constants'; +import ipaddr from 'ipaddr.js'; +import isbot from 'isbot'; +import { COLLECTION_TYPE, HOSTNAME_REGEX } from 'lib/constants'; +import { secret } from 'lib/crypto'; +import { getIpAddress, getJsonBody } from 'lib/detect'; +import { useCors, useSession, useValidate } from 'lib/middleware'; +import { CollectionType, YupRequest } from 'lib/types'; +import { NextApiRequest, NextApiResponse } from 'next'; +import { badRequest, createToken, forbidden, ok, send } from 'next-basics'; +import { saveEvent, saveSessionData } from 'queries'; +import * as yup from 'yup'; export interface CollectRequestBody { payload: { @@ -43,8 +44,32 @@ export interface NextApiRequestCollect extends NextApiRequest { city: string; }; headers: { [key: string]: any }; + yup: YupRequest; } +const schema = { + POST: yup.object().shape({ + payload: yup + .object() + .shape({ + data: yup.object(), + hostname: yup.string().matches(HOSTNAME_REGEX).max(100), + language: yup.string().max(35), + referrer: yup.string().max(500), + screen: yup.string().max(11), + title: yup.string().max(500), + url: yup.string().max(500), + website: yup.string().uuid().required(), + name: yup.string().max(50), + }) + .required(), + type: yup + .string() + .matches(/event|identify/i) + .required(), + }), +}; + export default async (req: NextApiRequestCollect, res: NextApiResponse) => { await useCors(req, res); @@ -54,11 +79,8 @@ export default async (req: NextApiRequestCollect, res: NextApiResponse) => { const { type, payload } = getJsonBody(req); - const error = validateBody({ type, payload }); - - if (error) { - return badRequest(res, error); - } + req.yup = schema; + await useValidate(req, res); if (await hasBlockedIp(req)) { return forbidden(res); @@ -118,22 +140,6 @@ export default async (req: NextApiRequestCollect, res: NextApiResponse) => { return send(res, token); }; -function validateBody({ type, payload }: CollectRequestBody) { - if (!type || !payload) { - return 'Invalid payload.'; - } - - if (type !== COLLECTION_TYPE.event && type !== COLLECTION_TYPE.identify) { - return 'Wrong payload type.'; - } - - const { data } = payload; - - if (data && !(typeof data === 'object' && !Array.isArray(data))) { - return 'Invalid event data.'; - } -} - async function hasBlockedIp(req: NextApiRequestCollect) { const ignoreIps = process.env.IGNORE_IP; const ignoreHostnames = process.env.IGNORE_HOSTNAME; diff --git a/pages/api/share/[id].ts b/src/pages/api/share/[id].ts similarity index 79% rename from pages/api/share/[id].ts rename to src/pages/api/share/[id].ts index 0592d216..cc782246 100644 --- a/pages/api/share/[id].ts +++ b/src/pages/api/share/[id].ts @@ -1,8 +1,10 @@ -import { NextApiRequestQueryBody } from 'lib/types'; import { secret } from 'lib/crypto'; +import { useValidate } from 'lib/middleware'; +import { NextApiRequestQueryBody } from 'lib/types'; import { NextApiResponse } from 'next'; import { createToken, methodNotAllowed, notFound, ok } from 'next-basics'; import { getWebsiteByShareId } from 'queries'; +import * as yup from 'yup'; export interface ShareRequestQuery { id: string; @@ -13,10 +15,19 @@ export interface ShareResponse { token: string; } +const schema = { + GET: yup.object().shape({ + id: yup.string().required(), + }), +}; + export default async ( req: NextApiRequestQueryBody, res: NextApiResponse, ) => { + req.yup = schema; + await useValidate(req, res); + const { id: shareId } = req.query; if (req.method === 'GET') { diff --git a/pages/api/teams/[id]/index.ts b/src/pages/api/teams/[id]/index.ts similarity index 75% rename from pages/api/teams/[id]/index.ts rename to src/pages/api/teams/[id]/index.ts index 7fb664a0..31c47b2f 100644 --- a/pages/api/teams/[id]/index.ts +++ b/src/pages/api/teams/[id]/index.ts @@ -1,10 +1,11 @@ import { Team } from '@prisma/client'; -import { NextApiRequestQueryBody } from 'lib/types'; import { canDeleteTeam, canUpdateTeam, canViewTeam } from 'lib/auth'; -import { useAuth } from 'lib/middleware'; +import { useAuth, useValidate } from 'lib/middleware'; +import { NextApiRequestQueryBody } from 'lib/types'; import { NextApiResponse } from 'next'; import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { deleteTeam, getTeamById, updateTeam } from 'queries'; +import * as yup from 'yup'; export interface TeamRequestQuery { id: string; @@ -15,12 +16,29 @@ export interface TeamRequestBody { accessCode: string; } +const schema = { + GET: yup.object().shape({ + id: yup.string().uuid().required(), + }), + POST: yup.object().shape({ + id: yup.string().uuid().required(), + name: yup.string().max(50).required(), + accessCode: yup.string().max(50).required(), + }), + DELETE: yup.object().shape({ + id: yup.string().uuid().required(), + }), +}; + export default async ( req: NextApiRequestQueryBody, res: NextApiResponse, ) => { await useAuth(req, res); + req.yup = schema; + await useValidate(req, res); + const { id: teamId } = req.query; if (req.method === 'GET') { diff --git a/pages/api/teams/[id]/users/[userId].ts b/src/pages/api/teams/[id]/users/[userId].ts similarity index 73% rename from pages/api/teams/[id]/users/[userId].ts rename to src/pages/api/teams/[id]/users/[userId].ts index 1e4ca623..adb635d5 100644 --- a/pages/api/teams/[id]/users/[userId].ts +++ b/src/pages/api/teams/[id]/users/[userId].ts @@ -1,18 +1,28 @@ import { canDeleteTeamUser } from 'lib/auth'; -import { useAuth } from 'lib/middleware'; +import { useAuth, useValidate } from 'lib/middleware'; import { NextApiRequestQueryBody } from 'lib/types'; import { NextApiResponse } from 'next'; import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { deleteTeamUser } from 'queries'; - +import * as yup from 'yup'; export interface TeamUserRequestQuery { id: string; userId: string; } +const schema = { + DELETE: yup.object().shape({ + id: yup.string().uuid().required(), + userId: yup.string().uuid().required(), + }), +}; + export default async (req: NextApiRequestQueryBody, res: NextApiResponse) => { await useAuth(req, res); + req.yup = schema; + await useValidate(req, res); + if (req.method === 'DELETE') { const { id: teamId, userId } = req.query; diff --git a/pages/api/teams/[id]/users/index.ts b/src/pages/api/teams/[id]/users/index.ts similarity index 57% rename from pages/api/teams/[id]/users/index.ts rename to src/pages/api/teams/[id]/users/index.ts index 6f8b077e..52b25da6 100644 --- a/pages/api/teams/[id]/users/index.ts +++ b/src/pages/api/teams/[id]/users/index.ts @@ -1,9 +1,9 @@ -import { canUpdateTeam, canViewTeam } from 'lib/auth'; +import { canViewTeam } from 'lib/auth'; import { useAuth } from 'lib/middleware'; import { NextApiRequestQueryBody, SearchFilter, TeamSearchFilterType } from 'lib/types'; import { NextApiResponse } from 'next'; -import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics'; -import { createTeamUser, getUserByUsername, getUsersByTeamId } from 'queries'; +import { methodNotAllowed, ok, unauthorized } from 'next-basics'; +import { getUsersByTeamId } from 'queries'; export interface TeamUserRequestQuery extends SearchFilter { id: string; @@ -38,24 +38,5 @@ export default async ( return ok(res, users); } - if (req.method === 'POST') { - if (!(await canUpdateTeam(req.auth, teamId))) { - return unauthorized(res, 'You must be the owner of this team.'); - } - - const { email, roleId: roleId } = req.body; - - // Check for User - const user = await getUserByUsername(email); - - if (!user) { - return badRequest(res, 'The User does not exists.'); - } - - const updated = await createTeamUser(user.id, teamId, roleId); - - return ok(res, updated); - } - return methodNotAllowed(res); }; diff --git a/pages/api/teams/[id]/websites/[websiteId].ts b/src/pages/api/teams/[id]/websites/[websiteId].ts similarity index 73% rename from pages/api/teams/[id]/websites/[websiteId].ts rename to src/pages/api/teams/[id]/websites/[websiteId].ts index 795295d3..ada1efdc 100644 --- a/pages/api/teams/[id]/websites/[websiteId].ts +++ b/src/pages/api/teams/[id]/websites/[websiteId].ts @@ -1,21 +1,32 @@ import { canDeleteTeamWebsite } from 'lib/auth'; -import { useAuth } from 'lib/middleware'; +import { useAuth, useValidate } from 'lib/middleware'; import { NextApiRequestQueryBody } from 'lib/types'; import { NextApiResponse } from 'next'; import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { deleteTeamWebsite } from 'queries/admin/teamWebsite'; +import * as yup from 'yup'; export interface TeamWebsitesRequestQuery { id: string; websiteId: string; } +const schema = { + DELETE: yup.object().shape({ + id: yup.string().uuid().required(), + websiteId: yup.string().uuid().required(), + }), +}; + export default async ( req: NextApiRequestQueryBody, res: NextApiResponse, ) => { await useAuth(req, res); + req.yup = schema; + await useValidate(req, res); + const { id: teamId, websiteId } = req.query; if (req.method === 'DELETE') { diff --git a/pages/api/teams/[id]/websites/index.ts b/src/pages/api/teams/[id]/websites/index.ts similarity index 71% rename from pages/api/teams/[id]/websites/index.ts rename to src/pages/api/teams/[id]/websites/index.ts index dcd08939..4de32709 100644 --- a/pages/api/teams/[id]/websites/index.ts +++ b/src/pages/api/teams/[id]/websites/index.ts @@ -1,9 +1,10 @@ import { canViewTeam } from 'lib/auth'; -import { useAuth } from 'lib/middleware'; +import { useAuth, useValidate } from 'lib/middleware'; import { NextApiRequestQueryBody, SearchFilter, WebsiteSearchFilterType } from 'lib/types'; +import { getFilterValidation } from 'lib/yup'; import { NextApiResponse } from 'next'; import { methodNotAllowed, ok, unauthorized } from 'next-basics'; -import { getWebsites, getWebsitesByTeamId } from 'queries'; +import { getWebsitesByTeamId } from 'queries'; import { createTeamWebsites } from 'queries/admin/teamWebsite'; export interface TeamWebsiteRequestQuery extends SearchFilter { @@ -14,12 +15,28 @@ export interface TeamWebsiteRequestBody { websiteIds?: string[]; } +import * as yup from 'yup'; + +const schema = { + GET: yup.object().shape({ + id: yup.string().uuid().required(), + ...getFilterValidation(/All|Name|Domain/i), + }), + POST: yup.object().shape({ + id: yup.string().uuid().required(), + websiteIds: yup.array().of(yup.string()).min(1).required(), + }), +}; + export default async ( req: NextApiRequestQueryBody, res: NextApiResponse, ) => { await useAuth(req, res); + req.yup = schema; + await useValidate(req, res); + const { id: teamId } = req.query; if (req.method === 'GET') { diff --git a/pages/api/teams/index.ts b/src/pages/api/teams/index.ts similarity index 72% rename from pages/api/teams/index.ts rename to src/pages/api/teams/index.ts index 997ed885..dd742b9e 100644 --- a/pages/api/teams/index.ts +++ b/src/pages/api/teams/index.ts @@ -1,23 +1,39 @@ import { Team } from '@prisma/client'; import { canCreateTeam } from 'lib/auth'; import { uuid } from 'lib/crypto'; -import { useAuth } from 'lib/middleware'; +import { useAuth, useValidate } from 'lib/middleware'; import { NextApiRequestQueryBody, SearchFilter, TeamSearchFilterType } from 'lib/types'; +import { getFilterValidation } from 'lib/yup'; import { NextApiResponse } from 'next'; import { getRandomChars, methodNotAllowed, ok, unauthorized } from 'next-basics'; import { createTeam, getTeamsByUserId } from 'queries'; +import * as yup from 'yup'; export interface TeamsRequestQuery extends SearchFilter {} -export interface TeamsRequestBody extends SearchFilter { +export interface TeamsRequestBody { name: string; } +export interface MyTeamsRequestQuery extends SearchFilter {} + +const schema = { + GET: yup.object().shape({ + ...getFilterValidation(/All|Name|Owner/i), + }), + POST: yup.object().shape({ + name: yup.string().max(50).required(), + }), +}; + export default async ( req: NextApiRequestQueryBody, res: NextApiResponse, ) => { await useAuth(req, res); + req.yup = schema; + await useValidate(req, res); + const { user: { id: userId }, } = req.auth; diff --git a/pages/api/teams/join.ts b/src/pages/api/teams/join.ts similarity index 76% rename from pages/api/teams/join.ts rename to src/pages/api/teams/join.ts index ce7367a0..06feda8a 100644 --- a/pages/api/teams/join.ts +++ b/src/pages/api/teams/join.ts @@ -1,21 +1,30 @@ import { Team } from '@prisma/client'; -import { NextApiRequestQueryBody } from 'lib/types'; -import { useAuth } from 'lib/middleware'; -import { NextApiResponse } from 'next'; -import { methodNotAllowed, ok, notFound } from 'next-basics'; -import { createTeamUser, getTeamByAccessCode, getTeamUser } from 'queries'; import { ROLES } from 'lib/constants'; - +import { useAuth, useValidate } from 'lib/middleware'; +import { NextApiRequestQueryBody } from 'lib/types'; +import { NextApiResponse } from 'next'; +import { methodNotAllowed, notFound, ok } from 'next-basics'; +import { createTeamUser, getTeamByAccessCode, getTeamUser } from 'queries'; +import * as yup from 'yup'; export interface TeamsJoinRequestBody { accessCode: string; } +const schema = { + POST: yup.object().shape({ + accessCode: yup.string().max(50).required(), + }), +}; + export default async ( req: NextApiRequestQueryBody, res: NextApiResponse, ) => { await useAuth(req, res); + req.yup = schema; + await useValidate(req, res); + if (req.method === 'POST') { const { accessCode } = req.body; diff --git a/pages/api/users/[id]/index.ts b/src/pages/api/users/[id]/index.ts similarity index 82% rename from pages/api/users/[id]/index.ts rename to src/pages/api/users/[id]/index.ts index e09b1b5f..3ac560ed 100644 --- a/pages/api/users/[id]/index.ts +++ b/src/pages/api/users/[id]/index.ts @@ -1,9 +1,10 @@ -import { NextApiRequestQueryBody, Role, User } from 'lib/types'; import { canDeleteUser, canUpdateUser, canViewUser } from 'lib/auth'; -import { useAuth } from 'lib/middleware'; +import { useAuth, useValidate } from 'lib/middleware'; +import { NextApiRequestQueryBody, Role, User } from 'lib/types'; import { NextApiResponse } from 'next'; import { badRequest, hashPassword, methodNotAllowed, ok, unauthorized } from 'next-basics'; import { deleteUser, getUserById, getUserByUsername, updateUser } from 'queries'; +import * as yup from 'yup'; export interface UserRequestQuery { id: string; @@ -15,12 +16,27 @@ export interface UserRequestBody { role: Role; } +const schema = { + GET: yup.object().shape({ + id: yup.string().uuid().required(), + }), + POST: yup.object().shape({ + id: yup.string().uuid().required(), + username: yup.string().max(255), + password: yup.string(), + role: yup.string().matches(/admin|user|view-only/i), + }), +}; + export default async ( req: NextApiRequestQueryBody, res: NextApiResponse, ) => { await useAuth(req, res); + req.yup = schema; + await useValidate(req, res); + const { user: { id: userId, isAdmin }, } = req.auth; diff --git a/pages/api/users/[id]/teams.ts b/src/pages/api/users/[id]/teams.ts similarity index 75% rename from pages/api/users/[id]/teams.ts rename to src/pages/api/users/[id]/teams.ts index 831a992d..eb34410c 100644 --- a/pages/api/users/[id]/teams.ts +++ b/src/pages/api/users/[id]/teams.ts @@ -1,9 +1,10 @@ -import { useAuth, useCors } from 'lib/middleware'; +import { useAuth, useCors, useValidate } from 'lib/middleware'; import { NextApiRequestQueryBody, SearchFilter, TeamSearchFilterType } from 'lib/types'; +import { getFilterValidation } from 'lib/yup'; import { NextApiResponse } from 'next'; import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { getTeamsByUserId } from 'queries'; - +import * as yup from 'yup'; export interface UserTeamsRequestQuery extends SearchFilter { id: string; } @@ -14,6 +15,13 @@ export interface UserTeamsRequestBody { shareId: string; } +const schema = { + GET: yup.object().shape({ + id: yup.string().uuid().required(), + ...getFilterValidation('/All|Name|Owner/i'), + }), +}; + export default async ( req: NextApiRequestQueryBody, res: NextApiResponse, @@ -21,6 +29,9 @@ export default async ( await useCors(req, res); await useAuth(req, res); + req.yup = schema; + await useValidate(req, res); + const { user } = req.auth; const { id: userId } = req.query; diff --git a/pages/api/users/[id]/usage.ts b/src/pages/api/users/[id]/usage.ts similarity index 84% rename from pages/api/users/[id]/usage.ts rename to src/pages/api/users/[id]/usage.ts index 0118df92..b0fc2055 100644 --- a/pages/api/users/[id]/usage.ts +++ b/src/pages/api/users/[id]/usage.ts @@ -1,8 +1,9 @@ -import { useAuth, useCors } from 'lib/middleware'; +import { useAuth, useCors, useValidate } from 'lib/middleware'; import { NextApiRequestQueryBody } from 'lib/types'; import { NextApiResponse } from 'next'; import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { getEventDataUsage, getEventUsage, getUserWebsites } from 'queries'; +import * as yup from 'yup'; export interface UserUsageRequestQuery { id: string; @@ -21,6 +22,14 @@ export interface UserUsageRequestResponse { }[]; } +const schema = { + GET: yup.object().shape({ + id: yup.string().uuid().required(), + startAt: yup.number().integer().required(), + endAt: yup.number().integer().moreThan(yup.ref('startAt')).required(), + }), +}; + export default async ( req: NextApiRequestQueryBody, res: NextApiResponse, @@ -28,6 +37,9 @@ export default async ( await useCors(req, res); await useAuth(req, res); + req.yup = schema; + await useValidate(req, res); + const { user } = req.auth; if (req.method === 'GET') { diff --git a/pages/api/users/[id]/websites.ts b/src/pages/api/users/[id]/websites.ts similarity index 64% rename from pages/api/users/[id]/websites.ts rename to src/pages/api/users/[id]/websites.ts index 0e9231f7..65e9a0e8 100644 --- a/pages/api/users/[id]/websites.ts +++ b/src/pages/api/users/[id]/websites.ts @@ -1,25 +1,36 @@ -import { useAuth, useCors } from 'lib/middleware'; +import { useAuth, useCors, useValidate } from 'lib/middleware'; import { NextApiRequestQueryBody, SearchFilter, WebsiteSearchFilterType } from 'lib/types'; +import { getFilterValidation } from 'lib/yup'; import { NextApiResponse } from 'next'; import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { getWebsitesByUserId } from 'queries'; +import * as yup from 'yup'; export interface UserWebsitesRequestQuery extends SearchFilter { id: string; -} -export interface UserWebsitesRequestBody { - name: string; - domain: string; - shareId: string; + includeTeams?: boolean; + onlyTeams?: boolean; } +const schema = { + GET: yup.object().shape({ + id: yup.string().uuid().required(), + includeTeams: yup.boolean(), + onlyTeams: yup.boolean(), + ...getFilterValidation(/All|Name|Domain/i), + }), +}; + export default async ( - req: NextApiRequestQueryBody, + req: NextApiRequestQueryBody, res: NextApiResponse, ) => { await useCors(req, res); await useAuth(req, res); + req.yup = schema; + await useValidate(req, res); + const { user } = req.auth; const { id: userId, page, filter, pageSize, includeTeams, onlyTeams } = req.query; diff --git a/pages/api/users/index.ts b/src/pages/api/users/index.ts similarity index 70% rename from pages/api/users/index.ts rename to src/pages/api/users/index.ts index 5e913c02..991986e8 100644 --- a/pages/api/users/index.ts +++ b/src/pages/api/users/index.ts @@ -1,8 +1,9 @@ import { canCreateUser, canViewUsers } from 'lib/auth'; import { ROLES } from 'lib/constants'; import { uuid } from 'lib/crypto'; -import { useAuth } from 'lib/middleware'; +import { useAuth, useValidate } from 'lib/middleware'; import { NextApiRequestQueryBody, Role, SearchFilter, User, UserSearchFilterType } from 'lib/types'; +import { getFilterValidation } from 'lib/yup'; import { NextApiResponse } from 'next'; import { badRequest, hashPassword, methodNotAllowed, ok, unauthorized } from 'next-basics'; import { createUser, getUserByUsername, getUsers } from 'queries'; @@ -12,15 +13,34 @@ export interface UsersRequestBody { username: string; password: string; id: string; - role?: Role; + role: Role; } +import * as yup from 'yup'; +const schema = { + GET: yup.object().shape({ + ...getFilterValidation(/All|Username/i), + }), + POST: yup.object().shape({ + username: yup.string().max(255).required(), + password: yup.string().required(), + id: yup.string().uuid(), + role: yup + .string() + .matches(/admin|user|view-only/i) + .required(), + }), +}; + export default async ( req: NextApiRequestQueryBody, res: NextApiResponse, ) => { await useAuth(req, res); + req.yup = schema; + await useValidate(req, res); + if (req.method === 'GET') { if (!(await canViewUsers(req.auth))) { return unauthorized(res); @@ -28,7 +48,7 @@ export default async ( const { page, filter, pageSize } = req.query; - const users = await getUsers({ page, filter, pageSize: +pageSize || null }); + const users = await getUsers({ page, filter, pageSize: pageSize ? +pageSize : null }); return ok(res, users); } diff --git a/pages/api/websites/[id]/active.ts b/src/pages/api/websites/[id]/active.ts similarity index 76% rename from pages/api/websites/[id]/active.ts rename to src/pages/api/websites/[id]/active.ts index 99c8d999..abc23dd7 100644 --- a/pages/api/websites/[id]/active.ts +++ b/src/pages/api/websites/[id]/active.ts @@ -1,14 +1,21 @@ import { WebsiteActive, NextApiRequestQueryBody } from 'lib/types'; import { canViewWebsite } from 'lib/auth'; -import { useAuth, useCors } from 'lib/middleware'; +import { useAuth, useCors, useValidate } from 'lib/middleware'; import { NextApiResponse } from 'next'; import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { getActiveVisitors } from 'queries'; +import * as yup from 'yup'; export interface WebsiteActiveRequestQuery { id: string; } +const schema = { + GET: yup.object().shape({ + id: yup.string().uuid().required(), + }), +}; + export default async ( req: NextApiRequestQueryBody, res: NextApiResponse, @@ -16,6 +23,9 @@ export default async ( await useCors(req, res); await useAuth(req, res); + req.yup = schema; + await useValidate(req, res); + const { id: websiteId } = req.query; if (req.method === 'GET') { diff --git a/pages/api/websites/[id]/daterange.ts b/src/pages/api/websites/[id]/daterange.ts similarity index 77% rename from pages/api/websites/[id]/daterange.ts rename to src/pages/api/websites/[id]/daterange.ts index dc043560..bfa5338e 100644 --- a/pages/api/websites/[id]/daterange.ts +++ b/src/pages/api/websites/[id]/daterange.ts @@ -1,14 +1,21 @@ import { WebsiteActive, NextApiRequestQueryBody } from 'lib/types'; import { canViewWebsite } from 'lib/auth'; -import { useAuth, useCors } from 'lib/middleware'; +import { useAuth, useCors, useValidate } from 'lib/middleware'; import { NextApiResponse } from 'next'; import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { getWebsiteDateRange } from 'queries'; +import * as yup from 'yup'; export interface WebsiteDateRangeRequestQuery { id: string; } +const schema = { + GET: yup.object().shape({ + id: yup.string().uuid().required(), + }), +}; + export default async ( req: NextApiRequestQueryBody, res: NextApiResponse, @@ -16,6 +23,9 @@ export default async ( await useCors(req, res); await useAuth(req, res); + req.yup = schema; + await useValidate(req, res); + const { id: websiteId } = req.query; if (req.method === 'GET') { diff --git a/pages/api/websites/[id]/events.ts b/src/pages/api/websites/[id]/events.ts similarity index 70% rename from pages/api/websites/[id]/events.ts rename to src/pages/api/websites/[id]/events.ts index 7d4f999f..427cb40e 100644 --- a/pages/api/websites/[id]/events.ts +++ b/src/pages/api/websites/[id]/events.ts @@ -1,6 +1,6 @@ import { WebsiteMetric, NextApiRequestQueryBody } from 'lib/types'; import { canViewWebsite } from 'lib/auth'; -import { useAuth, useCors } from 'lib/middleware'; +import { useAuth, useCors, useValidate } from 'lib/middleware'; import moment from 'moment-timezone'; import { NextApiResponse } from 'next'; import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics'; @@ -16,9 +16,21 @@ export interface WebsiteEventsRequestQuery { unit: string; timezone: string; url: string; - eventName: string; } +import * as yup from 'yup'; + +const schema = { + GET: yup.object().shape({ + id: yup.string().uuid().required(), + startAt: yup.number().integer().required(), + endAt: yup.number().integer().moreThan(yup.ref('startAt')).required(), + unit: yup.string().required(), + timezone: yup.string().required(), + url: yup.string(), + }), +}; + export default async ( req: NextApiRequestQueryBody, res: NextApiResponse, @@ -26,7 +38,10 @@ export default async ( await useCors(req, res); await useAuth(req, res); - const { id: websiteId, timezone, url, eventName } = req.query; + req.yup = schema; + await useValidate(req, res); + + const { id: websiteId, timezone, url } = req.query; const { startDate, endDate, unit } = await parseDateRangeQuery(req); if (req.method === 'GET') { @@ -44,7 +59,6 @@ export default async ( timezone, unit, url, - eventName, }); return ok(res, events); diff --git a/pages/api/websites/[id]/index.ts b/src/pages/api/websites/[id]/index.ts similarity index 88% rename from pages/api/websites/[id]/index.ts rename to src/pages/api/websites/[id]/index.ts index 3d053d0e..0e5aacce 100644 --- a/pages/api/websites/[id]/index.ts +++ b/src/pages/api/websites/[id]/index.ts @@ -2,7 +2,7 @@ import { NextApiResponse } from 'next'; import { methodNotAllowed, ok, serverError, unauthorized } from 'next-basics'; import { Website, NextApiRequestQueryBody } from 'lib/types'; import { canViewWebsite, canUpdateWebsite, canDeleteWebsite } from 'lib/auth'; -import { useAuth, useCors } from 'lib/middleware'; +import { useAuth, useCors, useValidate } from 'lib/middleware'; import { deleteWebsite, getWebsiteById, updateWebsite } from 'queries'; import { SHARE_ID_REGEX } from 'lib/constants'; @@ -16,6 +16,14 @@ export interface WebsiteRequestBody { shareId: string; } +import * as yup from 'yup'; + +const schema = { + GET: yup.object().shape({ + id: yup.string().uuid().required(), + }), +}; + export default async ( req: NextApiRequestQueryBody, res: NextApiResponse, @@ -23,6 +31,9 @@ export default async ( await useCors(req, res); await useAuth(req, res); + req.yup = schema; + await useValidate(req, res); + const { id: websiteId } = req.query; if (req.method === 'GET') { diff --git a/pages/api/websites/[id]/metrics.ts b/src/pages/api/websites/[id]/metrics.ts similarity index 79% rename from pages/api/websites/[id]/metrics.ts rename to src/pages/api/websites/[id]/metrics.ts index 7c84583c..b8c37339 100644 --- a/pages/api/websites/[id]/metrics.ts +++ b/src/pages/api/websites/[id]/metrics.ts @@ -2,30 +2,40 @@ import { NextApiResponse } from 'next'; import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics'; import { WebsiteMetric, NextApiRequestQueryBody } from 'lib/types'; import { canViewWebsite } from 'lib/auth'; -import { useAuth, useCors } from 'lib/middleware'; +import { useAuth, useCors, useValidate } from 'lib/middleware'; import { SESSION_COLUMNS, EVENT_COLUMNS, FILTER_COLUMNS } from 'lib/constants'; import { getPageviewMetrics, getSessionMetrics } from 'queries'; import { parseDateRangeQuery } from 'lib/query'; +import * as yup from 'yup'; export interface WebsiteMetricsRequestQuery { id: string; type: string; startAt: number; endAt: number; - url: string; - referrer: string; - title: string; - query: string; - event: string; - os: string; - browser: string; - device: string; - country: string; - region: string; - city: string; - language: string; + url?: string; + referrer?: string; + title?: string; + query?: string; + os?: string; + browser?: string; + device?: string; + country?: string; + region?: string; + city?: string; + language?: string; + event?: string; } +const schema = { + GET: yup.object().shape({ + id: yup.string().uuid().required(), + type: yup.string().required(), + startAt: yup.number().required(), + endAt: yup.number().required(), + }), +}; + export default async ( req: NextApiRequestQueryBody, res: NextApiResponse, @@ -33,6 +43,9 @@ export default async ( await useCors(req, res); await useAuth(req, res); + req.yup = schema; + await useValidate(req, res); + const { id: websiteId, type, @@ -40,7 +53,6 @@ export default async ( referrer, title, query, - event, os, browser, device, @@ -48,6 +60,7 @@ export default async ( region, city, language, + event, } = req.query; if (req.method === 'GET') { @@ -64,7 +77,6 @@ export default async ( referrer, title, query, - event, os, browser, device, @@ -72,10 +84,9 @@ export default async ( region, city, language, + event, }; - filters[type] = undefined; - const column = FILTER_COLUMNS[type] || type; if (SESSION_COLUMNS.includes(type)) { diff --git a/pages/api/websites/[id]/pageviews.ts b/src/pages/api/websites/[id]/pageviews.ts similarity index 87% rename from pages/api/websites/[id]/pageviews.ts rename to src/pages/api/websites/[id]/pageviews.ts index c5532e76..9985ca89 100644 --- a/pages/api/websites/[id]/pageviews.ts +++ b/src/pages/api/websites/[id]/pageviews.ts @@ -3,7 +3,7 @@ import { NextApiResponse } from 'next'; import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics'; import { NextApiRequestQueryBody, WebsitePageviews } from 'lib/types'; import { canViewWebsite } from 'lib/auth'; -import { useAuth, useCors } from 'lib/middleware'; +import { useAuth, useCors, useValidate } from 'lib/middleware'; import { getPageviewStats, getSessionStats } from 'queries'; import { parseDateRangeQuery } from 'lib/query'; @@ -24,6 +24,13 @@ export interface WebsitePageviewRequestQuery { city?: string; } +import * as yup from 'yup'; +const schema = { + GET: yup.object().shape({ + id: yup.string().uuid().required(), + }), +}; + export default async ( req: NextApiRequestQueryBody, res: NextApiResponse, @@ -31,6 +38,9 @@ export default async ( await useCors(req, res); await useAuth(req, res); + req.yup = schema; + await useValidate(req, res); + const { id: websiteId, timezone, diff --git a/pages/api/websites/[id]/reports.ts b/src/pages/api/websites/[id]/reports.ts similarity index 80% rename from pages/api/websites/[id]/reports.ts rename to src/pages/api/websites/[id]/reports.ts index 60c6f714..738f6b37 100644 --- a/pages/api/websites/[id]/reports.ts +++ b/src/pages/api/websites/[id]/reports.ts @@ -1,5 +1,5 @@ import { canViewWebsite } from 'lib/auth'; -import { useAuth, useCors } from 'lib/middleware'; +import { useAuth, useCors, useValidate } from 'lib/middleware'; import { NextApiRequestQueryBody, ReportSearchFilterType, SearchFilter } from 'lib/types'; import { NextApiResponse } from 'next'; import { methodNotAllowed, ok, unauthorized } from 'next-basics'; @@ -9,6 +9,13 @@ export interface ReportsRequestQuery extends SearchFilter, res: NextApiResponse, @@ -16,6 +23,9 @@ export default async ( await useCors(req, res); await useAuth(req, res); + req.yup = schema; + await useValidate(req, res); + const { id: websiteId } = req.query; if (req.method === 'GET') { diff --git a/pages/api/websites/[id]/reset.ts b/src/pages/api/websites/[id]/reset.ts similarity index 75% rename from pages/api/websites/[id]/reset.ts rename to src/pages/api/websites/[id]/reset.ts index 23b5305d..cfd5e767 100644 --- a/pages/api/websites/[id]/reset.ts +++ b/src/pages/api/websites/[id]/reset.ts @@ -1,6 +1,6 @@ import { NextApiRequestQueryBody } from 'lib/types'; import { canUpdateWebsite } from 'lib/auth'; -import { useAuth, useCors } from 'lib/middleware'; +import { useAuth, useCors, useValidate } from 'lib/middleware'; import { NextApiResponse } from 'next'; import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { resetWebsite } from 'queries'; @@ -9,6 +9,13 @@ export interface WebsiteResetRequestQuery { id: string; } +import * as yup from 'yup'; +const schema = { + GET: yup.object().shape({ + id: yup.string().uuid().required(), + }), +}; + export default async ( req: NextApiRequestQueryBody, res: NextApiResponse, @@ -16,6 +23,9 @@ export default async ( await useCors(req, res); await useAuth(req, res); + req.yup = schema; + await useValidate(req, res); + const { id: websiteId } = req.query; if (req.method === 'POST') { diff --git a/pages/api/websites/[id]/stats.ts b/src/pages/api/websites/[id]/stats.ts similarity index 89% rename from pages/api/websites/[id]/stats.ts rename to src/pages/api/websites/[id]/stats.ts index a77c7eaf..caf54910 100644 --- a/pages/api/websites/[id]/stats.ts +++ b/src/pages/api/websites/[id]/stats.ts @@ -2,7 +2,7 @@ import { subMinutes, differenceInMinutes } from 'date-fns'; import { NextApiResponse } from 'next'; import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { canViewWebsite } from 'lib/auth'; -import { useAuth, useCors } from 'lib/middleware'; +import { useAuth, useCors, useValidate } from 'lib/middleware'; import { NextApiRequestQueryBody, WebsiteStats } from 'lib/types'; import { parseDateRangeQuery } from 'lib/query'; import { getWebsiteStats } from 'queries'; @@ -24,6 +24,13 @@ export interface WebsiteStatsRequestQuery { city: string; } +import * as yup from 'yup'; +const schema = { + GET: yup.object().shape({ + id: yup.string().uuid().required(), + }), +}; + export default async ( req: NextApiRequestQueryBody, res: NextApiResponse, @@ -31,6 +38,9 @@ export default async ( await useCors(req, res); await useAuth(req, res); + req.yup = schema; + await useValidate(req, res); + const { id: websiteId, url, diff --git a/pages/api/websites/[id]/values.ts b/src/pages/api/websites/[id]/values.ts similarity index 82% rename from pages/api/websites/[id]/values.ts rename to src/pages/api/websites/[id]/values.ts index ad8625bd..d90a1682 100644 --- a/pages/api/websites/[id]/values.ts +++ b/src/pages/api/websites/[id]/values.ts @@ -1,6 +1,6 @@ import { NextApiRequestQueryBody } from 'lib/types'; import { canViewWebsite } from 'lib/auth'; -import { useAuth, useCors } from 'lib/middleware'; +import { useAuth, useCors, useValidate } from 'lib/middleware'; import { NextApiResponse } from 'next'; import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics'; import { EVENT_COLUMNS, FILTER_COLUMNS, SESSION_COLUMNS } from 'lib/constants'; @@ -10,6 +10,13 @@ export interface WebsiteResetRequestQuery { id: string; } +import * as yup from 'yup'; +const schema = { + GET: yup.object().shape({ + id: yup.string().uuid().required(), + }), +}; + export default async ( req: NextApiRequestQueryBody, res: NextApiResponse, @@ -17,6 +24,9 @@ export default async ( await useCors(req, res); await useAuth(req, res); + req.yup = schema; + await useValidate(req, res); + const { id: websiteId, type } = req.query; if (req.method === 'GET') { diff --git a/pages/api/websites/index.ts b/src/pages/api/websites/index.ts similarity index 72% rename from pages/api/websites/index.ts rename to src/pages/api/websites/index.ts index f94fa037..d724f12f 100644 --- a/pages/api/websites/index.ts +++ b/src/pages/api/websites/index.ts @@ -1,11 +1,13 @@ import { canCreateWebsite } from 'lib/auth'; import { uuid } from 'lib/crypto'; -import { useAuth, useCors } from 'lib/middleware'; +import { useAuth, useCors, useValidate } from 'lib/middleware'; import { NextApiRequestQueryBody, SearchFilter, WebsiteSearchFilterType } from 'lib/types'; import { NextApiResponse } from 'next'; import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { createWebsite } from 'queries'; import userWebsites from 'pages/api/users/[id]/websites'; +import * as yup from 'yup'; +import { getFilterValidation } from 'lib/yup'; export interface WebsitesRequestQuery extends SearchFilter {} @@ -15,12 +17,25 @@ export interface WebsitesRequestBody { shareId: string; } +const schema = { + GET: yup.object().shape({ + ...getFilterValidation(/All|Name|Domain/i), + }), + POST: yup.object().shape({ + name: yup.string().max(100).required(), + domain: yup.string().max(500).required(), + shareId: yup.string().max(50), + }), +}; + export default async ( req: NextApiRequestQueryBody, res: NextApiResponse, ) => { await useCors(req, res); await useAuth(req, res); + req.yup = schema; + await useValidate(req, res); const { user: { id: userId }, @@ -30,7 +45,7 @@ export default async ( req.query.id = userId; req.query.pageSize = 100; - return userWebsites(req, res); + return userWebsites(req as any, res); } if (req.method === 'POST') { diff --git a/pages/console/[[...id]].js b/src/pages/console/[[...id]].js similarity index 100% rename from pages/console/[[...id]].js rename to src/pages/console/[[...id]].js diff --git a/pages/dashboard/index.js b/src/pages/dashboard/index.js similarity index 85% rename from pages/dashboard/index.js rename to src/pages/dashboard/index.js index 061697f4..c1a3c09e 100644 --- a/pages/dashboard/index.js +++ b/src/pages/dashboard/index.js @@ -1,6 +1,6 @@ import AppLayout from 'components/layout/AppLayout'; import Dashboard from 'components/pages/dashboard/Dashboard'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; export default function DashboardPage() { const { formatMessage, labels } = useMessages(); diff --git a/pages/index.js b/src/pages/index.js similarity index 100% rename from pages/index.js rename to src/pages/index.js diff --git a/pages/login.js b/src/pages/login.js similarity index 100% rename from pages/login.js rename to src/pages/login.js diff --git a/pages/logout.js b/src/pages/logout.js similarity index 93% rename from pages/logout.js rename to src/pages/logout.js index 675f1932..ef89080c 100644 --- a/pages/logout.js +++ b/src/pages/logout.js @@ -1,6 +1,6 @@ import { useEffect } from 'react'; import { useRouter } from 'next/router'; -import useApi from 'hooks/useApi'; +import useApi from 'components/hooks/useApi'; import { setUser } from 'store/app'; import { removeClientAuthToken } from 'lib/client'; diff --git a/pages/reports/[id].js b/src/pages/reports/[id].js similarity index 92% rename from pages/reports/[id].js rename to src/pages/reports/[id].js index 2520e87d..101881a1 100644 --- a/pages/reports/[id].js +++ b/src/pages/reports/[id].js @@ -1,7 +1,7 @@ import { useRouter } from 'next/router'; import AppLayout from 'components/layout/AppLayout'; import ReportDetails from 'components/pages/reports/ReportDetails'; -import { useApi, useMessages } from 'hooks'; +import { useApi, useMessages } from 'components/hooks'; export default function () { const { formatMessage, labels } = useMessages(); diff --git a/pages/reports/create.js b/src/pages/reports/create.js similarity index 87% rename from pages/reports/create.js rename to src/pages/reports/create.js index 763e2c63..08f97f28 100644 --- a/pages/reports/create.js +++ b/src/pages/reports/create.js @@ -1,6 +1,6 @@ import AppLayout from 'components/layout/AppLayout'; import ReportTemplates from 'components/pages/reports/ReportTemplates'; -import { useMessages } from 'hooks'; +import { useMessages } from 'components/hooks'; export default function () { const { formatMessage, labels } = useMessages(); diff --git a/pages/reports/funnel.js b/src/pages/reports/funnel.js similarity index 86% rename from pages/reports/funnel.js rename to src/pages/reports/funnel.js index 4acdef37..78174f7b 100644 --- a/pages/reports/funnel.js +++ b/src/pages/reports/funnel.js @@ -1,6 +1,6 @@ import AppLayout from 'components/layout/AppLayout'; import FunnelReport from 'components/pages/reports/funnel/FunnelReport'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; export default function () { const { formatMessage, labels } = useMessages(); diff --git a/pages/reports/index.js b/src/pages/reports/index.js similarity index 86% rename from pages/reports/index.js rename to src/pages/reports/index.js index ff3b4e86..a1a13a68 100644 --- a/pages/reports/index.js +++ b/src/pages/reports/index.js @@ -1,6 +1,6 @@ import AppLayout from 'components/layout/AppLayout'; import ReportsPage from 'components/pages/reports/ReportsPage'; -import { useMessages } from 'hooks'; +import { useMessages } from 'components/hooks'; export default function () { const { formatMessage, labels } = useMessages(); diff --git a/pages/reports/insights.js b/src/pages/reports/insights.js similarity index 88% rename from pages/reports/insights.js rename to src/pages/reports/insights.js index 45236e10..c5220721 100644 --- a/pages/reports/insights.js +++ b/src/pages/reports/insights.js @@ -1,6 +1,6 @@ import AppLayout from 'components/layout/AppLayout'; import InsightsReport from 'components/pages/reports/insights/InsightsReport'; -import { useMessages } from 'hooks'; +import { useMessages } from 'components/hooks'; export default function () { const { formatMessage, labels } = useMessages(); diff --git a/pages/reports/retention.js b/src/pages/reports/retention.js similarity index 86% rename from pages/reports/retention.js rename to src/pages/reports/retention.js index b7f0bd0f..7f5d4cf2 100644 --- a/pages/reports/retention.js +++ b/src/pages/reports/retention.js @@ -1,6 +1,6 @@ import AppLayout from 'components/layout/AppLayout'; import RetentionReport from 'components/pages/reports/retention/RetentionReport'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; export default function () { const { formatMessage, labels } = useMessages(); diff --git a/pages/settings/profile/index.js b/src/pages/settings/profile/index.js similarity index 89% rename from pages/settings/profile/index.js rename to src/pages/settings/profile/index.js index 8827f1da..d340c193 100644 --- a/pages/settings/profile/index.js +++ b/src/pages/settings/profile/index.js @@ -1,7 +1,7 @@ import AppLayout from 'components/layout/AppLayout'; import SettingsLayout from 'components/layout/SettingsLayout'; import ProfileSettings from 'components/pages/settings/profile/ProfileSettings'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; export default function () { const { formatMessage, labels } = useMessages(); diff --git a/pages/settings/teams/[id].js b/src/pages/settings/teams/[id].js similarity index 93% rename from pages/settings/teams/[id].js rename to src/pages/settings/teams/[id].js index a68ef80c..775a6a08 100644 --- a/pages/settings/teams/[id].js +++ b/src/pages/settings/teams/[id].js @@ -2,7 +2,7 @@ import AppLayout from 'components/layout/AppLayout'; import SettingsLayout from 'components/layout/SettingsLayout'; import TeamSettings from 'components/pages/settings/teams/TeamSettings'; import { useRouter } from 'next/router'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; export default function ({ disabled }) { const router = useRouter(); diff --git a/pages/settings/teams/index.js b/src/pages/settings/teams/index.js similarity index 91% rename from pages/settings/teams/index.js rename to src/pages/settings/teams/index.js index 51739c31..7e56a7d7 100644 --- a/pages/settings/teams/index.js +++ b/src/pages/settings/teams/index.js @@ -1,7 +1,7 @@ import AppLayout from 'components/layout/AppLayout'; import SettingsLayout from 'components/layout/SettingsLayout'; import TeamsList from 'components/pages/settings/teams/TeamsList'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; export default function ({ disabled }) { const { formatMessage, labels } = useMessages(); diff --git a/pages/settings/users/[id].js b/src/pages/settings/users/[id].js similarity index 93% rename from pages/settings/users/[id].js rename to src/pages/settings/users/[id].js index d1e53419..fdd708fd 100644 --- a/pages/settings/users/[id].js +++ b/src/pages/settings/users/[id].js @@ -2,7 +2,7 @@ import AppLayout from 'components/layout/AppLayout'; import SettingsLayout from 'components/layout/SettingsLayout'; import UserSettings from 'components/pages/settings/users/UserSettings'; import { useRouter } from 'next/router'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; export default function ({ disabled }) { const router = useRouter(); diff --git a/pages/settings/users/index.js b/src/pages/settings/users/index.js similarity index 91% rename from pages/settings/users/index.js rename to src/pages/settings/users/index.js index ee325adc..90026d87 100644 --- a/pages/settings/users/index.js +++ b/src/pages/settings/users/index.js @@ -1,7 +1,7 @@ import AppLayout from 'components/layout/AppLayout'; import SettingsLayout from 'components/layout/SettingsLayout'; import UsersList from 'components/pages/settings/users/UsersList'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; export default function ({ disabled }) { const { formatMessage, labels } = useMessages(); diff --git a/pages/settings/websites/[id].js b/src/pages/settings/websites/[id].js similarity index 93% rename from pages/settings/websites/[id].js rename to src/pages/settings/websites/[id].js index f828369e..506da107 100644 --- a/pages/settings/websites/[id].js +++ b/src/pages/settings/websites/[id].js @@ -2,7 +2,7 @@ import AppLayout from 'components/layout/AppLayout'; import { useRouter } from 'next/router'; import WebsiteSettings from 'components/pages/settings/websites/WebsiteSettings'; import SettingsLayout from 'components/layout/SettingsLayout'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; export default function ({ disabled }) { const router = useRouter(); diff --git a/pages/settings/websites/index.js b/src/pages/settings/websites/index.js similarity index 92% rename from pages/settings/websites/index.js rename to src/pages/settings/websites/index.js index 899ad7c7..f4551f4a 100644 --- a/pages/settings/websites/index.js +++ b/src/pages/settings/websites/index.js @@ -1,7 +1,7 @@ import AppLayout from 'components/layout/AppLayout'; import SettingsLayout from 'components/layout/SettingsLayout'; import WebsitesList from 'components/pages/settings/websites/WebsitesList'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; export default function ({ disabled }) { const { formatMessage, labels } = useMessages(); diff --git a/pages/share/[...id].js b/src/pages/share/[...id].js similarity index 89% rename from pages/share/[...id].js rename to src/pages/share/[...id].js index 1e424382..a2c69df8 100644 --- a/pages/share/[...id].js +++ b/src/pages/share/[...id].js @@ -1,7 +1,7 @@ import { useRouter } from 'next/router'; import ShareLayout from 'components/layout/ShareLayout'; import WebsiteDetailsPage from 'components/pages/websites/WebsiteDetailsPage'; -import useShareToken from 'hooks/useShareToken'; +import useShareToken from 'components/hooks/useShareToken'; export default function () { const router = useRouter(); diff --git a/pages/sso.js b/src/pages/sso.js similarity index 100% rename from pages/sso.js rename to src/pages/sso.js diff --git a/pages/websites/[id]/event-data.js b/src/pages/websites/[id]/event-data.js similarity index 89% rename from pages/websites/[id]/event-data.js rename to src/pages/websites/[id]/event-data.js index 8b44616d..b99d2fc9 100644 --- a/pages/websites/[id]/event-data.js +++ b/src/pages/websites/[id]/event-data.js @@ -1,7 +1,7 @@ import { useRouter } from 'next/router'; import AppLayout from 'components/layout/AppLayout'; import WebsiteEventDataPage from 'components/pages/websites/WebsiteEventDataPage'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; export default function () { const { formatMessage, labels } = useMessages(); diff --git a/pages/websites/[id]/index.js b/src/pages/websites/[id]/index.js similarity index 89% rename from pages/websites/[id]/index.js rename to src/pages/websites/[id]/index.js index bec7a45f..d3ec5f93 100644 --- a/pages/websites/[id]/index.js +++ b/src/pages/websites/[id]/index.js @@ -1,7 +1,7 @@ import { useRouter } from 'next/router'; import AppLayout from 'components/layout/AppLayout'; import WebsiteDetailsPage from 'components/pages/websites/WebsiteDetailsPage'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; export default function () { const { formatMessage, labels } = useMessages(); diff --git a/pages/websites/[id]/realtime.js b/src/pages/websites/[id]/realtime.js similarity index 100% rename from pages/websites/[id]/realtime.js rename to src/pages/websites/[id]/realtime.js diff --git a/pages/websites/[id]/reports.js b/src/pages/websites/[id]/reports.js similarity index 100% rename from pages/websites/[id]/reports.js rename to src/pages/websites/[id]/reports.js diff --git a/pages/websites/index.js b/src/pages/websites/index.js similarity index 84% rename from pages/websites/index.js rename to src/pages/websites/index.js index 42a327bc..43eed640 100644 --- a/pages/websites/index.js +++ b/src/pages/websites/index.js @@ -1,6 +1,6 @@ import AppLayout from 'components/layout/AppLayout'; import WebsitesPage from 'components/pages/websites/WebsitesPage'; -import useMessages from 'hooks/useMessages'; +import useMessages from 'components/hooks/useMessages'; export default function () { const { formatMessage, labels } = useMessages(); diff --git a/queries/admin/report.ts b/src/queries/admin/report.ts similarity index 100% rename from queries/admin/report.ts rename to src/queries/admin/report.ts diff --git a/queries/admin/team.ts b/src/queries/admin/team.ts similarity index 100% rename from queries/admin/team.ts rename to src/queries/admin/team.ts diff --git a/queries/admin/teamUser.ts b/src/queries/admin/teamUser.ts similarity index 100% rename from queries/admin/teamUser.ts rename to src/queries/admin/teamUser.ts diff --git a/queries/admin/teamWebsite.ts b/src/queries/admin/teamWebsite.ts similarity index 100% rename from queries/admin/teamWebsite.ts rename to src/queries/admin/teamWebsite.ts diff --git a/queries/admin/user.ts b/src/queries/admin/user.ts similarity index 94% rename from queries/admin/user.ts rename to src/queries/admin/user.ts index dfb923f3..dfe8ea28 100644 --- a/queries/admin/user.ts +++ b/src/queries/admin/user.ts @@ -37,10 +37,10 @@ export async function getUserByUsername(username: string, options: GetUserOption } export async function getUsers( - UserSearchFilter: UserSearchFilter = {}, + searchFilter: UserSearchFilter, options?: { include?: Prisma.UserInclude }, ): Promise> { - const { teamId, filter, filterType = USER_FILTER_TYPES.all } = UserSearchFilter; + const { teamId, filter, filterType = USER_FILTER_TYPES.all } = searchFilter; const mode = prisma.getSearchMode(); const where: Prisma.UserWhereInput = { @@ -67,19 +67,25 @@ export async function getUsers( }, }), }; + const [pageFilters, getParameters] = prisma.getPageFilters({ orderBy: 'username', - ...UserSearchFilter, + ...searchFilter, }); - const users = await prisma.client.user.findMany({ - where: { - ...where, - deletedAt: null, - }, - ...pageFilters, - ...(options?.include && { include: options.include }), - }); + const users = await prisma.client.user + .findMany({ + where: { + ...where, + deletedAt: null, + }, + ...pageFilters, + ...(options?.include && { include: options.include }), + }) + .then(a => { + return a.map(({ password, ...rest }) => rest); + }); + const count = await prisma.client.user.count({ where: { ...where, diff --git a/queries/admin/website.ts b/src/queries/admin/website.ts similarity index 100% rename from queries/admin/website.ts rename to src/queries/admin/website.ts diff --git a/queries/analytics/eventData/getEventDataEvents.ts b/src/queries/analytics/eventData/getEventDataEvents.ts similarity index 100% rename from queries/analytics/eventData/getEventDataEvents.ts rename to src/queries/analytics/eventData/getEventDataEvents.ts diff --git a/queries/analytics/eventData/getEventDataFields.ts b/src/queries/analytics/eventData/getEventDataFields.ts similarity index 100% rename from queries/analytics/eventData/getEventDataFields.ts rename to src/queries/analytics/eventData/getEventDataFields.ts diff --git a/queries/analytics/eventData/getEventDataStats.ts b/src/queries/analytics/eventData/getEventDataStats.ts similarity index 100% rename from queries/analytics/eventData/getEventDataStats.ts rename to src/queries/analytics/eventData/getEventDataStats.ts diff --git a/queries/analytics/eventData/getEventDataUsage.ts b/src/queries/analytics/eventData/getEventDataUsage.ts similarity index 100% rename from queries/analytics/eventData/getEventDataUsage.ts rename to src/queries/analytics/eventData/getEventDataUsage.ts diff --git a/queries/analytics/eventData/saveEventData.ts b/src/queries/analytics/eventData/saveEventData.ts similarity index 100% rename from queries/analytics/eventData/saveEventData.ts rename to src/queries/analytics/eventData/saveEventData.ts diff --git a/queries/analytics/events/getEventMetrics.ts b/src/queries/analytics/events/getEventMetrics.ts similarity index 100% rename from queries/analytics/events/getEventMetrics.ts rename to src/queries/analytics/events/getEventMetrics.ts diff --git a/queries/analytics/events/getEventUsage.ts b/src/queries/analytics/events/getEventUsage.ts similarity index 100% rename from queries/analytics/events/getEventUsage.ts rename to src/queries/analytics/events/getEventUsage.ts diff --git a/queries/analytics/events/getEvents.ts b/src/queries/analytics/events/getEvents.ts similarity index 100% rename from queries/analytics/events/getEvents.ts rename to src/queries/analytics/events/getEvents.ts diff --git a/queries/analytics/events/saveEvent.ts b/src/queries/analytics/events/saveEvent.ts similarity index 94% rename from queries/analytics/events/saveEvent.ts rename to src/queries/analytics/events/saveEvent.ts index 51087a59..a6457d7e 100644 --- a/queries/analytics/events/saveEvent.ts +++ b/src/queries/analytics/events/saveEvent.ts @@ -137,10 +137,15 @@ async function clickhouseQuery(data: { website_id: websiteId, session_id: sessionId, event_id: uuid(), - country: country ? country : null, - subdivision1: country && subdivision1 ? `${country}-${subdivision1}` : null, - subdivision2: subdivision2 ? subdivision2 : null, - city: city ? city : null, + country: country, + subdivision1: + country && subdivision1 + ? subdivision1.includes('-') + ? subdivision1 + : `${country}-${subdivision1}` + : null, + subdivision2: subdivision2, + city: city, url_path: urlPath?.substring(0, URL_LENGTH), url_query: urlQuery?.substring(0, URL_LENGTH), referrer_path: referrerPath?.substring(0, URL_LENGTH), diff --git a/queries/analytics/getActiveVisitors.ts b/src/queries/analytics/getActiveVisitors.ts similarity index 100% rename from queries/analytics/getActiveVisitors.ts rename to src/queries/analytics/getActiveVisitors.ts diff --git a/queries/analytics/getRealtimeData.ts b/src/queries/analytics/getRealtimeData.ts similarity index 100% rename from queries/analytics/getRealtimeData.ts rename to src/queries/analytics/getRealtimeData.ts diff --git a/queries/analytics/getValues.ts b/src/queries/analytics/getValues.ts similarity index 100% rename from queries/analytics/getValues.ts rename to src/queries/analytics/getValues.ts diff --git a/queries/analytics/getWebsiteDateRange.ts b/src/queries/analytics/getWebsiteDateRange.ts similarity index 100% rename from queries/analytics/getWebsiteDateRange.ts rename to src/queries/analytics/getWebsiteDateRange.ts diff --git a/queries/analytics/getWebsiteStats.ts b/src/queries/analytics/getWebsiteStats.ts similarity index 100% rename from queries/analytics/getWebsiteStats.ts rename to src/queries/analytics/getWebsiteStats.ts diff --git a/queries/analytics/pageviews/getPageviewMetrics.ts b/src/queries/analytics/pageviews/getPageviewMetrics.ts similarity index 100% rename from queries/analytics/pageviews/getPageviewMetrics.ts rename to src/queries/analytics/pageviews/getPageviewMetrics.ts diff --git a/queries/analytics/pageviews/getPageviewStats.ts b/src/queries/analytics/pageviews/getPageviewStats.ts similarity index 100% rename from queries/analytics/pageviews/getPageviewStats.ts rename to src/queries/analytics/pageviews/getPageviewStats.ts diff --git a/queries/analytics/reports/getFunnel.ts b/src/queries/analytics/reports/getFunnel.ts similarity index 100% rename from queries/analytics/reports/getFunnel.ts rename to src/queries/analytics/reports/getFunnel.ts diff --git a/queries/analytics/reports/getInsights.ts b/src/queries/analytics/reports/getInsights.ts similarity index 100% rename from queries/analytics/reports/getInsights.ts rename to src/queries/analytics/reports/getInsights.ts diff --git a/queries/analytics/reports/getRetention.ts b/src/queries/analytics/reports/getRetention.ts similarity index 100% rename from queries/analytics/reports/getRetention.ts rename to src/queries/analytics/reports/getRetention.ts diff --git a/queries/analytics/sessions/createSession.ts b/src/queries/analytics/sessions/createSession.ts similarity index 89% rename from queries/analytics/sessions/createSession.ts rename to src/queries/analytics/sessions/createSession.ts index 4fd36d2e..65dbd794 100644 --- a/queries/analytics/sessions/createSession.ts +++ b/src/queries/analytics/sessions/createSession.ts @@ -30,7 +30,7 @@ export async function createSession(data: Prisma.SessionCreateInput) { screen, language, country, - subdivision1: country && subdivision1 ? `${country}-${subdivision1}` : null, + subdivision1, subdivision2, city, }, diff --git a/queries/analytics/sessions/getSession.ts b/src/queries/analytics/sessions/getSession.ts similarity index 100% rename from queries/analytics/sessions/getSession.ts rename to src/queries/analytics/sessions/getSession.ts diff --git a/queries/analytics/sessions/getSessionMetrics.ts b/src/queries/analytics/sessions/getSessionMetrics.ts similarity index 79% rename from queries/analytics/sessions/getSessionMetrics.ts rename to src/queries/analytics/sessions/getSessionMetrics.ts index fb546a73..af358c52 100644 --- a/queries/analytics/sessions/getSessionMetrics.ts +++ b/src/queries/analytics/sessions/getSessionMetrics.ts @@ -25,17 +25,22 @@ async function relationalQuery(websiteId: string, column: string, filters: Query joinSession: SESSION_COLUMNS.includes(column), }, ); + const includeCountry = column === 'city' || column === 'subdivision1'; return rawQuery( ` - select ${column} x, count(*) y + select + ${column} x, + count(*) y + ${includeCountry ? ', country' : ''} from website_event ${joinSession} where website_event.website_id = {{websiteId::uuid}} and website_event.created_at between {{startDate}} and {{endDate}} and website_event.event_type = {{eventType}} ${filterQuery} - group by 1 + group by 1 + ${includeCountry ? ', 3' : ''} order by 2 desc limit 100`, params, @@ -48,17 +53,21 @@ async function clickhouseQuery(websiteId: string, column: string, filters: Query ...filters, eventType: EVENT_TYPE.pageView, }); + const includeCountry = column === 'city' || column === 'subdivision1'; return rawQuery( ` select - ${column} x, count(distinct session_id) y + ${column} x, + count(distinct session_id) y + ${includeCountry ? ', country' : ''} from website_event where website_id = {websiteId:UUID} and created_at between {startDate:DateTime} and {endDate:DateTime} and event_type = {eventType:UInt32} ${filterQuery} - group by x + group by x + ${includeCountry ? ', country' : ''} order by y desc limit 100 `, diff --git a/queries/analytics/sessions/getSessionStats.ts b/src/queries/analytics/sessions/getSessionStats.ts similarity index 100% rename from queries/analytics/sessions/getSessionStats.ts rename to src/queries/analytics/sessions/getSessionStats.ts diff --git a/queries/analytics/sessions/getSessions.ts b/src/queries/analytics/sessions/getSessions.ts similarity index 100% rename from queries/analytics/sessions/getSessions.ts rename to src/queries/analytics/sessions/getSessions.ts diff --git a/queries/analytics/sessions/saveSessionData.ts b/src/queries/analytics/sessions/saveSessionData.ts similarity index 100% rename from queries/analytics/sessions/saveSessionData.ts rename to src/queries/analytics/sessions/saveSessionData.ts diff --git a/queries/index.js b/src/queries/index.js similarity index 100% rename from queries/index.js rename to src/queries/index.js diff --git a/store/app.js b/src/store/app.js similarity index 100% rename from store/app.js rename to src/store/app.js diff --git a/store/dashboard.js b/src/store/dashboard.js similarity index 100% rename from store/dashboard.js rename to src/store/dashboard.js diff --git a/store/queries.js b/src/store/queries.js similarity index 100% rename from store/queries.js rename to src/store/queries.js diff --git a/store/version.js b/src/store/version.js similarity index 97% rename from store/version.js rename to src/store/version.js index c232c7fa..3b5afaac 100644 --- a/store/version.js +++ b/src/store/version.js @@ -1,5 +1,5 @@ import { create } from 'zustand'; -import produce from 'immer'; +import { produce } from 'immer'; import semver from 'semver'; import { CURRENT_VERSION, VERSION_CHECK, UPDATES_URL } from 'lib/constants'; import { getItem } from 'next-basics'; diff --git a/store/websites.ts b/src/store/websites.ts similarity index 94% rename from store/websites.ts rename to src/store/websites.ts index 0d210af6..5d0eeccd 100644 --- a/store/websites.ts +++ b/src/store/websites.ts @@ -1,5 +1,5 @@ import { create } from 'zustand'; -import produce from 'immer'; +import { produce } from 'immer'; import { DateRange } from 'lib/types'; const store = create(() => ({})); diff --git a/styles/index.css b/src/styles/index.css similarity index 100% rename from styles/index.css rename to src/styles/index.css diff --git a/styles/locale.css b/src/styles/locale.css similarity index 100% rename from styles/locale.css rename to src/styles/locale.css diff --git a/styles/variables.css b/src/styles/variables.css similarity index 100% rename from styles/variables.css rename to src/styles/variables.css diff --git a/tracker/index.d.ts b/src/tracker/index.d.ts similarity index 100% rename from tracker/index.d.ts rename to src/tracker/index.d.ts diff --git a/tracker/index.js b/src/tracker/index.js similarity index 100% rename from tracker/index.js rename to src/tracker/index.js diff --git a/tsconfig.json b/tsconfig.json index b022d603..71094dd7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,6 @@ "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, - "incremental": true, "lib": ["dom", "dom.iterable", "esnext"], "skipLibCheck": true, "esModuleInterop": true, @@ -18,11 +17,21 @@ "forceConsistentCasingInFileNames": true, "allowJs": true, "strict": true, - "baseUrl": ".", "strictNullChecks": false, "noEmit": true, - "jsx": "preserve" + "jsx": "preserve", + "incremental": false, + "baseUrl": "./src", + "paths": { + "assets/*": ["./assets/*"], + "components/*": ["./components/*"], + "lib/*": ["./lib/*"], + "pages/*": ["./pages/*"], + "queries/*": ["./queries/*"], + "store/*": ["./store/*"], + "styles/*": ["./styles/*"] + } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "next-env.d.ts"], "exclude": ["node_modules"] } diff --git a/yarn.lock b/yarn.lock index e18f833c..5d99a135 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15,7 +15,7 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.22.5": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.22.5": version "7.22.5" resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz" integrity sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ== @@ -1550,7 +1550,7 @@ resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz" integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== -"@jridgewell/sourcemap-codec@^1.4.13": +"@jridgewell/sourcemap-codec@^1.4.13", "@jridgewell/sourcemap-codec@^1.4.15": version "1.4.15" resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== @@ -1754,10 +1754,10 @@ slash "^3.0.0" tiny-glob "^0.2.9" -"@next/env@13.3.1": - version "13.3.1" - resolved "https://registry.yarnpkg.com/@next/env/-/env-13.3.1.tgz#589707043065f6b71d411ed9b8f1ffd057c0fd4a" - integrity sha512-EDtCoedIZC7JlUQ3uaQpSc4aVmyhbLHmQVALg7pFfQgOTjgSnn7mKtA0DiCMkYvvsx6aFb5octGMtWrOtGXW9A== +"@next/env@13.4.19": + version "13.4.19" + resolved "https://registry.yarnpkg.com/@next/env/-/env-13.4.19.tgz#46905b4e6f62da825b040343cbc233144e9578d3" + integrity sha512-FsAT5x0jF2kkhNkKkukhsyYOrRqtSxrEhfliniIq0bwWbuXLgyt3Gv0Ml+b91XwjwArmuP7NxCiGd++GGKdNMQ== "@next/eslint-plugin-next@12.3.4": version "12.3.4" @@ -1766,50 +1766,50 @@ dependencies: glob "7.1.7" -"@next/swc-darwin-arm64@13.3.1": - version "13.3.1" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.3.1.tgz#2c9719dd10a9cdf63bf50a7576b05dcf78999fe8" - integrity sha512-UXPtriEc/pBP8luSLSCZBcbzPeVv+SSjs9cH/KygTbhmACye8/OOXRZO13Z2Wq1G0gLmEAIHQAOuF+vafPd2lw== +"@next/swc-darwin-arm64@13.4.19": + version "13.4.19" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.19.tgz#77ad462b5ced4efdc26cb5a0053968d2c7dac1b6" + integrity sha512-vv1qrjXeGbuF2mOkhkdxMDtv9np7W4mcBtaDnHU+yJG+bBwa6rYsYSCI/9Xm5+TuF5SbZbrWO6G1NfTh1TMjvQ== -"@next/swc-darwin-x64@13.3.1": - version "13.3.1" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-13.3.1.tgz#0be90342c89e53a390ccd9bece15f7f5cd480049" - integrity sha512-lT36yYxosCfLtplFzJWgo0hrPu6/do8+msgM7oQkPeohDNdhjtjFUgOOwdSnPublLR6Mo2Ym4P/wl5OANuD2bw== +"@next/swc-darwin-x64@13.4.19": + version "13.4.19" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.19.tgz#aebe38713a4ce536ee5f2a291673e14b715e633a" + integrity sha512-jyzO6wwYhx6F+7gD8ddZfuqO4TtpJdw3wyOduR4fxTUCm3aLw7YmHGYNjS0xRSYGAkLpBkH1E0RcelyId6lNsw== -"@next/swc-linux-arm64-gnu@13.3.1": - version "13.3.1" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.3.1.tgz#a7353265839f8b8569a346a444dc3ab3770d297e" - integrity sha512-wRb76nLWJhonH8s3kxC/1tFguEkeOPayIwe9mkaz1G/yeS3OrjeyKMJsb4+Kdg0zbTo53bNCOl59NNtDM7yyyw== +"@next/swc-linux-arm64-gnu@13.4.19": + version "13.4.19" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.19.tgz#ec54db65b587939c7b94f9a84800f003a380f5a6" + integrity sha512-vdlnIlaAEh6H+G6HrKZB9c2zJKnpPVKnA6LBwjwT2BTjxI7e0Hx30+FoWCgi50e+YO49p6oPOtesP9mXDRiiUg== -"@next/swc-linux-arm64-musl@13.3.1": - version "13.3.1" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.3.1.tgz#24552e6102c350e372f83f505a1d93c880551a50" - integrity sha512-qz3BzjJRZ16Iq/jrp+pjiYOc0jTjHlfmxQmZk9x/+5uhRP6/eWQSTAPVJ33BMo6oK5O5N4644OgTAbzXzorecg== +"@next/swc-linux-arm64-musl@13.4.19": + version "13.4.19" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.19.tgz#1f5e2c1ea6941e7d530d9f185d5d64be04279d86" + integrity sha512-aU0HkH2XPgxqrbNRBFb3si9Ahu/CpaR5RPmN2s9GiM9qJCiBBlZtRTiEca+DC+xRPyCThTtWYgxjWHgU7ZkyvA== -"@next/swc-linux-x64-gnu@13.3.1": - version "13.3.1" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.3.1.tgz#5f335a683b6eafa52307b12af97782993b6c45ff" - integrity sha512-6mgkLmwlyWlomQmpl21I3hxgqE5INoW4owTlcLpNsd1V4wP+J46BlI/5zV5KWWbzjfncIqzXoeGs5Eg+1GHODA== +"@next/swc-linux-x64-gnu@13.4.19": + version "13.4.19" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.19.tgz#96b0882492a2f7ffcce747846d3680730f69f4d1" + integrity sha512-htwOEagMa/CXNykFFeAHHvMJeqZfNQEoQvHfsA4wgg5QqGNqD5soeCer4oGlCol6NGUxknrQO6VEustcv+Md+g== -"@next/swc-linux-x64-musl@13.3.1": - version "13.3.1" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.3.1.tgz#58e5aad6f97203a0788783f66324456c8f9cdb50" - integrity sha512-uqm5sielhQmKJM+qayIhgZv1KlS5pqTdQ99b+Z7hMWryXS96qE0DftTmMZowBcUL6x7s2vSXyH5wPtO1ON7LBg== +"@next/swc-linux-x64-musl@13.4.19": + version "13.4.19" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.19.tgz#f276b618afa321d2f7b17c81fc83f429fb0fd9d8" + integrity sha512-4Gj4vvtbK1JH8ApWTT214b3GwUh9EKKQjY41hH/t+u55Knxi/0wesMzwQRhppK6Ddalhu0TEttbiJ+wRcoEj5Q== -"@next/swc-win32-arm64-msvc@13.3.1": - version "13.3.1" - resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.3.1.tgz#f8ed1badab57ed4503969758754e6fb0cf326753" - integrity sha512-WomIiTj/v3LevltlibNQKmvrOymNRYL+a0dp5R73IwPWN5FvXWwSELN/kiNALig/+T3luc4qHNTyvMCp9L6U5Q== +"@next/swc-win32-arm64-msvc@13.4.19": + version "13.4.19" + resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.19.tgz#1599ae0d401da5ffca0947823dac577697cce577" + integrity sha512-bUfDevQK4NsIAHXs3/JNgnvEY+LRyneDN788W2NYiRIIzmILjba7LaQTfihuFawZDhRtkYCv3JDC3B4TwnmRJw== -"@next/swc-win32-ia32-msvc@13.3.1": - version "13.3.1" - resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.3.1.tgz#7f599c8975b09ee5527cc49b9e5a4d13be50635a" - integrity sha512-M+PoH+0+q658wRUbs285RIaSTYnGBSTdweH/0CdzDgA6Q4rBM0sQs4DHmO3BPP0ltCO/vViIoyG7ks66XmCA5g== +"@next/swc-win32-ia32-msvc@13.4.19": + version "13.4.19" + resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.19.tgz#55cdd7da90818f03e4da16d976f0cb22045d16fd" + integrity sha512-Y5kikILFAr81LYIFaw6j/NrOtmiM4Sf3GtOc0pn50ez2GCkr+oejYuKGcwAwq3jiTKuzF6OF4iT2INPoxRycEA== -"@next/swc-win32-x64-msvc@13.3.1": - version "13.3.1" - resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.3.1.tgz#192d43ab44ebb98bd4f5865d0e1d7ce62703182f" - integrity sha512-Sl1F4Vp5Z1rNXWZYqJwMuWRRol4bqOB6+/d7KqkgQ4AcafKPN1PZmpkCoxv4UFHtFNIB7EotnuIhtXu3zScicQ== +"@next/swc-win32-x64-msvc@13.4.19": + version "13.4.19" + resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.19.tgz#648f79c4e09279212ac90d871646ae12d80cdfce" + integrity sha512-YzA78jBDXMYiINdPdJJwGgPNT3YqBNNGhsthsDoWHL9p24tEJn9ViQf/ZqTbwSpX/RrkPupLfuuTH2sf73JBAw== "@nodelib/fs.scandir@2.1.5": version "2.1.5" @@ -1832,22 +1832,22 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@prisma/client@5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@prisma/client/-/client-5.0.0.tgz#9f0cd4164f4ffddb28bb1811c27eb7fa1e01a087" - integrity sha512-XlO5ELNAQ7rV4cXIDJUNBEgdLwX3pjtt9Q/RHqDpGf43szpNJx2hJnggfFs7TKNx0cOFsl6KJCSfqr5duEU/bQ== +"@prisma/client@5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@prisma/client/-/client-5.2.0.tgz#cbfdd440614b38736563a7999f39922fcde0ed50" + integrity sha512-AiTjJwR4J5Rh6Z/9ZKrBBLel3/5DzUNntMohOy7yObVnVoTNVFi2kvpLZlFuKO50d7yDspOtW6XBpiAd0BVXbQ== dependencies: - "@prisma/engines-version" "4.17.0-26.6b0aef69b7cdfc787f822ecd7cdc76d5f1991584" + "@prisma/engines-version" "5.2.0-25.2804dc98259d2ea960602aca6b8e7fdc03c1758f" -"@prisma/engines-version@4.17.0-26.6b0aef69b7cdfc787f822ecd7cdc76d5f1991584": - version "4.17.0-26.6b0aef69b7cdfc787f822ecd7cdc76d5f1991584" - resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-4.17.0-26.6b0aef69b7cdfc787f822ecd7cdc76d5f1991584.tgz#b36eda5620872d3fac810c302a7e46cf41daa033" - integrity sha512-HHiUF6NixsldsP3JROq07TYBLEjXFKr6PdH8H4gK/XAoTmIplOJBCgrIUMrsRAnEuGyRoRLXKXWUb943+PFoKQ== +"@prisma/engines-version@5.2.0-25.2804dc98259d2ea960602aca6b8e7fdc03c1758f": + version "5.2.0-25.2804dc98259d2ea960602aca6b8e7fdc03c1758f" + resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-5.2.0-25.2804dc98259d2ea960602aca6b8e7fdc03c1758f.tgz#11366e7ff031c908debf4983248d40046016de37" + integrity sha512-jsnKT5JIDIE01lAeCj2ghY9IwxkedhKNvxQeoyLs6dr4ZXynetD0vTy7u6wMJt8vVPv8I5DPy/I4CFaoXAgbtg== -"@prisma/engines@5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-5.0.0.tgz#5249650eabe77c458c90f2be97d8210353c2e22e" - integrity sha512-kyT/8fd0OpWmhAU5YnY7eP31brW1q1YrTGoblWrhQJDiN/1K+Z8S1kylcmtjqx5wsUGcP1HBWutayA/jtyt+sg== +"@prisma/engines@5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-5.2.0.tgz#e5dff48eb324c8137393933292d44ea5c3bc2ce7" + integrity sha512-dT7FOLUCdZmq+AunLqB1Iz+ZH/IIS1Fz2THmKZQ6aFONrQD/BQ5ecJ7g2wGS2OgyUFf4OaLam6/bxmgdOBDqig== "@react-spring/animated@~9.7.3": version "9.7.3" @@ -1969,19 +1969,19 @@ dependencies: slash "^4.0.0" -"@rollup/plugin-buble@^0.21.3": - version "0.21.3" - resolved "https://registry.npmjs.org/@rollup/plugin-buble/-/plugin-buble-0.21.3.tgz" - integrity sha512-Iv8cCuFPnMdqV4pcyU+OrfjOfagPArRQ1PyQjx5KgHk3dARedI+8PNTLSMpJts0lQJr8yF2pAU4GxpxCBJ9HYw== +"@rollup/plugin-buble@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@rollup/plugin-buble/-/plugin-buble-1.0.2.tgz#30af390341b0888490f781fcf17e469198d118a2" + integrity sha512-Hz9+AigRWwS93vmorrVrhyG9SdSCZAkBDx614w09iFQYFUAP2HmdUrQyZsb1WO2n+iDvPFznrTE16la+eGNcEQ== dependencies: - "@rollup/pluginutils" "^3.0.8" + "@rollup/pluginutils" "^5.0.1" "@types/buble" "^0.19.2" buble "^0.20.0" -"@rollup/plugin-commonjs@^24.1.0": - version "24.1.0" - resolved "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-24.1.0.tgz" - integrity sha512-eSL45hjhCWI0jCCXcNtLVqM5N1JlBGvlFfY0m6oOYnLCJ6N0qEXoZql4sY2MOUArzhH4SA/qBpTxvvZp2Sc+DQ== +"@rollup/plugin-commonjs@^25.0.4": + version "25.0.4" + resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.4.tgz#a7547a0c4ec3fa79818eb313e1de0023e548f4e6" + integrity sha512-L92Vz9WUZXDnlQQl3EwbypJR4+DM2EbsO+/KOcEkP4Mc6Ct453EeDB2uH9lgRwj4w5yflgNpq9pHOiY8aoUXBQ== dependencies: "@rollup/pluginutils" "^5.0.1" commondir "^1.0.1" @@ -1992,15 +1992,15 @@ "@rollup/plugin-json@^6.0.0": version "6.0.0" - resolved "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.0.0.tgz" + resolved "https://registry.yarnpkg.com/@rollup/plugin-json/-/plugin-json-6.0.0.tgz#199fea6670fd4dfb1f4932250569b14719db234a" integrity sha512-i/4C5Jrdr1XUarRhVu27EEwjt4GObltD7c+MkCIpO2QIbojw8MUs+CCTqOphQi3Qtg1FLmYt+l+6YeoIf51J7w== dependencies: "@rollup/pluginutils" "^5.0.1" -"@rollup/plugin-node-resolve@^15.0.2": - version "15.1.0" - resolved "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.1.0.tgz" - integrity sha512-xeZHCgsiZ9pzYVgAo9580eCGqwh/XCEUM9q6iQfGNocjgkufHAqC3exA+45URvhiYV8sBF9RlBai650eNs7AsA== +"@rollup/plugin-node-resolve@^15.2.0": + version "15.2.0" + resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.0.tgz#982053b237f81471aace570472e88a456d211621" + integrity sha512-mKur03xNGT8O9ODO6FtT43ITGqHWZbKPdVJHZb+iV9QYcdlhUUB0wgknvA4KCUmC5oHJF6O2W1EgmyOQyVUI4Q== dependencies: "@rollup/pluginutils" "^5.0.1" "@types/resolve" "1.20.2" @@ -2009,22 +2009,13 @@ is-module "^1.0.0" resolve "^1.22.1" -"@rollup/plugin-replace@^4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-4.0.0.tgz" - integrity sha512-+rumQFiaNac9y64OHtkHGmdjm7us9bo1PlbgQfdihQtuNxzjpaB064HbRnewUOggLQxVCCyINfStkgmBeQpv1g== +"@rollup/plugin-replace@^5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@rollup/plugin-replace/-/plugin-replace-5.0.2.tgz#45f53501b16311feded2485e98419acb8448c61d" + integrity sha512-M9YXNekv/C/iHHK+cvORzfRYfPbq0RDD8r0G+bMiTXjNGKulPnCT9O3Ss46WfhI6ZOCgApOP7xAdmCQJ+U2LAA== dependencies: - "@rollup/pluginutils" "^3.1.0" - magic-string "^0.25.7" - -"@rollup/pluginutils@^3.0.8", "@rollup/pluginutils@^3.1.0": - version "3.1.0" - resolved "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz" - integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== - dependencies: - "@types/estree" "0.0.39" - estree-walker "^1.0.1" - picomatch "^2.2.2" + "@rollup/pluginutils" "^5.0.1" + magic-string "^0.27.0" "@rollup/pluginutils@^5.0.1", "@rollup/pluginutils@^5.0.2": version "5.0.2" @@ -2040,75 +2031,99 @@ resolved "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz" integrity sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg== +"@svgr/babel-plugin-add-jsx-attribute@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz#4001f5d5dd87fa13303e36ee106e3ff3a7eb8b22" + integrity sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g== + "@svgr/babel-plugin-add-jsx-attribute@^6.5.1": version "6.5.1" resolved "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-6.5.1.tgz" integrity sha512-9PYGcXrAxitycIjRmZB+Q0JaN07GZIWaTBIGQzfaZv+qr1n8X1XUEJ5rZ/vx6OVD9RRYlrNnXWExQXcmZeD/BQ== -"@svgr/babel-plugin-add-jsx-attribute@^7.0.0": - version "7.0.0" - resolved "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-7.0.0.tgz" - integrity sha512-khWbXesWIP9v8HuKCl2NU2HNAyqpSQ/vkIl36Nbn4HIwEYSRWL0H7Gs6idJdha2DkpFDWlsqMELvoCE8lfFY6Q== - -"@svgr/babel-plugin-remove-jsx-attribute@*", "@svgr/babel-plugin-remove-jsx-attribute@^7.0.0": +"@svgr/babel-plugin-remove-jsx-attribute@*": version "7.0.0" resolved "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-7.0.0.tgz" integrity sha512-iiZaIvb3H/c7d3TH2HBeK91uI2rMhZNwnsIrvd7ZwGLkFw6mmunOCoVnjdYua662MqGFxlN9xTq4fv9hgR4VXQ== -"@svgr/babel-plugin-remove-jsx-empty-expression@*", "@svgr/babel-plugin-remove-jsx-empty-expression@^7.0.0": +"@svgr/babel-plugin-remove-jsx-attribute@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz#69177f7937233caca3a1afb051906698f2f59186" + integrity sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA== + +"@svgr/babel-plugin-remove-jsx-empty-expression@*": version "7.0.0" resolved "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-7.0.0.tgz" integrity sha512-sQQmyo+qegBx8DfFc04PFmIO1FP1MHI1/QEpzcIcclo5OAISsOJPW76ZIs0bDyO/DBSJEa/tDa1W26pVtt0FRw== +"@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz#c2c48104cfd7dcd557f373b70a56e9e3bdae1d44" + integrity sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA== + +"@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz#8fbb6b2e91fa26ac5d4aa25c6b6e4f20f9c0ae27" + integrity sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ== + "@svgr/babel-plugin-replace-jsx-attribute-value@^6.5.1": version "6.5.1" resolved "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-6.5.1.tgz" integrity sha512-8DPaVVE3fd5JKuIC29dqyMB54sA6mfgki2H2+swh+zNJoynC8pMPzOkidqHOSc6Wj032fhl8Z0TVn1GiPpAiJg== -"@svgr/babel-plugin-replace-jsx-attribute-value@^7.0.0": - version "7.0.0" - resolved "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-7.0.0.tgz" - integrity sha512-i6MaAqIZXDOJeikJuzocByBf8zO+meLwfQ/qMHIjCcvpnfvWf82PFvredEZElErB5glQFJa2KVKk8N2xV6tRRA== +"@svgr/babel-plugin-svg-dynamic-title@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz#1d5ba1d281363fc0f2f29a60d6d936f9bbc657b0" + integrity sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og== "@svgr/babel-plugin-svg-dynamic-title@^6.5.1": version "6.5.1" resolved "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-6.5.1.tgz" integrity sha512-FwOEi0Il72iAzlkaHrlemVurgSQRDFbk0OC8dSvD5fSBPHltNh7JtLsxmZUhjYBZo2PpcU/RJvvi6Q0l7O7ogw== -"@svgr/babel-plugin-svg-dynamic-title@^7.0.0": - version "7.0.0" - resolved "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-7.0.0.tgz" - integrity sha512-BoVSh6ge3SLLpKC0pmmN9DFlqgFy4NxNgdZNLPNJWBUU7TQpDWeBuyVuDW88iXydb5Cv0ReC+ffa5h3VrKfk1w== +"@svgr/babel-plugin-svg-em-dimensions@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz#35e08df300ea8b1d41cb8f62309c241b0369e501" + integrity sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g== "@svgr/babel-plugin-svg-em-dimensions@^6.5.1": version "6.5.1" resolved "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-6.5.1.tgz" integrity sha512-gWGsiwjb4tw+ITOJ86ndY/DZZ6cuXMNE/SjcDRg+HLuCmwpcjOktwRF9WgAiycTqJD/QXqL2f8IzE2Rzh7aVXA== -"@svgr/babel-plugin-svg-em-dimensions@^7.0.0": - version "7.0.0" - resolved "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-7.0.0.tgz" - integrity sha512-tNDcBa+hYn0gO+GkP/AuNKdVtMufVhU9fdzu+vUQsR18RIJ9RWe7h/pSBY338RO08wArntwbDk5WhQBmhf2PaA== +"@svgr/babel-plugin-transform-react-native-svg@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz#90a8b63998b688b284f255c6a5248abd5b28d754" + integrity sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q== "@svgr/babel-plugin-transform-react-native-svg@^6.5.1": version "6.5.1" resolved "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-6.5.1.tgz" integrity sha512-2jT3nTayyYP7kI6aGutkyfJ7UMGtuguD72OjeGLwVNyfPRBD8zQthlvL+fAbAKk5n9ZNcvFkp/b1lZ7VsYqVJg== -"@svgr/babel-plugin-transform-react-native-svg@^7.0.0": - version "7.0.0" - resolved "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-7.0.0.tgz" - integrity sha512-qw54u8ljCJYL2KtBOjI5z7Nzg8LnSvQOP5hPKj77H4VQL4+HdKbAT5pnkkZLmHKYwzsIHSYKXxHouD8zZamCFQ== +"@svgr/babel-plugin-transform-svg-component@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz#013b4bfca88779711f0ed2739f3f7efcefcf4f7e" + integrity sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw== "@svgr/babel-plugin-transform-svg-component@^6.5.1": version "6.5.1" resolved "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-6.5.1.tgz" integrity sha512-a1p6LF5Jt33O3rZoVRBqdxL350oge54iZWHNI6LJB5tQ7EelvD/Mb1mfBiZNAan0dt4i3VArkFRjA4iObuNykQ== -"@svgr/babel-plugin-transform-svg-component@^7.0.0": - version "7.0.0" - resolved "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-7.0.0.tgz" - integrity sha512-CcFECkDj98daOg9jE3Bh3uyD9kzevCAnZ+UtzG6+BQG/jOQ2OA3jHnX6iG4G1MCJkUQFnUvEv33NvQfqrb/F3A== +"@svgr/babel-preset@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-8.1.0.tgz#0e87119aecdf1c424840b9d4565b7137cabf9ece" + integrity sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug== + dependencies: + "@svgr/babel-plugin-add-jsx-attribute" "8.0.0" + "@svgr/babel-plugin-remove-jsx-attribute" "8.0.0" + "@svgr/babel-plugin-remove-jsx-empty-expression" "8.0.0" + "@svgr/babel-plugin-replace-jsx-attribute-value" "8.0.0" + "@svgr/babel-plugin-svg-dynamic-title" "8.0.0" + "@svgr/babel-plugin-svg-em-dimensions" "8.0.0" + "@svgr/babel-plugin-transform-react-native-svg" "8.1.0" + "@svgr/babel-plugin-transform-svg-component" "8.0.0" "@svgr/babel-preset@^6.5.1": version "6.5.1" @@ -2124,19 +2139,16 @@ "@svgr/babel-plugin-transform-react-native-svg" "^6.5.1" "@svgr/babel-plugin-transform-svg-component" "^6.5.1" -"@svgr/babel-preset@^7.0.0": - version "7.0.0" - resolved "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-7.0.0.tgz" - integrity sha512-EX/NHeFa30j5UjldQGVQikuuQNHUdGmbh9kEpBKofGUtF0GUPJ4T4rhoYiqDAOmBOxojyot36JIFiDUHUK1ilQ== +"@svgr/core@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/core/-/core-8.1.0.tgz#41146f9b40b1a10beaf5cc4f361a16a3c1885e88" + integrity sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA== dependencies: - "@svgr/babel-plugin-add-jsx-attribute" "^7.0.0" - "@svgr/babel-plugin-remove-jsx-attribute" "^7.0.0" - "@svgr/babel-plugin-remove-jsx-empty-expression" "^7.0.0" - "@svgr/babel-plugin-replace-jsx-attribute-value" "^7.0.0" - "@svgr/babel-plugin-svg-dynamic-title" "^7.0.0" - "@svgr/babel-plugin-svg-em-dimensions" "^7.0.0" - "@svgr/babel-plugin-transform-react-native-svg" "^7.0.0" - "@svgr/babel-plugin-transform-svg-component" "^7.0.0" + "@babel/core" "^7.21.3" + "@svgr/babel-preset" "8.1.0" + camelcase "^6.2.0" + cosmiconfig "^8.1.3" + snake-case "^3.0.4" "@svgr/core@^6.5.1": version "6.5.1" @@ -2149,15 +2161,13 @@ camelcase "^6.2.0" cosmiconfig "^7.0.1" -"@svgr/core@^7.0.0": - version "7.0.0" - resolved "https://registry.npmjs.org/@svgr/core/-/core-7.0.0.tgz" - integrity sha512-ztAoxkaKhRVloa3XydohgQQCb0/8x9T63yXovpmHzKMkHO6pkjdsIAWKOS4bE95P/2quVh1NtjSKlMRNzSBffw== +"@svgr/hast-util-to-babel-ast@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz#6952fd9ce0f470e1aded293b792a2705faf4ffd4" + integrity sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q== dependencies: - "@babel/core" "^7.21.3" - "@svgr/babel-preset" "^7.0.0" - camelcase "^6.2.0" - cosmiconfig "^8.1.3" + "@babel/types" "^7.21.3" + entities "^4.4.0" "@svgr/hast-util-to-babel-ast@^6.5.1": version "6.5.1" @@ -2167,13 +2177,15 @@ "@babel/types" "^7.20.0" entities "^4.4.0" -"@svgr/hast-util-to-babel-ast@^7.0.0": - version "7.0.0" - resolved "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-7.0.0.tgz" - integrity sha512-42Ej9sDDEmsJKjrfQ1PHmiDiHagh/u9AHO9QWbeNx4KmD9yS5d1XHmXUNINfUcykAU+4431Cn+k6Vn5mWBYimQ== +"@svgr/plugin-jsx@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz#96969f04a24b58b174ee4cd974c60475acbd6928" + integrity sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA== dependencies: - "@babel/types" "^7.21.3" - entities "^4.4.0" + "@babel/core" "^7.21.3" + "@svgr/babel-preset" "8.1.0" + "@svgr/hast-util-to-babel-ast" "8.0.0" + svg-parser "^2.0.4" "@svgr/plugin-jsx@^6.5.1": version "6.5.1" @@ -2185,15 +2197,14 @@ "@svgr/hast-util-to-babel-ast" "^6.5.1" svg-parser "^2.0.4" -"@svgr/plugin-jsx@^7.0.0": - version "7.0.0" - resolved "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-7.0.0.tgz" - integrity sha512-SWlTpPQmBUtLKxXWgpv8syzqIU8XgFRvyhfkam2So8b3BE0OS0HPe5UfmlJ2KIC+a7dpuuYovPR2WAQuSyMoPw== +"@svgr/plugin-svgo@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz#b115b7b967b564f89ac58feae89b88c3decd0f00" + integrity sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA== dependencies: - "@babel/core" "^7.21.3" - "@svgr/babel-preset" "^7.0.0" - "@svgr/hast-util-to-babel-ast" "^7.0.0" - svg-parser "^2.0.4" + cosmiconfig "^8.1.3" + deepmerge "^4.3.1" + svgo "^3.0.2" "@svgr/plugin-svgo@^6.5.1": version "6.5.1" @@ -2204,19 +2215,10 @@ deepmerge "^4.2.2" svgo "^2.8.0" -"@svgr/plugin-svgo@^7.0.0": - version "7.0.0" - resolved "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-7.0.0.tgz" - integrity sha512-263znzlu3qTKj71/ot5G9l2vpL4CW+pr2IexBFIwwB+fRAXE9Xnw2rUFgE6P4+37N9siOuC4lKkgBfUCOLFRKQ== - dependencies: - cosmiconfig "^8.1.3" - deepmerge "^4.3.1" - svgo "^3.0.2" - -"@svgr/rollup@^7.0.0": - version "7.0.0" - resolved "https://registry.npmjs.org/@svgr/rollup/-/rollup-7.0.0.tgz" - integrity sha512-zlx0lxtxTnrXFF+ISuff+hht2XcWXa6uXEliwQbz+o0/qRIrcqyB9ShalO9ekVWB5icgxCWQ5lDaULJTt/pTlA== +"@svgr/rollup@^8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/rollup/-/rollup-8.1.0.tgz#2c8e09655336cda4b7843799a5d2a5860300b030" + integrity sha512-0XR1poYvPQoPpmfDYLEqUGu5ePAQ4pdgN3VFsZBNAeze7qubVpsIY1o1R6PZpKep/DKu33GSm2NhwpCLkMs2Cw== dependencies: "@babel/core" "^7.21.3" "@babel/plugin-transform-react-constant-elements" "^7.21.3" @@ -2224,9 +2226,9 @@ "@babel/preset-react" "^7.18.6" "@babel/preset-typescript" "^7.21.0" "@rollup/pluginutils" "^5.0.2" - "@svgr/core" "^7.0.0" - "@svgr/plugin-jsx" "^7.0.0" - "@svgr/plugin-svgo" "^7.0.0" + "@svgr/core" "8.1.0" + "@svgr/plugin-jsx" "8.1.0" + "@svgr/plugin-svgo" "8.1.0" "@svgr/webpack@^6.2.1": version "6.5.1" @@ -2242,24 +2244,24 @@ "@svgr/plugin-jsx" "^6.5.1" "@svgr/plugin-svgo" "^6.5.1" -"@swc/helpers@0.5.0": - version "0.5.0" - resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.0.tgz#bf1d807b60f7290d0ec763feea7ccdeda06e85f1" - integrity sha512-SjY/p4MmECVVEWspzSRpQEM3sjR17sP8PbGxELWrT+YZMBfiUyt1MRUNjMV23zohwlG2HYtCQOsCwsTHguXkyg== +"@swc/helpers@0.5.1": + version "0.5.1" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.1.tgz#e9031491aa3f26bfcc974a67f48bd456c8a5357a" + integrity sha512-sJ902EfIzn1Fa+qYmjdQqh8tPsoxyBz+8yBKC2HKUxyezKJFwPGOn7pv4WY6QuQW//ySQi5lJjA/ZT9sNWWNTg== dependencies: tslib "^2.4.0" -"@tanstack/query-core@4.32.0": - version "4.32.0" - resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-4.32.0.tgz#e0f4a830283612430450c13badd353766423f523" - integrity sha512-ei4IYwL2kmlKSlCw9WgvV7PpXi0MiswVwfQRxawhJA690zWO3dU49igaQ/UMTl+Jy9jj9dK5IKAYvbX7kUvviQ== +"@tanstack/query-core@4.33.0": + version "4.33.0" + resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-4.33.0.tgz#7756da9a75a424e521622b1d84eb55b7a2b33715" + integrity sha512-qYu73ptvnzRh6se2nyBIDHGBQvPY1XXl3yR769B7B6mIDD7s+EZhdlWHQ67JI6UOTFRaI7wupnTnwJ3gE0Mr/g== -"@tanstack/react-query@^4.16.1": - version "4.32.0" - resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-4.32.0.tgz#701b45b149cfd4b54a68705f9100973db3ba5d5d" - integrity sha512-B8WUMcByYAH9500ENejDCATOmEZhqjtS9wsfiQ3BNa+s+yAynY8SESI8WWHhSqUmjd0pmCSFRP6BOUGSda3QXA== +"@tanstack/react-query@^4.33.0": + version "4.33.0" + resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-4.33.0.tgz#e927b0343a6ecaa948fee59e9ca98fe561062638" + integrity sha512-97nGbmDK0/m0B86BdiXzx3EW9RcDYKpnyL2+WwyuLHEgpfThYAnXFaMMmnTDuAO4bQJXEhflumIEUfKmP7ESGA== dependencies: - "@tanstack/query-core" "4.32.0" + "@tanstack/query-core" "4.33.0" use-sync-external-store "^1.2.0" "@trysound/sax@0.2.0": @@ -2332,16 +2334,18 @@ resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz" integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA== -"@types/estree@0.0.39": - version "0.0.39" - resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz" - integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== - "@types/estree@^0.0.50": version "0.0.50" resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz" integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw== +"@types/fs-extra@^8.0.1": + version "8.1.2" + resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-8.1.2.tgz#7125cc2e4bdd9bd2fc83005ffdb1d0ba00cca61f" + integrity sha512-SvSrYXfWSc7R4eqnOzbQF4TZmfpNSM9FrSWLU3EUnWBuyZqNBOrv1B1JA3byUDPUl9z4Ab3jeZG2eDdySlgNMg== + dependencies: + "@types/node" "*" + "@types/fs-extra@^9.0.1": version "9.0.13" resolved "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz" @@ -2410,6 +2414,11 @@ resolved "https://registry.npmjs.org/@types/node/-/node-17.0.38.tgz" integrity sha512-5jY9RhV7c0Z4Jy09G+NIDTsCZ5G0L5n+Z+p+Y7t5VJHM30bgwzSjVtlcBxqAj+6L/swIlvtOSzr8rBk/aNyV2g== +"@types/node@^18.11.9": + version "18.17.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.17.6.tgz#0296e9a30b22d2a8fcaa48d3c45afe51474ca55b" + integrity sha512-fGmT/P7z7ecA6bv/ia5DlaWCH4YeZvAQMNpUhrJjtAhOhZfoxS1VLUgU2pdk63efSjQaOJWdXMuAJsws+8I6dg== + "@types/normalize-package-data@^2.4.0": version "2.4.1" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" @@ -2425,6 +2434,13 @@ resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz" integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== +"@types/react-dom@^18.0.8": + version "18.2.7" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.7.tgz#67222a08c0a6ae0a0da33c3532348277c70abb63" + integrity sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA== + dependencies: + "@types/react" "*" + "@types/react-redux@^7.1.20": version "7.1.24" resolved "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.24.tgz" @@ -2444,6 +2460,15 @@ "@types/scheduler" "*" csstype "^3.0.2" +"@types/react@^18.0.25": + version "18.2.20" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.20.tgz#1605557a83df5c8a2cc4eeb743b3dfc0eb6aaeb2" + integrity sha512-WKNtmsLWJM/3D5mG4U84cysVY31ivmyw85dE84fOCk5Hx78wezB/XEjVPWl2JTZ5FkEeaTJf+VgUAUn3PE7Isw== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + "@types/resolve@1.20.2": version "1.20.2" resolved "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz" @@ -3432,7 +3457,7 @@ colord@^2.9.1, colord@^2.9.2, colord@^2.9.3: resolved "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz" integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== -colorette@^1.4.0: +colorette@^1.1.0, colorette@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz" integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g== @@ -4097,6 +4122,14 @@ domutils@^3.0.1: domelementtype "^2.3.0" domhandler "^5.0.1" +dot-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" + integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + dotenv@^10.0.0: version "10.0.0" resolved "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz" @@ -4560,11 +4593,6 @@ estree-walker@^0.6.1: resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz" integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w== -estree-walker@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz" - integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== - estree-walker@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz" @@ -4822,6 +4850,15 @@ fs-extra@^11.0.0: jsonfile "^6.0.1" universalify "^2.0.0" +fs-extra@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs-extra@^9.0.0: version "9.1.0" resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz" @@ -4945,6 +4982,11 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" +glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + glob@7.1.7: version "7.1.7" resolved "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz" @@ -5020,6 +5062,20 @@ globalyzer@0.1.0: resolved "https://registry.yarnpkg.com/globalyzer/-/globalyzer-0.1.0.tgz#cb76da79555669a1519d5a8edf093afaa0bf1465" integrity sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q== +globby@10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/globby/-/globby-10.0.1.tgz#4782c34cb75dd683351335c5829cc3420e606b22" + integrity sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A== + dependencies: + "@types/glob" "^7.1.1" + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.0.3" + glob "^7.1.3" + ignore "^5.1.1" + merge2 "^1.2.3" + slash "^3.0.0" + globby@^10.0.1: version "10.0.2" resolved "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz" @@ -5532,6 +5588,11 @@ is-plain-obj@^2.0.0: resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz" integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== +is-plain-object@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-3.0.1.tgz#662d92d24c0aa4302407b0d45d21f2251c85f85b" + integrity sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g== + is-plain-object@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" @@ -5752,6 +5813,13 @@ jsonc-parser@^3.2.0: resolved "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz" integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w== +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== + optionalDependencies: + graceful-fs "^4.1.6" + jsonfile@^6.0.1: version "6.1.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" @@ -6041,6 +6109,13 @@ loud-rejection@^2.2.0: currently-unhandled "^0.4.1" signal-exit "^3.0.2" +lower-case@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" + integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== + dependencies: + tslib "^2.0.3" + lru-cache@^10.0.0: version "10.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.0.tgz#b9e2a6a72a129d81ab317202d93c7691df727e61" @@ -6074,12 +6149,12 @@ magic-string@^0.27.0: dependencies: "@jridgewell/sourcemap-codec" "^1.4.13" -magic-string@^0.30.0: - version "0.30.0" - resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz" - integrity sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ== +magic-string@^0.30.2: + version "0.30.2" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.2.tgz#dcf04aad3d0d1314bc743d076c50feb29b3c7aca" + integrity sha512-lNZdu7pewtq/ZvWUp9Wpf/x7WzMTsR26TWV03BRZrXFsv+BI6dy8RAiKgm1uM/kyR0rCfUcqvOlXKG66KhIGug== dependencies: - "@jridgewell/sourcemap-codec" "^1.4.13" + "@jridgewell/sourcemap-codec" "^1.4.15" make-dir@^3.0.0: version "3.1.0" @@ -6380,33 +6455,43 @@ next-basics@^0.36.0: jsonwebtoken "^9.0.0" pure-rand "^6.0.2" -next@13.3.1: - version "13.3.1" - resolved "https://registry.yarnpkg.com/next/-/next-13.3.1.tgz#17625f7423db2e059d71b41bd9031756cf2b33bc" - integrity sha512-eByWRxPzKHs2oQz1yE41LX35umhz86ZSZ+mYyXBqn2IBi2hyUqxBA88avywdr4uyH+hCJczegGsDGWbzQA5Rqw== +next@13.4.19: + version "13.4.19" + resolved "https://registry.yarnpkg.com/next/-/next-13.4.19.tgz#2326e02aeedee2c693d4f37b90e4f0ed6882b35f" + integrity sha512-HuPSzzAbJ1T4BD8e0bs6B9C1kWQ6gv8ykZoRWs5AQoiIuqbGHHdQO7Ljuvg05Q0Z24E2ABozHe6FxDvI6HfyAw== dependencies: - "@next/env" "13.3.1" - "@swc/helpers" "0.5.0" + "@next/env" "13.4.19" + "@swc/helpers" "0.5.1" busboy "1.6.0" caniuse-lite "^1.0.30001406" postcss "8.4.14" styled-jsx "5.1.1" + watchpack "2.4.0" + zod "3.21.4" optionalDependencies: - "@next/swc-darwin-arm64" "13.3.1" - "@next/swc-darwin-x64" "13.3.1" - "@next/swc-linux-arm64-gnu" "13.3.1" - "@next/swc-linux-arm64-musl" "13.3.1" - "@next/swc-linux-x64-gnu" "13.3.1" - "@next/swc-linux-x64-musl" "13.3.1" - "@next/swc-win32-arm64-msvc" "13.3.1" - "@next/swc-win32-ia32-msvc" "13.3.1" - "@next/swc-win32-x64-msvc" "13.3.1" + "@next/swc-darwin-arm64" "13.4.19" + "@next/swc-darwin-x64" "13.4.19" + "@next/swc-linux-arm64-gnu" "13.4.19" + "@next/swc-linux-arm64-musl" "13.4.19" + "@next/swc-linux-x64-gnu" "13.4.19" + "@next/swc-linux-x64-musl" "13.4.19" + "@next/swc-win32-arm64-msvc" "13.4.19" + "@next/swc-win32-ia32-msvc" "13.4.19" + "@next/swc-win32-x64-msvc" "13.4.19" nice-try@^1.0.4: version "1.0.5" resolved "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== +no-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" + integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== + dependencies: + lower-case "^2.0.2" + tslib "^2.0.3" + node-abi@^3.3.0: version "3.45.0" resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.45.0.tgz#f568f163a3bfca5aacfce1fbeee1fa2cc98441f5" @@ -6788,7 +6873,7 @@ picocolors@^1.0.0: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.3.1: +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== @@ -7438,12 +7523,12 @@ pretty-bytes@^5.6.0: resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== -prisma@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/prisma/-/prisma-5.0.0.tgz#f6571c46dc2478172cb7bc1bb62d74026a2c2630" - integrity sha512-KYWk83Fhi1FH59jSpavAYTt2eoMVW9YKgu8ci0kuUnt6Dup5Qy47pcB4/TLmiPAbhGrxxSz7gsSnJcCmkyPANA== +prisma@5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/prisma/-/prisma-5.2.0.tgz#a302dc2635cdec1d22d552ece837fb29a03563b9" + integrity sha512-FfFlpjVCkZwrqxDnP4smlNYSH1so+CbfjgdpioFzGGqlQAEm6VHAYSzV7jJgC3ebtY9dNOhDMS2+4/1DDSM7bQ== dependencies: - "@prisma/engines" "5.0.0" + "@prisma/engines" "5.2.0" promise.series@^0.2.0: version "0.2.0" @@ -8023,6 +8108,17 @@ rimraf@^3.0.0, rimraf@^3.0.2: dependencies: glob "^7.1.3" +rollup-plugin-copy@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/rollup-plugin-copy/-/rollup-plugin-copy-3.4.0.tgz#f1228a3ffb66ffad8606e2f3fb7ff23141ed3286" + integrity sha512-rGUmYYsYsceRJRqLVlE9FivJMxJ7X6jDlP79fmFkL8sJs7VVMSVyA2yfyL+PGyO/vJs4A87hwhgVfz61njI+uQ== + dependencies: + "@types/fs-extra" "^8.0.1" + colorette "^1.1.0" + fs-extra "^8.1.0" + globby "10.0.1" + is-plain-object "^3.0.0" + rollup-plugin-delete@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/rollup-plugin-delete/-/rollup-plugin-delete-2.0.0.tgz" @@ -8030,14 +8126,14 @@ rollup-plugin-delete@^2.0.0: dependencies: del "^5.1.0" -rollup-plugin-dts@^5.3.0: - version "5.3.0" - resolved "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-5.3.0.tgz" - integrity sha512-8FXp0ZkyZj1iU5klkIJYLjIq/YZSwBoERu33QBDxm/1yw5UU4txrEtcmMkrq+ZiKu3Q4qvPCNqc3ovX6rjqzbQ== +rollup-plugin-dts@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/rollup-plugin-dts/-/rollup-plugin-dts-5.3.1.tgz#c2841269a3a5cb986b7791b0328e6a178eba108f" + integrity sha512-gusMi+Z4gY/JaEQeXnB0RUdU82h1kF0WYzCWgVmV4p3hWXqelaKuCvcJawfeg+EKn2T1Ie+YWF2OiN1/L8bTVg== dependencies: - magic-string "^0.30.0" + magic-string "^0.30.2" optionalDependencies: - "@babel/code-frame" "^7.18.6" + "@babel/code-frame" "^7.22.5" rollup-plugin-esbuild@^5.0.0: version "5.0.0" @@ -8050,10 +8146,10 @@ rollup-plugin-esbuild@^5.0.0: joycon "^3.1.1" jsonc-parser "^3.2.0" -rollup-plugin-node-externals@^5.1.2: - version "5.1.3" - resolved "https://registry.npmjs.org/rollup-plugin-node-externals/-/rollup-plugin-node-externals-5.1.3.tgz" - integrity sha512-Q3VMjsn39r0/mjKrX++rFlC7kwL7YZdScdyU7BEo+PrEremal3mnol/1X+wQUU++7NeqC1ZNAeRYnHGtsTu9GQ== +rollup-plugin-node-externals@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/rollup-plugin-node-externals/-/rollup-plugin-node-externals-6.1.1.tgz#dff1a85073fe3c0b2c423b280259fe80392026a8" + integrity sha512-127OFMkpH5rBVlRHRBDUMk1m1sGuzbGy7so5aj/IkpUb2r3+wOWjR/erUzd2ChEQWPsxsyQG6xpYYvPBAdcBRA== rollup-plugin-postcss@^4.0.2: version "4.0.2" @@ -8091,10 +8187,10 @@ rollup-pluginutils@^2.8.2: dependencies: estree-walker "^0.6.1" -rollup@^2.70.1: - version "2.79.1" - resolved "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz" - integrity sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw== +rollup@^3.28.0: + version "3.28.0" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.28.0.tgz#a3c70004b01934760c0cb8df717c7a1d932389a2" + integrity sha512-d7zhvo1OUY2SXSM6pfNjgD5+d0Nz87CUp4mt8l/GgVP3oBsPwzNvSzyu1me6BSG9JIgWNTVcafIXBIyM8yQ3yw== optionalDependencies: fsevents "~2.3.2" @@ -8339,6 +8435,14 @@ slice-ansi@^4.0.0: astral-regex "^2.0.0" is-fullwidth-code-point "^3.0.0" +snake-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c" + integrity sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg== + dependencies: + dot-case "^3.0.4" + tslib "^2.0.3" + sort-keys@^4.0.0: version "4.2.0" resolved "https://registry.npmjs.org/sort-keys/-/sort-keys-4.2.0.tgz" @@ -8940,6 +9044,11 @@ tslib@^2.0.1, tslib@^2.1.0: resolved "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz" integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== +tslib@^2.0.3: + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + tslib@^2.4.0: version "2.5.0" resolved "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz" @@ -9052,11 +9161,16 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -typescript@^4.0, typescript@^4.5, typescript@^4.9.5: +typescript@^4.0, typescript@^4.5: version "4.9.5" resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz" integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== +typescript@^5.1.6: + version "5.1.6" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274" + integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA== + ufo@^1.0.0, ufo@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.1.2.tgz#d0d9e0fa09dece0c31ffd57bd363f030a35cfe76" @@ -9128,6 +9242,11 @@ unicode-property-aliases-ecmascript@^2.0.0: resolved "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz" integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + universalify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" @@ -9228,6 +9347,14 @@ vue@^3.2.23: "@vue/server-renderer" "3.2.36" "@vue/shared" "3.2.36" +watchpack@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" + integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== + dependencies: + glob-to-regexp "^0.4.1" + graceful-fs "^4.1.2" + web-streams-polyfill@^3.0.3: version "3.2.1" resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6" @@ -9394,6 +9521,11 @@ yup@^0.32.11: property-expr "^2.0.4" toposort "^2.0.2" +zod@3.21.4: + version "3.21.4" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.21.4.tgz#10882231d992519f0a10b5dd58a38c9dabbb64db" + integrity sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw== + zustand@^4.3.8: version "4.3.9" resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.3.9.tgz#a7d4332bbd75dfd25c6848180b3df1407217f2ad"