mirror of
https://github.com/umami-software/umami.git
synced 2026-02-05 21:27:20 +01:00
Start using react-zen.
This commit is contained in:
parent
020cfdc646
commit
71e4f8f49b
24 changed files with 1872 additions and 609 deletions
|
|
@ -1,9 +1,11 @@
|
|||
'use client';
|
||||
import { Loading } from 'react-basics';
|
||||
import { Grid, Loading } from '@umami/react-zen';
|
||||
import Script from 'next/script';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { useLogin, useConfig } from '@/components/hooks';
|
||||
import UpdateNotice from './UpdateNotice';
|
||||
import NavBar from '@/app/(main)/NavBar';
|
||||
import Page from '@/components/layout/Page';
|
||||
import { useLogin, useConfig } from '@/components/hooks';
|
||||
|
||||
export function App({ children }) {
|
||||
const { user, isLoading, error } = useLogin();
|
||||
|
|
@ -27,13 +29,16 @@ export function App({ children }) {
|
|||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
<UpdateNotice user={user} config={config} />
|
||||
{process.env.NODE_ENV === 'production' && !pathname.includes('/share/') && (
|
||||
<Script src={`${process.env.basePath || ''}/telemetry.js`} />
|
||||
)}
|
||||
</>
|
||||
<Grid rows="auto 1fr">
|
||||
<NavBar />
|
||||
<Page>
|
||||
<UpdateNotice user={user} config={config} />
|
||||
{children}
|
||||
{process.env.NODE_ENV === 'production' && !pathname.includes('/share/') && (
|
||||
<Script src={`${process.env.basePath || ''}/telemetry.js`} />
|
||||
)}
|
||||
</Page>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,16 +10,6 @@
|
|||
z-index: 200;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.links {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
|
@ -48,15 +38,10 @@
|
|||
border-bottom: 2px solid var(--primary400);
|
||||
}
|
||||
|
||||
.actions,
|
||||
.mobile {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.mobile {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
|
@ -65,8 +50,7 @@
|
|||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.links,
|
||||
.actions {
|
||||
.links {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,15 @@
|
|||
'use client';
|
||||
import { useEffect } from 'react';
|
||||
import { Icon, Text } from 'react-basics';
|
||||
import { Icon, Text, ThemeButton, Row } from '@umami/react-zen';
|
||||
import Link from 'next/link';
|
||||
import classNames from 'classnames';
|
||||
import HamburgerButton from '@/components/common/HamburgerButton';
|
||||
import ThemeButton from '@/components/input/ThemeButton';
|
||||
import LanguageButton from '@/components/input/LanguageButton';
|
||||
import ProfileButton from '@/components/input/ProfileButton';
|
||||
import TeamsButton from '@/components/input/TeamsButton';
|
||||
import Icons from '@/components/icons';
|
||||
import { useMessages, useNavigation, useTeamUrl } from '@/components/hooks';
|
||||
import { getItem, setItem } from '@/lib/storage';
|
||||
import styles from './NavBar.module.css';
|
||||
import { useMessages, useTeamUrl } from '@/components/hooks';
|
||||
|
||||
export function NavBar() {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { pathname, router } = useNavigation();
|
||||
const { teamId, renderTeamUrl } = useTeamUrl();
|
||||
|
||||
const cloudMode = !!process.env.cloudMode;
|
||||
const { renderTeamUrl } = useTeamUrl();
|
||||
|
||||
const links = [
|
||||
{ label: formatMessage(labels.dashboard), url: renderTeamUrl('/dashboard') },
|
||||
|
|
@ -27,106 +18,32 @@ export function NavBar() {
|
|||
{ label: formatMessage(labels.settings), url: renderTeamUrl('/settings') },
|
||||
].filter(n => n);
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
label: formatMessage(labels.dashboard),
|
||||
url: renderTeamUrl('/dashboard'),
|
||||
},
|
||||
!cloudMode && {
|
||||
label: formatMessage(labels.settings),
|
||||
url: renderTeamUrl('/settings'),
|
||||
children: [
|
||||
...(teamId
|
||||
? [
|
||||
{
|
||||
label: formatMessage(labels.team),
|
||||
url: renderTeamUrl('/settings/team'),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
label: formatMessage(labels.websites),
|
||||
url: renderTeamUrl('/settings/websites'),
|
||||
},
|
||||
...(!teamId
|
||||
? [
|
||||
{
|
||||
label: formatMessage(labels.teams),
|
||||
url: renderTeamUrl('/settings/teams'),
|
||||
},
|
||||
{
|
||||
label: formatMessage(labels.users),
|
||||
url: '/settings/users',
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
label: formatMessage(labels.members),
|
||||
url: renderTeamUrl('/settings/members'),
|
||||
},
|
||||
]),
|
||||
],
|
||||
},
|
||||
{
|
||||
label: formatMessage(labels.profile),
|
||||
url: '/profile',
|
||||
},
|
||||
!cloudMode && { label: formatMessage(labels.logout), url: '/logout' },
|
||||
].filter(n => n);
|
||||
|
||||
const handleTeamChange = (teamId: string) => {
|
||||
const url = teamId ? `/teams/${teamId}` : '/';
|
||||
if (!cloudMode) {
|
||||
setItem('umami.team', { id: teamId });
|
||||
}
|
||||
router.push(cloudMode ? `${process.env.cloudUrl}${url}` : url);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!cloudMode) {
|
||||
const teamIdLocal = getItem('umami.team')?.id;
|
||||
|
||||
if (teamIdLocal && teamIdLocal !== teamId) {
|
||||
router.push(
|
||||
pathname !== '/' && pathname !== '/dashboard' ? '/' : `/teams/${teamIdLocal}/dashboard`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}, [cloudMode]);
|
||||
|
||||
return (
|
||||
<div className={styles.navbar}>
|
||||
<div className={styles.logo}>
|
||||
<Icon size="lg">
|
||||
<Row justifyContent="space-between" alignItems="center" paddingX="4" paddingY="3">
|
||||
<Row alignItems="center" gap="3">
|
||||
<Icon size="md">
|
||||
<Icons.Logo />
|
||||
</Icon>
|
||||
<Text>umami</Text>
|
||||
</div>
|
||||
<div className={styles.links}>
|
||||
<Text size="3" weight="bold">
|
||||
umami
|
||||
</Text>
|
||||
</Row>
|
||||
<Row gap="4">
|
||||
{links.map(({ url, label }) => {
|
||||
return (
|
||||
<Link
|
||||
key={url}
|
||||
href={url}
|
||||
className={classNames({ [styles.selected]: pathname.startsWith(url) })}
|
||||
prefetch={url !== '/settings'}
|
||||
>
|
||||
<Link key={url} href={url} prefetch={url !== '/settings'}>
|
||||
<Text>{label}</Text>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className={styles.actions}>
|
||||
<TeamsButton onChange={handleTeamChange} />
|
||||
</Row>
|
||||
<Row justifyContent="flex-end">
|
||||
<TeamsButton />
|
||||
<ThemeButton />
|
||||
<LanguageButton />
|
||||
<ProfileButton />
|
||||
</div>
|
||||
<div className={styles.mobile}>
|
||||
<TeamsButton onChange={handleTeamChange} showText={false} />
|
||||
<HamburgerButton menuItems={menuItems} />
|
||||
</div>
|
||||
</div>
|
||||
</Row>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
.notice {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
gap: 20px;
|
||||
margin: 60px auto;
|
||||
align-self: center;
|
||||
background: var(--base50);
|
||||
padding: 20px;
|
||||
border: 1px solid var(--base300);
|
||||
border-radius: var(--border-radius);
|
||||
z-index: 9999;
|
||||
box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.message {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--font-color100);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
flex: 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 992px) {
|
||||
.message {
|
||||
height: 80px;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,10 @@
|
|||
import { useEffect, useCallback, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { Button } from 'react-basics';
|
||||
import { Button, AlertBanner, Flexbox } from '@umami/react-zen';
|
||||
import { setItem } from '@/lib/storage';
|
||||
import useStore, { checkVersion } from '@/store/version';
|
||||
import { REPO_URL, VERSION_CHECK } from '@/lib/constants';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import styles from './UpdateNotice.module.css';
|
||||
|
||||
export function UpdateNotice({ user, config }) {
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
|
|
@ -47,19 +45,15 @@ export function UpdateNotice({ user, config }) {
|
|||
return null;
|
||||
}
|
||||
|
||||
return createPortal(
|
||||
<div className={styles.notice}>
|
||||
<div className={styles.message}>
|
||||
{formatMessage(messages.newVersionAvailable, { version: `v${latest}` })}
|
||||
</div>
|
||||
<div className={styles.buttons}>
|
||||
<Button variant="primary" onClick={handleViewClick}>
|
||||
return (
|
||||
<Flexbox justifyContent="space-between" alignItems="center">
|
||||
<AlertBanner title={formatMessage(messages.newVersionAvailable, { version: `v${latest}` })}>
|
||||
<Button variant="primary" onPress={handleViewClick}>
|
||||
{formatMessage(labels.viewDetails)}
|
||||
</Button>
|
||||
<Button onClick={handleDismissClick}>{formatMessage(labels.dismiss)}</Button>
|
||||
</div>
|
||||
</div>,
|
||||
document.body,
|
||||
<Button onPress={handleDismissClick}>{formatMessage(labels.dismiss)}</Button>
|
||||
</AlertBanner>
|
||||
</Flexbox>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
3
src/app/(main)/boards/BoardsPage.tsx
Normal file
3
src/app/(main)/boards/BoardsPage.tsx
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export default function BoardsPage() {
|
||||
return <h1>hi.</h1>;
|
||||
}
|
||||
10
src/app/(main)/boards/page.tsx
Normal file
10
src/app/(main)/boards/page.tsx
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import BoardsPage from './BoardsPage';
|
||||
import { Metadata } from 'next';
|
||||
|
||||
export default function () {
|
||||
return <BoardsPage />;
|
||||
}
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Boards',
|
||||
};
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
.layout {
|
||||
display: grid;
|
||||
grid-template-rows: max-content 1fr;
|
||||
grid-template-columns: 1fr;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.nav {
|
||||
height: 60px;
|
||||
width: 100vw;
|
||||
grid-column: 1;
|
||||
grid-row: 1 / 2;
|
||||
}
|
||||
|
||||
.body {
|
||||
grid-column: 1;
|
||||
grid-row: 2 / 3;
|
||||
min-height: 0;
|
||||
height: calc(100vh - 60px);
|
||||
height: calc(100dvh - 60px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
|
@ -1,22 +1,8 @@
|
|||
import { Metadata } from 'next';
|
||||
import App from './App';
|
||||
import NavBar from './NavBar';
|
||||
import Page from '@/components/layout/Page';
|
||||
import styles from './layout.module.css';
|
||||
|
||||
export default async function ({ children }) {
|
||||
return (
|
||||
<App>
|
||||
<main className={styles.layout}>
|
||||
<nav className={styles.nav}>
|
||||
<NavBar />
|
||||
</nav>
|
||||
<section className={styles.body}>
|
||||
<Page>{children}</Page>
|
||||
</section>
|
||||
</main>
|
||||
</App>
|
||||
);
|
||||
return <App>{children}</App>;
|
||||
}
|
||||
|
||||
export const metadata: Metadata = {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import WebsiteDetailsPage from './WebsiteDetailsPage';
|
||||
import { Metadata } from 'next';
|
||||
|
||||
export default async function WebsitePage({ params }: { params: { websiteId: string } }) {
|
||||
export default async function WebsitePage({ params }: { params: Promise<{ websiteId: string }> }) {
|
||||
const { websiteId } = await params;
|
||||
|
||||
return <WebsiteDetailsPage websiteId={websiteId} />;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
'use client';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { ReactBasicsProvider } from 'react-basics';
|
||||
import { ZenProvider } from '@umami/react-zen';
|
||||
import ErrorBoundary from '@/components/common/ErrorBoundary';
|
||||
import { useLocale } from '@/components/hooks';
|
||||
import 'chartjs-adapter-date-fns';
|
||||
import { useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { RouterProvider } from 'react-aria-components';
|
||||
|
||||
const client = new QueryClient({
|
||||
defaultOptions: {
|
||||
|
|
@ -32,14 +34,18 @@ function MessagesProvider({ children }) {
|
|||
}
|
||||
|
||||
export function Providers({ children }) {
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<MessagesProvider>
|
||||
<QueryClientProvider client={client}>
|
||||
<ReactBasicsProvider>
|
||||
<ErrorBoundary>{children}</ErrorBoundary>
|
||||
</ReactBasicsProvider>
|
||||
</QueryClientProvider>
|
||||
</MessagesProvider>
|
||||
<ZenProvider>
|
||||
<RouterProvider navigate={router.push}>
|
||||
<MessagesProvider>
|
||||
<QueryClientProvider client={client}>
|
||||
<ErrorBoundary>{children}</ErrorBoundary>
|
||||
</QueryClientProvider>
|
||||
</MessagesProvider>
|
||||
</RouterProvider>
|
||||
</ZenProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import '@fontsource/inter/300.css';
|
|||
import '@fontsource/inter/400.css';
|
||||
import '@fontsource/inter/500.css';
|
||||
import '@fontsource/inter/700.css';
|
||||
import 'react-basics/dist/styles.css';
|
||||
import '@/styles/index.css';
|
||||
import '@umami/react-zen/styles.css';
|
||||
import '@/styles/global.css';
|
||||
import '@/styles/variables.css';
|
||||
|
||||
export default function ({ children }) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue