diff --git a/.gitignore b/.gitignore index 32d3cbce..1f55fbcc 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ /build /public/umami.js /public/geo +/public/lang /lang-compiled # misc diff --git a/Dockerfile b/Dockerfile index 31ea0054..7d086546 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,8 @@ # Build image FROM node:12.18-alpine AS build +ARG BASE_PATH ARG DATABASE_TYPE +ENV BASE_PATH=$BASE_PATH ENV DATABASE_URL "postgresql://umami:umami@db:5432/umami" \ DATABASE_TYPE=$DATABASE_TYPE WORKDIR /build diff --git a/README.md b/README.md index 397dcdbf..98fe2150 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ A detailed getting started guide can be found at [https://umami.is/docs/](https: ### Requirements -- A server with Node.js 10.13 or newer +- A server with Node.js 12 or newer - A database (MySQL or Postgresql) ### Get the source code and install packages diff --git a/assets/bars.svg b/assets/bars.svg new file mode 100644 index 00000000..91c83c48 --- /dev/null +++ b/assets/bars.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/xmark.svg b/assets/xmark.svg new file mode 100644 index 00000000..6d72bf6d --- /dev/null +++ b/assets/xmark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/components/common/Calendar.js b/components/common/Calendar.js index 92fd311c..fd28363c 100644 --- a/components/common/Calendar.js +++ b/components/common/Calendar.js @@ -27,7 +27,7 @@ import styles from './Calendar.module.css'; import Icon from './Icon'; export default function Calendar({ date, minDate, maxDate, onChange }) { - const [locale] = useLocale(); + const { locale } = useLocale(); const [selectMonth, setSelectMonth] = useState(false); const [selectYear, setSelectYear] = useState(false); diff --git a/components/common/DateFilter.js b/components/common/DateFilter.js index 7e35a010..ba3417d1 100644 --- a/components/common/DateFilter.js +++ b/components/common/DateFilter.js @@ -55,7 +55,7 @@ const filterOptions = [ ]; function DateFilter({ value, startDate, endDate, onChange, className }) { - const [locale] = useLocale(); + const { locale } = useLocale(); const [showPicker, setShowPicker] = useState(false); const displayValue = value === 'custom' ? ( @@ -102,7 +102,7 @@ function DateFilter({ value, startDate, endDate, onChange, className }) { } const CustomRange = ({ startDate, endDate, onClick }) => { - const [locale] = useLocale(); + const { locale } = useLocale(); function handleClick(e) { e.stopPropagation(); diff --git a/components/common/RefreshButton.js b/components/common/RefreshButton.js index 61f06a3c..91a43ab8 100644 --- a/components/common/RefreshButton.js +++ b/components/common/RefreshButton.js @@ -12,7 +12,7 @@ import useLocale from 'hooks/useLocale'; function RefreshButton({ websiteId }) { const dispatch = useDispatch(); - const [locale] = useLocale(); + const { locale } = useLocale(); const [dateRange] = useDateRange(websiteId); const [loading, setLoading] = useState(false); const completed = useSelector(state => state.queries[`/api/website/${websiteId}/stats`]); diff --git a/components/common/WorldMap.js b/components/common/WorldMap.js index 0fa4c234..6dc8a359 100644 --- a/components/common/WorldMap.js +++ b/components/common/WorldMap.js @@ -24,7 +24,7 @@ function WorldMap({ data, className }) { }), [theme], ); - const [locale] = useLocale(); + const { locale } = useLocale(); const countryNames = useCountryNames(locale); function getFillColor(code) { diff --git a/components/forms/ResetForm.js b/components/forms/ResetForm.js new file mode 100644 index 00000000..791039ac --- /dev/null +++ b/components/forms/ResetForm.js @@ -0,0 +1,98 @@ +import React, { useState } from 'react'; +import { FormattedMessage } from 'react-intl'; +import { Formik, Form, Field } from 'formik'; +import Button from 'components/common/Button'; +import FormLayout, { + FormButtons, + FormError, + FormMessage, + FormRow, +} from 'components/layout/FormLayout'; +import usePost from 'hooks/usePost'; + +const CONFIRMATION_WORD = 'RESET'; + +const validate = ({ confirmation }) => { + const errors = {}; + + if (confirmation !== CONFIRMATION_WORD) { + errors.confirmation = !confirmation ? ( + + ) : ( + + ); + } + + return errors; +}; + +export default function ResetForm({ values, onSave, onClose }) { + const post = usePost(); + const [message, setMessage] = useState(); + + const handleSubmit = async ({ type, id }) => { + const { ok, data } = await post(`/api/${type}/${id}/reset`); + + if (ok) { + onSave(); + } else { + setMessage( + data || , + ); + } + }; + + return ( + + + {props => ( +
+
+ {values.name} }} + /> +
+
+ +
+

+ {CONFIRMATION_WORD} }} + /> +

+ +
+ + +
+
+ + + + + {message} +
+ )} +
+
+ ); +} diff --git a/components/layout/Footer.js b/components/layout/Footer.js index 73e010bc..0e1a4cb5 100644 --- a/components/layout/Footer.js +++ b/components/layout/Footer.js @@ -4,11 +4,15 @@ import { FormattedMessage } from 'react-intl'; import Link from 'components/common/Link'; import styles from './Footer.module.css'; import useVersion from 'hooks/useVersion'; +import useLocale from 'hooks/useLocale'; +import { rtlLocales } from 'lib/lang'; export default function Footer() { const { current } = useVersion(); + const { locale } = useLocale(); + return ( -