From 656df4f846b862de5e0dc95a1802c0b7cec03cf2 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Wed, 1 Feb 2023 18:39:54 -0800 Subject: [PATCH] Update teams features. --- components/common/ErrorMessage.js | 9 ++-- components/icons.js | 2 + components/messages.js | 20 ++++++++- components/metrics/ActiveUsers.js | 33 ++++++-------- components/metrics/ChartTooltip.js | 5 ++- components/metrics/Legend.js | 5 ++- components/metrics/WebsiteChart.js | 23 +++++++++- components/metrics/WebsiteHeader.js | 24 +++++------ components/metrics/WebsiteHeader.module.css | 12 ++++-- .../pages/settings/teams/JoinTeamForm.js | 43 +++++++++++++++++++ .../pages/settings/teams/TeamMembers.js | 2 +- .../pages/settings/teams/TeamSettings.js | 18 ++++---- .../pages/settings/teams/TeamWebsites.js | 2 +- components/pages/settings/teams/TeamsList.js | 31 ++++++++++++- components/pages/settings/teams/TeamsTable.js | 36 +++++++++------- .../pages/settings/users/UserSettings.js | 18 ++++---- .../pages/settings/users/UserWebsites.js | 2 +- .../settings/websites/WebsiteSettings.js | 17 +++++--- components/pages/websites/WebsiteChartList.js | 18 ++++---- pages/api/auth/login.ts | 6 +-- pages/api/teams/index.ts | 7 +-- pages/api/teams/join.ts | 34 +++++++++++++++ queries/admin/user.ts | 24 ++++++----- 23 files changed, 278 insertions(+), 113 deletions(-) create mode 100644 components/pages/settings/teams/JoinTeamForm.js create mode 100644 pages/api/teams/join.ts diff --git a/components/common/ErrorMessage.js b/components/common/ErrorMessage.js index ccd96c80..926e64ac 100644 --- a/components/common/ErrorMessage.js +++ b/components/common/ErrorMessage.js @@ -1,14 +1,17 @@ -import { FormattedMessage } from 'react-intl'; -import { Icon, Icons } from 'react-basics'; +import { useIntl } from 'react-intl'; +import { Icon, Icons, Text } from 'react-basics'; +import { messages } from 'components/messages'; import styles from './ErrorMessage.module.css'; export default function ErrorMessage() { + const { formatMessage } = useIntl(); + return (
- + {formatMessage(messages.error)}
); } diff --git a/components/icons.js b/components/icons.js index a8d6cd79..4868edc6 100644 --- a/components/icons.js +++ b/components/icons.js @@ -1,4 +1,5 @@ import { Icons } from 'react-basics'; +import AddUser from 'assets/add-user.svg'; import Bolt from 'assets/bolt.svg'; import Calendar from 'assets/calendar.svg'; import Clock from 'assets/clock.svg'; @@ -15,6 +16,7 @@ import Users from 'assets/users.svg'; const icons = { ...Icons, + AddUser, Bolt, Calendar, Clock, diff --git a/components/messages.js b/components/messages.js index d4299597..c9ce76b9 100644 --- a/components/messages.js +++ b/components/messages.js @@ -26,8 +26,11 @@ export const labels = defineMessages({ team: { id: 'label.team', defaultMessage: 'Team' }, regenerate: { id: 'label.regenerate', defaultMessage: 'Regenerate' }, remove: { id: 'label.remove', defaultMessage: 'Remove' }, + join: { id: 'label.join', defaultMessage: 'Join' }, createTeam: { id: 'label.create-team', defaultMessage: 'Create team' }, + joinTeam: { id: 'label.join-team', defaultMessage: 'Join team' }, settings: { id: 'label.settings', defaultMessage: 'Settings' }, + owner: { id: 'label.owner', defaultMessage: 'Owner' }, teamOwner: { id: 'label.team-owner', defaultMessage: 'Team owner' }, teamMember: { id: 'label.team-member', defaultMessage: 'Team member' }, teamGuest: { id: 'label.team-guest', defaultMessage: 'Team guest' }, @@ -61,6 +64,7 @@ export const labels = defineMessages({ logout: { id: 'label.logout', defaultMessage: 'Logout' }, singleDay: { id: 'label.single-day', defaultMessage: 'Single day' }, dateRange: { id: 'label.date-range', defaultMessage: 'Date range' }, + viewDetails: { id: 'label.view-details', defaultMessage: 'View details' }, }); export const messages = defineMessages({ @@ -79,7 +83,7 @@ export const messages = defineMessages({ }, noTeams: { id: 'message.no-teams', - defaultMessage: 'You have no created any teams.', + defaultMessage: 'You have not created any teams.', }, shareUrl: { id: 'message.share-url', @@ -120,6 +124,14 @@ export const messages = defineMessages({ id: 'message.go-to-settings', defaultMessage: 'Go to settings', }, + activeUsers: { + id: 'message.active-users', + defaultMessage: '{x} current {x, plural, one {visitor} other {visitors}}', + }, + teamNotFound: { + id: 'message.team-not-found', + defaultMessage: 'Team not found.', + }, }); export const devices = defineMessages({ @@ -129,6 +141,12 @@ export const devices = defineMessages({ mobile: { id: 'metrics.device.mobile', defaultMessage: 'Mobile' }, }); +export function getMessage(id, formatMessage) { + const message = Object.values(messages).find(value => value.id === id); + + return message ? formatMessage(message) : id; +} + export function getDeviceMessage(device) { return devices[device] || labels.unknown; } diff --git a/components/metrics/ActiveUsers.js b/components/metrics/ActiveUsers.js index 83895b5a..664e23a9 100644 --- a/components/metrics/ActiveUsers.js +++ b/components/metrics/ActiveUsers.js @@ -1,16 +1,20 @@ import { useMemo } from 'react'; import { StatusLight } from 'react-basics'; -import { FormattedMessage } from 'react-intl'; -import classNames from 'classnames'; +import { useIntl } from 'react-intl'; import useApi from 'hooks/useApi'; +import { messages } from 'components/messages'; import styles from './ActiveUsers.module.css'; -export default function ActiveUsers({ websiteId, className, value, refetchInterval = 60000 }) { - const url = websiteId ? `/websites/${websiteId}/active` : null; +export default function ActiveUsers({ websiteId, value, refetchInterval = 60000 }) { + const { formatMessage } = useIntl(); const { get, useQuery } = useApi(); - const { data } = useQuery(['websites:active', websiteId], () => get(url), { - refetchInterval, - }); + const { data } = useQuery( + ['websites:active', websiteId], + () => get(`/websites/${websiteId}/active`), + { + refetchInterval, + }, + ); const count = useMemo(() => { if (websiteId) { @@ -25,17 +29,8 @@ export default function ActiveUsers({ websiteId, className, value, refetchInterv } return ( -
- -
-
- -
-
-
+ +
{formatMessage(messages.activeUsers, { x: count })}
+
); } diff --git a/components/metrics/ChartTooltip.js b/components/metrics/ChartTooltip.js index 503b44e1..c409f462 100644 --- a/components/metrics/ChartTooltip.js +++ b/components/metrics/ChartTooltip.js @@ -15,8 +15,9 @@ export default function ChartTooltip({ chartId, tooltip }) {
{title}
- - {value} {label} + + {value} {label} +
diff --git a/components/metrics/Legend.js b/components/metrics/Legend.js index f10b29bc..32267970 100644 --- a/components/metrics/Legend.js +++ b/components/metrics/Legend.js @@ -34,8 +34,9 @@ export default function Legend({ chart }) { className={classNames(styles.label, { [styles.hidden]: hidden })} onClick={() => handleClick(datasetIndex)} > - - {text} + + {text} + ); })} diff --git a/components/metrics/WebsiteChart.js b/components/metrics/WebsiteChart.js index daab7f72..c731cc6c 100644 --- a/components/metrics/WebsiteChart.js +++ b/components/metrics/WebsiteChart.js @@ -1,5 +1,7 @@ import { useMemo } from 'react'; -import { Row, Column, Loading } from 'react-basics'; +import { useIntl } from 'react-intl'; +import { Button, Icon, Text, Row, Column, Loading } from 'react-basics'; +import Link from 'next/link'; import PageviewsChart from './PageviewsChart'; import MetricsBar from './MetricsBar'; import WebsiteHeader from './WebsiteHeader'; @@ -12,6 +14,8 @@ import useDateRange from 'hooks/useDateRange'; import useTimezone from 'hooks/useTimezone'; import usePageQuery from 'hooks/usePageQuery'; import { getDateArray, getDateLength, getDateRangeValues } from 'lib/date'; +import Icons from 'components/icons'; +import { labels } from 'components/messages'; import styles from './WebsiteChart.module.css'; export default function WebsiteChart({ @@ -20,8 +24,10 @@ export default function WebsiteChart({ domain, stickyHeader = false, showChart = true, + showDetailsButton = false, onDataLoad = () => {}, }) { + const { formatMessage } = useIntl(); const [dateRange, setDateRange] = useDateRange(websiteId); const { startDate, endDate, unit, value, modified } = dateRange; const [timezone] = useTimezone(); @@ -82,7 +88,20 @@ export default function WebsiteChart({ return ( <> - + + {showDetailsButton && ( + + + + + + )} + - - - - {title} - - - - - - + + + + {title} + + + + {children} + + ); } diff --git a/components/metrics/WebsiteHeader.module.css b/components/metrics/WebsiteHeader.module.css index f7700fe8..e3147f53 100644 --- a/components/metrics/WebsiteHeader.module.css +++ b/components/metrics/WebsiteHeader.module.css @@ -1,15 +1,21 @@ +.header { + height: 100px; +} + .title { display: flex; flex-direction: row; align-items: center; gap: 10px; - font-size: var(--font-size-lg); + font-size: 24px; + font-weight: 700; overflow: hidden; } -.active { +.body { display: flex; flex-direction: row; align-items: center; - justify-content: center; + justify-content: flex-end; + gap: 30px; } diff --git a/components/pages/settings/teams/JoinTeamForm.js b/components/pages/settings/teams/JoinTeamForm.js new file mode 100644 index 00000000..387968fd --- /dev/null +++ b/components/pages/settings/teams/JoinTeamForm.js @@ -0,0 +1,43 @@ +import { useRef } from 'react'; +import { useIntl } from 'react-intl'; +import { + Form, + FormRow, + FormInput, + FormButtons, + TextField, + Button, + SubmitButton, +} from 'react-basics'; +import useApi from 'hooks/useApi'; +import { labels, getMessage } from 'components/messages'; + +export default function TeamJoinForm({ onSave, onClose }) { + const { formatMessage } = useIntl(); + const { post, useMutation } = useApi(); + const { mutate, error } = useMutation(data => post('/teams/join', data)); + const ref = useRef(null); + + const handleSubmit = async data => { + mutate(data, { + onSuccess: async () => { + onSave(); + onClose(); + }, + }); + }; + + return ( +
+ + + + + + + {formatMessage(labels.join)} + + +
+ ); +} diff --git a/components/pages/settings/teams/TeamMembers.js b/components/pages/settings/teams/TeamMembers.js index f4319955..c9901e34 100644 --- a/components/pages/settings/teams/TeamMembers.js +++ b/components/pages/settings/teams/TeamMembers.js @@ -4,7 +4,7 @@ import TeamMembersTable from 'components/pages/settings/teams/TeamMembersTable'; export default function TeamMembers({ teamId }) { const { get, useQuery } = useApi(); - const { data, isLoading } = useQuery(['team-members', teamId], () => + const { data, isLoading } = useQuery(['teams:users', teamId], () => get(`/teams/${teamId}/users`), ); diff --git a/components/pages/settings/teams/TeamSettings.js b/components/pages/settings/teams/TeamSettings.js index 3bcfedfc..6d3c70fa 100644 --- a/components/pages/settings/teams/TeamSettings.js +++ b/components/pages/settings/teams/TeamSettings.js @@ -40,14 +40,16 @@ export default function TeamSettings({ teamId }) { return ( {toast} - - - - Teams - - {values?.name} - - + + + Teams + + {values?.name} + + } + /> {formatMessage(labels.details)} {formatMessage(labels.members)} diff --git a/components/pages/settings/teams/TeamWebsites.js b/components/pages/settings/teams/TeamWebsites.js index f00ee057..1a04f490 100644 --- a/components/pages/settings/teams/TeamWebsites.js +++ b/components/pages/settings/teams/TeamWebsites.js @@ -7,7 +7,7 @@ import { messages } from 'components/messages'; export default function TeamWebsites({ teamId }) { const { formatMessage } = useIntl(); const { get, useQuery } = useApi(); - const { data, isLoading } = useQuery(['teams/websites', teamId], () => + const { data, isLoading } = useQuery(['teams:websites', teamId], () => get(`/teams/${teamId}/websites`), ); const hasData = data && data.length !== 0; diff --git a/components/pages/settings/teams/TeamsList.js b/components/pages/settings/teams/TeamsList.js index cae33567..591a836e 100644 --- a/components/pages/settings/teams/TeamsList.js +++ b/components/pages/settings/teams/TeamsList.js @@ -1,5 +1,5 @@ import { useState } from 'react'; -import { Button, Icon, Modal, ModalTrigger, useToast, Icons, Text } from 'react-basics'; +import { Button, Icon, Modal, ModalTrigger, useToast, Text, Flexbox } from 'react-basics'; import { useIntl } from 'react-intl'; import useApi from 'hooks/useApi'; import EmptyPlaceholder from 'components/common/EmptyPlaceholder'; @@ -8,6 +8,8 @@ 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'; +import Icons from 'components/icons'; +import TeamJoinForm from './JoinTeamForm'; export default function TeamsList() { const { formatMessage } = useIntl(); @@ -22,6 +24,24 @@ export default function TeamsList() { showToast({ message: formatMessage(messages.saved), variant: 'success' }); }; + const handleJoin = () => { + showToast({ message: formatMessage(messages.saved), variant: 'success' }); + }; + + const joinButton = ( + + + + {close => } + + + ); + const createButton = ( - - - - ); + const rowData = { + ...row, + owner: row.teamUsers.find(({ role }) => role === ROLES.teamOwner)?.user?.username, + action: ( + + + + + + + + ), + }; return ( - + {(data, key, colIndex) => { return ( diff --git a/components/pages/settings/users/UserSettings.js b/components/pages/settings/users/UserSettings.js index c41b9ce4..e3fad23c 100644 --- a/components/pages/settings/users/UserSettings.js +++ b/components/pages/settings/users/UserSettings.js @@ -46,14 +46,16 @@ export default function UserSettings({ userId }) { return ( {toast} - - - - {formatMessage(labels.users)} - - {values?.username} - - + + + {formatMessage(labels.users)} + + {values?.username} + + } + /> {formatMessage(labels.details)} {formatMessage(labels.websites)} diff --git a/components/pages/settings/users/UserWebsites.js b/components/pages/settings/users/UserWebsites.js index 28fddaa4..326464f7 100644 --- a/components/pages/settings/users/UserWebsites.js +++ b/components/pages/settings/users/UserWebsites.js @@ -7,7 +7,7 @@ import { messages } from 'components/messages'; export default function UserWebsites({ userId }) { const { formatMessage } = useIntl(); const { get, useQuery } = useApi(); - const { data, isLoading } = useQuery(['user/websites', userId], () => + const { data, isLoading } = useQuery(['user:websites', userId], () => get(`/users/${userId}/websites`), ); const hasData = data && data.length !== 0; diff --git a/components/pages/settings/websites/WebsiteSettings.js b/components/pages/settings/websites/WebsiteSettings.js index a6ee5092..e61c699d 100644 --- a/components/pages/settings/websites/WebsiteSettings.js +++ b/components/pages/settings/websites/WebsiteSettings.js @@ -49,13 +49,16 @@ export default function WebsiteSettings({ websiteId }) { return ( {toast} - - - - {formatMessage(labels.websites)} - - {values?.name} - + + + {formatMessage(labels.websites)} + + {values?.name} + + } + >