Initial commit

Created from https://vercel.com/new
This commit is contained in:
WaylonWalker 2021-12-08 18:25:42 +00:00
commit 4047a7ec23
378 changed files with 29334 additions and 0 deletions

View file

@ -0,0 +1,17 @@
import React from 'react';
import classNames from 'classnames';
import styles from './ButtonLayout.module.css';
export default function ButtonLayout({ className, children, align = 'center' }) {
return (
<div
className={classNames(styles.buttons, className, {
[styles.left]: align === 'left',
[styles.center]: align === 'center',
[styles.right]: align === 'right',
})}
>
{children}
</div>
);
}

View file

@ -0,0 +1,20 @@
.buttons {
display: flex;
align-items: center;
}
.buttons button + * {
margin-left: 10px;
}
.center {
justify-content: center;
}
.left {
justify-content: flex-start;
}
.right {
justify-content: flex-end;
}

View file

@ -0,0 +1,36 @@
import React from 'react';
import classNames from 'classnames';
import { FormattedMessage } from 'react-intl';
import Link from 'components/common/Link';
import styles from './Footer.module.css';
import useVersion from 'hooks/useVersion';
import useLocale from 'hooks/useLocale';
export default function Footer() {
const { current } = useVersion();
const { dir } = useLocale();
return (
<footer className="container" dir={dir}>
<div className={classNames(styles.footer, 'row')}>
<div className="col-12 col-md-4" />
<div className="col-12 col-md-4">
<FormattedMessage
id="message.powered-by"
defaultMessage="Powered by {name}"
values={{
name: (
<Link href="https://umami.is">
<b>umami</b>
</Link>
),
}}
/>
</div>
<div className={classNames(styles.version, 'col-12 col-md-4')}>
<Link href={`https://github.com/mikecao/umami/releases`}>{`v${current}`}</Link>
</div>
</div>
</footer>
);
}

View file

@ -0,0 +1,18 @@
.footer {
display: flex;
justify-content: space-between;
align-items: center;
font-size: var(--font-size-small);
min-height: 100px;
text-align: center;
}
.version {
text-align: right;
}
@media only screen and (max-width: 768px) {
.version {
text-align: center;
}
}

View file

@ -0,0 +1,32 @@
import React from 'react';
import { useSpring, animated } from 'react-spring';
import classNames from 'classnames';
import { ErrorMessage } from 'formik';
import styles from './FormLayout.module.css';
export default function FormLayout({ className, children }) {
return <div className={classNames(styles.form, className)}>{children}</div>;
}
export const FormButtons = ({ className, children }) => (
<div className={classNames(styles.buttons, className)}>{children}</div>
);
export const FormError = ({ name }) => {
return <ErrorMessage name={name}>{msg => <ErrorTag msg={msg} />}</ErrorMessage>;
};
const ErrorTag = ({ msg }) => {
const props = useSpring({ opacity: 1, from: { opacity: 0 } });
return (
<animated.div className={styles.error} style={props}>
<div className={styles.msg}>{msg}</div>
</animated.div>
);
};
export const FormRow = ({ children }) => <div className={styles.row}>{children}</div>;
export const FormMessage = ({ children }) =>
children ? <div className={styles.message}>{children}</div> : null;

View file

@ -0,0 +1,94 @@
.form {
display: flex;
flex-direction: column;
justify-content: center;
}
.form label {
display: block;
min-width: 100px;
}
.row {
display: flex;
flex-wrap: wrap;
align-items: center;
position: relative;
margin-bottom: 20px;
line-height: 1.8;
padding: 0 20px;
}
.row > div {
position: relative;
flex: 1 1;
}
.row > div > input {
width: 100%;
min-width: 240px;
}
.buttons {
display: flex;
justify-content: center;
margin-top: 20px;
}
.buttons button + button {
margin-left: 10px;
}
.error {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
top: 0;
left: calc(100% + 16px);
bottom: 0;
z-index: 1;
}
.msg {
color: var(--msgColor);
background: var(--red400);
font-size: var(--font-size-small);
padding: 4px 8px;
border-radius: 4px;
white-space: nowrap;
}
.error:after {
content: '';
position: absolute;
top: 0;
left: -5px;
bottom: 0;
margin: auto;
width: 10px;
height: 10px;
background: var(--red400);
transform: rotate(45deg);
}
.message {
text-align: center;
margin: 20px 0;
padding: 4px 8px;
border-radius: 4px;
color: var(--gray50);
background: var(--gray800);
}
@media only screen and (max-width: 576px) {
.error {
align-items: flex-start;
top: calc(100% + 7px);
left: 0;
}
.error:after {
left: 10px;
}
}

