diff --git a/components/messages.js b/components/messages.js
index 77a26d27..60edcbfb 100644
--- a/components/messages.js
+++ b/components/messages.js
@@ -46,6 +46,7 @@ export const labels = defineMessages({
deleteWebsite: { id: 'label.delete-website', defaultMessage: 'Delete website' },
reset: { id: 'label.reset', defaultMessage: 'Reset' },
addWebsite: { id: 'label.add-website', defaultMessage: 'Add website' },
+ addWebsites: { id: 'label.add-websites', defaultMessage: 'Add websites' },
changePassword: { id: 'label.change-password', defaultMessage: 'Change password' },
currentPassword: { id: 'label.current-password', defaultMessage: 'Current password' },
newPassword: { id: 'label.new-password', defaultMessage: 'New password' },
@@ -98,8 +99,6 @@ export const labels = defineMessages({
sessions: { id: 'label.sessions', defaultMessage: 'Sessions' },
pageNotFound: { id: 'message.page-not-found', defaultMessage: 'Page not found' },
logs: { id: 'label.activity-log', defaultMessage: 'Activity log' },
- dismiss: { id: 'label.dismiss', defaultMessage: 'Dismiss' },
- poweredBy: { id: 'label.powered-by', defaultMessage: 'Powered by {name}' },
});
export const messages = defineMessages({
@@ -141,6 +140,10 @@ export const messages = defineMessages({
id: 'message.reset-website',
defaultMessage: 'To reset this website, type {confirmation} in the box below to confirm.',
},
+ websitesShared: {
+ id: 'message.shared-website',
+ defaultMessage: 'Websites can be viewed by the entire team.',
+ },
invalidDomain: {
id: 'message.invalid-domain',
defaultMessage: 'Invalid domain. Do not include http/https.',
@@ -158,6 +161,14 @@ export const messages = defineMessages({
id: 'messages.no-websites',
defaultMessage: 'You do not have any websites configured.',
},
+ noTeamWebsites: {
+ id: 'messages.no-team-websites',
+ defaultMessage: 'This team does not have any websites.',
+ },
+ websitesAreShared: {
+ id: 'messages.websites-are-shared',
+ defaultMessage: 'Websites can be viewed by anyone on the team.',
+ },
noMatchPassword: { id: 'message.no-match-password', defaultMessage: 'Passwords do not match.' },
goToSettings: {
id: 'message.go-to-settings',
@@ -179,17 +190,6 @@ export const messages = defineMessages({
id: 'message.event-log',
defaultMessage: '{event} on {url}',
},
- newVersionAvailable: {
- id: 'new-version-available',
- defaultMessage: 'A new version of Umami {version} is available!',
- },
-});
-
-export const devices = defineMessages({
- desktop: { id: 'metrics.device.desktop', defaultMessage: 'Desktop' },
- laptop: { id: 'metrics.device.laptop', defaultMessage: 'Laptop' },
- tablet: { id: 'metrics.device.tablet', defaultMessage: 'Tablet' },
- mobile: { id: 'metrics.device.mobile', defaultMessage: 'Mobile' },
});
export function getMessage(id, formatMessage) {
@@ -197,7 +197,3 @@ export function getMessage(id, formatMessage) {
return message ? formatMessage(message) : id;
}
-
-export function getDeviceMessage(device) {
- return devices[device] || labels.unknown;
-}
diff --git a/components/pages/settings/teams/TeamWebsites.js b/components/pages/settings/teams/TeamWebsites.js
index 1a04f490..494a6eb0 100644
--- a/components/pages/settings/teams/TeamWebsites.js
+++ b/components/pages/settings/teams/TeamWebsites.js
@@ -1,13 +1,26 @@
-import { Loading } from 'react-basics';
-import { useIntl } from 'react-intl';
+import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
+import { labels, messages } from 'components/messages';
+import TeamWebsitesTable from 'components/pages/settings/teams/TeamWebsitesTable';
import useApi from 'hooks/useApi';
-import WebsitesTable from 'components/pages/settings/websites/WebsitesTable';
-import { messages } from 'components/messages';
+import {
+ ActionForm,
+ Button,
+ Icon,
+ Icons,
+ Loading,
+ Modal,
+ ModalTrigger,
+ Text,
+ useToast,
+} from 'react-basics';
+import { useIntl } from 'react-intl';
+import WebsiteAddTeamForm from 'components/pages/settings/teams/WebsiteAddTeamForm';
export default function TeamWebsites({ teamId }) {
+ const { toast, showToast } = useToast();
const { formatMessage } = useIntl();
const { get, useQuery } = useApi();
- const { data, isLoading } = useQuery(['teams:websites', teamId], () =>
+ const { data, isLoading, refetch } = useQuery(['teams:websites', teamId], () =>
get(`/teams/${teamId}/websites`),
);
const hasData = data && data.length !== 0;
@@ -16,10 +29,37 @@ export default function TeamWebsites({ teamId }) {
return ;
}
+ const handleSave = async () => {
+ await refetch();
+ showToast({ message: formatMessage(messages.saved), variant: 'success' });
+ };
+
+ const addButton = (
+
+
+
+ {close => }
+
+
+ );
+
return (
- {hasData &&
}
- {!hasData && formatMessage(messages.noData)}
+ {toast}
+ {hasData && (
+
{addButton}
+ )}
+ {hasData &&
}
+ {!hasData && (
+
+ {addButton}
+
+ )}
);
}
diff --git a/components/pages/settings/teams/TeamWebsitesTable.js b/components/pages/settings/teams/TeamWebsitesTable.js
new file mode 100644
index 00000000..b0d34221
--- /dev/null
+++ b/components/pages/settings/teams/TeamWebsitesTable.js
@@ -0,0 +1,105 @@
+import Link from 'next/link';
+import {
+ Table,
+ TableHeader,
+ TableBody,
+ TableRow,
+ TableCell,
+ TableColumn,
+ Button,
+ Text,
+ Icon,
+ Icons,
+ Flexbox,
+} from 'react-basics';
+import { useIntl } from 'react-intl';
+import { labels } from 'components/messages';
+import useUser from 'hooks/useUser';
+import useApi from 'hooks/useApi';
+
+export default function TeamWebsitesTable({ teamId, data = [], onSave }) {
+ const { formatMessage } = useIntl();
+ const { user } = useUser();
+ const { del, useMutation } = useApi();
+ const { mutate } = useMutation(data => del(`/teamWebsites/${data.teamWebsiteId}`));
+
+ const columns = [
+ { name: 'name', label: formatMessage(labels.name), style: { flex: 2 } },
+ { name: 'domain', label: formatMessage(labels.domain) },
+ { name: 'action', label: ' ' },
+ ];
+
+ const handleRemoveWebsite = teamWebsiteId => {
+ mutate(
+ { teamWebsiteId },
+ {
+ onSuccess: async () => {
+ onSave();
+ },
+ },
+ );
+ };
+
+ return (
+
+
+ {(column, index) => {
+ return (
+
+ {column.label}
+
+ );
+ }}
+
+
+ {(row, keys, rowIndex) => {
+ const { id: teamWebsiteId } = row;
+ const { id: websiteId, name, domain, userId } = row.website;
+ const { teamUser } = row.team;
+ const owner = teamUser[0];
+ const canRemove = user.id === userId || user.id === owner.userId;
+
+ row.name = name;
+ row.domain = domain;
+
+ row.action = (
+
+
+
+
+
+
+ {canRemove && (
+
+ )}
+
+ );
+
+ return (
+
+ {(data, key, colIndex) => {
+ return (
+
+
+ {data[key]}
+
+
+ );
+ }}
+
+ );
+ }}
+
+
+ );
+}
diff --git a/components/pages/settings/teams/TeamsList.js b/components/pages/settings/teams/TeamsList.js
index b48e9971..0e6ef9c2 100644
--- a/components/pages/settings/teams/TeamsList.js
+++ b/components/pages/settings/teams/TeamsList.js
@@ -76,7 +76,10 @@ export default function TeamsList() {
{hasData && }
{!hasData && (
- {createButton}
+
+ {joinButton}
+ {createButton}
+
)}
diff --git a/components/pages/settings/teams/TeamsTable.js b/components/pages/settings/teams/TeamsTable.js
index db99f9db..112a0992 100644
--- a/components/pages/settings/teams/TeamsTable.js
+++ b/components/pages/settings/teams/TeamsTable.js
@@ -1,26 +1,28 @@
+import { labels } from 'components/messages';
+import useUser from 'hooks/useUser';
+import { ROLES } from 'lib/constants';
import Link from 'next/link';
import {
+ Button,
+ Flexbox,
+ Icon,
+ Icons,
+ Modal,
+ ModalTrigger,
Table,
- TableHeader,
TableBody,
- TableRow,
TableCell,
TableColumn,
- Button,
- Icon,
- Flexbox,
- Icons,
+ TableHeader,
+ TableRow,
Text,
- ModalTrigger,
- Modal,
} from 'react-basics';
import { useIntl } from 'react-intl';
-import { labels } from 'components/messages';
-import { ROLES } from 'lib/constants';
import TeamDeleteForm from './TeamDeleteForm';
export default function TeamsTable({ data = [], onDelete }) {
const { formatMessage } = useIntl();
+ const { user } = useUser();
const columns = [
{ name: 'name', label: formatMessage(labels.name), style: { flex: 2 } },
@@ -42,10 +44,12 @@ export default function TeamsTable({ data = [], onDelete }) {
{(row, keys, rowIndex) => {
const { id } = row;
+ const owner = row.teamUser.find(({ role }) => role === ROLES.teamOwner);
+ const showDelete = user.id === owner?.userId;
const rowData = {
...row,
- owner: row.teamUser.find(({ role }) => role === ROLES.teamOwner)?.user?.username,
+ owner: owner?.user?.username,
action: (
@@ -58,24 +62,26 @@ export default function TeamsTable({ data = [], onDelete }) {
-
-
-
- {close => (
-
- )}
-
-
+ {showDelete && (
+
+
+
+ {close => (
+
+ )}
+
+
+ )}
),
};
diff --git a/components/pages/settings/teams/WebsiteAddTeamForm.js b/components/pages/settings/teams/WebsiteAddTeamForm.js
new file mode 100644
index 00000000..5d85e5de
--- /dev/null
+++ b/components/pages/settings/teams/WebsiteAddTeamForm.js
@@ -0,0 +1,62 @@
+import { labels } from 'components/messages';
+import useApi from 'hooks/useApi';
+import { useRef, useState } from 'react';
+import { Button, Dropdown, Form, FormButtons, FormRow, Item, SubmitButton } from 'react-basics';
+import { useIntl } from 'react-intl';
+import WebsiteTags from './WebsiteTags';
+
+export default function WebsiteAddTeamForm({ teamId, onSave, onClose }) {
+ const { formatMessage } = useIntl();
+ const { get, post, useQuery, useMutation } = useApi();
+ const { mutate, error } = useMutation(data => post(`/teams/${teamId}/websites`, data));
+ const { data: websites } = useQuery(['websites'], () => get('/websites'));
+ const [newWebsites, setNewWebsites] = useState([]);
+ const formRef = useRef();
+
+ const handleSubmit = () => {
+ mutate(
+ { websiteIds: newWebsites },
+ {
+ onSuccess: async () => {
+ onSave();
+ onClose();
+ },
+ },
+ );
+ };
+
+ const handleAddWebsite = value => {
+ if (!newWebsites.some(a => a === value)) {
+ const nextValue = [...newWebsites];
+
+ nextValue.push(value);
+
+ setNewWebsites(nextValue);
+ }
+ };
+
+ const handleRemoveWebsite = value => {
+ const newValue = newWebsites.filter(a => a !== value);
+
+ setNewWebsites(newValue);
+ };
+
+ return (
+ <>
+
+ >
+ );
+}
diff --git a/components/pages/settings/teams/WebsiteTags.js b/components/pages/settings/teams/WebsiteTags.js
new file mode 100644
index 00000000..19179422
--- /dev/null
+++ b/components/pages/settings/teams/WebsiteTags.js
@@ -0,0 +1,29 @@
+import { Button, Icon, Icons, Text } from 'react-basics';
+import styles from './WebsiteTags.module.css';
+
+export default function WebsiteTags({ items = [], websites = [], onClick }) {
+ if (websites.length === 0) {
+ return null;
+ }
+
+ return (
+
+ {websites.map(websiteId => {
+ const website = items.find(a => a.id === websiteId);
+
+ return (
+
+
+
+ );
+ })}
+
+ );
+}
diff --git a/components/pages/settings/teams/WebsiteTags.module.css b/components/pages/settings/teams/WebsiteTags.module.css
new file mode 100644
index 00000000..50ae60a0
--- /dev/null
+++ b/components/pages/settings/teams/WebsiteTags.module.css
@@ -0,0 +1,11 @@
+.filters {
+ display: flex;
+ justify-content: flex-start;
+ align-items: flex-start;
+}
+
+.tag {
+ text-align: center;
+ margin-bottom: 10px;
+ margin-right: 20px;
+}
diff --git a/components/pages/settings/websites/WebsiteSettings.js b/components/pages/settings/websites/WebsiteSettings.js
index 02693c72..273942ee 100644
--- a/components/pages/settings/websites/WebsiteSettings.js
+++ b/components/pages/settings/websites/WebsiteSettings.js
@@ -59,7 +59,7 @@ export default function WebsiteSettings({ websiteId }) {
}
>
-
+