From 36cd5a393a5454f8e2fc023c7ee4451ef282c249 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 18 Sep 2020 19:53:50 -0700 Subject: [PATCH 001/188] Convert accounts dashboard button into a link. --- assets/external-link.svg | 1 + components/settings/AccountSettings.js | 33 +++++++++++-------- .../settings/AccountSettings.module.css | 1 + package.json | 2 +- 4 files changed, 23 insertions(+), 14 deletions(-) create mode 100644 assets/external-link.svg diff --git a/assets/external-link.svg b/assets/external-link.svg new file mode 100644 index 00000000..ed09306f --- /dev/null +++ b/assets/external-link.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/components/settings/AccountSettings.js b/components/settings/AccountSettings.js index bc555a0f..2471ba21 100644 --- a/components/settings/AccountSettings.js +++ b/components/settings/AccountSettings.js @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import { FormattedMessage } from 'react-intl'; -import { useRouter } from 'next/router'; +import Link from 'next/link'; import classNames from 'classnames'; import PageHeader from 'components/layout/PageHeader'; import Button from 'components/common/Button'; @@ -16,11 +16,10 @@ import Pen from 'assets/pen.svg'; import Plus from 'assets/plus.svg'; import Trash from 'assets/trash.svg'; import Check from 'assets/check.svg'; -import List from 'assets/list-ul.svg'; +import LinkIcon from 'assets/external-link.svg'; import styles from './AccountSettings.module.css'; export default function AccountSettings() { - const router = useRouter(); const [addAccount, setAddAccount] = useState(); const [editAccount, setEditAccount] = useState(); const [deleteAccount, setDeleteAccount] = useState(); @@ -30,16 +29,18 @@ export default function AccountSettings() { const Checkmark = ({ is_admin }) => (is_admin ? } size="medium" /> : null); + const DashboardLink = row => + row.is_admin ? null : ( + + + } /> + + + ); + const Buttons = row => row.username !== 'admin' ? ( - + + ); +} diff --git a/components/settings/DateRangeSetting.module.css b/components/settings/DateRangeSetting.module.css new file mode 100644 index 00000000..230e7c97 --- /dev/null +++ b/components/settings/DateRangeSetting.module.css @@ -0,0 +1,3 @@ +.button { + margin-left: 10px; +} diff --git a/components/settings/ProfileSettings.js b/components/settings/ProfileSettings.js index 10386318..f28226c5 100644 --- a/components/settings/ProfileSettings.js +++ b/components/settings/ProfileSettings.js @@ -1,40 +1,27 @@ import React, { useState } from 'react'; import { FormattedMessage } from 'react-intl'; -import { useDispatch, useSelector } from 'react-redux'; +import { useSelector } from 'react-redux'; import PageHeader from 'components/layout/PageHeader'; import Button from 'components/common/Button'; import Modal from 'components/common/Modal'; import Toast from 'components/common/Toast'; import ChangePasswordForm from 'components/forms/ChangePasswordForm'; -import DateFilter from 'components/common/DateFilter'; +import TimezoneSetting from 'components/settings/TimezoneSetting'; import Dots from 'assets/ellipsis-h.svg'; -import { getTimezone } from 'lib/date'; -import { setItem } from 'lib/web'; -import useDateRange from 'hooks/useDateRange'; -import { setDateRange } from 'redux/actions/websites'; import styles from './ProfileSettings.module.css'; +import DateRangeSetting from './DateRangeSetting'; export default function ProfileSettings() { - const dispatch = useDispatch(); const user = useSelector(state => state.user); const [changePassword, setChangePassword] = useState(false); const [message, setMessage] = useState(); const { user_id } = user; - const timezone = getTimezone(); - const dateRange = useDateRange(0); - const { startDate, endDate, value } = dateRange; function handleSave() { setChangePassword(false); setMessage(); } - function handleDateChange(values) { - const { value } = values; - setItem(`umami.date-range`, value === 'custom' ? values : value); - dispatch(setDateRange(0, values)); - } - return ( <> @@ -47,7 +34,7 @@ export default function ProfileSettings() { -
+
@@ -55,17 +42,14 @@ export default function ProfileSettings() {
-
{timezone}
+
+ +
-
- +
+
{changePassword && ( diff --git a/components/settings/ProfileSettings.module.css b/components/settings/ProfileSettings.module.css index ea7711ac..fdd8252f 100644 --- a/components/settings/ProfileSettings.module.css +++ b/components/settings/ProfileSettings.module.css @@ -1,3 +1,3 @@ -.date { +.list dd { display: flex; } diff --git a/components/settings/TimezoneSetting.js b/components/settings/TimezoneSetting.js new file mode 100644 index 00000000..54751a45 --- /dev/null +++ b/components/settings/TimezoneSetting.js @@ -0,0 +1,31 @@ +import React from 'react'; +import { FormattedMessage } from 'react-intl'; +import { listTimeZones } from 'timezone-support'; +import DropDown from '../common/DropDown'; +import Button from '../common/Button'; +import useTimezone from 'hooks/useTimezone'; +import { getTimezone } from 'lib/date'; +import styles from './TimezoneSetting.module.css'; + +export default function TimezoneSetting() { + const [timezone, saveTimezone] = useTimezone(); + const options = listTimeZones().map(n => ({ label: n, value: n })); + + function handleReset() { + saveTimezone(getTimezone()); + } + + return ( + <> + + + + ); +} diff --git a/components/settings/TimezoneSetting.module.css b/components/settings/TimezoneSetting.module.css new file mode 100644 index 00000000..9561111d --- /dev/null +++ b/components/settings/TimezoneSetting.module.css @@ -0,0 +1,8 @@ +.menu { + max-height: 300px; + overflow-y: auto; +} + +.button { + margin-left: 10px; +} diff --git a/hooks/useDateRange.js b/hooks/useDateRange.js index 13703d9f..77f892de 100644 --- a/hooks/useDateRange.js +++ b/hooks/useDateRange.js @@ -1,24 +1,41 @@ -import { useSelector } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { parseISO } from 'date-fns'; import { getDateRange } from 'lib/date'; -import { getItem } from 'lib/web'; +import { getItem, setItem } from 'lib/web'; +import { setDateRange } from '../redux/actions/websites'; +import { DATE_RANGE_CONFIG, DEFAULT_DATE_RANGE } from 'lib/constants'; +import useForceUpdate from './useForceUpdate'; -export default function useDateRange(websiteId, defaultDateRange = '24hour') { - const globalDefault = getItem('umami.date-range'); +export default function useDateRange(websiteId, defaultDateRange = DEFAULT_DATE_RANGE) { + const dispatch = useDispatch(); + const dateRange = useSelector(state => state.websites[websiteId]?.dateRange); + const forceUpdate = useForceUpdate(); + + const globalDefault = getItem(DATE_RANGE_CONFIG); let globalDateRange; - if (typeof globalDefault === 'string') { - globalDateRange = getDateRange(globalDefault); - } else if (typeof globalDefault === 'object') { - globalDateRange = { - ...globalDefault, - startDate: parseISO(globalDefault.startDate), - endDate: parseISO(globalDefault.endDate), - }; + if (globalDefault) { + if (typeof globalDefault === 'string') { + globalDateRange = getDateRange(globalDefault); + } else if (typeof globalDefault === 'object') { + globalDateRange = { + ...globalDefault, + startDate: parseISO(globalDefault.startDate), + endDate: parseISO(globalDefault.endDate), + }; + } } - return useSelector( - state => - state.websites[websiteId]?.dateRange || globalDateRange || getDateRange(defaultDateRange), - ); + function saveDateRange(values) { + const { value } = values; + + if (websiteId) { + dispatch(setDateRange(websiteId, values)); + } else { + setItem(DATE_RANGE_CONFIG, value === 'custom' ? values : value); + forceUpdate(); + } + } + + return [dateRange || globalDateRange || getDateRange(defaultDateRange), saveDateRange]; } diff --git a/hooks/useForceUpdate.js b/hooks/useForceUpdate.js new file mode 100644 index 00000000..2b8d6101 --- /dev/null +++ b/hooks/useForceUpdate.js @@ -0,0 +1,9 @@ +import { useCallback, useState } from 'react'; + +export default function useForceUpdate() { + const [, update] = useState(Object.create(null)); + + return useCallback(() => { + update(Object.create(null)); + }, [update]); +} diff --git a/hooks/useLocale.js b/hooks/useLocale.js index 185c1f19..788b9b28 100644 --- a/hooks/useLocale.js +++ b/hooks/useLocale.js @@ -1,11 +1,14 @@ import { useDispatch, useSelector } from 'react-redux'; import { updateApp } from 'redux/actions/app'; +import { setItem } from 'lib/web'; +import { LOCALE_CONFIG } from 'lib/constants'; export default function useLocale() { const locale = useSelector(state => state.app.locale); const dispatch = useDispatch(); function setLocale(value) { + setItem(LOCALE_CONFIG, value); dispatch(updateApp({ locale: value })); } diff --git a/hooks/useTimezone.js b/hooks/useTimezone.js new file mode 100644 index 00000000..5de39f9a --- /dev/null +++ b/hooks/useTimezone.js @@ -0,0 +1,17 @@ +import { useState, useCallback } from 'react'; +import { getTimezone } from 'lib/date'; +import { getItem, setItem } from 'lib/web'; + +export default function useTimezone() { + const [timezone, setTimezone] = useState(getItem('umami.timezone') || getTimezone()); + + const saveTimezone = useCallback( + value => { + setItem('umami.timezone', value); + setTimezone(value); + }, + [setTimezone], + ); + + return [timezone, saveTimezone]; +} diff --git a/lang/de-DE.json b/lang/de-DE.json index 75d2b1ea..96ba0f99 100644 --- a/lang/de-DE.json +++ b/lang/de-DE.json @@ -11,6 +11,7 @@ "button.login": "Anmelden", "button.more": "Mehr", "button.refresh": "Aktualisieren", + "button.reset": "Reset", "button.save": "Speichern", "button.single-day": "Ein Tag", "button.view-details": "Details anzeigen", diff --git a/lang/en-US.json b/lang/en-US.json index a583e05c..54ee8cad 100644 --- a/lang/en-US.json +++ b/lang/en-US.json @@ -11,6 +11,7 @@ "button.login": "Login", "button.more": "More", "button.refresh": "Refresh", + "button.reset": "Reset", "button.save": "Save", "button.single-day": "Single day", "button.view-details": "View details", diff --git a/lang/es-MX.json b/lang/es-MX.json index 4a7cdd57..ed8cf1a2 100644 --- a/lang/es-MX.json +++ b/lang/es-MX.json @@ -11,6 +11,7 @@ "button.login": "Iniciar sesión", "button.more": "Más", "button.refresh": "Refresh", + "button.reset": "Reset", "button.save": "Guardar", "button.single-day": "Single day", "button.view-details": "Ver detalles", diff --git a/lang/fr-FR.json b/lang/fr-FR.json index 7536e839..8cfedb33 100644 --- a/lang/fr-FR.json +++ b/lang/fr-FR.json @@ -11,6 +11,7 @@ "button.login": "Connexion", "button.more": "Plus", "button.refresh": "Refresh", + "button.reset": "Reset", "button.save": "Sauvegarder", "button.single-day": "Single day", "button.view-details": "Voir les details", diff --git a/lang/ja-JP.json b/lang/ja-JP.json index a9318697..27d3ca34 100644 --- a/lang/ja-JP.json +++ b/lang/ja-JP.json @@ -11,6 +11,7 @@ "button.login": "ログイン", "button.more": "さらに表示", "button.refresh": "Refresh", + "button.reset": "Reset", "button.save": "保存", "button.single-day": "Single day", "button.view-details": "詳細表示", diff --git a/lang/mn-MN.json b/lang/mn-MN.json index 2a354fe4..0dd543bf 100644 --- a/lang/mn-MN.json +++ b/lang/mn-MN.json @@ -11,6 +11,7 @@ "button.login": "Нэвтрэх", "button.more": "Цааш", "button.refresh": "Refresh", + "button.reset": "Reset", "button.save": "Хадгалах", "button.single-day": "Single day", "button.view-details": "Дэлгэрүүлж харах", diff --git a/lang/nl-NL.json b/lang/nl-NL.json index e26c5266..2c49098c 100644 --- a/lang/nl-NL.json +++ b/lang/nl-NL.json @@ -11,6 +11,7 @@ "button.login": "Inloggen", "button.more": "Toon meer", "button.refresh": "Refresh", + "button.reset": "Reset", "button.save": "Opslaan", "button.single-day": "Single day", "button.view-details": "Meer details", diff --git a/lang/ru-RU.json b/lang/ru-RU.json index 0fd1b449..5cbedf0b 100644 --- a/lang/ru-RU.json +++ b/lang/ru-RU.json @@ -11,6 +11,7 @@ "button.login": "Войти", "button.more": "Больше", "button.refresh": "Refresh", + "button.reset": "Reset", "button.save": "Сохранить", "button.single-day": "Single day", "button.view-details": "Посмотреть детали", diff --git a/lang/tr-TR.json b/lang/tr-TR.json index 36c3b58c..6a93d1dd 100644 --- a/lang/tr-TR.json +++ b/lang/tr-TR.json @@ -11,6 +11,7 @@ "button.login": "Giriş Yap", "button.more": "Detaylı göster", "button.refresh": "Refresh", + "button.reset": "Reset", "button.save": "Kaydet", "button.single-day": "Single day", "button.view-details": "Detayı incele", diff --git a/lang/zh-CN.json b/lang/zh-CN.json index cbcc6f1c..9c6ddda4 100644 --- a/lang/zh-CN.json +++ b/lang/zh-CN.json @@ -11,6 +11,7 @@ "button.login": "登录", "button.more": "更多", "button.refresh": "刷新", + "button.reset": "Reset", "button.save": "保存", "button.single-day": "单日", "button.view-details": "查看更多", diff --git a/lib/constants.js b/lib/constants.js index e9641c68..0e2468ab 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -1,4 +1,9 @@ export const AUTH_COOKIE_NAME = 'umami.auth'; +export const LOCALE_CONFIG = 'umami.locale'; +export const TIMEZONE_CONFIG = 'umami.timezone'; +export const DATE_RANGE_CONFIG = 'umami.date-range'; + +export const DEFAULT_DATE_RANGE = '24hour'; export const POSTGRESQL = 'postgresql'; export const MYSQL = 'mysql'; diff --git a/package.json b/package.json index 37209cea..547537f3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "umami", - "version": "0.41.0", + "version": "0.42.0", "description": "A simple, fast, website analytics alternative to Google Analytics. ", "author": "Mike Cao ", "license": "MIT", @@ -83,6 +83,7 @@ "redux-thunk": "^2.3.0", "request-ip": "^2.1.3", "thenby": "^1.3.4", + "timezone-support": "^2.0.2", "tinycolor2": "^1.4.1", "unfetch": "^4.1.0", "uuid": "^8.3.0" diff --git a/redux/actions/app.js b/redux/actions/app.js index 72636a74..cbb8285b 100644 --- a/redux/actions/app.js +++ b/redux/actions/app.js @@ -1,9 +1,10 @@ import { createSlice } from '@reduxjs/toolkit'; import { getItem } from 'lib/web'; +import { LOCALE_CONFIG } from 'lib/constants'; const app = createSlice({ name: 'app', - initialState: { locale: getItem('umami.locale') || 'en-US' }, + initialState: { locale: getItem(LOCALE_CONFIG) || 'en-US' }, reducers: { updateApp(state, action) { state = action.payload; diff --git a/yarn.lock b/yarn.lock index da2fa5cb..201c7e5d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2699,6 +2699,11 @@ commander@2, commander@^2.20.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== +commander@2.20.0: + version "2.20.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" + integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== + commander@^6.0.0, commander@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/commander/-/commander-6.1.0.tgz#f8d722b78103141006b66f4c7ba1e97315ba75bc" @@ -8608,6 +8613,13 @@ timers-browserify@^2.0.4: dependencies: setimmediate "^1.0.4" +timezone-support@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/timezone-support/-/timezone-support-2.0.2.tgz#801d6924478b1b60f09b90699ce1127a6044cbe7" + integrity sha512-J/1PyHCX76vOPuJzCyHMQMH2wTjXCJ30R5EXaS/QTi+xYsL0thS0pubDrHCWnfG4zU1jpPJtctnBBRCOpcJZeQ== + dependencies: + commander "2.20.0" + tiny-lru@7.0.6: version "7.0.6" resolved "https://registry.yarnpkg.com/tiny-lru/-/tiny-lru-7.0.6.tgz#b0c3cdede1e5882aa2d1ae21cb2ceccf2a331f24" From b21225302b4069f8d2a686ae1afd4d00a882a273 Mon Sep 17 00:00:00 2001 From: tim-hub Date: Sun, 20 Sep 2020 11:06:40 +1200 Subject: [PATCH 007/188] switch to yarn to keep consistency (cherry picked from commit 5fd19dcb6e167603fd4f5dffe88b1b153060f453) --- Dockerfile | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 871b8a87..566d1d4a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,11 +5,13 @@ ARG DATABASE_TYPE ENV DATABASE_URL "postgresql://umami:umami@db:5432/umami" \ DATABASE_TYPE=$DATABASE_TYPE -COPY . /app WORKDIR /app - -RUN npm install && npm run build - EXPOSE 3000 -CMD ["npm", "start"] +COPY package.json yarn.lock /app/ +RUN yarn install --frozen-lockfile + +COPY . /app +RUN yarn build + +CMD ["yarn", "start"] From ffd75ad4d577525a17ad9d429354a45797e0d17c Mon Sep 17 00:00:00 2001 From: MrZhiin Date: Sun, 20 Sep 2020 11:04:44 +0800 Subject: [PATCH 008/188] Update Simplified Chinese language --- lang/zh-CN.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lang/zh-CN.json b/lang/zh-CN.json index 9c6ddda4..a51a42be 100644 --- a/lang/zh-CN.json +++ b/lang/zh-CN.json @@ -11,7 +11,7 @@ "button.login": "登录", "button.more": "更多", "button.refresh": "刷新", - "button.reset": "Reset", + "button.reset": "重置", "button.save": "保存", "button.single-day": "单日", "button.view-details": "查看更多", @@ -21,7 +21,7 @@ "label.current-password": "目前密码", "label.custom-range": "自定义时间段", "label.dashboard": "仪表板", - "label.default-date-range": "Default date range", + "label.default-date-range": "默认日期范围", "label.domain": "域名", "label.enable-share-url": "激活共享链接", "label.invalid": "输入无效", @@ -40,7 +40,7 @@ "label.this-month": "本月", "label.this-week": "本周", "label.this-year": "今年", - "label.timezone": "Timezone", + "label.timezone": "时区", "label.today": "今天", "label.unknown": "未知", "label.username": "用户名", From c9a6e29cac739601978b9c19193dbe6fccca5d64 Mon Sep 17 00:00:00 2001 From: Kamino <67395018+err931@users.noreply.github.com> Date: Sun, 20 Sep 2020 15:04:31 +0900 Subject: [PATCH 009/188] Update Japanese lang --- lang/ja-JP.json | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lang/ja-JP.json b/lang/ja-JP.json index 27d3ca34..b15e0c92 100644 --- a/lang/ja-JP.json +++ b/lang/ja-JP.json @@ -5,23 +5,23 @@ "button.cancel": "キャンセル", "button.change-password": "パスワード変更", "button.copy-to-clipboard": "クリップボードにコピー", - "button.date-range": "Date range", + "button.date-range": "期間", "button.delete": "削除", "button.edit": "編集", "button.login": "ログイン", "button.more": "さらに表示", - "button.refresh": "Refresh", - "button.reset": "Reset", + "button.refresh": "更新", + "button.reset": "リセット", "button.save": "保存", - "button.single-day": "Single day", + "button.single-day": "一日のみ", "button.view-details": "詳細表示", "label.accounts": "アカウント", "label.administrator": "管理者", "label.confirm-password": "パスワード(確認)", "label.current-password": "現在のパスワード", - "label.custom-range": "Custom range", + "label.custom-range": "期間を指定する", "label.dashboard": "ダッシュボード", - "label.default-date-range": "Default date range", + "label.default-date-range": "最初に表示する期間", "label.domain": "ドメイン", "label.enable-share-url": "共有リンクを有効にする", "label.invalid": "無効", @@ -40,9 +40,9 @@ "label.this-month": "今月", "label.this-week": "今週", "label.this-year": "今年", - "label.timezone": "Timezone", + "label.timezone": "タイムゾーン", "label.today": "今日", - "label.unknown": "Unknown", + "label.unknown": "不明", "label.username": "ユーザー名", "label.websites": "Webサイト", "message.active-users": "{x}人が閲覧中です。", @@ -67,10 +67,10 @@ "metrics.bounce-rate": "直帰率", "metrics.browsers": "ブラウザ", "metrics.countries": "国", - "metrics.device.desktop": "Desktop", - "metrics.device.laptop": "Laptop", - "metrics.device.mobile": "Mobile", - "metrics.device.tablet": "Tablet", + "metrics.device.desktop": "デスクトップ", + "metrics.device.laptop": "ノートPC", + "metrics.device.mobile": "携帯電話", + "metrics.device.tablet": "タブレット", "metrics.devices": "デバイス", "metrics.events": "イベント", "metrics.filter.combined": "パスまで", From d94c5c7dbe50b362ee14dcde6582f7b3c6cfdada Mon Sep 17 00:00:00 2001 From: ym-project Date: Sun, 20 Sep 2020 15:00:23 +0800 Subject: [PATCH 010/188] update russian --- lang/ru-RU.json | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lang/ru-RU.json b/lang/ru-RU.json index 5cbedf0b..b9e19336 100644 --- a/lang/ru-RU.json +++ b/lang/ru-RU.json @@ -5,15 +5,15 @@ "button.cancel": "Отменить", "button.change-password": "Изменить пароль", "button.copy-to-clipboard": "Скопировать в буфер обмена", - "button.date-range": "Date range", + "button.date-range": "Диапазон дат", "button.delete": "Удалить", "button.edit": "Редактировать", "button.login": "Войти", "button.more": "Больше", - "button.refresh": "Refresh", - "button.reset": "Reset", + "button.refresh": "Обновить", + "button.reset": "Сбросить", "button.save": "Сохранить", - "button.single-day": "Single day", + "button.single-day": "Один день", "button.view-details": "Посмотреть детали", "label.accounts": "Аккаунты", "label.administrator": "Администратор", @@ -21,7 +21,7 @@ "label.current-password": "Текущий пароль", "label.custom-range": "Другой период", "label.dashboard": "Информационная панель", - "label.default-date-range": "Default date range", + "label.default-date-range": "Диапазон дат по-умолчанию", "label.domain": "Домен", "label.enable-share-url": "Разрешить делиться ссылкой", "label.invalid": "Некорректный", @@ -40,9 +40,9 @@ "label.this-month": "Этот месяц", "label.this-week": "Эта неделя", "label.this-year": "Этот год", - "label.timezone": "Timezone", + "label.timezone": "Часовой пояс", "label.today": "Сегодня", - "label.unknown": "Unknown", + "label.unknown": "Неизвестно", "label.username": "Имя пользователя", "label.websites": "Сайты", "message.active-users": "{x} текущих посетителей", @@ -57,7 +57,7 @@ "message.no-data-available": "Нет данных.", "message.no-websites-configured": "У вас нет настроенных сайтов.", "message.page-not-found": "Страница не найдена.", - "message.powered-by": "на движке {name}", + "message.powered-by": "На движке {name}", "message.save-success": "Успешно сохранено.", "message.share-url": "Это публичная ссылка для {target}.", "message.track-stats": "Чтобы отслеживать статистику для {target}, поместите следующий код в раздел {head} вашего сайта.", @@ -67,10 +67,10 @@ "metrics.bounce-rate": "Отказы", "metrics.browsers": "Браузеры", "metrics.countries": "Страны", - "metrics.device.desktop": "Desktop", - "metrics.device.laptop": "Laptop", - "metrics.device.mobile": "Mobile", - "metrics.device.tablet": "Tablet", + "metrics.device.desktop": "Настольный компьютер", + "metrics.device.laptop": "Ноутбук", + "metrics.device.mobile": "Смартфон", + "metrics.device.tablet": "Планшет", "metrics.devices": "Устройства", "metrics.events": "События", "metrics.filter.combined": "Объединенные", From aa265d1d426396788d52e49b874e69e132510c74 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sun, 20 Sep 2020 01:33:39 -0700 Subject: [PATCH 011/188] Dark mode. --- components/common/Button.module.css | 3 +- components/common/ButtonGroup.module.css | 4 +- components/common/Dropdown.module.css | 2 +- components/common/Link.module.css | 4 +- components/common/Menu.module.css | 4 +- components/common/Modal.module.css | 4 +- components/common/NavMenu.module.css | 2 + components/common/Toast.module.css | 2 +- components/common/WorldMap.js | 60 ++++++++++++------- components/common/WorldMap.module.css | 1 - components/forms/DatePickerForm.js | 2 +- components/layout/FormLayout.module.css | 2 +- components/layout/Header.js | 13 ++-- components/layout/Layout.js | 2 +- components/layout/Layout.module.css | 6 ++ components/metrics/BarChart.js | 26 +++++++- components/metrics/BarChart.module.css | 2 +- components/metrics/MetricsTable.module.css | 2 +- .../{common => settings}/LanguageButton.js | 4 +- .../LanguageButton.module.css | 0 components/settings/ThemeButton.js | 13 ++++ components/settings/TimezoneSetting.js | 4 +- hooks/useLocale.js | 8 +-- hooks/useTheme.js | 21 +++++++ lib/constants.js | 32 ++++++++++ package.json | 2 +- redux/actions/app.js | 17 ++++-- styles/index.css | 10 +++- styles/variables.css | 29 +++++++++ 29 files changed, 221 insertions(+), 60 deletions(-) create mode 100644 components/layout/Layout.module.css rename components/{common => settings}/LanguageButton.js (95%) rename components/{common => settings}/LanguageButton.module.css (100%) create mode 100644 components/settings/ThemeButton.js create mode 100644 hooks/useTheme.js diff --git a/components/common/Button.module.css b/components/common/Button.module.css index faae656b..351f5ecc 100644 --- a/components/common/Button.module.css +++ b/components/common/Button.module.css @@ -3,6 +3,7 @@ justify-content: center; align-items: center; font-size: var(--font-size-normal); + color: var(--gray900); background: var(--gray100); padding: 8px 16px; border-radius: 4px; @@ -18,7 +19,7 @@ } .button:active { - color: initial; + color: var(--gray700); } .large { diff --git a/components/common/ButtonGroup.module.css b/components/common/ButtonGroup.module.css index d18a8e9c..bc60f8d3 100644 --- a/components/common/ButtonGroup.module.css +++ b/components/common/ButtonGroup.module.css @@ -7,6 +7,7 @@ .group .button { border-radius: 0; + color: var(--gray800); background: var(--gray50); border-left: 1px solid var(--gray500); padding: 4px 8px; @@ -24,6 +25,7 @@ margin: 0; } -.selected { +.group .button.selected { + color: var(--gray900); font-weight: 600; } diff --git a/components/common/Dropdown.module.css b/components/common/Dropdown.module.css index 250e6c29..4b94f58f 100644 --- a/components/common/Dropdown.module.css +++ b/components/common/Dropdown.module.css @@ -20,5 +20,5 @@ } .icon { - padding-left: 10px; + padding-left: 20px; } diff --git a/components/common/Link.module.css b/components/common/Link.module.css index d6dc0536..24d8f84c 100644 --- a/components/common/Link.module.css +++ b/components/common/Link.module.css @@ -2,7 +2,7 @@ a.link, a.link:active, a.link:visited { position: relative; - color: #2c2c2c; + color: var(--gray900); text-decoration: none; } @@ -12,7 +12,7 @@ a.link:before { bottom: -2px; width: 0; height: 2px; - background: #2680eb; + background: var(--primary400); opacity: 0.5; transition: width 100ms; } diff --git a/components/common/Menu.module.css b/components/common/Menu.module.css index 9bcd642f..65551837 100644 --- a/components/common/Menu.module.css +++ b/components/common/Menu.module.css @@ -8,14 +8,14 @@ .option { font-size: var(--font-size-small); font-weight: normal; - background: #fff; + background: var(--gray50); padding: 4px 16px; cursor: pointer; white-space: nowrap; } .option:hover { - background: #f5f5f5; + background: var(--gray100); } .float { diff --git a/components/common/Modal.module.css b/components/common/Modal.module.css index 3702e774..bf2491c7 100644 --- a/components/common/Modal.module.css +++ b/components/common/Modal.module.css @@ -16,8 +16,8 @@ right: 0; bottom: 0; margin: auto; - background: var(--gray900); - opacity: 0.1; + background: #000; + opacity: 0.5; } .content { diff --git a/components/common/NavMenu.module.css b/components/common/NavMenu.module.css index c5d6c9db..7be73973 100644 --- a/components/common/NavMenu.module.css +++ b/components/common/NavMenu.module.css @@ -1,4 +1,5 @@ .menu { + color: var(--gray800); border: 1px solid var(--gray500); border-radius: 4px; overflow: hidden; @@ -16,5 +17,6 @@ } .selected { + color: var(--gray900); font-weight: 600; } diff --git a/components/common/Toast.module.css b/components/common/Toast.module.css index bfcd26bb..a65abfd1 100644 --- a/components/common/Toast.module.css +++ b/components/common/Toast.module.css @@ -9,7 +9,7 @@ justify-content: space-between; align-items: center; padding: 8px 16px; - color: var(--gray50); + color: var(--msgColor); background: var(--green400); margin: auto; z-index: 2; diff --git a/components/common/WorldMap.js b/components/common/WorldMap.js index f10dd542..f6eec8d6 100644 --- a/components/common/WorldMap.js +++ b/components/common/WorldMap.js @@ -1,44 +1,57 @@ -import React, { useState } from 'react'; +import React, { useState, useMemo } from 'react'; import ReactTooltip from 'react-tooltip'; +import { ComposableMap, Geographies, Geography, ZoomableGroup } from 'react-simple-maps'; import classNames from 'classnames'; import tinycolor from 'tinycolor2'; -import { ComposableMap, Geographies, Geography, ZoomableGroup } from 'react-simple-maps'; +import useTheme from 'hooks/useTheme'; +import { THEME_COLORS } from 'lib/constants'; import styles from './WorldMap.module.css'; const geoUrl = '/world-110m.json'; -export default function WorldMap({ - data, - className, - baseColor = '#e9f3fd', - fillColor = '#f5f5f5', - strokeColor = '#2680eb', - hoverColor = '#2680eb', -}) { +export default function WorldMap({ data, className }) { const [tooltip, setTooltip] = useState(); + const [theme] = useTheme(); + const colors = useMemo( + () => ({ + baseColor: THEME_COLORS[theme].primary, + fillColor: THEME_COLORS[theme].gray100, + strokeColor: THEME_COLORS[theme].primary, + hoverColor: THEME_COLORS[theme].primary, + }), + [theme], + ); function getFillColor(code) { - if (code === 'AQ') return '#ffffff'; + if (code === 'AQ') return; const country = data?.find(({ x }) => x === code); - return country ? tinycolor(baseColor).darken(country.z) : fillColor; + + if (!country) { + return colors.fillColor; + } + + return tinycolor(colors.baseColor)[theme === 'light' ? 'lighten' : 'darken']( + 40 * (1.0 - country.z / 100), + ); } - function getStrokeColor(code) { - return code === 'AQ' ? '#ffffff' : strokeColor; - } - - function getHoverColor(code) { - return code === 'AQ' ? '#ffffff' : hoverColor; + function getOpacity(code) { + return code === 'AQ' ? 0 : 1; } function handleHover({ ISO_A2: code, NAME: name }) { + if (code === 'AQ') return; const country = data?.find(({ x }) => x === code); setTooltip(`${name}: ${country?.y || 0} visitors`); } return ( -
- +
+ {({ geographies }) => { @@ -50,10 +63,11 @@ export default function WorldMap({ key={geo.rsmKey} geography={geo} fill={getFillColor(code)} - stroke={getStrokeColor(code)} + stroke={colors.strokeColor} + opacity={getOpacity(code)} style={{ default: { outline: 'none' }, - hover: { outline: 'none', fill: getHoverColor(code) }, + hover: { outline: 'none', fill: colors.hoverColor }, pressed: { outline: 'none' }, }} onMouseOver={() => handleHover(geo.properties)} @@ -65,7 +79,7 @@ export default function WorldMap({ - {tooltip} + {tooltip}
); } diff --git a/components/common/WorldMap.module.css b/components/common/WorldMap.module.css index bf84d697..c2528038 100644 --- a/components/common/WorldMap.module.css +++ b/components/common/WorldMap.module.css @@ -1,5 +1,4 @@ .container { overflow: hidden; position: relative; - background: #fff; } diff --git a/components/forms/DatePickerForm.js b/components/forms/DatePickerForm.js index f8b6416e..673cd7bd 100644 --- a/components/forms/DatePickerForm.js +++ b/components/forms/DatePickerForm.js @@ -6,7 +6,7 @@ import Button from 'components/common/Button'; import { FormButtons } from 'components/layout/FormLayout'; import { getDateRangeValues } from 'lib/date'; import styles from './DatePickerForm.module.css'; -import ButtonGroup from '../common/ButtonGroup'; +import ButtonGroup from 'components/common/ButtonGroup'; const FILTER_DAY = 0; const FILTER_RANGE = 1; diff --git a/components/layout/FormLayout.module.css b/components/layout/FormLayout.module.css index 0b82ee7c..90e1b8c2 100644 --- a/components/layout/FormLayout.module.css +++ b/components/layout/FormLayout.module.css @@ -39,7 +39,7 @@ } .msg { - color: var(--gray50); + color: var(--msgColor); background: var(--red400); font-size: var(--font-size-small); padding: 4px 8px; diff --git a/components/layout/Header.js b/components/layout/Header.js index f275bda3..46c9b3e4 100644 --- a/components/layout/Header.js +++ b/components/layout/Header.js @@ -3,11 +3,12 @@ import { FormattedMessage } from 'react-intl'; import { useSelector } from 'react-redux'; import classNames from 'classnames'; import Link from 'components/common/Link'; -import UserButton from '../common/UserButton'; -import Icon from '../common/Icon'; +import UserButton from 'components/common/UserButton'; +import Icon from 'components/common/Icon'; +import LanguageButton from 'components/settings/LanguageButton'; +import ThemeButton from 'components/settings/ThemeButton'; import Logo from 'assets/logo.svg'; import styles from './Header.module.css'; -import LanguageButton from '../common/LanguageButton'; export default function Header() { const user = useSelector(state => state.user); @@ -32,10 +33,14 @@ export default function Header() { + ) : ( - + <> + + + )}
diff --git a/components/layout/Layout.js b/components/layout/Layout.js index 021745cc..b16a0717 100644 --- a/components/layout/Layout.js +++ b/components/layout/Layout.js @@ -16,8 +16,8 @@ export default function Layout({ title, children, header = true, footer = true } {header &&
}
{children}
-
{footer &&