diff --git a/components/input/LanguageButton.js b/components/input/LanguageButton.js index afd447ac..e6da5991 100644 --- a/components/input/LanguageButton.js +++ b/components/input/LanguageButton.js @@ -1,14 +1,11 @@ -import { Icon, Button, PopupTrigger, Popup, Tooltip, Text } from 'react-basics'; -import { useIntl } from 'react-intl'; +import { Icon, Button, PopupTrigger, Popup, Text } from 'react-basics'; import classNames from 'classnames'; import { languages } from 'lib/lang'; import useLocale from 'hooks/useLocale'; import Icons from 'components/icons'; -import { labels } from 'components/messages'; import styles from './LanguageButton.module.css'; -export default function LanguageButton({ tooltipPosition = 'top', menuPosition = 'right' }) { - const { formatMessage } = useIntl(); +export default function LanguageButton() { const { locale, saveLocale } = useLocale(); const items = Object.keys(languages).map(key => ({ ...languages[key], value: key })); @@ -18,14 +15,12 @@ export default function LanguageButton({ tooltipPosition = 'top', menuPosition = return ( - - - - + +
{items.map(({ value, label }) => { return ( diff --git a/components/input/ProfileButton.js b/components/input/ProfileButton.js new file mode 100644 index 00000000..c7b46a8f --- /dev/null +++ b/components/input/ProfileButton.js @@ -0,0 +1,53 @@ +import { Icon, Button, PopupTrigger, Popup, Menu, Item, Text } from 'react-basics'; +import { useRouter } from 'next/router'; +import Icons from 'components/icons'; +import useMessages from 'hooks/useMessages'; +import useUser from 'hooks/useUser'; +import styles from './ProfileButton.module.css'; + +export default function ProfileButton() { + const { formatMessage, labels } = useMessages(); + const { user } = useUser(); + const router = useRouter(); + + const handleSelect = key => { + if (key === 'profile') { + router.push('/settings/profile'); + } + if (key === 'logout') { + router.push('/logout'); + } + }; + + return ( + + + + + + {user.username} + + + + + + {formatMessage(labels.profile)} + + + + + + {formatMessage(labels.logout)} + + + + + ); +} diff --git a/components/input/ProfileButton.module.css b/components/input/ProfileButton.module.css new file mode 100644 index 00000000..2ed5306b --- /dev/null +++ b/components/input/ProfileButton.module.css @@ -0,0 +1,9 @@ +.menu { + width: 200px; +} + +.item { + display: flex; + gap: 12px; + background: var(--base50); +} diff --git a/components/input/ThemeButton.js b/components/input/ThemeButton.js index cca3560a..692e886f 100644 --- a/components/input/ThemeButton.js +++ b/components/input/ThemeButton.js @@ -1,14 +1,12 @@ import { useTransition, animated } from 'react-spring'; -import { Button, Icon, Tooltip } from 'react-basics'; +import { Button, Icon } from 'react-basics'; import { useIntl } from 'react-intl'; import useTheme from 'hooks/useTheme'; import Icons from 'components/icons'; -import { labels } from 'components/messages'; import styles from './ThemeButton.module.css'; -export default function ThemeButton({ tooltipPosition = 'top' }) { +export default function ThemeButton() { const [theme, setTheme] = useTheme(); - const { formatMessage } = useIntl(); const transitions = useTransition(theme, { initial: { opacity: 1 }, @@ -28,14 +26,12 @@ export default function ThemeButton({ tooltipPosition = 'top' }) { } return ( - - - + ); } diff --git a/components/layout/AppLayout.module.css b/components/layout/AppLayout.module.css index 746ada39..8ed52c92 100644 --- a/components/layout/AppLayout.module.css +++ b/components/layout/AppLayout.module.css @@ -1,15 +1,21 @@ .layout { display: grid; - grid-template-rows: 1fr; - grid-template-columns: max-content 1fr; + grid-template-rows: max-content 1fr; + grid-template-columns: 1fr; } .nav { - grid-row: 1 / 3; + height: 60px; + width: 100vw; + z-index: 100; + grid-column: 1; + grid-row: 1 / 2; } .body { - grid-area: 1 / 2; - overflow: auto; - height: 100vh; + grid-column: 1; + grid-row: 2 / 3; + min-height: 0; + max-height: calc(100vh - 60px); + overflow-y: auto; } diff --git a/components/layout/NavBar.js b/components/layout/NavBar.js index 723da5df..d4defcb6 100644 --- a/components/layout/NavBar.js +++ b/components/layout/NavBar.js @@ -1,69 +1,50 @@ import { useState } from 'react'; -import { useIntl } from 'react-intl'; import { Icon, Text } from 'react-basics'; +import Link from 'next/link'; import classNames from 'classnames'; import Icons from 'components/icons'; import ThemeButton from 'components/input/ThemeButton'; import LanguageButton from 'components/input/LanguageButton'; -import LogoutButton from 'components/input/LogoutButton'; -import { labels } from 'components/messages'; -import useUser from 'hooks/useUser'; -import NavGroup from './NavGroup'; +import ProfileButton from 'components/input/ProfileButton'; import styles from './NavBar.module.css'; import useConfig from 'hooks/useConfig'; +import useMessages from 'hooks/useMessages'; export default function NavBar() { - const { user } = useUser(); const { cloudMode } = useConfig(); - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); const [minimized, setMinimized] = useState(false); - const tooltipPosition = minimized ? 'right' : 'top'; - const analytics = [ + const links = [ { label: formatMessage(labels.dashboard), url: '/dashboard', icon: }, { label: formatMessage(labels.realtime), url: '/realtime', icon: }, - ]; - - const settings = [ - !cloudMode && { - label: formatMessage(labels.websites), - url: '/settings/websites', - icon: , - }, - user?.isAdmin && { - label: formatMessage(labels.users), - url: '/settings/users', - icon: , - }, - !cloudMode && { - label: formatMessage(labels.teams), - url: '/settings/teams', - icon: , - }, - { label: formatMessage(labels.profile), url: '/settings/profile', icon: }, + !cloudMode && { label: formatMessage(labels.settings), url: '/settings', icon: }, ].filter(n => n); const handleMinimize = () => setMinimized(state => !state); return (
-
+
umami - - -
- - -
-
- - - {!cloudMode && } -
+
+ {links.map(({ url, icon, label }) => { + return ( + + {icon} + {label} + + ); + })} +
+
+ + + {!cloudMode && }
); diff --git a/components/layout/NavBar.module.css b/components/layout/NavBar.module.css index 1d387643..4accadf4 100644 --- a/components/layout/NavBar.module.css +++ b/components/layout/NavBar.module.css @@ -1,62 +1,49 @@ .navbar { position: relative; display: flex; - flex-direction: column; + flex-direction: row; align-items: center; + height: 60px; background: var(--base75); - height: 100%; - width: 200px; - border-right: 2px solid var(--base200); + border-bottom: 1px solid var(--base200); + padding: 0 20px; } -.header { +.logo { display: flex; align-items: center; justify-content: center; gap: 10px; font-size: 16px; font-weight: 700; - padding: 20px 0; cursor: pointer; + min-width: 0; } -.header:hover .icon { - visibility: visible; -} - -.icon { - visibility: hidden; - position: absolute; - right: -10px; - border-radius: 100%; - color: var(--base50); - background: var(--base800); - height: 20px; - width: 20px; -} - -.minimized.navbar { - width: 60px; -} - -.minimized .text { - display: none; -} - -.footer { - flex: 1; +.links { display: flex; - flex-direction: column; - justify-content: flex-end; - padding: 20px; + flex-direction: row; + gap: 20px; + padding: 0 40px; + flex: 1; + font-weight: 700; } -.buttons { +.links a { display: flex; align-items: center; - justify-content: center; + gap: 10px; + color: var(--font-color100); } -.minimized .buttons { - flex-direction: column; +.links a:hover { + color: var(--primary400); +} + +.actions { + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-end; + min-width: 0; } diff --git a/components/layout/Page.js b/components/layout/Page.js index ff6e049d..e8ddc44a 100644 --- a/components/layout/Page.js +++ b/components/layout/Page.js @@ -1,6 +1,6 @@ import classNames from 'classnames'; -import styles from './Page.module.css'; import { Banner, Loading } from 'react-basics'; +import styles from './Page.module.css'; export default function Page({ className, error, loading, children }) { if (error) { diff --git a/components/metrics/WebsiteChart.js b/components/metrics/WebsiteChart.js index 9489767c..f11410d8 100644 --- a/components/metrics/WebsiteChart.js +++ b/components/metrics/WebsiteChart.js @@ -84,7 +84,7 @@ export default function WebsiteChart({ diff --git a/components/pages/settings/profile/ProfileDetails.js b/components/pages/settings/profile/ProfileDetails.js index d8f183c2..5569053c 100644 --- a/components/pages/settings/profile/ProfileDetails.js +++ b/components/pages/settings/profile/ProfileDetails.js @@ -1,15 +1,14 @@ import { Form, FormRow } from 'react-basics'; -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/pages/settings/profile/ThemeSetting'; import useUser from 'hooks/useUser'; -import { labels } from 'components/messages'; +import useMessages from 'hooks/useMessages'; export default function ProfileDetails() { const { user } = useUser(); - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); if (!user) { return null; @@ -20,7 +19,9 @@ export default function ProfileDetails() { return (
{username} - {role} + + {formatMessage(labels[role] || labels.unknown)} + diff --git a/hooks/useMessages.js b/hooks/useMessages.js new file mode 100644 index 00000000..b0202fce --- /dev/null +++ b/hooks/useMessages.js @@ -0,0 +1,8 @@ +import { useIntl } from 'react-intl'; +import { messages, labels } from 'components/messages'; + +export default function useMessages() { + const { formatMessage } = useIntl(); + + return { formatMessage, messages, labels }; +} diff --git a/pages/_app.js b/pages/_app.js index ad9e71ac..21116467 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -29,7 +29,7 @@ export default function App({ Component, pageProps }) { const Wrapper = ({ children }) => {children}; - if (!config || config.uiDisabled) { + if (config?.uiDisabled) { return null; } diff --git a/styles/index.css b/styles/index.css index 42b2534e..1a57208e 100644 --- a/styles/index.css +++ b/styles/index.css @@ -1,8 +1,3 @@ -html { - overflow-x: hidden; - margin-right: calc(-1 * (100vw - 100%)); -} - html, body { font-family: Inter, -apple-system, system-ui, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, @@ -20,6 +15,7 @@ body { flex: 1; color: var(--font-color100); background: var(--base50); + overflow: hidden; } *, @@ -64,7 +60,8 @@ svg { #__next { display: flex; flex-direction: column; + flex: 1; width: 100%; height: 100%; - flex: 1; + overflow: hidden; }