Skip to content

Commit

Permalink
Merge pull request umami-software#240 from mikecao/dev
Browse files Browse the repository at this point in the history
v0.67.0 Localize country names
  • Loading branch information
mikecao authored Oct 1, 2020
2 parents dc735f5 + 074f248 commit c125669
Show file tree
Hide file tree
Showing 36 changed files with 176 additions and 312 deletions.
10 changes: 7 additions & 3 deletions components/common/WorldMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import tinycolor from 'tinycolor2';
import useTheme from 'hooks/useTheme';
import { THEME_COLORS } from 'lib/constants';
import styles from './WorldMap.module.css';
import useCountryNames from 'hooks/useCountryNames';
import useLocale from 'hooks/useLocale';

const geoUrl = '/world-110m.json';

Expand All @@ -21,6 +23,8 @@ export default function WorldMap({ data, className }) {
}),
[theme],
);
const [locale] = useLocale();
const countryNames = useCountryNames(locale);

function getFillColor(code) {
if (code === 'AQ') return;
Expand All @@ -39,10 +43,10 @@ export default function WorldMap({ data, className }) {
return code === 'AQ' ? 0 : 1;
}

function handleHover({ ISO_A2: code, NAME: name }) {
function handleHover(code) {
if (code === 'AQ') return;
const country = data?.find(({ x }) => x === code);
setTooltip(`${name}: ${country?.y || 0} visitors`);
setTooltip(`${countryNames[code]}: ${country?.y || 0} visitors`);
}

return (
Expand Down Expand Up @@ -70,7 +74,7 @@ export default function WorldMap({ data, className }) {
hover: { outline: 'none', fill: colors.hoverColor },
pressed: { outline: 'none' },
}}
onMouseOver={() => handleHover(geo.properties)}
onMouseOver={() => handleHover(code)}
onMouseOut={() => setTooltip(null)}
/>
);
Expand Down
10 changes: 5 additions & 5 deletions components/forms/AccountEditForm.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useState } from 'react';
import { FormattedMessage } from 'react-intl';
import { Formik, Form, Field } from 'formik';
import { useRouter } from 'next/router';
import { post } from 'lib/web';
import Button from 'components/common/Button';
import FormLayout, {
Expand Down Expand Up @@ -29,18 +30,17 @@ const validate = ({ user_id, username, password }) => {
};

export default function AccountEditForm({ values, onSave, onClose }) {
const { basePath } = useRouter();
const [message, setMessage] = useState();

const handleSubmit = async values => {
const response = await post(`/api/account`, values);
const { ok, data } = await post(`${basePath}/api/account`, values);

if (typeof response !== 'string') {
if (ok) {
onSave();
} else {
setMessage(
response || (
<FormattedMessage id="message.failure" defaultMessage="Something went wrong." />
),
data || <FormattedMessage id="message.failure" defaultMessage="Something went wrong." />,
);
}
};
Expand Down
10 changes: 5 additions & 5 deletions components/forms/ChangePasswordForm.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useState } from 'react';
import { FormattedMessage } from 'react-intl';
import { useRouter } from 'next/router';
import { Formik, Form, Field } from 'formik';
import { post } from 'lib/web';
import Button from 'components/common/Button';
Expand Down Expand Up @@ -37,18 +38,17 @@ const validate = ({ current_password, new_password, confirm_password }) => {
};

export default function ChangePasswordForm({ values, onSave, onClose }) {
const { basePath } = useRouter();
const [message, setMessage] = useState();

const handleSubmit = async values => {
const response = await post(`/api/account/password`, values);
const { ok, data } = await post(`${basePath}/api/account/password`, values);

if (typeof response !== 'string') {
if (ok) {
onSave();
} else {
setMessage(
response || (
<FormattedMessage id="message.failure" defaultMessage="Something went wrong." />
),
data || <FormattedMessage id="message.failure" defaultMessage="Something went wrong." />,
);
}
};
Expand Down
12 changes: 8 additions & 4 deletions components/forms/DeleteForm.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import React, { useState } from 'react';
import { FormattedMessage } from 'react-intl';
import { useRouter } from 'next/router';
import { Formik, Form, Field } from 'formik';
import { del } from 'lib/web';
import Button from 'components/common/Button';
Expand All @@ -8,7 +10,6 @@ import FormLayout, {
FormMessage,
FormRow,
} from 'components/layout/FormLayout';
import { FormattedMessage } from 'react-intl';

const CONFIRMATION_WORD = 'DELETE';

Expand All @@ -27,15 +28,18 @@ const validate = ({ confirmation }) => {
};

export default function DeleteForm({ values, onSave, onClose }) {
const { basePath } = useRouter();
const [message, setMessage] = useState();

const handleSubmit = async ({ type, id }) => {
const response = await del(`/api/${type}/${id}`);
const { ok, data } = await del(`${basePath}/api/${type}/${id}`);

if (typeof response !== 'string') {
if (ok) {
onSave();
} else {
setMessage(<FormattedMessage id="message.failure" defaultMessage="Something went wrong." />);
setMessage(
data || <FormattedMessage id="message.failure" defaultMessage="Something went wrong." />,
);
}
};

Expand Down
16 changes: 10 additions & 6 deletions components/forms/LoginForm.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useState } from 'react';
import { FormattedMessage } from 'react-intl';
import { Formik, Form, Field } from 'formik';
import Router from 'next/router';
import { useRouter } from 'next/router';
import { post } from 'lib/web';
import Button from 'components/common/Button';
import FormLayout, {
Expand All @@ -28,22 +28,26 @@ const validate = ({ username, password }) => {
};

export default function LoginForm() {
const router = useRouter();
const [message, setMessage] = useState();

const handleSubmit = async ({ username, password }) => {
const response = await post('/api/auth/login', { username, password });
const { ok, status, data } = await post(`${router.basePath}/api/auth/login`, {
username,
password,
});

if (typeof response !== 'string') {
await Router.push('/');
if (ok) {
return router.push('/');
} else {
setMessage(
response.startsWith('401') ? (
status === 401 ? (
<FormattedMessage
id="message.incorrect-username-password"
defaultMessage="Incorrect username/password."
/>
) : (
response
data
),
);
}
Expand Down
10 changes: 7 additions & 3 deletions components/forms/WebsiteEditForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import FormLayout, {
} from 'components/layout/FormLayout';
import Checkbox from 'components/common/Checkbox';
import { DOMAIN_REGEX } from 'lib/constants';
import { useRouter } from 'next/router';

const initialValues = {
name: '',
Expand All @@ -34,15 +35,18 @@ const validate = ({ name, domain }) => {
};

export default function WebsiteEditForm({ values, onSave, onClose }) {
const { basePath } = useRouter();
const [message, setMessage] = useState();

const handleSubmit = async values => {
const response = await post(`/api/website`, values);
const { ok, data } = await post(`${basePath}/api/website`, values);

if (typeof response !== 'string') {
if (ok) {
onSave();
} else {
setMessage(<FormattedMessage id="message.failure" defaultMessage="Something went wrong." />);
setMessage(
data || <FormattedMessage id="message.failure" defaultMessage="Something went wrong." />,
);
}
};

Expand Down
13 changes: 11 additions & 2 deletions components/metrics/CountriesTable.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import React from 'react';
import MetricsTable from './MetricsTable';
import { countryFilter, percentFilter } from 'lib/filters';
import { percentFilter } from 'lib/filters';
import { FormattedMessage } from 'react-intl';
import useCountryNames from 'hooks/useCountryNames';
import useLocale from 'hooks/useLocale';

export default function CountriesTable({ websiteId, token, limit, onDataLoad = () => {} }) {
const [locale] = useLocale();
const countryNames = useCountryNames(locale);

function renderLabel({ x }) {
return <div className={locale}>{countryNames[x]}</div>;
}

return (
<MetricsTable
title={<FormattedMessage id="metrics.countries" defaultMessage="Countries" />}
Expand All @@ -12,8 +21,8 @@ export default function CountriesTable({ websiteId, token, limit, onDataLoad = (
websiteId={websiteId}
token={token}
limit={limit}
dataFilter={countryFilter}
onDataLoad={data => onDataLoad(percentFilter(data))}
renderLabel={renderLabel}
/>
);
}
2 changes: 1 addition & 1 deletion components/pages/WebsiteDetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export default function WebsiteDetails({ websiteId, token }) {
} = usePageQuery();

const BackButton = () => (
<div className={styles.backButton}>
<div key="back-button" className={styles.backButton}>
<Link
key="back-button"
href={router.pathname}
Expand Down
34 changes: 34 additions & 0 deletions hooks/useCountryNames.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useState, useEffect } from 'react';
import { useRouter } from 'next/router';
import { get } from 'lib/web';
import enUS from 'public/country/en-US.json';

const countryNames = {
'en-US': enUS,
};

export default function useCountryNames(locale) {
const [list, setList] = useState(countryNames[locale] || enUS);
const { basePath } = useRouter();

async function loadData(locale) {
const { ok, data } = await get(`${basePath}/country/${locale}.json`);

if (ok) {
countryNames[locale] = data;
setList(countryNames[locale]);
} else {
setList(enUS);
}
}

useEffect(() => {
if (!countryNames[locale]) {
loadData(locale);
} else {
setList(countryNames[locale]);
}
}, [locale]);

return list;
}
8 changes: 6 additions & 2 deletions hooks/useFetch.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ import { useState, useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { get } from 'lib/web';
import { updateQuery } from 'redux/actions/queries';
import { useRouter } from 'next/router';

export default function useFetch(url, params = {}, options = {}) {
const dispatch = useDispatch();
const [data, setData] = useState();
const [status, setStatus] = useState();
const [error, setError] = useState();
const [loading, setLoadiing] = useState(false);
const { basePath } = useRouter();
const keys = Object.keys(params)
.sort()
.map(key => params[key]);
Expand All @@ -18,11 +21,12 @@ export default function useFetch(url, params = {}, options = {}) {
setLoadiing(true);
setError(null);
const time = performance.now();
const data = await get(url, params);
const { data, status } = await get(`${basePath}${url}`, params);

dispatch(updateQuery({ url, time: performance.now() - time, completed: Date.now() }));

setData(data);
setStatus(status);
onDataLoad(data);
} catch (e) {
console.error(e);
Expand All @@ -46,5 +50,5 @@ export default function useFetch(url, params = {}, options = {}) {
}
}, [url, ...keys, ...update]);

return { data, error, loading, loadData };
return { data, status, error, loading };
}
32 changes: 19 additions & 13 deletions hooks/useRequireLogin.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { useState, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { updateUser } from 'redux/actions/user';
import { useRouter } from 'next/router';
import { get } from '../lib/web';

export async function fetchUser() {
const res = await fetch('/api/auth/verify');
Expand All @@ -13,29 +15,33 @@ export async function fetchUser() {
}

export default function useRequireLogin() {
const router = useRouter();
const dispatch = useDispatch();
const storeUser = useSelector(state => state.user);
const [loading, setLoading] = useState(!storeUser);
const [user, setUser] = useState(storeUser);

useEffect(() => {
if (!loading && user) {
return;
async function loadUser() {
setLoading(true);

const { ok, data } = await get(`${router.basePath}/api/auth/verify`);

if (!ok) {
return router.push('/login');
}

setLoading(true);
await dispatch(updateUser(data));

fetchUser().then(async user => {
if (!user) {
window.location.href = '/login';
return;
}
setUser(user);
setLoading(false);
}

await dispatch(updateUser(user));
useEffect(() => {
if (!loading && user) {
return;
}

setUser(user);
setLoading(false);
});
loadUser();
}, []);

return { user, loading };
Expand Down
2 changes: 1 addition & 1 deletion hooks/useTheme.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { getItem, setItem } from 'lib/web';
import { THEME_CONFIG } from 'lib/constants';
import { useEffect } from 'react';

export default function useLocale() {
export default function useTheme() {
const theme = useSelector(state => state.app.theme || getItem(THEME_CONFIG) || 'light');
const dispatch = useDispatch();

Expand Down
Loading

0 comments on commit c125669

Please sign in to comment.