diff --git a/.github/workflows/stale-issues.yml b/.github/workflows/stale-issues.yml index 24711fba7..f16040149 100644 --- a/.github/workflows/stale-issues.yml +++ b/.github/workflows/stale-issues.yml @@ -22,3 +22,4 @@ jobs: operations-per-run: 200 ascending: true repo-token: ${{ secrets.GITHUB_TOKEN }} + exempt-issue-labels: bug,enhancement diff --git a/.gitignore b/.gitignore index 99087ab50..050397c90 100644 --- a/.gitignore +++ b/.gitignore @@ -34,9 +34,7 @@ yarn-error.log* # local env files .env -.env.development.local -.env.test.local -.env.production.local +.env.* *.dev.yml diff --git a/Dockerfile b/Dockerfile index e0c7e8c37..12951a73b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -35,7 +35,7 @@ ENV NEXT_TELEMETRY_DISABLED 1 RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs -RUN yarn add npm-run-all dotenv prisma +RUN yarn add npm-run-all dotenv prisma semver # You only need to copy next.config.js if you are NOT using the default configuration COPY --from=builder /app/next.config.js . @@ -53,6 +53,7 @@ USER nextjs EXPOSE 3000 +ENV HOSTNAME 0.0.0.0 ENV PORT 3000 CMD ["yarn", "start-docker"] diff --git a/README.md b/README.md index 02a44e1e1..32e78e31c 100644 --- a/README.md +++ b/README.md @@ -72,13 +72,13 @@ docker compose up -d Alternatively, to pull just the Umami Docker image with PostgreSQL support: ```bash -docker pull docker.umami.dev/umami-software/umami:postgresql-latest +docker pull ghcr.io/umami-software/umami:postgresql-latest ``` Or with MySQL support: ```bash -docker pull docker.umami.dev/umami-software/umami:mysql-latest +docker pull ghcr.io/umami-software/umami:mysql-latest ``` ## Getting updates diff --git a/db/clickhouse/schema.sql b/db/clickhouse/schema.sql index 94b560c3e..44428e94c 100644 --- a/db/clickhouse/schema.sql +++ b/db/clickhouse/schema.sql @@ -66,7 +66,7 @@ CREATE TABLE umami.website_event_queue ( ) ENGINE = Kafka SETTINGS kafka_broker_list = 'domain:9092,domain:9093,domain:9094', -- input broker list - kafka_topic_list = 'events', + kafka_topic_list = 'event', kafka_group_name = 'event_consumer_group', kafka_format = 'JSONEachRow', kafka_max_block_size = 1048576, diff --git a/db/mysql/migrations/02_report_schema_session_data/migration.sql b/db/mysql/migrations/02_report_schema_session_data/migration.sql index 497088993..1649ace2d 100644 --- a/db/mysql/migrations/02_report_schema_session_data/migration.sql +++ b/db/mysql/migrations/02_report_schema_session_data/migration.sql @@ -1,9 +1,9 @@ -- AlterTable -ALTER TABLE `event_data` RENAME COLUMN `event_data_type` TO `data_type`; -ALTER TABLE `event_data` RENAME COLUMN `event_date_value` TO `date_value`; -ALTER TABLE `event_data` RENAME COLUMN `event_id` TO `event_data_id`; -ALTER TABLE `event_data` RENAME COLUMN `event_numeric_value` TO `number_value`; -ALTER TABLE `event_data` RENAME COLUMN `event_string_value` TO `string_value`; +ALTER TABLE `event_data` CHANGE `event_data_type` `data_type` INTEGER UNSIGNED NOT NULL; +ALTER TABLE `event_data` CHANGE `event_date_value` `date_value` TIMESTAMP(0) NULL; +ALTER TABLE `event_data` CHANGE `event_id` `event_data_id` VARCHAR(36) NOT NULL; +ALTER TABLE `event_data` CHANGE `event_numeric_value` `number_value` DECIMAL(19,4) NULL; +ALTER TABLE `event_data` CHANGE `event_string_value` `string_value` VARCHAR(500) NULL; -- CreateTable CREATE TABLE `session_data` ( @@ -50,4 +50,4 @@ WHERE data_type = 2; UPDATE event_data SET string_value = CONCAT(REPLACE(DATE_FORMAT(date_value, '%Y-%m-%d %T'), ' ', 'T'), 'Z') -WHERE data_type = 4; \ No newline at end of file +WHERE data_type = 4; diff --git a/next-env.d.ts b/next-env.d.ts index 4f11a03dc..fd36f9494 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,5 +1,6 @@ /// /// +/// // NOTE: This file should not be edited // see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/next.config.js b/next.config.js index cc3cde7c6..cf7dce7fb 100644 --- a/next.config.js +++ b/next.config.js @@ -6,7 +6,7 @@ const pkg = require('./package.json'); const contentSecurityPolicy = ` default-src 'self'; img-src *; - script-src 'self' 'unsafe-eval'; + script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; connect-src 'self' api.umami.is; frame-ancestors 'self' ${process.env.ALLOWED_FRAME_URLS}; @@ -74,16 +74,23 @@ if (process.env.CLOUD_MODE && process.env.CLOUD_URL && process.env.DISABLE_LOGIN }); } +const basePath = process.env.BASE_PATH; + +/** @type {import('next').NextConfig} */ const config = { + reactStrictMode: false, env: { - cloudMode: process.env.CLOUD_MODE, + basePath: basePath || '', + cloudMode: !!process.env.CLOUD_MODE, cloudUrl: process.env.CLOUD_URL, configUrl: '/config', currentVersion: pkg.version, defaultLocale: process.env.DEFAULT_LOCALE, + disableLogin: process.env.DISABLE_LOGIN, + disableUI: process.env.DISABLE_UI, isProduction: process.env.NODE_ENV === 'production', }, - basePath: process.env.BASE_PATH, + basePath, output: 'standalone', eslint: { ignoreDuringBuilds: true, @@ -92,11 +99,23 @@ const config = { ignoreBuildErrors: true, }, webpack(config) { - config.module.rules.push({ - test: /\.svg$/, - issuer: /\.{js|jsx|ts|tsx}$/, - use: ['@svgr/webpack'], - }); + const fileLoaderRule = config.module.rules.find(rule => rule.test?.test?.('.svg')); + + config.module.rules.push( + { + ...fileLoaderRule, + test: /\.svg$/i, + resourceQuery: /url/, + }, + { + test: /\.svg$/i, + issuer: fileLoaderRule.issuer, + resourceQuery: { not: [...fileLoaderRule.resourceQuery.not, /url/] }, + use: ['@svgr/webpack'], + }, + ); + + fileLoaderRule.exclude = /\.svg$/i; config.resolve.alias['public'] = path.resolve('./public'); diff --git a/package.components.json b/package.components.json index feb3fc2e6..41e72579f 100644 --- a/package.components.json +++ b/package.components.json @@ -1,6 +1,6 @@ { "name": "@umami/components", - "version": "0.11.0", + "version": "0.1.0", "description": "Umami React components.", "author": "Mike Cao ", "license": "MIT", diff --git a/package.json b/package.json index 8694b83b3..807c2f5ad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "umami", - "version": "2.6.2", + "version": "2.8.0", "description": "A simple, fast, privacy-focused alternative to Google Analytics.", "author": "Mike Cao ", "license": "MIT", @@ -30,7 +30,7 @@ "check-db": "node scripts/check-db.js", "check-env": "node scripts/check-env.js", "copy-db-files": "node scripts/copy-db-files.js", - "extract-messages": "formatjs extract \"{pages,components}/**/*.js\" --out-file build/messages.json", + "extract-messages": "formatjs extract \"src/{pages,components}/**/*.js\" --out-file build/messages.json", "merge-messages": "node scripts/merge-messages.js", "generate-lang": "npm-run-all extract-messages merge-messages", "format-lang": "node scripts/format-lang.js", @@ -62,16 +62,17 @@ ".next/cache" ], "dependencies": { + "@clickhouse/client": "^0.2.2", "@fontsource/inter": "^4.5.15", - "@prisma/client": "5.2.0", + "@prisma/client": "5.3.1", + "@react-spring/web": "^9.7.3", "@tanstack/react-query": "^4.33.0", - "@umami/prisma-client": "^0.2.0", - "@umami/redis-client": "^0.5.0", + "@umami/prisma-client": "^0.3.0", + "@umami/redis-client": "^0.16.0", "chalk": "^4.1.1", "chart.js": "^4.2.1", "chartjs-adapter-date-fns": "^3.0.0", "classnames": "^2.3.1", - "clickhouse": "^2.5.0", "colord": "^2.9.2", "cors": "^2.8.5", "cross-spawn": "^7.0.3", @@ -92,22 +93,22 @@ "kafkajs": "^2.1.0", "maxmind": "^4.3.6", "moment-timezone": "^0.5.35", - "next": "13.4.19", - "next-basics": "^0.36.0", + "next": "^13.5.3", + "next-basics": "^0.37.0", "node-fetch": "^3.2.8", "npm-run-all": "^4.1.5", + "prisma": "5.3.1", "react": "^18.2.0", - "react-basics": "^0.98.0", + "react-basics": "^0.105.0", "react-beautiful-dnd": "^13.1.0", "react-dom": "^18.2.0", "react-error-boundary": "^4.0.4", - "react-intl": "^5.24.7", + "react-intl": "^6.4.7", "react-simple-maps": "^2.3.0", - "react-spring": "^9.4.4", "react-use-measure": "^2.0.4", "react-window": "^1.8.6", "request-ip": "^3.3.0", - "semver": "^7.5.2", + "semver": "^7.5.4", "thenby": "^1.3.4", "timezone-support": "^2.0.2", "uuid": "^9.0.0", @@ -124,12 +125,12 @@ "@rollup/plugin-node-resolve": "^15.2.0", "@rollup/plugin-replace": "^5.0.2", "@svgr/rollup": "^8.1.0", - "@svgr/webpack": "^6.2.1", + "@svgr/webpack": "^8.1.0", "@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", + "@typescript-eslint/eslint-plugin": "^6.7.3", + "@typescript-eslint/parser": "^6.7.3", "cross-env": "^7.0.3", "esbuild": "^0.17.17", "eslint": "^8.33.0", @@ -139,15 +140,14 @@ "eslint-plugin-import": "^2.26.0", "eslint-plugin-prettier": "^4.0.0", "extract-react-intl-messages": "^4.1.1", - "husky": "^7.0.0", - "lint-staged": "^11.0.0", - "postcss": "^8.4.21", + "husky": "^8.0.3", + "lint-staged": "^14.0.1", + "postcss": "^8.4.31", "postcss-flexbugs-fixes": "^5.0.2", "postcss-import": "^15.1.0", "postcss-preset-env": "7.8.3", "postcss-rtlcss": "^4.0.1", "prettier": "^2.6.2", - "prisma": "5.2.0", "prompts": "2.4.2", "rollup": "^3.28.0", "rollup-plugin-copy": "^3.4.0", diff --git a/public/images/os/windows-mobile.png b/public/images/os/windows-mobile.png new file mode 100644 index 000000000..4a899a30f Binary files /dev/null and b/public/images/os/windows-mobile.png differ diff --git a/public/intl/messages/am-ET.json b/public/intl/messages/am-ET.json index f48fe83c4..cb9ff4f99 100644 --- a/public/intl/messages/am-ET.json +++ b/public/intl/messages/am-ET.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -769,6 +793,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -793,6 +823,12 @@ "value": "Screens" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/ar-SA.json b/public/intl/messages/ar-SA.json index a9a12404e..17a859167 100644 --- a/public/intl/messages/ar-SA.json +++ b/public/intl/messages/ar-SA.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -769,6 +793,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -793,6 +823,12 @@ "value": "الشاشات" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/be-BY.json b/public/intl/messages/be-BY.json index 4978aa45c..c8081e373 100644 --- a/public/intl/messages/be-BY.json +++ b/public/intl/messages/be-BY.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -769,6 +793,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -793,6 +823,12 @@ "value": "Экраны" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/bn-BD.json b/public/intl/messages/bn-BD.json index 938f6f98f..6b8875adf 100644 --- a/public/intl/messages/bn-BD.json +++ b/public/intl/messages/bn-BD.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -769,6 +793,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -793,6 +823,12 @@ "value": "স্ক্রিনগুলি" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/ca-ES.json b/public/intl/messages/ca-ES.json index 694b49c22..f21a739cc 100644 --- a/public/intl/messages/ca-ES.json +++ b/public/intl/messages/ca-ES.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -769,6 +793,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -793,6 +823,12 @@ "value": "Screens" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/cs-CZ.json b/public/intl/messages/cs-CZ.json index 3fd34c31d..e316e9731 100644 --- a/public/intl/messages/cs-CZ.json +++ b/public/intl/messages/cs-CZ.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -769,6 +793,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -793,6 +823,12 @@ "value": "Screens" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/da-DK.json b/public/intl/messages/da-DK.json index d8da1c3eb..05b0c572a 100644 --- a/public/intl/messages/da-DK.json +++ b/public/intl/messages/da-DK.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -769,6 +793,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -793,6 +823,12 @@ "value": "Screens" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/de-CH.json b/public/intl/messages/de-CH.json index aa0b2d942..ecc153361 100644 --- a/public/intl/messages/de-CH.json +++ b/public/intl/messages/de-CH.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -769,6 +793,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -793,6 +823,12 @@ "value": "Bildschirmuflösige" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/de-DE.json b/public/intl/messages/de-DE.json index 136cd31dd..5e74647c1 100644 --- a/public/intl/messages/de-DE.json +++ b/public/intl/messages/de-DE.json @@ -20,7 +20,7 @@ "label.add": [ { "type": 0, - "value": "Add" + "value": "Hinzufügen" } ], "label.add-description": [ @@ -32,7 +32,7 @@ "label.add-website": [ { "type": 0, - "value": "Webseite hinzufügen" + "value": "Website hinzufügen" } ], "label.admin": [ @@ -44,7 +44,7 @@ "label.after": [ { "type": 0, - "value": "After" + "value": "Nach" } ], "label.all": [ @@ -86,7 +86,7 @@ "label.before": [ { "type": 0, - "value": "Before" + "value": "Vor" } ], "label.bounce-rate": [ @@ -134,7 +134,7 @@ "label.city": [ { "type": 0, - "value": "City" + "value": "Stadt" } ], "label.clear-all": [ @@ -158,7 +158,7 @@ "label.contains": [ { "type": 0, - "value": "Contains" + "value": "Enthält" } ], "label.continue": [ @@ -176,13 +176,19 @@ "label.country": [ { "type": 0, - "value": "Country" + "value": "Land" + } + ], + "label.create": [ + { + "type": 0, + "value": "Create" } ], "label.create-report": [ { "type": 0, - "value": "Report erstellen" + "value": "Bericht erstellen" } ], "label.create-team": [ @@ -230,7 +236,7 @@ "label.date": [ { "type": 0, - "value": "Date" + "value": "Datum" } ], "label.date-range": [ @@ -242,7 +248,7 @@ "label.day": [ { "type": 0, - "value": "Day" + "value": "Tag" } ], "label.default-date-range": [ @@ -272,7 +278,7 @@ "label.delete-website": [ { "type": 0, - "value": "Webseite löschen" + "value": "Website löschen" } ], "label.description": [ @@ -296,7 +302,7 @@ "label.device": [ { "type": 0, - "value": "Device" + "value": "Gerät" } ], "label.devices": [ @@ -314,7 +320,7 @@ "label.does-not-contain": [ { "type": 0, - "value": "Does not contain" + "value": "Enthält nicht" } ], "label.domain": [ @@ -356,7 +362,7 @@ "label.event-data": [ { "type": 0, - "value": "Event daten" + "value": "Eventdaten" } ], "label.events": [ @@ -368,19 +374,25 @@ "label.false": [ { "type": 0, - "value": "False" + "value": "Falsch" } ], "label.field": [ { "type": 0, - "value": "Field" + "value": "Feld" } ], "label.fields": [ { "type": 0, - "value": "Fields" + "value": "Felder" + } + ], + "label.filter": [ + { + "type": 0, + "value": "Filter" } ], "label.filter-combined": [ @@ -398,7 +410,7 @@ "label.filters": [ { "type": 0, - "value": "Filters" + "value": "Filter" } ], "label.funnel": [ @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,28 +443,34 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, - "value": "Is" + "value": "Ist" } ], "label.is-not": [ { "type": 0, - "value": "Is not" + "value": "Ist nicht" } ], "label.is-not-set": [ { "type": 0, - "value": "Is not set" + "value": "Ist nicht gesetzt" } ], "label.is-set": [ { "type": 0, - "value": "Is set" + "value": "Ist gesetzt" } ], "label.join": [ @@ -576,7 +600,7 @@ "label.my-websites": [ { "type": 0, - "value": "My websites" + "value": "Meine Websites" } ], "label.name": [ @@ -618,7 +642,7 @@ "label.page-of": [ { "type": 0, - "value": "Page " + "value": "Seite " }, { "type": 1, @@ -626,7 +650,7 @@ }, { "type": 0, - "value": " of " + "value": " von " }, { "type": 1, @@ -642,7 +666,7 @@ "label.pageTitle": [ { "type": 0, - "value": "Page title" + "value": "Seitentitel" } ], "label.pages": [ @@ -742,7 +766,7 @@ "label.reports": [ { "type": 0, - "value": "Reporte" + "value": "Berichte" } ], "label.required": [ @@ -769,6 +793,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -793,6 +823,12 @@ "value": "Bildschirmauflösungen" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, @@ -808,7 +844,7 @@ "label.sessions": [ { "type": 0, - "value": "Sessions" + "value": "Sitzungen" } ], "label.settings": [ @@ -850,37 +886,37 @@ "label.team-guest": [ { "type": 0, - "value": "Team Gast" + "value": "Gast des Teams" } ], "label.team-id": [ { "type": 0, - "value": "Team ID" + "value": "Team-ID" } ], "label.team-member": [ { "type": 0, - "value": "Team Mitglied" + "value": "Team-Mitglied" } ], "label.team-name": [ { "type": 0, - "value": "Team name" + "value": "Name des Teams" } ], "label.team-owner": [ { "type": 0, - "value": "Team Eigentümer" + "value": "Team-Eigentümer" } ], "label.team-websites": [ { "type": 0, - "value": "Team websites" + "value": "Team-Websites" } ], "label.teams": [ @@ -940,13 +976,13 @@ "label.total": [ { "type": 0, - "value": "Total" + "value": "Gesamt" } ], "label.total-records": [ { "type": 0, - "value": "Total records" + "value": "Datensätze insgesamt" } ], "label.tracking-code": [ @@ -958,19 +994,19 @@ "label.true": [ { "type": 0, - "value": "True" + "value": "Wahr" } ], "label.type": [ { "type": 0, - "value": "Type" + "value": "Typ" } ], "label.unique": [ { "type": 0, - "value": "Unique" + "value": "Eindeutig" } ], "label.unique-visitors": [ @@ -988,7 +1024,7 @@ "label.untitled": [ { "type": 0, - "value": "Untitled" + "value": "Unbenannt" } ], "label.url": [ @@ -1024,7 +1060,7 @@ "label.value": [ { "type": 0, - "value": "Value" + "value": "Wert" } ], "label.view": [ @@ -1042,7 +1078,7 @@ "label.view-only": [ { "type": 0, - "value": "View only" + "value": "Nur ansehen" } ], "label.views": [ @@ -1060,25 +1096,25 @@ "label.website": [ { "type": 0, - "value": "Webseite" + "value": "Website" } ], "label.website-id": [ { "type": 0, - "value": "Webseite ID" + "value": "Website ID" } ], "label.websites": [ { "type": 0, - "value": "Webseiten" + "value": "Websites" } ], "label.window": [ { "type": 0, - "value": "Window" + "value": "Fenster" } ], "label.yesterday": [ @@ -1166,7 +1202,7 @@ "message.delete-account": [ { "type": 0, - "value": "To delete this account, type " + "value": "Um dieses Konto zu löschen, geben Sie zur Bestätigung " }, { "type": 1, @@ -1174,13 +1210,13 @@ }, { "type": 0, - "value": " in the box below to confirm." + "value": " in das Feld unten ein." } ], "message.delete-website": [ { "type": 0, - "value": "To delete this website, type " + "value": "Um diese Website zu löschen, geben Sie zur Bestätigung " }, { "type": 1, @@ -1188,7 +1224,7 @@ }, { "type": 0, - "value": " in the box below to confirm." + "value": " in das Feld unten ein." } ], "message.delete-website-warning": [ @@ -1238,7 +1274,7 @@ "message.min-password-length": [ { "type": 0, - "value": "Minimale länge von " + "value": "Minimale Länge von " }, { "type": 1, @@ -1252,15 +1288,11 @@ "message.new-version-available": [ { "type": 0, - "value": "A new version of Umami " + "value": "Eine neue Version von Umami ist verfügbar: " }, { "type": 1, "value": "version" - }, - { - "type": 0, - "value": " is available!" } ], "message.no-data-available": [ @@ -1272,7 +1304,7 @@ "message.no-event-data": [ { "type": 0, - "value": "No event data is available." + "value": "Es sind keine Ereignisdaten verfügbar." } ], "message.no-match-password": [ @@ -1308,7 +1340,7 @@ "message.no-websites-configured": [ { "type": 0, - "value": "Es ist keine Webseite vorhanden." + "value": "Es ist keine Website vorhanden." } ], "message.page-not-found": [ @@ -1320,7 +1352,7 @@ "message.reset-website": [ { "type": 0, - "value": "To reset this website, type " + "value": "Um diese Website zurückzusetzen, geben Sie zur Bestätigung " }, { "type": 1, @@ -1328,13 +1360,13 @@ }, { "type": 0, - "value": " in the box below to confirm." + "value": " in das Feld unten ein." } ], "message.reset-website-warning": [ { "type": 0, - "value": "Alle Daten für diese Webseite werden gelöscht, jedoch bleibt der Tracking Code bestehen." + "value": "Alle Daten für diese Website werden gelöscht, jedoch bleibt der Tracking Code bestehen." } ], "message.saved": [ @@ -1346,7 +1378,7 @@ "message.share-url": [ { "type": 0, - "value": "Ihre Webseitenstatistik ist unter der folgenden URL öffentlich zugänglich:" + "value": "Die Statistiken Ihrer Website sind unter folgender URL öffentlich zugänglich:" } ], "message.team-already-member": [ @@ -1364,7 +1396,7 @@ "message.team-websites-info": [ { "type": 0, - "value": "Webseiten können von jedem im Team eingesehen werden." + "value": "Websites können von jedem im Team eingesehen werden." } ], "message.tracking-code": [ diff --git a/public/intl/messages/el-GR.json b/public/intl/messages/el-GR.json index d3ff5e424..eb6b73ce0 100644 --- a/public/intl/messages/el-GR.json +++ b/public/intl/messages/el-GR.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -769,6 +793,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -793,6 +823,12 @@ "value": "Screens" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/en-GB.json b/public/intl/messages/en-GB.json index 0e6ac6149..68f242481 100644 --- a/public/intl/messages/en-GB.json +++ b/public/intl/messages/en-GB.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -769,6 +793,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -793,6 +823,12 @@ "value": "Screens" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/en-US.json b/public/intl/messages/en-US.json index 64a99ae1b..0ee5b1e64 100644 --- a/public/intl/messages/en-US.json +++ b/public/intl/messages/en-US.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -769,6 +793,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -793,6 +823,12 @@ "value": "Screens" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/es-ES.json b/public/intl/messages/es-ES.json index 43e101709..5fd90efd8 100644 --- a/public/intl/messages/es-ES.json +++ b/public/intl/messages/es-ES.json @@ -104,7 +104,7 @@ "label.browser": [ { "type": 0, - "value": "Browser" + "value": "Navegador" } ], "label.browsers": [ @@ -134,7 +134,7 @@ "label.city": [ { "type": 0, - "value": "City" + "value": "Ciudad" } ], "label.clear-all": [ @@ -176,13 +176,19 @@ "label.country": [ { "type": 0, - "value": "Country" + "value": "País" + } + ], + "label.create": [ + { + "type": 0, + "value": "Crear" } ], "label.create-report": [ { "type": 0, - "value": "Crear reporte" + "value": "Crear informe" } ], "label.create-team": [ @@ -230,7 +236,7 @@ "label.date": [ { "type": 0, - "value": "Date" + "value": "Fecha" } ], "label.date-range": [ @@ -242,7 +248,7 @@ "label.day": [ { "type": 0, - "value": "Day" + "value": "Día" } ], "label.default-date-range": [ @@ -278,7 +284,7 @@ "label.description": [ { "type": 0, - "value": "Descripciones" + "value": "Descripción" } ], "label.desktop": [ @@ -296,7 +302,7 @@ "label.device": [ { "type": 0, - "value": "Device" + "value": "Dispositivo" } ], "label.devices": [ @@ -308,7 +314,7 @@ "label.dismiss": [ { "type": 0, - "value": "Ignorar" + "value": "Cerrar" } ], "label.does-not-contain": [ @@ -326,7 +332,7 @@ "label.dropoff": [ { "type": 0, - "value": "Dropoff" + "value": "Abandono" } ], "label.edit": [ @@ -368,7 +374,7 @@ "label.false": [ { "type": 0, - "value": "False" + "value": "Falso" } ], "label.field": [ @@ -383,6 +389,12 @@ "value": "Campos" } ], + "label.filter": [ + { + "type": 0, + "value": "Filtro" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Comprender conversión y abandono de usuarios." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -446,7 +470,7 @@ "label.is-set": [ { "type": 0, - "value": "Is set" + "value": "Está establecido" } ], "label.join": [ @@ -576,7 +600,7 @@ "label.my-websites": [ { "type": 0, - "value": "My websites" + "value": "Mis sitios web" } ], "label.name": [ @@ -600,7 +624,7 @@ "label.os": [ { "type": 0, - "value": "OS" + "value": "Sistema" } ], "label.overview": [ @@ -618,7 +642,7 @@ "label.page-of": [ { "type": 0, - "value": "Page " + "value": "Página " }, { "type": 1, @@ -626,7 +650,7 @@ }, { "type": 0, - "value": " of " + "value": " de " }, { "type": 1, @@ -642,7 +666,7 @@ "label.pageTitle": [ { "type": 0, - "value": "Page title" + "value": "Título de página" } ], "label.pages": [ @@ -660,7 +684,7 @@ "label.powered-by": [ { "type": 0, - "value": "Con la ayuda de " + "value": "Analíticas de " }, { "type": 1, @@ -682,7 +706,7 @@ "label.query": [ { "type": 0, - "value": "Query" + "value": "Consulta" } ], "label.query-parameters": [ @@ -700,7 +724,7 @@ "label.referrer": [ { "type": 0, - "value": "Referrer" + "value": "Referido" } ], "label.referrers": [ @@ -742,7 +766,7 @@ "label.reports": [ { "type": 0, - "value": "Reportes" + "value": "Informes" } ], "label.required": [ @@ -760,13 +784,19 @@ "label.reset-website": [ { "type": 0, - "value": "Reiniciar estadísticas" + "value": "Reiniciar analíticas" } ], "label.retention": [ { "type": 0, - "value": "Retention" + "value": "Retención" + } + ], + "label.retention-description": [ + { + "type": 0, + "value": "Medir la frecuencia con la que los usuarios vuelven a tu sitio web." } ], "label.role": [ @@ -793,6 +823,12 @@ "value": "Pantallas" } ], + "label.search": [ + { + "type": 0, + "value": "Buscar" + } + ], "label.select-date": [ { "type": 0, @@ -814,7 +850,7 @@ "label.settings": [ { "type": 0, - "value": "Configuraciones" + "value": "Ajustes" } ], "label.share-url": [ @@ -856,7 +892,7 @@ "label.team-id": [ { "type": 0, - "value": "ID de equipo" + "value": "ID del equipo" } ], "label.team-member": [ @@ -868,7 +904,7 @@ "label.team-name": [ { "type": 0, - "value": "Team name" + "value": "Nombre del equipo" } ], "label.team-owner": [ @@ -880,7 +916,7 @@ "label.team-websites": [ { "type": 0, - "value": "Team websites" + "value": "Sitios web del equipo" } ], "label.teams": [ @@ -1252,7 +1288,7 @@ "message.new-version-available": [ { "type": 0, - "value": "A new version of Umami " + "value": "Una nueva versión de Umami " }, { "type": 1, @@ -1260,7 +1296,7 @@ }, { "type": 0, - "value": " is available!" + "value": " está disponible" } ], "message.no-data-available": [ @@ -1340,7 +1376,7 @@ "message.saved": [ { "type": 0, - "value": "Guardado." + "value": "Guardado" } ], "message.share-url": [ diff --git a/public/intl/messages/es-MX.json b/public/intl/messages/es-MX.json index c238951fe..c3ef099df 100644 --- a/public/intl/messages/es-MX.json +++ b/public/intl/messages/es-MX.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -769,6 +793,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -793,6 +823,12 @@ "value": "Pantallas" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/fa-IR.json b/public/intl/messages/fa-IR.json index 757b5ae8e..e09b155b6 100644 --- a/public/intl/messages/fa-IR.json +++ b/public/intl/messages/fa-IR.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -769,6 +793,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -793,6 +823,12 @@ "value": "Screens" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/fi-FI.json b/public/intl/messages/fi-FI.json index 5fdf5b193..2130f82cd 100644 --- a/public/intl/messages/fi-FI.json +++ b/public/intl/messages/fi-FI.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -769,6 +793,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -793,6 +823,12 @@ "value": "Screens" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/fo-FO.json b/public/intl/messages/fo-FO.json index 3eb3f452c..bc11c56e1 100644 --- a/public/intl/messages/fo-FO.json +++ b/public/intl/messages/fo-FO.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -769,6 +793,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -793,6 +823,12 @@ "value": "Screens" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/fr-FR.json b/public/intl/messages/fr-FR.json index 326c99a46..99937de70 100644 --- a/public/intl/messages/fr-FR.json +++ b/public/intl/messages/fr-FR.json @@ -104,7 +104,7 @@ "label.browser": [ { "type": 0, - "value": "Browser" + "value": "Navigateur" } ], "label.browsers": [ @@ -134,7 +134,7 @@ "label.city": [ { "type": 0, - "value": "City" + "value": "Ville" } ], "label.clear-all": [ @@ -176,7 +176,13 @@ "label.country": [ { "type": 0, - "value": "Country" + "value": "Pays" + } + ], + "label.create": [ + { + "type": 0, + "value": "Créer" } ], "label.create-report": [ @@ -242,7 +248,7 @@ "label.day": [ { "type": 0, - "value": "Day" + "value": "Jour" } ], "label.default-date-range": [ @@ -296,7 +302,7 @@ "label.device": [ { "type": 0, - "value": "Device" + "value": "Appareil" } ], "label.devices": [ @@ -326,7 +332,7 @@ "label.dropoff": [ { "type": 0, - "value": "Dropoff" + "value": "Abandons" } ], "label.edit": [ @@ -350,19 +356,19 @@ "label.event": [ { "type": 0, - "value": "Event" + "value": "Évènement" } ], "label.event-data": [ { "type": 0, - "value": "Données d'événements" + "value": "Données d'évènements" } ], "label.events": [ { "type": 0, - "value": "Événements" + "value": "Évènements" } ], "label.false": [ @@ -383,6 +389,12 @@ "value": "Champs" } ], + "label.filter": [ + { + "type": 0, + "value": "Filtrer" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Entonnoir" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Suivi des conversions et des taux d'abandons." + } + ], "label.greater-than": [ { "type": 0, @@ -425,28 +443,34 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Analyse précise des données en utilisant des segments et des filtres." + } + ], "label.is": [ { "type": 0, - "value": "Est égal" + "value": "Est" } ], "label.is-not": [ { "type": 0, - "value": "N'est pas égal" + "value": "N'est pas" } ], "label.is-not-set": [ { "type": 0, - "value": "Is not set" + "value": "N'est pas défini" } ], "label.is-set": [ { "type": 0, - "value": "Is set" + "value": "Est défini" } ], "label.join": [ @@ -568,7 +592,7 @@ "label.my-websites": [ { "type": 0, - "value": "My websites" + "value": "Mes sites" } ], "label.name": [ @@ -618,7 +642,7 @@ }, { "type": 0, - "value": " of " + "value": " sur " }, { "type": 1, @@ -634,7 +658,7 @@ "label.pageTitle": [ { "type": 0, - "value": "Page title" + "value": "Titre de page" } ], "label.pages": [ @@ -680,7 +704,7 @@ "label.query-parameters": [ { "type": 0, - "value": "Paramètres d'URL" + "value": "Paramètres de requête" } ], "label.realtime": [ @@ -692,7 +716,7 @@ "label.referrer": [ { "type": 0, - "value": "Referrer" + "value": "Site référent" } ], "label.referrers": [ @@ -716,7 +740,7 @@ "label.region": [ { "type": 0, - "value": "Region" + "value": "Région" } ], "label.regions": [ @@ -758,7 +782,13 @@ "label.retention": [ { "type": 0, - "value": "Retention" + "value": "Rétention" + } + ], + "label.retention-description": [ + { + "type": 0, + "value": "Mesure de l'atractivité du site en visualisant les taux d'utilisateurs qui reviennent." } ], "label.role": [ @@ -785,6 +815,12 @@ "value": "Résolutions d'écran" } ], + "label.search": [ + { + "type": 0, + "value": "Rechercher" + } + ], "label.select-date": [ { "type": 0, @@ -860,7 +896,7 @@ "label.team-name": [ { "type": 0, - "value": "Team name" + "value": "Nom de l'équipe" } ], "label.team-owner": [ @@ -872,7 +908,7 @@ "label.team-websites": [ { "type": 0, - "value": "Team websites" + "value": "Sites d'équipes" } ], "label.teams": [ @@ -1052,7 +1088,7 @@ "label.website": [ { "type": 0, - "value": "Website" + "value": "Site" } ], "label.website-id": [ @@ -1248,7 +1284,7 @@ "message.new-version-available": [ { "type": 0, - "value": "A new version of Umami " + "value": "Une nouvelle version d'Umami " }, { "type": 1, @@ -1256,7 +1292,7 @@ }, { "type": 0, - "value": " is available!" + "value": " est disponible !" } ], "message.no-data-available": [ @@ -1336,7 +1372,7 @@ "message.saved": [ { "type": 0, - "value": "Enregistré avec succès." + "value": "Enregistré." } ], "message.share-url": [ diff --git a/public/intl/messages/ga-ES.json b/public/intl/messages/ga-ES.json index d086b57f8..b5fabeffe 100644 --- a/public/intl/messages/ga-ES.json +++ b/public/intl/messages/ga-ES.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -777,6 +801,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -801,6 +831,12 @@ "value": "Screens" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/he-IL.json b/public/intl/messages/he-IL.json index dc206268c..16f625257 100644 --- a/public/intl/messages/he-IL.json +++ b/public/intl/messages/he-IL.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -761,6 +785,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -785,6 +815,12 @@ "value": "Screens" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/hi-IN.json b/public/intl/messages/hi-IN.json index 91f1f0267..df3bcb401 100644 --- a/public/intl/messages/hi-IN.json +++ b/public/intl/messages/hi-IN.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -769,6 +793,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -793,6 +823,12 @@ "value": "Screens" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/hr-HR.json b/public/intl/messages/hr-HR.json index cd8d4d38b..8388dd486 100644 --- a/public/intl/messages/hr-HR.json +++ b/public/intl/messages/hr-HR.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -769,6 +793,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -793,6 +823,12 @@ "value": "Screens" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/hu-HU.json b/public/intl/messages/hu-HU.json index e39182b18..c3da1af06 100644 --- a/public/intl/messages/hu-HU.json +++ b/public/intl/messages/hu-HU.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -769,6 +793,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -793,6 +823,12 @@ "value": "Screens" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/id-ID.json b/public/intl/messages/id-ID.json index 97526840b..eda5c0b3e 100644 --- a/public/intl/messages/id-ID.json +++ b/public/intl/messages/id-ID.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -761,6 +785,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -785,6 +815,12 @@ "value": "Layar" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/it-IT.json b/public/intl/messages/it-IT.json index a93715d3d..bdc015f5a 100644 --- a/public/intl/messages/it-IT.json +++ b/public/intl/messages/it-IT.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -769,6 +793,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -793,6 +823,12 @@ "value": "Screens" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/ja-JP.json b/public/intl/messages/ja-JP.json index 63b49aa59..4e2a8fc47 100644 --- a/public/intl/messages/ja-JP.json +++ b/public/intl/messages/ja-JP.json @@ -179,6 +179,12 @@ "value": "国" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "フィールド" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "分析" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "見通し" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -769,6 +793,12 @@ "value": "保持" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -793,6 +823,12 @@ "value": "画面サイズ" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/km-KH.json b/public/intl/messages/km-KH.json index 1f7b82ca4..68c71ebbd 100644 --- a/public/intl/messages/km-KH.json +++ b/public/intl/messages/km-KH.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -761,6 +785,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -785,6 +815,12 @@ "value": "ប្រភេទឧបករណ៍" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/ko-KR.json b/public/intl/messages/ko-KR.json index 26413708b..5a3c90348 100644 --- a/public/intl/messages/ko-KR.json +++ b/public/intl/messages/ko-KR.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -773,6 +797,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -797,6 +827,12 @@ "value": "Screens" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/lt-LT.json b/public/intl/messages/lt-LT.json index 21610b7bd..6369cd3c1 100644 --- a/public/intl/messages/lt-LT.json +++ b/public/intl/messages/lt-LT.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -874,6 +898,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -898,6 +928,12 @@ "value": "Screens" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/mn-MN.json b/public/intl/messages/mn-MN.json index 013e5c88c..1a2210453 100644 --- a/public/intl/messages/mn-MN.json +++ b/public/intl/messages/mn-MN.json @@ -20,13 +20,13 @@ "label.add": [ { "type": 0, - "value": "Add" + "value": "Нэмэх" } ], "label.add-description": [ { "type": 0, - "value": "Add description" + "value": "Тайлбар нэмэх" } ], "label.add-website": [ @@ -44,7 +44,7 @@ "label.after": [ { "type": 0, - "value": "After" + "value": "Хойно" } ], "label.all": [ @@ -68,7 +68,7 @@ "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": [ @@ -134,7 +134,7 @@ "label.city": [ { "type": 0, - "value": "City" + "value": "Хот" } ], "label.clear-all": [ @@ -158,7 +158,7 @@ "label.contains": [ { "type": 0, - "value": "Contains" + "value": "Агуулах" } ], "label.continue": [ @@ -176,13 +176,19 @@ "label.country": [ { "type": 0, - "value": "Country" + "value": "Улс" + } + ], + "label.create": [ + { + "type": 0, + "value": "Үүсгэх" } ], "label.create-report": [ { "type": 0, - "value": "Create report" + "value": "Тайлан үүсгэх" } ], "label.create-team": [ @@ -230,7 +236,7 @@ "label.date": [ { "type": 0, - "value": "Date" + "value": "Огноо" } ], "label.date-range": [ @@ -242,7 +248,7 @@ "label.day": [ { "type": 0, - "value": "Day" + "value": "Өдөр" } ], "label.default-date-range": [ @@ -278,7 +284,7 @@ "label.description": [ { "type": 0, - "value": "Description" + "value": "Тайлбар" } ], "label.desktop": [ @@ -296,7 +302,7 @@ "label.device": [ { "type": 0, - "value": "Device" + "value": "Төхөөрөмж" } ], "label.devices": [ @@ -314,7 +320,7 @@ "label.does-not-contain": [ { "type": 0, - "value": "Does not contain" + "value": "Агуулахгүй" } ], "label.domain": [ @@ -326,7 +332,7 @@ "label.dropoff": [ { "type": 0, - "value": "Dropoff" + "value": "Уналт" } ], "label.edit": [ @@ -350,13 +356,13 @@ "label.event": [ { "type": 0, - "value": "Event" + "value": "Үйлдэл" } ], "label.event-data": [ { "type": 0, - "value": "Event data" + "value": "Үйлдлийн өгөгдөл" } ], "label.events": [ @@ -368,19 +374,25 @@ "label.false": [ { "type": 0, - "value": "False" + "value": "Худал" } ], "label.field": [ { "type": 0, - "value": "Field" + "value": "Талбар" } ], "label.fields": [ { "type": 0, - "value": "Fields" + "value": "Талбар" + } + ], + "label.filter": [ + { + "type": 0, + "value": "Шүүлтүүр" } ], "label.filter-combined": [ @@ -398,55 +410,67 @@ "label.filters": [ { "type": 0, - "value": "Filters" + "value": "Шүүлтүүр" } ], "label.funnel": [ { "type": 0, - "value": "Funnel" + "value": "Цутгал" + } + ], + "label.funnel-description": [ + { + "type": 0, + "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.insights-description": [ + { + "type": 0, + "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": [ @@ -522,13 +546,13 @@ "label.less-than": [ { "type": 0, - "value": "Less than" + "value": "Бага" } ], "label.less-than-equals": [ { "type": 0, - "value": "Less than or equals" + "value": "Бага буюу тэнцүү" } ], "label.login": [ @@ -576,7 +600,7 @@ "label.my-websites": [ { "type": 0, - "value": "My websites" + "value": "Миний вебүүд" } ], "label.name": [ @@ -606,7 +630,7 @@ "label.overview": [ { "type": 0, - "value": "Overview" + "value": "Тойм" } ], "label.owner": [ @@ -618,19 +642,19 @@ "label.page-of": [ { "type": 0, - "value": "Page " - }, - { - "type": 1, - "value": "current" - }, - { - "type": 0, - "value": " of " + "value": "Хуудас " }, { "type": 1, "value": "total" + }, + { + "type": 0, + "value": "-с " + }, + { + "type": 1, + "value": "current" } ], "label.page-views": [ @@ -642,7 +666,7 @@ "label.pageTitle": [ { "type": 0, - "value": "Page title" + "value": "Хуудасны гарчиг" } ], "label.pages": [ @@ -700,7 +724,7 @@ "label.referrer": [ { "type": 0, - "value": "Referrer" + "value": "Чиглүүлэгч" } ], "label.referrers": [ @@ -724,7 +748,7 @@ "label.region": [ { "type": 0, - "value": "Region" + "value": "Бүс" } ], "label.regions": [ @@ -742,7 +766,7 @@ "label.reports": [ { "type": 0, - "value": "Reports" + "value": "Тайлан" } ], "label.required": [ @@ -766,7 +790,13 @@ "label.retention": [ { "type": 0, - "value": "Retention" + "value": "Барилт" + } + ], + "label.retention-description": [ + { + "type": 0, + "value": "Хэрэглэгчид таны веб рүү дахин хандах буюу хэрэглэгчидээ хэр тогтоож буйг хэмжих." } ], "label.role": [ @@ -778,7 +808,7 @@ "label.run-query": [ { "type": 0, - "value": "Run query" + "value": "Query ажиллуулах" } ], "label.save": [ @@ -793,10 +823,16 @@ "value": "Дэлгэц" } ], + "label.search": [ + { + "type": 0, + "value": "Хайх" + } + ], "label.select-date": [ { "type": 0, - "value": "Select date" + "value": "Огноо сонгох" } ], "label.select-website": [ @@ -832,7 +868,7 @@ "label.sum": [ { "type": 0, - "value": "Sum" + "value": "Нийлбэр" } ], "label.tablet": [ @@ -868,7 +904,7 @@ "label.team-name": [ { "type": 0, - "value": "Team name" + "value": "Багийн нэр" } ], "label.team-owner": [ @@ -880,7 +916,7 @@ "label.team-websites": [ { "type": 0, - "value": "Team websites" + "value": "Багийн вебүүд" } ], "label.teams": [ @@ -940,13 +976,13 @@ "label.total": [ { "type": 0, - "value": "Total" + "value": "Нийт" } ], "label.total-records": [ { "type": 0, - "value": "Total records" + "value": "Нийт мөриийн тоо" } ], "label.tracking-code": [ @@ -958,13 +994,13 @@ "label.true": [ { "type": 0, - "value": "True" + "value": "Үнэн" } ], "label.type": [ { "type": 0, - "value": "Type" + "value": "Төрөл" } ], "label.unique": [ @@ -988,7 +1024,7 @@ "label.untitled": [ { "type": 0, - "value": "Untitled" + "value": "Гарчиггүй" } ], "label.url": [ @@ -1024,7 +1060,7 @@ "label.value": [ { "type": 0, - "value": "Value" + "value": "Утга" } ], "label.view": [ @@ -1042,7 +1078,7 @@ "label.view-only": [ { "type": 0, - "value": "View only" + "value": "Зөвхөн үзэх" } ], "label.views": [ @@ -1060,7 +1096,7 @@ "label.website": [ { "type": 0, - "value": "Website" + "value": "Веб" } ], "label.website-id": [ @@ -1078,7 +1114,7 @@ "label.window": [ { "type": 0, - "value": "Window" + "value": "Цонх" } ], "label.yesterday": [ @@ -1174,7 +1210,7 @@ "message.delete-account": [ { "type": 0, - "value": "To delete this account, type " + "value": "Энэ бүртгэлийг устгахын тулд доорх хэсэгт " }, { "type": 1, @@ -1182,13 +1218,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, @@ -1196,7 +1232,7 @@ }, { "type": 0, - "value": " in the box below to confirm." + "value": " гэж бичиж баталгаажуулна уу." } ], "message.delete-website-warning": [ @@ -1260,7 +1296,7 @@ "message.new-version-available": [ { "type": 0, - "value": "A new version of Umami " + "value": "Umami-н шинэ хувилбар " }, { "type": 1, @@ -1268,7 +1304,7 @@ }, { "type": 0, - "value": " is available!" + "value": " гарсан байна!" } ], "message.no-data-available": [ @@ -1280,7 +1316,7 @@ "message.no-event-data": [ { "type": 0, - "value": "No event data is available." + "value": "Үйлдлийн өгөгдөл алга." } ], "message.no-match-password": [ @@ -1292,7 +1328,7 @@ "message.no-results-found": [ { "type": 0, - "value": "No results were found." + "value": "Ямар ч үр дүн олдсонгүй." } ], "message.no-team-websites": [ diff --git a/public/intl/messages/ms-MY.json b/public/intl/messages/ms-MY.json index e022e1227..874f3a3dd 100644 --- a/public/intl/messages/ms-MY.json +++ b/public/intl/messages/ms-MY.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -761,6 +785,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -785,6 +815,12 @@ "value": "Screens" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/my-MM.json b/public/intl/messages/my-MM.json index 3995a22b0..33b449793 100644 --- a/public/intl/messages/my-MM.json +++ b/public/intl/messages/my-MM.json @@ -41,6 +41,12 @@ "value": "အက်ဒမင်" } ], + "label.after": [ + { + "type": 0, + "value": "ပြီးနောက်" + } + ], "label.all": [ { "type": 0, @@ -59,6 +65,12 @@ "value": "အန်နလစ်တစ်" } ], + "label.average": [ + { + "type": 0, + "value": "ပျမ်းမျှ" + } + ], "label.average-visit-time": [ { "type": 0, @@ -71,12 +83,30 @@ "value": "နောက်သို့" } ], + "label.before": [ + { + "type": 0, + "value": "မတိုင်မီ" + } + ], "label.bounce-rate": [ { "type": 0, "value": "Bounce နှုန်း" } ], + "label.breakdown": [ + { + "type": 0, + "value": "ခွဲခြမ်းစိတ်ဖြာမှု" + } + ], + "label.browser": [ + { + "type": 0, + "value": "Browser" + } + ], "label.browsers": [ { "type": 0, @@ -101,6 +131,12 @@ "value": "မြို့များ" } ], + "label.city": [ + { + "type": 0, + "value": "City" + } + ], "label.clear-all": [ { "type": 0, @@ -119,6 +155,12 @@ "value": "စကားဝှက်အတည်ပြုသည်" } ], + "label.contains": [ + { + "type": 0, + "value": "ပါဝင်သည်" + } + ], "label.continue": [ { "type": 0, @@ -131,6 +173,24 @@ "value": "နိုင်ငံများ" } ], + "label.country": [ + { + "type": 0, + "value": "Country" + } + ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], + "label.create-report": [ + { + "type": 0, + "value": "ရီပို့လုပ်မည်" + } + ], "label.create-team": [ { "type": 0, @@ -173,12 +233,24 @@ "value": "ဒေတာ" } ], + "label.date": [ + { + "type": 0, + "value": "Date" + } + ], "label.date-range": [ { "type": 0, "value": "ရက်အပိုင်းအခြား" } ], + "label.day": [ + { + "type": 0, + "value": "Day" + } + ], "label.default-date-range": [ { "type": 0, @@ -209,6 +281,12 @@ "value": "ဝက်ဘ်ဆိုဒ်ကိုဖျက်မည်" } ], + "label.description": [ + { + "type": 0, + "value": "ရှင်းပြချက်" + } + ], "label.desktop": [ { "type": 0, @@ -221,6 +299,12 @@ "value": "အသေးစိတ်" } ], + "label.device": [ + { + "type": 0, + "value": "Device" + } + ], "label.devices": [ { "type": 0, @@ -233,6 +317,12 @@ "value": "ပိတ်ပါ" } ], + "label.does-not-contain": [ + { + "type": 0, + "value": "မပါဝင်ပါ" + } + ], "label.domain": [ { "type": 0, @@ -281,6 +371,12 @@ "value": "အဖြစ်အပျက်များ" } ], + "label.false": [ + { + "type": 0, + "value": "မှားသည်" + } + ], "label.field": [ { "type": 0, @@ -293,6 +389,12 @@ "value": "Field အမည်များ" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -305,18 +407,72 @@ "value": "အရှိအတိုင်း" } ], + "label.filters": [ + { + "type": 0, + "value": "Filter များ" + } + ], "label.funnel": [ { "type": 0, "value": "ဖန်နယ်" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], + "label.greater-than": [ + { + "type": 0, + "value": "ထက်ပို၍ကြီးသည်" + } + ], + "label.greater-than-equals": [ + { + "type": 0, + "value": "ထက်ပို၍ကြီးသည်သို့မဟုတ်တူသည်" + } + ], "label.insights": [ { "type": 0, "value": "အသေးစိတ်သိမြင်နိုင်ရန်" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], + "label.is": [ + { + "type": 0, + "value": "Is" + } + ], + "label.is-not": [ + { + "type": 0, + "value": "Is not" + } + ], + "label.is-not-set": [ + { + "type": 0, + "value": "Is not set" + } + ], + "label.is-set": [ + { + "type": 0, + "value": "Is set" + } + ], "label.join": [ { "type": 0, @@ -387,6 +543,18 @@ "value": "အသင်းမှထွက်မည်" } ], + "label.less-than": [ + { + "type": 0, + "value": "ထက်ပို၍ငယ်သည်" + } + ], + "label.less-than-equals": [ + { + "type": 0, + "value": "ထက်ပို၍ငယ်သည်သို့မဟုတ်တူသည်" + } + ], "label.login": [ { "type": 0, @@ -399,12 +567,24 @@ "value": "လော့ဂ်အောက်လုပ်မည်" } ], + "label.max": [ + { + "type": 0, + "value": "အများဆုံး" + } + ], "label.members": [ { "type": 0, "value": "အဖွဲ့ဝင်များ" } ], + "label.min": [ + { + "type": 0, + "value": "အနည်းဆုံး" + } + ], "label.mobile": [ { "type": 0, @@ -417,6 +597,12 @@ "value": "နောက်ထပ်" } ], + "label.my-websites": [ + { + "type": 0, + "value": "My websites" + } + ], "label.name": [ { "type": 0, @@ -435,24 +621,54 @@ "value": "မရှိပါ" } ], - "label.operating-systems": [ + "label.os": [ { "type": 0, "value": "ကွန်ပျူတာလည်ပတ်မှုစနစ်" } ], + "label.overview": [ + { + "type": 0, + "value": "အပေါ်ယံမြင်ကွင်း" + } + ], "label.owner": [ { "type": 0, "value": "ပိုင်ဆိုင်သူ" } ], + "label.page-of": [ + { + "type": 0, + "value": "Page " + }, + { + "type": 1, + "value": "current" + }, + { + "type": 0, + "value": " of " + }, + { + "type": 1, + "value": "total" + } + ], "label.page-views": [ { "type": 0, "value": "ဝင်ရောက်ကြည့်ရှုသူ" } ], + "label.pageTitle": [ + { + "type": 0, + "value": "Page title" + } + ], "label.pages": [ { "type": 0, @@ -505,6 +721,12 @@ "value": "အချိန်နှင့်တပြေးညီ" } ], + "label.referrer": [ + { + "type": 0, + "value": "Referrer" + } + ], "label.referrers": [ { "type": 0, @@ -523,6 +745,12 @@ "value": "ပြန်ထုတ်မည်" } ], + "label.region": [ + { + "type": 0, + "value": "Region" + } + ], "label.regions": [ { "type": 0, @@ -559,6 +787,18 @@ "value": "ဝက်ဘ်ဆိုဒ်ဒေတာကိုဖျက်မည်" } ], + "label.retention": [ + { + "type": 0, + "value": "Retention" + } + ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -583,6 +823,12 @@ "value": "မြင်ကွင်းများ" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, @@ -619,6 +865,12 @@ "value": "တစ်ရက်အတွင်း" } ], + "label.sum": [ + { + "type": 0, + "value": "ပေါင်းလဒ်" + } + ], "label.tablet": [ { "type": 0, @@ -649,12 +901,24 @@ "value": "အသင်းဝင်" } ], + "label.team-name": [ + { + "type": 0, + "value": "Team name" + } + ], "label.team-owner": [ { "type": 0, "value": "အသင်းကိုပိုင်ဆိုင်သူ" } ], + "label.team-websites": [ + { + "type": 0, + "value": "Team websites" + } + ], "label.teams": [ { "type": 0, @@ -709,12 +973,42 @@ "value": "ဇယားများကို အဖွင့်အပိတ်လုပ်မည်" } ], + "label.total": [ + { + "type": 0, + "value": "စုစုပေါင်း" + } + ], + "label.total-records": [ + { + "type": 0, + "value": "မှတ်တမ်းစုစုပေါင်း" + } + ], "label.tracking-code": [ { "type": 0, "value": "ထရက်လုပ်သည့် ကုဒ်" } ], + "label.true": [ + { + "type": 0, + "value": "မှန်သည်" + } + ], + "label.type": [ + { + "type": 0, + "value": "အမျိုးအစား" + } + ], + "label.unique": [ + { + "type": 0, + "value": "Unique" + } + ], "label.unique-visitors": [ { "type": 0, @@ -727,6 +1021,12 @@ "value": "မသိသော" } ], + "label.untitled": [ + { + "type": 0, + "value": "ခေါင်းစဉ်မရှိ" + } + ], "label.url": [ { "type": 0, @@ -757,6 +1057,12 @@ "value": "အသုံးပြုသူများ" } ], + "label.value": [ + { + "type": 0, + "value": "တန်ဖိုး" + } + ], "label.view": [ { "type": 0, @@ -817,168 +1123,6 @@ "value": "မနေ့က" } ], - "labels.after": [ - { - "type": 0, - "value": "ပြီးနောက်" - } - ], - "labels.average": [ - { - "type": 0, - "value": "ပျမ်းမျှ" - } - ], - "labels.before": [ - { - "type": 0, - "value": "မတိုင်မီ" - } - ], - "labels.breakdown": [ - { - "type": 0, - "value": "ခွဲခြမ်းစိတ်ဖြာမှု" - } - ], - "labels.contains": [ - { - "type": 0, - "value": "ပါဝင်သည်" - } - ], - "labels.create-report": [ - { - "type": 0, - "value": "ရီပို့လုပ်မည်" - } - ], - "labels.description": [ - { - "type": 0, - "value": "ရှင်းပြချက်" - } - ], - "labels.does-not-contain": [ - { - "type": 0, - "value": "မပါဝင်ပါ" - } - ], - "labels.does-not-equal": [ - { - "type": 0, - "value": "မတူညီပါ" - } - ], - "labels.equals": [ - { - "type": 0, - "value": "တူညီသည်" - } - ], - "labels.false": [ - { - "type": 0, - "value": "မှားသည်" - } - ], - "labels.filters": [ - { - "type": 0, - "value": "Filter များ" - } - ], - "labels.greater-than": [ - { - "type": 0, - "value": "ထက်ပို၍ကြီးသည်" - } - ], - "labels.greater-than-equals": [ - { - "type": 0, - "value": "ထက်ပို၍ကြီးသည်သို့မဟုတ်တူသည်" - } - ], - "labels.less-than": [ - { - "type": 0, - "value": "ထက်ပို၍ငယ်သည်" - } - ], - "labels.less-than-equals": [ - { - "type": 0, - "value": "ထက်ပို၍ငယ်သည်သို့မဟုတ်တူသည်" - } - ], - "labels.max": [ - { - "type": 0, - "value": "အများဆုံး" - } - ], - "labels.min": [ - { - "type": 0, - "value": "အနည်းဆုံး" - } - ], - "labels.overview": [ - { - "type": 0, - "value": "အပေါ်ယံမြင်ကွင်း" - } - ], - "labels.sum": [ - { - "type": 0, - "value": "ပေါင်းလဒ်" - } - ], - "labels.total": [ - { - "type": 0, - "value": "စုစုပေါင်း" - } - ], - "labels.total-records": [ - { - "type": 0, - "value": "မှတ်တမ်းစုစုပေါင်း" - } - ], - "labels.true": [ - { - "type": 0, - "value": "မှန်သည်" - } - ], - "labels.type": [ - { - "type": 0, - "value": "အမျိုးအစား" - } - ], - "labels.unique": [ - { - "type": 0, - "value": "Unique" - } - ], - "labels.untitled": [ - { - "type": 0, - "value": "ခေါင်းစဉ်မရှိ" - } - ], - "labels.value": [ - { - "type": 0, - "value": "တန်ဖိုး" - } - ], "message.active-users": [ { "type": 1, diff --git a/public/intl/messages/nb-NO.json b/public/intl/messages/nb-NO.json index 82576ff89..010bd2ad4 100644 --- a/public/intl/messages/nb-NO.json +++ b/public/intl/messages/nb-NO.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -769,6 +793,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -793,6 +823,12 @@ "value": "Screens" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/nl-NL.json b/public/intl/messages/nl-NL.json index 5ee25206b..66c5f4faf 100644 --- a/public/intl/messages/nl-NL.json +++ b/public/intl/messages/nl-NL.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -769,6 +793,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -793,6 +823,12 @@ "value": "Schermen" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/pl-PL.json b/public/intl/messages/pl-PL.json index 6da1ff7ad..4621a918d 100644 --- a/public/intl/messages/pl-PL.json +++ b/public/intl/messages/pl-PL.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Pola" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Lejek" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -769,6 +793,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -793,6 +823,12 @@ "value": "Ekrany" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/pt-BR.json b/public/intl/messages/pt-BR.json index ba508a504..790e13161 100644 --- a/public/intl/messages/pt-BR.json +++ b/public/intl/messages/pt-BR.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Campos" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funil" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -769,6 +793,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -793,6 +823,12 @@ "value": "Telas" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/pt-PT.json b/public/intl/messages/pt-PT.json index a6431fb3b..511d74fc6 100644 --- a/public/intl/messages/pt-PT.json +++ b/public/intl/messages/pt-PT.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -769,6 +793,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -793,6 +823,12 @@ "value": "Screens" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/ro-RO.json b/public/intl/messages/ro-RO.json index 1438ab413..9181c1035 100644 --- a/public/intl/messages/ro-RO.json +++ b/public/intl/messages/ro-RO.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -769,6 +793,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -793,6 +823,12 @@ "value": "Screens" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/ru-RU.json b/public/intl/messages/ru-RU.json index b3213e679..93b89291d 100644 --- a/public/intl/messages/ru-RU.json +++ b/public/intl/messages/ru-RU.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -769,6 +793,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -793,6 +823,12 @@ "value": "Экраны" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/si-LK.json b/public/intl/messages/si-LK.json index f4e5bca2f..578a8abed 100644 --- a/public/intl/messages/si-LK.json +++ b/public/intl/messages/si-LK.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -769,6 +793,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -793,6 +823,12 @@ "value": "Screens" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/sk-SK.json b/public/intl/messages/sk-SK.json index b7e2914a9..6b375afed 100644 --- a/public/intl/messages/sk-SK.json +++ b/public/intl/messages/sk-SK.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -769,6 +793,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -793,6 +823,12 @@ "value": "Screens" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/sl-SI.json b/public/intl/messages/sl-SI.json index ee62a3153..6d435020c 100644 --- a/public/intl/messages/sl-SI.json +++ b/public/intl/messages/sl-SI.json @@ -179,6 +179,12 @@ "value": "Država" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Polja" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Prodajni lijak" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Vpogled" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -769,6 +793,12 @@ "value": "Ohranjanje uporabnikov" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -793,6 +823,12 @@ "value": "Zasloni" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/sv-SE.json b/public/intl/messages/sv-SE.json index 4a7f4130c..9824be155 100644 --- a/public/intl/messages/sv-SE.json +++ b/public/intl/messages/sv-SE.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -769,6 +793,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -793,6 +823,12 @@ "value": "Upplösning" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/ta-IN.json b/public/intl/messages/ta-IN.json index 90fb9ebf1..9726b46d5 100644 --- a/public/intl/messages/ta-IN.json +++ b/public/intl/messages/ta-IN.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -769,6 +793,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -793,6 +823,12 @@ "value": "Screens" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/th-TH.json b/public/intl/messages/th-TH.json index c30a9d61f..6988c653b 100644 --- a/public/intl/messages/th-TH.json +++ b/public/intl/messages/th-TH.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -761,6 +785,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -785,6 +815,12 @@ "value": "ขนาดหน้าจอ" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/tr-TR.json b/public/intl/messages/tr-TR.json index 138681adb..f15a3b732 100644 --- a/public/intl/messages/tr-TR.json +++ b/public/intl/messages/tr-TR.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -769,6 +793,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -793,6 +823,12 @@ "value": "Ekranlar" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/uk-UA.json b/public/intl/messages/uk-UA.json index bdc2d345b..2c602c947 100644 --- a/public/intl/messages/uk-UA.json +++ b/public/intl/messages/uk-UA.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -769,6 +793,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -793,6 +823,12 @@ "value": "Screens" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/ur-PK.json b/public/intl/messages/ur-PK.json index 2005bc719..23195ffc0 100644 --- a/public/intl/messages/ur-PK.json +++ b/public/intl/messages/ur-PK.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -769,6 +793,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -793,6 +823,12 @@ "value": "Screens" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/vi-VN.json b/public/intl/messages/vi-VN.json index 9fe0dd4ed..4c3ef05cb 100644 --- a/public/intl/messages/vi-VN.json +++ b/public/intl/messages/vi-VN.json @@ -179,6 +179,12 @@ "value": "Country" } ], + "label.create": [ + { + "type": 0, + "value": "Create" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "Fields" } ], + "label.filter": [ + { + "type": 0, + "value": "Filter" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "Funnel" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "Understand the conversion and drop-off rate of users." + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "Insights" } ], + "label.insights-description": [ + { + "type": 0, + "value": "Dive deeper into your data by using segments and filters." + } + ], "label.is": [ { "type": 0, @@ -761,6 +785,12 @@ "value": "Retention" } ], + "label.retention-description": [ + { + "type": 0, + "value": "Measure your website stickiness by tracking how often users return." + } + ], "label.role": [ { "type": 0, @@ -785,6 +815,12 @@ "value": "Screens" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/zh-CN.json b/public/intl/messages/zh-CN.json index ec5c441b2..6441e763b 100644 --- a/public/intl/messages/zh-CN.json +++ b/public/intl/messages/zh-CN.json @@ -179,6 +179,12 @@ "value": "国家/地区" } ], + "label.create": [ + { + "type": 0, + "value": "创建" + } + ], "label.create-report": [ { "type": 0, @@ -374,13 +380,19 @@ "label.field": [ { "type": 0, - "value": "Field" + "value": "字段" } ], "label.fields": [ { "type": 0, - "value": "Fields" + "value": "字段" + } + ], + "label.filter": [ + { + "type": 0, + "value": "筛选器" } ], "label.filter-combined": [ @@ -407,16 +419,22 @@ "value": "分析" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "了解用户的转换率和退出率。" + } + ], "label.greater-than": [ { "type": 0, - "value": "Greater than" + "value": "大于" } ], "label.greater-than-equals": [ { "type": 0, - "value": "Greater than or equals" + "value": "大于或等于" } ], "label.insights": [ @@ -425,6 +443,12 @@ "value": "见解" } ], + "label.insights-description": [ + { + "type": 0, + "value": "通过使用筛选器和划分时间段来更深入地研究数据。" + } + ], "label.is": [ { "type": 0, @@ -777,6 +801,12 @@ "value": "保留" } ], + "label.retention-description": [ + { + "type": 0, + "value": "通过跟踪用户返回的频率来衡量网站的用户粘性。" + } + ], "label.role": [ { "type": 0, @@ -801,6 +831,12 @@ "value": "屏幕尺寸" } ], + "label.search": [ + { + "type": 0, + "value": "搜索" + } + ], "label.select-date": [ { "type": 0, diff --git a/public/intl/messages/zh-TW.json b/public/intl/messages/zh-TW.json index c980c4bb9..82f54a667 100644 --- a/public/intl/messages/zh-TW.json +++ b/public/intl/messages/zh-TW.json @@ -179,6 +179,12 @@ "value": "國家" } ], + "label.create": [ + { + "type": 0, + "value": "建立" + } + ], "label.create-report": [ { "type": 0, @@ -383,6 +389,12 @@ "value": "欄位" } ], + "label.filter": [ + { + "type": 0, + "value": "篩選器" + } + ], "label.filter-combined": [ { "type": 0, @@ -407,6 +419,12 @@ "value": "漏斗" } ], + "label.funnel-description": [ + { + "type": 0, + "value": "瞭解使用者的轉換率和退出率" + } + ], "label.greater-than": [ { "type": 0, @@ -425,6 +443,12 @@ "value": "洞察" } ], + "label.insights-description": [ + { + "type": 0, + "value": "透過使用區段和篩選器來深入探索你的數據" + } + ], "label.is": [ { "type": 0, @@ -773,6 +797,12 @@ "value": "保留" } ], + "label.retention-description": [ + { + "type": 0, + "value": "透過追蹤使用者回訪的頻率來衡量您的網站黏著度。" + } + ], "label.role": [ { "type": 0, @@ -797,6 +827,12 @@ "value": "螢幕" } ], + "label.search": [ + { + "type": 0, + "value": "Search" + } + ], "label.select-date": [ { "type": 0, diff --git a/rollup.components.config.mjs b/rollup.components.config.mjs index c4481d0ee..9be073908 100644 --- a/rollup.components.config.mjs +++ b/rollup.components.config.mjs @@ -19,6 +19,7 @@ const customResolver = resolve({ const aliasConfig = { entries: [ + { find: /^app/, replacement: path.resolve('./src/app') }, { find: /^components/, replacement: path.resolve('./src/components') }, { find: /^hooks/, replacement: path.resolve('./src/hooks') }, { find: /^lib/, replacement: path.resolve('./src/lib') }, diff --git a/scripts/check-db.js b/scripts/check-db.js index a84a775ce..757843ac3 100644 --- a/scripts/check-db.js +++ b/scripts/check-db.js @@ -66,12 +66,16 @@ async function checkDatabaseVersion() { async function checkV1Tables() { try { - await prisma.$queryRaw`select * from account limit 1`; + // check for v1 migrations before v2 release date + const record = + await prisma.$queryRaw`select * from _prisma_migrations where started_at < '2023-04-17'`; - error( - 'Umami v1 tables detected. For how to upgrade from v1 to v2 go to https://umami.is/docs/migrate-v1-v2.', - ); - process.exit(1); + if (record.length > 0) { + error( + 'Umami v1 tables detected. For how to upgrade from v1 to v2 go to https://umami.is/docs/migrate-v1-v2.', + ); + process.exit(1); + } } catch (e) { // Ignore } diff --git a/scripts/merge-messages.js b/scripts/merge-messages.js index a74153146..572e9a7ed 100644 --- a/scripts/merge-messages.js +++ b/scripts/merge-messages.js @@ -4,7 +4,7 @@ const path = require('path'); const prettier = require('prettier'); const messages = require('../build/messages.json'); -const dest = path.resolve(__dirname, '../lang'); +const dest = path.resolve(__dirname, '../src/lang'); const files = fs.readdirSync(dest); const keys = Object.keys(messages).sort(); @@ -14,7 +14,7 @@ with the existing files under `lang`. Any newly added keys will be printed to the console. */ files.forEach(file => { - const lang = require(`../lang/${file}`); + const lang = require(`../src/lang/${file}`); console.log(`Merging ${file}`); diff --git a/scripts/start-env.js b/scripts/start-env.js index bfaf1330f..e9fe2a4b4 100644 --- a/scripts/start-env.js +++ b/scripts/start-env.js @@ -1,4 +1,8 @@ require('dotenv').config(); const cli = require('next/dist/cli/next-start'); -cli.nextStart(['-p', process.env.PORT || 3000, '-H', process.env.HOSTNAME || '0.0.0.0']); +cli.nextStart({ + '--port': process.env.PORT || 3000, + '--hostname': process.env.HOSTNAME || '0.0.0.0', + _: [], +}); diff --git a/src/app/(main)/NavBar.js b/src/app/(main)/NavBar.js new file mode 100644 index 000000000..211adf5fb --- /dev/null +++ b/src/app/(main)/NavBar.js @@ -0,0 +1,58 @@ +'use client'; +import { Icon, Text } from 'react-basics'; +import Link from 'next/link'; +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 { usePathname } from 'next/navigation'; +import styles from './NavBar.module.css'; + +export function NavBar() { + const pathname = usePathname(); + const { formatMessage, labels } = useMessages(); + + const links = [ + { label: formatMessage(labels.dashboard), url: '/dashboard' }, + { label: formatMessage(labels.websites), url: '/websites' }, + { label: formatMessage(labels.reports), url: '/reports' }, + { label: formatMessage(labels.settings), url: '/settings' }, + ].filter(n => n); + + return ( +
+
+ + + + umami +
+
+ {links.map(({ url, label }) => { + return ( + + {label} + + ); + })} +
+
+ + + +
+
+ +
+
+ ); +} + +export default NavBar; diff --git a/src/components/layout/NavBar.module.css b/src/app/(main)/NavBar.module.css similarity index 75% rename from src/components/layout/NavBar.module.css rename to src/app/(main)/NavBar.module.css index dd5085a03..fd022ecab 100644 --- a/src/components/layout/NavBar.module.css +++ b/src/app/(main)/NavBar.module.css @@ -1,7 +1,7 @@ .navbar { + display: grid; + grid-template-columns: max-content 1fr 1fr; position: relative; - display: flex; - flex-direction: row; align-items: center; height: 60px; background: var(--base75); @@ -9,17 +9,6 @@ padding: 0 20px; } -.left, -.right { - display: flex; - flex-direction: row; - align-items: center; -} - -.right { - justify-content: flex-end; -} - .logo { display: flex; flex-direction: row; @@ -35,29 +24,24 @@ flex-direction: row; gap: 30px; padding: 0 40px; - flex: 1; font-weight: 700; + max-height: 60px; } -.links a { - display: flex; - align-items: center; - gap: 10px; - line-height: 60px; +.links a, +.links a:active, +.links a:visited { color: var(--font-color200); + line-height: 60px; border-bottom: 2px solid transparent; } -.links span { - white-space: nowrap; -} - .links a:hover { color: var(--font-color100); border-bottom: 2px solid var(--primary400); } -.links .selected { +.links a.selected { color: var(--font-color100); border-bottom: 2px solid var(--primary400); } @@ -68,7 +52,6 @@ flex-direction: row; align-items: center; justify-content: flex-end; - min-width: 0; } .mobile { @@ -76,6 +59,10 @@ } @media only screen and (max-width: 768px) { + .navbar { + grid-template-columns: repeat(2, 1fr); + } + .links, .actions { display: none; diff --git a/src/app/(main)/Shell.tsx b/src/app/(main)/Shell.tsx new file mode 100644 index 000000000..980abb621 --- /dev/null +++ b/src/app/(main)/Shell.tsx @@ -0,0 +1,27 @@ +'use client'; +import Script from 'next/script'; +import { usePathname } from 'next/navigation'; +import UpdateNotice from 'components/common/UpdateNotice'; +import { useRequireLogin, useConfig } from 'components/hooks'; + +export function Shell({ children }) { + const { user } = useRequireLogin(); + const config = useConfig(); + const pathname = usePathname(); + + if (!user || !config) { + return null; + } + + return ( + <> + {children} + + {process.env.NODE_ENV === 'production' && !pathname.includes('/share/') && ( + `; diff --git a/src/components/pages/settings/websites/WebsiteData.js b/src/app/(main)/settings/websites/[id]/WebsiteData.js similarity index 89% rename from src/components/pages/settings/websites/WebsiteData.js rename to src/app/(main)/settings/websites/[id]/WebsiteData.js index 08d6702e1..07dc92575 100644 --- a/src/components/pages/settings/websites/WebsiteData.js +++ b/src/app/(main)/settings/websites/[id]/WebsiteData.js @@ -1,6 +1,6 @@ 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 WebsiteDeleteForm from './WebsiteDeleteForm'; +import WebsiteResetForm from './WebsiteResetForm'; import useMessages from 'components/hooks/useMessages'; export function WebsiteData({ websiteId, onSave }) { diff --git a/src/components/pages/settings/websites/WebsiteDeleteForm.js b/src/app/(main)/settings/websites/[id]/WebsiteDeleteForm.js similarity index 100% rename from src/components/pages/settings/websites/WebsiteDeleteForm.js rename to src/app/(main)/settings/websites/[id]/WebsiteDeleteForm.js diff --git a/src/components/pages/settings/websites/WebsiteEditForm.js b/src/app/(main)/settings/websites/[id]/WebsiteEditForm.js similarity index 100% rename from src/components/pages/settings/websites/WebsiteEditForm.js rename to src/app/(main)/settings/websites/[id]/WebsiteEditForm.js diff --git a/src/components/pages/settings/websites/WebsiteResetForm.js b/src/app/(main)/settings/websites/[id]/WebsiteResetForm.js similarity index 100% rename from src/components/pages/settings/websites/WebsiteResetForm.js rename to src/app/(main)/settings/websites/[id]/WebsiteResetForm.js diff --git a/src/app/(main)/settings/websites/[id]/page.js b/src/app/(main)/settings/websites/[id]/page.js new file mode 100644 index 000000000..37324659a --- /dev/null +++ b/src/app/(main)/settings/websites/[id]/page.js @@ -0,0 +1,9 @@ +import WebsiteSettings from '../WebsiteSettings'; + +export default async function WebsiteSettingsPage({ params: { id } }) { + if (process.env.cloudMode) { + return null; + } + + return ; +} diff --git a/src/app/(main)/settings/websites/page.tsx b/src/app/(main)/settings/websites/page.tsx new file mode 100644 index 000000000..2c83dce0c --- /dev/null +++ b/src/app/(main)/settings/websites/page.tsx @@ -0,0 +1,16 @@ +import WebsitesDataTable from './WebsitesDataTable'; +import WebsitesHeader from './WebsitesHeader'; +import { Metadata } from 'next'; + +export default function () { + return ( + <> + + + + ); +} + +export const metadata: Metadata = { + title: 'Websites Settings | umami', +}; diff --git a/src/app/(main)/websites/WebsitesBrowse.js b/src/app/(main)/websites/WebsitesBrowse.js new file mode 100644 index 000000000..f1bab7bf2 --- /dev/null +++ b/src/app/(main)/websites/WebsitesBrowse.js @@ -0,0 +1,31 @@ +'use client'; +import WebsitesDataTable from '../settings/websites/WebsitesDataTable'; +import { useMessages } from 'components/hooks'; +import { useState } from 'react'; +import { Item, Tabs } from 'react-basics'; + +const TABS = { + myWebsites: 'my-websites', + teamWebsites: 'team-websites', +}; + +export function WebsitesBrowse() { + const { formatMessage, labels } = useMessages(); + const [tab, setTab] = useState(TABS.myWebsites); + const allowEdit = !process.env.cloudMode; + + return ( + <> + + {formatMessage(labels.myWebsites)} + {formatMessage(labels.teamWebsites)} + + {tab === TABS.myWebsites && } + {tab === TABS.teamWebsites && ( + + )} + + ); +} + +export default WebsitesBrowse; diff --git a/src/components/pages/websites/WebsiteChart.js b/src/app/(main)/websites/[id]/WebsiteChart.js similarity index 92% rename from src/components/pages/websites/WebsiteChart.js rename to src/app/(main)/websites/[id]/WebsiteChart.js index 7e20e7854..d05ff4220 100644 --- a/src/components/pages/websites/WebsiteChart.js +++ b/src/app/(main)/websites/[id]/WebsiteChart.js @@ -1,6 +1,6 @@ import { useMemo } from 'react'; import PageviewsChart from 'components/metrics/PageviewsChart'; -import { useApi, useDateRange, useTimezone, usePageQuery } from 'components/hooks'; +import { useApi, useDateRange, useTimezone, useNavigation } from 'components/hooks'; import { getDateArray } from 'lib/date'; export function WebsiteChart({ websiteId }) { @@ -9,7 +9,7 @@ export function WebsiteChart({ websiteId }) { const [timezone] = useTimezone(); const { query: { url, referrer, os, browser, device, country, region, city, title }, - } = usePageQuery(); + } = useNavigation(); const { get, useQuery } = useApi(); const { data, isLoading } = useQuery( diff --git a/src/components/pages/websites/WebsiteChart.module.css b/src/app/(main)/websites/[id]/WebsiteChart.module.css similarity index 100% rename from src/components/pages/websites/WebsiteChart.module.css rename to src/app/(main)/websites/[id]/WebsiteChart.module.css diff --git a/src/components/pages/websites/WebsiteChartList.js b/src/app/(main)/websites/[id]/WebsiteChartList.js similarity index 90% rename from src/components/pages/websites/WebsiteChartList.js rename to src/app/(main)/websites/[id]/WebsiteChartList.js index 56cbe157b..23764dbb8 100644 --- a/src/components/pages/websites/WebsiteChartList.js +++ b/src/app/(main)/websites/[id]/WebsiteChartList.js @@ -2,9 +2,8 @@ import { Button, Text, Icon } from 'react-basics'; import { useMemo } from 'react'; import { firstBy } from 'thenby'; import Link from 'next/link'; -import WebsiteChart from 'components/pages/websites/WebsiteChart'; +import WebsiteChart from './WebsiteChart'; import useDashboard from 'store/dashboard'; -import styles from './WebsiteList.module.css'; import WebsiteHeader from './WebsiteHeader'; import { WebsiteMetricsBar } from './WebsiteMetricsBar'; import { useMessages, useLocale } from 'components/hooks'; @@ -27,7 +26,7 @@ export default function WebsiteChartList({ websites, showCharts, limit }) {
{ordered.map(({ id }, index) => { return index < limit ? ( -
+
+ + {close => { + return ( + + { + handleAddFilter(value); + close(); + }} + allowFilterSelect={false} + /> + + ); + }} + + + ); +} + +export default WebsiteFilterButton; diff --git a/src/components/pages/websites/WebsiteHeader.js b/src/app/(main)/websites/[id]/WebsiteHeader.js similarity index 80% rename from src/components/pages/websites/WebsiteHeader.js rename to src/app/(main)/websites/[id]/WebsiteHeader.js index fb4e09863..bf34a2530 100644 --- a/src/components/pages/websites/WebsiteHeader.js +++ b/src/app/(main)/websites/[id]/WebsiteHeader.js @@ -1,7 +1,8 @@ +'use client'; import classNames from 'classnames'; -import { Row, Column, Text, Button, Icon } from 'react-basics'; +import { Text, Button, Icon } from 'react-basics'; import Link from 'next/link'; -import { useRouter } from 'next/router'; +import { usePathname } from 'next/navigation'; import Favicon from 'components/common/Favicon'; import ActiveUsers from 'components/metrics/ActiveUsers'; import Icons from 'components/icons'; @@ -10,7 +11,7 @@ import styles from './WebsiteHeader.module.css'; export function WebsiteHeader({ websiteId, showLinks = true, children }) { const { formatMessage, labels } = useMessages(); - const { pathname } = useRouter(); + const pathname = usePathname(); const { data: website } = useWebsite(websiteId); const { name, domain } = website || {}; @@ -38,17 +39,19 @@ export function WebsiteHeader({ websiteId, showLinks = true, children }) { ]; return ( - - +
+
{name} - - +
+
{showLinks && (
{links.map(({ label, icon, path }) => { - const selected = path ? pathname.endsWith(path) : pathname === '/websites/[id]'; + const selected = path + ? pathname.endsWith(path) + : pathname.match(/^\/websites\/[\w-]+$/); return ( @@ -67,8 +70,8 @@ export function WebsiteHeader({ websiteId, showLinks = true, children }) {
)} {children} - - +
+
); } diff --git a/src/components/pages/websites/WebsiteHeader.module.css b/src/app/(main)/websites/[id]/WebsiteHeader.module.css similarity index 83% rename from src/components/pages/websites/WebsiteHeader.module.css rename to src/app/(main)/websites/[id]/WebsiteHeader.module.css index 93e622d90..3e58c8a3e 100644 --- a/src/components/pages/websites/WebsiteHeader.module.css +++ b/src/app/(main)/websites/[id]/WebsiteHeader.module.css @@ -1,6 +1,6 @@ .header { - display: flex; - flex-direction: row; + display: grid; + grid-template-columns: 1fr max-content; align-items: center; } @@ -35,6 +35,10 @@ } @media only screen and (max-width: 768px) { + .header { + grid-template-columns: 1fr; + } + .links { justify-content: space-evenly; flex: 1; @@ -49,7 +53,7 @@ .icon, .icon svg { - width: 30px; - height: 30px; + width: 20px; + height: 20px; } } diff --git a/src/components/pages/websites/WebsiteMenuView.js b/src/app/(main)/websites/[id]/WebsiteMenuView.js similarity index 61% rename from src/components/pages/websites/WebsiteMenuView.js rename to src/app/(main)/websites/[id]/WebsiteMenuView.js index 8c74d6151..c501645a4 100644 --- a/src/components/pages/websites/WebsiteMenuView.js +++ b/src/app/(main)/websites/[id]/WebsiteMenuView.js @@ -1,6 +1,4 @@ -import { Icon, Button, Flexbox, Text } from 'react-basics'; -import Link from 'next/link'; -import { GridRow, GridColumn } from 'components/layout/Grid'; +import { Icons, Icon, Text, Dropdown, Item } from 'react-basics'; import BrowsersTable from 'components/metrics/BrowsersTable'; import CountriesTable from 'components/metrics/CountriesTable'; import RegionsTable from 'components/metrics/RegionsTable'; @@ -13,12 +11,11 @@ import QueryParametersTable from 'components/metrics/QueryParametersTable'; import ReferrersTable from 'components/metrics/ReferrersTable'; 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 'components/hooks/usePageQuery'; +import useNavigation from 'components/hooks/useNavigation'; import useMessages from 'components/hooks/useMessages'; +import LinkButton from 'components/common/LinkButton'; import styles from './WebsiteMenuView.module.css'; -import useLocale from 'components/hooks/useLocale'; const views = { url: PagesTable, @@ -38,93 +35,106 @@ const views = { export default function WebsiteMenuView({ websiteId, websiteDomain }) { const { formatMessage, labels } = useMessages(); - const { dir } = useLocale(); const { - resolveUrl, + router, + makeUrl, + pathname, query: { view }, - } = usePageQuery(); + } = useNavigation(); const items = [ { key: 'url', label: formatMessage(labels.pages), - url: resolveUrl({ view: 'url' }), + url: makeUrl({ view: 'url' }), }, { key: 'referrer', label: formatMessage(labels.referrers), - url: resolveUrl({ view: 'referrer' }), + url: makeUrl({ view: 'referrer' }), }, { key: 'browser', label: formatMessage(labels.browsers), - url: resolveUrl({ view: 'browser' }), + url: makeUrl({ view: 'browser' }), }, { key: 'os', label: formatMessage(labels.os), - url: resolveUrl({ view: 'os' }), + url: makeUrl({ view: 'os' }), }, { key: 'device', label: formatMessage(labels.devices), - url: resolveUrl({ view: 'device' }), + url: makeUrl({ view: 'device' }), }, { key: 'country', label: formatMessage(labels.countries), - url: resolveUrl({ view: 'country' }), + url: makeUrl({ view: 'country' }), }, { key: 'region', label: formatMessage(labels.regions), - url: resolveUrl({ view: 'region' }), + url: makeUrl({ view: 'region' }), }, { key: 'city', label: formatMessage(labels.cities), - url: resolveUrl({ view: 'city' }), + url: makeUrl({ view: 'city' }), }, { key: 'language', label: formatMessage(labels.languages), - url: resolveUrl({ view: 'language' }), + url: makeUrl({ view: 'language' }), }, { key: 'screen', label: formatMessage(labels.screens), - url: resolveUrl({ view: 'screen' }), + url: makeUrl({ view: 'screen' }), }, { key: 'event', label: formatMessage(labels.events), - url: resolveUrl({ view: 'event' }), + url: makeUrl({ view: 'event' }), }, { key: 'query', label: formatMessage(labels.queryParameters), - url: resolveUrl({ view: 'query' }), + url: makeUrl({ view: 'query' }), }, ]; const DetailsComponent = views[view] || (() => null); + const handleChange = view => { + router.push(makeUrl({ view })); + }; + + const renderValue = value => items.find(({ key }) => key === value)?.label; + return ( - - - - - - - - - - +
+
+ + + + + {formatMessage(labels.back)} + + + + {({ key, label }) => {label}} + +
+
- - +
+
); } diff --git a/src/app/(main)/websites/[id]/WebsiteMenuView.module.css b/src/app/(main)/websites/[id]/WebsiteMenuView.module.css new file mode 100644 index 000000000..b3dcb8d04 --- /dev/null +++ b/src/app/(main)/websites/[id]/WebsiteMenuView.module.css @@ -0,0 +1,63 @@ +.layout { + display: grid; + grid-template-columns: 300px 1fr; + border-top: 1px solid var(--base300); +} + +.menu { + display: flex; + flex-direction: column; + position: relative; + padding: 20px 20px 20px 0; +} + +.back { + display: inline-flex; + align-items: center; + align-self: center; + margin-bottom: 20px; +} + +.content { + min-height: 800px; + padding: 20px 0 20px 20px; + border-left: 1px solid var(--base300); +} + +.dropdown { + display: none; +} + +@media screen and (max-width: 992px) { + .layout { + grid-template-columns: 1fr; + } + + .content { + border: 0; + } + + .back { + align-self: start; + margin: 0; + } + + .nav { + display: none; + } + + .dropdown { + display: flex; + width: 200px; + align-self: end; + } + + .menu { + display: flex; + flex-direction: row; + gap: 20px; + align-items: center; + justify-content: space-between; + padding-right: 0; + } +} diff --git a/src/app/(main)/websites/[id]/WebsiteMetricsBar.js b/src/app/(main)/websites/[id]/WebsiteMetricsBar.js new file mode 100644 index 000000000..0dd6a4e2f --- /dev/null +++ b/src/app/(main)/websites/[id]/WebsiteMetricsBar.js @@ -0,0 +1,112 @@ +import classNames from 'classnames'; +import { useApi, useDateRange, useMessages, useNavigation, useSticky } from 'components/hooks'; +import WebsiteDateFilter from 'components/input/WebsiteDateFilter'; +import MetricCard from 'components/metrics/MetricCard'; +import MetricsBar from 'components/metrics/MetricsBar'; +import { formatShortTime } from 'lib/format'; +import WebsiteFilterButton from './WebsiteFilterButton'; +import styles from './WebsiteMetricsBar.module.css'; + +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 { + query: { url, referrer, title, os, browser, device, country, region, city }, + } = useNavigation(); + + const { data, error, isLoading, isFetched } = useQuery( + [ + 'websites:stats', + { websiteId, modified, url, referrer, title, os, browser, device, country, region, city }, + ], + () => + get(`/websites/${websiteId}/stats`, { + startAt: +startDate, + endAt: +endDate, + url, + referrer, + title, + os, + browser, + device, + country, + region, + city, + }), + ); + + const { pageviews, uniques, bounces, totaltime } = data || {}; + const num = Math.min(data && uniques.value, data && bounces.value); + const diffs = data && { + pageviews: pageviews.value - pageviews.change, + uniques: uniques.value - uniques.change, + bounces: bounces.value - bounces.change, + totaltime: totaltime.value - totaltime.change, + }; + + return ( +
+ + {pageviews && uniques && ( + <> + + + Number(n).toFixed(0) + '%'} + reverseColors + /> + `${n < 0 ? '-' : ''}${formatShortTime(Math.abs(~~n), ['m', 's'], ' ')}`} + /> + + )} + +
+ {showFilter && } + +
+
+ ); +} + +export default WebsiteMetricsBar; diff --git a/src/app/(main)/websites/[id]/WebsiteMetricsBar.module.css b/src/app/(main)/websites/[id]/WebsiteMetricsBar.module.css new file mode 100644 index 000000000..db48bd550 --- /dev/null +++ b/src/app/(main)/websites/[id]/WebsiteMetricsBar.module.css @@ -0,0 +1,46 @@ +.container { + display: grid; + grid-template-columns: 1fr max-content; + justify-content: space-between; + align-items: center; + background: var(--base50); + z-index: var(--z-index-above); + min-height: 120px; + padding-bottom: 20px; +} + +.actions { + display: flex; + align-items: center; + flex-direction: row; + justify-content: flex-end; + gap: 10px; +} + +@media screen and (max-width: 1200px) { + .container { + grid-template-columns: 1fr; + } + + .actions { + margin: 20px 0; + } +} + +@media screen and (min-width: 992px) { + .sticky { + position: sticky; + top: -1px; + } + + .isSticky { + padding: 10px 0; + border-bottom: 1px solid var(--base300); + } +} + +@media screen and (max-width: 768px) { + .button { + display: none; + } +} diff --git a/src/app/(main)/websites/[id]/WebsiteTableView.js b/src/app/(main)/websites/[id]/WebsiteTableView.js new file mode 100644 index 000000000..7c71b84b9 --- /dev/null +++ b/src/app/(main)/websites/[id]/WebsiteTableView.js @@ -0,0 +1,41 @@ +import { useState } from 'react'; +import { Grid, GridRow } from 'components/layout/Grid'; +import PagesTable from 'components/metrics/PagesTable'; +import ReferrersTable from 'components/metrics/ReferrersTable'; +import BrowsersTable from 'components/metrics/BrowsersTable'; +import OSTable from 'components/metrics/OSTable'; +import DevicesTable from 'components/metrics/DevicesTable'; +import WorldMap from 'components/common/WorldMap'; +import CountriesTable from 'components/metrics/CountriesTable'; +import EventsTable from 'components/metrics/EventsTable'; +import EventsChart from 'components/metrics/EventsChart'; + +export default function WebsiteTableView({ websiteId }) { + const [countryData, setCountryData] = useState(); + const tableProps = { + websiteId, + limit: 10, + }; + + return ( + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/src/app/(main)/websites/[id]/event-data/EventDataMetricsBar.js b/src/app/(main)/websites/[id]/event-data/EventDataMetricsBar.js new file mode 100644 index 000000000..5be191854 --- /dev/null +++ b/src/app/(main)/websites/[id]/event-data/EventDataMetricsBar.js @@ -0,0 +1,38 @@ +import { useApi, useDateRange } from 'components/hooks'; +import MetricCard from 'components/metrics/MetricCard'; +import useMessages from 'components/hooks/useMessages'; +import WebsiteDateFilter from 'components/input/WebsiteDateFilter'; +import MetricsBar from 'components/metrics/MetricsBar'; +import styles from './EventDataMetricsBar.module.css'; + +export function EventDataMetricsBar({ websiteId }) { + const { formatMessage, labels } = useMessages(); + const { get, useQuery } = useApi(); + const [dateRange] = useDateRange(websiteId); + const { startDate, endDate, modified } = dateRange; + + const { data, error, isLoading, isFetched } = useQuery( + ['event-data:stats', { websiteId, startDate, endDate, modified }], + () => + get(`/event-data/stats`, { + websiteId, + startAt: +startDate, + endAt: +endDate, + }), + ); + + return ( +
+ + + + + +
+ +
+
+ ); +} + +export default EventDataMetricsBar; diff --git a/src/components/pages/websites/WebsiteMetricsBar.module.css b/src/app/(main)/websites/[id]/event-data/EventDataMetricsBar.module.css similarity index 53% rename from src/components/pages/websites/WebsiteMetricsBar.module.css rename to src/app/(main)/websites/[id]/event-data/EventDataMetricsBar.module.css index 52decfc65..408396c37 100644 --- a/src/components/pages/websites/WebsiteMetricsBar.module.css +++ b/src/app/(main)/websites/[id]/event-data/EventDataMetricsBar.module.css @@ -1,5 +1,6 @@ .container { - display: flex; + display: grid; + grid-template-columns: 1fr 1fr; justify-content: space-between; align-items: center; padding: 10px 0; @@ -11,25 +12,15 @@ .actions { display: flex; - align-items: center; flex-direction: row; + align-items: center; justify-content: flex-end; - gap: 10px; + flex: 1; } -@media only screen and (max-width: 1200px) { - .actions { - margin-top: 40px; - } -} - -@media only screen and (min-width: 992px) { - .sticky { - position: sticky; - top: -1px; - } - - .isSticky { - border-bottom: 1px solid var(--base300); +@media only screen and (max-width: 992px) { + .container { + grid-template-columns: 1fr; + grid-template-rows: 1fr 1fr; } } diff --git a/src/components/pages/event-data/EventDataTable.js b/src/app/(main)/websites/[id]/event-data/EventDataTable.js similarity index 84% rename from src/components/pages/event-data/EventDataTable.js rename to src/app/(main)/websites/[id]/event-data/EventDataTable.js index c79916ce5..fb98e7e76 100644 --- a/src/components/pages/event-data/EventDataTable.js +++ b/src/app/(main)/websites/[id]/event-data/EventDataTable.js @@ -1,12 +1,12 @@ import Link from 'next/link'; import { GridTable, GridColumn } from 'react-basics'; -import { useMessages, usePageQuery } from 'components/hooks'; +import { useMessages, useNavigation } from 'components/hooks'; import Empty from 'components/common/Empty'; import { DATA_TYPES } from 'lib/constants'; export function EventDataTable({ data = [] }) { const { formatMessage, labels } = useMessages(); - const { resolveUrl } = usePageQuery(); + const { makeUrl } = useNavigation(); if (data.length === 0) { return ; @@ -16,7 +16,7 @@ export function EventDataTable({ data = [] }) { {row => ( - + {row.eventName} )} diff --git a/src/components/pages/event-data/EventDataValueTable.js b/src/app/(main)/websites/[id]/event-data/EventDataValueTable.js similarity index 89% rename from src/components/pages/event-data/EventDataValueTable.js rename to src/app/(main)/websites/[id]/event-data/EventDataValueTable.js index 75c11e32b..4e50f5b9b 100644 --- a/src/components/pages/event-data/EventDataValueTable.js +++ b/src/app/(main)/websites/[id]/event-data/EventDataValueTable.js @@ -1,5 +1,5 @@ import { GridTable, GridColumn, Button, Icon, Text } from 'react-basics'; -import { useMessages, usePageQuery } from 'components/hooks'; +import { useMessages, useNavigation } from 'components/hooks'; import Link from 'next/link'; import Icons from 'components/icons'; import PageHeader from 'components/layout/PageHeader'; @@ -8,12 +8,12 @@ import { DATA_TYPES } from 'lib/constants'; export function EventDataValueTable({ data = [], event }) { const { formatMessage, labels } = useMessages(); - const { resolveUrl } = usePageQuery(); + const { makeUrl } = useNavigation(); const Title = () => { return ( <> - + + + + + + ); +} + +export default WebsiteReports; diff --git a/src/app/(main)/websites/[id]/reports/page.tsx b/src/app/(main)/websites/[id]/reports/page.tsx new file mode 100644 index 000000000..bf564025d --- /dev/null +++ b/src/app/(main)/websites/[id]/reports/page.tsx @@ -0,0 +1,9 @@ +import WebsiteReports from './WebsiteReports'; + +export default function WebsiteReportsPage({ params: { id } }) { + if (!id) { + return null; + } + + return ; +} diff --git a/src/app/(main)/websites/page.tsx b/src/app/(main)/websites/page.tsx new file mode 100644 index 000000000..a15425101 --- /dev/null +++ b/src/app/(main)/websites/page.tsx @@ -0,0 +1,16 @@ +import WebsitesHeader from 'app/(main)/settings/websites/WebsitesHeader'; +import WebsitesBrowse from './WebsitesBrowse'; +import { Metadata } from 'next'; + +export default function WebsitesPage() { + return ( + <> + + + + ); +} + +export const metadata: Metadata = { + title: 'Websites | umami', +}; diff --git a/src/app/Providers.tsx b/src/app/Providers.tsx new file mode 100644 index 000000000..c3d626993 --- /dev/null +++ b/src/app/Providers.tsx @@ -0,0 +1,39 @@ +'use client'; +import { IntlProvider } from 'react-intl'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { ReactBasicsProvider } from 'react-basics'; +import ErrorBoundary from 'components/common/ErrorBoundary'; +import useLocale from 'components/hooks/useLocale'; +import 'chartjs-adapter-date-fns'; + +const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + refetchOnWindowFocus: false, + }, + }, +}); + +function MessagesProvider({ children }) { + const { locale, messages } = useLocale(); + return ( + null}> + {children} + + ); +} + +export function Providers({ children }) { + return ( + + + + {children} + + + + ); +} + +export default Providers; diff --git a/src/app/layout.tsx b/src/app/layout.tsx new file mode 100644 index 000000000..e2478a958 --- /dev/null +++ b/src/app/layout.tsx @@ -0,0 +1,36 @@ +import { Metadata } from 'next'; +import Providers from './Providers'; +import '@fontsource/inter/400.css'; +import '@fontsource/inter/700.css'; +import '@fontsource/inter/800.css'; +import 'react-basics/dist/styles.css'; +import 'styles/locale.css'; +import 'styles/index.css'; +import 'styles/variables.css'; + +export default function RootLayout({ children }) { + return ( + + + + + + + + + + + + + + + + {children} + + + ); +} + +export const metadata: Metadata = { + title: 'umami', +}; diff --git a/src/components/pages/login/LoginForm.js b/src/app/login/LoginForm.js similarity index 96% rename from src/components/pages/login/LoginForm.js rename to src/app/login/LoginForm.js index 797eea14d..59d145bf3 100644 --- a/src/components/pages/login/LoginForm.js +++ b/src/app/login/LoginForm.js @@ -1,3 +1,4 @@ +'use client'; import { useMutation } from '@tanstack/react-query'; import { Form, @@ -9,7 +10,7 @@ import { SubmitButton, Icon, } from 'react-basics'; -import { useRouter } from 'next/router'; +import { useRouter } from 'next/navigation'; import useApi from 'components/hooks/useApi'; import { setUser } from 'store/app'; import { setClientAuthToken } from 'lib/client'; diff --git a/src/components/pages/login/LoginForm.module.css b/src/app/login/LoginForm.module.css similarity index 100% rename from src/components/pages/login/LoginForm.module.css rename to src/app/login/LoginForm.module.css diff --git a/src/components/pages/login/LoginLayout.module.css b/src/app/login/page.module.css similarity index 76% rename from src/components/pages/login/LoginLayout.module.css rename to src/app/login/page.module.css index d12306ea8..45115d5b6 100644 --- a/src/components/pages/login/LoginLayout.module.css +++ b/src/app/login/page.module.css @@ -1,6 +1,5 @@ -.layout { +.page { display: flex; - flex-direction: column; align-items: center; justify-content: center; height: 100vh; diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx new file mode 100644 index 000000000..2ac3f7244 --- /dev/null +++ b/src/app/login/page.tsx @@ -0,0 +1,25 @@ +import LoginForm from './LoginForm'; +import { Metadata } from 'next'; +import styles from './page.module.css'; + +async function getDisabled() { + return !!process.env.LOGIN_DISABLED; +} + +export default async function LoginPage() { + const disabled = await getDisabled(); + + if (disabled) { + return null; + } + + return ( +
+ +
+ ); +} + +export const metadata: Metadata = { + title: 'Login | umami', +}; diff --git a/src/pages/logout.js b/src/app/logout/Logout.js similarity index 68% rename from src/pages/logout.js rename to src/app/logout/Logout.js index ef89080c3..e9da0373e 100644 --- a/src/pages/logout.js +++ b/src/app/logout/Logout.js @@ -1,10 +1,12 @@ +'use client'; import { useEffect } from 'react'; -import { useRouter } from 'next/router'; +import { useRouter } from 'next/navigation'; import useApi from 'components/hooks/useApi'; import { setUser } from 'store/app'; import { removeClientAuthToken } from 'lib/client'; -export default function ({ disabled }) { +export function Logout() { + const disabled = !!(process.env.disableLogin || process.env.cloudMode); const router = useRouter(); const { post } = useApi(); @@ -27,10 +29,4 @@ export default function ({ disabled }) { return null; } -export async function getServerSideProps() { - return { - props: { - disabled: !!(process.env.DISABLE_LOGIN || process.env.CLOUD_MODE), - }, - }; -} +export default Logout; diff --git a/src/app/logout/page.tsx b/src/app/logout/page.tsx new file mode 100644 index 000000000..bce247364 --- /dev/null +++ b/src/app/logout/page.tsx @@ -0,0 +1,5 @@ +import Logout from './Logout'; + +export default function () { + return ; +} diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx new file mode 100644 index 000000000..16c5bbcbe --- /dev/null +++ b/src/app/not-found.tsx @@ -0,0 +1,13 @@ +'use client'; +import { Flexbox } from 'react-basics'; +import useMessages from 'components/hooks/useMessages'; + +export default function () { + const { formatMessage, labels } = useMessages(); + + return ( + +

{formatMessage(labels.pageNotFound)}

+
+ ); +} diff --git a/src/app/page.tsx b/src/app/page.tsx new file mode 100644 index 000000000..6a146801d --- /dev/null +++ b/src/app/page.tsx @@ -0,0 +1,6 @@ +'use client'; +import { redirect } from 'next/navigation'; + +export default function RootPage() { + redirect('/dashboard'); +} diff --git a/src/components/layout/Footer.js b/src/app/share/[...id]/Footer.js similarity index 95% rename from src/components/layout/Footer.js rename to src/app/share/[...id]/Footer.js index 3a07c12a8..84d4162f7 100644 --- a/src/components/layout/Footer.js +++ b/src/app/share/[...id]/Footer.js @@ -1,3 +1,4 @@ +'use client'; import { CURRENT_VERSION, HOMEPAGE_URL } from 'lib/constants'; import styles from './Footer.module.css'; diff --git a/src/components/layout/Footer.module.css b/src/app/share/[...id]/Footer.module.css similarity index 80% rename from src/components/layout/Footer.module.css rename to src/app/share/[...id]/Footer.module.css index 348c92d88..5dc2d5841 100644 --- a/src/components/layout/Footer.module.css +++ b/src/app/share/[...id]/Footer.module.css @@ -1,10 +1,10 @@ .footer { display: flex; flex-direction: row; + align-items: center; justify-content: flex-end; font-size: var(--font-size-sm); - line-height: 30px; - margin: 40px 0; + height: 100px; } .footer a { diff --git a/src/app/share/[...id]/Header.js b/src/app/share/[...id]/Header.js new file mode 100644 index 000000000..41e93f52e --- /dev/null +++ b/src/app/share/[...id]/Header.js @@ -0,0 +1,30 @@ +'use client'; +import { Icon, Text } from 'react-basics'; +import Link from 'next/link'; +import LanguageButton from 'components/input/LanguageButton'; +import ThemeButton from 'components/input/ThemeButton'; +import SettingsButton from 'components/input/SettingsButton'; +import Icons from 'components/icons'; +import styles from './Header.module.css'; + +export function Header() { + return ( +
+
+ + + + + umami + +
+
+ + + +
+
+ ); +} + +export default Header; diff --git a/src/components/layout/Header.module.css b/src/app/share/[...id]/Header.module.css similarity index 86% rename from src/components/layout/Header.module.css rename to src/app/share/[...id]/Header.module.css index 26f305521..d353d79a1 100644 --- a/src/components/layout/Header.module.css +++ b/src/app/share/[...id]/Header.module.css @@ -2,6 +2,7 @@ display: flex; flex-direction: row; align-items: center; + justify-content: space-between; width: 100%; height: 100px; } @@ -38,10 +39,3 @@ min-width: 100%; } } - -@media only screen and (max-width: 768px) { - .buttons, - .links { - display: none; - } -} diff --git a/src/app/share/[...id]/Share.js b/src/app/share/[...id]/Share.js new file mode 100644 index 000000000..99ba64078 --- /dev/null +++ b/src/app/share/[...id]/Share.js @@ -0,0 +1,25 @@ +'use client'; +import WebsiteDetails from 'app/(main)/websites/[id]/WebsiteDetails'; +import useShareToken from 'components/hooks/useShareToken'; +import styles from './Share.module.css'; +import Page from 'components/layout/Page'; +import Header from './Header'; +import Footer from './Footer'; + +export default function Share({ shareId }) { + const { shareToken, isLoading } = useShareToken(shareId); + + if (isLoading || !shareToken) { + return null; + } + + return ( +
+ +
+ +
+ +
+ ); +} diff --git a/src/app/share/[...id]/Share.module.css b/src/app/share/[...id]/Share.module.css new file mode 100644 index 000000000..d985435c1 --- /dev/null +++ b/src/app/share/[...id]/Share.module.css @@ -0,0 +1,4 @@ +.container { + flex: 1; + min-height: calc(100vh - 200px); +} diff --git a/src/app/share/[...id]/page.tsx b/src/app/share/[...id]/page.tsx new file mode 100644 index 000000000..ca1541653 --- /dev/null +++ b/src/app/share/[...id]/page.tsx @@ -0,0 +1,5 @@ +import Share from './Share'; + +export default function ({ params: { id } }) { + return ; +} diff --git a/src/pages/sso.js b/src/app/sso/page.tsx similarity index 60% rename from src/pages/sso.js rename to src/app/sso/page.tsx index 6e6352061..75ea945d4 100644 --- a/src/pages/sso.js +++ b/src/app/sso/page.tsx @@ -1,11 +1,14 @@ +'use client'; import { useEffect } from 'react'; import { Loading } from 'react-basics'; -import { useRouter } from 'next/router'; +import { useRouter, useSearchParams } from 'next/navigation'; import { setClientAuthToken } from 'lib/client'; -export default function () { +export default function SSOPage() { const router = useRouter(); - const { token, url } = router.query; + const search = useSearchParams(); + const url = search.get('url'); + const token = search.get('token'); useEffect(() => { if (url && token) { diff --git a/src/components/common/DataTable.module.css b/src/components/common/DataTable.module.css new file mode 100644 index 000000000..e738c8958 --- /dev/null +++ b/src/components/common/DataTable.module.css @@ -0,0 +1,52 @@ +.table { + grid-template-rows: repeat(auto-fit, max-content); +} + +.table td { + align-items: center; + max-height: max-content; +} + +.search { + max-width: 300px; + margin: 20px 0; +} + +.action { + justify-content: flex-end; + gap: 5px; +} + +.body { + display: flex; + flex-direction: column; + position: relative; + overflow-x: auto; +} + +.body td { + display: flex; + gap: 10px; + min-height: 70px; + align-items: center; + min-width: min-content; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.body > div > div > div { + display: flex; + gap: 10px; +} + +.pager { + margin: 20px 0; +} + +.status { + display: flex; + align-items: center; + justify-content: center; + min-height: 200px; +} diff --git a/src/components/common/DataTable.tsx b/src/components/common/DataTable.tsx new file mode 100644 index 000000000..a3c63c0ab --- /dev/null +++ b/src/components/common/DataTable.tsx @@ -0,0 +1,92 @@ +import { ReactNode, Dispatch, SetStateAction } from 'react'; +import classNames from 'classnames'; +import { Banner, Loading, SearchField } from 'react-basics'; +import { useMessages } from 'components/hooks'; +import Empty from 'components/common/Empty'; +import Pager from 'components/common/Pager'; +import styles from './DataTable.module.css'; + +const DEFAULT_SEARCH_DELAY = 600; + +export interface DataTableProps { + queryResult: { + result: { + page: number; + pageSize: number; + count: number; + data: any[]; + }; + params: { + query: string; + page: number; + }; + setParams: Dispatch>; + isLoading: boolean; + error: unknown; + }; + searchDelay?: number; + allowSearch?: boolean; + allowPaging?: boolean; + children: ReactNode | ((data: any) => ReactNode); +} + +export function DataTable({ + queryResult, + searchDelay = 600, + allowSearch = true, + allowPaging = true, + children, +}: DataTableProps) { + const { formatMessage, labels, messages } = useMessages(); + const { result, error, isLoading, params, setParams } = queryResult || {}; + const { page, pageSize, count, data } = result || {}; + const { query } = params || {}; + const hasData = Boolean(!isLoading && data?.length); + const noResults = Boolean(!isLoading && query && !hasData); + + const handleSearch = query => { + setParams({ ...params, query, page: params.page ? page : 1 }); + }; + + const handlePageChange = page => { + setParams({ ...params, query, page }); + }; + + if (error) { + return {formatMessage(messages.error)}; + } + + return ( + <> + {allowSearch && (hasData || query) && ( + + )} +
+ {hasData ? (typeof children === 'function' ? children(result) : children) : null} + {isLoading && } + {!isLoading && !hasData && !query && } + {noResults && } +
+ {allowPaging && hasData && ( + + )} + + ); +} + +export default DataTable; diff --git a/src/components/common/Empty.js b/src/components/common/Empty.tsx similarity index 72% rename from src/components/common/Empty.js rename to src/components/common/Empty.tsx index c0be761a0..2c7fcd4a2 100644 --- a/src/components/common/Empty.js +++ b/src/components/common/Empty.tsx @@ -2,7 +2,12 @@ import classNames from 'classnames'; import styles from './Empty.module.css'; import useMessages from 'components/hooks/useMessages'; -export function Empty({ message, className }) { +export interface EmptyProps { + message?: string; + className?: string; +} + +export function Empty({ message, className }: EmptyProps) { const { formatMessage, messages } = useMessages(); return ( diff --git a/src/components/common/FilterLink.js b/src/components/common/FilterLink.js index 2a95e011d..89648255a 100644 --- a/src/components/common/FilterLink.js +++ b/src/components/common/FilterLink.js @@ -2,13 +2,13 @@ import { Icon, Icons } from 'react-basics'; import classNames from 'classnames'; import Link from 'next/link'; import { safeDecodeURI } from 'next-basics'; -import usePageQuery from 'components/hooks/usePageQuery'; +import useNavigation from 'components/hooks/useNavigation'; import useMessages from 'components/hooks/useMessages'; import styles from './FilterLink.module.css'; export function FilterLink({ id, value, label, externalUrl, children, className }) { const { formatMessage, labels } = useMessages(); - const { resolveUrl, query } = usePageQuery(); + const { makeUrl, query } = useNavigation(); const active = query[id] !== undefined; const selected = query[id] === value; @@ -22,7 +22,7 @@ export function FilterLink({ id, value, label, externalUrl, children, className {children} {!value && `(${label || formatMessage(labels.unknown)})`} {value && ( - + {safeDecodeURI(label || value)} )} diff --git a/src/components/common/LinkButton.js b/src/components/common/LinkButton.js index 54c7fa631..a9a8562db 100644 --- a/src/components/common/LinkButton.js +++ b/src/components/common/LinkButton.js @@ -1,12 +1,19 @@ +import classNames from 'classnames'; import Link from 'next/link'; -import { Icon, Icons, Text } from 'react-basics'; +import { useLocale } from 'components/hooks'; import styles from './LinkButton.module.css'; -export function LinkButton({ href, icon, children }) { +export function LinkButton({ href, className, variant, scroll = true, children }) { + const { dir } = useLocale(); + return ( - - {icon || } - {children} + + {children} ); } diff --git a/src/components/common/LinkButton.module.css b/src/components/common/LinkButton.module.css index ae8a3b627..5561f536f 100644 --- a/src/components/common/LinkButton.module.css +++ b/src/components/common/LinkButton.module.css @@ -26,3 +26,82 @@ .button:visited { color: var(--base900); } + +.button.disabled { + color: var(--disabled-color) !important; + background-color: var(--disabled-background) !important; + border-color: transparent !important; + pointer-events: none; +} + +.button.primary { + color: var(--light50); + background: var(--primary400); +} + +.button.primary:hover { + color: var(--light50); + background: var(--primary500); +} + +.button.primary:active { + color: var(--light50); + background: var(--primary600); +} + +.button.secondary { + border: 1px solid var(--border-color); + background: var(--base50); +} + +.button.secondary:hover { + background: var(--base75); +} + +.button.secondary:active { + background: var(--base100); +} + +.button.quiet { + color: var(--base900); + background: transparent; +} + +.button.quiet:hover { + background: var(--base100); +} + +.button.quiet:active { + background: var(--base200); +} + +.button.danger { + color: var(--light50); + background: var(--red800); +} + +.button.danger:hover { + color: var(--light50); + background: var(--red900); +} + +.button.danger:active { + color: var(--light50); + background: var(--red1000); +} + +.button.size-sm { + font-size: var(--font-size-sm); + height: calc(var(--base-height) * 0.75); + padding: 0 calc(var(--size600) * 0.75); +} + +.button.size-md { + font-size: var(--font-size-md); +} + +.button.size-lg { + font-size: var(--font-size-lg); + height: calc(var(--base-height) * 1.25); + padding: 0 calc(var(--size600) * 1.25); +} diff --git a/src/components/common/MobileMenu.js b/src/components/common/MobileMenu.js index de1e9ffa8..83a05dff3 100644 --- a/src/components/common/MobileMenu.js +++ b/src/components/common/MobileMenu.js @@ -1,11 +1,11 @@ import { createPortal } from 'react-dom'; import classNames from 'classnames'; -import { useRouter } from 'next/router'; +import { usePathname } from 'next/navigation'; import Link from 'next/link'; import styles from './MobileMenu.module.css'; export function MobileMenu({ items = [], onClose }) { - const { pathname } = useRouter(); + const pathname = usePathname(); const Items = ({ items, className }) => (
diff --git a/src/components/common/Pager.js b/src/components/common/Pager.js index 7a5e7ed5f..a21d35d98 100644 --- a/src/components/common/Pager.js +++ b/src/components/common/Pager.js @@ -1,14 +1,15 @@ -import styles from './Pager.module.css'; -import { Button, Flexbox, Icon, Icons } from 'react-basics'; +import classNames from 'classnames'; +import { Button, Icon, Icons } from 'react-basics'; import useMessages from 'components/hooks/useMessages'; +import styles from './Pager.module.css'; -export function Pager({ page, pageSize, count, onPageChange }) { +export function Pager({ page, pageSize, count, onPageChange, className }) { const { formatMessage, labels } = useMessages(); - const maxPage = Math.ceil(count / pageSize); + const maxPage = pageSize && count ? Math.ceil(count / pageSize) : 0; const lastPage = page === maxPage; const firstPage = page === 1; - if (count === 0) { + if (count === 0 || !maxPage) { return null; } @@ -24,21 +25,25 @@ export function Pager({ page, pageSize, count, onPageChange }) { } return ( - - - - {formatMessage(labels.pageOf, { current: page, total: maxPage })} - - - +
+
{formatMessage(labels.numberOfRecords, { x: count })}
+
+ +
+ {formatMessage(labels.pageOf, { current: page, total: maxPage })} +
+ +
+
+
); } diff --git a/src/components/common/Pager.module.css b/src/components/common/Pager.module.css index 99eb70ce0..880c1b401 100644 --- a/src/components/common/Pager.module.css +++ b/src/components/common/Pager.module.css @@ -1,7 +1,32 @@ -.container { - margin-top: 20px; +.pager { + display: grid; + grid-template-columns: repeat(3, 1fr); + align-items: center; +} + +.nav { + display: flex; + align-items: center; + justify-content: center; } .text { + font-size: var(--font-size-md); margin: 0 16px; + justify-content: center; +} + +.count { + color: var(--base600); + font-weight: 700; +} + +@media only screen and (max-width: 992px) { + .pager { + grid-template-columns: repeat(2, 1fr); + } + + .nav { + justify-content: end; + } } diff --git a/src/components/common/SettingsTable.js b/src/components/common/SettingsTable.js deleted file mode 100644 index 2df3b391b..000000000 --- a/src/components/common/SettingsTable.js +++ /dev/null @@ -1,100 +0,0 @@ -import Empty from 'components/common/Empty'; -import useMessages from 'components/hooks/useMessages'; -import { useState } from 'react'; -import { - SearchField, - Table, - TableBody, - TableCell, - TableColumn, - TableHeader, - TableRow, -} from 'react-basics'; -import styles from './SettingsTable.module.css'; -import Pager from 'components/common/Pager'; - -export function SettingsTable({ - columns = [], - data, - children, - cellRender, - showSearch, - showPaging, - onFilterChange, - onPageChange, - onPageSizeChange, - filterValue, -}) { - const { formatMessage, messages } = useMessages(); - const [filter, setFilter] = useState(filterValue); - const { data: value, page, count, pageSize } = data; - - const handleFilterChange = value => { - setFilter(value); - onFilterChange(value); - }; - - return ( - <> - {showSearch && !!value.length && ( - - )} - {value.length === 0 && filterValue && ( - - )} - {value.length > 0 && ( - - - {(column, index) => { - return ( - - {column.label} - - ); - }} - - - {(row, keys, rowIndex) => { - row.action = children(row, keys, rowIndex); - - return ( - - {(data, key, colIndex) => { - return ( - - - {cellRender ? cellRender(row, data, key, colIndex) : data[key]} - - ); - }} - - ); - }} - - {showPaging && ( - - )} -
- )} - - ); -} - -export default SettingsTable; diff --git a/src/components/common/SettingsTable.module.css b/src/components/common/SettingsTable.module.css deleted file mode 100644 index fd6cddfad..000000000 --- a/src/components/common/SettingsTable.module.css +++ /dev/null @@ -1,44 +0,0 @@ -.cell { - align-items: center; -} - -.row .cell:last-child { - gap: 10px; - justify-content: flex-end; -} - -.label { - display: none; - font-weight: 700; -} - -@media screen and (max-width: 992px) { - .header .cell { - display: none; - } - - .label { - display: block; - min-width: 100px; - } - - .row .cell { - padding-left: 0; - flex-basis: 100%; - } -} - -@media screen and (max-width: 1200px) { - .row { - flex-wrap: wrap; - } - - .header .cell:last-child { - display: none; - } - - .row .cell:last-child { - padding-left: 0; - flex-basis: 100%; - } -} diff --git a/src/components/common/UpdateNotice.js b/src/components/common/UpdateNotice.js index 23907948c..509df95ce 100644 --- a/src/components/common/UpdateNotice.js +++ b/src/components/common/UpdateNotice.js @@ -1,17 +1,18 @@ +'use client'; import { useEffect, useCallback, useState } from 'react'; import { createPortal } from 'react-dom'; -import { Button, Row, Column } from 'react-basics'; +import { Button } from 'react-basics'; 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 'components/hooks/useMessages'; -import { useRouter } from 'next/router'; +import { usePathname } from 'next/navigation'; export function UpdateNotice({ user, config }) { const { formatMessage, labels, messages } = useMessages(); const { latest, checked, hasUpdate, releaseUrl } = useStore(); - const { pathname } = useRouter(); + const pathname = usePathname(); const [dismissed, setDismissed] = useState(checked); const allowUpdate = user?.isAdmin && @@ -46,17 +47,17 @@ export function UpdateNotice({ user, config }) { } return createPortal( - - +
+
{formatMessage(messages.newVersionAvailable, { version: `v${latest}` })} - - +
+
- - , +
+
, document.body, ); } diff --git a/src/components/common/WorldMap.js b/src/components/common/WorldMap.js index b593099b7..ff34d5f2b 100644 --- a/src/components/common/WorldMap.js +++ b/src/components/common/WorldMap.js @@ -1,5 +1,4 @@ import { useState, useMemo } from 'react'; -import { useRouter } from 'next/router'; import { ComposableMap, Geographies, Geography, ZoomableGroup } from 'react-simple-maps'; import classNames from 'classnames'; import { colord } from 'colord'; @@ -8,16 +7,18 @@ import { ISO_COUNTRIES, MAP_FILE } from 'lib/constants'; import useTheme from 'components/hooks/useTheme'; import useCountryNames from 'components/hooks/useCountryNames'; import useLocale from 'components/hooks/useLocale'; +import useMessages from 'components/hooks/useMessages'; import { formatLongNumber } from 'lib/format'; import { percentFilter } from 'lib/filters'; import styles from './WorldMap.module.css'; export function WorldMap({ data, className }) { - const { basePath } = useRouter(); const [tooltip, setTooltipPopup] = useState(); const { theme, colors } = useTheme(); const { locale } = useLocale(); + const { formatMessage, labels } = useMessages(); const countryNames = useCountryNames(locale); + const visitorsLabel = formatMessage(labels.visitors).toLocaleLowerCase(locale); const metrics = useMemo(() => (data ? percentFilter(data) : []), [data]); function getFillColor(code) { @@ -40,7 +41,7 @@ export function WorldMap({ data, className }) { function handleHover(code) { if (code === 'AQ') return; const country = metrics?.find(({ x }) => x === code); - setTooltipPopup(`${countryNames[code]}: ${formatLongNumber(country?.y || 0)} visitors`); + setTooltipPopup(`${countryNames[code]}: ${formatLongNumber(country?.y || 0)} ${visitorsLabel}`); } return ( @@ -51,7 +52,7 @@ export function WorldMap({ data, className }) { > - + {({ geographies }) => { return geographies.map(geo => { const code = ISO_COUNTRIES[geo.id]; diff --git a/src/components/hooks/index.js b/src/components/hooks/index.js index 2596ba57e..697d54c3f 100644 --- a/src/components/hooks/index.js +++ b/src/components/hooks/index.js @@ -10,7 +10,7 @@ export * from './useFormat'; export * from './useLanguageNames'; export * from './useLocale'; export * from './useMessages'; -export * from './usePageQuery'; +export * from './useNavigation'; export * from './useReport'; export * from './useReports'; export * from './useRequireLogin'; @@ -20,4 +20,3 @@ export * from './useTheme'; export * from './useTimezone'; export * from './useUser'; export * from './useWebsite'; -export * from './useWebsiteReports'; diff --git a/src/components/hooks/useApi.ts b/src/components/hooks/useApi.ts index f41547a9e..75a928d53 100644 --- a/src/components/hooks/useApi.ts +++ b/src/components/hooks/useApi.ts @@ -1,4 +1,3 @@ -import { useRouter } from 'next/router'; import * as reactQuery from '@tanstack/react-query'; import { useApi as nextUseApi } from 'next-basics'; import { getClientAuthToken } from 'lib/client'; @@ -8,12 +7,11 @@ import useStore from 'store/app'; const selector = state => state.shareToken; export function useApi() { - const { basePath } = useRouter(); const shareToken = useStore(selector); const { get, post, put, del } = nextUseApi( { authorization: `Bearer ${getClientAuthToken()}`, [SHARE_TOKEN_HEADER]: shareToken?.token }, - basePath, + process.env.basePath, ); return { get, post, put, del, ...reactQuery }; diff --git a/src/components/hooks/useCountryNames.js b/src/components/hooks/useCountryNames.js index 51cabf34c..40611865c 100644 --- a/src/components/hooks/useCountryNames.js +++ b/src/components/hooks/useCountryNames.js @@ -1,5 +1,4 @@ import { useState, useEffect } from 'react'; -import { useRouter } from 'next/router'; import { httpGet } from 'next-basics'; import enUS from 'public/intl/country/en-US.json'; @@ -9,10 +8,9 @@ const countryNames = { export function useCountryNames(locale) { const [list, setList] = useState(countryNames[locale] || enUS); - const { basePath } = useRouter(); async function loadData(locale) { - const { data } = await httpGet(`${basePath}/intl/country/${locale}.json`); + const { data } = await httpGet(`${process.env.basePath}/intl/country/${locale}.json`); if (data) { countryNames[locale] = data; diff --git a/src/components/hooks/useFilterQuery.ts b/src/components/hooks/useFilterQuery.ts new file mode 100644 index 000000000..37c28b7e6 --- /dev/null +++ b/src/components/hooks/useFilterQuery.ts @@ -0,0 +1,27 @@ +import { useState } from 'react'; +import { useApi } from 'components/hooks/useApi'; +import { UseQueryOptions } from '@tanstack/react-query'; + +export function useFilterQuery(key: any[], fn, options?: UseQueryOptions) { + const [params, setParams] = useState({ + query: '', + page: 1, + }); + const { useQuery } = useApi(); + + const { data, ...other } = useQuery([...key, params], fn.bind(null, params), options); + + return { + result: data as { + page: number; + pageSize: number; + count: number; + data: any[]; + }, + ...other, + params, + setParams, + }; +} + +export default useFilterQuery; diff --git a/src/components/hooks/useFormat.js b/src/components/hooks/useFormat.js index 3fd10ec8e..0e609c488 100644 --- a/src/components/hooks/useFormat.js +++ b/src/components/hooks/useFormat.js @@ -2,6 +2,7 @@ import useMessages from './useMessages'; import { BROWSERS } from 'lib/constants'; import useLocale from './useLocale'; import useCountryNames from './useCountryNames'; +import regions from 'public/iso-3166-2.json'; export function useFormat() { const { formatMessage, labels } = useMessages(); @@ -16,6 +17,10 @@ export function useFormat() { return countryNames[value] || value; }; + const formatRegion = value => { + return regions[value] ? regions[value] : value; + }; + const formatDevice = value => { return formatMessage(labels[value] || labels.unknown); }; @@ -26,6 +31,8 @@ export function useFormat() { return formatBrowser(value); case 'country': return formatCountry(value); + case 'region': + return formatRegion(value); case 'device': return formatDevice(value); default: @@ -33,7 +40,7 @@ export function useFormat() { } }; - return { formatBrowser, formatCountry, formatDevice, formatValue }; + return { formatBrowser, formatCountry, formatRegion, formatDevice, formatValue }; } export default useFormat; diff --git a/src/components/hooks/useLanguageNames.js b/src/components/hooks/useLanguageNames.js index ff59e93dc..3823a26bd 100644 --- a/src/components/hooks/useLanguageNames.js +++ b/src/components/hooks/useLanguageNames.js @@ -1,5 +1,4 @@ import { useState, useEffect } from 'react'; -import { useRouter } from 'next/router'; import { httpGet } from 'next-basics'; import enUS from 'public/intl/language/en-US.json'; @@ -9,10 +8,9 @@ const languageNames = { export function useLanguageNames(locale) { const [list, setList] = useState(languageNames[locale] || enUS); - const { basePath } = useRouter(); async function loadData(locale) { - const { data } = await httpGet(`${basePath}/intl/language/${locale}.json`); + const { data } = await httpGet(`${process.env.basePath}/intl/language/${locale}.json`); if (data) { languageNames[locale] = data; diff --git a/src/components/hooks/useLocale.js b/src/components/hooks/useLocale.js index 1374af81f..71574d86b 100644 --- a/src/components/hooks/useLocale.js +++ b/src/components/hooks/useLocale.js @@ -1,5 +1,4 @@ import { useEffect } from 'react'; -import { useRouter } from 'next/router'; import { httpGet, setItem } from 'next-basics'; import { LOCALE_CONFIG } from 'lib/constants'; import { getDateLocale, getTextDirection } from 'lib/lang'; @@ -15,13 +14,12 @@ const selector = state => state.locale; export function useLocale() { const locale = useStore(selector); - const { basePath } = useRouter(); const forceUpdate = useForceUpdate(); const dir = getTextDirection(locale); const dateLocale = getDateLocale(locale); async function loadMessages(locale) { - const { ok, data } = await httpGet(`${basePath}/intl/messages/${locale}.json`); + const { ok, data } = await httpGet(`${process.env.basePath}/intl/messages/${locale}.json`); if (ok) { messages[locale] = data; diff --git a/src/components/hooks/useNavigation.js b/src/components/hooks/useNavigation.js new file mode 100644 index 000000000..658e81ed0 --- /dev/null +++ b/src/components/hooks/useNavigation.js @@ -0,0 +1,27 @@ +import { useMemo } from 'react'; +import { usePathname, useRouter, useSearchParams } from 'next/navigation'; +import { buildUrl } from 'next-basics'; + +export function useNavigation() { + const router = useRouter(); + const pathname = usePathname(); + const params = useSearchParams(); + + const query = useMemo(() => { + const obj = {}; + + for (const [key, value] of params.entries()) { + obj[key] = decodeURIComponent(value); + } + + return obj; + }, [params]); + + function makeUrl(params, reset) { + return reset ? pathname : buildUrl(pathname, { ...query, ...params }); + } + + return { pathname, query, router, makeUrl }; +} + +export default useNavigation; diff --git a/src/components/hooks/usePageQuery.js b/src/components/hooks/usePageQuery.js deleted file mode 100644 index b275d5807..000000000 --- a/src/components/hooks/usePageQuery.js +++ /dev/null @@ -1,33 +0,0 @@ -import { useMemo } from 'react'; -import { useRouter } from 'next/router'; -import { buildUrl } from 'next-basics'; - -export function usePageQuery() { - const router = useRouter(); - const { pathname, search } = location; - const { asPath } = router; - - const query = useMemo(() => { - if (!search) { - return {}; - } - - const params = search.substring(1).split('&'); - - return params.reduce((obj, item) => { - const [key, value] = item.split('='); - - obj[key] = decodeURIComponent(value); - - return obj; - }, {}); - }, [search]); - - function resolveUrl(params, reset) { - return buildUrl(asPath.split('?')[0], { ...(reset ? {} : query), ...params }); - } - - return { pathname, query, resolveUrl, router }; -} - -export default usePageQuery; diff --git a/src/components/hooks/useReport.js b/src/components/hooks/useReport.js index 72f90af33..7c698b4e7 100644 --- a/src/components/hooks/useReport.js +++ b/src/components/hooks/useReport.js @@ -2,18 +2,20 @@ import { produce } from 'immer'; import { useCallback, useEffect, useState } from 'react'; import { useTimezone } from './useTimezone'; import useApi from './useApi'; - -const baseParameters = { - name: 'Untitled', - description: '', - parameters: {}, -}; +import useMessages from './useMessages'; export function useReport(reportId, defaultParameters) { const [report, setReport] = useState(null); const [isRunning, setIsRunning] = useState(false); const { get, post } = useApi(); const [timezone] = useTimezone(); + const { formatMessage, labels } = useMessages(); + + const baseParameters = { + name: formatMessage(labels.untitled), + description: '', + parameters: {}, + }; const loadReport = async id => { const data = await get(`/reports/${id}`); diff --git a/src/components/hooks/useRequireLogin.ts b/src/components/hooks/useRequireLogin.ts index 950bb60ac..76460a557 100644 --- a/src/components/hooks/useRequireLogin.ts +++ b/src/components/hooks/useRequireLogin.ts @@ -1,10 +1,8 @@ import { useEffect } from 'react'; -import { useRouter } from 'next/router'; import useApi from 'components/hooks/useApi'; import useUser from 'components/hooks/useUser'; -export function useRequireLogin(handler: (data?: object) => void) { - const router = useRouter(); +export function useRequireLogin(handler?: (data?: object) => void) { const { get } = useApi(); const { user, setUser } = useUser(); @@ -15,7 +13,7 @@ export function useRequireLogin(handler: (data?: object) => void) { setUser(typeof handler === 'function' ? handler(data) : (data as any)?.user); } catch { - await router.push('/login'); + location.href = `${process.env.basePath || ''}/login`; } } diff --git a/src/components/hooks/useShareToken.js b/src/components/hooks/useShareToken.js index 3d6b9698b..5062c73ec 100644 --- a/src/components/hooks/useShareToken.js +++ b/src/components/hooks/useShareToken.js @@ -1,4 +1,3 @@ -import { useEffect } from 'react'; import useStore, { setShareToken } from 'store/app'; import useApi from './useApi'; @@ -6,23 +5,16 @@ const selector = state => state.shareToken; export function useShareToken(shareId) { const shareToken = useStore(selector); - const { get } = useApi(); + const { get, useQuery } = useApi(); + const { isLoading, error } = useQuery(['share', shareId], async () => { + const data = await get(`/share/${shareId}`); - async function loadToken(id) { - const data = await get(`/share/${id}`); + setShareToken(data); - if (data) { - setShareToken(data); - } - } + return data; + }); - useEffect(() => { - if (shareId) { - loadToken(shareId); - } - }, [shareId]); - - return shareToken; + return { shareToken, isLoading, error }; } export default useShareToken; diff --git a/src/components/hooks/useWebsiteReports.js b/src/components/hooks/useWebsiteReports.js deleted file mode 100644 index c637bc76a..000000000 --- a/src/components/hooks/useWebsiteReports.js +++ /dev/null @@ -1,38 +0,0 @@ -import { useState } from 'react'; -import useApi from './useApi'; -import useApiFilter from 'components/hooks/useApiFilter'; - -export function useWebsiteReports(websiteId) { - const [modified, setModified] = useState(Date.now()); - const { get, useQuery, del, useMutation } = useApi(); - const { mutate } = useMutation(reportId => del(`/reports/${reportId}`)); - const { filter, page, pageSize, handleFilterChange, handlePageChange, handlePageSizeChange } = - useApiFilter(); - const { data, error, isLoading } = useQuery( - ['reports:website', { websiteId, modified, filter, page, pageSize }], - () => get(`/websites/${websiteId}/reports`, { websiteId, filter, page, pageSize }), - ); - - const deleteReport = id => { - mutate(id, { - onSuccess: () => { - setModified(Date.now()); - }, - }); - }; - - return { - reports: data, - error, - isLoading, - deleteReport, - filter, - page, - pageSize, - handleFilterChange, - handlePageChange, - handlePageSizeChange, - }; -} - -export default useWebsiteReports; diff --git a/src/components/input/LanguageButton.module.css b/src/components/input/LanguageButton.module.css index 3d4c0c569..cc5d649a1 100644 --- a/src/components/input/LanguageButton.module.css +++ b/src/components/input/LanguageButton.module.css @@ -1,7 +1,6 @@ .menu { - display: flex; - flex-flow: row wrap; - min-width: 640px; + display: grid; + grid-template-columns: repeat(3, 1fr); padding: 10px; background: var(--base50); z-index: var(--z-index-popup); @@ -14,7 +13,7 @@ display: flex; align-items: center; justify-content: space-between; - min-width: calc(100% / 3); + min-width: 200px; border-radius: 5px; padding: 5px 10px; } @@ -32,3 +31,15 @@ .icon { color: var(--primary400); } + +@media screen and (max-width: 992px) { + .menu { + grid-template-columns: repeat(2, 1fr); + } +} + +@media screen and (max-width: 768px) { + .menu { + transform: translateX(40px); + } +} diff --git a/src/components/input/LogoutButton.js b/src/components/input/LogoutButton.js index 2b04a78a2..6ca358a12 100644 --- a/src/components/input/LogoutButton.js +++ b/src/components/input/LogoutButton.js @@ -5,7 +5,7 @@ import useMessages from 'components/hooks/useMessages'; export function LogoutButton({ tooltipPosition = 'top' }) { const { formatMessage, labels } = useMessages(); return ( - + - +
)} - +
); } diff --git a/src/components/input/WebsiteDateFilter.module.css b/src/components/input/WebsiteDateFilter.module.css index 986f5c178..6f2e822dc 100644 --- a/src/components/input/WebsiteDateFilter.module.css +++ b/src/components/input/WebsiteDateFilter.module.css @@ -1,7 +1,17 @@ +.container { + display: flex; + align-items: center; + gap: 10px; +} + .dropdown { min-width: 200px; } +.buttons { + display: flex; +} + .buttons button:first-child { border-top-right-radius: 0; border-bottom-right-radius: 0; diff --git a/src/components/input/WebsiteSelect.js b/src/components/input/WebsiteSelect.js index 1bdc4608a..078389d31 100644 --- a/src/components/input/WebsiteSelect.js +++ b/src/components/input/WebsiteSelect.js @@ -1,11 +1,12 @@ import { Dropdown, Item } from 'react-basics'; import useApi from 'components/hooks/useApi'; import useMessages from 'components/hooks/useMessages'; +import styles from './WebsiteSelect.module.css'; export function WebsiteSelect({ websiteId, onSelect }) { const { formatMessage, labels } = useMessages(); const { get, useQuery } = useApi(); - const { data } = useQuery(['websites:me'], () => get('/me/websites')); + const { data } = useQuery(['websites:me'], () => get('/me/websites', { pageSize: 100 })); const renderValue = value => { return data?.data?.find(({ id }) => id === value)?.name; @@ -13,6 +14,7 @@ export function WebsiteSelect({ websiteId, onSelect }) { return ( - - - {title ? `${title} | umami` : 'umami'} - - -
- {children} -
-
- ); -} - -export default AppLayout; diff --git a/src/components/layout/Grid.js b/src/components/layout/Grid.js index 0276063b5..86b08887b 100644 --- a/src/components/layout/Grid.js +++ b/src/components/layout/Grid.js @@ -1,13 +1,18 @@ -import { Row, Column } from 'react-basics'; import classNames from 'classnames'; +import { mapChildren } from 'react-basics'; import styles from './Grid.module.css'; -export function GridRow(props) { - const { className, ...otherProps } = props; - return ; +export function Grid({ className, ...otherProps }) { + return
; } -export function GridColumn(props) { - const { className, ...otherProps } = props; - return ; +export function GridRow(props) { + const { columns = 'two', className, children, ...otherProps } = props; + return ( +
+ {mapChildren(children, child => { + return
{child}
; + })} +
+ ); } diff --git a/src/components/layout/Grid.module.css b/src/components/layout/Grid.module.css index dc2e8ff6c..f72a5f126 100644 --- a/src/components/layout/Grid.module.css +++ b/src/components/layout/Grid.module.css @@ -1,27 +1,52 @@ -.col { - display: flex; - flex-direction: column; - padding: 20px; +.grid { + display: grid; } .row { + display: grid; + grid-template-columns: repeat(6, 1fr); border-top: 1px solid var(--base300); - min-height: 430px; } -.row > .col { +.col { + padding: 20px; + min-height: 430px; border-inline-start: 1px solid var(--base300); } -.row > .col:first-child { +.col:first-child { border-inline-start: 0; padding-inline-start: 0; } -.row > .col:last-child { +.col:last-child { padding-inline-end: 0; } +.col.two { + grid-column: span 3; +} + +.col.three { + grid-column: span 2; +} + +.col.two-one:first-child { + grid-column: span 4; +} + +.col.two-one:last-child { + grid-column: span 2; +} + +.col.one-two:first-child { + grid-column: span 2; +} + +.col.one-two:last-child { + grid-column: span 4; +} + @media only screen and (max-width: 992px) { .row { border: 0; @@ -33,4 +58,11 @@ border-inline-end: 0; padding: 20px 0; } + + .col.two, + .col.three, + .col.one-two, + .col.two-one { + grid-column: span 6 !important; + } } diff --git a/src/components/layout/Header.js b/src/components/layout/Header.js deleted file mode 100644 index 21cdd2513..000000000 --- a/src/components/layout/Header.js +++ /dev/null @@ -1,31 +0,0 @@ -import { Column, Icon, Row, Text } from 'react-basics'; -import Link from 'next/link'; -import LanguageButton from 'components/input/LanguageButton'; -import ThemeButton from 'components/input/ThemeButton'; -import SettingsButton from 'components/input/SettingsButton'; -import Icons from 'components/icons'; -import styles from './Header.module.css'; - -export function Header() { - return ( -
- - - - - - - umami - - - - - - - - -
- ); -} - -export default Header; diff --git a/src/components/layout/NavBar.js b/src/components/layout/NavBar.js deleted file mode 100644 index 07627e2a2..000000000 --- a/src/components/layout/NavBar.js +++ /dev/null @@ -1,63 +0,0 @@ -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'; - -export function NavBar() { - const { pathname } = useRouter(); - const { formatMessage, labels } = useMessages(); - - const links = [ - { label: formatMessage(labels.dashboard), url: '/dashboard' }, - { label: formatMessage(labels.websites), url: '/websites' }, - { label: formatMessage(labels.reports), url: '/reports' }, - { label: formatMessage(labels.settings), url: '/settings' }, - ].filter(n => n); - - return ( -
- - -
- - - - umami -
-
- {links.map(({ url, label }) => { - return ( - - {label} - - ); - })} -
-
- -
- - - -
-
- -
-
-
-
- ); -} - -export default NavBar; diff --git a/src/components/layout/NavGroup.js b/src/components/layout/NavGroup.js index 94f9d8e66..361dffb5a 100644 --- a/src/components/layout/NavGroup.js +++ b/src/components/layout/NavGroup.js @@ -1,7 +1,7 @@ import { useState } from 'react'; import { Icon, Text, TooltipPopup } from 'react-basics'; import classNames from 'classnames'; -import { useRouter } from 'next/router'; +import { usePathname } from 'next/navigation'; import Link from 'next/link'; import Icons from 'components/icons'; import styles from './NavGroup.module.css'; @@ -13,7 +13,7 @@ export function NavGroup({ allowExpand = true, minimized = false, }) { - const { pathname } = useRouter(); + const pathname = usePathname(); const [expanded, setExpanded] = useState(defaultExpanded); const handleExpand = () => setExpanded(state => !state); diff --git a/src/components/layout/Page.module.css b/src/components/layout/Page.module.css index c546971b6..52893157c 100644 --- a/src/components/layout/Page.module.css +++ b/src/components/layout/Page.module.css @@ -2,6 +2,10 @@ flex: 1; display: flex; flex-direction: column; - background: var(--base50); position: relative; + width: 100%; + max-width: 1320px; + margin: 0 auto; + padding: 0 20px; + min-height: calc(100vh - 60px); } diff --git a/src/components/layout/Page.js b/src/components/layout/Page.tsx similarity index 68% rename from src/components/layout/Page.js rename to src/components/layout/Page.tsx index 4f42aa55a..2f7020128 100644 --- a/src/components/layout/Page.js +++ b/src/components/layout/Page.tsx @@ -1,16 +1,28 @@ +'use client'; +import { ReactNode } from 'react'; import classNames from 'classnames'; import { Banner, Loading } from 'react-basics'; import useMessages from 'components/hooks/useMessages'; import styles from './Page.module.css'; -export function Page({ className, error, loading, children }) { +export function Page({ + className, + error, + isLoading, + children, +}: { + className?: string; + error?: unknown; + isLoading?: boolean; + children?: ReactNode; +}) { const { formatMessage, messages } = useMessages(); if (error) { return {formatMessage(messages.error)}; } - if (loading) { + if (isLoading) { return ; } diff --git a/src/components/layout/PageHeader.module.css b/src/components/layout/PageHeader.module.css index 8e615b935..a4eeb4c6b 100644 --- a/src/components/layout/PageHeader.module.css +++ b/src/components/layout/PageHeader.module.css @@ -36,9 +36,4 @@ .header { margin-bottom: 10px; } - - .actions { - flex-basis: 100%; - order: -1; - } } diff --git a/src/components/layout/PageHeader.js b/src/components/layout/PageHeader.tsx similarity index 58% rename from src/components/layout/PageHeader.js rename to src/components/layout/PageHeader.tsx index f13631409..2261bebcb 100644 --- a/src/components/layout/PageHeader.js +++ b/src/components/layout/PageHeader.tsx @@ -1,8 +1,14 @@ import classNames from 'classnames'; -import React from 'react'; +import React, { ReactNode } from 'react'; import styles from './PageHeader.module.css'; -export function PageHeader({ title, children, className }) { +export interface PageHeaderProps { + title?: ReactNode; + className?: string; + children?: ReactNode; +} + +export function PageHeader({ title, className, children }: PageHeaderProps) { return (
{title &&
{title}
} diff --git a/src/components/layout/ReportsLayout.js b/src/components/layout/ReportsLayout.js deleted file mode 100644 index 374da2635..000000000 --- a/src/components/layout/ReportsLayout.js +++ /dev/null @@ -1,23 +0,0 @@ -import { Column, Row } from 'react-basics'; -import styles from './ReportsLayout.module.css'; - -export function ReportsLayout({ children, filter, header }) { - return ( - <> - {header} - - {filter && ( - -

Filters

- {filter} -
- )} - - {children} - -
- - ); -} - -export default ReportsLayout; diff --git a/src/components/layout/ReportsLayout.module.css b/src/components/layout/ReportsLayout.module.css deleted file mode 100644 index 6922665fa..000000000 --- a/src/components/layout/ReportsLayout.module.css +++ /dev/null @@ -1,23 +0,0 @@ -.filter { - margin-top: 30px; - min-width: 200px; - max-width: 100vw; - padding: 10px; - background: var(--base50); - border-radius: 5px; - border: 1px solid var(--border-color); -} - -.filter h2 { - padding-bottom: 20px; -} - -.content { - min-height: 50vh; -} - -@media only screen and (max-width: 768px) { - .menu { - display: none; - } -} diff --git a/src/components/layout/SettingsLayout.module.css b/src/components/layout/SettingsLayout.module.css deleted file mode 100644 index 08ff02aa6..000000000 --- a/src/components/layout/SettingsLayout.module.css +++ /dev/null @@ -1,20 +0,0 @@ -.menu { - display: flex; - flex-direction: column; - padding-top: 40px; - padding-right: 20px; -} - -.content { - min-height: 50vh; -} - -@media only screen and (max-width: 768px) { - .menu { - display: none; - } - - .content { - margin-top: 20px; - } -} diff --git a/src/components/layout/ShareLayout.js b/src/components/layout/ShareLayout.js deleted file mode 100644 index c634e1b6d..000000000 --- a/src/components/layout/ShareLayout.js +++ /dev/null @@ -1,15 +0,0 @@ -import { Container } from 'react-basics'; -import Header from './Header'; -import Footer from './Footer'; - -export function ShareLayout({ children }) { - return ( - -
-
{children}
-