View file

@ -0,0 +1,31 @@
import React from 'react';
import classNames from 'classnames';
import styles from './GridLayout.module.css';
export default function GridLayout({ className, children }) {
return <div className={classNames(styles.grid, className)}>{children}</div>;
}
export const GridRow = ({ className, children }) => {
return <div className={classNames(styles.row, className, 'row')}>{children}</div>;
};
export const GridColumn = ({ xs, sm, md, lg, xl, className, children }) => {
const classes = [];
classes.push(xs ? `col-${xs}` : 'col-12');
if (sm) {
classes.push(`col-sm-${sm}`);
}
if (md) {
classes.push(`col-md-${md}`);
}
if (lg) {
classes.push(`col-lg-${lg}`);
}
if (xl) {
classes.push(`col-lg-${xl}`);
}
return <div className={classNames(styles.col, classes, className)}>{children}</div>;
};

View file

@ -0,0 +1,40 @@
.grid {
display: flex;
flex-direction: column;
}
.col {
display: flex;
flex-direction: column;
}
.row {
border-top: 1px solid var(--gray300);
min-height: 430px;
}
.row > .col {
border-left: 1px solid var(--gray300);
padding: 20px;
}
.row > .col:first-child {
border-left: 0;
padding-left: 0;
}
.row > .col:last-child {
padding-right: 0;
}
@media only screen and (max-width: 992px) {
.row {
border: 0;
}
.row > .col {
border-top: 1px solid var(--gray300);
border-left: 0;
padding: 20px 0;
}
}

View file

@ -0,0 +1,71 @@
import React, { useState } from 'react';
import { FormattedMessage } from 'react-intl';
import { useSelector } from 'react-redux';
import classNames from 'classnames';
import Link from 'components/common/Link';
import Icon from 'components/common/Icon';
import LanguageButton from 'components/settings/LanguageButton';
import ThemeButton from 'components/settings/ThemeButton';
import UpdateNotice from 'components/common/UpdateNotice';
import UserButton from 'components/settings/UserButton';
import Button from 'components/common/Button';
import Logo from 'assets/logo.svg';
import styles from './Header.module.css';
import useLocale from 'hooks/useLocale';
import XMark from 'assets/xmark.svg';
import Bars from 'assets/bars.svg';
export default function Header() {
const user = useSelector(state => state.user);
const [active, setActive] = useState(false);
const { dir } = useLocale();
function handleClick() {
setActive(state => !state);
}
return (
<nav className="container" dir={dir}>
{user?.is_admin && <UpdateNotice />}
<div className={classNames(styles.header, 'row align-items-center')}>
<div className={styles.nav}>
<div className="">
<div className={styles.title}>
<Icon icon={<Logo />} size="large" className={styles.logo} />
<Link href={user ? '/' : 'https://umami.is'}>umami</Link>
</div>
</div>
<Button
className={styles.burger}
icon={active ? <XMark /> : <Bars />}
onClick={handleClick}
/>
{user && (
<div className={styles.items}>
<div className={active ? classNames(styles.active) : ''}>
<Link href="/dashboard">
<FormattedMessage id="label.dashboard" defaultMessage="Dashboard" />
</Link>
<Link href="/realtime">
<FormattedMessage id="label.realtime" defaultMessage="Realtime" />
</Link>
<Link href="/settings">
<FormattedMessage id="label.settings" defaultMessage="Settings" />
</Link>
</div>
</div>
)}
<div className={styles.items}>
<div className={active ? classNames(styles.active) : ''}>
<div className={styles.buttons}>
<ThemeButton />
<LanguageButton menuAlign="right" />
{user && <UserButton />}
</div>
</div>
</div>
</div>
</div>
</nav>
);
}

View file

