Refactored to use app folder.
This commit is contained in:
1 parent
40cfcd41e9
commit
9a52cdd2e1
258 files changed
+2025
-2258
No files matched your search
+24
-8
@@ -6,7 +6,7 @@ const pkg = require('./package.json');
|
||||
const contentSecurityPolicy = `
|
||||
default-src 'self';
|
||||
img-src *;
|
||||
script-src 'self' 'unsafe-eval';
|
||||
script-src 'self' 'unsafe-eval' 'unsafe-inline';
|
||||
style-src 'self' 'unsafe-inline';
|
||||
connect-src 'self' api.umami.is;
|
||||
frame-ancestors 'self' ${process.env.ALLOWED_FRAME_URLS};
|
||||
@@ -74,16 +74,20 @@ if (process.env.CLOUD_MODE && process.env.CLOUD_URL && process.env.DISABLE_LOGIN
|
||||
});
|
||||
}
|
||||
|
||||
const basePath = process.env.BASE_PATH;
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const config = {
|
||||
env: {
|
||||
cloudMode: process.env.CLOUD_MODE,
|
||||
basePath: basePath || '',
|
||||
cloudMode: !!process.env.CLOUD_MODE,
|
||||
cloudUrl: process.env.CLOUD_URL,
|
||||
configUrl: '/config',
|
||||
currentVersion: pkg.version,
|
||||
defaultLocale: process.env.DEFAULT_LOCALE,
|
||||
isProduction: process.env.NODE_ENV === 'production',
|
||||
},
|
||||
basePath: process.env.BASE_PATH,
|
||||
basePath,
|
||||
output: 'standalone',
|
||||
eslint: {
|
||||
ignoreDuringBuilds: true,
|
||||
@@ -92,11 +96,23 @@ const config = {
|
||||
ignoreBuildErrors: true,
|
||||
},
|
||||
webpack(config) {
|
||||
config.module.rules.push({
|
||||
test: /\.svg$/,
|
||||
issuer: /\.{js|jsx|ts|tsx}$/,
|
||||
use: ['@svgr/webpack'],
|
||||
});
|
||||
const fileLoaderRule = config.module.rules.find(rule => rule.test?.test?.('.svg'));
|
||||
|
||||
config.module.rules.push(
|
||||
{
|
||||
...fileLoaderRule,
|
||||
test: /\.svg$/i,
|
||||
resourceQuery: /url/,
|
||||
},
|
||||
{
|
||||
test: /\.svg$/i,
|
||||
issuer: fileLoaderRule.issuer,
|
||||
resourceQuery: { not: [...fileLoaderRule.resourceQuery.not, /url/] },
|
||||
use: ['@svgr/webpack'],
|
||||
},
|
||||
);
|
||||
|
||||
fileLoaderRule.exclude = /\.svg$/i;
|
||||
|
||||
config.resolve.alias['public'] = path.resolve('./public');
|
||||
|
||||
|
||||
+8
-8
@@ -64,7 +64,7 @@
|
||||
"@fontsource/inter": "^4.5.15",
|
||||
"@prisma/client": "5.3.1",
|
||||
"@tanstack/react-query": "^4.33.0",
|
||||
"@umami/prisma-client": "^0.2.0",
|
||||
"@umami/prisma-client": "^0.3.0",
|
||||
"@umami/redis-client": "^0.15.0",
|
||||
"chalk": "^4.1.1",
|
||||
"chart.js": "^4.2.1",
|
||||
@@ -91,7 +91,7 @@
|
||||
"kafkajs": "^2.1.0",
|
||||
"maxmind": "^4.3.6",
|
||||
"moment-timezone": "^0.5.35",
|
||||
"next": "13.5.2",
|
||||
"next": "13.5.3",
|
||||
"next-basics": "^0.36.0",
|
||||
"node-fetch": "^3.2.8",
|
||||
"npm-run-all": "^4.1.5",
|
||||
@@ -100,7 +100,7 @@
|
||||
"react-beautiful-dnd": "^13.1.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-error-boundary": "^4.0.4",
|
||||
"react-intl": "^5.24.7",
|
||||
"react-intl": "^6.4.7",
|
||||
"react-simple-maps": "^2.3.0",
|
||||
"react-spring": "^9.4.4",
|
||||
"react-use-measure": "^2.0.4",
|
||||
@@ -123,12 +123,12 @@
|
||||
"@rollup/plugin-node-resolve": "^15.2.0",
|
||||
"@rollup/plugin-replace": "^5.0.2",
|
||||
"@svgr/rollup": "^8.1.0",
|
||||
"@svgr/webpack": "^6.2.1",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"@types/node": "^18.11.9",
|
||||
"@types/react": "^18.0.25",
|
||||
"@types/react-dom": "^18.0.8",
|
||||
"@typescript-eslint/eslint-plugin": "^5.50.0",
|
||||
"@typescript-eslint/parser": "^5.50.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.3",
|
||||
"@typescript-eslint/parser": "^6.7.3",
|
||||
"cross-env": "^7.0.3",
|
||||
"esbuild": "^0.17.17",
|
||||
"eslint": "^8.33.0",
|
||||
@@ -138,8 +138,8 @@
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"extract-react-intl-messages": "^4.1.1",
|
||||
"husky": "^7.0.0",
|
||||
"lint-staged": "^11.0.0",
|
||||
"husky": "^8.0.3",
|
||||
"lint-staged": "^14.0.1",
|
||||
"postcss": "^8.4.21",
|
||||
"postcss-flexbugs-fixes": "^5.0.2",
|
||||
"postcss-import": "^15.1.0",
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
'use client';
|
||||
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 ProfileButton from 'components/input/ProfileButton';
|
||||
import useMessages from 'components/hooks/useMessages';
|
||||
import HamburgerButton from 'components/common/HamburgerButton';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import styles from './NavBar.module.css';
|
||||
|
||||
export function NavBar() {
|
||||
const pathname = usePathname();
|
||||
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={styles.navbar}>
|
||||
<div className={styles.logo}>
|
||||
<Icon size="lg">
|
||||
<Icons.Logo />
|
||||
</Icon>
|
||||
<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>
|
||||
<div className={styles.actions}>
|
||||
<ThemeButton />
|
||||
<LanguageButton />
|
||||
<ProfileButton />
|
||||
</div>
|
||||
<div className={styles.mobile}>
|
||||
<HamburgerButton />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default NavBar;
|
||||
@@ -1,7 +1,7 @@
|
||||
.navbar {
|
||||
display: grid;
|
||||
grid-template-columns: max-content 1fr 1fr;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: 60px;
|
||||
background: var(--base75);
|
||||
@@ -9,17 +9,6 @@
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.left,
|
||||
.right {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -35,29 +24,24 @@
|
||||
flex-direction: row;
|
||||
gap: 30px;
|
||||
padding: 0 40px;
|
||||
flex: 1;
|
||||
font-weight: 700;
|
||||
max-height: 60px;
|
||||
}
|
||||
|
||||
.links a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
line-height: 60px;
|
||||
.links a,
|
||||
.links a:active,
|
||||
.links a:visited {
|
||||
color: var(--font-color200);
|
||||
line-height: 60px;
|
||||
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 {
|
||||
.links a.selected {
|
||||
color: var(--font-color100);
|
||||
border-bottom: 2px solid var(--primary400);
|
||||
}
|
||||
@@ -68,7 +52,6 @@
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.mobile {
|
||||
@@ -76,6 +59,10 @@
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.navbar {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.links,
|
||||
.actions {
|
||||
display: none;
|
||||
@@ -0,0 +1,27 @@
|
||||
'use client';
|
||||
import Script from 'next/script';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import UpdateNotice from 'components/common/UpdateNotice';
|
||||
import { useRequireLogin, useConfig } from 'components/hooks';
|
||||
|
||||
export function Shell({ children }) {
|
||||
const { user } = useRequireLogin();
|
||||
const config = useConfig();
|
||||
const pathname = usePathname();
|
||||
|
||||
if (!user || !config) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
<UpdateNotice user={user} config={config} />
|
||||
{process.env.NODE_ENV === 'production' && !pathname.includes('/share/') && (
|
||||
<Script src={`telemetry.js`} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Shell;
|
||||
@@ -2,11 +2,11 @@ import WebsiteSelect from 'components/input/WebsiteSelect';
|
||||
import Page from 'components/layout/Page';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import EventsChart from 'components/metrics/EventsChart';
|
||||
import WebsiteChart from 'components/pages/websites/WebsiteChart';
|
||||
import WebsiteChart from '../websites/[id]/WebsiteChart';
|
||||
import useApi from 'components/hooks/useApi';
|
||||
import Head from 'next/head';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import Script from 'next/script';
|
||||
import { Button, Column, Row } from 'react-basics';
|
||||
import styles from './TestConsole.module.css';
|
||||
File renamed without changes.
@@ -0,0 +1,15 @@
|
||||
import TestConsole from '../TestConsole';
|
||||
|
||||
async function getEnabled() {
|
||||
return !!process.env.ENABLE_TEST_CONSOLE;
|
||||
}
|
||||
|
||||
export default async function ConsolePage() {
|
||||
const enabled = await getEnabled();
|
||||
|
||||
if (!enabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <TestConsole />;
|
||||
}
|
||||
+12
-12
@@ -1,11 +1,11 @@
|
||||
import { Button, Icon, Icons, Text } from 'react-basics';
|
||||
'use client';
|
||||
import { Button, Icon, Icons, Loading, Text } from 'react-basics';
|
||||
import Link from 'next/link';
|
||||
import Page from 'components/layout/Page';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import Pager from 'components/common/Pager';
|
||||
import WebsiteChartList from 'components/pages/websites/WebsiteChartList';
|
||||
import DashboardSettingsButton from 'components/pages/dashboard/DashboardSettingsButton';
|
||||
import DashboardEdit from 'components/pages/dashboard/DashboardEdit';
|
||||
import WebsiteChartList from '../websites/[id]/WebsiteChartList';
|
||||
import DashboardSettingsButton from 'app/(app)/dashboard/DashboardSettingsButton';
|
||||
import DashboardEdit from 'app/(app)/dashboard/DashboardEdit';
|
||||
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
|
||||
import useApi from 'components/hooks/useApi';
|
||||
import useDashboard from 'store/dashboard';
|
||||
@@ -20,18 +20,18 @@ export function Dashboard() {
|
||||
const { get, useQuery } = useApi();
|
||||
const { page, handlePageChange } = useApiFilter();
|
||||
const pageSize = 10;
|
||||
const {
|
||||
data: result,
|
||||
isLoading,
|
||||
error,
|
||||
} = useQuery(['websites', page, pageSize], () =>
|
||||
const { data: result, isLoading } = useQuery(['websites', page, pageSize], () =>
|
||||
get('/websites', { includeTeams: 1, page, pageSize }),
|
||||
);
|
||||
const { data, count } = result || {};
|
||||
const hasData = data && data?.length !== 0;
|
||||
|
||||
if (isLoading) {
|
||||
return <Loading size="lg" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Page loading={isLoading} error={error}>
|
||||
<>
|
||||
<PageHeader title={formatMessage(labels.dashboard)}>
|
||||
{!editing && hasData && <DashboardSettingsButton />}
|
||||
</PageHeader>
|
||||
@@ -63,7 +63,7 @@ export function Dashboard() {
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Page>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,10 @@
|
||||
import Dashboard from 'app/(app)/dashboard/Dashboard';
|
||||
import { Metadata } from 'next';
|
||||
|
||||
export default function () {
|
||||
return <Dashboard />;
|
||||
}
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Dashboard | umami',
|
||||
};
|
||||
File renamed without changes.
@@ -0,0 +1,20 @@
|
||||
'use client';
|
||||
import Shell from './Shell';
|
||||
import NavBar from './NavBar';
|
||||
import Page from 'components/layout/Page';
|
||||
import styles from './layout.module.css';
|
||||
|
||||
export default function AppLayout({ children }) {
|
||||
return (
|
||||
<Shell>
|
||||
<main className={styles.layout}>
|
||||
<nav className={styles.nav}>
|
||||
<NavBar />
|
||||
</nav>
|
||||
<section className={styles.body}>
|
||||
<Page>{children}</Page>
|
||||
</section>
|
||||
</main>
|
||||
</Shell>
|
||||
);
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,20 +1,18 @@
|
||||
'use client';
|
||||
import { createContext } from 'react';
|
||||
import Page from 'components/layout/Page';
|
||||
import styles from './reports.module.css';
|
||||
import { useReport } from 'components/hooks';
|
||||
import styles from './Report.module.css';
|
||||
|
||||
export const ReportContext = createContext(null);
|
||||
|
||||
export function Report({ reportId, defaultParameters, children, ...props }) {
|
||||
const report = useReport(reportId, defaultParameters);
|
||||
|
||||
//console.log({ report });
|
||||
|
||||
return (
|
||||
<ReportContext.Provider value={{ ...report }}>
|
||||
<Page {...props} className={styles.container}>
|
||||
<div {...props} className={styles.container}>
|
||||
{children}
|
||||
</Page>
|
||||
</div>
|
||||
</ReportContext.Provider>
|
||||
);
|
||||
}
|
||||
File renamed without changes.
@@ -1,4 +1,4 @@
|
||||
import styles from './reports.module.css';
|
||||
import styles from './Report.module.css';
|
||||
|
||||
export function ReportBody({ children }) {
|
||||
return <div className={styles.body}>{children}</div>;
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
import { useContext } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { Icon, LoadingButton, InlineEditField, useToasts } from 'react-basics';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import { useMessages, useApi } from 'components/hooks';
|
||||
import { ReportContext } from './Report';
|
||||
import styles from './ReportHeader.module.css';
|
||||
import reportStyles from './reports.module.css';
|
||||
import reportStyles from './Report.module.css';
|
||||
|
||||
export function ReportHeader({ icon }) {
|
||||
const { report, updateReport } = useContext(ReportContext);
|
||||
File renamed without changes.
@@ -1,4 +1,4 @@
|
||||
import styles from './reports.module.css';
|
||||
import styles from './Report.module.css';
|
||||
|
||||
export function ReportMenu({ children }) {
|
||||
return <div className={styles.menu}>{children}</div>;
|
||||
@@ -0,0 +1,24 @@
|
||||
'use client';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import Link from 'next/link';
|
||||
import { Button, Icon, Icons, Text } from 'react-basics';
|
||||
import { useMessages } from 'components/hooks';
|
||||
|
||||
export function ReportsHeader() {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<PageHeader title={formatMessage(labels.reports)}>
|
||||
<Link href="/reports/create">
|
||||
<Button variant="primary">
|
||||
<Icon>
|
||||
<Icons.Plus />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.createReport)}</Text>
|
||||
</Button>
|
||||
</Link>
|
||||
</PageHeader>
|
||||
);
|
||||
}
|
||||
|
||||
export default ReportsHeader;
|
||||
@@ -0,0 +1,37 @@
|
||||
'use client';
|
||||
import { useApi } from 'components/hooks';
|
||||
import ReportsTable from './ReportsTable';
|
||||
import useFilterQuery from 'components/hooks/useFilterQuery';
|
||||
import DataTable from 'components/common/DataTable';
|
||||
|
||||
function useReports() {
|
||||
const { get, del, useMutation } = useApi();
|
||||
const { mutate } = useMutation(reportId => del(`/reports/${reportId}`));
|
||||
const reports = useFilterQuery(['reports'], params => get(`/reports`, params));
|
||||
|
||||
const deleteReport = id => {
|
||||
mutate(id, {
|
||||
onSuccess: () => {
|
||||
reports.refetch();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return { reports, deleteReport };
|
||||
}
|
||||
|
||||
export default function ReportsList() {
|
||||
const { reports, deleteReport } = useReports();
|
||||
|
||||
const handleDelete = async (id, callback) => {
|
||||
await deleteReport(id);
|
||||
await reports.refetch();
|
||||
callback?.();
|
||||
};
|
||||
|
||||
return (
|
||||
<DataTable {...reports.getProps()}>
|
||||
{({ data }) => <ReportsTable data={data} showDomain={true} onDelete={handleDelete} />}
|
||||
</DataTable>
|
||||
);
|
||||
}
|
||||
File renamed without changes.
@@ -0,0 +1,26 @@
|
||||
'use client';
|
||||
import FunnelReport from '../funnel/FunnelReport';
|
||||
import EventDataReport from '../event-data/EventDataReport';
|
||||
import InsightsReport from '../insights/InsightsReport';
|
||||
import RetentionReport from '../retention/RetentionReport';
|
||||
import { useApi } from 'components/hooks';
|
||||
|
||||
const reports = {
|
||||
funnel: FunnelReport,
|
||||
'event-data': EventDataReport,
|
||||
insights: InsightsReport,
|
||||
retention: RetentionReport,
|
||||
};
|
||||
|
||||
export default function ReportDetails({ reportId }) {
|
||||
const { get, useQuery } = useApi();
|
||||
const { data: report } = useQuery(['reports', reportId], () => get(`/reports/${reportId}`));
|
||||
|
||||
if (!report) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const ReportComponent = reports[report.type];
|
||||
|
||||
return <ReportComponent reportId={reportId} />;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import ReportDetails from './ReportDetails';
|
||||
import { Metadata } from 'next';
|
||||
|
||||
export default function ReportDetailsPage({ params: { id } }) {
|
||||
if (!id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <ReportDetails reportId={id} />;
|
||||
}
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Reports | umami',
|
||||
};
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
import Link from 'next/link';
|
||||
import { Button, Icons, Text, Icon } from 'react-basics';
|
||||
import Page from 'components/layout/Page';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import Funnel from 'assets/funnel.svg';
|
||||
import Lightbulb from 'assets/lightbulb.svg';
|
||||
@@ -57,7 +57,7 @@ export function ReportTemplates({ showHeader = true }) {
|
||||
];
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<>
|
||||
{showHeader && <PageHeader title={formatMessage(labels.reports)} />}
|
||||
<div className={styles.reports}>
|
||||
{reports.map(({ title, description, url, icon }) => {
|
||||
@@ -66,7 +66,7 @@ export function ReportTemplates({ showHeader = true }) {
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Page>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
File renamed without changes.
@@ -0,0 +1,10 @@
|
||||
import ReportTemplates from './ReportTemplates';
|
||||
import { Metadata } from 'next';
|
||||
|
||||
export default function ReportsCreatePage() {
|
||||
return <ReportTemplates />;
|
||||
}
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Create Report | umami',
|
||||
};
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { useContext, useRef } from 'react';
|
||||
import { useApi, useMessages } from 'components/hooks';
|
||||
import { Form, FormRow, FormButtons, SubmitButton, PopupTrigger, Icon, Popup } from 'react-basics';
|
||||
import { ReportContext } from 'components/pages/reports/Report';
|
||||
import { ReportContext } from '../Report';
|
||||
import Empty from 'components/common/Empty';
|
||||
import { DATA_TYPES, REPORT_PARAMETERS } from 'lib/constants';
|
||||
import Icons from 'components/icons';
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+1
-1
@@ -13,7 +13,7 @@ import {
|
||||
} from 'react-basics';
|
||||
import Icons from 'components/icons';
|
||||
import UrlAddForm from './UrlAddForm';
|
||||
import { ReportContext } from 'components/pages/reports/Report';
|
||||
import { ReportContext } from '../Report';
|
||||
import BaseParameters from '../BaseParameters';
|
||||
import ParameterList from '../ParameterList';
|
||||
import PopupForm from '../PopupForm';
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
'use client';
|
||||
import FunnelChart from './FunnelChart';
|
||||
import FunnelTable from './FunnelTable';
|
||||
import FunnelParameters from './FunnelParameters';
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,10 @@
|
||||
import FunnelReport from './FunnelReport';
|
||||
import { Metadata } from 'next';
|
||||
|
||||
export default function FunnelReportPage() {
|
||||
return <FunnelReport reportId={null} />;
|
||||
}
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Funnel Report | umami',
|
||||
};
|
||||
+1
-1
@@ -10,7 +10,7 @@ import {
|
||||
Popup,
|
||||
TooltipPopup,
|
||||
} from 'react-basics';
|
||||
import { ReportContext } from 'components/pages/reports/Report';
|
||||
import { ReportContext } from '../Report';
|
||||
import Icons from 'components/icons';
|
||||
import BaseParameters from '../BaseParameters';
|
||||
import ParameterList from '../ParameterList';
|
||||
File renamed without changes.
+1
@@ -1,3 +1,4 @@
|
||||
'use client';
|
||||
import Report from '../Report';
|
||||
import ReportHeader from '../ReportHeader';
|
||||
import ReportMenu from '../ReportMenu';
|
||||
File renamed without changes.
@@ -0,0 +1,10 @@
|
||||
import InsightsReport from './InsightsReport';
|
||||
import { Metadata } from 'next';
|
||||
|
||||
export default function () {
|
||||
return <InsightsReport reportId={null} />;
|
||||
}
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Insights Report | umami',
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
import ReportsHeader from './ReportsHeader';
|
||||
import ReportsList from './ReportsList';
|
||||
|
||||
export default function ReportsPage() {
|
||||
return (
|
||||
<>
|
||||
<ReportsHeader />
|
||||
<ReportsList />
|
||||
</>
|
||||
);
|
||||
}
|
||||
export const metadata = {
|
||||
title: 'Reports | umami',
|
||||
};
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { useContext, useRef } from 'react';
|
||||
import { useMessages } from 'components/hooks';
|
||||
import { Form, FormButtons, FormRow, SubmitButton } from 'react-basics';
|
||||
import { ReportContext } from 'components/pages/reports/Report';
|
||||
import { ReportContext } from '../Report';
|
||||
import { MonthSelect } from 'components/input/MonthSelect';
|
||||
import BaseParameters from '../BaseParameters';
|
||||
import { parseDateRange } from 'lib/date';
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
'use client';
|
||||
import RetentionTable from './RetentionTable';
|
||||
import RetentionParameters from './RetentionParameters';
|
||||
import Report from '../Report';
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,9 @@
|
||||
import RetentionReport from './RetentionReport';
|
||||
|
||||
export default function () {
|
||||
return <RetentionReport reportId={null} />;
|
||||
}
|
||||
|
||||
export const metadata = {
|
||||
title: 'Create Report | umami',
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import classNames from 'classnames';
|
||||
import { Menu, Item } from 'react-basics';
|
||||
import { useRouter } from 'next/router';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
import styles from './SideNav.module.css';
|
||||
|
||||
@@ -11,13 +11,13 @@ export function SideNav({
|
||||
scroll = false,
|
||||
onSelect = () => {},
|
||||
}) {
|
||||
const { asPath } = useRouter();
|
||||
const pathname = usePathname();
|
||||
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) })}
|
||||
className={classNames(styles.item, { [styles.selected]: pathname.startsWith(url) })}
|
||||
>
|
||||
<Link href={url} shallow={shallow} scroll={scroll}>
|
||||
{label}
|
||||
File renamed without changes.
+7
-2
@@ -1,6 +1,11 @@
|
||||
.layout {
|
||||
display: grid;
|
||||
grid-template-columns: max-content 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 240px;
|
||||
padding-top: 40px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
import { Row, Column } from 'react-basics';
|
||||
import { useRouter } from 'next/router';
|
||||
import SideNav from './SideNav';
|
||||
'use client';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import useUser from 'components/hooks/useUser';
|
||||
import useMessages from 'components/hooks/useMessages';
|
||||
import styles from './SettingsLayout.module.css';
|
||||
import SideNav from './SideNav';
|
||||
import styles from './layout.module.css';
|
||||
|
||||
export function SettingsLayout({ children }) {
|
||||
export default function SettingsLayout({ children }) {
|
||||
const { user } = useUser();
|
||||
const { pathname } = useRouter();
|
||||
const pathname = usePathname();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const cloudMode = Boolean(process.env.cloudMode);
|
||||
|
||||
@@ -21,17 +21,13 @@ export function SettingsLayout({ children }) {
|
||||
const getKey = () => items.find(({ url }) => pathname === url)?.key;
|
||||
|
||||
return (
|
||||
<Row>
|
||||
<div className={styles.layout}>
|
||||
{!cloudMode && (
|
||||
<Column className={styles.menu} defaultSize={12} md={4} lg={3} xl={2}>
|
||||
<div className={styles.menu}>
|
||||
<SideNav items={items} shallow={true} selectedKey={getKey()} />
|
||||
</Column>
|
||||
</div>
|
||||
)}
|
||||
<Column className={styles.content} defaultSize={12} md={8} lg={9} xl={10}>
|
||||
{children}
|
||||
</Column>
|
||||
</Row>
|
||||
<div className={styles.content}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SettingsLayout;
|
||||
File renamed without changes.
File renamed without changes.
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { Button, Icon, Text, useToasts, ModalTrigger, Modal } from 'react-basics';
|
||||
import PasswordEditForm from 'components/pages/settings/profile/PasswordEditForm';
|
||||
import PasswordEditForm from 'app/(app)/settings/profile/PasswordEditForm';
|
||||
import Icons from 'components/icons';
|
||||
import useMessages from 'components/hooks/useMessages';
|
||||
|
||||
File renamed without changes.
@@ -0,0 +1,11 @@
|
||||
'use client';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import { useMessages } from 'components/hooks';
|
||||
|
||||
export function ProfileHeader() {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
return <PageHeader title={formatMessage(labels.profile)}></PageHeader>;
|
||||
}
|
||||
|
||||
export default ProfileHeader;
|
||||
+7
-6
@@ -1,14 +1,15 @@
|
||||
'use client';
|
||||
import { Form, FormRow } from 'react-basics';
|
||||
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 TimezoneSetting from 'app/(app)/settings/profile/TimezoneSetting';
|
||||
import DateRangeSetting from 'app/(app)/settings/profile/DateRangeSetting';
|
||||
import LanguageSetting from 'app/(app)/settings/profile/LanguageSetting';
|
||||
import ThemeSetting from 'app/(app)/settings/profile/ThemeSetting';
|
||||
import PasswordChangeButton from './PasswordChangeButton';
|
||||
import useUser from 'components/hooks/useUser';
|
||||
import useMessages from 'components/hooks/useMessages';
|
||||
import { ROLES } from 'lib/constants';
|
||||
|
||||
export function ProfileDetails() {
|
||||
export function ProfileSettings() {
|
||||
const { user } = useUser();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const cloudMode = Boolean(process.env.cloudMode);
|
||||
@@ -58,4 +59,4 @@ export function ProfileDetails() {
|
||||
);
|
||||
}
|
||||
|
||||
export default ProfileDetails;
|
||||
export default ProfileSettings;
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,11 @@
|
||||
import ProfileHeader from './ProfileHeader';
|
||||
import ProfileSettings from './ProfileSettings';
|
||||
|
||||
export default function () {
|
||||
return (
|
||||
<>
|
||||
<ProfileHeader />
|
||||
<ProfileSettings />
|
||||
</>
|
||||
);
|
||||
}
|
||||
File renamed without changes.
@@ -0,0 +1,25 @@
|
||||
import { Button, Icon, Icons, Modal, ModalTrigger, Text } from 'react-basics';
|
||||
import useMessages from 'components/hooks/useMessages';
|
||||
import TeamDeleteForm from './TeamDeleteForm';
|
||||
|
||||
export function TeamDeleteButton({ teamId, teamName, onDelete }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<ModalTrigger>
|
||||
<Button>
|
||||
<Icon>
|
||||
<Icons.Trash />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.delete)}</Text>
|
||||
</Button>
|
||||
<Modal title={formatMessage(labels.deleteTeam)}>
|
||||
{close => (
|
||||
<TeamDeleteForm teamId={teamId} teamName={teamName} onSave={onDelete} onClose={close} />
|
||||
)}
|
||||
</Modal>
|
||||
</ModalTrigger>
|
||||
);
|
||||
}
|
||||
|
||||
export default TeamDeleteButton;
|
||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,35 @@
|
||||
import { Button, Icon, Icons, Modal, ModalTrigger, Text } from 'react-basics';
|
||||
import useMessages from 'components/hooks/useMessages';
|
||||
import useLocale from 'components/hooks/useLocale';
|
||||
import useUser from 'components/hooks/useUser';
|
||||
import TeamDeleteForm from './TeamLeaveForm';
|
||||
|
||||
export function TeamLeaveButton({ teamId, teamName, onLeave }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { dir } = useLocale();
|
||||
const { user } = useUser();
|
||||
|
||||
return (
|
||||
<ModalTrigger>
|
||||
<Button>
|
||||
<Icon rotate={dir === 'rtl' ? 180 : 0}>
|
||||
<Icons.ArrowRight />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.leave)}</Text>
|
||||
</Button>
|
||||
<Modal title={formatMessage(labels.leaveTeam)}>
|
||||
{close => (
|
||||
<TeamDeleteForm
|
||||
teamId={teamId}
|
||||
userId={user.id}
|
||||
teamName={teamName}
|
||||
onSave={onLeave}
|
||||
onClose={close}
|
||||
/>
|
||||
)}
|
||||
</Modal>
|
||||
</ModalTrigger>
|
||||
);
|
||||
}
|
||||
|
||||
export default TeamLeaveButton;
|
||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,24 @@
|
||||
import { Button, Icon, Modal, ModalTrigger, Text } from 'react-basics';
|
||||
import Icons from 'components/icons';
|
||||
import useMessages from 'components/hooks/useMessages';
|
||||
import TeamAddForm from './TeamAddForm';
|
||||
|
||||
export function TeamsAddButton({ onAdd }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<ModalTrigger>
|
||||
<Button variant="primary">
|
||||
<Icon>
|
||||
<Icons.Plus />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.createTeam)}</Text>
|
||||
</Button>
|
||||
<Modal title={formatMessage(labels.createTeam)}>
|
||||
{close => <TeamAddForm onSave={onAdd} onClose={close} />}
|
||||
</Modal>
|
||||
</ModalTrigger>
|
||||
);
|
||||
}
|
||||
|
||||
export default TeamsAddButton;
|
||||
@@ -0,0 +1,24 @@
|
||||
'use client';
|
||||
import { Flexbox } from 'react-basics';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import { ROLES } from 'lib/constants';
|
||||
import useUser from 'components/hooks/useUser';
|
||||
import useMessages from 'components/hooks/useMessages';
|
||||
import TeamsJoinButton from './TeamsJoinButton';
|
||||
import TeamsAddButton from './TeamsAddButton';
|
||||
|
||||
export function TeamsHeader() {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { user } = useUser();
|
||||
|
||||
return (
|
||||
<PageHeader title={formatMessage(labels.teams)}>
|
||||
<Flexbox gap={10}>
|
||||
<TeamsJoinButton />
|
||||
{user.role !== ROLES.viewOnly && <TeamsAddButton />}
|
||||
</Flexbox>
|
||||
</PageHeader>
|
||||
);
|
||||
}
|
||||
|
||||
export default TeamsHeader;
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Button, Icon, Modal, ModalTrigger, Text, useToasts } from 'react-basics';
|
||||
import Icons from 'components/icons';
|
||||
import useMessages from 'components/hooks/useMessages';
|
||||
import TeamJoinForm from './TeamJoinForm';
|
||||
|
||||
export function TeamsJoinButton() {
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { showToast } = useToasts();
|
||||
|
||||
const handleJoin = () => {
|
||||
showToast({ message: formatMessage(messages.saved), variant: 'success' });
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalTrigger>
|
||||
<Button variant="secondary">
|
||||
<Icon>
|
||||
<Icons.AddUser />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.joinTeam)}</Text>
|
||||
</Button>
|
||||
<Modal title={formatMessage(labels.joinTeam)}>
|
||||
{close => <TeamJoinForm onSave={handleJoin} onClose={close} />}
|
||||
</Modal>
|
||||
</ModalTrigger>
|
||||
);
|
||||
}
|
||||
|
||||
export default TeamsJoinButton;
|
||||
@@ -0,0 +1,19 @@
|
||||
'use client';
|
||||
import DataTable from 'components/common/DataTable';
|
||||
import TeamsTable from 'app/(app)/settings/teams/TeamsTable';
|
||||
import useApi from 'components/hooks/useApi';
|
||||
import useFilterQuery from 'components/hooks/useFilterQuery';
|
||||
|
||||
export function TeamsList() {
|
||||
const { get } = useApi();
|
||||
const filterQuery = useFilterQuery(['teams'], params => {
|
||||
return get(`/teams`, {
|
||||
...params,
|
||||
});
|
||||
});
|
||||
const { getProps } = filterQuery;
|
||||
|
||||
return <DataTable {...getProps()}>{({ data }) => <TeamsTable data={data} />}</DataTable>;
|
||||
}
|
||||
|
||||
export default TeamsList;
|
||||
@@ -0,0 +1,46 @@
|
||||
'use client';
|
||||
import useMessages from 'components/hooks/useMessages';
|
||||
import useUser from 'components/hooks/useUser';
|
||||
import { ROLES } from 'lib/constants';
|
||||
import Link from 'next/link';
|
||||
import { Button, Flexbox, GridColumn, GridTable, Icon, Icons, Text } from 'react-basics';
|
||||
import TeamDeleteButton from './TeamDeleteButton';
|
||||
import TeamLeaveButton from './TeamLeaveButton';
|
||||
|
||||
export function TeamsTable({ data = [] }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { user } = useUser();
|
||||
|
||||
return (
|
||||
<GridTable data={data}>
|
||||
<GridColumn name="name" label={formatMessage(labels.name)} />
|
||||
<GridColumn name="owner" label={formatMessage(labels.owner)}>
|
||||
{row => row.teamUser.find(({ role }) => role === ROLES.teamOwner)?.user?.username}
|
||||
</GridColumn>
|
||||
<GridColumn name="action" label=" " alignment="end">
|
||||
{row => {
|
||||
const { id, name, teamUser } = row;
|
||||
const owner = teamUser.find(({ role }) => role === ROLES.teamOwner);
|
||||
const showDelete = user.id === owner?.userId;
|
||||
|
||||
return (
|
||||
<Flexbox gap={10}>
|
||||
<Link href={`/settings/teams/${id}`}>
|
||||
<Button>
|
||||
<Icon>
|
||||
<Icons.Edit />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.view)}</Text>
|
||||
</Button>
|
||||
</Link>
|
||||
{showDelete && <TeamDeleteButton teamId={id} teamName={name} />}
|
||||
{!showDelete && <TeamLeaveButton teamId={id} teamName={name} />}
|
||||
</Flexbox>
|
||||
);
|
||||
}}
|
||||
</GridColumn>
|
||||
</GridTable>
|
||||
);
|
||||
}
|
||||
|
||||
export default TeamsTable;
|
||||
File renamed without changes.
File renamed without changes.
+1
-1
@@ -1,7 +1,7 @@
|
||||
import useApi from 'components/hooks/useApi';
|
||||
import { useRef, useState } from 'react';
|
||||
import { Button, Dropdown, Form, FormButtons, FormRow, Item, SubmitButton } from 'react-basics';
|
||||
import WebsiteTags from './WebsiteTags';
|
||||
import WebsiteTags from '../WebsiteTags';
|
||||
import useMessages from 'components/hooks/useMessages';
|
||||
|
||||
export function TeamAddWebsiteForm({ teamId, onSave, onClose }) {
|
||||
File renamed without changes.
File renamed without changes.
+1
-1
@@ -1,8 +1,8 @@
|
||||
import { Loading, useToasts } from 'react-basics';
|
||||
import TeamMembersTable from 'components/pages/settings/teams/TeamMembersTable';
|
||||
import useApi from 'components/hooks/useApi';
|
||||
import useMessages from 'components/hooks/useMessages';
|
||||
import useApiFilter from 'components/hooks/useApiFilter';
|
||||
import TeamMembersTable from './TeamMembersTable';
|
||||
|
||||
export function TeamMembers({ teamId, readOnly }) {
|
||||
const { showToast } = useToasts();
|
||||
Loaded 100 of 258 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user