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 (
- {isCurrentUser && ( - - - - - - )} - + + + + + + - + - + - + - + ); 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 (
- {username} - {role} - + {username} + {role} + - + - + - + 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 (
- + - + + {formatMessage(labels.save)} + 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 (
- + - + - + - + - Save + {formatMessage(labels.save)} ); 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} - - {hasData && } {!hasData && ( - + )} {edit && ( - + {close => } )} diff --git a/components/pages/settings/teams/TeamsTable.js b/components/pages/settings/teams/TeamsTable.js index 9082187a..ae4c0116 100644 --- a/components/pages/settings/teams/TeamsTable.js +++ b/components/pages/settings/teams/TeamsTable.js @@ -8,17 +8,25 @@ import { TableColumn, Button, Icon, + Flexbox, + Icons, + Text, } from 'react-basics'; -import styles from './TeamsTable.module.css'; +import { useIntl } from 'react-intl'; +import { labels } from 'components/messages'; -const columns = [ - { name: 'name', label: 'Name', style: { flex: 2 } }, - { name: 'action', label: ' ' }, -]; +const { ArrowRight } = Icons; export default function TeamsTable({ data = [] }) { + const { formatMessage } = useIntl(); + + const columns = [ + { name: 'name', label: formatMessage(labels.name), style: { flex: 2 } }, + { name: 'action', label: ' ' }, + ]; + return ( -
+
{(column, index) => { return ( @@ -33,28 +41,28 @@ export default function TeamsTable({ data = [] }) { const { id } = row; row.action = ( - + ); return ( {(data, key, colIndex) => { return ( - - {data[key]} + + + {data[key]} + ); }} diff --git a/components/pages/settings/teams/TeamsTable.module.css b/components/pages/settings/teams/TeamsTable.module.css deleted file mode 100644 index b0579f34..00000000 --- a/components/pages/settings/teams/TeamsTable.module.css +++ /dev/null @@ -1,18 +0,0 @@ -.table th, -.table td { - flex: 2; -} - -.cell { - display: flex; - align-items: center; -} - -.cell:last-child { - justify-content: flex-end; -} - -.actions { - display: flex; - gap: 12px; -} diff --git a/components/pages/settings/users/UserAddButton.js b/components/pages/settings/users/UserAddButton.js index 88601065..7f1ba624 100644 --- a/components/pages/settings/users/UserAddButton.js +++ b/components/pages/settings/users/UserAddButton.js @@ -1,16 +1,12 @@ import { useState } from 'react'; -import { defineMessages, useIntl } from 'react-intl'; +import { useIntl } from 'react-intl'; import { Button, Icon, Text, Modal, useToast, Icons } from 'react-basics'; import UserAddForm from './UserAddForm'; +import { labels, messages } from 'components/messages'; const { Plus } = Icons; -const messages = defineMessages({ - createUser: { id: 'label.create-user', defaultMessage: 'Create user' }, - saved: { id: 'message.saved-successfully', defaultMessage: 'Saved successfully.' }, -}); - -export default function UserAddButton() { +export default function UserAddButton({ onSave }) { const { formatMessage } = useIntl(); const [edit, setEdit] = useState(false); const { toast, showToast } = useToast(); @@ -18,6 +14,7 @@ export default function UserAddButton() { const handleSave = () => { showToast({ message: formatMessage(messages.saved), variant: 'success' }); setEdit(false); + onSave(); }; const handleAdd = () => { @@ -35,11 +32,11 @@ export default function UserAddButton() { - {formatMessage(messages.createUser)} + {formatMessage(labels.createUser)} {edit && ( - - {() => } + + )} diff --git a/components/pages/settings/users/UserAddForm.js b/components/pages/settings/users/UserAddForm.js index f7d9ee81..efa04b65 100644 --- a/components/pages/settings/users/UserAddForm.js +++ b/components/pages/settings/users/UserAddForm.js @@ -10,19 +10,11 @@ import { SubmitButton, Button, } from 'react-basics'; -import { useIntl, defineMessages } from 'react-intl'; +import { useIntl } from 'react-intl'; import useApi from 'hooks/useApi'; import { ROLES } from 'lib/constants'; import { labels } from 'components/messages'; -const messages = defineMessages({ - 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' }, -}); - export default function UserAddForm({ onSave, onClose }) { const { post, useMutation } = useApi(); const { mutate, error, isLoading } = useMutation(data => post(`/users`, data)); @@ -38,30 +30,30 @@ export default function UserAddForm({ onSave, onClose }) { const renderValue = value => { if (value === ROLES.user) { - return formatMessage(messages.user); + return formatMessage(labels.user); } if (value === ROLES.admin) { - return formatMessage(messages.admin); + return formatMessage(labels.admin); } }; return (
- + - + - + - - {formatMessage(messages.user)} - {formatMessage(messages.admin)} + + {formatMessage(labels.user)} + {formatMessage(labels.admin)} diff --git a/components/pages/settings/users/UserDeleteForm.js b/components/pages/settings/users/UserDeleteForm.js index e7ca37e9..a464ae4f 100644 --- a/components/pages/settings/users/UserDeleteForm.js +++ b/components/pages/settings/users/UserDeleteForm.js @@ -1,36 +1,29 @@ import { useMutation } from '@tanstack/react-query'; import useApi from 'hooks/useApi'; import { Button, Form, FormButtons, SubmitButton } from 'react-basics'; -import { defineMessages, useIntl } from 'react-intl'; -import { labels } from 'components/messages'; +import { useIntl } from 'react-intl'; +import { labels, messages } from 'components/messages'; -const messages = defineMessages({ - confirm: { id: 'label.confirm', defaultMessage: 'Confirm' }, - warning: { - id: 'message.confirm-delete-user', - defaultMessage: 'Are you sure you want to delete this user?', - }, -}); - -export default function UserDeleteForm({ userId, onSave, onClose }) { +export default function UserDeleteForm({ userId, username, onSave, onClose }) { const { formatMessage } = useIntl(); const { del } = useApi(); - const { mutate, error, isLoading } = useMutation(data => del(`/users/${userId}`, data)); + const { mutate, error, isLoading } = useMutation(() => del(`/users/${userId}`)); const handleSubmit = async data => { mutate(data, { onSuccess: async () => { onSave(); + onClose(); }, }); }; return ( -

{formatMessage(messages.warning)}

+

{formatMessage(messages.deleteUserWarning, { username })}

- {formatMessage(labels.save)} + {formatMessage(labels.delete)}
+
{(column, index) => { return ( - + {column.label} ); @@ -35,33 +47,57 @@ export default function UsersTable({ data = [] }) { {(row, keys, rowIndex) => { - row.created = formatDistance(new Date(row.createdAt), new Date(), { - addSuffix: true, - }); - - row.action = ( -
- - - -
- ); + const rowData = { + ...row, + created: formatDistance(new Date(row.createdAt), new Date(), { + addSuffix: true, + }), + role: formatMessage( + labels[Object.keys(ROLES).find(key => ROLES[key] === row.role) || labels.unknown], + ), + action: ( + <> + + + + + + {close => ( + + )} + + + ), + }; return ( - + {(data, key, colIndex) => { return ( - - {data[key]} + + + {data[key]} + ); }} diff --git a/components/pages/settings/users/UsersTable.module.css b/components/pages/settings/users/UsersTable.module.css deleted file mode 100644 index a105bede..00000000 --- a/components/pages/settings/users/UsersTable.module.css +++ /dev/null @@ -1,26 +0,0 @@ -.table th, -.table td { - flex: 2; -} - -.cell { - display: flex; - align-items: center; -} - -.input { - flex: 2; -} - -.cell:last-child { - justify-content: flex-end; -} - -.actions { - display: flex; - gap: 12px; -} - -.empty { - min-height: 300px; -} diff --git a/components/pages/settings/websites/ShareUrl.js b/components/pages/settings/websites/ShareUrl.js index 88997bae..b3a5e82d 100644 --- a/components/pages/settings/websites/ShareUrl.js +++ b/components/pages/settings/websites/ShareUrl.js @@ -8,13 +8,16 @@ import { Button, Toggle, } from 'react-basics'; +import { useIntl } from 'react-intl'; import { useEffect, useMemo, useRef, useState } from 'react'; import { getRandomChars } from 'next-basics'; import useApi from 'hooks/useApi'; +import { labels, messages } from 'components/messages'; const generateId = () => getRandomChars(16); export default function ShareUrl({ websiteId, data, onSave }) { + const { formatMessage } = useIntl(); const { name, shareId } = data; const [id, setId] = useState(shareId); const { post, useMutation } = useApi(); @@ -62,26 +65,24 @@ export default function ShareUrl({ websiteId, data, onSave }) { }, [id, shareId]); return ( - - - - Enable share URL - - + <> + + {formatMessage(labels.enableShareUrl)} + {id && ( - <> + -

Your website stats are publically available at the following URL:

+

{formatMessage(messages.shareUrl)}

- +
- Save + {formatMessage(labels.save)} - + )} - + ); } diff --git a/components/pages/settings/websites/TrackingCode.js b/components/pages/settings/websites/TrackingCode.js index 99fb51e1..2822cbdf 100644 --- a/components/pages/settings/websites/TrackingCode.js +++ b/components/pages/settings/websites/TrackingCode.js @@ -1,7 +1,10 @@ import { TextArea } from 'react-basics'; import { TRACKER_SCRIPT_URL } from 'lib/constants'; +import { messages } from 'components/messages'; +import { useIntl } from 'react-intl'; export default function TrackingCode({ websiteId }) { + const { formatMessage } = useIntl(); const url = TRACKER_SCRIPT_URL.startsWith('http') ? TRACKER_SCRIPT_URL : `${location.origin}${TRACKER_SCRIPT_URL}`; @@ -10,10 +13,7 @@ export default function TrackingCode({ websiteId }) { return ( <> -

- To track stats for this website, place the following code in the <head>{' '} - section of your HTML. -

+

{formatMessage(messages.trackingCode)}