diff --git a/components/messages.js b/components/messages.js
index a87ed093..a5683d20 100644
--- a/components/messages.js
+++ b/components/messages.js
@@ -5,11 +5,108 @@ export const labels = defineMessages({
required: { id: 'label.required', defaultMessage: 'Required' },
save: { id: 'label.save', defaultMessage: 'Save' },
cancel: { id: 'label.cancel', defaultMessage: 'Cancel' },
+ continue: { id: 'label.continue', defaultMessage: 'Continue' },
+ delete: { id: 'label.delete', defaultMessage: 'Delete' },
+ users: { id: 'label.users', defaultMessage: 'Users' },
+ createUser: { id: 'label.create-user', defaultMessage: 'Create user' },
+ username: { id: 'label.username', defaultMessage: 'Username' },
+ password: { id: 'label.password', defaultMessage: 'Password' },
+ role: { id: 'label.role', defaultMessage: 'Role' },
+ user: { id: 'label.user', defaultMessage: 'User' },
+ admin: { id: 'label.admin', defaultMessage: 'Admin' },
+ confirm: { id: 'label.confirm', defaultMessage: 'Confirm' },
+ details: { id: 'label.details', defaultMessage: 'Details' },
+ websites: { id: 'label.websites', defaultMessage: 'Websites' },
+ created: { id: 'label.created', defaultMessage: 'Created' },
+ edit: { id: 'label.edit', defaultMessage: 'Edit' },
+ name: { id: 'label.name', defaultMessage: 'Name' },
+ members: { id: 'label.members', defaultMessage: 'Members' },
+ accessCode: { id: 'label.access-code', defaultMessage: 'Access code' },
+ teamId: { id: 'label.team-id', defaultMessage: 'Team ID' },
+ team: { id: 'label.team', defaultMessage: 'Team' },
+ regenerate: { id: 'label.regenerate', defaultMessage: 'Regenerate' },
+ remove: { id: 'label.remove', defaultMessage: 'Remove' },
+ createTeam: { id: 'label.create-team', defaultMessage: 'Create team' },
+ settings: { id: 'label.settings', defaultMessage: 'Settings' },
+ teamOwner: { id: 'label.team-owner', defaultMessage: 'Team owner' },
+ teamMember: { id: 'label.team-member', defaultMessage: 'Team member' },
+ teamGuest: { id: 'label.team-guest', defaultMessage: 'Team guest' },
+ enableShareUrl: { id: 'label.enable-share-url', defaultMessage: 'Enable share URL' },
+ data: { id: 'label.data', defaultMessage: 'Data' },
+ trackingCode: { id: 'label.tracking-code', defaultMessage: 'Tracking code' },
+ shareUrl: { id: 'label.share-url', defaultMessage: 'Share URL' },
+ actions: { id: 'label.actions', defaultMessage: 'Actions' },
+ view: { id: 'label.view', defaultMessage: 'View' },
+ domain: { id: 'label.domain', defaultMessage: 'Domain' },
+ websiteId: { id: 'label.website-id', defaultMessage: 'Website ID' },
+ resetWebsite: { id: 'label.reset-website', defaultMessage: 'Reset website' },
+ deleteWebsite: { id: 'label.delete-website', defaultMessage: 'Delete website' },
+ reset: { id: 'label.reset', defaultMessage: 'Reset' },
+ addWebsite: { id: 'label.add-website', defaultMessage: 'Add website' },
+ changePassword: { id: 'label.change-password', defaultMessage: 'Change password' },
+ currentPassword: { id: 'label.current-password', defaultMessage: 'Current password' },
+ newPassword: { id: 'label.new-password', defaultMessage: 'New password' },
+ confirmPassword: { id: 'label.confirm-password', defaultMessage: 'Confirm password' },
+ timezone: { id: 'label.timezone', defaultMessage: 'Timezone' },
+ dateRange: { id: 'label.default-date-range', defaultMessage: 'Default date range' },
+ language: { id: 'label.language', defaultMessage: 'Language' },
+ theme: { id: 'label.theme', defaultMessage: 'Theme' },
+ profile: { id: 'label.profile', defaultMessage: 'Profile' },
});
export const messages = defineMessages({
error: { id: 'message.error', defaultMessage: 'Something went wrong.' },
- saved: { id: 'message.saved-successfully', defaultMessage: 'Saved successfully.' },
+ saved: { id: 'message.saved', defaultMessage: 'Saved successfully.' },
+ noUsers: { id: 'message.no-users', defaultMessage: 'There are no users.' },
+ userDeleted: { id: 'message.user-deleted', defaultMessage: 'User deleted successfully.' },
+ noData: { id: 'message.no-data', defaultMessage: 'No data available.' },
+ deleteUserWarning: {
+ id: 'message.delete-user-warning',
+ defaultMessage: 'Are you sure you want to delete {username}?',
+ },
+ minPasswordLength: {
+ id: 'message.min-password-length',
+ defaultMessage: 'Minimum length of 8 characters',
+ },
+ noTeams: {
+ id: 'message.no-teams',
+ defaultMessage: 'You have no created any teams.',
+ },
+ shareUrl: {
+ id: 'message.share-url',
+ defaultMessage: 'Your website stats are publically available at the following URL:',
+ },
+ trackingCode: {
+ id: 'message.tracking-code',
+ defaultMessage:
+ 'To track stats for this website, place the following code in the
section of your HTML.',
+ },
+ deleteWebsite: {
+ id: 'message.delete-website',
+ defaultMessage: 'To delete this website, type {confirmation} in the box below to confirm.',
+ },
+ resetWebsite: {
+ id: 'message.reset-website',
+ defaultMessage: 'To reset this website, type {confirmation} in the box below to confirm.',
+ },
+ invalidDomain: {
+ id: 'message.invalid-domain',
+ defaultMessage: 'Invalid domain. Do not include http/https.',
+ },
+ resetWebsiteWarning: {
+ id: 'message.reset-website-warning',
+ defaultMessage:
+ 'All statistics for this website will be deleted, but your settings will remain intact.',
+ },
+ deleteWebsiteWarning: {
+ id: 'message.delete-website-warning',
+ defaultMessage: 'All website data will be deleted.',
+ },
+ noWebsites: {
+ id: 'messages.no-websites',
+ defaultMessage: 'You do not have any websites configured.',
+ },
+ noMatchPassword: { id: 'message.no-match-password', defaultMessage: 'Passwords do not match.' },
});
export const devices = defineMessages({
diff --git a/components/pages/settings/profile/DateRangeSetting.js b/components/pages/settings/profile/DateRangeSetting.js
index 4f129045..a5139cb3 100644
--- a/components/pages/settings/profile/DateRangeSetting.js
+++ b/components/pages/settings/profile/DateRangeSetting.js
@@ -1,10 +1,12 @@
-import { FormattedMessage } from 'react-intl';
+import { useIntl } from 'react-intl';
import DateFilter from 'components/common/DateFilter';
import { Button, Flexbox } from 'react-basics';
import useDateRange from 'hooks/useDateRange';
import { DEFAULT_DATE_RANGE } from 'lib/constants';
+import { labels } from 'components/messages';
export default function DateRangeSetting() {
+ const { formatMessage } = useIntl();
const [dateRange, setDateRange] = useDateRange();
const { startDate, endDate, value } = dateRange;
@@ -13,9 +15,7 @@ export default function DateRangeSetting() {
return (
-
+
);
}
diff --git a/components/pages/settings/profile/LanguageSetting.js b/components/pages/settings/profile/LanguageSetting.js
index 70857a20..d5aa064f 100644
--- a/components/pages/settings/profile/LanguageSetting.js
+++ b/components/pages/settings/profile/LanguageSetting.js
@@ -1,12 +1,9 @@
-import { useIntl, defineMessages } from 'react-intl';
+import { useIntl } from 'react-intl';
import { Button, Dropdown, Item, Flexbox } from 'react-basics';
import useLocale from 'hooks/useLocale';
import { DEFAULT_LOCALE } from 'lib/constants';
import { languages } from 'lib/lang';
-
-const messages = defineMessages({
- reset: { id: 'label.reset', defaultMessage: 'Reset' },
-});
+import { labels } from 'components/messages';
export default function LanguageSetting() {
const { formatMessage } = useIntl();
@@ -28,7 +25,7 @@ export default function LanguageSetting() {
>
{item => - {languages[item].label}
}
-
+
);
}
diff --git a/components/pages/settings/profile/PasswordChangeButton.js b/components/pages/settings/profile/PasswordChangeButton.js
index 1cac0083..26cff377 100644
--- a/components/pages/settings/profile/PasswordChangeButton.js
+++ b/components/pages/settings/profile/PasswordChangeButton.js
@@ -1,46 +1,29 @@
-import { useState } from 'react';
-import { defineMessages, useIntl } from 'react-intl';
-import { Button, Icon, Text, Modal, useToast } from 'react-basics';
+import { useIntl } from 'react-intl';
+import { Button, Icon, Text, useToast, ModalTrigger } from 'react-basics';
import PasswordEditForm from 'components/pages/settings/profile/PasswordEditForm';
import { Lock } from 'components/icons';
-
-const messages = defineMessages({
- changePassword: { id: 'label.change-password', defaultMessage: 'Change password' },
- saved: { id: 'message.saved-successfully', defaultMessage: 'Saved successfully.' },
-});
+import { labels, messages } from 'components/messages';
export default function PasswordChangeButton() {
const { formatMessage } = useIntl();
- const [edit, setEdit] = useState(false);
const { toast, showToast } = useToast();
const handleSave = () => {
showToast({ message: formatMessage(messages.saved), variant: 'success' });
- setEdit(false);
- };
-
- const handleEdit = () => {
- setEdit(true);
- };
-
- const handleClose = () => {
- setEdit(false);
};
return (
<>
{toast}
-
- {edit && (
-
- {() => }
-
- )}
+
+
+ {close => }
+
>
);
}
diff --git a/components/pages/settings/profile/PasswordEditForm.js b/components/pages/settings/profile/PasswordEditForm.js
index 6b446d75..c11c96f4 100644
--- a/components/pages/settings/profile/PasswordEditForm.js
+++ b/components/pages/settings/profile/PasswordEditForm.js
@@ -1,75 +1,65 @@
import { useRef } from 'react';
import { Form, FormRow, FormInput, FormButtons, PasswordField, Button } from 'react-basics';
+import { useIntl } from 'react-intl';
import useApi from 'hooks/useApi';
-import useUser from 'hooks/useUser';
+import { labels, messages } from 'components/messages';
-export default function PasswordEditForm({ userId, onSave, onClose }) {
- const user = useUser();
- const isCurrentUser = !userId || user?.id === userId;
- const url = isCurrentUser ? `/users/${user?.id}/password` : `/users/${user?.id}`;
+export default function PasswordEditForm({ onSave, onClose }) {
+ const { formatMessage } = useIntl();
const { post, useMutation } = useApi();
- const { mutate, error, isLoading } = useMutation(data => post(url, data));
+ const { mutate, error, isLoading } = useMutation(data => post('/me/password', data));
const ref = useRef(null);
const handleSubmit = async data => {
- const payload = isCurrentUser
- ? data
- : {
- password: data.newPassword,
- };
-
- mutate(payload, {
+ mutate(data, {
onSuccess: async () => {
onSave();
- ref.current.reset();
},
});
};
const samePassword = value => {
if (value !== ref?.current?.getValues('newPassword')) {
- return "Passwords don't match";
+ return formatMessage(messages.noMatchPassword);
}
return true;
};
return (
);
diff --git a/components/pages/settings/profile/ProfileDetails.js b/components/pages/settings/profile/ProfileDetails.js
index 5413fbea..346ca17a 100644
--- a/components/pages/settings/profile/ProfileDetails.js
+++ b/components/pages/settings/profile/ProfileDetails.js
@@ -1,19 +1,11 @@
import { Form, FormRow } from 'react-basics';
-import { useIntl, defineMessages } from 'react-intl';
+import { useIntl } from 'react-intl';
import TimezoneSetting from 'components/pages/settings/profile/TimezoneSetting';
import DateRangeSetting from 'components/pages/settings/profile/DateRangeSetting';
import LanguageSetting from 'components/pages/settings/profile/LanguageSetting';
import ThemeSetting from 'components/buttons/ThemeSetting';
import useUser from 'hooks/useUser';
-
-const messages = defineMessages({
- username: { id: 'label.username', defaultMessage: 'Username' },
- role: { id: 'label.role', defaultMessage: 'Role' },
- timezone: { id: 'label.timezone', defaultMessage: 'Timezone' },
- dateRange: { id: 'label.default-date-range', defaultMessage: 'Default date range' },
- language: { id: 'label.language', defaultMessage: 'Language' },
- theme: { id: 'label.theme', defaultMessage: 'Theme' },
-});
+import { labels } from 'components/messages';
export default function ProfileDetails() {
const { user } = useUser();
@@ -27,18 +19,18 @@ export default function ProfileDetails() {
return (
diff --git a/components/pages/settings/profile/ProfileDetails.module.css b/components/pages/settings/profile/ProfileDetails.module.css
deleted file mode 100644
index fdd8252f..00000000
--- a/components/pages/settings/profile/ProfileDetails.module.css
+++ /dev/null
@@ -1,3 +0,0 @@
-.list dd {
- display: flex;
-}
diff --git a/components/pages/settings/profile/ProfileSettings.js b/components/pages/settings/profile/ProfileSettings.js
index 700ae640..d4d35db7 100644
--- a/components/pages/settings/profile/ProfileSettings.js
+++ b/components/pages/settings/profile/ProfileSettings.js
@@ -1,13 +1,10 @@
import { Breadcrumbs, Item } from 'react-basics';
-import { defineMessages, useIntl } from 'react-intl';
+import { useIntl } from 'react-intl';
import Page from 'components/layout/Page';
import PageHeader from 'components/layout/PageHeader';
import ProfileDetails from './ProfileDetails';
import PasswordChangeButton from './PasswordChangeButton';
-
-const messages = defineMessages({
- profile: { id: 'label.profile', defaultMessage: 'Profile' },
-});
+import { labels } from 'components/messages';
export default function ProfileSettings() {
const { formatMessage } = useIntl();
@@ -16,7 +13,7 @@ export default function ProfileSettings() {
- - {formatMessage(messages.profile)}
+ - {formatMessage(labels.profile)}
diff --git a/components/pages/settings/profile/TimezoneSetting.js b/components/pages/settings/profile/TimezoneSetting.js
index 6e8a42a0..11cc450e 100644
--- a/components/pages/settings/profile/TimezoneSetting.js
+++ b/components/pages/settings/profile/TimezoneSetting.js
@@ -1,12 +1,9 @@
import { Dropdown, Item, Button, Flexbox } from 'react-basics';
-import { useIntl, defineMessages } from 'react-intl';
+import { useIntl } from 'react-intl';
import { listTimeZones } from 'timezone-support';
import useTimezone from 'hooks/useTimezone';
import { getTimezone } from 'lib/date';
-
-const messages = defineMessages({
- reset: { id: 'label.reset', defaultMessage: 'Reset' },
-});
+import { labels } from 'components/messages';
export default function TimezoneSetting() {
const { formatMessage } = useIntl();
@@ -25,7 +22,7 @@ export default function TimezoneSetting() {
>
{item => - {item}
}
-
+
);
}
diff --git a/components/pages/settings/teams/TeamAddForm.js b/components/pages/settings/teams/TeamAddForm.js
index b7d2e599..7ee3b985 100644
--- a/components/pages/settings/teams/TeamAddForm.js
+++ b/components/pages/settings/teams/TeamAddForm.js
@@ -1,8 +1,19 @@
import { useRef } from 'react';
-import { Form, FormRow, FormInput, FormButtons, TextField, Button } from 'react-basics';
+import { useIntl } from 'react-intl';
+import {
+ Form,
+ FormRow,
+ FormInput,
+ FormButtons,
+ TextField,
+ Button,
+ SubmitButton,
+} from 'react-basics';
import useApi from 'hooks/useApi';
+import { labels } from 'components/messages';
export default function TeamAddForm({ onSave, onClose }) {
+ const { formatMessage } = useIntl();
const { post, useMutation } = useApi();
const { mutate, error, isLoading } = useMutation(data => post('/teams', data));
const ref = useRef(null);
@@ -17,17 +28,17 @@ export default function TeamAddForm({ onSave, onClose }) {
return (
diff --git a/components/pages/settings/teams/TeamEditForm.js b/components/pages/settings/teams/TeamEditForm.js
index 03ca7215..5b19a0ef 100644
--- a/components/pages/settings/teams/TeamEditForm.js
+++ b/components/pages/settings/teams/TeamEditForm.js
@@ -8,13 +8,16 @@ import {
Button,
Flexbox,
} from 'react-basics';
+import { useIntl } from 'react-intl';
import { getRandomChars } from 'next-basics';
import { useRef, useState } from 'react';
import useApi from 'hooks/useApi';
+import { labels } from 'components/messages';
const generateId = () => getRandomChars(16);
export default function TeamEditForm({ teamId, data, onSave }) {
+ const { formatMessage } = useIntl();
const { post, useMutation } = useApi();
const { mutate, error } = useMutation(data => post(`/teams/${teamId}`, data));
const ref = useRef(null);
@@ -40,22 +43,22 @@ export default function TeamEditForm({ teamId, data, onSave }) {
return (
);
diff --git a/components/pages/settings/teams/TeamMembersTable.js b/components/pages/settings/teams/TeamMembersTable.js
index a4b92fb8..a90d2d6d 100644
--- a/components/pages/settings/teams/TeamMembersTable.js
+++ b/components/pages/settings/teams/TeamMembersTable.js
@@ -7,18 +7,27 @@ import {
TableColumn,
Button,
Icon,
+ Icons,
+ Flexbox,
+ Text,
} from 'react-basics';
-import styles from './TeamsTable.module.css';
+import { ROLES } from 'lib/constants';
+import { labels } from 'components/messages';
+import { useIntl } from 'react-intl';
-const columns = [
- { name: 'username', label: 'Username', style: { flex: 4 } },
- { name: 'role', label: 'Role' },
- { name: 'action', label: '' },
-];
+const { Close } = Icons;
export default function TeamMembersTable({ data = [] }) {
+ const { formatMessage } = useIntl();
+
+ const columns = [
+ { name: 'username', label: formatMessage(labels.username), style: { flex: 4 } },
+ { name: 'role', label: formatMessage(labels.role) },
+ { name: 'action', label: '' },
+ ];
+
return (
-
+
{(column, index) => {
return (
@@ -30,25 +39,35 @@ export default function TeamMembersTable({ data = [] }) {
{(row, keys, rowIndex) => {
- row.action = (
-
-
-
- );
+ const rowData = {
+ username: row?.user?.username,
+ role: formatMessage(
+ labels[Object.keys(ROLES).find(key => ROLES[key] === row.role) || labels.unknown],
+ ),
+ action: (
+
+
+
+ ),
+ };
return (
-
+
{(data, key, colIndex) => {
return (
-
- {data[key] ?? data?.user?.[key]}
+
+
+ {data[key]}
+
);
}}
diff --git a/components/pages/settings/teams/TeamDetails.js b/components/pages/settings/teams/TeamSettings.js
similarity index 71%
rename from components/pages/settings/teams/TeamDetails.js
rename to components/pages/settings/teams/TeamSettings.js
index c00b714a..3bcfedfc 100644
--- a/components/pages/settings/teams/TeamDetails.js
+++ b/components/pages/settings/teams/TeamSettings.js
@@ -1,4 +1,5 @@
import { useEffect, useState } from 'react';
+import { useIntl } from 'react-intl';
import { Breadcrumbs, Item, Tabs, useToast } from 'react-basics';
import useApi from 'hooks/useApi';
import Link from 'next/link';
@@ -6,8 +7,11 @@ import Page from 'components/layout/Page';
import TeamEditForm from 'components/pages/settings/teams/TeamEditForm';
import PageHeader from 'components/layout/PageHeader';
import TeamMembers from 'components/pages/settings/teams/TeamMembers';
+import { labels, messages } from 'components/messages';
+import TeamWebsites from './TeamWebsites';
-export default function TeamDetails({ teamId }) {
+export default function TeamSettings({ teamId }) {
+ const { formatMessage } = useIntl();
const [values, setValues] = useState(null);
const [tab, setTab] = useState('details');
const { get, useQuery } = useApi();
@@ -23,7 +27,7 @@ export default function TeamDetails({ teamId }) {
);
const handleSave = data => {
- showToast({ message: 'Saved successfully.', variant: 'success' });
+ showToast({ message: formatMessage(messages.saved), variant: 'success' });
setValues(state => ({ ...state, ...data }));
};
@@ -44,13 +48,14 @@ export default function TeamDetails({ teamId }) {
- {values?.name}
-
- - Details
- - Members
- - Websites
+
+ - {formatMessage(labels.details)}
+ - {formatMessage(labels.members)}
+ - {formatMessage(labels.websites)}
{tab === 'details' && }
{tab === 'members' && }
+ {tab === 'websites' && }
);
}
diff --git a/components/pages/settings/teams/TeamWebsites.js b/components/pages/settings/teams/TeamWebsites.js
new file mode 100644
index 00000000..f611fbbb
--- /dev/null
+++ b/components/pages/settings/teams/TeamWebsites.js
@@ -0,0 +1,25 @@
+import { Loading } from 'react-basics';
+import { useIntl } from 'react-intl';
+import useApi from 'hooks/useApi';
+import WebsitesTable from 'components/pages/settings/websites/WebsitesTable';
+import { messages } from 'components/messages';
+
+export default function TeamWebsites({ teamId }) {
+ const { formatMessage } = useIntl();
+ const { get, useQuery } = useApi();
+ const { data, isLoading } = useQuery(['team/websites', teamId], () =>
+ get(`/teams/${teamId}/websites`),
+ );
+ const hasData = data && data.length !== 0;
+
+ if (isLoading) {
+ return ;
+ }
+
+ return (
+
+ {hasData && }
+ {!hasData && formatMessage(messages.noData)}
+
+ );
+}
diff --git a/components/pages/settings/teams/TeamsList.js b/components/pages/settings/teams/TeamsList.js
index b1a30f80..fab1bc38 100644
--- a/components/pages/settings/teams/TeamsList.js
+++ b/components/pages/settings/teams/TeamsList.js
@@ -1,13 +1,18 @@
import { useState } from 'react';
-import { Button, Icon, Modal, useToast } from 'react-basics';
+import { Button, Icon, Modal, useToast, Icons, Text } from 'react-basics';
+import { useIntl } from 'react-intl';
import useApi from 'hooks/useApi';
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
import TeamAddForm from 'components/pages/settings/teams/TeamAddForm';
import PageHeader from 'components/layout/PageHeader';
import TeamsTable from 'components/pages/settings/teams/TeamsTable';
import Page from 'components/layout/Page';
+import { labels, messages } from 'components/messages';
+
+const { Plus } = Icons;
export default function TeamsList() {
+ const { formatMessage } = useIntl();
const [edit, setEdit] = useState(false);
const [update, setUpdate] = useState(0);
const { get, useQuery } = useApi();
@@ -22,7 +27,7 @@ export default function TeamsList() {
const handleSave = () => {
setEdit(false);
setUpdate(state => state + 1);
- showToast({ message: 'Team saved.', variant: 'success' });
+ showToast({ message: formatMessage(messages.saved), variant: 'success' });
};
const handleClose = () => {
@@ -32,21 +37,27 @@ export default function TeamsList() {
return (
{toast}
-
-