@ -0,0 +1,116 @@
.navbar {
align-items: stretch;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
width: 100%;
}
.burger {
display: none;
}
.header {
display: flex;
min-height: 100px;
width: 100%;
}
.title {
font-size: var(--font-size-large);
display: flex;
align-items: center;
line-height: 1.4;
}
.logo {
margin-right: 12px;
}
.nav {
display: flex;
align-items: center;
font-size: var(--font-size-normal);
font-weight: 600;
width: 100%;
justify-content: space-between;
}
.items {
display: flex;
justify-content: center;
align-items: center;
font-size: var(--font-size-normal);
font-weight: 600;
}
.nav a + a {
margin-left: 40px;
}
.buttons {
display: flex;
justify-content: flex-end;
align-items: center;
}
@media only screen and (max-width: 992px) {
.nav {
font-size: var(--font-size-large);
justify-content: space-between;
margin: 20px 0;
}
.items {
flex-wrap: wrap;
}
}
@media only screen and (max-width: 768px) {
.header {
padding: 0 15px;
}
.title {
padding: 0.5rem;
margin-bottom: 0.5rem;
}
.nav {
font-size: var(--font-size-normal);
flex-wrap: wrap;
justify-content: center;
flex-direction: column;
position: relative;
}
.items {
display: flex;
justify-content: unset;
font-size: var(--font-size-normal);
font-weight: 600;
}
.items > div {
display: none;
}
.header .active {
display: inherit;
width: 100%;
}
.items a {
width: 100%;
}
.burger {
display: block;
background: none;
border: 1px solid var(--gray900);
border-radius: 4px;
cursor: pointer;
position: absolute;
top: 0;
right: 0;
}
}

View file

@ -0,0 +1,24 @@
import React from 'react';
import Head from 'next/head';
import Header from 'components/layout/Header';
import Footer from 'components/layout/Footer';
import useLocale from 'hooks/useLocale';
export default function Layout({ title, children, header = true, footer = true }) {
const { dir } = useLocale();
return (
<>
<Head>
<title>umami{title && ` - ${title}`}</title>
</Head>
{header && <Header />}
<main className="container" dir={dir}>
{children}
</main>
{footer && <Footer />}
<div id="__modals" dir={dir} />
</>
);
}

View file

@ -0,0 +1,6 @@
.layout {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
}

View file

@ -0,0 +1,39 @@
import React from 'react';
import { useRouter } from 'next/router';
import classNames from 'classnames';
import NavMenu from 'components/common/NavMenu';
import styles from './MenuLayout.module.css';
export default function MenuLayout({
menu,
selectedOption,
className,
menuClassName,
contentClassName,
children,
replace = false,
}) {
const router = useRouter();
function handleSelect(url) {
if (replace) {
router.replace(url);
} else {
router.push(url);
}
}
return (
<div className={classNames(styles.container, className, 'row')}>
<NavMenu
options={menu}
selectedOption={selectedOption}
className={classNames(styles.menu, menuClassName, 'col-12 col-lg-2')}
onSelect={handleSelect}
/>
<div className={classNames(styles.content, contentClassName, 'col-12 col-lg-10')}>
{children}
</div>
</div>
);
}

View file

@ -0,0 +1,31 @@
.container {
display: flex;
flex: 1;
height: 100%;
}
.container .menu {
padding: 30px 0;
border: 0;
}
.container .content {
flex: 1;
position: relative;
border-left: 1px solid var(--gray300);
padding-left: 30px;
margin-left: 30px;
}
@media only screen and (max-width: 992px) {
.container {
height: auto;
}
.container .content {
border-top: 1px solid var(--gray300);
border-left: 0;
padding-left: 0;
margin-left: 0;
}
}

26
components/layout/Page.js Normal file
View file

@ -0,0 +1,26 @@
import React from 'react';
import classNames from 'classnames';
import styles from './Page.module.css';
export default class Page extends React.Component {
getSnapshotBeforeUpdate() {
if (window.pageXOffset === 0 && window.pageYOffset === 0) return null;
// Return the scrolled position as the snapshot value
return { x: window.pageXOffset, y: window.pageYOffset };
}
/* eslint-disable no-unused-vars */
componentDidUpdate(prevProps, prevState, snapshot) {
if (snapshot !== null) {
// Restore the scrolled position after re-rendering
window.scrollTo(snapshot.x, snapshot.y);
}
}
/* eslint-enable no-unused-vars */
render() {
const { className, children } = this.props;
return <div className={classNames(styles.page, className)}>{children}</div>;
}
}

View file

@ -0,0 +1,7 @@
.page {
flex: 1;
display: flex;
flex-direction: column;
padding: 0 30px;
background: var(--gray50);
}

View file

@ -0,0 +1,7 @@
import React from 'react';
import classNames from 'classnames';
import styles from './PageHeader.module.css';
export default function PageHeader({ children, className }) {
return <div className={classNames(styles.header, className)}>{children}</div>;
}

View file

@ -0,0 +1,9 @@
.header {
display: flex;
justify-content: space-between;
align-items: center;
align-content: center;
min-height: 80px;
align-self: stretch;
font-weight: bold;
}