Compare commits
166 Commits
Author | SHA1 | Date |
---|---|---|
![]() |
732fc73fe6 | |
![]() |
586529a5ca | |
![]() |
22e2f79bff | |
![]() |
6f4cc9e84c | |
![]() |
e77e030f2c | |
![]() |
a43bbc51f6 | |
![]() |
bf26874965 | |
![]() |
6cdf9a68d1 | |
![]() |
402acf563e | |
![]() |
2d6077fe16 | |
![]() |
c9ec055996 | |
![]() |
3f0815855c | |
![]() |
46615fe7eb | |
![]() |
9345c9ac9e | |
![]() |
938047ce45 | |
![]() |
34757e7b2a | |
![]() |
01fe8b44a3 | |
![]() |
ae85bf696b | |
![]() |
f450fc35fb | |
![]() |
4c544361fd | |
![]() |
4ea3eec98c | |
![]() |
5864aee043 | |
![]() |
c49e2c6974 | |
![]() |
02f031bde1 | |
![]() |
3f1ed750f0 | |
![]() |
d73def80c7 | |
![]() |
4b72918a91 | |
![]() |
44a1c7fa10 | |
![]() |
c47d246a79 | |
![]() |
89d37e0045 | |
![]() |
4045cfd039 | |
![]() |
c3beb10e3b | |
![]() |
b6f3f57455 | |
![]() |
4876684461 | |
![]() |
9e11866ddd | |
![]() |
1e9a4ad08f | |
![]() |
92ab391ef8 | |
![]() |
c23373d164 | |
![]() |
1b8c2c4dde | |
![]() |
26a9cb67d8 | |
![]() |
4a51a5db3f | |
![]() |
8bc0fb92ba | |
![]() |
ff9a6335d1 | |
![]() |
c5bffb97cc | |
![]() |
2e3d2924e7 | |
![]() |
8666965930 | |
![]() |
5027d9d945 | |
![]() |
6cb2429ee1 | |
![]() |
ef59e93adc | |
![]() |
47f63d80c4 | |
![]() |
36b767af75 | |
![]() |
00d0421623 | |
![]() |
987e3aabbd | |
![]() |
a2bce7ae2c | |
![]() |
6469dd296d | |
![]() |
71fcad26a5 | |
![]() |
80438bf84c | |
![]() |
eaaf3a0a5c | |
![]() |
a7e6c32460 | |
![]() |
39f630400f | |
![]() |
3ba75a6737 | |
![]() |
71628bec90 | |
![]() |
6f80908160 | |
![]() |
21c4ec068d | |
![]() |
115a2560a7 | |
![]() |
f246183094 | |
![]() |
edce62eede | |
![]() |
223f0051ed | |
![]() |
6c09dd1e23 | |
![]() |
5b4bd67455 | |
![]() |
e6c5a9b17d | |
![]() |
69b9458975 | |
![]() |
65c07f5034 | |
![]() |
b9a0f0442e | |
![]() |
a0894866b9 | |
![]() |
39cafafd76 | |
![]() |
31df0f7f07 | |
![]() |
8bddc666b4 | |
![]() |
b2d8423dcb | |
![]() |
5d8fb2f791 | |
![]() |
3039ad9315 | |
![]() |
1baf4d5571 | |
![]() |
322990b03b | |
![]() |
7aac95a410 | |
![]() |
cce966c48c | |
![]() |
5bfbc71e7f | |
![]() |
1d639c77a2 | |
![]() |
80aad4ef55 | |
![]() |
c4d3d6fb09 | |
![]() |
288f77d6ff | |
![]() |
4bd8b44420 | |
![]() |
5f30cbd789 | |
![]() |
5cd4e9fd8d | |
![]() |
fa3659fcf6 | |
![]() |
a84d2181e7 | |
![]() |
981abd5966 | |
![]() |
136f962218 | |
![]() |
aba523cbee | |
![]() |
d2c9c45461 | |
![]() |
dbc19e4ffd | |
![]() |
f0d13efc7a | |
![]() |
03ba225587 | |
![]() |
e63591e35d | |
![]() |
98028f1756 | |
![]() |
3b6c6846b1 | |
![]() |
ae49317c93 | |
![]() |
e33a8e054b | |
![]() |
1cfebc8c78 | |
![]() |
06d19e8594 | |
![]() |
eb7a83b404 | |
![]() |
afde1377dc | |
![]() |
b1856c7eb3 | |
![]() |
f2c0b4253a | |
![]() |
1520f21b82 | |
![]() |
1d9cb0d7c7 | |
![]() |
dbafc98142 | |
![]() |
b39ef68372 | |
![]() |
b95208afcd | |
![]() |
374555aff3 | |
![]() |
af14ccb60e | |
![]() |
8bd289ad01 | |
![]() |
9c2242012e | |
![]() |
cc20f898b1 | |
![]() |
2cd1f5541b | |
![]() |
083a9ffc2c | |
![]() |
ac8d8bbd1e | |
![]() |
ab48a0882a | |
![]() |
aca612c833 | |
![]() |
a52bfaa629 | |
![]() |
0889542f3b | |
![]() |
16ece9ad64 | |
![]() |
e8ca23c390 | |
![]() |
3eabe9b958 | |
![]() |
78030eb4a0 | |
![]() |
db31810e68 | |
![]() |
81bc5d730e | |
![]() |
64d2cf3805 | |
![]() |
3d64985f1a | |
![]() |
03f7f910f2 | |
![]() |
ab0066517d | |
![]() |
939f4752bb | |
![]() |
56ad1c1602 | |
![]() |
c548ac9ca3 | |
![]() |
b3c1eb1437 | |
![]() |
e19d612db2 | |
![]() |
94df42a306 | |
![]() |
904f9408d8 | |
![]() |
10a4921942 | |
![]() |
a7daae2d20 | |
![]() |
bac8edd113 | |
![]() |
54b018f7ba | |
![]() |
5997efc276 | |
![]() |
0926a4c310 | |
![]() |
da79e4c19e | |
![]() |
6ee846d102 | |
![]() |
0f6cfca522 | |
![]() |
a33706585b | |
![]() |
459fbc03ce | |
![]() |
b1fbb20967 | |
![]() |
0217f19d99 | |
![]() |
dea4b4766c | |
![]() |
c0e79f5e9d | |
![]() |
4c92da6fc6 | |
![]() |
329df22a5d | |
![]() |
b4f6dfedda | |
![]() |
005b14b629 |
|
@ -4,3 +4,4 @@ Dockerfile
|
||||||
.gitignore
|
.gitignore
|
||||||
.DS_Store
|
.DS_Store
|
||||||
node_modules
|
node_modules
|
||||||
|
.idea
|
|
@ -46,6 +46,7 @@
|
||||||
"react/react-in-jsx-scope": "off",
|
"react/react-in-jsx-scope": "off",
|
||||||
"react/prop-types": "off",
|
"react/prop-types": "off",
|
||||||
"import/no-anonymous-default-export": "off",
|
"import/no-anonymous-default-export": "off",
|
||||||
|
"import/no-named-as-default": "off",
|
||||||
"@next/next/no-img-element": "off",
|
"@next/next/no-img-element": "off",
|
||||||
"@typescript-eslint/no-empty-function": "off",
|
"@typescript-eslint/no-empty-function": "off",
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
name: "🐛 Bug Report"
|
||||||
|
description: Create a bug report for Umami.
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Describe the Bug
|
||||||
|
description: A clear and concise description of what the bug is.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: Database
|
||||||
|
description: What database are you using?
|
||||||
|
options:
|
||||||
|
- PostgreSQL
|
||||||
|
- MySQL
|
||||||
|
- Umami Cloud
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Relevant log output
|
||||||
|
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
|
||||||
|
render: shell
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Which browser are you using? (if relevant)
|
||||||
|
description: 'For example: Chrome, Edge, Firefox, etc'
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: How are you deploying your application? (if relevant)
|
||||||
|
description: 'For example: Vercel, Railway, Docker, etc'
|
|
@ -0,0 +1,10 @@
|
||||||
|
name: "✨ Feature Request"
|
||||||
|
description: Create a feature or enhancement request for Umami.
|
||||||
|
labels: ['enhancement']
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Describe the feature or enhancement
|
||||||
|
description: A clear and concise description of what the feature or enhancement is.
|
||||||
|
validations:
|
||||||
|
required: true
|
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: "🤔 Ask a question"
|
||||||
|
url: https://github.com/umami-software/umami/discussions
|
||||||
|
about: Ask questions and discuss with other community members.
|
|
@ -18,7 +18,7 @@ jobs:
|
||||||
db-type: [postgresql, mysql]
|
db-type: [postgresql, mysql]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- uses: mr-smithers-excellent/docker-build-push@v6
|
- uses: mr-smithers-excellent/docker-build-push@v6
|
||||||
name: Build & push Docker image for ${{ matrix.db-type }}
|
name: Build & push Docker image for ${{ matrix.db-type }}
|
||||||
|
|
|
@ -3,7 +3,6 @@ name: Create docker images
|
||||||
on: [create]
|
on: [create]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: Build, push, and deploy
|
name: Build, push, and deploy
|
||||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||||
|
@ -14,7 +13,7 @@ jobs:
|
||||||
db-type: [postgresql, mysql]
|
db-type: [postgresql, mysql]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Set env
|
- name: Set env
|
||||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||||
|
|
|
@ -17,6 +17,7 @@ node_modules
|
||||||
/build
|
/build
|
||||||
/public/script.js
|
/public/script.js
|
||||||
/geo
|
/geo
|
||||||
|
/dist
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
/public/
|
/public/script.js
|
|
@ -12,6 +12,7 @@ RUN yarn install --frozen-lockfile
|
||||||
FROM node:18-alpine AS builder
|
FROM node:18-alpine AS builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=deps /app/node_modules ./node_modules
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
|
COPY docker/middleware.js .
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
ARG DATABASE_TYPE
|
ARG DATABASE_TYPE
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# umami
|
# umami
|
||||||
|
|
||||||
Umami is a simple, fast, privacy-focused alternative to Google Analytics.
|
Umami MAMI is a simple, fast, privacy-focused alternative to Google Analytics.
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Icon, Text, Flexbox } from 'react-basics';
|
import { Icon, Text, Flexbox } from 'react-basics';
|
||||||
import Logo from 'assets/logo.svg';
|
import Logo from 'assets/logo.svg';
|
||||||
|
|
||||||
function EmptyPlaceholder({ message, children }) {
|
export function EmptyPlaceholder({ message, children }) {
|
||||||
return (
|
return (
|
||||||
<Flexbox direction="column" alignItems="center" justifyContent="center" gap={60} height={600}>
|
<Flexbox direction="column" alignItems="center" justifyContent="center" gap={60} height={600}>
|
||||||
<Icon size="xl">
|
<Icon size="xl">
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
import { ErrorBoundary as Boundary } from 'react-error-boundary';
|
||||||
|
import { Button } from 'react-basics';
|
||||||
|
import useMessages from 'hooks/useMessages';
|
||||||
|
import styles from './ErrorBoundry.module.css';
|
||||||
|
|
||||||
|
const logError = (error, info) => {
|
||||||
|
console.error(error, info.componentStack);
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ErrorBoundary({ children }) {
|
||||||
|
const { formatMessage, messages } = useMessages();
|
||||||
|
|
||||||
|
const fallbackRender = ({ error, resetErrorBoundary }) => {
|
||||||
|
console.log({ error });
|
||||||
|
return (
|
||||||
|
<div className={styles.error} role="alert">
|
||||||
|
<h1>{formatMessage(messages.error)}</h1>
|
||||||
|
<h3>{error.message}</h3>
|
||||||
|
<pre>{error.stack}</pre>
|
||||||
|
<Button onClick={resetErrorBoundary}>OK</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Boundary fallbackRender={fallbackRender} onError={logError}>
|
||||||
|
{children}
|
||||||
|
</Boundary>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ErrorBoundary;
|
|
@ -0,0 +1,19 @@
|
||||||
|
.error {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin: auto;
|
||||||
|
z-index: var(--z-index-overlay);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 600px;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error button {
|
||||||
|
align-self: center;
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ import { Icon, Icons, Text } from 'react-basics';
|
||||||
import styles from './ErrorMessage.module.css';
|
import styles from './ErrorMessage.module.css';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
|
|
||||||
export default function ErrorMessage() {
|
export function ErrorMessage() {
|
||||||
const { formatMessage, messages } = useMessages();
|
const { formatMessage, messages } = useMessages();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -14,3 +14,5 @@ export default function ErrorMessage() {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default ErrorMessage;
|
||||||
|
|
|
@ -5,7 +5,7 @@ function getHostName(url) {
|
||||||
return match && match.length > 1 ? match[1] : null;
|
return match && match.length > 1 ? match[1] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Favicon({ domain, ...props }) {
|
export function Favicon({ domain, ...props }) {
|
||||||
const hostName = domain ? getHostName(domain) : null;
|
const hostName = domain ? getHostName(domain) : null;
|
||||||
|
|
||||||
return hostName ? (
|
return hostName ? (
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
.favicon {
|
.favicon {
|
||||||
margin-right: 8px;
|
margin-inline-end: 8px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { ButtonGroup, Button, Flexbox } from 'react-basics';
|
import { ButtonGroup, Button, Flexbox } from 'react-basics';
|
||||||
|
|
||||||
export default function FilterButtons({ items, selectedKey, onSelect }) {
|
export function FilterButtons({ items, selectedKey, onSelect }) {
|
||||||
return (
|
return (
|
||||||
<Flexbox justifyContent="center">
|
<Flexbox justifyContent="center">
|
||||||
<ButtonGroup items={items} selectedKey={selectedKey} onSelect={onSelect}>
|
<ButtonGroup items={items} selectedKey={selectedKey} onSelect={onSelect}>
|
||||||
|
@ -9,3 +9,5 @@ export default function FilterButtons({ items, selectedKey, onSelect }) {
|
||||||
</Flexbox>
|
</Flexbox>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default FilterButtons;
|
||||||
|
|
|
@ -6,24 +6,23 @@ import usePageQuery from 'hooks/usePageQuery';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
import styles from './FilterLink.module.css';
|
import styles from './FilterLink.module.css';
|
||||||
|
|
||||||
export default function FilterLink({ id, value, label, externalUrl }) {
|
export function FilterLink({ id, value, label, externalUrl, children, className }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { resolveUrl, query } = usePageQuery();
|
const { resolveUrl, query } = usePageQuery();
|
||||||
const active = query[id] !== undefined;
|
const active = query[id] !== undefined;
|
||||||
const selected = query[id] === value;
|
const selected = query[id] === value;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.row}>
|
<div
|
||||||
{!value && `(${label || formatMessage(labels.unknown)})`}
|
className={classNames(styles.row, className, {
|
||||||
{value && (
|
|
||||||
<Link
|
|
||||||
href={resolveUrl({ [id]: value })}
|
|
||||||
className={classNames(styles.label, {
|
|
||||||
[styles.inactive]: active && !selected,
|
[styles.inactive]: active && !selected,
|
||||||
[styles.active]: active && selected,
|
[styles.active]: active && selected,
|
||||||
})}
|
})}
|
||||||
replace
|
|
||||||
>
|
>
|
||||||
|
{children}
|
||||||
|
{!value && `(${label || formatMessage(labels.unknown)})`}
|
||||||
|
{value && (
|
||||||
|
<Link href={resolveUrl({ [id]: value })} className={styles.label} replace>
|
||||||
{safeDecodeURI(label || value)}
|
{safeDecodeURI(label || value)}
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
@ -37,3 +36,5 @@ export default function FilterLink({ id, value, label, externalUrl }) {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default FilterLink;
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
.row {
|
.row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row.inactive {
|
.row.inactive {
|
||||||
color: var(--base500);
|
color: var(--base500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.row.inactive img {
|
||||||
|
opacity: 0.35;
|
||||||
|
}
|
||||||
|
|
||||||
.row.active {
|
.row.active {
|
||||||
color: var(--base900);
|
color: var(--base900);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
@ -14,7 +19,7 @@
|
||||||
|
|
||||||
.row .link {
|
.row .link {
|
||||||
display: none;
|
display: none;
|
||||||
margin-left: 20px;
|
margin-inline-start: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row .label {
|
.row .label {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import Icons from 'components/icons';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
import useConfig from 'hooks/useConfig';
|
import useConfig from 'hooks/useConfig';
|
||||||
|
|
||||||
export default function HamburgerButton() {
|
export function HamburgerButton() {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const [active, setActive] = useState(false);
|
const [active, setActive] = useState(false);
|
||||||
const { cloudMode } = useConfig();
|
const { cloudMode } = useConfig();
|
||||||
|
@ -57,3 +57,5 @@ export default function HamburgerButton() {
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default HamburgerButton;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
|
||||||
import { Tooltip } from 'react-basics';
|
import { Tooltip } from 'react-basics';
|
||||||
import styles from './HoverTooltip.module.css';
|
import styles from './HoverTooltip.module.css';
|
||||||
|
|
||||||
export default function HoverTooltip({ tooltip }) {
|
export function HoverTooltip({ tooltip }) {
|
||||||
const [position, setPosition] = useState({ x: -1000, y: -1000 });
|
const [position, setPosition] = useState({ x: -1000, y: -1000 });
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -23,3 +23,5 @@ export default function HoverTooltip({ tooltip }) {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default HoverTooltip;
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { useRouter } from 'next/router';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import styles from './MobileMenu.module.css';
|
import styles from './MobileMenu.module.css';
|
||||||
|
|
||||||
export default function MobileMenu({ items = [], onClose }) {
|
export function MobileMenu({ items = [], onClose }) {
|
||||||
const { pathname } = useRouter();
|
const { pathname } = useRouter();
|
||||||
|
|
||||||
const Items = ({ items, className }) => (
|
const Items = ({ items, className }) => (
|
||||||
|
@ -34,3 +34,5 @@ export default function MobileMenu({ items = [], onClose }) {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default MobileMenu;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import classNames from 'classnames';
|
||||||
import styles from './NoData.module.css';
|
import styles from './NoData.module.css';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
|
|
||||||
function NoData({ className }) {
|
export function NoData({ className }) {
|
||||||
const { formatMessage, messages } = useMessages();
|
const { formatMessage, messages } = useMessages();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Table, TableHeader, TableBody, TableRow, TableCell, TableColumn } from 'react-basics';
|
import { Table, TableHeader, TableBody, TableRow, TableCell, TableColumn } from 'react-basics';
|
||||||
import styles from './SettingsTable.module.css';
|
import styles from './SettingsTable.module.css';
|
||||||
|
|
||||||
export default function SettingsTable({ columns = [], data = [], children, cellRender }) {
|
export function SettingsTable({ columns = [], data = [], children, cellRender }) {
|
||||||
return (
|
return (
|
||||||
<Table columns={columns} rows={data}>
|
<Table columns={columns} rows={data}>
|
||||||
<TableHeader className={styles.header}>
|
<TableHeader className={styles.header}>
|
||||||
|
@ -34,3 +34,5 @@ export default function SettingsTable({ columns = [], data = [], children, cellR
|
||||||
</Table>
|
</Table>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default SettingsTable;
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { REPO_URL, VERSION_CHECK } from 'lib/constants';
|
||||||
import styles from './UpdateNotice.module.css';
|
import styles from './UpdateNotice.module.css';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
|
|
||||||
export default function UpdateNotice() {
|
export function UpdateNotice() {
|
||||||
const { formatMessage, labels, messages } = useMessages();
|
const { formatMessage, labels, messages } = useMessages();
|
||||||
const { latest, checked, hasUpdate, releaseUrl } = useStore();
|
const { latest, checked, hasUpdate, releaseUrl } = useStore();
|
||||||
const [dismissed, setDismissed] = useState(false);
|
const [dismissed, setDismissed] = useState(false);
|
||||||
|
@ -50,3 +50,5 @@ export default function UpdateNotice() {
|
||||||
</Row>
|
</Row>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default UpdateNotice;
|
||||||
|
|
|
@ -3,16 +3,16 @@ import { useRouter } from 'next/router';
|
||||||
import { ComposableMap, Geographies, Geography, ZoomableGroup } from 'react-simple-maps';
|
import { ComposableMap, Geographies, Geography, ZoomableGroup } from 'react-simple-maps';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { colord } from 'colord';
|
import { colord } from 'colord';
|
||||||
import useTheme from 'hooks/useTheme';
|
import HoverTooltip from 'components/common/HoverTooltip';
|
||||||
import { ISO_COUNTRIES, THEME_COLORS, MAP_FILE } from 'lib/constants';
|
import { ISO_COUNTRIES, THEME_COLORS, MAP_FILE } from 'lib/constants';
|
||||||
import styles from './WorldMap.module.css';
|
import useTheme from 'hooks/useTheme';
|
||||||
import useCountryNames from 'hooks/useCountryNames';
|
import useCountryNames from 'hooks/useCountryNames';
|
||||||
import useLocale from 'hooks/useLocale';
|
import useLocale from 'hooks/useLocale';
|
||||||
import HoverTooltip from './HoverTooltip';
|
|
||||||
import { formatLongNumber } from 'lib/format';
|
import { formatLongNumber } from 'lib/format';
|
||||||
import { percentFilter } from 'lib/filters';
|
import { percentFilter } from 'lib/filters';
|
||||||
|
import styles from './WorldMap.module.css';
|
||||||
|
|
||||||
function WorldMap({ data, className }) {
|
export function WorldMap({ data, className }) {
|
||||||
const { basePath } = useRouter();
|
const { basePath } = useRouter();
|
||||||
const [tooltip, setTooltip] = useState();
|
const [tooltip, setTooltip] = useState();
|
||||||
const [theme] = useTheme();
|
const [theme] = useTheme();
|
||||||
|
|
|
@ -9,7 +9,7 @@ import useApi from 'hooks/useApi';
|
||||||
import useDateRange from 'hooks/useDateRange';
|
import useDateRange from 'hooks/useDateRange';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
|
|
||||||
function DateFilter({ websiteId, value, className }) {
|
export function DateFilter({ websiteId, value, className }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { get } = useApi();
|
const { get } = useApi();
|
||||||
const [dateRange, setDateRange] = useDateRange(websiteId);
|
const [dateRange, setDateRange] = useDateRange(websiteId);
|
||||||
|
@ -23,7 +23,7 @@ function DateFilter({ websiteId, value, className }) {
|
||||||
if (data) {
|
if (data) {
|
||||||
setDateRange({ value, ...getDateRangeValues(new Date(data.createdAt), Date.now()) });
|
setDateRange({ value, ...getDateRangeValues(new Date(data.createdAt), Date.now()) });
|
||||||
}
|
}
|
||||||
} else {
|
} else if (value !== 'all') {
|
||||||
setDateRange(value);
|
setDateRange(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ function DateFilter({ websiteId, value, className }) {
|
||||||
value: '90day',
|
value: '90day',
|
||||||
},
|
},
|
||||||
{ label: formatMessage(labels.thisYear), value: '1year' },
|
{ label: formatMessage(labels.thisYear), value: '1year' },
|
||||||
{
|
websiteId && {
|
||||||
label: formatMessage(labels.allTime),
|
label: formatMessage(labels.allTime),
|
||||||
value: 'all',
|
value: 'all',
|
||||||
divider: true,
|
divider: true,
|
||||||
|
@ -71,7 +71,7 @@ function DateFilter({ websiteId, value, className }) {
|
||||||
value: 'custom',
|
value: 'custom',
|
||||||
divider: true,
|
divider: true,
|
||||||
},
|
},
|
||||||
];
|
].filter(n => n);
|
||||||
|
|
||||||
const renderValue = value => {
|
const renderValue = value => {
|
||||||
return value === 'custom' ? (
|
return value === 'custom' ? (
|
||||||
|
|
|
@ -5,8 +5,8 @@ import useLocale from 'hooks/useLocale';
|
||||||
import Icons from 'components/icons';
|
import Icons from 'components/icons';
|
||||||
import styles from './LanguageButton.module.css';
|
import styles from './LanguageButton.module.css';
|
||||||
|
|
||||||
export default function LanguageButton() {
|
export function LanguageButton() {
|
||||||
const { locale, saveLocale } = useLocale();
|
const { locale, saveLocale, dir } = useLocale();
|
||||||
const items = Object.keys(languages).map(key => ({ ...languages[key], value: key }));
|
const items = Object.keys(languages).map(key => ({ ...languages[key], value: key }));
|
||||||
|
|
||||||
function handleSelect(value) {
|
function handleSelect(value) {
|
||||||
|
@ -20,7 +20,7 @@ export default function LanguageButton() {
|
||||||
<Icons.Globe />
|
<Icons.Globe />
|
||||||
</Icon>
|
</Icon>
|
||||||
</Button>
|
</Button>
|
||||||
<Popup position="bottom" alignment="end">
|
<Popup position="bottom" alignment={dir === 'rtl' ? 'start' : 'end'}>
|
||||||
<div className={styles.menu}>
|
<div className={styles.menu}>
|
||||||
{items.map(({ value, label }) => {
|
{items.map(({ value, label }) => {
|
||||||
return (
|
return (
|
||||||
|
@ -43,3 +43,5 @@ export default function LanguageButton() {
|
||||||
</PopupTrigger>
|
</PopupTrigger>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default LanguageButton;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Button, Icon, Icons, Tooltip } from 'react-basics';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
|
|
||||||
export default function LogoutButton({ tooltipPosition = 'top' }) {
|
export function LogoutButton({ tooltipPosition = 'top' }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
return (
|
return (
|
||||||
<Link href="/logout">
|
<Link href="/logout">
|
||||||
|
@ -16,3 +16,5 @@ export default function LogoutButton({ tooltipPosition = 'top' }) {
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default LogoutButton;
|
||||||
|
|
|
@ -5,12 +5,14 @@ import useMessages from 'hooks/useMessages';
|
||||||
import useUser from 'hooks/useUser';
|
import useUser from 'hooks/useUser';
|
||||||
import useConfig from 'hooks/useConfig';
|
import useConfig from 'hooks/useConfig';
|
||||||
import styles from './ProfileButton.module.css';
|
import styles from './ProfileButton.module.css';
|
||||||
|
import useLocale from 'hooks/useLocale';
|
||||||
|
|
||||||
export default function ProfileButton() {
|
export function ProfileButton() {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
const { cloudMode } = useConfig();
|
const { cloudMode } = useConfig();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { dir } = useLocale();
|
||||||
|
|
||||||
const handleSelect = key => {
|
const handleSelect = key => {
|
||||||
if (key === 'profile') {
|
if (key === 'profile') {
|
||||||
|
@ -31,7 +33,7 @@ export default function ProfileButton() {
|
||||||
<Icons.ChevronDown />
|
<Icons.ChevronDown />
|
||||||
</Icon>
|
</Icon>
|
||||||
</Button>
|
</Button>
|
||||||
<Popup position="bottom" alignment="end">
|
<Popup position="bottom" alignment={dir === 'rtl' ? 'start' : 'end'}>
|
||||||
<Menu variant="popup" onSelect={handleSelect} className={styles.menu}>
|
<Menu variant="popup" onSelect={handleSelect} className={styles.menu}>
|
||||||
<Item key="user" className={styles.item}>
|
<Item key="user" className={styles.item}>
|
||||||
<Text>{user.username}</Text>
|
<Text>{user.username}</Text>
|
||||||
|
@ -55,3 +57,5 @@ export default function ProfileButton() {
|
||||||
</PopupTrigger>
|
</PopupTrigger>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default ProfileButton;
|
||||||
|
|
|
@ -4,7 +4,7 @@ import useDateRange from 'hooks/useDateRange';
|
||||||
import Icons from 'components/icons';
|
import Icons from 'components/icons';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
|
|
||||||
function RefreshButton({ websiteId, isLoading }) {
|
export function RefreshButton({ websiteId, isLoading }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const [dateRange] = useDateRange(websiteId);
|
const [dateRange] = useDateRange(websiteId);
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import Icons from 'components/icons';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
import styles from './SettingsButton.module.css';
|
import styles from './SettingsButton.module.css';
|
||||||
|
|
||||||
export default function SettingsButton() {
|
export function SettingsButton() {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -33,3 +33,5 @@ export default function SettingsButton() {
|
||||||
</PopupTrigger>
|
</PopupTrigger>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default SettingsButton;
|
||||||
|
|
|
@ -4,7 +4,7 @@ import useTheme from 'hooks/useTheme';
|
||||||
import Icons from 'components/icons';
|
import Icons from 'components/icons';
|
||||||
import styles from './ThemeButton.module.css';
|
import styles from './ThemeButton.module.css';
|
||||||
|
|
||||||
export default function ThemeButton() {
|
export function ThemeButton() {
|
||||||
const [theme, setTheme] = useTheme();
|
const [theme, setTheme] = useTheme();
|
||||||
|
|
||||||
const transitions = useTransition(theme, {
|
const transitions = useTransition(theme, {
|
||||||
|
@ -34,3 +34,5 @@ export default function ThemeButton() {
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default ThemeButton;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Dropdown, Item } from 'react-basics';
|
||||||
import useApi from 'hooks/useApi';
|
import useApi from 'hooks/useApi';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
|
|
||||||
export default function WebsiteSelect({ websiteId, onSelect }) {
|
export function WebsiteSelect({ websiteId, onSelect }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { get, useQuery } = useApi();
|
const { get, useQuery } = useApi();
|
||||||
const { data } = useQuery(['websites:me'], () => get('/me/websites'));
|
const { data } = useQuery(['websites:me'], () => get('/me/websites'));
|
||||||
|
@ -25,3 +25,5 @@ export default function WebsiteSelect({ websiteId, onSelect }) {
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default WebsiteSelect;
|
||||||
|
|
|
@ -5,9 +5,10 @@ import NavBar from 'components/layout/NavBar';
|
||||||
import UpdateNotice from 'components/common/UpdateNotice';
|
import UpdateNotice from 'components/common/UpdateNotice';
|
||||||
import useRequireLogin from 'hooks/useRequireLogin';
|
import useRequireLogin from 'hooks/useRequireLogin';
|
||||||
import useConfig from 'hooks/useConfig';
|
import useConfig from 'hooks/useConfig';
|
||||||
|
import { CURRENT_VERSION } from 'lib/constants';
|
||||||
import styles from './AppLayout.module.css';
|
import styles from './AppLayout.module.css';
|
||||||
|
|
||||||
export default function AppLayout({ title, children }) {
|
export function AppLayout({ title, children }) {
|
||||||
const { user } = useRequireLogin();
|
const { user } = useRequireLogin();
|
||||||
const config = useConfig();
|
const config = useConfig();
|
||||||
const { pathname } = useRouter();
|
const { pathname } = useRouter();
|
||||||
|
@ -19,7 +20,7 @@ export default function AppLayout({ title, children }) {
|
||||||
const allowUpdate = user?.isAdmin && !config?.updatesDisabled && !pathname.includes('/share/');
|
const allowUpdate = user?.isAdmin && !config?.updatesDisabled && !pathname.includes('/share/');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.layout}>
|
<div className={styles.layout} data-app-version={CURRENT_VERSION}>
|
||||||
{allowUpdate && <UpdateNotice />}
|
{allowUpdate && <UpdateNotice />}
|
||||||
<Head>
|
<Head>
|
||||||
<title>{title ? `${title} | umami` : 'umami'}</title>
|
<title>{title ? `${title} | umami` : 'umami'}</title>
|
||||||
|
@ -33,3 +34,5 @@ export default function AppLayout({ title, children }) {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default AppLayout;
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { CURRENT_VERSION, HOMEPAGE_URL, REPO_URL } from 'lib/constants';
|
||||||
import { labels } from 'components/messages';
|
import { labels } from 'components/messages';
|
||||||
import styles from './Footer.module.css';
|
import styles from './Footer.module.css';
|
||||||
|
|
||||||
export default function Footer() {
|
export function Footer() {
|
||||||
return (
|
return (
|
||||||
<footer className={styles.footer}>
|
<footer className={styles.footer}>
|
||||||
<Row>
|
<Row>
|
||||||
|
@ -29,3 +29,5 @@ export default function Footer() {
|
||||||
</footer>
|
</footer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Footer;
|
||||||
|
|
|
@ -10,16 +10,16 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.row > .col {
|
.row > .col {
|
||||||
border-left: 1px solid var(--base300);
|
border-inline-start: 1px solid var(--base300);
|
||||||
}
|
}
|
||||||
|
|
||||||
.row > .col:first-child {
|
.row > .col:first-child {
|
||||||
border-left: 0;
|
border-inline-start: 0;
|
||||||
padding-left: 0;
|
padding-inline-start: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row > .col:last-child {
|
.row > .col:last-child {
|
||||||
padding-right: 0;
|
padding-inline-end: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 992px) {
|
@media only screen and (max-width: 992px) {
|
||||||
|
@ -29,7 +29,7 @@
|
||||||
|
|
||||||
.row > .col {
|
.row > .col {
|
||||||
border-top: 1px solid var(--base300);
|
border-top: 1px solid var(--base300);
|
||||||
border-left: 0;
|
border-inline-end: 0;
|
||||||
padding: 20px 0;
|
padding: 20px 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import SettingsButton from 'components/input/SettingsButton';
|
||||||
import Icons from 'components/icons';
|
import Icons from 'components/icons';
|
||||||
import styles from './Header.module.css';
|
import styles from './Header.module.css';
|
||||||
|
|
||||||
export default function Header() {
|
export function Header() {
|
||||||
return (
|
return (
|
||||||
<header className={styles.header}>
|
<header className={styles.header}>
|
||||||
<Row>
|
<Row>
|
||||||
|
@ -27,3 +27,5 @@ export default function Header() {
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Header;
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { useState } from 'react';
|
|
||||||
import { Icon, Text, Row, Column } from 'react-basics';
|
import { Icon, Text, Row, Column } from 'react-basics';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
@ -12,7 +11,7 @@ import useMessages from 'hooks/useMessages';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import HamburgerButton from '../common/HamburgerButton';
|
import HamburgerButton from '../common/HamburgerButton';
|
||||||
|
|
||||||
export default function NavBar() {
|
export function NavBar() {
|
||||||
const { pathname } = useRouter();
|
const { pathname } = useRouter();
|
||||||
const { cloudMode } = useConfig();
|
const { cloudMode } = useConfig();
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
@ -61,3 +60,5 @@ export default function NavBar() {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default NavBar;
|
||||||
|
|
|
@ -6,7 +6,7 @@ import Link from 'next/link';
|
||||||
import Icons from 'components/icons';
|
import Icons from 'components/icons';
|
||||||
import styles from './NavGroup.module.css';
|
import styles from './NavGroup.module.css';
|
||||||
|
|
||||||
export default function NavGroup({
|
export function NavGroup({
|
||||||
title,
|
title,
|
||||||
items,
|
items,
|
||||||
defaultExpanded = true,
|
defaultExpanded = true,
|
||||||
|
@ -54,3 +54,5 @@ export default function NavGroup({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default NavGroup;
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { Banner, Loading } from 'react-basics';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
import styles from './Page.module.css';
|
import styles from './Page.module.css';
|
||||||
|
|
||||||
export default function Page({ className, error, loading, children }) {
|
export function Page({ className, error, loading, children }) {
|
||||||
const { formatMessage, messages } = useMessages();
|
const { formatMessage, messages } = useMessages();
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
|
@ -16,3 +16,5 @@ export default function Page({ className, error, loading, children }) {
|
||||||
|
|
||||||
return <div className={classNames(styles.page, className)}>{children}</div>;
|
return <div className={classNames(styles.page, className)}>{children}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Page;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styles from './PageHeader.module.css';
|
import styles from './PageHeader.module.css';
|
||||||
|
|
||||||
export default function PageHeader({ title, children }) {
|
export function PageHeader({ title, children }) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
<div className={styles.title}>{title}</div>
|
<div className={styles.title}>{title}</div>
|
||||||
|
@ -9,3 +9,5 @@ export default function PageHeader({ title, children }) {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default PageHeader;
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { Row, Column } from 'react-basics';
|
import { Row, Column } from 'react-basics';
|
||||||
import classNames from 'classnames';
|
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import SideNav from './SideNav';
|
import SideNav from './SideNav';
|
||||||
import useUser from 'hooks/useUser';
|
import useUser from 'hooks/useUser';
|
||||||
|
@ -7,7 +6,7 @@ import useMessages from 'hooks/useMessages';
|
||||||
import useConfig from 'hooks/useConfig';
|
import useConfig from 'hooks/useConfig';
|
||||||
import styles from './SettingsLayout.module.css';
|
import styles from './SettingsLayout.module.css';
|
||||||
|
|
||||||
export default function SettingsLayout({ children }) {
|
export function SettingsLayout({ children }) {
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
const { pathname } = useRouter();
|
const { pathname } = useRouter();
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
@ -35,3 +34,5 @@ export default function SettingsLayout({ children }) {
|
||||||
</Row>
|
</Row>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default SettingsLayout;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Container } from 'react-basics';
|
||||||
import Header from './Header';
|
import Header from './Header';
|
||||||
import Footer from './Footer';
|
import Footer from './Footer';
|
||||||
|
|
||||||
export default function ShareLayout({ children }) {
|
export function ShareLayout({ children }) {
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<Header />
|
<Header />
|
||||||
|
@ -11,3 +11,5 @@ export default function ShareLayout({ children }) {
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default ShareLayout;
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { useRouter } from 'next/router';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import styles from './SideNav.module.css';
|
import styles from './SideNav.module.css';
|
||||||
|
|
||||||
export default function SideNav({ selectedKey, items, shallow, onSelect = () => {} }) {
|
export function SideNav({ selectedKey, items, shallow, onSelect = () => {} }) {
|
||||||
const { asPath } = useRouter();
|
const { asPath } = useRouter();
|
||||||
return (
|
return (
|
||||||
<Menu items={items} selectedKey={selectedKey} className={styles.menu} onSelect={onSelect}>
|
<Menu items={items} selectedKey={selectedKey} className={styles.menu} onSelect={onSelect}>
|
||||||
|
@ -21,3 +21,5 @@ export default function SideNav({ selectedKey, items, shallow, onSelect = () =>
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default SideNav;
|
||||||
|
|
|
@ -62,6 +62,7 @@ export const labels = defineMessages({
|
||||||
queries: { id: 'label.queries', defaultMessage: 'Queries' },
|
queries: { id: 'label.queries', defaultMessage: 'Queries' },
|
||||||
teams: { id: 'label.teams', defaultMessage: 'Teams' },
|
teams: { id: 'label.teams', defaultMessage: 'Teams' },
|
||||||
analytics: { id: 'label.analytics', defaultMessage: 'Analytics' },
|
analytics: { id: 'label.analytics', defaultMessage: 'Analytics' },
|
||||||
|
login: { id: 'label.login', defaultMessage: 'Login' },
|
||||||
logout: { id: 'label.logout', defaultMessage: 'Logout' },
|
logout: { id: 'label.logout', defaultMessage: 'Logout' },
|
||||||
singleDay: { id: 'label.single-day', defaultMessage: 'Single day' },
|
singleDay: { id: 'label.single-day', defaultMessage: 'Single day' },
|
||||||
dateRange: { id: 'label.date-range', defaultMessage: 'Date range' },
|
dateRange: { id: 'label.date-range', defaultMessage: 'Date range' },
|
||||||
|
@ -94,12 +95,12 @@ export const labels = defineMessages({
|
||||||
thisMonth: { id: 'label.this-month', defaultMessage: 'This month' },
|
thisMonth: { id: 'label.this-month', defaultMessage: 'This month' },
|
||||||
thisYear: { id: 'label.this-year', defaultMessage: 'This year' },
|
thisYear: { id: 'label.this-year', defaultMessage: 'This year' },
|
||||||
allTime: { id: 'label.all-time', defaultMessage: 'All time' },
|
allTime: { id: 'label.all-time', defaultMessage: 'All time' },
|
||||||
customRange: { id: 'label.custom-range', defaultMessage: 'Custom-range' },
|
customRange: { id: 'label.custom-range', defaultMessage: 'Custom range' },
|
||||||
selectWebsite: { id: 'label.select-website', defaultMessage: 'Select website' },
|
selectWebsite: { id: 'label.select-website', defaultMessage: 'Select website' },
|
||||||
all: { id: 'label.all', defaultMessage: 'All' },
|
all: { id: 'label.all', defaultMessage: 'All' },
|
||||||
sessions: { id: 'label.sessions', defaultMessage: 'Sessions' },
|
sessions: { id: 'label.sessions', defaultMessage: 'Sessions' },
|
||||||
pageNotFound: { id: 'message.page-not-found', defaultMessage: 'Page not found' },
|
pageNotFound: { id: 'message.page-not-found', defaultMessage: 'Page not found' },
|
||||||
logs: { id: 'label.activity-log', defaultMessage: 'Activity log' },
|
activityLog: { id: 'label.activity-log', defaultMessage: 'Activity log' },
|
||||||
dismiss: { id: 'label.dismiss', defaultMessage: 'Dismiss' },
|
dismiss: { id: 'label.dismiss', defaultMessage: 'Dismiss' },
|
||||||
poweredBy: { id: 'label.powered-by', defaultMessage: 'Powered by {name}' },
|
poweredBy: { id: 'label.powered-by', defaultMessage: 'Powered by {name}' },
|
||||||
pageViews: { id: 'label.page-views', defaultMessage: 'Page views' },
|
pageViews: { id: 'label.page-views', defaultMessage: 'Page views' },
|
||||||
|
|
|
@ -4,7 +4,7 @@ import useApi from 'hooks/useApi';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
import styles from './ActiveUsers.module.css';
|
import styles from './ActiveUsers.module.css';
|
||||||
|
|
||||||
export default function ActiveUsers({ websiteId, value, refetchInterval = 60000 }) {
|
export function ActiveUsers({ websiteId, value, refetchInterval = 60000 }) {
|
||||||
const { formatMessage, messages } = useMessages();
|
const { formatMessage, messages } = useMessages();
|
||||||
const { get, useQuery } = useApi();
|
const { get, useQuery } = useApi();
|
||||||
const { data } = useQuery(
|
const { data } = useQuery(
|
||||||
|
@ -34,3 +34,5 @@ export default function ActiveUsers({ websiteId, value, refetchInterval = 60000
|
||||||
</StatusLight>
|
</StatusLight>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default ActiveUsers;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { useState, useRef, useEffect, useMemo, useCallback } from 'react';
|
import { useState, useRef, useEffect, useMemo, useCallback } from 'react';
|
||||||
import { StatusLight } from 'react-basics';
|
import { StatusLight, Loading } from 'react-basics';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import Chart from 'chart.js/auto';
|
import Chart from 'chart.js/auto';
|
||||||
import HoverTooltip from 'components/common/HoverTooltip';
|
import HoverTooltip from 'components/common/HoverTooltip';
|
||||||
|
@ -11,7 +11,7 @@ import useTheme from 'hooks/useTheme';
|
||||||
import { DEFAULT_ANIMATION_DURATION, THEME_COLORS } from 'lib/constants';
|
import { DEFAULT_ANIMATION_DURATION, THEME_COLORS } from 'lib/constants';
|
||||||
import styles from './BarChart.module.css';
|
import styles from './BarChart.module.css';
|
||||||
|
|
||||||
export default function BarChart({
|
export function BarChart({
|
||||||
datasets,
|
datasets,
|
||||||
unit,
|
unit,
|
||||||
animationDuration = DEFAULT_ANIMATION_DURATION,
|
animationDuration = DEFAULT_ANIMATION_DURATION,
|
||||||
|
@ -39,6 +39,26 @@ export default function BarChart({
|
||||||
return +label > 1000 ? formatLongNumber(label) : label;
|
return +label > 1000 ? formatLongNumber(label) : label;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderXLabel = useCallback(
|
||||||
|
(label, index, values) => {
|
||||||
|
const d = new Date(values[index].value);
|
||||||
|
|
||||||
|
switch (unit) {
|
||||||
|
case 'minute':
|
||||||
|
return dateFormat(d, 'h:mm', locale);
|
||||||
|
case 'hour':
|
||||||
|
return dateFormat(d, 'p', locale);
|
||||||
|
case 'day':
|
||||||
|
return dateFormat(d, 'MMM d', locale);
|
||||||
|
case 'month':
|
||||||
|
return dateFormat(d, 'MMM', locale);
|
||||||
|
default:
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[locale, unit],
|
||||||
|
);
|
||||||
|
|
||||||
const renderTooltip = useCallback(
|
const renderTooltip = useCallback(
|
||||||
model => {
|
model => {
|
||||||
const { opacity, labelColors, dataPoints } = model.tooltip;
|
const { opacity, labelColors, dataPoints } = model.tooltip;
|
||||||
|
@ -115,6 +135,7 @@ export default function BarChart({
|
||||||
color: colors.text,
|
color: colors.text,
|
||||||
autoSkip: false,
|
autoSkip: false,
|
||||||
maxRotation: 0,
|
maxRotation: 0,
|
||||||
|
callback: renderXLabel,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
y: {
|
y: {
|
||||||
|
@ -135,7 +156,7 @@ export default function BarChart({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}, [animationDuration, renderTooltip, stacked, colors, unit]);
|
}, [animationDuration, renderTooltip, renderXLabel, stacked, colors, unit, locale]);
|
||||||
|
|
||||||
const createChart = () => {
|
const createChart = () => {
|
||||||
Chart.defaults.font.family = 'Inter';
|
Chart.defaults.font.family = 'Inter';
|
||||||
|
@ -156,13 +177,16 @@ export default function BarChart({
|
||||||
const updateChart = () => {
|
const updateChart = () => {
|
||||||
setTooltip(null);
|
setTooltip(null);
|
||||||
|
|
||||||
|
datasets.forEach((dataset, index) => {
|
||||||
|
chart.current.data.datasets[index].data = dataset.data;
|
||||||
|
chart.current.data.datasets[index].label = dataset.label;
|
||||||
|
});
|
||||||
|
|
||||||
chart.current.options = getOptions();
|
chart.current.options = getOptions();
|
||||||
|
|
||||||
chart.current.data.datasets = datasets;
|
onUpdate(chart.current);
|
||||||
|
|
||||||
chart.current.update();
|
chart.current.update();
|
||||||
|
|
||||||
onUpdate(chart.current);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -173,11 +197,12 @@ export default function BarChart({
|
||||||
updateChart();
|
updateChart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [datasets, unit, theme, animationDuration, locale, loading]);
|
}, [datasets, unit, theme, animationDuration, locale]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={classNames(styles.chart, className)}>
|
<div className={classNames(styles.chart, className)}>
|
||||||
|
{loading && <Loading position="page" icon="dots" />}
|
||||||
<canvas ref={canvas} />
|
<canvas ref={canvas} />
|
||||||
</div>
|
</div>
|
||||||
<Legend chart={chart.current} />
|
<Legend chart={chart.current} />
|
||||||
|
@ -185,3 +210,5 @@ export default function BarChart({
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default BarChart;
|
||||||
|
|
|
@ -3,7 +3,7 @@ import MetricsTable from 'components/metrics/MetricsTable';
|
||||||
import { BROWSERS } from 'lib/constants';
|
import { BROWSERS } from 'lib/constants';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
|
|
||||||
export default function BrowsersTable({ websiteId, ...props }) {
|
export function BrowsersTable({ websiteId, ...props }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
function renderLink({ x: browser }) {
|
function renderLink({ x: browser }) {
|
||||||
|
@ -21,3 +21,5 @@ export default function BrowsersTable({ websiteId, ...props }) {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default BrowsersTable;
|
||||||
|
|
|
@ -4,7 +4,7 @@ import FilterLink from 'components/common/FilterLink';
|
||||||
import useLocale from 'hooks/useLocale';
|
import useLocale from 'hooks/useLocale';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
|
|
||||||
export default function CitiesTable({ websiteId, ...props }) {
|
export function CitiesTable({ websiteId, ...props }) {
|
||||||
const { locale } = useLocale();
|
const { locale } = useLocale();
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
|
@ -28,3 +28,5 @@ export default function CitiesTable({ websiteId, ...props }) {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default CitiesTable;
|
||||||
|
|
|
@ -4,16 +4,21 @@ import useCountryNames from 'hooks/useCountryNames';
|
||||||
import useLocale from 'hooks/useLocale';
|
import useLocale from 'hooks/useLocale';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
|
|
||||||
export default function CountriesTable({ websiteId, ...props }) {
|
export function CountriesTable({ websiteId, ...props }) {
|
||||||
const { locale } = useLocale();
|
const { locale } = useLocale();
|
||||||
const countryNames = useCountryNames(locale);
|
const countryNames = useCountryNames(locale);
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
function renderLink({ x: code }) {
|
function renderLink({ x: code }) {
|
||||||
return (
|
return (
|
||||||
<div className={locale}>
|
<FilterLink
|
||||||
<FilterLink id="country" value={countryNames[code] && code} label={countryNames[code]} />
|
id="country"
|
||||||
</div>
|
className={locale}
|
||||||
|
value={countryNames[code] && code}
|
||||||
|
label={countryNames[code]}
|
||||||
|
>
|
||||||
|
<img src={`/images/flags/${code?.toLowerCase() || 'xx'}.png`} alt={code} />
|
||||||
|
</FilterLink>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,3 +33,5 @@ export default function CountriesTable({ websiteId, ...props }) {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default CountriesTable;
|
||||||
|
|
|
@ -3,13 +3,12 @@ import useMeasure from 'react-use-measure';
|
||||||
import { FixedSizeList } from 'react-window';
|
import { FixedSizeList } from 'react-window';
|
||||||
import { useSpring, animated, config } from 'react-spring';
|
import { useSpring, animated, config } from 'react-spring';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
import NoData from 'components/common/NoData';
|
import NoData from 'components/common/NoData';
|
||||||
import { formatNumber, formatLongNumber } from 'lib/format';
|
import { formatNumber, formatLongNumber } from 'lib/format';
|
||||||
import styles from './DataTable.module.css';
|
import styles from './DataTable.module.css';
|
||||||
import useMessages from '../../hooks/useMessages';
|
import useMessages from '../../hooks/useMessages';
|
||||||
|
|
||||||
export default function DataTable({
|
export function DataTable({
|
||||||
data = [],
|
data = [],
|
||||||
title,
|
title,
|
||||||
metric,
|
metric,
|
||||||
|
@ -102,3 +101,5 @@ const AnimatedRow = ({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default DataTable;
|
||||||
|
|
|
@ -68,8 +68,8 @@
|
||||||
|
|
||||||
.value {
|
.value {
|
||||||
width: 50px;
|
width: 50px;
|
||||||
text-align: right;
|
text-align: end;
|
||||||
margin-right: 10px;
|
margin-inline-end: 10px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
@ -79,7 +79,7 @@
|
||||||
width: 50px;
|
width: 50px;
|
||||||
color: var(--base600);
|
color: var(--base600);
|
||||||
border-left: 1px solid var(--base600);
|
border-left: 1px solid var(--base600);
|
||||||
padding-left: 10px;
|
padding-inline-start: 10px;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { FILTER_DAY, FILTER_RANGE } from 'lib/constants';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
import styles from './DatePickerForm.module.css';
|
import styles from './DatePickerForm.module.css';
|
||||||
|
|
||||||
export default function DatePickerForm({
|
export function DatePickerForm({
|
||||||
startDate: defaultStartDate,
|
startDate: defaultStartDate,
|
||||||
endDate: defaultEndDate,
|
endDate: defaultEndDate,
|
||||||
minDate,
|
minDate,
|
||||||
|
@ -78,3 +78,5 @@ export default function DatePickerForm({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default DatePickerForm;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import MetricsTable from './MetricsTable';
|
||||||
import FilterLink from 'components/common/FilterLink';
|
import FilterLink from 'components/common/FilterLink';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
|
|
||||||
export default function DevicesTable({ websiteId, ...props }) {
|
export function DevicesTable({ websiteId, ...props }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
function renderLink({ x: device }) {
|
function renderLink({ x: device }) {
|
||||||
|
@ -26,3 +26,5 @@ export default function DevicesTable({ websiteId, ...props }) {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default DevicesTable;
|
||||||
|
|
|
@ -9,7 +9,7 @@ import useTimezone from 'hooks/useTimezone';
|
||||||
import usePageQuery from 'hooks/usePageQuery';
|
import usePageQuery from 'hooks/usePageQuery';
|
||||||
import { EVENT_COLORS } from 'lib/constants';
|
import { EVENT_COLORS } from 'lib/constants';
|
||||||
|
|
||||||
export default function EventsChart({ websiteId, className, token }) {
|
export function EventsChart({ websiteId, className, token }) {
|
||||||
const { get, useQuery } = useApi();
|
const { get, useQuery } = useApi();
|
||||||
const [{ startDate, endDate, unit, modified }] = useDateRange(websiteId);
|
const [{ startDate, endDate, unit, modified }] = useDateRange(websiteId);
|
||||||
const [timezone] = useTimezone();
|
const [timezone] = useTimezone();
|
||||||
|
@ -76,3 +76,5 @@ export default function EventsChart({ websiteId, className, token }) {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default EventsChart;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import MetricsTable from './MetricsTable';
|
import MetricsTable from './MetricsTable';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
|
|
||||||
export default function EventsTable({ websiteId, ...props }) {
|
export function EventsTable({ websiteId, ...props }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
function handleDataLoad(data) {
|
function handleDataLoad(data) {
|
||||||
|
@ -19,3 +19,5 @@ export default function EventsTable({ websiteId, ...props }) {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default EventsTable;
|
||||||
|
|
|
@ -4,7 +4,7 @@ import usePageQuery from 'hooks/usePageQuery';
|
||||||
import styles from './FilterTags.module.css';
|
import styles from './FilterTags.module.css';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
|
|
||||||
export default function FilterTags({ params }) {
|
export function FilterTags({ params }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const {
|
const {
|
||||||
router,
|
router,
|
||||||
|
@ -50,3 +50,5 @@ export default function FilterTags({ params }) {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default FilterTags;
|
||||||
|
|
|
@ -4,7 +4,7 @@ import useLanguageNames from 'hooks/useLanguageNames';
|
||||||
import useLocale from 'hooks/useLocale';
|
import useLocale from 'hooks/useLocale';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
|
|
||||||
export default function LanguagesTable({ websiteId, onDataLoad, ...props }) {
|
export function LanguagesTable({ websiteId, onDataLoad, ...props }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { locale } = useLocale();
|
const { locale } = useLocale();
|
||||||
const languageNames = useLanguageNames(locale);
|
const languageNames = useLanguageNames(locale);
|
||||||
|
@ -25,3 +25,5 @@ export default function LanguagesTable({ websiteId, onDataLoad, ...props }) {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default LanguagesTable;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { StatusLight } from 'react-basics';
|
import { StatusLight } from 'react-basics';
|
||||||
import { colord } from 'colord';
|
import { colord } from 'colord';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
@ -5,11 +6,11 @@ import useLocale from 'hooks/useLocale';
|
||||||
import useForceUpdate from 'hooks/useForceUpdate';
|
import useForceUpdate from 'hooks/useForceUpdate';
|
||||||
import styles from './Legend.module.css';
|
import styles from './Legend.module.css';
|
||||||
|
|
||||||
export default function Legend({ chart }) {
|
export function Legend({ chart }) {
|
||||||
const { locale } = useLocale();
|
const { locale } = useLocale();
|
||||||
const forceUpdate = useForceUpdate();
|
const forceUpdate = useForceUpdate();
|
||||||
|
|
||||||
function handleClick(index) {
|
const handleClick = index => {
|
||||||
const meta = chart.getDatasetMeta(index);
|
const meta = chart.getDatasetMeta(index);
|
||||||
|
|
||||||
meta.hidden = meta.hidden === null ? !chart.data.datasets[index].hidden : null;
|
meta.hidden = meta.hidden === null ? !chart.data.datasets[index].hidden : null;
|
||||||
|
@ -17,7 +18,11 @@ export default function Legend({ chart }) {
|
||||||
chart.update();
|
chart.update();
|
||||||
|
|
||||||
forceUpdate();
|
forceUpdate();
|
||||||
}
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
forceUpdate();
|
||||||
|
}, [locale]);
|
||||||
|
|
||||||
if (!chart?.legend?.legendItems.find(({ text }) => text)) {
|
if (!chart?.legend?.legendItems.find(({ text }) => text)) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -43,3 +48,5 @@ export default function Legend({ chart }) {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Legend;
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.label + .label {
|
.label + .label {
|
||||||
margin-left: 20px;
|
margin-inline-start: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hidden {
|
.hidden {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { useSpring, animated } from 'react-spring';
|
||||||
import { formatNumber } from 'lib/format';
|
import { formatNumber } from 'lib/format';
|
||||||
import styles from './MetricCard.module.css';
|
import styles from './MetricCard.module.css';
|
||||||
|
|
||||||
const MetricCard = ({
|
export const MetricCard = ({
|
||||||
value = 0,
|
value = 0,
|
||||||
change = 0,
|
change = 0,
|
||||||
label,
|
label,
|
||||||
|
|
|
@ -9,7 +9,7 @@ import MetricCard from './MetricCard';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
import styles from './MetricsBar.module.css';
|
import styles from './MetricsBar.module.css';
|
||||||
|
|
||||||
export default function MetricsBar({ websiteId }) {
|
export function MetricsBar({ websiteId }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { get, useQuery } = useApi();
|
const { get, useQuery } = useApi();
|
||||||
const [dateRange] = useDateRange(websiteId);
|
const [dateRange] = useDateRange(websiteId);
|
||||||
|
@ -111,3 +111,5 @@ export default function MetricsBar({ websiteId }) {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default MetricsBar;
|
||||||
|
|
|
@ -13,8 +13,9 @@ import { DEFAULT_ANIMATION_DURATION } from 'lib/constants';
|
||||||
import Icons from 'components/icons';
|
import Icons from 'components/icons';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
import styles from './MetricsTable.module.css';
|
import styles from './MetricsTable.module.css';
|
||||||
|
import useLocale from 'hooks/useLocale';
|
||||||
|
|
||||||
export default function MetricsTable({
|
export function MetricsTable({
|
||||||
websiteId,
|
websiteId,
|
||||||
type,
|
type,
|
||||||
className,
|
className,
|
||||||
|
@ -69,6 +70,7 @@ export default function MetricsTable({
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}, [data, error, dataFilter, filterOptions]);
|
}, [data, error, dataFilter, filterOptions]);
|
||||||
|
const { dir } = useLocale();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(styles.container, className)}>
|
<div className={classNames(styles.container, className)}>
|
||||||
|
@ -80,7 +82,7 @@ export default function MetricsTable({
|
||||||
<Link href={router.pathname} as={resolveUrl({ view: type })}>
|
<Link href={router.pathname} as={resolveUrl({ view: type })}>
|
||||||
<Button variant="quiet">
|
<Button variant="quiet">
|
||||||
<Text>{formatMessage(labels.more)}</Text>
|
<Text>{formatMessage(labels.more)}</Text>
|
||||||
<Icon size="sm">
|
<Icon size="sm" rotate={dir === 'rtl' ? 180 : 0}>
|
||||||
<Icons.ArrowRight />
|
<Icons.ArrowRight />
|
||||||
</Icon>
|
</Icon>
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -90,3 +92,5 @@ export default function MetricsTable({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default MetricsTable;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import MetricsTable from './MetricsTable';
|
||||||
import FilterLink from 'components/common/FilterLink';
|
import FilterLink from 'components/common/FilterLink';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
|
|
||||||
export default function OSTable({ websiteId, ...props }) {
|
export function OSTable({ websiteId, ...props }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
function renderLink({ x: os }) {
|
function renderLink({ x: os }) {
|
||||||
|
@ -20,3 +20,5 @@ export default function OSTable({ websiteId, ...props }) {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default OSTable;
|
||||||
|
|
|
@ -5,7 +5,7 @@ import useMessages from 'hooks/useMessages';
|
||||||
import usePageQuery from 'hooks/usePageQuery';
|
import usePageQuery from 'hooks/usePageQuery';
|
||||||
import { emptyFilter } from 'lib/filters';
|
import { emptyFilter } from 'lib/filters';
|
||||||
|
|
||||||
export default function PagesTable({ websiteId, showFilters, ...props }) {
|
export function PagesTable({ websiteId, showFilters, ...props }) {
|
||||||
const {
|
const {
|
||||||
router,
|
router,
|
||||||
resolveUrl,
|
resolveUrl,
|
||||||
|
@ -47,3 +47,5 @@ export default function PagesTable({ websiteId, showFilters, ...props }) {
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default PagesTable;
|
||||||
|
|
|
@ -4,18 +4,12 @@ import BarChart from './BarChart';
|
||||||
import { THEME_COLORS } from 'lib/constants';
|
import { THEME_COLORS } from 'lib/constants';
|
||||||
import useTheme from 'hooks/useTheme';
|
import useTheme from 'hooks/useTheme';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
|
import useLocale from 'hooks/useLocale';
|
||||||
|
|
||||||
export default function PageviewsChart({
|
export function PageviewsChart({ websiteId, data, unit, records, className, loading, ...props }) {
|
||||||
websiteId,
|
|
||||||
data,
|
|
||||||
unit,
|
|
||||||
records,
|
|
||||||
className,
|
|
||||||
loading,
|
|
||||||
...props
|
|
||||||
}) {
|
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const [theme] = useTheme();
|
const [theme] = useTheme();
|
||||||
|
const { locale } = useLocale();
|
||||||
|
|
||||||
const colors = useMemo(() => {
|
const colors = useMemo(() => {
|
||||||
const primaryColor = colord(THEME_COLORS[theme].primary);
|
const primaryColor = colord(THEME_COLORS[theme].primary);
|
||||||
|
@ -52,7 +46,7 @@ export default function PageviewsChart({
|
||||||
...colors.views,
|
...colors.views,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}, [data]);
|
}, [data, locale, colors]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BarChart
|
<BarChart
|
||||||
|
@ -66,3 +60,5 @@ export default function PageviewsChart({
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default PageviewsChart;
|
||||||
|
|
|
@ -12,7 +12,7 @@ const filters = {
|
||||||
[FILTER_COMBINED]: paramFilter,
|
[FILTER_COMBINED]: paramFilter,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function QueryParametersTable({ websiteId, showFilters, ...props }) {
|
export function QueryParametersTable({ websiteId, showFilters, ...props }) {
|
||||||
const [filter, setFilter] = useState(FILTER_COMBINED);
|
const [filter, setFilter] = useState(FILTER_COMBINED);
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
|
@ -49,3 +49,5 @@ export default function QueryParametersTable({ websiteId, showFilters, ...props
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default QueryParametersTable;
|
||||||
|
|
|
@ -23,7 +23,7 @@ function mapData(data) {
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RealtimeChart({ data, unit, ...props }) {
|
export function RealtimeChart({ data, unit, ...props }) {
|
||||||
const endDate = startOfMinute(new Date());
|
const endDate = startOfMinute(new Date());
|
||||||
const startDate = subMinutes(endDate, REALTIME_RANGE);
|
const startDate = subMinutes(endDate, REALTIME_RANGE);
|
||||||
const prevEndDate = useRef(endDate);
|
const prevEndDate = useRef(endDate);
|
||||||
|
@ -58,3 +58,5 @@ export default function RealtimeChart({ data, unit, ...props }) {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default RealtimeChart;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import MetricsTable from './MetricsTable';
|
||||||
import FilterLink from 'components/common/FilterLink';
|
import FilterLink from 'components/common/FilterLink';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
|
|
||||||
export default function ReferrersTable({ websiteId, ...props }) {
|
export function ReferrersTable({ websiteId, ...props }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
const renderLink = ({ x: referrer }) => {
|
const renderLink = ({ x: referrer }) => {
|
||||||
|
@ -29,3 +29,5 @@ export default function ReferrersTable({ websiteId, ...props }) {
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default ReferrersTable;
|
||||||
|
|
|
@ -3,19 +3,25 @@ import { emptyFilter } from 'lib/filters';
|
||||||
import FilterLink from 'components/common/FilterLink';
|
import FilterLink from 'components/common/FilterLink';
|
||||||
import useLocale from 'hooks/useLocale';
|
import useLocale from 'hooks/useLocale';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
|
import useCountryNames from 'hooks/useCountryNames';
|
||||||
import regions from 'public/iso-3166-2.json';
|
import regions from 'public/iso-3166-2.json';
|
||||||
|
|
||||||
export default function RegionsTable({ websiteId, ...props }) {
|
export function RegionsTable({ websiteId, ...props }) {
|
||||||
const { locale } = useLocale();
|
const { locale } = useLocale();
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
const countryNames = useCountryNames(locale);
|
||||||
|
|
||||||
function renderLink({ x }) {
|
const renderLabel = x => {
|
||||||
|
return regions[x] ? `${regions[x]}, ${countryNames[x.split('-')[0]]}` : x;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderLink = ({ x: code }) => {
|
||||||
return (
|
return (
|
||||||
<div className={locale}>
|
<FilterLink id="region" className={locale} value={code} label={renderLabel(code)}>
|
||||||
<FilterLink id="region" value={x} label={regions[x] || x} />
|
<img src={`/images/flags/${code?.split('-')?.[0]?.toLowerCase() || 'xx'}.png`} alt={code} />
|
||||||
</div>
|
</FilterLink>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MetricsTable
|
<MetricsTable
|
||||||
|
@ -29,3 +35,5 @@ export default function RegionsTable({ websiteId, ...props }) {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default RegionsTable;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import MetricsTable from './MetricsTable';
|
import MetricsTable from './MetricsTable';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
|
|
||||||
export default function ScreenTable({ websiteId, ...props }) {
|
export function ScreenTable({ websiteId, ...props }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -14,3 +14,5 @@ export default function ScreenTable({ websiteId, ...props }) {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default ScreenTable;
|
||||||
|
|
|
@ -18,8 +18,9 @@ import Icons from 'components/icons';
|
||||||
import useSticky from 'hooks/useSticky';
|
import useSticky from 'hooks/useSticky';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
import styles from './WebsiteChart.module.css';
|
import styles from './WebsiteChart.module.css';
|
||||||
|
import useLocale from 'hooks/useLocale';
|
||||||
|
|
||||||
export default function WebsiteChart({
|
export function WebsiteChart({
|
||||||
websiteId,
|
websiteId,
|
||||||
name,
|
name,
|
||||||
domain,
|
domain,
|
||||||
|
@ -72,6 +73,7 @@ export default function WebsiteChart({
|
||||||
return { pageviews: [], sessions: [] };
|
return { pageviews: [], sessions: [] };
|
||||||
}, [data, modified]);
|
}, [data, modified]);
|
||||||
|
|
||||||
|
const { dir } = useLocale();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WebsiteHeader websiteId={websiteId} name={name} domain={domain}>
|
<WebsiteHeader websiteId={websiteId} name={name} domain={domain}>
|
||||||
|
@ -80,8 +82,10 @@ export default function WebsiteChart({
|
||||||
<Button variant="primary">
|
<Button variant="primary">
|
||||||
<Text>{formatMessage(labels.viewDetails)}</Text>
|
<Text>{formatMessage(labels.viewDetails)}</Text>
|
||||||
<Icon>
|
<Icon>
|
||||||
|
<Icon rotate={dir === 'rtl' ? 180 : 0}>
|
||||||
<Icons.ArrowRight />
|
<Icons.ArrowRight />
|
||||||
</Icon>
|
</Icon>
|
||||||
|
</Icon>
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
@ -124,3 +128,5 @@ export default function WebsiteChart({
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default WebsiteChart;
|
||||||
|
|
|
@ -3,7 +3,7 @@ import Favicon from 'components/common/Favicon';
|
||||||
import ActiveUsers from './ActiveUsers';
|
import ActiveUsers from './ActiveUsers';
|
||||||
import styles from './WebsiteHeader.module.css';
|
import styles from './WebsiteHeader.module.css';
|
||||||
|
|
||||||
export default function WebsiteHeader({ websiteId, name, domain, children }) {
|
export function WebsiteHeader({ websiteId, name, domain, children }) {
|
||||||
return (
|
return (
|
||||||
<Row className={styles.header} justifyContent="center">
|
<Row className={styles.header} justifyContent="center">
|
||||||
<Column className={styles.title} variant="two">
|
<Column className={styles.title} variant="two">
|
||||||
|
@ -17,3 +17,5 @@ export default function WebsiteHeader({ websiteId, name, domain, children }) {
|
||||||
</Row>
|
</Row>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default WebsiteHeader;
|
||||||
|
|
|
@ -11,7 +11,7 @@ import Script from 'next/script';
|
||||||
import { Button, Column, Row } from 'react-basics';
|
import { Button, Column, Row } from 'react-basics';
|
||||||
import styles from './TestConsole.module.css';
|
import styles from './TestConsole.module.css';
|
||||||
|
|
||||||
export default function TestConsole() {
|
export function TestConsole() {
|
||||||
const { get, useQuery } = useApi();
|
const { get, useQuery } = useApi();
|
||||||
const { data, isLoading, error } = useQuery(['websites:me'], () => get('/me/websites'));
|
const { data, isLoading, error } = useQuery(['websites:me'], () => get('/me/websites'));
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
@ -132,3 +132,5 @@ export default function TestConsole() {
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default TestConsole;
|
||||||
|
|
|
@ -10,8 +10,9 @@ import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
|
||||||
import useApi from 'hooks/useApi';
|
import useApi from 'hooks/useApi';
|
||||||
import useDashboard from 'store/dashboard';
|
import useDashboard from 'store/dashboard';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
|
import useLocale from 'hooks/useLocale';
|
||||||
|
|
||||||
export default function Dashboard({ userId }) {
|
export function Dashboard({ userId }) {
|
||||||
const { formatMessage, labels, messages } = useMessages();
|
const { formatMessage, labels, messages } = useMessages();
|
||||||
const dashboard = useDashboard();
|
const dashboard = useDashboard();
|
||||||
const { showCharts, limit, editing } = dashboard;
|
const { showCharts, limit, editing } = dashboard;
|
||||||
|
@ -19,6 +20,7 @@ export default function Dashboard({ userId }) {
|
||||||
const { get, useQuery } = useApi();
|
const { get, useQuery } = useApi();
|
||||||
const { data, isLoading, error } = useQuery(['websites'], () => get('/websites', { userId }));
|
const { data, isLoading, error } = useQuery(['websites'], () => get('/websites', { userId }));
|
||||||
const hasData = data && data.length !== 0;
|
const hasData = data && data.length !== 0;
|
||||||
|
const { dir } = useLocale();
|
||||||
|
|
||||||
function handleMore() {
|
function handleMore() {
|
||||||
setMax(max + limit);
|
setMax(max + limit);
|
||||||
|
@ -33,7 +35,7 @@ export default function Dashboard({ userId }) {
|
||||||
<EmptyPlaceholder message={formatMessage(messages.noWebsitesConfigured)}>
|
<EmptyPlaceholder message={formatMessage(messages.noWebsitesConfigured)}>
|
||||||
<Link href="/settings/websites">
|
<Link href="/settings/websites">
|
||||||
<Button>
|
<Button>
|
||||||
<Icon>
|
<Icon rotate={dir === 'rtl' ? 180 : 0}>
|
||||||
<Icons.ArrowRight />
|
<Icons.ArrowRight />
|
||||||
</Icon>
|
</Icon>
|
||||||
<Text>{formatMessage(messages.goToSettings)}</Text>
|
<Text>{formatMessage(messages.goToSettings)}</Text>
|
||||||
|
@ -48,7 +50,7 @@ export default function Dashboard({ userId }) {
|
||||||
{max < data.length && (
|
{max < data.length && (
|
||||||
<Flexbox justifyContent="center">
|
<Flexbox justifyContent="center">
|
||||||
<Button onClick={handleMore}>
|
<Button onClick={handleMore}>
|
||||||
<Icon>
|
<Icon rotate={dir === 'rtl' ? 180 : 0}>
|
||||||
<Icons.More />
|
<Icons.More />
|
||||||
</Icon>
|
</Icon>
|
||||||
<Text>{formatMessage(labels.more)}</Text>
|
<Text>{formatMessage(labels.more)}</Text>
|
||||||
|
@ -60,3 +62,5 @@ export default function Dashboard({ userId }) {
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Dashboard;
|
||||||
|
|
|
@ -9,7 +9,7 @@ import styles from './DashboardEdit.module.css';
|
||||||
|
|
||||||
const dragId = 'dashboard-website-ordering';
|
const dragId = 'dashboard-website-ordering';
|
||||||
|
|
||||||
export default function DashboardEdit({ websites }) {
|
export function DashboardEdit({ websites }) {
|
||||||
const settings = useDashboard();
|
const settings = useDashboard();
|
||||||
const { websiteOrder } = settings;
|
const { websiteOrder } = settings;
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
@ -98,3 +98,5 @@ export default function DashboardEdit({ websites }) {
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default DashboardEdit;
|
||||||
|
|
|
@ -3,7 +3,7 @@ import Icons from 'components/icons';
|
||||||
import { saveDashboard } from 'store/dashboard';
|
import { saveDashboard } from 'store/dashboard';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
|
|
||||||
export default function DashboardSettingsButton() {
|
export function DashboardSettingsButton() {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
const menuOptions = [
|
const menuOptions = [
|
||||||
|
@ -42,3 +42,5 @@ export default function DashboardSettingsButton() {
|
||||||
</PopupTrigger>
|
</PopupTrigger>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default DashboardSettingsButton;
|
||||||
|
|
|
@ -17,7 +17,7 @@ import useMessages from 'hooks/useMessages';
|
||||||
import Logo from 'assets/logo.svg';
|
import Logo from 'assets/logo.svg';
|
||||||
import styles from './LoginForm.module.css';
|
import styles from './LoginForm.module.css';
|
||||||
|
|
||||||
export default function LoginForm() {
|
export function LoginForm() {
|
||||||
const { formatMessage, labels, getMessage } = useMessages();
|
const { formatMessage, labels, getMessage } = useMessages();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { post } = useApi();
|
const { post } = useApi();
|
||||||
|
@ -53,10 +53,12 @@ export default function LoginForm() {
|
||||||
</FormRow>
|
</FormRow>
|
||||||
<FormButtons>
|
<FormButtons>
|
||||||
<SubmitButton className={styles.button} variant="primary" disabled={isLoading}>
|
<SubmitButton className={styles.button} variant="primary" disabled={isLoading}>
|
||||||
Log in
|
{formatMessage(labels.login)}
|
||||||
</SubmitButton>
|
</SubmitButton>
|
||||||
</FormButtons>
|
</FormButtons>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default LoginForm;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import Head from 'next/head';
|
||||||
import useLocale from 'hooks/useLocale';
|
import useLocale from 'hooks/useLocale';
|
||||||
import styles from './LoginLayout.module.css';
|
import styles from './LoginLayout.module.css';
|
||||||
|
|
||||||
export default function LoginLayout({ children }) {
|
export function LoginLayout({ children }) {
|
||||||
const { dir } = useLocale();
|
const { dir } = useLocale();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -14,3 +14,5 @@ export default function LoginLayout({ children }) {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default LoginLayout;
|
||||||
|
|
|
@ -4,7 +4,7 @@ import useLocale from 'hooks/useLocale';
|
||||||
import useCountryNames from 'hooks/useCountryNames';
|
import useCountryNames from 'hooks/useCountryNames';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
|
|
||||||
export default function RealtimeCountries({ data }) {
|
export function RealtimeCountries({ data }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { locale } = useLocale();
|
const { locale } = useLocale();
|
||||||
const countryNames = useCountryNames(locale);
|
const countryNames = useCountryNames(locale);
|
||||||
|
@ -23,3 +23,5 @@ export default function RealtimeCountries({ data }) {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default RealtimeCountries;
|
||||||
|
|
|
@ -25,7 +25,7 @@ function mergeData(state = [], data = [], time) {
|
||||||
.filter(({ timestamp }) => timestamp >= time);
|
.filter(({ timestamp }) => timestamp >= time);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RealtimeDashboard({ websiteId }) {
|
export function RealtimeDashboard({ websiteId }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [currentData, setCurrentData] = useState();
|
const [currentData, setCurrentData] = useState();
|
||||||
|
@ -63,7 +63,7 @@ export default function RealtimeDashboard({ websiteId }) {
|
||||||
currentData.countries = percentFilter(
|
currentData.countries = percentFilter(
|
||||||
currentData.sessions
|
currentData.sessions
|
||||||
.reduce((arr, data) => {
|
.reduce((arr, data) => {
|
||||||
if (!arr.find(({ sessionId }) => sessionId === data.sessionId)) {
|
if (!arr.find(({ id }) => id === data.id)) {
|
||||||
return arr.concat(data);
|
return arr.concat(data);
|
||||||
}
|
}
|
||||||
return arr;
|
return arr;
|
||||||
|
@ -84,7 +84,7 @@ export default function RealtimeDashboard({ websiteId }) {
|
||||||
);
|
);
|
||||||
|
|
||||||
currentData.visitors = currentData.sessions.reduce((arr, val) => {
|
currentData.visitors = currentData.sessions.reduce((arr, val) => {
|
||||||
if (!arr.find(({ sessionId }) => sessionId === val.sessionId)) {
|
if (!arr.find(({ id }) => id === val.id)) {
|
||||||
return arr.concat(val);
|
return arr.concat(val);
|
||||||
}
|
}
|
||||||
return arr;
|
return arr;
|
||||||
|
@ -125,3 +125,5 @@ export default function RealtimeDashboard({ websiteId }) {
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default RealtimeDashboard;
|
||||||
|
|
|
@ -2,21 +2,33 @@ import MetricCard from 'components/metrics/MetricCard';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
import styles from './RealtimeHeader.module.css';
|
import styles from './RealtimeHeader.module.css';
|
||||||
|
|
||||||
export default function RealtimeHeader({ data = {} }) {
|
export function RealtimeHeader({ data = {} }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { pageviews, visitors, events, countries } = data;
|
const { pageviews, visitors, events, countries } = data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
<div className={styles.metrics}>
|
<div className={styles.metrics}>
|
||||||
<MetricCard label={formatMessage(labels.views)} value={pageviews?.length} hideComparison />
|
|
||||||
<MetricCard
|
<MetricCard
|
||||||
|
className={styles.card}
|
||||||
|
label={formatMessage(labels.views)}
|
||||||
|
value={pageviews?.length}
|
||||||
|
hideComparison
|
||||||
|
/>
|
||||||
|
<MetricCard
|
||||||
|
className={styles.card}
|
||||||
label={formatMessage(labels.visitors)}
|
label={formatMessage(labels.visitors)}
|
||||||
value={visitors?.length}
|
value={visitors?.length}
|
||||||
hideComparison
|
hideComparison
|
||||||
/>
|
/>
|
||||||
<MetricCard label={formatMessage(labels.events)} value={events?.length} hideComparison />
|
|
||||||
<MetricCard
|
<MetricCard
|
||||||
|
className={styles.card}
|
||||||
|
label={formatMessage(labels.events)}
|
||||||
|
value={events?.length}
|
||||||
|
hideComparison
|
||||||
|
/>
|
||||||
|
<MetricCard
|
||||||
|
className={styles.card}
|
||||||
label={formatMessage(labels.countries)}
|
label={formatMessage(labels.countries)}
|
||||||
value={countries?.length}
|
value={countries?.length}
|
||||||
hideComparison
|
hideComparison
|
||||||
|
@ -25,3 +37,5 @@ export default function RealtimeHeader({ data = {} }) {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default RealtimeHeader;
|
||||||
|
|
|
@ -7,4 +7,15 @@
|
||||||
|
|
||||||
.metrics {
|
.metrics {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
justify-self: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 992px) {
|
||||||
|
.card {
|
||||||
|
flex-basis: calc(50% - 20px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import useApi from 'hooks/useApi';
|
||||||
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
|
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
|
|
||||||
export default function RealtimeHome() {
|
export function RealtimeHome() {
|
||||||
const { formatMessage, labels, messages } = useMessages();
|
const { formatMessage, labels, messages } = useMessages();
|
||||||
const { get, useQuery } = useApi();
|
const { get, useQuery } = useApi();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
@ -27,3 +27,5 @@ export default function RealtimeHome() {
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default RealtimeHome;
|
||||||
|
|
|
@ -25,7 +25,7 @@ const icons = {
|
||||||
[TYPE_EVENT]: <Icons.Bolt />,
|
[TYPE_EVENT]: <Icons.Bolt />,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RealtimeLog({ data, websiteDomain }) {
|
export function RealtimeLog({ data, websiteDomain }) {
|
||||||
const { formatMessage, labels, messages, FormattedMessage } = useMessages();
|
const { formatMessage, labels, messages, FormattedMessage } = useMessages();
|
||||||
const { locale } = useLocale();
|
const { locale } = useLocale();
|
||||||
const countryNames = useCountryNames(locale);
|
const countryNames = useCountryNames(locale);
|
||||||
|
@ -142,7 +142,7 @@ export default function RealtimeLog({ data, websiteDomain }) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.table}>
|
<div className={styles.table}>
|
||||||
<FilterButtons items={buttons} selectedKey={filter} onSelect={setFilter} />
|
<FilterButtons items={buttons} selectedKey={filter} onSelect={setFilter} />
|
||||||
<div className={styles.header}>{formatMessage(labels.logs)}</div>
|
<div className={styles.header}>{formatMessage(labels.activityLog)}</div>
|
||||||
<div className={styles.body}>
|
<div className={styles.body}>
|
||||||
{logs?.length === 0 && <NoData />}
|
{logs?.length === 0 && <NoData />}
|
||||||
{logs?.length > 0 && (
|
{logs?.length > 0 && (
|
||||||
|
@ -154,3 +154,5 @@ export default function RealtimeLog({ data, websiteDomain }) {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default RealtimeLog;
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
font-size: var(--font-size-md);
|
font-size: var(--font-size-md);
|
||||||
line-height: 50px;
|
line-height: 40px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,6 +42,7 @@
|
||||||
|
|
||||||
.detail {
|
.detail {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
|
@ -6,7 +6,7 @@ import DataTable from 'components/metrics/DataTable';
|
||||||
import { FILTER_PAGES, FILTER_REFERRERS } from 'lib/constants';
|
import { FILTER_PAGES, FILTER_REFERRERS } from 'lib/constants';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
|
|
||||||
export default function RealtimeUrls({ websiteDomain, data = {} }) {
|
export function RealtimeUrls({ websiteDomain, data = {} }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { pageviews } = data;
|
const { pageviews } = data;
|
||||||
const [filter, setFilter] = useState(FILTER_REFERRERS);
|
const [filter, setFilter] = useState(FILTER_REFERRERS);
|
||||||
|
@ -97,3 +97,5 @@ export default function RealtimeUrls({ websiteDomain, data = {} }) {
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default RealtimeUrls;
|
||||||
|
|
|
@ -4,7 +4,7 @@ import useDateRange from 'hooks/useDateRange';
|
||||||
import { DEFAULT_DATE_RANGE } from 'lib/constants';
|
import { DEFAULT_DATE_RANGE } from 'lib/constants';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
|
|
||||||
export default function DateRangeSetting() {
|
export function DateRangeSetting() {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const [dateRange, setDateRange] = useDateRange();
|
const [dateRange, setDateRange] = useDateRange();
|
||||||
const { startDate, endDate, value } = dateRange;
|
const { startDate, endDate, value } = dateRange;
|
||||||
|
@ -18,3 +18,5 @@ export default function DateRangeSetting() {
|
||||||
</Flexbox>
|
</Flexbox>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default DateRangeSetting;
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { DEFAULT_LOCALE } from 'lib/constants';
|
||||||
import { languages } from 'lib/lang';
|
import { languages } from 'lib/lang';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
|
|
||||||
export default function LanguageSetting() {
|
export function LanguageSetting() {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { locale, saveLocale } = useLocale();
|
const { locale, saveLocale } = useLocale();
|
||||||
const options = Object.keys(languages);
|
const options = Object.keys(languages);
|
||||||
|
@ -28,3 +28,5 @@ export default function LanguageSetting() {
|
||||||
</Flexbox>
|
</Flexbox>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default LanguageSetting;
|
||||||
|
|
|
@ -3,7 +3,7 @@ import PasswordEditForm from 'components/pages/settings/profile/PasswordEditForm
|
||||||
import Icons from 'components/icons';
|
import Icons from 'components/icons';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
|
|
||||||
export default function PasswordChangeButton() {
|
export function PasswordChangeButton() {
|
||||||
const { formatMessage, labels, messages } = useMessages();
|
const { formatMessage, labels, messages } = useMessages();
|
||||||
const { toast, showToast } = useToast();
|
const { toast, showToast } = useToast();
|
||||||
|
|
||||||
|
@ -28,3 +28,5 @@ export default function PasswordChangeButton() {
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default PasswordChangeButton;
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { Form, FormRow, FormInput, FormButtons, PasswordField, Button } from 're
|
||||||
import useApi from 'hooks/useApi';
|
import useApi from 'hooks/useApi';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
|
|
||||||
export default function PasswordEditForm({ onSave, onClose }) {
|
export function PasswordEditForm({ onSave, onClose }) {
|
||||||
const { formatMessage, labels, messages } = useMessages();
|
const { formatMessage, labels, messages } = useMessages();
|
||||||
const { post, useMutation } = useApi();
|
const { post, useMutation } = useApi();
|
||||||
const { mutate, error, isLoading } = useMutation(data => post('/me/password', data));
|
const { mutate, error, isLoading } = useMutation(data => post('/me/password', data));
|
||||||
|
@ -37,7 +37,7 @@ export default function PasswordEditForm({ onSave, onClose }) {
|
||||||
name="newPassword"
|
name="newPassword"
|
||||||
rules={{
|
rules={{
|
||||||
required: 'Required',
|
required: 'Required',
|
||||||
minLength: { value: 8, message: formatMessage(messages.minPasswordLength) },
|
minLength: { value: 8, message: formatMessage(messages.minPasswordLength, { n: 8 }) },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<PasswordField autoComplete="new-password" />
|
<PasswordField autoComplete="new-password" />
|
||||||
|
@ -48,7 +48,7 @@ export default function PasswordEditForm({ onSave, onClose }) {
|
||||||
name="confirmPassword"
|
name="confirmPassword"
|
||||||
rules={{
|
rules={{
|
||||||
required: formatMessage(labels.required),
|
required: formatMessage(labels.required),
|
||||||
minLength: { value: 8, message: formatMessage(messages.minPasswordLength) },
|
minLength: { value: 8, message: formatMessage(messages.minPasswordLength, { n: 8 }) },
|
||||||
validate: samePassword,
|
validate: samePassword,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -64,3 +64,5 @@ export default function PasswordEditForm({ onSave, onClose }) {
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default PasswordEditForm;
|
||||||
|
|
|
@ -8,7 +8,7 @@ import useUser from 'hooks/useUser';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
import useConfig from 'hooks/useConfig';
|
import useConfig from 'hooks/useConfig';
|
||||||
|
|
||||||
export default function ProfileDetails() {
|
export function ProfileDetails() {
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { cloudMode } = useConfig();
|
const { cloudMode } = useConfig();
|
||||||
|
@ -45,3 +45,5 @@ export default function ProfileDetails() {
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default ProfileDetails;
|
||||||
|
|
|
@ -3,7 +3,7 @@ import PageHeader from 'components/layout/PageHeader';
|
||||||
import ProfileDetails from './ProfileDetails';
|
import ProfileDetails from './ProfileDetails';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
|
|
||||||
export default function ProfileSettings() {
|
export function ProfileSettings() {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -13,3 +13,5 @@ export default function ProfileSettings() {
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default ProfileSettings;
|
||||||
|
|
|
@ -5,7 +5,7 @@ import Sun from 'assets/sun.svg';
|
||||||
import Moon from 'assets/moon.svg';
|
import Moon from 'assets/moon.svg';
|
||||||
import styles from './ThemeSetting.module.css';
|
import styles from './ThemeSetting.module.css';
|
||||||
|
|
||||||
export default function ThemeSetting() {
|
export function ThemeSetting() {
|
||||||
const [theme, setTheme] = useTheme();
|
const [theme, setTheme] = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -29,3 +29,5 @@ export default function ThemeSetting() {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default ThemeSetting;
|
||||||
|
|
|
@ -4,7 +4,7 @@ import useTimezone from 'hooks/useTimezone';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
import { getTimezone } from 'lib/date';
|
import { getTimezone } from 'lib/date';
|
||||||
|
|
||||||
export default function TimezoneSetting() {
|
export function TimezoneSetting() {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const [timezone, saveTimezone] = useTimezone();
|
const [timezone, saveTimezone] = useTimezone();
|
||||||
const options = listTimeZones();
|
const options = listTimeZones();
|
||||||
|
@ -26,3 +26,5 @@ export default function TimezoneSetting() {
|
||||||
</Flexbox>
|
</Flexbox>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default TimezoneSetting;
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {
|
||||||
import useApi from 'hooks/useApi';
|
import useApi from 'hooks/useApi';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
|
|
||||||
export default function TeamAddForm({ onSave, onClose }) {
|
export function TeamAddForm({ onSave, onClose }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { post, useMutation } = useApi();
|
const { post, useMutation } = useApi();
|
||||||
const { mutate, error, isLoading } = useMutation(data => post('/teams', data));
|
const { mutate, error, isLoading } = useMutation(data => post('/teams', data));
|
||||||
|
@ -44,3 +44,5 @@ export default function TeamAddForm({ onSave, onClose }) {
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default TeamAddForm;
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { Button, Dropdown, Form, FormButtons, FormRow, Item, SubmitButton } from
|
||||||
import WebsiteTags from './WebsiteTags';
|
import WebsiteTags from './WebsiteTags';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
|
|
||||||
export default function TeamAddWebsiteForm({ teamId, onSave, onClose }) {
|
export function TeamAddWebsiteForm({ teamId, onSave, onClose }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { get, post, useQuery, useMutation } = useApi();
|
const { get, post, useQuery, useMutation } = useApi();
|
||||||
const { mutate, error } = useMutation(data => post(`/teams/${teamId}/websites`, data));
|
const { mutate, error } = useMutation(data => post(`/teams/${teamId}/websites`, data));
|
||||||
|
@ -44,7 +44,7 @@ export default function TeamAddWebsiteForm({ teamId, onSave, onClose }) {
|
||||||
<>
|
<>
|
||||||
<Form onSubmit={handleSubmit} error={error} ref={formRef}>
|
<Form onSubmit={handleSubmit} error={error} ref={formRef}>
|
||||||
<FormRow label={formatMessage(labels.websites)}>
|
<FormRow label={formatMessage(labels.websites)}>
|
||||||
<Dropdown items={websites} onChange={handleAddWebsite}>
|
<Dropdown items={websites} onChange={handleAddWebsite} style={{ width: 300 }}>
|
||||||
{({ id, name }) => <Item key={id}>{name}</Item>}
|
{({ id, name }) => <Item key={id}>{name}</Item>}
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</FormRow>
|
</FormRow>
|
||||||
|
@ -59,3 +59,5 @@ export default function TeamAddWebsiteForm({ teamId, onSave, onClose }) {
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default TeamAddWebsiteForm;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Button, Form, FormButtons, SubmitButton } from 'react-basics';
|
||||||
import useApi from 'hooks/useApi';
|
import useApi from 'hooks/useApi';
|
||||||
import useMessages from 'hooks/useMessages';
|
import useMessages from 'hooks/useMessages';
|
||||||
|
|
||||||
export default function TeamDeleteForm({ teamId, teamName, onSave, onClose }) {
|
export function TeamDeleteForm({ teamId, teamName, onSave, onClose }) {
|
||||||
const { formatMessage, labels, messages, FormattedMessage } = useMessages();
|
const { formatMessage, labels, messages, FormattedMessage } = useMessages();
|
||||||
const { del, useMutation } = useApi();
|
const { del, useMutation } = useApi();
|
||||||
const { mutate, error, isLoading } = useMutation(data => del(`/teams/${teamId}`, data));
|
const { mutate, error, isLoading } = useMutation(data => del(`/teams/${teamId}`, data));
|
||||||
|
@ -30,3 +30,5 @@ export default function TeamDeleteForm({ teamId, teamName, onSave, onClose }) {
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default TeamDeleteForm;
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue