diff --git a/README.md b/README.md
index ce9f2bfe..5fe9dd96 100644
--- a/README.md
+++ b/README.md
@@ -48,13 +48,7 @@ mysql://username:mypassword@localhost:3306/mydb
yarn build
```
-### Create database tables
-
-```bash
-yarn update-db
-```
-
-This will also create a login account with username **admin** and password **umami**.
+The build step will also create tables in your database if you ae installing for the first time. It will also create a login account with username **admin** and password **umami**.
### Start the application
diff --git a/components/common/EventDataButton.js b/components/common/EventDataButton.js
new file mode 100644
index 00000000..2b895840
--- /dev/null
+++ b/components/common/EventDataButton.js
@@ -0,0 +1,48 @@
+import List from 'assets/list-ul.svg';
+import Modal from 'components/common/Modal';
+import PropTypes from 'prop-types';
+import { useState } from 'react';
+import { FormattedMessage } from 'react-intl';
+import Button from './Button';
+import EventDataForm from 'components/forms/EventDataForm';
+import styles from './EventDataButton.module.css';
+
+function EventDataButton({ websiteId }) {
+ const [showEventData, setShowEventData] = useState(false);
+
+ function handleClick() {
+ if (!showEventData) {
+ setShowEventData(true);
+ }
+ }
+
+ function handleClose() {
+ setShowEventData(false);
+ }
+
+ return (
+ <>
+ }
+ tooltip={}
+ tooltipId="button-event"
+ size="small"
+ onClick={handleClick}
+ className={styles.button}
+ >
+ Event Data
+
+ {showEventData && (
+ }>
+
+
+ )}
+ >
+ );
+}
+
+EventDataButton.propTypes = {
+ websiteId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+};
+
+export default EventDataButton;
diff --git a/components/common/EventDataButton.module.css b/components/common/EventDataButton.module.css
new file mode 100644
index 00000000..cd2a2ed6
--- /dev/null
+++ b/components/common/EventDataButton.module.css
@@ -0,0 +1,3 @@
+.button {
+ width: fit-content;
+}
diff --git a/components/common/RefreshButton.js b/components/common/RefreshButton.js
index 0b197bd2..be1a8a1d 100644
--- a/components/common/RefreshButton.js
+++ b/components/common/RefreshButton.js
@@ -11,7 +11,7 @@ import useDateRange from 'hooks/useDateRange';
function RefreshButton({ websiteId }) {
const [dateRange] = useDateRange(websiteId);
const [loading, setLoading] = useState(false);
- const selector = useCallback(state => state[`/website/${websiteId}/stats`], [websiteId]);
+ const selector = useCallback(state => state[`/websites/${websiteId}/stats`], [websiteId]);
const completed = useStore(selector);
function handleClick() {
diff --git a/components/common/UpdateNotice.js b/components/common/UpdateNotice.js
index 7419abe8..54c5fb83 100644
--- a/components/common/UpdateNotice.js
+++ b/components/common/UpdateNotice.js
@@ -18,7 +18,7 @@ export default function UpdateNotice() {
function handleViewClick() {
updateCheck();
setDismissed(true);
- location.href = releaseUrl || REPO_URL;
+ open(releaseUrl || REPO_URL, '_blank');
}
function handleDismissClick() {
diff --git a/components/forms/AccountEditForm.js b/components/forms/AccountEditForm.js
index 97317aff..70125656 100644
--- a/components/forms/AccountEditForm.js
+++ b/components/forms/AccountEditForm.js
@@ -15,13 +15,13 @@ const initialValues = {
password: '',
};
-const validate = ({ user_id, username, password }) => {
+const validate = ({ id, username, password }) => {
const errors = {};
if (!username) {
errors.username = ;
}
- if (!user_id && !password) {
+ if (!id && !password) {
errors.password = ;
}
@@ -33,7 +33,8 @@ export default function AccountEditForm({ values, onSave, onClose }) {
const [message, setMessage] = useState();
const handleSubmit = async values => {
- const { ok, data } = await post('/account', values);
+ const { id } = values;
+ const { ok, data } = await post(id ? `/accounts/${id}` : '/accounts', values);
if (ok) {
onSave();
diff --git a/components/forms/ChangePasswordForm.js b/components/forms/ChangePasswordForm.js
index 29f34521..4ee657e6 100644
--- a/components/forms/ChangePasswordForm.js
+++ b/components/forms/ChangePasswordForm.js
@@ -9,6 +9,7 @@ import FormLayout, {
FormRow,
} from 'components/layout/FormLayout';
import useApi from 'hooks/useApi';
+import useUser from '../../hooks/useUser';
const initialValues = {
current_password: '',
@@ -39,9 +40,10 @@ const validate = ({ current_password, new_password, confirm_password }) => {
export default function ChangePasswordForm({ values, onSave, onClose }) {
const { post } = useApi();
const [message, setMessage] = useState();
+ const { user } = useUser();
const handleSubmit = async values => {
- const { ok, data } = await post('/account/password', values);
+ const { ok, data } = await post(`/accounts/${user.userId}/password`, values);
if (ok) {
onSave();
diff --git a/components/forms/EventDataForm.js b/components/forms/EventDataForm.js
new file mode 100644
index 00000000..db8e7fbb
--- /dev/null
+++ b/components/forms/EventDataForm.js
@@ -0,0 +1,262 @@
+import classNames from 'classnames';
+import Button from 'components/common/Button';
+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: 'Maxmimum', 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,
+ start_at: +startDate,
+ end_at: +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/EventDataForm.module.css b/components/forms/EventDataForm.module.css
new file mode 100644
index 00000000..19d76f77
--- /dev/null
+++ b/components/forms/EventDataForm.module.css
@@ -0,0 +1,38 @@
+.container {
+ display: flex;
+}
+
+.form {
+ border-right: 1px solid var(--gray300);
+ width: 420px;
+}
+
+.filters {
+ padding: 10px 5px;
+}
+
+.filters + .filters {
+ border-top: 1px solid var(--gray300);
+ min-height: 250px;
+}
+
+.table {
+ padding: 10px;
+ min-height: 430px;
+ min-width: 400px;
+}
+
+.formButtons {
+ justify-content: flex-start;
+ margin-left: 20px;
+}
+
+.dropdown {
+ min-height: 39px;
+ min-width: 240px;
+}
+
+.filterTag {
+ flex-wrap: wrap;
+ margin: 10px 5px 5px 5px;
+}
diff --git a/components/forms/ShareUrlForm.js b/components/forms/ShareUrlForm.js
index a77d3fbc..9800e54b 100644
--- a/components/forms/ShareUrlForm.js
+++ b/components/forms/ShareUrlForm.js
@@ -8,7 +8,7 @@ import CopyButton from 'components/common/CopyButton';
export default function TrackingCodeForm({ values, onClose }) {
const ref = useRef();
const { basePath } = useRouter();
- const { name, share_id } = values;
+ const { name, shareId } = values;
return (
@@ -27,7 +27,7 @@ export default function TrackingCodeForm({ values, onClose }) {
spellCheck={false}
defaultValue={`${
document.location.origin
- }${basePath}/share/${share_id}/${encodeURIComponent(name)}`}
+ }${basePath}/share/${shareId}/${encodeURIComponent(name)}`}
readOnly
/>
diff --git a/components/forms/TrackingCodeForm.js b/components/forms/TrackingCodeForm.js
index e75260f7..52df3bde 100644
--- a/components/forms/TrackingCodeForm.js
+++ b/components/forms/TrackingCodeForm.js
@@ -26,7 +26,7 @@ export default function TrackingCodeForm({ values, onClose }) {
rows={3}
cols={60}
spellCheck={false}
- defaultValue={``}
readOnly
diff --git a/components/forms/WebsiteEditForm.js b/components/forms/WebsiteEditForm.js
index 80a6dfc0..b56e21c1 100644
--- a/components/forms/WebsiteEditForm.js
+++ b/components/forms/WebsiteEditForm.js
@@ -38,18 +38,17 @@ const validate = ({ name, domain }) => {
};
const OwnerDropDown = ({ user, accounts }) => {
- console.info(styles);
const { setFieldValue, values } = useFormikContext();
useEffect(() => {
- if (values.user_id != null && values.owner === '') {
- setFieldValue('owner', values.user_id.toString());
- } else if (user?.user_id && values.owner === '') {
- setFieldValue('owner', user.user_id.toString());
+ if (values.userId != null && values.owner === '') {
+ setFieldValue('owner', values.userId.toString());
+ } else if (user?.id && values.owner === '') {
+ setFieldValue('owner', user.id.toString());
}
}, [accounts, setFieldValue, user, values]);
- if (user?.is_admin) {
+ if (user?.isAdmin) {
return (