diff --git a/components/common/HamburgerButton.js b/components/common/HamburgerButton.js
index 77aaae75..9c92b282 100644
--- a/components/common/HamburgerButton.js
+++ b/components/common/HamburgerButton.js
@@ -19,6 +19,20 @@ export default function HamburgerButton() {
!cloudMode && {
label: formatMessage(labels.settings),
value: '/settings',
+ children: [
+ {
+ label: formatMessage(labels.websites),
+ value: '/settings/websites',
+ },
+ {
+ label: formatMessage(labels.teams),
+ value: '/settings/teams',
+ },
+ {
+ label: formatMessage(labels.users),
+ value: '/settings/users',
+ },
+ ],
},
{
label: formatMessage(labels.profile),
diff --git a/components/common/MobileMenu.js b/components/common/MobileMenu.js
index aa737e7e..4168a6c6 100644
--- a/components/common/MobileMenu.js
+++ b/components/common/MobileMenu.js
@@ -1,17 +1,36 @@
import classNames from 'classnames';
+import { useRouter } from 'next/router';
import Link from 'next/link';
import styles from './MobileMenu.module.css';
export default function MobileMenu({ items = [], onClose }) {
+ const { pathname } = useRouter();
+
+ const Items = ({ items, className }) => (
+
+ {items.map(({ label, value, children }) => {
+ const selected = pathname === value;
+
+ return (
+ <>
+
+ {label}
+
+ {children && }
+ >
+ );
+ })}
+
+ );
+
return (
-
- {items.map(({ label, value }) => (
-
- {label}
-
- ))}
-
+
);
}
diff --git a/components/common/MobileMenu.module.css b/components/common/MobileMenu.module.css
index aa9aafd0..cfe6cf37 100644
--- a/components/common/MobileMenu.module.css
+++ b/components/common/MobileMenu.module.css
@@ -25,5 +25,15 @@
}
a.item {
+ color: var(--base600);
+}
+
+a.item.selected,
+.submenu a.item.selected {
color: var(--base900);
}
+
+.submenu a.item {
+ color: var(--base600);
+ margin-left: 40px;
+}
diff --git a/components/common/SettingsTable.js b/components/common/SettingsTable.js
new file mode 100644
index 00000000..ac29d54e
--- /dev/null
+++ b/components/common/SettingsTable.js
@@ -0,0 +1,36 @@
+import { Table, TableHeader, TableBody, TableRow, TableCell, TableColumn } from 'react-basics';
+import styles from './SettingsTable.module.css';
+
+export default function SettingsTable({ columns = [], data = [], children, cellRender }) {
+ return (
+
+
+ {(column, index) => {
+ return (
+
+ {column.label}
+
+ );
+ }}
+
+
+ {(row, keys, rowIndex) => {
+ row.action = children(row, keys, rowIndex);
+
+ return (
+
+ {(data, key, colIndex) => {
+ return (
+
+
+ {cellRender ? cellRender(row, data, key, colIndex) : data[key]}
+
+ );
+ }}
+
+ );
+ }}
+
+
+ );
+}
diff --git a/components/common/SettingsTable.module.css b/components/common/SettingsTable.module.css
new file mode 100644
index 00000000..023d1338
--- /dev/null
+++ b/components/common/SettingsTable.module.css
@@ -0,0 +1,40 @@
+.row .cell:last-child {
+ gap: 10px;
+ justify-content: flex-end;
+}
+
+.label {
+ display: none;
+ font-weight: 700;
+}
+
+@media screen and (max-width: 992px) {
+ .header .cell {
+ display: none;
+ }
+
+ .label {
+ display: block;
+ min-width: 100px;
+ }
+
+ .row .cell {
+ padding-left: 0;
+ flex-basis: 100%;
+ }
+}
+
+@media screen and (max-width: 1200px) {
+ .row {
+ flex-wrap: wrap;
+ }
+
+ .header .cell:last-child {
+ display: none;
+ }
+
+ .row .cell:last-child {
+ padding-left: 0;
+ flex-basis: 100%;
+ }
+}
diff --git a/components/layout/NavBar.module.css b/components/layout/NavBar.module.css
index 6c2faaaf..05dce2af 100644
--- a/components/layout/NavBar.module.css
+++ b/components/layout/NavBar.module.css
@@ -49,6 +49,10 @@
border-bottom: 2px solid transparent;
}
+.links span {
+ white-space: nowrap;
+}
+
.links a:hover {
color: var(--font-color100);
border-bottom: 2px solid var(--primary400);
diff --git a/components/layout/PageHeader.js b/components/layout/PageHeader.js
index 0d014316..05c87f73 100644
--- a/components/layout/PageHeader.js
+++ b/components/layout/PageHeader.js
@@ -1,13 +1,9 @@
import React from 'react';
-import classNames from 'classnames';
-import { useBreakpoint } from 'react-basics';
import styles from './PageHeader.module.css';
export default function PageHeader({ title, children }) {
- const breakPoint = useBreakpoint();
-
return (
-
+
diff --git a/components/layout/PageHeader.module.css b/components/layout/PageHeader.module.css
index d5481727..8a2a6800 100644
--- a/components/layout/PageHeader.module.css
+++ b/components/layout/PageHeader.module.css
@@ -16,6 +16,10 @@
color: var(--base900);
}
+.header > div {
+ line-height: 60px;
+}
+
.title {
display: flex;
align-items: center;
@@ -25,7 +29,22 @@
line-height: 50px;
}
-.xs .actions,
-.sm .actions {
- flex-basis: 100%;
+.actions {
+ display: flex;
+ justify-content: flex-end;
+}
+
+@media only screen and (max-width: 992px) {
+ .header {
+ margin-bottom: 10px;
+ }
+
+ .title {
+ font-size: 18px;
+ }
+
+ .actions {
+ flex-basis: 100%;
+ order: -1;
+ }
}
diff --git a/components/layout/SettingsLayout.js b/components/layout/SettingsLayout.js
index 2b98aca5..ea0456e0 100644
--- a/components/layout/SettingsLayout.js
+++ b/components/layout/SettingsLayout.js
@@ -20,16 +20,16 @@ export default function SettingsLayout({ children }) {
{ key: 'profile', label: formatMessage(labels.profile), url: '/settings/profile' },
].filter(n => n);
- const getKey = () => items.find(({ url }) => pathname.startsWith(url))?.key;
+ const getKey = () => items.find(({ url }) => pathname === url)?.key;
return (
-
+
{!cloudMode && (
-
+
)}
-
+
{children}
diff --git a/components/layout/SettingsLayout.module.css b/components/layout/SettingsLayout.module.css
index 068681f5..569b903b 100644
--- a/components/layout/SettingsLayout.module.css
+++ b/components/layout/SettingsLayout.module.css
@@ -8,6 +8,8 @@
min-height: 50vh;
}
-.hideMenu .content {
- margin: 0 auto;
+@media only screen and (max-width: 768px) {
+ .menu {
+ display: none;
+ }
}
diff --git a/components/layout/SideNav.module.css b/components/layout/SideNav.module.css
index 8b7d0d42..b664194d 100644
--- a/components/layout/SideNav.module.css
+++ b/components/layout/SideNav.module.css
@@ -13,10 +13,3 @@
padding: 0;
border-radius: var(--border-radius);
}
-
-@media only screen and (max-width: 768px) {
- .menu {
- flex-direction: row;
- flex-wrap: wrap;
- }
-}
diff --git a/components/pages/settings/teams/TeamsTable.js b/components/pages/settings/teams/TeamsTable.js
index 1319c6d4..854b7bd9 100644
--- a/components/pages/settings/teams/TeamsTable.js
+++ b/components/pages/settings/teams/TeamsTable.js
@@ -1,126 +1,90 @@
import Link from 'next/link';
-import {
- Button,
- Flexbox,
- Icon,
- Icons,
- Modal,
- ModalTrigger,
- Table,
- TableBody,
- TableCell,
- TableColumn,
- TableHeader,
- TableRow,
- Text,
-} from 'react-basics';
+import { Button, Icon, Icons, Modal, ModalTrigger, Text } from 'react-basics';
import TeamDeleteForm from './TeamDeleteForm';
import TeamLeaveForm from './TeamLeaveForm';
import useMessages from 'hooks/useMessages';
import useUser from 'hooks/useUser';
import { ROLES } from 'lib/constants';
+import SettingsTable from 'components/common/SettingsTable';
export default function TeamsTable({ data = [], onDelete }) {
const { formatMessage, labels } = useMessages();
const { user } = useUser();
const columns = [
- { name: 'name', label: formatMessage(labels.name), style: { flex: 2 } },
+ { name: 'name', label: formatMessage(labels.name) },
{ name: 'owner', label: formatMessage(labels.owner) },
{ name: 'action', label: ' ' },
];
+ const cellRender = (row, data, key) => {
+ if (key === 'owner') {
+ return row.teamUser.find(({ role }) => role === ROLES.teamOwner)?.user?.username;
+ }
+ return data[key];
+ };
+
return (
-
-
- {(column, index) => {
- return (
-
- {column.label}
-
- );
- }}
-
-
- {(row, keys, rowIndex) => {
- const { id, teamUser } = row;
- const owner = row.teamUser.find(({ role }) => role === ROLES.teamOwner);
- const showDelete = user.id === owner?.userId;
- const teamUserId = teamUser.find(a => a.userId === user.id).id;
+
+ {row => {
+ const { id, teamUser } = row;
+ const owner = teamUser.find(({ role }) => role === ROLES.teamOwner);
+ const showDelete = user.id === owner?.userId;
- const rowData = {
- ...row,
- owner: owner?.user?.username,
- action: (
-
-
-
-
- {showDelete && (
-
-
-
- {close => (
-
- )}
-
-
- )}
- {!showDelete && (
-
-
-
- {close => (
-
- )}
-
-
- )}
-
- ),
- };
-
- return (
-
- {(data, key, colIndex) => {
- return (
-
-
- {data[key]}
-
-
- );
- }}
-
- );
- }}
-
-
+ return (
+ <>
+
+
+
+ {showDelete && (
+
+
+
+ {close => (
+
+ )}
+
+
+ )}
+ {!showDelete && (
+
+
+
+ {close => (
+
+ )}
+
+
+ )}
+ >
+ );
+ }}
+
);
}
diff --git a/components/pages/settings/users/UsersTable.js b/components/pages/settings/users/UsersTable.js
index 1542fe28..5ea3806c 100644
--- a/components/pages/settings/users/UsersTable.js
+++ b/components/pages/settings/users/UsersTable.js
@@ -1,109 +1,71 @@
-import {
- Button,
- Text,
- Icon,
- Table,
- TableBody,
- TableCell,
- TableColumn,
- TableHeader,
- TableRow,
- Flexbox,
- Icons,
- ModalTrigger,
- Modal,
-} from 'react-basics';
+import { Button, Text, Icon, Icons, ModalTrigger, Modal } from 'react-basics';
import { formatDistance } from 'date-fns';
import Link from 'next/link';
import useUser from 'hooks/useUser';
import UserDeleteForm from './UserDeleteForm';
import { ROLES } from 'lib/constants';
import useMessages from 'hooks/useMessages';
+import SettingsTable from 'components/common/SettingsTable';
export default function UsersTable({ data = [], onDelete }) {
const { formatMessage, labels } = useMessages();
const { user } = useUser();
const columns = [
- { name: 'username', label: formatMessage(labels.username), style: { flex: 2 } },
- { name: 'role', label: formatMessage(labels.role), style: { flex: 1 } },
- { name: 'created', label: formatMessage(labels.created), style: { flex: 1 } },
- { name: 'action', label: ' ', style: { flex: 2 } },
+ { name: 'username', label: formatMessage(labels.username), style: { flex: 1.5 } },
+ { name: 'role', label: formatMessage(labels.role) },
+ { name: 'created', label: formatMessage(labels.created) },
+ { name: 'action', label: ' ' },
];
- return (
-
-
- {(column, index) => {
- return (
-
- {column.label}
-
- );
- }}
-
-
- {(row, keys, rowIndex) => {
- 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 => (
-
- )}
-
-
- >
- ),
- };
+ const cellRender = (row, data, key) => {
+ if (key === 'created') {
+ return formatDistance(new Date(row.createdAt), new Date(), {
+ addSuffix: true,
+ });
+ }
+ if (key === 'role') {
+ return formatMessage(
+ labels[Object.keys(ROLES).find(key => ROLES[key] === row.role)] || labels.unknown,
+ );
+ }
+ return data[key];
+ };
- return (
-
- {(data, key, colIndex) => {
- return (
-
-
- {data[key]}
-
-
- );
- }}
-
- );
- }}
-
-
+ return (
+
+ {(row, keys, rowIndex) => {
+ return (
+ <>
+
+
+
+
+
+
+ {close => (
+
+ )}
+
+
+ >
+ );
+ }}
+
);
}
diff --git a/components/pages/settings/websites/WebsitesTable.js b/components/pages/settings/websites/WebsitesTable.js
index d658be48..0ed79e4f 100644
--- a/components/pages/settings/websites/WebsitesTable.js
+++ b/components/pages/settings/websites/WebsitesTable.js
@@ -1,83 +1,45 @@
import Link from 'next/link';
-import {
- Table,
- TableHeader,
- TableBody,
- TableRow,
- TableCell,
- TableColumn,
- Button,
- Text,
- Icon,
- Icons,
- Flexbox,
- useBreakpoint,
-} from 'react-basics';
+import { Button, Text, Icon, Icons } from 'react-basics';
+import SettingsTable from 'components/common/SettingsTable';
import useMessages from 'hooks/useMessages';
import useConfig from 'hooks/useConfig';
export default function WebsitesTable({ data = [] }) {
const { formatMessage, labels } = useMessages();
const { openExternal } = useConfig();
- const breakPoint = useBreakpoint();
const columns = [
- { name: 'name', label: formatMessage(labels.name), style: { flex: 2 } },
+ { name: 'name', label: formatMessage(labels.name) },
{ name: 'domain', label: formatMessage(labels.domain) },
- { name: 'action', label: ' ', style: { flexBasis: '100%' } },
+ { name: 'action', label: ' ' },
];
return (
-
-
- {(column, index) => {
- return (
-
- {column.label}
-
- );
- }}
-
-
- {(row, keys, rowIndex) => {
- const { id } = row;
+
+ {row => {
+ const { id } = row;
- row.action = (
-
-
-
-
-
-
-
-
- );
-
- return (
-
- {(data, key, colIndex) => {
- return (
-
-
- {data[key]}
-
-
- );
- }}
-
- );
- }}
-
-
+ return (
+ <>
+
+
+
+
+
+
+ >
+ );
+ }}
+
);
}
diff --git a/components/pages/settings/websites/WebsitesTable.module.css b/components/pages/settings/websites/WebsitesTable.module.css
new file mode 100644
index 00000000..a26c349f
--- /dev/null
+++ b/components/pages/settings/websites/WebsitesTable.module.css
@@ -0,0 +1,13 @@
+@media screen and (max-width: 992px) {
+ .row {
+ flex-wrap: wrap;
+ }
+
+ .header .actions {
+ display: none;
+ }
+
+ .actions {
+ flex-basis: 100%;
+ }
+}
diff --git a/package.json b/package.json
index b63935a2..c699d1f3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "umami",
- "version": "2.0.0-beta.4",
+ "version": "2.0.0-beta.5",
"description": "A simple, fast, privacy-focused alternative to Google Analytics.",
"author": "Mike Cao ",
"license": "MIT",
@@ -94,7 +94,7 @@
"node-fetch": "^3.2.8",
"npm-run-all": "^4.1.5",
"react": "^18.2.0",
- "react-basics": "^0.75.0",
+ "react-basics": "^0.76.0",
"react-beautiful-dnd": "^13.1.0",
"react-dom": "^18.2.0",
"react-intl": "^5.24.7",
diff --git a/pages/api/config.ts b/pages/api/config.ts
index bccfd048..a8ae1cb0 100644
--- a/pages/api/config.ts
+++ b/pages/api/config.ts
@@ -16,7 +16,7 @@ export default async (req: NextApiRequest, res: NextApiResponse)
trackerScriptName: process.env.TRACKER_SCRIPT_NAME,
updatesDisabled: !!process.env.DISABLE_UPDATES,
telemetryDisabled: !!process.env.DISABLE_TELEMETRY,
- cloudMode: !!process.env.CLOUD_MODE,
+ cloudMode: false, //!!process.env.CLOUD_MODE,
});
}
diff --git a/queries/admin/user.ts b/queries/admin/user.ts
index 431e934b..1f3a2bd0 100644
--- a/queries/admin/user.ts
+++ b/queries/admin/user.ts
@@ -23,6 +23,7 @@ export async function getUser(
export async function getUsers(): Promise {
return prisma.client.user.findMany({
+ take: 100,
where: {
deletedAt: null,
},
diff --git a/yarn.lock b/yarn.lock
index b759ff50..10c18dc6 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6727,10 +6727,10 @@ rc@^1.2.7:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
-react-basics@^0.75.0:
- version "0.75.0"
- resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.75.0.tgz#501ba7fae6e0659ec8f7e811a4303ef83bd57b13"
- integrity sha512-mDm+L/cw4LX4LylJW0MyV+YFxhZ0tMa/bCIs1QsApcRGvF4ahlB1rdwWn6/p01PVSHe7xS/55lSklp3b7IWOaw==
+react-basics@^0.76.0:
+ version "0.76.0"
+ resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.76.0.tgz#7369ba68409388f458a2ecf73a86603884fc0711"
+ integrity sha512-RRtudldMecbuT/ap1giy6OdNc1t8gfGdyfXDTy4x99PWN9kvfS8MU11cfyQif8F0C6v9wKFu2taxklQQarE+mw==
dependencies:
classnames "^2.3.1"
date-fns "^2.29.3"