+
- } onClick={onClose} />
+
{items.map(({ label, value }) => (
-
- {label}
+
+
+ {label}
+
))}
diff --git a/components/common/RefreshButton.js b/components/common/RefreshButton.js
index 507ecd34..a23ea9c5 100644
--- a/components/common/RefreshButton.js
+++ b/components/common/RefreshButton.js
@@ -1,12 +1,10 @@
import { useState, useEffect, useCallback } from 'react';
-import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import useStore from 'store/queries';
import { setDateRange } from 'store/websites';
import { Button, Icon } from 'react-basics';
-import Refresh from 'assets/redo.svg';
-import Dots from 'assets/ellipsis-h.svg';
import useDateRange from 'hooks/useDateRange';
+import Icons from 'components/icons';
function RefreshButton({ websiteId }) {
const [dateRange] = useDateRange(websiteId);
@@ -17,7 +15,7 @@ function RefreshButton({ websiteId }) {
function handleClick() {
if (!loading && dateRange) {
setLoading(true);
- if (/^[\d]+/.test(dateRange.value)) {
+ if (/^\d+/.test(dateRange.value)) {
setDateRange(websiteId, dateRange.value);
} else {
setDateRange(websiteId, dateRange);
@@ -36,13 +34,11 @@ function RefreshButton({ websiteId }) {
size="small"
onClick={handleClick}
>
-
{loading ? : }
+
+
+
);
}
-RefreshButton.propTypes = {
- websiteId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
-};
-
export default RefreshButton;
diff --git a/components/common/UpdateNotice.module.css b/components/common/UpdateNotice.module.css
index 5161ea71..1837b4df 100644
--- a/components/common/UpdateNotice.module.css
+++ b/components/common/UpdateNotice.module.css
@@ -6,7 +6,6 @@
}
.message {
- font-size: var(--font-size-sm);
font-weight: 600;
flex: 1;
text-align: center;
diff --git a/components/forms/EventDataForm.js b/components/forms/EventDataForm.js
deleted file mode 100644
index e72e90f8..00000000
--- a/components/forms/EventDataForm.js
+++ /dev/null
@@ -1,262 +0,0 @@
-import classNames from 'classnames';
-import { Button } from 'react-basics';
-import DateFilter from 'components/common/DateFilter';
-import DropDown from 'components/common/DropDown';
-import FormLayout, {
- FormButtons,
- FormError,
- FormMessage,
- FormRow,
-} from 'components/layout/FormLayout';
-import DataTable from 'components/metrics/DataTable';
-import FilterTags from 'components/metrics/FilterTags';
-import { Field, Form, Formik } from 'formik';
-import useApi from 'hooks/useApi';
-import useDateRange from 'hooks/useDateRange';
-import { useState, useEffect } from 'react';
-import { FormattedMessage } from 'react-intl';
-import styles from './EventDataForm.module.css';
-import useTimezone from 'hooks/useTimezone';
-
-export const filterOptions = [
- { label: 'Count', value: 'count' },
- { label: 'Average', value: 'avg' },
- { label: 'Minimum', value: 'min' },
- { label: 'Maximum', value: 'max' },
- { label: 'Sum', value: 'sum' },
-];
-
-export const dateOptions = [
- { label:
, value: '1day' },
- {
- label: (
-
- ),
- value: '24hour',
- },
- {
- label:
,
- value: '-1day',
- },
- {
- label:
,
- value: '1week',
- divider: true,
- },
- {
- label: (
-
- ),
- value: '7day',
- },
- {
- label:
,
- value: '1month',
- divider: true,
- },
- {
- label: (
-
- ),
- value: '30day',
- },
- {
- label: (
-
- ),
- value: '90day',
- },
- { label:
, value: '1year' },
- {
- label:
,
- value: 'custom',
- divider: true,
- },
-];
-
-export default function EventDataForm({ websiteId, onClose, className }) {
- const { post } = useApi();
- const [message, setMessage] = useState();
- const [columns, setColumns] = useState({});
- const [filters, setFilters] = useState({});
- const [data, setData] = useState([]);
- const [dateRange, setDateRange] = useDateRange('report');
- const { startDate, endDate, value } = dateRange;
- const [timezone] = useTimezone();
- const [isValid, setIsValid] = useState(false);
-
- useEffect(() => {
- if (Object.keys(columns).length > 0) {
- setIsValid(true);
- } else {
- setIsValid(false);
- }
- }, [columns]);
-
- const handleAddTag = (value, list, setState, resetForm) => {
- setState({ ...list, [`${value.field}`]: value.value });
- resetForm();
- };
-
- const handleRemoveTag = (value, list, setState) => {
- const newList = { ...list };
-
- delete newList[`${value}`];
-
- setState(newList);
- };
-
- const handleSubmit = async () => {
- const params = {
- website_id: websiteId,
- startAt: +startDate,
- endAt: +endDate,
- timezone,
- columns,
- filters,
- };
-
- const { ok, data } = await post(`/websites/${websiteId}/eventdata`, params);
-
- if (!ok) {
- setMessage(
);
- setData([]);
- } else {
- setData(data);
- setMessage(null);
- }
- };
-
- return (
- <>
-
{message}
-
-
-
-
-
-
-
-
-
-
-
- handleAddTag(value, columns, setColumns, resetForm)
- }
- >
- {({ values, setFieldValue }) => (
-
- )}
-
-
handleRemoveTag(value, columns, setColumns)}
- />
-
-
-
- handleAddTag(value, filters, setFilters, resetForm)
- }
- >
- {({ values }) => (
-
- )}
-
-
handleRemoveTag(value, filters, setFilters)}
- />
-
-
-
-
-
-
-
-
-
-
-
- >
- );
-}
diff --git a/components/forms/Form.module.css b/components/forms/Form.module.css
deleted file mode 100644
index 9185da43..00000000
--- a/components/forms/Form.module.css
+++ /dev/null
@@ -1,63 +0,0 @@
-.form {
- display: flex;
- flex-direction: column;
- gap: 30px;
- width: 300px;
- margin: 0 auto;
-}
-
-.header {
- font-size: 24px;
- font-weight: 700;
- text-align: center;
- margin: 30px auto;
-}
-
-.info {
- text-align: center;
- padding: 30px 0;
-}
-
-.footer {
- display: flex;
- flex-direction: column;
- gap: 20px;
- font-size: 14px;
- text-align: center;
- margin: 30px auto;
-}
-
-.footer a {
- font-weight: 600;
-}
-
-.buttons {
- justify-content: center;
-}
-
-.button {
- flex: 1;
- justify-content: center;
-}
-
-.error {
- width: 600px;
- margin: 0 auto 30px;
- background: var(--base50);
- padding: 16px;
- color: var(--red400);
- border: 1px solid var(--red400);
- border-radius: 5px;
- text-align: center;
-}
-
-.success {
- width: 600px;
- margin: 60px auto;
- background: var(--base50);
- padding: 16px;
- color: var(--green400);
- border: 1px solid var(--green400);
- border-radius: 5px;
- text-align: center;
-}
diff --git a/components/forms/LoginForm.module.css b/components/forms/LoginForm.module.css
deleted file mode 100644
index dfd5456a..00000000
--- a/components/forms/LoginForm.module.css
+++ /dev/null
@@ -1,23 +0,0 @@
-.login {
- display: flex;
- flex-direction: column;
- margin-top: 80px;
-}
-
-.login form {
- margin: 0 auto;
-}
-
-.icon {
- display: flex;
- justify-content: center;
- margin: 0 auto;
-}
-
-.header {
- margin-bottom: 30px;
-}
-
-.header h1 {
- margin: 12px 0;
-}
diff --git a/components/forms/TeamAddForm.js b/components/forms/TeamAddForm.js
deleted file mode 100644
index 78c316ab..00000000
--- a/components/forms/TeamAddForm.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import { useRef } from 'react';
-import { Form, FormInput, FormButtons, TextField, Button } from 'react-basics';
-import useApi from 'hooks/useApi';
-import styles from './Form.module.css';
-import { useMutation } from '@tanstack/react-query';
-
-export default function TeamAddForm({ onSave, onClose }) {
- const { post } = useApi();
- const { mutate, error, isLoading } = useMutation(data => post('/teams', data));
- const ref = useRef(null);
-
- const handleSubmit = async data => {
- mutate(data, {
- onSuccess: async () => {
- onSave();
- },
- });
- };
-
- return (
-
- );
-}
diff --git a/components/forms/TeamEditForm.js b/components/forms/TeamEditForm.js
deleted file mode 100644
index 2ba8c661..00000000
--- a/components/forms/TeamEditForm.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import { SubmitButton, Form, FormInput, FormRow, FormButtons, TextField } from 'react-basics';
-import { useMutation } from '@tanstack/react-query';
-import { useRef } from 'react';
-import useApi from 'hooks/useApi';
-
-export default function TeamEditForm({ teamId, data, onSave }) {
- const { post } = useApi();
- const { mutate, error } = useMutation(data => post(`/teams/${teamId}`, data));
- const ref = useRef(null);
-
- const handleSubmit = async data => {
- mutate(data, {
- onSuccess: async () => {
- ref.current.reset(data);
- onSave(data);
- },
- });
- };
-
- return (
-
- );
-}
diff --git a/components/forms/TrackingCodeForm.js b/components/forms/TrackingCodeForm.js
deleted file mode 100644
index 191248f5..00000000
--- a/components/forms/TrackingCodeForm.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import useConfig from 'hooks/useConfig';
-import { useRef } from 'react';
-import { Form, FormRow, TextArea } from 'react-basics';
-
-export default function TrackingCodeForm({ websiteId }) {
- const ref = useRef(null);
- const { trackerScriptName } = useConfig();
- const code = ``;
-
- return (
- <>
-
- >
- );
-}
diff --git a/components/forms/UserDeleteForm.js b/components/forms/UserDeleteForm.js
deleted file mode 100644
index beefd368..00000000
--- a/components/forms/UserDeleteForm.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import { useMutation } from '@tanstack/react-query';
-import useApi from 'hooks/useApi';
-import { Button, Form, FormButtons, FormInput, SubmitButton, TextField } from 'react-basics';
-import styles from './Form.module.css';
-
-const CONFIRM_VALUE = 'DELETE';
-
-export default function UserDeleteForm({ userId, onSave, onClose }) {
- const { del } = useApi();
- const { mutate, error, isLoading } = useMutation(data => del(`/users/${userId}`, data));
-
- const handleSubmit = async data => {
- mutate(data, {
- onSuccess: async () => {
- onSave();
- },
- });
- };
-
- return (
-
- );
-}
diff --git a/components/forms/UserEditForm.js b/components/forms/UserEditForm.js
deleted file mode 100644
index 28233250..00000000
--- a/components/forms/UserEditForm.js
+++ /dev/null
@@ -1,64 +0,0 @@
-import {
- Dropdown,
- Item,
- Form,
- FormButtons,
- FormInput,
- TextField,
- SubmitButton,
-} from 'react-basics';
-import { useRef } from 'react';
-import { useMutation } from '@tanstack/react-query';
-import useApi from 'hooks/useApi';
-import { ROLES } from 'lib/constants';
-import styles from './UserForm.module.css';
-
-const items = [
- {
- value: ROLES.user,
- label: 'User',
- },
- {
- value: ROLES.admin,
- label: 'Admin',
- },
-];
-
-export default function UserEditForm({ data, onSave }) {
- const { id } = data;
- const { post } = useApi();
- const { mutate, error } = useMutation(({ username }) => post(`/user/${id}`, { username }));
- const ref = useRef(null);
-
- const handleSubmit = async data => {
- mutate(data, {
- onSuccess: async () => {
- onSave(data);
- ref.current.reset(data);
- },
- });
- };
-
- return (
-
- );
-}
diff --git a/components/forms/UserForm.module.css b/components/forms/UserForm.module.css
deleted file mode 100644
index 793682e1..00000000
--- a/components/forms/UserForm.module.css
+++ /dev/null
@@ -1,6 +0,0 @@
-.form {
- display: flex;
- flex-direction: column;
- gap: 30px;
- width: 300px;
-}
diff --git a/components/forms/UserPasswordForm.js b/components/forms/UserPasswordForm.js
deleted file mode 100644
index ff474608..00000000
--- a/components/forms/UserPasswordForm.js
+++ /dev/null
@@ -1,74 +0,0 @@
-import { useRef } from 'react';
-import { Form, FormInput, FormButtons, PasswordField, Button } from 'react-basics';
-import useApi from 'hooks/useApi';
-import styles from './UserPasswordForm.module.css';
-import useUser from 'hooks/useUser';
-
-export default function UserPasswordForm({ onSave, onClose, userId }) {
- const user = useUser();
-
- const isCurrentUser = !userId || user?.id === userId;
- const url = isCurrentUser ? `/users/${user?.id}/password` : `/users/${user?.id}`;
- const { post, useMutation } = useApi();
- const { mutate, error, isLoading } = useMutation(data => post(url, data));
- const ref = useRef(null);
-
- const handleSubmit = async data => {
- const payload = isCurrentUser
- ? data
- : {
- password: data.newPassword,
- };
-
- mutate(payload, {
- onSuccess: async () => {
- onSave();
- ref.current.reset();
- },
- });
- };
-
- const samePassword = value => {
- if (value !== ref?.current?.getValues('newPassword')) {
- return "Passwords don't match";
- }
- return true;
- };
-
- return (
-
- );
-}
diff --git a/components/forms/UserPasswordForm.module.css b/components/forms/UserPasswordForm.module.css
deleted file mode 100644
index 793682e1..00000000
--- a/components/forms/UserPasswordForm.module.css
+++ /dev/null
@@ -1,6 +0,0 @@
-.form {
- display: flex;
- flex-direction: column;
- gap: 30px;
- width: 300px;
-}
diff --git a/components/forms/WebsiteAddForm.js b/components/forms/WebsiteAddForm.js
deleted file mode 100644
index 45392ed7..00000000
--- a/components/forms/WebsiteAddForm.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import { useRef } from 'react';
-import { Form, FormInput, FormButtons, TextField, Button, SubmitButton } from 'react-basics';
-import useApi from 'hooks/useApi';
-import styles from './Form.module.css';
-import { useMutation } from '@tanstack/react-query';
-import { DOMAIN_REGEX } from 'lib/constants';
-
-export default function WebsiteAddForm({ onSave, onClose }) {
- const { post } = useApi();
- const { mutate, error, isLoading } = useMutation(data => post('/websites', data));
- const ref = useRef(null);
-
- const handleSubmit = async data => {
- mutate(data, {
- onSuccess: async () => {
- onSave();
- },
- });
- };
-
- return (
-
- );
-}
diff --git a/components/forms/WebsiteDeleteForm.js b/components/forms/WebsiteDeleteForm.js
deleted file mode 100644
index 9b6ad698..00000000
--- a/components/forms/WebsiteDeleteForm.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import { useMutation } from '@tanstack/react-query';
-import useApi from 'hooks/useApi';
-import { Button, Form, FormButtons, FormInput, SubmitButton, TextField } from 'react-basics';
-import styles from './Form.module.css';
-
-const CONFIRM_VALUE = 'DELETE';
-
-export default function WebsiteDeleteForm({ websiteId, onSave, onClose }) {
- const { del } = useApi();
- const { mutate, error, isLoading } = useMutation(data => del(`/websites/${websiteId}`, data));
-
- const handleSubmit = async data => {
- mutate(data, {
- onSuccess: async () => {
- onSave();
- },
- });
- };
-
- return (
-
- );
-}
diff --git a/components/forms/WebsiteEditForm.js b/components/forms/WebsiteEditForm.js
deleted file mode 100644
index dd3d3989..00000000
--- a/components/forms/WebsiteEditForm.js
+++ /dev/null
@@ -1,47 +0,0 @@
-import { SubmitButton, Form, FormInput, FormRow, FormButtons, TextField } from 'react-basics';
-import { useMutation } from '@tanstack/react-query';
-import { useRef } from 'react';
-import useApi from 'hooks/useApi';
-import { DOMAIN_REGEX } from 'lib/constants';
-
-export default function WebsiteEditForm({ websiteId, data, onSave }) {
- const { post } = useApi();
- const { mutate, error } = useMutation(data => post(`/websites/${websiteId}`, data));
- const ref = useRef(null);
-
- const handleSubmit = async data => {
- mutate(data, {
- onSuccess: async () => {
- ref.current.reset(data);
- onSave(data);
- },
- });
- };
-
- return (
-
- );
-}
diff --git a/components/forms/WebsiteReset.js b/components/forms/WebsiteReset.js
deleted file mode 100644
index 6534798b..00000000
--- a/components/forms/WebsiteReset.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import WebsiteDeleteForm from 'components/forms/WebsiteDeleteForm';
-import WebsiteResetForm from 'components/forms/WebsiteResetForm';
-import { useRouter } from 'next/router';
-import { useState } from 'react';
-import { Button, Form, FormRow, Modal } from 'react-basics';
-
-export default function WebsiteReset({ websiteId, onSave }) {
- const [modal, setModal] = useState(null);
- const router = useRouter();
-
- const handleReset = async () => {
- setModal(null);
- onSave();
- };
-
- const handleDelete = async () => {
- onSave();
- await router.push('/websites');
- };
-
- const handleClose = () => setModal(null);
-
- return (
-
- );
-}
diff --git a/components/forms/WebsiteResetForm.js b/components/forms/WebsiteResetForm.js
deleted file mode 100644
index a40c5cbb..00000000
--- a/components/forms/WebsiteResetForm.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import { useMutation } from '@tanstack/react-query';
-import useApi from 'hooks/useApi';
-import { Button, Form, FormButtons, FormInput, SubmitButton, TextField } from 'react-basics';
-import styles from './Form.module.css';
-
-const CONFIRM_VALUE = 'RESET';
-
-export default function WebsiteResetForm({ websiteId, onSave, onClose }) {
- const { post } = useApi();
- const { mutate, error, isLoading } = useMutation(data =>
- post(`/websites/${websiteId}/reset`, data),
- );
-
- const handleSubmit = async data => {
- mutate(data, {
- onSuccess: async () => {
- onSave();
- },
- });
- };
-
- return (
-
- );
-}
diff --git a/components/icons.js b/components/icons.js
new file mode 100644
index 00000000..4868edc6
--- /dev/null
+++ b/components/icons.js
@@ -0,0 +1,35 @@
+import { Icons } from 'react-basics';
+import AddUser from 'assets/add-user.svg';
+import Bolt from 'assets/bolt.svg';
+import Calendar from 'assets/calendar.svg';
+import Clock from 'assets/clock.svg';
+import Dashboard from 'assets/dashboard.svg';
+import Gear from 'assets/gear.svg';
+import Globe from 'assets/globe.svg';
+import Lock from 'assets/lock.svg';
+import Logo from 'assets/logo.svg';
+import Moon from 'assets/moon.svg';
+import Profile from 'assets/profile.svg';
+import Sun from 'assets/sun.svg';
+import User from 'assets/user.svg';
+import Users from 'assets/users.svg';
+
+const icons = {
+ ...Icons,
+ AddUser,
+ Bolt,
+ Calendar,
+ Clock,
+ Dashboard,
+ Gear,
+ Globe,
+ Lock,
+ Logo,
+ Moon,
+ Profile,
+ Sun,
+ User,
+ Users,
+};
+
+export default icons;
diff --git a/components/layout/AppLayout.js b/components/layout/AppLayout.js
new file mode 100644
index 00000000..2b5f0b92
--- /dev/null
+++ b/components/layout/AppLayout.js
@@ -0,0 +1,29 @@
+import { Container } from 'react-basics';
+import Head from 'next/head';
+import NavBar from 'components/layout/NavBar';
+import useRequireLogin from 'hooks/useRequireLogin';
+import styles from './AppLayout.module.css';
+
+export default function AppLayout({ title, children }) {
+ const { user } = useRequireLogin();
+
+ if (!user) {
+ return null;
+ }
+
+ return (
+
+
+
{title ? `${title} | umami` : 'umami'}
+
+
+
+
+
+
+ {children}
+
+
+
+ );
+}
diff --git a/components/layout/AppLayout.module.css b/components/layout/AppLayout.module.css
new file mode 100644
index 00000000..173100a9
--- /dev/null
+++ b/components/layout/AppLayout.module.css
@@ -0,0 +1,16 @@
+.layout {
+ display: grid;
+ grid-template-rows: 1fr;
+ grid-template-columns: minmax(60px, 200px) 1fr;
+ height: 100vh;
+ overflow: hidden;
+}
+
+.nav {
+ grid-row: 1 / 3;
+}
+
+.body {
+ grid-area: 1 / 2;
+ overflow: auto;
+}
diff --git a/components/layout/Footer.js b/components/layout/Footer.js
index c8035fef..ec1571fb 100644
--- a/components/layout/Footer.js
+++ b/components/layout/Footer.js
@@ -1,33 +1,38 @@
+import { Row, Column } from 'react-basics';
import { useRouter } from 'next/router';
import Script from 'next/script';
import classNames from 'classnames';
-import { FormattedMessage } from 'react-intl';
+import { useIntl, defineMessages } from 'react-intl';
import Link from 'components/common/Link';
-import styles from './Footer.module.css';
import { CURRENT_VERSION, HOMEPAGE_URL, REPO_URL } from 'lib/constants';
+import styles from './Footer.module.css';
-export default function Footer() {
+const messages = defineMessages({
+ poweredBy: { id: 'message.powered-by', defaultMessage: 'Powered by {name}' },
+});
+
+export default function Footer({ className }) {
const { pathname } = useRouter();
+ const { formatMessage } = useIntl();
return (
-
diff --git a/components/metrics/DataTable.module.css b/components/metrics/DataTable.module.css
index 93c8f622..070dca1f 100644
--- a/components/metrics/DataTable.module.css
+++ b/components/metrics/DataTable.module.css
@@ -1,7 +1,6 @@
.table {
position: relative;
height: 100%;
- font-size: var(--font-size-sm);
display: grid;
grid-template-rows: fit-content(100%) auto;
overflow: hidden;
@@ -23,11 +22,9 @@
.title {
display: flex;
font-weight: 600;
- font-size: var(--font-size-md);
}
.metric {
- font-size: var(--font-size-sm);
font-weight: 600;
text-align: center;
width: 100px;
diff --git a/components/forms/DatePickerForm.js b/components/metrics/DatePickerForm.js
similarity index 54%
rename from components/forms/DatePickerForm.js
rename to components/metrics/DatePickerForm.js
index 07f620aa..d5a6533b 100644
--- a/components/forms/DatePickerForm.js
+++ b/components/metrics/DatePickerForm.js
@@ -1,14 +1,15 @@
-import Calendar from 'components/common/Calendar';
-import { FormButtons } from 'components/layout/FormLayout';
-import { isAfter, isBefore, isSameDay } from 'date-fns';
-import { getDateRangeValues } from 'lib/date';
import { useState } from 'react';
-import { Button, ButtonGroup } from 'react-basics';
-import { FormattedMessage } from 'react-intl';
+import { Button, ButtonGroup, Calendar } from 'react-basics';
+import { useIntl } from 'react-intl';
+import { isAfter, isBefore, isSameDay } from 'date-fns';
+import useLocale from 'hooks/useLocale';
+import { getDateRangeValues } from 'lib/date';
+import { getDateLocale } from 'lib/lang';
+import { labels } from 'components/messages';
import styles from './DatePickerForm.module.css';
-const FILTER_DAY = 0;
-const FILTER_RANGE = 1;
+const FILTER_DAY = 'day';
+const FILTER_RANGE = 'range';
export default function DatePickerForm({
startDate: defaultStartDate,
@@ -24,59 +25,59 @@ export default function DatePickerForm({
const [date, setDate] = useState(defaultStartDate);
const [startDate, setStartDate] = useState(defaultStartDate);
const [endDate, setEndDate] = useState(defaultEndDate);
+ const { locale } = useLocale();
+ const { formatMessage } = useIntl();
const disabled =
selected === FILTER_DAY
? isAfter(minDate, date) && isBefore(maxDate, date)
: isAfter(startDate, endDate);
- const buttons = [
- {
- label:
,
- value: FILTER_DAY,
- },
- {
- label:
,
- value: FILTER_RANGE,
- },
- ];
-
- function handleSave() {
+ const handleSave = () => {
if (selected === FILTER_DAY) {
onChange({ ...getDateRangeValues(date, date), value: 'custom' });
} else {
onChange({ ...getDateRangeValues(startDate, endDate), value: 'custom' });
}
- }
+ };
return (
-
+
+
+
+
- {selected === FILTER_DAY ? (
+ {selected === FILTER_DAY && (
- ) : (
+ )}
+ {selected === FILTER_RANGE && (
<>
-
+
>
)}
-
-
);
}
diff --git a/components/forms/DatePickerForm.module.css b/components/metrics/DatePickerForm.module.css
similarity index 83%
rename from components/forms/DatePickerForm.module.css
rename to components/metrics/DatePickerForm.module.css
index 92e59bb7..a3d9851d 100644
--- a/components/forms/DatePickerForm.module.css
+++ b/components/metrics/DatePickerForm.module.css
@@ -26,6 +26,14 @@
margin-bottom: 20px;
}
+.buttons {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 10px;
+ margin-top: 20px;
+}
+
@media only screen and (max-width: 768px) {
.calendars {
flex-direction: column;
diff --git a/components/metrics/EventDataForm.js b/components/metrics/EventDataForm.js
new file mode 100644
index 00000000..dbcc5917
--- /dev/null
+++ b/components/metrics/EventDataForm.js
@@ -0,0 +1,241 @@
+import { useMutation } from '@tanstack/react-query';
+import classNames from 'classnames';
+import DateFilter from 'components/common/DateFilter';
+import DataTable from 'components/metrics/DataTable';
+import FilterTags from 'components/metrics/FilterTags';
+import useApi from 'hooks/useApi';
+import useDateRange from 'hooks/useDateRange';
+import useTimezone from 'hooks/useTimezone';
+import { useEffect, useState, useRef } from 'react';
+import {
+ Button,
+ Dropdown,
+ Flexbox,
+ Form,
+ FormButtons,
+ FormInput,
+ FormRow,
+ Item,
+ TextField,
+} from 'react-basics';
+import { FormattedMessage } from 'react-intl';
+import { FormMessage } from '../layout/FormLayout';
+import styles from './EventDataForm.module.css';
+
+export const filterOptions = [
+ { label: 'Count', value: 'count' },
+ { label: 'Average', value: 'avg' },
+ { label: 'Minimum', value: 'min' },
+ { label: 'Maximum', value: 'max' },
+ { label: 'Sum', value: 'sum' },
+];
+
+export const dateOptions = [
+ { label:
, value: '1day' },
+ {
+ label: (
+
+ ),
+ value: '24hour',
+ },
+ {
+ label:
,
+ value: '-1day',
+ },
+ {
+ label:
,
+ value: '1week',
+ divider: true,
+ },
+ {
+ label: (
+
+ ),
+ value: '7day',
+ },
+ {
+ label:
,
+ value: '1month',
+ divider: true,
+ },
+ {
+ label: (
+
+ ),
+ value: '30day',
+ },
+ {
+ label: (
+
+ ),
+ value: '90day',
+ },
+ { label:
, value: '1year' },
+ {
+ label:
,
+ value: 'custom',
+ divider: true,
+ },
+];
+
+export default function EventDataForm({ websiteId, onClose, className }) {
+ const { post } = useApi();
+ const [message, setMessage] = useState();
+ const { mutate } = useMutation(data => post(`/websites/${websiteId}/eventdata`, data));
+ const [columns, setColumns] = useState({});
+ const [filters, setFilters] = useState({});
+ const [type, setType] = useState('');
+ const [data, setData] = useState([]);
+ const [dateRange, setDateRange] = useDateRange('report');
+ const { startDate, endDate, value } = dateRange;
+ const [timezone] = useTimezone();
+ const [isValid, setIsValid] = useState(false);
+ const columnRef = useRef(null);
+ const filterRef = useRef(null);
+
+ useEffect(() => {
+ if (Object.keys(columns).length > 0) {
+ setIsValid(true);
+ } else {
+ setIsValid(false);
+ }
+ }, [columns]);
+
+ const handleAddTag = (value, list, setState, ref, clearDropdown) => {
+ setState({ ...list, [`${value.field}`]: value.value });
+
+ ref.current.reset({ field: '', value: '' });
+
+ if (clearDropdown) {
+ setType('');
+ }
+ };
+
+ const handleRemoveTag = (value, list, setState) => {
+ const newList = { ...list };
+
+ delete newList[`${value}`];
+
+ setState(newList);
+ };
+
+ const handleSubmit = async () => {
+ const params = {
+ website_id: websiteId,
+ startAt: +startDate,
+ endAt: +endDate,
+ timezone,
+ columns,
+ filters,
+ };
+
+ mutate(params, {
+ onSuccess: async data => {
+ setData(data);
+ setMessage(null);
+ },
+ onError: async () => {
+ setMessage(
+
,
+ );
+ setData([]);
+ },
+ });
+ };
+
+ return (
+ <>
+
+ {message}
+
+
+
+
+ }>
+
+
+
+
+
+ handleRemoveTag(value, columns, setColumns)}
+ />
+
+
+
+ handleRemoveTag(value, filters, setFilters)}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/components/forms/EventDataForm.module.css b/components/metrics/EventDataForm.module.css
similarity index 88%
rename from components/forms/EventDataForm.module.css
rename to components/metrics/EventDataForm.module.css
index fd0ad290..c78eb290 100644
--- a/components/forms/EventDataForm.module.css
+++ b/components/metrics/EventDataForm.module.css
@@ -3,8 +3,9 @@
}
.form {
+ padding: 20px;
border-right: 1px solid var(--base300);
- width: 420px;
+ width: 425px;
}
.filters {
@@ -16,6 +17,10 @@
min-height: 250px;
}
+.message {
+ width: 100%;
+}
+
.table {
padding: 10px;
min-height: 430px;
diff --git a/components/metrics/FilterTags.js b/components/metrics/FilterTags.js
index 1fb27271..75d38371 100644
--- a/components/metrics/FilterTags.js
+++ b/components/metrics/FilterTags.js
@@ -1,7 +1,6 @@
import classNames from 'classnames';
import { safeDecodeURI } from 'next-basics';
-import { Button } from 'react-basics';
-import Times from 'assets/times.svg';
+import { Button, Icon, Icons } from 'react-basics';
import styles from './FilterTags.module.css';
export default function FilterTags({ className, params, onClick }) {
@@ -16,8 +15,11 @@ export default function FilterTags({ className, params, onClick }) {
}
return (
- } onClick={() => onClick(key)} variant="action" iconRight>
+ onClick(key)} variant="action" iconRight>
{`${key}: ${safeDecodeURI(params[key])}`}
+
+
+
);
diff --git a/components/metrics/Legend.js b/components/metrics/Legend.js
index f10b29bc..32267970 100644
--- a/components/metrics/Legend.js
+++ b/components/metrics/Legend.js
@@ -34,8 +34,9 @@ export default function Legend({ chart }) {
className={classNames(styles.label, { [styles.hidden]: hidden })}
onClick={() => handleClick(datasetIndex)}
>
-
-
{text}
+
+ {text}
+
);
})}
diff --git a/components/metrics/MetricCard.module.css b/components/metrics/MetricCard.module.css
index 7de16157..f80b641b 100644
--- a/components/metrics/MetricCard.module.css
+++ b/components/metrics/MetricCard.module.css
@@ -6,7 +6,7 @@
}
.value {
- font-size: var(--font-size-xl);
+ font-size: var(--font-size-xxl);
line-height: 40px;
min-height: 40px;
font-weight: 600;
@@ -14,7 +14,6 @@
}
.label {
- font-size: var(--font-size-md);
white-space: nowrap;
display: flex;
align-items: center;
diff --git a/components/metrics/MetricsTable.js b/components/metrics/MetricsTable.js
index 25388fbe..642ce818 100644
--- a/components/metrics/MetricsTable.js
+++ b/components/metrics/MetricsTable.js
@@ -1,11 +1,10 @@
import { useMemo } from 'react';
-import { Loading } from 'react-basics';
+import { Loading, Icons } from 'react-basics';
import { defineMessages, useIntl } from 'react-intl';
import firstBy from 'thenby';
import classNames from 'classnames';
import Link from 'components/common/Link';
import useApi from 'hooks/useApi';
-import Arrow from 'assets/arrow-right.svg';
import { percentFilter } from 'lib/filters';
import useDateRange from 'hooks/useDateRange';
import usePageQuery from 'hooks/usePageQuery';
@@ -80,7 +79,7 @@ export default function MetricsTable({