mirror of
https://github.com/umami-software/umami.git
synced 2026-02-09 07:07:17 +01:00
Refactored to use app folder.
This commit is contained in:
parent
40cfcd41e9
commit
9a52cdd2e1
258 changed files with 2025 additions and 2258 deletions
|
|
@ -1,32 +0,0 @@
|
|||
import { Container } from 'react-basics';
|
||||
import Head from 'next/head';
|
||||
import NavBar from 'components/layout/NavBar';
|
||||
import UpdateNotice from 'components/common/UpdateNotice';
|
||||
import { useRequireLogin, useConfig } from 'components/hooks';
|
||||
import styles from './AppLayout.module.css';
|
||||
|
||||
export function AppLayout({ title, children }) {
|
||||
const { user } = useRequireLogin();
|
||||
const config = useConfig();
|
||||
|
||||
if (!user || !config || config?.uiDisabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.layout}>
|
||||
<UpdateNotice user={user} config={config} />
|
||||
<Head>
|
||||
<title>{title ? `${title} | umami` : 'umami'}</title>
|
||||
</Head>
|
||||
<nav className={styles.nav}>
|
||||
<NavBar />
|
||||
</nav>
|
||||
<main className={styles.body}>
|
||||
<Container>{children}</Container>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AppLayout;
|
||||
|
|
@ -1,23 +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;
|
||||
z-index: var(--z-index-popup);
|
||||
}
|
||||
|
||||
.body {
|
||||
grid-column: 1;
|
||||
grid-row: 2 / 3;
|
||||
min-height: 0;
|
||||
height: calc(100vh - 60px);
|
||||
overflow-y: auto;
|
||||
padding-bottom: 60px;
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
import { CURRENT_VERSION, HOMEPAGE_URL } from 'lib/constants';
|
||||
import styles from './Footer.module.css';
|
||||
|
||||
export function Footer() {
|
||||
return (
|
||||
<footer className={styles.footer}>
|
||||
<a href={HOMEPAGE_URL}>
|
||||
<b>umami</b> {`v${CURRENT_VERSION}`}
|
||||
</a>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
|
||||
export default Footer;
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
.footer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
font-size: var(--font-size-sm);
|
||||
line-height: 30px;
|
||||
margin: 40px 0;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
color: var(--font-color100);
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
import { Column, Icon, Row, Text } from 'react-basics';
|
||||
import Link from 'next/link';
|
||||
import LanguageButton from 'components/input/LanguageButton';
|
||||
import ThemeButton from 'components/input/ThemeButton';
|
||||
import SettingsButton from 'components/input/SettingsButton';
|
||||
import Icons from 'components/icons';
|
||||
import styles from './Header.module.css';
|
||||
|
||||
export function Header() {
|
||||
return (
|
||||
<header className={styles.header}>
|
||||
<Row className={styles.row}>
|
||||
<Column>
|
||||
<Link href="https://umami.is" target="_blank" className={styles.title}>
|
||||
<Icon size="lg">
|
||||
<Icons.Logo />
|
||||
</Icon>
|
||||
<Text>umami</Text>
|
||||
</Link>
|
||||
</Column>
|
||||
<Column className={styles.buttons}>
|
||||
<ThemeButton tooltipPosition="bottom" />
|
||||
<LanguageButton tooltipPosition="bottom" menuPosition="bottom" />
|
||||
<SettingsButton />
|
||||
</Column>
|
||||
</Row>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
export default Header;
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
.header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.row {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: 700;
|
||||
color: var(--font-color100) !important;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 992px) {
|
||||
.header .buttons {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.links {
|
||||
order: 2;
|
||||
margin: 20px 0;
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.buttons,
|
||||
.links {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
import { Icon, Text, Row, Column } from 'react-basics';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import classNames from 'classnames';
|
||||
import Icons from 'components/icons';
|
||||
import ThemeButton from 'components/input/ThemeButton';
|
||||
import LanguageButton from 'components/input/LanguageButton';
|
||||
import ProfileButton from 'components/input/ProfileButton';
|
||||
import useMessages from 'components/hooks/useMessages';
|
||||
import HamburgerButton from 'components/common/HamburgerButton';
|
||||
import styles from './NavBar.module.css';
|
||||
|
||||
export function NavBar() {
|
||||
const { pathname } = useRouter();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
const links = [
|
||||
{ label: formatMessage(labels.dashboard), url: '/dashboard' },
|
||||
{ label: formatMessage(labels.websites), url: '/websites' },
|
||||
{ label: formatMessage(labels.reports), url: '/reports' },
|
||||
{ label: formatMessage(labels.settings), url: '/settings' },
|
||||
].filter(n => n);
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.navbar)}>
|
||||
<Row>
|
||||
<Column className={styles.left}>
|
||||
<div className={styles.logo}>
|
||||
<Icon size="lg">
|
||||
<Icons.Logo />
|
||||
</Icon>
|
||||
<Text className={styles.text}>umami</Text>
|
||||
</div>
|
||||
<div className={styles.links}>
|
||||
{links.map(({ url, label }) => {
|
||||
return (
|
||||
<Link
|
||||
key={url}
|
||||
href={url}
|
||||
className={classNames({ [styles.selected]: pathname.startsWith(url) })}
|
||||
>
|
||||
<Text>{label}</Text>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Column>
|
||||
<Column className={styles.right}>
|
||||
<div className={styles.actions}>
|
||||
<ThemeButton />
|
||||
<LanguageButton />
|
||||
<ProfileButton />
|
||||
</div>
|
||||
<div className={styles.mobile}>
|
||||
<HamburgerButton />
|
||||
</div>
|
||||
</Column>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default NavBar;
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
.navbar {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: 60px;
|
||||
background: var(--base75);
|
||||
border-bottom: 1px solid var(--base300);
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.left,
|
||||
.right {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.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;
|
||||
gap: 30px;
|
||||
padding: 0 40px;
|
||||
flex: 1;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.links a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
line-height: 60px;
|
||||
color: var(--font-color200);
|
||||
border-bottom: 2px solid transparent;
|
||||
}
|
||||
|
||||
.links span {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.links a:hover {
|
||||
color: var(--font-color100);
|
||||
border-bottom: 2px solid var(--primary400);
|
||||
}
|
||||
|
||||
.links .selected {
|
||||
color: var(--font-color100);
|
||||
border-bottom: 2px solid var(--primary400);
|
||||
}
|
||||
|
||||
.actions,
|
||||
.mobile {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.mobile {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.links,
|
||||
.actions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobile {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { useState } from 'react';
|
||||
import { Icon, Text, TooltipPopup } from 'react-basics';
|
||||
import classNames from 'classnames';
|
||||
import { useRouter } from 'next/router';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
import Icons from 'components/icons';
|
||||
import styles from './NavGroup.module.css';
|
||||
|
|
@ -13,7 +13,7 @@ export function NavGroup({
|
|||
allowExpand = true,
|
||||
minimized = false,
|
||||
}) {
|
||||
const { pathname } = useRouter();
|
||||
const pathname = usePathname();
|
||||
const [expanded, setExpanded] = useState(defaultExpanded);
|
||||
|
||||
const handleExpand = () => setExpanded(state => !state);
|
||||
|
|
|
|||
|
|
@ -5,4 +5,7 @@
|
|||
background: var(--base50);
|
||||
position: relative;
|
||||
height: 100%;
|
||||
max-width: 1320px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,27 @@
|
|||
import { ReactNode } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Banner, Loading } from 'react-basics';
|
||||
import useMessages from 'components/hooks/useMessages';
|
||||
import styles from './Page.module.css';
|
||||
|
||||
export function Page({ className, error, loading, children }) {
|
||||
export function Page({
|
||||
className,
|
||||
error,
|
||||
isLoading,
|
||||
children,
|
||||
}: {
|
||||
className?: string;
|
||||
error?: unknown;
|
||||
isLoading?: boolean;
|
||||
children?: ReactNode;
|
||||
}) {
|
||||
const { formatMessage, messages } = useMessages();
|
||||
|
||||
if (error) {
|
||||
return <Banner variant="error">{formatMessage(messages.error)}</Banner>;
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
if (isLoading) {
|
||||
return <Loading icon="spinner" size="xl" position="page" />;
|
||||
}
|
||||
|
||||
|
|
@ -1,8 +1,14 @@
|
|||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import React, { ReactNode } from 'react';
|
||||
import styles from './PageHeader.module.css';
|
||||
|
||||
export function PageHeader({ title, children, className }) {
|
||||
export interface PageHeaderProps {
|
||||
title?: string;
|
||||
className?: string;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function PageHeader({ title, className, children }: PageHeaderProps) {
|
||||
return (
|
||||
<div className={classNames(styles.header, className)}>
|
||||
{title && <div className={styles.title}>{title}</div>}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
import { Column, Row } from 'react-basics';
|
||||
import styles from './ReportsLayout.module.css';
|
||||
|
||||
export function ReportsLayout({ children, filter, header }) {
|
||||
return (
|
||||
<>
|
||||
<Row>{header}</Row>
|
||||
<Row>
|
||||
{filter && (
|
||||
<Column className={styles.filter} defaultSize={12} md={4} lg={3} xl={3}>
|
||||
<h2>Filters</h2>
|
||||
{filter}
|
||||
</Column>
|
||||
)}
|
||||
<Column className={styles.content} defaultSize={12} md={8} lg={9} xl={9}>
|
||||
{children}
|
||||
</Column>
|
||||
</Row>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ReportsLayout;
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
.filter {
|
||||
margin-top: 30px;
|
||||
min-width: 200px;
|
||||
max-width: 100vw;
|
||||
padding: 10px;
|
||||
background: var(--base50);
|
||||
border-radius: 5px;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.filter h2 {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.content {
|
||||
min-height: 50vh;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.menu {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
import { Row, Column } from 'react-basics';
|
||||
import { useRouter } from 'next/router';
|
||||
import SideNav from './SideNav';
|
||||
import useUser from 'components/hooks/useUser';
|
||||
import useMessages from 'components/hooks/useMessages';
|
||||
import styles from './SettingsLayout.module.css';
|
||||
|
||||
export function SettingsLayout({ children }) {
|
||||
const { user } = useUser();
|
||||
const { pathname } = useRouter();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const cloudMode = Boolean(process.env.cloudMode);
|
||||
|
||||
const items = [
|
||||
{ key: 'websites', label: formatMessage(labels.websites), url: '/settings/websites' },
|
||||
{ key: 'teams', label: formatMessage(labels.teams), url: '/settings/teams' },
|
||||
user.isAdmin && { key: 'users', label: formatMessage(labels.users), url: '/settings/users' },
|
||||
{ key: 'profile', label: formatMessage(labels.profile), url: '/settings/profile' },
|
||||
].filter(n => n);
|
||||
|
||||
const getKey = () => items.find(({ url }) => pathname === url)?.key;
|
||||
|
||||
return (
|
||||
<Row>
|
||||
{!cloudMode && (
|
||||
<Column className={styles.menu} defaultSize={12} md={4} lg={3} xl={2}>
|
||||
<SideNav items={items} shallow={true} selectedKey={getKey()} />
|
||||
</Column>
|
||||
)}
|
||||
<Column className={styles.content} defaultSize={12} md={8} lg={9} xl={10}>
|
||||
{children}
|
||||
</Column>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
export default SettingsLayout;
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
.menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: 40px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.content {
|
||||
min-height: 50vh;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.menu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
import { Container } from 'react-basics';
|
||||
import Header from './Header';
|
||||
import Footer from './Footer';
|
||||
|
||||
export function ShareLayout({ children }) {
|
||||
return (
|
||||
<Container>
|
||||
<Header />
|
||||
<main>{children}</main>
|
||||
<Footer />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default ShareLayout;
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
import classNames from 'classnames';
|
||||
import { Menu, Item } from 'react-basics';
|
||||
import { useRouter } from 'next/router';
|
||||
import Link from 'next/link';
|
||||
import styles from './SideNav.module.css';
|
||||
|
||||
export function SideNav({
|
||||
selectedKey,
|
||||
items,
|
||||
shallow = true,
|
||||
scroll = false,
|
||||
onSelect = () => {},
|
||||
}) {
|
||||
const { asPath } = useRouter();
|
||||
return (
|
||||
<Menu items={items} selectedKey={selectedKey} className={styles.menu} onSelect={onSelect}>
|
||||
{({ key, label, url }) => (
|
||||
<Item
|
||||
key={key}
|
||||
className={classNames(styles.item, { [styles.selected]: asPath.startsWith(url) })}
|
||||
>
|
||||
<Link href={url} shallow={shallow} scroll={scroll}>
|
||||
{label}
|
||||
</Link>
|
||||
</Item>
|
||||
)}
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
||||
export default SideNav;
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
.menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.item a {
|
||||
color: var(--font-color100);
|
||||
flex: 1;
|
||||
padding: var(--size300) var(--size600);
|
||||
}
|
||||
|
||||
.item {
|
||||
padding: 0;
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.selected {
|
||||
font-weight: 700;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue